Skip to content

SDK: credits double-count OpenRouter's upstream_inference_cost — usage.cost already includes it #1164

Description

@nordicnode

While looking into Discord reports of inflated usage ("token counts so inflated that it's literally impossible", credit burn far above comparable subscriptions), I traced the SDK's credit calculation and found what looks like a real double-count on every OpenRouter-served request.

The bug

All three cost paths sum usage.cost and usage.cost_details.upstream_inference_cost:

  • sdk/src/impl/llm.ts:394-396 (reportCost, streaming path)
  • sdk/src/impl/llm.ts:733-737 (non-stream generateText path)
  • sdk/src/impl/llm.ts:804-808 (non-stream Responses path)
costOverrideDollars =
  (openrouterUsage.cost ?? 0) +
  (openrouterUsage.costDetails?.upstreamInferenceCost ?? 0)

That total feeds calculateUsedCredits (sdk/src/impl/llm.ts:62-65) → onCostCalculated, i.e. the credits actually charged/shown to the user.

Per OpenRouter's usage accounting docs, usage.cost is "the total amount charged to your account" and cost_details.upstream_inference_cost is "the actual cost charged by the upstream AI provider" — the upstream component of that total, not an additional amount. On a normal (non-BYOK) OpenRouter route, upstream inference is nearly all of cost, so cost + upstream roughly doubles the true spend before the margin is applied. For a model where upstream is 90% of the total, credits come out ~1.9× too high — which matches the "extreme inflation" reports much better than token counts alone do.

The repo already documents the correct semantics

common/src/constants/freebuff-models.ts:313-320 (the Solar Pro 4 BYOK note): on BYOK routes top-level usage.cost is 0 and the real spend arrives in cost_details.upstream_inference_cost, and "extractUsageAndCost takes the max of the two, so the ledger is right; a naive read of cost is not". So the server-side ledger takes max(cost, upstream) — correct for both shapes — while the SDK's user-facing credit path sums them, which is only correct in the BYOK shape (cost = 0) and double-counts in the common shape.

Suggested fix

Take the max at all three sites, matching the ledger's documented semantics:

costOverrideDollars = Math.max(
  openrouterUsage.cost ?? 0,
  openrouterUsage.costDetails?.upstreamInferenceCost ?? 0,
)

Happy to open a PR.

Related (not this bug, but likely behind the "millions of input tokens" half of the reports)

  • Cumulative per-request inputTokens includes cache-read tokens at full weight (sdk/src/impl/llm.ts:168-197), and every agent step re-sends the full context (packages/agent-runtime/src/run-agent-step.ts:518), so summed input grows quadratically with steps even when billed credits don't (cache discounts cost, not the reported token figure). "Millions on simple tasks" is what that math produces for any multi-step task with real file context.
  • Subagent usage is both rolled into the parent's creditsUsed (packages/agent-runtime/src/tools/handlers/tool/spawn-agents.ts:218-253) and written as its own run row — any consumer summing rows across the run tree counts each subagent twice. Backend display code is outside this repo, so this may or may not affect what users see.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions