| import { feature } from 'bun:bundle' |
| import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages.mjs' |
| import { randomUUID } from 'crypto' |
| import last from 'lodash-es/last.js' |
| import { |
| getSessionId, |
| isSessionPersistenceDisabled, |
| } from 'src/bootstrap/state.js' |
| import type { |
| PermissionMode, |
| SDKCompactBoundaryMessage, |
| SDKMessage, |
| SDKPermissionDenial, |
| SDKStatus, |
| SDKUserMessageReplay, |
| } from 'src/entrypoints/agentSdkTypes.js' |
| import { accumulateUsage, updateUsage } from 'src/services/api/claude.js' |
| import type { NonNullableUsage } from 'src/services/api/logging.js' |
| import { EMPTY_USAGE } from 'src/services/api/logging.js' |
| import stripAnsi from 'strip-ansi' |
| import type { Command } from './commands.js' |
| import { getSlashCommandToolSkills } from './commands.js' |
| import { |
| LOCAL_COMMAND_STDERR_TAG, |
| LOCAL_COMMAND_STDOUT_TAG, |
| } from './constants/xml.js' |
| import { |
| getModelUsage, |
| getTotalAPIDuration, |
| getTotalCost, |
| } from './cost-tracker.js' |
| import type { CanUseToolFn } from './hooks/useCanUseTool.js' |
| import { loadMemoryPrompt } from './memdir/memdir.js' |
| import { hasAutoMemPathOverride } from './memdir/paths.js' |
| import { query } from './query.js' |
| import { categorizeRetryableAPIError } from './services/api/errors.js' |
| import type { MCPServerConnection } from './services/mcp/types.js' |
| import type { AppState } from './state/AppState.js' |
| import { type Tools, type ToolUseContext, toolMatchesName } from './Tool.js' |
| import type { AgentDefinition } from './tools/AgentTool/loadAgentsDir.js' |
| import { SYNTHETIC_OUTPUT_TOOL_NAME } from './tools/SyntheticOutputTool/SyntheticOutputTool.js' |
| import type { Message } from './types/message.js' |
| import type { OrphanedPermission } from './types/textInputTypes.js' |
| import { createAbortController } from './utils/abortController.js' |
| import type { AttributionState } from './utils/commitAttribution.js' |
| import { getGlobalConfig } from './utils/config.js' |
| import { getCwd } from './utils/cwd.js' |
| import { isBareMode, isEnvTruthy } from './utils/envUtils.js' |
| import { getFastModeState } from './utils/fastMode.js' |
| import { |
| type FileHistoryState, |
| fileHistoryEnabled, |
| fileHistoryMakeSnapshot, |
| } from './utils/fileHistory.js' |
| import { |
| cloneFileStateCache, |
| type FileStateCache, |
| } from './utils/fileStateCache.js' |
| import { headlessProfilerCheckpoint } from './utils/headlessProfiler.js' |
| import { registerStructuredOutputEnforcement } from './utils/hooks/hookHelpers.js' |
| import { getInMemoryErrors } from './utils/log.js' |
| import { countToolCalls, SYNTHETIC_MESSAGES } from './utils/messages.js' |
| import { |
| getMainLoopModel, |
| parseUserSpecifiedModel, |
| } from './utils/model/model.js' |
| import { loadAllPluginsCacheOnly } from './utils/plugins/pluginLoader.js' |
| import { |
| type ProcessUserInputContext, |
| processUserInput, |
| } from './utils/processUserInput/processUserInput.js' |
| import { fetchSystemPromptParts } from './utils/queryContext.js' |
| import { setCwd } from './utils/Shell.js' |
| import { |
| flushSessionStorage, |
| recordTranscript, |
| } from './utils/sessionStorage.js' |
| import { asSystemPrompt } from './utils/systemPromptType.js' |
| import { resolveThemeSetting } from './utils/systemTheme.js' |
| import { |
| shouldEnableThinkingByDefault, |
| type ThinkingConfig, |
| } from './utils/thinking.js' |
|
|
| |
| |
| const messageSelector = |
| (): typeof import('src/components/MessageSelector.js') => |
| require('src/components/MessageSelector.js') |
|
|
| import { |
| localCommandOutputToSDKAssistantMessage, |
| toSDKCompactMetadata, |
| } from './utils/messages/mappers.js' |
| import { |
| buildSystemInitMessage, |
| sdkCompatToolName, |
| } from './utils/messages/systemInit.js' |
| import { |
| getScratchpadDir, |
| isScratchpadEnabled, |
| } from './utils/permissions/filesystem.js' |
| |
| import { |
| handleOrphanedPermission, |
| isResultSuccessful, |
| normalizeMessage, |
| } from './utils/queryHelpers.js' |
|
|
| |
| |
| const getCoordinatorUserContext: ( |
| mcpClients: ReadonlyArray<{ name: string }>, |
| scratchpadDir?: string, |
| ) => { [k: string]: string } = feature('COORDINATOR_MODE') |
| ? require('./coordinator/coordinatorMode.js').getCoordinatorUserContext |
| : () => ({}) |
| |
|
|
| |
| |
| const snipModule = feature('HISTORY_SNIP') |
| ? (require('./services/compact/snipCompact.js') as typeof import('./services/compact/snipCompact.js')) |
| : null |
| const snipProjection = feature('HISTORY_SNIP') |
| ? (require('./services/compact/snipProjection.js') as typeof import('./services/compact/snipProjection.js')) |
| : null |
| |
|
|
| export type QueryEngineConfig = { |
| cwd: string |
| tools: Tools |
| commands: Command[] |
| mcpClients: MCPServerConnection[] |
| agents: AgentDefinition[] |
| canUseTool: CanUseToolFn |
| getAppState: () => AppState |
| setAppState: (f: (prev: AppState) => AppState) => void |
| initialMessages?: Message[] |
| readFileCache: FileStateCache |
| customSystemPrompt?: string |
| appendSystemPrompt?: string |
| includeDefaultUserContext?: boolean |
| userSpecifiedModel?: string |
| fallbackModel?: string |
| thinkingConfig?: ThinkingConfig |
| temperatureOverride?: number |
| fixedShellCwd?: string |
| maxTurns?: number |
| maxBudgetUsd?: number |
| taskBudget?: { total: number } |
| jsonSchema?: Record<string, unknown> |
| verbose?: boolean |
| replayUserMessages?: boolean |
| |
| handleElicitation?: ToolUseContext['handleElicitation'] |
| includePartialMessages?: boolean |
| setSDKStatus?: (status: SDKStatus) => void |
| abortController?: AbortController |
| orphanedPermission?: OrphanedPermission |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| snipReplay?: ( |
| yieldedSystemMsg: Message, |
| store: Message[], |
| ) => { messages: Message[]; executed: boolean } | undefined |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export class QueryEngine { |
| private config: QueryEngineConfig |
| private mutableMessages: Message[] |
| private abortController: AbortController |
| private permissionDenials: SDKPermissionDenial[] |
| private totalUsage: NonNullableUsage |
| private hasHandledOrphanedPermission = false |
| private readFileState: FileStateCache |
| |
| |
| |
| |
| |
| private discoveredSkillNames = new Set<string>() |
| private loadedNestedMemoryPaths = new Set<string>() |
|
|
| constructor(config: QueryEngineConfig) { |
| this.config = config |
| this.mutableMessages = config.initialMessages ?? [] |
| this.abortController = config.abortController ?? createAbortController() |
| this.permissionDenials = [] |
| this.readFileState = config.readFileCache |
| this.totalUsage = EMPTY_USAGE |
| } |
|
|
| async *submitMessage( |
| prompt: string | ContentBlockParam[], |
| options?: { uuid?: string; isMeta?: boolean }, |
| ): AsyncGenerator<SDKMessage, void, unknown> { |
| const { |
| cwd, |
| commands, |
| tools, |
| mcpClients, |
| verbose = false, |
| thinkingConfig, |
| maxTurns, |
| maxBudgetUsd, |
| taskBudget, |
| canUseTool, |
| customSystemPrompt, |
| appendSystemPrompt, |
| includeDefaultUserContext, |
| userSpecifiedModel, |
| fallbackModel, |
| jsonSchema, |
| temperatureOverride, |
| getAppState, |
| setAppState, |
| replayUserMessages = false, |
| includePartialMessages = false, |
| agents = [], |
| setSDKStatus, |
| orphanedPermission, |
| } = this.config |
|
|
| this.discoveredSkillNames.clear() |
| setCwd(cwd) |
| const persistSession = !isSessionPersistenceDisabled() |
| const startTime = Date.now() |
|
|
| |
| const wrappedCanUseTool: CanUseToolFn = async ( |
| tool, |
| input, |
| toolUseContext, |
| assistantMessage, |
| toolUseID, |
| forceDecision, |
| ) => { |
| const result = await canUseTool( |
| tool, |
| input, |
| toolUseContext, |
| assistantMessage, |
| toolUseID, |
| forceDecision, |
| ) |
|
|
| |
| if (result.behavior !== 'allow') { |
| this.permissionDenials.push({ |
| tool_name: sdkCompatToolName(tool.name), |
| tool_use_id: toolUseID, |
| tool_input: input, |
| }) |
| } |
|
|
| return result |
| } |
|
|
| const initialAppState = getAppState() |
| const initialMainLoopModel = userSpecifiedModel |
| ? parseUserSpecifiedModel(userSpecifiedModel) |
| : getMainLoopModel() |
|
|
| const initialThinkingConfig: ThinkingConfig = thinkingConfig |
| ? thinkingConfig |
| : shouldEnableThinkingByDefault() !== false |
| ? { type: 'adaptive' } |
| : { type: 'disabled' } |
|
|
| headlessProfilerCheckpoint('before_getSystemPrompt') |
| |
| const customPrompt = |
| typeof customSystemPrompt === 'string' ? customSystemPrompt : undefined |
| const { |
| defaultSystemPrompt, |
| userContext: baseUserContext, |
| systemContext, |
| } = await fetchSystemPromptParts({ |
| tools, |
| mainLoopModel: initialMainLoopModel, |
| additionalWorkingDirectories: Array.from( |
| initialAppState.toolPermissionContext.additionalWorkingDirectories.keys(), |
| ), |
| mcpClients, |
| customSystemPrompt: customPrompt, |
| includeDefaultUserContext, |
| }) |
| headlessProfilerCheckpoint('after_getSystemPrompt') |
| const userContext = { |
| ...baseUserContext, |
| ...getCoordinatorUserContext( |
| mcpClients, |
| isScratchpadEnabled() ? getScratchpadDir() : undefined, |
| ), |
| } |
|
|
| |
| |
| |
| |
| |
| |
| const memoryMechanicsPrompt = |
| customPrompt !== undefined && hasAutoMemPathOverride() |
| ? await loadMemoryPrompt() |
| : null |
|
|
| const systemPrompt = asSystemPrompt([ |
| ...(customPrompt !== undefined ? [customPrompt] : defaultSystemPrompt), |
| ...(memoryMechanicsPrompt ? [memoryMechanicsPrompt] : []), |
| ...(appendSystemPrompt ? [appendSystemPrompt] : []), |
| ]) |
|
|
| |
| const hasStructuredOutputTool = tools.some(t => |
| toolMatchesName(t, SYNTHETIC_OUTPUT_TOOL_NAME), |
| ) |
| if (jsonSchema && hasStructuredOutputTool) { |
| registerStructuredOutputEnforcement(setAppState, getSessionId()) |
| } |
|
|
| let processUserInputContext: ProcessUserInputContext = { |
| messages: this.mutableMessages, |
| |
| |
| |
| |
| |
| |
| |
| setMessages: fn => { |
| this.mutableMessages = fn(this.mutableMessages) |
| }, |
| onChangeAPIKey: () => {}, |
| handleElicitation: this.config.handleElicitation, |
| options: { |
| commands, |
| debug: false, |
| tools, |
| verbose, |
| mainLoopModel: initialMainLoopModel, |
| thinkingConfig: initialThinkingConfig, |
| mcpClients, |
| mcpResources: {}, |
| ideInstallationStatus: null, |
| isNonInteractiveSession: true, |
| customSystemPrompt, |
| appendSystemPrompt, |
| fixedShellCwd: this.config.fixedShellCwd, |
| agentDefinitions: { activeAgents: agents, allAgents: [] }, |
| theme: resolveThemeSetting(getGlobalConfig().theme), |
| maxBudgetUsd, |
| }, |
| getAppState, |
| setAppState, |
| abortController: this.abortController, |
| readFileState: this.readFileState, |
| nestedMemoryAttachmentTriggers: new Set<string>(), |
| loadedNestedMemoryPaths: this.loadedNestedMemoryPaths, |
| dynamicSkillDirTriggers: new Set<string>(), |
| discoveredSkillNames: this.discoveredSkillNames, |
| setInProgressToolUseIDs: () => {}, |
| setResponseLength: () => {}, |
| updateFileHistoryState: ( |
| updater: (prev: FileHistoryState) => FileHistoryState, |
| ) => { |
| setAppState(prev => { |
| const updated = updater(prev.fileHistory) |
| if (updated === prev.fileHistory) return prev |
| return { ...prev, fileHistory: updated } |
| }) |
| }, |
| updateAttributionState: ( |
| updater: (prev: AttributionState) => AttributionState, |
| ) => { |
| setAppState(prev => { |
| const updated = updater(prev.attribution) |
| if (updated === prev.attribution) return prev |
| return { ...prev, attribution: updated } |
| }) |
| }, |
| setSDKStatus, |
| } |
|
|
| |
| if (orphanedPermission && !this.hasHandledOrphanedPermission) { |
| this.hasHandledOrphanedPermission = true |
| for await (const message of handleOrphanedPermission( |
| orphanedPermission, |
| tools, |
| this.mutableMessages, |
| processUserInputContext, |
| )) { |
| yield message |
| } |
| } |
|
|
| const { |
| messages: messagesFromUserInput, |
| shouldQuery, |
| allowedTools, |
| model: modelFromUserInput, |
| resultText, |
| } = await processUserInput({ |
| input: prompt, |
| mode: 'prompt', |
| setToolJSX: () => {}, |
| context: { |
| ...processUserInputContext, |
| messages: this.mutableMessages, |
| }, |
| messages: this.mutableMessages, |
| uuid: options?.uuid, |
| isMeta: options?.isMeta, |
| querySource: 'sdk', |
| }) |
|
|
| |
| this.mutableMessages.push(...messagesFromUserInput) |
|
|
| |
| const messages = [...this.mutableMessages] |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (persistSession && messagesFromUserInput.length > 0) { |
| const transcriptPromise = recordTranscript(messages) |
| if (isBareMode()) { |
| void transcriptPromise |
| } else { |
| await transcriptPromise |
| if ( |
| isEnvTruthy(process.env.CLAUDE_CODE_EAGER_FLUSH) || |
| isEnvTruthy(process.env.CLAUDE_CODE_IS_COWORK) |
| ) { |
| await flushSessionStorage() |
| } |
| } |
| } |
|
|
| |
| const replayableMessages = messagesFromUserInput.filter( |
| msg => |
| (msg.type === 'user' && |
| !msg.isMeta && |
| !msg.toolUseResult && |
| messageSelector().selectableUserMessagesFilter(msg)) || |
| (msg.type === 'system' && msg.subtype === 'compact_boundary'), |
| ) |
| const messagesToAck = replayUserMessages ? replayableMessages : [] |
|
|
| |
| setAppState(prev => ({ |
| ...prev, |
| toolPermissionContext: { |
| ...prev.toolPermissionContext, |
| alwaysAllowRules: { |
| ...prev.toolPermissionContext.alwaysAllowRules, |
| command: allowedTools, |
| }, |
| }, |
| })) |
|
|
| const mainLoopModel = modelFromUserInput ?? initialMainLoopModel |
|
|
| |
| |
| processUserInputContext = { |
| messages, |
| setMessages: () => {}, |
| onChangeAPIKey: () => {}, |
| handleElicitation: this.config.handleElicitation, |
| options: { |
| commands, |
| debug: false, |
| tools, |
| verbose, |
| mainLoopModel, |
| thinkingConfig: initialThinkingConfig, |
| mcpClients, |
| mcpResources: {}, |
| ideInstallationStatus: null, |
| isNonInteractiveSession: true, |
| customSystemPrompt, |
| appendSystemPrompt, |
| fixedShellCwd: this.config.fixedShellCwd, |
| theme: resolveThemeSetting(getGlobalConfig().theme), |
| agentDefinitions: { activeAgents: agents, allAgents: [] }, |
| maxBudgetUsd, |
| }, |
| getAppState, |
| setAppState, |
| abortController: this.abortController, |
| readFileState: this.readFileState, |
| nestedMemoryAttachmentTriggers: new Set<string>(), |
| loadedNestedMemoryPaths: this.loadedNestedMemoryPaths, |
| dynamicSkillDirTriggers: new Set<string>(), |
| discoveredSkillNames: this.discoveredSkillNames, |
| setInProgressToolUseIDs: () => {}, |
| setResponseLength: () => {}, |
| updateFileHistoryState: processUserInputContext.updateFileHistoryState, |
| updateAttributionState: processUserInputContext.updateAttributionState, |
| setSDKStatus, |
| } |
|
|
| headlessProfilerCheckpoint('before_skills_plugins') |
| |
| |
| |
| |
| const [skills, { enabled: enabledPlugins }] = await Promise.all([ |
| getSlashCommandToolSkills(getCwd()), |
| loadAllPluginsCacheOnly(), |
| ]) |
| headlessProfilerCheckpoint('after_skills_plugins') |
|
|
| yield buildSystemInitMessage({ |
| tools, |
| mcpClients, |
| model: mainLoopModel, |
| permissionMode: initialAppState.toolPermissionContext |
| .mode as PermissionMode, |
| commands, |
| agents, |
| skills, |
| plugins: enabledPlugins, |
| fastMode: initialAppState.fastMode, |
| }) |
|
|
| |
| headlessProfilerCheckpoint('system_message_yielded') |
|
|
| if (!shouldQuery) { |
| |
| |
| |
| for (const msg of messagesFromUserInput) { |
| if ( |
| msg.type === 'user' && |
| typeof msg.message.content === 'string' && |
| (msg.message.content.includes(`<${LOCAL_COMMAND_STDOUT_TAG}>`) || |
| msg.message.content.includes(`<${LOCAL_COMMAND_STDERR_TAG}>`) || |
| msg.isCompactSummary) |
| ) { |
| yield { |
| type: 'user', |
| message: { |
| ...msg.message, |
| content: stripAnsi(msg.message.content), |
| }, |
| session_id: getSessionId(), |
| parent_tool_use_id: null, |
| uuid: msg.uuid, |
| timestamp: msg.timestamp, |
| isReplay: !msg.isCompactSummary, |
| isSynthetic: msg.isMeta || msg.isVisibleInTranscriptOnly, |
| } as SDKUserMessageReplay |
| } |
|
|
| |
| |
| |
| |
| if ( |
| msg.type === 'system' && |
| msg.subtype === 'local_command' && |
| typeof msg.content === 'string' && |
| (msg.content.includes(`<${LOCAL_COMMAND_STDOUT_TAG}>`) || |
| msg.content.includes(`<${LOCAL_COMMAND_STDERR_TAG}>`)) |
| ) { |
| yield localCommandOutputToSDKAssistantMessage(msg.content, msg.uuid) |
| } |
|
|
| if (msg.type === 'system' && msg.subtype === 'compact_boundary') { |
| yield { |
| type: 'system', |
| subtype: 'compact_boundary' as const, |
| session_id: getSessionId(), |
| uuid: msg.uuid, |
| compact_metadata: toSDKCompactMetadata(msg.compactMetadata), |
| } as SDKCompactBoundaryMessage |
| } |
| } |
|
|
| if (persistSession) { |
| await recordTranscript(messages) |
| if ( |
| isEnvTruthy(process.env.CLAUDE_CODE_EAGER_FLUSH) || |
| isEnvTruthy(process.env.CLAUDE_CODE_IS_COWORK) |
| ) { |
| await flushSessionStorage() |
| } |
| } |
|
|
| yield { |
| type: 'result', |
| subtype: 'success', |
| is_error: false, |
| duration_ms: Date.now() - startTime, |
| duration_api_ms: getTotalAPIDuration(), |
| num_turns: messages.length - 1, |
| result: resultText ?? '', |
| stop_reason: null, |
| session_id: getSessionId(), |
| total_cost_usd: getTotalCost(), |
| usage: this.totalUsage, |
| modelUsage: getModelUsage(), |
| permission_denials: this.permissionDenials, |
| fast_mode_state: getFastModeState( |
| mainLoopModel, |
| initialAppState.fastMode, |
| ), |
| uuid: randomUUID(), |
| } |
| return |
| } |
|
|
| if (fileHistoryEnabled() && persistSession) { |
| messagesFromUserInput |
| .filter(messageSelector().selectableUserMessagesFilter) |
| .forEach(message => { |
| void fileHistoryMakeSnapshot( |
| (updater: (prev: FileHistoryState) => FileHistoryState) => { |
| setAppState(prev => ({ |
| ...prev, |
| fileHistory: updater(prev.fileHistory), |
| })) |
| }, |
| message.uuid, |
| ) |
| }) |
| } |
|
|
| |
| let currentMessageUsage: NonNullableUsage = EMPTY_USAGE |
| let turnCount = 1 |
| let hasAcknowledgedInitialMessages = false |
| |
| let structuredOutputFromTool: unknown |
| |
| let lastStopReason: string | null = null |
| |
| |
| |
| |
| const errorLogWatermark = getInMemoryErrors().at(-1) |
| |
| const initialStructuredOutputCalls = jsonSchema |
| ? countToolCalls(this.mutableMessages, SYNTHETIC_OUTPUT_TOOL_NAME) |
| : 0 |
|
|
| for await (const message of query({ |
| messages, |
| systemPrompt, |
| userContext, |
| systemContext, |
| canUseTool: wrappedCanUseTool, |
| toolUseContext: processUserInputContext, |
| fallbackModel, |
| querySource: 'sdk', |
| maxTurns, |
| taskBudget, |
| temperatureOverride, |
| })) { |
| |
| if ( |
| message.type === 'assistant' || |
| message.type === 'user' || |
| (message.type === 'system' && message.subtype === 'compact_boundary') |
| ) { |
| |
| |
| |
| |
| |
| |
| |
| |
| if ( |
| persistSession && |
| message.type === 'system' && |
| message.subtype === 'compact_boundary' |
| ) { |
| const tailUuid = message.compactMetadata?.preservedSegment?.tailUuid |
| if (tailUuid) { |
| const tailIdx = this.mutableMessages.findLastIndex( |
| m => m.uuid === tailUuid, |
| ) |
| if (tailIdx !== -1) { |
| await recordTranscript(this.mutableMessages.slice(0, tailIdx + 1)) |
| } |
| } |
| } |
| messages.push(message) |
| if (persistSession) { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (message.type === 'assistant') { |
| void recordTranscript(messages) |
| } else { |
| await recordTranscript(messages) |
| } |
| } |
|
|
| |
| if (!hasAcknowledgedInitialMessages && messagesToAck.length > 0) { |
| hasAcknowledgedInitialMessages = true |
| for (const msgToAck of messagesToAck) { |
| if (msgToAck.type === 'user') { |
| yield { |
| type: 'user', |
| message: msgToAck.message, |
| session_id: getSessionId(), |
| parent_tool_use_id: null, |
| uuid: msgToAck.uuid, |
| timestamp: msgToAck.timestamp, |
| isReplay: true, |
| } as SDKUserMessageReplay |
| } |
| } |
| } |
| } |
|
|
| if (message.type === 'user') { |
| turnCount++ |
| } |
|
|
| switch (message.type) { |
| case 'tombstone': |
| |
| break |
| case 'assistant': |
| |
| |
| |
| if (message.message.stop_reason != null) { |
| lastStopReason = message.message.stop_reason |
| } |
| this.mutableMessages.push(message) |
| yield* normalizeMessage(message) |
| break |
| case 'progress': |
| this.mutableMessages.push(message) |
| |
| |
| |
| |
| |
| if (persistSession) { |
| messages.push(message) |
| void recordTranscript(messages) |
| } |
| yield* normalizeMessage(message) |
| break |
| case 'user': |
| this.mutableMessages.push(message) |
| yield* normalizeMessage(message) |
| break |
| case 'stream_event': |
| if (message.event.type === 'message_start') { |
| |
| currentMessageUsage = EMPTY_USAGE |
| currentMessageUsage = updateUsage( |
| currentMessageUsage, |
| message.event.message.usage, |
| ) |
| } |
| 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 |
| } |
| } |
| if (message.event.type === 'message_stop') { |
| |
| this.totalUsage = accumulateUsage( |
| this.totalUsage, |
| currentMessageUsage, |
| ) |
| } |
|
|
| if (includePartialMessages) { |
| yield { |
| type: 'stream_event' as const, |
| event: message.event, |
| session_id: getSessionId(), |
| parent_tool_use_id: null, |
| uuid: randomUUID(), |
| } |
| } |
|
|
| break |
| case 'attachment': |
| this.mutableMessages.push(message) |
| |
| if (persistSession) { |
| messages.push(message) |
| void recordTranscript(messages) |
| } |
|
|
| |
| if (message.attachment.type === 'structured_output') { |
| structuredOutputFromTool = message.attachment.data |
| } |
| |
| else if (message.attachment.type === 'max_turns_reached') { |
| if (persistSession) { |
| if ( |
| isEnvTruthy(process.env.CLAUDE_CODE_EAGER_FLUSH) || |
| isEnvTruthy(process.env.CLAUDE_CODE_IS_COWORK) |
| ) { |
| await flushSessionStorage() |
| } |
| } |
| yield { |
| type: 'result', |
| subtype: 'error_max_turns', |
| duration_ms: Date.now() - startTime, |
| duration_api_ms: getTotalAPIDuration(), |
| is_error: true, |
| num_turns: message.attachment.turnCount, |
| stop_reason: lastStopReason, |
| session_id: getSessionId(), |
| total_cost_usd: getTotalCost(), |
| usage: this.totalUsage, |
| modelUsage: getModelUsage(), |
| permission_denials: this.permissionDenials, |
| fast_mode_state: getFastModeState( |
| mainLoopModel, |
| initialAppState.fastMode, |
| ), |
| uuid: randomUUID(), |
| errors: [ |
| `Reached maximum number of turns (${message.attachment.maxTurns})`, |
| ], |
| } |
| return |
| } |
| |
| else if ( |
| replayUserMessages && |
| message.attachment.type === 'queued_command' |
| ) { |
| yield { |
| type: 'user', |
| message: { |
| role: 'user' as const, |
| content: message.attachment.prompt, |
| }, |
| session_id: getSessionId(), |
| parent_tool_use_id: null, |
| uuid: message.attachment.source_uuid || message.uuid, |
| timestamp: message.timestamp, |
| isReplay: true, |
| } as SDKUserMessageReplay |
| } |
| break |
| case 'stream_request_start': |
| |
| break |
| case 'system': { |
| |
| |
| |
| |
| |
| |
| |
| const snipResult = this.config.snipReplay?.( |
| message, |
| this.mutableMessages, |
| ) |
| if (snipResult !== undefined) { |
| if (snipResult.executed) { |
| this.mutableMessages.length = 0 |
| this.mutableMessages.push(...snipResult.messages) |
| } |
| break |
| } |
| this.mutableMessages.push(message) |
| |
| if ( |
| message.subtype === 'compact_boundary' && |
| message.compactMetadata |
| ) { |
| |
| |
| |
| |
| const mutableBoundaryIdx = this.mutableMessages.length - 1 |
| if (mutableBoundaryIdx > 0) { |
| this.mutableMessages.splice(0, mutableBoundaryIdx) |
| } |
| const localBoundaryIdx = messages.length - 1 |
| if (localBoundaryIdx > 0) { |
| messages.splice(0, localBoundaryIdx) |
| } |
|
|
| yield { |
| type: 'system', |
| subtype: 'compact_boundary' as const, |
| session_id: getSessionId(), |
| uuid: message.uuid, |
| compact_metadata: toSDKCompactMetadata(message.compactMetadata), |
| } |
| } |
| if (message.subtype === 'api_error') { |
| yield { |
| type: 'system', |
| subtype: 'api_retry' as const, |
| attempt: message.retryAttempt, |
| max_retries: message.maxRetries, |
| retry_delay_ms: message.retryInMs, |
| error_status: message.error.status ?? null, |
| error: categorizeRetryableAPIError(message.error), |
| session_id: getSessionId(), |
| uuid: message.uuid, |
| } |
| } |
| |
| break |
| } |
| case 'tool_use_summary': |
| |
| yield { |
| type: 'tool_use_summary' as const, |
| summary: message.summary, |
| preceding_tool_use_ids: message.precedingToolUseIds, |
| session_id: getSessionId(), |
| uuid: message.uuid, |
| } |
| break |
| } |
|
|
| |
| if (maxBudgetUsd !== undefined && getTotalCost() >= maxBudgetUsd) { |
| if (persistSession) { |
| if ( |
| isEnvTruthy(process.env.CLAUDE_CODE_EAGER_FLUSH) || |
| isEnvTruthy(process.env.CLAUDE_CODE_IS_COWORK) |
| ) { |
| await flushSessionStorage() |
| } |
| } |
| yield { |
| type: 'result', |
| subtype: 'error_max_budget_usd', |
| duration_ms: Date.now() - startTime, |
| duration_api_ms: getTotalAPIDuration(), |
| is_error: true, |
| num_turns: turnCount, |
| stop_reason: lastStopReason, |
| session_id: getSessionId(), |
| total_cost_usd: getTotalCost(), |
| usage: this.totalUsage, |
| modelUsage: getModelUsage(), |
| permission_denials: this.permissionDenials, |
| fast_mode_state: getFastModeState( |
| mainLoopModel, |
| initialAppState.fastMode, |
| ), |
| uuid: randomUUID(), |
| errors: [`Reached maximum budget ($${maxBudgetUsd})`], |
| } |
| return |
| } |
|
|
| |
| if (message.type === 'user' && jsonSchema) { |
| const currentCalls = countToolCalls( |
| this.mutableMessages, |
| SYNTHETIC_OUTPUT_TOOL_NAME, |
| ) |
| const callsThisQuery = currentCalls - initialStructuredOutputCalls |
| const maxRetries = parseInt( |
| process.env.MAX_STRUCTURED_OUTPUT_RETRIES || '5', |
| 10, |
| ) |
| if (callsThisQuery >= maxRetries) { |
| if (persistSession) { |
| if ( |
| isEnvTruthy(process.env.CLAUDE_CODE_EAGER_FLUSH) || |
| isEnvTruthy(process.env.CLAUDE_CODE_IS_COWORK) |
| ) { |
| await flushSessionStorage() |
| } |
| } |
| yield { |
| type: 'result', |
| subtype: 'error_max_structured_output_retries', |
| duration_ms: Date.now() - startTime, |
| duration_api_ms: getTotalAPIDuration(), |
| is_error: true, |
| num_turns: turnCount, |
| stop_reason: lastStopReason, |
| session_id: getSessionId(), |
| total_cost_usd: getTotalCost(), |
| usage: this.totalUsage, |
| modelUsage: getModelUsage(), |
| permission_denials: this.permissionDenials, |
| fast_mode_state: getFastModeState( |
| mainLoopModel, |
| initialAppState.fastMode, |
| ), |
| uuid: randomUUID(), |
| errors: [ |
| `Failed to provide valid structured output after ${maxRetries} attempts`, |
| ], |
| } |
| return |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| const result = messages.findLast( |
| m => m.type === 'assistant' || m.type === 'user', |
| ) |
| |
| |
| |
| const edeResultType = result?.type ?? 'undefined' |
| const edeLastContentType = |
| result?.type === 'assistant' |
| ? (last(result.message.content)?.type ?? 'none') |
| : 'n/a' |
|
|
| |
| |
| |
| if (persistSession) { |
| if ( |
| isEnvTruthy(process.env.CLAUDE_CODE_EAGER_FLUSH) || |
| isEnvTruthy(process.env.CLAUDE_CODE_IS_COWORK) |
| ) { |
| await flushSessionStorage() |
| } |
| } |
|
|
| if (!isResultSuccessful(result, lastStopReason)) { |
| yield { |
| type: 'result', |
| subtype: 'error_during_execution', |
| duration_ms: Date.now() - startTime, |
| duration_api_ms: getTotalAPIDuration(), |
| is_error: true, |
| num_turns: turnCount, |
| stop_reason: lastStopReason, |
| session_id: getSessionId(), |
| total_cost_usd: getTotalCost(), |
| usage: this.totalUsage, |
| modelUsage: getModelUsage(), |
| permission_denials: this.permissionDenials, |
| fast_mode_state: getFastModeState( |
| mainLoopModel, |
| initialAppState.fastMode, |
| ), |
| uuid: randomUUID(), |
| |
| |
| |
| |
| |
| errors: (() => { |
| const all = getInMemoryErrors() |
| const start = errorLogWatermark |
| ? all.lastIndexOf(errorLogWatermark) + 1 |
| : 0 |
| return [ |
| `[ede_diagnostic] result_type=${edeResultType} last_content_type=${edeLastContentType} stop_reason=${lastStopReason}`, |
| ...all.slice(start).map(_ => _.error), |
| ] |
| })(), |
| } |
| return |
| } |
|
|
| |
| let textResult = '' |
| let isApiError = false |
|
|
| if (result.type === 'assistant') { |
| const lastContent = last(result.message.content) |
| if ( |
| lastContent?.type === 'text' && |
| !SYNTHETIC_MESSAGES.has(lastContent.text) |
| ) { |
| textResult = lastContent.text |
| } |
| isApiError = Boolean(result.isApiErrorMessage) |
| } |
|
|
| yield { |
| type: 'result', |
| subtype: 'success', |
| is_error: isApiError, |
| duration_ms: Date.now() - startTime, |
| duration_api_ms: getTotalAPIDuration(), |
| num_turns: turnCount, |
| result: textResult, |
| stop_reason: lastStopReason, |
| session_id: getSessionId(), |
| total_cost_usd: getTotalCost(), |
| usage: this.totalUsage, |
| modelUsage: getModelUsage(), |
| permission_denials: this.permissionDenials, |
| structured_output: structuredOutputFromTool, |
| fast_mode_state: getFastModeState( |
| mainLoopModel, |
| initialAppState.fastMode, |
| ), |
| uuid: randomUUID(), |
| } |
| } |
|
|
| interrupt(): void { |
| this.abortController.abort() |
| } |
|
|
| getMessages(): readonly Message[] { |
| return this.mutableMessages |
| } |
|
|
| getReadFileState(): FileStateCache { |
| return this.readFileState |
| } |
|
|
| getSessionId(): string { |
| return getSessionId() |
| } |
|
|
| setModel(model: string): void { |
| this.config.userSpecifiedModel = model |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export async function* ask({ |
| commands, |
| prompt, |
| promptUuid, |
| isMeta, |
| cwd, |
| tools, |
| mcpClients, |
| verbose = false, |
| thinkingConfig, |
| maxTurns, |
| maxBudgetUsd, |
| taskBudget, |
| canUseTool, |
| mutableMessages = [], |
| getReadFileCache, |
| setReadFileCache, |
| customSystemPrompt, |
| appendSystemPrompt, |
| userSpecifiedModel, |
| fallbackModel, |
| jsonSchema, |
| getAppState, |
| setAppState, |
| abortController, |
| replayUserMessages = false, |
| includePartialMessages = false, |
| handleElicitation, |
| agents = [], |
| setSDKStatus, |
| orphanedPermission, |
| }: { |
| commands: Command[] |
| prompt: string | Array<ContentBlockParam> |
| promptUuid?: string |
| isMeta?: boolean |
| cwd: string |
| tools: Tools |
| verbose?: boolean |
| mcpClients: MCPServerConnection[] |
| thinkingConfig?: ThinkingConfig |
| maxTurns?: number |
| maxBudgetUsd?: number |
| taskBudget?: { total: number } |
| canUseTool: CanUseToolFn |
| mutableMessages?: Message[] |
| customSystemPrompt?: string |
| appendSystemPrompt?: string |
| userSpecifiedModel?: string |
| fallbackModel?: string |
| jsonSchema?: Record<string, unknown> |
| getAppState: () => AppState |
| setAppState: (f: (prev: AppState) => AppState) => void |
| getReadFileCache: () => FileStateCache |
| setReadFileCache: (cache: FileStateCache) => void |
| abortController?: AbortController |
| replayUserMessages?: boolean |
| includePartialMessages?: boolean |
| handleElicitation?: ToolUseContext['handleElicitation'] |
| agents?: AgentDefinition[] |
| setSDKStatus?: (status: SDKStatus) => void |
| orphanedPermission?: OrphanedPermission |
| }): AsyncGenerator<SDKMessage, void, unknown> { |
| const engine = new QueryEngine({ |
| cwd, |
| tools, |
| commands, |
| mcpClients, |
| agents, |
| canUseTool, |
| getAppState, |
| setAppState, |
| initialMessages: mutableMessages, |
| readFileCache: cloneFileStateCache(getReadFileCache()), |
| customSystemPrompt, |
| appendSystemPrompt, |
| userSpecifiedModel, |
| fallbackModel, |
| thinkingConfig, |
| maxTurns, |
| maxBudgetUsd, |
| taskBudget, |
| jsonSchema, |
| verbose, |
| handleElicitation, |
| replayUserMessages, |
| includePartialMessages, |
| setSDKStatus, |
| abortController, |
| orphanedPermission, |
| ...(feature('HISTORY_SNIP') |
| ? { |
| snipReplay: (yielded: Message, store: Message[]) => { |
| if (!snipProjection!.isSnipBoundaryMessage(yielded)) |
| return undefined |
| return snipModule!.snipCompactIfNeeded(store, { force: true }) |
| }, |
| } |
| : {}), |
| }) |
|
|
| try { |
| yield* engine.submitMessage(prompt, { |
| uuid: promptUuid, |
| isMeta, |
| }) |
| } finally { |
| setReadFileCache(engine.getReadFileState()) |
| } |
| } |
|
|