Skip to content

feat(claudecode): estimate session cost from token usage - #2379

Draft
entire[bot] wants to merge 31 commits into
mainfrom
feat/claudecode-cost
Draft

feat(claudecode): estimate session cost from token usage#2379
entire[bot] wants to merge 31 commits into
mainfrom
feat/claudecode-cost

Conversation

@entire

@entire entire Bot commented Sep 11, 2026

Copy link
Copy Markdown

https://entire.io/gh/entireio/cli/trails/812

This draft pull request was opened by Entire after CI was requested for the linked trail. Feel free to edit the title or body — the link above is what keeps the trail and PR connected.

suhaanthayyil and others added 30 commits July 14, 2026 22:45
Versioned per-provider rate tables (USD per MTok) embedded via go:embed,
with exact-id then alias-glob lookup and no implicit family fallback: an
unknown model yields no estimate rather than a guessed price. Cache rates
default to the Anthropic convention (read 0.1x, write 1.25x of input)
unless a model sets explicit rates. Users can replace or extend entries
and disable estimation entirely through a new settings.pricing block.
…oint metadata

TokenUsage gains optional cost_usd/cost_source (reported|estimated|mixed)
carried through every accumulation path including subagent totals; nil
means unknown, never $0. A new ModelUsageCalculator capability lets agents
attribute usage to the model that produced it (model can change mid-session,
so session-level attribution is not enough); Claude Code implements it with
streaming dedup keeping the model on the kept row. A centralized dispatcher
prices each per-model bucket once, adds a remainder bucket when flat totals
exceed bucket coverage, and folds honest provenance (mixed when any tokens
stay unpriced). Buckets persist as model_usage[] on session metadata and
aggregate onto the checkpoint summary, so downstream consumers can ingest
per-model cost without re-deriving it.
…mmands

Renders cost_usd with its provenance suffix, e.g. "Cost:  $0.23 (estimated)";
absent cost renders nothing rather than $0.00. checkpoint tokens --compare
adds a cost delta only when both sides carry cost, and tokens profile reports
coverage as "across N of M checkpoints". Display never computes cost; it
renders what the lifecycle stored.
Extend the embedded Anthropic pricing table so any Claude model id
resolves to a rate, regardless of how the caller spells it.

Models: add the current lineup and the legacy tiers that older
transcripts still reference — mythos-5, opus 4.5/4.1/4.0, opus 3
(claude-3-opus), sonnet 4.5/4.0, 3.7/3.5/3 sonnet, and 3.5/3 haiku —
alongside the existing entries, and correct the opus 4.7/4.6 effective
dates. Rates and cache convention are unchanged (cache read/write are
derived at 0.1x/1.25x of input).

Aliases: every entry now carries the id spellings seen in the wild —
Anthropic dated ids (<id>-2*), slash-prefixed ids (anthropic/<id>),
Bedrock ids and regional inference profiles (anthropic.<id>[-*],
us|eu|apac.anthropic.<id>*, which also cover legacy dated -vN:0 forms),
and Vertex ids (<id>@*).

Long-context [1m] fix: Claude Code emits ids like "claude-fable-5[1m]",
but path.Match reads a literal "[1m]" alias as a character class, so it
would never match. Rather than add a broken alias, Lookup now strips a
trailing "[...]" suffix from the query before matching, so the id
resolves to its base model and bills at the base rate (1M-context
requests carry no long-context premium on current-generation models).

doc.go records that behavior plus two known under-estimates: 1-hour-TTL
cache writes (bill 2x input vs the 1.25x 5-minute default, pending
per-TTL bucket parsing) and fast-mode turns (usage.speed="fast", a
premium not published in-table).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ex legacy ids

A resolution sweep over real-world model-id spellings surfaced three forms
the alias set missed: Bedrock global inference profiles
(global.anthropic.<id>-v1:0), slash-prefixed dated ids
(anthropic/<id>-<date>), and Vertex legacy versioned ids (<id>-v2@<date>).
Each entry now carries alias globs for all three; the slash alias became a
glob so dated variants resolve too.
model_usage entries persist full TokenUsage values, so the remainder bucket
must account for api_call_count too — otherwise per-model buckets don't sum
to the flat total on that field whenever a shortfall bucket is created.
…nsation

