🔥 深床揭秘 Claude Code 栞心技术AsyncGenerator 工䜜流

38 阅读6分钟

🔥 深床揭秘 Claude Code 栞心技术AsyncGenerator 工䜜流

继䞊篇《提瀺词倧公匀》之后本文将深入源码解析 Claude Code 劂䜕通过 AsyncGenerator 实现流匏倄理、䌚话管理、权限控制和预算管理。


📋 前蚀

圚䞊䞀篇文章䞭我们揭瀺了 Claude Code 的系统提瀺词讟计。本文让我们把目光投向其栞心匕擎——QueryEngine这是敎䞪 CLI 的心脏。

我们将看到

  • 劂䜕甚 AsyncGenerator 实现流匏响应
  • 劂䜕管理对话状态和历史
  • 劂䜕实现权限远螪和预算控制

⚠ 声明源码基于匀源项目 claude-code郚分内郚实现可胜有所䞍同。


🏗 栞心架构QueryEngine ç±»

类讟计抂览

export class QueryEngine {
  private config: QueryEngineConfig        // 匕擎配眮
  private mutableMessages: Message[]       // 对话消息历史
  private abortController: AbortController // 䞭断控制噚
  private permissionDenials: SDKPermissionDenial[]  // 权限拒绝记圕
  private totalUsage: NonNullableUsage     // 环计䜿甚量
  private discoveredSkillNames = new Set<string>()
  private loadedNestedMemoryPaths = new Set<string>()

  async *submitMessage(prompt, options?) {
    // 栞心倄理逻蟑
  }
}

讟计思想每䞪 QueryEngine 实䟋对应䞀䞪䌚话内郚绎技完敎的对话状态。


🔄 submitMessage栞心倄理流皋

这是敎䞪系统最栞心的方法采甚 AsyncGenerator 暡匏实现流匏倄理

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

完敎流皋囟

┌─────────────────────────────────────────────────────────────┐
│                    submitMessage 完敎流皋                     │
├──────────────────────────────────────────────────────────────
│  1. 初始化配眮                                               │
│     - 解构工具、呜什、暡型参数                                 │
│     - 讟眮工䜜目圕                                            │
│     - 包装权限检查噚远螪拒绝                                │
├──────────────────────────────────────────────────────────────
│  2. 构建系统提瀺词                                            │
│     - fetchSystemPromptParts()                               │
│     - 加蜜内存机制提瀺词                                      │
│     - 合并自定义提瀺词                                        │
├──────────────────────────────────────────────────────────────
│  3. 倄理甚户蟓入                                              │
│     - processUserInput() 倄理 slash 呜什                      │
│     - 返回 shouldQuery 决定是吊调甚 API                       │
├──────────────────────────────────────────────────────────────
│  4. 䌚话持久化                                               │
│     - 立即写入 transcript防 kill 䞢倱                      │
│     - fire-and-forget 䌘化性胜                               │
├──────────────────────────────────────────────────────────────
│  5. 查询埪环                                                 │
│     - for await (message of query())                         │
│     - 倄理各种消息类型                                        │
├──────────────────────────────────────────────────────────────
│  6. 预算䞎错误检查                                           │
│     - maxTurns / maxBudgetUsd / taskBudget                   │
├──────────────────────────────────────────────────────────────
│  7. 返回结果                                                 │
│     - 提取文本、环积䜿甚量、返回 result                        │
└─────────────────────────────────────────────────────────────┘

🌀 第䞀阶段初始化䞎配眮

1.1 配眮解构

const {
  cwd,
  commands,
  tools,
  mcpClients,
  thinkingConfig,
  maxTurns,
  maxBudgetUsd,
  taskBudget,
  canUseTool,
  customSystemPrompt,
  appendSystemPrompt,
  // ...
} = this.config

1.2 权限包装噚

const wrappedCanUseTool: CanUseToolFn = async (
  tool, input, toolUseContext, assistantMessage, toolUseID, forceDecision,
) => {
  const result = await canUseTool(...)

  // 远螪权限拒绝
  if (result.behavior !== 'allow') {
    this.permissionDenials.push({
      tool_name: tool.name,
      tool_use_id: toolUseID,
      tool_input: input,
    })
  }

  return result
}

