mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-15 14:23:02 -07:00
feat(ui): restore threshold hint and thin arrow to compression message
This commit is contained in:
@@ -98,6 +98,7 @@ describe('compressCommand', () => {
|
||||
beforePercentage: 20,
|
||||
afterPercentage: 10,
|
||||
isManual: true,
|
||||
thresholdPercentage: 20,
|
||||
},
|
||||
},
|
||||
expect.any(Number),
|
||||
|
||||
@@ -76,8 +76,9 @@ export const compressCommand: SlashCommand = {
|
||||
isPending: false,
|
||||
beforePercentage,
|
||||
afterPercentage,
|
||||
compressionStatus: compressed.compressionStatus,
|
||||
compressionStatus: (Number(compressed.compressionStatus) as unknown) as CompressionStatus,
|
||||
isManual: true,
|
||||
thresholdPercentage: Math.round(threshold * 100),
|
||||
},
|
||||
} as HistoryItemCompression,
|
||||
Date.now(),
|
||||
|
||||
@@ -22,8 +22,13 @@ export interface CompressionDisplayProps {
|
||||
export function CompressionMessage({
|
||||
compression,
|
||||
}: CompressionDisplayProps): React.JSX.Element {
|
||||
const { isPending, beforePercentage, afterPercentage, compressionStatus } =
|
||||
compression;
|
||||
const {
|
||||
isPending,
|
||||
beforePercentage,
|
||||
afterPercentage,
|
||||
compressionStatus,
|
||||
thresholdPercentage,
|
||||
} = compression;
|
||||
|
||||
const getCompressionText = () => {
|
||||
if (isPending) {
|
||||
@@ -31,8 +36,13 @@ export function CompressionMessage({
|
||||
}
|
||||
|
||||
switch (compressionStatus) {
|
||||
case CompressionStatus.COMPRESSED:
|
||||
return `Context compressed (${beforePercentage}% ➔ ${afterPercentage}%).`;
|
||||
case CompressionStatus.COMPRESSED: {
|
||||
let text = `Context compressed (${beforePercentage}% → ${afterPercentage}%).`;
|
||||
if (thresholdPercentage != null) {
|
||||
text += ` Adjust threshold (${thresholdPercentage}%) in /settings.`;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
case CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT:
|
||||
return 'Compression was not beneficial for this history size.';
|
||||
case CompressionStatus.COMPRESSION_FAILED_TOKEN_COUNT_ERROR:
|
||||
|
||||
@@ -2637,6 +2637,7 @@ describe('useGeminiStream', () => {
|
||||
afterPercentage: 5,
|
||||
compressionStatus: 'compressed',
|
||||
isManual: false,
|
||||
thresholdPercentage: 20,
|
||||
},
|
||||
}),
|
||||
expect.any(Number),
|
||||
@@ -2696,6 +2697,68 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should add informational messages when ChatCompressed event is received with a large prompt even if showContextCompression is false', async () => {
|
||||
vi.mocked(tokenLimit).mockReturnValue(10000);
|
||||
vi.mocked(
|
||||
mockConfig.getContextWindowCompressionThreshold,
|
||||
).mockReturnValue(0.2); // 20%
|
||||
vi.mocked(mockConfig.getShowContextCompression).mockReturnValue(false);
|
||||
|
||||
// Setup mock to return a stream with ChatCompressed event and a large requestTokenCount (25%)
|
||||
mockSendMessageStream.mockReturnValue(
|
||||
(async function* () {
|
||||
yield {
|
||||
type: ServerGeminiEventType.ChatCompressed,
|
||||
value: {
|
||||
originalTokenCount: 1000,
|
||||
newTokenCount: 500,
|
||||
compressionStatus: 'compressed',
|
||||
requestTokenCount: 2500, // 25% > 20%
|
||||
},
|
||||
};
|
||||
yield {
|
||||
type: ServerGeminiEventType.Content,
|
||||
value: 'Response after compression',
|
||||
};
|
||||
yield {
|
||||
type: ServerGeminiEventType.Finished,
|
||||
value: {
|
||||
finishReason: 'STOP',
|
||||
usageMetadata: {
|
||||
promptTokenCount: 10,
|
||||
candidatesTokenCount: 20,
|
||||
totalTokenCount: 30,
|
||||
},
|
||||
},
|
||||
};
|
||||
})(),
|
||||
);
|
||||
|
||||
const { result } = renderHookWithDefaults();
|
||||
|
||||
// Submit a query
|
||||
await act(async () => {
|
||||
await result.current.submitQuery('Test large prompt compression');
|
||||
});
|
||||
|
||||
// Check that compression message WAS added despite the setting
|
||||
await waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'compression',
|
||||
compression: expect.objectContaining({
|
||||
beforePercentage: 10,
|
||||
afterPercentage: 5,
|
||||
compressionStatus: 'compressed',
|
||||
isManual: false,
|
||||
thresholdPercentage: 20,
|
||||
}),
|
||||
}),
|
||||
expect.any(Number),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
reason: 'STOP',
|
||||
|
||||
@@ -38,8 +38,12 @@ import {
|
||||
GeminiCliOperation,
|
||||
getPlanModeExitMessage,
|
||||
isBackgroundExecutionData,
|
||||
<<<<<<< HEAD
|
||||
Kind,
|
||||
ACTIVATE_SKILL_TOOL_NAME,
|
||||
=======
|
||||
CompressionStatus,
|
||||
>>>>>>> 97ff2bea4 (feat(ui): restore threshold hint and thin arrow to compression message)
|
||||
} from '@google/gemini-cli-core';
|
||||
import type {
|
||||
Config,
|
||||
@@ -1153,7 +1157,12 @@ export const useGeminiStream = (
|
||||
? Math.round((eventValue.newTokenCount / limit) * 100)
|
||||
: null;
|
||||
|
||||
if (!config.getShowContextCompression()) {
|
||||
const threshold = config.getContextWindowCompressionThreshold();
|
||||
const isLargePrompt =
|
||||
eventValue?.requestTokenCount != null &&
|
||||
eventValue.requestTokenCount / limit > threshold;
|
||||
|
||||
if (!config.getShowContextCompression() && !isLargePrompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1164,8 +1173,9 @@ export const useGeminiStream = (
|
||||
isPending: false,
|
||||
beforePercentage,
|
||||
afterPercentage,
|
||||
compressionStatus: eventValue?.compressionStatus ?? null,
|
||||
compressionStatus: eventValue ? ((Number(eventValue.compressionStatus) as unknown) as CompressionStatus) : null,
|
||||
isManual: false,
|
||||
thresholdPercentage: Math.round(threshold * 100),
|
||||
},
|
||||
timestamp: new Date(userMessageTimestamp),
|
||||
} as HistoryItemWithoutId,
|
||||
|
||||
@@ -141,7 +141,8 @@ export interface CompressionProps {
|
||||
beforePercentage: number | null;
|
||||
afterPercentage: number | null;
|
||||
compressionStatus: CompressionStatus | null;
|
||||
isManual?: boolean;
|
||||
isManual: boolean;
|
||||
thresholdPercentage?: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -248,6 +248,9 @@ describe('Gemini Client (client.ts)', () => {
|
||||
getEnableHooks: vi.fn().mockReturnValue(false),
|
||||
getChatCompression: vi.fn().mockReturnValue(undefined),
|
||||
getCompressionThreshold: vi.fn().mockReturnValue(undefined),
|
||||
getShowContextWindowWarning: vi.fn().mockReturnValue(false),
|
||||
getShowContextCompression: vi.fn().mockReturnValue(false),
|
||||
getContextWindowCompressionThreshold: vi.fn().mockReturnValue(0.2),
|
||||
getSkipNextSpeakerCheck: vi.fn().mockReturnValue(false),
|
||||
getShowModelInfoInChat: vi.fn().mockReturnValue(false),
|
||||
getContinueOnFailedApiCall: vi.fn(),
|
||||
@@ -1617,6 +1620,7 @@ ${JSON.stringify(
|
||||
originalTokenCount: initialTokenCount,
|
||||
newTokenCount: 400,
|
||||
compressionStatus: CompressionStatus.COMPRESSED,
|
||||
requestTokenCount: 50, // Added to match updated interface
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1643,10 +1647,13 @@ ${JSON.stringify(
|
||||
}),
|
||||
);
|
||||
|
||||
// 2. Should contain compression event
|
||||
// 2. Should contain compression event with requestTokenCount
|
||||
expect(events).toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: GeminiEventType.ChatCompressed,
|
||||
value: expect.objectContaining({
|
||||
requestTokenCount: expect.any(Number),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -608,7 +608,19 @@ export class GeminiClient {
|
||||
// Check for context window overflow
|
||||
const modelForLimitCheck = this._getActiveModelForCurrentTurn();
|
||||
|
||||
const compressed = await this.tryCompressChat(prompt_id, false);
|
||||
// Estimate tokens. For text-only requests, we estimate based on character length.
|
||||
// For requests with non-text parts (like images, tools), we use the countTokens API.
|
||||
const estimatedRequestTokenCount = await calculateRequestTokenCount(
|
||||
request,
|
||||
this.getContentGeneratorOrFail(),
|
||||
modelForLimitCheck,
|
||||
);
|
||||
|
||||
const compressed = await this.tryCompressChat(
|
||||
prompt_id,
|
||||
false,
|
||||
estimatedRequestTokenCount,
|
||||
);
|
||||
|
||||
if (compressed.compressionStatus === CompressionStatus.COMPRESSED) {
|
||||
yield { type: GeminiEventType.ChatCompressed, value: compressed };
|
||||
@@ -619,17 +631,13 @@ export class GeminiClient {
|
||||
|
||||
await this.tryMaskToolOutputs(this.getHistory());
|
||||
|
||||
// Estimate tokens. For text-only requests, we estimate based on character length.
|
||||
// For requests with non-text parts (like images, tools), we use the countTokens API.
|
||||
const estimatedRequestTokenCount = await calculateRequestTokenCount(
|
||||
request,
|
||||
this.getContentGeneratorOrFail(),
|
||||
modelForLimitCheck,
|
||||
);
|
||||
|
||||
if (estimatedRequestTokenCount > remainingTokenCount) {
|
||||
if (!this.config.getShowContextWindowWarning()) {
|
||||
const forcedCompressed = await this.tryCompressChat(prompt_id, true);
|
||||
const forcedCompressed = await this.tryCompressChat(
|
||||
prompt_id,
|
||||
true,
|
||||
estimatedRequestTokenCount,
|
||||
);
|
||||
if (
|
||||
forcedCompressed.compressionStatus === CompressionStatus.COMPRESSED
|
||||
) {
|
||||
@@ -1175,6 +1183,7 @@ export class GeminiClient {
|
||||
async tryCompressChat(
|
||||
prompt_id: string,
|
||||
force: boolean = false,
|
||||
requestTokenCount?: number,
|
||||
): Promise<ChatCompressionInfo> {
|
||||
// If the model is 'auto', we will use a placeholder model to check.
|
||||
// Compression occurs before we choose a model, so calling `count_tokens`
|
||||
@@ -1190,6 +1199,11 @@ export class GeminiClient {
|
||||
this.hasFailedCompressionAttempt,
|
||||
);
|
||||
|
||||
const resultInfo = {
|
||||
...info,
|
||||
requestTokenCount,
|
||||
};
|
||||
|
||||
if (
|
||||
info.compressionStatus ===
|
||||
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT
|
||||
@@ -1225,7 +1239,7 @@ export class GeminiClient {
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
return resultInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -188,6 +188,7 @@ export interface ChatCompressionInfo {
|
||||
originalTokenCount: number;
|
||||
newTokenCount: number;
|
||||
compressionStatus: CompressionStatus;
|
||||
requestTokenCount?: number;
|
||||
}
|
||||
|
||||
export type ServerGeminiChatCompressedEvent = {
|
||||
|
||||
Reference in New Issue
Block a user