Both extractSessionData and the Copilot session-wide backfill passed an
empty fallbackModel to tokenUsageWithCost even though state.ModelName is in
scope at their call sites (the live-transcript sibling already threads it).
That left the Copilot CLI backfill permanently unpriced and degraded Claude
Code checkpoints to a mixed cost source whenever subagent usage produced a
remainder bucket. Both paths now receive the session model.
…pic cache rates

Table.add now canonicalizes the index key (trimmed, lower-cased) and stores
the trimmed id, so an override whose id differs only in case or whitespace
replaces the builtin entry instead of appending a phantom duplicate. An
alias-less override inherits the replaced entry's aliases, keeping the dated
and provider-prefixed spellings resolvable. Lookup's fast path probes the index
with the same canonicalization.

Estimate's nil-cache-rate fallbacks are now provider-aware: the Anthropic
0.1x/1.25x multipliers apply only to anthropic-provider rates; any other
provider missing an explicit cache rate falls back to the full input rate
(1.0x) rather than silently undercharging. The openai and google tables now set
cache_write_per_mtok explicitly. doc.go documents the Anthropic-only scope.

validateRate rejects empty/whitespace aliases and malformed glob aliases
(path.Match ErrBadPattern) so a mistyped override alias fails loudly at load
instead of silently never matching.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merging a priced usage with an unpriced-but-token-bearing usage now folds to
CostSourceMixed, mirroring foldBucketCost's partial-coverage rule so an
aggregate that combines a costed session with an uncosted token-bearing one no
longer masquerades as fully priced.

Adds one exported helper, types.MergeCostSourceUsages(a, b), which behaves like
MergeCostSource over the two usages' (source, cost) pairs but additionally
returns mixed when exactly one side is priced and the other carries nonzero
billable tokens with no cost. Applied at the three scalar merge sites:
accumulateTokenUsage (strategy), aggregateTokenUsage (checkpoint), and
addCheckpointTokenUsage (cli). The in-place accumulate keeps computing the
source before the cost and token fields are summed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
remainderBucket now flattens the flat total's SubagentTokens subtree (at
arbitrary depth) before computing the per-field shortfall against the per-model
buckets. Real extractors report main-transcript totals in the scalar fields and
subagent totals in a SubagentTokens subtree that CalculateModelUsage never sees,
so previously the subagent tokens went entirely unpriced — a Claude Code session
with 100k main + 500k subagent tokens priced only the 100k. The subagent
shortfall now lands in a remainder bucket priced under the fallback model.

bucketTokens drops the SubagentTokens subtree so a fallback bucket carries
main-scoped scalar counts only: the remainder bucket already covers the subtree,
and keeping it on the fallback bucket would both double-count downstream and
alias the caller's subtree. PriceUsage builds its single bucket the same way
(nil subtree, cost preserved) so a reported-cost usage is untouched while its
subagent pointer is no longer shared. accumulateTokenUsage's existing==nil branch
deep-copies SubagentTokens so two aggregates that fold the same step cannot
cross-contaminate each other's subagent counts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… them

A minimal price override ({id, input, output}) inherited aliases but not
the provider, so the provider-gated cache multipliers stopped applying and
cache-read tokens billed at the full input rate (10x overcharge on
Anthropic models). Overrides now inherit provider and explicit cache-rate
pointers from the replaced entry, mirroring alias inheritance.
Two condensation-time cost-attribution fixes.

Fix 1 (reprice-after-backfill): CondenseSession priced the extracted
session data before backfilling the model from the transcript. For agents
whose model only comes from the transcript (Pi implements ModelExtractor;
state.ModelName can be "" at condensation, e.g. a mid-turn commit before
agent_end), the pricing pass ran with an empty fallbackModel, so the flat
usage came out unpriced under an empty-model bucket while the persisted
checkpoint recorded Model="gpt-5.5" with a nil cost and ModelUsage=[{Model:"",…}].
After the model is recovered, re-price the SAME extracted transcript slice
with the recovered model (both extraction paths priced from
state.CheckpointTranscriptStart with an empty subagents dir, so the reprice
is equivalent) and swap in the priced usage and real-model buckets. Gated by
sessionDataUnpricedFromTranscript so the reprice only fires when the
transcript actually yielded unpriced usage, leaving the state.ModelUsage
mirror fallback intact.

