Skip to content

fix(pricing): take models.dev rates from the authoring catalog - #1541

Merged
ryoppippi merged 32 commits into
mainfrom
fix/models-dev-provider-trust
Aug 15, 2026
Merged

fix(pricing): take models.dev rates from the authoring catalog#1541
ryoppippi merged 32 commits into
mainfrom
fix/models-dev-provider-trust

Conversation

@ryoppippi

@ryoppippi ryoppippi commented Jul 29, 2026

Copy link
Copy Markdown
Member

Summary

models.dev publishes one catalog per provider, so every model appears once per provider that serves it, each with that provider's own rates — and some of those rates are two-stage: Grok, GPT-5.x and Gemini bill requests above a context boundary at higher prices. This PR fixes which catalog a rate comes from, and prices those long-context tiers from data instead of a hardcoded table.

What changed

Selection is by catalog trust. The authoring catalog wins, cloud platforms reselling at list price plus a published regional premium come next, resellers are last. Authorship is derived from the models/<author>/<id>.toml directory scan, so new authors need no maintenance. Every published id is embedded; separately priced tiers and unversioned aliases are marked exact-only so the fuzzy lookup cannot resolve a base model onto them.

Long-context tiers come from models.dev cost.tiers. The generator emits each model's band in the upstream shape, the Rust loader parses the snapshot and a live api.json with the same code, and models whose base rates come from LiteLLM (gpt-5.x) get the band filled in from the snapshot. The hardcoded builtin_long_context_rates table is deleted — which also fixed two prices it had frozen after OpenAI's gpt-5.6 price cut (luna was over-reported at 5x). Built-in entries are now a last resort, never an overwrite.

The tier is selected by the whole context. Adapters normalize usage so input_tokens is the uncached remainder, but vendors choose the tier by the request's whole context: a Grok turn re-reading 8M cached tokens with 10K fresh ones is a long-context request. The comparison now sums uncached input, cache reads and cache writes.

