diff --git a/server/gemini-response-handler.js b/server/gemini-response-handler.js index f57ad072..6f314443 100644 --- a/server/gemini-response-handler.js +++ b/server/gemini-response-handler.js @@ -7,10 +7,13 @@ function buildGeminiTokenBudget(tokens) { return null; } - const inputTokens = Number(tokens.input || 0); - const outputTokens = Number(tokens.output || 0); - const used = Number(tokens.total || inputTokens + outputTokens || 0); - if (used <= 0) { + const parsedInputTokens = Number(tokens.input); + const parsedOutputTokens = Number(tokens.output); + const inputTokens = Number.isFinite(parsedInputTokens) ? parsedInputTokens : 0; + const outputTokens = Number.isFinite(parsedOutputTokens) ? parsedOutputTokens : 0; + const parsedUsed = Number(tokens.total); + const used = Number.isFinite(parsedUsed) ? parsedUsed : inputTokens + outputTokens; + if (!Number.isFinite(used) || used <= 0) { return null; } diff --git a/server/opencode-cli.js b/server/opencode-cli.js index 119c5ee4..6f3e0f65 100644 --- a/server/opencode-cli.js +++ b/server/opencode-cli.js @@ -28,8 +28,9 @@ function readOpenCodeTokenUsage(sessionId) { return null; } - const db = new Database(dbPath, { readonly: true, fileMustExist: true }); + let db = null; try { + db = new Database(dbPath, { readonly: true, fileMustExist: true }); const columns = db.prepare('PRAGMA table_info(session)').all(); const columnNames = new Set(columns.map((column) => column.name)); const requiredColumns = ['tokens_input', 'tokens_output', 'tokens_reasoning', 'tokens_cache_read', 'tokens_cache_write']; @@ -72,8 +73,12 @@ function readOpenCodeTokenUsage(sessionId) { output: outputTokens, }, }; + } catch { + return null; } finally { - db.close(); + if (db) { + db.close(); + } } } diff --git a/server/routes/commands.js b/server/routes/commands.js index 82a0e7e0..09022e0c 100644 --- a/server/routes/commands.js +++ b/server/routes/commands.js @@ -291,12 +291,6 @@ Custom commands can be created in: const hasTokenBreakdown = inputTokensRaw > 0 || outputTokens > 0; const used = reportedUsed || inputTokensRaw + outputTokens; - // If we only have total used tokens, keep the list populated without guessing output. - const inputTokens = - hasTokenBreakdown - ? inputTokensRaw - : used; - return { type: "builtin", action: "cost", @@ -305,10 +299,14 @@ Custom commands can be created in: used, total, }, - tokenBreakdown: { - input: inputTokens, - output: outputTokens, - }, + ...(hasTokenBreakdown + ? { + tokenBreakdown: { + input: inputTokensRaw, + output: outputTokens, + }, + } + : {}), provider, model, }, diff --git a/src/components/chat/view/subcomponents/CommandResultModal.tsx b/src/components/chat/view/subcomponents/CommandResultModal.tsx index d51f7c96..2e391ebe 100644 --- a/src/components/chat/view/subcomponents/CommandResultModal.tsx +++ b/src/components/chat/view/subcomponents/CommandResultModal.tsx @@ -495,12 +495,31 @@ function CostContent({ data }: { data: CostCommandData }) { const total = Number(data.tokenUsage?.total ?? 0); const model = data.model || 'Unknown'; const provider = getProviderLabel(data.provider, data.provider || 'Unknown'); - const inputTokens = Number(data.tokenBreakdown?.input ?? 0); - const outputTokens = Number(data.tokenBreakdown?.output ?? 0); + const hasBreakdown = + typeof data.tokenBreakdown?.input === 'number' || + typeof data.tokenBreakdown?.output === 'number'; const usageRows = [ { label: 'Total tokens used', value: formatNumber(used), icon: Activity }, - { label: 'Input tokens', value: formatNumber(inputTokens), icon: TerminalSquare }, - { label: 'Output tokens', value: formatNumber(outputTokens), icon: Coins }, + ...(hasBreakdown + ? [ + { + label: 'Input tokens', + value: formatNumber(Number(data.tokenBreakdown?.input ?? 0)), + icon: TerminalSquare, + }, + { + label: 'Output tokens', + value: formatNumber(Number(data.tokenBreakdown?.output ?? 0)), + icon: Coins, + }, + ] + : [ + { + label: 'Breakdown', + value: 'Unavailable', + icon: TerminalSquare, + }, + ]), ...(total > 0 ? [{ label: 'Context window', value: formatNumber(total), icon: Gauge }] : []),