Fix 2 (skip zero-token buckets): priceBuckets estimated buckets with no
billable tokens, producing a $0.00 "estimated" cost. Add a bucketHasTokens
guard so zero-usage buckets stay unpriced (nil cost, empty source),
consistent with the ModelUsageCalculator path and preventing aggregated
provenance from flipping reported to mixed. Reported costs on zero-token
buckets still win via the existing CostUSD!=nil early-continue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tate total

The reprice-after-backfill swap accepted any non-nil re-extraction, so a
zero-data window (assistant activity before CheckpointTranscriptStart, or a
Pi fork abandoning the token-consuming branch) clobbered usage restored
from checkpoint state with zeros. The swap now requires real token data.
When the session-state total aliases the pre-reprice usage it is repointed
to the priced copy, keeping the state diagnostic consistent with the
persisted checkpoint.
Released 2026-07-09: sol $5/$30, terra $2.50/$15, luna $1/$6 per MTok,
cache reads at 10% of input and — new in this family — cache writes at
1.25x input, all set explicitly. Alias forms cover dated, slash-prefixed,
and vertex-style spellings; a guard test pins gpt-5.6-sol-pro (a distinct,
differently priced model) to a lookup miss rather than Sol rates.
Known limitation documented: >272K-token prompts bill 2x input / 1.5x
output, which the flat-rate table cannot express (undercount only).
Keeps CondenseSession under the maintainability threshold and drops the
always-empty subagentsDir parameter from tokenUsageWithCost (condensation
never passes one; the call-site TODOs document the open question).
…variants

The OpenAI table carried the legacy gpt-5 rate (1.25/10) for gpt-5.5 and
gpt-5.4, which understates their real cost. Correct them to the current
published rates:

  - gpt-5.5: 5 / 30 (cache 0.5 / 6.25)
  - gpt-5.4: 2.5 / 15 (cache 0.25 / 3.125)

Both carry effective_date 2026-07-10 (the correction date; the old rate was
never actually in effect, it was a copy-paste of gpt-5). gpt-5 itself stays
1.25/10 — that is a genuine legacy rate, not the mistake. Historical persisted
estimates that already billed gpt-5.5/5.4 at the old rate are left as-is: they
are estimate-grade, not invoices, and are not retroactively repriced.

