统一管理:所有工具,共用一份合同
什么是"合同"? 假设你要雇一个快递员(工具),不管是顺丰还是菜鸟,你作为寄件人都有同样的期待:能告诉我它能不能送、送之前我要签什么授权、送的过程中实时更新进度、送完告诉我结果。这些期待就是"合同"——合同规定了双方的权利和义务,雇主(Agent Loop)不需要关心每家快递公司内部怎么实现,只要按合同调用就行。
src/Tool.ts 定义了整个工具系统的基础类型。每个工具,无论是内置的 BashTool 还是用户接入的 MCP 工具,都必须实现这个接口。
接口的核心是 buildTool() 函数:
// src/Tool.ts
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
return {
...TOOL_DEFAULTS,
userFacingName: () => def.name,
...def,
} as BuiltTool<D>
}
buildTool() 的作用类似工厂函数:工具开发者只需要提供"有差异的部分",其余都由 TOOL_DEFAULTS 填充默认值。默认值遵循失败关闭原则:
// src/Tool.ts
const TOOL_DEFAULTS = {
isEnabled: () => true,
isConcurrencySafe: (_input?) => false, // 默认不允许并发
isReadOnly: (_input?) => false, // 默认假设会写入
isDestructive: (_input?) => false, // 默认假设可撤销
checkPermissions: (input, _ctx?) =>
Promise.resolve({ behavior: 'allow', updatedInput: input }), // 默认放行
toAutoClassifierInput: (_input?) => '', // 默认不参与安全分类
userFacingName: (_input?) => '',
}
这个设计很谨慎:isConcurrencySafe 默认 false,意味着新工具默认不允许并发执行,必须显式声明自己是并发安全的,才会被 Agent Loop 并行调用。
什么叫"显式声明"? 就是工具开发者主动在代码里写出来 isConcurrencySafe: () => true,明确告诉系统"我支持并发"。与之相对的是隐式——什么都不写,系统按默认值处理。两者对比:
| 隐式(什么都不写) | 显式(主动声明) | |
|---|---|---|
| 代码形式 | 省略 isConcurrencySafe 字段 | 写 isConcurrencySafe: () => true |
| 系统行为 | 取 TOOL_DEFAULTS 的默认值 false,不允许并发 | 系统知道这个工具可以和其他工具同时运行 |
| 典型例子 | 新工具刚创建,开发者还不确定是否安全 | GlobTool、GrepTool(只读搜索,天然并发安全) |
这是"失败关闭"(fail-closed)原则的体现:宁可保守地串行执行,也不冒并发带来数据竞争的风险。只有开发者主动站出来说"我没问题",系统才允许并发。