mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-08 01:53:49 -07:00
fix(context): Fix snapshot recovery across sessions. (#26939)
This commit is contained in:
@@ -103,6 +103,7 @@ export interface CliArgs {
|
||||
useWriteTodos: boolean | undefined;
|
||||
outputFormat: string | undefined;
|
||||
fakeResponses: string | undefined;
|
||||
fakeResponsesNonStrict?: string | undefined;
|
||||
recordResponses: string | undefined;
|
||||
startupMessages?: string[];
|
||||
rawOutput: boolean | undefined;
|
||||
@@ -474,6 +475,12 @@ export async function parseArguments(
|
||||
description: 'Path to a file with fake model responses for testing.',
|
||||
hidden: true,
|
||||
})
|
||||
.option('fake-responses-non-strict', {
|
||||
type: 'string',
|
||||
description:
|
||||
'Path to a file with fake model responses for testing (non-strict mode).',
|
||||
hidden: true,
|
||||
})
|
||||
.option('record-responses', {
|
||||
type: 'string',
|
||||
description: 'Path to a file to record model responses for testing.',
|
||||
@@ -1074,6 +1081,7 @@ export async function loadCliConfig(
|
||||
gemmaModelRouter: settings.experimental?.gemmaModelRouter,
|
||||
adk: settings.experimental?.adk,
|
||||
fakeResponses: argv.fakeResponses,
|
||||
fakeResponsesNonStrict: argv.fakeResponsesNonStrict,
|
||||
recordResponses: argv.recordResponses,
|
||||
retryFetchErrors: settings.general?.retryFetchErrors,
|
||||
billing: settings.billing,
|
||||
|
||||
@@ -14,7 +14,6 @@ import { type HistoryItem } from '../types.js';
|
||||
import { convertSessionToHistoryFormats } from '../hooks/useSessionBrowser.js';
|
||||
import { revertFileChanges } from '../utils/rewindFileOps.js';
|
||||
import { RewindOutcome } from '../components/RewindConfirmation.js';
|
||||
import type { Content } from '@google/genai';
|
||||
import {
|
||||
checkExhaustive,
|
||||
coreEvents,
|
||||
@@ -58,7 +57,7 @@ async function rewindConversation(
|
||||
const { uiHistory } = convertSessionToHistoryFormats(conversation.messages);
|
||||
const clientHistory = convertSessionToClientHistory(conversation.messages);
|
||||
|
||||
client.setHistory(clientHistory as Content[]);
|
||||
client.setHistory(clientHistory);
|
||||
|
||||
// Reset context manager as we are rewinding history
|
||||
await context.services.agentContext?.config
|
||||
|
||||
@@ -194,14 +194,16 @@ describe('convertSessionToHistoryFormats', () => {
|
||||
|
||||
const clientHistory = convertSessionToClientHistory(messages);
|
||||
expect(clientHistory).toHaveLength(2);
|
||||
expect(clientHistory[0]).toEqual({
|
||||
role: 'user',
|
||||
parts: [{ text: 'Hello' }],
|
||||
});
|
||||
expect(clientHistory[1]).toEqual({
|
||||
role: 'model',
|
||||
parts: [{ text: 'Hi there' }],
|
||||
});
|
||||
expect(clientHistory.map((h) => h.content)).toEqual([
|
||||
{
|
||||
role: 'user',
|
||||
parts: [{ text: 'Hello' }],
|
||||
},
|
||||
{
|
||||
role: 'model',
|
||||
parts: [{ text: 'Hi there' }],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should convert thinking tokens (thoughts) to thinking history items', () => {
|
||||
@@ -254,10 +256,12 @@ describe('convertSessionToHistoryFormats', () => {
|
||||
|
||||
const clientHistory = convertSessionToClientHistory(messages);
|
||||
expect(clientHistory).toHaveLength(1);
|
||||
expect(clientHistory[0]).toEqual({
|
||||
role: 'user',
|
||||
parts: [{ text: 'Expanded content' }],
|
||||
});
|
||||
expect(clientHistory.map((h) => h.content)).toEqual([
|
||||
{
|
||||
role: 'user',
|
||||
parts: [{ text: 'Expanded content' }],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should filter out slash commands from client history but keep in UI', () => {
|
||||
@@ -316,33 +320,35 @@ describe('convertSessionToHistoryFormats', () => {
|
||||
|
||||
const clientHistory = convertSessionToClientHistory(messages);
|
||||
expect(clientHistory).toHaveLength(3); // User, Model (call), User (response)
|
||||
expect(clientHistory[0]).toEqual({
|
||||
role: 'user',
|
||||
parts: [{ text: 'What time is it?' }],
|
||||
});
|
||||
expect(clientHistory[1]).toEqual({
|
||||
role: 'model',
|
||||
parts: [
|
||||
{
|
||||
functionCall: {
|
||||
name: 'get_time',
|
||||
args: {},
|
||||
id: 'call_1',
|
||||
expect(clientHistory.map((h) => h.content)).toEqual([
|
||||
{
|
||||
role: 'user',
|
||||
parts: [{ text: 'What time is it?' }],
|
||||
},
|
||||
{
|
||||
role: 'model',
|
||||
parts: [
|
||||
{
|
||||
functionCall: {
|
||||
name: 'get_time',
|
||||
args: {},
|
||||
id: 'call_1',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(clientHistory[2]).toEqual({
|
||||
role: 'user',
|
||||
parts: [
|
||||
{
|
||||
functionResponse: {
|
||||
id: 'call_1',
|
||||
name: 'get_time',
|
||||
response: { output: '12:00' },
|
||||
],
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
parts: [
|
||||
{
|
||||
functionResponse: {
|
||||
name: 'get_time',
|
||||
response: { output: '12:00' },
|
||||
id: 'call_1',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,22 +12,28 @@ import {
|
||||
convertSessionToClientHistory,
|
||||
uiTelemetryService,
|
||||
loadConversationRecord,
|
||||
type Config,
|
||||
type ResumedSessionData,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type {
|
||||
HistoryTurn,
|
||||
Config,
|
||||
ResumedSessionData,
|
||||
} from '@google/gemini-cli-core';
|
||||
import {
|
||||
convertSessionToHistoryFormats,
|
||||
type SessionInfo,
|
||||
} from '../../utils/sessionUtils.js';
|
||||
import type { Part } from '@google/genai';
|
||||
|
||||
export { convertSessionToHistoryFormats };
|
||||
|
||||
import type { Part } from '@google/genai';
|
||||
|
||||
export const useSessionBrowser = (
|
||||
config: Config,
|
||||
onLoadHistory: (
|
||||
uiHistory: HistoryItemWithoutId[],
|
||||
clientHistory: Array<{ role: 'user' | 'model'; parts: Part[] }>,
|
||||
clientHistory: Array<
|
||||
{ role: 'user' | 'model'; parts: Part[] } | HistoryTurn
|
||||
>,
|
||||
resumedSessionData: ResumedSessionData,
|
||||
) => Promise<void>,
|
||||
) => {
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
ResumedSessionData,
|
||||
ConversationRecord,
|
||||
MessageRecord,
|
||||
HistoryTurn,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
|
||||
import type { HistoryItemWithoutId } from '../types.js';
|
||||
@@ -527,10 +528,12 @@ describe('useSessionResume', () => {
|
||||
|
||||
// Should only have the non-slash-command message
|
||||
expect(clientHistory).toHaveLength(1);
|
||||
expect(clientHistory[0]).toEqual({
|
||||
role: 'user',
|
||||
parts: [{ text: 'Regular message' }],
|
||||
});
|
||||
expect(clientHistory.map((h: HistoryTurn) => h.content)).toEqual([
|
||||
{
|
||||
role: 'user',
|
||||
parts: [{ text: 'Regular message' }],
|
||||
},
|
||||
]);
|
||||
|
||||
// But UI history should have both
|
||||
expect(mockHistoryManager.addItem).toHaveBeenCalledTimes(2);
|
||||
|
||||
@@ -7,14 +7,17 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
coreEvents,
|
||||
type Config,
|
||||
type ResumedSessionData,
|
||||
convertSessionToClientHistory,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Part } from '@google/genai';
|
||||
import type {
|
||||
HistoryTurn,
|
||||
Config,
|
||||
ResumedSessionData,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { HistoryItemWithoutId } from '../types.js';
|
||||
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
|
||||
import { convertSessionToHistoryFormats } from './useSessionBrowser.js';
|
||||
import type { Part } from '@google/genai';
|
||||
|
||||
interface UseSessionResumeParams {
|
||||
config: Config;
|
||||
@@ -54,7 +57,9 @@ export function useSessionResume({
|
||||
const loadHistoryForResume = useCallback(
|
||||
async (
|
||||
uiHistory: HistoryItemWithoutId[],
|
||||
clientHistory: Array<{ role: 'user' | 'model'; parts: Part[] }>,
|
||||
clientHistory: Array<
|
||||
{ role: 'user' | 'model'; parts: Part[] } | HistoryTurn
|
||||
>,
|
||||
resumedData: ResumedSessionData,
|
||||
) => {
|
||||
// Wait for the client.
|
||||
|
||||
Reference in New Issue
Block a user