结论
d063b47 引入的 TOOL_NAME_ALIASES 重命名表取错了来源,而且作用方向也反了。对照 command-code@1.54.0 的 dist/cli.mjs(未混淆,可直读):
- 线上重命名只有一个,不是四个;
- 另外三个来自
resolveToolNameAlias,那是客户端修复模型调用旧工具名的逻辑,还带 defaults 语义 —— 与 wire 无关;
- CLI 在
messages 里重命名,不在 tools 声明里;当前的实现正好反过来。
(#36 报的下游症状是同一个 bug 的另一面,这里补上根因。)
证据:CLI 1.54.0 的四处源码
① 线上重命名只有一项
nw="search_tools", rw="tool_search", // 两个常量
function toWireToolName(e){return e===rw?nw:e}
② 那张四项表是入站别名,不是出站重命名
ow={bash_output:{to:"shell_output"},
task_output:{to:"shell_output",defaults:{wait:"exit"}},
[rw]:{to:nw},
read_multiple_files:{to:"read_file"}}
它的消费者是 resolveToolNameAlias:
function resolveToolNameAlias(e){
const t=ow[e.toolName]; if(void 0===t) return e;
const n={...e.input};
for(const[e,r] of Object.entries(t.defaults??{}))
void 0!==n[e]&&null!==n[e]||"wait"===e&&blockCancelsWait(n)||(n[e]=r);
return {toolName:t.to, input:n,
note:\`Repair note: the tool \`\${e.toolName}\` is now \`\${t.to}\`; this call ran as \${t.to} with your arguments carried over. Call \`\${t.to}\` directly next time.\`};
}
调用点在工具执行器里(参数是 {toolName,input,signal,onUpdate,toolCallId,permissionMode}),用途是「模型调了退役名 → 本地按新名字跑,并回一句 Repair note 让模型下次改口」。两条与 wire 无关的铁证:
- 它产出
note —— 一段给模型看的自然语言;
- 它带
defaults(task_output 会补 wait:"exit")—— 补默认参数是执行语义,不是重命名。
③ toWireTools 完全不做重命名
function toWireTools(e){return e.map(e=>({name:e.name,description:e.description,input_schema:e.input_schema}))}
④ toWireMessages 里 tool-call 与 tool-result 都用别名,且缓存的是别名后的名字
if("tool_use"===t.type){
if(t.providerExecuted) continue;
const r=toWireToolName(t.name);
n.set(t.id,r); // ← 存别名后的名字
e.push({type:"tool-call",toolCallId:t.id,toolName:r,input:t.input});
continue}
...
"tool_result"===t.type && e.push({type:"tool-result",toolCallId:t.tool_use_id,
toolName:n.get(t.tool_use_id)??"unknown", // ← 查出来的也是别名
output:toWireToolOutput(t.content)})
当前实现的三处偏差
proxy.mjs:
| 位置 |
现状 |
CLI |
681 行 TOOL_NAME_ALIASES |
4 项 |
线上只有 1 项(tool_search→search_tools) |
657 行 params.tools[].name |
应用别名 |
不应用(toWireTools 原样) |
525 / 580 行 toolNameMap[tc.id] |
存原名 |
存别名后的名字 |
633 行 assistant tool-call.toolName |
原名 |
别名 |
646 行 tool-result.toolName |
原名(查 map) |
别名 |
也就是说:该改名的地方(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 同理。
建议修法
// 线上唯一的重命名(CLI 的 toWireToolName,rw→nw)
const WIRE_TOOL_ALIASES = { tool_search: 'search_tools' };
function toWireToolName(name) { return WIRE_TOOL_ALIASES[name] || name; }
// ① tools 声明原样下发,不套别名
body.params.tools = (tools || []).map(t => ({
name: t.function?.name || t.name || '',
description: ..., input_schema: ...,
}));
// ② messages 里 tool-call / tool-result 都套别名,
// 并且 map 里存**别名后**的名字(CLI 的 n.set(t.id, r))
toolNameMap[tc.id] = toWireToolName(tc.function?.name || '');
parts.push({ type: 'tool-call', toolCallId: tc.id,
toolName: toWireToolName(tc.function?.name || ''), input: ... });
至于 bash_output / task_output / read_multiple_files:那是模型侧的退役名,要不要修复是产品决策(CLI 会连 defaults 一起补),但不该出现在 wire 上。
复验方式
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 'ow={[^}]*}[^}]*}' cli.mjs # → 四项表
grep -o 'function toWireTools([^}]*}' cli.mjs # → 无别名
附:顺带提一句,README 里引用的 PROTOCOL-FACTS-1.53.1.md 在仓库里不存在(git ls-tree -r upstream/master 只有 12 个文件),是悬空引用。
结论
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 个文件),是悬空引用。