feat: add cache token tracking and cache-aware cost calculation
Some checks failed
CI / Check (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Formatting (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Release Build (push) Has been cancelled

Track cache_read_tokens and cache_write_tokens end-to-end: parse from
provider responses (OpenAI, DeepSeek, Grok, Gemini), persist to SQLite,
apply cache-aware pricing from the model registry, and surface in API
responses and the dashboard.

- Add cache fields to ProviderResponse, StreamUsage, RequestLog structs
- Parse cached_tokens (OpenAI/Grok), prompt_cache_hit/miss (DeepSeek),
  cachedContentTokenCount (Gemini) from provider responses
- Send stream_options.include_usage for streaming; capture real usage
  from final SSE chunk in AggregatingStream
- ALTER TABLE migration for cache_read_tokens/cache_write_tokens columns
- Cache-aware cost formula using registry cache_read/cache_write rates
- Update Provider trait calculate_cost signature across all providers
- Add cache_read_tokens/cache_write_tokens to Usage API response
- Dashboard: cache hit rate card, cache columns in pricing and usage
  tables, cache token aggregation in SQL queries
- Remove API debug panel and verbose console logging from api.js
- Bump static asset cache-bust to v5
This commit is contained in:
2026-03-02 14:45:21 -05:00
parent 232f092f27
commit db5824f0fb
19 changed files with 352 additions and 109 deletions

View File

@@ -184,21 +184,27 @@ class AnalyticsPage {
if (!tableBody) return;
if (data.length === 0) {
tableBody.innerHTML = '<tr><td colspan="7" class="text-center">No historical data found</td></tr>';
tableBody.innerHTML = '<tr><td colspan="9" class="text-center">No historical data found</td></tr>';
return;
}
tableBody.innerHTML = data.map(row => `
<tr>
<td>${row.date}</td>
<td><span class="badge-client">${row.client}</span></td>
<td>${row.provider}</td>
<td><code class="code-sm">${row.model}</code></td>
<td>${row.requests.toLocaleString()}</td>
<td>${window.api.formatNumber(row.tokens)}</td>
<td>${window.api.formatCurrency(row.cost)}</td>
</tr>
`).join('');
tableBody.innerHTML = data.map(row => {
const cacheRead = row.cache_read_tokens || 0;
const cacheWrite = row.cache_write_tokens || 0;
return `
<tr>
<td>${row.date}</td>
<td><span class="badge-client">${row.client}</span></td>
<td>${row.provider}</td>
<td><code class="code-sm">${row.model}</code></td>
<td>${row.requests.toLocaleString()}</td>
<td>${window.api.formatNumber(row.tokens)}</td>
<td>${window.api.formatNumber(cacheRead)}</td>
<td>${window.api.formatNumber(cacheWrite)}</td>
<td>${window.api.formatCurrency(row.cost)}</td>
</tr>
`;
}).join('');
}
setupEventListeners() {