深入拆解 Claude Code 源码(五):35 个内置工具如何让 AI “手眼通天”
系列:深入拆解 Claude Code 源码 | 第 5 篇 / 共 8 篇
关键词:Claude Code, 工具系统, buildTool, BashTool, FileEditTool, AgentTool, 并发安全
开篇:AI 的"手"和"眼"
一个 AI 助手能做什么,完全取决于它拥有什么工具。Claude Code 之所以能真正帮你写代码,而不是只给建议,就是因为它有 35+ 个内置工具,可以:
- 读你的文件(FileRead)
- 改你的代码(FileEdit, FileWrite)
- 跑你的命令(Bash, PowerShell)
- 搜索你的项目(Glob, Grep)
- 派子 Agent 去干活(Agent)
- 上网查资料(WebFetch, WebSearch)
这些工具不是随意堆砌的,它们遵循统一的架构模式。今天我们来拆解这个"瑞士军刀"的设计。
一、buildTool 模式 — 工厂函数如何统一 35 把刀
所有工具都通过 buildTool() 工厂函数构建。这不仅仅是创建对象那么简单——它定义了一套完整的契约,让每个工具都有统一的行为接口。
完整 Tool 接口(20+ 字段)
直接看源码 src/Tool.ts,这个接口可不简单,分为 7 大类:
// src/Tool.ts — 精简展示核心字段
export type Tool<Input, Output, P> = {
// ① 基本信息
name: string; aliases?: string[]; searchHint?: string
inputSchema: Input; outputSchema?: z.ZodType<unknown>
maxResultSizeChars: number; strict?: boolean
// ② 核心行为
description(input, options): Promise<string>
prompt(options): Promise<string>
call(args, context, canUseTool, parentMessage, onProgress): Promise<ToolResult<Output>>
// ③ 验证与权限
validateInput?(input, context): Promise<ValidationResult>
checkPermissions(input, context): Promise<PermissionResult>
preparePermissionMatcher?(input): Promise<(pattern: string) => boolean>
// ④ 标记与分类(7 个布尔标记)
isEnabled(): boolean; isConcurrencySafe(input): boolean; isReadOnly(input): boolean
isDestructive?(input): boolean; isMcp?: boolean; shouldDefer?: boolean; alwaysLoad?: boolean
// ⑤ 中断与交互
interruptBehavior?(): 'cancel' | 'block'
isSearchOrReadCommand?(input): { isSearch: boolean; isRead: boolean; isList?: boolean }
// ⑥ UI 渲染(8 个渲染方法)
renderToolUseMessage; renderToolResultMessage?; renderToolUseProgressMessage?
renderToolUseRejectedMessage?; renderToolUseErrorMessage?; renderToolUseTag?
renderToolUseQueuedMessage?; renderGroupedToolUse?
isResultTruncated?(output): boolean
// ⑦ 数据转换
mapToolResultToToolResultBlockParam(content, toolUseID): ToolResultBlockParam
toAutoClassifierInput(input): unknown
getPath?(input): string
getActivityDescription?(input): string | null
getToolUseSummary?(input): string | null
extractSearchText?(out): string
mcpInfo?: { serverName: string; toolName: string }
backfillObservableInput?(input: Record<string, unknown>): void
}
30+ 个方法/属性,覆盖 UI 渲染、权限控制、并发调度、自动分类、安全审计——每个工具都"承诺"实现了这些能力。
buildTool() 工厂如何工作
看实际实现:
// 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?) => '',
}
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
return {
...TOOL_DEFAULTS, // 先铺默认值
userFacingName: () => def.name, // 默认用工具名
...def, // 再覆盖自定义部分
} as BuiltTool<D>
}
关键设计:默认值采用"失败即关闭"策略。isConcurrencySafe 默认 false(假定不安全),isReadOnly 默认 false(假定有写操作)。只有只读工具(如 FileReadTool)会显式设置 isConcurrencySafe: () => true。
构建示例 — FileReadTool
const FileReadTool = buildTool({
name: 'Read',
searchHint: 'read file contents', // ToolSearch 关键词
description: 'Read a file from the filesystem',
inputSchema: z.object({
file_path: z.string().describe('Absolute path to the file'),
offset: z.number().optional().describe('Starting line number'),
limit: z.number().optional().describe('Number of lines to read'),
pages: z.string().optional().describe('PDF page range'),
}),
isConcurrencySafe: () => true, // 只读,并发安全
isReadOnly: () => true,
maxResultSizeChars: Infinity, // Read 工具自己限制,不持久化
toAutoClassifierInput: () => '', // 文件读取对安全分类无意义
getPath(input) { return input.file_path },
getActivityDescription(input) {
return `Reading ${path.basename(input.file_path)}`
},
// ...
})
二、文件操作工具族 — 四把"手术刀"
2.1 FileReadTool — 文件读取(最精细的读取器)
| 属性 | 值 |
|---|---|
| 并发安全 | 是 |
| 只读 | 是 |
| 最大结果 | Infinity(Read 工具自行限制) |
| 延迟加载 | 否 |
输入 Schema:
z.object({
file_path: z.string(), // 绝对路径
offset: z.number().optional(), // 起始行号
limit: z.number().optional(), // 读取行数
pages: z.string().optional(), // PDF 页码范围(如 "1-5", "3", "10-20")
})
PDF 支持:
import { PDF_EXTRACT_SIZE_THRESHOLD, PDF_MAX_PAGES_PER_READ } from '../../constants/apiLimits.js'
import { extractPDFPages, getPDFPageCount, readPDF } from '../../utils/pdf.js'
处理流程:先 isPDFSupported() 检查,再 parsePDFPageRange() 解析页码,超阈值时分页提取。每次最多读 PDF_MAX_PAGES_PER_READ 页。
图片处理(多模态核心):
import {
compressImageBufferWithTokenLimit, // 按 token 限制压缩
detectImageFormatFromBuffer, // 检测格式
maybeResizeAndDownsampleImageBuffer, // 智能缩放
} from '../../utils/imageResizer.js'
读取图片时自动检测格式、缩放尺寸、压缩到 token 限制内,以多模态格式返回。
Token 估算:两种策略并用——roughTokenCountEstimationForFileType() 按扩展名粗估(如 .py 约 3.5 字符/token),countTokensWithAPI() 在关键场景精确计数。
文件去重缓存:当同一文件、同样 offset/limit 被二次读取时,返回 FILE_UNCHANGED_STUB 标记而非重传内容,节省大量 token。
设备文件保护:/dev/zero, /dev/random, /dev/stdin 等路径直接拒绝,避免进程挂起。
Read-before-Edit 追踪:读取记录存入 readFileState(FileStateCache),FileEditTool 执行前检查——没读过就拒绝编辑。
技能目录发现:读取文件时顺带扫描 discoverSkillDirsForPaths(),实现"边读边发现"。
2.2 FileEditTool — 文件编辑(精确字符串替换)
| 属性 | 值 |
|---|---|
| 并发安全 | 否 |
| 只读 | 否 |
| 最大结果 | 100,000 字符 |
| 严格模式 | 是 |
输入 Schema:
z.strictObject({
file_path: z.string(), // 绝对路径
old_string: z.string(), // 被替换的文本
new_string: z.string(), // 新文本
replace_all: z.boolean().optional(), // 全局替换
})
核心机制:精确字符串替换(非正则)
注意,这里不是正则替换,而是精确的字符串匹配。源码里用 findActualString() 处理引号风格差异:
// 处理引号风格差异(单引号 ↔ 双引号自动兼容)
const actualOldString = findActualString(file, old_string)
if (!actualOldString) {
return { result: false, message: '...', errorCode: 8 }
}
安全检查流水线(10 步验证,这是重点):
async validateInput(input, toolUseContext) {
const fullFilePath = expandPath(file_path)
// ① 密钥泄露检查 → ② 空变更检查 → ③ 权限拒绝规则
// ④ UNC 路径安全(见下文) → ⑤ 文件大小限制(1 GiB)
// ⑥ .ipynb 拒绝 → ⑦ Read-before-Edit → ⑧ 修改时间检查
// ⑨ old_string 存在性 → ⑩ new_string 重复检查
}
UNC 路径安全检查(一个很细节的安全防护):
// SECURITY: Skip filesystem operations for UNC paths to prevent NTLM credential leaks.
// On Windows, fs.existsSync() on UNC paths triggers SMB authentication which could
// leak credentials to malicious servers.
if (fullFilePath.startsWith('\\\\') || fullFilePath.startsWith('//')) {
return { result: true }
}
Windows 上的 UNC 路径(如 \\evil-server\share)如果调用 fs.existsSync(),会触发 SMB 认证,可能泄露 NTLM 凭证给恶意服务器。所以直接跳过文件系统操作。
编辑完成后的副作用链:
// 编辑成功后依次执行:
checkTeamMemSecrets(...) // ① 密钥检查(再次确认)
fileHistoryTrackEdit(...) // ② 文件历史追踪
diagnosticTracker.clearForFile(...) // ③ 诊断追踪(LSP 集成)
notifyVscodeFileUpdated(...) // ④ VSCode 通知
activateConditionalSkillsForPaths(...) // ⑤ 技能激活
fetchSingleFileGitDiff(...) // ⑥ Git diff 获取
2.3 FileWriteTool — 文件写入(创建或完全覆盖)
| 属性 | 值 |
|---|---|
| 并发安全 | 否 |
| 只读 | 否 |
| 最大结果 | 100,000 字符 |
| 严格模式 | 是 |
输入 Schema:
z.strictObject({
file_path: z.string(), // 绝对路径
content: z.string(), // 文件内容
})
输出结构(比你想象的丰富):
z.object({
type: z.enum(['create', 'update']), // 新建还是覆盖
filePath: z.string(), // 写入的文件路径
content: z.string(), // 写入的内容
structuredPatch: z.array(hunkSchema), // diff 补丁
originalFile: z.string().nullable(), // 原文件内容(新建时为 null)
gitDiff: gitDiffSchema().optional(), // Git diff 信息
})
输出包含 type 字段区分新建和更新,以及完整的 structuredPatch 和 originalFile——这让 UI 层能显示精确的 diff 视图。
安全检查:与 FileEditTool 类似,包含密钥检查、权限检查、Read-before-Edit 检查(覆盖现有文件时)。新增文件不需要先读取。
2.4 NotebookEditTool — Jupyter Notebook 编辑
| 属性 | 值 |
|---|---|
| 并发安全 | 否 |
| 只读 | 否 |
| 最大结果 | 100,000 字符 |
| 延迟加载 | 是(shouldDefer: true) |
输入 Schema:
z.strictObject({
notebook_path: z.string(), // 绝对路径(.ipynb)
cell_id: z.string().optional(), // 单元格 ID
new_source: z.string(), // 新源码
cell_type: z.enum(['code', 'markdown']).optional(),
edit_mode: z.enum(['replace', 'insert', 'delete']).optional(),
})
单元格 ID 查找逻辑(两级查找):
// 先按实际 ID 查找,再按 cell-N 格式索引
function parseCellId(notebook: NotebookContent, cellId: string) {
// 第一级:精确 ID 匹配
const byId = notebook.cells.find(c => c.id === cellId)
if (byId) return byId
// 第二级:cell-N 格式索引(cell-0, cell-1, ...)
const match = cellId.match(/^cell-(\d+)$/)
if (match) {
const index = parseInt(match[1], 10)
if (index < notebook.cells.length) {
return notebook.cells[index]
}
}
return null
}
其他细节:
- 代码单元格修改时自动重置
execution_count和outputs为空 - nbformat 4.5+ 自动生成新的 cell ID(使用 UUID)
- replace 模式越界时自动转为 insert
- JSON 写入使用 1 空格缩进(标准
.ipynb格式) - 输出包含
original_file和updated_file用于 diff 显示
三、搜索与发现工具
GlobTool — 文件模式匹配
input: {
pattern: string // 如 "**/*.ts"
path?: string // 搜索目录
}
并发安全、只读、最大 100 文件结果、按修改时间排序。当 ant 版本有内嵌搜索工具(hasEmbeddedSearchTools())时不注册。
GrepTool — 内容搜索
使用 ripgrep 引擎,支持正则、文件类型过滤、多行匹配、上下文行数控制、分页偏移。完整参数:
input: {
pattern: string, // 正则表达式
path?: string, // 搜索路径
glob?: string, // 文件过滤 glob
output_mode: 'content' | 'files_with_matches' | 'count',
-B?, -A?, -C?: number, // 上下文行数
-n?: boolean, // 行号
-i?: boolean, // 大小写不敏感
-o?: boolean, // 仅匹配部分
type?: string, // 文件类型
head_limit?: number, // 结果限制
offset?: number, // 分页偏移
multiline?: boolean, // 多行匹配
}
ToolSearchTool — 工具搜索(延迟加载的"开关")
input: {
query: string, // 查询字符串(支持 select:<name> 直接选择)
max_results?: number, // 最大结果数(默认 5)
}
当工具集过大时,部分工具标记 shouldDefer: true 不会出现在初始 prompt 中。模型需要用 ToolSearchTool 搜索并激活它们。基于关键词评分匹配,select:<name> 前缀可直接选择。
四、Shell 执行工具 — 最复杂的两个工具
4.1 BashTool — Bash 命令执行
这是 Claude Code 中最复杂的工具之一,涉及安全解析(bash AST)、沙箱(SandboxManager)、Git 追踪、图片输出检测、任务系统等子系统。
输入 Schema:
z.object({
command: z.string(), // Bash 命令
timeout: z.number().optional(), // 超时毫秒(最大 600,000)
description: z.string().optional(), // 命令描述
run_in_background: z.boolean().optional(), // 后台运行
dangerouslyDisableSandbox: z.boolean().optional(), // 禁用沙箱
})
五套命令分类系统:
// 搜索类(UI 可折叠显示)
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', 'strings',
// 数据处理
'jq', 'awk', 'cut', 'sort', 'uniq', 'tr'
])
// 目录列表类(从 READ 分出来,UI 显示 "Listed N directories" 而非 "Read N files")
const BASH_LIST_COMMANDS = new Set(['ls', 'tree', 'du'])
// 语义中性命令(管道中可跳过,不影响整体分类)
const BASH_SEMANTIC_NEUTRAL_COMMANDS = new Set([
'echo', 'printf', 'true', 'false', ':' // bash no-op
])
// 静默类(成功时通常无 stdout)
const BASH_SILENT_COMMANDS = new Set([
'mv', 'cp', 'rm', 'mkdir', 'rmdir', 'chmod',
'chown', 'chgrp', 'touch', 'ln', 'cd', 'export', 'unset', 'wait'
])
命令分类逻辑(核心函数):
export function isSearchOrReadBashCommand(command: string): {
isSearch: boolean; isRead: boolean; isList: boolean
} {
// 对于管道命令(如 `cat file | grep pattern`),ALL 部分都必须是搜索/读取
// 语义中性命令(echo, printf, true, false, :) 在任何位置都跳过
// 例如 `ls dir && echo "---" && ls dir2` 仍被分类为只读
}
时间常量:
const PROGRESS_THRESHOLD_MS = 2000 // 2 秒后显示进度
const ASSISTANT_BLOCKING_BUDGET_MS = 15_000 // 15 秒后自动后台化
其他特性:
- 沙箱支持:
SandboxManager提供沙箱隔离,shouldUseSandbox.ts决定是否启用,dangerouslyDisableSandbox可强制禁用 - Git 操作追踪:
trackGitOperations()检测 commit/push/cherry-pick/merge/rebase 和 gh pr 操作,用于指标统计 - 图片输出检测:
isImageOutput()检测命令输出图片(如 matplotlib),自动resizeShellImageOutput()并返回多模态结果
4.2 PowerShellTool — PowerShell 命令执行
Windows 专用,门控:isPowerShellToolEnabled()。
命令分类集合:
const PS_SEARCH_COMMANDS = new Set([
'Select-String', 'Get-ChildItem', 'FindStr', 'Where.exe'
])
const PS_READ_COMMANDS = new Set([
'Get-Content', 'Get-Item', 'Test-Path', 'Resolve-Path',
'Get-Process', 'Get-Service', 'Get-ChildItem'
])
Windows PowerShell 5.1 兼容:Claude Code 必须同时兼容 Windows PowerShell 5.1 和 PowerShell 7+,源码中大量使用兼容性处理。
安全机制:isReadOnlyCommand() 只读验证、hasSyncSecurityConcerns() 安全检测、resolveToCanonical() 命令规范化(别名转完整命令)、interpretCommandResult() 结果解释。共享 BashTool 的沙箱、UI 和工具结果存储逻辑。
五、Agent 与多 Agent 工具 — 最依赖的工具
5.1 AgentTool — Agent 执行
这是 Claude Code 中依赖最多的工具(约 45 个依赖项),横跨任务系统、工具池、worktree、远程执行、系统提示词等子系统。
双重 Schema 设计(baseInputSchema + fullInputSchema):
// 基础 schema(5 个字段)
const baseInputSchema = lazySchema(() => z.object({
description: z.string().describe('A short (3-5 word) description'),
prompt: z.string().describe('The task for the agent to perform'),
subagent_type: z.string().optional(),
model: z.enum(['sonnet', 'opus', 'haiku']).optional(),
run_in_background: z.boolean().optional(),
}))
// 完整 schema(基础 + 多 agent + 隔离,共 10 个字段)
const fullInputSchema = lazySchema(() => {
const multiAgentInputSchema = z.object({
name: z.string().optional(), // agent 名称(可通过 SendMessage 寻址)
team_name: z.string().optional(), // 团队名称
mode: permissionModeSchema().optional(), // 权限模式覆盖
})
return baseInputSchema().merge(multiAgentInputSchema).extend({
isolation: z.enum(['worktree', 'remote']).optional(),
cwd: z.string().optional(), // 工作目录覆盖
})
})
120 秒自动后台化:
function getAutoBackgroundMs(): number {
if (isEnvTruthy(process.env.CLAUDE_AUTO_BACKGROUND_TASKS) ||
getFeatureValue_CACHED_MAY_BE_STALE('tengu_auto_background_agents', false)) {
return 120_000 // 2 分钟后自动转为后台
}
return 0 // 禁用
}
进度阈值:
const PROGRESS_THRESHOLD_MS = 2000 // 2 秒后显示后台提示
5.2 SendMessageTool — 多 Agent 消息
input: {
to: string, // 接收者标识
message: string | object, // 消息(支持结构化 JSON)
summary?: string, // UI 预览摘要
}
接收者类型(四种寻址方式):
| to 值 | 含义 |
|---|---|
"teammate_name" | 发送给指定队友 |
"*" | 广播给所有队友 |
"uds:<path>" | 通过 Unix Domain Socket 发送给对等进程 |
"bridge:<id>" | 通过 bridge 发送给远程实例 |
消息支持结构化 JSON,包括 shutdown_request、shutdown_response、plan_approval_response 等内部协议。
5.3 TeamCreateTool / TeamDeleteTool
// TeamCreateTool
input: {
team_name: string, // 团队名称
description?: string, // 团队描述
agent_type?: string, // agent 类型
}
// 通过 generateWordSlug() 生成唯一团队名称
// TeamDeleteTool
input: {} // 空!使用 AppState 中的当前团队上下文
// 清理 ~/.claude/teams/ 和 ~/.claude/tasks/ 目录
// 仅在所有队友关闭后可用
门控:isAgentSwarmsEnabled()。
六、工具注册与过滤 — 一把一把地选刀
6.1 getAllBaseTools() — 完整工具清单
src/tools.ts 中的 getAllBaseTools() 是所有工具的"注册表",分三大类注册:
export function getAllBaseTools(): Tools {
return [
// ─── 始终注册的核心工具(15 个)───
AgentTool, TaskOutputTool, BashTool,
...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
ExitPlanModeV2Tool, FileReadTool, FileEditTool, FileWriteTool,
NotebookEditTool, WebFetchTool, TodoWriteTool, WebSearchTool,
TaskStopTool, AskUserQuestionTool, SkillTool, EnterPlanModeTool,
getSendMessageTool(), BriefTool,
// ─── 条件编译工具(feature flag 门控)───
...(process.env.USER_TYPE === 'ant' ? [ConfigTool, TungstenTool] : []),
...(isTodoV2Enabled() ? [TaskCreateTool, TaskGetTool, TaskUpdateTool, TaskListTool] : []),
...(isEnvTruthy(process.env.ENABLE_LSP_TOOL) ? [LSPTool] : []),
...(isWorktreeModeEnabled() ? [EnterWorktreeTool, ExitWorktreeTool] : []),
...(isAgentSwarmsEnabled() ? [getTeamCreateTool(), getTeamDeleteTool()] : []),
...(process.env.USER_TYPE === 'ant' && REPLTool ? [REPLTool] : []),
...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []),
...cronTools, // AGENT_TRIGGERS feature flag
// ─── 实验性/平台工具 ───
...(SuggestBackgroundPRTool ? [SuggestBackgroundPRTool] : []),
...(WebBrowserTool ? [WebBrowserTool] : []),
...(OverflowTestTool ? [OverflowTestTool] : []),
...(CtxInspectTool ? [CtxInspectTool] : []),
...(TerminalCaptureTool ? [TerminalCaptureTool] : []),
...(WorkflowTool ? [WorkflowTool] : []),
...(SleepTool ? [SleepTool] : []),
...(RemoteTriggerTool ? [RemoteTriggerTool] : []),
...(MonitorTool ? [MonitorTool] : []),
...(SendUserFileTool ? [SendUserFileTool] : []),
...(PushNotificationTool ? [PushNotificationTool] : []),
...(SubscribePRTool ? [SubscribePRTool] : []),
...(getPowerShellTool() ? [getPowerShellTool()] : []),
...(SnipTool ? [SnipTool] : []),
...(ListPeersTool ? [ListPeersTool] : []),
...(VerifyPlanExecutionTool ? [VerifyPlanExecutionTool] : []),
...(process.env.NODE_ENV === 'test' ? [TestingPermissionTool] : []),
ListMcpResourcesTool, ReadMcpResourceTool,
]
}
注意 TeamCreateTool 和 TeamDeleteTool 使用了 lazy require 来打破循环依赖:
// Lazy require to break circular dependency
const getTeamCreateTool = () =>
require('./tools/TeamCreateTool/TeamCreateTool.js').TeamCreateTool
const getTeamDeleteTool = () =>
require('./tools/TeamDeleteTool/TeamDeleteTool.js').TeamDeleteTool
const getSendMessageTool = () =>
require('./tools/SendMessageTool/SendMessageTool.js').SendMessageTool
6.2 getTools() — 权限过滤
export const getTools = (permissionContext: ToolPermissionContext): Tools => {
// SIMPLE 模式:只有 Bash, Read, Edit
if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
// REPL 模式下用 REPLTool 包装原始工具
if (isReplModeEnabled() && REPLTool) {
return filterToolsByDenyRules([REPLTool], permissionContext)
}
return filterToolsByDenyRules([BashTool, FileReadTool, FileEditTool], permissionContext)
}
const tools = getAllBaseTools().filter(tool => !specialTools.has(tool.name))
let allowedTools = filterToolsByDenyRules(tools, permissionContext)
// REPL 模式下隐藏原始工具(它们在 REPL VM 内部可用)
if (isReplModeEnabled()) {
allowedTools = allowedTools.filter(tool => !REPL_ONLY_TOOLS.has(tool.name))
}
return allowedTools.filter((_, i) => isEnabled[i])
}
6.3 assembleToolPool() — 合并策略
export function assembleToolPool(
permissionContext: ToolPermissionContext,
mcpTools: Tools,
): Tools {
const builtInTools = getTools(permissionContext)
const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)
// 各自排序以保证 prompt-cache 稳定性
// 内置工具作为前缀,MCP 工具追加
// uniqBy 保证内置工具在名称冲突时优先
const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name)
return uniqBy(
[...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
'name',
)
}
这里有个很巧的设计:内置工具和 MCP 工具各自按名称排序后拼接,但 内置工具永远在前面。这样 claude_code_system_cache_policy 的缓存断点就能稳定命中,不会因为 MCP 工具插入而失效。
6.4 filterToolsByDenyRules() — MCP 前缀支持
支持 MCP 服务器前缀规则——比如 mcp__server 规则会屏蔽该服务器的所有工具,模型根本看不到它们。与运行时权限检查使用同一匹配器。
七、并发控制 — 谁能一起跑,谁必须排队
7.1 partitionToolCalls() — 分区逻辑
src/services/tools/toolOrchestration.ts 负责并发调度:
async function* runTools(toolCalls, context) {
// 1. 分区:并发安全 vs 必须串行
const { concurrent, serial } = partitionToolCalls(toolCalls)
// 2. 并发执行安全批次
if (concurrent.length > 0) {
yield* runToolsConcurrently(concurrent, context)
}
// 3. 串行执行敏感操作
for (const call of serial) {
yield* runToolsSerially([call], context)
}
}
并发安全判断:
isConcurrencySafe() === true:Read, Glob, Grep, WebFetch 等只读操作isConcurrencySafe() === false:Bash, FileEdit, FileWrite 等写操作
7.2 最大并发数
// 环境变量控制,默认 10
CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY = 10
7.3 contextModifier 排队
非并发安全的工具可返回 contextModifier 修改后续工具的上下文,这些修改器必须串行执行,不能并行。
八、工具权限集常量 — 5 个安全"围栏"
src/constants/tools.ts 定义了 5 个关键的工具权限集合,控制不同场景下哪些工具可用:
ALL_AGENT_DISALLOWED_TOOLS — 所有 Agent 禁用工具
export const ALL_AGENT_DISALLOWED_TOOLS = new Set([
TASK_OUTPUT_TOOL_NAME, // 防止递归
EXIT_PLAN_MODE_V2_TOOL_NAME, // 计划模式是主线程抽象
ENTER_PLAN_MODE_TOOL_NAME,
// 非 ant 用户禁止嵌套 Agent
...(process.env.USER_TYPE === 'ant' ? [] : [AGENT_TOOL_NAME]),
ASK_USER_QUESTION_TOOL_NAME, // Agent 不能直接问用户
TASK_STOP_TOOL_NAME, // 需要主线程任务状态
// 防止递归工作流
...(feature('WORKFLOW_SCRIPTS') ? [WORKFLOW_TOOL_NAME] : []),
])
CUSTOM_AGENT_DISALLOWED_TOOLS — 自定义 Agent 禁用工具
export const CUSTOM_AGENT_DISALLOWED_TOOLS = new Set([
...ALL_AGENT_DISALLOWED_TOOLS, // 继承全部
// 目前没有额外限制,但预留了扩展点
])
ASYNC_AGENT_ALLOWED_TOOLS — 异步 Agent 允许工具(白名单)
export const ASYNC_AGENT_ALLOWED_TOOLS = new Set([
FILE_READ_TOOL_NAME,
WEB_SEARCH_TOOL_NAME,
TODO_WRITE_TOOL_NAME,
GREP_TOOL_NAME,
WEB_FETCH_TOOL_NAME,
GLOB_TOOL_NAME,
...SHELL_TOOL_NAMES, // Bash, PowerShell
FILE_EDIT_TOOL_NAME,
FILE_WRITE_TOOL_NAME,
NOTEBOOK_EDIT_TOOL_NAME,
SKILL_TOOL_NAME,
SYNTHETIC_OUTPUT_TOOL_NAME,
TOOL_SEARCH_TOOL_NAME,
ENTER_WORKTREE_TOOL_NAME,
EXIT_WORKTREE_TOOL_NAME,
])
IN_PROCESS_TEAMMATE_ALLOWED_TOOLS — 进程内队友额外工具
export const IN_PROCESS_TEAMMATE_ALLOWED_TOOLS = new Set([
TASK_CREATE_TOOL_NAME,
TASK_GET_TOOL_NAME,
TASK_LIST_TOOL_NAME,
TASK_UPDATE_TOOL_NAME,
SEND_MESSAGE_TOOL_NAME,
// 定时任务(带 agentId 标记,路由到队友的消息队列)
...(feature('AGENT_TRIGGERS')
? [CRON_CREATE_TOOL_NAME, CRON_DELETE_TOOL_NAME, CRON_LIST_TOOL_NAME]
: []),
])
COORDINATOR_MODE_ALLOWED_TOOLS — 协调者模式工具
export const COORDINATOR_MODE_ALLOWED_TOOLS = new Set([
AGENT_TOOL_NAME, // 只能派活
TASK_STOP_TOOL_NAME, // 停止任务
SEND_MESSAGE_TOOL_NAME, // 发消息
SYNTHETIC_OUTPUT_TOOL_NAME, // 结构化输出
])
协调者模式下,主 Agent 只有 4 个工具——它不干活,只指挥。
九、条件加载汇总
| 工具 | 启用条件 |
|---|---|
| GlobTool, GrepTool | !hasEmbeddedSearchTools() |
| TaskCreate/Get/Update/List | isTodoV2Enabled() |
| EnterWorktree, ExitWorktree | isWorktreeModeEnabled() |
| TeamCreate, TeamDelete | isAgentSwarmsEnabled() |
| PowerShellTool | isPowerShellToolEnabled() |
| LSPTool | ENABLE_LSP_TOOL 环境变量 |
| ToolSearchTool | isToolSearchEnabledOptimistic() |
| CronCreate/Delete/List | AGENT_TRIGGERS feature flag |
| RemoteTriggerTool | AGENT_TRIGGERS_REMOTE feature flag |
| BriefTool | KAIROS / KAIROS_BRIEF feature flag |
| SleepTool | PROACTIVE / KAIROS feature flag |
| MonitorTool | MONITOR_TOOL feature flag |
| ConfigTool, TungstenTool, REPLTool | USER_TYPE === 'ant' |
| WebBrowserTool | WEB_BROWSER_TOOL feature flag |
| SyntheticOutputTool | 非交互会话 |
| TestingPermissionTool | NODE_ENV === 'test' |
十、设计亮点总结
| 设计决策 | 价值 |
|---|---|
buildTool() 工厂 + 默认值 | 统一接口,新工具只需覆盖差异部分 |
| 默认不安全(fail-closed) | 新工具不会意外获得并发安全等权限 |
| 五套命令分类系统 | BashTool 根据命令类型智能调整 UI 展示 |
| UNC 路径安全检查 | 防止 Windows 上 NTLM 凭证泄露 |
| Read-before-Edit 强制 | 防止 AI 盲目编辑未读文件 |
| 120s 自动后台化 | 长时间 Agent 不阻塞用户交互 |
| 权限集常量 | 协调者/异步 Agent/自定义 Agent 各有边界 |
| 延迟 require 打破循环 | 工具间复杂依赖关系优雅解决 |
| prompt-cache 稳定排序 | 内置+MCP 工具合并不影响缓存命中 |
条件编译(feature()) | 编译时死代码消除,不同版本有不同工具集 |
下篇预告
第六篇:20 个后端服务的微服务架构
工具是 AI 的"手脚",服务层就是 AI 的"后勤保障"。Claude Code 的
services/目录包含了 20+ 个子系统:API 通信、MCP 集成、OAuth 认证、推测执行、团队记忆同步、自动梦境……下一篇,我们将深入服务层,看看这些"幕后英雄"是如何支撑整个系统的。
标签:
Claude Code工具系统buildToolBashToolAgentTool并发安全源码分析TypeScript
5344

被折叠的 条评论
为什么被折叠?



