Files
claude-code-mirror/claude-code-中文Wiki/02-核心模块详解.md
2026-04-03 13:01:19 +08:00

20 KiB
Raw Blame History

核心模块详解

本文件说明:详细分析 Claude Code 的核心源文件,理解其实现原理。

1. main.tsx - CLI 入口点

1.1 功能概述

main.tsx 是 Claude Code 应用程序的入口点,负责:

  • CLI 参数解析
  • 应用程序初始化
  • REPLRead-Eval-Print Loop启动
  • 各种启动前检查和配置

1.2 启动流程

// 1. 启动性能分析
profileCheckpoint('main_tsx_entry')

// 2. MDM 设置预读取(并行)
startMdmRawRead()

// 3. Keychain 凭据预读取(并行)
startKeychainPrefetch()

// 4. 初始化遥测
initializeTelemetryAfterTrust()

// 5. 获取引导数据
const bootstrapData = await fetchBootstrapData()

// 6. 启动 REPL
launchRepl()

1.3 关键导入

import { Command as CommanderCommand } from '@commander-js/extra-typings'
import React from 'react'
import chalk from 'chalk'
import { getTools } from './tools.js'
import { getCommands, filterCommandsForRemoteMode } from './commands.js'
import { getSystemContext, getUserContext } from './context.js'

1.4 核心配置初始化

// 权限模式设置
const initialPermissionMode = await initialPermissionModeFromCLI()

// 工具权限上下文初始化
initializeToolPermissionContext({
  mode: initialPermissionMode,
  additionalWorkingDirectories: new Map(),
  alwaysAllowRules: {},
  alwaysDenyRules: {},
  alwaysAskRules: {},
  isBypassPermissionsModeAvailable: false,
})

// 获取工具列表
const tools = getTools(appState.toolPermissionContext)

// 获取命令列表
const commands = await getCommands(cwd)

2. QueryEngine.ts - LLM 查询引擎核心

2.1 功能概述

QueryEngine 是 Claude Code 的核心查询处理类,负责:

  • 管理对话生命周期(每个对话一个 QueryEngine 实例)
  • 处理用户消息提交
  • 执行 API 调用循环
  • 处理流式响应
  • 编排工具执行
  • 管理会话状态消息、Token 使用等)

2.2 类结构

export class QueryEngine {
  private config: QueryEngineConfig
  private mutableMessages: Message[]      // 对话消息列表
  private abortController: AbortController // 中断控制器
  private permissionDenials: SDKPermissionDenial[] // 权限拒绝记录
  private totalUsage: NonNullableUsage     // 累计 Token 使用
  private hasHandledOrphanedPermission = false
  private readFileState: FileStateCache    // 文件读取缓存
  private discoveredSkillNames = new Set<string>()
  private loadedNestedMemoryPaths = new Set<string>()
}

2.3 核心方法submitMessage()

这是处理用户输入的主要方法:

async *submitMessage(
  prompt: string | ContentBlockParam[],
  options?: { uuid?: string; isMeta?: boolean },
): AsyncGenerator<SDKMessage, void, unknown>

执行流程

  1. 初始化阶段

    this.discoveredSkillNames.clear()
    setCwd(cwd)
    
    // 获取系统提示词
    const { defaultSystemPrompt, userContext, systemContext } =
      await fetchSystemPromptParts({ tools, mainLoopModel, ... })
    
  2. 处理用户输入

    const { messages, shouldQuery, allowedTools, model, resultText } =
      await processUserInput({
        input: prompt,
        mode: 'prompt',
        context: processUserInputContext,
      })
    
    // 更新权限规则
    setAppState(prev => ({
      ...prev,
      toolPermissionContext: {
        ...prev.toolPermissionContext,
        alwaysAllowRules: { command: allowedTools }
      }
    }))
    
  3. 构建系统提示

    const systemPrompt = asSystemPrompt([
      ...(customPrompt ?? defaultSystemPrompt),
      ...(memoryMechanicsPrompt ?? []),  // 记忆机制提示
      ...(appendSystemPrompt ?? [])
    ])
    
  4. 查询循环

    for await (const message of query({
      messages,
      systemPrompt,
      userContext,
      systemContext,
      canUseTool: wrappedCanUseTool,
    })) {
      // 处理各种消息类型
      switch (message.type) {
        case 'assistant': /* 记录助手消息 */ break
        case 'progress': /* 进度更新 */ break
        case 'user': /* 用户消息 */ break
        case 'stream_event': /* 流式事件 */ break
        case 'attachment': /* 附件处理 */ break
      }
    }
    

