摘要 / Summary
Codex 桌面版内置插件 codex-app-tools 的工具 codex_app__automation_update 携带 parameters.anyOf[1].oneOf(分支不可证明互斥)的工具 schema。当路由目标为 DeepSeek 这类"严格 OpenAI 兼容"上游、且协议探测选用了 Moonshot MFJS 工具 schema 方言时,MFJS 编译器对整个请求 fail-closed,返回 422——一个默认插件的单个工具 schema 形态会杀死经代理的所有工具会话,与 v3.18 修复的 parameters: null 属同一类问题(本轮为 Codex 新版把该工具升级为真实 schema 后复发)。
English: One Codex built-in tool (codex_app__automation_update) carries a non-disjoint anyOf/oneOf parameter schema. Under the Moonshot MFJS tool-schema dialect chosen by protocol probing for strict upstreams like DeepSeek, the MFJS compiler fails the entire /responses request with 422. This is the same class as the v3.18 parameters: null fix (#4706/#5315), now recurring because a newer Codex build gives the tool a real schema. Suggest fail-open-with-event for unrepresentable tool schemas, plus a probe recheck (below).
版本 / Version
- CCSwitchMulti: v3.19.2-26(Windows Portable)
- Codex 桌面版: 26.901.x(内置插件
codex-app-tools@openai-bundled 0.1.3)
- 操作系统: Windows
- 涉及应用: Codex
重现步骤 / Steps to Reproduce
- MultiRouter 路由(如 "Codex GPT + DeepSeek")目标供应商选 DeepSeek,模型
deepseek-v4-flash-vision-exp;进行过协议探测,探测档案记录的工具 schema 方言为 Moonshot MFJS(或该卡协议探测后自动采用 MFJS)。
- Codex 桌面版启用内置插件
codex-app-tools@openai-bundled(默认启用),注册 MCP 服务 codex_app(工具:automation_update、create_thread、send_message_to_thread、fork_thread、handoff_thread)。
- 任意会话发一条消息(请求会自动携带该工具列表)。
实际结果:请求重连 5/5 后返回:
unexpected status 422 Unprocessable Entity: CC Switch local proxy failed while handling
Codex endpoint /responses. Provider: Deepseek; model: deepseek-v4-flash-vision-exp;
cause: 请求/响应转换错误: tool `automation_update` schema at
`$.tools[9].tools[0].parameters.anyOf[1].oneOf` is incompatible with Moonshot MFJS:
oneOf can only be represented as MFJS anyOf when every branch is provably disjoint
期望行为:单个工具 schema 无法安全表示时,不应让整个请求 422。应降级/豁免该工具(如非互斥 oneOf → anyOf + 该工具 strict: false),并记录结构化事件;会话应当照常可用。
根因 / Root Cause
codex_tool_schema.rs 的 MfjsCompiler::compile_object:oneOf 分支若不能证明两两互斥,直接 Err(fail-closed),错误穿透到整个请求。
- 该工具 schema 来源是 Codex 侧(openai-bundled
codex-app-tools/desktop-mcp.json),其形态不受 CCSM 控制;Codex 桌面版更新即可改变形态(v3.18 时该工具还是 parameters: null,本轮已是真实 anyOf + oneOf,因此同类问题会随 Codex 更新复发)。
建议 1:fail-open + 事件(治标且可发布)
与现有"down-level 注释/格式标注则置 strict: false 保留工具"的哲学一致(codex_tool_schema.rs 已有先例,见 annotations 注释),建议把"不可证明互斥的 oneOf"同样降级为 anyOf + relaxed(工具 strict: false)而不是报错;空 oneOf 仍可保留 fail-closed。改动很小:
// codex_tool_schema.rs: compile_object() 内 oneOf 分支
if !one_of_branches_are_pairwise_disjoint(branches) {
self.relaxed = true; // 降级为 anyOf + strict:false,替换原 Err
}
格式更激进的“整请求 fail”可改为:记录 redacted 事件(参考已有的 hosted_tool_not_called / dropped-tool 结构化日志),仅豁免该工具,不让 422 打到 Codex。
(我本地已按此改过并验证思路:DeepSeek 路由切手动覆盖 + 工具 Schema=OpenAI JSON Schema 直传可用,说明 MFJS 收紧并非 DeepSeek 必需。)
建议 2:回查探测为何对 DeepSeek 选 MFJS(可能的根治)
protocol_compatibility/runner.rs should_retry_with_moonshot 的触发条件之一是"forced tool 调用未返回有效结果"(模型未调用),而不只是"上游拒绝 schema"(400/422)。DeepSeek 已实证接受原始 OpenAI schema(手动 openai 直传正常),MFJS 很可能是当初探测时模型未返回工具调用被误升的。建议:
- 仅当上游明确拒绝 schema(400/422)时才升级到 Moonshot MFJS;
- 模型未调用强制工具时重试但不切换方言。
若此点修正,DeepSeek 等严格上游可能根本不需要 MFJS 编译器,该类 422 自然消失。
临时 Workaround(已在生产验证)
目标供应商卡 → 高级选项 →「协议配置方式」= 手动覆盖(高级) →「工具 Schema」= OpenAI JSON Schema,保存后重启 Codex。注意:仅当协议配置方式为手动且「最终使用协议」与探测 transport 一致时才写入 meta(codexProtocolSettings.ts buildCodexProtocolMeta 的 !matchesProbeTransport 会静默不写入);表单默认显示的 "OpenAI JSON Schema" 是读取缺省回退值,不代表已生效。
摘要 / Summary
Codex 桌面版内置插件
codex-app-tools的工具codex_app__automation_update携带parameters.anyOf[1].oneOf(分支不可证明互斥)的工具 schema。当路由目标为 DeepSeek 这类"严格 OpenAI 兼容"上游、且协议探测选用了 Moonshot MFJS 工具 schema 方言时,MFJS 编译器对整个请求 fail-closed,返回 422——一个默认插件的单个工具 schema 形态会杀死经代理的所有工具会话,与 v3.18 修复的parameters: null属同一类问题(本轮为 Codex 新版把该工具升级为真实 schema 后复发)。English: One Codex built-in tool (
codex_app__automation_update) carries a non-disjointanyOf/oneOfparameter schema. Under the Moonshot MFJS tool-schema dialect chosen by protocol probing for strict upstreams like DeepSeek, the MFJS compiler fails the entire/responsesrequest with 422. This is the same class as the v3.18parameters: nullfix (#4706/#5315), now recurring because a newer Codex build gives the tool a real schema. Suggest fail-open-with-event for unrepresentable tool schemas, plus a probe recheck (below).版本 / Version
codex-app-tools@openai-bundled0.1.3)重现步骤 / Steps to Reproduce
deepseek-v4-flash-vision-exp;进行过协议探测,探测档案记录的工具 schema 方言为 Moonshot MFJS(或该卡协议探测后自动采用 MFJS)。codex-app-tools@openai-bundled(默认启用),注册 MCP 服务codex_app(工具:automation_update、create_thread、send_message_to_thread、fork_thread、handoff_thread)。实际结果:请求重连 5/5 后返回:
期望行为:单个工具 schema 无法安全表示时,不应让整个请求 422。应降级/豁免该工具(如非互斥
oneOf→anyOf+ 该工具strict: false),并记录结构化事件;会话应当照常可用。根因 / Root Cause
codex_tool_schema.rs的MfjsCompiler::compile_object:oneOf分支若不能证明两两互斥,直接Err(fail-closed),错误穿透到整个请求。codex-app-tools/desktop-mcp.json),其形态不受 CCSM 控制;Codex 桌面版更新即可改变形态(v3.18 时该工具还是parameters: null,本轮已是真实anyOf + oneOf,因此同类问题会随 Codex 更新复发)。建议 1:fail-open + 事件(治标且可发布)
与现有"down-level 注释/格式标注则置
strict: false保留工具"的哲学一致(codex_tool_schema.rs已有先例,见 annotations 注释),建议把"不可证明互斥的 oneOf"同样降级为anyOf+relaxed(工具strict: false)而不是报错;空oneOf仍可保留 fail-closed。改动很小:格式更激进的“整请求 fail”可改为:记录 redacted 事件(参考已有的
hosted_tool_not_called/ dropped-tool 结构化日志),仅豁免该工具,不让 422 打到 Codex。(我本地已按此改过并验证思路:DeepSeek 路由切手动覆盖 + 工具 Schema=OpenAI JSON Schema 直传可用,说明 MFJS 收紧并非 DeepSeek 必需。)
建议 2:回查探测为何对 DeepSeek 选 MFJS(可能的根治)
protocol_compatibility/runner.rsshould_retry_with_moonshot的触发条件之一是"forced tool 调用未返回有效结果"(模型未调用),而不只是"上游拒绝 schema"(400/422)。DeepSeek 已实证接受原始 OpenAI schema(手动openai直传正常),MFJS 很可能是当初探测时模型未返回工具调用被误升的。建议:若此点修正,DeepSeek 等严格上游可能根本不需要 MFJS 编译器,该类 422 自然消失。
临时 Workaround(已在生产验证)
目标供应商卡 → 高级选项 →「协议配置方式」= 手动覆盖(高级) →「工具 Schema」= OpenAI JSON Schema,保存后重启 Codex。注意:仅当协议配置方式为手动且「最终使用协议」与探测 transport 一致时才写入 meta(
codexProtocolSettings.tsbuildCodexProtocolMeta的!matchesProbeTransport会静默不写入);表单默认显示的 "OpenAI JSON Schema" 是读取缺省回退值,不代表已生效。