parent
89f682f081
commit
6fc7028031
|
@ -398,46 +398,22 @@ export const useGeminiStream = (
|
||||||
break;
|
break;
|
||||||
case ServerGeminiEventType.ToolCallRequest:
|
case ServerGeminiEventType.ToolCallRequest:
|
||||||
toolCallRequests.push(event.value);
|
toolCallRequests.push(event.value);
|
||||||
await logger?.logMessage(
|
|
||||||
MessageSenderType.TOOL_REQUEST,
|
|
||||||
JSON.stringify(event.value.args),
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case ServerGeminiEventType.UserCancelled:
|
case ServerGeminiEventType.UserCancelled:
|
||||||
handleUserCancelledEvent(userMessageTimestamp);
|
handleUserCancelledEvent(userMessageTimestamp);
|
||||||
break;
|
break;
|
||||||
case ServerGeminiEventType.Error:
|
case ServerGeminiEventType.Error:
|
||||||
handleErrorEvent(event.value, userMessageTimestamp);
|
handleErrorEvent(event.value, userMessageTimestamp);
|
||||||
await logger?.logMessage(
|
|
||||||
MessageSenderType.SERVER_ERROR,
|
|
||||||
JSON.stringify(event.value),
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case ServerGeminiEventType.ChatCompressed:
|
case ServerGeminiEventType.ChatCompressed:
|
||||||
handleChatCompressionEvent();
|
handleChatCompressionEvent();
|
||||||
await logger?.logMessage(
|
|
||||||
MessageSenderType.SYSTEM,
|
|
||||||
'Compressing Chat',
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case ServerGeminiEventType.UsageMetadata:
|
case ServerGeminiEventType.UsageMetadata:
|
||||||
addUsage(event.value);
|
addUsage(event.value);
|
||||||
await logger?.logMessage(
|
|
||||||
MessageSenderType.SYSTEM,
|
|
||||||
'Usage Metadata: ' + JSON.stringify(event.value),
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case ServerGeminiEventType.ToolCallConfirmation:
|
case ServerGeminiEventType.ToolCallConfirmation:
|
||||||
await logger?.logMessage(
|
|
||||||
MessageSenderType.SYSTEM,
|
|
||||||
JSON.stringify(event.value),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case ServerGeminiEventType.ToolCallResponse:
|
case ServerGeminiEventType.ToolCallResponse:
|
||||||
await logger?.logMessage(
|
// do nothing
|
||||||
MessageSenderType.SYSTEM,
|
|
||||||
JSON.stringify(event.value),
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
// enforces exhaustive switch-case
|
// enforces exhaustive switch-case
|
||||||
|
@ -446,7 +422,6 @@ export const useGeminiStream = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await logger?.logMessage(MessageSenderType.SYSTEM, geminiMessageBuffer);
|
|
||||||
if (toolCallRequests.length > 0) {
|
if (toolCallRequests.length > 0) {
|
||||||
scheduleToolCalls(toolCallRequests, signal);
|
scheduleToolCalls(toolCallRequests, signal);
|
||||||
}
|
}
|
||||||
|
@ -459,7 +434,6 @@ export const useGeminiStream = (
|
||||||
scheduleToolCalls,
|
scheduleToolCalls,
|
||||||
handleChatCompressionEvent,
|
handleChatCompressionEvent,
|
||||||
addUsage,
|
addUsage,
|
||||||
logger,
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -19,27 +19,38 @@ import path from 'node:path';
|
||||||
import { Content } from '@google/genai';
|
import { Content } from '@google/genai';
|
||||||
|
|
||||||
const GEMINI_DIR = '.gemini';
|
const GEMINI_DIR = '.gemini';
|
||||||
const LOG_FILE_NAME_PREFIX = 'logs';
|
const LOG_FILE_NAME = 'logs.json';
|
||||||
const CHECKPOINT_FILE_NAME = 'checkpoint.json';
|
const CHECKPOINT_FILE_NAME = 'checkpoint.json';
|
||||||
const TEST_LOG_FILE_PATH = path.join(
|
const TEST_LOG_FILE_PATH = path.join(process.cwd(), GEMINI_DIR, LOG_FILE_NAME);
|
||||||
process.cwd(),
|
|
||||||
GEMINI_DIR,
|
|
||||||
LOG_FILE_NAME_PREFIX,
|
|
||||||
);
|
|
||||||
const TEST_CHECKPOINT_FILE_PATH = path.join(
|
const TEST_CHECKPOINT_FILE_PATH = path.join(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
GEMINI_DIR,
|
GEMINI_DIR,
|
||||||
CHECKPOINT_FILE_NAME,
|
CHECKPOINT_FILE_NAME,
|
||||||
);
|
);
|
||||||
|
|
||||||
async function cleanupLogFiles() {
|
async function cleanupLogFile() {
|
||||||
|
try {
|
||||||
|
await fs.unlink(TEST_LOG_FILE_PATH);
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
||||||
|
// Other errors during unlink are ignored for cleanup purposes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await fs.unlink(TEST_CHECKPOINT_FILE_PATH);
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
||||||
|
// Other errors during unlink are ignored for cleanup purposes
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const geminiDirPath = path.join(process.cwd(), GEMINI_DIR);
|
const geminiDirPath = path.join(process.cwd(), GEMINI_DIR);
|
||||||
const dirContents = await fs.readdir(geminiDirPath);
|
const dirContents = await fs.readdir(geminiDirPath);
|
||||||
for (const file of dirContents) {
|
for (const file of dirContents) {
|
||||||
if (
|
if (
|
||||||
file.startsWith(LOG_FILE_NAME_PREFIX) ||
|
(file.startsWith(LOG_FILE_NAME + '.') ||
|
||||||
file.startsWith(CHECKPOINT_FILE_NAME)
|
file.startsWith(CHECKPOINT_FILE_NAME + '.')) &&
|
||||||
|
file.endsWith('.bak')
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
await fs.unlink(path.join(geminiDirPath, file));
|
await fs.unlink(path.join(geminiDirPath, file));
|
||||||
|
@ -55,12 +66,9 @@ async function cleanupLogFiles() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readLogFile(sessionId: string): Promise<LogEntry[]> {
|
async function readLogFile(): Promise<LogEntry[]> {
|
||||||
try {
|
try {
|
||||||
const content = await fs.readFile(
|
const content = await fs.readFile(TEST_LOG_FILE_PATH, 'utf-8');
|
||||||
`${TEST_LOG_FILE_PATH}-${sessionId}.json`,
|
|
||||||
'utf-8',
|
|
||||||
);
|
|
||||||
return JSON.parse(content) as LogEntry[];
|
return JSON.parse(content) as LogEntry[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
||||||
|
@ -82,25 +90,25 @@ describe('Logger', () => {
|
||||||
vi.resetAllMocks();
|
vi.resetAllMocks();
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
vi.setSystemTime(new Date('2025-01-01T12:00:00.000Z'));
|
vi.setSystemTime(new Date('2025-01-01T12:00:00.000Z'));
|
||||||
await cleanupLogFiles();
|
await cleanupLogFile();
|
||||||
logger = new Logger(testSessionId);
|
logger = new Logger(testSessionId);
|
||||||
await logger.initialize();
|
await logger.initialize();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
logger.close();
|
logger.close();
|
||||||
await cleanupLogFiles();
|
await cleanupLogFile();
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await cleanupLogFiles();
|
await cleanupLogFile();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('initialize', () => {
|
describe('initialize', () => {
|
||||||
it('should create .gemini directory and an empty log file if none exist', async () => {
|
it('should create .gemini directory and an empty log file if none exist', async () => {
|
||||||
await cleanupLogFiles();
|
await cleanupLogFile();
|
||||||
const geminiDirPath = path.join(process.cwd(), GEMINI_DIR);
|
const geminiDirPath = path.join(process.cwd(), GEMINI_DIR);
|
||||||
try {
|
try {
|
||||||
await fs.rm(geminiDirPath, { recursive: true, force: true });
|
await fs.rm(geminiDirPath, { recursive: true, force: true });
|
||||||
|
@ -110,17 +118,18 @@ describe('Logger', () => {
|
||||||
|
|
||||||
const newLogger = new Logger(testSessionId);
|
const newLogger = new Logger(testSessionId);
|
||||||
await newLogger.initialize();
|
await newLogger.initialize();
|
||||||
|
|
||||||
const dirExists = await fs
|
const dirExists = await fs
|
||||||
.access(geminiDirPath)
|
.access(geminiDirPath)
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
.catch(() => false);
|
.catch(() => false);
|
||||||
expect(dirExists).toBe(true);
|
expect(dirExists).toBe(true);
|
||||||
const fileExists = await fs
|
const fileExists = await fs
|
||||||
.access(path.join('', newLogger.getLogFilePath() ?? ''))
|
.access(TEST_LOG_FILE_PATH)
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
.catch(() => false);
|
.catch(() => false);
|
||||||
expect(fileExists).toBe(true);
|
expect(fileExists).toBe(true);
|
||||||
const logContent = await readLogFile(testSessionId);
|
const logContent = await readLogFile();
|
||||||
expect(logContent).toEqual([]);
|
expect(logContent).toEqual([]);
|
||||||
newLogger.close();
|
newLogger.close();
|
||||||
});
|
});
|
||||||
|
@ -152,17 +161,10 @@ describe('Logger', () => {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
await fs.mkdir(path.join(process.cwd(), GEMINI_DIR), { recursive: true });
|
await fs.mkdir(path.join(process.cwd(), GEMINI_DIR), { recursive: true });
|
||||||
await fs.writeFile(
|
await fs.writeFile(TEST_LOG_FILE_PATH, JSON.stringify(existingLogs));
|
||||||
`${TEST_LOG_FILE_PATH}-${currentSessionId}.json`,
|
|
||||||
JSON.stringify(existingLogs),
|
|
||||||
);
|
|
||||||
const newLogger = new Logger(currentSessionId);
|
const newLogger = new Logger(currentSessionId);
|
||||||
await newLogger.initialize();
|
await newLogger.initialize();
|
||||||
|
expect(newLogger['messageId']).toBe(2);
|
||||||
const messageCount = existingLogs.filter(
|
|
||||||
(log) => log.sessionId === currentSessionId,
|
|
||||||
).length;
|
|
||||||
expect(newLogger['messageId']).toBe(messageCount);
|
|
||||||
expect(newLogger['logs']).toEqual(existingLogs);
|
expect(newLogger['logs']).toEqual(existingLogs);
|
||||||
newLogger.close();
|
newLogger.close();
|
||||||
});
|
});
|
||||||
|
@ -178,10 +180,7 @@ describe('Logger', () => {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
await fs.mkdir(path.join(process.cwd(), GEMINI_DIR), { recursive: true });
|
await fs.mkdir(path.join(process.cwd(), GEMINI_DIR), { recursive: true });
|
||||||
await fs.writeFile(
|
await fs.writeFile(TEST_LOG_FILE_PATH, JSON.stringify(existingLogs));
|
||||||
`${TEST_LOG_FILE_PATH}-some-other-session.json`,
|
|
||||||
JSON.stringify(existingLogs),
|
|
||||||
);
|
|
||||||
const newLogger = new Logger('a-new-session');
|
const newLogger = new Logger('a-new-session');
|
||||||
await newLogger.initialize();
|
await newLogger.initialize();
|
||||||
expect(newLogger['messageId']).toBe(0);
|
expect(newLogger['messageId']).toBe(0);
|
||||||
|
@ -196,82 +195,70 @@ describe('Logger', () => {
|
||||||
await logger.initialize(); // Second call should not change state
|
await logger.initialize(); // Second call should not change state
|
||||||
expect(logger['messageId']).toBe(initialMessageId);
|
expect(logger['messageId']).toBe(initialMessageId);
|
||||||
expect(logger['logs'].length).toBe(initialLogCount);
|
expect(logger['logs'].length).toBe(initialLogCount);
|
||||||
const logsFromFile = await readLogFile(testSessionId);
|
const logsFromFile = await readLogFile();
|
||||||
expect(logsFromFile.length).toBe(1);
|
expect(logsFromFile.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle invalid JSON in log file by backing it up and starting fresh', async () => {
|
it('should handle invalid JSON in log file by backing it up and starting fresh', async () => {
|
||||||
const logFilePath = `${TEST_LOG_FILE_PATH}-${testSessionId}.json`;
|
await fs.mkdir(path.join(process.cwd(), GEMINI_DIR), { recursive: true });
|
||||||
await fs.mkdir(path.dirname(logFilePath), { recursive: true });
|
await fs.writeFile(TEST_LOG_FILE_PATH, 'invalid json');
|
||||||
await fs.writeFile(logFilePath, 'invalid json');
|
|
||||||
|
|
||||||
const newLogger = new Logger(testSessionId);
|
|
||||||
const consoleDebugSpy = vi
|
const consoleDebugSpy = vi
|
||||||
.spyOn(console, 'debug')
|
.spyOn(console, 'debug')
|
||||||
.mockImplementation(() => {});
|
.mockImplementation(() => {});
|
||||||
|
const newLogger = new Logger(testSessionId);
|
||||||
await newLogger.initialize();
|
await newLogger.initialize();
|
||||||
|
|
||||||
expect(consoleDebugSpy).toHaveBeenCalledWith(
|
expect(consoleDebugSpy).toHaveBeenCalledWith(
|
||||||
expect.stringContaining('Invalid JSON in log file'),
|
expect.stringContaining('Invalid JSON in log file'),
|
||||||
expect.any(SyntaxError),
|
expect.any(SyntaxError),
|
||||||
);
|
);
|
||||||
|
const logContent = await readLogFile();
|
||||||
expect(newLogger['logs']).toEqual([]);
|
expect(logContent).toEqual([]);
|
||||||
|
|
||||||
const dirContents = await fs.readdir(
|
const dirContents = await fs.readdir(
|
||||||
path.join(process.cwd(), GEMINI_DIR),
|
path.join(process.cwd(), GEMINI_DIR),
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
dirContents.some(
|
dirContents.some(
|
||||||
(f) =>
|
(f) =>
|
||||||
f.startsWith(`${path.basename(logFilePath)}.invalid_json`) &&
|
f.startsWith(LOG_FILE_NAME + '.invalid_json') && f.endsWith('.bak'),
|
||||||
f.endsWith('.bak'),
|
|
||||||
),
|
),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
|
|
||||||
newLogger.close();
|
newLogger.close();
|
||||||
consoleDebugSpy.mockRestore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle non-array JSON in log file by backing it up and starting fresh', async () => {
|
it('should handle non-array JSON in log file by backing it up and starting fresh', async () => {
|
||||||
const logFilePath = `${TEST_LOG_FILE_PATH}-${testSessionId}.json`;
|
await fs.mkdir(path.join(process.cwd(), GEMINI_DIR), { recursive: true });
|
||||||
await fs.mkdir(path.dirname(logFilePath), { recursive: true });
|
await fs.writeFile(
|
||||||
await fs.writeFile(logFilePath, JSON.stringify({ not: 'an array' }));
|
TEST_LOG_FILE_PATH,
|
||||||
|
JSON.stringify({ not: 'an array' }),
|
||||||
const newLogger = new Logger(testSessionId);
|
);
|
||||||
const consoleDebugSpy = vi
|
const consoleDebugSpy = vi
|
||||||
.spyOn(console, 'debug')
|
.spyOn(console, 'debug')
|
||||||
.mockImplementation(() => {});
|
.mockImplementation(() => {});
|
||||||
|
const newLogger = new Logger(testSessionId);
|
||||||
await newLogger.initialize();
|
await newLogger.initialize();
|
||||||
await fs.writeFile(logFilePath, JSON.stringify({ not: 'an array' }));
|
|
||||||
expect(consoleDebugSpy).toHaveBeenCalledWith(
|
expect(consoleDebugSpy).toHaveBeenCalledWith(
|
||||||
`Log file at ${logFilePath} is not a valid JSON array. Starting with empty logs.`,
|
`Log file at ${TEST_LOG_FILE_PATH} is not a valid JSON array. Starting with empty logs.`,
|
||||||
);
|
);
|
||||||
expect(newLogger['logs']).toEqual([]);
|
const logContent = await readLogFile();
|
||||||
|
expect(logContent).toEqual([]);
|
||||||
const logContent = await fs.readFile(logFilePath, 'utf-8');
|
|
||||||
expect(JSON.parse(logContent)).toEqual({ not: 'an array' });
|
|
||||||
|
|
||||||
const dirContents = await fs.readdir(
|
const dirContents = await fs.readdir(
|
||||||
path.join(process.cwd(), GEMINI_DIR),
|
path.join(process.cwd(), GEMINI_DIR),
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
dirContents.some(
|
dirContents.some(
|
||||||
(f) =>
|
(f) =>
|
||||||
f.startsWith(`${path.basename(logFilePath)}.malformed_array`) &&
|
f.startsWith(LOG_FILE_NAME + '.malformed_array') &&
|
||||||
f.endsWith('.bak'),
|
f.endsWith('.bak'),
|
||||||
),
|
),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
|
|
||||||
newLogger.close();
|
newLogger.close();
|
||||||
consoleDebugSpy.mockRestore();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('logMessage', () => {
|
describe('logMessage', () => {
|
||||||
it('should append a message to the log file and update in-memory logs', async () => {
|
it('should append a message to the log file and update in-memory logs', async () => {
|
||||||
await logger.logMessage(MessageSenderType.USER, 'Hello, world!');
|
await logger.logMessage(MessageSenderType.USER, 'Hello, world!');
|
||||||
const logsFromFile = await readLogFile(testSessionId);
|
const logsFromFile = await readLogFile();
|
||||||
expect(logsFromFile.length).toBe(1);
|
expect(logsFromFile.length).toBe(1);
|
||||||
expect(logsFromFile[0]).toMatchObject({
|
expect(logsFromFile[0]).toMatchObject({
|
||||||
sessionId: testSessionId,
|
sessionId: testSessionId,
|
||||||
|
@ -289,7 +276,7 @@ describe('Logger', () => {
|
||||||
await logger.logMessage(MessageSenderType.USER, 'First');
|
await logger.logMessage(MessageSenderType.USER, 'First');
|
||||||
vi.advanceTimersByTime(1000);
|
vi.advanceTimersByTime(1000);
|
||||||
await logger.logMessage(MessageSenderType.USER, 'Second');
|
await logger.logMessage(MessageSenderType.USER, 'Second');
|
||||||
const logs = await readLogFile(testSessionId);
|
const logs = await readLogFile();
|
||||||
expect(logs.length).toBe(2);
|
expect(logs.length).toBe(2);
|
||||||
expect(logs[0].messageId).toBe(0);
|
expect(logs[0].messageId).toBe(0);
|
||||||
expect(logs[1].messageId).toBe(1);
|
expect(logs[1].messageId).toBe(1);
|
||||||
|
@ -307,7 +294,7 @@ describe('Logger', () => {
|
||||||
expect(consoleDebugSpy).toHaveBeenCalledWith(
|
expect(consoleDebugSpy).toHaveBeenCalledWith(
|
||||||
'Logger not initialized or session ID missing. Cannot log message.',
|
'Logger not initialized or session ID missing. Cannot log message.',
|
||||||
);
|
);
|
||||||
expect((await readLogFile(testSessionId)).length).toBe(0);
|
expect((await readLogFile()).length).toBe(0);
|
||||||
uninitializedLogger.close();
|
uninitializedLogger.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -335,7 +322,7 @@ describe('Logger', () => {
|
||||||
// Log from logger2. It reads file (sees {s1,0}, {s1,1}, {s1,2}), its internal msgId for s1 is 3.
|
// Log from logger2. It reads file (sees {s1,0}, {s1,1}, {s1,2}), its internal msgId for s1 is 3.
|
||||||
await logger2.logMessage(MessageSenderType.USER, 'L2M2'); // L2 internal msgId becomes 4, writes {s1, 3}
|
await logger2.logMessage(MessageSenderType.USER, 'L2M2'); // L2 internal msgId becomes 4, writes {s1, 3}
|
||||||
|
|
||||||
const logsFromFile = await readLogFile(concurrentSessionId);
|
const logsFromFile = await readLogFile();
|
||||||
expect(logsFromFile.length).toBe(4);
|
expect(logsFromFile.length).toBe(4);
|
||||||
const messageIdsInFile = logsFromFile
|
const messageIdsInFile = logsFromFile
|
||||||
.map((log) => log.messageId)
|
.map((log) => log.messageId)
|
||||||
|
@ -348,8 +335,8 @@ describe('Logger', () => {
|
||||||
expect(messagesInFile).toEqual(['L1M1', 'L2M1', 'L1M2', 'L2M2']);
|
expect(messagesInFile).toEqual(['L1M1', 'L2M1', 'L1M2', 'L2M2']);
|
||||||
|
|
||||||
// Check internal state (next messageId each logger would use for that session)
|
// Check internal state (next messageId each logger would use for that session)
|
||||||
expect(logger1['messageId']).toBe(3);
|
expect(logger1['messageId']).toBe(3); // L1 wrote 0, then 2. Next is 3.
|
||||||
expect(logger2['messageId']).toBe(4);
|
expect(logger2['messageId']).toBe(4); // L2 wrote 1, then 3. Next is 4.
|
||||||
|
|
||||||
logger1.close();
|
logger1.close();
|
||||||
logger2.close();
|
logger2.close();
|
||||||
|
@ -374,6 +361,55 @@ describe('Logger', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getPreviousUserMessages', () => {
|
||||||
|
it('should retrieve all user messages from logs, sorted newest first', async () => {
|
||||||
|
// This test now verifies that messages from different sessions are included
|
||||||
|
// and sorted correctly by timestamp, as the session-based sorting was removed.
|
||||||
|
const loggerSort = new Logger('session-1');
|
||||||
|
await loggerSort.initialize();
|
||||||
|
await loggerSort.logMessage(MessageSenderType.USER, 'S1M0_ts100000'); // msgId 0
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
await loggerSort.logMessage(MessageSenderType.USER, 'S1M1_ts101000'); // msgId 1
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
await loggerSort.logMessage(MessageSenderType.USER, 'S2M0_ts102000'); // msgId 0 for s2
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
await loggerSort.logMessage(
|
||||||
|
'model' as MessageSenderType,
|
||||||
|
'S2_Model_ts103000',
|
||||||
|
);
|
||||||
|
vi.advanceTimersByTime(1000);
|
||||||
|
await loggerSort.logMessage(MessageSenderType.USER, 'S2M1_ts104000'); // msgId 1 for s2
|
||||||
|
loggerSort.close();
|
||||||
|
|
||||||
|
// A new logger will load all previous logs regardless of session
|
||||||
|
const finalLogger = new Logger('final-session');
|
||||||
|
await finalLogger.initialize();
|
||||||
|
|
||||||
|
const messages = await finalLogger.getPreviousUserMessages();
|
||||||
|
expect(messages).toEqual([
|
||||||
|
'S2M1_ts104000',
|
||||||
|
'S2M0_ts102000',
|
||||||
|
'S1M1_ts101000',
|
||||||
|
'S1M0_ts100000',
|
||||||
|
]);
|
||||||
|
finalLogger.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty array if no user messages exist', async () => {
|
||||||
|
await logger.logMessage('system' as MessageSenderType, 'System boot');
|
||||||
|
const messages = await logger.getPreviousUserMessages();
|
||||||
|
expect(messages).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty array if logger not initialized', async () => {
|
||||||
|
const uninitializedLogger = new Logger(testSessionId);
|
||||||
|
uninitializedLogger.close();
|
||||||
|
const messages = await uninitializedLogger.getPreviousUserMessages();
|
||||||
|
expect(messages).toEqual([]);
|
||||||
|
uninitializedLogger.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('saveCheckpoint', () => {
|
describe('saveCheckpoint', () => {
|
||||||
const conversation: Content[] = [
|
const conversation: Content[] = [
|
||||||
{ role: 'user', parts: [{ text: 'Hello' }] },
|
{ role: 'user', parts: [{ text: 'Hello' }] },
|
||||||
|
|
|
@ -9,14 +9,11 @@ import { promises as fs } from 'node:fs';
|
||||||
import { Content } from '@google/genai';
|
import { Content } from '@google/genai';
|
||||||
|
|
||||||
const GEMINI_DIR = '.gemini';
|
const GEMINI_DIR = '.gemini';
|
||||||
const LOG_FILE_NAME_PREFIX = 'logs';
|
const LOG_FILE_NAME = 'logs.json';
|
||||||
const CHECKPOINT_FILE_NAME = 'checkpoint.json';
|
const CHECKPOINT_FILE_NAME = 'checkpoint.json';
|
||||||
|
|
||||||
export enum MessageSenderType {
|
export enum MessageSenderType {
|
||||||
USER = 'user',
|
USER = 'user',
|
||||||
SYSTEM = 'system',
|
|
||||||
TOOL_REQUEST = 'tool_request',
|
|
||||||
SERVER_ERROR = 'server_error',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LogEntry {
|
export interface LogEntry {
|
||||||
|
@ -40,10 +37,6 @@ export class Logger {
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
getLogFilePath(): string | undefined {
|
|
||||||
return this.logFilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _readLogFile(): Promise<LogEntry[]> {
|
private async _readLogFile(): Promise<LogEntry[]> {
|
||||||
if (!this.logFilePath) {
|
if (!this.logFilePath) {
|
||||||
throw new Error('Log file path not set during read attempt.');
|
throw new Error('Log file path not set during read attempt.');
|
||||||
|
@ -103,10 +96,7 @@ export class Logger {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.geminiDir = path.resolve(process.cwd(), GEMINI_DIR);
|
this.geminiDir = path.resolve(process.cwd(), GEMINI_DIR);
|
||||||
this.logFilePath = path.join(
|
this.logFilePath = path.join(this.geminiDir, LOG_FILE_NAME);
|
||||||
this.geminiDir,
|
|
||||||
`${LOG_FILE_NAME_PREFIX}-${this.sessionId}.json`,
|
|
||||||
);
|
|
||||||
this.checkpointFilePath = path.join(this.geminiDir, CHECKPOINT_FILE_NAME);
|
this.checkpointFilePath = path.join(this.geminiDir, CHECKPOINT_FILE_NAME);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in New Issue