Appearance
工具系统
这一层回答的是:模型发出的 tool call 怎么变成一次真实的本地或远程操作,结果怎么回到模型和 UI。
两层抽象
text
ToolDefinition 应用层定义(coding-agent/extensions/types.ts)
带提示词片段、UI 渲染、暴露方式、执行上下文
AgentTool core 运行时使用的接口(agent/types.ts)
只关心 name / parameters / execute / executionMode
wrapToolDefinition(def) ToolDefinition -> AgentTool
createToolDefinitionFromAgentTool(t) AgentTool -> 最小 ToolDefinition真实工具(read / bash / edit / write / grep / find / ls / powershell)都是先写 createXxxToolDefinition(),再用 wrapToolDefinition() 转成 core 工具。
ToolDefinition 字段
| 字段 | 作用 |
|---|---|
name / label / description | 模型调用名、UI 标签、模型可见描述 |
promptSnippet | 默认系统提示词 Available tools 部分的一行摘要 |
promptGuidelines | 追加到 Guidelines 部分的行为约束 |
parameters | TypeBox schema,provider 侧转成 JSON Schema |
constrainedSampling | 约束采样请求,如 { type: "json_schema", strict: "prefer" } |
renderShell | UI 使用标准外壳还是工具自己绘制 |
prepareArguments | 在 schema 校验前兼容旧参数格式 |
outputSchema | structuredContent 的 JSON Schema |
exposure | 模型如何触达这个工具 |
namespace / reachesTools | 分组信息,以及能触达哪些其它工具 |
annotations | 只读、破坏性、幂等、开放世界等提示 |
defaultActive | 注册时是否自动激活 |
prepareLoadout | 工具 loadout 变化时调整模型看到的描述、隐藏声明 |
executionMode | sequential 或 parallel 覆盖 |
execute | 真正执行 |
renderCall / renderResult | 自定义 UI 渲染 |
exposure 语义
| 值 | 含义 |
|---|---|
direct | 注册即激活,直接声明给模型 |
model-only | 像 direct 一样可声明,但语义上只给模型用 |
codemode | 不直接声明,codemode 脚本可以通过 ctx.executeTool() 调用 |
deferred | 类似 codemode,但不列进 codemode 清单,只能被 tool search 找到 |
hidden | 注册但不可达 |
三个集合要区分:
text
active 声明给模型的工具(agent.state.tools)
callable 工具之间可以用 ctx.executeTool() 调的工具
registered 所有注册的工具direct / model-only 注册时自动激活;codemode / deferred 只有被显式激活才会声明,但始终可以被调用。
注册与激活
AgentSession._refreshToolRegistry() 每次重建注册表:
text
内置工具定义 + 扩展注册的工具 + SDK customTools
-> 按 allow/deny 过滤
-> 每个 definition 用扩展 runner 包成 AgentTool
-> 计算下一批 active 名称
-> _applyToolLoadout()active 名称的来源:
- 上一次的 active 集合
- allowlist 中命名的工具
- 新注册且默认激活的工具
- 从 transcript 恢复、但还没注册的工具(
_pendingToolNames)
_applyToolLoadout() 会跑每个工具的 prepareLoadout 钩子,得到模型可见的描述覆盖和 hiddenDeclarations,然后写 agent.state.tools。被隐藏声明的工具仍然 active、仍然 callable,只是请求里不带它的声明。
执行路径
一条模型发起的工具调用:
text
查找 tool
-> prepareArguments() 兼容旧参数
-> validateToolArguments() schema 校验
-> beforeToolCall() 权限、拦截、参数检查
-> execute(id, params, signal, onUpdate)
-> afterToolCall() 结果改写
-> createToolResultMessage()工具失败不会让循环抛异常,都会被转换成 isError: true 的 tool result:
| 失败点 | 结果 |
|---|---|
| 找不到工具 | 错误 result |
| 参数校验失败 | 错误 result,带校验错误信息 |
beforeToolCall 返回 { block: true } | 错误 result,reason 作为文本 |
execute 抛错 | 捕获后转错误 result |
afterToolCall 抛错 | 转错误 result |
executePreparedToolCall() 还负责:
- 用
performance.now()测durationMs - 工具 promise settle 后忽略迟到的
onUpdate - 等待已发出的部分结果事件完成
afterToolCall 的合并是逐字段替换,没有深合并。特别注意:如果它替换了 content 却没有返回 structuredContent,原 structured content 会被丢弃,因为它可能和新内容不一致。
结果结构
AgentToolResult:
ts
{
content: (TextContent | ImageContent)[]; // 给模型
details?: T; // 给 UI / 日志
structuredContent?: JsonValue; // 给程序化调用者,如 codemode
usage?: Usage;
isError?: boolean;
terminate?: boolean;
}持久化成 ToolResultMessage 后还会带 toolCallId、toolName、nestedCalls、durationMs、timestamp。
执行模式
全局默认 parallel。一批工具只有在下面任意一条成立时才整批转 sequential:
text
config.toolExecution === "sequential"
批次里有任一工具 executionMode === "sequential"否则整批按 parallel 执行。
parallel 不是完全无序:
- 按原始顺序 preflight(查找、参数、校验、
beforeToolCall) - 允许的工具并发执行
tool_execution_end按完成顺序发出toolResult消息按原始 tool call 顺序加入 context
两个示例工具
read:
readSchema是path/offset/limitoutputSchema是文本或图片块- 图片走
processImage(),根据模型inputLimits.images.resize缩放 - 文本按行数和字节数截断,提示里给出继续读的
offset - 成功时设置
structuredContent
bash:
bashSchema是command/timeout- 用
OutputAccumulator收集输出,按BASH_UPDATE_THROTTLE_MS节流发onUpdate - 截断到末尾若干行/字节,完整输出写临时文件
- 非零退出码不抛错,而是返回
isError: true和structuredContent(含 exit code、耗时) - abort 和 timeout 都是错误,但会附带已经产生的输出
嵌套调用
ctx.executeTool() 走 NestedToolCallRunner:
text
调用 id 形式:<callerId>/<n>
事件带 parentToolCallId
经过同样的 prepare/validate/hooks/permission 管线
不进入 transcript
调用记录和 usage 汇总到父 toolResult 的 nestedCalls嵌套调用同样支持 sequential 语义:如果宿主是 sequential,或目标工具声明了 sequential,就会进一条独占队列串行执行。
权限
权限不是一个单独模块,而是 beforeToolCall 钩子。AgentSession 把它接到扩展的 tool_call 事件:
text
handler 返回 { block: true, reason } -> 阻止执行,模型看到错误结果
handler 直接改 event.input -> 修改参数,而不是返回新参数
返回 terminate: true -> 参与批次提前结束判定结果侧还有 tool_result 扩展事件,可以改写 content、details、structuredContent、isError、usage。图片归一化在扩展钩子之后执行,所以扩展注入的图片也会被处理。
建议阅读顺序
packages/agent/src/types.tsAgentToolAgentToolResultBeforeToolCallResult/AfterToolCallResult
packages/agent/src/agent-loop.tsexecuteToolCalls()prepareToolCall()/executePreparedToolCall()/finalizeExecutedToolCall()runToolCall()
packages/coding-agent/src/core/extensions/types.tsToolDefinitionToolExposureToolLoadout
packages/coding-agent/src/core/tools/tool-definition-wrapper.tsread.tsbash.ts