From 3a8defed0953ffde99ad4374f0cd841c55d24305 Mon Sep 17 00:00:00 2001 From: bambuvnn <114802750+bambuvnn@users.noreply.github.com> Date: Fri, 1 May 2026 18:35:52 +0700 Subject: [PATCH] fix: tolerate missing request detail logs table (#1848) Integrated into release/v3.7.8 --- src/lib/db/detailedLogs.ts | 25 ++++++++++++++++++++++++- tests/unit/db-detailed-logs.test.ts | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/lib/db/detailedLogs.ts b/src/lib/db/detailedLogs.ts index d879cf96b1..f811e751a0 100644 --- a/src/lib/db/detailedLogs.ts +++ b/src/lib/db/detailedLogs.ts @@ -33,6 +33,25 @@ export interface RequestDetailLog { no_log?: boolean; } +let requestDetailLogsTableExistsCache: boolean | undefined; + +function requestDetailLogsTableExists(): boolean { + if (requestDetailLogsTableExistsCache !== undefined) { + return requestDetailLogsTableExistsCache; + } + + const db = getDbInstance(); + const row = db + .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'request_detail_logs'") + .get() as { name?: string } | undefined; + requestDetailLogsTableExistsCache = Boolean(row?.name); + return requestDetailLogsTableExistsCache; +} + +export function resetRequestDetailLogsTableExistsCache(): void { + requestDetailLogsTableExistsCache = undefined; +} + /** Returns true if detailed logging is enabled in settings */ export async function isDetailedLoggingEnabled(): Promise { try { @@ -48,7 +67,7 @@ export async function isDetailedLoggingEnabled(): Promise { export function saveRequestDetailLog(entry: RequestDetailLog): void { const noLogEnabled = Boolean(entry.no_log) || (entry.api_key_id ? isNoLog(entry.api_key_id) : false); - if (noLogEnabled) return; + if (noLogEnabled || !requestDetailLogsTableExists()) return; const db = getDbInstance(); const id = entry.id ?? uuidv4(); @@ -81,6 +100,7 @@ export function saveRequestDetailLog(entry: RequestDetailLog): void { /** Fetch detailed logs (latest first) */ export function getRequestDetailLogs(limit = 50, offset = 0): RequestDetailLog[] { + if (!requestDetailLogsTableExists()) return []; const db = getDbInstance(); const rows = db .prepare( @@ -97,6 +117,7 @@ export function getRequestDetailLogs(limit = 50, offset = 0): RequestDetailLog[] /** Get a single detailed log by ID */ export function getRequestDetailLogById(id: string): RequestDetailLog | null { + if (!requestDetailLogsTableExists()) return null; const db = getDbInstance(); const row = db.prepare("SELECT * FROM request_detail_logs WHERE id = ?").get(id) as | Record @@ -106,6 +127,7 @@ export function getRequestDetailLogById(id: string): RequestDetailLog | null { /** Get the most recent detailed log for a call log ID */ export function getRequestDetailLogByCallLogId(callLogId: string): RequestDetailLog | null { + if (!requestDetailLogsTableExists()) return null; const db = getDbInstance(); const row = db .prepare( @@ -122,6 +144,7 @@ export function getRequestDetailLogByCallLogId(callLogId: string): RequestDetail /** Get total count of detailed logs */ export function getRequestDetailLogCount(): number { + if (!requestDetailLogsTableExists()) return 0; const db = getDbInstance(); const row = db.prepare("SELECT COUNT(*) as cnt FROM request_detail_logs").get() as { cnt: number; diff --git a/tests/unit/db-detailed-logs.test.ts b/tests/unit/db-detailed-logs.test.ts index acdcc088b3..639be62404 100644 --- a/tests/unit/db-detailed-logs.test.ts +++ b/tests/unit/db-detailed-logs.test.ts @@ -23,6 +23,7 @@ const { createStructuredSSECollector } = async function resetStorage() { core.resetDbInstance(); apiKeysDb.resetApiKeyState(); + detailedLogsDb.resetRequestDetailLogsTableExistsCache(); for (let attempt = 0; attempt < 10; attempt++) { try { @@ -72,6 +73,24 @@ test("isDetailedLoggingEnabled follows the stored setting", async () => { assert.equal(await detailedLogsDb.isDetailedLoggingEnabled(), true); }); +test("legacy detailed log helpers tolerate databases without request_detail_logs", () => { + const db = core.getDbInstance(); + db.exec("DROP TABLE request_detail_logs"); + + assert.doesNotThrow(() => + detailedLogsDb.saveRequestDetailLog({ + id: "missing-table-write", + call_log_id: "call-missing-table", + provider: "openai", + model: "gpt-4.1", + }) + ); + assert.deepEqual(detailedLogsDb.getRequestDetailLogs(), []); + assert.equal(detailedLogsDb.getRequestDetailLogCount(), 0); + assert.equal(detailedLogsDb.getRequestDetailLogById("missing-table-write"), null); + assert.equal(detailedLogsDb.getRequestDetailLogByCallLogId("call-missing-table"), null); +}); + test("saveRequestDetailLog persists protected payloads and compacted stream summaries", () => { const collector = createStructuredSSECollector({ stage: "provider-response" }); collector.push({