Dotted and dashed spellings merge onto one entry. grok-4.5 (xAI, tiered) and grok-4-5 (a reseller's flat copy) tied in the fuzzy lookup, so grok-4.5-build could bill flat. Spellings contend for one slot keyed by the normalized id in the generator and the live loader alike, and within a trust tier a catalog publishing a long-context band outranks one with more cache detail.

The runtime loader applies the same rules as generation — trust ranking, token/asset verdicts from the authored catalog, zero-cost and modality guards, detail-ordered tie-breaks — carried in via the generated models-dev-catalog-rules.json, so --offline and a live refresh cannot disagree.

Eligibility reads the authored catalog. Asset-priced models (whisper-large-v3 per second, gemini-*-image per image) stay out even when a reseller describes them as text-only. The committed snapshots are also excluded from the typos formatter, which had rewritten gemini-2.5-flash-nothink into a nonexistent id, and are deflated at embed time.

Grok resolves pricing candidates exactly before fuzzily, so an override naming the request's model precisely beats a fuzzy hit on a shorter spelling.

Real-log verification

169-day Claude + Codex log: byte-identical to main in online and offline modes — the models it contains were priced correctly before and stay untouched.

85-session Grok Build log (grok-4.5/4.6-build; 47 of 86 turn×model rows above 200K context):

mode main this PR
auto / display (recorded ticks) $17.43 $17.43
calculate $13.41 — every turn billed short-context $24.69 — 200K tier applied

Whole-catalogue probe (every snapshot id × {100K, 300K} input): nothing loses pricing; every changed cost is an intended category (tier application, regional premiums, spelling-merge corrections).

Testing

cargo test --workspace, just test, nix flake check — all green. New regression fences: resold-model list rates, asset-priced exclusion both ways, tier data through both grok spellings, whole-context tier selection with cache-heavy usage, spelling-merge including entry removal, tier-aware and detail-ordered claim comparison, exact-before-fuzzy candidate resolution, the gpt-5.6 price-cut rates, and the snapshot's byte-for-byte reproducibility from the pinned input.

Remaining follow-ups (out of scope)

  • Absolute fast-mode rates (experimental.modes.fast) still bill via the fast multiplier; a logged -fast id bills at the only catalogue rate published for it.
  • Per-provider regional premiums beyond what catalogs publish as their own entries.

Copilot AI review requested due to automatic review settings July 29, 2026 14:04
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
ccusage-guide 99c5a4a Commit Preview URL

Branch Preview URL
Aug 15 2026, 10:38 AM

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The models.dev generator now applies catalog-derived trust, modality, pricing, and exact-match rules. It emits catalog metadata and expanded pricing snapshots. The Rust runtime embeds compressed snapshots and applies deterministic catalog selection and lookup behavior.

Changes

Models.dev pricing selection and runtime integration

Layer / File(s) Summary
Trust-aware catalog selection and generation
nix/tools/models-dev-gen/compact.ts, nix/tools/models-dev-gen/gen.ts, nix/tools/models-dev-gen/compact.test.ts
The generator builds catalog indexes, ranks providers, filters non-token-priced models, marks exact-only variants, emits catalog rules, and tests these decisions.
Compressed snapshot build and runtime pricing
rust/crates/ccusage-core/build.rs, rust/crates/ccusage-core/src/pricing.rs, rust/Cargo.toml, rust/crates/ccusage-core/Cargo.toml
Build-time snapshots are minified and deflated. Runtime code inflates them lazily, ranks catalog claims, filters ineligible entries, and controls fuzzy lookup.
Generated pricing catalog update
rust/crates/ccusage-core/src/models-dev-pricing.json
Provider and model pricing, cache rates, aliases, and context limits are expanded and reshaped.
Catalog-rule snapshot packaging
.github/scripts/update-models-dev-lock.nu, justfile, nix/*.nix, rust/crates/ccusage-core/src/models-dev-catalog-rules.json, rust/crates/ccusage-core/README.md, nix/treefmt.nix
Catalog rules are generated, tracked, formatted, included in build inputs, and documented.
Grok pricing fallback validation
rust/adapters/grok/src/parser.rs
Tests verify build-form fallback and raw model override keys.

Estimated code review effort: 5 (Critical) | ~100 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ModelsDev
  participant Generator
  participant Snapshots
  participant RustBuild
  participant PricingRuntime
  ModelsDev->>Generator: catalog models, modalities, and pricing
  Generator->>Snapshots: pricing and catalog-rules JSON
  Snapshots->>RustBuild: committed snapshots
  RustBuild->>PricingRuntime: compressed embedded data
  PricingRuntime->>PricingRuntime: rank catalogs and enforce lookup rules
Loading

Possibly related PRs

Suggested reviewers: pullfrog

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main pricing change: models.dev rates now prefer the authoring catalog.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/models-dev-provider-trust

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai @cubic-dev-ai please review.

Two areas worth extra attention:

  1. nix/tools/models-dev-gen/compact.ts — the trust tiering decides which models.dev catalog supplies each price. A wrong tier assignment silently changes reported costs rather than failing, so the tier boundaries and the zai/zhipuai alias are the risky part.
  2. The reseller last-resort gate in isEmbeddableModelsDevCandidate. It keeps retired first-party models that only resellers still list, bounded to ids the authored catalog knows. Please check whether that bound can drop a model an adapter actually records.

@cubic-dev-ai

cubic-dev-ai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai @cubic-dev-ai please review.

Two areas worth extra attention:

  1. nix/tools/models-dev-gen/compact.ts — the trust tiering decides which models.dev catalog supplies each price. A wrong tier assignment silently changes reported costs rather than failing, so the tier boundaries and the zai/zhipuai alias are the risky part.
    ...

@ryoppippi I have started the AI code review. It will take a few minutes to complete.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== PR changed files =='
git diff --name-status HEAD^ HEAD

echo
echo '== Generator outline =='
ast-grep outline nix/tools/models-dev-gen/compact.ts --items all 2>/dev/null || true

echo
echo '== Focused generator diff =='
git diff --unified=80 HEAD^ HEAD -- nix/tools/models-dev-gen/compact.ts

echo
echo '== Candidate gate / tiering / lookup references =='
rg -n -C 5 \
  -e 'isEmbeddableModelsDevCandidate\s*\(' \
  -e 'authoring|authored|trust|tier|zhipuai|zai' \
  -e 'models\.dev|modelsDev' \
  --glob '!nix/tools/models-dev-gen/compact.ts' \
  .

echo
echo '== Likely adapter/model-definition files =='
fd -t f . | rg -i '(adapter|provider|model|pricing|cost|catalog)' | head -250

Length of output: 174213


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== compact.ts =='
cat -n nix/tools/models-dev-gen/compact.ts

echo
echo '== Generator candidate selection =='
sed -n '55,135p' nix/tools/models-dev-gen/gen.ts | cat -n

echo
echo '== Runtime PR diff =='
git diff --unified=100 HEAD^ HEAD -- rust/crates/ccusage-core/src/pricing.rs

echo
echo '== Pricing lookup and aliases =='
ast-grep outline rust/crates/ccusage-core/src/pricing.rs --items all 2>/dev/null || true
sed -n '360,535p' rust/crates/ccusage-core/src/pricing.rs | cat -n
sed -n '1,260p' rust/crates/ccusage-core/src/model_aliases.rs | cat -n

echo
echo '== Adapter model extraction / model-key construction =='
rg -n -C 4 --glob 'rust/adapters/**/src/*.rs' \
  -e '\bmodel\b.*(?:String|str|Cow|CompactString)' \
  -e '(?:model|provider).*(?:id|name)' \
  -e 'format!\(.*(?:model|provider)' \
  -e 'join\(.*(?:model|provider)' \
  -e 'PricingMap|find(?:_entry|_exact)?\(' \
  rust/adapters

echo
echo '== Snapshot keys with reseller-style namespaces =='
python3 - <<'PY'
import json
p = 'rust/crates/ccusage-core/src/models-dev-pricing.json'
with open(p) as f:
    keys = list(json.load(f))
for k in keys:
    if '/' in k or '.' in k:
        print(k)
PY

Length of output: 161175


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== compact.ts, focused =='
sed -n '1,270p' nix/tools/models-dev-gen/compact.ts | cat -n

echo
echo '== Compact tests covering trust and gate =='
sed -n '1,225p' nix/tools/models-dev-gen/compact.test.ts | cat -n

echo
echo '== Exact pricing lookup fallback =='
rg -n -A85 -B10 'fn find_entry_or_alias|fn find_exact|fn find\(' rust/crates/ccusage-core/src/pricing.rs

echo
echo '== OpenCode and Droid candidate construction =='
sed -n '195,250p' rust/adapters/opencode/src/parser.rs | cat -n
sed -n '175,207p' rust/adapters/droid/src/parser.rs | cat -n

echo
echo '== Provider-qualified snapshot keys changed by this PR =='
git diff --unified=0 HEAD^ HEAD -- rust/crates/ccusage-core/src/models-dev-pricing.json \
  | rg '^[+-]"[^"]+' || true

echo
echo '== Embedded snapshot keys likely queried by adapter provider prefixes =='
python3 - <<'PY'
import json
from collections import Counter
with open('rust/crates/ccusage-core/src/models-dev-pricing.json') as f:
    keys = json.load(f).keys()
prefixes = ('anthropic/', 'openrouter/', 'google/', 'xai/', 'openai/', 'github_copilot/')
for k in keys:
    if k.startswith(prefixes):
        print(k)
PY

Length of output: 29672


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Branch and merge-base =='
git status --short --branch
git remote -v
git merge-base HEAD origin/main 2>/dev/null || true
base="$(git merge-base HEAD origin/main 2>/dev/null || git rev-list --max-parents=0 HEAD | tail -1)"
echo "BASE=$base"
git diff --stat "$base"...HEAD
git diff --name-status "$base"...HEAD

echo
echo '== Complete PR changes to generator and snapshots =='
git diff --unified=35 "$base"...HEAD -- \
  nix/tools/models-dev-gen/compact.ts \
  nix/tools/models-dev-gen/gen.ts \
  nix/tools/models-dev-gen/compact.test.ts \
  rust/crates/ccusage-core/src/models-dev-pricing.json \
  rust/crates/ccusage-core/src/models-dev-provider-trust.json

echo
echo '== Available models.dev source paths =='
fd -H -t d -d 5 '^models$|models\.dev$' . 2>/dev/null || true
fd -H -t f -d 8 -e toml . 2>/dev/null | rg '/models/' | head -80 || true

echo
echo '== Static catalog-id usage and types =='
rg -n -C 4 'catalogId|catalog_id|catalog' nix/tools/models-dev-gen

echo
echo '== Tests and code that call isEmbeddableModelsDevCandidate =='
rg -n -C 5 'isEmbeddableModelsDevCandidate\s*\(' nix/tools/models-dev-gen

Length of output: 152626


@ryoppippi Reviewed the requested areas; I found no actionable issues.

  • The trust ordering is deterministic: authored provider directories outrank the explicit cloud-platform tier, which outranks resellers. The zai exception is narrowly scoped and the generated runtime trust artifact retains both zai and zhipuai.
  • The reseller gate does not appear to drop a currently supported adapter lookup path. The adapters examined try the normalized bare model ID as well as provider-qualified candidates, while platform-specific identifiers remain included through the higher-trust platform tier.
  • I also confirmed the runtime loader consumes the generated trust artifact and sorts providers deterministically rather than relying on hash-map iteration order.

I did not run the test suite in this environment.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes — replaces the hardcoded model-name regex and provider-id priority list for models.dev pricing selection with a catalog-index-based trust tier system, and brings the same ranking into the Rust runtime loader.

  • Trust tier selection in the generator — the /claude|anthropic|kimi|moonshot/i regex and hardcoded candidateProviderPriority() are replaced by modelsDevProviderTrust(), which derives authorship from the canonical catalog directory scan. isEmbeddableModelsDevCandidate() restricts reseller entries to models the authored catalog still knows, and isPriceableModelsDevCost() plus isTextOutputModel() reject flat-fee catalogs and non-text-output models.
  • New committed artifactmodels-dev-provider-trust.json carries the sorted owner and platform provider-id lists so the Rust runtime can rank live api.json responses without needing the catalog itself.
  • Runtime catalog rankingload_models_dev_json_missing sorts providers by trust rank before loading, so authoring catalogs always load first regardless of hash iteration order. The runtime zero-cost guard mirrors the generator's.
  • Build-time minification — the committed indented models-dev-pricing.json is minified via serde_json::to_string() at build time, keeping the tree copy reviewable without shipping indentation in the binary.
  • Wiring and tests — the new artifact is plumbed through default.nix, justfile, cargo-artifacts.nix, package.nix, and update-models-dev-lock.nu. 14 new TypeScript tests cover the trust tiers, replace/embed decisions, and filtering; 4 new Rust tests cover grok-build-0.1 offline pricing, author-price regression for kimi-k2.7-code, live catalog ranking, and live flat-fee rejection.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pkg-pr-new

pkg-pr-new Bot commented Jul 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

ccusage

npx https://pkg.pr.new/ccusage@1541

@ccusage/ccusage-darwin-arm64

npx https://pkg.pr.new/@ccusage/ccusage-darwin-arm64@1541

@ccusage/ccusage-darwin-x64

npx https://pkg.pr.new/@ccusage/ccusage-darwin-x64@1541

@ccusage/ccusage-linux-arm64

npx https://pkg.pr.new/@ccusage/ccusage-linux-arm64@1541

@ccusage/ccusage-linux-x64

npx https://pkg.pr.new/@ccusage/ccusage-linux-x64@1541

@ccusage/ccusage-win32-x64

npx https://pkg.pr.new/@ccusage/ccusage-win32-x64@1541

commit: 99c5a4a

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 14ea18527fb7
Base SHA: c4be7c150fd1

This compares the PR package against the configured base package on the same CI runner.

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 348.8ms 2.89 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 286.0ms 3.52 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 127.7ms 7.88 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 101.4ms 9.93 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 31.4ms 29.6ms 1.06x 55.25 MiB 55.00 MiB 1.00x 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 28.6ms 28.3ms 1.01x 55.50 MiB 55.50 MiB 1.00x 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 30.2ms 25.8ms 1.17x 55.25 MiB 55.00 MiB 1.00x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 26.7ms 27.4ms 0.97x 54.75 MiB 55.25 MiB 1.01x 0.03 MiB/s 0.03 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 341.6ms 331.5ms 1.03x 964.58 MiB 938.83 MiB 0.97x 2.95 GiB/s 3.04 GiB/s
codex --offline --json 1.01 GiB 125.6ms 128.6ms 0.98x 388.64 MiB 404.90 MiB 1.04x 8.02 GiB/s 7.83 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.78 KiB 18.78 KiB +0.00 KiB 1.00x
installed native package binary 4158.97 KiB 4159.28 KiB +0.31 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 14ea18527fb7
Base SHA: c4be7c150fd1

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 348.3ms 2.89 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 340.8ms 2.95 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 129.7ms 7.76 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 113.5ms 8.87 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published native ccusage binary from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 31.4ms 5.2ms 6.02x 54.75 MiB 12.70 MiB 0.23x 0.05 MiB/s 0.30 MiB/s
claude session --offline --json 0.00 MiB 26.8ms 2.9ms 9.32x 55.00 MiB 12.70 MiB 0.23x 0.06 MiB/s 0.54 MiB/s
codex daily --offline --json 0.00 MiB 24.7ms 2.5ms 9.85x 55.25 MiB 10.70 MiB 0.19x 0.03 MiB/s 0.34 MiB/s
codex session --offline --json 0.00 MiB 25.5ms 2.3ms 10.90x 55.00 MiB 10.69 MiB 0.19x 0.03 MiB/s 0.37 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published native ccusage binary from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 367.8ms 334.8ms 1.10x 944.83 MiB 952.83 MiB 1.01x 2.74 GiB/s 3.01 GiB/s
codex --offline --json 1.01 GiB 118.7ms 96.0ms 1.24x 400.65 MiB 398.89 MiB 1.00x 8.48 GiB/s 10.49 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.78 KiB 18.78 KiB +0.00 KiB 1.00x
installed native package binary 4158.97 KiB 4159.28 KiB +0.31 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/crates/ccusage-core/src/models-dev-pricing.json`:
- Around line 4045-4052: Update the pricing snapshot generator’s eligibility
predicate to exclude asset-priced models such as whisper-large-v3 from token
pricing, then regenerate models-dev-pricing.json without that entry. Add a
regression test covering exclusion of asset-priced models while preserving
token-priced model inclusion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fc321687-1f22-40e0-b1db-eb54a0af7975

📥 Commits

Reviewing files that changed from the base of the PR and between c4be7c1 and 14ea185.

📒 Files selected for processing (13)
  • .github/scripts/update-models-dev-lock.nu
  • justfile
  • nix/cargo-artifacts.nix
  • nix/tools/models-dev-gen/compact.test.ts
  • nix/tools/models-dev-gen/compact.ts
  • nix/tools/models-dev-gen/default.nix
  • nix/tools/models-dev-gen/gen.ts
  • package.nix
  • rust/crates/ccusage-core/README.md
  • rust/crates/ccusage-core/build.rs
  • rust/crates/ccusage-core/src/models-dev-pricing.json
  • rust/crates/ccusage-core/src/models-dev-provider-trust.json
  • rust/crates/ccusage-core/src/pricing.rs

Comment thread rust/crates/ccusage-core/src/models-dev-pricing.json Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 13 files

Not reviewed (too large): rust/crates/ccusage-core/src/models-dev-pricing.json (~3,844 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread nix/tools/models-dev-gen/gen.ts Outdated
Comment thread nix/tools/models-dev-gen/compact.ts Outdated
Copilot AI review requested due to automatic review settings July 29, 2026 14:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review
@cubic-dev-ai review

Pushed 5995ba1, which fixes the asset-priced pricing CodeRabbit found and a second instance of the same root cause (gemini-2.5-flash-image / gemini-3-pro-image-preview embedded at per-image rates via a reseller catalogue that describes them as text-only). Eligibility now reads modalities from the authored catalog. Details in the review thread.

@cubic-dev-ai

cubic-dev-ai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai review
@cubic-dev-ai review

Pushed 5995ba1, which fixes the asset-priced pricing CodeRabbit found and a second instance of the same root cause (gemini-2.5-flash-image / gemini-3-pro-image-preview embedded at per-image rates via a reseller catalogue that describes them as text-only). Eligibility now reads modalities from the authored catalog. Details in the review thread.

@ryoppippi I have started the AI code review. It will take a few minutes to complete.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Commit and changed files =='
git show --no-ext-diff --format=fuller --name-status 5995ba1

echo
echo '== Focused diff =='
git diff --no-ext-diff --unified=80 5995ba1^ 5995ba1 -- \
  nix/tools/models-dev-gen/compact.ts \
  nix/tools/models-dev-gen/gen.ts \
  nix/tools/models-dev-gen/compact.test.ts \
  rust/crates/ccusage-core/src/models-dev-pricing.json \
  rust/crates/ccusage-core/src/models-dev-provider-trust.json

echo
echo '== Generator symbol map =='
ast-grep outline nix/tools/models-dev-gen/compact.ts --items all
ast-grep outline nix/tools/models-dev-gen/gen.ts --items all
ast-grep outline nix/tools/models-dev-gen/compact.test.ts --items all

echo
echo '== Eligibility, modality, and catalog-index references =='
rg -n -C 8 \
  -e '\bisTextOutputModel\s*\(' \
  -e '\bisEmbeddableModelsDevCandidate\s*\(' \
  -e '\bbuildModelsDevCatalogIndex\s*\(' \
  -e '\bmodalities\b' \
  -e 'authoredModel' \
  nix/tools/models-dev-gen

echo
echo '== Snapshot entries mentioned by user =='
python3 - <<'PY'
import json
path = 'rust/crates/ccusage-core/src/models-dev-pricing.json'
with open(path, encoding='utf-8') as f:
    pricing = json.load(f)
for needle in ('gemini-2.5-flash-image', 'gemini-3-pro-image-preview'):
    matches = {k: v for k, v in pricing.items() if needle in k}
    print(f'{needle}: {len(matches)} matches')
    for key, value in matches.items():
        print(key, json.dumps(value, sort_keys=True))
PY

Length of output: 69629


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Locate authored model catalog sources =='
fd -H -t f -e toml . | rg '(^|/)models/[^/]+/[^/]+\.toml$' | head -20

echo
echo '== Bare authored-ID collisions (if catalog sources are present) =='
python3 - <<'PY'
from collections import defaultdict
from pathlib import Path

paths = [p for p in Path('.').rglob('*.toml') if len(p.parts) >= 3 and p.parts[-3] == 'models']
by_id = defaultdict(list)
for path in paths:
    by_id[path.stem].append(path.as_posix())

collisions = {model_id: sources for model_id, sources in by_id.items() if len(sources) > 1}
print(f'catalog TOMLs: {len(paths)}')
print(f'bare-ID collisions: {len(collisions)}')
for model_id, sources in sorted(collisions.items())[:100]:
    print(model_id)
    for source in sources:
        print(f'  {source}')
PY

echo
echo '== Rust snapshot-regression test and pricing-test map =='
ast-grep outline rust/crates/ccusage-core/src/pricing.rs --items all
rg -n -C 12 \
  -e 'gemini-2\.5-flash-image' \
  -e 'gemini-3-pro-image-preview' \
  -e 'whisper-large-v3' \
  -e 'qwen3-asr-flash' \
  rust/crates/ccusage-core/src/pricing.rs

echo
echo '== Generator source provenance and catalog model-key assumptions =='
rg -n -C 5 \
  -e 'generateCatalog' \
  -e 'models:' \
  -e 'Object\.entries\(models\)' \
  -e 'authoredModalities' \
  nix/tools/models-dev-gen

Length of output: 1706


🏁 Script executed:

#!/bin/bash
set -u

echo '== Available catalog-related paths (non-failing) =='
fd -H -t d -d 8 . . 2>/dev/null | rg -i 'models.?dev|catalog|models$' || true
fd -H -t f -d 10 . . 2>/dev/null | rg -i '(models.?dev|catalog).*\.(toml|json|nix|ts)$' || true

echo
echo '== Rust snapshot-regression tests =='
ast-grep outline rust/crates/ccusage-core/src/pricing.rs --items all || true
rg -n -C 14 \
  -e 'gemini-2\.5-flash-image' \
  -e 'gemini-3-pro-image-preview' \
  -e 'whisper-large-v3' \
  -e 'qwen3-asr-flash' \
  rust/crates/ccusage-core/src/pricing.rs || true

echo
echo '== Nix input / source provenance for models.dev =='
rg -n -C 5 -i \
  -e 'models-dev' \
  -e 'models\.dev' \
  -e 'modelsDev' \
  flake.nix flake.lock nix package.nix justfile .github 2>/dev/null || true

echo
echo '== Call sites and authored modality-map implementation =='
rg -n -C 8 \
  -e '\bauthoredModalities\b' \
  -e '\bisTokenPricedModel\s*\(' \
  -e '\bbuildModelsDevCatalogIndex\s*\(' \
  nix/tools/models-dev-gen || true

echo
echo '== Commit-specific Rust test diff =='
git diff --no-ext-diff --unified=100 5995ba1^ 5995ba1 -- rust/crates/ccusage-core/src/pricing.rs

Length of output: 88327


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo='anomalyco/models.dev'
rev='52d5045ee7c89127a3f8966b48599b9dec8afed6'
tree_file="$(mktemp)"

gh api "repos/${repo}/git/trees/${rev}?recursive=1" > "$tree_file"

python3 - "$tree_file" <<'PY'
import json
import sys
from collections import defaultdict
from pathlib import PurePosixPath

with open(sys.argv[1], encoding='utf-8') as f:
    tree = json.load(f)

print(f"tree_truncated: {tree.get('truncated')}")
by_id = defaultdict(list)
for entry in tree.get('tree', []):
    path = entry.get('path', '')
    parts = PurePosixPath(path).parts
    if len(parts) == 3 and parts[0] == 'models' and path.endswith('.toml'):
        by_id[PurePosixPath(path).stem].append(path)

collisions = {model_id: paths for model_id, paths in by_id.items() if len(paths) > 1}
print(f"model_tomls: {sum(map(len, by_id.values()))}")
print(f"bare_id_collisions: {len(collisions)}")
for model_id in sorted(collisions):
    print(f"{model_id}\t" + "\t".join(sorted(collisions[model_id])))
PY

rm -f "$tree_file"

Length of output: 1221


@ryoppippi Reviewed 5995ba1; no actionable issues found.

The new eligibility rule correctly takes modalities from the authored catalog before considering reseller metadata, so a reseller’s text-only description can no longer admit an asset-priced model. The generated snapshot excludes the reported Gemini image models and whisper-large-v3, while regression tests retain valid multimodal text models.

I also checked the exact pinned models.dev revision: its 279 authored model TOMLs have no duplicate bare model IDs, so the bare-ID authoredModalities index does not conflate providers in this snapshot.

I did not run the test suite in this environment.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed changes — the isTextOutputModel eligibility check is replaced by isTokenPricedModel, which reads modalities from the authored catalog to correctly reject duration-priced models (whisper-large-v3) and image-output models (gemini-2.5-flash-image, gemini-3-pro-image-preview) regardless of how reseller catalogs describe them.

  • isTokenPricedModel consults the authored catalogModelsDevCatalogIndex gains authoredModalities, buildModelsDevCatalogIndex extracts modalities from the canonical models, and isTokenPricedModel uses authored modalities as the authoritative signal. The serving catalog's modalities are a fallback for models the authored catalog omits.
  • Asset-priced models are removed from the snapshotwhisper-large-v3 (audio input, billed per second), gemini-2.5-flash-image (image output, billed per image), and gemini-3-pro-image-preview no longer appear in the embedded JSON.
  • New tests fence the filtering — six new TypeScript tests cover text-token models with audio/video inputs, duration-priced rejection, image-output rejection via authored modalities, and fallback behavior. One new Rust test asserts the three asset-priced models are absent while text models with audio/video input survive.

✅ No new issues found.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 5995ba1191f8
Base SHA: c4be7c150fd1

This compares the PR package against the configured base package on the same CI runner.

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 328.5ms 3.07 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 319.6ms 3.15 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 119.4ms 8.43 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 96.4ms 10.44 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 26.9ms 23.3ms 1.16x 55.25 MiB 55.00 MiB 1.00x 0.06 MiB/s 0.07 MiB/s
claude session --offline --json 0.00 MiB 23.9ms 22.8ms 1.05x 55.25 MiB 54.75 MiB 0.99x 0.06 MiB/s 0.07 MiB/s
codex daily --offline --json 0.00 MiB 23.8ms 22.6ms 1.05x 55.25 MiB 55.00 MiB 1.00x 0.04 MiB/s 0.04 MiB/s
codex session --offline --json 0.00 MiB 22.6ms 23.1ms 0.98x 55.00 MiB 55.00 MiB 1.00x 0.04 MiB/s 0.04 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 353.6ms 340.8ms 1.04x 948.82 MiB 966.83 MiB 1.02x 2.85 GiB/s 2.95 GiB/s
codex --offline --json 1.01 GiB 115.1ms 116.6ms 0.99x 410.65 MiB 430.89 MiB 1.05x 8.75 GiB/s 8.63 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.78 KiB 18.78 KiB +0.00 KiB 1.00x
installed native package binary 4158.97 KiB 4159.28 KiB +0.31 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 5995ba1191f8
Base SHA: c4be7c150fd1

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 351.8ms 2.86 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 328.0ms 3.07 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 122.6ms 8.21 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 95.1ms 10.59 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published native ccusage binary from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 27.2ms 5.4ms 5.07x 55.00 MiB 12.69 MiB 0.23x 0.06 MiB/s 0.29 MiB/s
claude session --offline --json 0.00 MiB 25.8ms 3.1ms 8.29x 55.00 MiB 12.69 MiB 0.23x 0.06 MiB/s 0.50 MiB/s
codex daily --offline --json 0.00 MiB 26.1ms 2.5ms 10.33x 55.00 MiB 10.69 MiB 0.19x 0.03 MiB/s 0.34 MiB/s
codex session --offline --json 0.00 MiB 23.9ms 2.4ms 10.02x 55.00 MiB 10.70 MiB 0.19x 0.04 MiB/s 0.36 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published native ccusage binary from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 372.4ms 324.8ms 1.15x 962.82 MiB 942.57 MiB 0.98x 2.70 GiB/s 3.10 GiB/s
codex --offline --json 1.01 GiB 116.7ms 98.0ms 1.19x 410.63 MiB 402.89 MiB 0.98x 8.63 GiB/s 10.28 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.78 KiB 18.78 KiB +0.00 KiB 1.00x
installed native package binary 4158.97 KiB 4159.28 KiB +0.31 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 13 files

Not reviewed (too large): rust/crates/ccusage-core/src/models-dev-pricing.json (~3,817 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread rust/crates/ccusage-core/src/pricing.rs Outdated
Comment thread rust/crates/ccusage-core/src/pricing.rs Outdated
ryoppippi added a commit that referenced this pull request Jul 29, 2026
… too

The asset-priced guard and the same-tier tie-break existed only in the
snapshot generator, so an online refresh could still price a model the
embedded snapshot deliberately excludes, and could pick a different catalog
than the snapshot for the same model. Offline and default runs disagreed.

The live `api.json` records neither who authored a model nor the authored
modalities, so both have to be carried in from generation time. The
generated artifact therefore grows an `assetPricedModelIds` list, and is
renamed from `models-dev-provider-trust.json` to
`models-dev-catalog-rules.json` now that it carries more than provider
trust.

The loader applies it the same way the generator does: the authored verdict
decides where it exists, so a reseller describing an image model as
text-only cannot reintroduce a per-image rate, and entries the authored
catalog has never seen fall back to their own modalities, which is enough to
reject an audio-only transcriber from any catalog.

Same-tier ties now compare how much pricing detail an entry carries -
explicit cache-read, cache-write, context limit - before falling back to the
provider id, matching `shouldReplaceModelsDevPricingCandidate`. That needed
the loader to track which catalog claimed each id during a pass, so a better
candidate can replace a weaker one; ids absent from that record still belong
to another pricing source and are left alone. Replacements do not increment
the loaded count, which stays a count of resolved models.

Reported by cubic on #1541.
Copilot AI review requested due to automatic review settings July 29, 2026 15:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review
@cubic-dev-ai review

Pushed 42e9edc and 351c668, addressing both cubic findings:

  • P1 — the asset-priced guard now applies to live models.dev responses too, not just the generated snapshot. The generated artifact carries the authored verdict (assetPricedModelIds) and is renamed models-dev-catalog-rules.json.
  • P2 — same-tier ties in the live loader now use the generator's detail-first ordering, so online and offline resolve the same catalogue.

Also fixed a clippy::collapsible_if in the new modality check.

nix flake check passes all 10 checks; just test is green.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Commit metadata and changed files =='
git show --no-ext-diff --format=fuller --name-status 42e9edc
git show --no-ext-diff --format=fuller --name-status 351c668

echo
echo '== Focused combined diff =='
git diff --no-ext-diff --unified=100 42e9edc^ 351c668 -- \
  nix/tools/models-dev-gen/compact.ts \
  nix/tools/models-dev-gen/gen.ts \
  nix/tools/models-dev-gen/compact.test.ts \
  nix/tools/models-dev-gen/default.nix \
  rust/crates/ccusage-core/build.rs \
  rust/crates/ccusage-core/src/pricing.rs \
  rust/crates/ccusage-core/src/models-dev-catalog-rules.json \
  rust/crates/ccusage-core/src/models-dev-provider-trust.json \
  nix/cargo-artifacts.nix \
  package.nix \
  justfile \
  .github/scripts/update-models-dev-lock.nu

echo
echo '== Relevant source outlines =='
ast-grep outline nix/tools/models-dev-gen/compact.ts --items all
ast-grep outline nix/tools/models-dev-gen/gen.ts --items all
ast-grep outline rust/crates/ccusage-core/src/pricing.rs --items all

echo
echo '== Artifact/schema and live-loader references =='
rg -n -C 8 \
  -e 'models-dev-(catalog-rules|provider-trust)' \
  -e 'assetPricedModelIds' \
  -e 'ModelsDevCatalogRules|ModelsDevProviderTrust' \
  -e 'models_dev_.*trust|models_dev_.*rules' \
  -e 'sort_by|sort_unstable_by|cmp\(' \
  -e 'compareModelsDevPricingCandidates|compare.*Candidate' \
  nix rust package.nix justfile .github

echo
echo '== Generator and runtime tests around the new behavior =='
rg -n -C 12 \
  -e 'asset[- ]priced|assetPriced|whisper-large-v3|gemini-2\.5-flash-image|gemini-3-pro-image-preview' \
  -e 'same[- ]tier|detail-first|cache_read|cache_write|context' \
  nix/tools/models-dev-gen/compact.test.ts \
  rust/crates/ccusage-core/src/pricing.rs

Length of output: 202285


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== TypeScript candidate comparator, authored modality classification, and artifact builder =='
rg -n -A150 -B25 \
  -e 'function compareModelsDevPricingCandidates|const compareModelsDevPricingCandidates|export function compareModelsDevPricingCandidates' \
  -e 'function isTokenPricedModel|export function isTokenPricedModel' \
  -e 'function modelsDevCatalogRulesArtifact|export function modelsDevCatalogRulesArtifact' \
  nix/tools/models-dev-gen/compact.ts

echo
echo '== Generator: catalog rules emission and asset-priced model collection =='
rg -n -A100 -B20 \
  -e 'modelsDevCatalogRulesArtifact' \
  -e 'assetPricedModelIds' \
  -e 'CATALOG_RULES|RULES_OUTFILE|PROVIDER_TRUST_OUTFILE' \
  -e 'isTokenPricedModel' \
  nix/tools/models-dev-gen/gen.ts nix/tools/models-dev-gen/default.nix

echo
echo '== Runtime: rules rank, provider ordering, asset filtering, and claim replacement =='
sed -n '130,205p' rust/crates/ccusage-core/src/pricing.rs | cat -n
sed -n '415,540p' rust/crates/ccusage-core/src/pricing.rs | cat -n

echo
echo '== Complete artifact rename/reference audit =='
rg -n -C2 'models-dev-provider-trust|models-dev-catalog-rules|PROVIDER_TRUST_OUTFILE|CATALOG_RULES_OUTFILE' \
  . \
  -g '!flake.lock'

echo
echo '== Commit-level test changes only =='
git diff --no-ext-diff --unified=35 42e9edc^ 351c668 -- \
  nix/tools/models-dev-gen/compact.test.ts \
  rust/crates/ccusage-core/src/pricing.rs

Length of output: 69915


@ryoppippi I found one remaining issue in the P2 fix.

compareModelsDevPricingCandidates in nix/tools/models-dev-gen/compact.ts compares same-tier candidates lexicographically: explicit cache_read, then cache_write, then context limit. However, the live loader in rust/crates/ccusage-core/src/pricing.rs reduces these to a single detail count.

Consequently, a reseller entry with cache_write + context (detail 2) beats one with only explicit cache_read (detail 1) online, whereas generation chooses the explicit-cache_read entry. This leaves offline and live pricing capable of diverging.

The asset-priced catalog-rules artifact and its rename otherwise appear correctly wired through generation and runtime loading.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cubic-dev-ai

cubic-dev-ai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai review
@cubic-dev-ai review

Pushed 42e9edc and 351c668, addressing both cubic findings:

...

@ryoppippi Looks good. I've verified both fixes against the code.

P1 — asset-priced guard for live responses. The assetPricedModelIds list flows from modelsDevCatalogRulesArtifact() in compact.ts into the committed models-dev-catalog-rules.json, and ModelsDevCatalogRules::is_token_priced() in pricing.rs checks it first before falling back to the serving catalog's modalities. That closes the gap where a reseller describing an image model as text-only could reintroduce a per-image rate online. The new test live_models_dev_pricing_skips_models_priced_per_asset covers both directions (authored verdict and unlisted-model fallback).

P2 — same-tier tie-breaking. The claims map carrying (trust, detail) tuples, with detail computed from cache_read + cache_write + context_limit, mirrors the generator's shouldReplaceModelsDevPricingCandidate. The sort by trust descending then provider id ascending means hash iteration order no longer decides anything. Test live_models_dev_pricing_prefers_the_more_detailed_entry_within_a_tier confirms replacement works and doesn't double-count the model.

The clippy::collapsible_if fix in the Rust side reads clean — the modality match went from nested conditions to flat slice patterns.

Tip: get faster answers by chatting with cubic’s review copilot in the review UI

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
rust/crates/ccusage-core/src/pricing.rs (1)

2167-2241: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use fixtures for the new models.dev loader payloads.

These are parser/loader tests with full provider payloads; move the JSON into fs_fixture files to keep cases reusable and readable. As per coding guidelines, “Prefer fixture-backed parser and loader tests for Rust code.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/ccusage-core/src/pricing.rs` around lines 2167 - 2241, Move the
inline JSON payloads from the tests
live_models_dev_pricing_skips_models_priced_per_asset and
live_models_dev_pricing_prefers_the_more_detailed_entry_within_a_tier into
reusable fs_fixture files, then update each test to load its corresponding
fixture through the existing fixture helper while preserving the current
assertions and behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/crates/ccusage-core/src/pricing.rs`:
- Line 441: Update the pricing claim-selection logic around claims and the
replacement path near lines 494–500 and 526–528 to rank candidates
lexicographically by cache-read, cache-write, then context, matching the
generator rather than reducing them to a count. When replacing a winning entry,
replace the context limit atomically, including clearing the prior limit when
the winner has no context limit.

---

Nitpick comments:
In `@rust/crates/ccusage-core/src/pricing.rs`:
- Around line 2167-2241: Move the inline JSON payloads from the tests
live_models_dev_pricing_skips_models_priced_per_asset and
live_models_dev_pricing_prefers_the_more_detailed_entry_within_a_tier into
reusable fs_fixture files, then update each test to load its corresponding
fixture through the existing fixture helper while preserving the current
assertions and behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ef7304e1-dd77-4764-a464-90411b1e048f

📥 Commits

Reviewing files that changed from the base of the PR and between 5995ba1 and 351c668.

📒 Files selected for processing (11)
  • .github/scripts/update-models-dev-lock.nu
  • justfile
  • nix/cargo-artifacts.nix
  • nix/tools/models-dev-gen/compact.test.ts
  • nix/tools/models-dev-gen/compact.ts
  • nix/tools/models-dev-gen/default.nix
  • nix/tools/models-dev-gen/gen.ts
  • package.nix
  • rust/crates/ccusage-core/README.md
  • rust/crates/ccusage-core/src/models-dev-catalog-rules.json
  • rust/crates/ccusage-core/src/pricing.rs
🚧 Files skipped from review as they are similar to previous changes (7)
  • nix/cargo-artifacts.nix
  • package.nix
  • .github/scripts/update-models-dev-lock.nu
  • rust/crates/ccusage-core/README.md
  • nix/tools/models-dev-gen/gen.ts
  • nix/tools/models-dev-gen/compact.test.ts
  • nix/tools/models-dev-gen/compact.ts

Comment thread rust/crates/ccusage-core/src/pricing.rs Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes — the five-line isTokenPricedModel call in the generator is replicated in the Rust runtime so the online refresh path makes the same filtering and tiebreaking decisions as the embedded snapshot.

  • Renamed the artifact for claritymodels-dev-provider-trust.json becomes models-dev-catalog-rules.json, and it now carries assetPricedModelIds in addition to the owner and platform lists. The TypeScript export, Rust struct, Nix wiring, and docs all follow the rename.
  • Runtime is_token_priced mirrors the generator's filterModelsDevCatalogRules checks the precomputed assetPricedModelIds blacklist first (covering models the authored catalog says price per asset), then falls through to the serving catalog's modalities for models the authored catalog does not list. The ModelsDevJson::Models path now runs through the same rules.
  • Same-tier detail tiebreakingload_models_dev_models tracks per-model (trust, detail) claims so a reseller entry with cache-read and context-limit data can replace a weaker reseller entry for the same model, matching how shouldReplaceModelsDevPricingCandidate works in the generator.
  • New tests fence the online paritylive_models_dev_pricing_skips_models_priced_per_asset, live_models_dev_pricing_prefers_the_more_detailed_entry_within_a_tier, plus the existing catalog-ranking and flat-fee tests.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@ryoppippi

Copy link
Copy Markdown
Member Author

@cubic-dev-ai review

Pushed 263c22f: cache-write explicitness is now tracked rather than inferred (the round-3 heuristic is gone), and the Grok troubleshooting note matches the Pricing bullet. Full workspace tests, Node tests and clippy green.

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

Pushed 263c22f: cache-write explicitness is now tracked rather than inferred (the round-3 heuristic is gone), and the Grok troubleshooting note matches the Pricing bullet. Full workspace tests, Node tests and clippy green.

@ryoppippi I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 20 files

Not reviewed (too large): rust/crates/ccusage-core/src/models-dev-pricing.json (~23,508 lines), rust/crates/ccusage-core/src/pricing.rs (~1,811 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No new issues found in the commits since 860a587; the existing provider tie-break finding is unchanged.

Reviewed changes — Reviewed the cache-write explicitness change and its propagation through LiteLLM/models.dev parsing, overrides, the GLM fallback patch, and regression coverage. The focused ccusage-core pricing suite passed with 82 tests, and Rust formatting passed.

  • Tracked cache-write provenancePricing now records whether cache-write rates were explicitly published in LiteLLM and models.dev data, rather than inferring provenance from the numeric value.
  • Restricted GLM fallback patchingput_builtin_glm now fills cache fields only when the loaded entry did not publish them, preserving an explicit rate even when it equals the derived default.
  • Covered the regression — added a test proving an explicitly published cache-write rate survives the GLM provider patch and updated affected constructors and Grok pricing documentation.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Fix it ➔View workflow run | Using GPT Luna (free via Pullfrog for OSS) | 𝕏

@blacksmith-sh

This comment has been minimized.

Two catalogs declaring the same provider id are one provider to generation,
which breaks exact-strength ties by `(sourceProviderId, sourceModelId)` -
and with the ids colliding, by the source model key. The live loader kept
whichever catalog arrived first instead, so a same-strength duplicate could
resolve to a different rate online than in the snapshot. The claim slot now
records the declared id and source key it was claimed under, and an exactly
equal claim yields to a smaller source key from the same declared id.
Differing declared ids were already settled by arrival order, which matches
generation's ascending walk.

Reported by pullfrog on #1541.

Also narrows the new `cache_create_explicit` field to `pub(crate)`: nothing
outside the crate reads it, and the hawk gate fails the build for exactly
that.
@ryoppippi

Copy link
Copy Markdown
Member Author

@cubic-dev-ai review
@pullfrog review

Pushed 5795951: exact claim ties within a declared provider id now break by source key (generation's ordering), and the new cache_create_explicit field is pub(crate) per the hawk gate that failed CI. The build-windows-x64 failure on the previous run was the self-hosted runner losing connectivity, not code; this push reruns it.

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review
@pullfrog review

Pushed 5795951: exact claim ties within a declared provider id now break by source key (generation's ordering), and the new cache_create_explicit field is pub(crate) per the hawk gate that failed CI. The build-windows-x64 failure on the previous run was the self-hosted runner losing connectivity, not code; this push reruns it.

@ryoppippi I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 20 files

Not reviewed (too large): rust/crates/ccusage-core/src/models-dev-pricing.json (~23,508 lines), rust/crates/ccusage-core/src/pricing.rs (~1,915 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes — Reviewed the delta in 5795951 against the prior Pullfrog review, including the exact-claim tie fix, the Rust visibility adjustment, and the associated regression coverage.

  • Aligned equal-claim ordering — Added the declared provider ID and source key to live loader claim slots so ties within one declared provider resolve by source key, matching the generator's ordering even when provider map keys differ.
  • Applied the hawk visibility fix — Narrowed Pricing::cache_create_explicit to pub(crate) without changing its internal provider-patch behavior.
  • Added regression coverage — Covered the case where the lexicographically smaller source key appears in the catalog with the larger map key.

Validation: focused ccusage-core pricing tests, Rust formatting, and all 22 models.dev generator tests passed. The direct hawk command was unavailable in this environment, but the visibility change matches the reported CI gate requirement and the Rust workspace compiled successfully.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using GPT Luna (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes — reviewed the final incremental pricing commit after the prior Pullfrog review, focusing on deterministic live models.dev claim selection for duplicate declared provider IDs.

  • Recorded complete claim identityModelsDevClaimSlot now retains the declared provider ID and source model key, allowing equal-strength claims from the same declared provider to use the generator's smaller-source-key tie-break.
  • Added cross-catalog regression coverage — the new test places the winning source key behind the later provider map key and verifies that the live loader selects the same rate as generation.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using GPT Luna (free via Pullfrog for OSS) | 𝕏

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 579595105067
Base SHA: 849ec7a858b8

Performance comparison skipped.

Base package URL was not ready before 300.000s. Fixture performance comparison requires a base package when --base-dir is not provided.

Base package: 849ec7a858b8

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 579595105067
Base SHA: 849ec7a858b8

Performance comparison skipped.

Base package URL was not ready before 300.000s. Fixture performance comparison requires a base package when --base-dir is not provided.

Base package: 849ec7a858b8

hawk: pub(crate) was still wider than its uses, which never leave the
pricing module.
@ryoppippi

Copy link
Copy Markdown
Member Author

@BlackSmith review it

@blacksmith-sh

blacksmith-sh Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Your payment method failed for a Blacksmith invoice. [code]smith is paused until it is resolved. Update your payment method on the billing page.

Update payment method

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: bc1b37219e62
Base SHA: 849ec7a858b8

Performance comparison skipped.

Base package URL was not ready before 300.000s. Fixture performance comparison requires a base package when --base-dir is not provided.

Base package: 849ec7a858b8

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: bc1b37219e62
Base SHA: 849ec7a858b8

Performance comparison skipped.

Base package URL was not ready before 300.000s. Fixture performance comparison requires a base package when --base-dir is not provided.

Base package: 849ec7a858b8

@ryoppippi

ryoppippi commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

@codesmith-bot review it

@blacksmith-sh

blacksmith-sh Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Your payment method failed for a Blacksmith invoice. [code]smith is paused until it is resolved. Update your payment method on the billing page.

Update payment method

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes — reviewed the merge from main since the prior Pullfrog review, including the regenerated models.dev catalog rules and pricing snapshot plus the related dependency pins.

  • Regenerated catalog rules — Added the newly authored model ids from the updated models.dev input while preserving the generator/runtime trust and eligibility contract.
  • Regenerated pricing data — Added provider-specific entries and updated rates, context limits, and long-context tiers from the new pinned catalog.
  • Updated dependency pins — Refreshed the models.dev and LiteLLM inputs and the workspace package-manager pin used to produce the snapshots.

Validation: the focused ccusage-core pricing suite passed all 83 tests, including snapshot parsing, asset-priced exclusion, and Grok long-context regressions.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using GPT Luna (free via Pullfrog for OSS) | 𝕏

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 99c5a4ac88d7
Base SHA: 6b4be826ab33

Performance comparison skipped.

Base package URL was not ready before 300.000s. Fixture performance comparison requires a base package when --base-dir is not provided.

Base package: 6b4be826ab33

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 99c5a4ac88d7
Base SHA: 6b4be826ab33

Performance comparison skipped.

Base package URL was not ready before 300.000s. Fixture performance comparison requires a base package when --base-dir is not provided.

Base package: 6b4be826ab33

@ryoppippi
ryoppippi merged commit b936c29 into main Aug 15, 2026
36 of 37 checks passed
@ryoppippi
ryoppippi deleted the fix/models-dev-provider-trust branch August 15, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants