
1. 为什么 Codex 接第三方模型总在/v1/responses上翻车先说清楚这篇要解决什么。MoonBridge 是一个协议转换与模型路由代理对外暴露 OpenAI Responses API/v1/responses对内把请求翻译成 Anthropic Messages、Gemini、OpenAI Chat Completions 等上游格式。它适合谁适合手里已经有 Codex、想让 Codex 跑第三方模型但被/v1/responses和/v1/chat/completions协议差异卡住的开发者。核心检索词就三个MoonBridge、Responses API、Chat Completions API。我先把问题场景摆出来。Codex 新版本默认走/v1/responses请求体里带的是input数组、tools工具声明、instructions这类字段。而大量第三方模型服务只实现了/v1/chat/completions认的是messages数组、role/content结构。你直接把 Codex 的 Responses 请求打到只支持 Chat Completions 的服务上最常见的结局就是 400 或 404连模型都没开始推理。很多人第一反应是「Key 写错了」或者「模型名不对」。我试过排查一圈发现大部分时候这两样都没问题真正错位的是协议层。Codex 认为自己在跟一个 Responses 端点说话上游却只听得懂 Chat Completions中间没有翻译请求自然对不上。MoonBridge 的价值就在这一层它让 Codex 继续以为自己在调/v1/responses同时让后端模型不必原生支持 Responses。结构上就是 Codex → MoonBridgeResponses 入口→ 上游Chat Completions / Anthropic / Gemini。但这里有个必须提前说清的边界MoonBridge 解决的是「入口协议」和「模型路由」它不会自动抹平所有 agent 工具能力差异。Codex 不是普通聊天客户端它会带一整套工具比如 shell、update_plan、web_search、image_generation、view_image、MCP namespace tools。这些工具在 Responses API 里作为tools数组一起发出去MoonBridge 要转的不是一段 prompt而是一个带工具、带上下文、带调用约束的 agent 请求。这就是为什么「加一层代理就完事」的想法会落空。所以本篇的路线是先把 MoonBridge 的配置落到 TaoToken 统一通道上给出可复制的配置片段和 Base URL、鉴权字段改法再用一次真实请求验证两种 API 格式的转换结果最后看 Codex 调用到底成不成。全程给命令、给参数、给报错对照你可以跟着做。2. TaoToken 前置把 Base URL 和 Key 统一到一条通道在动 MoonBridge 之前先把上游通道准备好。这一步的目的是让 MoonBridge 有一个稳定的、OpenAI 兼容的上游可以转发而不是每换一个模型就改一次配置。我用 TaoToken 作为统一通道官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 。先解释为什么要有这一步。MoonBridge 本身是转换层它需要一个「上游 provider」来承接转换后的请求。如果你把上游直接指向某个只支持 Chat Completions 的第三方服务那 MoonBridge 的 provider 配置就得跟着那个服务的字段走换模型就换配置。把上游统一到 TaoToken 之后MoonBridge 只需要认一个 Base URL 和一把 Key模型切换在通道侧完成MoonBridge 的配置基本不用动。具体操作分三步。第一步拿到 API Key。进入控制台后创建密钥地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 创建完把 Key 复制出来形如sk-xxxx。第二步确认你要用的模型 ID。不同模型在通道里的 ID 不一样别凭记忆写去模型列表或文档里核对文档入口 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。第三步记住两个地址Base URL 用https://taotoken.net/api注意这里不加任何查询参数Key 放在Authorization: Bearer 你的Key头里。这里有个容易踩的坑Base URL 到底带不带/v1。很多 OpenAI 兼容客户端要求 Base URL 以/v1结尾然后自己拼/chat/completions。TaoToken 的 API 基址是https://taotoken.net/api具体拼接规则以文档为准。我的做法是先在文档里确认一次再写进配置避免出现/api/v1/v1/chat/completions这种双 v1 的路径。验证通道是否通最省事的办法是先用模型对话页面发一条消息地址 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。如果那边能正常返回说明 Key 和通道没问题接下来 MoonBridge 的报错就可以聚焦在协议转换上而不是怀疑鉴权。如果你后面打算长期用 Codex 跑编码任务或 agent 流程可以顺带了解 Coding Plan入口 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。它和本篇的接入不冲突属于通道侧的用量方案先知道有这么个东西即可。这一步做完你手里应该有三样东西一个可用的 Key、一个确认过的模型 ID、一个 Base URL。下面进 MoonBridge 配置。3. 可复制配置MoonBridge 的 JSON/TOML 片段与 Codex 三件套这一节是全文最需要照着抄的部分。MoonBridge 的配置核心是两件事对外暴露 Responses 入口对内声明上游 provider。下面给一份可直接改的配置骨架字段名以你本地 MoonBridge 版本为准路径和原文保持一致。先看 MoonBridge 侧的配置。假设它读取一个config.toml结构大致如下# MoonBridge 配置骨架 [server] host 127.0.0.1 port 8787 # 对外暴露 Responses API 入口 [api] responses_path /v1/responses chat_path /v1/chat/completions # 上游 provider统一指向 TaoToken 通道 [[providers]] name taotoken type openai-chat # 上游按 Chat Completions 协议对接 base_url https://taotoken.net/api api_key sk-你的Key model 你的模型ID # 模型别名路由Codex 请求的模型名映射到上游模型 [router] default_provider taotoken如果你用的是 JSON 配置等价片段是这样{ server: { host: 127.0.0.1, port: 8787 }, api: { responses_path: /v1/responses, chat_path: /v1/chat/completions }, providers: [ { name: taotoken, type: openai-chat, base_url: https://taotoken.net/api, api_key: sk-你的Key, model: 你的模型ID } ], router: { default_provider: taotoken } }关键点解释一下。type openai-chat告诉 MoonBridge上游说的是 Chat Completions你收到 Responses 请求后要往这个方向转。base_url填 TaoToken 的 API 基址api_key填你上一步拿到的 Keymodel填核对过的模型 ID。responses_path是 Codex 会打过来的入口chat_path是给普通 Chat Completions 客户端用的入口两个都留着方便对照测试。然后是 Codex 侧的三件套。Codex 的配置通常落在~/.codex/config.toml或项目级配置里核心是 Base URL、Key、Model ID 三项对齐# Codex 配置指向 MoonBridge 的 Responses 入口 model 你的模型ID model_provider moonbridge [model_providers.moonbridge] name moonbridge base_url http://127.0.0.1:8787/v1 env_key MOONBRIDGE_API_KEY wire_api responses这里wire_api responses是重点它让 Codex 用 Responses 协议发请求正好打到 MoonBridge 的/v1/responses。base_url指向本地 MoonBridge注意结尾的/v1和 MoonBridge 的responses_path拼起来要正好是/v1/responses。env_key指定从环境变量读 Key启动前导出export MOONBRIDGE_API_KEYsk-你的Key如果你用的是 Cline MCP 或 Claude Code 这类客户端三件套的写法逻辑一样Base URL 指向 MoonBridge 的入口Key 用环境变量注入Model ID 和 MoonBridge 的router对齐。CC Switch 场景下也是同样三件套别只改 Key 不改 Base URL那样请求还是会打到旧端点。配置写完先别急着接 Codex。用 curl 直接打 MoonBridge 的 Responses 入口确认转换层本身是活的curl -s http://127.0.0.1:8787/v1/responses \ -H Authorization: Bearer $MOONBRIDGE_API_KEY \ -H Content-Type: application/json \ -d { model: 你的模型ID, input: [ { role: user, content: 用一句话说明什么是协议转换 } ] }如果这条能返回内容说明 MoonBridge 已经把 Responses 请求转成 Chat Completions 打到 TaoToken 并拿回了结果。下一节我们看返回结构长什么样以及 Codex 真正调用时会发生什么。4. 验证请求Responses 与 Chat Completions 的转换结果对照这一节用一次真实请求把两种 API 格式的转换结果摊开看。先明确预期你发出去的是 Responses 格式MoonBridge 转成 Chat Completions 发给上游上游返回 Chat Completions 格式MoonBridge 再转回 Responses 格式给你。中间任何一步字段对不上都会在返回里露馅。先看 Responses 请求长什么样。Codex 发出的典型结构包含model、input消息数组、tools工具声明、instructions系统指令。其中input里的消息用role和contentcontent可能是字符串也可能是分块数组。tools里每个工具有type、name、parameters等字段。MoonBridge 要做的转换是把input映射成 Chat Completions 的messages把instructions映射成system消息把tools映射成 Chat Completions 的tools数组type: functionfunction.namefunction.parameters。返回方向上把 Chat Completions 的choices[0].message.content映射回 Responses 的output结构。用上一节的 curl 发一条纯文本请求正常返回大致是这样字段名以实际为准{ id: resp_xxx, object: response, model: 你的模型ID, output: [ { type: message, role: assistant, content: [ { type: output_text, text: 协议转换就是把一种接口格式翻译成另一种。 } ] } ], usage: { input_tokens: 18, output_tokens: 22 } }看到output数组里有output_text说明文本链路通了。这一步只验证了「消息文本」的转换还没碰工具。接下来验证工具转换。发一条带tools的 Responses 请求curl -s http://127.0.0.1:8787/v1/responses \ -H Authorization: Bearer $MOONBRIDGE_API_KEY \ -H Content-Type: application/json \ -d { model: 你的模型ID, input: [ { role: user, content: 现在几点如果需要工具就调用。 } ], tools: [ { type: function, name: get_time, description: 获取当前时间, parameters: { type: object, properties: {}, required: [] } } ] }如果上游模型支持 function calling返回里会出现工具调用意图MoonBridge 需要把它转回 Responses 的function_call类型。这里就是最容易出问题的地方Responses 的内建工具比如image_generation、web_search没有name字段而 Chat Completions 的 function 工具必须有name。MoonBridge 如果把这些内建工具当普通函数转发上游收到的function.name就是空字符串严格校验的服务会直接拒绝。对照一下两种格式的工具字段差异维度Responses APIChat Completions API消息字段inputmessages系统指令instructionssystem角色消息工具类型type: function及内建类型type: function工具名位置顶层namefunction.name返回结构output数组choices[].message这张表就是 MoonBridge 转换逻辑的对照清单。文本消息能对上不代表工具能对上。验证时一定要分两步先纯文本再带工具。纯文本通了只说明「桥能过人」带工具通了才说明「桥能过车」。最后看 Codex 调用。把 Codex 配置指向 MoonBridge 后跑一个简单任务观察 Codex 的日志里请求是否打到/v1/responses以及返回是否被正确解析。如果 Codex 报reading choices之类的解析错误说明返回结构没转回 Responses 格式MoonBridge 的返回映射有问题。5. 常见报错排查401、local proxy failed、reading choices、OAuth这一节按真实报错来对。你大概率会碰到下面几类逐个说清原因和改法。第一类401 Unauthorized。这个最直接Key 不对或没带上。检查三处MoonBridge 配置里的api_key是不是 TaoToken 的 KeyCodex 侧env_key指定的环境变量有没有导出curl 测试时Authorization头有没有写。特别注意别把 MoonBridge 自己的入口 Key 和上游 TaoToken 的 Key 搞混前者是 Codex 访问 MoonBridge 用的后者是 MoonBridge 访问上游用的两个可以相同也可以不同但都要对。第二类local proxy failed。这个通常出现在 Codex 连本地 MoonBridge 时。原因一般是 MoonBridge 没启动、端口不对、或者base_url拼错。先确认 MoonBridge 进程在跑curl http://127.0.0.1:8787/v1/responses能不能通。再看 Codex 的base_url是不是http://127.0.0.1:8787/v1端口和 MoonBridge 的server.port一致。如果 MoonBridge 监听的是0.0.0.0而你写localhost一般也能通但写错 IP 就会 failed。第三类reading choices 相关报错。这个说明 Codex 收到了返回但返回结构不是它期望的 Responses 格式它在里面找choices找不到。根因是 MoonBridge 的返回映射没做对把上游的 Chat Completions 原始返回直接透传了没有转成 Responses 的output结构。改法是检查 MoonBridge 的响应转换逻辑确认choices[0].message.content被映射到了output[].content[].text。这类问题在纯文本请求里可能不明显一带工具就暴露。第四类OAuth 相关报错。如果你用的是 Claude Code 或某些走 OAuth 的客户端报错可能提示鉴权方式不匹配。这类客户端默认走 OAuth 流程而 MoonBridge 入口期望的是 Bearer Key。改法是把客户端的鉴权方式切到 API Key 模式Base URL 指向 MoonBridgeKey 用环境变量注入。Claude Code 接入时Base URL、Key、Model ID 三件套要写全缺一个都会在鉴权或路由阶段失败。再补一个高频坑模型 ID 不匹配。Codex 请求里写的model和 MoonBridgerouter里映射的上游模型不一致时有的上游会直接报模型不存在。核对方法是把 Codex 配置里的model、MoonBridge 的router映射、TaoToken 文档里的模型 ID 三处对齐。排查顺序建议固定下来先 curl 打 MoonBridge 纯文本通了再带工具再上 Codex。每一步只引入一个新变量报错就能定位到具体层。别一上来就 Codex 全链路跑那样 401、转换错误、解析错误会混在一起很难分清。6. 把 Codex 接到 TaoToken 统一通道的完整路径到这里链路已经清楚了Codex 用 Responses 协议打 MoonBridgeMoonBridge 转成 Chat Completions 打 TaoToken 统一通道返回再转回 Responses 给 Codex。你要复现的话按这个顺序走一遍。先在 TaoToken 侧准备好 Key 和模型 ID控制台创建密钥的入口是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 模型 ID 在文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里核对。API 基址用 https://taotoken.net/api Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。然后在 MoonBridge 配置里把 provider 指向 TaoTokentype设为openai-chatbase_url填https://taotoken.net/apiapi_key和model填上一步的值。启动 MoonBridge用 curl 验证纯文本和带工具两条请求。最后把 Codex 的base_url指向 MoonBridge 的/v1wire_api设为responsesKey 用环境变量注入Model ID 和 MoonBridge 的 router 对齐。跑一个简单任务看日志确认请求路径和返回解析。如果你更想先直观感受模型返回可以直接用模型对话页面发消息入口 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。长期跑编码或 agent 任务的话Coding Plan 入口 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 可以了解下。最后留一句实测体会MoonBridge 能做桥但桥上的每一种工具都需要明确的通行规则。文本消息通了只是第一步带工具的 agent 请求才是真正的考验。把纯文本和带工具分开验证报错就不会混在一起。