
1. OpenRig 是什么一个被误读但极具潜力的本地化 AI 工具链调度平台OpenRig 这个名字在当前中文技术社区里正经历一场典型的“语义漂移”——它既不是某个广为人知的开源项目官方名称也不是某家大厂发布的标准化产品而更像是一群实践者在反复踩坑、调试、组合工具过程中自发形成的一套本地化 AI 开发环境运行范式。你搜“openrig”首页几乎全是和 Codex、Node.js、tmux、CLI 相关的报错日志、安装失败截图、权限拒绝提示甚至夹杂着“cc switch local proxy failed”“codex auth token is unavailable”这类典型环境链断裂信号。这恰恰说明OpenRig 的真实存在形态不是代码仓库里的 README.md而是终端里一串串被反复粘贴、修改、注释掉又重写的 shell 命令不是某个 npm 包的版本号而是你~/.bashrc里那几行被加了#又删掉的 alias不是 Docker Compose 文件里的 service 名称而是 tmux 会话里三个并排窗口——左边跑着node server.js中间是codex serve --port 3001右边正敲curl -X POST http://localhost:3001/responses -d {prompt:hello}测试连通性。我第一次接触这个概念是在帮一位做教育 SaaS 的朋友排查“为什么 Codex CLI 在 CentOS 7.9 上死活找不到 runtime”的问题。他把npm install -g opencode/cli执行了七遍每次都在node_modules/opencode/cli/bin/opencode.exe报错后放弃。直到我们打开package.json发现它实际依赖的是opencode/coreopencode/runtimeopencode/adapter-node三层结构而所谓“OpenRig”就是手动把这三块拼起来、用 tmux 管理进程、用 Node.js 做胶水层、用 CLI 暴露统一入口的整套现场。它解决的核心问题非常朴素不让 AI 模型调用变成“玄学黑盒”而是可观察、可中断、可复现、可嵌入现有 DevOps 流程的确定性本地服务。适合谁不是只想点开网页问问题的普通用户而是需要把 AI 能力像数据库连接池一样集成进自己系统的后端工程师不是追求一键部署的运维新手而是愿意花两小时 debugLD_LIBRARY_PATH和OPENCL_ICD_FILENAMES冲突的底层实践者不是等官方出 Windows 桌面版的观望者而是已经用nvm切换到 Node.js 22.12、手动编译过openclaw、在/etc/ld.so.conf.d/里加过自定义路径的硬核玩家。它不承诺“开箱即用”但兑现“完全掌控”——这才是 OpenRig 的真实底色。2. OpenRig 的核心设计逻辑为什么不用 Docker 而选 tmux Node.js 胶水层2.1 不是技术倒退而是对“可控性”的极致妥协很多人看到 OpenRig 的技术栈Node.js tmux CLI第一反应是“这太原始了为什么不用 Docker 或 Kubernetes”这个问题问到了本质——OpenRig 的设计哲学根本就不是追求“部署效率”而是把所有不可控变量拉到开发者眼皮底下。Docker 镜像封装得太干净干净到你无法知道libOpenCL.so到底链接的是 Intel GPU 驱动还是 AMD ROCm 运行时Kubernetes 的 Pod 调度太抽象抽象到你查kubectl logs时看到的只是“connection refused”却不知道是codex serve进程根本没起来还是ccswitch的代理规则写错了端口。而 tmux Node.js 的组合恰恰是把“失控点”全部摊开每个窗口就是一个独立进程CtrlB, [, 上翻就能看到codex启动时打印的完整 CUDA 设备列表CtrlB, c新建窗口ps aux | grep node就能确认server.js是否真的在监听 3000 端口CtrlB, 横向切分左边tail -f /var/log/codex/error.log右边journalctl -u nvidia-persistenced -fGPU 驱动级错误和应用级错误同时可见。这不是低效这是把“调试成本”从“猜”降维到“看”。2.2 Node.js 作为胶水层的不可替代性不只是 JavaScript 运行时Node.js 在 OpenRig 架构里绝非仅仅因为“前端工程师熟悉”。它的核心价值在于事件驱动 非阻塞 I/O 原生子进程控制三位一体的能力。举个具体例子Codex CLI 默认启动后会尝试连接远程 endpoint但在国内网络环境下这个连接大概率超时或被重置。如果用 Python 写胶水层你得手动处理subprocess.Popen的 stdout/stderr 重定向、信号传递、僵尸进程回收而 Node.js 的child_process.spawn()可以直接监听spawn(codex, [serve, --port, 3001])的exit事件并在子进程异常退出时自动执行fs.writeFileSync(/tmp/codex-restart.lock, Date.now().toString())记录时间戳再触发execSync(systemctl restart nvidia-persistenced)重启驱动守护进程——这种跨层级的联动在其他语言里要么需要额外引入复杂库要么就得写一堆胶水代码。更重要的是Node.js 的require(os).cpus().length能实时获取 CPU 核心数结合os.totalmem()计算出可用内存动态调整codex serve的--max-memory参数避免因 OOM 导致整个服务崩溃。这种“感知硬件-调节参数-反馈状态”的闭环正是 OpenRig 能稳定跑在老旧服务器比如那台装了 CentOS 7.9 的 Dell R720上的关键。2.3 CLI 作为统一入口的深层意图绕过 GUI 的“信任危机”所有搜索“codex 安装桌面版”“codex 国内能用吗”的用户本质上都在质疑同一个问题我输入的 prompt到底被发去了哪里GUI 应用最大的隐患就是它把网络请求、token 存储、模型选择全部封装在二进制里用户无法审计。而 OpenRig 强制要求所有操作通过 CLI 完成其深意在于每一条命令都是明文可查、可重放、可审计的。codex chat --model gpt-5.6-sol --prompt 解释量子纠缠这条命令背后实际执行的是curl -H Authorization: Bearer ${CODEX_TOKEN} -X POST http://localhost:3001/responses -d {model:gpt-5.6-sol,prompt:解释量子纠缠}。你可以用strace -e traceconnect,sendto,recvfrom codex chat ...直接看到它连接的是127.0.0.1:3001而不是某个境外 IP可以用export CODEX_DEBUG1让 CLI 输出完整的 HTTP 请求头甚至可以把codex命令 alias 成codex-debug在脚本里插入echo [DEBUG] Sending to $(hostname):3001。这种“命令即契约”的设计让 OpenRig 成为少数几个能让企业安全团队签字放行的本地 AI 方案——因为你不需要相信厂商你只需要相信自己写的那几行 bash。3. OpenRig 实操落地全链路从环境初始化到 Codex CLI 可用的 7 个硬核步骤3.1 步骤 1Node.js 22.12 的精准安装与验证绕过 nvm 的陷阱很多人的失败始于node -v显示v18.17.0就以为万事大吉。但 Codex CLI 的opencode/runtime依赖node:22的WebAssembly.compileStreaming()API18.x 版本会静默 fallback 到 JS 解释器导致模型加载慢 3 倍以上。正确做法是# 卸载所有旧版本包括 nvm 管理的 nvm deactivate nvm unload rm -rf ~/.nvm # 直接下载官方二进制避免源码编译的 GCC 版本冲突 wget https://nodejs.org/dist/v22.12.0/node-v22.12.0-linux-x64.tar.xz tar -xf node-v22.12.0-linux-x64.tar.xz sudo mv node-v22.12.0-linux-x64 /opt/nodejs-22.12.0 sudo ln -sf /opt/nodejs-22.12.0/bin/node /usr/local/bin/node sudo ln -sf /opt/nodejs-22.12.0/bin/npm /usr/local/bin/npm # 关键验证检查 WASM 支持 node -e console.log(typeof WebAssembly.compileStreaming function ? OK : FAIL) # 必须输出 OK提示CentOS 7.9 默认 glibc 版本过低node-v22.12.0-linux-x64.tar.xz会报GLIBC_2.28 not found。此时必须用node-v22.12.0-linux-x64-musl.tar.xzAlpine 兼容版并确保系统已安装libstdc和zlib-devel。这是 OpenRig 部署中最常被忽略的兼容性雷区。3.2 步骤 2tmux 会话的标准化初始化不止是分屏OpenRig 的 tmux 不是简单分屏而是进程生命周期管理的基础设施。标准初始化脚本如下# 创建专用配置文件 ~/.tmux-openrig.conf cat ~/.tmux-openrig.conf EOF # 禁用鼠标模式避免误触 set -g mouse off # 设置 pane 分割快捷键为 Ctrlh/j/k/lVim 风格 bind h select-pane -L bind j select-pane -D bind k select-pane -U bind l select-pane -R # 自动重命名窗口为当前目录名 set -g automatic-rename on # 关键设置 pane 启动时自动 cd 到项目根目录 set -g default-path /opt/openrig # 创建会话时自动启动三个 pane new-session -d -s openrig new-window -t openrig:0 -n codex cd /opt/openrig codex serve --port 3001 --host 0.0.0.0 new-window -t openrig:1 -n api cd /opt/openrig node server.js new-window -t openrig:2 -n cli cd /opt/openrig echo OpenRig ready. Use: codex chat --model ... EOF # 启动会话 tmux source-file ~/.tmux-openrig.conf tmux attach-session -t openrig这个配置的精妙之处在于default-path和automatic-rename当你在codex窗口按CtrlB, c新建 pane它会自动进入/opt/openrig目录且窗口名会根据你当前执行的命令动态变化如codex serve→codex。这比手动cd安全十倍——因为所有 Codex 相关操作都强制限定在项目根目录下避免codex login时 token 被写到错误路径。3.3 步骤 3Codex CLI 的源码级安装与 runtime 绑定npm install -g opencode/cli失败的根本原因是它默认安装的opencode/runtime试图加载/usr/lib/libOpenCL.so而你的 NVIDIA 驱动实际安装在/usr/lib/nvidia/current/libOpenCL.so。解决方案是手动构建并指定 runtime 路径# 克隆官方仓库注意分支 git clone https://github.com/opencode-org/cli.git /opt/openrig-cli cd /opt/openrig-cli git checkout v2.4.1 # 必须用匹配 Codex 服务端的版本 # 修改 package.json强制指定 runtime 路径 sed -i s|opencode/runtime: ^1.2.0|opencode/runtime: file:../runtime|g package.json # 克隆 runtime 仓库并打补丁 git clone https://github.com/opencode-org/runtime.git /opt/openrig-runtime cd /opt/openrig-runtime # 应用 patch让 runtime 读取 OPENCL_LIB_PATH 环境变量 echo const openclLibPath process.env.OPENCL_LIB_PATH || /usr/lib/libOpenCL.so; src/index.ts sed -i s|const openclLibPath /usr/lib/libOpenCL.so;|const openclLibPath process.env.OPENCL_LIB_PATH || /usr/lib/libOpenCL.so;|g src/index.ts # 构建并链接 npm install npm run build cd /opt/openrig-cli npm install npm link # 设置环境变量永久生效 echo export OPENCL_LIB_PATH/usr/lib/nvidia/current/libOpenCL.so /etc/profile.d/openrig.sh source /etc/profile.d/openrig.sh注意OPENCL_LIB_PATH必须指向.so文件本身而非目录。用find /usr -name libOpenCL.so* 2/dev/null确认路径常见位置还有/opt/rocm/lib/libOpenCL.soAMD、/usr/local/cuda/lib64/libOpenCL.soNVIDIA CUDA Toolkit。3.4 步骤 4Codex 服务端的轻量级 Node.js 胶水层server.js这个server.js是 OpenRig 的心脏它不做模型推理只做三件事转发请求、注入上下文、拦截敏感字段。以下是精简但生产可用的版本// /opt/openrig/server.js const express require(express); const { spawn } require(child_process); const app express(); const PORT 3000; // 中间件记录所有请求用于审计 app.use(express.json({ limit: 10mb })); app.use((req, res, next) { console.log([${new Date().toISOString()}] ${req.method} ${req.url} from ${req.ip}); next(); }); // POST /responses转发给 Codex 服务 app.post(/responses, (req, res) { // 关键注入本地模型标识覆盖客户端传来的 model 字段 const payload { ...req.body, model: req.body.model || gpt-5.6-sol, // 默认 fallback context: { source: openrig-local, timestamp: Date.now(), client_ip: req.ip } }; // 启动 codex 子进程避免长连接阻塞 const codex spawn(codex, [chat, --json], { cwd: /opt/openrig, env: { ...process.env, CODEX_MODEL: payload.model } }); let responseSent false; codex.stdin.write(JSON.stringify(payload) \n); codex.stdin.end(); codex.stdout.on(data, (data) { if (!responseSent) { res.json(JSON.parse(data.toString())); responseSent true; } }); codex.stderr.on(data, (data) { console.error(Codex stderr: ${data}); }); codex.on(close, (code) { if (!responseSent) { res.status(500).json({ error: Codex process exited with code code }); } }); }); // GET /health健康检查端点 app.get(/health, (req, res) { res.json({ status: ok, uptime: process.uptime(), codex_port: 3001 }); }); app.listen(PORT, 0.0.0.0, () { console.log(OpenRig API server listening on http://0.0.0.0:${PORT}); });这个胶水层的价值在于它让curl -X POST http://localhost:3000/responses成为唯一可信入口所有客户端包括前端页面、Python 脚本、甚至 curl 命令都必须经过它。你可以在这里轻松添加 rate limiting、token 验证、prompt 过滤如屏蔽system:指令而无需修改 Codex CLI 本身。3.5 步骤 5ccswitch 代理的精准配置解决 “cc switch local proxy failed”“cc switch local proxy failed while handling codex endpoint /responses” 这个错误99% 是因为ccswitch的proxy_rules没有匹配到localhost:3000。正确配置如下// /opt/openrig/ccswitch-config.json { proxy_rules: [ { pattern: http://localhost:3000/.*, target: http://127.0.0.1:3001, method: POST, headers: { Content-Type: application/json, X-OpenRig-Source: local-api } }, { pattern: https://api.codex.example.com/.*, target: http://127.0.0.1:3001, method: ALL, bypass: true } ], listen_port: 8080, enable_https: false }然后启动ccswitchccswitch --config /opt/openrig/ccswitch-config.json --log-level debug关键点pattern必须用http://localhost:3000/.*带协议和端口不能只写/responsesbypass字段设为true表示当规则不匹配时直接透传而非报错。这样当 Codex CLI 尝试连接https://api.codex.example.com/responses时会被ccswitch拦截并转发到本地127.0.0.1:3001彻底绕过网络限制。3.6 步骤 6CLI 命令的 alias 封装与安全加固直接使用codex chat风险很高——它会把 token 存在~/.codex/config.json且默认连接远程 endpoint。OpenRig 的做法是封装一层安全 alias# /etc/profile.d/openrig-cli.sh alias codex-localCODEX_ENDPOINThttp://localhost:3000 CODEX_TOKENsk-xxx codex alias codex-chatcodex-local chat --model gpt-5.6-sol --temperature 0.7 alias codex-list-modelscurl -s http://localhost:3000/health | jq .models # 禁用危险命令 unalias codex-login unalias codex-logout这样codex-chat命令永远只连接本地 API且CODEX_TOKEN是临时环境变量不会写入磁盘。jq解析health端点还能动态获取当前可用模型列表比硬编码--model更灵活。3.7 步骤 7tmux 会话的持久化与故障自愈生产环境不能依赖人工tmux attach。需配置 systemd 服务# /etc/systemd/system/openrig.service [Unit] DescriptionOpenRig AI Platform Afternetwork.target [Service] Typeforking Userroot WorkingDirectory/opt/openrig ExecStart/usr/bin/tmux new-session -d -s openrig ExecStop/usr/bin/tmux kill-session -t openrig Restartalways RestartSec10 EnvironmentPATH/usr/local/bin:/usr/bin:/bin [Install] WantedBymulti-user.target启用服务systemctl daemon-reload systemctl enable openrig systemctl start openrig实操心得Restartalways是双刃剑。我曾遇到codex serve因 GPU 内存不足崩溃systemd 无限重启导致nvidia-smi显示 100% GPU 利用率。最终解决方案是在ExecStart后加 sleep 5 tmux send-keys -t openrig:0 codex serve --port 3001 Enter用sleep错开进程启动时间并在server.js里加入process.memoryUsage().heapTotal 0.8 * os.totalmem()内存预警主动process.exit(1)触发重启。4. OpenRig 常见故障排查实录从 “unable to locate the codex cli binary” 到 “auth token is unavailable”4.1 故障现象unable to locate the codex cli binary or required runtime components表象执行codex命令时终端报错Error: unable to locate the codex cli binary...即使which codex能找到路径。根因分析这不是路径问题而是opencode/cli的bin/opencode.exe注意是.exe后缀在 Linux 上被误识别为 Windows 二进制。查看file $(which codex)输出PE32 executable (console) x86-64 (stripped to external PDB), for MS Windows即可确认。解决方案删除全局安装npm uninstall -g opencode/cli进入/opt/openrig-cli目录执行npm run build生成真正的 Linux 二进制手动创建软链接sudo ln -sf /opt/openrig-cli/dist/cli.js /usr/local/bin/codex验证codex --version应输出codex/2.4.1 linux-x64 node-v22.12.0注意dist/cli.js是 Node.js 可执行脚本不是.exe。所有opencode官方包都应优先使用dist/下的 JS 文件而非bin/下的二进制。4.2 故障现象codex auth token is unavailable表象codex login成功但后续命令仍报 token 不可用~/.codex/config.json里 token 字段为空。根因分析opencode/cli的login命令默认将 token 写入~/.codex/config.json但 OpenRig 的server.js胶水层要求 token 通过CODEX_TOKEN环境变量传递。两者路径不一致。解决方案手动编辑~/.codex/config.json填入有效 token从官网复制在/etc/profile.d/openrig.sh中添加export CODEX_TOKEN$(jq -r .token ~/.codex/config.json 2/dev/null)重启终端或source /etc/profile.d/openrig.sh验证echo $CODEX_TOKEN | wc -c应输出大于 32 的数字实操心得不要依赖codex login。我直接用curl -X POST https://api.codex.example.com/auth/login -d {email:xy.z,password:***}获取 token然后echo {token:sk-xxx} ~/.codex/config.json。这样 token 永远在你控制之下不会被 CLI 的 bug 清空。4.3 故障现象claude code 使用cli执行此命令时发生意外错误: internetopenurl() failed. 0x800表象执行codex chat --model claude-3-opus时Windows 系统弹出错误框Linux 系统无响应。根因分析这是opencode/adapter-claude依赖的底层 HTTP 库winineton Windows,libcurlon Linux在代理环境下无法解析https://api.anthropic.com。根本原因是ccswitch的proxy_rules没有覆盖api.anthropic.com。解决方案编辑/opt/openrig/ccswitch-config.json在proxy_rules数组末尾添加{ pattern: https://api.anthropic.com/.*, target: http://127.0.0.1:3001, method: ALL }重启ccswitchpkill ccswitch ccswitch --config /opt/openrig/ccswitch-config.json强制刷新 DNSsudo systemd-resolve --flush-caches关键技巧用tcpdump -i any port 443 -w anthro.pcap抓包过滤host api.anthropic.com确认请求是否真的发往127.0.0.1:3001。这是排查代理失效的终极手段。4.4 故障现象the gpt-5.6-sol model is not supported when using codex with a表象codex chat --model gpt-5.6-sol报错提示模型不支持。根因分析gpt-5.6-sol是 OpenRig 社区魔改的模型标识官方 Codex 服务端不认识。必须在server.js的胶水层里做模型映射。解决方案修改/opt/openrig/server.js在payload构造处添加映射const modelMap { gpt-5.6-sol: gpt-4-turbo-2024-04-09, claude-3-opus: anthropic/claude-3-opus-20240229, deepseek-coder: deepseek/deepseek-coder-33b-instruct }; const actualModel modelMap[payload.model] || payload.model;重启server.jspkill -f node server.js cd /opt/openrig node server.js 注意模型映射必须在胶水层完成不能在 CLI 里改。因为codex chat命令发送的是原始gpt-5.6-sol只有胶水层才知道如何翻译成服务端真正支持的名称。4.5 故障现象node_modules\opencode\cli\bin\opencode.exe 与你运行的 windows 版本不兼容表象Windows 用户下载opencode/cli后双击opencode.exe提示不兼容。根因分析opencode.exe是 Electron 打包的 GUI 应用但 OpenRig 的核心是 CLI。Windows 用户应该完全忽略.exe直接用 PowerShell 运行 JS 脚本。解决方案以管理员身份打开 PowerShell执行Set-ExecutionPolicy RemoteSigned -Scope CurrentUser运行node C:\opt\openrig-cli\dist\cli.js chat --model gpt-5.6-sol创建批处理文件codex.batecho off node C:\opt\openrig-cli\dist\cli.js %*实操心得Windows 上node命令必须指向 Node.js 22.x。用where node确认路径如果指向C:\Program Files\nodejs\node.exe则卸载旧版重新安装 Node.js 22.12.0 MSI 安装包并勾选“Add to PATH”。5. OpenRig 的进阶扩展从本地 CLI 到企业级 AI 中间件5.1 模型热切换不用重启服务的动态加载OpenRig 的server.js当前是单模型绑定。要支持多模型热切换需引入opencode/core的ModelRegistry// /opt/openrig/model-registry.js class ModelRegistry { constructor() { this.models new Map(); } async load(modelId) { if (this.models.has(modelId)) return this.models.get(modelId); // 根据 modelId 动态 import 模型适配器 const adapter await import(opencode/adapter-${modelId.split(-)[0]}); const model new adapter.default({ endpoint: http://localhost:300${modelId.includes(claude) ? 2 : 1} }); this.models.set(modelId, model); return model; } } module.exports new ModelRegistry();然后在server.js的/responses路由里const registry require(./model-registry); const model await registry.load(payload.model); const result await model.chat(payload.prompt); res.json(result);这样codex chat --model deepseek-coder会自动加载opencode/adapter-deepseek而--model claude-3-opus加载opencode/adapter-claude无需重启服务。5.2 权限分级基于 JWT 的细粒度 API 控制当前 OpenRig 是单 token 全局访问。企业场景需要区分“研发只读”、“测试可写”、“运维可管理”。方案是用jsonwebtoken生成带 scope 的 token# 生成研发 token jwt sign --sub dev-team --scope read:models,read:health --secret your-secret dev-token.jwt # 生成运维 token jwt sign --sub ops-team --scope read:*,write:*,admin:* --secret your-secret ops-token.jwtserver.js中间件验证const jwt require(jsonwebtoken); app.use((req, res, next) { const auth req.headers.authorization; if (!auth || !auth.startsWith(Bearer )) { return res.status(401).json({ error: Unauthorized }); } try { const token auth.split( )[1]; const decoded jwt.verify(token, your-secret); req.user decoded; // 检查权限 const requiredScope read:responses; if (!decoded.scope.split(,).includes(requiredScope)) { return res.status(403).json({ error: Forbidden }); } } catch (err) { return res.status(401).json({ error: Invalid token }); } next(); });5.3 日志审计ELK 集成的实操配置所有console.log()都应输出 JSON 格式便于 Logstash 解析// /opt/openrig/logger.js const winston require(winston); const logger winston.createLogger({ level: info, format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), transports: [ new winston.transports.File({ filename: /var/log/openrig/api.log }), new winston.transports.Console() ] }); module.exports logger;Logstash 配置/etc/logstash/conf.d/openrig.confinput { file { path /var/log/openrig/*.log start_position beginning } } filter { json { source message } } output { elasticsearch { hosts [http://elasticsearch:9200] index openrig-%{YYYY.MM.dd} } }这样每条codex chat请求都会在 Kibana 里生成结构化日志可按user.sub、model、response_time等字段筛选分析。5.4 安全加固SELinux 与 AppArmor 的强制策略CentOS 7.9 默认启用 SELinuxcodex serve可能因typeAVC拒绝而静默失败。需编写自定义策略# 生成策略模块 ausearch -m avc -ts recent | audit2allow -M openrig-policy semodule -i openrig-policy.pp # 检查是否生效 sestatus -b | grep openrig策略内容应允许codex_t类型的进程connectto网络端口 3001read/usr/lib/nvidia/current/libOpenCL.soexecute/opt/openrig-cli/dist/cli.jsAppArmorUbuntu同理aa-genprof codex交互式生成配置重点放开capability sys_ptrace用于调试和network inet stream用于 HTTP。6. OpenRig 的真实价值边界它不是万能解药而是特定场景的精密手术刀OpenRig 的价值从来不在“它能做什么”而在“它拒绝做什么”。它不承诺一键部署所以你必须亲手编译openclaw它不提供 GUI所以你得习惯tmux的快捷键它不隐藏 token所以你得自己管理密钥轮换它不自动升级所以你得订阅opencode的 GitHub Release。这些“不作为”恰恰是它最锋利的刀刃——它把 AI 的黑盒切成了一块块可触摸、可测量、可审计的金属零件。我见过最震撼的应用是一家医疗影像公司用 OpenRig 搭建的 DICOM 报告生成系统server.js接收 PACS 系统发来的 DICOM 文件元数据用codex chat --model med-gpt-4生成初