From 2fdad50691d888e2216b81f8b0bd62f31573524b Mon Sep 17 00:00:00 2001 From: Shammi Anand Date: Fri, 18 Jul 2025 01:19:40 +0530 Subject: [PATCH 1/7] fix(core): Improve compression message clarity for small history cases --- .../messages/CompressionMessage.tsx | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.tsx index c7ef122b4ba..f69a09fadc4 100644 --- a/packages/cli/src/ui/components/messages/CompressionMessage.tsx +++ b/packages/cli/src/ui/components/messages/CompressionMessage.tsx @@ -21,26 +21,36 @@ export interface CompressionDisplayProps { export const CompressionMessage: React.FC = ({ compression, }) => { - const text = compression.isPending - ? 'Compressing chat history' - : `Chat history compressed from ${compression.originalTokenCount ?? 'unknown'}` + - ` to ${compression.newTokenCount ?? 'unknown'} tokens.`; + const { isPending, originalTokenCount, newTokenCount } = compression; + + const getCompressionText = () => { + if (isPending) { + return 'Compressing chat history'; + } + + const originalTokens = originalTokenCount ?? 0; + const newTokens = newTokenCount ?? 0; + + if (newTokens >= originalTokens) { + return 'Skipping compression for small history as the process would have increased its size.'; + } + + return `Chat history compressed from ${originalTokens} to ${newTokens} tokens.`; + }; + + const text = getCompressionText(); return ( - {compression.isPending ? ( + {isPending ? ( ) : ( )} - + {text} From cecafe0b88aba117ff17f97e9189600e822f62a9 Mon Sep 17 00:00:00 2001 From: Shammi Anand Date: Fri, 18 Jul 2025 01:33:24 +0530 Subject: [PATCH 2/7] feat: use consistent colors for compression text --- .../ui/components/messages/CompressionMessage.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.tsx index f69a09fadc4..7b7243e106d 100644 --- a/packages/cli/src/ui/components/messages/CompressionMessage.tsx +++ b/packages/cli/src/ui/components/messages/CompressionMessage.tsx @@ -23,14 +23,14 @@ export const CompressionMessage: React.FC = ({ }) => { const { isPending, originalTokenCount, newTokenCount } = compression; + const originalTokens = originalTokenCount ?? 0; + const newTokens = newTokenCount ?? 0; + const getCompressionText = () => { if (isPending) { return 'Compressing chat history'; } - const originalTokens = originalTokenCount ?? 0; - const newTokens = newTokenCount ?? 0; - if (newTokens >= originalTokens) { return 'Skipping compression for small history as the process would have increased its size.'; } @@ -39,6 +39,11 @@ export const CompressionMessage: React.FC = ({ }; const text = getCompressionText(); + const color = isPending + ? Colors.AccentPurple + : newTokens >= originalTokens + ? Colors.AccentYellow + : Colors.AccentGreen; return ( @@ -50,9 +55,7 @@ export const CompressionMessage: React.FC = ({ )} - - {text} - + {text} ); From b5be8ca612784f276ae265320511c82da86823dd Mon Sep 17 00:00:00 2001 From: Shammi Anand Date: Sat, 23 Aug 2025 01:02:39 +0530 Subject: [PATCH 3/7] feat(tests): tests for CompressionMessage component --- .../messages/CompressionMessage.test.tsx | 117 ++++++++++++++++++ .../messages/CompressionMessage.tsx | 5 - 2 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 packages/cli/src/ui/components/messages/CompressionMessage.test.tsx diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx new file mode 100644 index 00000000000..f77cdf63d48 --- /dev/null +++ b/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx @@ -0,0 +1,117 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { render } from 'ink-testing-library'; +import { CompressionMessage, CompressionDisplayProps } from './CompressionMessage.js'; +import { CompressionProps } from '../../types.js'; +import { describe, it, expect } from 'vitest'; + +describe('', () => { + const createCompressionProps = ( + overrides: Partial = {} + ): CompressionDisplayProps => ({ + compression: { + isPending: false, + originalTokenCount: null, + newTokenCount: null, + ...overrides, + }, + }); + + describe('pending state', () => { + it('renders pending message when compression is in progress', () => { + const props = createCompressionProps({ isPending: true }); + const { lastFrame } = render(); + const output = lastFrame(); + + expect(output).toContain('Compressing chat history'); + }); + }); + + describe('normal compression (successful token reduction)', () => { + it('renders success message when tokens are reduced', () => { + const props = createCompressionProps({ + isPending: false, + originalTokenCount: 100, + newTokenCount: 50, + }); + const { lastFrame } = render(); + const output = lastFrame(); + + expect(output).toContain('✦'); + expect(output).toContain('Chat history compressed from 100 to 50 tokens.'); + }); + }); + + describe('skipped compression (tokens increased or same)', () => { + it('renders skip message when compression would increase token count', () => { + const props = createCompressionProps({ + isPending: false, + originalTokenCount: 50, + newTokenCount: 75, + }); + const { lastFrame } = render(); + const output = lastFrame(); + + expect(output).toContain('✦'); + expect(output).toContain('Skipping compression for small history as the process would have increased its size.'); + }); + + it('renders skip message when token counts are equal', () => { + const props = createCompressionProps({ + isPending: false, + originalTokenCount: 50, + newTokenCount: 50, + }); + const { lastFrame } = render(); + const output = lastFrame(); + + expect(output).toContain('Skipping compression for small history as the process would have increased its size.'); + }); + }); + + describe('message content validation', () => { + it('displays correct compression statistics', () => { + const testCases = [ + { original: 200, new: 80, expected: 'compressed from 200 to 80 tokens' }, + { original: 500, new: 150, expected: 'compressed from 500 to 150 tokens' }, + { original: 1500, new: 400, expected: 'compressed from 1500 to 400 tokens' }, + ]; + + testCases.forEach(({ original, new: newTokens, expected }) => { + const props = createCompressionProps({ + isPending: false, + originalTokenCount: original, + newTokenCount: newTokens, + }); + const { lastFrame } = render(); + const output = lastFrame(); + + expect(output).toContain(expected); + }); + }); + + it('always shows skip message when new tokens >= original tokens', () => { + const testCases = [ + { original: 50, new: 60 }, // Increased + { original: 100, new: 100 }, // Same + ]; + + testCases.forEach(({ original, new: newTokens }) => { + const props = createCompressionProps({ + isPending: false, + originalTokenCount: original, + newTokenCount: newTokens, + }); + const { lastFrame } = render(); + const output = lastFrame(); + + expect(output).toContain('Skipping compression for small history as the process would have increased its size.'); + expect(output).not.toContain('compressed from'); + }); + }); + }); +}); diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.tsx index c81945b56fd..8c75a4d2c8d 100644 --- a/packages/cli/src/ui/components/messages/CompressionMessage.tsx +++ b/packages/cli/src/ui/components/messages/CompressionMessage.tsx @@ -40,11 +40,6 @@ export const CompressionMessage: React.FC = ({ }; const text = getCompressionText(); - const color = isPending - ? Colors.AccentPurple - : newTokens >= originalTokens - ? Colors.AccentYellow - : Colors.AccentGreen; return ( From 1e675d144663a00e2f27607cacc1e1a5477adda8 Mon Sep 17 00:00:00 2001 From: Shammi Anand Date: Sat, 23 Aug 2025 01:08:04 +0530 Subject: [PATCH 4/7] chore(lint): `npm run preflight` --- .../messages/CompressionMessage.test.tsx | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx index f77cdf63d48..b4177e54670 100644 --- a/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx +++ b/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx @@ -5,13 +5,16 @@ */ import { render } from 'ink-testing-library'; -import { CompressionMessage, CompressionDisplayProps } from './CompressionMessage.js'; +import { + CompressionMessage, + CompressionDisplayProps, +} from './CompressionMessage.js'; import { CompressionProps } from '../../types.js'; import { describe, it, expect } from 'vitest'; describe('', () => { const createCompressionProps = ( - overrides: Partial = {} + overrides: Partial = {}, ): CompressionDisplayProps => ({ compression: { isPending: false, @@ -42,7 +45,9 @@ describe('', () => { const output = lastFrame(); expect(output).toContain('✦'); - expect(output).toContain('Chat history compressed from 100 to 50 tokens.'); + expect(output).toContain( + 'Chat history compressed from 100 to 50 tokens.', + ); }); }); @@ -57,7 +62,9 @@ describe('', () => { const output = lastFrame(); expect(output).toContain('✦'); - expect(output).toContain('Skipping compression for small history as the process would have increased its size.'); + expect(output).toContain( + 'Skipping compression for small history as the process would have increased its size.', + ); }); it('renders skip message when token counts are equal', () => { @@ -69,16 +76,30 @@ describe('', () => { const { lastFrame } = render(); const output = lastFrame(); - expect(output).toContain('Skipping compression for small history as the process would have increased its size.'); + expect(output).toContain( + 'Skipping compression for small history as the process would have increased its size.', + ); }); }); describe('message content validation', () => { it('displays correct compression statistics', () => { const testCases = [ - { original: 200, new: 80, expected: 'compressed from 200 to 80 tokens' }, - { original: 500, new: 150, expected: 'compressed from 500 to 150 tokens' }, - { original: 1500, new: 400, expected: 'compressed from 1500 to 400 tokens' }, + { + original: 200, + new: 80, + expected: 'compressed from 200 to 80 tokens', + }, + { + original: 500, + new: 150, + expected: 'compressed from 500 to 150 tokens', + }, + { + original: 1500, + new: 400, + expected: 'compressed from 1500 to 400 tokens', + }, ]; testCases.forEach(({ original, new: newTokens, expected }) => { @@ -109,7 +130,9 @@ describe('', () => { const { lastFrame } = render(); const output = lastFrame(); - expect(output).toContain('Skipping compression for small history as the process would have increased its size.'); + expect(output).toContain( + 'Skipping compression for small history as the process would have increased its size.', + ); expect(output).not.toContain('compressed from'); }); }); From 190a943caf786a5117113dba603aa98238f16a15 Mon Sep 17 00:00:00 2001 From: Shammi Anand Date: Sat, 23 Aug 2025 19:40:09 +0530 Subject: [PATCH 5/7] fix: review comments with token threshold --- .../messages/CompressionMessage.test.tsx | 55 +++++++++++++++++-- .../messages/CompressionMessage.tsx | 8 ++- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx index b4177e54670..3b9f7d77867 100644 --- a/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx +++ b/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx @@ -49,6 +49,28 @@ describe('', () => { 'Chat history compressed from 100 to 50 tokens.', ); }); + + it('renders success message for large successful compressions', () => { + const testCases = [ + { original: 50000, new: 25000 }, // Large compression + { original: 700000, new: 350000 }, // Very large compression + ]; + + testCases.forEach(({ original, new: newTokens }) => { + const props = createCompressionProps({ + isPending: false, + originalTokenCount: original, + newTokenCount: newTokens, + }); + const { lastFrame } = render(); + const output = lastFrame(); + + expect(output).toContain('✦'); + expect(output).toContain(`compressed from ${original} to ${newTokens} tokens`); + expect(output).not.toContain('Skipping compression'); + expect(output).not.toContain('did not reduce size'); + }); + }); }); describe('skipped compression (tokens increased or same)', () => { @@ -63,7 +85,7 @@ describe('', () => { expect(output).toContain('✦'); expect(output).toContain( - 'Skipping compression for small history as the process would have increased its size.', + 'Compression was not beneficial for this history size.', ); }); @@ -77,7 +99,7 @@ describe('', () => { const output = lastFrame(); expect(output).toContain( - 'Skipping compression for small history as the process would have increased its size.', + 'Compression was not beneficial for this history size.', ); }); }); @@ -115,10 +137,34 @@ describe('', () => { }); }); - it('always shows skip message when new tokens >= original tokens', () => { + it('shows skip message for small histories when new tokens >= original tokens', () => { const testCases = [ { original: 50, new: 60 }, // Increased { original: 100, new: 100 }, // Same + { original: 49999, new: 50000 }, // Just under 50k threshold + ]; + + testCases.forEach(({ original, new: newTokens }) => { + const props = createCompressionProps({ + isPending: false, + originalTokenCount: original, + newTokenCount: newTokens, + }); + const { lastFrame } = render(); + const output = lastFrame(); + + expect(output).toContain( + 'Compression was not beneficial for this history size.', + ); + expect(output).not.toContain('compressed from'); + }); + }); + + it('shows compression failure message for large histories when new tokens >= original tokens', () => { + const testCases = [ + { original: 50000, new: 50100 }, // At 50k threshold + { original: 700000, new: 710000 }, // Large history case + { original: 100000, new: 100000 }, // Large history, same count ]; testCases.forEach(({ original, new: newTokens }) => { @@ -131,9 +177,10 @@ describe('', () => { const output = lastFrame(); expect(output).toContain( - 'Skipping compression for small history as the process would have increased its size.', + 'compression did not reduce size', ); expect(output).not.toContain('compressed from'); + expect(output).not.toContain('Compression was not beneficial'); }); }); }); diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.tsx index 8c75a4d2c8d..63dd4502843 100644 --- a/packages/cli/src/ui/components/messages/CompressionMessage.tsx +++ b/packages/cli/src/ui/components/messages/CompressionMessage.tsx @@ -33,7 +33,13 @@ export const CompressionMessage: React.FC = ({ } if (newTokens >= originalTokens) { - return 'Skipping compression for small history as the process would have increased its size.'; + // For smaller histories (< 50k tokens), compression overhead likely exceeds benefits + if (originalTokens < 50000) { + return 'Compression was not beneficial for this history size.'; + } + // For larger histories where compression should work but didn't, + // this suggests an issue with the compression process itself + return 'Chat history compression did not reduce size. This may indicate issues with the compression prompt.'; } return `Chat history compressed from ${originalTokens} to ${newTokens} tokens.`; From e1ef6d5a8cb699b4b7ae9cf29cb2069bcb52b657 Mon Sep 17 00:00:00 2001 From: Shammi Anand Date: Thu, 28 Aug 2025 02:14:18 +0530 Subject: [PATCH 6/7] fix(lint): consistent type imports --- .../src/ui/components/messages/CompressionMessage.test.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx index 3b9f7d77867..cf58ff9d045 100644 --- a/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx +++ b/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx @@ -5,11 +5,12 @@ */ import { render } from 'ink-testing-library'; +import type { + CompressionDisplayProps} from './CompressionMessage.js'; import { - CompressionMessage, - CompressionDisplayProps, + CompressionMessage } from './CompressionMessage.js'; -import { CompressionProps } from '../../types.js'; +import type { CompressionProps } from '../../types.js'; import { describe, it, expect } from 'vitest'; describe('', () => { From 588dc56282cbe95873d06bb306fb54582005796f Mon Sep 17 00:00:00 2001 From: jacob314 Date: Thu, 4 Sep 2025 17:35:56 -0700 Subject: [PATCH 7/7] Updates to align with changes to package/core about compression status changes. --- .../messages/CompressionMessage.test.tsx | 28 +++++++++----- .../messages/CompressionMessage.tsx | 38 +++++++++++-------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx index cf58ff9d045..1c56a7326a3 100644 --- a/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx +++ b/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx @@ -5,11 +5,9 @@ */ import { render } from 'ink-testing-library'; -import type { - CompressionDisplayProps} from './CompressionMessage.js'; -import { - CompressionMessage -} from './CompressionMessage.js'; +import type { CompressionDisplayProps } from './CompressionMessage.js'; +import { CompressionMessage } from './CompressionMessage.js'; +import { CompressionStatus } from '@google/gemini-cli-core'; import type { CompressionProps } from '../../types.js'; import { describe, it, expect } from 'vitest'; @@ -21,6 +19,7 @@ describe('', () => { isPending: false, originalTokenCount: null, newTokenCount: null, + compressionStatus: CompressionStatus.COMPRESSED, ...overrides, }, }); @@ -41,6 +40,7 @@ describe('', () => { isPending: false, originalTokenCount: 100, newTokenCount: 50, + compressionStatus: CompressionStatus.COMPRESSED, }); const { lastFrame } = render(); const output = lastFrame(); @@ -62,12 +62,15 @@ describe('', () => { isPending: false, originalTokenCount: original, newTokenCount: newTokens, + compressionStatus: CompressionStatus.COMPRESSED, }); const { lastFrame } = render(); const output = lastFrame(); expect(output).toContain('✦'); - expect(output).toContain(`compressed from ${original} to ${newTokens} tokens`); + expect(output).toContain( + `compressed from ${original} to ${newTokens} tokens`, + ); expect(output).not.toContain('Skipping compression'); expect(output).not.toContain('did not reduce size'); }); @@ -80,6 +83,8 @@ describe('', () => { isPending: false, originalTokenCount: 50, newTokenCount: 75, + compressionStatus: + CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT, }); const { lastFrame } = render(); const output = lastFrame(); @@ -95,6 +100,8 @@ describe('', () => { isPending: false, originalTokenCount: 50, newTokenCount: 50, + compressionStatus: + CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT, }); const { lastFrame } = render(); const output = lastFrame(); @@ -130,6 +137,7 @@ describe('', () => { isPending: false, originalTokenCount: original, newTokenCount: newTokens, + compressionStatus: CompressionStatus.COMPRESSED, }); const { lastFrame } = render(); const output = lastFrame(); @@ -150,6 +158,8 @@ describe('', () => { isPending: false, originalTokenCount: original, newTokenCount: newTokens, + compressionStatus: + CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT, }); const { lastFrame } = render(); const output = lastFrame(); @@ -173,13 +183,13 @@ describe('', () => { isPending: false, originalTokenCount: original, newTokenCount: newTokens, + compressionStatus: + CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT, }); const { lastFrame } = render(); const output = lastFrame(); - expect(output).toContain( - 'compression did not reduce size', - ); + expect(output).toContain('compression did not reduce size'); expect(output).not.toContain('compressed from'); expect(output).not.toContain('Compression was not beneficial'); }); diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.tsx index b842926a325..1c97f5ab631 100644 --- a/packages/cli/src/ui/components/messages/CompressionMessage.tsx +++ b/packages/cli/src/ui/components/messages/CompressionMessage.tsx @@ -4,12 +4,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type React from 'react'; import { Box, Text } from 'ink'; import type { CompressionProps } from '../../types.js'; import Spinner from 'ink-spinner'; import { Colors } from '../../colors.js'; import { SCREEN_READER_MODEL_PREFIX } from '../../textConstants.js'; +import { CompressionStatus } from '@google/gemini-cli-core'; export interface CompressionDisplayProps { compression: CompressionProps; @@ -19,10 +19,11 @@ export interface CompressionDisplayProps { * Compression messages appear when the /compress command is run, and show a loading spinner * while compression is in progress, followed up by some compression stats. */ -export const CompressionMessage: React.FC = ({ +export function CompressionMessage({ compression, -}) => { - const { isPending, originalTokenCount, newTokenCount } = compression; +}: CompressionDisplayProps): React.JSX.Element { + const { isPending, originalTokenCount, newTokenCount, compressionStatus } = + compression; const originalTokens = originalTokenCount ?? 0; const newTokens = newTokenCount ?? 0; @@ -32,17 +33,24 @@ export const CompressionMessage: React.FC = ({ return 'Compressing chat history'; } - if (newTokens >= originalTokens) { - // For smaller histories (< 50k tokens), compression overhead likely exceeds benefits - if (originalTokens < 50000) { - return 'Compression was not beneficial for this history size.'; - } - // For larger histories where compression should work but didn't, - // this suggests an issue with the compression process itself - return 'Chat history compression did not reduce size. This may indicate issues with the compression prompt.'; + switch (compressionStatus) { + case CompressionStatus.COMPRESSED: + return `Chat history compressed from ${originalTokens} to ${newTokens} tokens.`; + case CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT: + // For smaller histories (< 50k tokens), compression overhead likely exceeds benefits + if (originalTokens < 50000) { + return 'Compression was not beneficial for this history size.'; + } + // For larger histories where compression should work but didn't, + // this suggests an issue with the compression process itself + return 'Chat history compression did not reduce size. This may indicate issues with the compression prompt.'; + 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.'; + default: + return ''; } - - return `Chat history compressed from ${originalTokens} to ${newTokens} tokens.`; }; const text = getCompressionText(); @@ -68,4 +76,4 @@ export const CompressionMessage: React.FC = ({ ); -}; +}