make tag required for /chat (#2904)
This commit is contained in:
parent
34935d6558
commit
38445f63f0
|
@ -699,7 +699,7 @@ export const useSlashCommandProcessor = (
|
|||
{
|
||||
name: 'chat',
|
||||
description:
|
||||
'Manage conversation history. Usage: /chat <list|save|resume> [tag]',
|
||||
'Manage conversation history. Usage: /chat <list|save|resume> <tag>',
|
||||
action: async (_mainCommand, subCommand, args) => {
|
||||
const tag = (args || '').trim();
|
||||
const logger = new Logger(config?.getSessionId() || '');
|
||||
|
@ -716,19 +716,27 @@ export const useSlashCommandProcessor = (
|
|||
if (!subCommand) {
|
||||
addMessage({
|
||||
type: MessageType.ERROR,
|
||||
content: 'Missing command\nUsage: /chat <list|save|resume> [tag]',
|
||||
content: 'Missing command\nUsage: /chat <list|save|resume> <tag>',
|
||||
timestamp: new Date(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
switch (subCommand) {
|
||||
case 'save': {
|
||||
if (!tag) {
|
||||
addMessage({
|
||||
type: MessageType.ERROR,
|
||||
content: 'Missing tag. Usage: /chat save <tag>',
|
||||
timestamp: new Date(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const history = chat.getHistory();
|
||||
if (history.length > 0) {
|
||||
await logger.saveCheckpoint(chat?.getHistory() || [], tag);
|
||||
addMessage({
|
||||
type: MessageType.INFO,
|
||||
content: `Conversation checkpoint saved${tag ? ' with tag: ' + tag : ''}.`,
|
||||
content: `Conversation checkpoint saved with tag: ${tag}.`,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
} else {
|
||||
|
@ -743,11 +751,19 @@ export const useSlashCommandProcessor = (
|
|||
case 'resume':
|
||||
case 'restore':
|
||||
case 'load': {
|
||||
if (!tag) {
|
||||
addMessage({
|
||||
type: MessageType.ERROR,
|
||||
content: 'Missing tag. Usage: /chat resume <tag>',
|
||||
timestamp: new Date(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const conversation = await logger.loadCheckpoint(tag);
|
||||
if (conversation.length === 0) {
|
||||
addMessage({
|
||||
type: MessageType.INFO,
|
||||
content: `No saved checkpoint found${tag ? ' with tag: ' + tag : ''}.`,
|
||||
content: `No saved checkpoint found with tag: ${tag}.`,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
return;
|
||||
|
|
|
@ -393,12 +393,6 @@ describe('Logger', () => {
|
|||
{ role: 'model', parts: [{ text: 'Hi there' }] },
|
||||
];
|
||||
|
||||
it('should save a checkpoint to the default file when no tag is provided', async () => {
|
||||
await logger.saveCheckpoint(conversation);
|
||||
const fileContent = await fs.readFile(TEST_CHECKPOINT_FILE_PATH, 'utf-8');
|
||||
expect(JSON.parse(fileContent)).toEqual(conversation);
|
||||
});
|
||||
|
||||
it('should save a checkpoint to a tagged file when a tag is provided', async () => {
|
||||
const tag = 'my-test-tag';
|
||||
await logger.saveCheckpoint(conversation, tag);
|
||||
|
@ -418,7 +412,7 @@ describe('Logger', () => {
|
|||
.mockImplementation(() => {});
|
||||
|
||||
await expect(
|
||||
uninitializedLogger.saveCheckpoint(conversation),
|
||||
uninitializedLogger.saveCheckpoint(conversation, 'tag'),
|
||||
).resolves.not.toThrow();
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
'Logger not initialized or checkpoint file path not set. Cannot save a checkpoint.',
|
||||
|
@ -439,11 +433,6 @@ describe('Logger', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should load from the default checkpoint file when no tag is provided', async () => {
|
||||
const loaded = await logger.loadCheckpoint();
|
||||
expect(loaded).toEqual(conversation);
|
||||
});
|
||||
|
||||
it('should load from a tagged checkpoint file when a tag is provided', async () => {
|
||||
const tag = 'my-load-tag';
|
||||
const taggedConversation = [
|
||||
|
@ -468,9 +457,9 @@ describe('Logger', () => {
|
|||
expect(loaded).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return an empty array if the default checkpoint file does not exist', async () => {
|
||||
it('should return an empty array if the checkpoint file does not exist', async () => {
|
||||
await fs.unlink(TEST_CHECKPOINT_FILE_PATH); // Ensure it's gone
|
||||
const loaded = await logger.loadCheckpoint();
|
||||
const loaded = await logger.loadCheckpoint('missing');
|
||||
expect(loaded).toEqual([]);
|
||||
});
|
||||
|
||||
|
@ -479,11 +468,11 @@ describe('Logger', () => {
|
|||
const consoleErrorSpy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
const loadedCheckpoint = await logger.loadCheckpoint();
|
||||
const loadedCheckpoint = await logger.loadCheckpoint('missing');
|
||||
expect(loadedCheckpoint).toEqual([]);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Failed to read or parse checkpoint file'),
|
||||
expect.any(SyntaxError),
|
||||
expect.any(Error),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -493,7 +482,7 @@ describe('Logger', () => {
|
|||
const consoleErrorSpy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
const loadedCheckpoint = await uninitializedLogger.loadCheckpoint();
|
||||
const loadedCheckpoint = await uninitializedLogger.loadCheckpoint('tag');
|
||||
expect(loadedCheckpoint).toEqual([]);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
'Logger not initialized or checkpoint file path not set. Cannot load checkpoint.',
|
||||
|
|
|
@ -10,7 +10,6 @@ import { Content } from '@google/genai';
|
|||
import { getProjectTempDir } from '../utils/paths.js';
|
||||
|
||||
const LOG_FILE_NAME = 'logs.json';
|
||||
const CHECKPOINT_FILE_NAME = 'checkpoint.json';
|
||||
|
||||
export enum MessageSenderType {
|
||||
USER = 'user',
|
||||
|
@ -27,7 +26,6 @@ export interface LogEntry {
|
|||
export class Logger {
|
||||
private geminiDir: string | undefined;
|
||||
private logFilePath: string | undefined;
|
||||
private checkpointFilePath: string | undefined;
|
||||
private sessionId: string | undefined;
|
||||
private messageId = 0; // Instance-specific counter for the next messageId
|
||||
private initialized = false;
|
||||
|
@ -98,7 +96,6 @@ export class Logger {
|
|||
|
||||
this.geminiDir = getProjectTempDir(process.cwd());
|
||||
this.logFilePath = path.join(this.geminiDir, LOG_FILE_NAME);
|
||||
this.checkpointFilePath = path.join(this.geminiDir, CHECKPOINT_FILE_NAME);
|
||||
|
||||
try {
|
||||
await fs.mkdir(this.geminiDir, { recursive: true });
|
||||
|
@ -234,18 +231,18 @@ export class Logger {
|
|||
}
|
||||
}
|
||||
|
||||
_checkpointPath(tag: string | undefined): string {
|
||||
if (!this.checkpointFilePath || !this.geminiDir) {
|
||||
throw new Error('Checkpoint file path not set.');
|
||||
_checkpointPath(tag: string): string {
|
||||
if (!tag.length) {
|
||||
throw new Error('No checkpoint tag specified.');
|
||||
}
|
||||
if (!tag) {
|
||||
return this.checkpointFilePath;
|
||||
if (!this.geminiDir) {
|
||||
throw new Error('Checkpoint file path not set.');
|
||||
}
|
||||
return path.join(this.geminiDir, `checkpoint-${tag}.json`);
|
||||
}
|
||||
|
||||
async saveCheckpoint(conversation: Content[], tag?: string): Promise<void> {
|
||||
if (!this.initialized || !this.checkpointFilePath) {
|
||||
async saveCheckpoint(conversation: Content[], tag: string): Promise<void> {
|
||||
if (!this.initialized) {
|
||||
console.error(
|
||||
'Logger not initialized or checkpoint file path not set. Cannot save a checkpoint.',
|
||||
);
|
||||
|
@ -259,8 +256,8 @@ export class Logger {
|
|||
}
|
||||
}
|
||||
|
||||
async loadCheckpoint(tag?: string): Promise<Content[]> {
|
||||
if (!this.initialized || !this.checkpointFilePath) {
|
||||
async loadCheckpoint(tag: string): Promise<Content[]> {
|
||||
if (!this.initialized) {
|
||||
console.error(
|
||||
'Logger not initialized or checkpoint file path not set. Cannot load checkpoint.',
|
||||
);
|
||||
|
@ -268,7 +265,6 @@ export class Logger {
|
|||
}
|
||||
|
||||
const path = this._checkpointPath(tag);
|
||||
|
||||
try {
|
||||
const fileContent = await fs.readFile(path, 'utf-8');
|
||||
const parsedContent = JSON.parse(fileContent);
|
||||
|
@ -280,12 +276,12 @@ export class Logger {
|
|||
}
|
||||
return parsedContent as Content[];
|
||||
} catch (error) {
|
||||
console.error(`Failed to read or parse checkpoint file ${path}:`, error);
|
||||
const nodeError = error as NodeJS.ErrnoException;
|
||||
if (nodeError.code === 'ENOENT') {
|
||||
// File doesn't exist, which is fine. Return empty array.
|
||||
return [];
|
||||
}
|
||||
console.error(`Failed to read or parse checkpoint file ${path}:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -293,7 +289,6 @@ export class Logger {
|
|||
close(): void {
|
||||
this.initialized = false;
|
||||
this.logFilePath = undefined;
|
||||
this.checkpointFilePath = undefined;
|
||||
this.logs = [];
|
||||
this.sessionId = undefined;
|
||||
this.messageId = 0;
|
||||
|
|
Loading…
Reference in New Issue