feat(cli): lower compression threshold to 0.2 and update UX

- Change default compression threshold to 200k

- Introduce 'ChatCompressing' event to handle UI loading

- Subtly display context optimization in message footer

- Fix associated snapshot tests
This commit is contained in:
Taylor Mullen
2026-02-17 23:12:07 -08:00
parent 884acda2dc
commit 3031aef1a4
10 changed files with 79 additions and 53 deletions

View File

@@ -1061,6 +1061,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
backgroundShells,
dismissBackgroundShell,
retryStatus,
isCompressing,
} = useGeminiStream(
config.getGeminiClient(),
historyManager.history,
@@ -1613,6 +1614,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
streamingState,
shouldShowFocusHint,
retryStatus,
customWittyPhrases: isCompressing ? ['Optimizing context...'] : undefined,
});
const handleGlobalKeypress = useCallback(

View File

@@ -17,7 +17,7 @@ import { ToolGroupMessage } from './messages/ToolGroupMessage.js';
import { GeminiMessageContent } from './messages/GeminiMessageContent.js';
import { CompressionMessage } from './messages/CompressionMessage.js';
import { WarningMessage } from './messages/WarningMessage.js';
import { Box } from 'ink';
import { Box, Text } from 'ink';
import { AboutBox } from './AboutBox.js';
import { StatsDisplay } from './StatsDisplay.js';
import { ModelStatsDisplay } from './ModelStatsDisplay.js';
@@ -75,6 +75,14 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
{itemForDisplay.type === 'user_shell' && (
<UserShellMessage text={itemForDisplay.text} width={terminalWidth} />
)}
{itemForDisplay.type === 'auto_compression' && (
<Box paddingLeft={2} marginTop={0}>
<Text dimColor>
(Context optimized: {itemForDisplay.compression.originalTokenCount}{' '}
{itemForDisplay.compression.newTokenCount} tokens)
</Text>
</Box>
)}
{itemForDisplay.type === 'gemini' && (
<GeminiMessage
text={itemForDisplay.text}

View File

@@ -77,39 +77,6 @@ exports[`InputPrompt > mouse interaction > should toggle paste expansion on doub
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 4`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> [Pasted Text: 10 lines]
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 5`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
 > [Pasted Text: 10 lines] 
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 6`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
 > line1 
 line2 
 line3 
 line4 
 line5 
 line6 
 line7 
 line8 
 line9 
 line10 
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 7`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
 > [Pasted Text: 10 lines] 
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄"
`;
exports[`InputPrompt > snapshots > should not show inverted cursor when shell is focused 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Type your message or @path/to/file

View File

@@ -12,7 +12,6 @@ import { theme } from '../../semantic-colors.js';
import { SCREEN_READER_MODEL_PREFIX } from '../../textConstants.js';
import { useUIState } from '../../contexts/UIStateContext.js';
import { useAlternateBuffer } from '../../hooks/useAlternateBuffer.js';
interface GeminiMessageProps {
text: string;
isPending: boolean;

View File

@@ -53,6 +53,7 @@ import { type Part, type PartListUnion, FinishReason } from '@google/genai';
import type {
HistoryItem,
HistoryItemThinking,
HistoryItemAutoCompression,
HistoryItemWithoutId,
HistoryItemToolGroup,
IndividualToolCallDisplay,
@@ -203,6 +204,8 @@ export const useGeminiStream = (
const [pendingHistoryItem, pendingHistoryItemRef, setPendingHistoryItem] =
useStateAndRef<HistoryItemWithoutId | null>(null);
const [isCompressing, setIsCompressing] = useState<boolean>(false);
const [lastGeminiActivityTime, setLastGeminiActivityTime] =
useState<number>(0);
const [pushedToolCallIds, pushedToolCallIdsRef, setPushedToolCallIds] =
@@ -765,7 +768,10 @@ export const useGeminiStream = (
if (pendingHistoryItemRef.current) {
addItem(pendingHistoryItemRef.current, userMessageTimestamp);
}
setPendingHistoryItem({ type: 'gemini', text: '' });
setPendingHistoryItem({
type: 'gemini',
text: '',
});
newGeminiMessageBuffer = eventValue;
}
// Split large messages for better rendering performance. Ideally,
@@ -773,11 +779,23 @@ export const useGeminiStream = (
const splitPoint = findLastSafeSplitPoint(newGeminiMessageBuffer);
if (splitPoint === newGeminiMessageBuffer.length) {
// Update the existing message with accumulated content
setPendingHistoryItem((item) => ({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
type: item?.type as 'gemini' | 'gemini_content',
text: newGeminiMessageBuffer,
}));
setPendingHistoryItem((item) => {
if (!item) return null;
if (item.type === 'gemini') {
return {
...item,
type: 'gemini',
text: newGeminiMessageBuffer,
};
} else if (item.type === 'gemini_content') {
return {
...item,
type: 'gemini_content',
text: newGeminiMessageBuffer,
};
}
return item;
});
} else {
// This indicates that we need to split up this Gemini Message.
// Splitting a message is primarily a performance consideration. There is a
@@ -799,7 +817,10 @@ export const useGeminiStream = (
},
userMessageTimestamp,
);
setPendingHistoryItem({ type: 'gemini_content', text: afterText });
setPendingHistoryItem({
type: 'gemini_content',
text: afterText,
});
newGeminiMessageBuffer = afterText;
}
return newGeminiMessageBuffer;
@@ -956,14 +977,16 @@ export const useGeminiStream = (
addItem(pendingHistoryItemRef.current, userMessageTimestamp);
setPendingHistoryItem(null);
}
return addItem({
type: 'info',
text:
`IMPORTANT: This conversation exceeded the compress threshold. ` +
`A compressed context will be sent for future messages (compressed from: ` +
`${eventValue?.originalTokenCount ?? 'unknown'} to ` +
`${eventValue?.newTokenCount ?? 'unknown'} tokens).`,
});
if (eventValue) {
addItem(
{
type: 'auto_compression',
compression: eventValue,
} as HistoryItemAutoCompression,
userMessageTimestamp,
);
}
},
[addItem, pendingHistoryItemRef, setPendingHistoryItem],
);
@@ -1095,6 +1118,10 @@ export const useGeminiStream = (
let geminiMessageBuffer = '';
const toolCallRequests: ToolCallRequestInfo[] = [];
for await (const event of stream) {
if (event.type !== ServerGeminiEventType.ChatCompressing) {
setIsCompressing(false);
}
if (
event.type !== ServerGeminiEventType.Thought &&
thoughtRef.current !== null
@@ -1140,7 +1167,11 @@ export const useGeminiStream = (
event.value.contextCleared,
);
break;
case ServerGeminiEventType.ChatCompressing:
setIsCompressing(true);
break;
case ServerGeminiEventType.ChatCompressed:
setIsCompressing(false);
handleChatCompressionEvent(event.value, userMessageTimestamp);
break;
case ServerGeminiEventType.ToolCallConfirmation:
@@ -1683,6 +1714,7 @@ export const useGeminiStream = (
return {
streamingState,
isCompressing,
submitQuery,
initError,
pendingHistoryItems,

View File

@@ -6,6 +6,7 @@
import {
type CompressionStatus,
type ChatCompressionInfo,
type GeminiCLIExtension,
type MCPServerConfig,
type ThoughtSummary,
@@ -248,6 +249,11 @@ export type HistoryItemThinking = HistoryItemBase & {
thought: ThoughtSummary;
};
export type HistoryItemAutoCompression = HistoryItemBase & {
type: 'auto_compression';
compression: ChatCompressionInfo;
};
export type HistoryItemChatList = HistoryItemBase & {
type: 'chat_list';
chats: ChatDetail[];
@@ -372,6 +378,7 @@ export type HistoryItemWithoutId =
| HistoryItemMcpStatus
| HistoryItemChatList
| HistoryItemThinking
| HistoryItemAutoCompression
| HistoryItemHooksList;
export type HistoryItem = HistoryItemWithoutId & { id: number };