From c276d0c7b6c2dec4c38335355eb4a903c2379409 Mon Sep 17 00:00:00 2001 From: Christian Gunderman Date: Thu, 19 Feb 2026 19:06:36 +0000 Subject: [PATCH] Fix message too large issue. (#19499) --- packages/core/src/agents/local-executor.ts | 7 ++++ packages/core/src/core/client.ts | 9 +++++ packages/core/src/core/turn.ts | 3 ++ .../services/chatCompressionService.test.ts | 4 ++- .../src/services/chatCompressionService.ts | 34 ++++++++++++++++--- 5 files changed, 52 insertions(+), 5 deletions(-) diff --git a/packages/core/src/agents/local-executor.ts b/packages/core/src/agents/local-executor.ts index 0d2f009a9e..bcb4e888ce 100644 --- a/packages/core/src/agents/local-executor.ts +++ b/packages/core/src/agents/local-executor.ts @@ -689,6 +689,13 @@ export class LocalAgentExecutor { chat.setHistory(newHistory); this.hasFailedCompressionAttempt = false; } + } else if (info.compressionStatus === CompressionStatus.CONTENT_TRUNCATED) { + if (newHistory) { + chat.setHistory(newHistory); + // Do NOT reset hasFailedCompressionAttempt. + // We only truncated content because summarization previously failed. + // We want to keep avoiding expensive summarization calls. + } } } diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 951da7d6ef..0951eb397b 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -1082,6 +1082,15 @@ export class GeminiClient { this.updateTelemetryTokenCount(); this.forceFullIdeContext = true; } + } else if (info.compressionStatus === CompressionStatus.CONTENT_TRUNCATED) { + if (newHistory) { + // We truncated content to save space, but summarization is still "failed". + // We update the chat context directly without resetting the failure flag. + this.getChat().setHistory(newHistory); + this.updateTelemetryTokenCount(); + // We don't reset the chat session fully like in COMPRESSED because + // this is a lighter-weight intervention. + } } return info; diff --git a/packages/core/src/core/turn.ts b/packages/core/src/core/turn.ts index f31050dd83..b868da8e4f 100644 --- a/packages/core/src/core/turn.ts +++ b/packages/core/src/core/turn.ts @@ -180,6 +180,9 @@ export enum CompressionStatus { /** The compression was not necessary and no action was taken */ NOOP, + + /** The compression was skipped due to previous failure, but content was truncated to budget */ + CONTENT_TRUNCATED, } export interface ChatCompressionInfo { diff --git a/packages/core/src/services/chatCompressionService.test.ts b/packages/core/src/services/chatCompressionService.test.ts index 39b82869bd..4ddd38e25c 100644 --- a/packages/core/src/services/chatCompressionService.test.ts +++ b/packages/core/src/services/chatCompressionService.test.ts @@ -226,8 +226,10 @@ describe('ChatCompressionService', () => { false, mockModel, mockConfig, - true, + false, ); + // It should now attempt compression even if previously failed (logic removed) + // But since history is small, it will be NOOP due to threshold expect(result.info.compressionStatus).toBe(CompressionStatus.NOOP); expect(result.newHistory).toBeNull(); }); diff --git a/packages/core/src/services/chatCompressionService.ts b/packages/core/src/services/chatCompressionService.ts index 6f5366aad5..9878358966 100644 --- a/packages/core/src/services/chatCompressionService.ts +++ b/packages/core/src/services/chatCompressionService.ts @@ -240,10 +240,7 @@ export class ChatCompressionService { const curatedHistory = chat.getHistory(true); // Regardless of `force`, don't do anything if the history is empty. - if ( - curatedHistory.length === 0 || - (hasFailedCompressionAttempt && !force) - ) { + if (curatedHistory.length === 0) { return { newHistory: null, info: { @@ -285,6 +282,35 @@ export class ChatCompressionService { config, ); + // If summarization previously failed (and not forced), we only rely on truncation. + // We do NOT attempt to invoke the LLM for summarization again to avoid repeated failures/costs. + if (hasFailedCompressionAttempt && !force) { + const truncatedTokenCount = estimateTokenCountSync( + truncatedHistory.flatMap((c) => c.parts || []), + ); + + // If truncation reduced the size, we consider it a successful "compression" (truncation only). + if (truncatedTokenCount < originalTokenCount) { + return { + newHistory: truncatedHistory, + info: { + originalTokenCount, + newTokenCount: truncatedTokenCount, + compressionStatus: CompressionStatus.CONTENT_TRUNCATED, + }, + }; + } + + return { + newHistory: null, + info: { + originalTokenCount, + newTokenCount: originalTokenCount, + compressionStatus: CompressionStatus.NOOP, + }, + }; + } + const splitPoint = findCompressSplitPoint( truncatedHistory, 1 - COMPRESSION_PRESERVE_THRESHOLD,