2.4 流式响应处理

QueryEngine 处理多种流式事件:

if (message.type === 'stream_event') {
  // message_start: 新消息开始
  if (message.event.type === 'message_start') {
    currentMessageUsage = EMPTY_USAGE
    currentMessageUsage = updateUsage(currentMessageUsage, message.event.message.usage)
  }

  // message_delta: 消息增量更新
  if (message.event.type === 'message_delta') {
    currentMessageUsage = updateUsage(currentMessageUsage, message.event.usage)
    if (message.event.delta.stop_reason != null) {
      lastStopReason = message.event.delta.stop_reason
    }
  }

  // message_stop: 消息结束
  if (message.event.type === 'message_stop') {
    this.totalUsage = accumulateUsage(this.totalUsage, currentMessageUsage)
  }
}

2.5 思考模式 (Thinking Mode)

Claude Code 支持 Claude 的思考模式:

const initialThinkingConfig: ThinkingConfig = thinkingConfig
  ? thinkingConfig
  : shouldEnableThinkingByDefault() !== false
    ? { type: 'adaptive' }  // 自适应思考
    : { type: 'disabled' }  // 禁用

思考规则(注释中的说明):

  1. 包含思考或编辑块的消息必须在 max_thinking_length > 0 的查询中
  2. 思考块不能是消息块中的最后一个
  3. 思考块必须在整个助手轨迹中保留

2.6 错误恢复机制

// USD 预算超限检查
if (maxBudgetUsd !== undefined && getTotalCost() >= maxBudgetUsd) {
  yield {
    type: 'result',
    subtype: 'error_max_budget_usd',
    ...
  }
  return
}

// 最大轮次检查
if (maxTurns && nextTurnCount > maxTurns) {
  yield { type: 'result', subtype: 'error_max_turns', ... }
  return
}

// 结构化输出重试限制
if (callsThisQuery >= maxRetries) {
  yield { type: 'result', subtype: 'error_max_structured_output_retries', ... }
  return
}

3. Tool.ts - 工具基类和接口定义

3.1 工具接口定义

export type Tool<
  Input extends AnyObject = AnyObject,
  Output = unknown,
  P extends ToolProgressData = ToolProgressData,
> = {
  // 工具名称
  readonly name: string

  // 可选别名(用于工具重命名后的向后兼容)
  aliases?: string[]

  // 工具搜索提示词
  searchHint?: string

  // 核心执行方法
  call(
    args: z.infer<Input>,
    context: ToolUseContext,
    canUseTool: CanUseToolFn,
    parentMessage: AssistantMessage,
    onProgress?: ToolCallProgress<P>,
  ): Promise<ToolResult<Output>>

  // 获取工具描述
  description(
    input: z.infer<Input>,
    options: {...}
  ): Promise<string>

  // 输入 Schema
  readonly inputSchema: Input

  // 输入 JSON Schema用于 MCP 工具)
  readonly inputJSONSchema?: ToolInputJSONSchema

  // 输出 Schema
  outputSchema?: z.ZodType<unknown>
}

3.2 工具权限方法

// 验证输入是否有效
validateInput?(
  input: z.infer<Input>,
  context: ToolUseContext,
): Promise<ValidationResult>

// 检查权限
checkPermissions(
  input: z.infer<Input>,
  context: ToolUseContext,
): Promise<PermissionResult>

// 准备权限匹配器
preparePermissionMatcher?(
  input: z.infer<Input>,
): Promise<(pattern: string) => boolean>

3.3 工具渲染方法

// 渲染工具使用消息
renderToolUseMessage(
  input: Partial<z.infer<Input>>,
  options: { theme: ThemeName; verbose: boolean; commands?: Command[] }
): React.ReactNode

// 渲染工具结果消息
renderToolResultMessage?(
  content: Output,
  progressMessagesForMessage: ProgressMessage<P>[],
  options: {...}
): React.ReactNode

// 渲染工具使用标签
renderToolUseTag?(input: Partial<z.infer<Input>>): React.ReactNode

