agents: per-model rate table + runner cost computation (cursor parity with claude-code) #953

Closed
opened 2026-05-08 12:05:13 +00:00 by claude-desktop · 1 comment
Collaborator

User story

As an operator I want every task to display its accumulated USD cost regardless of provider, so I can compare run cost across models and providers in the dashboard.

Context

Claude Code's result.total_cost_usd is set by the SDK. Cursor's SDK does not expose USD cost in any message — cursor-sdk-adapter.ts hardcodes totalCostUsd: 0. Without computing cost ourselves, every cursor row in task_history shows €0.00 forever.

Acceptance criteria

Rate table

  • Extend apps/server/src/infrastructure/agent/models-cache.ts (or sibling model-rates.ts) with per-model rates: { provider, modelId, inputPerMTok, outputPerMTok, cacheReadPerMTok, cacheCreationPerMTok }.
  • Seed at minimum: every Anthropic Claude 4.x model (Opus 4.7, Sonnet 4.6, Haiku 4.5), every Cursor model the SDK lists today (Composer 2, GPT-5.5, plus whatever Agent.listModels returns), and any GPT-5/Anthropic Bedrock variants we already use.
  • Source rates from the canonical pricing pages (Anthropic, OpenAI, Cursor); link the URL in a comment per model row. Mark unverified rates with a TODO so a real-world run can surface mismatches.
  • Allow runtime override via the existing service-config table — operator can patch a rate without a redeploy when pricing changes.

Runner / event log

  • On every usage_delta (and the terminal result), compute incremental cost from the table and accumulate into record.cost_usd. Persist to task_history.cost_usd on terminal.
  • If a model is not in the table, log a single warning per task and treat cost as 0 + emit a system { subtype: "cost_unknown_model", details: { model } } so the UI can flag the row instead of silently zeroing it.

Frontend

  • Cost chip rendered next to the token meter in the same surfaces. Format: 3 sig figs USD, e.g. $0.043 / $1.27.
  • When cost_unknown_model was raised for the run, show ? with a tooltip explaining the missing rate row.

Tests

  • Unit tests for the cost computation: feed a known usage stream, assert the running total matches hand-computed cost.
  • Test for the unknown-model fallback path.
  • Test that a runtime rate override applied mid-run is not retroactively applied — cost is computed from the rate at delta time, not at terminal time.

Out of scope

  • Currency conversion (USD↔EUR) — display USD only for now; multi-currency is a separate concern.
  • Budget enforcement / auto-cancel — separate ops issue.
  • Historical re-pricing of past runs — task_history.cost_usd is immutable.

References

  • Parent: #950
  • Depends on: live token meter issue (usage_delta event)
  • Pricing pages: anthropic.com/pricing, openai.com/api/pricing, cursor.com/pricing (verify each)
## User story As an operator I want every task to display its accumulated USD cost regardless of provider, so I can compare run cost across models and providers in the dashboard. ## Context Claude Code's `result.total_cost_usd` is set by the SDK. Cursor's SDK does not expose USD cost in any message — `cursor-sdk-adapter.ts` hardcodes `totalCostUsd: 0`. Without computing cost ourselves, every cursor row in `task_history` shows €0.00 forever. ## Acceptance criteria ### Rate table - [ ] Extend `apps/server/src/infrastructure/agent/models-cache.ts` (or sibling `model-rates.ts`) with per-model rates: `{ provider, modelId, inputPerMTok, outputPerMTok, cacheReadPerMTok, cacheCreationPerMTok }`. - [ ] Seed at minimum: every Anthropic Claude 4.x model (Opus 4.7, Sonnet 4.6, Haiku 4.5), every Cursor model the SDK lists today (Composer 2, GPT-5.5, plus whatever `Agent.listModels` returns), and any GPT-5/Anthropic Bedrock variants we already use. - [ ] Source rates from the canonical pricing pages (Anthropic, OpenAI, Cursor); link the URL in a comment per model row. Mark unverified rates with a TODO so a real-world run can surface mismatches. - [ ] Allow runtime override via the existing `service-config` table — operator can patch a rate without a redeploy when pricing changes. ### Runner / event log - [ ] On every `usage_delta` (and the terminal `result`), compute incremental cost from the table and accumulate into `record.cost_usd`. Persist to `task_history.cost_usd` on terminal. - [ ] If a model is not in the table, log a single warning per task and treat cost as 0 + emit a `system { subtype: "cost_unknown_model", details: { model } }` so the UI can flag the row instead of silently zeroing it. ### Frontend - [ ] Cost chip rendered next to the token meter in the same surfaces. Format: 3 sig figs USD, e.g. `$0.043` / `$1.27`. - [ ] When `cost_unknown_model` was raised for the run, show `?` with a tooltip explaining the missing rate row. ### Tests - [ ] Unit tests for the cost computation: feed a known usage stream, assert the running total matches hand-computed cost. - [ ] Test for the unknown-model fallback path. - [ ] Test that a runtime rate override applied mid-run is *not* retroactively applied — cost is computed from the rate at delta time, not at terminal time. ## Out of scope - Currency conversion (USD↔EUR) — display USD only for now; multi-currency is a separate concern. - Budget enforcement / auto-cancel — separate ops issue. - Historical re-pricing of past runs — `task_history.cost_usd` is immutable. ## References - Parent: #950 - Depends on: live token meter issue (`usage_delta` event) - Pricing pages: anthropic.com/pricing, openai.com/api/pricing, cursor.com/pricing (verify each)
Collaborator

🤖 Auto-assigned to code-lead (heuristic: area:agents → code-lead (architecture-touching)). Reply /unassign to reroute.

🤖 Auto-assigned to **code-lead** (heuristic: area:agents → code-lead (architecture-touching)). Reply `/unassign` to reroute.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
charles/claude-hooks#953
No description provided.