02.2 工具契约与注册

关注源码

  • src/Tool.ts
  • src/tools.ts
  • src/utils/toolPool.ts

Tool 不是简单函数

在这套系统里,一个 Tool 同时定义:

  • 输入 schema 与 API schema
  • prompt 文案
  • 调用逻辑 call()
  • 是否只读、是否 destructive、是否可并发
  • 权限检查与输入校验
  • tool result 如何映射回 transcript
  • tool use / tool result / progress 的 UI 渲染
  • transcript search 文本
  • auto-classifier 输入

这让工具成为横跨三层的统一对象:

  • 模型可见能力
  • 运行时可执行能力
  • UI 可展示能力

ToolUseContext 是运行时最重的上下文对象之一

ToolUseContext 里包含:

  • 当前工具池、命令池、主模型、thinking config
  • getAppState() / setAppState()
  • read file cache
  • notifications / prompt request / permission 相关回调
  • response length、stream mode、OS notification
  • 当前 messages
  • 内容替换状态、查询链路跟踪、agentId/agentType

可以把它理解为“工具运行时的环境对象”,不是单纯的调用参数。

buildTool() 体现了默认安全策略

Tool.ts 里的 buildTool() 会给工具填充一组默认实现:

  • isEnabled -> true
  • isConcurrencySafe -> false
  • isReadOnly -> false
  • isDestructive -> false
  • checkPermissions -> allow
  • toAutoClassifierInput -> ''

尤其重要的是:

  • 并发默认关闭
  • 读写默认按写处理

这说明工具系统是保守设计,而不是默认放开。

工具池装配流程

1. getAllBaseTools()

这里定义“当前构建理论上有哪些工具”,并受:

  • feature gate
  • USER_TYPE
  • simple mode / REPL mode
  • worktree / LSP / todo v2 等能力开关

影响。

2. getTools(permissionContext)

把理论工具池裁剪成“当前会话内建可见工具池”,同时处理:

  • deny rule 过滤
  • simple mode
  • REPL 隐藏 primitive tools
  • coordinator mode 特殊组合

3. assembleToolPool(permissionContext, mcpTools)

把内建工具与 MCP 工具合并,并保持:

  • deny rules 同样作用于 MCP
  • built-in 作为前缀
  • 名称排序稳定
  • uniqBy(name) 去重

这里专门强调 prompt-cache 稳定排序,说明工具顺序本身就是缓存契约的一部分。

4. mergeAndFilterTools(initialTools, assembled, mode)

交互式 REPL 还会进一步把初始 tools 与组装好的 tools 合并,并应用 coordinator mode 的最终过滤。

工具延迟加载

ToolSearchTool 与 deferred tools 让工具系统支持“大工具池但不把完整 schema 一次塞给模型”。这背后有两个目的:

  • 控制 prompt 体积
  • 保持缓存稳定

因此工具注册并不只是“罗列功能”,而是 prompt 工程的一部分。

设计上最重要的三个点

1. 工具对象横跨模型、运行时、UI 三个世界

这让系统不会出现“模型看到一套能力、运行时执行另一套、UI 渲染第三套”的分裂。

2. 工具池组装高度依赖上下文

权限模式、MCP 连接、运行模式、feature gate 都会改变“模型最终能看到什么”。

3. prompt-cache 稳定性是工具层一等约束

工具的排序、过滤、是否 deferred,不只是实现细节,而是缓存命中策略。