// 渲染进度消息
renderToolUseProgressMessage?(
  progressMessagesForMessage: ProgressMessage<P>[],
  options: {...}
): React.ReactNode

3.4 buildTool 工厂函数

const TOOL_DEFAULTS = {
  isEnabled: () => true,
  isConcurrencySafe: (_input?: unknown) => false,
  isReadOnly: (_input?: unknown) => false,
  isDestructive: (_input?: unknown) => false,
  checkPermissions: async () => ({ 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>
}

3.5 工具权限上下文

export type ToolPermissionContext = {
  mode: PermissionMode  // 'default' | 'auto' | 'bypass' | 'plan'
  additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
  alwaysAllowRules: ToolPermissionRulesBySource
  alwaysDenyRules: ToolPermissionRulesBySource
  alwaysAskRules: ToolPermissionRulesBySource
  isBypassPermissionsModeAvailable: boolean
  isAutoModeAvailable?: boolean
  strippedDangerousRules?: ToolPermissionRulesBySource
  shouldAvoidPermissionPrompts?: boolean
  awaitAutomatedChecksBeforeDialog?: boolean
  prePlanMode?: PermissionMode
}

4. commands.ts - 命令注册中心

4.1 功能概述

commands.ts 是 Claude Code 的命令注册中心,管理所有 slash commands/ 开头的命令)。

4.2 命令类型

type CommandType =
  | 'prompt'    // 展开为提示发送给模型
  | 'local'     // 本地执行,返回文本
  | 'local-jsx' // 本地执行,渲染 Ink UI

4.3 命令定义结构

interface Command {
  type: CommandType
  name: string
  aliases?: string[]
  description: string
  contentLength: number
  source: 'builtin' | 'plugin' | 'bundled' | 'mcp' | ...
  availability?: ('claude-ai' | 'console')[]
  // ...
}

4.4 命令加载流程

// 1. 获取技能目录命令
const skillDirCommands = await getSkillDirCommands(cwd)

// 2. 获取插件命令
const pluginCommands = await getPluginCommands()

// 3. 获取内置命令
const builtinCommands = COMMANDS()

// 4. 合并并过滤
const allCommands = [
  ...bundledSkills,
  ...builtinPluginSkills,
  ...skillDirCommands,
  ...workflowCommands,
  ...pluginCommands,
  ...pluginSkills,
  ...COMMANDS(),
]

// 5. 检查可用性要求
const filteredCommands = allCommands.filter(cmd =>
  meetsAvailabilityRequirement(cmd)
)

// 6. 检查是否启用
.filter(cmd => isCommandEnabled(cmd))

4.5 远程安全命令

export const REMOTE_SAFE_COMMANDS: Set<Command> = new Set([
  session, exit, clear, help, theme, color, vim,
  cost, usage, copy, btw, feedback, plan,
  keybindings, statusline, stickers, mobile,
])

4.6 命令查找

export function findCommand(
  commandName: string,
  commands: Command[],
): Command | undefined {
  return commands.find(
    _ =>
      _.name === commandName ||
      getCommandName(_) === commandName ||
      _.aliases?.includes(commandName)
  )
}

5. context.ts - 系统/用户上下文收集

5.1 功能概述

context.ts 负责收集系统上下文和用户上下文,这些上下文会被预置到每个对话的开头。

5.2 Git 状态收集

export const getGitStatus = memoize(async (): Promise<string | null> => {
  // 检查是否是 Git 仓库
  const isGit = await getIsGit()
  if (!isGit) return null

  // 并行获取多个 Git 信息
  const [branch, mainBranch, status, log, userName] = await Promise.all([
    getBranch(),
    getDefaultBranch(),
    execFileNoThrow(gitExe(), ['status', '--short'], ...),
    execFileNoThrow(gitExe(), ['log', '--oneline', '-n', '5'], ...),
    execFileNoThrow(gitExe(), ['config', 'user.name'], ...),
  ])

  return [
    `Current branch: ${branch}`,
    `Main branch: ${mainBranch}`,
    `Git user: ${userName}`,
    `Status:\n${truncatedStatus}`,
    `Recent commits:\n${log}`,
  ].join('\n\n')
})

5.3 系统上下文

export const getSystemContext = memoize(async () => {
  const gitStatus = await getGitStatus()

  // 缓存破坏注入(仅用于调试)
  const injection = feature('BREAK_CACHE_COMMAND')
    ? getSystemPromptInjection()
    : null

  return {
    ...(gitStatus && { gitStatus }),
    ...(injection && { cacheBreaker: `[CACHE_BREAKER: ${injection}]` }),
  }
})

5.4 用户上下文

export const getUserContext = memoize(async () => {
  // 获取 CLAUDE.md 文件内容
  const shouldDisableClaudeMd = isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_CLAUDE_MDS)
    || (isBareMode() && getAdditionalDirectoriesForClaudeMd().length === 0)

  const claudeMd = shouldDisableClaudeMd
    ? null
    : getClaudeMds(filterInjectedMemoryFiles(await getMemoryFiles()))

  // 缓存 CLAUDE.md 内容供自动模式分类器使用
  setCachedClaudeMdContent(claudeMd || null)

  return {
    ...(claudeMd && { claudeMd }),
    currentDate: `Today's date is ${getLocalISODate()}.`,
  }
})

