fix: correct OpenCode cache hit rates
Include cache reads and writes in the complete input denominator for OpenCode usage. Use the same calculation for stored records, summary totals, and renderer fallbacks so existing history is corrected without migration. Add regressions for full cache hits, cache writes, mixed Runtime aggregation, and persisted conversation/system usage. Focused validation passed 41 tests; repository-wide checks were blocked by concurrent work-mode changes. Release note: 修复 OpenCode 缓存命中率偏高或完全命中时显示为空的问题,已有用量记录在重新查询时按完整输入量计算。
This commit is contained in:
@@ -7395,6 +7395,49 @@ describe('AssistantDatabase', () => {
|
||||
database.close()
|
||||
})
|
||||
|
||||
it.each([false, true])('normalizes persisted OpenCode cache input in records and totals (system=%s)', async (system) => {
|
||||
const database = await createDatabase()
|
||||
try {
|
||||
const taskId = randomUUID()
|
||||
database.createTask({
|
||||
id: taskId,
|
||||
conversationId: system ? `knowledge:${randomUUID()}` : randomUUID(),
|
||||
title: 'Cache usage',
|
||||
instructions: 'Cache usage',
|
||||
workMode: 'ask'
|
||||
})
|
||||
const calls = [
|
||||
{ runtime: 'opencode', provider: 'goodbuddy-openai-chat', input: 60, cacheRead: 40, cacheWrite: 0 },
|
||||
{ runtime: 'opencode', provider: 'goodbuddy-openai-responses', input: 10, cacheRead: 90, cacheWrite: 0 },
|
||||
{ runtime: 'opencode', provider: 'custom', input: 0, cacheRead: 100, cacheWrite: 0 },
|
||||
{ runtime: 'opencode', provider: 'goodbuddy-anthropic', input: 50, cacheRead: 30, cacheWrite: 20 },
|
||||
{ runtime: 'model', provider: 'openai', input: 100, cacheRead: 40, cacheWrite: 0 },
|
||||
{ runtime: 'model', provider: 'anthropic', input: 50, cacheRead: 30, cacheWrite: 20 }
|
||||
]
|
||||
for (const [index, call] of calls.entries()) {
|
||||
database.upsertModelUsageCall({
|
||||
requestId: taskId, callId: `call-${index}`, model: 'test-model',
|
||||
output: 10, ...call
|
||||
})
|
||||
}
|
||||
// Reopen persisted raw usage: existing records need no data migration.
|
||||
database.close()
|
||||
database.initialize('C:\\Workspace')
|
||||
const summary = database.getTokenUsageSummary()
|
||||
expect(summary.records).toHaveLength(6)
|
||||
for (const record of summary.records) {
|
||||
expect(record.cacheInput).toBe(100)
|
||||
}
|
||||
expect(summary.totals).toMatchObject({ callCount: 6, cacheRead: 330, cacheInput: 600 })
|
||||
expect(system ? summary.systemTotals : summary.conversationTotals).toEqual(summary.totals)
|
||||
expect(system ? summary.conversationTotals : summary.systemTotals).toMatchObject({
|
||||
callCount: 0, cacheRead: 0, cacheInput: 0
|
||||
})
|
||||
} finally {
|
||||
database.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('persists global magic notes and AI comments without todo proposals', async () => {
|
||||
const database = await createDatabase()
|
||||
const globalNote = database.createMagicNote({
|
||||
|
||||
@@ -5918,6 +5918,13 @@ export class AssistantDatabase {
|
||||
}
|
||||
|
||||
getTokenUsageSummary(): TokenUsageSummary {
|
||||
// OpenCode reports uncached input for every provider, including OpenAI.
|
||||
const cacheInputSql = `usage.input_tokens + CASE
|
||||
WHEN usage.runtime = 'opencode'
|
||||
OR LOWER(usage.provider) LIKE '%anthropic%'
|
||||
THEN usage.cache_read_tokens + usage.cache_write_tokens
|
||||
ELSE 0
|
||||
END`
|
||||
const systemSourceSql = `CASE
|
||||
WHEN tasks.conversation_id LIKE 'heartbeat:%' THEN 'heartbeat'
|
||||
WHEN tasks.conversation_id LIKE 'supervision:%' THEN 'supervision'
|
||||
@@ -5947,14 +5954,7 @@ export class AssistantDatabase {
|
||||
SUM(usage.output_tokens) AS output_tokens,
|
||||
SUM(usage.cache_read_tokens) AS cache_read_tokens,
|
||||
SUM(usage.cache_write_tokens) AS cache_write_tokens,
|
||||
SUM(
|
||||
usage.input_tokens +
|
||||
CASE
|
||||
WHEN LOWER(usage.provider) LIKE '%anthropic%'
|
||||
THEN usage.cache_read_tokens + usage.cache_write_tokens
|
||||
ELSE 0
|
||||
END
|
||||
) AS cache_input_tokens
|
||||
SUM(${cacheInputSql}) AS cache_input_tokens
|
||||
FROM model_usage_calls usage
|
||||
JOIN tasks ON tasks.id = usage.request_id
|
||||
LEFT JOIN projects ON projects.id = tasks.project_id
|
||||
@@ -6017,17 +6017,7 @@ export class AssistantDatabase {
|
||||
COALESCE(SUM(usage.output_tokens), 0) AS output_tokens,
|
||||
COALESCE(SUM(usage.cache_read_tokens), 0) AS cache_read_tokens,
|
||||
COALESCE(SUM(usage.cache_write_tokens), 0) AS cache_write_tokens,
|
||||
COALESCE(
|
||||
SUM(
|
||||
usage.input_tokens +
|
||||
CASE
|
||||
WHEN LOWER(usage.provider) LIKE '%anthropic%'
|
||||
THEN usage.cache_read_tokens + usage.cache_write_tokens
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0
|
||||
) AS cache_input_tokens
|
||||
COALESCE(SUM(${cacheInputSql}), 0) AS cache_input_tokens
|
||||
FROM model_usage_calls usage
|
||||
LEFT JOIN tasks ON tasks.id = usage.request_id
|
||||
GROUP BY is_system`
|
||||
|
||||
Reference in New Issue
Block a user