Claude Code 工具系统源码解读
Claude Code 工具系统源码解读
版本基于 main 分支 | 源码目录:
/mnt/e/code/cc/claude-code-main
概述
Claude Code 的工具系统(Tools)是其 Agent 能力的核心基础设施,允许 AI 模型通过工具调用(Tool Use)与文件系统、Shell、第三方服务进行交互。本文档深入分析工具系统的五个核心维度:注册与发现、执行与隔离、结果序列化、错误处理、内置工具实现。
1. 工具注册与发现机制
1.1 核心入口
工具系统的入口位于 src/tools.ts,负责所有内置工具的注册、过滤和组合。
// src/tools.ts
export function getAllBaseTools(): Tools {
return [
AgentTool,
TaskOutputTool,
BashTool,
// ... 条件编译的工具
...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
// ... 更多工具
]
}1.2 工具构建工厂
src/Tool.ts 中的 buildTool() 工厂函数是所有工具的统一入口:
// src/Tool.ts (L783)
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
return {
...TOOL_DEFAULTS,
userFacingName: () => def.name,
...def,
} as BuiltTool<D>
}默认属性填充(L757-769):
| 默认方法 | 默认行为 | 安全策略 |
|---|---|---|
isEnabled | () => true | - |
isConcurrencySafe | () => false | 默认不安全,需显式声明 |
isReadOnly | () => false | 默认写入操作 |
isDestructive | () => false | 默认非破坏性 |
checkPermissions | { behavior: 'allow' } | 默认允许 |
toAutoClassifierInput | () => '' | 跳过分类器 |
userFacingName | () => name | 回退到工具名 |
1.3 工具类型定义
Tool 接口(L362-695)是整个系统的核心类型契约:
export type Tool<
Input extends AnyObject = AnyObject,
Output = unknown,
P extends ToolProgressData = ToolProgressData,
> = {
readonly name: string
readonly inputSchema: Input
readonly outputSchema?: z.ZodType<unknown>
readonly maxResultSizeChars: number // 大结果阈值
// 核心方法
call(args, context, canUseTool, parentMessage, onProgress?): Promise<ToolResult<Output>>
description(input, options): Promise<string>
prompt(options): Promise<string>
// 安全相关
isConcurrencySafe(input): boolean
isReadOnly(input): boolean
isDestructive?(input): boolean
validateInput?(input, context): Promise<ValidationResult>
checkPermissions(input, context): Promise<PermissionResult>
// UI 渲染
renderToolUseMessage(input, options): React.ReactNode
renderToolResultMessage(content, progress, options): React.ReactNode
renderToolUseProgressMessage?(progress, options): React.ReactNode
// 元数据
aliases?: string[] // 向后兼容的别名
searchHint?: string // 关键词提示
isMcp?: boolean
isLsp?: boolean
shouldDefer?: boolean // 延迟加载
alwaysLoad?: boolean // 始终加载
}1.4 工具池组装
// src/tools.ts (L345-367)
export function assembleToolPool(
permissionContext: ToolPermissionContext,
mcpTools: Tools,
): Tools {
const builtInTools = getTools(permissionContext)
const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)
// 按名称排序保持提示词缓存稳定
const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name)
return uniqBy(
[...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
'name',
)
}关键设计:内置工具按名称排序后作为连续前缀,MCP 工具在后面。这是为了配合服务器的缓存策略——缓存断点放在最后一个内置工具之后。
1.5 条件注册机制
工具通过 feature() 标志和 process.env 实现条件编译:
// src/tools.ts
const cronTools = feature('AGENT_TRIGGERS')
? [
require('./tools/ScheduleCronTool/CronCreateTool.js').CronCreateTool,
require('./tools/ScheduleCronTool/CronDeleteTool.js').CronDeleteTool,
require('./tools/ScheduleCronTool/CronListTool.js').CronListTool,
]
: []
const MonitorTool = feature('MONITOR_TOOL')
? require('./tools/MonitorTool/MonitorTool.js').MonitorTool
: null1.6 工具拒绝规则
// src/tools.ts
export function filterToolsByDenyRules<
T extends {
name: string
mcpInfo?: { serverName: string; toolName: string }
},
>(tools: readonly T[], permissionContext: ToolPermissionContext): T[] {
return tools.filter(tool => !getDenyRuleForTool(permissionContext, tool))
}1.7 协调器模式工具过滤
COORDINATOR_MODE 下仅允许特定工具:
// src/constants/tools.ts
export const COORDINATOR_MODE_ALLOWED_TOOLS = new Set([
AGENT_TOOL_NAME,
TASK_STOP_TOOL_NAME,
SEND_MESSAGE_TOOL_NAME,
SYNTHETIC_OUTPUT_TOOL_NAME,
])2. 延迟加载与 ToolSearch 机制
2.1 延迟加载概述
当 MCP 工具数量庞大时,工具定义本身可能消耗大量上下文。ToolSearch 机制允许:
- 初始仅发送
defer_loading: true的工具声明 - 模型通过
ToolSearchTool按需发现和加载工具 - 工具引用(
tool_reference)在消息历史中追踪已发现工具
2.2 工具搜索模式
// src/utils/toolSearch.ts
export type ToolSearchMode = 'tst' | 'tst-auto' | 'standard'
// tst: 始终延迟 MCP 和 shouldDefer 工具
// tst-auto: 超过阈值后自动启用
// standard: 所有工具直接暴露
阈值计算:
function getAutoToolSearchTokenThreshold(model: string): number {
const betas = getMergedBetas(model)
const contextWindow = getContextWindowForModel(model, betas)
const percentage = getAutoToolSearchPercentage() / 100 // 默认 10%
return Math.floor(contextWindow * percentage)
}2.3 已发现工具追踪
// src/utils/toolSearch.ts
export function extractDiscoveredToolNames(messages: Message[]): Set<string> {
// 从 tool_result 中提取 tool_reference 块
// 跨会话持久化(compact 边界保存快照)
}2.4 延迟工具池变化检测
// src/utils/toolSearch.ts
export function getDeferredToolsDelta(
tools: Tools,
messages: Message[],
): DeferredToolsDelta | null {
// 检测新增/移除的延迟工具
// 通过 deferred_tools_delta 附件通知模型
}3. 工具执行流程与隔离
3.1 执行入口
src/services/tools/toolExecution.ts 中的 runToolUse() 是工具执行的起点:
// src/services/tools/toolExecution.ts (L337)
export async function* runToolUse(
toolUse: ToolUseBlock,
assistantMessage: AssistantMessage,
canUseTool: CanUseToolFn,
toolUseContext: ToolUseContext,
): AsyncGenerator<MessageUpdateLazy, void>3.2 完整执行流程
┌─────────────────────────────────────────────────────────────┐
│ 1. 工具查找 (L345-356) │
│ findToolByName() → 工具实例 │
│ 别名回退查找 → 支持旧名称 │
├─────────────────────────────────────────────────────────────┤
│ 2. 输入验证 (L615-680) │
│ tool.inputSchema.safeParse() → Zod 验证 │
│ tool.validateInput?() → 自定义验证 │
├─────────────────────────────────────────────────────────────┤
│ 3. PreToolUse Hooks (L800-862) │
│ runPreToolUseHooks() → 权限前置检查 │
├─────────────────────────────────────────────────────────────┤
│ 4. 权限决策 (L921-1104) │
│ resolveHookPermissionDecision() │
│ canUseTool() → 用户交互 │
├─────────────────────────────────────────────────────────────┤
│ 5. 工具调用 (L1207-1222) │
│ tool.call() → 实际执行 │
├─────────────────────────────────────────────────────────────┤
│ 6. PostToolUse Hooks (L1483-1531) │
│ runPostToolUseHooks() → 后置处理 │
├─────────────────────────────────────────────────────────────┤
│ 7. 结果序列化 (L1403-1478) │
│ processToolResultBlock() → 持久化/映射 │
└─────────────────────────────────────────────────────────────┘3.3 并发安全与隔离
工具编排器 (src/services/tools/toolOrchestration.ts):
// 核心分区逻辑
function partitionToolCalls(
toolUseMessages: ToolUseBlock[],
toolUseContext: ToolUseContext,
): Batch[] {
return toolUseMessages.reduce((acc: Batch[], toolUse) => {
const tool = findToolByName(toolUseContext.options.tools, toolUse.name)
const isConcurrencySafe = tool?.isConcurrencySafe(parsedInput.data) ?? false
if (isConcurrencySafe && acc[acc.length - 1]?.isConcurrencySafe) {
// 可并发工具合并批次
acc[acc.length - 1]!.blocks.push(toolUse)
} else {
// 非并发安全工具单独批次
acc.push({ isConcurrencySafe, blocks: [toolUse] })
}
return acc
}, [])
}并发执行:runToolsConcurrently() 使用 all() 生成器实现:
- 可并发工具(只读)并行执行
- 非并发安全工具串行执行
- 最大并发数:
CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY(默认 10)
3.4 流式执行器
StreamingToolExecutor(src/services/tools/StreamingToolExecutor.ts)处理流式场景:
export class StreamingToolExecutor {
private tools: TrackedTool[] = []
// 核心调度逻辑
private canExecuteTool(isConcurrencySafe: boolean): boolean {
const executingTools = this.tools.filter(t => t.status === 'executing')
return (
executingTools.length === 0 ||
(isConcurrencySafe && executingTools.every(t => t.isConcurrencySafe))
)
}
}关键设计:
- Bash 错误级联:Bash 工具错误会中止所有兄弟工具
- 中断行为:通过
interruptBehavior()控制 ('cancel'|'block') - 进度优先:进度消息立即 yield,完整结果按顺序返回
3.5 进度报告机制
// toolExecution.ts L140-168
function streamedCheckPermissionsAndCallTool(...) {
const stream = new Stream<MessageUpdateLazy>()
checkPermissionsAndCallTool(
tool,
toolUseID,
input,
toolUseContext,
canUseTool,
assistantMessage,
messageId,
requestId,
mcpServerType,
mcpServerBaseUrl,
progress => {
// 进度事件入队
stream.enqueue({
message: createProgressMessage({
toolUseID: progress.toolUseID,
parentToolUseID: toolUseID,
data: progress.data,
}),
})
},
)
}4. 工具结果序列化
4.1 Schema 缓存
工具模式在提示词中位于位置 2(系统提示之前),任何字节级变化都会破坏整个 ~11K token 的工具块。toolSchemaCache.ts 实现会话级缓存:
// src/utils/toolSchemaCache.ts
const TOOL_SCHEMA_CACHE = new Map<string, CachedSchema>()
export function clearToolSchemaCache(): void {
TOOL_SCHEMA_CACHE.clear()
}缓存失效场景:
- GrowthBook 特性开关变化(
tengu_tool_pear,tengu_fgts) - MCP 服务器重连
tool.prompt()中的动态内容
4.2 结果存储策略
src/utils/toolResultStorage.ts 实现大结果持久化:
// 持久化阈值判断
export function getPersistenceThreshold(
toolName: string,
declaredMaxResultSizeChars: number,
): number {
// Infinity = 硬性拒绝持久化(如 Read 工具)
if (!Number.isFinite(declaredMaxResultSizeChars)) {
return declaredMaxResultSizeChars
}
// GrowthBook 覆盖 > 硬编码默认值
const overrides = getFeatureValue_CACHED_MAY_BE_STALE(...)
if (typeof override === 'number') {
return override
}
return Math.min(declaredMaxResultSizeChars, DEFAULT_MAX_RESULT_SIZE_CHARS)
}4.3 大结果持久化流程
// 持久化决策
async function maybePersistLargeToolResult(
toolResultBlock: ToolResultBlockParam,
toolName: string,
persistenceThreshold?: number,
): Promise<ToolResultBlockParam> {
// 1. 空结果处理(防止尾随停止序列问题)
if (isToolResultContentEmpty(content)) {
return { ...toolResultBlock, content: `(${toolName} completed with no output)` }
}
// 2. 跳过图片内容
if (hasImageBlock(content)) {
return toolResultBlock
}
// 3. 大小检查
const size = contentSize(content)
if (size <= threshold) {
return toolResultBlock
}
// 4. 持久化到磁盘
const result = await persistToolResult(content, toolResultBlock.tool_use_id)
return { ...toolResultBlock, content: buildLargeToolResultMessage(result) }
}持久化路径:{projectDir}/{sessionId}/tool-results/{id}.txt|.json
4.4 消息级预算
工具结果不仅在单工具级别控制,还在消息聚合级别限制:
// 按 API 级别消息分组
function collectCandidatesByMessage(messages: Message[]): ToolResultCandidate[][] {
// 同一次 AssistantMessage 的 tool_result 合并计算
// 确保 normalizeMessagesForAPI 的合并行为一致
}
// 预算执行
export async function enforceToolResultBudget(
messages: Message[],
state: ContentReplacementState,
skipToolNames: ReadonlySet<string> = new Set(),
): Promise<{ messages: Message[]; newlyReplaced: ToolResultReplacementRecord[] }>预算策略:
- 优先持久化最大的新鲜结果
- 已见结果(
seenIds)的命运被冻结 - 缓存的替换(
replacements)保证提示词缓存稳定
4.5 结果预览生成
export function generatePreview(
content: string,
maxBytes: number,
): { preview: string; hasMore: boolean } {
// 在换行符边界截断,避免切割行
const lastNewline = truncated.lastIndexOf('\n')
const cutPoint = lastNewline > maxBytes * 0.5 ? lastNewline : maxBytes
return { preview: content.slice(0, cutPoint), hasMore: true }
}4.6 内容替换状态
跨会话保持一致性:
// src/utils/toolResultStorage.ts
export type ContentReplacementState = {
seenIds: Set<string> // 已见过的 tool_use_id
replacements: Map<string, string> // id → 替换文本
}设计目标:缓存共享 fork(如 agentSummary)需要相同决策保证提示词前缀一致。
5. 工具错误处理
5.1 错误分类
src/services/tools/toolExecution.ts 中的 classifyToolError():
export function classifyToolError(error: unknown): string {
// 1. TelemetrySafeError → 使用 telemetryMessage
if (error instanceof TelemetrySafeError) {
return error.telemetryMessage.slice(0, 200)
}
// 2. Node.js 文件系统错误 → 使用错误码 (ENOENT, EACCES)
if (error instanceof Error) {
const errnoCode = getErrnoCode(error)
if (typeof errnoCode === 'string') {
return `Error:${errnoCode}`
}
// 3. 命名错误类型 (ShellError, ImageSizeError)
if (error.name && error.name !== 'Error' && error.name.length > 3) {
return error.name.slice(0, 60)
}
return 'Error'
}
return 'UnknownError'
}5.2 错误格式化
src/utils/toolErrors.ts:
export function formatError(error: unknown): string {
if (error instanceof AbortError) {
return error.message || INTERRUPT_MESSAGE_FOR_TOOL_USE
}
// 截断长错误信息(保留首尾)
const parts = getErrorParts(error)
const fullMessage = parts.filter(Boolean).join('\n').trim()
if (fullMessage.length <= 10000) {
return fullMessage
}
const halfLength = 5000
return `${start}\n\n... [${fullMessage.length - 10000} characters truncated] ...\n\n${end}`
}
export function formatZodValidationError(
toolName: string,
error: ZodError,
): string {
// 分类错误:缺失参数、意外参数、类型不匹配
const missingParams = error.issues
.filter(err => err.code === 'invalid_type' && err.message.includes('received undefined'))
.map(err => formatValidationPath(err.path))
const unexpectedParams = error.issues
.filter(err => err.code === 'unrecognized_keys')
.flatMap(err => err.keys)
const typeMismatchParams = error.issues
.filter(err => err.code === 'invalid_type' && !err.message.includes('received undefined'))
// ...
}5.3 错误处理流程
工具调用失败
│
├─→ AbortError → 中断消息
│
├─→ ShellError → Exit code + stderr + stdout
│
├─→ McpAuthError → MCP 客户端状态 → 'needs-auth'
│
└─→ 其他错误 → 格式化 → 日志记录
│
├─→ PostToolUseFailureHooks
│
└─→ 错误消息返回给模型5.4 权限拒绝处理
权限拒绝不触发常规错误,而是生成特殊的 tool_result:
// 权限拒绝结果
const messageContent: ContentBlockParam[] = [
{
type: 'tool_result',
content: errorMessage,
is_error: true,
tool_use_id: toolUseID,
},
]6. 内置工具实现
6.1 工具实现模式
以 BashTool 为例 (src/tools/BashTool/BashTool.tsx):
export const BashTool = buildTool({
name: BASH_TOOL_NAME,
searchHint: 'execute shell commands',
maxResultSizeChars: 30_000,
strict: true,
// Zod 输入/输出模式
get inputSchema(): InputSchema {
return inputSchema()
},
get outputSchema(): OutputSchema {
return outputSchema()
},
// 安全方法
isConcurrencySafe(input) {
return this.isReadOnly?.(input) ?? false
},
isReadOnly(input) {
return checkReadOnlyConstraints(input, compoundCommandHasCd).behavior === 'allow'
},
async checkPermissions(input, context): Promise<PermissionResult> {
return bashToolHasPermission(input, context)
},
// 渲染方法
renderToolUseMessage,
renderToolResultMessage,
mapToolResultToToolResultBlockParam({ stdout, stderr, ... }, toolUseID) {
// 映射到 API 格式
return {
tool_use_id: toolUseID,
type: 'tool_result',
content: [stdout, errorMessage, backgroundInfo].filter(Boolean).join('\n'),
is_error: interrupted
}
},
// 核心执行
async call(input: BashToolInput, toolUseContext, _canUseTool, parentMessage, onProgress) {
// 执行命令,返回结果
return { data: { stdout, stderr, interrupted, ... } }
},
})6.2 BashTool 命令分类
BashTool 通过静态分析将命令分类为可折叠显示:
// 搜索命令
const BASH_SEARCH_COMMANDS = new Set(['find', 'grep', 'rg', 'ag', 'ack', 'locate', 'which', 'whereis'])
// 读取命令
const BASH_READ_COMMANDS = new Set(['cat', 'head', 'tail', 'less', 'more', 'wc', 'stat', 'file', 'jq', 'awk', 'cut', 'sort', 'uniq', 'tr'])
// 列表命令
const BASH_LIST_COMMANDS = new Set(['ls', 'tree', 'du'])
// 静默命令(成功无输出)
const BASH_SILENT_COMMANDS = new Set(['mv', 'cp', 'rm', 'mkdir', 'rmdir', 'chmod', 'chown', 'chgrp', 'touch', 'ln', 'cd', 'export', 'unset', 'wait'])6.3 工具执行隔离
BashTool 使用 SandboxManager 实现进程隔离:
// 沙箱决策
shouldUseSandbox: shouldUseSandbox(input)
// 注释说明隔离策略
// - 动态配置禁用命令
// - 用户配置的排除命令 (settings.json)
// - 模式匹配支持(前缀、通配符)
6.4 核心内置工具一览
| 工具 | 文件 | 职责 |
|---|---|---|
BashTool | tools/BashTool/BashTool.tsx | Shell 命令执行 |
FileEditTool | tools/FileEditTool/FileEditTool.ts | 文件编辑 |
FileReadTool | tools/FileReadTool/FileReadTool.ts | 文件读取 |
FileWriteTool | tools/FileWriteTool/FileWriteTool.ts | 文件写入 |
GlobTool | tools/GlobTool/GlobTool.ts | 文件模式匹配 |
GrepTool | tools/GrepTool/GrepTool.ts | 内容搜索 |
WebFetchTool | tools/WebFetchTool/WebFetchTool.ts | 网页抓取 |
WebSearchTool | tools/WebSearchTool/WebSearchTool.ts | 网络搜索 |
AgentTool | tools/AgentTool/AgentTool.tsx | 子 Agent 调用 |
TaskCreateTool | tools/TaskCreateTool/TaskCreateTool.ts | 任务创建 |
TaskListTool | tools/TaskListTool/TaskListTool.ts | 任务列表 |
MCPTool | tools/MCPTool/MCPTool.ts | MCP 服务器集成 |
NotebookEditTool | tools/NotebookEditTool/NotebookEditTool.ts | Jupyter 笔记本编辑 |
7. 钩子系统
7.1 钩子类型
// PreToolUseHooks - 工具执行前
// PostToolUseHooks - 工具执行后
// PostToolUseFailureHooks - 工具失败后
7.2 钩子执行流程
// src/services/tools/toolHooks.ts
export async function* runPreToolUseHooks(...): AsyncGenerator<
| { type: 'message'; message: MessageUpdateLazy }
| { type: 'hookPermissionResult'; hookPermissionResult: PermissionResult }
| { type: 'hookUpdatedInput'; updatedInput: Record<string, unknown> }
| { type: 'preventContinuation' }
| { type: 'stopReason' }
| { type: 'stop' } // 停止执行
>关键能力:
- 钩子可修改输入 (
hookUpdatedInput) - 钩子可提供权限决策 (
hookPermissionResult) - 钩子可阻止继续执行 (
preventContinuation)
7.3 权限决策解析
// resolveHookPermissionDecision() 关键逻辑
if (hookPermissionResult?.behavior === 'allow') {
// Hook 允许 ≠ 跳过 settings.json 拒绝/询问规则
const ruleCheck = await checkRuleBasedPermissions(tool, hookInput, toolUseContext)
if (ruleCheck === null) {
return { decision: hookPermissionResult, input: hookInput }
}
// ...
}8. MCP 工具集成
8.1 MCP 工具发现
MCP 工具通过 MCPTool 包装:
export type Tool<
Input extends AnyObject = AnyObject,
Output = unknown,
P extends ToolProgressData = ToolProgressData,
> = {
// MCP 特有
mcpInfo?: { serverName: string; toolName: string }
isMcp?: boolean
// ...
}8.2 MCP 服务器类型检测
// toolExecution.ts
function getMcpServerType(
toolName: string,
mcpClients: MCPServerConnection[],
): McpServerType {
// stdio, sse, http, ws, sdk, sse-ide, ws-ide, claudeai-proxy
}8.3 MCP 工具执行
// toolExecution.ts 中检测 MCP 工具
if (tool.isMcp) {
// MCP 特定处理
await addToolResult(toolOutput)
}9. 关键设计模式
9.1 工厂模式
所有工具通过 buildTool() 工厂创建,保证一致性:
// 默认值集中定义
const TOOL_DEFAULTS = {
isEnabled: () => true,
isConcurrencySafe: () => false,
isReadOnly: () => false,
// ...
}9.2 生成器模式
工具执行使用 AsyncGenerator 实现流式处理:
export async function* runToolUse(...): AsyncGenerator<MessageUpdateLazy, void> {
// 流式产出进度和结果
yield { message: createProgressMessage({ ... }) }
yield { message: createUserMessage({ ... }) }
}9.3 策略模式
权限检查、安全验证等使用可替换策略:
// 多种权限来源
checkPermissions() → hook → rule → classifier → canUseTool9.4 记忆化模式
工具结果持久化状态确保提示词缓存稳定:
// 结果替换状态跨会话保持一致
ContentReplacementState = {
seenIds: Set<string>, // 已见 ID
replacements: Map<string, string> // ID → 替换文本
}9.5 提示词缓存稳定性
工具系统多处设计确保提示词缓存稳定:
- 工具排序:内置工具按名称排序作为连续前缀
- Schema 缓存:会话级缓存防止重复渲染
- 内容替换状态冻结:已见结果的命运不变
10. 工具限制常量
// src/constants/toolLimits.ts
export const DEFAULT_MAX_RESULT_SIZE_CHARS = 50_000 // 单工具结果阈值
export const MAX_TOOL_RESULTS_PER_MESSAGE_CHARS = 200_000 // 消息聚合阈值
export const BYTES_PER_TOKEN = 4
export const MAX_TOOL_RESULT_TOKENS = 100_00011. 总结
Claude Code 的工具系统是一个精心设计的复杂基础设施:
- 注册机制:条件编译 + 工厂模式,灵活可控
- 延迟加载:ToolSearch 机制优化大工具集场景
- 执行隔离:并发安全分区 + 流式执行 + AbortController
- 结果管理:多层持久化策略 + 预算控制 + 预览生成
- 错误处理:分类处理 + 格式化 + 钩子集成
- 安全模型:多层权限检查 + 沙箱隔离 + 规则匹配
系统核心在于 提示词缓存稳定性和用户体验流畅性的平衡,通过状态冻结、预算分配、进度优先等机制实现。
文档更新: 2026-04-03