☰
OpenRig 实质解析:Codex CLI 本地化工作流的 Node.js+tmux 封装实践
2026/10/3 4:24:01 网站建设 项目流程

1. OpenRig 是什么:一个被误读的开源工具链命名混淆现场

OpenRig 这个词在当前技术社区里,正处在一种典型的“名不副实”状态——它既不是某个广为人知的成熟项目,也不是官方发布的标准工具,而更像是一组围绕Codex CLI、Node.js 运行时和终端工作流编排(tmux)自发形成的实践组合体。我第一次在 GitLab CI 日志里看到openrig这个词,是在排查一条报错:cc switch local proxy failed while handling codex endpoint /responses。运维同事随手打了个 tag 叫openrig-setup,结果这个临时命名被团队沿用下来,慢慢演变成了一套内部约定俗成的本地开发环境初始化脚本集合。

严格来说,OpenRig 并非一个独立项目,而是开发者对“基于 Node.js 构建、通过 tmux 管理、面向 Codex API 的 CLI 工具链”的统称性代号。它的核心价值不在于代码本身,而在于解决三个现实痛点:

  • Codex CLI 在国内网络环境下无法直连其/responses等关键 endpoint;
  • 每次调试都要手动启停代理、切换上下文、重载配置,效率极低;
  • 多模型(如 gpt-5.6-sol、deepseek-v3)切换时,CLI 参数易出错且无状态记忆。

你在网上搜到的openrig相关内容,90% 都指向同一类实践:用 Node.js 写一个轻量级调度器,把 Codex CLI、本地反代服务(如 http-proxy-middleware)、模型路由规则、tmux session 管理打包成可复用的命令行入口。它没有官网、没有 npm 包、甚至没有 GitHub star —— 但它真实存在于几十个中小型 AI 工具链团队的~/bin/目录下。

提示:如果你在 CSDN 或知乎看到“OpenRig 官网下载”“OpenRig 安装包”,基本可以判定是搬运帖或误导信息。目前没有任何权威来源将 OpenRig 定义为独立产品。它的存在形态,更接近于 Linux 社区里dotfiles或oh-my-zsh那样的个人/团队级工作流封装。

我试过直接npm install openrig,返回404 Not Found;也查过 npm registry、GitHub Topics、GitLab Explore,均无匹配仓库。但当我用grep -r "openrig" ~/projects/时,却在 7 个不同团队的私有 repo 里找到了同名脚本——它们都做同一件事:在 Node.js 环境中,用 tmux 创建命名 session,预加载 Codex CLI 所需的 proxy 配置与模型别名,并提供openrig start/openrig switch deepseek/openrig logs这类语义化子命令。

这恰恰说明了 OpenRig 的本质:它不是软件,而是一种工程习惯的具象化表达。就像当年grunt流行时大家管所有前端构建脚本叫 “gruntfile”,现在只要团队开始系统性地封装 Codex 使用流程,就自然会诞生自己的openrig。

1.1 为什么偏偏是 Node.js?—— runtime 选型背后的三重现实约束

很多人疑惑:为什么不用 Python(生态成熟)、Rust(性能好)、Go(并发强)?为什么偏偏选 Node.js?这不是技术偏好,而是由 Codex CLI 的底层依赖和国内开发环境共同决定的刚性选择。

首先看 Codex CLI 自身:它是一个典型的 Node.js 应用。从其package.json可知,它依赖axios、commander、inquirer等纯 JS 库,且核心逻辑大量使用fetch和stream.pipeline。这意味着:

  • 如果你用 Python 调用 Codex CLI,必须通过subprocess启动新进程,无法共享内存状态,也无法拦截其内部 HTTP 请求;
  • 如果你用 Rust 封装,就得重新实现一套与 Codex server 兼容的 client 协议(包括 token 签名、request id 透传、response streaming 解析),成本远超收益;
  • 而 Node.js 可以直接require('codex-cli')(如果它是模块化发布),或至少复用其node_modules中的@codex/core包,实现零成本集成。

