Skip to content

Log the real cost of Anthropic (BYOK) calls and Exa research - #179

Merged
Programcode merged 7 commits into
mainfrom
cost/fix-cost-logging
Oct 22, 2026
Merged

Programcode merged 7 commits into
mainfrom
cost/fix-cost-logging

Conversation

@Programcode

@Programcode Programcode commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

What was wrong

Anthropic rows logged $0. Every Claude call (Haiku 5.5, Opus 5.5, Sonnet 5) goes through OpenRouter on our own Anthropic key (BYOK). For those calls OpenRouter's usage.cost is only its own fee, which is 0. The real charge is in usage.cost_details.upstream_inference_cost, and we ignored it. Pricing wasn't missing: we read the wrong field. Example staging row: Haiku 5.5, 1,527 in / 671 out, cost: 0, upstream $0.0004882.

Exa wasn't logged at all. Searches and page reads never reached ai_requests. Exa returns the charge with every response (costDollars.total); we threw it away.

What changed

  • _shared/cost.ts: for a BYOK response the cost is OpenRouter's fee plus the upstream charge. A BYOK response without the upstream figure is logged as unknown (null), not $0. The calculation version goes to openrouter-usage-v2, so new rows can be told apart from old ones. All OpenRouter paths (main calls, streaming, the ai-stream Ask, Jev) share this function.
  • _shared/research.ts: every Exa search and page read reports its charge into the request's research usage, including the fallback search and the URL reads Ask does on its own. Exa costs go into the cost of the request's existing row, with one raw_usage.calls entry per Exa call (endpoint, request id, cost breakdown). There are no new rows, because the app counts ai_requests rows as daily usage. An error status isn't billed and isn't logged. A call that was sent but never settled (cancelled, timed out, unreadable body) makes the row's cost unknown, not lower.
  • Migration 20261022120000_backfill_byok_request_costs.sql recomputes the old $0 rows from the raw usage each row already stores. It only changes priced openrouter-usage-v1 rows that contain a BYOK call and have usable numbers. Repaired rows get the version openrouter-usage-v1-byok-backfill, so it can't apply twice. It doesn't delete anything or touch raw_usage. If you'd rather not rewrite history, it can be dropped without affecting the rest.

What it changes in the numbers

Read-only dry run of the backfill on staging: 124 rows go from $0 to $9.80 in total (Sonnet 5 $7.74, Opus 5.5 $2.03, Haiku 5.5 $0.008, the rest under 2¢).

Local live run (verify-debatelab --live, real Exa, OpenRouter pinned to Muse Spark): each research request made 8 or 9 Exa calls for about $0.033, next to about $0.0027 for its model calls. Exa was about 90% of what these requests cost and was missing from the logs entirely.

Verification

All local, at this branch. GitHub Actions is broken repo-wide, so there is no CI result.

  • npm run verify at the first commit: exit 0. docs, lint, typecheck, 305 unit, 30 collaboration and 361 edge tests, build, SW, and 28 browser smokes passed. 2 smokes were flaky (30 s timeouts in the attack page and the announcements page; load average was about 33).
  • After the second commit (Exa unsettled calls): npm run test:edge 361 passed, eslint clean, deno check on ai and ai-stream.
  • verify-debatelab up --live, drive recipes/live-generation on desktop-chromium: 3 passed. A temporary trigger copied the ai_requests rows before teardown. The generate, attack, brainstorm, factoids and Ask rows all carry openrouter-usage-v2, and their cost includes every Exa call, with none unpriced. The final commit only changed the unsettled-call path, which this run didn't exercise.
  • Backfill tested on the local DB with 8 hand-made rows in a rolled-back transaction: single BYOK, merged, nested repair retry, non-BYOK, missing upstream, malformed upstream, unpriced, already v2. The first run updated the 3 it should; the second run updated 0.
  • ./.superset/db-gate.sh --db-only: pgTAP 23 files / 441 tests pass. The first run's daily-quota lock-timing check failed under load; the rerun passed everything.
  • After merging main (Run research helper calls on a fixed cheap model #163 helper model, Trim AI prompts and cache Anthropic system prompts #177 prompt budget; the only conflict was exaGetContents gaining maxChars, now its last parameter): npm run test:edge 366 passed, deno check clean on ai and ai-stream.
  • New tests: BYOK cost and its fail-closed case, Exa merging and missing-charge cases (cost.test.ts), and research reporting for billed, error-status and unsettled Exa calls (research.test.ts).

Not proven locally: a real BYOK response end to end. The live proxy pins to Muse Spark, which isn't BYOK. The unit tests use the exact shape of a staging Haiku row, and the first Anthropic rows on staging after merge are the live check.

Deploying

Merging ships nothing on the backend. For production: apply the migration, then deploy ai and ai-stream (both use cost.ts; only ai uses the Exa change).

🤖 Generated with Claude Code

Programcode and others added 2 commits October 22, 2026 11:14
OpenRouter reports cost: 0 for calls billed to our own provider key (every
Anthropic model), with the real charge in cost_details.upstream_inference_cost.
The calculator now adds it, and a BYOK response without it stays unpriced.

Exa searches and page reads now report costDollars.total into the request's
research usage, so the parent ai_requests row includes them and raw_usage
keeps one entry per Exa call.

A migration recomputes the old $0 rows from their saved raw usage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sent Exa call that is cancelled, times out, or returns an unreadable body
may still be billed. It now reports an incomplete usage entry instead of
nothing, so the row's cost is unknown rather than quietly too low. Error
statuses are still not billed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 11:46
@vercel

vercel Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
debate-navigator Ready Ready Preview Oct 22, 2026 12:36pm UTC
debatelab-staging Ready Ready Preview Oct 22, 2026 12:36pm UTC

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Next included review available in 25 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between b2e92b6 and a248751.

📒 Files selected for processing: 8
  • docs/architecture.md
  • supabase/functions/_shared/cost.test.ts
  • supabase/functions/_shared/cost.ts
  • supabase/functions/_shared/research.test.ts
  • supabase/functions/_shared/research.ts
  • supabase/functions/ai/features/ask.ts
  • supabase/functions/ai/index.ts
  • supabase/migrations/20261022160000_backfill_byok_request_costs.sql
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing.

Comment @coderabbitai help to get the list of available commands.

# Conflicts:
#	supabase/functions/_shared/research.test.ts
#	supabase/functions/_shared/research.ts
@vercel

vercel Bot commented Oct 22, 2026

Copy link
Copy Markdown

Deployment failed for project debatelab-staging with the following error:

Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/adam-gaborjanis-projects?upgradeToPro=build-rate-limit

@vercel

vercel Bot commented Oct 22, 2026

Copy link
Copy Markdown

Deployment failed for project debate-navigator with the following error:

Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/adam-gaborjanis-projects?upgradeToPro=build-rate-limit

Keep calculator v2 for the logged cost (BYOK fee plus the provider
charge) next to #178 cap figure, which still counts only OpenRouter fee.
Rename the backfill to 20261022160000: #180 took 20261022120000.
@Programcode
Programcode merged commit c27cace into main Oct 22, 2026
1 of 14 checks passed
Programcode added a commit that referenced this pull request Oct 22, 2026
Rename the refund migration to 20261022170000: production already has
20261022160000 (#179), so 20261022150000 would be out of order.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants