| import { feature } from 'bun:bundle' |
| import chalk from 'chalk' |
| import { markPostCompaction } from 'src/bootstrap/state.js' |
| import { getSystemPrompt } from '../../constants/prompts.js' |
| import { getSystemContext, getUserContext } from '../../context.js' |
| import { getShortcutDisplay } from '../../keybindings/shortcutFormat.js' |
| import { notifyCompaction } from '../../services/api/promptCacheBreakDetection.js' |
| import { |
| type CompactionResult, |
| compactConversation, |
| ERROR_MESSAGE_INCOMPLETE_RESPONSE, |
| ERROR_MESSAGE_NOT_ENOUGH_MESSAGES, |
| ERROR_MESSAGE_USER_ABORT, |
| mergeHookInstructions, |
| } from '../../services/compact/compact.js' |
| import { suppressCompactWarning } from '../../services/compact/compactWarningState.js' |
| import { microcompactMessages } from '../../services/compact/microCompact.js' |
| import { runPostCompactCleanup } from '../../services/compact/postCompactCleanup.js' |
| import { trySessionMemoryCompaction } from '../../services/compact/sessionMemoryCompact.js' |
| import { setLastSummarizedMessageId } from '../../services/SessionMemory/sessionMemoryUtils.js' |
| import type { ToolUseContext } from '../../Tool.js' |
| import type { LocalCommandCall } from '../../types/command.js' |
| import type { Message } from '../../types/message.js' |
| import { hasExactErrorMessage } from '../../utils/errors.js' |
| import { executePreCompactHooks } from '../../utils/hooks.js' |
| import { logError } from '../../utils/log.js' |
| import { getMessagesAfterCompactBoundary } from '../../utils/messages.js' |
| import { getUpgradeMessage } from '../../utils/model/contextWindowUpgradeCheck.js' |
| import { |
| buildEffectiveSystemPrompt, |
| type SystemPrompt, |
| } from '../../utils/systemPrompt.js' |
|
|
| |
| const reactiveCompact = feature('REACTIVE_COMPACT') |
| ? (require('../../services/compact/reactiveCompact.js') as typeof import('../../services/compact/reactiveCompact.js')) |
| : null |
| |
|
|
| export const call: LocalCommandCall = async (args, context) => { |
| const { abortController } = context |
| let { messages } = context |
|
|
| |
| |
| messages = getMessagesAfterCompactBoundary(messages) |
|
|
| if (messages.length === 0) { |
| throw new Error('No messages to compact') |
| } |
|
|
| const customInstructions = args.trim() |
|
|
| try { |
| |
| |
| if (!customInstructions) { |
| const sessionMemoryResult = await trySessionMemoryCompaction( |
| messages, |
| context.agentId, |
| ) |
| if (sessionMemoryResult) { |
| getUserContext.cache.clear?.() |
| runPostCompactCleanup() |
| |
| |
| if (feature('PROMPT_CACHE_BREAK_DETECTION')) { |
| notifyCompaction( |
| context.options.querySource ?? 'compact', |
| context.agentId, |
| ) |
| } |
| markPostCompaction() |
| |
| suppressCompactWarning() |
|
|
| return { |
| type: 'compact', |
| compactionResult: sessionMemoryResult, |
| displayText: buildDisplayText(context), |
| } |
| } |
| } |
|
|
| |
| |
| if (reactiveCompact?.isReactiveOnlyMode()) { |
| return await compactViaReactive( |
| messages, |
| context, |
| customInstructions, |
| reactiveCompact, |
| ) |
| } |
|
|
| |
| |
| const microcompactResult = await microcompactMessages(messages, context) |
| const messagesForCompact = microcompactResult.messages |
|
|
| const result = await compactConversation( |
| messagesForCompact, |
| context, |
| await getCacheSharingParams(context, messagesForCompact), |
| false, |
| customInstructions, |
| false, |
| ) |
|
|
| |
| |
| setLastSummarizedMessageId(undefined) |
|
|
| |
| suppressCompactWarning() |
|
|
| getUserContext.cache.clear?.() |
| runPostCompactCleanup() |
|
|
| return { |
| type: 'compact', |
| compactionResult: result, |
| displayText: buildDisplayText(context, result.userDisplayMessage), |
| } |
| } catch (error) { |
| if (abortController.signal.aborted) { |
| throw new Error('Compaction canceled.') |
| } else if (hasExactErrorMessage(error, ERROR_MESSAGE_NOT_ENOUGH_MESSAGES)) { |
| throw new Error(ERROR_MESSAGE_NOT_ENOUGH_MESSAGES) |
| } else if (hasExactErrorMessage(error, ERROR_MESSAGE_INCOMPLETE_RESPONSE)) { |
| throw new Error(ERROR_MESSAGE_INCOMPLETE_RESPONSE) |
| } else { |
| logError(error) |
| throw new Error(`Error during compaction: ${error}`) |
| } |
| } |
| } |
|
|
| async function compactViaReactive( |
| messages: Message[], |
| context: ToolUseContext, |
| customInstructions: string, |
| reactive: NonNullable<typeof reactiveCompact>, |
| ): Promise<{ |
| type: 'compact' |
| compactionResult: CompactionResult |
| displayText: string |
| }> { |
| context.onCompactProgress?.({ |
| type: 'hooks_start', |
| hookType: 'pre_compact', |
| }) |
| context.setSDKStatus?.('compacting') |
|
|
| try { |
| |
| |
| |
| const [hookResult, cacheSafeParams] = await Promise.all([ |
| executePreCompactHooks( |
| { trigger: 'manual', customInstructions: customInstructions || null }, |
| context.abortController.signal, |
| ), |
| getCacheSharingParams(context, messages), |
| ]) |
| const mergedInstructions = mergeHookInstructions( |
| customInstructions, |
| hookResult.newCustomInstructions, |
| ) |
|
|
| context.setStreamMode?.('requesting') |
| context.setResponseLength?.(() => 0) |
| context.onCompactProgress?.({ type: 'compact_start' }) |
|
|
| const outcome = await reactive.reactiveCompactOnPromptTooLong( |
| messages, |
| cacheSafeParams, |
| { customInstructions: mergedInstructions, trigger: 'manual' }, |
| ) |
|
|
| if (!outcome.ok) { |
| |
| |
| |
| switch (outcome.reason) { |
| case 'too_few_groups': |
| throw new Error(ERROR_MESSAGE_NOT_ENOUGH_MESSAGES) |
| case 'aborted': |
| throw new Error(ERROR_MESSAGE_USER_ABORT) |
| case 'exhausted': |
| case 'error': |
| case 'media_unstrippable': |
| throw new Error(ERROR_MESSAGE_INCOMPLETE_RESPONSE) |
| } |
| } |
|
|
| |
| |
| |
| setLastSummarizedMessageId(undefined) |
| runPostCompactCleanup() |
| suppressCompactWarning() |
| getUserContext.cache.clear?.() |
|
|
| |
| |
| |
| |
| const combinedMessage = |
| [hookResult.userDisplayMessage, outcome.result.userDisplayMessage] |
| .filter(Boolean) |
| .join('\n') || undefined |
|
|
| return { |
| type: 'compact', |
| compactionResult: { |
| ...outcome.result, |
| userDisplayMessage: combinedMessage, |
| }, |
| displayText: buildDisplayText(context, combinedMessage), |
| } |
| } finally { |
| context.setStreamMode?.('requesting') |
| context.setResponseLength?.(() => 0) |
| context.onCompactProgress?.({ type: 'compact_end' }) |
| context.setSDKStatus?.(null) |
| } |
| } |
|
|
| function buildDisplayText( |
| context: ToolUseContext, |
| userDisplayMessage?: string, |
| ): string { |
| const upgradeMessage = getUpgradeMessage('tip') |
| const expandShortcut = getShortcutDisplay( |
| 'app:toggleTranscript', |
| 'Global', |
| 'ctrl+o', |
| ) |
| const dimmed = [ |
| ...(context.options.verbose |
| ? [] |
| : [`(${expandShortcut} to see full summary)`]), |
| ...(userDisplayMessage ? [userDisplayMessage] : []), |
| ...(upgradeMessage ? [upgradeMessage] : []), |
| ] |
| return chalk.dim('Compacted ' + dimmed.join('\n')) |
| } |
|
|
| async function getCacheSharingParams( |
| context: ToolUseContext, |
| forkContextMessages: Message[], |
| ): Promise<{ |
| systemPrompt: SystemPrompt |
| userContext: { [k: string]: string } |
| systemContext: { [k: string]: string } |
| toolUseContext: ToolUseContext |
| forkContextMessages: Message[] |
| }> { |
| const appState = context.getAppState() |
| const defaultSysPrompt = await getSystemPrompt( |
| context.options.tools, |
| context.options.mainLoopModel, |
| Array.from( |
| appState.toolPermissionContext.additionalWorkingDirectories.keys(), |
| ), |
| context.options.mcpClients, |
| ) |
| const systemPrompt = buildEffectiveSystemPrompt({ |
| mainThreadAgentDefinition: undefined, |
| toolUseContext: context, |
| customSystemPrompt: context.options.customSystemPrompt, |
| defaultSystemPrompt: defaultSysPrompt, |
| appendSystemPrompt: context.options.appendSystemPrompt, |
| }) |
| const [userContext, systemContext] = await Promise.all([ |
| getUserContext(), |
| getSystemContext(), |
| ]) |
| return { |
| systemPrompt, |
| userContext, |
| systemContext, |
| toolUseContext: context, |
| forkContextMessages, |
| } |
| } |
|
|