上一篇我们分析了 Agent 生命周期。今天深入第二个主题:工具编排。Claude Code 有 30+ 种工具,如何统一管理?如何设计执行流水线?
工具是什么?
在 Agent 架构中,工具是"能力单元":
typescript
type Tool<Input, Output> = {
// 身份
name: string
aliases?: string[]
description: string
// Schema
inputSchema: z.ZodType<Input>
outputSchema?: z.ZodType<Output>
// 执行
call(args, context): Promise<ToolResult<Output>>
// 权限
checkPermissions(args, context): Promise<PermissionResult>
// 属性
isConcurrencySafe(args): boolean
isReadOnly(args): boolean
isDestructive?(args): boolean
isEnabled(): boolean
// UI
renderToolUseMessage(args): React.ReactNode
}
每个工具封装一个能力,有完整的生命周期钩子。
工具分类
按功能分类
| 类别 | 工具 |
|---|---|
| 文件操作 | Read, Edit, Write, Glob, Grep |
| 命令执行 | Bash, PowerShell |
| Agent 管理 | Agent, Skill |
| 网络访问 | WebFetch, WebSearch |
| 用户交互 | AskUserQuestion |
| 任务管理 | TaskCreate, TaskUpdate |
| 规划 | EnterPlanMode, ExitPlanMode |
| MCP | mcp__{server}__{tool} |
| 其他 | NotebookEdit, LSP, Config |
按安全属性分类
| 属性 | 工具 |
|---|---|
| 并发安全 | Read, Glob, Grep, WebFetch, WebSearch |
| 只读 | Read, Glob, Grep, WebFetch, WebSearch |
| 破坏性 | Edit(overwrite), Bash(rm, force push) |
| 需权限 | Bash, Edit, Write, Agent |
buildTool 工厂函数
统一的工具构建:
typescript
const TOOL_DEFAULTS = {
isEnabled: () => true,
isConcurrencySafe: () => false, // 默认不安全
isReadOnly: () => false, // 默认写入
isDestructive: () => false, // 默认不破坏
checkPermissions: (input) => ({ behavior: 'allow' }),
userFacingName: () => this.name,
}
function buildTool<D extends ToolDefinition>(def: D): Tool {
return {
...TOOL_DEFAULTS,
...def,
}
}
安全默认值:工具必须显式声明"安全",不是默认安全。
工具执行流水线
完整的执行流程:
typescript
async function executeToolCall(toolUse, context, canUseTool): ToolResult {
const { name, input, id } = toolUse
// === 阶段 1: 解析 ===
const tool = findTool(name, context.options.tools)
if (!tool) {
return { type: 'error', error: `Unknown tool: ${name}` }
}
// === 阶段 2: 输入验证 ===
let validatedInput = input
if (tool.validateInput) {
const validationResult = await tool.validateInput(input, context)
if (!validationResult.valid) {
return { type: 'validation_error', errors: validationResult.errors }
}
validatedInput = validationResult.updatedInput ?? input
}
// === 阶段 3: PreToolUse Hook ===
const preHookResult = await executePreToolUseHooks({
toolName: name,
toolInput: validatedInput,
toolUseId: id,
}, context.abortController.signal)
if (preHookResult.outcome === 'blocking') {
return {
type: 'blocked',
reason: preHookResult.blockingError,
}
}
// === 阶段 4: 权限检查 ===
const permissionResult = await checkToolPermission(tool, validatedInput, context)
if (permissionResult.behavior === 'deny') {
return {
type: 'denied',
reason: permissionResult.message,
}
}
if (permissionResult.behavior === 'ask') {
// 显示权限对话框
const userDecision = await showPermissionPrompt({
toolName: name,
input: validatedInput,
message: permissionResult.message,
suggestions: permissionResult.suggestions,
})
if (userDecision.denied) {
return { type: 'denied', reason: 'User denied' }
}
// 用户可能修改输入
validatedInput = userDecision.updatedInput ?? validatedInput
}
// === 阶段 5: 执行 ===
const startTime = Date.now()
let result: ToolResult
try {
result = await tool.call(validatedInput, context, canUseTool)
} catch (error) {
// === 阶段 6a: 错误处理 ===
await executePostToolUseFailureHooks({
toolName: name,
toolInput: validatedInput,
error,
})
return {
type: 'error',
error: error.message,
}
}
const durationMs = Date.now() - startTime
// === 阶段 7: PostToolUse Hook ===
await executePostToolUseHooks({
toolName: name,
toolInput: validatedInput,
result: result.data,
durationMs,
})
// === 阶段 8: 结果处理 ===
const finalResult = processToolResult(result, tool, context)
return {
type: 'success',
toolUseId: id,
content: finalResult,
durationMs,
}
}
8 个阶段,每个阶段有明确的职责。
权限检查的多层设计
typescript
async function checkToolPermission(tool, input, context): PermissionResult {
// === 层 1: 工具自身检查 ===
const toolPermission = await tool.checkPermissions(input, context)
if (toolPermission.behavior !== 'passthrough') {
return toolPermission
}
// === 层 2: 规则匹配 ===
const ruleResult = checkPermissionRules(tool.name, input, context)
if (ruleResult.behavior !== 'passthrough') {
return ruleResult
}
// === 层 3: 模式检查 ===
const modeResult = checkPermissionMode(context.toolPermissionContext.mode)
if (modeResult.behavior !== 'passthrough') {
return modeResult
}
// === 层 4: 工作目录检查 ===
const cwdResult = checkWorkingDirectory(tool.name, input, context)
if (cwdResult.behavior !== 'passthrough') {
return cwdResult
}
// === 层 5: 安全模式检查 ===
const safetyResult = checkSafetyPatterns(tool.name, input)
if (safetyResult.behavior !== 'passthrough') {
return safetyResult
}
// === 层 6: 默认询问 ===
return {
behavior: 'ask',
message: `Permission required for ${tool.name}`,
}
}
层层把关,每层可以决定结果,只有全部 passthrough 才最终询问用户。
工具注册与发现
typescript
type Tools = Map<string, Tool>
async function buildTools(context: ToolUseContext): Tools {
const tools = new Map()
// 1. 核心工具(始终加载)
const coreTools = [
FileReadTool,
FileEditTool,
FileWriteTool,
GlobTool,
GrepTool,
BashTool,
AgentTool,
SkillTool,
WebFetchTool,
WebSearchTool,
AskUserQuestionTool,
TaskCreateTool,
TaskUpdateTool,
EnterPlanModeTool,
ExitPlanModeTool,
]
for (const tool of coreTools) {
if (tool.isEnabled()) {
tools.set(tool.name, tool)
for (const alias of tool.aliases ?? []) {
tools.set(alias, tool)
}
}
}
// 2. MCP 工具(动态加载)
for (const mcpClient of context.options.mcpClients) {
if (mcpClient.type === 'connected') {
for (const mcpTool of mcpClient.tools) {
const toolName = `mcp__${mcpClient.name}__${mcpTool.name}`
const tool = createMcpTool(mcpTool, mcpClient)
tools.set(toolName, tool)
}
}
}
// 3. 动态 Skill 工具
const skills = await loadSkills(context)
for (const skill of skills) {
if (skill.isMcp) {
const skillTool = createSkillTool(skill)
tools.set(skillTool.name, skillTool)
}
}
return tools
}
三层来源:核心工具、MCP 工具、动态 Skill。
MCP 工具动态创建
typescript
function createMcpTool(mcpTool, mcpClient): Tool {
return buildTool({
name: `mcp__${mcpClient.name}__${mcpTool.name}`,
isMcp: true,
mcpInfo: {
serverName: mcpClient.name,
toolName: mcpTool.name,
},
// Schema 从 MCP 定义转换
inputSchema: convertMcpSchemaToZod(mcpTool.inputSchema),
// 执行调用 MCP server
async call(args, context) {
const result = await mcpClient.client.callTool({
name: mcpTool.name,
arguments: args,
})
return { data: result.content }
},
// 权限继承 MCP server 权限
async checkPermissions(args, context) {
return checkMcpPermission(mcpClient, mcpTool, args, context)
},
// MCP 工具默认不安全
isConcurrencySafe: () => false,
isReadOnly: () => mcpTool.annotations?.readOnly ?? false,
})
}
MCP 工具从协议定义动态生成,每个 server 的工具独立命名。
工具调用追踪
typescript
type ToolCallTracking = {
toolUseId: string
toolName: string
startTime: number
status: 'pending' | 'running' | 'completed' | 'failed'
input?: unknown
result?: unknown
error?: Error
}
const toolCallRegistry = new Map<string, ToolCallTracking>()
function registerToolCall(toolUseId, toolName, input) {
toolCallRegistry.set(toolUseId, {
toolUseId,
toolName,
startTime: Date.now(),
status: 'running',
input,
})
}
function completeToolCall(toolUseId, result) {
const tracking = toolCallRegistry.get(toolUseId)
if (tracking) {
tracking.status = 'completed'
tracking.result = result
tracking.durationMs = Date.now() - tracking.startTime
}
}
追踪每个工具调用,用于 UI 显示和调试。
进度报告
typescript
type ToolProgress = {
type: 'start' | 'progress' | 'complete'
toolUseId: string
data?: {
type: 'bash_progress'
output: string
} | {
type: 'file_progress'
bytesRead: number
}
}
// 工具调用时传入 onProgress
const result = await tool.call(input, context, canUseTool, onProgress)
function onProgress(progress: ToolProgress) {
// 更新 UI
updateToolProgress(progress)
// 广播给 SDK
broadcastToolProgress(progress)
}
长时间工具(如 Bash)可以实时报告进度。
工具结果处理
大结果截断
typescript
const MAX_RESULT_SIZE_CHARS = 100_000
function processToolResult(result, tool, context): string {
const content = formatResultContent(result)
if (content.length > tool.maxResultSizeChars) {
// 写入临时文件
const filePath = writeToolResultToFile(content)
// 返回摘要
return `Result saved to: ${filePath}\nPreview: ${content.slice(0, 1000)}...`
}
return content
}
大结果不直接返回给 API,写入文件,返回引用。
结果格式化
typescript
function formatResultContent(result: unknown): string {
if (typeof result === 'string') {
return result
}
if (result.type === 'text') {
return result.content
}
if (result.type === 'image') {
return `[Image: ${result.filePath}]`
}
if (result.type === 'error') {
return `Error: ${result.message}`
}
return JSON.stringify(result, null, 2)
}
统一格式化,确保 API 理解。
工具并发控制
typescript
type ConcurrencyState = {
runningTools: Set<string>
safeToolsRunning: Set<string>
unsafeToolsRunning: Set<string>
}
function canExecuteTool(tool, input, state): boolean {
const isSafe = tool.isConcurrencySafe(input)
if (isSafe) {
// 安全工具可以并发
return true
}
// 不安全工具需要等待其他不安全工具完成
return state.unsafeToolsRunning.size === 0
}
async function executeWithConcurrencyControl(tool, input, state) {
// 等待并发条件满足
while (!canExecuteTool(tool, input, state)) {
await waitForUnsafeToolCompletion(state)
}
// 注册执行
state.runningTools.add(toolUseId)
if (!tool.isConcurrencySafe(input)) {
state.unsafeToolsRunning.add(toolUseId)
}
// 执行
const result = await tool.call(input, context)
// 清理
state.runningTools.delete(toolUseId)
if (!tool.isConcurrencySafe(input)) {
state.unsafeToolsRunning.delete(toolUseId)
}
return result
}
安全工具(Read, Glob)可以并发,不安全工具(Edit, Bash)串行执行。
上下文修改器
某些工具执行后需要修改上下文:
typescript
type ToolResultWithContextModifier = {
data: unknown
contextModifier?: (ctx: ToolUseContext) => ToolUseContext
}
// SkillTool 的例子
async call({ skill, args }, context) {
const processedCommand = await processSlashCommand(skill, args, context)
return {
data: { success: true },
newMessages: processedCommand.messages,
contextModifier(ctx) {
return {
...ctx,
options: {
...ctx.options,
allowedTools: processedCommand.allowedTools,
model: processedCommand.model,
},
}
},
}
}
// 执行后应用修改
if (result.contextModifier) {
context = result.contextModifier(context)
}
SkillTool 可以修改 allowedTools------这允许 skill 限制可用工具。
总结
工具编排展示了工厂模式和流水线设计:
| 设计点 | 实现 |
|---|---|
| 工厂模式 | buildTool() 统一构建,安全默认值 |
| 分类系统 | 功能分类 + 安全属性分类 |
| 执行流水线 | 8 阶段:解析→验证→Hook→权限→执行→错误→Hook→结果 |
| 权限多层 | 6 层检查,层层把关 |
| 动态加载 | MCP 工具、Skill 工具动态创建 |
| 进度报告 | onProgress 回调,实时 UI |
| 并发控制 | 安全并发,不安全串行 |
| 上下文修改 | contextModifier 模式 |
下一篇,我们将深入上下文管理------Token 预算如何分配,文件状态如何追踪。