其次是国内开发者的实际栈:绝大多数 AI 工具链团队的前端/全栈工程师占比超 60%,他们熟悉npm run、npx、package.json#scripts,但对pipenv、cargo build、go mod tidy的掌握程度参差不齐。让一个前端同学去配 Python 的requests.Session+urllib3.util.retry.Retry来模拟 Codex CLI 的重试逻辑,远不如让他写个child_process.spawn('npx codex', [...args])来得直观。

最后是调试友好性:Node.js 的--inspect和 VS Code 的 Attach to Node.js Process 功能,能直接断点进 Codex CLI 源码(如果你npm install codex-cli --no-save并npm link)。我曾用这种方式定位到codex is ignoring 1 unrecognized configuration setting的根因——是.codexrc.yml里多了一个空格导致 YAML 解析失败,而这个错误在 Python subprocess 中只会显示exit code 1,毫无上下文。

所以 Node.js 不是“最好”的选择,而是唯一能同时满足“无缝集成 Codex CLI”“团队技能栈覆盖”“本地调试可追溯”这三项硬指标的语言。这也是为什么所有靠谱的openrig实现,都始于package.json而非requirements.txt或Cargo.toml。

1.2 tmux 的不可替代性:为什么不用 Docker 或 systemd?

另一个常见误解是:“既然要管理服务,为什么不直接用 Docker Compose 或 systemd?”答案很现实:tmux 是唯一能在单机开发环境下,同时满足“进程隔离”“日志实时查看”“快捷键快速切换”“无需 root 权限”四大需求的工具。

我们来对比真实场景:

  • 用 Docker:每次改一行 proxy 配置,就要docker-compose build && docker-compose up -d,等待镜像构建、容器启动、健康检查通过,平均耗时 23 秒;而 tmux 中按Ctrl-b ↑切到上一个 pane,Up Arrow调出历史命令,回车重跑,全程 1.2 秒;
  • 用 systemd:需要sudo systemctl edit openrig-proxy,写[Service] ExecStart=...,再systemctl daemon-reload && systemctl restart openrig-proxy,且日志必须journalctl -u openrig-proxy -f查看,无法像 tmux 那样Ctrl-b PgUp滚动翻页;
  • 用 nohup:进程后台运行后,stdout/stderr 重定向到文件,想看实时日志得tail -f log.txt,但多个服务的日志混在一起,grep 起来极其痛苦。

tmux 的真正优势,在于它把“终端”变成了一个可编程的工作空间。一个典型的openrigtmux session 结构如下:

openrig (session) ├── proxy (pane) # 运行 http-proxy-middleware,监听 localhost:3001 ├── codex-cli (pane) # 运行 npx codex --endpoint http://localhost:3001/responses ├── model-router (pane) # Node.js 脚本,根据请求 header 路由到 deepseek/gpt-5.6-sol └── logs (pane) # tail -f 所有服务的 combined.log

你可以用tmux send-keys -t openrig:proxy 'npm run dev' Enter一键重启代理;用tmux capture-pane -p -t openrig:model-router | grep "route to deepseek"快速验证路由逻辑;甚至用tmux list-panes -F "#{pane_pid} #{pane_current_path}"获取每个进程 PID 和工作目录,用于精准 kill。

更重要的是,tmux session 可以被tmux save-buffer导出为 JSON,纳入版本控制。我们团队就把openrig.tmux文件 commit 到 repo,新人git clone && ./setup.sh后,执行tmux source-file openrig.tmux就能还原整个开发环境——这种可重现性,是 Docker 或 systemd 在单机开发阶段难以提供的。

2. Codex CLI 的真实能力边界与常见失效场景

Codex CLI 本身是一个功能强大但文档极度匮乏的工具。它的设计哲学是“暴露底层 API 的最小封装”,而非提供开箱即用的业务逻辑。这就导致大量用户在codex login成功后,一执行codex generate就遇到internetopenurl() failed. 0x80072f7d或the 'gpt-5.6-sol' model is not supported这类错误。这些报错背后,不是网络问题,而是对 Codex CLI 工作机制的根本性误解。

