openrig 配置编排:用 yaml 和 node.js 统一管理 claude code 与 codex
1. openrig 到底是个什么东西第一次看到 openrig 这个名字很多人会以为是某个硬件机架项目毕竟 rig 在英文里常指设备支架、钻机或者测试台。但放到 claude code、codex、yaml、node.js 这组关键词里它的真实身份就清晰了这是一套围绕 AI 编程助手做本地配置编排的开源工具思路核心目标是把 claude code、codex 这类命令行 AI 编程工具的模型接入、代理转发、参数配置统一管起来用一份 yaml 描述清楚再用 node.js 跑起来。我最初接触这类需求是因为同时用 claude code 和 codex 两套工具每换一个模型供应商就要改一遍环境变量、改一遍配置文件时间长了根本记不住哪个 key 对应哪个端点。openrig 这类项目解决的正是这个痛点把用哪个模型、走哪个端点、传什么参数、给哪个工具用这几件事从散落的 shell 脚本和环境变量里抽出来收敛到一份结构化的 yaml 里。你改一处配置claude code 和 codex 都能读到同一份定义不用再两头维护。它适合谁三类人最需要。第一类是同时使用多个 AI 编程工具的开发者claude code 写一个项目、codex 跑另一个项目配置容易打架。第二类是需要频繁切换模型的人今天用云端模型明天想接本地模型做对比测试。第三类是想把团队配置标准化的技术负责人一份 yaml 提交到仓库所有人拉下来就能用不用口头传授你要先 export 哪个变量。需要提前说清楚的是openrig 本身不是一个官方产品更像是一类配置编排方案的统称。你在社区里看到的实现可能各有差异但底层逻辑是一致的yaml 做声明node.js 做执行代理层做协议转换最终让 claude code 和 codex 这类工具以为自己在跟原生端点对话。理解了这条主线后面所有细节都好推。2. 整体设计思路与方案选型拆解2.1 为什么用 yaml 而不是 json 或 toml配置格式的选择看着是小事实际影响日常使用体验。openrig 这类项目普遍选 yaml理由很实在。json 的问题是写注释不方便而 AI 工具配置里恰恰有大量需要注释的地方比如这个端点只在测试环境用这个 key 下个月过期。json 标准不支持注释硬塞注释字段又会让解析逻辑变脏。toml 表达嵌套结构时层级一深就啰嗦而模型配置天然是多层嵌套供应商下面有端点端点下面有模型模型下面有参数。yaml 的优势在于缩进即层级写起来接近自然语言注释用 # 就行列表和字典混排也自然。一份典型的模型配置大概长这样providers: - name: local-lab endpoint: http://127.0.0.1:1234/v1 models: - id: qwen2.5-coder context: 32768 tools: true - name: cloud-a endpoint: https://api.example.com/v1 models: - id: gpt-5.6-sol context: 128000 tools: true这种结构一眼能看懂改起来也不容易出错。但 yaml 有个经典坑必须提前讲缩进必须用空格绝对不能用 tab。我见过太多人从编辑器里复制配置tab 和空格混在一起报错信息还特别隐晦排查半天才发现是缩进问题。建议在编辑器里把 yaml 文件的 tab 自动转空格打开一劳永逸。2.2 node.js 在其中的角色定位为什么这类工具偏爱 node.js因为 claude code 和 codex 本身就是 node.js 生态里的命令行工具用 npm 全局安装运行时依赖 node 环境。openrig 用 node.js 实现等于跟被编排的工具处在同一个运行时里调用子进程、读写配置、起本地代理服务都不用跨语言。node.js 在这里干三件事。第一件是解析 yaml读进来变成内存对象。第二件是起一个本地 HTTP 服务做代理把 claude code 或 codex 发过来的请求按配置转发到真实端点必要时做请求体和响应体的格式转换。第三件是管理进程按需拉起或关闭工具实例。node.js 版本选择上有个实际教训。网上经常能看到类似 error installing 24.21.0: node.js v24.21.0 is not yet released 这种报错本质是版本号写错了或者源里还没有这个版本。稳妥做法是装 LTS 版本去 node.js 官网下载页选标着 LTS 的那个别追最新的奇数版本。LTS 意味着长期维护生态兼容性最好claude code 和 codex 这类工具在 LTS 上跑最不容易出幺蛾子。2.3 代理层为什么是必需的有人会问既然 claude code 和 codex 都能直接配端点为什么还要在中间加一层代理直接配不是更简单直接配在单一场景下确实简单但多工具多模型场景下会失控。claude code 和 codex 对请求格式的要求不完全一样同一个模型端点claude code 发过去的请求体结构和 codex 发过去的可能不同。如果每个工具都直连你就得为每个工具单独准备一份端点配置模型一多就是组合爆炸。代理层的价值是把工具差异和模型差异解耦。工具只管往本地代理发请求代理负责翻译成目标端点能懂的格式。这样新增一个模型只需要在 yaml 里加一段所有工具自动可用。这也是为什么社区里会出现 cc switch local proxy failed while handling codex endpoint /responses 这类报错本质是代理在处理 codex 的 /responses 端点时格式转换没对上。理解了这个分层排查这类问题就有方向了先确认代理有没有正确识别请求来自哪个工具再确认转换逻辑有没有覆盖这个端点。3. 核心细节解析与实操要点3.1 环境准备node.js 安装的正确姿势一切从 node.js 开始。Windows 用户去 node.js 官网下载 LTS 的 msi 安装包双击一路下一步即可安装时勾选自动配置环境变量。装完打开新的命令行窗口敲node -v npm -v两个命令都能输出版本号说明装好了。如果提示找不到命令八成是环境变量没生效关掉命令行重开一次还不行就手动把 node 安装目录加进 PATH。Ubuntu 用户建议用 nvm 管理版本比直接 apt 装灵活得多curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.bashrc nvm install --lts nvm use --lts用 nvm 的好处是以后想换版本一条命令搞定不用跟系统包管理器较劲。装完同样用 node -v 验证。注意不要用 sudo 去装全局 npm 包权限问题会埋下很多隐患。如果遇到权限报错正确做法是配置 npm 的用户级全局目录而不是加 sudo。3.2 claude code 与 codex 的安装与共存claude code 和 codex 都是 npm 全局包安装命令类似npm install -g anthropic-ai/claude-code npm install -g openai/codex装完之后两个工具的命令分别是 claude 和 codex。这里有个常见问题两个工具都想读自己的配置目录默认路径不同一般不会冲突。但如果你之前手动改过环境变量比如设过全局的 API 端点变量就可能出现一个工具读到了另一个工具的配置。排查方法是分别运行两个工具的配置查看命令确认各自读到的端点是不是你预期的。vscode 里配置 claude code 是另一个高频需求。装好 claude code 的 vscode 扩展后扩展会调用本地的 claude 命令。如果 vscode 里报找不到命令通常是 vscode 启动时继承的环境变量和终端不一致。解决办法是在 vscode 设置里显式指定 claude 命令的完整路径或者从已经配好环境的终端里用 code 命令启动 vscode让它继承正确的 PATH。3.3 yaml 配置文件的结构设计openrig 的核心就是这份 yaml。设计时要把几个维度分清楚供应商、端点、模型、工具绑定、参数覆盖。供应商层描述从哪来包含名称和基础端点。端点层描述具体地址因为同一个供应商可能有多个接入点。模型层描述用哪个模型包含模型 id、上下文长度、是否支持工具调用。工具绑定层描述给谁用把模型和 claude code、codex 关联起来。参数覆盖层描述特殊要求比如某个模型需要额外的请求头或者特殊的温度值。分层的好处是复用。三个模型来自同一个端点端点只写一次。两个工具用同一个模型绑定关系写两条就行模型定义不用重复。version: 1 providers: - name: lab endpoint: http://127.0.0.1:1234/v1 headers: Authorization: Bearer local-key models: - id: qwen2.5-coder provider: lab context: 32768 tools: true bindings: claude-code: - qwen2.5-coder codex: - qwen2.5-coder这份配置的意思是本地 lab 端点提供一个 qwen2.5-coder 模型claude code 和 codex 都能用它。改模型只需要动 models 段改绑定只需要动 bindings 段互不影响。3.4 参数计算上下文长度怎么定上下文长度这个参数很多人随手填其实有讲究。填太小长文件读一半就被截断工具会莫名其妙丢上下文。填太大超出模型实际能力请求直接报错。正确做法是查模型官方文档给出的最大上下文然后留出安全余量。比如模型标称 32768实际配置填 30000 左右比较稳因为请求里除了你的代码还有系统提示词、工具定义等开销。如果模型支持动态扩展可以配置成按需增长但要注意显存或内存占用。工具调用开关同理。模型支持 function calling 就打开不支持就关掉别硬开。硬开的后果是工具发过去的工具定义被模型忽略或者模型返回格式不对导致解析失败表现就是 claude code 或 codex 卡住不动或者报奇怪的解析错误。4. 实操过程与核心环节实现4.1 从零搭起本地代理的完整流程假设你已经装好 node.js LTS接下来按顺序走。第一步建项目目录初始化mkdir openrig-lab cd openrig-lab npm init -y npm install js-yaml expressjs-yaml 负责解析配置express 负责起代理服务。这两个是最小依赖够用了。第二步写配置文件 config.yaml内容参考上一节的结构把端点换成你实际要用的。第三步写代理主程序 index.js。核心逻辑是读配置、起服务、按路径转发const fs require(fs); const yaml require(js-yaml); const express require(express); const config yaml.load(fs.readFileSync(./config.yaml, utf8)); const app express(); app.use(express.json({ limit: 10mb })); app.post(/v1/messages, async (req, res) { const target config.providers[0].endpoint /messages; const resp await fetch(target, { method: POST, headers: { Content-Type: application/json, ...config.providers[0].headers }, body: JSON.stringify(req.body) }); const data await resp.json(); res.json(data); }); app.listen(8787, () console.log(proxy on 8787));这段代码是最简版本只处理一个端点。实际用的时候要按配置里的模型名做路由还要处理流式响应。流式响应是重点claude code 和 codex 都依赖流式输出代理如果直接把流式响应缓冲成完整响应再返回工具会一直等到超时才显示内容。正确做法是把上游的流直接 pipe 给下游中间不做缓冲。第四步把 claude code 和 codex 的端点指向本地代理export ANTHROPIC_BASE_URLhttp://127.0.0.1:8787 export OPENAI_BASE_URLhttp://127.0.0.1:8787/v1具体变量名以两个工具的官方文档为准不同版本可能有差异。设完之后启动工具看代理日志有没有收到请求收到就说明链路通了。4.2 流式响应的处理细节流式处理是这类代理最容易翻车的地方。我踩过的坑是一开始用 await resp.json() 拿完整响应结果 claude code 那边一直转圈等了几十秒才一次性吐出全部内容体验极差。改成流式转发后app.post(/v1/messages, async (req, res) { const target config.providers[0].endpoint /messages; const upstream await fetch(target, { method: POST, headers: { Content-Type: application/json, ...config.providers[0].headers }, body: JSON.stringify(req.body) }); res.setHeader(Content-Type, text/event-stream); res.setHeader(Cache-Control, no-cache); const reader upstream.body.getReader(); while (true) { const { done, value } await reader.read(); if (done) break; res.write(value); } res.end(); });关键点是设置正确的 Content-Type 为 text/event-stream并且逐块 write 而不是等全部读完。这样 claude code 就能像直连一样实时看到输出。注意流式转发时不要对数据做 JSON 解析再重新序列化那样会破坏分块边界导致工具端解析失败。原样透传字节流最安全。4.3 codex 端点 /responses 的适配codex 用的是 /responses 端点和 claude code 的 /messages 端点格式不同。代理要能识别请求来自哪个工具然后走不同的转换逻辑。识别方法有两种。一种是按路径区分/v1/messages 走 claude 逻辑/v1/responses 走 codex 逻辑。另一种是按请求头里的特征字段区分。按路径更简单可靠。codex 的请求体里有个字段标识模型代理根据这个字段查配置找到对应的真实端点和模型 id替换后再转发。响应回来时如果格式和 codex 预期的不一致还要做一次转换。社区里 cc switch local proxy failed while handling codex endpoint /responses 这个报错多半就是转换环节漏了某个字段或者模型名没在配置里找到导致路由失败。排查这类问题的顺序是先看代理日志里收到的原始请求体确认模型名字段的值再查配置里有没有这个模型再看转发出去的实际请求最后看响应转换后的结果。四步走下来问题基本定位。4.4 接入本地模型的实操记录用 claude code 调用本地模型是很多人的刚需因为可以离线跑、不消耗额度、方便调试。本地模型服务通常暴露一个兼容 OpenAI 格式的端点比如 http://127.0.0.1:1234/v1。配置里把这个端点写进 providers模型 id 写本地服务实际加载的模型名。然后关键一步claude code 默认按 Anthropic 的消息格式发请求本地服务按 OpenAI 格式收请求中间必须转换。转换的核心是消息结构。Anthropic 格式里 system 是顶层字段OpenAI 格式里 system 是 messages 数组里 role 为 system 的一条。Anthropic 的 content 可以是字符串或块数组OpenAI 的 content 通常是字符串。工具定义两边格式也不同需要逐字段映射。这块转换逻辑写起来琐碎但不算难建议单独抽一个函数输入 Anthropic 请求体输出 OpenAI 请求体反过来再写一个。写完之后用几个典型请求测一遍确认字段没丢。5. 常见问题与排查技巧实录5.1 安装阶段的典型报错安装阶段最高频的问题集中在 node.js 版本和 npm 权限上。下面这张表是我整理的实际遇到过的报错和对应处理。报错信息根本原因处理方式node.js v24.21.0 is not yet released版本号不存在或源未同步改用 LTS 版本去官网确认实际版本号npm ERR! permission denied全局目录权限不足配置用户级全局目录不要用 sudocommand not found: claude全局 bin 目录不在 PATH把 npm 全局 bin 目录加进 PATHclaude code 提示组织禁用了订阅账号权限或配置问题检查登录状态和配置来源确认使用方式关于 your organization has disabled claude subscription access for claude code 这类提示本质是账号层面的访问控制不是工具本身的问题。遇到时先确认自己用的是哪种接入方式再对照官方文档检查配置不要盲目改代码。5.2 代理运行时的排查思路代理跑起来之后的问题八成出在三个地方路由没匹配上、格式转换出错、流式处理中断。路由问题的表现是请求发出去没反应或者返回 404。排查方法是看代理日志有没有打印收到的路径对比配置里定义的路由规则。路径大小写、结尾斜杠都可能导致不匹配。格式转换问题的表现是工具端报解析错误或者模型返回的内容明显不对。排查方法是把转换前后的请求体和响应体都打到日志里逐字段对比。重点看模型名、消息数组、工具定义这三块。流式中断的表现是输出到一半卡住。排查方法是看上游连接有没有提前关闭以及代理有没有正确处理 reader 的 done 状态。有时候是上游超时设置太短长响应还没生成完连接就断了适当调大超时时间能解决。5.3 多工具共存的配置隔离claude code 和 codex 同时用的时候最容易出的问题是配置串台。比如你给 claude code 设了端点 A给 codex 设了端点 B结果两个都走了 A。根因通常是环境变量作用域没控制好。如果你在 shell 的全局配置文件里 export 了端点变量所有从这个 shell 启动的工具都会读到。正确做法是用工具各自的配置文件或者用 direnv 这类工具做目录级的环境变量管理进到哪个项目目录就加载哪套配置。另一个隔离手段是在 openrig 的 yaml 里给每个工具单独定义绑定代理根据请求来源自动路由。这样即使环境变量设成同一个代理地址代理内部也能把请求分发到正确的模型端点。5.4 模型切换后的验证清单每次在 yaml 里改了模型配置重启代理后建议按这个清单验证一遍代理启动日志有没有报配置解析错误yaml 缩进问题会在这里暴露。用 curl 直接打代理端点确认能拿到响应排除工具本身的问题。启动 claude code发一个简单请求确认有输出且格式正常。启动 codex同样发一个请求确认 /responses 端点工作正常。检查流式输出是否实时有没有卡顿或一次性吐出。这五步走完基本能确认配置生效。跳过任何一步都可能留下隐患等到实际写代码时才发现排查成本更高。6. 我在这套方案上踩过的坑和总结的经验配置编排这类工具坑往往不在核心逻辑上而在边角细节里。我印象最深的一次是 yaml 里一个模型 id 写错了大小写代理路由找不到对应模型直接返回了默认模型结果 claude code 用着用着发现回答风格不对查了半天才定位到是配置里的拼写问题。从那以后我养成了一个习惯配置改完先用一个校验脚本过一遍确认所有引用的模型 id 都在 models 段里有定义。另一个经验是关于日志的。代理层一定要把请求和响应的关键字段打出来但不要打完整的请求体因为里面可能有敏感信息。打模型名、端点、状态码、耗时这几个字段就够了。出问题时这几个字段能覆盖大部分排查场景又不会泄露内容。还有一点是关于版本锁定的。node.js 用 LTS依赖包在 package.json 里锁定版本别用 ^ 让它自动升级。代理这种中间层上游依赖一个小版本变动就可能改变行为锁定版本能保证今天能跑的配置下个月还能跑。最后说个扩展方向。这套 yaml 加代理的结构除了管 claude code 和 codex还能扩展到其他命令行 AI 工具。只要工具支持自定义端点就能接进来。配置结构不用大改加一段绑定就行。我目前把三个工具都接到了同一份配置上切换模型只需要改一行 yaml重启代理所有工具同步生效比之前每个工具单独维护省心太多。