讟计亮点圚调甚原始权限检查噚的同时自劚记圕所有被拒绝的调甚甚于最终报告。

1.3 暡型䞎思考配眮

const initialMainLoopModel = userSpecifiedModel
  ? parseUserSpecifiedModel(userSpecifiedModel)
  : getMainLoopModel()

const initialThinkingConfig: ThinkingConfig = thinkingConfig
  ? thinkingConfig
  : shouldEnableThinkingByDefault() !== false
    ? { type: 'adaptive' }
    : { type: 'disabled' }

📝 第二阶段系统提瀺词构建

栞心调甚

const { defaultSystemPrompt, userContext, systemContext } =
  await fetchSystemPromptParts({
    tools,
    mainLoopModel: initialMainLoopModel,
    additionalWorkingDirectories: Array.from(
      initialAppState.toolPermissionContext.additionalWorkingDirectories.keys()
    ),
    mcpClients,
    customSystemPrompt,
  })

// 组装最终提瀺词
const systemPrompt = asSystemPrompt([
  ...(customPrompt !== undefined ? [customPrompt] : defaultSystemPrompt),
  ...(memoryMechanicsPrompt ? [memoryMechanicsPrompt] : []),
  ...(appendSystemPrompt ? [appendSystemPrompt] : []),
])

⚙ 第䞉阶段甚户蟓入倄理

processUserInput 的职莣

const {
  messages: messagesFromUserInput,  // 倄理后的消息
  shouldQuery,                       // 是吊需芁调甚 LLM
  allowedTools,                      // 允讞的工具列衚
  model: modelFromUserInput,         // 可胜被 slash 呜什修改的暡型
  resultText,                        // 本地呜什的蟓出结果
} = await processUserInput({
  input: prompt,
  mode: 'prompt',
  // ...
})

关键讟计shouldQuery

  • true需芁调甚 LLM API 获取响应
  • false本地呜什已倄理完成盎接返回结果

💡 䟋劂甚户蟓入 /help这是本地呜什䞍需芁调甚 LLM。


💟 第四阶段䌚话持久化关键讟计

// 圚 API 调甚前就先写入 transcript
if (persistSession && messagesFromUserInput.length > 0) {
  const transcriptPromise = recordTranscript(messages)

  if (isBareMode()) {
    // 脚本暡匏fire-and-forget
    void transcriptPromise
  } else {
    // 亀互暡匏等埅写入完成
    await transcriptPromise
    if (isEnvTruthy(process.env.CLAUDE_CODE_EAGER_FLUSH)) {
      await flushSessionStorage()
    }
  }
}

讟计意囟

即䜿圚 API 响应回来之前进皋被 kill劂甚户点击 Stop--resume 也胜恢倍䌚话

这是 Claude Code 胜实现「可恢倍䌚话」的栞心保障。


🔁 第五阶段查询埪环

调甚 query() 生成噚

for await (const message of query({
  messages,
  systemPrompt,
  userContext,
  systemContext,
  canUseTool: wrappedCanUseTool,
  toolUseContext: processUserInputContext,
  fallbackModel,
  maxTurns,
  taskBudget,
})) {
  // 倄理各种消息类型
}

消息类型分发

switch (message.type) {
  case 'tombstone':
    // 删陀消息控制信号
    break

  case 'assistant':
    // AI 响应
    this.mutableMessages.push(message)
    yield* normalizeMessage(message)
    break

  case 'progress':
    // 进床通知
    this.mutableMessages.push(message)
    yield* normalizeMessage(message)
    break

  case 'user':
    turnCount++
    break

  case 'stream_event':
    // 流匏事件
    if (message.event.type === 'message_start') {
      // 重眮圓前消息䜿甚统计
      currentMessageUsage = EMPTY_USAGE
    }
    if (message.event.type === 'message_delta') {
      // 曎新䜿甚量 + stop_reason
      currentMessageUsage = updateUsage(currentMessageUsage, message.event.usage)
    }
    if (message.event.type === 'message_stop') {
      // 环积到总䜿甚量
      this.totalUsage = accumulateUsage(this.totalUsage, currentMessageUsage)
    }
    break

  case 'attachment':
    // 附件structured_output, max_turns_reached
    break

  case 'system':
    // 系统消息compact_boundary, api_error
    break

  case 'tool_use_summary':
    // 工具调甚摘芁
    yield { type: 'tool_use_summary', ... }
    break
}

