Skip to content

工具系统 ​

这一层回答的是:模型发出的 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 部分的行为约束
parametersTypeBox schema,provider 侧转成 JSON Schema
constrainedSampling约束采样请求,如 { type: "json_schema", strict: "prefer" }
renderShellUI 使用标准外壳还是工具自己绘制
prepareArguments在 schema 校验前兼容旧参数格式
outputSchemastructuredContent 的 JSON Schema
exposure模型如何触达这个工具
namespace / reachesTools分组信息,以及能触达哪些其它工具
annotations只读、破坏性、幂等、开放世界等提示
defaultActive注册时是否自动激活
prepareLoadout工具 loadout 变化时调整模型看到的描述、隐藏声明
executionModesequential 或 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 不是完全无序:

  1. 按原始顺序 preflight(查找、参数、校验、beforeToolCall)
  2. 允许的工具并发执行
  3. tool_execution_end 按完成顺序发出
  4. toolResult 消息按原始 tool call 顺序加入 context

两个示例工具 ​

read:

  • readSchema 是 path / offset / limit
  • outputSchema 是文本或图片块
  • 图片走 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。图片归一化在扩展钩子之后执行,所以扩展注入的图片也会被处理。

建议阅读顺序 ​

  1. packages/agent/src/types.ts

    • AgentTool
    • AgentToolResult
    • BeforeToolCallResult / AfterToolCallResult
  2. packages/agent/src/agent-loop.ts

    • executeToolCalls()
    • prepareToolCall() / executePreparedToolCall() / finalizeExecutedToolCall()
    • runToolCall()
  3. packages/coding-agent/src/core/extensions/types.ts

    • ToolDefinition
    • ToolExposure
    • ToolLoadout
  4. packages/coding-agent/src/core/tools/

    • tool-definition-wrapper.ts
    • read.ts
    • bash.ts