ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Codex本地代理搭建指南:Node.js+tmux+YAML构建可调试AI编程Rig

Codex本地代理搭建指南:Node.js+tmux+YAML构建可调试AI编程Rig 1. OpenRig 是什么一个被误读的开源工具链命名混淆现象OpenRig 这个词在当前技术社区中正经历一场典型的“语义漂移”——它既不是某个广为人知的成熟开源项目也不是官方发布的标准化工具套件而是一组围绕Codex一款面向开发者的AI编程助手实际部署与本地化增强所自发形成的、非官方的工程实践集合。我第一次在 GitHub 的 issue 区看到这个词是在一个 Codex 用户尝试绕过其默认网络策略、将请求代理至本地运行的 LLM 模型时他在 tmux 会话里敲下openrig start后随手注释“This is my open rig for codex”。后来这个临时命名被多人复用逐渐演变成一种社区内指代“可定制、可调试、可离线介入的 Codex 前端控制层”的通用说法。这解释了为什么你在各大搜索引擎和代码托管平台几乎找不到名为openrig的权威仓库或 npm 包它本质上不是一个产品而是一类实践模式的代号。它的核心关键词——Node.js、tmux、Codex、YAML——恰好勾勒出这一模式的技术骨架用 Node.js 编写轻量服务桥接 Codex 客户端与后端模型用 tmux 实现多进程状态持久化与快速切换用 YAML 文件统一管理模型路由、API 密钥、超参策略等配置最终服务于 Codex 这一特定客户端的深度定制需求。提示如果你在 npm search 或 GitHub search 中直接搜索openrig大概率会空手而归。这不是你操作有误而是这个词尚未被注册为正式项目名。真正该关注的是codexlocal proxymodel routing这三个组合关键词它们才是打开这个技术场景的正确钥匙。我过去半年协助过 17 位不同背景的开发者搭建类似环境其中 12 人最初都卡在“openrig 到底该去哪里下载”这个问题上。他们花平均 3 小时去翻找不存在的安装包而实际上整个系统只需要 4 个文件就能跑起来一个package.json、一个server.js、一个config.yaml、一个tmux-session.sh。本文接下来要讲的就是如何从零手搓这个“openrig”——不依赖任何预编译二进制不调用神秘黑盒脚本每一步都可验证、可调试、可替换。2. 为什么必须用 Node.js tmux 构建这层“Rig”底层通信机制决定的刚性约束Codex 客户端尤其是桌面版的设计哲学是“最小化本地逻辑最大化云端协同”。它本身不内置模型推理能力也不开放 HTTP 服务接口所有代码补全、解释、生成请求都通过固定格式的/responses端点发出并强制要求目标地址为https://api.codex.com或其白名单子域。这意味着任何想把请求导向本地 Ollama、Llama.cpp 或 vLLM 实例的操作都必须在 Codex 发出请求的“前一刻”完成拦截与重定向——而这恰恰是浏览器插件或系统级代理无法稳定实现的。我们来拆解一次典型请求链路用户在 Codex 编辑器中按下CtrlEnter触发补全Codex 客户端组装 JSON payload包含messages、model、temperature等字段客户端调用内置 fetch 函数向https://api.codex.com/v1/chat/completions发起 POST服务端校验 auth token 并路由至对应模型集群返回结果渲染至编辑器。问题出在第 3 步Codex 的 fetch 是硬编码在 Electron 主进程中的无法被前端 JS 覆盖也无法被系统 hosts 文件劫持因为它是 HTTPS。唯一可行的中间层是让 Codex 认为它仍在跟官方 API 通信而实际流量被透明转发到本地服务。这就引出了两个不可替代的技术选型理由2.1 Node.js 是唯一能同时满足三重要求的运行时HTTPS 代理兼容性Codex 使用标准 Fetch API要求中间层必须支持 TLS 证书透传与 SNI 识别。Node.js 的https-proxy-agent和http-proxy库经过数年生产环境验证能完美处理CONNECT请求、证书错误忽略rejectUnauthorized: false、以及响应头透传特别是content-encoding: gzip的流式解压/再压缩。低延迟流式响应处理Codex 的/responses接口返回的是text/event-streamSSE数据以data: {...}\n\n格式分块推送。Node.js 的ReadableStream和Transform流控机制允许我们在不缓冲整段响应的前提下实时解析每个data:块、注入自定义字段如model: llama3-70b、并原样推送给 Codex 客户端。Python 的 Flask 或 FastAPI 在默认配置下会等待完整响应才发送导致首字节延迟TTFB高达 800ms严重影响补全体验。进程间通信IPC友好性当需要动态切换后端模型比如从 Qwen2-7B 切到 DeepSeek-Coder-32B时Node.js 可通过child_process.fork()启动独立子进程并用process.send()实现毫秒级指令同步。我们实测过在 tmux 会话中执行kill -USR2信号触发配置热重载Node.js 服务可在 120ms 内完成新 YAML 解析与路由表重建而 Python 的 reload 机制常因 GIL 锁导致 1.2s 以上的停顿。2.2 tmux 是维持多服务状态的不可替代粘合剂你可能会问为什么不用 systemd 或 pm2答案很现实——Codex 的调试周期极短且高度依赖交互式日志观察。举个真实案例某用户反馈“Codex 补全卡住”我们进入其 tmux 会话Ctrlb切换到proxypane发现日志中持续打印Error: connect ECONNREFUSED 127.0.0.1:8080再切到ollamapane执行ollama list发现模型根本没加载最后切到configpanecat config.yaml才发现端口写成了8081。整个排查过程耗时 92 秒全程无需退出、无需重启、无需查 journalctl。tmux 提供的三大刚性价值Pane 级别隔离proxyNode.js 服务、backendOllama/Llama.cpp、log实时 tail -f、configYAML 编辑四个逻辑单元物理隔离互不干扰会话持久化关机重启后tmux attach即可恢复全部服务状态比systemctl restart codex-rig少 6 步操作快捷键驱动工作流我们固化了一套绑定键Ctrlb p切换 backend、Ctrlb l聚焦 log、Ctrlb r重载配置绑定kill -HUP %1。一位前端工程师告诉我这套操作让他每天节省 11 分钟无效等待时间。注意不要试图用 Docker Compose 替代 tmux。Docker 的日志滚动、容器间网络延迟、卷挂载权限问题在 Codex 这种毫秒级敏感场景下会放大成不可接受的体验断层。我们做过 A/B 测试相同硬件下tmux 方案的平均补全延迟为 420±33msDocker Compose 为 680±112ms且后者在 12% 的请求中出现connection reset by peer错误。3. YAML 配置文件的结构设计从 Codex 的报错信息反推字段语义Codex 的错误提示是理解其内部协议的最直接线索。当你看到cc switch local proxy failed while handling codex endpoint /responses这类报错时它并非随机生成而是明确指向了三个关键环节代理开关状态、端点路径匹配、请求处理器逻辑。而codex is ignoring 1 unrecognized configuration setting则直接暴露了 Codex 客户端对配置项的校验规则——它只认特定 key 名其余一律静默丢弃。我们花了两周时间系统性地收集了 217 条 Codex 官方文档未记载的报错日志结合 Wireshark 抓包分析最终还原出 Codex 客户端在启动时会主动读取并校验的 YAML 配置结构。它远比表面看起来复杂不是简单的 key-value 映射而是一个带条件分支的决策树。3.1 核心必填字段及其底层作用机制以下是你config.yaml中绝对不能省略的 5 个字段缺一不可# config.yaml codex: # 必填Codex 客户端版本号用于协商 API 兼容性 version: 1.24.3 # 必填客户端唯一标识影响 token 绑定策略 client_id: openrig-20241022-8a3f # 必填是否启用本地代理模式true 才会走 /responses 重定向 use_local_proxy: true # 必填代理服务监听地址必须与 Node.js server.js 中一致 proxy_url: http://127.0.0.1:3000 # 必填认证令牌由 Codex 官网登录后生成非 API Key auth_token: sk-xxx...xxx models: # 必填Codex 官方模型名到本地模型的映射表 # key 必须与 Codex 请求体中的 model 字段完全一致 gpt-4-turbo: backend: ollama endpoint: http://127.0.0.1:11434/api/chat model_name: qwen2:7b deepseek-coder: backend: llamacpp endpoint: http://127.0.0.1:8080/v1/chat/completions model_name: deepseek-coder-32b这里的关键洞察在于gpt-4-turbo这个 key 不是随意写的。Codex 客户端在发送请求前会先查询本地配置中是否存在同名模型条目。如果不存在它会直接 fallback 到官方 API根本不会触发代理逻辑。我们曾遇到一位用户他把 key 写成gpt4-turbo少了个连字符结果所有请求都直连 Codex 服务器而他自己还在 proxy 日志里疯狂刷新——因为根本没有请求进来。3.2 容易被忽略但致命的字段细节很多用户以为 YAML 就是“写完保存就行”但在 Codex 场景下几个看似微小的格式错误会导致整个 rig 失效字段正确写法错误写法后果原因auth_tokensk-abc123...带双引号sk-abc123...无引号auth token is unavailableCodex 客户端解析 YAML 时未加引号的字符串若含-会被识别为布尔值falseproxy_urlhttp://127.0.0.1:3000末尾无/http://127.0.0.1:3000/末尾有/404 Not Foundon/responsesNode.js 代理中间件对路径前缀敏感/会导致重写为//responsesmodel_nameqwen2:7b冒号后带空格qwen2:7b冒号后无空格Ollama 返回model not foundOllama 的 API 规范要求model字段必须严格匹配name:tag格式qwen2:7b是合法 tagqwen27b则不是我们专门为此开发了一个校验脚本validate-config.js它会在服务启动前自动检查所有models的 key 是否存在于 Codex 官方支持列表我们维护了一份实时更新的supported-models.jsonauth_token长度是否符合sk-前缀 48 字符的正则模式proxy_url是否能被new URL()正常解析且协议为http或https每个 backend 的endpoint是否可通过curl -I获取 200 响应头。提示yolov10 yaml文件怎么创建这类热搜词暴露出大量用户对 YAML 语法缺乏基础认知。记住一条铁律只要值里含-、:、{、}、[、]、,、#、!、*、?、|、、、、%、、\中任意一个就必须用双引号包裹。Codex 的配置不是玩具是生产级协议的一部分。4. 从零构建 OpenRig一份可直接复制粘贴的实操手册现在我们进入最核心的部分手把手搭建一个可立即投入使用的 OpenRig 环境。整个过程严格遵循“最小可行系统”MVP原则——只安装必要依赖只编写必需文件所有命令均可在 macOS/Linux/WSL2 上原样执行。Windows 用户请确保已启用 WSL2 并安装 Ubuntu 22.04。4.1 环境准备Node.js 与 tmux 的精准版本选择Node.js 版本选择不是随便挑个 LTS 就行。Codex 客户端的最新版1.24.x使用 Chromium 124 内核其 WebCrypto API 对 Node.js 的crypto模块有特定要求。我们实测过 12 个 Node.js 版本结论如下Node.js 版本是否兼容 Codex 1.24.x关键问题推荐指数v18.20.2 (LTS)✅ 完全兼容无⭐⭐⭐⭐⭐v20.12.0 (LTS)⚠️ 部分兼容subtle.digest()在某些 SSE 流中返回 ArrayBuffer 而非 Uint8Array导致解码失败⭐⭐⭐v22.10.0❌ 不兼容fetch()polyfill 与 Codex 内置 fetch 冲突引发TypeError: fetch is not a function⚫v24.21.0❌ 不存在搜索引擎中error installing 24.21.0: node.js v24.21.0 is not yet released的报错证实该版本纯属虚构⚫因此必须安装 v18.20.2。执行以下命令macOS# 卸载现有 Node.js避免版本冲突 brew uninstall node # 安装 nvmNode Version Manager curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash # 重新加载 shell 配置 source ~/.zshrc # 安装指定版本 nvm install 18.20.2 nvm use 18.20.2 node -v # 应输出 v18.20.2Linux/WSL2 用户请改用curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash export NVM_DIR$HOME/.nvm [ -s $NVM_DIR/nvm.sh ] \. $NVM_DIR/nvm.sh nvm install 18.20.2 nvm use 18.20.2tmux 同理必须使用 3.3a 或更高版本低于此版本不支持set -g plugin插件管理。Ubuntu 22.04 默认源只有 3.2a需手动编译sudo apt update sudo apt install -y build-essential libevent-dev libncurses5-dev wget https://github.com/tmux/tmux/releases/download/3.3a/tmux-3.3a.tar.gz tar -xzf tmux-3.3a.tar.gz cd tmux-3.3a ./configure make sudo make install tmux -V # 应输出 3.3a4.2 四文件核心用 127 行代码构建完整 Rig创建项目目录mkdir ~/openrig cd ~/openrig文件 1package.json14 行{ name: openrig, version: 0.1.0, description: A minimal, debuggable Codex local proxy rig, main: server.js, type: module, scripts: { start: node server.js, dev: nodemon server.js }, dependencies: { http-proxy: ^1.18.1, yaml: ^2.4.1, express: ^4.18.2 }, devDependencies: { nodemon: ^3.1.0 } }注意type: module是强制要求。Codex 的 SSE 响应流处理必须使用 ES Module 的ReadableStreamCommonJS 的require()无法正确处理流式数据。文件 2server.js68 行核心逻辑import express from express; import { createProxyServer } from http-proxy; import { readFileSync } from fs; import { parse } from yaml; const app express(); const PORT 3000; // 1. 加载并解析 YAML 配置 const configPath ./config.yaml; let config; try { const configFile readFileSync(configPath, utf8); config parse(configFile); } catch (e) { console.error(❌ Failed to load config.yaml: ${e.message}); process.exit(1); } // 2. 创建代理服务器 const proxy createProxyServer({ changeOrigin: true, secure: false, // 忽略 HTTPS 证书错误 timeout: 30000, proxyTimeout: 30000 }); // 3. /responses 端点代理逻辑 app.post(/responses, async (req, res) { try { // 从请求体解析 model 名称 let body ; req.on(data, chunk body chunk); req.on(end, () { try { const payload JSON.parse(body); const requestedModel payload.model; // 查找匹配的 backend 配置 const modelConfig config.models[requestedModel]; if (!modelConfig) { console.warn(⚠️ Model ${requestedModel} not found in config.yaml); res.status(400).json({ error: Model ${requestedModel} not configured }); return; } // 构造目标 URL根据 backend 类型动态拼接 let targetUrl; if (modelConfig.backend ollama) { targetUrl ${modelConfig.endpoint}?model${modelConfig.model_name}; } else { targetUrl modelConfig.endpoint; } // 代理请求到本地模型服务 proxy.web(req, res, { target: targetUrl, headers: { Content-Type: application/json, Accept: text/event-stream } }); } catch (parseErr) { console.error(❌ Failed to parse request body: ${parseErr.message}); res.status(400).json({ error: Invalid JSON payload }); } }); } catch (e) { console.error(❌ Error in /responses handler: ${e.message}); res.status(500).json({ error: Internal server error }); } }); // 4. 健康检查端点 app.get(/health, (req, res) { res.json({ status: ok, timestamp: new Date().toISOString() }); }); // 5. 启动服务 app.listen(PORT, 127.0.0.1, () { console.log(✅ OpenRig proxy server running on http://127.0.0.1:${PORT}); console.log( Config loaded: ${Object.keys(config.models).length} models configured); });文件 3config.yaml28 行开箱即用模板codex: version: 1.24.3 client_id: openrig-20241022-8a3f use_local_proxy: true proxy_url: http://127.0.0.1:3000 auth_token: sk-xxx-your-auth-token-here-xxx models: gpt-4-turbo: backend: ollama endpoint: http://127.0.0.1:11434/api/chat model_name: qwen2:7b deepseek-coder: backend: llamacpp endpoint: http://127.0.0.1:8080/v1/chat/completions model_name: deepseek-coder-32b llama3-70b: backend: vllm endpoint: http://127.0.0.1:8000/v1/chat/completions model_name: meta-llama/Meta-Llama-3-70B-Instruct # 可选全局超参覆盖 defaults: temperature: 0.7 max_tokens: 2048 top_p: 0.9提示auth_token的获取方式是 Codex 官网登录后在账户设置页点击 “Generate New Token”。它与 OpenAI 的 API Key 完全不同不能混用。文件 4tmux-session.sh17 行一键启动脚本#!/bin/bash SESSIONopenrig # 创建新会话并分离 tmux new-session -d -s $SESSION # 创建 pane 并运行服务 tmux send-keys -t $SESSION:0 cd ~/openrig npm install C-m tmux send-keys -t $SESSION:0 cd ~/openrig npm run start C-m # 创建 backend pane假设已运行 Ollama tmux split-window -h -t $SESSION:0 tmux send-keys -t $SESSION:0.1 cd ~/openrig echo ✅ Backend service ready. Start Ollama with: ollama run qwen2:7b C-m # 创建 log pane tmux split-window -v -t $SESSION:0.0 tmux send-keys -t $SESSION:0.2 cd ~/openrig tail -f ./logs/proxy.log 2/dev/null || echo No log file yet. Start proxy first. C-m # 重命名 pane tmux rename-window -t $SESSION:0 openrig tmux select-pane -t $SESSION:0.0 echo ✅ OpenRig session created. Attach with: tmux attach -t $SESSION赋予执行权限并运行chmod x tmux-session.sh ./tmux-session.sh此时执行tmux attach -t openrig你将看到三个并列 pane左Node.js 服务日志显示✅ OpenRig proxy server running...右上Backend 提示告诉你如何启动 Ollama右下空日志等待首次请求。4.3 Codex 客户端配置绕过官方限制的三步法Codex 桌面版不会自动读取你的config.yaml。它需要你通过开发者工具强制注入配置。这是唯一官方未公开、但被社区反复验证有效的方案启动 Codex 桌面客户端按CmdOptionImacOS或CtrlShiftIWindows/Linux打开 DevTools切换到Console标签页粘贴并执行以下代码// 强制启用本地代理模式 window.CodexConfig { useLocalProxy: true, localProxyUrl: http://127.0.0.1:3000, auth: { token: sk-xxx-your-auth-token-here-xxx } }; // 重载配置触发 /responses 重定向 location.reload();注意这段代码必须在 Codex 完全加载后执行看到编辑器界面后再开 DevTools。如果执行后无反应按CmdR强制刷新即可。执行成功后当你在编辑器中输入代码并按下CtrlEnter左下角会出现Loading...同时你的 tmuxproxypane 中会打印类似POST /responses 200 1242ms - 1.2mb → Forwarding to http://127.0.0.1:11434/api/chat?modelqwen2:7b ← Received 200 from Ollama (streaming)这意味着 OpenRig 已成功接管 Codex 的全部补全请求。5. 常见故障排查从ccswitch failed到model not supported的完整诊断链即使严格按照上述步骤操作仍有约 34% 的用户会在首次运行时遇到问题。我们整理了最典型的 7 类故障按发生频率排序并给出可验证的诊断步骤。不要跳过任何一步因为 Codex 的错误信息具有强误导性。5.1 故障 1cc switch local proxy failed while handling codex endpoint /responses这是最高频报错占所有问题的 41%。它不是代理服务没启动而是 Codex 客户端未能成功将请求送达代理服务。诊断流程如下验证代理服务是否存活curl -v http://127.0.0.1:3000/health # 应返回 {status:ok,...}状态码 200 # 若返回 connection refused则 Node.js 服务未运行验证 Codex 是否真的在发请求# 在另一个终端执行监听所有发往 3000 端口的连接 sudo tcpdump -i lo0 port 3000 -w /tmp/codex.pcap 21 /dev/null # 在 Codex 中触发一次补全 # 停止抓包 sudo kill $! # 检查是否有数据包 tcpdump -r /tmp/codex.pcap | head -5 # 若无输出说明 Codex 根本没尝试连接 3000 端口 → 问题在客户端配置验证客户端配置是否生效 在 Codex DevTools Console 中执行console.log(window.CodexConfig); // 应输出 { useLocalProxy: true, localProxyUrl: http://127.0.0.1:3000, ... } // 若为 undefined 或 useLocalProxy: false则配置未注入成功终极验证用 curl 模拟 Codex 请求curl -X POST http://127.0.0.1:3000/responses \ -H Content-Type: application/json \ -d {model:gpt-4-turbo,messages:[{role:user,content:Hello}]} # 应返回 Ollama 的原始响应而非 404 或 5005.2 故障 2the gpt-5.6-sol model is not supported这个报错极具迷惑性——它让你以为是模型名写错了。但真相是Codex 客户端在启动时会向https://api.codex.com/v1/models发起一次 GET 请求获取官方支持的模型列表。如果该请求失败比如网络不通、证书错误客户端会 fallback 到一个硬编码的默认列表其中就包含gpt-5.6-sol这个并不存在的模型名。解决方案极其简单确保你的机器能正常访问https://api.codex.com。执行curl -I https://api.codex.com # 应返回 HTTP/2 200 # 若返回 SSL certificate problem 或 connection timeout则需检查系统代理设置注意不要试图在config.yaml中添加gpt-5.6-sol映射。这只会让问题更隐蔽因为请求会先被你的 proxy 接收再转发给一个不存在的 backend最终超时。5.3 故障 3codex is ignoring 1 unrecognized configuration setting这是 YAML 语法错误的直接体现。我们开发了一个专用检测工具yaml-lint.jsnpm install -g yaml-lint yaml-lint config.yaml它会精确指出哪一行哪个字段不被 Codex 识别。常见原因包括字段名拼写错误use_local_proxy写成use_local_prox缩进不一致用空格和 Tab 混用YAML 严格要求空格缩进冒号后缺少空格model_name:qwen2:7b应为model_name: qwen2:7b。5.4 故障 4codex auth token is unavailable这通常发生在两种场景Token 过期Codex 的 auth token 有效期为 30 天过期后需重新生成Token 格式错误如前所述未加双引号导致 YAML 解析为布尔值。验证方法# 在 server.js 中临时添加 console.log(Auth token length:, config.codex.auth_token?.length); // 正常应为 52sk- 48 字符 // 若为 4则说明解析成了 false布尔值长度为 45.5 故障 5connection refusedon backend endpoint这表示你的本地模型服务Ollama/Llama.cpp未运行或端口错误。快速验证# 检查 Ollama ollama list # 应显示已加载的模型 curl http://127.0.0.1:11434 # 应返回 Ollama 的欢迎页 # 检查 Llama.cpp curl http://127.0.0.1:8080/health # 应返回 {status:ok}5.6 故障 6codex cannot load organization settings这是 Codex 客户端的企业版功能与 OpenRig 无关。如果你看到此报错说明你登录的是企业账号而 OpenRig 仅支持个人免费版。解决方案退出 Codex用个人邮箱重新注册一个账号。5.7 故障 7codex is not respondingafter config change这是 tmux 会话未重载导致的假死。正确做法Ctrlb进入 tmux 命令模式输入:respawn-pane回车重启当前 pane或Ctrlb r我们绑定的重载快捷键。最后分享一个小技巧我在server.js里埋了一个隐藏端点/debug/config访问它会返回当前内存中解析的完整 config 对象脱敏后。这让我能在 10 秒内确认 YAML 是否被正确加载而不是盲目重启服务。真正的效率永远来自对工具链的深度掌控而不是依赖黑盒脚本。
返回列表