4.1 Tool 接口定义
上述三层架构的起点是 src/Tool.ts 里的 Tool 接口——内置、MCP、REPL 所有工具的统一契约,也是整个系统的核心类型:
typescript
export type Tool<Input, Output, P extends ToolProgressData> = {
// ===== 元数据 =====
name: string // 工具唯一标识
aliases?: string[] // 别名(兼容旧名称)
maxResultSizeChars: number // 结果最大字符数
shouldDefer?: boolean // 是否延迟加载(ToolSearch 动态发现)
// ===== 核心执行 =====
call(args, context, canUseTool, parentMessage, onProgress?): Promise<ToolResult<Output>>
// ===== 提示词与描述 =====
description(input, options): Promise<string>
prompt(options): Promise<string>
// ===== Schema 定义 =====
inputSchema: Input // Zod 输入 Schema
inputJSONSchema?: ToolInputJSONSchema // JSON Schema(API 兼容)
// ===== 安全与权限 =====
isConcurrencySafe(input): boolean // 是否可并发执行
isReadOnly(input): boolean // 是否只读操作
isDestructive?(input): boolean // 是否破坏性操作
validateInput?(input, context): Promise<ValidationResult>
checkPermissions(input, context): Promise<PermissionResult>
// ===== UI 渲染(React 组件)=====
renderToolUseMessage(input, options): React.ReactNode
renderToolResultMessage?(content, progress, options): React.ReactNode
}每个工具返回的 ToolResult 不仅包含数据,还可以注入额外消息或修改上下文:
typescript
export type ToolResult<T> = {
data: T // 工具输出数据
newMessages?: Message[] // 额外注入的消息
contextModifier?: (ctx) => ToolUseContext // 上下文修改器
}buildTool 工厂模式
所有工具的创建都通过 buildTool() 工厂函数完成。这个函数将 TOOL_DEFAULTS 与工具的自定义定义合并,确保每个工具都有完整的方法集:
typescript
const TOOL_DEFAULTS = {
isEnabled: () => true,
isConcurrencySafe: () => false, // 默认假定不安全,防止并发问题
isReadOnly: () => false, // 默认假定有写入,需要权限检查
isDestructive: () => false,
checkPermissions: () => ({ behavior: 'allow', updatedInput }), // 默认允许
toAutoClassifierInput: () => '', // 默认跳过分类器
}
function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
return {
...TOOL_DEFAULTS,
userFacingName: () => def.name,
...def,
} as BuiltTool<D>
}这是一套 fail-closed(默认关闭)的安全设计:
isConcurrencySafe: () => false:新工具默认不可并发执行。只有经过验证确实安全的工具(如纯读取操作)才显式 opt-in 为true。这避免了新增工具因遗漏并发安全标记而导致竞态条件。isReadOnly: () => false:默认假设工具有写入副作用,因此必须经过权限检查。只读工具(如 GrepTool、GlobTool)显式声明自己为只读以跳过权限弹窗。toAutoClassifierInput: () => '':默认跳过 LLM 分类器的自动审批。这意味着安全相关的工具不会被意外自动批准——必须由工具作者显式提供分类器输入格式。
这种设计确保了:任何新工具在缺少显式配置的情况下,都会走最保守的路径——需要权限、不可并发、不自动批准。
工具目录结构
每个工具独立存放在 src/tools/ 下的同名目录中,遵循统一的文件组织约定:
src/tools/FileEditTool/
├── FileEditTool.ts // 主实现:call(), validateInput(), checkPermissions()
├── UI.tsx // React 渲染:renderToolUseMessage, renderToolResultMessage
├── types.ts // Zod inputSchema + TypeScript 类型
├── prompt.ts // 工具特定的 system prompt 注入内容
├── constants.ts // 常量定义
└── utils.ts // 辅助函数(如 diff 生成、引号标准化)这种分离的好处是:
- 执行逻辑在
.ts、渲染逻辑在UI.tsx,二者完全解耦,修改 UI 不影响工具行为 types.ts中定义的 Zod Schema 既用于运行时验证,也自动转换为 JSON Schema 发送给 API- 每个工具可以通过
prompt.ts向系统提示词注入工具特定的使用指南,例如 FileEditTool 注入关于精确匹配的规则