## 以下内容为ai排查,不保证完全正确 ## 摘要 当 Codex 通过 CC Switch 使用 commandcode-proxy 时,`tool_search` 会被代理单向重命名为 `search_tools`。由于响应方向没有恢复原名,Codex 收到的是普通工具调用 `function_call: search_tools`,而不是内部的 `tool_search_call`,最终报错: ```text unsupported call: search_tools ``` 因此 multi_agent_v1 无法加载,spawn_agent、wait_agent、close_agent 等子代理工具全部不可用。 ## 环境 commandcode-proxy:master 远端版本:cce214d1db9d15c36ea1b59c1b0fb996834d323a Codex CLI:0.148.0-alpha.9 Codex Desktop:26.810.52044 Codex wire_api:responses CC Switch provider API Format:openai_chat 代理链路:Codex → CC Switch → commandcode-proxy → Command Code 复现模型:deepseek/deepseek-v4.1-flash ## 复现步骤 启动 commandcode-proxy。 将 Codex 的 base_url 指向 CC Switch 的本地代理。 在 Codex 中发起需要动态工具搜索的请求,例如: 请创建一个子代理,只回复 OK。 观察 Codex 会话事件。 ## 实际行为 代理向 Codex 返回: { "type": "function_call", "name": "search_tools", "arguments": "{\"query\":\"subagent spawn tools\"}" } 随后工具执行结果为: unsupported call: search_tools 会话中没有 tool_search_call,也不会出现 multi_agent_v1 或 spawn_agent。 ## 预期行为 Codex 应该收到 tool_search,并将其转换为内部动态工具搜索事件。 加载 multi_agent_v1 后,应依次观察到: tool_search_call tool_search_output multi_agent_v1 spawn_agent ## 根本原因 [proxy.mjs#L686-L692](https://github.com/MAXeaglet/commandcode-proxy/blob/cce214d1db9d15c36ea1b59c1b0fb996834d323a/proxy.mjs#L686-L692) 中存在单向别名: const TOOL_NAME_ALIASES = { bash_output: 'shell_output', task_output: 'shell_output', tool_search: 'search_tools', read_multiple_files: 'read_file', }; function toWireToolName(name) { return TOOL_NAME_ALIASES[name] || name; } 该别名会在 [proxy.mjs#L662](https://github.com/MAXeaglet/commandcode-proxy/blob/cce214d1db9d15c36ea1b59c1b0fb996834d323a/proxy.mjs#L662) 应用于上游工具声明,但响应转换直接透传上游的 event.toolName,没有执行反向映射: [Chat 流式](https://github.com/MAXeaglet/commandcode-proxy/blob/cce214d1db9d15c36ea1b59c1b0fb996834d323a/proxy.mjs#L767) [Chat 非流式](https://github.com/MAXeaglet/commandcode-proxy/blob/cce214d1db9d15c36ea1b59c1b0fb996834d323a/proxy.mjs#L1590) [Anthropic 流式](https://github.com/MAXeaglet/commandcode-proxy/blob/cce214d1db9d15c36ea1b59c1b0fb996834d323a/proxy.mjs#L2109) [Anthropic 非流式](https://github.com/MAXeaglet/commandcode-proxy/blob/cce214d1db9d15c36ea1b59c1b0fb996834d323a/proxy.mjs#L2459) [Responses 流式](https://github.com/MAXeaglet/commandcode-proxy/blob/cce214d1db9d15c36ea1b59c1b0fb996834d323a/proxy.mjs#L2969) [Responses 非流式](https://github.com/MAXeaglet/commandcode-proxy/blob/cce214d1db9d15c36ea1b59c1b0fb996834d323a/proxy.mjs#L3251) 结果是: 客户端 tool_search → 代理上游 search_tools → 模型返回 search_tools → 代理原样返回 search_tools → Codex 无法识别并报 unsupported call ## 建议修复 建议不要简单删除该别名,因为 tool_search -> search_tools 可能用于兼容 Command Code CLI 或其他客户端。 推荐实现请求级、可逆的工具名映射: 保留上游所需的 search_tools 别名。 为每个请求建立反向映射,例如 search_tools -> tool_search。 将反向映射应用到所有流式和非流式响应转换路径。 出站时同时映射工具声明、tool_choice、历史 tool_calls 和 tool-result 名称。 仅在请求确实声明了 tool_search 时启用反向映射,避免误伤用户自定义的 search_tools 工具。 不能简单对完整别名表执行全局反转,因为 bash_output 和 task_output 都映射到 shell_output,反转会产生歧义。 ## 验收标准 Codex 请求动态工具搜索时产生 tool_search_call。 tool_search_output 成功包含 multi_agent_v1。 spawn_agent 可以成功创建子代理。 Chat Completions、Anthropic Messages、Responses API 均能正确处理。 上述协议的流式与非流式模式均有测试覆盖。 bash_output、task_output、read_multiple_files 的现有别名行为不回归。 ## 临时规避方案 删除 tool_search: 'search_tools' 这一条别名并重启代理后,Codex 可以恢复正常,子代理已实测可用。 该方案适合只服务 Codex 的部署,但没有保留代理原本的上游兼容行为,因此长期修复仍建议使用可逆映射。
Activity
jasper-khan commentedon Sep 26, 2026
确实,在codex里不能调用子代理,我也遇到了这个问题。ccswitch+codex
xelr233 commentedon Oct 2, 2026
这是 #37 的同一个 bug(
TOOL_NAME_ALIASES单向别名,上行改了下行不改),你的排查链路(重命名点 → 六条响应路径没做反向映射 → Codexunsupported call)完全正确。建议直接看 xelr233/commandcode-proxy 的 master —— 这个问题在那里已经修掉了,不是「删掉
tool_search一行」的临时规避,而是把整张别名表连toWireToolName一起删除,声明与消息全程原样透传(收尾提交3cefa6b)。#37 里有完整的根因分析,这里只说结论:toWireToolName只服务于一个特例:CLI 自己退役过tool_search这个名字(createRetiredToolSearchTool,visible:()=>false,从不进params.tools),重放旧会话时历史里才会残留旧名,所以只改messages、不改声明。那张四项表(bash_output/task_output/read_multiple_files…)是resolveToolNameAlias的执行侧别名 —— 模型喊了退役名本地按新名跑、回 Repair note、补 defaults —— 和 wire 无关。params.tools来自下游客户端,proxy 没有 catalog、没有退役名,请求里每个名字都是「当前名」。强行的「上行改名 + 下行反向映射」是能自洽,但完全没必要 —— 只要两端都不改名,声明与消息天然一致,BUG: d063b47中toWireToolName会重写工具名传给上游,但是回传给下游的时候不会改回原名,而且cli也不会强制改名 #36/fix: toWireToolName 取错了别名表来源,且作用方向与 CLI 相反(messages 该改没改、tools 声明不该改却改了) #37/[Bug] tool_search 被单向重命名为 search_tools,导致 Codex 动态工具搜索及子代理不可用 #52 的症状一并消失,也不存在你提到的「全局反转有歧义」的问题(bash_output/task_output都映射到shell_output)。tool_search才启用反向映射」是对的洞察,但顺着 fix: toWireToolName 取错了别名表来源,且作用方向与 CLI 相反(messages 该改没改、tools 声明不该改却改了) #37 再走一步:连正向都不要改。一旦客户端恰好声明了名为tool_search的工具,「只保留一项别名」的方案就会复现同一个 bug。fork 上的状态:
test/wire.test.mjs有专门一组 wire 契约测试:「声明不改名 /tool_search在 tool-call 里也不被重写 / tool-result 与 tool-call 名字一致」,node 22 / node 24 / bun 三矩阵全绿;bash_output/task_output/read_multiple_files那三项同样会让「下游声明的名字」和「模型回传的名字」对不上(BUG: d063b47中toWireToolName会重写工具名传给上游,但是回传给下游的时候不会改回原名,而且cli也不会强制改名 #36 报的就是它),只删tool_search一行修不干净。如果维护者想收这个修复,欢迎直接从 fork 的 master 取(或我按需拆成独立 PR)—— 相关讨论都在 #37。
MAXeaglet commentedon Oct 9, 2026
已随 PR #58 合入修复:此前误加的单向别名表已被彻底废除,工具名在 wire 层面全程原样透传,\ ool_search\ 保持原名下发,Codex 动态工具搜索与子代理已恢复正常,且已有自动化用例保护。