Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第6章:工具系统

本章深入分析 Claude Code 的工具系统实现,包括工具基类设计、各类工具的实现细节、工具注册机制和权限检查流程。

6.1 Tool.ts 基类设计

Tool.ts 定义了所有工具的基类接口,是工具系统的核心抽象。

Tool 类型定义

export type Tool<
  Input extends AnyObject = AnyObject,
  Output = unknown,
  P extends ToolProgressData = ToolProgressData,
> = {
  // 标识属性
  readonly name: string              // 工具名称
  aliases?: string[]                 // 别名列表(兼容重命名)
  searchHint?: string                // 搜索提示(3-10词)
  readonly shouldDefer?: boolean     // 是否延迟加载
  readonly alwaysLoad?: boolean      // 是否始终加载
  readonly strict?: boolean          // 是否启用严格模式
  
  // Schema 定义
  readonly inputSchema: Input        // Zod 输入 Schema
  readonly inputJSONSchema?: ToolInputJSONSchema  // JSON Schema(MCP工具)
  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; ... },
  ): Promise<string>
  
  // 行为判断方法
  isEnabled(): boolean               // 是否启用
  isConcurrencySafe(input): boolean  // 是否并发安全
  isReadOnly(input): boolean         // 是否只读
  isDestructive?(input): boolean     // 是否破坏性操作
  
  // 权限方法
  checkPermissions(input, context): Promise<PermissionResult>
  validateInput?(input, context): Promise<ValidationResult>
  
  // UI 渲染方法
  userFacingName(input): string
  renderToolUseMessage(input, options): React.ReactNode
  renderToolResultMessage?(content, progressMessages, options): React.ReactNode
  renderToolUseProgressMessage?(progressMessages, options): React.ReactNode
  
  // 其他方法
  prompt(options): Promise<string>   // 工具提示生成
  mapToolResultToToolResultBlockParam(content, toolUseID): ToolResultBlockParam
  getToolUseSummary?(input): string | null  // 简要描述
  getActivityDescription?(input): string | null  // Spinner 描述
}

buildTool 工厂函数

const TOOL_DEFAULTS = {
  isEnabled: () => true,
  isConcurrencySafe: (_input?: unknown) => false,  // 默认不安全
  isReadOnly: (_input?: unknown) => false,         // 默认可写
  isDestructive: (_input?: unknown) => false,
  checkPermissions: (input, _ctx) =>
    Promise.resolve({ behavior: 'allow', updatedInput: input }),
  toAutoClassifierInput: (_input?: unknown) => '',
  userFacingName: (_input?: unknown) => '',
}

export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
  return {
    ...TOOL_DEFAULTS,
    userFacingName: () => def.name,
    ...def,
  } as BuiltTool<D>
}

Fail-Closed 设计

工具默认值采用 fail-closed(失败即关闭)原则:isConcurrencySafe 默认 false,isReadOnly 默认 false。这意味着安全相关属性必须显式声明为安全,而非默认安全。

ToolUseContext 定义

export type ToolUseContext = {
  options: {
    commands: Command[]
    debug: boolean
    mainLoopModel: string
    tools: Tools
    verbose: boolean
    thinkingConfig: ThinkingConfig
    mcpClients: MCPServerConnection[]
    mcpResources: Record<string, ServerResource[]>
    isNonInteractiveSession: boolean
    agentDefinitions: AgentDefinitionsResult
    maxBudgetUsd?: number
    customSystemPrompt?: string
    appendSystemPrompt?: string
  }
  
  abortController: AbortController
  readFileState: FileStateCache
  getAppState(): AppState
  setAppState(f: (prev: AppState) => AppState): void
  
  // 回调函数
  setToolJSX?: SetToolJSXFn
  addNotification?: (notif: Notification) => void
  appendSystemMessage?: (msg: SystemMessage) => void
  sendOSNotification?: (opts) => void
  
  // 状态追踪
  messages: Message[]
  fileReadingLimits?: { maxTokens?: number; maxSizeBytes?: number }
  globLimits?: { maxResults?: number }
  toolDecisions?: Map<string, { source: string; decision: 'accept' | 'reject'; timestamp: number }>
  queryTracking?: QueryChainTracking
  
  // Agent 相关
  agentId?: AgentId
  agentType?: string
}

6.2 tools/ 目录各工具实现

工具分类总览

