feat(core): migrate chat recording to JSONL streaming (#23749)

This commit is contained in:
Spencer
2026-04-09 17:13:55 -04:00
committed by GitHub
parent 45100f7c0e
commit f744913584
15 changed files with 906 additions and 665 deletions

View File

@@ -11,7 +11,6 @@ import {
useSessionBrowser,
convertSessionToHistoryFormats,
} from './useSessionBrowser.js';
import * as fs from 'node:fs/promises';
import path from 'node:path';
import { getSessionFiles, type SessionInfo } from '../../utils/sessionUtils.js';
import {
@@ -19,6 +18,7 @@ import {
type ConversationRecord,
type MessageRecord,
CoreToolCallStatus,
loadConversationRecord,
} from '@google/gemini-cli-core';
import {
coreEvents,
@@ -46,6 +46,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
clear: vi.fn(),
hydrate: vi.fn(),
},
loadConversationRecord: vi.fn(),
};
});
@@ -55,7 +56,6 @@ const MOCKED_SESSION_ID = 'test-session-123';
const MOCKED_CURRENT_SESSION_ID = 'current-session-id';
describe('useSessionBrowser', () => {
const mockedFs = vi.mocked(fs);
const mockedPath = vi.mocked(path);
const mockedGetSessionFiles = vi.mocked(getSessionFiles);
@@ -98,7 +98,7 @@ describe('useSessionBrowser', () => {
fileName: MOCKED_FILENAME,
} as SessionInfo;
mockedGetSessionFiles.mockResolvedValue([mockSession]);
mockedFs.readFile.mockResolvedValue(JSON.stringify(mockConversation));
vi.mocked(loadConversationRecord).mockResolvedValue(mockConversation);
const { result } = await renderHook(() =>
useSessionBrowser(mockConfig, mockOnLoadHistory),
@@ -107,9 +107,8 @@ describe('useSessionBrowser', () => {
await act(async () => {
await result.current.handleResumeSession(mockSession);
});
expect(mockedFs.readFile).toHaveBeenCalledWith(
expect(loadConversationRecord).toHaveBeenCalledWith(
`${MOCKED_CHATS_DIR}/${MOCKED_FILENAME}`,
'utf8',
);
expect(mockConfig.setSessionId).toHaveBeenCalledWith(
'existing-session-456',
@@ -125,7 +124,9 @@ describe('useSessionBrowser', () => {
id: MOCKED_SESSION_ID,
fileName: MOCKED_FILENAME,
} as SessionInfo;
mockedFs.readFile.mockRejectedValue(new Error('File not found'));
vi.mocked(loadConversationRecord).mockRejectedValue(
new Error('File not found'),
);
const { result } = await renderHook(() =>
useSessionBrowser(mockConfig, mockOnLoadHistory),
@@ -149,7 +150,7 @@ describe('useSessionBrowser', () => {
id: MOCKED_SESSION_ID,
fileName: MOCKED_FILENAME,
} as SessionInfo;
mockedFs.readFile.mockResolvedValue('invalid json');
vi.mocked(loadConversationRecord).mockResolvedValue(null);
const { result } = await renderHook(() =>
useSessionBrowser(mockConfig, mockOnLoadHistory),

View File

@@ -6,14 +6,13 @@
import { useState, useCallback } from 'react';
import type { HistoryItemWithoutId } from '../types.js';
import * as fs from 'node:fs/promises';
import path from 'node:path';
import {
coreEvents,
convertSessionToClientHistory,
uiTelemetryService,
loadConversationRecord,
type Config,
type ConversationRecord,
type ResumedSessionData,
} from '@google/gemini-cli-core';
import {
@@ -61,10 +60,12 @@ export const useSessionBrowser = (
const originalFilePath = path.join(chatsDir, fileName);
// Load up the conversation.
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const conversation: ConversationRecord = JSON.parse(
await fs.readFile(originalFilePath, 'utf8'),
);
const conversation = await loadConversationRecord(originalFilePath);
if (!conversation) {
throw new Error(
`Failed to parse conversation from ${originalFilePath}`,
);
}
// Use the old session's ID to continue it.
const existingSessionId = conversation.sessionId;