Claude Code源码系列:4、1-关键功能模块-工具系统
概述 (Overview)
Claude Code 的工具系统是整个 AI Agent 与外部世界交互的核心桥梁。它不仅提供了类型安全的工具定义机制,还实现了完整的权限控制体系、并发执行策略、流式进度报告以及 MCP (Model Context Protocol) 工具集成。本文将从源码级别深入剖析这一系统的设计哲学、核心架构与关键实现细节。
设计哲学
工具系统遵循以下核心设计原则:
-
类型安全优先:使用 TypeScript 泛型 + Zod Schema 实现从定义到执行的全程类型校验 -
默认安全策略(Fail-closed): isConcurrencySafe默认false,isReadOnly默认false,确保新工具必须显式声明安全属性 -
延迟加载优化:通过 lazySchema和shouldDefer机制减少初始提示词成本 -
权限分层治理: validateInput → checkPermissions → canUseTool三层检查确保细粒度控制
系统全景架构图
+-----------------------------------------------------------------------+| Claude Code 工具系统架构 |+-----------------------------------------------------------------------+| || +------------------+ +------------------+ +------------------+ || | API Layer | | Tool Registry | | Permission | || | (Anthropic SDK) |---->| (tools.ts) |---->| Control Layer | || +------------------+ +------------------+ +------------------+ || | | | || v v v || +------------------+ +------------------+ +------------------+ || | StreamingTool | | Tool Pool | | Permission | || | Executor |<----| Assembly |<----| Context | || +------------------+ +------------------+ +------------------+ || | | | || v v v || +------------------+ +------------------+ +------------------+ || | Tool Execution | | Built-in Tools | | Rule Matching | || | (toolExecution) |---->| (FileReadTool, |---->| (permissions.ts)| || +------------------+ | BashTool, ...) | +------------------+ || | +------------------+ | || v | v || +------------------+ | +------------------+ || | Progress/Result | | | Hook System | || | Streaming |<------------+---------------| (PreToolUse, | || +------------------+ | PostToolUse) | || +------------------+ || || +------------------+ +------------------+ +------------------+ || | MCP Tool | | Agent Tool | | Skill Tool | || | Integration | | Subagent | | Execution | || +------------------+ +------------------+ +------------------+ || |+-----------------------------------------------------------------------+
1. 核心基类与类型设计 (Core Types & Interfaces)
1.1 Tool 泛型接口 – 三层类型架构
文件: src/Tool.ts:362-695
// src/Tool.ts:362-366export type Tool< Input extends AnyObject = AnyObject, // Zod Schema 类型约束 Output = unknown, // 输出类型(默认 unknown,灵活性高) P extends ToolProgressData = ToolProgressData, // 进度数据类型> = {// 基本信息字段 readonly name: string// 工具唯一标识名 aliases?: string[] // 别名列表(向后兼容用) searchHint?: string// ToolSearch 关键词匹配提示(3-10词)// Schema 定义 readonly inputSchema: Input // Zod 输入 Schema readonly inputJSONSchema?: ToolInputJSONSchema // MCP 工具可直接使用 JSON Schema outputSchema?: z.ZodType<unknown> // 输出 Schema(可选)// 核心执行方法 call( args: z.infer<Input>, context: ToolUseContext, canUseTool: CanUseToolFn, parentMessage: AssistantMessage, onProgress?: ToolCallProgress<P>, ): Promise<ToolResult<Output>>// 描述生成方法 description( input: z.infer<Input>, options: { isNonInteractiveSession: boolean toolPermissionContext: ToolPermissionContext tools: Tools }, ): Promise<string>// 工具特性判断方法 isConcurrencySafe(input: z.infer<Input>): boolean// 是否可并发执行 isEnabled(): boolean// 是否启用 isReadOnly(input: z.infer<Input>): boolean// 是否只读操作 isDestructive?(input: z.infer<Input>): boolean// 是否破坏性操作// 权限相关方法 checkPermissions( input: z.infer<Input>, context: ToolUseContext, ): Promise<PermissionResult> validateInput?( input: z.infer<Input>, context: ToolUseContext, ): Promise<ValidationResult> preparePermissionMatcher?(input: z.infer<Input>): Promise<(pattern: string) =>boolean>// UI 渲染方法 renderToolUseMessage(input: Partial<z.infer<Input>>, options): React.ReactNode renderToolResultMessage?(content: Output, progressMessages, options): React.ReactNode renderToolUseProgressMessage?(progressMessages, options): React.ReactNode renderToolUseRejectedMessage?(input, options): React.ReactNode renderToolUseErrorMessage?(result, options): React.ReactNode renderGroupedToolUse?(toolUses, options): React.ReactNode | null// 其他辅助方法 prompt(options): Promise<string> userFacingName(input: Partial<z.infer<Input>>): string getToolUseSummary?(input: Partial<z.infer<Input>>): string | null getActivityDescription?(input: Partial<z.infer<Input>>): string | null toAutoClassifierInput(input: z.infer<Input>): unknown mapToolResultToToolResultBlockParam(content: Output, toolUseID: string): ToolResultBlockParam// MCP 相关属性 isMcp?: boolean// 是否 MCP 工具 isLsp?: boolean// 是否 LSP 工具 mcpInfo?: { serverName: string; toolName: string } // MCP 元信息// 延迟加载属性 readonly shouldDefer?: boolean// 是否延迟加载(需 ToolSearch) readonly alwaysLoad?: boolean// 是否始终加载(即使启用 ToolSearch)// 结果限制 maxResultSizeChars: number// 结果持久化阈值 readonly strict?: boolean// 是否启用 API 严格模式}
泛型设计深度解析:
|
|
|
|
|---|---|---|
Input |
extends AnyObject |
|
Output |
= unknown |
|
P |
extends ToolProgressData |
|
1.2 AnyObject 类型约束
文件: src/Tool.ts:343
// src/Tool.ts:343exporttype AnyObject = z.ZodType<{ [key: string]: unknown }>
这是一个关键的类型约束设计:它确保所有工具的输入必须是 Zod 对象类型 Schema,而非原始类型或数组。这保证了:
-
工具输入始终是可验证的对象结构 -
权限规则可以基于字段级别进行匹配 -
Hook 系统可以访问结构化的输入参数
1.3 ToolResult 输出类型
文件: src/Tool.ts:321-336
// src/Tool.ts:321-336export type ToolResult<T> = { data: T // 泛型输出数据// 工具执行过程中产生的消息(追加到对话历史) newMessages?: ( | UserMessage | AssistantMessage | AttachmentMessage | SystemMessage )[]// 上下文修改器(仅对非并发安全工具有效)// 用于修改后续工具执行的上下文(如模型切换、权限调整) contextModifier?: (context: ToolUseContext) => ToolUseContext// MCP 协议元数据传递 mcpMeta?: { _meta?: Record<string, unknown> structuredContent?: Record<string, unknown> }}
关键设计点:
-
data: T– 泛型输出数据,由 outputSchema 定义类型 -
newMessages– 支持工具产生对话消息(如 SkillTool 扩展提示词) -
contextModifier– 仅对非并发安全工具有效,因为并发工具无法保证修改顺序 -
mcpMeta– MCP 协议的元数据透传通道
1.4 ToolProgress 进度类型体系
文件: src/Tool.ts:307-340
// src/Tool.ts:307-310exporttype ToolProgress<P extends ToolProgressData> = { toolUseID: string// 工具调用唯一 ID data: P // 进度数据(泛型)}// src/Tool.ts:338-340exporttype ToolCallProgress<P extends ToolProgressData = ToolProgressData> = ( progress: ToolProgress<P>,) => void
集中定义的进度类型 (src/types/tools.ts):
// 进度类型集中定义(从 src/types/tools.ts 导入)export type BashProgress = {type: 'bash_progress' pid: number output: string isInteractiveCheck?: boolean}export type AgentToolProgress = {type: 'agent_progress' message: Message agentId: string}export type WebSearchProgress = {type: 'web_search_progress' status: 'searching' | 'processing' | 'complete' resultsCount?: number}
1.5 Tools 集合类型
文件: src/Tool.ts:701
// src/Tool.ts:701exporttype Tools = readonly Tool[]
使用 readonly 确保工具集合不可变,防止意外修改。这是工具池传递过程中的重要安全保证。
1.6 工具发现与名称匹配
文件: src/Tool.ts:348-360
// src/Tool.ts:348-353/** * 检查工具是否匹配给定名称(主名称或别名) */exportfunctiontoolMatchesName( tool: { name: string; aliases?: string[] }, name: string,): boolean{return tool.name === name || (tool.aliases?.includes(name) ?? false)}// src/Tool.ts:358-360/** * 从工具列表中按名称或别名查找工具 */exportfunctionfindToolByName(tools: Tools, name: string): Tool | undefined{return tools.find(t => toolMatchesName(t, name))}
2. 核心机制与工厂模式 (Core Mechanisms & Initialization)
2.1 buildTool 工厂函数 – 类型安全的部分实现
文件: src/Tool.ts:783-792
// src/Tool.ts:783-792/** * 从部分定义构建完整的 Tool,填充安全默认值 * * 默认值(关键安全属性采用 fail-closed 策略): * - `isEnabled` → `true` * - `isConcurrencySafe` → `false` (假设不安全) * - `isReadOnly` → `false` (假设写入操作) * - `isDestructive` → `false` * - `checkPermissions` → `{ behavior: 'allow', updatedInput }` (透传到通用权限系统) * - `toAutoClassifierInput` → `''` (跳过分类器) * - `userFacingName` → `name` */exportfunctionbuildTool<DextendsAnyToolDef>(def: D): BuiltTool<D> {// 运行时展开:默认值 + 用户定义return { ...TOOL_DEFAULTS, userFacingName: () => def.name, ...def, } as BuiltTool<D>}
2.2 TOOL_DEFAULTS 默认值配置 – Fail-closed 安全策略
文件: src/Tool.ts:757-769
// src/Tool.ts:757-769const TOOL_DEFAULTS = {// 工具启用状态:默认启用 isEnabled: () => true,// 【关键安全】并发安全:默认 false// 新工具必须显式声明为 true 才能并发执行 isConcurrencySafe: (_input?: unknown) => false,// 【关键安全】只读属性:默认 false// 新工具必须显式声明为 true 才会被视为读操作 isReadOnly: (_input?: unknown) => false,// 破坏性操作:默认 false isDestructive: (_input?: unknown) => false,// 权限检查:默认允许,透传到通用权限系统 checkPermissions: ( input: { [key: string]: unknown }, _ctx?: ToolUseContext, ): Promise<PermissionResult> =>Promise.resolve({ behavior: 'allow', updatedInput: input }),// 分类器输入:默认空字符串(跳过安全分类器) toAutoClassifierInput: (_input?: unknown) => '',// 用户可见名称:默认空,buildTool 会覆盖为工具名 userFacingName: (_input?: unknown) => '',}
Fail-closed 策略深度解读:
|
|
|
|
|---|---|---|
isConcurrencySafe |
false |
|
isReadOnly |
false |
|
isDestructive |
false |
|
2.3 ToolDef 类型 – 可选实现的设计
文件: src/Tool.ts:707-726
// src/Tool.ts:707-714/** * 可由 buildTool 提供默认值的方法键 * ToolDef 可省略这些方法,buildTool 会自动填充 */type DefaultableToolKeys = | 'isEnabled' | 'isConcurrencySafe' | 'isReadOnly' | 'isDestructive' | 'checkPermissions' | 'toAutoClassifierInput' | 'userFacingName'// src/Tool.ts:721-726/** * buildTool 接受的工具定义类型 * 与 Tool 形状相同,但默认方法可选 */export type ToolDef< Input extends AnyObject = AnyObject, Output = unknown, P extends ToolProgressData = ToolProgressData,> = Omit<Tool<Input, Output, P>, DefaultableToolKeys> & Partial<Pick<Tool<Input, Output, P>, DefaultableToolKeys>>
2.4 BuiltTool 类型推导 – 类型级展开镜像
文件: src/Tool.ts:735-741
// src/Tool.ts:735-741/** * 类型级展开,镜像运行时 `{...TOOL_DEFAULTS, ...def}` * 对于每个可默认键: * - 如果 D 提供了(required),D 的类型优先 * - 如果 D 略了或 optional,默认类型填充 */type BuiltTool<D> = Omit<D, DefaultableToolKeys> & { [K in DefaultableToolKeys]-?: K extends keyof D ? undefinedextends D[K] ? ToolDefaults[K] // D 有但 optional → 用默认 : D[K] // D 有且 required → 用 D 的 : ToolDefaults[K] // D 没有 → 用默认}
2.5 lazySchema 延迟初始化 – 避免模块加载开销
文件: src/utils/lazySchema.ts
// src/utils/lazySchema.ts (全文)/** * 延迟 Schema 构造工厂 * 避免 Zod Schema 在模块初始化时立即构造的性能开销 * * 所有工具的 inputSchema/outputSchema 都使用此工厂 */exportfunctionlazySchema<T>(factory: () => T): () => T{let cached: T | undefinedreturn() => (cached ??= factory())}
使用示例 (src/tools/FileReadTool/FileReadTool.ts:227-243):
// src/tools/FileReadTool/FileReadTool.ts:227-243const inputSchema = lazySchema(() => z.strictObject({ file_path: z.string().describe('The absolute path to the file to read'), offset: semanticNumber(z.number().int().nonnegative().optional()).describe('The line number to start reading from (default: 1)', ), limit: semanticNumber(z.number().int().positive().optional()).describe('The number of lines to read', ), pages: z.string().optional().describe('Page range for PDF files'), }),)type InputSchema = ReturnType<typeof inputSchema>export type Input = z.infer<InputSchema>
2.6 延迟加载机制 (shouldDefer / alwaysLoad)
文件: src/Tool.ts:441-450
// src/Tool.ts:441-443/** * 延迟加载标记:工具需要 ToolSearch 才能调用 * 设置为 true 时,初始提示词不发送完整 Schema */readonly shouldDefer?: boolean// src/Tool.ts:448-450/** * 始终加载标记:即使启用 ToolSearch 也发送完整 Schema * 用于模型必须在第一轮看到的工具(如 Bash、Read) */readonly alwaysLoad?: boolean
3. 编排与执行流程 (Orchestration & Execution Flow)
3.1 StreamingToolExecutor 流式执行器
文件: src/services/tools/StreamingToolExecutor.ts
// src/services/tools/StreamingToolExecutor.ts:40-62/** * 流式工具执行器:随着 API 流入的工具调用动态执行 * * 并发控制策略: * - 并发安全工具可与其他并发安全工具并行执行 * - 非并发安全工具必须独占执行(exclusive access) * - 结果按工具接收顺序产出(保持时序一致性) */export class StreamingToolExecutor {private tools: TrackedTool[] = [] // 跟踪的工具列表private toolUseContext: ToolUseContext // 执行上下文private hasErrored = false// 是否有工具出错private erroredToolDescription = ''// 错误工具描述private siblingAbortController: AbortController // 兄弟进程中止控制器private discarded = false// 是否丢弃(fallback 场景)private progressAvailableResolve?: () => void// 进度可用唤醒回调constructor(private readonly toolDefinitions: Tools,private readonly canUseTool: CanUseToolFn, toolUseContext: ToolUseContext,) {this.toolUseContext = toolUseContextthis.siblingAbortController = createChildAbortController( toolUseContext.abortController, ) }}
3.2 TrackedTool 状态跟踪
文件: src/services/tools/StreamingToolExecutor.ts:19-32
// src/services/tools/StreamingToolExecutor.ts:19-32type ToolStatus = 'queued' | 'executing' | 'completed' | 'yielded'type TrackedTool = { id: string// 工具调用 ID block: ToolUseBlock // API 工具调用块 assistantMessage: AssistantMessage // 关联的助手消息 status: ToolStatus // 执行状态 isConcurrencySafe: boolean// 是否并发安全 promise?: Promise<void> // 执行 Promise results?: Message[] // 结果消息列表 pendingProgress: Message[] // 待产出的进度消息(立即 yield) contextModifiers?: Array<(context: ToolUseContext) => ToolUseContext> // 上下文修改器}
3.3 并发控制核心逻辑
文件: src/services/tools/StreamingToolExecutor.ts:129-135
// src/services/tools/StreamingToolExecutor.ts:129-135/** * 检查工具是否可以执行(基于当前并发状态) * * 条件: * 1. 无正在执行的工具 → 可以执行 * 2. 工具并发安全 + 所有正在执行的工具都并发安全 → 可以执行 */private canExecuteTool(isConcurrencySafe: boolean): boolean {const executingTools = this.tools.filter(t => t.status === 'executing')return ( executingTools.length === 0 || (isConcurrencySafe && executingTools.every(t => t.isConcurrencySafe)) )}
3.4 工具添加流程
文件: src/services/tools/StreamingToolExecutor.ts:76-124
// src/services/tools/StreamingToolExecutor.ts:76-124/** * 添加工具到执行队列 * 立即开始执行如果条件允许 */addTool(block: ToolUseBlock, assistantMessage: AssistantMessage): void {const toolDefinition = findToolByName(this.toolDefinitions, block.name)// 工具不存在 → 直接返回错误结果if (!toolDefinition) {this.tools.push({ id: block.id, block, assistantMessage, status: 'completed', isConcurrencySafe: true, pendingProgress: [], results: [ createUserMessage({ content: [ {type: 'tool_result', content: `<tool_use_error>Error: No such tool available: ${block.name}</tool_use_error>`, is_error: true, tool_use_id: block.id, }, ], toolUseResult: `Error: No such tool available: ${block.name}`, sourceToolAssistantUUID: assistantMessage.uuid, }), ], })return }// 解析输入并判断并发安全性const parsedInput = toolDefinition.inputSchema.safeParse(block.input)const isConcurrencySafe = parsedInput?.success ? (() => {try {returnBoolean(toolDefinition.isConcurrencySafe(parsedInput.data)) } catch {returnfalse// 异常时默认不安全 } })() : false // 加入跟踪队列this.tools.push({ id: block.id, block, assistantMessage, status: 'queued', isConcurrencySafe, pendingProgress: [], }) // 异步处理队列voidthis.processQueue()}
3.5 队列处理与执行调度
文件: src/services/tools/StreamingToolExecutor.ts:140-151
// src/services/tools/StreamingToolExecutor.ts:140-151/** * 处理队列,当并发条件允许时启动工具 */private async processQueue(): Promise<void> {for (const tool of this.tools) {if (tool.status !== 'queued') continueif (this.canExecuteTool(tool.isConcurrencySafe)) {await this.executeTool(tool) // 可以执行 → 启动 } else {// 无法执行此工具// 非并发安全工具需保持顺序 → 停止等待if (!tool.isConcurrencySafe) break } }}
3.6 完整执行流程 ASCII 图
+-----------------------------------------------------------------------+| 工具执行流程 |+-----------------------------------------------------------------------+| || API Stream || | || v || +--------------------------------------------------------+ || | StreamingToolExecutor.addTool(block, assistantMsg) | || | | || | 1. findToolByName() 查找工具定义 | || | 2. inputSchema.safeParse() 解析并校验输入 | || | 3. isConcurrencySafe() 判断并发安全性 | || | 4. 加入 TrackedTool 队列 (status='queued') | || | 5. 触发 processQueue() | || +--------------------------------------------------------+ || | || v || +--------------------------------------------------------+ || | processQueue() - 并发控制调度 | || | | || | +----------------------------------------------+ | || | | canExecuteTool(isConcurrencySafe) | | || | | | | || | | 条件判断: | | || | | - executingTools.length === 0 → 可以执行 | | || | | - 并发安全 + 全部并发安全 → 可以执行 | | || | | - 其他情况 → 需等待 | | || | +----------------------------------------------+ | || | | || | 调度策略: | || | - 可执行 → executeTool(tool) | || | - 不可执行 + 非并发安全 → break (保持顺序) | || | - 不可执行 + 并发安全 → 继续检查下一个 | || +--------------------------------------------------------+ || | || v || +--------------------------------------------------------+ || | executeTool(tool) | || | | || | 1. 创建 toolAbortController (子中止控制器) | || | 2. 更新状态: status = 'executing' | || | 3. 调用 runToolUse() 开始执行 | || | 4. 监听进度消息 (onProgress 回调) | || | - 进度消息 → pendingProgress 数组 | || | 5. 处理执行结果 | || | - 成功 → results = resultingMessages | || | - 失败 → hasErrored = true, 中止兄弟进程 | || | 6. 更新状态: status = 'completed' | || +--------------------------------------------------------+ || | || v || +--------------------------------------------------------+ || | runToolUse() [toolExecution.ts] | || | | || | 1. findToolByName() 查找工具 | || | 2. 工具不存在 → 返回错误消息 | || | 3. 调用 streamedCheckPermissionsAndCallTool() | || +--------------------------------------------------------+ || | || v || +--------------------------------------------------------+ || | streamedCheckPermissionsAndCallTool() | || | | || | +------------------+ +------------------+ | || | | 1. validateInput | | 2. PreToolUse | | || | | (工具特定) |->| Hooks | | || | | - 路径验证 | | - 用户钩子 | | || | | - 格式校验 | | - 自动批准 | | || | +------------------+ +------------------+ | || | | | | || | v v | || | +------------------+ +------------------+ | || | | 3. checkPermiss- | | 4. canUseTool | | || | | ions(工具) |->| (权限决策) | | || | | - 工具规则 | | - allow | | || | | - 内容规则 | | - deny | | || | | - passthrough | | - ask (交互) | | || | +------------------+ +------------------+ | || | | | | || | +--------------------+ | || | | | || | v | || | +------------------+ | || | | 权限决策 | | || | | - allow → 继续 | | || | | - deny → 返回错误| | || | | - ask → 等待用户 | | || | +------------------+ | || | | | || | v (权限允许) | || | +------------------+ | || | | 5. tool.call() | | || | | 执行核心逻辑 | | || | | - 文件操作 | | || | | - 命令执行 | | || | | - 网络请求 | | || | +------------------+ | || | | | || | v | || | +------------------+ +------------------+ | || | | 6. PostToolUse | | 7. 结果序列化 | | || | | Hooks |->| mapToolResult| | || | | - 结果钩子 | | ToBlockParam | | || | +------------------+ +------------------+ | || +--------------------------------------------------------+ || | || v || +--------------------------------------------------------+ || | getCompletedResults() / getRemainingResults() | || | | || | 流式产出策略: | || | 1. 进度消息立即 yield (pendingProgress) | || | 2. 完成结果按顺序 yield (results) | || | 3. 等待执行完成或进度可用 (progressPromise) | || +--------------------------------------------------------+ || | || v || API Response (tool_result blocks) || |+-----------------------------------------------------------------------+
3.7 进度消息流式产出
文件: src/services/tools/StreamingToolExecutor.ts:412-440
// src/services/tools/StreamingToolExecutor.ts:412-440/** * 获取已完成的结果(Generator 模式) * 进度消息立即产出,不等待工具完成 */*getCompletedResults(): Generator<MessageUpdate, void> {for (const tool of this.tools) {// 进度消息立即 yield(实时反馈)while (tool.pendingProgress.length > 0) {const progressMessage = tool.pendingProgress.shift()!yield { message: progressMessage, newContext: this.toolUseContext, } }// 完成的工具产出结果if (tool.status === 'completed' && tool.results) {for (const message of tool.results) {yield { message, newContext: tool.contextModifiers ? this.applyContextModifiers(tool.contextModifiers) : this.toolUseContext, } } tool.status = 'yielded'// 标记已产出 } }}
4. 关键内置实现示例 (Built-in Implementations)
4.1 FileReadTool – 多格式文件读取神器
文件: src/tools/FileReadTool/FileReadTool.ts:337-718
// src/tools/FileReadTool/FileReadTool.ts:337-395export const FileReadTool = buildTool({ name: FILE_READ_TOOL_NAME, searchHint: 'read files, images, PDFs, notebooks',// 输出自 bounded,永不持久化(避免循环 Read→file→Read) maxResultSizeChars: Infinity, strict: true,// Schema 定义get inputSchema(): InputSchema { return inputSchema() },get outputSchema(): OutputSchema { return outputSchema() },// 安全特性 isConcurrencySafe() { return true }, // 读操作可并发 isReadOnly() { return true }, // 纯读操作// 分类器输入 toAutoClassifierInput(input) { return input.file_path },// UI 分类 isSearchOrReadCommand() { return { isSearch: false, isRead: true } },// 路径获取 getPath({ file_path }): string { return file_path || getCwd() },// 路径扩展(防止 Hook 绕过) backfillObservableInput(input) {if (typeof input.file_path === 'string') { input.file_path = expandPath(input.file_path) } },// 权限匹配器准备async preparePermissionMatcher({ file_path }) {return pattern => matchWildcardPattern(pattern, file_path) },// 权限检查async checkPermissions(input, context): Promise<PermissionDecision> {const appState = context.getAppState()return checkReadPermissionForTool( FileReadTool, input, appState.toolPermissionContext, ) },})
validateInput 输入验证深度解析 (src/tools/FileReadTool/FileReadTool.ts:418-494):
// src/tools/FileReadTool/FileReadTool.ts:418-494async validateInput({ file_path, pages }, toolUseContext: ToolUseContext) {// 1. PDF 页码参数验证(纯字符串解析,无 I/O)if (pages !== undefined) {const parsed = parsePDFPageRange(pages)if (!parsed) {return { result: false, message: `Invalid pages parameter: "${pages}". Use formats like "1-5", "3", or "10-20".`, errorCode: 7, } }// 页数限制检查const rangeSize = parsed.lastPage - parsed.firstPage + 1if (rangeSize > PDF_MAX_PAGES_PER_READ) {return { result: false, message: `Page range exceeds maximum of ${PDF_MAX_PAGES_PER_READ} pages.`, errorCode: 8, } } }// 2. 路径扩展 + Deny 规则检查const fullFilePath = expandPath(file_path)const appState = toolUseContext.getAppState()const denyRule = matchingRuleForInput( fullFilePath, appState.toolPermissionContext,'read','deny', )if (denyRule !== null) {return { result: false, message: 'File is in a directory denied by permission settings.', errorCode: 1, } }// 3. 【安全关键】UNC 路径检查(防止 NTLM 凭证泄露)// Windows SMB 认证在 fs 操作时触发,需在权限批准后才执行const isUncPath = fullFilePath.startsWith('\\\\') || fullFilePath.startsWith('//')if (isUncPath) {return { result: true } // 跳过后续 I/O 检查 }// 4. 二进制文件扩展名检查(PDF/图片除外)const ext = path.extname(fullFilePath).toLowerCase()if ( hasBinaryExtension(fullFilePath) && !isPDFExtension(ext) && !IMAGE_EXTENSIONS.has(ext.slice(1)) ) {return { result: false, message: `Cannot read binary ${ext} files. Use appropriate tools.`, errorCode: 4, } }// 5. 【安全关键】阻塞设备文件检查// /dev/random, /dev/zero 等会产生无限输出或阻塞if (isBlockedDevicePath(fullFilePath)) {return { result: false, message: `Cannot read '${file_path}': device file would block.`, errorCode: 9, } }return { result: true }}
安全防护 – 阻塞设备文件列表 (src/tools/FileReadTool/FileReadTool.ts:98-115):
// src/tools/FileReadTool/FileReadTool.ts:98-115/** * 会阻塞或产生无限输出的设备文件路径 * 这些文件必须被阻止读取,否则会导致工具卡死 */const BLOCKED_DEVICE_PATHS = new Set(['/dev/zero', // 无限零字节'/dev/random', // 无限随机字节'/dev/urandom', // 无限随机字节'/dev/full', // 无限写入阻塞'/dev/stdin', // 标准输入阻塞'/dev/tty', // 终端设备阻塞'/dev/console', // 控制台阻塞])
智能去重机制 (src/tools/FileReadTool/FileReadTool.ts:523-572):
// src/tools/FileReadTool/FileReadTool.ts:523-572/** * 去重检查:避免重复发送相同文件内容 * * 如果已读取过相同范围且文件未修改: * - 返回 file_unchanged stub(节省 cache_creation tokens) * - 原始 tool_result 已在上下文中 * * 统计显示 ~18% 的 Read 调用是相同文件碰撞 */const readTimestamp = readFileState.get(fullFilePath)if (readTimestamp) {const currentMtime = getFileModificationTime(fullFilePath)// 文件未修改 + 范围匹配 → 返回 stubif (currentMtime === readTimestamp.timestamp) {const rangeMatch = (offset === readTimestamp.offset || offset === 1) && (limit === readTimestamp.limit || limit === undefined)if (rangeMatch) {return { data: {type: 'file_unchanged', file: { filePath: file_path }, }, } } }}
4.2 FileEditTool – 读-改-写原子性保证
文件: src/tools/FileEditTool/FileEditTool.ts:86-595
// src/tools/FileEditTool/FileEditTool.ts:86-132export const FileEditTool = buildTool({ name: FILE_EDIT_TOOL_NAME, searchHint: 'modify file contents in place', maxResultSizeChars: 100_000, strict: true,// 分类器输入:文件路径 + 新内容 toAutoClassifierInput(input) {return `${input.file_path}: ${input.new_string}` }, getPath(input): string { return input.file_path },// 路径扩展 backfillObservableInput(input) {if (typeof input.file_path === 'string') { input.file_path = expandPath(input.file_path) } },// 权限匹配器async preparePermissionMatcher({ file_path }) {return pattern => matchWildcardPattern(pattern, file_path) },async checkPermissions(input, context): Promise<PermissionDecision> {const appState = context.getAppState()return checkWritePermissionForTool( FileEditTool, input, appState.toolPermissionContext, ) },})
并发修改检测 – 原子性核心 (src/tools/FileEditTool/FileEditTool.ts:275-311):
// src/tools/FileEditTool/FileEditTool.ts:275-311/** * 并发修改检测:确保读-改-写原子性 * * 机制: * 1. 检查文件最后修改时间 * 2. 与上次读取时间戳比较 * 3. Windows 兼容:时间戳不可靠时使用内容比对 */const readTimestamp = toolUseContext.readFileState.get(fullFilePath)if (!readTimestamp || readTimestamp.isPartialView) {return { result: false, behavior: 'ask', message: FILE_UNREAD_ERROR, errorCode: 6, }}// 检查并发修改const lastWriteTime = getFileModificationTime(fullFilePath)if (lastWriteTime > readTimestamp.timestamp) {// Windows timestamp fallback:内容比对避免误报const isFullRead = !readTimestamp.offset && !readTimestamp.limitif (!isFullRead) {// 部分读取 → 无法比对,必须警告return { result: false, behavior: 'ask', message: FILE_UNEXPECTEDLY_MODIFIED_ERROR, errorCode: 11, } }// 全量读取 → 内容比对if (fileContent !== readTimestamp.content) {return { result: false, behavior: 'ask', message: FILE_UNEXPECTEDLY_MODIFIED_ERROR, errorCode: 11, } }}
多处匹配检测 (src/tools/FileEditTool/FileEditTool.ts:312-361):
// src/tools/FileEditTool/FileEditTool.ts:312-361/** * 多处匹配检测:防止意外替换多个位置 * * 场景: * - old_string 在文件中出现多次 * - 未设置 replace_all=true * - 需要明确提示用户 */const matches = countMatches(fileContent, old_string)if (matches > 1 && !replace_all) {return { result: false, behavior: 'ask', message: `Found ${matches} matches. Use replace_all to replace all occurrences.`, errorCode: 12, }}// 无匹配检测if (matches === 0) {// 智能提示:可能原因const suggestions = []// 1. 弯引号 vs 直引号if (old_string.includes("'") || old_string.includes('"')) { suggestions.push('Check for curly quotes vs straight quotes') }// 2. 行尾符差异if (fileContent.includes('\r\n') && !old_string.includes('\r\n')) { suggestions.push('File has CRLF line endings') }return { result: false, behavior: 'ask', message: `No matches found. ${suggestions.join('. ')}`, errorCode: 13, }}
4.3 BashTool – Shell 命令执行与后台任务
文件: src/tools/BashTool/BashTool.tsx:227-294
// src/tools/BashTool/BashTool.tsx:227-259// 完整输入 Schema(包含内部字段)const fullInputSchema = lazySchema(() => z.strictObject({ command: z.string().describe('The command to execute'), timeout: semanticNumber(z.number().optional()) .describe(`Optional timeout in milliseconds (max ${getMaxTimeoutMs()})`), description: z.string().optional() .describe('Clear, concise description of what this command does'), run_in_background: semanticBoolean(z.boolean().optional()) .describe('Set to true to run this command in the background'), dangerouslyDisableSandbox: semanticBoolean(z.boolean().optional()) .describe('Set to true to dangerously override sandbox mode'),// 内部字段:sed 编辑预览结果 _simulatedSedEdit: z.object({ filePath: z.string(), newContent: z.string() }).optional(),}))// 对外 Schema:移除内部字段const inputSchema = lazySchema(() => isBackgroundTasksDisabled ? fullInputSchema().omit({ run_in_background: true, _simulatedSedEdit: true }) : fullInputSchema().omit({ _simulatedSedEdit: true }))// src/tools/BashTool/BashTool.tsx:279-294// 输出 Schemaconst outputSchema = lazySchema(() => z.object({ stdout: z.string().describe('Standard output'), stderr: z.string().describe('Standard error output'), rawOutputPath: z.string().optional().describe('Path for large MCP outputs'), interrupted: z.boolean().describe('Whether command was interrupted'), isImage: z.boolean().optional().describe('stdout contains image data'), backgroundTaskId: z.string().optional().describe('Background task ID'), backgroundedByUser: z.boolean().optional().describe('User manually backgrounded'), assistantAutoBackgrounded: z.boolean().optional().describe('Auto-backgrounded'), persistedOutputPath: z.string().optional().describe('Persisted output path'), persistedOutputSize: z.number().optional().describe('Total output size'),}))
智能分类 – 搜索/读取命令识别 (src/tools/BashTool/BashTool.tsx:95-172):
// src/tools/BashTool/BashTool.tsx:95-172/** * 判断 Bash 命令是否为搜索/读取类型 * 用于 UI 折叠显示 */export functionisSearchOrReadBashCommand(command: string): boolean{// 静默命令列表(只读、无副作用)const BASH_SILENT_COMMANDS = new Set(['ls', 'dir', 'cat', 'head', 'tail', 'less', 'more','grep', 'rg', 'find', 'locate', 'which', 'whereis','wc', 'stat', 'file', 'du', 'df', 'pwd','git status', 'git log', 'git show', 'git diff','npm list', 'pip list', 'brew list', ])// 解析复合命令const partsWithOperators = splitCommandWithOperators(command)for (const part of partsWithOperators) {if (part === '||' || part === '&&' || part === '|' || part === ';') {continue// 跳过操作符 }const baseCommand = part.trim().split(/\s+/)[0]if (!BASH_SILENT_COMMANDS.has(baseCommand)) {return false// 非静默命令 → 不是纯搜索/读取 } }returntrue}
4.4 GlobTool – 文件名模式搜索
文件: src/tools/GlobTool/GlobTool.ts:57-198
// src/tools/GlobTool/GlobTool.ts:26-52// 输入 Schemaconst inputSchema = lazySchema(() => z.strictObject({ pattern: z.string() .describe('The glob pattern to match files against (e.g., "**/*.js")'), path: z.string().optional() .describe('The directory to search in (default: current working directory)'), }))// 输出 Schemaconst outputSchema = lazySchema(() => z.object({ filenames: z.array(z.string()) .describe('Matching file paths, relative to the search directory'), durationMs: z.number().describe('Search duration in milliseconds'), numFiles: z.number().describe('Number of matching files found'), truncated: z.boolean().describe('Whether results were truncated'), }))// src/tools/GlobTool/GlobTool.ts:57-149export const GlobTool = buildTool({ name: GLOB_TOOL_NAME, searchHint: 'find files by name pattern or wildcard', maxResultSizeChars: 100_000, isConcurrencySafe() { return true }, isReadOnly() { return true }, isSearchOrReadCommand() {return { isSearch: true, isRead: false } }, toAutoClassifierInput(input) { return input.pattern },async validateInput({ path }, context) {// UNC 路径安全检查if (path?.startsWith('\\\\') || path?.startsWith('//')) {return { result: true } }// 路径扩展const fullPath = path ? expandPath(path) : getCwd()// Deny 规则检查const denyRule = matchingRuleForInput(fullPath, ...)if (denyRule !== null) {return { result: false, message: 'Directory denied by permissions', errorCode: 1 } }return { result: true } },async call(input, { abortController, globLimits }) {const limit = globLimits?.maxResults ?? 100const { files, truncated } = await glob( input.pattern, path, { limit, offset: 0 }, abortController.signal, )// 相对路径转换(节省 token)const filenames = files.map(toRelativePath)return { data: { filenames, durationMs, numFiles: filenames.length, truncated, }, } },})
4.5 GrepTool – 内容搜索神器
文件: src/tools/GrepTool/GrepTool.ts:33-156
// src/tools/GrepTool/GrepTool.ts:33-89// 输入 Schemaconst inputSchema = lazySchema(() => z.strictObject({ pattern: z.string() .describe('The regular expression pattern to search for'), path: z.string().optional() .describe('The directory to search in'), output_mode: z.enum(['content', 'files_with_matches', 'count']) .default('content') .describe('Output mode: content (lines), files_with_matches, or count'), glob: z.string().optional() .describe('Glob pattern to filter files'),type: z.string().optional() .describe('File type filter (js, py, rust, etc.)'), head_limit: semanticNumber(z.number().int().positive().optional()) .describe('Limit output to first N results (default: 250)'), }))// 默认结果限制const DEFAULT_HEAD_LIMIT = 250// src/tools/GrepTool/GrepTool.ts:160-577export const GrepTool = buildTool({ name: GREP_TOOL_NAME, searchHint: 'search file contents with regex (ripgrep)', maxResultSizeChars: 100_000, isConcurrencySafe() { return true }, isReadOnly() { return true }, isSearchOrReadCommand() {return { isSearch: true, isRead: false } },async call(input, context) {const args = ['--hidden', '--max-columns', '500']// 排除版本控制目录for (const dir of ['.git', '.svn', '.hg', '.jj']) { args.push('--glob', `!${dir}`) }// 输出模式处理if (output_mode === 'files_with_matches') { args.push('-l') // 只输出文件名// 按修改时间排序(最近修改优先)const sortedMatches = results.sort((a, b) => b.mtimeMs - a.mtimeMs) }if (output_mode === 'count') { args.push('-c') // 统计匹配数 }// 应用结果限制const limit = head_limit ?? DEFAULT_HEAD_LIMIT args.push('--max-count', String(limit))return { data: { mode: output_mode, filenames, content, numMatches, }, } },})
4.6 AgentTool – 子代理执行与 Fork 优化
文件: src/tools/AgentTool/AgentTool.tsx:82-300
// src/tools/AgentTool/AgentTool.tsx:82-138// 输入 Schemaconst baseInputSchema = lazySchema(() => z.strictObject({ description: z.string() .describe('A short (3-5 word) description of what the agent will do'), prompt: z.string() .describe('The task for the agent to perform'), subagent_type: z.string().optional() .describe('Specialized agent type (general-purpose, Explore, Plan, etc.)'), model: z.enum(['sonnet', 'opus', 'haiku']).optional() .describe('Optional model override for the subagent'), run_in_background: z.boolean().optional() .describe('Run the agent in the background'), isolation: z.enum(['worktree', 'remote']).optional() .describe('Isolation mode: worktree (git worktree) or remote (CCR)'), }))// src/tools/AgentTool/AgentTool.tsx:196-300export const AgentTool = buildTool({ name: AGENT_TOOL_NAME, searchHint: 'spawn sub-agent for parallel work', maxResultSizeChars: 100_000,// 并发安全 isConcurrencySafe() { return true },async call(input, context) {// Fork 优化:共享父进程的 prompt cacheif (isForkSubagentEnabled()) {const messages = buildForkedMessages(prompt, context)// 使用父进程的 renderedSystemPrompt 避免 cache bust }// 隔离模式:Worktreeif (input.isolation === 'worktree') {const worktreePath = await createAgentWorktree()// 在独立的 git worktree 中执行 }// 隔离模式:Remote(CCR 远程执行)if (input.isolation === 'remote') {return await teleportToRemote(taskDef) }// 后台执行if (input.run_in_background) {const taskId = await spawnBackgroundAgent(...)// 完成后通知主线程 }return { data: { result: agentResult,// ... }, } },})
4.7 MCPTool – 动态工具模板
文件: src/tools/MCPTool/MCPTool.ts:1-77
// src/tools/MCPTool/MCPTool.ts:1-77/** * MCP 工具模板 * * 这是一个占位工具定义,在 MCP 客户端连接时 * 会动态覆盖 name、description、call 等方法 */export const MCPTool = buildTool({ isMcp: true, name: 'mcp', // 在 mcpClient.ts 中动态覆盖 maxResultSizeChars: 100_000,// 动态 Schema:接受任意输入 inputSchema: lazySchema(() => z.object({}).passthrough()), outputSchema: lazySchema(() => z.string()),// 权限透传:让通用权限系统处理async checkPermissions(): Promise<PermissionResult> {return { behavior: 'passthrough', message: 'MCPTool requires permission.' } },// 占位实现,在 mcpClient.ts 中动态注入async call() {return { data: '' } },async description() {return DESCRIPTION },async prompt() {return PROMPT },})
关键设计:MCP 工具是动态创建的模板,在 MCP 客户端连接时被实例化为具体工具,通过 mcpInfo 属性记录原始服务器和工具名称。
4.8 ToolSearchTool – 工具发现系统
文件: src/tools/ToolSearchTool/ToolSearchTool.ts:304-471
// src/tools/ToolSearchTool/ToolSearchTool.ts:21-45// 输入 Schemaconst inputSchema = lazySchema(() => z.strictObject({ query: z.string() .describe('Query to find deferred tools. Use "select:ToolName" for direct selection'), max_results: semanticNumber(z.number().int().positive().optional()) .describe('Maximum number of results (default: 5)'), }))// src/tools/ToolSearchTool/ToolSearchTool.ts:304-471export const ToolSearchTool = buildTool({ name: TOOL_SEARCH_TOOL_NAME,async call({ query, max_results }, context) {const deferredTools = tools.filter(isDeferredTool)// 直接选择:select:ToolName 格式if (query.startsWith('select:')) {const toolName = query.slice('select:'.length)return { matches: [toolName], total_deferred_tools: deferredTools.length, } }// 关键词搜索const matches = await searchToolsWithKeywords(query, deferredTools)return { matches: matches.slice(0, max_results ?? 5), total_deferred_tools: deferredTools.length, } },// 特殊序列化:返回 tool_reference 类型 mapToolResultToToolResultBlockParam(content, toolUseID) {return {type: 'tool_result', tool_use_id: toolUseID, content: content.matches.map(name => ({type: 'tool_reference', tool_name: name, })), } },})
5. 关键上下文/系统设计 (Context & Sub-systems)
5.1 ToolUseContext 执行上下文详解
文件: src/Tool.ts:158-300
// src/Tool.ts:158-300export type ToolUseContext = {// ==================== 配置选项 ==================== options: { commands: Command[] // 可用命令列表 debug: boolean// 调试模式 mainLoopModel: string// 主循环模型 tools: Tools // 可用工具列表 verbose: boolean// 详细模式 thinkingConfig: ThinkingConfig // 思考配置 mcpClients: MCPServerConnection[] // MCP 客户端连接 mcpResources: Record<string, ServerResource[]> // MCP 资源 isNonInteractiveSession: boolean// 非交互会话标志 agentDefinitions: AgentDefinitionsResult // 代理定义 maxBudgetUsd?: number// 预算限制 customSystemPrompt?: string// 自定义系统提示词 appendSystemPrompt?: string// 追加系统提示词 querySource?: QuerySource // 查询来源追踪 refreshTools?: () => Tools // 工具刷新回调 }// ==================== 状态管理 ==================== abortController: AbortController // 中止控制器 readFileState: FileStateCache // 文件读取状态缓存 getAppState(): AppState // 获取应用状态 setAppState(f: (prev: AppState) => AppState): void// 设置应用状态 setAppStateForTasks?: (f: (prev: AppState) => AppState) => void// 任务状态设置// ==================== UI 集成 ==================== setToolJSX?: SetToolJSXFn // 设置工具 JSX 渲染 addNotification?: (notif: Notification) => void// 添加通知 appendSystemMessage?: (msg) => void// 追加系统消息 sendOSNotification?: (opts) => void// 发送 OS 通知// ==================== 技能触发器 ==================== nestedMemoryAttachmentTriggers?: Set<string> // CLAUDE.md 嵌入触发 loadedNestedMemoryPaths?: Set<string> // 已加载的嵌套记忆路径 dynamicSkillDirTriggers?: Set<string> // 动态技能目录触发 discoveredSkillNames?: Set<string> // 已发现技能名称// ==================== 消息与状态 ==================== userModified?: boolean// 用户修改标志 setInProgressToolUseIDs: (f: (prev: Set<string>) => Set<string>) => void setHasInterruptibleToolInProgress?: (v: boolean) => void setResponseLength: (f: (prev: number) => number) => void messages: Message[] // 消息历史// ==================== 限制配置 ==================== fileReadingLimits?: { maxTokens?: number maxSizeBytes?: number } globLimits?: { maxResults?: number }// ==================== 权限追踪 ==================== toolDecisions?: Map<string, { source: string decision: 'accept' | 'reject' timestamp: number }> queryTracking?: QueryChainTracking // 查询链追踪 localDenialTracking?: DenialTrackingState // 本地拒绝追踪 contentReplacementState?: ContentReplacementState // 内容替换状态// ==================== 其他 ==================== requestPrompt?: (sourceName, toolInputSummary) => (request) => Promise<PromptResponse> handleElicitation?: (serverName, params, signal) => Promise<ElicitResult> toolUseId?: string// 工具调用 ID agentId?: AgentId // 代理 ID(子代理专用) agentType?: string// 代理类型 renderedSystemPrompt?: SystemPrompt // 父进程渲染的系统提示词(Fork 优化)}
5.2 ToolPermissionContext 权限上下文
文件: src/Tool.ts:123-138
// src/Tool.ts:123-138/** * 工具权限上下文 * 使用 DeepImmutable 确保配置不可变 */export type ToolPermissionContext = DeepImmutable<{ mode: PermissionMode // 权限模式 (default, accept, plan, auto) additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory> // 额外工作目录// ==================== 规则集 ==================== alwaysAllowRules: ToolPermissionRulesBySource // 始终允许规则 alwaysDenyRules: ToolPermissionRulesBySource // 始终拒绝规则 alwaysAskRules: ToolPermissionRulesBySource // 始终询问规则// ==================== 特殊标志 ==================== isBypassPermissionsModeAvailable: boolean// 绕过权限模式可用 isAutoModeAvailable?: boolean// 自动模式可用 strippedDangerousRules?: ToolPermissionRulesBySource // 剥离的危险规则 shouldAvoidPermissionPrompts?: boolean// 避免权限提示(后台代理) awaitAutomatedChecksBeforeDialog?: boolean// 等待自动检查后再显示对话框 prePlanMode?: PermissionMode // 计划模式前的权限模式}>
5.3 工具池组装机制
文件: src/tools.ts:193-367
// src/tools.ts:193-251/** * 获取所有基础工具列表 * 这是所有工具的注册中心 * * 注意:必须与 Statsig 配置保持同步以支持系统提示词缓存 */export functiongetAllBaseTools(): Tools{return [ AgentTool, TaskOutputTool, BashTool, ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]), ExitPlanModeV2Tool, FileReadTool, FileEditTool, FileWriteTool, NotebookEditTool, WebFetchTool, TodoWriteTool, WebSearchTool, TaskStopTool, AskUserQuestionTool, SkillTool, EnterPlanModeTool, ...(process.env.USER_TYPE === 'ant' ? [ConfigTool] : []), ...(process.env.USER_TYPE === 'ant' ? [TungstenTool] : []),// ... 条件性工具(根据 feature flags) ...(isTodoV2Enabled() ? [TaskCreateTool, TaskGetTool, TaskUpdateTool, TaskListTool] : []), ...(isWorktreeModeEnabled() ? [EnterWorktreeTool, ExitWorktreeTool] : []), getSendMessageTool(), ...(isAgentSwarmsEnabled() ? [getTeamCreateTool(), getTeamDeleteTool()] : []), ListMcpResourcesTool, ReadMcpResourceTool, ...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []), ]}// src/tools.ts:262-269/** * 按 Deny 规则过滤工具 * 工具级拒绝规则:规则无内容时匹配整个工具 */export functionfilterToolsByDenyRules<Textends{ name: string; mcpInfo?: { serverName: string; toolName: string } },>(tools: readonly T[], permissionContext: ToolPermissionContext): T[] {return tools.filter(tool => !getDenyRuleForTool(permissionContext, tool))}// src/tools.ts:345-367/** * 组装完整工具池(内置 + MCP) * * 这是工具池组装的单一真实来源: * 1. 获取内置工具(根据权限过滤) * 2. 过滤 MCP 工具 * 3. 去重合并(内置工具优先) * 4. 排序(Prompt Cache 稳定性) */export functionassembleToolPool( permissionContext: ToolPermissionContext, mcpTools: Tools,): Tools{const builtInTools = getTools(permissionContext)const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)// 排序策略:按名称排序,内置工具作为连续前缀// Prompt Cache 在最后一个内置工具后设置断点const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name)return uniqBy( [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),'name', )}
5.4 工具可用性控制常量
文件: src/constants/tools.ts
// src/constants/tools.ts:36-71/** * 所有代理禁用的工具集 * 这些工具不能在子代理中使用 */export const ALL_AGENT_DISALLOWED_TOOLS = new Set(['TaskOutput', // 防止递归调用'ExitPlanMode', // 计划模式是主线程抽象'TaskStop', // 需要主线程状态'Agent', // 防止嵌套(ant 类型除外)])/** * 异步代理允许的工具集 * 后台代理可以使用的工具列表 */export const ASYNC_AGENT_ALLOWED_TOOLS = new Set(['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob','WebFetch', 'WebSearch', 'Skill', 'NotebookEdit',])/** * 协调器模式允许的工具集 * 协调器(Coordinator)专用工具 */export const COORDINATOR_MODE_ALLOWED_TOOLS = new Set(['Agent', // 协调器核心:派发任务给工作者'TaskStop', // 停止工作者任务'SendMessage', // 发送消息给工作者'SyntheticOutput', // 合成输出工具])
6. 关键代码片段总结 (Code Snippets Cheatsheet)
6.1 工具定义模板
// 完整工具定义模板// src/Tool.ts:783-792 + src/tools/GlobTool/GlobTool.ts:57+export const MyTool = buildTool({ name: 'MyTool', searchHint: 'brief capability description (3-10 words)', maxResultSizeChars: 100_000, strict: true,// Schema 定义get inputSchema(): InputSchema { return inputSchema() },get outputSchema(): OutputSchema { return outputSchema() },// 安全属性 isConcurrencySafe() { return true }, // 或 false isReadOnly() { return true }, // 或 false isDestructive() { return false }, // 仅破坏性操作设 true// 分类器输入 toAutoClassifierInput(input) { return input.someField },// UI 分类(可选) isSearchOrReadCommand() { return { isSearch: true, isRead: false } },// 路径类工具必须实现 getPath(input): string { return input.file_path }, backfillObservableInput(input) {if (typeof input.file_path === 'string') { input.file_path = expandPath(input.file_path) } },async preparePermissionMatcher({ file_path }) {return pattern => matchWildcardPattern(pattern, file_path) },// 权限检查async checkPermissions(input, context): Promise<PermissionDecision> {const appState = context.getAppState()return checkWritePermissionForTool(MyTool, input, appState.toolPermissionContext) },// 输入验证(可选但推荐)async validateInput(input, context): Promise<ValidationResult> {// 1. 路径安全检查const fullPath = expandPath(input.file_path)// 2. Deny 规则检查// 3. 格式/范围验证return { result: true } },// 核心执行async call(input, context, canUseTool, parentMessage, onProgress) {// 执行逻辑return { data: result, newMessages?: [...], contextModifier?: (ctx) => modifiedCtx, } },// UI 渲染方法 renderToolUseMessage, renderToolResultMessage, mapToolResultToToolResultBlockParam(data, toolUseID) {return {type: 'tool_result', tool_use_id: toolUseID, content: serializedContent, } },})
6.2 lazySchema 使用模式
// src/utils/lazySchema.ts + 实际使用示例const inputSchema = lazySchema(() => z.strictObject({ file_path: z.string().describe('Absolute path...'), offset: semanticNumber(z.number().int().nonnegative().optional()), limit: semanticNumber(z.number().int().positive().optional()), }))type InputSchema = ReturnType<typeof inputSchema>exporttype Input = z.infer<InputSchema>
6.3 并发控制核心逻辑
// src/services/tools/StreamingToolExecutor.ts:129-135private canExecuteTool(isConcurrencySafe: boolean): boolean {const executingTools = this.tools.filter(t => t.status === 'executing')return ( executingTools.length === 0 || (isConcurrencySafe && executingTools.every(t => t.isConcurrencySafe)) )}
6.4 文件编辑原子性保证
// src/tools/FileEditTool/FileEditTool.ts:275-311// 并发修改检测const lastWriteTime = getFileModificationTime(fullFilePath)const lastRead = readFileState.get(fullFilePath)if (lastWriteTime > lastRead.timestamp) {// Windows fallback: 内容比对const isFullRead = !lastRead.offset && !lastRead.limitif (!isFullRead || fileContent !== lastRead.content) {throw new Error(FILE_UNEXPECTEDLY_MODIFIED_ERROR) }}
6.5 UNC 路径安全检查
// src/tools/FileReadTool/FileReadTool.ts:463-467 + FileEditTool:179-181// 防止 NTLM 凭证泄露const isUncPath = fullFilePath.startsWith('\\\\') || fullFilePath.startsWith('//')if (isUncPath) {return { result: true } // 跳过 I/O 检查,等权限批准后再操作}
6.6 权限规则匹配
// src/utils/permissions/permissions.ts:238-269functiontoolMatchesRule(tool: Pick<Tool, 'name' | 'mcpInfo'>, rule: PermissionRule): boolean{// 无内容规则 → 匹配整个工具if (rule.ruleValue.ruleContent === undefined) {return rule.ruleValue.toolName === tool.name }// MCP server-level 匹配const ruleInfo = mcpInfoFromString(rule.ruleValue.toolName)const toolInfo = mcpInfoFromString(tool.name)return ruleInfo?.serverName === toolInfo?.serverName}
6.7 进度消息回调
// 进度报告模式async call(input, context, canUseTool, parentMessage, onProgress) {// 执行过程中报告进度 onProgress?.({ toolUseID: parentMessage.message.id, data: {type: 'bash_progress', pid: process.pid, output: partialOutput, }, })// 最终返回结果return { data: finalResult }}
7. 总结 (Summary)
设计模式总结表
|
|
|
|
|---|---|---|
| 工厂模式 | buildTool
|
|
| 策略模式 | isConcurrencySafe
validateInput/checkPermissions |
|
| 观察者模式 | ToolCallProgress
|
|
| 模板方法模式 | Tool
|
|
| 延迟初始化 | lazySchema
|
|
安全最佳实践总结表
|
|
|
|
|---|---|---|
| UNC 路径防护 |
|
|
| 设备文件阻断 |
|
|
| 并发修改检测 |
|
|
| 多处匹配警告 |
|
|
| 二进制文件拒绝 |
|
|
| 路径遍历检测 |
|
|
| 沙箱隔离 |
|
|
性能优化策略总结表
|
|
|
|
|---|---|---|
| 延迟加载 | lazySchema
shouldDefer |
|
| 智能去重 |
|
|
| 相对路径 |
|
|
| 结果持久化 |
|
|
| 并发执行 |
|
|
| Prompt Cache 稳定排序 |
|
|
核心文件索引表
|
|
|
|
|---|---|---|
| Tool 类型定义 | src/Tool.ts |
|
| 工具注册中心 | src/tools.ts |
|
| 流式执行器 | src/services/tools/StreamingToolExecutor.ts |
|
| 权限类型 | src/types/permissions.ts |
|
| 权限检查 | src/utils/permissions/permissions.ts |
|
| FileReadTool | src/tools/FileReadTool/FileReadTool.ts |
|
| FileEditTool | src/tools/FileEditTool/FileEditTool.ts |
|
| BashTool | src/tools/BashTool/BashTool.tsx |
|
| AgentTool | src/tools/AgentTool/AgentTool.tsx |
|
| 工具常量 | src/constants/tools.ts |
|
Claude Code 的工具系统展现了一个成熟的 AI Agent 工具框架应有的设计水平:类型安全的泛型架构、分层治理的权限体系、智能的并发调度、流式的进度反馈、以及完善的 MCP 集成。这个架构值得任何构建 AI Agent 工具系统的开发者深入学习和借鉴。
夜雨聆风