Repository navigation
fix: toWireToolName 取错了别名表来源,且作用方向与 CLI 相反(messages 该改没改、tools 声明不该改却改了) #37
Description
Activity
- added a commit that references this issue
on Sep 14, 2026 结论:这张别名表应该整体删掉,
toWireToolName改回恒等补充 #37 里没拿到的那块拼图 ——
tool_search是 CLI 自己退役的名字,而且不可见:function createSearchToolsTool(e){ return { ..., schema: { name: nw, // nw = "search_tools",当前名 label: "SEARCH TOOLS", ... } } } function createRetiredToolSearchTool(e){ const t = createSearchToolsTool(e); return { ...t, visible: () => false, // ← 不进面向模型的 catalog schema: { ...t.schema, name: rw, // rw = "tool_search",退役名 description: \`Retired: use \${nw} instead. Calls to the \${rw} NAME are routed to \${nw} by the runner automatically. ...\` } } }
它确实从不上网:
function createToolRunner(e){ const t = e.tools ?? gg; const n = createSearchToolsTool(...); // search_tools const r = createRetiredToolSearchTool(...); // tool_search(visible:false) const o = () => [...t, n, r]; // allTools:只为 runner 按名查找 const s = createToolCatalog({ tools: o, ... }); } const o = t => { const n = dedupeTools(e.tools()); const r = t.includeHidden ? n : n.filter(isVisible); ... }; function isVisible(e){ return e.visible?.() !== false }
调用方那边,发请求用的那次
getSchemas不带includeHidden,只有本地查 schema 才开:const T = r.toolRunner.getSchemas({ mode: e }); // → 声明给上游 const M = t => (P ??= r.toolRunner.getSchemas({ mode: e, includeHidden: true })) .find(e => e.name === t); // → 纯本地查找
所以
params.tools里永远没有tool_search。四个函数排在一起就完全自洽了:函数 对名字做什么 为什么 toWireTools原样 {name,description,input_schema}声明列表里只有当前名,无需归一化 toWireMessagestool-call与tool-result都用toWireToolName,map 存改后的名字重放旧会话时历史里可能有退役名 resolveToolNameAlias(ow表)改的是执行目标,回一句给模型看的 Repair note,还补defaults模型喊了退役名 → 本地按新名字跑 toWireToolName不是「工具重命名设施」,而是「把自家 catalog 里那一个退役名字的历史归一化」。 它成立的前提是:CLI 自己退役过工具名,而且可能在重放用旧名字录下来的会话。
proxy 没有这个前提
params.tools来自下游客户端,proxy 没有 catalog、没有退役名;- 请求里出现的每个名字,对 proxy 来说都是「当前名」——没有东西需要归一化;
- 如果客户端真声明了一个叫
tool_search的工具,把它改掉是纯粹的错。
而且现在的改法(只改声明、不改消息)连内部自洽都做不到:CLI 是「声明用当前名 + 消息也用当前名」,现在是「声明改名 + 消息不改名」,两头都不对。
建议改法
① 默认(推荐):删掉整张表。
// 删除 TOOL_NAME_ALIASES / WIRE_TOOL_ALIASES 与 toWireToolName,三处调用点退回原名 body.params.tools = (tools || []).map(t => ({ name: t.function?.name || t.name || '', // 原样 description: ..., input_schema: ..., })); toolNameMap[tc.id] = tc.function?.name || ''; // 原样 parts.push({ type:'tool-call', toolCallId: tc.id, toolName: tc.function?.name || '', input: ... }); toolName: toolNameMap[msg.tool_call_id] || msg.name || '' // 原样
声明与消息天然一致,#36 的症状直接消失,也不需要任何「改回去」的对称逻辑。
⚠️ 只保留一项别名(tool_search→search_tools)并不能解决问题:一旦下游客户端恰好声明了一个名为tool_search的工具,就会复现同一个 bug —— 声明是tool_search、消息是search_tools。要么两端都不改,要么两端一起改;而「两端一起改」会把客户端自己的 API 面改写掉,所以只剩「都不改」。② 若确实要支持「重放真实 CLI 旧会话」:那也不该是出站改写,而应是入站归一化 + 显式开关(只有下游本身就是 CC CLI 形态的 harness 时才开),并且连
defaults一起补,与resolveToolNameAlias同语义。这样声明与消息仍然一致。唯独不要做成「上行改、下行不改」。
关于 #36
症状描述是对的(声明与消息不一致 → 下游派发不了),但有两处需要更正:
- 「cli 也不会强制改名」—— 对
bash_output成立,推广不成立。CLI 确实会改tool_search,只是只改这一项、只在messages里改; - 「本质是兼容旧工具名」—— 比这更彻底:那个旧名字 CLI 自己都不声明(
visible:()=>false),纯粹是重放历史用的。
至于「在原工具名上加几个随机字符(
bash_output-cmdc-m)」这个提议,方向是反的:- 下游必须能认出回传的工具名才能派发,加后缀直接破坏契约;
- 工具名是客户端自己的 API 面,不是指纹维度。真实 CLI 每个用户发出的
params.tools名字完全一样(shell_output/read_file/edit_file…),「多账号工具名相同」本来就是常态,加随机后缀反而制造了一个所有真实 CLI 都没有的偏差; - CLI 的
toWireTools是原样下发。
复验
curl -sSL "$(curl -sS https://registry.npmjs.org/command-code/latest \ | grep -o 'https://registry.npmjs.org/command-code/-/command-code-[^"]*\.tgz' | head -1)" \ | tar xzO package/dist/cli.mjs > cli.mjs grep -o 'function toWireToolName([^}]*}' cli.mjs # e===rw?nw:e grep -o 'function createRetiredToolSearchTool([^}]*}}' cli.mjs # visible:()=>false grep -o 'function toWireTools([^}]*}' cli.mjs # 无别名
(我这边已按 ① 改完并在真机 mock 上验证:声明与消息现在都原样透传;新增 4 条 wire 契约断言覆盖「声明不改名 /
tool_search在消息里也不改名 / tool-result 与 tool-call 名字一致」。)- added a commit that references this issue
on Sep 14, 2026 我提议的增加随机字符,主要是为了方便回传。最初我只是简单地在回传逻辑中增加了一张反向映射表,但很快发现,如果直接进行新旧值映射,下游传入新值时,也可能被错误地映射回旧值。
因此,还需要额外增加一个字段,用于标识下游传入的是否为新的工具名;如果是新工具名,则不再进行映射。这样一来,整体实现的代码量就会增加不少,不如直接自定义一个随机的下游不可能传的后缀。当然我也同意直接删去映射,直接请求。
特征方面近乎无解,最好是让下游别用这几个工具名:
- 上游可以检测到调用参数不一致,而且旧工具还会有提示词注入,这都和cli表现不一致
- 加后缀或者修改成随机字符也是明显特征
已随 PR #58 合入修复:\ oWireToolName\ 单向别名表已被彻底废除,所有工具名在 wire 层面全程原样透传,声明与消息保持同名一致。
结论
d063b47引入的TOOL_NAME_ALIASES重命名表取错了来源,而且作用方向也反了。对照command-code@1.54.0的dist/cli.mjs(未混淆,可直读):resolveToolNameAlias,那是客户端修复模型调用旧工具名的逻辑,还带defaults语义 —— 与 wire 无关;messages里重命名,不在tools声明里;当前的实现正好反过来。(
#36报的下游症状是同一个 bug 的另一面,这里补上根因。)证据:CLI 1.54.0 的四处源码
① 线上重命名只有一项
② 那张四项表是入站别名,不是出站重命名
它的消费者是
resolveToolNameAlias:调用点在工具执行器里(参数是
{toolName,input,signal,onUpdate,toolCallId,permissionMode}),用途是「模型调了退役名 → 本地按新名字跑,并回一句 Repair note 让模型下次改口」。两条与 wire 无关的铁证:note—— 一段给模型看的自然语言;defaults(task_output会补wait:"exit")—— 补默认参数是执行语义,不是重命名。③
toWireTools完全不做重命名④
toWireMessages里 tool-call 与 tool-result 都用别名,且缓存的是别名后的名字当前实现的三处偏差
proxy.mjs:TOOL_NAME_ALIASEStool_search→search_tools)params.tools[].nametoWireTools原样)toolNameMap[tc.id]tool-call.toolNametool-result.toolName也就是说:该改名的地方(messages)没改,不该改的地方(tools 声明)改了。
影响
下游 OpenAI 客户端声明的是
bash_output,wire 上params.tools变成shell_output,而模型如果调用了它,回流的tool-call.toolName又是shell_output—— 客户端按自己声明的bash_output找不到对应工具。这正是#36描述的现象。read_multiple_files → read_file、task_output → shell_output同理。建议修法
至于
bash_output/task_output/read_multiple_files:那是模型侧的退役名,要不要修复是产品决策(CLI 会连defaults一起补),但不该出现在 wire 上。复验方式
附:顺带提一句,
README里引用的PROTOCOL-FACTS-1.53.1.md在仓库里不存在(git ls-tree -r upstream/master只有 12 个文件),是悬空引用。