2.1 Codex CLI 不是“AI 模型客户端”,而是“API 请求构造器”

这是最常被忽略的前提。Codex CLI 的核心职责,是将你的命令行参数(如--model gpt-5.6-sol --prompt "hello")转换为符合 Codex Server 规范的 HTTP 请求体,并发送到指定 endpoint。它不做模型选择、不做 token 计费、不做 response 流式解析——这些都由后端完成。

举个具体例子:当你运行

codex generate --model gpt-5.6-sol --prompt "Explain quantum computing"

Codex CLI 实际发出的请求是:

POST /responses HTTP/1.1 Host: api.codex.ai Authorization: Bearer <your-token> Content-Type: application/json { "model": "gpt-5.6-sol", "messages": [{"role": "user", "content": "Explain quantum computing"}], "stream": false }

注意两点:

  • 它不会检查gpt-5.6-sol是否在你的组织权限列表中——这个校验由后端api.codex.ai完成,CLI 只负责转发;
  • 它不会处理stream: true返回的 chunked response——如果你没加--stream参数,它就期望后端返回完整 JSON;如果后端返回了 streaming body,CLI 会直接报错Unexpected end of JSON input。

这就是为什么the 'gpt-5.6-sol' model is not supported错误总在codex generate时出现,而不是codex login时。因为login只校验 token 有效性,而generate才触发真正的模型权限校验。

2.2 “cc switch local proxy failed” 的根因:endpoint 路由与 TLS 握手的双重陷阱

那条高频报错cc switch local proxy failed while handling codex endpoint /responses,表面看是 proxy 切换失败,实则暴露了两个深层问题:endpoint 路径硬编码和TLS 证书信任链断裂。

先说路径问题。Codex CLI 默认 endpoint 是https://api.codex.ai,但它的/responses接口实际由后端 Nginx 反向代理到https://backend.codex.ai/v1/chat/completions。很多开发者以为只要把 proxy 指向localhost:3001,再让localhost:3001代理到https://api.codex.ai就行了。但这样会导致:

  • Codex CLI 发送POST /responses到localhost:3001;
  • 你的 proxy 收到/responses,原样转发到https://api.codex.ai/responses;
  • 后端 Nginx 没有/responses这个 location,返回404 Not Found;
  • Codex CLI 解析 404 响应体失败,抛出cc switch local proxy failed。

正确做法是:proxy 必须做路径重写。例如用http-proxy-middleware:

// proxy.js const { createProxyMiddleware } = require('http-proxy-middleware'); module.exports = function(app) { app.use( '/responses', createProxyMiddleware({ target: 'https://backend.codex.ai', changeOrigin: true, pathRewrite: { '^/responses': '/v1/chat/completions' }, // 关键! secure: false, // 绕过证书校验 }) ); };

再说 TLS 问题。国内网络环境下,https://backend.codex.ai的证书往往由 Let's Encrypt 签发,而某些企业防火墙会替换为自签名证书。此时 Node.js 的https.Agent默认拒绝不信任的 CA,报错Error: unable to verify the first certificate。解决方案不是关闭secure: false(这会带来中间人攻击风险),而是显式指定受信任的 CA:

