( aiChatId: string, config: CompressionConfig, systemPrompt: string, llmAdapter: CompressionLlmAdapter, convManager: AIChatManager, logger: CompressionLogger = defaultLogger )
| 63 | } |
| 64 | |
| 65 | export async function checkAndCompress( |
| 66 | aiChatId: string, |
| 67 | config: CompressionConfig, |
| 68 | systemPrompt: string, |
| 69 | llmAdapter: CompressionLlmAdapter, |
| 70 | convManager: AIChatManager, |
| 71 | logger: CompressionLogger = defaultLogger |
| 72 | ): Promise<CompressionResult> { |
| 73 | if (!config.enabled) { |
| 74 | return { compressed: false, reason: 'skipped_disabled' } |
| 75 | } |
| 76 | |
| 77 | try { |
| 78 | const contextWindow = llmAdapter.contextWindow || DEFAULT_CONTEXT_WINDOW |
| 79 | const thresholdTokens = Math.floor(contextWindow * (config.tokenThresholdPercent / 100) * 0.95) |
| 80 | |
| 81 | const summary = convManager.getLatestSummary(aiChatId) |
| 82 | |
| 83 | let messages: Array<{ role: AIMessageRole; content: string; timestamp: number; contentBlocks?: ContentBlock[] }> |
| 84 | if (summary) { |
| 85 | const metaBlock = summary.contentBlocks?.find( |
| 86 | (b): b is Extract<ContentBlock, { type: 'summary_meta' }> => b.type === 'summary_meta' |
| 87 | ) |
| 88 | const boundary = metaBlock?.bufferBoundaryTimestamp ?? summary.timestamp |
| 89 | messages = convManager.getMessagesAfterSummary(aiChatId, boundary - 1) |
| 90 | } else { |
| 91 | messages = convManager.getAllUserAssistantMessages(aiChatId) |
| 92 | } |
| 93 | |
| 94 | const historyForTokenCount: Array<{ role: string; content: string }> = [] |
| 95 | if (summary) { |
| 96 | historyForTokenCount.push({ role: 'assistant', content: summary.content }) |
| 97 | } |
| 98 | for (const msg of messages) { |
| 99 | historyForTokenCount.push({ role: msg.role, content: msg.content }) |
| 100 | // Persisted tool results are replayed as toolCall/toolResult pairs each |
| 101 | // turn (see agent/history.ts), so they occupy real context and must be counted. |
| 102 | for (const toolText of replayedToolResultTexts(msg.contentBlocks)) { |
| 103 | historyForTokenCount.push({ role: 'tool', content: toolText }) |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | const currentTokens = countMessagesTokens(historyForTokenCount, systemPrompt) |
| 108 | |
| 109 | logger.info('Compression', `Token check: ${currentTokens} / ${thresholdTokens} (${contextWindow} window)`, { |
| 110 | aiChatId, |
| 111 | messageCount: messages.length, |
| 112 | hasSummary: !!summary, |
| 113 | }) |
| 114 | |
| 115 | if (currentTokens < thresholdTokens) { |
| 116 | return { compressed: false, reason: 'skipped_below_threshold', tokensBefore: currentTokens } |
| 117 | } |
| 118 | |
| 119 | const bufferTokenBudget = Math.floor(contextWindow * (config.bufferSizePercent / 100)) |
| 120 | const { bufferMessages, messagesToCompress } = splitMessagesForCompression(messages, bufferTokenBudget) |
| 121 | |
| 122 | const MIN_MESSAGES_TO_COMPRESS = 3 |
no test coverage detected