
1. 从“openrig”这个名字说起它到底想解决什么问题第一次看到openrig这个词我脑子里蹦出来的第一反应是“open”加“rig”——一个开放的、可拼装的“机架”或者“装置”。放到当下这个 AI 编程助手满天飞的环境里这个名字其实指向了一个非常具体的痛点把 Claude Code、Codex 这类命令行 AI 编程工具从“官方绑定”里解放出来让它们能自由接入任意模型、任意端点、任意本地服务。我接触 Claude Code 和 Codex 的时间不算短踩过的坑也足够多。最开始用 Claude Code 的时候最让人头疼的就是它默认只认官方那套订阅体系一旦你的账号权限、组织策略、地区策略出点问题直接给你甩一句your organization has disabled claude subscription access for claude code然后你就卡在那里了。Codex 那边也差不多codex无法加载组织设置、the gpt-5.6-sol model is not supported when using codex这类报错几乎每个想折腾的人都遇到过。openrig这类项目的核心价值就是在这两个工具和它们背后的模型服务之间插一层可配置的中间层。你可以把它理解成一个“转接头”Claude Code 和 Codex 各自说自己的方言Anthropic 的 Messages API、OpenAI 的 Responses API而 openrig 负责把这些方言翻译成目标模型能听懂的话再把结果翻译回来。这样一来你就能用 Claude Code 去调 LM Studio 里的本地模型也能用 Codex 去接 DeepSeek、Qwen、GLM 这些第三方服务。这篇文章我打算把 openrig 这套思路从头到尾拆一遍它依赖哪些基础组件Node.js、YAML 配置、核心的代理转发逻辑是怎么设计的、Claude Code 和 Codex 分别怎么接、常见的报错怎么排查。内容会偏实操代码和配置都会给到能直接抄的程度。适合两类人看一类是刚装完 Claude Code 或 Codex、被各种报错卡住的新手另一类是想把 AI 编程工具接到自己私有模型上的进阶玩家。2. 整体设计思路为什么是“代理层 YAML 配置”这套组合2.1 核心矛盾工具协议和模型协议对不上要理解 openrig 为什么要做成一个代理层得先搞清楚 Claude Code 和 Codex 各自在跟谁说话。Claude Code 是 Anthropic 出的命令行工具它内部走的是 Anthropic 的 Messages API 格式请求体长这样messages数组里每个元素带role和contentcontent还可以是块状结构text block、tool_use block、tool_result block。而 Codex 是 OpenAI 系的它走的是 Responses API请求结构、工具调用tool call的字段命名、流式返回的事件类型跟 Anthropic 那套完全不是一回事。问题就来了如果你想用 Claude Code 去调一个只支持 OpenAI 格式的模型服务两边根本对不上话。反过来用 Codex 去调一个只认 Anthropic 格式的端点也一样抓瞎。这就是为什么需要一层代理——它站在中间左边收 Claude Code 或 Codex 的请求右边按目标服务的格式发出去回来的时候再翻译一遍。提示很多人以为“接入第三方模型”就是改个 base_url 那么简单实际上协议不兼容才是真正的拦路虎。base_url 只是地址协议才是语言。2.2 为什么选 YAML 做配置而不是 JSON 或环境变量openrig 这类项目普遍用 YAML 来写配置这个选择不是随便定的。我对比过三种方案配置方式优点缺点适用场景环境变量简单、容器友好复杂嵌套结构表达困难多模型配置会爆炸单一模型、简单场景JSON结构清晰、机器友好不支持注释手写容易漏逗号长配置难维护程序生成、API 交互YAML支持注释、层级直观、多文档缩进敏感tab 和空格混用会报错多模型、多端点、需要注释说明openrig 要管理的是“多个模型 多个端点 各自的鉴权 各自的协议映射”这种嵌套结构用 YAML 写出来最舒服。你可以给每个 provider 单独写一段注释清楚它是干嘛的改的时候一眼就能找到。JSON 做不到注释环境变量更是没法表达嵌套。YAML 的坑也很典型缩进必须用空格不能用 tab冒号后面要留一个空格字符串里有特殊字符要加引号。我见过太多人yaml文件写错一个缩进整个服务起不来报错还特别隐晦。后面排查章节我会专门讲这个。2.3 Node.js 在整个链路里的角色openrig 跑在 Node.js 上这不是偶然。Claude Code 本身就是 Node.js 写的通过 npm 分发Codex CLI 也是 Node.js 生态的产物。用 Node.js 做代理层有几个实打实的好处第一事件循环天然适合做流式转发。AI 编程工具的响应基本都是 SSEServer-Sent Events流式的Node.js 的 stream 和 pipe 处理这种场景非常顺手不用像某些语言那样开线程池。第二和工具本身同源。你装 Claude Code 的时候已经装了 Node.js再跑一个 Node.js 写的代理环境依赖是复用的不用额外装 Python 或 Go 运行时。第三npm 生态里有现成的 HTTP 框架。Express、Fastify、Hono 这些都能快速搭起一个转发服务中间件机制处理鉴权、日志、错误也方便。所以node.js安装是整条链路的第一步这个后面会详细讲。node.js是干什么的这个问题放到这个场景里答案很明确它是 Claude Code、Codex、openrig 三者共同的运行时底座。3. 环境准备Node.js 安装与版本选择的那些坑3.1 Node.js 版本怎么选LTS 还是 Current装 Node.js 第一件事就是选版本。官网node.js官网下载上永远摆着两个选项LTS 和 Current。我的建议很明确——无脑选 LTS。LTS 是 Long Term Support长期支持版稳定、bug 少、生态兼容性好。Current 是最新特性版可能带着还没被生态消化完的改动。AI 编程工具这条链路上任何一个环节用了 Current 版都可能遇到某个依赖还没适配的情况。我印象很深的一次有人图新鲜装了 Current 版结果error installing 24.21.0: node.js v24.21.0 is not yet released or is not ava这种报错就来了——版本号根本不存在或者某个包还没发布对应版本。这种错误看着吓人其实就是版本选错了。具体操作上我更推荐用版本管理器而不是直接装官网安装包# 用 nvm 管理 Node.js 版本Linux/macOS curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.bashrc nvm install --lts nvm use --lts node -vWindows 用户可以用 nvm-windows或者直接去node.js官网下载页面拿 LTS 的 msi 安装包。装完之后node -v和npm -v都要能正常输出这是最基本的验证。注意如果你之前装过旧版本装新版本前最好先卸干净尤其是 Windows 上残留的 PATH 会导致node -v显示的还是旧版本。3.2 npm 镜像与全局安装权限国内环境下 npm 装包慢是常态配个镜像能省不少时间npm config set registry https://registry.npmmirror.com npm config get registry全局安装权限这块Linux/macOS 上如果不用 nvm 而是系统级安装npm install -g经常会报 EACCES 权限错误。用 nvm 就天然避开了这个问题因为包都装在用户目录下。Windows 上一般不会有这个问题但如果遇到用管理员权限开终端即可。3.3 验证环境是否就绪装完 Node.js 之后跑一遍这个检查清单node -v # 应输出 v20.x 或 v22.x 这类 LTS 版本号 npm -v # 应输出对应 npm 版本 npm config get registry # 确认镜像地址 which node # Linux/macOS 确认路径这四步都过了环境底座就算搭好了。接下来才是 Claude Code、Codex 和 openrig 的安装。4. Claude Code 与 Codex 的安装和基础配置4.1 Claude Code 安装npm 全局装最省心Claude Code 的安装方式官方推荐 npm 全局安装npm install -g anthropic-ai/claude-code claude --version装完之后第一次运行claude它会引导你做登录或者配置 API key。这里就是很多人卡住的地方——your organization has disabled claude subscription access for claude code这个报错本质上是账号层面的订阅权限问题不是安装问题。遇到这个要么换一个有权限的账号要么走 API key 模式要么就是本文要讲的——通过代理层接到别的模型上。claude code安装在 Windows 上稍微麻烦一点因为 Claude Code 早期对 Windows 原生支持一般很多人是在 WSL 里跑的。现在原生支持好多了但如果你在 Windows 上遇到奇怪的路径问题WSL 仍然是个稳妥选择。vscode配置claude code和claude code for vs code是另一条路——在 VS Code 里装 Claude Code 扩展这样能在编辑器内直接用。配置方式跟命令行版基本一致只是入口不同。4.2 Codex 安装注意 CLI 和桌面版的区别Codex 这边有 CLI 版和桌面版两条线。codex cli是命令行工具codex安装 windows桌面版则是带界面的。安装 CLI 版一般也是 npmnpm install -g openai/codex codex --versioncodex登录之后同样会遇到组织策略、模型支持的问题。codex无法加载组织设置这个报错通常是网络请求没通或者账号配置有问题。the gpt-5.6-sol model is not supported when using codex这种则是模型名对不上——你配置里写的模型名目标服务不认。codex接入deepseek是很多人关心的场景因为 DeepSeek 性价比高。但 Codex 默认走 OpenAI 的 Responses APIDeepSeek 的接口格式未必完全一致这就需要代理层来做协议转换。这正是 openrig 这类项目的用武之地。4.3 两个工具共存的注意事项Claude Code 和 Codex 装在同一台机器上一般不会冲突因为它们各自的配置目录、环境变量前缀都不一样。但有两个点要注意一是环境变量污染。有些第三方接入方案会让你设ANTHROPIC_BASE_URL、OPENAI_BASE_URL这类变量如果两个工具都读同名变量就会互相干扰。解决办法是给每个工具用独立的配置文件而不是全局环境变量。二是端口占用。如果你同时跑多个代理服务端口要错开。openrig 默认端口如果跟别的服务撞了改配置里的端口号即可。5. openrig 的核心代理转发与协议映射怎么实现5.1 代理层的基本骨架openrig 的核心是一个 HTTP 服务它至少要做三件事接收请求、转换协议、转发并回传。用 Node.js 写一个最小骨架大概是这样// proxy.js - 最小代理骨架 const http require(http); const https require(https); const TARGET_BASE process.env.TARGET_BASE || http://localhost:1234; const PORT process.env.PORT || 8787; const server http.createServer(async (req, res) { // 1. 收集请求体 let body ; req.on(data, chunk body chunk); req.on(end, async () { // 2. 协议转换这里是最关键的部分 const transformed transformRequest(req.url, body); // 3. 转发到目标服务 const targetUrl new URL(req.url, TARGET_BASE); const proxyReq (targetUrl.protocol https: ? https : http).request({ hostname: targetUrl.hostname, port: targetUrl.port, path: targetUrl.pathname targetUrl.search, method: req.method, headers: buildHeaders(req.headers), }, proxyRes { res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res); // 流式回传 }); proxyReq.on(error, err { res.writeHead(502); res.end(JSON.stringify({ error: err.message })); }); proxyReq.write(transformed); proxyReq.end(); }); }); server.listen(PORT, () console.log(openrig proxy on ${PORT}));这个骨架的关键点在于transformRequest和buildHeaders两个函数——它们负责把 Claude Code 或 Codex 发来的请求翻译成目标服务能懂的格式。proxyRes.pipe(res)这一行保证了流式响应能原样透传不会因为缓冲导致打字机效果卡顿。5.2 协议映射Anthropic Messages 与 OpenAI Responses 的差异这是整个项目最硬核的部分。我拿两个典型请求对比一下。Claude Code 发出来的请求Anthropic Messages 格式{ model: claude-sonnet-4, max_tokens: 4096, messages: [ { role: user, content: 帮我写个快排 } ], stream: true }Codex 发出来的请求OpenAI Responses 格式{ model: gpt-5-codex, input: 帮我写个快排, stream: true }注意差异Anthropic 用messages数组OpenAI Responses 用inputAnthropic 的content可以是字符串也可以是块数组Responses 的input结构又不一样。工具调用tool use的字段差异更大——Anthropic 叫tool_use/tool_resultOpenAI 叫function_call/tool_calls。代理层要做的就是把这些字段一一对应起来。写映射逻辑的时候我建议用一个显式的映射表而不是一堆 if-elsefunction anthropicToOpenAI(anthropicReq) { return { model: mapModelName(anthropicReq.model), input: anthropicReq.messages.map(m ({ role: m.role, content: typeof m.content string ? m.content : m.content.map(block blockToText(block)).join() })), stream: anthropicReq.stream, max_output_tokens: anthropicReq.max_tokens, }; }mapModelName这个函数很重要——它负责把 Claude Code 里写的模型名映射到目标服务实际支持的模型名。the gpt-5.6-sol model is not supported这类报错很多时候就是映射没配对。5.3 YAML 配置文件怎么写openrig 的配置用 YAML 组织一个典型的多模型配置长这样# openrig.yaml server: port: 8787 host: 127.0.0.1 providers: local-lmstudio: type: openai base_url: http://localhost:1234/v1 api_key: not-needed models: - name: qwen2.5-coder-7b alias: claude-sonnet-4 # 把本地模型伪装成 Claude 模型名 deepseek: type: openai base_url: https://api.deepseek.com/v1 api_key: ${DEEPSEEK_API_KEY} models: - name: deepseek-chat alias: gpt-5-codex routes: - match: /v1/messages provider: local-lmstudio protocol: anthropic-to-openai - match: /responses provider: deepseek protocol: openai-responses-to-chat这份配置里几个关键点alias字段是精髓。Claude Code 内部会写死一些模型名你没法直接改但通过 alias 映射可以让它以为自己在调claude-sonnet-4实际上请求被转发到了本地 Qwen 模型上。${DEEPSEEK_API_KEY}是环境变量插值避免把密钥硬编码进配置文件。这个语法不是 YAML 原生的是 openrig 在加载配置时自己解析的。routes段定义了路由规则什么样的请求路径走哪个 provider用哪种协议转换。/v1/messages是 Anthropic 的端点/responses是 OpenAI Responses 的端点。注意YAML 里alias后面的冒号一定要跟一个空格alias:claude-sonnet-4这种写法会被解析成字符串而不是键值对服务起不来还不报明确错误。5.4 流式响应的处理细节流式这块是最容易出问题的地方。Claude Code 和 Codex 都依赖 SSE 流式返回来实现“打字机”效果如果代理层把流缓冲了用户就会看到响应卡半天然后一次性蹦出来。处理 SSE 流的时候有几个细节必须注意第一不要用res.json()或res.send()那会把整个响应缓冲起来。要用pipe或者手动res.write()。第二SSE 的事件边界要保留。Anthropic 和 OpenAI 的 SSE 事件格式不同转换的时候要保证data:前缀、事件类型、[DONE]结束标记都正确。第三超时设置要合理。AI 生成长文本可能几十秒代理层的超时如果设太短会中途断开。Node.js 默认的 socket 超时是 2 分钟一般够用但如果你接的模型特别慢要手动调大。// 流式转发时保留 SSE 格式 proxyRes.on(data, chunk { // 如果需要在流中间做协议转换在这里处理 chunk res.write(chunk); }); proxyRes.on(end, () res.end());6. 常见报错排查与避坑实录6.1 报错速查表我把这条链路上最常见的报错整理成了一张表遇到问题先对号入座报错信息根本原因解决方向your organization has disabled claude subscription access账号订阅权限被组织策略限制换账号、走 API key、或接第三方模型codex无法加载组织设置网络请求未通或账号配置异常检查网络、重新登录、检查配置文件the gpt-5.6-sol model is not supported模型名映射错误检查 YAML 里的 alias 和实际模型名cc switch local proxy failed while handling codex endpoint /responses代理层处理 Responses 端点时出错检查协议转换逻辑、看代理日志error installing 24.21.0: node.js v24.21.0 is not yet releasedNode.js 版本号不存在或未发布改用 LTS 版本YAML 解析失败缩进用了 tab、冒号后缺空格用空格缩进、冒号后加空格端口被占用多个服务抢同一端口改配置里的端口号6.2 代理层调试的三个实用技巧技巧一先关流式用非流式请求验证协议转换。流式请求出问题时很难判断是协议转换错了还是流处理错了。把stream设成false发一次请求如果非流式能通说明协议转换没问题问题在流处理如果非流式也不通那就是协议映射本身错了。技巧二把代理层的请求和响应都打日志。在transformRequest前后各打一次在转发前后各打一次。这样能清楚看到“进来的长什么样、转换后长什么样、目标服务返回什么”。很多问题看一眼日志就明白了。console.log([IN], req.url, body.slice(0, 500)); console.log([OUT], JSON.stringify(transformed).slice(0, 500));技巧三用 curl 直接打代理端点绕过 Claude Code 和 Codex。这样能排除工具本身的干扰确认代理服务本身是好的curl -X POST http://127.0.0.1:8787/v1/messages \ -H Content-Type: application/json \ -d {model:claude-sonnet-4,max_tokens:100,messages:[{role:user,content:hi}]}如果 curl 能通但 Claude Code 不通那问题就在 Claude Code 的配置上不在代理。6.3 我踩过的几个坑坑一YAML 里的 tab。这个坑我踩过不止一次。从别处复制配置过来看着缩进是对的实际上混了 tab 和空格解析直接失败。解决办法是编辑器里开启“显示空白字符”一眼就能看出 tab。VS Code 里editor.renderWhitespace设成all就行。坑二环境变量没生效。${DEEPSEEK_API_KEY}这种插值如果环境变量没 export加载配置时会变成空字符串然后请求目标服务就 401。排查的时候先echo $DEEPSEEK_API_KEY确认一下。坑三模型名大小写。有些服务的模型名是大小写敏感的DeepSeek-Chat和deepseek-chat可能一个通一个不通。映射表里的名字最好从目标服务的文档里直接复制别手打。坑四本地模型服务没起。接 LM Studio 的时候忘了在 LM Studio 里点“Start Server”代理转发过去直接 connection refused。这种低级错误排查起来反而费时间因为你会一直怀疑是代理的问题。7. 把 Claude Code 和 Codex 接到本地模型上的完整流程7.1 本地模型服务准备以 LM Studio 为例先在 LM Studio 里加载一个模型比如 Qwen2.5-Coder然后在 Developer 标签页里启动本地服务默认端口 1234。启动后用 curl 验证一下curl http://localhost:1234/v1/models能返回模型列表说明本地服务是好的。7.2 启动 openrig 代理把前面写的 YAML 配置存成openrig.yaml然后启动代理export DEEPSEEK_API_KEYsk-xxxx node proxy.js --config openrig.yaml启动后看到openrig proxy on 8787就说明起来了。7.3 配置 Claude Code 指向代理Claude Code 通过环境变量指定 base URLexport ANTHROPIC_BASE_URLhttp://127.0.0.1:8787 export ANTHROPIC_API_KEYdummy-key claude这里的ANTHROPIC_API_KEY随便填一个非空值就行因为真正的鉴权在代理层到目标服务那一段。Claude Code 只要求这个变量存在。7.4 配置 Codex 指向代理Codex 的配置类似但变量名不同export OPENAI_BASE_URLhttp://127.0.0.1:8787/v1 export OPENAI_API_KEYdummy-key codex注意 Codex 的 base URL 通常要带/v1后缀具体看你的代理路由怎么配的。7.5 验证端到端链路配置完之后在 Claude Code 里随便问一句看响应是否正常。如果响应回来了但内容不对比如模型答非所问那可能是协议转换里某个字段映射错了。如果完全没响应看代理日志确认请求有没有到代理、有没有转发出去。8. 这套方案还能怎么扩展openrig 这套代理思路本质上是一个“协议适配 路由分发”的中间层它的扩展空间比想象中大。一个方向是多模型负载均衡。在 YAML 里给同一个 alias 配多个 provider代理层按轮询或按延迟选一个转发。这样本地模型和云端模型可以混用本地忙的时候自动切云端。另一个方向是请求改写和增强。比如在代理层统一注入 system prompt或者对请求做敏感词过滤、token 计数、成本统计。这些逻辑放在代理层Claude Code 和 Codex 本身不用改。还有一个方向是缓存。相同的请求直接返回缓存结果省 token 也省时间。对于调试阶段反复问同一个问题的场景特别有用。我自己在实际操作中的体会是代理层这东西一旦搭起来后面想接什么模型、想加什么功能都是改配置和加中间件的事不用动 Claude Code 和 Codex 本身。这种“把变化隔离在一层”的设计才是 openrig 这类项目真正的价值所在。最后再分享一个小技巧代理层的日志一定要带请求 ID这样在 Claude Code、代理、目标服务三处日志之间对账的时候能快速定位问题出在哪一段。