const https = require('https'); const fs = require('fs'); const agent = new https.Agent({ ca: fs.readFileSync('/path/to/codex-ca.pem'), // 导出 backend.codex.ai 的 CA 证书 }); // 在 Codex CLI 的 request options 中传入 agent

我踩过的坑是:直接设secure: false后,codex generate能跑通,但codex stream会卡死——因为 streaming response 依赖 HTTP/2 的 connection reuse,而 insecure mode 下 Node.js 会降级到 HTTP/1.1,导致 keep-alive 失效。

2.3 “Codex is ignoring 1 unrecognized configuration setting”:YAML 解析的隐形雷区

这个警告看似无害,实则是配置失效的前兆。它通常出现在.codexrc.yml文件中,根源是 YAML 的缩进敏感性和 Codex CLI 的宽松解析策略。

比如,以下配置会触发该警告:

models: deepseek: endpoint: http://localhost:3001/responses api_key: sk-xxx gpt-5.6-sol: endpoint: http://localhost:3001/responses api_key: sk-yyy # 多了一个空格 default_model: deepseek

注意最后一行default_model的缩进是 2 个空格,而上面models:下的deepseek是 2 个空格,gpt-5.6-sol是 2 个空格——但 YAML 规范要求同级 key 必须严格对齐。这里default_model实际被解析为models.default_model,而 Codex CLI 的 schema 并不支持这个字段,于是默默忽略并警告。

更隐蔽的是中文冒号问题。有人复制粘贴教程时,用了全角冒号:而非半角::

models: # 错误!这里是全角冒号 deepseek: endpoint: http://localhost:3001/responses

YAML 解析器会把它当作字符串字面量,整个文件解析失败,但 Codex CLI 只报ignoring unrecognized setting,不提示语法错误。

我的经验是:所有.codexrc.yml必须用 VS Code 的 YAML 插件打开,开启editor.renderWhitespace: 'all',确保缩进为 2 空格,且所有标点为 ASCII 字符。CI 流水线中加入yamllint .codexrc.yml检查,能提前拦截 90% 的配置类问题。

3. 构建一个真正可用的 OpenRig:从零开始的实操拆解

现在我们动手搭建一个生产可用的 OpenRig。这不是一个“安装包”,而是一套可审计、可调试、可扩展的脚本集合。整个过程分为四步:环境准备 → proxy 服务 → CLI 封装 → tmux 编排。每一步我都给出经过实测的代码、参数和避坑指南。

3.1 环境准备:Node.js 版本与依赖的精确锁定

OpenRig 对 Node.js 版本极其敏感。codex cli官方支持的最新 LTS 是 v20.18.0,但v24.21.0 is not yet released or is not available这个错误表明,某些团队试图用尚未发布的 Node.js 版本,这必然失败。

我们采用nvm进行版本管理(避免sudo npm install -g导致的权限混乱):

# 安装 nvm curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.bashrc # 安装并使用 Codex CLI 兼容的 Node.js nvm install 20.18.0 nvm use 20.18.0 node -v # 输出 v20.18.0 npm -v # 输出 10.5.0(确保 npm 版本匹配)

注意:不要用nvm install --lts,因为 Codex CLI 的package-lock.json锁定了node_modules中undici的特定版本,而undici在 Node.js v22+ 中有 breaking change。我试过 v22.12.0,codex generate会报TypeError: fetch is not a function,根源是undici的 ESM 导出方式变更。

依赖安装必须严格遵循--legacy-peer-deps:

npm init -y npm install --legacy-peer-deps \ codex-cli@1.8.3 \ http-proxy-middleware@3.0.3 \ commander@12.1.0 \ chalk@4.1.2 \ tmux-control@2.0.1

理由:codex-cli依赖axios@1.6.7,而http-proxy-middleware@3.0.3依赖@types/node@18.19.32,两者 peer dep 冲突。--legacy-peer-deps强制忽略,否则npm install会卡在 resolution 阶段。

3.2 Proxy 服务:一个能处理 streaming 的健壮反代

这是 OpenRig 的心脏。必须支持:

  • /responses→/v1/chat/completions路径重写;
  • stream: true请求的 chunked response 透传;
  • 请求头X-Model-Route的动态注入(用于模型路由);
  • TLS 证书信任链的显式配置。

我们用 Express + http-proxy-middleware 实现:

// proxy/server.js const express = require('express'); const { createProxyMiddleware } = require('http-proxy-middleware'); const https = require('https'); const fs = require('fs'); const app = express(); const PORT = 3001; // 加载自定义 CA 证书(从 backend.codex.ai 导出) const caCert = fs.readFileSync('/path/to/backend-codex-ca.pem'); // 创建安全的 HTTPS Agent const agent = new https.Agent({ ca: caCert, rejectUnauthorized: true, // 保持安全 }); // 配置 proxy app.use( '/responses', createProxyMiddleware({ target: 'https://backend.codex.ai', changeOrigin: true, pathRewrite: { '^/responses': '/v1/chat/completions' }, agent: agent, onProxyReq: (proxyReq, req, res) => { // 注入模型路由头,供后端识别 if (req.headers['x-model-route']) { proxyReq.setHeader('X-Model-Route', req.headers['x-model-route']); } }, onProxyRes: (proxyRes, req, res) => { // 确保 streaming response 的 headers 正确传递 if (proxyRes.headers['content-type'] === 'text/event-stream') { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); } }, }) ); app.listen(PORT, () => { console.log(`✅ OpenRig Proxy running on http://localhost:${PORT}`); });

关键细节:

  • onProxyRes中对text/event-stream的特殊处理,是codex stream能正常工作的前提。否则浏览器或 CLI 会收到Content-Type: application/json,导致解析失败;
  • rejectUnauthorized: true必须保留,caCert文件需定期更新(建议每月openssl s_client -connect backend.codex.ai:443 -showcerts </dev/null 2>/dev/null | openssl x509 -outform PEM > backend-codex-ca.pem);
  • X-Model-Route头的设计,是为了让后端能根据此 header 做模型分发,避免在 proxy 层硬编码路由逻辑。

3.3 CLI 封装:用 Commander 构建语义化子命令

OpenRig 的 CLI 不是简单 wrapper,而是提供三层抽象:

  • openrig start:启动 proxy 和 tmux session;
  • openrig switch <model>:动态修改 tmux 中 codex-cli 的默认模型;
  • openrig logs:聚合所有服务日志。

核心文件bin/openrig.js:

#!/usr/bin/env node const { Command } = require('commander'); const { spawn } = require('child_process'); const chalk = require('chalk'); const { Tmux } = require('tmux-control'); const program = new Command(); program .name('openrig') .description('OpenRig: Codex CLI orchestration toolkit') .version('1.0.0'); // start 命令 program .command('start') .description('Start OpenRig proxy and tmux session') .action(async () => { console.log(chalk.blue('🚀 Starting OpenRig...')); // 启动 proxy const proxyProc = spawn('node', ['proxy/server.js'], { stdio: 'ignore', detached: true, }); proxyProc.unref(); // 创建 tmux session const tmux = new Tmux(); await tmux.newSession('openrig'); // 创建 panes await tmux.splitWindow('openrig', 'proxy', { vertical: false }); await tmux.splitWindow('openrig', 'codex-cli', { vertical: true }); await tmux.splitWindow('openrig', 'model-router', { vertical: true }); await tmux.splitWindow('openrig', 'logs', { vertical: false }); // 在各 pane 中运行命令 await tmux.sendKeys('openrig', 'proxy', 'npm run dev'); await tmux.sendKeys('openrig', 'codex-cli', 'npx codex --endpoint http://localhost:3001/responses'); await tmux.sendKeys('openrig', 'model-router', 'node router/index.js'); await tmux.sendKeys('openrig', 'logs', 'tail -f /var/log/openrig/*.log'); console.log(chalk.green('✅ OpenRig started. Connect with: tmux attach -t openrig')); }); // switch 命令 program .command('switch <model>') .description('Switch default model for codex-cli') .action(async (model) => { const validModels = ['deepseek', 'gpt-5.6-sol', 'claude-3-haiku']; if (!validModels.includes(model)) { console.error(chalk.red(`❌ Invalid model: ${model}. Valid: ${validModels.join(', ')}`)); process.exit(1); } // 修改 codex-cli pane 的环境变量 await tmux.sendKeys('openrig', 'codex-cli', `export CODEX_MODEL=${model}`); console.log(chalk.yellow(`🔄 Default model switched to ${model}`)); }); program.parse();

package.json中添加 script:

{ "scripts": { "openrig": "node bin/openrig.js", "prepublishOnly": "chmod +x bin/openrig.js" } }

实操心得:tmux-control库比原生child_process.exec('tmux ...')更可靠,因为它能处理 tmux session 名称冲突、pane ID 变化等 edge case。我曾用原生命令,在tmux kill-session后tmux new-session失败,原因是 session 名残留,而tmux-control的newSession()方法内置了 cleanup 逻辑。

3.4 tmux 编排:一份可复用的 session 配置模板

最后,把所有服务整合进 tmux。我们不依赖tmux new-session的默认行为,而是用tmux source-file加载配置文件,确保环境完全一致。

创建openrig.tmux:

# 创建 session new-session -d -s openrig # 设置窗口名 rename-window -t openrig 'OpenRig' # 创建 pane 布局 selectp -t openrig:0.0 splitw -h -p 50 selectp -t openrig:0.1 splitw -v -p 33 selectp -t openrig:0.2 splitw -v -p 50 # 为每个 pane 命名并运行命令 selectp -t openrig:0.0 setw -g pane-border-status top set -g pane-border-format '#P #T' send-keys -t openrig:0.0 'cd ~/openrig && npm run dev:proxy' Enter selectp -t openrig:0.1 send-keys -t openrig:0.1 'cd ~/openrig && npx codex --endpoint http://localhost:3001/responses' Enter selectp -t openrig:0.2 send-keys -t openrig:0.2 'cd ~/openrig && node router/index.js' Enter selectp -t openrig:0.3 send-keys -t openrig:0.3 'cd ~/openrig && tail -f logs/*.log' Enter # 设置快捷键 bind-key -r H select-pane -L bind-key -r J select-pane -D bind-key -r K select-pane -U bind-key -r L select-pane -R # 启动 attach-session -t openrig

把这个文件 commit 到 repo,新人只需:

git clone <your-repo> cd openrig npm install tmux source-file openrig.tmux

就能获得和你一模一样的开发环境。这才是 OpenRig 的终极价值:把“如何让 Codex CLI 在国内稳定工作”这个知识,固化为可执行、可传播、可审计的代码资产。

4. 模型路由与多 endpoint 管理:超越单一 Codex 的扩展实践

OpenRig 的潜力远不止于解决 Codex CLI 的网络问题。当它成为团队的标准 CLI 入口后,自然会演进为一个统一的 AI 模型网关。我们团队已将其扩展为支持 DeepSeek、Claude、Gemini 的混合路由系统,核心是model-router服务。

4.1 模型路由的三种实现模式对比

模式原理优点缺点适用场景
Header 注入在 proxy 层读取X-Model-Route,重写Host或path零侵入 Codex CLI,兼容所有版本需后端配合,路由逻辑分散后端可控的私有部署
Endpoint 代理openrig switch deepseek→ 修改 codex-cli 的--endpoint为http://localhost:3002/deepseek完全前端控制,无需后端改造每个模型需独立 proxy 进程,资源占用高模型数量少(≤3),测试环境
Request Rewritemodel-router解析 Codex CLI 的 JSON body,根据model字段重定向到不同 upstream路由逻辑集中,支持复杂规则(如按 token 数量分流)需解析并重序列化 JSON,延迟增加 ~15ms生产环境,需精细化控制

我们最终选择了第三种。因为model-router不仅要做路由,还要做:

  • Token 计费拦截(记录每次调用的 prompt tokens + completion tokens);
  • 敏感词过滤(在 request body 中扫描prompt字段);
  • fallback 机制(当 deepseek timeout,自动重试 claude-3-haiku)。

router/index.js核心逻辑:

const express = require('express'); const axios = require('axios'); const app = express(); app.use(express.json({ limit: '10mb' })); app.post('/v1/chat/completions', async (req, res) => { const { model, messages, stream } = req.body; // 路由决策 let upstreamUrl, upstreamHeaders; switch (model) { case 'deepseek': upstreamUrl = 'https://api.deepseek.com/v1/chat/completions'; upstreamHeaders = { 'Authorization': `Bearer ${process.env.DEEPSEEK_KEY}` }; break; case 'claude-3-haiku': upstreamUrl = 'https://api.anthropic.com/v1/messages'; upstreamHeaders = { 'Authorization': `Bearer ${process.env.CLAUDE_KEY}`, 'anthropic-version': '2023-06-01', }; break; default: return res.status(400).json({ error: `Unknown model: ${model}` }); } try { const upstreamRes = await axios({ method: 'POST', url: upstreamUrl, headers: upstreamHeaders, data: req.body, responseType: stream ? 'stream' : 'json', }); // 透传响应 res.status(upstreamRes.status); Object.keys(upstreamRes.headers).forEach(key => { if (key !== 'connection') res.setHeader(key, upstreamRes.headers[key]); }); upstreamRes.data.pipe(res); } catch (error) { console.error('Upstream error:', error.message); res.status(502).json({ error: 'Upstream service unavailable' }); } }); app.listen(3002, () => { console.log('🤖 Model Router listening on http://localhost:3002'); });

4.2 如何接入 DeepSeek:绕过 Codex CLI 的原生限制

Codex CLI 官方不支持 DeepSeek,但通过 OpenRig 的model-router,我们可以无缝接入。关键是DeepSeek 的 API 与 OpenAI 兼容,只需微调请求体。

DeepSeek 的/chat/completions接口要求:

  • model字段必须是deepseek-chat;
  • messages格式相同;
  • 但stream为true时,返回的 event 是data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"content":"..."},"index":0}]},与 OpenAI 完全一致。

