UNPKG

rawi

Version:

Rawi (راوي) is the developer-friendly AI CLI that brings the power of 12 major AI providers directly to your terminal. With seamless shell integration, persistent conversations, and 200+ specialized prompt templates, Rawi transforms your command line into

1 lines 29.5 kB
{"version":3,"sources":["/home/mkabumattar/withrawi/rawi/dist/chunk-ZDWNVWWB.cjs","../src/core/database/adapter.ts"],"names":["DatabaseAdapter","getDatabaseFilePath","dbUrl"],"mappings":"AAAA;AACA,wDAAwC,wDAAgD,wDAAyC,wBCM1H,4BACe,wCACK,4BACA,IAadA,CAAAA,CAAN,KAAsB,CACnB,WAGR,CAAA,CAAc,CACZ,GAAI,CACF,IAAA,CAAK,MAAA,CAASC,iCAAAA,CAAoB,CAElC,IAAA,CAAK,eAAA,CAAgB,CAAA,CAErB,IAAA,CAAK,wBAAA,CAAyB,CAAA,CAE9B,IAAMC,CAAAA,CAAQ,CAAA,KAAA,EAAQ,IAAA,CAAK,MAAM,CAAA,CAAA;AAsFe;AAAA;AA8BhD,QAAA;AAgN4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,QAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBE,QAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,UAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,UAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASqC,2CAAA;AACC,4CAAA;AAAA;AAAA;AAAA;AAAA;AAgBtC,UAAA;AAAA;AAAA;AAKA,UAAA;AAAA;AAAA;AAKA,UAAA;AAAA;AAAA;AAKA,UAAA;AAAA;AAAA;AAKA,UAAA;AAAA;AAAA;AAKA,UAAA;AAAA;AAAA;AAakB,UAAA;AAAA;AAAA;AAsClD,QAAA;AAkCU;AAAA;AAuCH,IAAA;AA8BA;AAAA;AAsBG,0CAAA;AAAA;AAAA;AAAA;AA0CA,IAAA;AAAA;AAAA;AAAA;AAiDG,IAAA;AAoBI;AAAA;AAAA;AAAA;AAAA;AAUD,IAAA;AAAA;AAAA;AAcE,MAAA;AAAA;AAAA;AAOD,IAAA;AAAA;AAAA;AAmBF,MAAA;AAAA;AAAA;AAOD,IAAA;AAAA;AAAA;AAmBK,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeA,IAAA;AAAA;AAAA;AAAA;AAAA;AASD,IAAA;AAAA;AAAA;AA8BX,MAAA;AAAA;AAAA;AA6CX,MAAA;ADxxBue","file":"/home/mkabumattar/withrawi/rawi/dist/chunk-ZDWNVWWB.cjs","sourcesContent":[null,"import {\n accessSync,\n constants,\n existsSync,\n mkdirSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport {dirname} from 'node:path';\nimport {createClient} from '@libsql/client';\nimport {v4 as uuidv4} from 'uuid';\nimport {\n type ChatHistoryOptions,\n type ChatMessage,\n type ChatSession,\n DEFAULT_SESSION_TITLE_LENGTH,\n debugLog,\n getConfigDir,\n type HistoryStats,\n type SupportedProvider,\n} from '../shared/index.js';\nimport {getDatabaseFilePath} from './config/paths.js';\n\nexport class DatabaseAdapter {\n private client;\n private dbPath: string;\n\n constructor() {\n try {\n this.dbPath = getDatabaseFilePath();\n\n this.ensureConfigDir();\n\n this.ensureDatabaseFileExists();\n\n const dbUrl = `file:${this.dbPath}`;\n this.client = createClient({\n url: dbUrl,\n });\n\n debugLog('Database adapter initialized with URL:', dbUrl);\n } catch (error) {\n console.error('Error initializing database adapter:', error);\n throw new Error(\n `Failed to initialize database adapter: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n }\n\n private ensureConfigDir(): void {\n try {\n const configDir = getConfigDir();\n debugLog('Ensuring config directory exists:', configDir);\n\n if (!existsSync(configDir)) {\n debugLog('Config directory not found, creating it');\n mkdirSync(configDir, {recursive: true});\n debugLog('Config directory created successfully');\n } else {\n debugLog('Config directory already exists');\n }\n } catch (error) {\n console.error('Error ensuring config directory exists:', error);\n throw new Error(\n `Failed to create config directory: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n }\n\n private ensureDatabaseFileExists(): void {\n try {\n const dbDir = dirname(this.dbPath);\n if (!existsSync(dbDir)) {\n debugLog(`Database directory not found, creating it: ${dbDir}`);\n mkdirSync(dbDir, {recursive: true});\n }\n\n if (!existsSync(this.dbPath)) {\n debugLog('Database file not found, creating it:', this.dbPath);\n\n writeFileSync(this.dbPath, '');\n\n if (!existsSync(this.dbPath)) {\n throw new Error(`Failed to create database file at ${this.dbPath}`);\n }\n\n debugLog('Empty database file created successfully');\n } else {\n debugLog('Database file already exists');\n\n try {\n accessSync(this.dbPath, constants.W_OK);\n debugLog('Database file is writable');\n } catch (accessError) {\n console.error(\n 'Database file exists but is not writable!',\n accessError,\n );\n throw new Error(`Database file at ${this.dbPath} is not writable`);\n }\n }\n } catch (error) {\n console.error('Error ensuring database file exists:', error);\n throw new Error(\n `Failed to create or access database file: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n }\n\n public async ensureDatabaseInitialized(): Promise<void> {\n let attempt = 1;\n const maxAttempts = 3;\n\n while (attempt <= maxAttempts) {\n try {\n debugLog(`Checking if database schema exists (attempt ${attempt})...`);\n\n this.ensureConfigDir();\n\n this.ensureDatabaseFileExists();\n\n const tableResult = await this.client.execute(`\n SELECT count(*) as table_count FROM sqlite_master \n WHERE type='table' AND name IN ('chat_sessions', 'chat_messages');\n `);\n\n const tableCount = Number(tableResult.rows[0].table_count);\n\n if (tableCount === 2) {\n debugLog('Database schema verified, both tables exist');\n\n try {\n await this.client.execute('SELECT COUNT(*) FROM chat_sessions');\n await this.client.execute('SELECT COUNT(*) FROM chat_messages');\n debugLog('Database tables are accessible');\n return;\n } catch (queryError) {\n console.error(\n 'Tables exist but querying them failed:',\n queryError instanceof Error\n ? queryError.message\n : 'Unknown error',\n );\n }\n }\n\n debugLog(\n `Found only ${tableCount} of 2 required tables, initializing schema...`,\n );\n await this.initialize();\n return;\n } catch (error) {\n debugLog(`Error checking database schema (attempt ${attempt})`);\n\n if (error instanceof Error) {\n debugLog('Error details:', error.message);\n\n const isMissingTable =\n error.message.includes('no such table') ||\n error.message.includes('unable to open database file');\n\n if (isMissingTable) {\n debugLog(\n 'Database tables missing or database file issues. Initializing schema...',\n );\n\n if (attempt === maxAttempts) {\n try {\n debugLog(\n 'Last attempt - recreating database file from scratch',\n );\n\n if (this.client) {\n try {\n await this.client.close();\n } catch (e: unknown) {\n debugLog(\n 'Error closing client:',\n e instanceof Error ? e.message : 'Unknown error',\n );\n }\n }\n\n try {\n if (existsSync(this.dbPath)) {\n unlinkSync(this.dbPath);\n debugLog('Deleted existing database file');\n }\n } catch (fsError) {\n console.error(\n 'Error deleting database file:',\n fsError instanceof Error\n ? fsError.message\n : 'Unknown error',\n );\n }\n\n this.ensureDatabaseFileExists();\n\n this.client = createClient({\n url: `file:${this.dbPath}`,\n });\n\n debugLog(\n 'Reconnected to fresh database. Attempting initialization...',\n );\n await this.initialize();\n return;\n } catch (recreateError) {\n console.error(\n 'Failed to recreate and initialize database:',\n recreateError instanceof Error\n ? recreateError.message\n : 'Unknown error',\n );\n throw new Error(\n `Database recreation failed after ${maxAttempts} attempts`,\n );\n }\n } else {\n try {\n if (this.client) {\n try {\n await this.client.close();\n } catch (e: unknown) {\n debugLog(\n 'Error closing client:',\n e instanceof Error ? e.message : 'Unknown error',\n );\n }\n }\n\n this.ensureDatabaseFileExists();\n\n this.client = createClient({\n url: `file:${this.dbPath}`,\n });\n\n debugLog(\n 'Reconnected to database. Attempting initialization...',\n );\n await this.initialize();\n return;\n } catch (reconnectError) {\n console.error(\n 'Failed to reconnect and initialize database:',\n reconnectError instanceof Error\n ? reconnectError.message\n : 'Unknown error',\n );\n }\n }\n } else {\n console.error(\n 'Unexpected database error, not related to missing tables:',\n error,\n );\n }\n } else {\n debugLog(\n 'Unknown error type detected during initialization:',\n typeof error,\n );\n try {\n await this.initialize();\n return;\n } catch (initError) {\n console.error(\n 'Failed to initialize after unknown error:',\n initError instanceof Error ? initError.message : 'Unknown error',\n );\n }\n }\n }\n\n attempt++;\n\n if (attempt <= maxAttempts) {\n const delay = 500 * attempt;\n debugLog(`Waiting ${delay}ms before attempt ${attempt}...`);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n throw new Error(\n `Failed to initialize database after ${maxAttempts} attempts`,\n );\n }\n\n private async initialize(): Promise<void> {\n let retryCount = 0;\n const maxRetries = 3;\n\n while (retryCount < maxRetries) {\n try {\n this.ensureConfigDir();\n\n this.ensureDatabaseFileExists();\n\n try {\n await this.client.execute('PRAGMA quick_check;');\n debugLog('Database connection verified');\n } catch (connError: unknown) {\n console.error(\n 'Database connection test failed:',\n connError instanceof Error ? connError.message : 'Unknown error',\n );\n\n try {\n if (this.client) {\n try {\n await this.client.close();\n } catch {}\n }\n\n this.client = createClient({\n url: `file:${this.dbPath}`,\n });\n\n debugLog('Database client recreated');\n } catch (recreateError: unknown) {\n console.error(\n 'Failed to recreate database client:',\n recreateError instanceof Error\n ? recreateError.message\n : 'Unknown error',\n );\n throw new Error('Cannot connect to database');\n }\n }\n\n debugLog(\n `Creating database schema step by step (attempt ${retryCount + 1})...`,\n );\n\n try {\n await this.client.execute('PRAGMA journal_mode = WAL;');\n } catch (error: unknown) {\n const pragmaError =\n error instanceof Error ? error.message : 'Unknown error';\n debugLog('Warning: Failed to set journal_mode pragma:', pragmaError);\n }\n\n try {\n await this.client.execute('PRAGMA synchronous = NORMAL;');\n } catch (error: unknown) {\n const pragmaError =\n error instanceof Error ? error.message : 'Unknown error';\n debugLog('Warning: Failed to set synchronous pragma:', pragmaError);\n }\n\n try {\n await this.client.execute('PRAGMA foreign_keys = ON;');\n } catch (error: unknown) {\n const pragmaError =\n error instanceof Error ? error.message : 'Unknown error';\n debugLog('Warning: Failed to set foreign_keys pragma:', pragmaError);\n }\n\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS chat_sessions (\n id TEXT PRIMARY KEY,\n profile TEXT NOT NULL,\n title TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n message_count INTEGER NOT NULL DEFAULT 0\n );\n `);\n\n debugLog('Created chat_sessions table');\n\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS chat_messages (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL,\n role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),\n content TEXT NOT NULL,\n timestamp TEXT NOT NULL DEFAULT (datetime('now')),\n provider TEXT NOT NULL,\n model TEXT NOT NULL,\n temperature REAL,\n max_tokens INTEGER,\n metadata TEXT,\n FOREIGN KEY (session_id) REFERENCES chat_sessions (id) ON DELETE CASCADE\n );\n `);\n\n debugLog('Created chat_messages table');\n\n try {\n await this.client.execute(`\n CREATE TRIGGER IF NOT EXISTS update_session_message_count_insert\n AFTER INSERT ON chat_messages\n BEGIN\n UPDATE chat_sessions \n SET message_count = (\n SELECT COUNT(*) FROM chat_messages WHERE session_id = NEW.session_id\n ),\n updated_at = datetime('now')\n WHERE id = NEW.session_id;\n END;\n `);\n\n await this.client.execute(`\n CREATE TRIGGER IF NOT EXISTS update_session_message_count_delete\n AFTER DELETE ON chat_messages\n BEGIN\n UPDATE chat_sessions \n SET message_count = (\n SELECT COUNT(*) FROM chat_messages WHERE session_id = OLD.session_id\n ),\n updated_at = datetime('now')\n WHERE id = OLD.session_id;\n END;\n `);\n\n await this.client.execute(`\n CREATE TRIGGER IF NOT EXISTS generate_session_title\n AFTER INSERT ON chat_messages\n WHEN NEW.role = 'user' AND (\n SELECT title FROM chat_sessions WHERE id = NEW.session_id\n ) IS NULL\n BEGIN\n UPDATE chat_sessions \n SET title = CASE \n WHEN length(NEW.content) > ${DEFAULT_SESSION_TITLE_LENGTH} \n THEN substr(NEW.content, 1, ${DEFAULT_SESSION_TITLE_LENGTH}) || '...'\n ELSE NEW.content\n END\n WHERE id = NEW.session_id;\n END;\n `);\n\n debugLog('Created triggers');\n } catch (error: unknown) {\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown error';\n debugLog('Warning: Failed to create triggers:', errorMessage);\n debugLog('Continuing with basic functionality');\n }\n\n try {\n await this.client.execute(`\n CREATE INDEX IF NOT EXISTS idx_chat_sessions_profile \n ON chat_sessions (profile);\n `);\n\n await this.client.execute(`\n CREATE INDEX IF NOT EXISTS idx_chat_sessions_created_at \n ON chat_sessions (created_at DESC);\n `);\n\n await this.client.execute(`\n CREATE INDEX IF NOT EXISTS idx_chat_messages_session_id \n ON chat_messages (session_id);\n `);\n\n await this.client.execute(`\n CREATE INDEX IF NOT EXISTS idx_chat_messages_timestamp \n ON chat_messages (timestamp DESC);\n `);\n\n await this.client.execute(`\n CREATE INDEX IF NOT EXISTS idx_chat_messages_provider \n ON chat_messages (provider);\n `);\n\n await this.client.execute(`\n CREATE INDEX IF NOT EXISTS idx_chat_messages_content_fts \n ON chat_messages (content);\n `);\n\n debugLog('Created indexes');\n } catch (error: unknown) {\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown error';\n debugLog('Warning: Failed to create indexes:', errorMessage);\n debugLog('Continuing with basic functionality');\n }\n\n const tableResult = await this.client.execute(`\n SELECT count(*) as table_count FROM sqlite_master \n WHERE type='table' AND name IN ('chat_sessions', 'chat_messages');\n `);\n\n const tableCount = Number(tableResult.rows[0].table_count);\n\n if (tableCount !== 2) {\n throw new Error(`Expected 2 tables but found ${tableCount}`);\n }\n\n debugLog('Database schema initialized successfully');\n return;\n } catch (error) {\n console.error(\n `Error initializing database schema (attempt ${retryCount + 1}):`,\n error,\n );\n retryCount++;\n\n if (retryCount < maxRetries) {\n const delay = 500 * retryCount;\n debugLog(`Waiting ${delay}ms before retry...`);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n throw new Error(\n `Failed to initialize database after ${maxRetries} attempts`,\n );\n }\n\n async createSession(profile: string, title?: string): Promise<string> {\n await this.ensureDatabaseInitialized();\n\n const sessionId = uuidv4();\n\n await this.client.execute({\n sql: 'INSERT INTO chat_sessions (id, profile, title) VALUES (?, ?, ?)',\n args: [sessionId, profile, title || null],\n });\n\n return sessionId;\n }\n\n async getSession(sessionId: string): Promise<ChatSession | null> {\n await this.ensureDatabaseInitialized();\n const result = await this.client.execute({\n sql: 'SELECT * FROM chat_sessions WHERE id = ?',\n args: [sessionId],\n });\n\n if (result.rows.length === 0) {\n return null;\n }\n\n const row = result.rows[0];\n return {\n id: row.id as string,\n profile: row.profile as string,\n title: (row.title as string) ?? undefined,\n createdAt: row.created_at as string,\n updatedAt: row.updated_at as string,\n messageCount: Number(row.message_count),\n };\n }\n\n async getSessions(options: ChatHistoryOptions = {}): Promise<ChatSession[]> {\n await this.ensureDatabaseInitialized();\n const {profile, limit = 10, fromDate, toDate} = options;\n\n let sql = `\n SELECT * FROM chat_sessions\n WHERE 1=1\n `;\n const args: any[] = [];\n\n if (profile) {\n sql += ' AND profile = ?';\n args.push(profile);\n }\n\n if (fromDate) {\n sql += ' AND created_at >= ?';\n args.push(fromDate);\n }\n\n if (toDate) {\n sql += ' AND created_at <= ?';\n args.push(toDate);\n }\n\n sql += ' ORDER BY updated_at DESC LIMIT ?';\n args.push(limit);\n\n const result = await this.client.execute({sql, args});\n\n return result.rows.map((row) => ({\n id: row.id as string,\n profile: row.profile as string,\n title: (row.title as string) ?? undefined,\n createdAt: row.created_at as string,\n updatedAt: row.updated_at as string,\n messageCount: Number(row.message_count),\n }));\n }\n\n async deleteSession(sessionId: string): Promise<boolean> {\n await this.ensureDatabaseInitialized();\n const result = await this.client.execute({\n sql: 'DELETE FROM chat_sessions WHERE id = ?',\n args: [sessionId],\n });\n\n return result.rowsAffected > 0;\n }\n\n async updateSessionTitle(sessionId: string, title: string): Promise<boolean> {\n const result = await this.client.execute({\n sql: `UPDATE chat_sessions SET title = ?, updated_at = datetime('now') WHERE id = ?`,\n args: [title, sessionId],\n });\n\n return result.rowsAffected > 0;\n }\n\n async addMessage(\n sessionId: string,\n role: 'user' | 'assistant',\n content: string,\n provider: string,\n model: string,\n temperature?: number,\n maxTokens?: number,\n metadata?: any,\n ): Promise<string> {\n await this.ensureDatabaseInitialized();\n const messageId = uuidv4();\n\n await this.client.execute({\n sql: `INSERT INTO chat_messages (\n id, session_id, role, content, provider, model, \n temperature, max_tokens, metadata\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n messageId,\n sessionId,\n role,\n content,\n provider,\n model,\n temperature || null,\n maxTokens || null,\n metadata ? JSON.stringify(metadata) : null,\n ],\n });\n\n return messageId;\n }\n\n async getMessages(sessionId: string, limit?: number): Promise<ChatMessage[]> {\n await this.ensureDatabaseInitialized();\n let sql = `\n SELECT * FROM chat_messages \n WHERE session_id = ? \n ORDER BY timestamp ASC\n `;\n const args: any[] = [sessionId];\n\n if (limit) {\n sql += ' LIMIT ?';\n args.push(limit);\n }\n\n const result = await this.client.execute({sql, args});\n\n return result.rows.map((row) => ({\n id: row.id as string,\n sessionId: row.session_id as string,\n role: row.role as 'user' | 'assistant',\n content: row.content as string,\n timestamp: row.timestamp as string,\n provider: row.provider as SupportedProvider,\n model: row.model as string,\n temperature: row.temperature as number | undefined,\n maxTokens: row.max_tokens as number | undefined,\n metadata: row.metadata ? JSON.parse(row.metadata as string) : null,\n }));\n }\n\n async searchMessages(\n options: ChatHistoryOptions = {},\n ): Promise<ChatMessage[]> {\n await this.ensureDatabaseInitialized();\n const {\n profile,\n search,\n limit = 10,\n fromDate,\n toDate,\n provider,\n model,\n } = options;\n\n let sql = `\n SELECT m.* FROM chat_messages m\n JOIN chat_sessions s ON m.session_id = s.id\n WHERE 1=1\n `;\n const args: any[] = [];\n\n if (profile) {\n sql += ' AND s.profile = ?';\n args.push(profile);\n }\n\n if (search) {\n sql += ' AND m.content LIKE ?';\n args.push(`%${search}%`);\n }\n\n if (provider) {\n sql += ' AND m.provider = ?';\n args.push(provider);\n }\n\n if (model) {\n sql += ' AND m.model = ?';\n args.push(model);\n }\n\n if (fromDate) {\n sql += ' AND m.timestamp >= ?';\n args.push(fromDate);\n }\n\n if (toDate) {\n sql += ' AND m.timestamp <= ?';\n args.push(toDate);\n }\n\n sql += ' ORDER BY m.timestamp DESC LIMIT ?';\n args.push(limit);\n\n const result = await this.client.execute({sql, args});\n\n return result.rows.map((row) => ({\n id: row.id as string,\n sessionId: row.session_id as string,\n role: row.role as 'user' | 'assistant',\n content: row.content as string,\n timestamp: row.timestamp as string,\n provider: row.provider as SupportedProvider,\n model: row.model as string,\n temperature: row.temperature as number | undefined,\n maxTokens: row.max_tokens as number | undefined,\n metadata: row.metadata ? JSON.parse(row.metadata as string) : null,\n }));\n }\n\n async getStats(profile?: string): Promise<HistoryStats> {\n await this.ensureDatabaseInitialized();\n let sql = 'SELECT COUNT(*) as count FROM chat_sessions';\n const args: any[] = [];\n\n if (profile) {\n sql += ' WHERE profile = ?';\n args.push(profile);\n }\n\n const sessionResult = await this.client.execute({sql, args});\n const totalSessions = Number(sessionResult.rows[0].count);\n\n let messageSql = `\n SELECT \n COUNT(*) as total_count,\n SUM(CASE WHEN role = 'user' THEN 1 ELSE 0 END) as user_count,\n SUM(CASE WHEN role = 'assistant' THEN 1 ELSE 0 END) as assistant_count\n FROM chat_messages\n `;\n const messageArgs: any[] = [];\n\n if (profile) {\n messageSql += `\n JOIN chat_sessions ON chat_messages.session_id = chat_sessions.id\n WHERE chat_sessions.profile = ?\n `;\n messageArgs.push(profile);\n }\n\n const messageResult = await this.client.execute({\n sql: messageSql,\n args: messageArgs,\n });\n\n const totalMessages = Number(messageResult.rows[0].total_count);\n\n let providerSql = `\n SELECT provider, COUNT(*) as count\n FROM chat_messages\n `;\n const providerArgs: any[] = [];\n\n if (profile) {\n providerSql += `\n JOIN chat_sessions ON chat_messages.session_id = chat_sessions.id\n WHERE chat_sessions.profile = ?\n `;\n providerArgs.push(profile);\n }\n\n providerSql += ' GROUP BY provider ORDER BY count DESC';\n\n const providerResult = await this.client.execute({\n sql: providerSql,\n args: providerArgs,\n });\n\n const messagesByProvider: Record<string, number> = {};\n providerResult.rows.forEach((row) => {\n messagesByProvider[row.provider as string] = Number(row.count);\n });\n\n let modelSql = `\n SELECT model, COUNT(*) as count\n FROM chat_messages\n `;\n const modelArgs: any[] = [];\n\n if (profile) {\n modelSql += `\n JOIN chat_sessions ON chat_messages.session_id = chat_sessions.id\n WHERE chat_sessions.profile = ?\n `;\n modelArgs.push(profile);\n }\n\n modelSql += ' GROUP BY model ORDER BY count DESC';\n\n const modelResult = await this.client.execute({\n sql: modelSql,\n args: modelArgs,\n });\n\n const messagesByModel: Record<string, number> = {};\n modelResult.rows.forEach((row) => {\n messagesByModel[row.model as string] = Number(row.count);\n });\n\n const profileSql = `\n SELECT chat_sessions.profile, COUNT(*) as count\n FROM chat_messages\n JOIN chat_sessions ON chat_messages.session_id = chat_sessions.id\n GROUP BY chat_sessions.profile\n ORDER BY count DESC\n `;\n\n const profileResult = await this.client.execute({sql: profileSql});\n\n const messagesByProfile: Record<string, number> = {};\n profileResult.rows.forEach((row) => {\n messagesByProfile[row.profile as string] = Number(row.count);\n });\n\n let timeRangeSql = `\n SELECT \n MIN(timestamp) as oldest,\n MAX(timestamp) as newest\n FROM chat_messages\n `;\n const timeRangeArgs: any[] = [];\n\n if (profile) {\n timeRangeSql += `\n JOIN chat_sessions ON chat_messages.session_id = chat_sessions.id\n WHERE chat_sessions.profile = ?\n `;\n timeRangeArgs.push(profile);\n }\n\n const timeRangeResult = await this.client.execute({\n sql: timeRangeSql,\n args: timeRangeArgs,\n });\n\n return {\n totalSessions,\n totalMessages,\n messagesByProvider,\n messagesByModel,\n messagesByProfile,\n oldestMessage: timeRangeResult.rows[0].oldest as string,\n newestMessage: timeRangeResult.rows[0].newest as string,\n };\n }\n\n async deleteOldSessions(profile: string, days: number): Promise<number> {\n await this.ensureDatabaseInitialized();\n const date = new Date();\n date.setDate(date.getDate() - days);\n const cutoffDate = date.toISOString();\n\n const result = await this.client.execute({\n sql: `\n DELETE FROM chat_sessions \n WHERE profile = ? AND created_at < ?\n `,\n args: [profile, cutoffDate],\n });\n\n return result.rowsAffected;\n }\n\n async vacuum(): Promise<void> {\n await this.ensureDatabaseInitialized();\n await this.client.execute({sql: 'VACUUM'});\n }\n\n async exportChatHistory(options: ChatHistoryOptions = {}): Promise<{\n sessions: ChatSession[];\n messages: Record<string, ChatMessage[]>;\n stats: HistoryStats;\n }> {\n await this.ensureDatabaseInitialized();\n const {profile} = options;\n\n const sessions = await this.getSessions({\n profile,\n limit: 1000,\n });\n\n const messages: Record<string, ChatMessage[]> = {};\n for (const session of sessions) {\n messages[session.id] = await this.getMessages(session.id);\n }\n\n const stats = await this.getStats(profile);\n\n return {\n sessions,\n messages,\n stats,\n };\n }\n\n async close(): Promise<void> {\n await this.client.close();\n }\n}\n"]}