5.5 上下文缓存

所有上下文函数都使用 memoize 进行缓存,确保在一次对话中只计算一次:

export const getSystemContext = memoize(async () => {...})
export const getUserContext = memoize(async () => {...})
export const getGitStatus = memoize(async () => {...})

6. cost-tracker.ts - Token 成本追踪

6.1 功能概述

cost-tracker.ts 负责追踪和计算 API 调用的 Token 使用量和成本。

6.2 核心数据结构

type StoredCostState = {
  totalCostUSD: number
  totalAPIDuration: number
  totalAPIDurationWithoutRetries: number
  totalToolDuration: number
  totalLinesAdded: number
  totalLinesRemoved: number
  lastDuration: number | undefined
  modelUsage: { [modelName: string]: ModelUsage }
}

type ModelUsage = {
  inputTokens: number
  outputTokens: number
  cacheReadInputTokens: number
  cacheCreationInputTokens: number
  webSearchRequests: number
  costUSD: number
  contextWindow: number
  maxOutputTokens: number
}

6.3 成本计算

export function addToTotalSessionCost(
  cost: number,
  usage: Usage,
  model: string,
): number {
  const modelUsage = addToTotalModelUsage(cost, usage, model)
  addToTotalCostState(cost, modelUsage, model)

  // 发送到 Statsig 计数器
  getCostCounter()?.add(cost, { model })
  getTokenCounter()?.add(usage.input_tokens, { ...attrs, type: 'input' })
  getTokenCounter()?.add(usage.output_tokens, { ...attrs, type: 'output' })

  // 处理 Advisor 使用量
  for (const advisorUsage of getAdvisorUsage(usage)) {
    const advisorCost = calculateUSDCost(advisorUsage.model, advisorUsage)
    totalCost += addToTotalSessionCost(advisorCost, advisorUsage, advisorUsage.model)
  }
  return totalCost
}

6.4 成本格式化

export function formatTotalCost(): string {
  const costDisplay = formatCost(getTotalCostUSD())

  return chalk.dim(`
Total cost:            ${costDisplay}
Total duration (API):  ${formatDuration(getTotalAPIDuration())}
Total duration (wall): ${formatDuration(getTotalDuration())}
Total code changes:    ${linesAdded} lines added, ${linesRemoved} lines removed
${formatModelUsage()}
  `)
}

6.5 会话成本保存/恢复

// 保存当前会话成本到项目配置
export function saveCurrentSessionCosts(fpsMetrics?: FpsMetrics): void {
  saveCurrentProjectConfig(current => ({
    ...current,
    lastCost: getTotalCostUSD(),
    lastAPIDuration: getTotalAPIDuration(),
    lastLinesAdded: getTotalLinesAdded(),
    lastLinesRemoved: getTotalLinesRemoved(),
    lastModelUsage: Object.fromEntries(...),
    lastSessionId: getSessionId(),
  }))
}

// 恢复会话成本
export function restoreCostStateForSession(sessionId: string): boolean {
  const data = getStoredSessionCosts(sessionId)
  if (!data) return false
  setCostStateForRestore(data)
  return true
}

7. query.ts - 查询管道

7.1 功能概述

query.ts 是核心的查询处理管道,负责:

  • 消息流式 API 调用
  • 工具调用编排
  • 自动压缩
  • 错误恢复

7.2 主查询循环