New entries (all effective_date 2026-07-10):

  - gpt-5.3-codex: 1.75 / 14 (cache 0.175 / 2.1875).
  - gpt-5.5-priority: 12.50 / 75. The cache rates (1.25 / 15.625) are a
    2.5x-scaled assumption off the standard gpt-5.5 cache rates — flagged for
    verification against the published priority-tier cache pricing.
  - claude-opus-4-8-fast: 10 / 50, no explicit cache rates. Omitting them is
    deliberate: the Anthropic multipliers derive cache-read 1.0 (0.1x) and
    cache-write 12.5 (1.25x) from the fast input base, which is the intended
    fast-mode cache economics. Ordered BEFORE claude-opus-4-8 so the dated
    "-fast" glob wins over the base "claude-opus-4-8-2*" glob (which would
    otherwise capture claude-opus-4-8-<date>-fast).
  - cursor.json (new provider file): composer-2 (0.5/2.5), composer-2-fast
    (1.5/7.5), composer-2.5-fast (3.0/15.0). Cache rates are Anthropic-ratio
    assumptions — verify against cursor.com/docs/models-and-pricing. The
    models/*.json embed glob picks the new file up automatically.

The base gpt-5.5 alias is tightened from the broad "gpt-5.5-*" to the
dated-only "gpt-5.5-2*" (matching the gpt-5.6 family convention) so a dated
priority spelling (gpt-5.5-priority-<date>) resolves to gpt-5.5-priority
instead of being glob-captured by gpt-5.5. Both bare ids resolve via the exact
index regardless; the tightening only affects dated/suffixed glob resolution.

Deliberate omissions:

  - No opus-4-7-fast: 4-7 is deprecated and its fast-mode rate is inconsistent
    with the 4-8 fast base, so it is left out rather than guessed.
  - No composer-2.5 standard entry: the non-fast 2.5 rate is unconfirmed.
  - No gpt-5.6 priority entries: the priority tier for 5.6 is unpublished.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSS42VM7hpJrPkVQCxCsWb
Claude Code transcripts tag fast-mode turns with usage.speed == "fast" (vs
"standard" or ""). Those turns bill at a premium the standard model rate does
not capture, so per-model attribution must route them to the model's "-fast"
pricing variant (added in the prior commit).

  - messageUsage gains a Speed field (json:"speed").
  - CalculateModelUsage buckets each kept row under modelKeyWithSpeed(model,
    speed): a "fast" turn on claude-opus-4-8 lands in a "claude-opus-4-8-fast"
    bucket, everything else stays on the base id. Speed rides the SAME
    dedup-kept row as the model (the max-output streaming row), so attribution
    stays consistent with the flat token loop and buckets still sum to the flat
    total. The flat CalculateTokenUsage path is untouched.

modelKeyWithSpeed is a no-op for standard/empty speed, an empty model, or an id
that already ends in "-fast", so buckets never double-suffix.

Tests:

  - claudecode (no pricing import): a new fixture stream_session_fast.jsonl
    with a standard + fast turn on claude-opus-4-8 (the fast turn a streaming
    duplicate pair proving speed rides the kept max-output row) and a fast turn
    on a second model. Asserts the mixed model splits into "claude-opus-4-8"
    and "claude-opus-4-8-fast" buckets, the token split, and buckets == flat.
    The existing no-speed fixtures/tests prove absent speed keeps the base id
    (back-compat). modelKeyWithSpeed has a direct table test.
  - agent package (the priced half, respecting the claudecode-must-not-import-
    pricing rule): a "claude-opus-4-8-fast" bucket prices at 10/50 ($60 for
    1M+1M) while "claude-opus-4-8" prices at 5/25 ($30) via the real embedded
    table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSS42VM7hpJrPkVQCxCsWb
Codex can run on OpenAI's priority service tier, which bills at a premium the
standard model rate does not capture. Add an opt-in knob that prices those
turns under the model's "-priority" variant (added earlier).

  - PricingSettings gains CodexServiceTier (json:"codex_service_tier"),
    accepted by the strict unknown-field-rejecting decoder and applied by
    mergePricing. A nil-safe EntireSettings.CodexServiceTier() accessor
    defaults to "".
  - handleLifecycleTurnEnd computes the pricing model via pricingModelForTier:
    for a Codex turn with CodexServiceTier == "priority" (EqualFold) and a
    non-empty model, it prices under model+"-priority"; every other agent/tier
    is unchanged. The resolved model feeds BOTH the PriceUsage (hook-provided
    counts) and CalculateUsageWithCost (transcript) paths. Codex is
    transcript-based, so in practice the fallbackModel of CalculateUsageWithCost
    is what carries the tier. Settings are loaded only for Codex, so other
    agents' turn-end skips the extra read. An empty model or an already
    "-priority" id is returned as-is (no double-suffix).

Tests:

  - settings: JSON omitempty round-trip, the nil/empty-safe accessor default
    "", and a Load() merge of pricing.codex_service_tier through the strict
    decoder.
  - cli (respecting import boundaries: cli imports agent + pricing): a table
    test of pricingModelForTier (codex/priority, case-insensitivity, non-codex
    ignored, empty model, already-priority) and an end-to-end priced assertion
    that "priority" prices gpt-5.5 under gpt-5.5-priority (12.5/75 -> $87.5 for
    1M+1M) while no knob prices under gpt-5.5 (5/30 -> $35) via the real table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSS42VM7hpJrPkVQCxCsWb
Add an opt-in remote pricing layer that reads a locally cached table and
merges it beneath user overrides and above the embedded defaults. This is
the read/gating half; a later change adds the background refresh that
populates the cache.

pricing/remote.go: RemoteCache wraps the shared fileSchema (so validateRate
and the schema_version==1 contract are reused, not duplicated) with fetch
bookkeeping. The cache lives beside the discovery caches under
userdirs.Cache(). LoadRemoteEntries is read-only, does no network I/O, and
never errors: a missing/corrupt cache, absent Doc, unsupported
schema_version, or individually invalid entries all degrade to fewer (or
zero) merged entries.

settings: pricing.remote opt-in (nil/false default, nil-safe IsRemoteEnabled
accessor + ctx-level package func). LoadPricingTable, when enabled, layers
remote entries first then user pricing.models so a user override stays
authoritative; embedded LoadTable signature is unchanged. Debug-logs the
merged entry count, fetched_at, and staleness.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSS42VM7hpJrPkVQCxCsWb
Populate the remote pricing cache with a background daily refresh, so the
opt-in read path added earlier has something to merge.

pricing/remote.go: RefreshRemoteCache does a versioncheck-style conditional
fetch (2s timeout, 1 MiB cap, If-None-Match, entire-cli UA) under a
cross-process flock for herd control — a burst of spawned workers collapses
to one request via an under-lock FetchedAt re-check. Any failure (network,
timeout, non-200, oversized/garbage, or a doc failing the schema/sanity gate)
keeps the last good Doc and only bumps FetchedAt, so a broken endpoint is
retried at most once per 24h and never corrupts the served table. Writes are
atomic (temp + rename). ShouldRefresh gates the 24h backoff; RefreshResult +
RefreshRemoteCacheForce drive the manual command.

telemetry: generalize the detached spawner into SpawnDetached(exe, args...)
(behavior identical: Setpgid/detached, Dir "/", discarded stdio,
Start+Release), reused by analytics and the pricing refresh.

Triggers (never inline, never blocking): the hidden __refresh_pricing worker
runs the fetch; turn-end and root PersistentPostRun spawn it detached only
when remote is enabled and the cache is stale. The worker is Hidden, so the
post-run parent-chain guard skips it — no fork bomb, no telemetry/version
check. spawnDetachedRefresh is a package var so tests prove the triggers only
spawn, never fetch inline.

Manual surface: `entire tokens pricing-refresh` force-refreshes and reports
source, outcome, entry count, fetched_at, staleness, and whether merging is
enabled; documented in labs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSS42VM7hpJrPkVQCxCsWb
The Codex priority-tier knob (pricing.codex_service_tier="priority") was
applied at turn-end but discarded at condensation: the committed per-model
buckets were re-derived from the raw state.ModelName with no "-priority"
suffix, so the persisted checkpoint carried standard rates. A live gpt-5.5
Codex session with the knob set persisted $0.154513 (standard $5/$30) instead
of the expected $0.3862825 (priority $12.50/$75, = 2.5x standard).

Move the tier-suffix logic into a shared seam both call sites use:
settings.PricingModelForAgent (a method plus a ctx-loading package func). It
appends "-priority" only for a Codex session on the priority tier, never
double-suffixes, and leaves the stored model name raw — the suffix is a
pricing-lookup concern applied at the point of pricing. lifecycle.go delegates
to it (deleting the duplicate pricingModelForTier); condensation applies it in
the tokenUsageWithCost choke point every extraction/repricing path funnels
through, so turn-end and condensation now price the same tier.
Real Cursor CLI sessions report the model id "composer-2.5" (the current
default), captured live with real token counts, but cursor.json only carried
composer-2, composer-2-fast, and composer-2.5-fast, so the id resolved to no
rate and cost stayed nil. Add a composer-2.5 entry mirroring composer-2's
structure and standard $0.50/$2.50 per-M rate (published rate, same as
composer-2; fast tier $3/$15 already present), with cache rates and aliases
following the family convention. Exact-match lookup keeps composer-2.5 and
composer-2.5-fast distinct with no glob cross-match.
Cursor CLI and Gemini CLI report gemini-3.1-pro and gemini-3.5-flash today, but
google.json carried only the 3.0 generation (gemini-3-pro, gemini-3-flash), so
those ids resolved to no rate and cost stayed nil. Add both entries mirroring
the existing structure, cache convention, and alias style: gemini-3.1-pro at
$2/$12 per M (cache read $0.20) and gemini-3.5-flash at $1.50/$9 per M (cache
read $0.15). Exact-match lookup keeps the dotted 3.1/3.5 ids from cross-matching
the 3.0 "gemini-3-pro-*"/"gemini-3-flash-*" globs, and vice versa.
telemetry.SpawnDetached hardcoded cmd.Dir="/", so the detached
__refresh_pricing worker ran with cwd "/" and its own IsRemoteEnabled check
loaded settings relative to "/". A project-level .entire/settings.json enabling
pricing.remote was therefore invisible and the auto-refresh silently no-oped
forever (manual `entire tokens pricing-refresh` worked because it runs in the
project).

Generalize SpawnDetached to take a leading working-directory argument: analytics
passes "" to keep the platform default ("/" on Unix, temp dir on Windows), while
the pricing refresh passes the process working directory so the worker resolves
project settings via the normal cwd-based loader. A dir that no longer exists
falls back to the platform default (resolveDetachedDir) so a deleted project
directory never fails the spawn. The Windows and no-op build-tagged variants
take the same signature.
A tier-suffixed fallback model (e.g. gpt-5.5-priority from the Codex
service-tier knob) only reached the no-calculator fallback bucket. An
agent with a ModelUsageCalculator emits buckets under raw transcript
ids, which would price at standard rates and silently re-introduce the
tier-loss defect on that path. applyTierVariant retargets buckets whose
id equals the fallback's base onto the variant id before pricing; other
models stay raw since the table has no variant entry for them.

No agent on this branch implements a calculator for Codex, so behavior
here is unchanged; the seam is exercised by the tier fixture now
carrying the turn_context line every real rollout has before its
token_counts.
The service-tier knob suffixes whatever model the agent reports, but only
gpt-5.5 ships a -priority rate. A gpt-5.3-codex session with the knob set
priced under the nonexistent gpt-5.3-codex-priority id: entirely unpriced
(nil) instead of the standard-rate estimate, under a bucket id claiming a
premium that was never applied. resolveTierFallback keeps a suffixed
fallback only when the table prices it, otherwise reverting to the base
id — an honest undercount, mirroring the fast-rate rule.
claudecode's modelKeyWithSpeed appends "-fast" to any model reporting
usage.speed=="fast", but only claude-opus-4-8-fast has a published fast rate.
A fast turn on any other model (e.g. claude-fable-5) buckets as
claude-fable-5-fast, which priceBuckets can't resolve -> CostUSD nil: worse
than the base-rate estimate, under an id claiming a premium never applied.
This is the same failure class resolveTierFallback fixed for the Codex
-priority tier, but it bites ModelUsageCalculator buckets, which the
fallback-model-only resolveTierFallback never touches.

Add a downgradeUnpriceableVariants pass in CalculateUsageWithCost (after the
remainder, before pricing) that reverts any bucket whose id carries a known
variant suffix (-fast, -priority) to its base id when the table can't price the
variant but can price the base; a nil table downgrades too, and a variant whose
base is also unpriceable keeps its truthful id. Detection stays truthful at the
source; priceability is only known here at pricing time. Rekeying is in place,
not merging: same-model buckets are already emitted today (calculator bucket +
remainder) and folded by model key downstream and in foldBucketCost, so leaving
the duplicate conserves tokens and cost. Priceable variants
(claude-opus-4-8-fast) are unchanged.
A shortfall that was only an APICallCount delta (all four token fields
fully covered by per-model buckets) still emitted a spurious zero-token
remainder bucket. Also defensively normalize a costed-but-unlabeled
bucket (non-nil CostUSD with empty CostSource) to CostSourceEstimated in
foldBucketCost, so a latent mislabel can never render with no
reported/estimated/mixed suffix.
…covery

backfillModelAndReprice's identity check (state.TokenUsage ==
sessionData.TokenUsage) never fires for the Copilot CLI full-session
backfill, since sessionStateBackfillTokenUsage assigns state.TokenUsage
a distinct object from sessionData.TokenUsage's checkpoint-scoped slice.
That left the session-state diagnostic total permanently unpriced once
the model was later recovered, even though the persisted checkpoint was
priced correctly. Reprice it too, guarded so a zero-data recovery
window can't clobber a good value.
doc.go was stale: it said pricing was embedded-plus-settings-overrides
only with no runtime fetch. Describe the opt-in remote-pricing refresh
(remote.go, gated by pricing.remote) and clarify there is no inline
fetch on the hook/condensation path — the refresh is a detached
background worker.
modelKeyWithSpeed appended "-fast" to the raw message.model, so a
1m-context fast turn keyed to "claude-opus-4-8[1m]-fast". pricing.Lookup
only peels a bracketed "[1m]" suffix when the id ends in "]", so that
form resolved to neither the "-fast" variant nor the base rate, leaving
the fast premium unpriced. Strip the bracketed long-context marker
before appending "-fast" so the key lands on the priceable fast variant
(e.g. "claude-opus-4-8-fast").

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant