compact.ts
1 import { Command } from '../commands.js' 2 import { getContext } from '../context.js' 3 import { getMessagesGetter, getMessagesSetter } from '../messages.js' 4 import { API_ERROR_MESSAGE_PREFIX, querySonnet } from '../services/claude.js' 5 import { 6 createUserMessage, 7 normalizeMessagesForAPI, 8 } from '../utils/messages.js' 9 import { getCodeStyle } from '../utils/style.js' 10 import { clearTerminal } from '../utils/terminal.js' 11 12 const compact = { 13 type: 'local', 14 name: 'compact', 15 description: 'Clear conversation history but keep a summary in context', 16 isEnabled: true, 17 isHidden: false, 18 async call( 19 _, 20 { 21 options: { tools, slowAndCapableModel }, 22 abortController, 23 setForkConvoWithMessagesOnTheNextRender, 24 }, 25 ) { 26 // Get existing messages before clearing 27 const messages = getMessagesGetter()() 28 29 // Add summary request as a new message 30 const summaryRequest = createUserMessage( 31 "Provide a detailed but concise summary of our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next.", 32 ) 33 34 const summaryResponse = await querySonnet( 35 normalizeMessagesForAPI([...messages, summaryRequest]), 36 ['You are a helpful AI assistant tasked with summarizing conversations.'], 37 0, 38 tools, 39 abortController.signal, 40 { 41 dangerouslySkipPermissions: false, 42 model: slowAndCapableModel, 43 prependCLISysprompt: true, 44 }, 45 ) 46 47 // Extract summary from response, throw if we can't get it 48 const content = summaryResponse.message.content 49 const summary = 50 typeof content === 'string' 51 ? content 52 : content.length > 0 && content[0]?.type === 'text' 53 ? content[0].text 54 : null 55 56 if (!summary) { 57 throw new Error( 58 `Failed to generate conversation summary - response did not contain valid text content - ${summaryResponse}`, 59 ) 60 } else if (summary.startsWith(API_ERROR_MESSAGE_PREFIX)) { 61 throw new Error(summary) 62 } 63 64 // Substitute low token usage info so that the context-size UI warning goes 65 // away. The actual numbers don't matter too much: `countTokens` checks the 66 // most recent assistant message for usage numbers, so this estimate will 67 // be overridden quickly. 68 summaryResponse.message.usage = { 69 input_tokens: 0, 70 output_tokens: summaryResponse.message.usage.output_tokens, 71 cache_creation_input_tokens: 0, 72 cache_read_input_tokens: 0, 73 } 74 75 // Clear screen and messages 76 await clearTerminal() 77 getMessagesSetter()([]) 78 setForkConvoWithMessagesOnTheNextRender([ 79 createUserMessage( 80 `Use the /compact command to clear the conversation history, and start a new conversation with the summary in context.`, 81 ), 82 summaryResponse, 83 ]) 84 getContext.cache.clear?.() 85 getCodeStyle.cache.clear?.() 86 87 return '' // not used, just for typesafety. TODO: avoid this hack 88 }, 89 userFacingName() { 90 return 'compact' 91 }, 92 } satisfies Command 93 94 export default compact