From 2e41c6be0aa754bb01294d73d1d5d07b37ee01bb Mon Sep 17 00:00:00 2001 From: Neil Date: Sun, 20 Sep 2026 20:07:52 -0700 Subject: [PATCH] fix(opencode): include cache read and write usage totals --- .../opencode-usage-row-queries.ts | 30 +++++++++++++++---- src/main/opencode-usage/scanner.test.ts | 16 ++++++---- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/main/opencode-usage/opencode-usage-row-queries.ts b/src/main/opencode-usage/opencode-usage-row-queries.ts index 2e633d7cda2..ab7f34b759c 100644 --- a/src/main/opencode-usage/opencode-usage-row-queries.ts +++ b/src/main/opencode-usage/opencode-usage-row-queries.ts @@ -27,6 +27,7 @@ type OpenCodeSessionUsageRow = { tokens_output: number tokens_reasoning: number tokens_cache_read: number + tokens_cache_write: number } function getProjectJoin(db: Database.Database): string { @@ -61,6 +62,17 @@ function canReadSessionUsageRows(db: Database.Database): boolean { ) } +function getSessionCacheWriteSelect(db: Database.Database): string { + return columnExists(db, 'session', 'tokens_cache_write') ? 's.tokens_cache_write' : '0' +} + +function getSessionTokenTotalExpression(db: Database.Database): string { + const cacheWrite = columnExists(db, 'session', 'tokens_cache_write') + ? ' + tokens_cache_write' + : '' + return `tokens_input + tokens_output + tokens_reasoning + tokens_cache_read${cacheWrite}` +} + function getSessionUsageRowCount(db: Database.Database): number { if (!canReadSessionUsageRows(db)) { return 0 @@ -69,7 +81,7 @@ function getSessionUsageRowCount(db: Database.Database): number { .prepare( `SELECT COUNT(*) AS count FROM session - WHERE tokens_input + tokens_output + tokens_reasoning + tokens_cache_read > 0` + WHERE ${getSessionTokenTotalExpression(db)} > 0` ) .get() as { count?: number } | undefined return row?.count ?? 0 @@ -78,14 +90,17 @@ function getSessionUsageRowCount(db: Database.Database): number { function selectSessionUsageRows(db: Database.Database): OpenCodeUsageRow[] { const projectJoin = getProjectJoin(db) const sessionModelSelect = getSessionModelSelect(db) + const cacheWriteSelect = getSessionCacheWriteSelect(db) + const tokenTotalExpression = getSessionTokenTotalExpression(db) const rows = db .prepare( `SELECT s.id, s.id AS session_id, s.time_created, s.time_updated, s.directory, s.title, p.worktree, ${sessionModelSelect}, - s.cost, s.tokens_input, s.tokens_output, s.tokens_reasoning, s.tokens_cache_read + s.cost, s.tokens_input, s.tokens_output, s.tokens_reasoning, s.tokens_cache_read, + ${cacheWriteSelect} AS tokens_cache_write FROM session s ${projectJoin} - WHERE s.tokens_input + s.tokens_output + s.tokens_reasoning + s.tokens_cache_read > 0 + WHERE ${tokenTotalExpression.replaceAll('tokens_', 's.tokens_')} > 0 ORDER BY s.time_created, s.id` ) .all() as OpenCodeSessionUsageRow[] @@ -105,10 +120,15 @@ function selectSessionUsageRows(db: Database.Database): OpenCodeUsageRow[] { input: row.tokens_input, output: row.tokens_output, reasoning: row.tokens_reasoning, - total: row.tokens_input + row.tokens_output + row.tokens_reasoning, + total: + row.tokens_input + + row.tokens_output + + row.tokens_reasoning + + row.tokens_cache_read + + row.tokens_cache_write, cache: { read: row.tokens_cache_read, - write: 0 + write: row.tokens_cache_write } } }) diff --git a/src/main/opencode-usage/scanner.test.ts b/src/main/opencode-usage/scanner.test.ts index 4188a0f93a9..88559b0ec7d 100644 --- a/src/main/opencode-usage/scanner.test.ts +++ b/src/main/opencode-usage/scanner.test.ts @@ -43,6 +43,7 @@ function createSessionTotalsSchema(db: Database.Database): void { tokens_output INTEGER, tokens_reasoning INTEGER, tokens_cache_read INTEGER, + tokens_cache_write INTEGER, time_created INTEGER, time_updated INTEGER ); @@ -57,9 +58,9 @@ function insertSessionTotalsRow( db.prepare( `INSERT INTO session ( id, directory, title, model, cost, - tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, + tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, tokens_cache_write, time_created, time_updated - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` ).run( sessionId, `${WORKTREE}/packages/app`, @@ -70,6 +71,7 @@ function insertSessionTotalsRow( 100, 0, 0, + 0, 1_777_777_700_000, 1_777_777_800_000 ) @@ -235,6 +237,7 @@ describe('parseOpenCodeUsageDatabase', () => { tokens_output INTEGER, tokens_reasoning INTEGER, tokens_cache_read INTEGER, + tokens_cache_write INTEGER, time_created INTEGER, time_updated INTEGER ); @@ -243,9 +246,9 @@ describe('parseOpenCodeUsageDatabase', () => { db.prepare( `INSERT INTO session ( id, project_id, directory, title, model, cost, - tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, + tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, tokens_cache_write, time_created, time_updated - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` ).run( 'session-1', 'project-1', @@ -257,6 +260,7 @@ describe('parseOpenCodeUsageDatabase', () => { 500, 100, 250, + 75, 1_777_777_700_000, 1_777_777_800_000 ) @@ -274,7 +278,7 @@ describe('parseOpenCodeUsageDatabase', () => { totalCachedInputTokens: 250, totalOutputTokens: 500, totalReasoningOutputTokens: 100, - totalTokens: 1850, + totalTokens: 1925, estimatedCostUsd: 0.06 }) expect(parsed.dailyAggregates).toEqual([ @@ -284,7 +288,7 @@ describe('parseOpenCodeUsageDatabase', () => { cachedInputTokens: 250, outputTokens: 500, reasoningOutputTokens: 100, - totalTokens: 1850, + totalTokens: 1925, estimatedCostUsd: 0.06 }) ])