Skip to content

[Bug] tool_search 被单向重命名为 search_tools,导致 Codex 动态工具搜索及子代理不可用 #52

Description

@csascas

以下内容为ai排查,不保证完全正确

摘要

当 Codex 通过 CC Switch 使用 commandcode-proxy 时,tool_search 会被代理单向重命名为 search_tools。由于响应方向没有恢复原名,Codex 收到的是普通工具调用 function_call: search_tools,而不是内部的 tool_search_call,最终报错:

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 中存在单向别名:
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 应用于上游工具声明,但响应转换直接透传上游的 event.toolName,没有执行反向映射:
Chat 流式
Chat 非流式
Anthropic 流式
Anthropic 非流式
Responses 流式
Responses 非流式
结果是:
客户端 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

  1. jasper-khan commented on Sep 26, 2026

    @jasper-khan

    确实,在codex里不能调用子代理,我也遇到了这个问题。ccswitch+codex

  2. xelr233 commented on Oct 2, 2026

    @xelr233
    Contributor

    这是 #37 的同一个 bug(TOOL_NAME_ALIASES 单向别名,上行改了下行不改),你的排查链路(重命名点 → 六条响应路径没做反向映射 → Codex unsupported call)完全正确。

    建议直接看 xelr233/commandcode-proxy 的 master —— 这个问题在那里已经修掉了,不是「删掉 tool_search 一行」的临时规避,而是把整张别名表连 toWireToolName 一起删除,声明与消息全程原样透传(收尾提交 3cefa6b)。#37 里有完整的根因分析,这里只说结论:

    1. 对照 CLI 1.54.0,wire 协议里没有「工具重命名」这回事。 CLI 的 toWireToolName 只服务于一个特例:CLI 自己退役过 tool_search 这个名字(createRetiredToolSearchTool,visible:()=>false,从不进 params.tools),重放旧会话时历史里才会残留旧名,所以只改 messages、不改声明。那张四项表(bash_output/task_output/read_multiple_files…)是 resolveToolNameAlias 的执行侧别名 —— 模型喊了退役名本地按新名跑、回 Repair note、补 defaults —— 和 wire 无关。
    2. 反代没有这个前提。 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)。
    3. 你的「仅当下游声明了 tool_search 才启用反向映射」是对的洞察,但顺着 fix: toWireToolName 取错了别名表来源,且作用方向与 CLI 相反(messages 该改没改、tools 声明不该改却改了) #37 再走一步:连正向都不要改。一旦客户端恰好声明了名为 tool_search 的工具,「只保留一项别名」的方案就会复现同一个 bug。

    fork 上的状态:

    如果维护者想收这个修复,欢迎直接从 fork 的 master 取(或我按需拆成独立 PR)—— 相关讨论都在 #37。

  3. MAXeaglet commented on Oct 9, 2026

    @MAXeaglet
    Owner

    已随 PR #58 合入修复:此前误加的单向别名表已被彻底废除,工具名在 wire 层面全程原样透传,\ ool_search\ 保持原名下发,Codex 动态工具搜索与子代理已恢复正常,且已有自动化用例保护。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions