Commit graph

44 commits

Author SHA1 Message Date
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
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
Aditya Vikram Singh
66a776acfa fix(hermes): treat routing prefixes and session surfaces as classes
Price any OmniRoute/Cline/cmd/antigravity wrapper by peeling prefixes
and, for GLM 5.x, falling back to the newest priced sibling. Do not
require a new alias per model id.

Classify Hermes sessions by surface and workspace: ACP is the Buzz
app; project comes from git root or a real cwd, never $HOME or a
profile name.
2026-08-19 18:46:11 +05:30
Aditya Vikram Singh
f97d084109 fix(models): price gateway-prefixed glm-5.3 ids
Hermes stores Cline Pass as cp/cline-pass/glm-5.3. One prefix strip
leaves cline-pass/glm-5.3, which missed the bare glm-5.3 alias and
stayed $0. Price the last path segment through the same aliases.
2026-08-19 18:20:01 +05:30
Aditya Vikram Singh
0658308448 fix(hermes): price glm-5.3, label default as hermes, capture PR links
GLM-5.3 is missing from LiteLLM, so Hermes/Cline sessions priced at $0.
Alias it to the glm-5p2 sibling and keep the display name GLM-5.3.

The root Hermes profile was shown as project "default". Use "hermes"
for that profile only; named profiles and cwd inference are unchanged.