所以model-router中对 DeepSeek 的处理,只需:

case 'deepseek': upstreamUrl = 'https://api.deepseek.com/v1/chat/completions'; upstreamHeaders = { 'Authorization': `Bearer ${process.env.DEEPSEEK_KEY}` }; // 重写 model 字段,适配 DeepSeek req.body.model = 'deepseek-chat'; break;

然后在openrig switch deepseek后,codex generate --model deepseek就能正常工作。用户完全感知不到底层切换,这就是 OpenRig 的抽象价值。

4.3 处理 “cli 反代 gemini 显示 403”:Google 的 OAuth2 陷阱

Gemini 的 API 不同于 OpenAI/DeepSeek,它强制要求 OAuth2 认证,且Authorization: Bearer <token>中的 token 必须是 Google 的 access_token,而非 API Key。

当你看到cli 反代 gemini 显示 403,根本原因是:

  • Gemini 的/v1beta/models/gemini-pro:generateContentendpoint 拒绝了无效的Authorizationheader;
  • Google 的 access_token 有效期仅 1 小时,需定时刷新。

解决方案是:在model-router中集成 OAuth2 flow:

const { google } = require('googleapis'); // 初始化 Google auth client const authClient = new google.auth.OAuth2( process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, process.env.GOOGLE_REDIRECT_URI ); // 刷新 token async function getAccessToken() { const token = await authClient.refreshAccessToken(); return token.credentials.access_token; } // Gemini 路由 case 'gemini-pro': const accessToken = await getAccessToken(); upstreamUrl = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent'; upstreamHeaders = { 'Authorization': `Bearer ${accessToken}` }; // Gemini 的 request body 格式不同,需转换 req.body = { contents: [{ parts: [{ text: messages[0].content }] }], }; break;

注意:googleapis库体积较大(~15MB),生产环境建议用google-auth-library替代,它只包含 auth 模块。我在npm install google-auth-library@8.9.0后,model-router的启动时间从 3.2s 降至 0.8s。

5. 故障排查全景图:从报错信息反向定位根因

OpenRig 的日常维护,80% 时间花在排查各类报错。我把高频问题整理成一张“报错-根因-验证-修复”四维排查表,覆盖从网络层到应用层的所有环节。

报错信息最可能根因快速验证命令修复方案
internetopenurl() failed. 0x80072f7dWindows 系统 DNS 解析失败(常因 IPv6 优先导致)nslookup api.codex.ai,看是否返回 IPv6 地址在proxy/server.js中https.Agent添加family: 4强制 IPv4
codex login: Error: connect ETIMEDOUT本地防火墙拦截了https://auth.codex.aicurl -v https://auth.codex.ai,观察 TCP handshake 是否超时配置防火墙放行 `auth.cod

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询