export async function* query(
  params: QueryParams,
): AsyncGenerator<...> {
  const consumedCommandUuids: string[] = []
  const terminal = yield* queryLoop(params, consumedCommandUuids)
  return terminal
}

async function* queryLoop(
  params: QueryParams,
  consumedCommandUuids: string[],
): AsyncGenerator<...> {
  let state: State = {
    messages: params.messages,
    toolUseContext: params.toolUseContext,
    autoCompactTracking: undefined,
    maxOutputTokensRecoveryCount: 0,
    // ...
  }

  while (true) {
    // 1. 上下文压缩
    const { compactionResult } = await deps.autocompact(...)

    // 2. API 调用循环
    for await (const message of deps.callModel({...})) {
      if (message.type === 'assistant') {
        assistantMessages.push(message)
        // 处理工具调用块
      }
    }

    // 3. 工具执行
    if (needsFollowUp) {
      const toolUpdates = runTools(toolUseBlocks, ...)
      for await (const update of toolUpdates) {
        // 处理工具结果
      }
    }

    // 4. 递归继续
    state = { ...state, messages: [...messages, ...assistantMessages, ...toolResults] }
  }
}

7.3 工具执行编排

// 两种工具执行模式
if (streamingToolExecutor) {
  // 流式工具执行:在模型流式输出的同时执行工具
  logEvent('tengu_streaming_tool_execution_used', {...})
} else {
  // 批量工具执行:等待模型完全响应后执行
  logEvent('tengu_streaming_tool_execution_not_used', {...})
}

const toolUpdates = streamingToolExecutor
  ? streamingToolExecutor.getRemainingResults()
  : runTools(toolUseBlocks, assistantMessages, canUseTool, toolUseContext)

7.4 错误恢复机制

// 1. 模型回退
if (innerError instanceof FallbackTriggeredError && fallbackModel) {
  currentModel = fallbackModel
  attemptWithFallback = true
  continue
}

// 2. Prompt Too Long 恢复
if (isWithheld413) {
  // 尝试排空上下文折叠
  const drained = contextCollapse.recoverFromOverflow(...)
  if (drained.committed > 0) {
    state = { ...state, messages: drained.messages }
    continue
  }

  // 尝试响应式压缩
  const compacted = await reactiveCompact.tryReactiveCompact(...)
  if (compacted) {
    // ...
  }
}

// 3. Max Output Tokens 恢复
if (maxOutputTokensRecoveryCount < MAX_OUTPUT_TOKENS_RECOVERY_LIMIT) {
  const recoveryMessage = createUserMessage({
    content: `Output token limit hit. Resume directly...`
  })
  state = { ...state, messages: [...messages, recoveryMessage] }
  continue
}

7.5 上下文压缩

// Snip去除压缩
if (feature('HISTORY_SNIP')) {
  const snipResult = snipModule!.snipCompactIfNeeded(messagesForQuery)
  messagesForQuery = snipResult.messages
  snipTokensFreed = snipResult.tokensFreed
}

// 微压缩
const microcompactResult = await deps.microcompact(messagesForQuery, ...)
messagesForQuery = microcompactResult.messages

// 自动压缩
const { compactionResult } = await deps.autocompact(
  messagesForQuery,
  toolUseContext,
  { systemPrompt, userContext, systemContext, toolUseContext },
  querySource,
  tracking,
  snipTokensFreed,
)

7.6 工具使用摘要

// 为耗时的 Haiku 调用生成工具使用摘要
if (config.gates.emitToolUseSummaries && toolUseBlocks.length > 0) {
  const summary = await generateToolUseSummary({
    tools: toolInfoForSummary,
    signal: toolUseContext.abortController.signal,
    lastAssistantText,
  })

  // 异步生成,不阻塞下一个 API 调用
  nextPendingToolUseSummary = summary.then(s =>
    s ? createToolUseSummaryMessage(summary, toolUseIds) : null
  )
}

8. 总结

这七个核心文件共同构成了 Claude Code 的核心架构:

文件 职责
main.tsx CLI 入口,应用程序初始化
QueryEngine.ts 对话生命周期管理,消息处理
Tool.ts 工具基类和接口定义
commands.ts Slash 命令注册和管理
context.ts 系统/用户上下文收集
cost-tracker.ts Token 成本追踪
query.ts API 调用管道,工具编排

理解这些核心模块是深入研究 Claude Code 源码的基础。