Scan Hermes transcripts for https GitHub pull URLs and attach them so
the Pull requests page can attribute those sessions.
2026-08-19 18:06:51 +05:30
Aditya Vikram Singh
eadc99ef99
fix(models): correct Sonnet 4 thinking alias (#982)
Co-authored-by: Aditya Vikram Singh <247195684+avs-io@users.noreply.github.com>
2026-08-18 03:58:50 +03:00
iamtoruk
cecb6239c6 fix(serve): close the resident-process staleness and growth holes
Adversarial review of the serve design surfaced three weaknesses a
one-shot CLI never had, because it never lived long enough:

- Pricing-affecting config (model aliases, price overrides, local-model
  savings) now participates in the parse memo key. Config reloads fresh
  per request (the preAction hook), but a memoized or burst-reused parse
  embedded costs priced under the OLD config; the widened key makes any
  such change an automatic memo miss. New alias-hash helper + tests.
- Memory guard: past 3GB RSS the serve loop drops its in-memory memos
  (session cache + parse entries) and the next request re-parses once.
  The child never exits for this, so the client's death budget is
  untouched.
- codeburn serve typed in an interactive terminal now explains itself on
  stderr instead of hanging silently on stdin.
2026-08-10 10:03:33 -07:00
ozymandiashh
48fd0daa0c fix(providers): guard two malformed-input crashes in the parse path
- vscode-cline-parser: entry.ts was truthy-checked but not validity-checked,
  so a garbage timestamp made new Date(ts).toISOString() throw RangeError and
  abort the whole session parse. Validate the date, fall back to empty.
- models: parseLiteLLMEntry read fields off its argument with no null/type
  guard, so a null value in the remote LiteLLM pricing JSON threw and aborted
  the entire live pricing load. Return null for a null/non-object entry.

Both mutation-checked: the tests raise RangeError / TypeError before the fix.
2026-08-04 06:28:41 +03:00
Rick Culpepper (claude)
572c992aea models: name GPT-5.6, Grok 4.5 and ClinePass slugs instead of showing raw ids
Several model ids price correctly but had no SHORT_NAMES entry, so the
By Model panel rendered the raw slug next to properly named siblings:
`gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `grok-4.5`,
`qwen3.7-max`, `minimax-m3` and `mimo-v2.5-pro`.

All display-only; no dollar amounts move.

Notes on the less obvious ones:

- The GPT-5.6 variants are listed individually rather than as a bare
  `gpt-5.6`. A base entry would swallow every future `gpt-5.6-*` through
  the prefix match and hide the variant behind a sibling's label, which
  is exactly what getShortModelName's version-boundary rule prevents. An
  unlisted variant still falls through to its raw id, and there is a test
  pinning that.
- `grok-4.5` is the model the Grok Build harness runs and reports as
  `current_model_id`, so it takes the model's own name. Ids that really
  are `grok-build*` keep the "Grok Build" label, also covered by a test.
- ClinePass routes models as `cline-pass/<slug>`. No new prefix handling
  was needed: getShortModelName's path fallback already strips the
  prefix and re-resolves the bare slug, the same way it handles
  `accounts/fireworks/models/<slug>`.
- MiniMax M3 is mapped under both the lowercase OpenRouter slug and the
  capitalized spelling sessions report, since SHORT_NAMES matching is
  case-sensitive (the case-insensitive index covers pricing only).

`mimo-v2.5-pro` remains unpriced upstream; this only gives it a name.
2026-08-04 02:45:16 +03:00
AgentSeal
a8a954fd78 fix(models): price codex Kimi rollouts recorded as kimi/k3[1m]
Codex driving a Kimi backend records the model as kimi/k3[1m] (provider prefix
plus a [1m] context tag). getCanonicalName stripped the prefix but not the tag,
so it matched no alias and priced to $0 - Kimi-via-codex spend was silently
reported as free, and the menubar showed no Codex segment. Strip a trailing
[...] context tag so kimi/k3[1m] -> k3 -> kimi-k3, repairing both cost and the
display name.
2026-08-04 00:38:40 +02:00
reviewer
be5c0c00aa kimicode: discover desktop-runtime sessions and fix menubar visibility
- Resolve k3/k3-agent/k2d6-agent model aliases to canonical Kimi names
- Discover sessions across all Kimi Code homes (CLI + desktop runtime)
- Accept conv-*/ctitle-* session directory naming, not just session_*
- Add Kimi Code provider tab with brand color to the menubar
- Show short model names (Kimi K3, Kimi K2.6) in the menubar payload
2026-07-23 21:31:25 +02:00
Rick Culpepper
475ff502e0
models: resolve Fireworks-hosted models to friendly display names (#787)
* Resolve Fireworks-hosted models to friendly display names

Fireworks fleet models arrive as `accounts/fireworks/models/<slug>` and
were leaking the raw path/slug in reports and the dashboard (e.g.
`fireworks/mode…`, `glm-5p2`). getShortModelName's path fallback now takes
the last path segment and re-resolves it, so a known slug earns a friendly
name (GLM-5.2, Qwen 3.7 Plus, Kimi K2.7 Code, DeepSeek v4 Pro/Flash) while
an unmapped slug still falls through to the raw segment.

Display-only: getModelCosts prices off the full id, so dollar amounts are
unchanged. The By Model dashboard panel now renders through getShortModelName
too, so already-cached raw keys normalize without a cache rebuild.

* Merge By Model rows that resolve to the same display name

Once path-style ids normalize, a mixed-vintage cache can hold more than one
raw key for the same model (the full accounts/fireworks/models/<slug> path
from an older build and the bare slug/friendly name from a newer one). The
dashboard By Model panel aggregated by raw id, so those rendered as duplicate
rows once both mapped to the same display name.

Extract the aggregation into a pure, tested aggregateModelTotals() keyed by the
resolved display name so the rows merge into one. This also aligns the panel
with modelEfficiency, which is already keyed by getShortModelName.
2026-07-20 14:22:12 -07:00
iamtoruk
b71ec34a21 fix(usage): per-provider snapshot capture, placeholder guard, path-id display fallback 2026-07-18 03:37:42 -07:00
AgentSeal
bf6eee00f3 fix(report): only exact zero-rate overrides suppress the unpriced flag
getModelCosts resolves exact overrides before any table hit, so an exact
zero-rate override provably priced the model and means free. Prefix and
case-insensitive overrides resolve after table hits: a zero-rate stub
shadowed by one still reports $0 from the stub, so it stays flagged.

Refs #638
2026-07-09 21:22:23 +02:00
AgentSeal
e6162f6b50 fix(report): address unpriced-detector review findings
Adversarial review found two false-negative paths and two polish items:

- Drop the reverse-display-name suppression. Droid prices the lowercased
  display name and gets $0, so a $0 row keyed "Sonnet 4.6" is real
  uncounted spend; suppressing it because claude-sonnet-4-6 prices today
  hid exactly the failure this feature exists to expose.
- Treat zero-rate pricing hits as unpriced. LiteLLM ships [0,0] stubs
  for models it lists without a price; a stub hit means unknown price,
  not free. Explicit zero-rate user overrides still count as free.
- Make MenubarPayload.current.unpricedModels optional for source
  compatibility with payload producers that predate the field.
- Replace localeCompare with a byte comparison so tied rows sort
  identically on every host locale.

Refs #638
2026-07-09 21:17:47 +02:00
AgentSeal
231e7cc0d4 feat(report): surface unpriced models across overview, JSON, dashboard, and MCP
Models missing from the pricing tables silently contributed $0 to every
total with no indication anywhere (the warning was gated behind
CODEBURN_VERBOSE). Add render-time detection that flags aggregated model
rows with usage but $0 cost whose pricing lookup fails right now, and
surface it in overview, report/today/month JSON (unpricedModels), the
TUI By Model panel, and the MCP get_usage summary.

Render-time detection covers cached sessions (cost is computed at parse
time) and heals as soon as pricing data, an alias, or a price override
arrives. Rows with real cost are never flagged: aggregation keys rows by
display name, which the pricing lookup misses; $0 display-name rows are
reverse-resolved to their raw id before flagging so sessions cached
before their model's pricing landed don't get a misleading alias hint.
Local models and model-savings mappings stay excluded: $0 is correct
for them.

Fixes #638
2026-07-09 21:04:56 +02:00
AgentSeal
f2fa575a52 Fix model pricing variant resolution 2026-07-08 22:55:44 +02:00
Resham Joshi
f1a4e8cc4f
fix(cursor): use Cursor's real context tokens for input (#574) (#575)
* fix(cursor): use Cursor's real context tokens for input

Current Cursor builds leave the per-bubble tokenCount at {0,0}, so the provider
fell back to estimating input from visible text plus a second agentKv
content-char pass that double-counted the same conversation. Cursor records its
own tokenizer-accurate context size per conversation in
composerData.promptTokenBreakdown (the number behind the in-app context-window
bar); read that and credit it once per conversation for input instead.

Measured on a real local DB: today's Cursor input went 44,873 -> 168,486 tokens,
matching the sum of per-conversation context. The admin portal still counts
cumulative-per-turn plus cache, which are server-side only, so an opt-in Cursor
API stays the path to exact parity.

Output is a reply-text estimate; agentKv is retained for a tools/bash breakdown
in a follow-up.

* feat(cursor): add tools and bash-command breakdown from agentKv

Cursor logs the agent's tool calls (Read, Grep, Glob, Shell, ...) in agentKv
blobs. Join them to conversations via the turn requestId (carried on the bubble's
$.requestId and inherited positionally by the turn's agentKv rows) and attach each
conversation's tool list and Shell commands to the call that carries its input.

Measured on a real DB: Cursor now reports tools {Read, Grep, Glob, Shell,
SemanticSearch} and the executed shell commands, which were previously empty.

Removes the now-superseded parseAgentKv content-char estimate.

* fix(cursor): price composer-2.5 as Sonnet 4.6

composer-2.5 was missing from the built-in Cursor model aliases, so its usage
showed $0. Map it to claude-sonnet-4-6 like composer-2 (per cursor.com/blog).

* fix(cursor): cache version, model attribution, user message join, tool classification

- Bump CURSOR_CACHE_VERSION to 5: parser semantics changed (parseAgentKv
  removed, real context tokens from composerData.promptTokenBreakdown),
  stale v4 caches would show double-counted agentKv calls.
- Fix model attribution: real input tokens are credited on user bubbles
  (type=1) which carry no modelInfo. Add a pre-pass building composerId ->
  model from assistant bubbles so pricing/display uses the conversation's
  actual model instead of the default cursor-auto/sonnet-4.5.
- Fix buildUserMessageMap: was keying by JSON conversationId (empty in
  current Cursor builds). Now extracts composerId from the bubble key,
  matching parseBubbles.
- Add 'Shell' to BASH_TOOLS in classifier: Cursor's agent uses 'Shell'
  as the tool name, but it was missing from the bash tool set so Cursor
  agent turns with shell commands wouldn't classify as bash/build/test.
- Fix null coalescing in loadComposerInputTokens: r.used ?? r.ctx would
  fall through on a valid totalUsedTokens of 0. Use explicit null check.
- Decouple agentTools attachment from input credit: tools/bash were only
  attached on the first credited turn (creditedHere), silently dropping
  tool usage from subsequent turns in multi-turn conversations.
- Update stale comment about parseAgentKv being kept for a follow-up.
- Add tests for real token crediting, once-per-conversation, fallback,
  contextTokensUsed, tool/bash attribution, and model attribution.

* fix(cursor): avoid duplicating aggregated agent tools

* fix(cursor): price house composer models from Cursor's published rates

composer-1/1.5/2/2.5 were proxied to Claude Sonnet, overcounting cost
(~6x for composer-2/2.5). Use Cursor's published per-model rates instead,
and note in the parser why local reads undercount the admin console.

Co-authored-by: AgentSeal <hello@agentseal.org>

* fix(cursor): estimate non-Composer turn input from the agent stream

Non-Composer sessions (e.g. GPT) record no context-window meter and keep
the prompt in the agent stream, so the user bubble's own text is empty.
Those turns hit the 0/0-token fallback with text_length 0 and were dropped
entirely, so that model's traffic never appeared in the report.

loadAgentToolsByComposer now also sums the user-role stream text length per
conversation, and the meterless fallback estimates input from it (chars/4),
credited once per conversation, when the bubble text is empty. Turns with no
stream text are left untouched, so no phantom tokens are invented.

* fix(cursor): stable conversation crediting, restored stream coverage, and cache invalidation

Review fixes for the real-token accounting:

Conversation input now lands on one composer-anchored record
(cursor:composer-input:<id>) timestamped at composerData.createdAt, so
the credited day no longer depends on the parse window or cache floors,
daily-cache gap fills dedupe instead of multiplying, and each
conversation picks exactly one input source (real bubble tokenCounts,
the context meter, the agent stream, or visible text) so sources can
never stack or double count. A zero totalUsedTokens no longer shadows
contextTokensUsed.

The agent stream regained what the parseAgentKv removal dropped: tool
and system rows count as context, stream-only replies count as output,
and sessions with no bubble join are emitted again (DB mtime timestamp,
as before). Block-array content is measured by its text, not its JSON
envelope. Rows written before their requestId appears buffer forward
instead of inheriting the previous conversation, and a system row closes
the boundary. Tool names canonicalize to Bash and commands go through
extractBashCommands so cross-provider breakdowns merge; the classifier
no longer special-cases Shell (which also reclassified Copilot turns).
User bubbles consume their own queue entry so assistant replies pair
with the right question, and every cursor call is flagged
costIsEstimated.

The requestId and model joins ride the existing budgeted bubble scan
instead of two new unbounded full-table decodes, and the composerData
read seeks the key range. SQLITE_BUSY now propagates to the parser's
retry path instead of caching a silently degraded parse.

Upgrades actually take effect: the session cache gets a cursor parse
version, DAILY_CACHE_VERSION bumps to 10 so finalized days re-hydrate
under the new accounting, the cursor results cache bumps to v6, and the
builtin composer rates participate in the price config hash (rates now
cite cursor.com/docs/models).

Verified against a real Cursor store: all metered conversations match
the on-disk meter exactly, narrow and wide parse windows anchor
identically, repeat runs are byte-identical, and agentKv-only sessions
reappear.

---------

Co-authored-by: ozymandiashh <234437643+ozymandiashh@users.noreply.github.com>
2026-07-02 04:53:07 +02:00
ozymandiashh
2a0edd0d68
feat(pricing): add user price overrides for models (#390) (#560) 2026-06-28 19:07:29 +02:00
Resham Joshi
71b1a9ebce
fix: clean model names in reports and re-hydrate daily cache for new providers (#550)
Two post-0.9.12 cleanups.

Report model names: getShortModelName resolves a model's pricing alias before looking up its display name, so models priced via a sibling alias (ZCode/Hermes GLM-5.2 via glm-5p1, Grok Build via grok-build-0.1) leaked the internal pricing key as the model name in report --format json and the menubar model breakdown. Grok Composer was unmapped and showed raw. Add SHORT_NAMES entries so each resolves to its real name (GLM-5.2, Grok Build, Grok Composer 2.5 Fast). The models command was already correct because it uses each provider's own modelDisplayName.

Daily cache: providers added since the v8 rollup (Grok, Hermes, ZCode) parse usage that older binaries skipped, so days cached at v8 omit them and report 0 across history. Bump DAILY_CACHE_VERSION and MIN_SUPPORTED_VERSION to 9 to force a one-time full re-hydration so new providers backfill without a manual cache clear.
2026-06-22 03:38:50 +02:00
ozymandiashh
4dcb7e6c3d
fix(models): price Hermes lowercase glm-5.2 the same as GLM-5.2 (#545) 2026-06-22 01:02:59 +02:00
ozymandiashh
7c2d36f1f0
Distinguish gpt-5.3-codex-spark from base GPT-5.3 Codex label (#539)
gpt-5.3-codex-spark is a distinct model variant, but the longest-first
startsWith(key+'-') matcher (intended for reasoning suffixes -high/-low)
swept it into the base 'GPT-5.3 Codex' label, making a distinct model look
like the retired base in today/models/status output. Add an explicit
display entry in SHORT_NAMES and the codex provider so the longer key wins;
-high/-low still fall through to the base. Pricing is unaffected (LiteLLM
has no spark entry; cost falls back to the base rate as before).

Fixes #461
2026-06-22 00:44:56 +02:00
Tiago Santos
75c32e6d65
fix: fix and improve test isolation and collision with environment (#530)
* fix: fix and improve test isolation and collision with environment

* docs: remove unnecessary comment

* test(env-isolation): clear CODEBURN_FORCE_MACOS_MAJOR and pin TZ

Two env vars read in src/ were not isolated: CODEBURN_FORCE_MACOS_MAJOR
(now cleared so it cannot leak between tests) and TZ (now pinned to UTC,
since clearing it falls back to the OS zone and would shift date buckets
versus a clean CI runner).

---------

Co-authored-by: AgentSeal <hello@agentseal.org>
2026-06-20 13:42:10 +02:00
Resham Joshi
ad251cfa3d
chore(pricing): drop manual Fable/Mythos patch; fable now gap-filled from models.dev/OpenRouter; keep Fable 5 name (#464) 2026-06-09 21:22:38 +02:00
Resham Joshi
a385f65dee
feat(pricing): automatic gap-fill from models.dev and OpenRouter (#457)
Keep model pricing automatic instead of hand-coding new models. The bundler
now layers three sources in priority order: LiteLLM (broad list prices),
hand-curated MANUAL_ENTRIES overrides, then a separate last-resort fallback
file gap-filled from models.dev first-party makers (official direct prices)
and OpenRouter (resale backstop). New models such as MiniMax-M3 ($0.6/$2.4)
now price correctly with no per-model code.

The fallback is written to its own pricing-fallback.json and consulted only
case-insensitively as the final step in getModelCosts, so a reseller variant
name can never shadow a canonical or aliased match.

Fixes surfaced while building and verifying this:
- Alias precedence: LiteLLM ships snowflake/claude-4-opus ($5), which the
  bundler strips to a bare claude-4-opus key that shadowed the curated alias
  to claude-opus-4 ($15 official). An explicit alias for a bare name now wins
  over a coincidental stripped reseller key; the prefixed gateway price is
  still returned for the fully-qualified id.
- Zero-stub guard: LiteLLM [0,0] price stubs (e.g. GigaChat-2-Max) are
  excluded from the case-insensitive index so a case-mismatched query stays
  null and keeps firing the unknown-model warning instead of silently
  reporting $0.
- Negative-sentinel guard: OpenRouter returns -1 for variable/BYOK-priced
  models. The bundler now rejects any non-positive rate pair (and strips the
  sentinel from cache fields) so a negative per-token cost can never ship and
  subtract from spend totals.

Bundler hardening: bareKey strips @pin and date suffixes to match the runtime
canonical form, seen-set dedupes on both full and bare key shapes, and it logs
MANUAL_ENTRIES now covered upstream plus models.dev allowlist drift. Extracted
buildCosts so the cache-cost heuristics live in one place. Added a data-hygiene
test that fails CI if a rebundle reintroduces negative, free, or unreachable
fallback entries.
2026-06-09 21:17:23 +02:00
Resham Joshi
c36f3afa76
chore(pricing): temp Fable 5 + Mythos 5 launch pricing ($10/$50 per M) + names until LiteLLM indexes them (#463) 2026-06-09 20:51:51 +02:00
iamtoruk
aa9bd9f0f1 feat(models): derive Claude names and fast multipliers automatically
New Claude releases no longer need a hand-maintained SHORT_NAMES entry or
FAST_MULTIPLIERS row. Display names are derived from the claude-<family>-<major>-<minor>
id, and the fast-mode multiplier rides along as a 5th element in the LiteLLM
snapshot tuple (provider_specific_entry.fast). Fixes #420: claude-opus-4-8 gets
its own line and correct pricing instead of falling into the Opus 4 bucket.
2026-05-31 05:17:14 -07:00
Tony Amirault
bbbdcd4eb8
fix(models): map Warp Claude variants to canonical pricing IDs (#378)
Co-authored-by: Tony Amirault <tony.amirault@wakam.com>
Co-authored-by: Oz <oz-agent@warp.dev>
2026-05-22 02:05:26 -07:00
ozymandiashh
d0f1f82bf4
Fix Antigravity 2 Gemini 3.5 Flash tracking (#377) 2026-05-22 02:01:20 -07:00
ozymandiashh
17eada2aa1
fix: DeepSeek v4 Claude pricing through stale runtime cache (#367)
Some checks are pending
CI / semgrep (push) Waiting to run
2026-05-21 00:34:53 -07:00
AgentSeal
c85beeaeae
Fix Claude 1-hour cache write pricing (#317)
Co-authored-by: ozymandiashh <234437643+ozymandiashh@users.noreply.github.com>
Co-authored-by: iamtoruk <hello@agentseal.org>
2026-05-11 21:23:04 -07:00
Resham Joshi
cdf7169a89
Cursor model aliases: cover every variant so non-Auto sessions price (#159) (#290)
Cursor emits model names in a `claude-<dot-version>-<tier>` shape
(`claude-4.6-sonnet`, `claude-4.5-opus`, `claude-4.5-opus-high-thinking`,
etc.) plus its own `composer-1` house model. None of these match
the canonical LiteLLM pricing keys (`claude-sonnet-4-6`,
`claude-opus-4-5`).

The alias map in `src/models.ts` filled some of these in v0.9.4
but missed:

- plain no-suffix forms: `claude-4.5-opus`, `claude-4.5-sonnet`,
  `claude-4.6-opus`
- haiku tier: `claude-4.5-haiku`, `claude-4.6-haiku`
- forward-looking: `claude-4.7-opus`
- Cursor's house model: `composer-1`

The dashboard rendered $0 for sessions that used any unaliased
model — visible in the screenshots posted in #159 even after the
v0.9.4 fix that added the `-thinking` variants.

This PR fills the gaps and adds 16 regression tests under
`Cursor model variants resolve to pricing` that assert every
model name in `src/providers/cursor.ts:modelDisplayNames` plus
the additional plain forms resolves to a non-null pricing entry
with `inputCostPerToken > 0` and `outputCostPerToken > 0`. So a
future LiteLLM snapshot bump or a typo in the alias map will fail
the test before users see $0.

Direct hits in the snapshot (no alias needed): `gpt-5`, `gpt-5.2`,
`grok-code-fast-1`, `gemini-3-pro` (already aliased). These are
covered in the test suite as well so a snapshot that drops them
would also be caught.

Tests: 45 files, 617 passing locally (16 new). Closes #159.
2026-05-10 03:27:44 -07:00
iamtoruk
c2ab80d6e2 Merge main into feat/omp-support-model-aliases
Brings the PR branch up to the current main so the OMP provider and the
model-alias command can land cleanly. Resolves six merge conflicts and
applies a handful of small fixups alongside the resolution so the
feature matches the conventions set by the cursor-agent merge earlier
today.

Conflict resolutions:

  README.md               Combine cursor-agent and OMP rows in provider
                          list, Requirements, and data-location table;
                          take main's Node 22+ and node:sqlite text.
  src/cli.ts              Keep both new commands: model-alias and plan.
  src/config.ts           Add modelAliases alongside plan on the config
                          type.
  src/providers/index.ts  Keep the cursor-agent lazy-loader from main
                          and add omp to coreProviders. Fold the two
                          pi-module imports into one statement.
  src/providers/pi.ts     Keep the discovery-cache snapshot path from
                          main and the providerName parameterization
                          from the PR. Propagate providerName through
                          saveDiscoveryCache, loadDiscoveryCache, the
                          parserVersion tag, and the dedup key prefix
                          so OMP sources no longer stamp 'pi:' inside
                          their cache entries or dedup keys.
  tests/models.test.ts    Keep main's pricing-and-short-name tests and
                          add the PR's alias tests alongside, sharing a
                          single loadPricing setup and an afterEach
                          alias reset.

Fixups in the same commit:

  src/models.ts           Replace ?? chain in resolveAlias with
                          Object.hasOwn checks. The previous form
                          returned Object.prototype for a model named
                          '__proto__' and broke downstream
                          canonical.startsWith calls. Caught by the
                          existing prototype-pollution test suite.
  src/providers/pi.ts     Use source.provider in the dedup key prefix
                          and add a trailing newline to the file.
  tests/providers/omp.test.ts  Expect 'omp:' in the dedup key for OMP
                          sources, matching the fix above.

Feature work by @cgrossde.
2026-04-21 03:16:28 -07:00
iamtoruk
a4d261a536 fix: pricing accuracy, stream leak, CSV injection hardening
- Remove bidirectional fuzzy match in getModelCosts that could return
  wrong pricing when a short canonical name prefix-matched a longer key
- Use explicit undefined check in parseLiteLLMEntry so free models with
  zero cost are not silently dropped from the LiteLLM pricing database
- Destroy read stream in finally block of readSessionLines to prevent
  file descriptor leaks when the generator is abandoned early
- Extend CSV injection escaping to cover tab and carriage-return prefixes
- Add optional chaining fallback for empty periods in exportCsv/exportJson
- Add regression tests for all fixes (models, export, fs-utils)
2026-04-20 14:49:32 -07:00
AgentSeal
79e67f0bc9
Add OMP provider support and model alias mapping
- Add OMP provider reading from ~/.omp/agent/sessions (same JSONL
  format as Pi, shared parser)
- Parameterize discoverSessionsInDir with provider name so sessions
  carry correct provider field
- Add BUILTIN_ALIASES for proxy model name variants (anthropic--claude-*
  double-dash format) that don't match LiteLLM keys
- Add model-alias CLI command for user-defined name mappings
- Wire setModelAliases into preAction after config load
- Add modelAliases field to CodeburnConfig
- Update README: OMP in provider table, model-alias section
2026-04-16 23:35:46 +02:00