graph TD
    subgraph "文件操作工具"
        READ[FileReadTool<br/>文件读取]
        WRITE[FileWriteTool<br/>文件写入]
        EDIT[FileEditTool<br/>文件编辑]
    end
    
    subgraph "搜索工具"
        GLOB[GlobTool<br/>文件匹配]
        GREP[GrepTool<br/>内容搜索]
        SEARCH[ToolSearchTool<br/>工具搜索]
    end
    
    subgraph "执行工具"
        BASH[BashTool<br/>Shell 命令]
        TASK[TaskOutputTool<br/>任务输出]
        TASKSTOP[TaskStopTool<br/>任务停止]
    end
    
    subgraph "网络工具"
        WEBFETCH[WebFetchTool<br/>网页获取]
        WEBSEARCH[WebSearchTool<br/>网络搜索]
    end
    
    subgraph "Agent 工具"
        AGENT[AgentTool<br/>子 Agent]
        SKILL[SkillTool<br/>技能调用]
        WORKTREE[EnterWorktreeTool<br/>工作树]
    end
    
    subgraph "状态管理工具"
        TODO[TodoWriteTool<br/>任务列表]
        CONFIG[ConfigTool<br/>配置管理]
        PLAN[EnterPlanModeTool<br/>计划模式]
    end
    
    subgraph "其他工具"
        LSP[LSPTool<br/>语言服务]
        NOTEBOOK[NotebookEditTool<br/>Jupyter 编辑]
        ASK[AskUserQuestionTool<br/>用户提问]
    end

文件操作工具

FileReadTool

// 核心功能:读取文件内容
const FileReadTool = buildTool({
  name: 'Read',
  inputSchema: z.object({
    file_path: z.string(),
    offset: z.number().optional(),
    limit: z.number().optional(),
  }),
  
  async call(args, context, canUseTool, parentMessage, onProgress) {
    // 权限检查
    const permissionResult = await canUseTool(this, args, context, ...)
    if (permissionResult.behavior !== 'allow') {
      return { data: { error: permissionResult.reason } }
    }
    
    // 读取文件
    const content = await readFile(args.file_path, args.offset, args.limit)
    
    // 更新缓存
    context.readFileState.set(args.file_path, content)
    
    return { data: { content, path: args.file_path } }
  },
  
  isReadOnly: () => true,
  maxResultSizeChars: Infinity,  // 不持久化结果
})

FileEditTool

// 核心功能:精确编辑文件
const FileEditTool = buildTool({
  name: 'Edit',
  inputSchema: z.object({
    file_path: z.string(),
    old_string: z.string(),
    new_string: z.string(),
    replace_all: z.boolean().optional(),
  }),
  
  async call(args, context, ...) {
    // 验证 old_string 存在
    const currentContent = await readFile(args.file_path)
    if (!currentContent.includes(args.old_string)) {
      return { data: { error: 'old_string not found' } }
    }
    
    // 执行编辑
    const newContent = args.replace_all
      ? currentContent.replaceAll(args.old_string, args.new_string)
      : currentContent.replace(args.old_string, args.new_string)
    
    await writeFile(args.file_path, newContent)
    
    return { data: { success: true } }
  },
  
  isDestructive: () => true,  // 标记为破坏性操作
})

FileWriteTool

// 核心功能:写入新文件
const FileWriteTool = buildTool({
  name: 'Write',
  inputSchema: z.object({
    file_path: z.string(),
    content: z.string(),
  }),
  
  isDestructive: (input) => {
    // 覆盖已存在文件为破坏性
    return fs.existsSync(input.file_path)
  },
  
  checkPermissions: async (input, context) => {
    // 检查是否覆盖重要文件
    if (await isProtectedFile(input.file_path)) {
      return { behavior: 'deny', reason: 'Protected file' }
    }
    return { behavior: 'allow', updatedInput: input }
  },
})

搜索工具

GlobTool

// 核心功能:文件模式匹配
const GlobTool = buildTool({
  name: 'Glob',
  inputSchema: z.object({
    pattern: z.string(),
    path: z.string().optional(),
  }),
  
  searchHint: 'find files matching pattern',
  
  async call(args, context, ...) {
    const matches = await glob(args.pattern, {
      cwd: args.path ?? getCwd(),
      absolute: true,
    })
    
    return { data: { matches } }
  },
  
  isReadOnly: () => true,
  isConcurrencySafe: () => true,
  isSearchOrReadCommand: () => ({ isSearch: true, isRead: false }),
})

GrepTool

// 核心功能:内容正则搜索
const GrepTool = buildTool({
  name: 'Grep',
  inputSchema: z.object({
    pattern: z.string(),
    path: z.string().optional(),
    glob: z.string().optional(),
    output_mode: z.enum(['content', 'files_with_matches', 'count']).optional(),
    -C: z.number().optional(),  // 上下文行数
  }),
  
  searchHint: 'search file contents regex pattern',
  
  async call(args, context, ...) {
    const results = await grep(args.pattern, {
      path: args.path ?? getCwd(),
      glob: args.glob,
      outputMode: args.output_mode ?? 'files_with_matches',
      contextLines: args['-C'],
    })
    
    return { data: results }
  },
  
  isSearchOrReadCommand: () => ({ isSearch: true, isRead: false }),
})

