mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-31 17:35:30 +08:00
fix: refine token usage reporting (#807)
This commit is contained in:
@@ -88,22 +88,15 @@ function buildGeminiTokenUsage(tokens: unknown): AnyRecord | undefined {
|
||||
const record = tokens as AnyRecord;
|
||||
const input = Number(record.input || 0);
|
||||
const output = Number(record.output || 0);
|
||||
const cached = Number(record.cached || 0);
|
||||
const thoughts = Number(record.thoughts || 0);
|
||||
const tool = Number(record.tool || 0);
|
||||
|
||||
const totalFromFields = input + output + cached + thoughts + tool;
|
||||
const total = Number(record.total || totalFromFields || 0);
|
||||
const total = Number(record.total || input + output || 0);
|
||||
|
||||
return {
|
||||
used: total,
|
||||
total: total,
|
||||
inputTokens: input,
|
||||
outputTokens: output,
|
||||
breakdown: {
|
||||
input,
|
||||
output,
|
||||
cached,
|
||||
thoughts,
|
||||
tool,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ type OpenCodeHistoryRow = {
|
||||
type OpenCodeTokenTotals = {
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
cacheReadTokens: number;
|
||||
cacheCreationTokens: number;
|
||||
reasoningTokens: number;
|
||||
cacheReadTokens: number;
|
||||
cacheWriteTokens: number;
|
||||
};
|
||||
|
||||
const openOpenCodeDatabase = (): Database.Database | null => {
|
||||
@@ -106,11 +106,13 @@ const buildTokenUsage = (totals: OpenCodeTokenTotals | undefined): AnyRecord | u
|
||||
}
|
||||
|
||||
const inputTokens = totals.inputTokens;
|
||||
const displayInputTokens = inputTokens + totals.cacheReadTokens;
|
||||
const outputTokens = totals.outputTokens;
|
||||
const cacheReadTokens = totals.cacheReadTokens;
|
||||
const cacheCreationTokens = totals.cacheCreationTokens;
|
||||
const reasoningTokens = totals.reasoningTokens;
|
||||
const used = inputTokens + outputTokens + cacheReadTokens + cacheCreationTokens + reasoningTokens;
|
||||
const used = inputTokens
|
||||
+ outputTokens
|
||||
+ totals.reasoningTokens
|
||||
+ totals.cacheReadTokens
|
||||
+ totals.cacheWriteTokens;
|
||||
|
||||
if (used <= 0) {
|
||||
return undefined;
|
||||
@@ -118,14 +120,50 @@ const buildTokenUsage = (totals: OpenCodeTokenTotals | undefined): AnyRecord | u
|
||||
|
||||
return {
|
||||
used,
|
||||
total: used,
|
||||
inputTokens,
|
||||
inputTokens: displayInputTokens,
|
||||
outputTokens,
|
||||
cacheReadTokens,
|
||||
cacheCreationTokens,
|
||||
breakdown: {
|
||||
input: displayInputTokens,
|
||||
output: outputTokens,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const readOpenCodeSessionColumnTokenUsage = (
|
||||
db: Database.Database,
|
||||
sessionId: string,
|
||||
): AnyRecord | undefined => {
|
||||
const columns = db.prepare('PRAGMA table_info(session)').all() as { name: string }[];
|
||||
const columnNames = new Set(columns.map((column) => column.name));
|
||||
const requiredColumns = ['tokens_input', 'tokens_output', 'tokens_reasoning', 'tokens_cache_read', 'tokens_cache_write'];
|
||||
if (!requiredColumns.every((column) => columnNames.has(column))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const row = db.prepare(`
|
||||
SELECT
|
||||
tokens_input AS inputTokens,
|
||||
tokens_output AS outputTokens,
|
||||
tokens_reasoning AS reasoningTokens,
|
||||
tokens_cache_read AS cacheReadTokens,
|
||||
tokens_cache_write AS cacheWriteTokens
|
||||
FROM session
|
||||
WHERE id = ?
|
||||
`).get(sessionId) as OpenCodeTokenTotals | undefined;
|
||||
|
||||
if (!row) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return buildTokenUsage({
|
||||
inputTokens: Number(row.inputTokens ?? 0),
|
||||
outputTokens: Number(row.outputTokens ?? 0),
|
||||
reasoningTokens: Number(row.reasoningTokens ?? 0),
|
||||
cacheReadTokens: Number(row.cacheReadTokens ?? 0),
|
||||
cacheWriteTokens: Number(row.cacheWriteTokens ?? 0),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* OpenCode stores per-message token counts on assistant `message.data` objects
|
||||
* (see MessageV2.Assistant). Older DBs also had session-level counters; this
|
||||
@@ -135,13 +173,18 @@ const aggregateOpenCodeSessionTokenUsage = (
|
||||
db: Database.Database,
|
||||
sessionId: string,
|
||||
): AnyRecord | undefined => {
|
||||
const sessionColumnUsage = readOpenCodeSessionColumnTokenUsage(db, sessionId);
|
||||
if (sessionColumnUsage) {
|
||||
return sessionColumnUsage;
|
||||
}
|
||||
|
||||
const rows = db.prepare('SELECT data FROM message WHERE session_id = ?').all(sessionId) as { data: string }[];
|
||||
|
||||
let inputTokens = 0;
|
||||
let outputTokens = 0;
|
||||
let cacheReadTokens = 0;
|
||||
let cacheCreationTokens = 0;
|
||||
let reasoningTokens = 0;
|
||||
let cacheReadTokens = 0;
|
||||
let cacheWriteTokens = 0;
|
||||
|
||||
for (const row of rows) {
|
||||
const info = readJsonRecord(row.data);
|
||||
@@ -159,15 +202,15 @@ const aggregateOpenCodeSessionTokenUsage = (
|
||||
reasoningTokens += Number(tokens.reasoning ?? 0);
|
||||
const cache = readObjectRecord(tokens.cache);
|
||||
cacheReadTokens += Number(cache?.read ?? 0);
|
||||
cacheCreationTokens += Number(cache?.write ?? 0);
|
||||
cacheWriteTokens += Number(cache?.write ?? 0);
|
||||
}
|
||||
|
||||
return buildTokenUsage({
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
cacheReadTokens,
|
||||
cacheCreationTokens,
|
||||
reasoningTokens,
|
||||
cacheReadTokens,
|
||||
cacheWriteTokens,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -85,6 +85,12 @@ const createOpenCodeDatabase = async (homeDir: string, workspacePath: string): P
|
||||
path TEXT,
|
||||
agent TEXT,
|
||||
model TEXT,
|
||||
cost REAL NOT NULL DEFAULT 0,
|
||||
tokens_input INTEGER NOT NULL DEFAULT 0,
|
||||
tokens_output INTEGER NOT NULL DEFAULT 0,
|
||||
tokens_reasoning INTEGER NOT NULL DEFAULT 0,
|
||||
tokens_cache_read INTEGER NOT NULL DEFAULT 0,
|
||||
tokens_cache_write INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
@@ -124,9 +130,10 @@ const createOpenCodeDatabase = async (homeDir: string, workspacePath: string): P
|
||||
);
|
||||
db.prepare(`
|
||||
INSERT INTO session (
|
||||
id, project_id, slug, directory, title, version, time_created, time_updated, time_archived
|
||||
id, project_id, slug, directory, title, version, time_created, time_updated, time_archived,
|
||||
tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, tokens_cache_write
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
'open-session-1',
|
||||
'project-1',
|
||||
@@ -137,6 +144,11 @@ const createOpenCodeDatabase = async (homeDir: string, workspacePath: string): P
|
||||
1_700_000_000_000,
|
||||
1_700_000_004_000,
|
||||
null,
|
||||
10,
|
||||
20,
|
||||
7,
|
||||
3,
|
||||
2,
|
||||
);
|
||||
|
||||
const userMessageData = JSON.stringify({
|
||||
@@ -302,12 +314,13 @@ test('OpenCode sessions provider reads sqlite history and token usage', { concur
|
||||
assert.equal(history.messages[3]?.kind, 'tool_use');
|
||||
assert.deepEqual(history.messages[3]?.toolResult, { content: 'ok', isError: false });
|
||||
assert.deepEqual(history.tokenUsage, {
|
||||
used: 35,
|
||||
total: 35,
|
||||
inputTokens: 10,
|
||||
used: 42,
|
||||
inputTokens: 13,
|
||||
outputTokens: 20,
|
||||
cacheReadTokens: 3,
|
||||
cacheCreationTokens: 2,
|
||||
breakdown: {
|
||||
input: 13,
|
||||
output: 20,
|
||||
},
|
||||
});
|
||||
|
||||
const paged = await provider.fetchHistory('open-session-1', { limit: 2, offset: 0 });
|
||||
|
||||
Reference in New Issue
Block a user