mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
fix(core): Improve compression message clarity for small history cases (#4404)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { render } from 'ink-testing-library';
|
||||
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';
|
||||
|
||||
describe('<CompressionMessage />', () => {
|
||||
const createCompressionProps = (
|
||||
overrides: Partial<CompressionProps> = {},
|
||||
): CompressionDisplayProps => ({
|
||||
compression: {
|
||||
isPending: false,
|
||||
originalTokenCount: null,
|
||||
newTokenCount: null,
|
||||
compressionStatus: CompressionStatus.COMPRESSED,
|
||||
...overrides,
|
||||
},
|
||||
});
|
||||
|
||||
describe('pending state', () => {
|
||||
it('renders pending message when compression is in progress', () => {
|
||||
const props = createCompressionProps({ isPending: true });
|
||||
const { lastFrame } = render(<CompressionMessage {...props} />);
|
||||
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,
|
||||
compressionStatus: CompressionStatus.COMPRESSED,
|
||||
});
|
||||
const { lastFrame } = render(<CompressionMessage {...props} />);
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('✦');
|
||||
expect(output).toContain(
|
||||
'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,
|
||||
compressionStatus: CompressionStatus.COMPRESSED,
|
||||
});
|
||||
const { lastFrame } = render(<CompressionMessage {...props} />);
|
||||
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)', () => {
|
||||
it('renders skip message when compression would increase token count', () => {
|
||||
const props = createCompressionProps({
|
||||
isPending: false,
|
||||
originalTokenCount: 50,
|
||||
newTokenCount: 75,
|
||||
compressionStatus:
|
||||
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
||||
});
|
||||
const { lastFrame } = render(<CompressionMessage {...props} />);
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('✦');
|
||||
expect(output).toContain(
|
||||
'Compression was not beneficial for this history size.',
|
||||
);
|
||||
});
|
||||
|
||||
it('renders skip message when token counts are equal', () => {
|
||||
const props = createCompressionProps({
|
||||
isPending: false,
|
||||
originalTokenCount: 50,
|
||||
newTokenCount: 50,
|
||||
compressionStatus:
|
||||
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
||||
});
|
||||
const { lastFrame } = render(<CompressionMessage {...props} />);
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain(
|
||||
'Compression was not beneficial for this history 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,
|
||||
compressionStatus: CompressionStatus.COMPRESSED,
|
||||
});
|
||||
const { lastFrame } = render(<CompressionMessage {...props} />);
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain(expected);
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
compressionStatus:
|
||||
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
||||
});
|
||||
const { lastFrame } = render(<CompressionMessage {...props} />);
|
||||
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 }) => {
|
||||
const props = createCompressionProps({
|
||||
isPending: false,
|
||||
originalTokenCount: original,
|
||||
newTokenCount: newTokens,
|
||||
compressionStatus:
|
||||
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
||||
});
|
||||
const { lastFrame } = render(<CompressionMessage {...props} />);
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('compression did not reduce size');
|
||||
expect(output).not.toContain('compressed from');
|
||||
expect(output).not.toContain('Compression was not beneficial');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 { theme } from '../../semantic-colors.js';
|
||||
import { SCREEN_READER_MODEL_PREFIX } from '../../textConstants.js';
|
||||
import { CompressionStatus } from '@google/gemini-cli-core';
|
||||
|
||||
export interface CompressionDisplayProps {
|
||||
compression: CompressionProps;
|
||||
@@ -19,18 +19,46 @@ 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<CompressionDisplayProps> = ({
|
||||
export function CompressionMessage({
|
||||
compression,
|
||||
}) => {
|
||||
const text = compression.isPending
|
||||
? 'Compressing chat history'
|
||||
: `Chat history compressed from ${compression.originalTokenCount ?? 'unknown'}` +
|
||||
` to ${compression.newTokenCount ?? 'unknown'} tokens.`;
|
||||
}: CompressionDisplayProps): React.JSX.Element {
|
||||
const { isPending, originalTokenCount, newTokenCount, compressionStatus } =
|
||||
compression;
|
||||
|
||||
const originalTokens = originalTokenCount ?? 0;
|
||||
const newTokens = newTokenCount ?? 0;
|
||||
|
||||
const getCompressionText = () => {
|
||||
if (isPending) {
|
||||
return 'Compressing chat history';
|
||||
}
|
||||
|
||||
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 '';
|
||||
}
|
||||
};
|
||||
|
||||
const text = getCompressionText();
|
||||
|
||||
return (
|
||||
<Box flexDirection="row">
|
||||
<Box marginRight={1}>
|
||||
{compression.isPending ? (
|
||||
{isPending ? (
|
||||
<Spinner type="dots" />
|
||||
) : (
|
||||
<Text color={theme.text.accent}>✦</Text>
|
||||
@@ -48,4 +76,4 @@ export const CompressionMessage: React.FC<CompressionDisplayProps> = ({
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user