Commit graph

83 commits

Author SHA1 Message Date
Resham Joshi
6f4c3f772b
perf(models): memoize short display names by model id (#1727)
Warm reports re-derived the display name of every cached call, a linear
scan of the short-name table each time (about 0.5 s on a lifetime run).
The name depends only on the aliases and the pricing namespaces, so it is
memoized per model id and cleared when either changes.
2026-10-10 07:06:23 +02:00
Resham Joshi
7390810a61
fix(codex): count cache writes toward the long-context threshold, tier only above 272k (#1706)
The tier threshold summed input and cache read but left out cache-write
tokens, which codex splits out of input_tokens on gpt-5.6+ and gpt-6. A
280k-token prompt with 80k cache writes priced at base rates. OpenAI also
bills 272,000 exactly as short context, so the tier now starts above the
threshold. The audit recompute follows the same rule.
2026-10-10 02:55:47 +02:00
Resham Joshi
c826f1a479
fix(models): price kimi-for-coding by the model it served on the call date (#1676)
* fix(models): price kimi-for-coding by the model it served on the call date

kimi-for-coding is a moving alias. It priced as retired K2 Thinking for
every call. It now prices as K2.5 from 27 Jan 2026, K2.6 from 13 Apr and
K2.7 Code from 12 Jun (K2.8 Preview from 11 Sep has no published price,
so it stays on K2.7 Code). kimi-for-coding-highspeed prices at Moonshot's
K2.7 Code HighSpeed rate instead of $0. Daily cache v64.

* fix(models): mark K2.8 Preview kimi-for-coding calls as estimated

From 11 Sep 2026 kimi-for-coding served K2.8 Preview, which has no
published rate and prices as K2.7 Code. Those calls now carry the
estimated flag. The legacy kimi provider prices by the dated model too.

* fix(models): flag the K2.8 Preview stand-in where every call is read

cachedCallToApiCall now marks kimi-for-coding calls from 11 Sep 2026
estimated, so the flag reaches every provider and cached reads without a
parse bump. Calls with a reported cost are left alone. The kimi provider's
own flag and its parse version entry go, as they only duplicated this.
2026-10-08 00:02:38 +02:00
Resham Joshi
bbb3c5a609
fix(pricing): price Mistral Large 4 at its preview rate until LiteLLM lists it (#1656) 2026-10-06 21:00:51 +02:00
Resham Joshi
574a6af967
fix(pricing): take the maker's price for bare model ids (#1653)
Some checks are pending
CI / semgrep (push) Waiting to run
Tests / test (22) (push) Waiting to run
Tests / test (22.13.0) (push) Waiting to run
Tests / test (24) (push) Waiting to run
Tests / test (26) (push) Waiting to run
Tests / test-platforms (macos-latest) (push) Waiting to run
Tests / test-platforms (windows-latest) (push) Waiting to run
Tests / desktop (push) Waiting to run
* fix(pricing): bare model ids take the maker's price, not a reseller's

A bare id with no direct LiteLLM row went to whichever prefixed row came
first in the JSON, so azure_ai priced grok-4.6 at $1.25/M instead of xAI's
$2/M, and the free codestral/ row priced codestral-latest at $0. The
bundler and the live fetch now let the maker's own row claim the bare id,
and a $0 row yields to any priced one. Snapshot patched for the 9 keys
this changes; cache schema 8 so live caches rebuild. The grok-4.6 builtin
override is now identical to the snapshot and live rows, so it goes.

* fix(pricing): the maker's row wins a bare id even at $0

Gemma is free on Google's own API, so gemini/'s $0 row must not lose
gemma-4-26b-a4b-it and gemma-4-31b-it to a reseller's price. Priced beats
$0 only among non-maker rows; codestral-latest still goes to mistral/.
Snapshot now differs from main in 7 keys. Drops the stale
mistral-large-latest example from the fills-only comment.

* fix(pricing): pin deepseek-v3.2 output at DeepSeek's published $0.42
2026-10-06 19:56:41 +02:00
Resham Joshi
1a7284c39b
fix(copilot): count store-row output for unclosed sessions, price grok-4.6 at xAI rates (#1652)
Session-store rows carried output only for compaction requests; every
other request's output was left to the per-turn assistant.message or the
shutdown rollup residual. CLI 1.0.8x and ACP hosts (JetBrains AI Chat via
copilot --acp) write no per-turn output, and an ACP session usually ends
without session.shutdown, so its whole output was lost. Rows now carry
their own output_tokens; at serve time a row that pairs with a per-turn
call carrying output (older CLIs) serves with output 0, so output counts
once in every shape.

grok-4.6 priced at $1.25/M input: LiteLLM has no bare grok-4.6 row and
the stripped azure_ai/grok-4.6 row claims the bare id ahead of
xai/grok-4.6, in the bundler and the live fetch alike. A builtin price
pins xAI's list rate ($2/$6/$0.50 per M), which is what GitHub bills.

On real data every Copilot model's cost now equals GitHub's
total_nano_aiu to the digit.
2026-10-06 19:15:43 +02:00
Resham Joshi
3afcbd9480
fix(codex): price Flex tier turns and the gpt-reserve / gpt-5.3-spark ids (#1642)
* fix(codex): price Flex tier turns and the gpt-reserve / gpt-5.3-spark ids

Flex turns (service_tier "flex") billed at standard rates. They now bill at
the model's published `<rate>_flex` rates, carried as an optional seventh
snapshot slot (only the flex fields were added to the bundled snapshot). A
model without flex rates still bills Flex at standard.

gpt-reserve (Luna Reserve) prices as GPT-5.6 Luna and gpt-5.3-spark as
GPT-5.3 Codex Spark; both showed $0 with an unknown-model warning.

* fix(codex): mark gpt-reserve cost as estimated
2026-10-05 21:18:59 +02:00
Resham Joshi
6d9d233d4b
fix(codex): price Codex Auto Review by date (#1641) 2026-10-05 19:43:33 +02:00
ozymandiashh
ee23e94726
Price Codex Fast (priority tier) turns at priority rates (#1628)
Some checks are pending
CI / semgrep (push) Waiting to run
Tests / test (22.13.0) (push) Waiting to run
Tests / test (22) (push) Waiting to run
Tests / test (24) (push) Waiting to run
Tests / test (26) (push) Waiting to run
Tests / test-platforms (macos-latest) (push) Waiting to run
Tests / test-platforms (windows-latest) (push) Waiting to run
Tests / desktop (push) Waiting to run
* fix(codex): price Fast (priority tier) turns at priority rates

Codex bills turns run under the Fast speed setting through the priority
service tier, which the rollout files record per thread in
thread_settings_applied events. Track that tier through the parse (and
across append resumes and fork replays), mark the affected turns as
fast, and derive a fast multiplier from the LiteLLM priority rate keys
so those turns are priced at the priority rates instead of the standard
ones. Cache versions are bumped so already-cached Codex results are
re-priced.

* snapshot: priority slots only, no upstream drift

---------

Co-authored-by: Resham Joshi <65915470+iamtoruk@users.noreply.github.com>
2026-10-05 12:15:30 +02:00
ozymandiashh
986d72ce1e
fix(pricing): restore the DeepSeek v4 and gpt-5.6-codex repricing invariants (#1134) (#1577)
Some checks failed
CI / semgrep (push) Has been cancelled
Tests / desktop (push) Has been cancelled
Tests / test-platforms (windows-latest) (push) Has been cancelled
Tests / test (22) (push) Has been cancelled
Tests / test (22.13.0) (push) Has been cancelled
Tests / test (24) (push) Has been cancelled
Tests / test (26) (push) Has been cancelled
Tests / test-platforms (macos-latest) (push) Has been cancelled
* fix(pricing): restore the DeepSeek v4 and gpt-5.6-codex repricing invariants (#1134)

The 0.9.21 snapshot caught the 2026-08-24 repricings mid-transition and
relaxed two tests/models.test.ts invariants. Upstream has since synced:

- DeepSeek v4: LiteLLM now carries the official peak rates for both the
  bare and the deepseek/-namespaced rows (pro 1.32/3.96 per million,
  flash 0.30/1.20), so the stale hand-pinned MANUAL_ENTRIES (from before
  PR #27056 merged) are removed and prefixed-equals-bare is asserted
  again.
- gpt-5.6-codex / -max: still absent from LiteLLM, so their
  MANUAL_ENTRIES remain but now mirror the repriced gpt-5.6 base row
  verbatim (4/20 per million, tier block included). codex-equals-base
  is asserted again, and the long-context tier test now expects the
  codex SKUs to carry the base tier.

The refresh also exposed a pass-2 ordering defect in
scripts/bundle-litellm.mjs: a reseller row like
openrouter/deepseek/deepseek-v4-pro could claim the empty
deepseek/deepseek-v4-pro slot before the official row was reached (the
winner was decided purely by upstream JSON key order), pricing the
namespaced id at the resale rate. A prefixed row may now claim only a
stripped slot that is not itself an upstream entry name; multi-segment
ids that exist only as reseller rows keep resolving as before.

Bundled snapshot regenerated: 24 rows moved by upstream drift
(claude-sonnet-5-5 family added, deepseek-v4.1-flash repriced,
zai/glm-4.6 cache-write filled), plus the deepseek/codex corrections.
Token-priced calls re-price on read; no re-parse is needed.

* fix(pricing): repin cline-cli parse version and cover #1134 shadow bug

- session-cache.ts: bump the cline-cli parse version so cached estimated
  costs (e.g. offline DeepSeek v4.1 flash) re-price on read, since
  cline-cli is in REPORTED_COST_PROVIDERS.
- CHANGELOG.md: note the bundler fix also reprices zai/glm-4.6 from the
  vercel_ai_gateway resale row to the official row, and moves the two
  mistral embed models to the fallback table.
- bundle-litellm.test.ts: add a fixture covering a reseller-prefixed row
  that sorts before the official row it would otherwise shadow.

---------

Co-authored-by: Resham Joshi <65915470+iamtoruk@users.noreply.github.com>
2026-09-29 15:05:37 +02:00
ozymandiashh
700e4d1a27
fix(pricing): carry long-context tier rates through bundling, applied where billing evidence exists (#1478)
* fix(pricing): carry long-context tier rates through bundling and price them where billing evidence exists

LiteLLM publishes *_above_<n>k_tokens rates for 418 models - OpenAI's
gpt-5.4/5.5/5.6 family above 272k, Anthropic above 200k, and 128k/256k/
512k tiers elsewhere - but the bundler flattened every entry to the
fixed five-slot tuple and calculateCost had no threshold dimension, so
a request past its threshold priced at the base rate forever (#1076).

The tuple gains an optional sixth slot {threshold, input, output,
cacheWrite, cacheRead}, the threshold parsed from the key suffix
because LiteLLM ships no numeric field (272k means 272,000; #1075
verified assuming 128k fabricates +64% spend). Old bundles parse
unchanged; CACHE_SCHEMA_VERSION moves to 5 (4 is taken on main by the
implicit cache-write billing) so a stale on-disk live-pricing cache
cannot reintroduce the old behavior; the live-fetch parser picks the
same tier up; service-tier _priority/_flex variants and the 1-hour
cache-write combination are not context thresholds and stay out.

A tier applies when the request's prompt tokens (input + cached input,
the existing grok-4.6 definition) reach the threshold, each published
rate replacing its base and omitted slots keeping the base.
Application is gated on billing evidence via TIERED_PRICING_PROVIDERS:
codex today, where the maintainer's corpus measurement shows the
surcharge passing through; a real Copilot session on gpt-5.6-terra
with ~6M-token prompts billed at the BASE rate, so Copilot keeps base
pricing until such evidence exists - applying it there would fabricate
spend, the exact class #1075 warned about. The audit report recomputes
through the same tier swap so recomputedTotalUSD keeps tracking
attributedCostUSD.

* fix(pricing): read tier rates only from the chosen threshold; e2e codex tier test

Repair pass from review: both tierOf implementations now collect rates
per threshold and read only from the largest one, so a hypothetical
two-tier entry can never mix the smaller tier's rates under the bigger
threshold; the bundler validates tier values exactly like the live
parser (finite, non-negative). A codex end-to-end test drives a real
rollout past gpt-5.5's 272k prompt threshold and asserts the tiered
arithmetic explicitly (a typo'd provider string in codex.ts would
leave the call at base rates and fail it), alongside the no-provider
base-rate control that keeps the real Copilot billing pin intact.
Comment fixes: the priceOverride wording, the grok pin's actual
resolution branch, and the threading note on TIERED_PRICING_PROVIDERS
for whoever adds the next provider.

* fix(pricing): recompute the audit tier per call and re-derive warm daily caches

Both review blockers:

- audit-report no longer feeds a bucket's SUMMED tokens into the
  per-call 272k threshold (a \,979 phantom gap on the reviewer's
  corpus, any tiered codex model clearing the threshold on the sum
  alone). The recompute accumulates per call, through the same tier
  swap calculateCost applies, with each call's own prompt total; a new
  fixture mixes one 300k call with 200 small ones whose sum blows past
  the threshold and pins that only the big call tiers. Web search
  stays on the base rates - it never participates in a tier.
- DAILY_CACHE_VERSION 33 -> 34 with MIN_SUPPORTED_VERSION raised to
  match, the cfdaa685 convention: a 33 file written under tierless
  pricing is below the floor, is not adopted as the finalized base,
  and its days re-derive off the warm session cache (\ of codex
  spend on the reviewer's corpus lives only in the daily cache). The
  migration tests pin the new floor contract: no version-diff repairs
  merge into a below-floor file, and the v30 adoption test tracks
  CURRENT.

Nits from review: the kimi precision tightening is reverted to (8, 5),
and the tieredCostsFor comment is rewrapped.

Measured on this machine's corpus, tier-only (branch vs the refresh
branch, same data): +\.43 lifetime codex - this corpus runs far
more sessions past 272k than the reviewer's 8-of-51,801 (+\.012964);
both numbers are real, the corpora differ.

* fix(pricing): preserve base alias selection when bundling context tiers

* fix(pricing): address the 28 Sep review - floor stays at 28, tier bills the implicit cache-write rate

Restore main's two carry-forward tests: with MIN_SUPPORTED_VERSION kept
at 28 (per the review's correction, warm caches re-derive identically
under 41/28, so raising the floor is unnecessary) a v33 file sits in
the adopt-with-repairs window again and main's expectations hold; the
tier's warm-cache coverage stays in daily-cache-version-rederivation.

The tier-omits-slot test bills an implicit cache-write at the input
rate of the costs object in effect (#1544), so the tiered expectation
is 1_000 * cacheWriteCostPerToken('gpt-5.5', tieredCosts).

Note on the Codex estimate path: an estimated prompt can cross a
long-context threshold and tier itself; measured corpora hold no
estimated calls that cross.

* fix(pricing): pin claude-opus-4 primary and keep carried fallback rows hygienic in the refreshed snapshot

The requested regeneration surfaced two upstream moves beside the tier
work. LiteLLM dropped `claude-opus-4`; a refresh carries dropped ids
into the fallback tier, but the Cursor-style alias `claude-4-opus`
resolves against primary rows, so the bare id fell to the snowflake
gateway row and under-priced by 3x. Pinned at Anthropic's list price
in MANUAL_ENTRIES so it stays primary. LiteLLM also repriced
grok-4.6's base input 2e-6 -> 1.25e-6 (tier rates unchanged); the
below-threshold literal follows. Both changes reproduce identically
with main's unchanged bundler on the same live responses.

Today's drops also exposed a gap in the #1483 carry-forward: primary
files legitimately hold @pin, date-suffixed and free-on-both rows, and
carrying those verbatim imports exactly what
tests/pricing-fallback-data.test.ts keeps out of the fallback file
(main's own next refresh fails those tests today). The carry now skips
@pin/date-suffixed keys (no query can arrive in those forms) and
free-on-both rows (an unpriced model falls to expected-free handling);
vendor-prefixed keys stay carriable since queries match them verbatim.

---------

Co-authored-by: Resham Joshi <65915470+iamtoruk@users.noreply.github.com>
2026-09-28 19:10:39 +02:00
ozymandiashh
0259392a20
fix(models): name dot-form Claude minors and future GPT ids consistently (#1531)
* fix(models): name dot-form Claude minors and future GPT ids consistently (#1530)

One model id resolved to different display names per provider, and new
models showed truncated names until a hand-maintained entry landed:

- deriveClaudeShortName now accepts the dot-form minor some doors write
  on disk (GitHub Copilot's session store: claude-opus-4.8), so the name
  keeps its minor instead of truncating to "Opus 4".
- Unknown dotted-version GPT ids derive their display name (gpt-5.7-terra
  -> "GPT-5.7 Terra") the way unreleased Claude versions already do, so a
  new release needs no SHORT_NAMES entry. Legacy bare-major /
  date-packaged ids (gpt-9, gpt-4-1106-preview) stay raw, and curated
  labels keep winning.
- Pi/OMP/Copilot local display tables now match keys on segment
  boundaries via modelKeyMatches, so a bare `gpt-5` key can no longer
  capture `gpt-5.5` / `gpt-5.6-luna`; unmatched ids fall through to the
  global resolver, making one id show one name on every provider.
- The JetBrains token scan rejects a trailing dot, so `gpt-5` /
  `claude-opus-4` cannot match inside `gpt-5.6-luna` / `claude-opus-4.8`.

Fixes #1530

* fix(copilot): list the current JetBrains model ids in the token scan (#1530)

Review of #1531: the tighter #1530 boundary is right, but with no token
entries the newer ids fell through to copilot-anthropic-auto and priced
as Sonnet 4.5. Add the current dotted ids, longest first per the list's
order, so "model":"gpt-5.5", gpt-5.6-luna and claude-opus-4.8 resolve to
their own priced, correctly named rows; unknown future versions (a dot
followed by a digit) still return no token.
2026-09-28 18:48:21 +02:00
Resham Joshi
6dc81f5ce6
fix: four cost accuracy bugs in export and pricing (#1544)
Some checks are pending
CI / semgrep (push) Waiting to run
Tests / test-platforms (windows-latest) (push) Waiting to run
Tests / desktop (push) Waiting to run
Tests / test (22) (push) Waiting to run
Tests / test (22.13.0) (push) Waiting to run
Tests / test (24) (push) Waiting to run
Tests / test (26) (push) Waiting to run
Tests / test-platforms (macos-latest) (push) Waiting to run
* fix: four cost accuracy bugs in export and pricing

- export daily rows bucket each call on its own day (same rule as the day
  aggregator) and no longer skip a turn without a timestamp
- cache-write tokens on a non-Anthropic model with no published write rate
  bill at the input rate instead of a fabricated 1.25x (calculateCost and
  audit); daily cache v35 and pricing schema 4 so stored figures re-derive
- OpenCode/Kilo messages with output but no usage, and Copilot transcript
  calls with no token count, are marked estimated
- export records keep unrounded cost and gain estimated/unpriced columns

* fix: plain-decimal CSV costs, re-parse cached copilot transcripts for the estimated flag

* ci(upgrade-path): compare copilot records at cent precision across the export rounding change
2026-09-24 15:55:23 +02:00
Resham Joshi
2fc62f7876
0.9.25: correctness, energy, menu bar and installer fixes (#1511)
* 0.9.25: correctness + provider fixes, energy guard, logo, default DMG

CLI/engine fixes: local-model savings count reasoning; ZCode reasoning billed;
codex-credits version-boundary tiering; GLM alias repointed to z.ai discounted rate;
openclaw/pi/qwen timestamp mtime fallback; hermes delta timestamp;
sqlite/warp/hermes busy+permission reclassified as failed (no sealed empty);
codex-cache dev+ino; cursor-cache WAL; antigravity server TTL; quota timeout+
grokbot footer+claude near-expiry; serve RSS guard env+hysteresis; CSV export
subfolder; providers pane fixed window; Warp credit-based cost.
Desktop/menubar: unified flame logo; default DMG; docs signed+notarized.

* menubar: one running instance, self-relaunch on language change

The single-instance guard only retired strictly older copies, so two login
items starting in the same instant both survived, and a copy whose bundle
identity did not survive install was never seen at all. The decision is now
total (newest launch wins, ties break on pid) and peers are matched on bundle
id or executable name. --keep-both opts one launch out.

Language changes no longer have the desktop app quit and reopen the menu bar,
which made macOS attribute the new process to the desktop app and re-ask for
the data-access grant. The menu bar relaunches itself via the existing remote
command key; older menu bars that do not watch the key fall back to the old
path after 5s.

* desktop: export toast shows the saved path; resource buckets on app_close

The export toast showed the folder that was picked, not the dated subfolder
the files land in. The handler already parsed the CLI's saved line; it now
returns that path and the toast uses it.

app_close carries cpu/mem (and serveCpu/serveMem when serve reported) as bucket
labels, sampled on the existing flush interval. serve adds cpuSec and peak rssMb
to its answer frames. Unmeasured figures are omitted, never sent as zero.

* menubar guard never leaves zero instances; installer replaces in place; JSON export into folder

Guard: an unreadable launch date on either side could make two copies retire
each other. Unreadable peers now count only when their pid is lower, and a copy
that cannot read its own date retires lower pids only, so a disagreement keeps
at least one instance.

Installer: an existing bundle in /Applications or ~/Applications is replaced
where it lives instead of a second copy being added. The old bundle is moved
aside and restored on any failure; a cross-volume move falls back to a copy that
is re-verified with codesign. An unwritable location is never escalated: the
install goes to ~/Applications and the leftover is named.

Export: -o pointing at an existing folder writes the JSON inside it. Short
sessions omit the cpu buckets. setLanguage reopens the menu bar if its own
relaunch never comes back. AppRelaunch passes the bundle path as an argument.

* menubar guard orders by kernel start time; installer opens the copy that exists and recovers a killed install

Guard: launch dates could be unreadable and pid order can disagree with start
order after pid reuse, so two copies could still retire each other. Ordering now
uses the kernel-reported process start time, which both sides read identically.
A copy whose start time cannot be read is left out, so the worst case is two
menu bars, never none.

Installer: with the only copy in an unwritable /Applications, a plain run tried
to open a path that did not exist. It now opens the installed copy. A placement
killed mid-way is recovered on the next run, a concurrent install is refused,
and a failed restore names where the previous bundle is. Process matching uses
the exact executable name.

Export: a trailing separator names a folder. Telemetry cpu is measured from
telemetry start.

* installer: a recycled pid can no longer block installs; language change waits for the replacement process

An aside left by an earlier install counted as an active install whenever its
pid was live, so once that pid was reused every install was refused for good.
It now also has to be recent (ctime within 10 minutes), a pid we may not signal
is not ours, the refusal names the file, and an orphan that cannot be cleared is
reported instead of aborting the install.

setLanguage confirmed the relaunch with a matcher the dying process still
satisfied, so the reopen fallback never fired. It now waits for a pid that was
not running before.

* desktop: only the headline refreshes live; act, yield and the optimize scan move to slower tiers

An open window ran act report (a fresh CLI process each time), yield and an
optimize scan on every refresh. The headline poll now passes --no-optimize and
takes the snapshot path; act report refreshes on mount, on manual refresh and
every 10 minutes, yield every 5. The optimize scan runs at most once a day per
query scope, is stored in userData with its time, and is forced fresh by the
Optimize page or a manual refresh. Figures that are not live show their age,
and no stored scan means no savings clause rather than a zero.

On battery the live interval doubles; the chosen cadence stays the base.

* desktop: a stored optimize scan is only valid on the day it was computed; cadence changes no longer refetch

The scan cache is keyed on the argv, which says 'today' or '30days', not a date,
so yesterday's scan was served as today's. It is now reused only on the same
local day. Stored findings in Signals carry their age, a failed scan shows the
error instead of scanning forever, and the cold-boot label uses the time the app
received the payload.

usePolled fetched immediately whenever its interval changed, so every power
transition (and launching on battery) refetched every live hook and could spawn
an extra act report. Fetching and timer arming are now separate effects.
Optimize's yield is on the same 5 minute tier as Overview.

* desktop: re-ask for the optimize scan on the first render of a new local day

An app left open across midnight kept yesterday's stored savings under Today,
because the scan was only requested on mount, scope change or manual refresh.
The local day is now one of the hook's triggers; the previous day's figure is
dropped while the new scan runs.

* serve: a menubar poll is never answered from the status snapshot

Inside its settle window the status snapshot returns the pre-change payload on
purpose. The resident serve process then memoized that answer as clean, so after
a single write followed by quiet the headline stayed on the old figure until the
next write or the five minute memo cap. The snapshot is for one-shot runs; the
resident process already holds the parsed state, so it now skips it.

* tests stop writing the real serve pid file; superseded caches are swept; installer notes reach the desktop card

ServeConnection takes its pid file as a parameter, so the Swift tests no longer
share ~/.cache/codeburn/menubar-serve.pid with a running menu bar, and the
reaper only signals a record that is a serve --stdio command.

Superseded session-cache, codex-results and cursor-results files (100MB+ each)
are removed once untouched for 14 days, at most once a day. daily-cache and the
hermes ledger are never swept: they carry sealed days a version bump adopts.

The menu bar installer's leftover-copy advice is emitted as a marked line under
CODEBURN_PROGRESS and shown on the desktop card. The skipped-refresh NSLog is
debug only. The app's vitest workers turn off Node's built-in web storage.

* menubar: anonymous usage statistics, under the same consent as the desktop and the Windows tray

The Swift menu bar sent nothing, so every codeburn-menubar row was a Windows
tray. It now follows the desktop's telemetry.v1.json when that exists (read
only: same install id, same on/off, onboarded required) and otherwise keeps its
own state with the same region default (off in EU/EEA/UK/CH and when the region
is unknown). Settings gains one Privacy toggle, disabled when the desktop owns
the choice. Events use the tray's names; the daily usage snapshot is the CLI's
block passed through a sanitizer and is left to the desktop when it is the
consent source. Debug builds never send.

A resident CLI that gets disabled for the run now says so in the log.

* menubar statistics: private network session, abandoned batch on opt-out, a desktop No survives the desktop going away

The sender used URLSession.shared, so a server or CDN cookie and the default
User-Agent rode along and would have outlived an opt-out. It now owns an
ephemeral session with no cookies, no cache, a fixed agent string and no
redirects. A batch that is out when the toggle goes off is dropped rather than
re-queued under the fresh id. The desktop's answer is recorded locally, so a
missing or damaged state file never falls back to the region default, and the
standalone id is persisted when minted. The toggle stays usable as a local off
switch under desktop consent. Copy states what this app actually sends.

* vercel gateway spend is shown on its own row and kept out of totals by default

The gateway reports daily aggregates per model, with nothing to match against
the local tools that were pointed at it, so every headline counted that traffic
twice. Totals now leave the gateway slice out when all providers are shown; the
row keeps its full amount and is marked not in total, --provider vercel-gateway
is unchanged, and codeburn gateway-totals include folds it back in. The daily
cache still stores the slice, so the choice applies retroactively. Reports with
no gateway credential are byte-identical.

The gateway's request_count now feeds the call count instead of each daily row
counting as one call. Hermes: a mid-session price change lands on the day it is
observed and sealed days are never rewritten; stated in the code and changelog.

* gateway exclusion applies to every report through the parse, and the detail panels follow the headline

The first pass narrowed only the day totals, so By tool, top models, top
projects, activities, status json projects and any project-filtered table still
carried gateway spend next to a headline that did not, and By tool shares could
pass 100%. The rule now lives in parseAllSessions: with all providers in scope
and the opt-in off, aggregate-only sessions are dropped after the memo, and the
callers that derive cached days opt out so stored slices are unchanged.
day.projects is reduced like models. The gateway stays visible once, as a
labelled not-in-total row with no share, and standalone reports note the
excluded amount on stderr so machine output is unchanged.

* dashboard: the background history index follows the gateway exclusion too

The index keeps the whole corpus so it can fill the daily cache, but its
per-period projection handed that unfiltered list to the project, model and
activity panels, so they regained gateway spend once the index landed.

* ci: a hung lock suite fails in 90s and retries once with a warning; slow Windows tests get measured limits

The cache-lock step failed 6 of the last 60 runs with no output for five minutes
after the vitest banner; passing runs take 12-18s on every Node version. Each
attempt is now capped at 90s and retried once, with a warning whenever that
happens. On windows-latest a single CLI spawn takes 1.4-12s, so the four-spawn
status snapshot test and the c6 parser suite get limits taken from those runs,
each with its reason. Both test jobs print their ten slowest cases.

* desktop: Privacy & data uses the same rows as the rest of Settings

The pane mixed two static claims with two controls in its own layout. It now
uses the about-row pattern every other pane uses: title and hint on the left,
one control on the right. The claims became the subtitle. Rows: anonymous usage
statistics (toggle, link to what is sent), device sharing state with Manage,
local report snapshots with Clear, export with Open export.

* desktop: Privacy & data keeps one bounded column and rows that never squeeze

The card stretched with the window, stranding each control hundreds of pixels
from its text. This pane's heading, subtitle and card now share a 760px column
and one text measure. Rows stack on their own width, because the window can
never be narrow enough for the old viewport rule to fire; row buttons do not
wrap. The what-is-sent link sits on its own line, and the snapshot and export
descriptions fit their measure and say this machine.

* menubar tests clean up their scratch defaults and leave the real Keychain alone

Four test files never removed their UserDefaults suites and the rest removed the
domain but left the plist, so thousands of files piled up in
~/Library/Preferences. A shared helper forgets a suite and deletes its file, and
every test that makes a suite now uses it. cfprefsd still rewrites an empty plist
when the test process exits, so a run leaves about a hundred 42-byte files with
no test data in them. The real-Keychain round trip only runs with
CODEBURN_TEST_REAL_KEYCHAIN=1.

* desktop: the resident parser retires after 15 idle minutes and restarts on demand

The renderer stops polling while the window is hidden, but the serve child stayed
resident with its parsed cache, one to two GB on a large corpus. After 15 minutes
without a request it is stopped the way a settings restart stops it, without
spending the unexpected-death budget, and the next request starts a new resident
through the existing lazy path, never a one-shot. CODEBURN_SERVE_IDLE_MS tunes or
disables it.

* desktop: every settings pane keeps a bounded column, and dark buttons have an edge

Cards stretched with the window, so on a wide display controls sat up to 1300px
from their labels and intro lines ran past 140 characters. All panes now share
one left-aligned column (760px, 980px for projects, aliases and pricing, which
hold wide rows) and one text measure. Rows stack on their own width instead of a
viewport rule the 900px minimum window could never reach, and a long path in
Projects no longer overflows the pane.

In dark mode the secondary button matched or sat below its surface, and was
invisible on the plugin install dialog. It gets a face and a border from two new
tokens; light is unchanged. The onboarding consent label matches Settings.

* desktop: this device shows its fingerprint on its own line

The local name and the fingerprint are two adjacent inline spans, so they read as
one run of text.

* menubar: the resident parser retires after 15 idle minutes and restarts on demand

The menu bar kept its serve child, and the parsed cache with it, for the whole
run, even with the popover closed and nothing changing. After 15 minutes without
a request the child is retired without counting as a death, and the next request
starts a new resident through ensureStarted, never the one-shot fallback, whose
snapshot settle window would make the figure lag a whole tick. The window sits
above the longest gap between real requests, so a child stays warm while work is
happening. CodeBurnServeIdleSeconds tunes or disables it.

* serve: a poll re-checks only what the watcher saw change, and shard writes are coalesced

With a session being written, every poll re-listed about 2,400 directories and
re-fingerprinted about 6,000 transcripts, then rewrote the whole current-month
shard (49MB on a large corpus) and read it back on the next poll. The resident
process now reuses the previous listing and fingerprints for paths its root
watcher proves untouched, and falls back to the full sweep whenever the watcher
is missing, unhealthy, overflowed, armed after the sweep, does not cover the
path, the memo is older than 4.5 minutes, or the process was suspended. Shard
publication is held for up to 90s and flushed when stdin closes; a held write is
dropped rather than written over something another process published. One-shot
runs are unchanged. Active poll cost 0.71 to 0.33 CPU-s at a 30s cadence;
time-to-reflect unchanged at about 1.2s across append, new dir, new file, delete,
rename-over, in-flight, watcher overflow and unhealthy watcher.

Changelog: state the telemetry default as it is in this version.

* a retired parser gets to publish its held shards, and durable providers are never held

Both apps retired the serve child with SIGTERM before its stdin-close flush could
run, so every retire discarded the coalesced window. They now close stdin, give
the child a bounded grace to publish and exit, and signal it only if it is still
running; a settings restart does the same. Measured: flushed 0/5 before, 5/5
after, for both clients.

copilot's cache entries are the only record once Copilot prunes its own files, so
a held window that was dropped (retire, crash, kill -9) before the source was
pruned lost that spend for good. A window that a durable provider dirtied is
published on the poll that parsed it, as before coalescing existed. The RSS guard
publishes a held window before it clears the caches instead of discarding it.

* serve: release the lifetime parse after the fill, and make the collection real

The RSS guard dropped its memos and then called globalThis.gc, which exists only
under --expose-gc; no launcher passes it, so the collection had never run and the
freed pages never went back. On a cold start the resident idled at 2.2GB with
364MB of live heap: clearing every memo moved RSS by 0MB, a collection alone by
700MB.

After the background fill has sealed its summaries the process now publishes any
held shard window, drops the memos it can re-read from disk (the cold start had
loaded the cache with scope 'all', which then served every later poll), and
collects; it collects again a minute later for as long as a pass returns 64MB,
at most four times. Cold idle 2198MB to 700MB, warm 900MB to 781MB. Reports are
leaf-identical cold and warm, time-to-reflect unchanged, a released month scope
reloads in 0.3-0.5s.

* warp: the billed cost reaches reports and survives the cache

The provider derived a dollar cost from Warp's own billing record (provider cents,
charged usage, or credits), but warp was missing from the reported-cost
pass-through, so the cached call kept no cost and every report, cold or warm,
priced the conversation from its tokens. A fixture billed at $1.22 reported
$0.15. A call whose cost came from a billing record now keeps it; a call that
only has the token floor stores none and is still re-priced when prices change.

Warp entries re-parse once (provider parse version), and the daily cache moves to
v34 so days whose Warp source still exists re-derive; call counts do not move, so
nothing is double counted or dropped.

* openclaw and pi: a reported cost survives the cache like every other reported cost

Both providers read the cost their tool recorded for a message, and both were
missing from the reported-cost pass-through, so reports priced every call from
tokens instead. Fixtures reported $0.011 against $0.056 (openclaw) and $0.013
against $0.410 (pi). A call with a reported cost now keeps it; a call with none,
or an explicit zero, stores none and is still re-priced when prices change. omp
had been added to the list in #1150 and pi, the same parser, was never revisited.

The provider list is one named set, the unreachable providerCallToTurn is gone,
and both providers re-parse once; the v34 daily cache re-derives their days with
call counts unchanged.

* test: pin parse count per report and per resident poll
2026-09-21 00:16:21 +02:00
ozymandiashh
3bb72ade3e
chore(pricing): refresh the bundled snapshot; a refresh can no longer drop a model's pricing (#1483)
* chore(pricing): refresh the bundled snapshot with long-context tiers

Regenerated from live LiteLLM/models.dev/OpenRouter: 5,938 primary
entries (1,210 added, 11 removed, 298 repriced vs the 0.9.24 bundle)
and 51 fallback entries; 394 primary entries carry a tier slot. The
Grok Build family upstream is what makes grok-4.6-build priceable,
shifting the Grok parser's authoritative-model pick to the real
modelUsage id (pin updated with the reasoning in the test).

* chore(pricing): refresh the bundled snapshot; a refresh can no longer drop a model's pricing

Regenerated from live LiteLLM/models.dev/OpenRouter: 5,938 primary
entries (1,210 added, 298 repriced vs the 0.9.24 bundle), fallback 51
from the gap-fill plus 9 carried forward. Upstream actually removed
four entries (friendliai llama-3.1 bare and prefixed); seven more are
excluded by the per-token-output filter, which is a rule change and is
called out as one.

Two bundler rules from review:

- vendor-prefixed aliases no longer displace a richer entry for the
  same model: completeness (published cache slots) wins over
  first-write, so the new nebius/MiniMaxAI/MiniMax-M3 without
  cache-read rates cannot replace the publisher's entry that carries
  them;
- previously-priced fallback entries carry forward when neither the
  new primary nor the gap-fill covers their key exactly or
  vendor-prefixed - the form the resolution pipeline queries. Nine
  models would otherwise have lost all pricing; a dated variant
  sharing the bare name does not count as coverage because it does
  not answer the undated query.

The Grok Build family upstream makes grok-4.6-build priceable, so the
Grok parser's authoritative-model pick prefers the real modelUsage id
(pin updated with the reasoning in place).

* fix(pricing): exact-key coverage carry, slot-fill-only completeness guard, regenerated bundle (#1483)

- coveredByKey is exact-key only: the resolver never reaches vendor/<id> for
  a bare query, so the old endsWith clause dropped 96 priced fallback ids.
- Pass 2 stripped writes are strictly slot-filling: a richer upstream row may
  only fill missing cache slots when input/output rates are identical, never
  re-price a filled slot (was re-pricing 43 input/output + 34 cache rates).
- Carry now rescues dropped primary rows as well as fallback entries (twelve
  ids this cycle), so a refresh cannot leave a previously-priced model null.
- Regenerated: 5,942 primary + 212 fallback; 159 carried; zero coverage
  regression and zero guard-caused rate changes vs upstream/main, verified by
  resolving every id from either bundle through the real getModelCosts.

* test(models): glm-5.3 prices at its own row after the refresh (#1483)

Upstream LiteLLM now publishes a distinct z-ai/glm-5.3 rate, so the
prefixed spelling no longer equals the glm-5p2 sibling. Assert against
the snapshot's own row (the gpt-5.6-codex pattern) so the test survives
later upstream reprices and the z-ai/ -> zai/ namespace rename.
2026-09-19 11:25:21 -07:00
Resham Joshi
3925cade06
fix: four provider parsing and pricing attribution bugs (#1489)
Antigravity now stores the command basename instead of the full command line, so secrets in flags no longer reach the on-disk session cache, matching every other provider. A Copilot OpenTelemetry skill or tool is credited once per trace instead of once per chat span, so a multi-span trace no longer multiplies it in the skill and tool breakdowns; grand totals are unchanged, and the copilot parse version and the status snapshot version are bumped so warm caches re-derive. The daily-cache carried-remainder row now also carries a token shortfall, so per-model token sums reconcile to the day total where cost and calls already did. Bedrock provisioned-model and custom-model ARNs are recognised as unpriced Bedrock rather than hidden as local inference. Verified against the real cache: report, models and menubar totals identical to the cent.
2026-09-18 15:15:10 -07:00
Ulises
9cd7dc5c98
fix(models): a Bedrock -v1:0 version is not an Ollama tag (#1463)
Bedrock foundation-model ids end in a -[v]<major>:<minor> version. looksLikeLocalModel read that colon as an Ollama :tag, so an unpriced Bedrock model was classed as free local inference: excluded from models --unpriced, from the unpriced warning and from the pricing-coverage denominator, with metered spend showing $0. The version shape is now exempted before the tag rule. Covers ids without the v (openai.gpt-oss-120b-1:0, cohere.rerank-v3-5:0); Ollama and LM Studio tags are unaffected.
2026-09-17 12:50:41 +02:00
Resham Joshi
954bfe3e32
fix(models): price cliproxy-gateway routed ids as the models they wrap (#1439)
* fix(models): price cliproxy-gateway routed ids as the models they wrap

codex-cliproxy-gateway forwards only cliproxy/* ids to a local CLIProxyAPI,
so routed Codex sessions record cliproxy/<id> model names, sometimes with a
CLIProxyAPI provider path behind the wrapper (cliproxy/zcode/glm-5.3-flash).
None of those match a pricing row, so the tokens were counted but the dollars
were not, and the models table hides zero-cost rows — routed spend looked
absent rather than unpriced.

Peel the cliproxy/ wrapper like the other routing wrappers and treat zcode
as a known vendor namespace so the provider path strips to its priced glm-*
leaf. An unknown vendor behind the wrapper still fails closed.

* Fix cliproxy-gateway README link and correct bare-glm pricing comment

---------

Co-authored-by: ozymandiashh <234437643+ozymandiashh@users.noreply.github.com>
2026-09-15 21:13:27 +02:00
Aditya Vikram Singh
0455250526 derive versioned Claude family labels 2026-09-02 19:15:57 +05:30
sm0keyyy
b79cf40169
fix(omp): correct OMP period scoping, cost attribution, and Grok 4.6 prompt tiers (#1150)
Some checks failed
Tests / test (22) (push) Has been cancelled
Tests / test (22.13.0) (push) Has been cancelled
CI / semgrep (push) Has been cancelled
* fix(omp): surface nested agent activity

* fix(omp): retain reported agent costs

* refactor(omp): remove unused message field

* fix(status): refresh OMP agent payloads

* Fix Grok 4.6 prompt tier pricing

* fix(omp): retain timestamps and reprice zero costs

* Test aliased Grok tier overrides

* refactor(omp): extract tier/model helpers, pass structured subagent fields

- models.ts: pull the Grok 4.6 high-tier swap into tieredCostsFor() so
  the next tiered model extends one branch instead of copy-pasting the
  inline condition in calculateCost.
- pi.ts: extract the message-model resolution chain into
  resolveMessageModel(); document the zero-reported-cost heuristic
  (OMP writes cost.total = 0 for xai-oauth, so zero acts as absent).
- usage-aggregator.ts: stop formatting a display string into the OMP
  subagent row's name field; the payload carries agentName plus the
  structured fields (model, startedAt, token counts) and the menubar
  formats its own label. Removes the now-unused compactTokenCount.

---------

Co-authored-by: AgentSeal <resham0209@gmail.com>
2026-08-26 22:26:06 -07:00
iamtoruk
195bb9128e chore: release 0.9.21
Version 0.9.21 across CLI, desktop app, and Windows menubar; refreshed
LiteLLM pricing snapshot and fallback (catches the 2026-08-24 OpenAI
GPT-5.6 cut and DeepSeek v4 repricing mid-rollout, #1134 tracks
re-tightening); CHANGELOG stamped for 0.9.21; README release links to
0.9.21. Snapshot-pinned model tests updated to current catalog truth.
2026-08-24 11:16:55 -07:00
Aditya Vikram Singh
aa1e38016f fix(models): leave orcarouter/auto unpriced and bump daily cache to v28
Sonnet was a guess against a Qwen/Llama flash route. Fusion aliases keep
the 2026-08 live completion-model provenance. Warm days re-derive.
2026-08-23 20:54:21 +05:30
Aditya Vikram Singh
dcab9a451c Identify OrcaRouter route ids as the routed model, not a branded label 2026-08-23 17:59:27 +05:30
Aditya Vikram Singh
5b25641440 Price and label OrcaRouter gateway model ids as the model they route to
Add orcarouter/ as a routing wrapper like cmd/ and antigravity/, alias the
smart and fusion routes to their current upstream rows, and give each route
a branded display name. Unknown vendors nested inside the route still fail
closed.
2026-08-23 17:42:38 +05:30
iamtoruk
fa89015947 fix(models): price claude-haiku-4.5 copilot session-store rows (#1093)
The Copilot session-store.db writes the model as claude-haiku-4.5
(tier-first, dot), but no pricing alias existed for that raw id, so
calculateCost returned $0 while GitHub billed the real Anthropic rate
(kelchm's #946 validation: $0.0063536 for a sampled request). Aliases
it to the existing, correctly-priced claude-haiku-4-5 snapshot row --
no new rate invented. Unlike #1090's gpt-5.6-codex case, this id has
no prefix-fallback self-heal, so the daily cache bumps v26 -> v27 to
force re-derivation of already-finalized days.

Drafted with cline-pass/deepseek-v4-pro via local gateway.
2026-08-22 11:41:08 -07:00
iamtoruk
99c72f7ccc Update stale flat-rate test expectation for codex-auto-review
The merge with main pulled in #1056's codex-auto-review -> gpt-5.5
alias, which findUnpricedModels' pre-existing hasBillableRate check
now resolves for the raw id, so a synthetic $0 row for it is no
longer reported as unpriced (it correctly has a billable rate).

Removed it from the expected findUnpricedModels() output in the
"skips subscription / flat-rate product SKUs" test; the display-name
variant ('Codex Auto Review') is unaffected since getModelCosts does
not resolve display names, so it stays in the expected list. The
codex-auto-review / #1056 pricing interaction itself is already
covered by the "Codex activity ids (#1047)" describe block.
2026-08-22 04:25:47 -07:00
iamtoruk
f9c99a0651 Merge origin/main into fix/968-subscription-flat-rate
Resolves conflicts against main after #1078/#1084/#1088/#1090/#1092/
#1053/#1056 landed since this branch's last upstream merge.

- CHANGELOG.md: kept both entries (this PR's #968/#1050 note plus
  main's #1079/#1088, #1082, #1075 notes that had moved into the same
  "### Fixed" slot).
- Everything else (src/models.ts, main.ts, usage-aggregator.ts,
  daily-cache.ts, tests/*) merged cleanly with no conflict markers;
  git's recursive merge combined #1050's flat-rate classifier changes
  with #1056's codex-auto-review -> gpt-5.5 alias without overlap.

Verified the codex-auto-review / #1056 interaction post-merge:
isBuiltInFlatRateModel no longer matches codex-auto-review (dropped
per #1050), while MODEL_ALIASES still aliases it to gpt-5.5 (#1056),
so it prices at GPT-5.5 rates rather than $0. Covered by the existing
tests/models.test.ts "Codex activity ids (#1047)" describe block and
the "does not treat a priced sibling as expected-free" case.

getFlatRateModelsConfigHash's output is folded into
getDailyCacheConfigHash's template literal unconditionally
(flatRateModels=<hash>), so the flat-rate section always participates
in the daily-cache invalidation hash even when empty -- no
DAILY_CACHE_VERSION bump needed for this change.
2026-08-22 04:17:13 -07:00
Resham Joshi
13c1785df7
Merge pull request #1056 from avs-io/fix/1047-codex-activity-ids
fix(models): price Codex activity ids via the official underlying model
2026-08-22 04:14:32 -07:00
iamtoruk
00377968a0 Merge origin/main into fix/967-models-display-merge
Resolves conflicts from main's #1078 (billableOutputTokens for the
output bucket) and #1084 (models-report test exemplar swap) against
this branch's canonical-id row merging.

- src/audit-report.ts: import-line collision only. Union both sides'
  imports (billableOutputTokens + fallbackRawModelDisplayName/
  getShortModelName); both are used elsewhere in the file and neither
  side's logic needed further changes.
- src/models-report.ts: import-line collision resolved the same way.
  The row-construction conflict was structural, not a data conflict:
  main's side pushed one row per raw bucket (a simpler variant this
  branch's canonical-id folding already made obsolete downstream --
  rowsByKey/foldedCategoryCost/foldKey are used unconditionally past
  this point). Kept this branch's rowsByKey merge-by-canonical-id
  structure, which already computes credits from bucket.outputTokens
  (already billable-output-summed at the accumulation stage, main's
  change, untouched by this conflict) using the identical formula
  main used; folded main's explanatory comment about billable output
  into the kept credits block.
2026-08-22 03:43:24 -07:00
Aditya Vikram Singh
090499ef7e merge: rebase #1056 onto main with Codex v14 / daily v25
Main already spent Codex results v13 (#1079) and daily v24 (#1090).
Keep all three parse-version tokens (codex-pricing-v1 +
codex-tps-v1 + activity-price-v1) so a take-ours merge cannot
drop the pricing or throughput invalidation. Do not Extra High again.
2026-08-22 05:26:00 +05:30
iamtoruk
98348d973b fix(models): price gpt-5.6-codex and gpt-5.6-codex-max
Neither id is in LiteLLM yet, so litellm-snapshot.json had no row for
them (285 sessions / 5,446 calls priced at $0 on one real corpus,
flagged during #1075 verification). Add explicit snapshot rows sourced
from the pattern every prior Codex-suffixed id LiteLLM does carry
follows: it bills identically to its bare-model sibling of the same
generation (gpt-5-codex == gpt-5, gpt-5.1-codex == gpt-5.1-codex-max ==
gpt-5.1, gpt-5.2-codex == gpt-5.2, gpt-5.3-codex == gpt-5.3, verified
against the live model_prices_and_context_window.json) - both new ids
get the exact gpt-5.6 tuple rather than an invented rate.

getModelCosts already resolved both ids to the correct rate through
the `gpt-5.6` prefix fallback before this, so a fresh parse or a warm
session-cache read (codeburn models included) was already pricing
these calls correctly. The daily cache is the one place that does not
self-heal: it has no per-provider invalidation, so a day finalized
before a `gpt-5.6` snapshot row existed at all keeps that $0 forever.
Raising MIN_SUPPORTED_VERSION (v23 -> v24) forces the one-time
re-derivation, a lossless no-op for days already correct - the
mechanism itself is generic and already covered by
daily-cache-version-rederivation.test.ts. #1056 also claims 24 on its
own branch; whichever lands second takes the next number.

Closes #1077
2026-08-21 14:53:00 -07:00
Aditya Vikram Singh
33d344abc3 merge: rebase #1056 onto main with Codex v12 / daily v24
Main already spent Codex results v11 and daily v23 on #1075, and
result caches are now version-suffixed. Keep both parse-version
tokens (codex-pricing-v1 + activity-price-v1) so a take-ours merge
cannot drop the pricing invalidation. Do not Extra High again.
2026-08-22 02:35:51 +05:30
iamtoruk
7876c8e9d7 fix(pricing): version the pricing cache and drop codex-credits' dead reasoning param
The pricing cache written to disk had no schema version, so a cache written
by a pre-#1078 binary lacked cacheWriteCostIsExplicit on every entry. Reading
it back resolved the missing key to undefined (falsy), silently reintroducing
the surcharge-fabrication bug #1078 killed for up to CACHE_TTL_MS after an
upgrade. loadCachedPricing now rejects any cache whose version doesn't match
the current schema instead of reading it verbatim.

codexCredits() still accepted an optional reasoningTokens param that added it
to output - the exact double-count #1078 removed from every real caller. The
only caller never passed it; deleted it so it can't be reintroduced by
accident.

parser.ts's activeGeneratedTokens fallback went through billableOutputTokens
in #1078, but codex is the only caller of activeDurationMs/activeGeneratedTokens
and always sets both together, so the fallback branch is unreachable for it.
Reverted to reduce diff noise.
2026-08-21 12:48:55 -07:00
Aditya Vikram Singh
912281e9b6 fix(models): merge models report rows by canonical id, not display name
Maintainer review on #1053: display-name keys collide for distinct
SKUs (GPT-5 / GPT-5.3 Codex / Kimi K2 Thinking / Opus 4.6). Key on
the alias-resolved canonical id, keep the first raw id, and partial-sum
Codex credits when a merge mixes rated and unrated buckets.
2026-08-21 17:26:40 +05:30
Aditya Vikram Singh
65263da303 fix(models): classify real subscription SKUs and let --remove opt out built-ins
Maintainer review on #1050: drop metered codex-auto-review, add
kimi-for-coding-highspeed, match Warp's auto id, drop unsourced
big-pickle, and give model-flat-rate --remove power over built-ins.
2026-08-21 16:45:24 +05:30
Aditya Vikram Singh
61e76b7989 fix(models): price Codex auto-review as GPT-5.5 and take daily v22
Maintainer review on #1056: pin the activity id to the recommended
review model (existing bundled row, not an invented rate), resolve
the same alias for credits, and take daily-cache 22 so this PR does
not collide with #946 claiming v21.
2026-08-21 15:56:25 +05:30
Aditya Vikram Singh
f736569435 fix(models): reprice cached Codex auto-review and drop unproved sibling
Extra High MERGE AFTER FIX on #1056: bump Codex/session/daily cache so the 659M-token corpus reprices; do not invent codex-code-review.
2026-08-20 11:08:38 +05:30
Aditya Vikram Singh
7ee4ec2052 fix(models): price Codex activity ids via the official underlying model
Closes #1047.
2026-08-20 10:44:13 +05:30
Aditya Vikram Singh
21ef46523d fix(models): verbose unknown warning names both hatches
Extra High: calculateCost still told users to model-alias an
unknown SKU unconditionally. Route that warning through
unpricedModelHint so alias and model-flat-rate are both named.
2026-08-20 02:29:54 +05:30
Aditya Vikram Singh
4228c87595 fix(models): treat subscription SKUs as honestly $0
Unpriced warnings were firing on flat-rate product ids and telling
users to model-alias them, which invents spend.
2026-08-20 01:35:39 +05:30
iamtoruk
02059c961c Merge remote-tracking branch 'origin/main' into pr1039-rebase
# Conflicts:
#	src/models.ts
2026-08-19 12:07:02 -07:00
iamtoruk
bb5b71dff1 feat(models): name the MiMo v2 Flash row
It was the only MiMo row still rendering as its raw slug next to
"MiMo v2.5" and "MiMo v2.5 Pro". SORTED_SHORT_NAMES is longest-first, so
the two v2.5 entries keep their own labels.
2026-08-19 11:33:13 -07:00
iamtoruk
e6cac54661 fix(models): derive the vendor-namespace allowlist from the pricing catalog
The hand-written KNOWN_NAMESPACES set dropped pricing for vendor prefixes
LiteLLM itself indexes: x-ai/, nousresearch/, zhipu/, litellm_proxy/ and
openai_like/ all priced on main and went unpriced here. Derive the set from
the loaded pricing keys instead, so a vendor the catalog knows is never lost
to a stale list, and keep only the spellings no catalog carries as explicit
extras: the routing wrappers, the client-side kimi/ and mimo/ prefixes, and
the litellm_proxy/ + openai_like/ routes. Local runners are excluded on
purpose, so an unlisted ollama tag cannot strip down to a priced cloud row.

xiaomi/ stops being a routing wrapper: it is the namespace LiteLLM prices
MiMo under, and BUILTIN_ALIASES maps the bare MiMo ids INTO it, so peeling
pulled against the alias. It stays known via the derived set.

Also: a user price override for a bare id now wins over the catalog row a
routed spelling of it would otherwise hit, and a namespaced GLM-5.3 is no
longer LABELLED GLM-5.2 by the sibling alias it prices through.

Tests assert the allowlist through a price override on a synthetic id, so
they cannot rot with the snapshot; the glm-5.4 assertion that pinned on the
snapshot NOT carrying a model is dropped.
2026-08-19 11:31:34 -07:00
iamtoruk
ef040a4c4d test(models): pin the shipped MiMo v2 Flash crash; name the base 2.5 row
The `mimo-v2-flash -> xiaomi/mimo-v2-flash` alias shipped before this
branch and already cycled through display-name resolution, so
getShortModelName threw RangeError on every real MiMo v2 Flash session.
The new cycle-safe resolver fixes it, but nothing pinned the ids that
actually crashed in production: cover the four spellings found in a real
session cache, including the unnamespaced `mimo/mimo-v2-flash`.

Add the base `mimo-v2.5` display name so the row reads next to
"MiMo v2.5 Pro" instead of showing a raw slug; SORTED_SHORT_NAMES is
longest-first, so the Pro tier still wins its own entry.
2026-08-19 11:26:22 -07:00
Aditya Vikram Singh
827a41241c fix(models): keep getShortModelName unary for Array.map
CI typecheck failed: sessions-report maps getShortModelName, and
the Extra High cycle Set was a second parameter. Array.map fed
the index as `seen`.

Cycle tracking stays on an internal helper. Display and alias
behavior unchanged. No second Extra High.
2026-08-19 21:27:13 +05:30
Aditya Vikram Singh
d5ced78595 fix(models): cycle-safe short names; keep user-alias display
Extra High MERGE AFTER FIX on d3f86f5. mimo-v2.5 aliased to
xiaomi/mimo-v2.5 then last-segment recursed forever. Looking up
SHORT_NAMES before resolveAlias also froze user remaps of known
ids (gpt-4o still displayed as GPT-4o).

Follow user aliases first. Break strip→alias→leaf cycles.
Do not invent a Kimi rate. Do not paper over this with a
mimo-v2.5 SHORT_NAMES row.
2026-08-19 21:10:33 +05:30
Aditya Vikram Singh
d3f86f5d16 fix(models): alias bare MiMo 2.5 ids to the LiteLLM Xiaomi rows
Hermes and token-plan sessions store mimo-v2.5-pro. The snapshot
row is xiaomi/mimo-v2.5-pro. Same class as the existing
mimo-v2-flash alias. No invented rate.

Looking up the display name on the stripped leaf before following a
pricing alias, so cline-pass/mimo-v2.5-pro cannot recurse
strip → alias → last-segment forever.
2026-08-19 20:44:18 +05:30
Aditya Vikram Singh
fae4db0008 fix(hermes): restore z-ai pricing; require owner/repo; reject UNC
Extra High held 91754c5. z-ai/glm-5.2 went unpriced (Cline's real
vendor spelling). Forward-slash UNC became a workspace on POSIX.
PR matching used basename, so evil/codeburn collided. Tilde fences
were still scanned.

Add z-ai to known namespaces. Reject // UNC on POSIX. Attribute
PRs only when origin yields owner/repo. Strip ``` and ~~~ fences.
Bump Hermes parse version to v4.
2026-08-19 20:32:51 +05:30
Aditya Vikram Singh
91754c5f54 fix(hermes): fail closed on unknown namespaces, relative cwd, fenced PRs
Extra High held #1039 again. An unknown provider/model still became a
bare-model price via getCanonicalName's first-segment strip. Relative
cwd values like '.' could inherit the invoking repo. PR scrape treated
fenced dumps and other repos as attribution.

Peel only known vendor/router namespaces. Require an absolute
platform path before a Hermes cwd is a workspace. Ignore fenced URLs
and, when a git root exists, keep only that repo's pull links.
Bump the Hermes parse version so old cache rows reparse.
2026-08-19 20:19:29 +05:30
Aditya Vikram Singh
10fdec6037 fix(hermes): fail closed on unknown GLM, namespaces, and ACP identity
Extra High review held #1039. Stop pricing every future glm-5.x as 5.2,
stop collapsing unknown provider/org/model trees onto a priced leaf, and
stop calling ACP Buzz — source=acp is a transport and the DB has no
client field. Strip trailing punctuation on PR URLs and bump the Hermes
parse version so old cache rows reparse.
2026-08-19 19:42:42 +05:30