ð¥ æ·±åºŠæç§ 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 çŒçšä»£ççç»äœ³åèã
å ³èé 读ïŒ
- ãæç€ºè¯å€§å ¬åŒã - Claude Code ç³»ç»æç€ºè¯å®æŽè§£æ
- æºç å°åïŒgithub.com/anthropics/âŠ