Add Logger for command history (#435)
This commit is contained in:
parent
bda472f147
commit
cd13c5881b
|
@ -2,6 +2,9 @@
|
||||||
.env
|
.env
|
||||||
.env~
|
.env~
|
||||||
|
|
||||||
|
# gemini-code settings
|
||||||
|
.gemini/
|
||||||
|
|
||||||
# Dependency directory
|
# Dependency directory
|
||||||
node_modules
|
node_modules
|
||||||
bower_components
|
bower_components
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -20,7 +20,6 @@ import { ShellModeIndicator } from './components/ShellModeIndicator.js';
|
||||||
import { InputPrompt } from './components/InputPrompt.js';
|
import { InputPrompt } from './components/InputPrompt.js';
|
||||||
import { Footer } from './components/Footer.js';
|
import { Footer } from './components/Footer.js';
|
||||||
import { ThemeDialog } from './components/ThemeDialog.js';
|
import { ThemeDialog } from './components/ThemeDialog.js';
|
||||||
import { type Config } from '@gemini-code/server';
|
|
||||||
import { Colors } from './colors.js';
|
import { Colors } from './colors.js';
|
||||||
import { Help } from './components/Help.js';
|
import { Help } from './components/Help.js';
|
||||||
import { loadHierarchicalGeminiMemory } from '../config/config.js';
|
import { loadHierarchicalGeminiMemory } from '../config/config.js';
|
||||||
|
@ -29,9 +28,10 @@ import { Tips } from './components/Tips.js';
|
||||||
import { ConsoleOutput } from './components/ConsolePatcher.js';
|
import { ConsoleOutput } from './components/ConsolePatcher.js';
|
||||||
import { HistoryItemDisplay } from './components/HistoryItemDisplay.js';
|
import { HistoryItemDisplay } from './components/HistoryItemDisplay.js';
|
||||||
import { useHistory } from './hooks/useHistoryManager.js';
|
import { useHistory } from './hooks/useHistoryManager.js';
|
||||||
import process from 'node:process'; // For performMemoryRefresh
|
import { useLogger } from './hooks/useLogger.js';
|
||||||
import { MessageType } from './types.js'; // For performMemoryRefresh
|
import process from 'node:process';
|
||||||
import { getErrorMessage } from '@gemini-code/server'; // For performMemoryRefresh
|
import { MessageType } from './types.js';
|
||||||
|
import { getErrorMessage, type Config } from '@gemini-code/server';
|
||||||
|
|
||||||
interface AppProps {
|
interface AppProps {
|
||||||
config: Config;
|
config: Config;
|
||||||
|
@ -157,8 +157,16 @@ export const App = ({
|
||||||
[submitQuery],
|
[submitQuery],
|
||||||
);
|
);
|
||||||
|
|
||||||
const userMessages = useMemo(
|
const logger = useLogger();
|
||||||
() =>
|
const [userMessages, setUserMessages] = useState<string[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchUserMessages = async () => {
|
||||||
|
const pastMessages = (await logger?.getPreviousUserMessages()) || [];
|
||||||
|
if (pastMessages.length > 0) {
|
||||||
|
setUserMessages(pastMessages.reverse());
|
||||||
|
} else {
|
||||||
|
setUserMessages(
|
||||||
history
|
history
|
||||||
.filter(
|
.filter(
|
||||||
(item): item is HistoryItem & { type: 'user'; text: string } =>
|
(item): item is HistoryItem & { type: 'user'; text: string } =>
|
||||||
|
@ -167,8 +175,11 @@ export const App = ({
|
||||||
item.text.trim() !== '',
|
item.text.trim() !== '',
|
||||||
)
|
)
|
||||||
.map((item) => item.text),
|
.map((item) => item.text),
|
||||||
[history],
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchUserMessages();
|
||||||
|
}, [history, logger]);
|
||||||
|
|
||||||
const isInputActive = streamingState === StreamingState.Idle && !initError;
|
const isInputActive = streamingState === StreamingState.Idle && !initError;
|
||||||
|
|
||||||
|
|
|
@ -18,13 +18,14 @@ import {
|
||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
isNodeError,
|
isNodeError,
|
||||||
Config,
|
Config,
|
||||||
|
MessageSenderType,
|
||||||
|
ServerToolCallConfirmationDetails,
|
||||||
ToolCallConfirmationDetails,
|
ToolCallConfirmationDetails,
|
||||||
ToolCallResponseInfo,
|
ToolCallResponseInfo,
|
||||||
ServerToolCallConfirmationDetails,
|
|
||||||
ToolConfirmationOutcome,
|
ToolConfirmationOutcome,
|
||||||
ToolResultDisplay,
|
|
||||||
ToolEditConfirmationDetails,
|
ToolEditConfirmationDetails,
|
||||||
ToolExecuteConfirmationDetails,
|
ToolExecuteConfirmationDetails,
|
||||||
|
ToolResultDisplay,
|
||||||
partListUnionToString,
|
partListUnionToString,
|
||||||
} from '@gemini-code/server';
|
} from '@gemini-code/server';
|
||||||
import { type Chat, type PartListUnion, type Part } from '@google/genai';
|
import { type Chat, type PartListUnion, type Part } from '@google/genai';
|
||||||
|
@ -42,6 +43,7 @@ import { handleAtCommand } from './atCommandProcessor.js';
|
||||||
import { findLastSafeSplitPoint } from '../utils/markdownUtilities.js';
|
import { findLastSafeSplitPoint } from '../utils/markdownUtilities.js';
|
||||||
import { useStateAndRef } from './useStateAndRef.js';
|
import { useStateAndRef } from './useStateAndRef.js';
|
||||||
import { UseHistoryManagerReturn } from './useHistoryManager.js';
|
import { UseHistoryManagerReturn } from './useHistoryManager.js';
|
||||||
|
import { useLogger } from './useLogger.js';
|
||||||
|
|
||||||
enum StreamProcessingStatus {
|
enum StreamProcessingStatus {
|
||||||
Completed,
|
Completed,
|
||||||
|
@ -71,6 +73,7 @@ export const useGeminiStream = (
|
||||||
const [isResponding, setIsResponding] = useState<boolean>(false);
|
const [isResponding, setIsResponding] = useState<boolean>(false);
|
||||||
const [pendingHistoryItemRef, setPendingHistoryItem] =
|
const [pendingHistoryItemRef, setPendingHistoryItem] =
|
||||||
useStateAndRef<HistoryItemWithoutId | null>(null);
|
useStateAndRef<HistoryItemWithoutId | null>(null);
|
||||||
|
const logger = useLogger();
|
||||||
|
|
||||||
const onExec = useCallback(async (done: Promise<void>) => {
|
const onExec = useCallback(async (done: Promise<void>) => {
|
||||||
setIsResponding(true);
|
setIsResponding(true);
|
||||||
|
@ -117,6 +120,7 @@ export const useGeminiStream = (
|
||||||
if (typeof query === 'string') {
|
if (typeof query === 'string') {
|
||||||
const trimmedQuery = query.trim();
|
const trimmedQuery = query.trim();
|
||||||
onDebugMessage(`User query: '${trimmedQuery}'`);
|
onDebugMessage(`User query: '${trimmedQuery}'`);
|
||||||
|
await logger?.logMessage(MessageSenderType.USER, trimmedQuery);
|
||||||
|
|
||||||
// Handle UI-only commands first
|
// Handle UI-only commands first
|
||||||
if (handleSlashCommand(trimmedQuery)) {
|
if (handleSlashCommand(trimmedQuery)) {
|
||||||
|
@ -616,6 +620,7 @@ export const useGeminiStream = (
|
||||||
onDebugMessage,
|
onDebugMessage,
|
||||||
refreshStatic,
|
refreshStatic,
|
||||||
setInitError,
|
setInitError,
|
||||||
|
logger,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Logger } from '@gemini-code/server';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to manage the logger instance.
|
||||||
|
*/
|
||||||
|
export const useLogger = () => {
|
||||||
|
const [logger, setLogger] = useState<Logger | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const newLogger = new Logger();
|
||||||
|
/**
|
||||||
|
* Start async initialization, no need to await. Using await slows down the
|
||||||
|
* time from launch to see the gemini-cli prompt and it's better to not save
|
||||||
|
* messages than for the cli to hanging waiting for the logger to loading.
|
||||||
|
*/
|
||||||
|
newLogger
|
||||||
|
.initialize()
|
||||||
|
.then(() => {
|
||||||
|
setLogger(newLogger);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return logger;
|
||||||
|
};
|
|
@ -25,7 +25,8 @@
|
||||||
"@modelcontextprotocol/sdk": "^1.11.0",
|
"@modelcontextprotocol/sdk": "^1.11.0",
|
||||||
"diff": "^7.0.0",
|
"diff": "^7.0.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"fast-glob": "^3.3.3"
|
"fast-glob": "^3.3.3",
|
||||||
|
"sqlite3": "^5.1.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/diff": "^7.0.2",
|
"@types/diff": "^7.0.2",
|
||||||
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { Logger, MessageSenderType } from './logger.js';
|
||||||
|
|
||||||
|
// Mocks
|
||||||
|
const mockDb = {
|
||||||
|
exec: vi.fn((_sql, callback) => callback?.(null)),
|
||||||
|
all: vi.fn((_sql, _params, callback) => callback?.(null, [])),
|
||||||
|
run: vi.fn((_sql, _params, callback) => callback?.(null)),
|
||||||
|
close: vi.fn((callback) => callback?.(null)),
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mock('sqlite3', () => ({
|
||||||
|
Database: vi.fn((_dbPath, _options, callback) => {
|
||||||
|
process.nextTick(() => callback?.(null));
|
||||||
|
return mockDb;
|
||||||
|
}),
|
||||||
|
default: {
|
||||||
|
Database: vi.fn((_dbPath, _options, callback) => {
|
||||||
|
process.nextTick(() => callback?.(null));
|
||||||
|
return mockDb;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Logger', () => {
|
||||||
|
let logger: Logger;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
vi.resetAllMocks();
|
||||||
|
|
||||||
|
// Get a new instance for each test to ensure isolation,
|
||||||
|
logger = new Logger();
|
||||||
|
// We need to wait for the async initialize to complete
|
||||||
|
await logger.initialize().catch((err) => {
|
||||||
|
console.error('Error initializing logger:', err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
logger.close(); // Close the database connection after each test
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('initialize', () => {
|
||||||
|
it('should execute create tables if not exists', async () => {
|
||||||
|
expect(mockDb.exec).toHaveBeenCalledWith(
|
||||||
|
expect.stringMatching(/CREATE TABLE IF NOT EXISTS messages/),
|
||||||
|
expect.any(Function),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be idempotent', async () => {
|
||||||
|
mockDb.exec.mockClear();
|
||||||
|
|
||||||
|
await logger.initialize(); // Second call
|
||||||
|
|
||||||
|
expect(mockDb.exec).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('logMessage', () => {
|
||||||
|
it('should insert a message into the database', async () => {
|
||||||
|
const type = MessageSenderType.USER;
|
||||||
|
const message = 'Hello, world!';
|
||||||
|
await logger.logMessage(type, message);
|
||||||
|
expect(mockDb.run).toHaveBeenCalledWith(
|
||||||
|
"INSERT INTO messages (session_id, message_id, type, message, timestamp) VALUES (?, ?, ?, ?, datetime('now'))",
|
||||||
|
[expect.any(Number), 0, type, message], // sessionId, messageId, type, message
|
||||||
|
expect.any(Function),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should increment messageId for subsequent messages', async () => {
|
||||||
|
await logger.logMessage(MessageSenderType.USER, 'First message');
|
||||||
|
expect(mockDb.run).toHaveBeenCalledWith(
|
||||||
|
expect.any(String),
|
||||||
|
[expect.any(Number), 0, MessageSenderType.USER, 'First message'],
|
||||||
|
expect.any(Function),
|
||||||
|
);
|
||||||
|
await logger.logMessage(MessageSenderType.USER, 'Second message');
|
||||||
|
expect(mockDb.run).toHaveBeenCalledWith(
|
||||||
|
expect.any(String),
|
||||||
|
[expect.any(Number), 1, MessageSenderType.USER, 'Second message'], // messageId is now 1
|
||||||
|
expect.any(Function),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle database not initialized', async () => {
|
||||||
|
const uninitializedLogger = new Logger();
|
||||||
|
// uninitializedLogger.initialize() is not called
|
||||||
|
const consoleErrorSpy = vi
|
||||||
|
.spyOn(console, 'error')
|
||||||
|
.mockImplementation(() => {});
|
||||||
|
|
||||||
|
await uninitializedLogger.logMessage(MessageSenderType.USER, 'test');
|
||||||
|
|
||||||
|
expect(consoleErrorSpy).toHaveBeenCalledWith('Database not initialized.');
|
||||||
|
expect(mockDb.run).not.toHaveBeenCalled();
|
||||||
|
consoleErrorSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle error during db.run', async () => {
|
||||||
|
const error = new Error('db.run failed');
|
||||||
|
mockDb.run.mockImplementationOnce(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(_sql: any, _params: any, callback: any) => callback?.(error),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
logger.logMessage(MessageSenderType.USER, 'test'),
|
||||||
|
).rejects.toThrow('db.run failed');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getPreviousUserMessages', () => {
|
||||||
|
it('should query the database for messages', async () => {
|
||||||
|
mockDb.all.mockImplementationOnce(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(_sql: any, params: any, callback: any) =>
|
||||||
|
callback?.(null, [{ message: 'msg1' }, { message: 'msg2' }]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const messages = await logger.getPreviousUserMessages();
|
||||||
|
|
||||||
|
expect(mockDb.all).toHaveBeenCalledWith(
|
||||||
|
expect.stringMatching(/SELECT message FROM messages/),
|
||||||
|
[],
|
||||||
|
expect.any(Function),
|
||||||
|
);
|
||||||
|
expect(messages).toEqual(['msg1', 'msg2']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle database not initialized', async () => {
|
||||||
|
const uninitializedLogger = new Logger();
|
||||||
|
// uninitializedLogger.initialize() is not called
|
||||||
|
const consoleErrorSpy = vi
|
||||||
|
.spyOn(console, 'error')
|
||||||
|
.mockImplementation(() => {});
|
||||||
|
|
||||||
|
const messages = await uninitializedLogger.getPreviousUserMessages();
|
||||||
|
|
||||||
|
expect(consoleErrorSpy).toHaveBeenCalledWith('Database not initialized.');
|
||||||
|
expect(messages).toEqual([]);
|
||||||
|
expect(mockDb.all).not.toHaveBeenCalled();
|
||||||
|
consoleErrorSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle error during db.all', async () => {
|
||||||
|
const error = new Error('db.all failed');
|
||||||
|
mockDb.all.mockImplementationOnce(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(_sql: any, _params: any, callback: any) => callback?.(error, []),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(logger.getPreviousUserMessages()).rejects.toThrow(
|
||||||
|
'db.all failed',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('close', () => {
|
||||||
|
it('should close the database connection', () => {
|
||||||
|
logger.close();
|
||||||
|
expect(mockDb.close).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle database not initialized', () => {
|
||||||
|
const uninitializedLogger = new Logger();
|
||||||
|
// uninitializedLogger.initialize() is not called
|
||||||
|
uninitializedLogger.close();
|
||||||
|
expect(() => uninitializedLogger.close()).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle error during db.close', () => {
|
||||||
|
const error = new Error('db.close failed');
|
||||||
|
mockDb.close.mockImplementationOnce((callback: (error: Error) => void) =>
|
||||||
|
callback?.(error),
|
||||||
|
);
|
||||||
|
const consoleErrorSpy = vi
|
||||||
|
.spyOn(console, 'error')
|
||||||
|
.mockImplementation(() => {});
|
||||||
|
|
||||||
|
logger.close();
|
||||||
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||||
|
'Error closing database:',
|
||||||
|
error.message,
|
||||||
|
);
|
||||||
|
consoleErrorSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,131 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import path from 'node:path';
|
||||||
|
import sqlite3 from 'sqlite3';
|
||||||
|
import { promises as fs } from 'node:fs';
|
||||||
|
|
||||||
|
const GEMINI_DIR = '.gemini';
|
||||||
|
const DB_NAME = 'logs.db';
|
||||||
|
const CREATE_TABLE_SQL = `
|
||||||
|
CREATE TABLE IF NOT EXISTS messages (
|
||||||
|
session_id INTEGER,
|
||||||
|
message_id INTEGER,
|
||||||
|
timestamp TEXT,
|
||||||
|
type TEXT,
|
||||||
|
message TEXT
|
||||||
|
);`;
|
||||||
|
|
||||||
|
export enum MessageSenderType {
|
||||||
|
USER = 'user',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Logger {
|
||||||
|
private db: sqlite3.Database | undefined;
|
||||||
|
private sessionId: number | undefined;
|
||||||
|
private messageId: number | undefined;
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
async initialize(): Promise<void> {
|
||||||
|
if (this.db) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sessionId = Math.floor(Date.now() / 1000);
|
||||||
|
this.messageId = 0;
|
||||||
|
|
||||||
|
// Could be cleaner if our sqlite package supported promises.
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const DB_DIR = path.resolve(process.cwd(), GEMINI_DIR);
|
||||||
|
const DB_PATH = path.join(DB_DIR, DB_NAME);
|
||||||
|
fs.mkdir(DB_DIR, { recursive: true })
|
||||||
|
.then(() => {
|
||||||
|
this.db = new sqlite3.Database(
|
||||||
|
DB_PATH,
|
||||||
|
sqlite3.OPEN_READWRITE |
|
||||||
|
sqlite3.OPEN_CREATE |
|
||||||
|
sqlite3.OPEN_FULLMUTEX,
|
||||||
|
(err: Error | null) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and execute the SQL script in create_tables.sql
|
||||||
|
this.db?.exec(CREATE_TABLE_SQL, (err: Error | null) => {
|
||||||
|
if (err) {
|
||||||
|
this.db?.close();
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of previous user inputs sorted most recent first.
|
||||||
|
* @returns list of messages.
|
||||||
|
*/
|
||||||
|
async getPreviousUserMessages(): Promise<string[]> {
|
||||||
|
if (!this.db) {
|
||||||
|
console.error('Database not initialized.');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Most recent messages first
|
||||||
|
const query = `SELECT message FROM messages
|
||||||
|
WHERE type = '${MessageSenderType.USER}'
|
||||||
|
ORDER BY session_id DESC, message_id DESC`;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
this.db!.all(query, [], (err: Error | null, rows: any[]) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(rows.map((row) => row.message));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async logMessage(type: MessageSenderType, message: string): Promise<void> {
|
||||||
|
if (!this.db) {
|
||||||
|
console.error('Database not initialized.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const query = `INSERT INTO messages (session_id, message_id, type, message, timestamp) VALUES (?, ?, ?, ?, datetime('now'))`;
|
||||||
|
this.messageId = this.messageId! + 1;
|
||||||
|
this.db!.run(
|
||||||
|
query,
|
||||||
|
[this.sessionId || 0, this.messageId - 1, type, message],
|
||||||
|
(err: Error | null) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
if (this.db) {
|
||||||
|
this.db.close((err: Error | null) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error closing database:', err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.db = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ export * from './config/config.js';
|
||||||
|
|
||||||
// Export Core Logic
|
// Export Core Logic
|
||||||
export * from './core/client.js';
|
export * from './core/client.js';
|
||||||
|
export * from './core/logger.js';
|
||||||
export * from './core/prompts.js';
|
export * from './core/prompts.js';
|
||||||
export * from './core/turn.js';
|
export * from './core/turn.js';
|
||||||
export * from './core/geminiRequest.js';
|
export * from './core/geminiRequest.js';
|
||||||
|
|
Loading…
Reference in New Issue