执行工具

BashTool

// 核心功能:执行 Shell 命令
const BashTool = buildTool({
  name: 'Bash',
  inputSchema: z.object({
    command: z.string(),
    description: z.string().optional(),
    timeout: z.number().optional(),
    run_in_background: z.boolean().optional(),
  }),
  
  async call(args, context, canUseTool, parentMessage, onProgress) {
    // 进度回调
    onProgress?.({
      toolUseID: '...',
      data: { type: 'bash', command: args.command, status: 'running' }
    })
    
    // 执行命令
    const result = await execa(args.command, {
      timeout: args.timeout ?? 120000,
      cwd: getCwd(),
      shell: true,
    })
    
    onProgress?.({
      toolUseID: '...',
      data: { type: 'bash', command: args.command, status: 'completed' }
    })
    
    return {
      data: {
        stdout: result.stdout,
        stderr: result.stderr,
        exitCode: result.exitCode,
      }
    }
  },
  
  isDestructive: (input) => {
    // 检查是否破坏性命令
    const destructivePatterns = ['rm', 'delete', 'drop', 'truncate']
    return destructivePatterns.some(p => input.command.includes(p))
  },
})

Agent 工具

AgentTool

// 核心功能:派发子 Agent
const AgentTool = buildTool({
  name: 'Agent',
  inputSchema: z.object({
    name: z.string().optional(),
    color: z.enum(['blue', 'yellow', 'green', 'purple', 'orange']).optional(),
    description: z.string().optional(),
    prompt: z.string(),
    tools: z.array(z.string()).optional(),
    model: z.string().optional(),
    isolated: z.boolean().optional(),
  }),
  
  async call(args, context, ...) {
    // 创建 AgentDefinition
    const agentDef: AgentDefinition = {
      name: args.name ?? 'default',
      color: args.color ?? 'blue',
      description: args.description ?? '',
      model: args.model,
      tools: args.tools,
      isolated: args.isolated ?? false,
    }
    
    // 如果需要隔离,创建 Worktree
    if (args.isolated) {
      const worktree = await createWorktree()
      // ... 在 worktree 中执行 Agent
    }
    
    // 启动子 Agent
    const result = await runSubAgent(agentDef, args.prompt, context)
    
    return { data: result }
  },
})

6.3 工具注册与调用机制

tools.ts 工具注册

export function getAllBaseTools(): 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, TungstenTool] : []),
    ...(isEnvTruthy(process.env.ENABLE_LSP_TOOL) ? [LSPTool] : []),
    ...(isWorktreeModeEnabled() ? [EnterWorktreeTool, ExitWorktreeTool] : []),
    // ... 更多工具
  ]
}

export const getTools = (permissionContext: ToolPermissionContext): Tools => {
  // Simple 模式过滤
  if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
    return filterToolsByDenyRules([BashTool, FileReadTool, FileEditTool], permissionContext)
  }
  
  // 获取基础工具并过滤
  const tools = getAllBaseTools()
  let allowedTools = filterToolsByDenyRules(tools, permissionContext)
  
  // REPL 模式处理
  if (isReplModeEnabled()) {
    allowedTools = allowedTools.filter(tool => !REPL_ONLY_TOOLS.has(tool.name))
  }
  
  // isEnabled 过滤
  return allowedTools.filter(tool => tool.isEnabled())
}

// 组合工具池(内置 + MCP)
export function assembleToolPool(
  permissionContext: ToolPermissionContext,
  mcpTools: Tools,
): Tools {
  const builtInTools = getTools(permissionContext)
  const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)
  
  // 排序并去重(内置工具优先)
  return uniqBy(
    [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
    'name',
  )
}

工具调用流程

sequenceDiagram
    participant Model as Claude Model
    participant Query as query()
    participant Matcher as Tool Matcher
    participant Permission as Permission Check
    participant Tool as Tool Instance
    participant Executor as Tool Executor
    
    Model->>Query: tool_use block
    Query->>Matcher: 查找工具
    Matcher->>Matcher: toolMatchesName()
    Matcher-->>Query: Tool 实例
    
    Query->>Permission: canUseTool()
    Permission->>Permission: validateInput()
    Permission->>Permission: checkPermissions()
    Permission->>Permission: 用户确认(交互模式)
    Permission-->>Query: PermissionResult
    
    alt 允许执行
        Query->>Tool: call(input, context, ...)
        Tool->>Executor: 执行操作
        Executor-->>Tool: 结果数据
        Tool-->>Query: ToolResult
    else 拒绝执行
        Query-->>Model: tool_result (error)
    end

工具匹配函数

export function toolMatchesName(
  tool: { name: string; aliases?: string[] },
  name: string,
): boolean {
  return tool.name === name || (tool.aliases?.includes(name) ?? false)
}

