summaryrefslogtreecommitdiff
path: root/packages/server/src/core/logger.ts
blob: d12d424045c7a0f50639eefa1600bb64f14fb843 (plain)
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;
    }
  }
}