Problem
The dashboard's cost figures are computed in internal/webapi/store.go with a flat, model-agnostic rate:
func estimateCost(tokens int) float64 {
// ~$0.00025 per token as a rough estimate
return float64(tokens) * 0.00025
}
tokens is just InputTokens + OutputTokens summed from SessionDigest.Usage. This drives the run table's Cost column, the Avg Cost KPI, the trends chart, the compare view, and the CSV export.
What's wrong
- Model-agnostic — Opus, Sonnet, Haiku, and GPT‑5 all priced identically at ~$250/M blended. Real rates vary by 10×+ across models.
- No input/output split — output tokens cost 3–5× more than input on most providers.
- Ignores cache tokens —
CacheReadTokens (~10% of input price) and CacheWriteTokens (~125% of input price) are collected in UsageStats but excluded from the cost calc entirely.
- Ignores real cost data we already collect. The Copilot SDK reports
mm.Requests.Cost per model in SessionShutdown, captured as ModelUsage.RequestCost in internal/execution/session_usage_collector.go:108 — but the dashboard never reads it. PremiumRequests is also unused for $ display.
Example (from current dashboard)
| Run |
Tokens |
Flat estimate |
Real Opus 4.6 (rough) |
| 3P Full Suite |
41.7M |
$10,430 |
could be $3k–$15k depending on in/out/cache split |
The number is in the right order of magnitude for Opus by accident; for Haiku it would be ~10× too high.
Proposed fix
- Replace
estimateCost with a model-aware calculator that consumes per-model ModelUsage (input / output / cache‑read / cache‑write):
- Per-model rate table (input, output, cache_read, cache_write per 1M tokens) for known models — Claude family, GPT family, Gemini.
- Fall back to
RequestCost from the SDK's ModelMetrics when present (treat SDK-reported cost as authoritative).
- Fall back to a documented blended estimate only when neither is available, and surface that fact in the UI.
- Plumb
ModelMetrics through to outcomeToSummary / RunDetail so the calculator has per-model token breakdowns instead of just a flat sum.
- Add an accuracy disclaimer in the dashboard (e.g. a small info tooltip on the
Cost / Avg Cost headers) explaining whether the number is SDK-reported, model-table-priced, or a fallback estimate, and the date of the rate table. We want this statement regardless of which path produced the number.
- Tests:
- Unit tests for the calculator (known model, unknown model fallback, cache token handling, SDK-reported cost path).
- Update
internal/webapi/handlers_test.go and internal/models/outcome_test.go as needed.
- Update Playwright dashboard tests if the cost column / KPI rendering changes.
- Docs: brief note in
site/ (dashboard guide) describing how cost is calculated and its accuracy caveats.
Acceptance criteria
References
internal/webapi/store.go:160-171 — current estimateCost
internal/webapi/storage_adapter.go:79-115 — Summary() aggregation
internal/execution/session_usage_collector.go:95-121 — where RequestCost per model is collected but unused
internal/models/outcome.go:224 — ModelUsage.RequestCost field
Problem
The dashboard's cost figures are computed in
internal/webapi/store.gowith a flat, model-agnostic rate:tokensis justInputTokens + OutputTokenssummed fromSessionDigest.Usage. This drives the run table'sCostcolumn, theAvg CostKPI, the trends chart, the compare view, and the CSV export.What's wrong
CacheReadTokens(~10% of input price) andCacheWriteTokens(~125% of input price) are collected inUsageStatsbut excluded from the cost calc entirely.mm.Requests.Costper model inSessionShutdown, captured asModelUsage.RequestCostininternal/execution/session_usage_collector.go:108— but the dashboard never reads it.PremiumRequestsis also unused for $ display.Example (from current dashboard)
The number is in the right order of magnitude for Opus by accident; for Haiku it would be ~10× too high.
Proposed fix
estimateCostwith a model-aware calculator that consumes per-modelModelUsage(input / output / cache‑read / cache‑write):RequestCostfrom the SDK'sModelMetricswhen present (treat SDK-reported cost as authoritative).ModelMetricsthrough tooutcomeToSummary/RunDetailso the calculator has per-model token breakdowns instead of just a flat sum.Cost/Avg Costheaders) explaining whether the number is SDK-reported, model-table-priced, or a fallback estimate, and the date of the rate table. We want this statement regardless of which path produced the number.internal/webapi/handlers_test.goandinternal/models/outcome_test.goas needed.site/(dashboard guide) describing how cost is calculated and its accuracy caveats.Acceptance criteria
estimateCostremoved or kept only as the explicit fallback path.internal/webapi/(or newinternal/pricing/package) with at least Claude Opus/Sonnet/Haiku and GPT‑5 family.RequestCostis present, dashboard uses it directly.make test,make lint).cd web && npx playwright test --project=chromium).site/docs updated.References
internal/webapi/store.go:160-171— currentestimateCostinternal/webapi/storage_adapter.go:79-115—Summary()aggregationinternal/execution/session_usage_collector.go:95-121— whereRequestCostper model is collected but unusedinternal/models/outcome.go:224—ModelUsage.RequestCostfield