From a3fe9279d8b1b8826502d1f0522e381792003ec4 Mon Sep 17 00:00:00 2001 From: Abhi <43648792+abhipatel12@users.noreply.github.com> Date: Tue, 14 Oct 2025 00:02:03 -0400 Subject: [PATCH] fix(compression): prevent unnecessary summarization when history is too short (#11082) --- .../messages/CompressionMessage.tsx | 2 +- packages/core/src/core/client.test.ts | 45 ++++++++++++++++--- packages/core/src/core/client.ts | 8 ++++ 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.tsx index 0f69a779be..0364d9c1ee 100644 --- a/packages/cli/src/ui/components/messages/CompressionMessage.tsx +++ b/packages/cli/src/ui/components/messages/CompressionMessage.tsx @@ -47,7 +47,7 @@ export function CompressionMessage({ case CompressionStatus.COMPRESSION_FAILED_TOKEN_COUNT_ERROR: return 'Could not compress chat history due to a token counting error.'; case CompressionStatus.NOOP: - return 'Chat history is already compressed.'; + return 'Nothing to compress.'; default: return ''; } diff --git a/packages/core/src/core/client.test.ts b/packages/core/src/core/client.test.ts index aec1976844..dfb0167f28 100644 --- a/packages/core/src/core/client.test.ts +++ b/packages/core/src/core/client.test.ts @@ -590,16 +590,37 @@ describe('Gemini Client (client.ts)', () => { expect(newChat).toBe(initialChat); }); + it('should return NOOP if history is too short to compress', async () => { + const { client } = setup({ + chatHistory: [{ role: 'user', parts: [{ text: 'hi' }] }], + originalTokenCount: 50, + }); + + const result = await client.tryCompressChat('prompt-id-noop', false); + + expect(result).toEqual({ + compressionStatus: CompressionStatus.NOOP, + originalTokenCount: 50, + newTokenCount: 50, + }); + expect(mockGenerateContentFn).not.toHaveBeenCalled(); + }); + it('logs a telemetry event when compressing', async () => { vi.spyOn(ClearcutLogger.prototype, 'logChatCompressionEvent'); - const MOCKED_TOKEN_LIMIT = 1000; const MOCKED_CONTEXT_PERCENTAGE_THRESHOLD = 0.5; - vi.mocked(tokenLimit).mockReturnValue(MOCKED_TOKEN_LIMIT); vi.spyOn(client['config'], 'getChatCompression').mockReturnValue({ contextPercentageThreshold: MOCKED_CONTEXT_PERCENTAGE_THRESHOLD, }); - const history = [{ role: 'user', parts: [{ text: '...history...' }] }]; + const history = [ + { role: 'user', parts: [{ text: '...history...' }] }, + { role: 'model', parts: [{ text: '...history...' }] }, + { role: 'user', parts: [{ text: '...history...' }] }, + { role: 'model', parts: [{ text: '...history...' }] }, + { role: 'user', parts: [{ text: '...history...' }] }, + { role: 'model', parts: [{ text: '...history...' }] }, + ]; mockGetHistory.mockReturnValue(history); const originalTokenCount = @@ -674,7 +695,14 @@ describe('Gemini Client (client.ts)', () => { vi.spyOn(client['config'], 'getChatCompression').mockReturnValue({ contextPercentageThreshold: MOCKED_CONTEXT_PERCENTAGE_THRESHOLD, }); - const history = [{ role: 'user', parts: [{ text: '...history...' }] }]; + const history = [ + { role: 'user', parts: [{ text: '...history...' }] }, + { role: 'model', parts: [{ text: '...history...' }] }, + { role: 'user', parts: [{ text: '...history...' }] }, + { role: 'model', parts: [{ text: '...history...' }] }, + { role: 'user', parts: [{ text: '...history...' }] }, + { role: 'model', parts: [{ text: '...history...' }] }, + ]; mockGetHistory.mockReturnValue(history); const originalTokenCount = @@ -838,7 +866,14 @@ describe('Gemini Client (client.ts)', () => { }); it('should always trigger summarization when force is true, regardless of token count', async () => { - const history = [{ role: 'user', parts: [{ text: '...history...' }] }]; + const history = [ + { role: 'user', parts: [{ text: '...history...' }] }, + { role: 'model', parts: [{ text: '...history...' }] }, + { role: 'user', parts: [{ text: '...history...' }] }, + { role: 'model', parts: [{ text: '...history...' }] }, + { role: 'user', parts: [{ text: '...history...' }] }, + { role: 'model', parts: [{ text: '...history...' }] }, + ]; mockGetHistory.mockReturnValue(history); const originalTokenCount = 100; // Well below threshold, but > estimated new count diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 849249f4c9..74635e207f 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -777,6 +777,14 @@ My setup is complete. I will provide my first command in the next turn. const historyToCompress = curatedHistory.slice(0, splitPoint); const historyToKeep = curatedHistory.slice(splitPoint); + if (historyToCompress.length === 0) { + return { + originalTokenCount, + newTokenCount: originalTokenCount, + compressionStatus: CompressionStatus.NOOP, + }; + } + const summaryResponse = await this.config .getContentGenerator() .generateContent(