🛡 第六阶段预算䞎错误检查

䞉重保技机制

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

// 2. 最倧蜮次检查
if (message.attachment?.type === 'max_turns_reached') {
  yield { type: 'result', subtype: 'error_max_turns', ... }
  return
}

// 3. 结构化蟓出重试次数检查
if (jsonSchema && callsThisQuery >= maxRetries) {
  yield { type: 'result', subtype: 'error_max_structured_output_retries', ... }
  return
}

🎯 第䞃阶段返回结果

// 提取文本结果
let textResult = ''
if (result.type === 'assistant') {
  const lastContent = last(result.message.content)
  if (lastContent?.type === 'text') {
    textResult = lastContent.text
  }
}

// 返回成功结果
yield {
  type: 'result',
  subtype: 'success',
  result: textResult,
  duration_ms: Date.now() - startTime,
  duration_api_ms: getTotalAPIDuration(),
  num_turns: turnCount,
  total_cost_usd: getTotalCost(),
  usage: this.totalUsage,
  permission_denials: this.permissionDenials,
  // ...
}

🔑 栞心讟计亮点

1. AsyncGenerator 暡匏

async *submitMessage(...) {
  // 初始化
  // ...
  // 蟹倄理蟹返回
  for await (const message of query(...)) {
    yield* normalizeMessage(message)
  }
  // 最终结果
  yield { type: 'result', ... }
}

䌘势

  • 流匏响应甚户䜓验曎奜
  • 内存效率高䞍需芁等完敎响应
  • 倩然支持䞭断

2. 权限远螪

// 包装 canUseTool自劚记圕拒绝
const wrappedCanUseTool = async (...) => {
  const result = await canUseTool(...)
  if (result.behavior !== 'allow') {
    this.permissionDenials.push(...)
  }
  return result
}

最终返回给调甚者包含完敎的权限拒绝记圕。

3. 䌚话预持久化

// 圚 API 调甚前就写入
if (persistSession) {
  await recordTranscript(messages)  // 甚户消息已到蟟
}
// ... 然后才匀始 API 调甚
for await (const message of query(...)) {
  // 倄理响应
}

这确保了即䜿 API 从未返回䌚话也可恢倍。

4. 历史压猩蟹界

if (message.subtype === 'compact_boundary') {
  // 释攟压猩前的消息节省内存
  const boundaryIdx = this.mutableMessages.length - 1
  if (boundaryIdx > 0) {
    this.mutableMessages.splice(0, boundaryIdx)
  }
}

通过 compact_boundary 消息觊发历史压猩防止内存无限增长。


📊 数据流总结

甚户蟓入
    ↓
processUserInput() → shouldQuery?
    ↓
┌──────────────────────────────────────────┐
│ YES → query() 调甚 LLM                    │
│ NO  → 本地呜什执行盎接返回结果            │
└──────────────────────────────────────────┘
    ↓
for await message of query():
    ↓
    ├→ assistant → normalizeMessage → yield
    ├→ progress  → normalizeMessage → yield
    ├→ stream_event → 曎新䜿甚量
    ├→ system (compact_boundary) → 压猩历史
    └→ ...其他类型
    ↓
预算/蜮次检查
    ↓
返回 result

📝 总结

QueryEngine 展瀺了构建生产级 AI 对话系统的完敎范匏

特性实现方匏价倌
流匏倄理AsyncGenerator实时响应、内存高效
权限远螪包装 canUseTool透明记圕、可审计
䌚话恢倍预持久化 transcript可䞭断、可恢倍
预算控制倚重检查机制成本可控
历史压猩compact_boundary长䌚话内存䞍爆炞
错误倄理分类重试、结构化蟓出鲁棒性

这套架构䞍仅适甚于 CLI 工具也是构建任䜕 AI 猖皋代理的绝䜳参考。


关联阅读