1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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;
}
}
}
|