export function findToolByName(tools: Tools, name: string): Tool | undefined {
  return tools.find(t => toolMatchesName(t, name))
}

6.4 权限检查流程

权限检查流程图

flowchart TD
    subgraph "权限检查流程"
        START[Tool Use Request] --> VALIDATE{validateInput?}
        VALIDATE --> |"有"| VALIDATE_EXEC[执行输入验证]
        VALIDATE --> |"无"| PERMISSION[进入权限检查]
        VALIDATE_EXEC --> VALIDATE_RESULT{验证通过?}
        VALIDATE_RESULT --> |"否"| DENY1[返回错误]
        VALIDATE_RESULT --> |"是"| PERMISSION
        
        PERMISSION --> RULES[检查 Permission Rules]
        RULES --> ALWAYS_ALLOW{AlwaysAllow?}
        ALWAYS_ALLOW --> |"是"| ALLOW[允许执行]
        ALWAYS_ALLOW --> |"否"| ALWAYS_DENY{AlwaysDeny?}
        ALWAYS_DENY --> |"是"| DENY2[拒绝执行]
        ALWAYS_DENY --> |"否"| ALWAYS_ASK{AlwaysAsk?}
        ALWAYS_ASK --> |"是"| PROMPT[提示用户]
        ALWAYS_ASK --> |"否"| DEFAULT{默认规则}
        DEFAULT --> MODE{PermissionMode?}
        
        MODE --> |"auto"| AUTO_DECISION[自动决策]
        MODE --> |"default"| INTERACTIVE[交互确认]
        MODE --> |"plan"| PLAN_CHECK[Plan 模式检查]
        
        AUTO_DECISION --> ALLOW_OR_DENY
        INTERACTIVE --> USER_INPUT{用户选择}
        USER_INPUT --> |"允许"| ALLOW
        USER_INPUT --> |"拒绝"| DENY3
        PLAN_CHECK --> PLAN_ALLOW{符合 Plan?}
        PLAN_ALLOW --> |"是"| ALLOW
        PLAN_ALLOW --> |"否"| DENY4
        
        ALLOW --> EXECUTE[执行工具]
        DENY1 --> RETURN[返回结果]
        DENY2 --> RETURN
        DENY3 --> RETURN
        DENY4 --> RETURN
        EXECUTE --> RETURN
    end

PermissionResult 类型

export type PermissionResult = {
  behavior: 'allow' | 'deny'
  reason?: string
  updatedInput?: { [key: string]: unknown }
  decision?: 'accept' | 'reject'
  source?: 'rule' | 'user' | 'auto'
}

权限模式说明

模式说明行为
default默认模式交互式用户确认
auto自动模式根据规则自动决策
plan计划模式仅允许计划内操作
bypass绕过模式允许所有操作(危险)

bypass 模式风险

bypass 模式允许所有操作无需确认,仅用于测试或受控环境。生产环境应使用 defaultauto 模式。

权限规则配置

type ToolPermissionContext = {
  mode: PermissionMode
  additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
  alwaysAllowRules: ToolPermissionRulesBySource  // 允许规则
  alwaysDenyRules: ToolPermissionRulesBySource   // 拒绝规则
  alwaysAskRules: ToolPermissionRulesBySource    // 确认规则
  isBypassPermissionsModeAvailable: boolean
  shouldAvoidPermissionPrompts?: boolean         // 后台 Agent
}

// 规则示例
const alwaysAllowRules = {
  userSettings: { Bash: ['git *', 'npm *'] },
  projectSettings: { Read: ['.claude/*'] },
}

const alwaysDenyRules = {
  projectSettings: { Bash: ['rm *', 'sudo *'] },
}

工具执行包装

// runTools 实现
export async function* runTools(
  toolUseBlocks: ToolUseBlock[],
  assistantMessages: AssistantMessage[],
  canUseTool: CanUseToolFn,
  toolUseContext: ToolUseContext,
): AsyncGenerator<ToolUpdate> {
  for (const block of toolUseBlocks) {
    const tool = findToolByName(toolUseContext.options.tools, block.name)
    
    if (!tool) {
      yield {
        message: createToolResultMessage(block.id, `Unknown tool: ${block.name}`),
      }
      continue
    }
    
    // 权限检查
    const permissionResult = await canUseTool(tool, block.input, toolUseContext, ...)
    
    if (permissionResult.behavior === 'deny') {
      yield {
        message: createToolResultMessage(block.id, permissionResult.reason ?? 'Denied'),
      }
      continue
    }
    
    // 执行工具
    const result = await tool.call(block.input, toolUseContext, canUseTool, ...)
    
    yield {
      message: createToolResultMessage(block.id, result.data),
    }
  }
}

下一章将分析 技能系统 的实现细节。