mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-18 10:01:29 -07:00
perf: skip pre-compression history on session resume
On resume (-r), the CLI was loading and replaying the entire session recording, including messages that had already been compressed away. For long-running Forever Mode sessions this made resume extremely slow. Add lastCompressionIndex to ConversationRecord, stamped when compression succeeds. On resume, only messages from that index onward are loaded into the client history and UI. Fully backward compatible — old sessions without the field load all messages as before.
This commit is contained in:
@@ -358,7 +358,10 @@ export class GeminiAgent {
|
||||
config.setFileSystemService(acpFileSystemService);
|
||||
}
|
||||
|
||||
const clientHistory = convertSessionToClientHistory(sessionData.messages);
|
||||
const clientHistory = convertSessionToClientHistory(
|
||||
sessionData.messages,
|
||||
sessionData.lastCompressionIndex,
|
||||
);
|
||||
|
||||
const geminiClient = config.getGeminiClient();
|
||||
await geminiClient.initialize();
|
||||
|
||||
@@ -222,6 +222,7 @@ export async function runNonInteractive({
|
||||
await geminiClient.resumeChat(
|
||||
convertSessionToClientHistory(
|
||||
resumedSessionData.conversation.messages,
|
||||
resumedSessionData.conversation.lastCompressionIndex,
|
||||
),
|
||||
resumedSessionData,
|
||||
);
|
||||
|
||||
@@ -76,12 +76,17 @@ export const useSessionBrowser = (
|
||||
|
||||
// We've loaded it; tell the UI about it.
|
||||
setIsSessionBrowserOpen(false);
|
||||
const compressionIndex = conversation.lastCompressionIndex;
|
||||
const historyData = convertSessionToHistoryFormats(
|
||||
conversation.messages,
|
||||
compressionIndex,
|
||||
);
|
||||
await onLoadHistory(
|
||||
historyData.uiHistory,
|
||||
convertSessionToClientHistory(conversation.messages),
|
||||
convertSessionToClientHistory(
|
||||
conversation.messages,
|
||||
compressionIndex,
|
||||
),
|
||||
resumedSessionData,
|
||||
);
|
||||
} catch (error) {
|
||||
|
||||
@@ -109,12 +109,18 @@ export function useSessionResume({
|
||||
!hasLoadedResumedSession.current
|
||||
) {
|
||||
hasLoadedResumedSession.current = true;
|
||||
const compressionIndex =
|
||||
resumedSessionData.conversation.lastCompressionIndex;
|
||||
const historyData = convertSessionToHistoryFormats(
|
||||
resumedSessionData.conversation.messages,
|
||||
compressionIndex,
|
||||
);
|
||||
void loadHistoryForResume(
|
||||
historyData.uiHistory,
|
||||
convertSessionToClientHistory(resumedSessionData.conversation.messages),
|
||||
convertSessionToClientHistory(
|
||||
resumedSessionData.conversation.messages,
|
||||
compressionIndex,
|
||||
),
|
||||
resumedSessionData,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,10 +10,16 @@ import {
|
||||
extractFirstUserMessage,
|
||||
formatRelativeTime,
|
||||
hasUserOrAssistantMessage,
|
||||
convertSessionToHistoryFormats,
|
||||
SessionError,
|
||||
} from './sessionUtils.js';
|
||||
import type { Config, MessageRecord } from '@google/gemini-cli-core';
|
||||
import type {
|
||||
Config,
|
||||
MessageRecord,
|
||||
ConversationRecord,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { SESSION_FILE_PREFIX } from '@google/gemini-cli-core';
|
||||
import { MessageType } from '../ui/types.js';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
@@ -765,3 +771,80 @@ describe('formatRelativeTime', () => {
|
||||
expect(formatRelativeTime(thirtySecondsAgo.toISOString())).toBe('Just now');
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertSessionToHistoryFormats', () => {
|
||||
const messages: ConversationRecord['messages'] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'user',
|
||||
timestamp: '2024-01-01T10:00:00Z',
|
||||
content: 'First question',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'gemini',
|
||||
timestamp: '2024-01-01T10:01:00Z',
|
||||
content: 'First answer',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'user',
|
||||
timestamp: '2024-01-01T10:02:00Z',
|
||||
content: 'Second question',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'gemini',
|
||||
timestamp: '2024-01-01T10:03:00Z',
|
||||
content: 'Second answer',
|
||||
},
|
||||
];
|
||||
|
||||
it('should convert all messages when startIndex is undefined', () => {
|
||||
const { uiHistory } = convertSessionToHistoryFormats(messages);
|
||||
|
||||
expect(uiHistory).toHaveLength(4);
|
||||
expect(uiHistory[0]).toEqual({
|
||||
type: MessageType.USER,
|
||||
text: 'First question',
|
||||
});
|
||||
expect(uiHistory[1]).toEqual({
|
||||
type: MessageType.GEMINI,
|
||||
text: 'First answer',
|
||||
});
|
||||
expect(uiHistory[2]).toEqual({
|
||||
type: MessageType.USER,
|
||||
text: 'Second question',
|
||||
});
|
||||
expect(uiHistory[3]).toEqual({
|
||||
type: MessageType.GEMINI,
|
||||
text: 'Second answer',
|
||||
});
|
||||
});
|
||||
|
||||
it('should show only post-compression messages with a leading info message when startIndex is provided', () => {
|
||||
const { uiHistory } = convertSessionToHistoryFormats(messages, 2);
|
||||
|
||||
// Should have: 1 info message + 2 remaining messages
|
||||
expect(uiHistory).toHaveLength(3);
|
||||
|
||||
// First item is the compression info message
|
||||
expect(uiHistory[0].type).toBe(MessageType.INFO);
|
||||
expect((uiHistory[0] as { type: string; text: string }).text).toContain(
|
||||
'2 messages',
|
||||
);
|
||||
expect((uiHistory[0] as { type: string; text: string }).text).toContain(
|
||||
'compressed',
|
||||
);
|
||||
|
||||
// Remaining items are the post-compression messages
|
||||
expect(uiHistory[1]).toEqual({
|
||||
type: MessageType.USER,
|
||||
text: 'Second question',
|
||||
});
|
||||
expect(uiHistory[2]).toEqual({
|
||||
type: MessageType.GEMINI,
|
||||
text: 'Second answer',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -526,15 +526,31 @@ export class SessionSelector {
|
||||
|
||||
/**
|
||||
* Converts session/conversation data into UI history format.
|
||||
*
|
||||
* @param messages - The full array of recorded messages.
|
||||
* @param startIndex - If provided, only messages from this index onward are
|
||||
* converted. A leading info item is added to indicate earlier history was
|
||||
* compressed away.
|
||||
*/
|
||||
export function convertSessionToHistoryFormats(
|
||||
messages: ConversationRecord['messages'],
|
||||
startIndex?: number,
|
||||
): {
|
||||
uiHistory: HistoryItemWithoutId[];
|
||||
} {
|
||||
const uiHistory: HistoryItemWithoutId[] = [];
|
||||
const hasCompressedHistory =
|
||||
startIndex != null && startIndex > 0 && startIndex < messages.length;
|
||||
const slice = hasCompressedHistory ? messages.slice(startIndex) : messages;
|
||||
|
||||
for (const msg of messages) {
|
||||
if (hasCompressedHistory) {
|
||||
uiHistory.push({
|
||||
type: MessageType.INFO,
|
||||
text: `ℹ️ Earlier history (${startIndex} messages) was compressed. Showing conversation from last compression point.`,
|
||||
});
|
||||
}
|
||||
|
||||
for (const msg of slice) {
|
||||
// Add thoughts if present
|
||||
if (msg.type === 'gemini' && msg.thoughts && msg.thoughts.length > 0) {
|
||||
for (const thought of msg.thoughts) {
|
||||
|
||||
Reference in New Issue
Block a user