mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
fix(core): ensure chat compression summary persists across session resumes
- Modified ChatRecordingService.initialize to accept an optional initialHistory parameter. - When initialHistory is provided during session resumption (e.g., after chat compression), it now overwrites the messages in the session file on disk. - Updated GeminiChat constructor to pass the history to ChatRecordingService.initialize. - Implemented apiContentToMessageRecords helper to convert API Content objects to storage-compatible MessageRecord objects. - This ensures that the compressed chat history (the summary) is immediately synced to disk, preventing it from being lost when the session is closed and resumed. - Added a unit test in chatRecordingService.test.ts to verify the new overwrite behavior. Fixes #21335
This commit is contained in:
@@ -255,7 +255,7 @@ export class GeminiChat {
|
|||||||
) {
|
) {
|
||||||
validateHistory(history);
|
validateHistory(history);
|
||||||
this.chatRecordingService = new ChatRecordingService(config);
|
this.chatRecordingService = new ChatRecordingService(config);
|
||||||
this.chatRecordingService.initialize(resumedSessionData, kind);
|
this.chatRecordingService.initialize(resumedSessionData, kind, history);
|
||||||
this.lastPromptTokenCount = estimateTokenCountSync(
|
this.lastPromptTokenCount = estimateTokenCountSync(
|
||||||
this.history.flatMap((c) => c.parts || []),
|
this.history.flatMap((c) => c.parts || []),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -122,6 +122,50 @@ describe('ChatRecordingService', () => {
|
|||||||
const conversation = JSON.parse(fs.readFileSync(sessionFile, 'utf8'));
|
const conversation = JSON.parse(fs.readFileSync(sessionFile, 'utf8'));
|
||||||
expect(conversation.sessionId).toBe('old-session-id');
|
expect(conversation.sessionId).toBe('old-session-id');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should overwrite existing messages if initialHistory is provided', () => {
|
||||||
|
const chatsDir = path.join(testTempDir, 'chats');
|
||||||
|
fs.mkdirSync(chatsDir, { recursive: true });
|
||||||
|
const sessionFile = path.join(chatsDir, 'session-overwrite.json');
|
||||||
|
const initialData = {
|
||||||
|
sessionId: 'test-session-id',
|
||||||
|
projectHash: 'test-project-hash',
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
id: 'msg-1',
|
||||||
|
type: 'user',
|
||||||
|
content: 'Old Message',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
fs.writeFileSync(sessionFile, JSON.stringify(initialData));
|
||||||
|
|
||||||
|
const newHistory: Content[] = [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
parts: [{ text: 'Compressed Summary' }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
chatRecordingService.initialize(
|
||||||
|
{
|
||||||
|
filePath: sessionFile,
|
||||||
|
conversation: initialData as ConversationRecord,
|
||||||
|
},
|
||||||
|
'main',
|
||||||
|
newHistory,
|
||||||
|
);
|
||||||
|
|
||||||
|
const conversation = JSON.parse(
|
||||||
|
fs.readFileSync(sessionFile, 'utf8'),
|
||||||
|
) as ConversationRecord;
|
||||||
|
expect(conversation.messages).toHaveLength(1);
|
||||||
|
expect(conversation.messages[0].content).toEqual([
|
||||||
|
{ text: 'Compressed Summary' },
|
||||||
|
]);
|
||||||
|
expect(conversation.messages[0].type).toBe('user');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('recordMessage', () => {
|
describe('recordMessage', () => {
|
||||||
|
|||||||
@@ -147,10 +147,14 @@ export class ChatRecordingService {
|
|||||||
*
|
*
|
||||||
* @param resumedSessionData Data from a previous session to resume from.
|
* @param resumedSessionData Data from a previous session to resume from.
|
||||||
* @param kind The kind of conversation (main or subagent).
|
* @param kind The kind of conversation (main or subagent).
|
||||||
|
* @param initialHistory The starting history for this session. If provided when resuming an
|
||||||
|
* existing session (e.g., after chat compression), this will overwrite the messages currently
|
||||||
|
* stored on disk to ensure the file reflects the new session state.
|
||||||
*/
|
*/
|
||||||
initialize(
|
initialize(
|
||||||
resumedSessionData?: ResumedSessionData,
|
resumedSessionData?: ResumedSessionData,
|
||||||
kind?: 'main' | 'subagent',
|
kind?: 'main' | 'subagent',
|
||||||
|
initialHistory?: Content[],
|
||||||
): void {
|
): void {
|
||||||
try {
|
try {
|
||||||
this.kind = kind;
|
this.kind = kind;
|
||||||
@@ -163,6 +167,10 @@ export class ChatRecordingService {
|
|||||||
// Update the session ID in the existing file
|
// Update the session ID in the existing file
|
||||||
this.updateConversation((conversation) => {
|
this.updateConversation((conversation) => {
|
||||||
conversation.sessionId = this.sessionId;
|
conversation.sessionId = this.sessionId;
|
||||||
|
if (initialHistory) {
|
||||||
|
conversation.messages =
|
||||||
|
this.apiContentToMessageRecords(initialHistory);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear any cached data to force fresh reads
|
// Clear any cached data to force fresh reads
|
||||||
@@ -190,7 +198,7 @@ export class ChatRecordingService {
|
|||||||
projectHash: this.projectHash,
|
projectHash: this.projectHash,
|
||||||
startTime: new Date().toISOString(),
|
startTime: new Date().toISOString(),
|
||||||
lastUpdated: new Date().toISOString(),
|
lastUpdated: new Date().toISOString(),
|
||||||
messages: [],
|
messages: this.apiContentToMessageRecords(initialHistory || []),
|
||||||
kind: this.kind,
|
kind: this.kind,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -215,6 +223,24 @@ export class ChatRecordingService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts API Content array to storage-compatible MessageRecord array.
|
||||||
|
*/
|
||||||
|
private apiContentToMessageRecords(history: Content[]): MessageRecord[] {
|
||||||
|
return history.map((content) => {
|
||||||
|
const type = content.role === 'model' ? 'gemini' : 'user';
|
||||||
|
const record = {
|
||||||
|
id: randomUUID(),
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
type,
|
||||||
|
content: content.parts,
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||||
|
return record as MessageRecord;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private getLastMessage(
|
private getLastMessage(
|
||||||
conversation: ConversationRecord,
|
conversation: ConversationRecord,
|
||||||
): MessageRecord | undefined {
|
): MessageRecord | undefined {
|
||||||
|
|||||||
Reference in New Issue
Block a user