diff --git a/src/providers/sqlite-session-parser.ts b/src/providers/sqlite-session-parser.ts index 7d81e9d8..b1e962f0 100644 --- a/src/providers/sqlite-session-parser.ts +++ b/src/providers/sqlite-session-parser.ts @@ -36,7 +36,21 @@ type SessionTokenRow = { tokens_reasoning?: number tokens_cache_read?: number tokens_cache_write?: number - model_id?: string + model?: Uint8Array | string +} + +function parseSessionModel(value: Uint8Array | string | undefined): string | undefined { + try { + const parsed: unknown = JSON.parse(blobToText(value)) + if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return undefined + + const model = parsed as Record + const id = typeof model['id'] === 'string' ? model['id'].trim() : '' + const providerID = typeof model['providerID'] === 'string' ? model['providerID'].trim() : '' + return id && providerID ? `${providerID}/${id}` : undefined + } catch { + return undefined + } } function tryQuerySessionTokens(db: SqliteDatabase, sessionId: string): { @@ -45,7 +59,9 @@ function tryQuerySessionTokens(db: SqliteDatabase, sessionId: string): { } | null { try { const rows = db.query( - `SELECT cost, tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, tokens_cache_write, model_id FROM session WHERE id = ?`, + `SELECT cost, tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, tokens_cache_write, + CAST(model AS BLOB) AS model + FROM session WHERE id = ?`, [sessionId], ) if (rows.length === 0) return null @@ -57,7 +73,7 @@ function tryQuerySessionTokens(db: SqliteDatabase, sessionId: string): { reasoning: r.tokens_reasoning ?? 0, cacheRead: r.tokens_cache_read ?? 0, cacheWrite: r.tokens_cache_write ?? 0, - model: r.model_id ?? undefined, + model: parseSessionModel(r.model), } } catch { return null @@ -324,4 +340,3 @@ export async function discoverSqliteSessions( return sessions } - diff --git a/src/session-cache.ts b/src/session-cache.ts index 461dd461..5daff31c 100644 --- a/src/session-cache.ts +++ b/src/session-cache.ts @@ -222,9 +222,10 @@ export const PROVIDER_PARSE_VERSIONS: Record = { 'lingtai-tui': 'token-ledger-registry-activity-v3', 'ibm-bob': 'worktree-project-grouping-v1', kiro: 'ide-parsing-v1-est-cost', + opencode: 'session-model-v1', quickdesk: 'emf-sqlite-v2-est-cost', kimicode: 'wire-usage-v1-est-cost', - 'kilo-code': 'worktree-project-grouping-v1', + 'kilo-code': 'worktree-project-grouping-v1-session-model-v1', 'roo-code': 'worktree-project-grouping-v1', warp: 'worktree-project-grouping-v1-est-cost', antigravity: 'worktree-project-grouping-v5', diff --git a/tests/providers/opencode.test.ts b/tests/providers/opencode.test.ts index 553f4571..494cdac6 100644 --- a/tests/providers/opencode.test.ts +++ b/tests/providers/opencode.test.ts @@ -823,10 +823,15 @@ skipUnlessSqlite('opencode provider - session parsing', () => { db.exec(`ALTER TABLE session ADD COLUMN tokens_reasoning INTEGER`) db.exec(`ALTER TABLE session ADD COLUMN tokens_cache_read INTEGER`) db.exec(`ALTER TABLE session ADD COLUMN tokens_cache_write INTEGER`) - db.exec(`ALTER TABLE session ADD COLUMN model_id TEXT`) + db.exec(`ALTER TABLE session ADD COLUMN model TEXT`) insertSession(db, 'sess-1') - db.prepare(`UPDATE session SET cost = 0.15, tokens_input = 5000, tokens_output = 2000, tokens_reasoning = 0, tokens_cache_read = 3000, tokens_cache_write = 1000, model_id = 'claude-sonnet-4-20250514' WHERE id = 'sess-1'`).run() + db.prepare(`UPDATE session SET cost = ?, tokens_input = ?, tokens_output = ?, tokens_reasoning = ?, tokens_cache_read = ?, tokens_cache_write = ?, model = ? WHERE id = ?`) + .run(0.15, 5000, 2000, 0, 3000, 1000, JSON.stringify({ + providerID: 'anthropic', + id: 'claude-sonnet-4-20250514', + variant: 'high', + }), 'sess-1') insertMessage(db, 'msg-1', 'sess-1', 1700000001000, { role: 'assistant', modelID: 'claude-sonnet-4-20250514', @@ -840,7 +845,7 @@ skipUnlessSqlite('opencode provider - session parsing', () => { expect(calls[0]!.cacheReadInputTokens).toBe(3000) expect(calls[0]!.cacheCreationInputTokens).toBe(1000) expect(calls[0]!.costUSD).toBeGreaterThan(0) - expect(calls[0]!.model).toBe('claude-sonnet-4-20250514') + expect(calls[0]!.model).toBe('anthropic/claude-sonnet-4-20250514') expect(calls[0]!.deduplicationKey).toBe('opencode:sess-1:session-level') })