Add multiple pricing data sources support - #745
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThis PR adds a selectable pricing backend (LiteLLM, models.dev, or auto) via config and CLI, implements models.dev ingestion and conversion, routes/merges pricing in the core fetcher, and threads the selection through commands, adapters, and data loaders. ChangesMulti-source pricing backend selection
🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
packages/internal/src/pricing.ts[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: Oops! Something went wrong! :( ESLint: 9.35.0 Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'eslint-plugin-format' imported from /node_modules/.pnpm/@antfu+eslint-config@4.19.0_@vue[email protected][email protected][email protected]_vit_670a2c5c75d4275eabd7bc195a173ee6/node_modules/@antfu/eslint-config/dist/index.js 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/codex/package.json (1)
20-20: Revert thebinentry to point to the compiled output.The
binentry was changed from"./dist/index.js"to"./src/index.ts", but for a published package, the bin entry must point to the compiled/built output, not the TypeScript source. When installed from npm/registry, consumers will not have a TypeScript loader available, and the package will fail to execute. This also contradicts thepublishConfigsection (lines 59–61), which correctly points to"./dist/index.js".Apply this diff to fix the bin entry:
"bin": { - "ccusage-codex": "./src/index.ts" + "ccusage-codex": "./dist/index.js" },packages/internal/src/pricing.ts (1)
59-71:useModelsDevboolean cannot express a true “models.dev only” modeThe models.dev integration here is generally well‑structured:
fetchModelsDevPricingis wrapped inResult.try, merge semantics correctly prefer LiteLLM data, and failures gracefully fall back to LiteLLM‑only pricing.However, with
LiteLLMPricingFetcherOptions.useModelsDev?: booleanand:this.useModelsDev = Boolean(options.useModelsDev); ... Result.andThen(async (liteLLMPricing) => { if (!this.useModelsDev) return Result.succeed(liteLLMPricing); const modelsDevResult = await this.fetchModelsDevPricing(); ... const merged = this.mergePricingMaps(liteLLMPricing, modelsDevResult.value); return Result.succeed(merged); })the fetcher only supports two behaviors:
- LiteLLM only (
useModelsDev === false)- LiteLLM as the base, plus extra models from models.dev (
useModelsDev === true)There is no code path that returns models.dev pricing alone or skips the LiteLLM HTTP fetch altogether, which means an upstream
"modelsdev"source cannot be implemented faithfully with this API.To support the three intended modes, this class likely needs a richer option, for example:
sourceMode?: "litellm" | "auto" | "modelsdev"(preferred), or- A separate
modelsDevOnly?: booleanthat changesensurePricingLoadedto, in"modelsdev"mode, fetch and cache models.dev pricing even if LiteLLM is unreachable or not contacted at all.Once that’s in place, callers like
PricingFetcherinapps/ccusagecan pass through the exact mode instead of a boolean and you can add tests that verify:
"modelsdev"does not invoke LiteLLM,"auto"merges as it does now, and- failures in models.dev still leave you with a sane LiteLLM‑only map.
Also applies to: 96-112, 150-175, 217-231
🧹 Nitpick comments (5)
apps/ccusage/config-schema.json (1)
76-86:pricingSourceschema additions are consistent across commandsThe new
pricingSourceproperties (enum:auto/litellm/modelsdev, defaultauto) on defaults and all relevant commands line up with the CLI and docs and should validate config correctly. The duplication of the same schema block per command is acceptable here, though in the future you could consider a shared$refto keep them perfectly in sync.Also applies to: 191-201, 317-327, 427-437, 542-552, 657-667, 735-745
packages/internal/src/models-dev-pricing.ts (1)
78-122: Please re‑checkResult/async usage infetchModelsDevPricingand consider adding testsTwo things worth a closer look:
Result.try/Result.pipepattern
Indebug.ts,Result.tryis used with a thunk and then invoked (e.g.,const parser = Result.try({ try: () => JSON.parse(...), ... }); const result = parser();). Here you’re passingfetch(MODELS_DEV_API_URL)andresponse.json()directly as thetryvalues insideResult.try, and feeding those intoResult.pipe/Result.andThen. That’s a different pattern and may not behave as intended depending on the exact@praha/byethrowAPI (e.g., whethertryis expected to be a function vs an already-started Promise). It would be good to confirm against the library docs and, if needed, align this with the same thunk style you use elsewhere. As per coding guidelines, prefer the established Result patterns for clarity and correctness.
async+Result.ResultAsyncreturn type
Declaringexport async function fetchModelsDevPricing(): Result.ResultAsync<...>means the resolved value of the function is aResultAsync, so callers seePromise<ResultAsync<...>>. IfResultAsyncis itself promise-like (common pattern), this could lead to nested promises. You may want to either:
- Drop the
asynckeyword and return theResult.pipe(...)expression directly, or- Change the return type to the resolved type you actually want callers to
await.Additionally,
fetchModelsDevPricingis not currently covered by tests. A small test that mocksglobalThis.fetchto return a minimal models.dev‑style payload and asserts that the resultingMapcontains both provider‑prefixed and unprefixed keys would lock in the behavior and catch any pipeline/regression issues.Also applies to: 124-180
apps/ccusage/src/commands/session.ts (1)
62-71: Pricing source plumbing is correct; consider unifying timezone/locale usageThe new
loadSessionDatacall correctly passesmergedOptions.pricingSource(and other merged fields), so pricing-source selection and config defaults will affect data loading as intended.However, the table’s
dateFormatterstill usesctx.values.timezone/ctx.values.locale, so timezone/locale coming from config (viamergeConfigWithArgs) may influence loading but not display. Consider switching the formatter tomergedOptions.timezone/mergedOptions.localefor consistency.Also applies to: 130-134
apps/ccusage/src/commands/blocks.ts (1)
166-176: Blocks command correctly threadspricingSource; consider harmonizing locale/timezone usageUsing
mergedOptions.*forsince,until,mode,order,offline,pricingSource,timezone, andlocaleinloadSessionBlockData, and formode/order/pricingSourceinstartLiveMonitoring, cleanly integrates the new pricing-source selection into both report and live-monitor flows.As in
session.ts, you now have a mix ofmergedOptions(for loading) andctx.values(for things likectx.values.localein formatting). If you want config-file overrides for locale/timezone to be fully respected, it would be worth standardizing those remaining call sites onmergedOptionsin a follow-up.Also applies to: 251-259
apps/ccusage/src/data-loader.ts (1)
3566-3566: Updated tests match the extended PricingFetcher constructorAll the updated test cases now instantiate
new PricingFetcher(false, "auto"), which aligns with the new(offline, pricingSource = "auto")signature and preserves the previous behavior of using online pricing in auto/merged mode. Once the underlying"modelsdev"semantics are finalized, you may want to add a few tests that exercise non‑defaultpricingSourcevalues, but the current changes are consistent.Also applies to: 3575-3575, 3583-3583, 3602-3602, 3609-3609, 3625-3625, 3636-3636, 3657-3657, 3670-3670, 3687-3687, 3701-3701, 3710-3710, 3717-3717, 3739-3739, 3747-3747, 3753-3753
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
apps/ccusage/config-schema.json(7 hunks)apps/ccusage/package.json(2 hunks)apps/ccusage/src/_live-monitor.ts(5 hunks)apps/ccusage/src/_live-rendering.ts(2 hunks)apps/ccusage/src/_pricing-fetcher.ts(2 hunks)apps/ccusage/src/_shared-args.ts(5 hunks)apps/ccusage/src/_types.ts(1 hunks)apps/ccusage/src/commands/_blocks.live.ts(1 hunks)apps/ccusage/src/commands/blocks.ts(2 hunks)apps/ccusage/src/commands/session.ts(1 hunks)apps/ccusage/src/commands/statusline.ts(2 hunks)apps/ccusage/src/data-loader.ts(20 hunks)apps/ccusage/src/debug.ts(1 hunks)apps/codex/package.json(2 hunks)apps/mcp/package.json(2 hunks)docs/guide/cli-options.md(1 hunks)docs/guide/configuration.md(2 hunks)docs/guide/pricing-sources.md(1 hunks)packages/internal/package.json(1 hunks)packages/internal/src/models-dev-pricing.ts(1 hunks)packages/internal/src/pricing.ts(5 hunks)
🧰 Additional context used
📓 Path-based instructions (15)
apps/ccusage/src/**/*.ts
📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)
apps/ccusage/src/**/*.ts: Write tests in-source usingif (import.meta.vitest != null)blocks instead of separate test files
Use Vitest globals (describe,it,expect) without imports in test blocks
In tests, use current Claude 4 models (sonnet-4, opus-4)
Usefs-fixturewithcreateFixture()to simulate Claude data in tests
Only export symbols that are actually used by other modules
Do not use console.log; use the logger utilities fromsrc/logger.tsinstead
Files:
apps/ccusage/src/_live-rendering.tsapps/ccusage/src/debug.tsapps/ccusage/src/commands/_blocks.live.tsapps/ccusage/src/commands/statusline.tsapps/ccusage/src/_types.tsapps/ccusage/src/_pricing-fetcher.tsapps/ccusage/src/_live-monitor.tsapps/ccusage/src/data-loader.tsapps/ccusage/src/commands/session.tsapps/ccusage/src/_shared-args.tsapps/ccusage/src/commands/blocks.ts
apps/ccusage/**/*.ts
📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)
apps/ccusage/**/*.ts: NEVER useawait import()dynamic imports anywhere (especially in tests)
Prefer@praha/byethrowResult type for error handling instead of try-catch
Use.tsextensions for local imports (e.g.,import { foo } from './utils.ts')
Files:
apps/ccusage/src/_live-rendering.tsapps/ccusage/src/debug.tsapps/ccusage/src/commands/_blocks.live.tsapps/ccusage/src/commands/statusline.tsapps/ccusage/src/_types.tsapps/ccusage/src/_pricing-fetcher.tsapps/ccusage/src/_live-monitor.tsapps/ccusage/src/data-loader.tsapps/ccusage/src/commands/session.tsapps/ccusage/src/_shared-args.tsapps/ccusage/src/commands/blocks.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js,jsx}: Use ESLint for linting and formatting with tab indentation and double quotes
No console.log allowed except where explicitly disabled with eslint-disable; use logger.ts instead
Use file paths with Node.js path utilities for cross-platform compatibility
Use variables starting with lowercase (camelCase) for variable names
Can use UPPER_SNAKE_CASE for constants
Files:
apps/ccusage/src/_live-rendering.tsapps/ccusage/src/debug.tsapps/ccusage/src/commands/_blocks.live.tsapps/ccusage/src/commands/statusline.tsapps/ccusage/src/_types.tspackages/internal/src/pricing.tspackages/internal/src/models-dev-pricing.tsapps/ccusage/src/_pricing-fetcher.tsapps/ccusage/src/_live-monitor.tsapps/ccusage/src/data-loader.tsapps/ccusage/src/commands/session.tsapps/ccusage/src/_shared-args.tsapps/ccusage/src/commands/blocks.ts
**/*.ts{,x}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript with strict mode and bundler module resolution
Files:
apps/ccusage/src/_live-rendering.tsapps/ccusage/src/debug.tsapps/ccusage/src/commands/_blocks.live.tsapps/ccusage/src/commands/statusline.tsapps/ccusage/src/_types.tspackages/internal/src/pricing.tspackages/internal/src/models-dev-pricing.tsapps/ccusage/src/_pricing-fetcher.tsapps/ccusage/src/_live-monitor.tsapps/ccusage/src/data-loader.tsapps/ccusage/src/commands/session.tsapps/ccusage/src/_shared-args.tsapps/ccusage/src/commands/blocks.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Use.tsextensions for local file imports (e.g.,import { foo } from './utils.ts')
Prefer @praha/byethrow Result type over traditional try-catch for functional error handling
UseResult.try()for wrapping operations that may throw (JSON parsing, etc.)
UseResult.isFailure()for checking errors (more readable than!Result.isSuccess())
Use early return pattern (if (Result.isFailure(result)) continue;) instead of ternary operators when checking Results
Keep traditional try-catch only for file I/O with complex error handling or legacy code that's hard to refactor
Always useResult.isFailure()andResult.isSuccess()type guards for better code clarity
Use uppercase (PascalCase) for type names
Only export constants, functions, and types that are actually used by other modules - internal constants used only within the same file should NOT be exported
In-source testing pattern: write tests directly in source files usingif (import.meta.vitest != null)blocks
CRITICAL: DO NOT useawait import()dynamic imports anywhere in the codebase - this causes tree-shaking issues
CRITICAL: Never use dynamic imports withawait import()in vitest test blocks - this is particularly problematic for test execution
Vitest globals (describe,it,expect) are enabled and available without imports since globals are configured
Create mock data usingfs-fixturewithcreateFixture()for Claude data directory simulation in tests
All test files must use current Claude 4 models (claude-sonnet-4-20250514, claude-opus-4-20250514), not outdated Claude 3 models
Model names in tests must exactly match LiteLLM's pricing database entries
Files:
apps/ccusage/src/_live-rendering.tsapps/ccusage/src/debug.tsapps/ccusage/src/commands/_blocks.live.tsapps/ccusage/src/commands/statusline.tsapps/ccusage/src/_types.tspackages/internal/src/pricing.tspackages/internal/src/models-dev-pricing.tsapps/ccusage/src/_pricing-fetcher.tsapps/ccusage/src/_live-monitor.tsapps/ccusage/src/data-loader.tsapps/ccusage/src/commands/session.tsapps/ccusage/src/_shared-args.tsapps/ccusage/src/commands/blocks.ts
**/*.{ts,tsx,json}
📄 CodeRabbit inference engine (CLAUDE.md)
Claude model naming convention:
claude-{model-type}-{generation}-{date}(e.g.,claude-sonnet-4-20250514, NOTclaude-4-sonnet-20250514)
Files:
apps/ccusage/src/_live-rendering.tspackages/internal/package.jsonapps/ccusage/src/debug.tsapps/ccusage/src/commands/_blocks.live.tsapps/ccusage/src/commands/statusline.tsapps/ccusage/src/_types.tspackages/internal/src/pricing.tspackages/internal/src/models-dev-pricing.tsapps/ccusage/src/_pricing-fetcher.tsapps/ccusage/src/_live-monitor.tsapps/ccusage/src/data-loader.tsapps/ccusage/config-schema.jsonapps/ccusage/src/commands/session.tsapps/ccusage/src/_shared-args.tsapps/mcp/package.jsonapps/codex/package.jsonapps/ccusage/src/commands/blocks.tsapps/ccusage/package.json
**/package.json
📄 CodeRabbit inference engine (CLAUDE.md)
Dependencies should always be added as devDependencies unless explicitly requested otherwise
Files:
packages/internal/package.jsonapps/mcp/package.jsonapps/codex/package.jsonapps/ccusage/package.json
docs/guide/**/*.md
📄 CodeRabbit inference engine (docs/CLAUDE.md)
docs/guide/**/*.md: Place screenshots immediately after the main heading (H1) on guide pages that include screenshots
User-facing guides should live under the docs/guide/ directory
Files:
docs/guide/pricing-sources.mddocs/guide/configuration.mddocs/guide/cli-options.md
docs/**/*.md
📄 CodeRabbit inference engine (docs/CLAUDE.md)
docs/**/*.md: Use image paths relative to the docs public root (e.g., /screenshot.png for assets in /docs/public/)
Always include descriptive alt text for images and screenshots
For code blocks that should skip ESLint parsing (e.g., containing ...), add immediately before the code block
Files:
docs/guide/pricing-sources.mddocs/guide/configuration.mddocs/guide/cli-options.md
**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.md: Place screenshots immediately after the main heading (H1) in documentation pages for immediate visual context
Use relative image paths like/screenshot.pngfor images stored in/docs/public/in documentation
Always include descriptive alt text for images in documentation for accessibility
Files:
docs/guide/pricing-sources.mddocs/guide/configuration.mddocs/guide/cli-options.md
**/data-loader.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Silently skip malformed JSONL lines during parsing in data loading operations
Files:
apps/ccusage/src/data-loader.ts
apps/mcp/**/package.json
📄 CodeRabbit inference engine (apps/mcp/CLAUDE.md)
Add new dependencies as
devDependenciesunless explicitly requested otherwise
Files:
apps/mcp/package.json
apps/*/package.json
📄 CodeRabbit inference engine (CLAUDE.md)
All projects under
apps/ship as bundled CLIs/binaries - treat runtime dependencies as bundled assets by listing everything in each app'sdevDependencies(neverdependencies)
Files:
apps/mcp/package.jsonapps/codex/package.jsonapps/ccusage/package.json
apps/codex/**/package.json
📄 CodeRabbit inference engine (apps/codex/CLAUDE.md)
Package Codex as a bundled CLI and keep every runtime dependency in devDependencies so the bundle includes shipped code
Files:
apps/codex/package.json
apps/ccusage/**/package.json
📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)
apps/ccusage/**/package.json: Add dependencies as devDependencies unless explicitly required otherwise
Because the CLI is bundled, keep all runtime libraries in devDependencies so the bundler captures them
Files:
apps/ccusage/package.json
🧠 Learnings (30)
📓 Common learnings
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Fetch per-model pricing from LiteLLM model_prices_and_context_window.json via LiteLLMPricingFetcher using an offline cache scoped to Codex-prefixed models; handle aliases (e.g., gpt-5-codex → gpt-5) in CodexPricingSource
📚 Learning: 2025-11-25T14:42:34.734Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T14:42:34.734Z
Learning: Applies to **/*.{ts,tsx} : Only export constants, functions, and types that are actually used by other modules - internal constants used only within the same file should NOT be exported
Applied to files:
packages/internal/package.json
📚 Learning: 2025-09-17T18:29:15.764Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/mcp/CLAUDE.md:0-0
Timestamp: 2025-09-17T18:29:15.764Z
Learning: Applies to apps/mcp/**/*.ts : Only export what is actually used
Applied to files:
packages/internal/package.jsonapps/mcp/package.json
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/**/package.json : Because the CLI is bundled, keep all runtime libraries in devDependencies so the bundler captures them
Applied to files:
packages/internal/package.jsonapps/codex/package.jsonapps/ccusage/package.json
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Only export symbols that are actually used by other modules
Applied to files:
packages/internal/package.jsonapps/codex/package.jsonapps/ccusage/package.json
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/**/package.json : Add dependencies as devDependencies unless explicitly required otherwise
Applied to files:
packages/internal/package.jsonapps/codex/package.jsonapps/ccusage/package.json
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/**/*.ts : Use `.ts` extensions for local imports (e.g., `import { foo } from './utils.ts'`)
Applied to files:
packages/internal/package.jsonapps/ccusage/package.json
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Applies to apps/codex/**/package.json : Package Codex as a bundled CLI and keep every runtime dependency in devDependencies so the bundle includes shipped code
Applied to files:
packages/internal/package.jsonapps/codex/package.jsonapps/ccusage/package.json
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Write tests in-source using `if (import.meta.vitest != null)` blocks instead of separate test files
Applied to files:
packages/internal/package.jsonpackages/internal/src/models-dev-pricing.ts
📚 Learning: 2025-11-25T14:42:34.734Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T14:42:34.734Z
Learning: Applies to apps/*/package.json : All projects under `apps/` ship as bundled CLIs/binaries - treat runtime dependencies as bundled assets by listing everything in each app's `devDependencies` (never `dependencies`)
Applied to files:
packages/internal/package.jsonapps/codex/package.jsonapps/ccusage/package.json
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Fetch per-model pricing from LiteLLM model_prices_and_context_window.json via LiteLLMPricingFetcher using an offline cache scoped to Codex-prefixed models; handle aliases (e.g., gpt-5-codex → gpt-5) in CodexPricingSource
Applied to files:
apps/ccusage/src/commands/statusline.tsdocs/guide/pricing-sources.mdpackages/internal/src/pricing.tspackages/internal/src/models-dev-pricing.tsapps/ccusage/src/_pricing-fetcher.tsapps/ccusage/src/data-loader.ts
📚 Learning: 2025-11-25T14:42:34.734Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T14:42:34.734Z
Learning: Applies to **/calculate-cost.ts : Cost calculations require exact model name matches with LiteLLM's database
Applied to files:
apps/ccusage/src/_types.tspackages/internal/src/pricing.tspackages/internal/src/models-dev-pricing.tsapps/ccusage/src/_pricing-fetcher.tsapps/ccusage/src/data-loader.ts
📚 Learning: 2025-11-25T14:42:34.734Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T14:42:34.734Z
Learning: Refer to individual CLAUDE.md files in apps/ccusage/CLAUDE.md, apps/mcp/CLAUDE.md, and docs/CLAUDE.md for package-specific guidance
Applied to files:
docs/guide/pricing-sources.md
📚 Learning: 2025-11-25T14:42:34.734Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T14:42:34.734Z
Learning: Applies to **/*.{ts,tsx} : Model names in tests must exactly match LiteLLM's pricing database entries
Applied to files:
packages/internal/src/pricing.tspackages/internal/src/models-dev-pricing.ts
📚 Learning: 2025-11-25T14:42:34.734Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T14:42:34.734Z
Learning: Applies to **/*.{ts,tsx} : Use `Result.try()` for wrapping operations that may throw (JSON parsing, etc.)
Applied to files:
packages/internal/src/pricing.ts
📚 Learning: 2025-11-25T14:42:34.734Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T14:42:34.734Z
Learning: The application cannot calculate costs for models not supported by LiteLLM
Applied to files:
packages/internal/src/pricing.ts
📚 Learning: 2025-11-25T14:42:34.734Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T14:42:34.734Z
Learning: Applies to **/*.{ts,tsx} : In-source testing pattern: write tests directly in source files using `if (import.meta.vitest != null)` blocks
Applied to files:
packages/internal/src/models-dev-pricing.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Use Vitest globals (`describe`, `it`, `expect`) without imports in test blocks
Applied to files:
packages/internal/src/models-dev-pricing.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : In tests, use current Claude 4 models (sonnet-4, opus-4)
Applied to files:
apps/ccusage/src/_pricing-fetcher.ts
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Pricing tests must inject stub offline loaders to avoid network access
Applied to files:
apps/ccusage/src/_pricing-fetcher.tsapps/ccusage/src/data-loader.ts
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Command flag --offline forces use of the embedded pricing snapshot
Applied to files:
docs/guide/configuration.md
📚 Learning: 2025-09-17T18:29:15.764Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/mcp/CLAUDE.md:0-0
Timestamp: 2025-09-17T18:29:15.764Z
Learning: Applies to apps/mcp/**/package.json : Add new dependencies as `devDependencies` unless explicitly requested otherwise
Applied to files:
apps/mcp/package.jsonapps/codex/package.jsonapps/ccusage/package.json
📚 Learning: 2025-09-17T18:29:15.764Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/mcp/CLAUDE.md:0-0
Timestamp: 2025-09-17T18:29:15.764Z
Learning: Applies to apps/mcp/**/*.{test,spec}.ts : Use `fs-fixture` for mock data in tests of MCP server functionality
Applied to files:
apps/mcp/package.json
📚 Learning: 2025-09-17T18:29:15.764Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/mcp/CLAUDE.md:0-0
Timestamp: 2025-09-17T18:29:15.764Z
Learning: Applies to apps/mcp/**/*.ts : Use `.ts` extensions for local (relative) imports
Applied to files:
apps/mcp/package.json
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Treat Codex as a sibling to apps/ccusage; reuse shared packages, command names, and flag semantics; diverge only when Codex-specific data requires it and document inline
Applied to files:
apps/codex/package.json
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: After any code change, run format, typecheck, and tests in parallel (`pnpm run format`, `pnpm typecheck`, `pnpm run test`)
Applied to files:
apps/codex/package.jsonapps/ccusage/package.json
📚 Learning: 2025-11-25T14:42:34.734Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T14:42:34.734Z
Learning: After making any code changes, run `pnpm run format`, `pnpm typecheck`, and `pnpm run test` in parallel
Applied to files:
apps/codex/package.jsonapps/ccusage/package.json
📚 Learning: 2025-11-25T14:42:34.734Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T14:42:34.734Z
Learning: Run `pnpm run format` to format code with ESLint, which writes changes
Applied to files:
apps/codex/package.json
📚 Learning: 2025-09-17T18:29:15.764Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/mcp/CLAUDE.md:0-0
Timestamp: 2025-09-17T18:29:15.764Z
Learning: After code changes, always run `pnpm run format`, `pnpm typecheck`, and `pnpm run test` in parallel
Applied to files:
apps/codex/package.jsonapps/ccusage/package.json
📚 Learning: 2025-11-25T14:42:34.734Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T14:42:34.734Z
Learning: Run `pnpm typecheck` to type check code with TypeScript
Applied to files:
apps/codex/package.json
🧬 Code graph analysis (8)
apps/ccusage/src/_live-rendering.ts (2)
apps/ccusage/src/_types.ts (1)
PricingSource(154-154)apps/codex/src/_types.ts (1)
PricingSource(53-55)
apps/ccusage/src/debug.ts (1)
apps/ccusage/src/_pricing-fetcher.ts (1)
PricingFetcher(40-54)
apps/ccusage/src/commands/statusline.ts (2)
apps/ccusage/src/_shared-args.ts (1)
sharedArgs(24-132)apps/ccusage/src/data-loader.ts (1)
calculateContextTokens(1237-1313)
apps/ccusage/src/_types.ts (1)
apps/codex/src/_types.ts (1)
PricingSource(53-55)
packages/internal/src/pricing.ts (2)
packages/internal/src/logger.ts (1)
createLogger(5-17)packages/internal/src/models-dev-pricing.ts (1)
fetchModelsDevPricing(78-122)
apps/ccusage/src/_pricing-fetcher.ts (2)
apps/ccusage/src/_types.ts (1)
PricingSource(154-154)packages/internal/src/pricing.ts (1)
LiteLLMPricingFetcher(96-406)
apps/ccusage/src/data-loader.ts (3)
apps/ccusage/src/_types.ts (1)
PricingSource(154-154)apps/codex/src/_types.ts (1)
PricingSource(53-55)apps/ccusage/src/_pricing-fetcher.ts (1)
PricingFetcher(40-54)
apps/ccusage/src/_shared-args.ts (1)
apps/ccusage/src/_types.ts (2)
PricingSource(154-154)PricingSources(149-149)
🪛 markdownlint-cli2 (0.18.1)
docs/guide/pricing-sources.md
107-107: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
174-174: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
docs/guide/configuration.md
86-86: Hard tabs
Column: 1
(MD010, no-hard-tabs)
87-87: Hard tabs
Column: 1
(MD010, no-hard-tabs)
🔇 Additional comments (17)
apps/codex/package.json (2)
6-8: Field reordering is acceptable.The metadata fields (
bugs,funding,license,author,type,engines) have been reordered and theenginessection relocated, but the content remains unchanged. These are purely cosmetic changes and do not affect functionality.Also applies to: 13-16, 55-57
35-54: Runtime dependencies are correctly placed indevDependencies.All dependencies are properly configured as
devDependencies, which is correct for a bundled CLI where the bundler will capture all runtime code at build time.apps/ccusage/src/debug.ts (1)
94-96: Explicitly settingofflineandpricingSourcelooks correctUsing
new PricingFetcher(false, "auto")matches the new constructor signature and preserves prior behavior while making the defaults explicit. No issues from this change.packages/internal/package.json (1)
7-14: Newmodels-dev-pricingexport is wired correctlyExporting
"./models-dev-pricing": "./src/models-dev-pricing.ts"is consistent with the existing internal API surface and points to the correct TS source file.docs/guide/configuration.md (1)
81-88:pricingSourcedocumentation matches config & CLI wiringAdding
"pricingSource": "auto"to the defaults example and documenting “Pricing Source:auto/litellm/modelsdevwith--pricing-sourcealigns with the new schema and shared CLI option. The example command mirrors the actual flags, and the link to the Pricing Sources guide is a good discoverability touch.Also applies to: 142-149, 151-152
apps/ccusage/package.json (1)
6-8: Metadata and engines updates look consistentAdding
bugs,funding,license,author, and"type": "module"improves package metadata, and theengines.node: ">=20.19.4"constraint is reasonable given the modern Node features used elsewhere in the app. No issues from these changes.Also applies to: 13-16, 90-92
apps/mcp/package.json (1)
6-8: MCP package metadata/engines updates are fineNew
bugs,funding,license,author, and"type": "module"fields plus theengines.nodeconstraint keep the MCP package in line with the main app’s metadata and runtime assumptions. No functional concerns.Also applies to: 13-16, 65-67
packages/internal/src/models-dev-pricing.ts (1)
5-73: Conversion and schema typing for models.dev pricing look solidThe valibot schemas for cost/limit/model/provider/API and the
ModelsDev*types are clear, andconvertModelsDevToLiteLLMcorrectly converts per‑million prices to per‑token and mapscontext/outputinto the LiteLLM limit fields. The in-source tests exercise both the happy path and missing-field behavior and match the in-repo testing guidelines.docs/guide/cli-options.md (1)
78-99: Short flag-pis documented for two different optionsThe "Pricing Source" section introduces
--pricing-sourcewith short flag-p, but the "Daily Command" section and the aliases table still document--projectas also using-p. Verify that distinct short flags are used in the CLI implementation and confirm the aliases table reflects the correct mappings for both options.apps/ccusage/src/commands/_blocks.live.ts (1)
33-39: PropagatingpricingSourceinto live monitor state looks correctForwarding
config.pricingSourceintomonitorConfigkeeps the live monitor’s pricing behavior aligned with the CLI/config selection and the updatedLiveMonitorConfigtype, without changing any other control flow.apps/ccusage/src/_live-rendering.ts (1)
11-12:LiveMonitoringConfigextension is consistent with upstream/downstream usageAdding
pricingSource: PricingSourceand importingPricingSourcematches howstartLiveMonitoringis called inblocks.tsand howLiveMonitorConfigis defined in_live-monitor.ts, without altering rendering logic.Also applies to: 45-53
apps/ccusage/src/_types.ts (1)
143-155: Pricing source enum and union follow existing patternsDefining
PricingSourcesand derivingPricingSourceviaTupleToUnionmirrorsCostModes/CostModeandSortOrders/SortOrder, giving a tight, type-safe source of truth for CLI and config without adding runtime complexity.apps/ccusage/src/commands/statusline.ts (1)
101-110: Statusline wiring forpricingSourceinto context calculation is soundExposing
pricingSourceviasharedArgs.pricingSourceand passingmergedOptions.pricingSourceintocalculateContextTokensaligns this command with the new pricing-source-aware context limit logic, without perturbing existing cost-source behavior.Also applies to: 435-438
apps/ccusage/src/_live-monitor.ts (1)
12-18: Live monitor now respects the configured pricing sourceExtending
LiveMonitorConfigwithpricingSourceand passing it intonew PricingFetcher(false, config.pricingSource)ensures live monitoring uses the same pricing-source selection as the rest of ccusage, without changing retention, file scanning, or block-identification logic. The in-source tests’ configs were correctly updated to includepricingSource: 'auto', keeping them in sync with the new type.Also applies to: 32-38, 75-77, 295-301, 333-339
docs/guide/pricing-sources.md (1)
107-111: Add languages to fenced code blocks and verify configuration guide linkThe price-conversion and log-output snippets use bare ``` fences, which trigger MD040; adding a language like
text(or `bash` if you prefer) will satisfy markdownlint and improve readability:-``` +```text models.dev: $3 per million tokens Converted: $0.000003 per token...
-+text
ℹ Fetching latest model pricing from LiteLLM...
...Also, the "Configuration Files" link points to
/guide/config-files; please verify that this slug exists and resolves correctly in your site structure.apps/ccusage/src/_shared-args.ts (1)
2-10: Pricing source CLI wiring looks consistent and type‑safe
PricingSource/PricingSourcesare correctly imported and used for the newpricingSourceenum arg; default"auto" as const satisfies PricingSourcematches the union, andsharedCommandConfig.toKebab = truewill expose this as--pricing-sourceas intended. The other description tweaks remain accurate given the current behavior.Also applies to: 44-52, 58-62, 76-83, 84-91
apps/ccusage/src/data-loader.ts (1)
13-21: PricingSource is correctly threaded through data loading and context‑limit pathsImporting
PricingSource, extendingLoadOptionswithpricingSource?: PricingSource, and passingoptions?.pricingSource ?? "auto"into everynew PricingFetcher(...)call (loadDailyUsageData,loadSessionData,loadSessionUsageById,loadSessionBlockData, andcalculateContextTokens) keeps behavior backward‑compatible while allowing callers to control the data source. The newcalculateContextTokensparameter and default also look consistent with the rest of the API.Also applies to: 728-740, 748-782, 890-941, 1098-1120, 1353-1363, 1237-1241, 1278-1293
| /** | ||
| * Determines whether to use models.dev based on pricing source setting | ||
| * @param pricingSource - The pricing source mode ('auto', 'litellm', or 'modelsdev') | ||
| * @param offline - Whether offline mode is enabled | ||
| * @returns true if models.dev should be used, false otherwise | ||
| */ | ||
| function shouldUseModelsDev(pricingSource: PricingSource, offline: boolean): boolean { | ||
| if (offline) { | ||
| return false; // Never use models.dev in offline mode | ||
| } | ||
|
|
||
| switch (pricingSource) { | ||
| case 'auto': | ||
| return true; // Use both sources (merged) | ||
| case 'litellm': | ||
| return false; // LiteLLM only | ||
| case 'modelsdev': | ||
| return true; // models.dev only (will be handled by fetcher options) | ||
| default: | ||
| return true; // Default to auto | ||
| } | ||
| } |
There was a problem hiding this comment.
modelsdev mode is indistinguishable from auto in current wiring
shouldUseModelsDev collapses both "auto" and "modelsdev" into useModelsDev = true, and PricingFetcher only passes this boolean into LiteLLMPricingFetcher. Since the underlying fetcher always fetches LiteLLM first and then optionally merges models.dev data, there is no way for pricingSource: "modelsdev" to mean “models.dev only” as advertised; it still hits LiteLLM and prefers LiteLLM data on key collisions.
To align behavior with the CLI modes, consider:
- Passing the full
PricingSource(or an explicit"auto" | "litellm" | "modelsdev"mode) intoLiteLLMPricingFetcherinstead of a boolean, and - Branching in the internal fetch logic so
"modelsdev"only ever calls models.dev (with an optional offline fallback) and does not require LiteLLM to be reachable.
This will also let you add tests that assert LiteLLM is not contacted when pricingSource === "modelsdev".
Also applies to: 41-52
🤖 Prompt for AI Agents
In apps/ccusage/src/_pricing-fetcher.ts around lines 17-38 (and likewise 41-52),
the function shouldUseModelsDev currently collapses "auto" and "modelsdev" into
a boolean that causes the fetcher to always contact LiteLLM first; change the
wiring to pass the full PricingSource ("auto" | "litellm" | "modelsdev") into
LiteLLMPricingFetcher instead of a boolean, update LiteLLMPricingFetcher to
branch on that mode so that "modelsdev" triggers only models.dev calls (with an
optional offline fallback) and never contacts LiteLLM, keep "auto" as the merged
behavior and "litellm" as LiteLLM-only, and add tests asserting that when
pricingSource === "modelsdev" LiteLLM is not contacted.
|
AFAIK, motels.dev does not support models that have two‑stage pricing changes, such as Sonnet’s 1MB model. Are you taking that into consideration? |
Add a pricingSource option across the shared CLI, config schema, all-agent loaders, Claude loaders, and agent adapters so callers can choose LiteLLM, models.dev, or an auto merge mode for calculated costs. Implement models.dev fetching in the internal pricing layer by validating the provider/model API shape and converting per-million token prices into the existing LiteLLM per-token pricing format. The auto mode preserves LiteLLM precedence and fills missing entries from models.dev, while offline mode continues to use embedded pricing snapshots instead of fetching models.dev. Document the new --pricing-source flag and regenerate the ccusage config schema. models.dev remains outside macro prefetching, so it is only used for live pricing fetches.
98b6218 to
fa3e7aa
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/ccusage/src/commands/session.ts (1)
49-57:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPropagate
pricingSourceto the non---idsession load path.Line 56 threads
pricingSourceonly for--id, but Line 74-81 still callsloadSessionDatawithout it. Regularsessionoutput will ignore--pricing-source/config and diverge fromsession --id.Suggested fix
sessionData = await loadSessionData({ since: mergedOptions.since, until: mergedOptions.until, mode: mergedOptions.mode, offline: mergedOptions.offline, + pricingSource: mergedOptions.pricingSource, singleThread: mergedOptions.singleThread, timezone: mergedOptions.timezone, });🤖 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 `@apps/ccusage/src/commands/session.ts` around lines 49 - 57, The non-`--id` execution path currently omits mergedOptions.pricingSource when calling loadSessionData, so the CLI ignores --pricing-source/config for the regular session command while handleSessionIdLookup passes it for the --id path; update the branch that calls loadSessionData (the call that uses mergedOptions.mode, mergedOptions.offline, mergedOptions.timezone) to also pass mergedOptions.pricingSource so loadSessionData receives pricingSource consistently across both code paths (mirror the values object keys used in handleSessionIdLookup).
🤖 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 `@packages/internal/src/pricing.ts`:
- Around line 278-284: The offline check is skipped when pricingSource ===
'modelsdev', causing network calls even in offline mode; update the condition
order in the method that decides pricing by checking this.offline before testing
this.pricingSource so that if this.offline is true you call loadOfflinePricing()
first (and return), otherwise proceed to handle pricingSource and call
loadModelsDevPricing() when appropriate; locate the decision logic referencing
pricingSource, loadModelsDevPricing, offline, and loadOfflinePricing and reorder
the conditionals so offline takes precedence.
---
Outside diff comments:
In `@apps/ccusage/src/commands/session.ts`:
- Around line 49-57: The non-`--id` execution path currently omits
mergedOptions.pricingSource when calling loadSessionData, so the CLI ignores
--pricing-source/config for the regular session command while
handleSessionIdLookup passes it for the --id path; update the branch that calls
loadSessionData (the call that uses mergedOptions.mode, mergedOptions.offline,
mergedOptions.timezone) to also pass mergedOptions.pricingSource so
loadSessionData receives pricingSource consistently across both code paths
(mirror the values object keys used in handleSessionIdLookup).
🪄 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
Run ID: 8fa54f0f-bec1-4948-90a8-07eeab809319
📒 Files selected for processing (22)
apps/ccusage/config-schema.jsonapps/ccusage/src/adapter/amp/index.tsapps/ccusage/src/adapter/claude/data-loader.tsapps/ccusage/src/adapter/claude/index.tsapps/ccusage/src/adapter/codex/index.tsapps/ccusage/src/adapter/opencode/index.tsapps/ccusage/src/adapter/types.tsapps/ccusage/src/commands/agent.tsapps/ccusage/src/commands/all.tsapps/ccusage/src/commands/blocks.tsapps/ccusage/src/commands/codex.tsapps/ccusage/src/commands/session.tsapps/ccusage/src/commands/session_id.tsapps/ccusage/src/commands/statusline.tsapps/ccusage/src/pricing-fetcher.tsapps/ccusage/src/shared-args.tsapps/ccusage/src/types.tsdocs/guide/cli-options.mddocs/guide/configuration.mddocs/guide/statusline.mdpackages/internal/src/models-dev-pricing.tspackages/internal/src/pricing.ts
✅ Files skipped from review due to trivial changes (3)
- docs/guide/statusline.md
- docs/guide/cli-options.md
- docs/guide/configuration.md
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/internal/src/models-dev-pricing.ts
models.dev currently exposes flat token prices, so using it as the primary pricing source could bypass LiteLLM tier metadata for models such as Claude Sonnet with higher prices above large context thresholds. Fetch LiteLLM metadata alongside models.dev for the explicit modelsdev source, copy only the known tiered pricing fields when present, and fall back to flat models.dev pricing if LiteLLM cannot be loaded. This keeps models.dev as the selected price source while preserving the existing tiered cost calculation path. Add a regression test for a models.dev Claude Sonnet row with LiteLLM above-200k fields and document the behavior in the CLI and configuration guides.
|
Closing this stale pricing-source PR as not planned. The models.dev fallback path is risky for ccusage because current pricing logic depends on LiteLLM fields such as tiered/cache pricing, and this PR still has the unresolved tiered-pricing concern noted earlier. A future pricing-source change should be designed against the current pricing module and preserve tiered model behavior explicitly. |
Mainland China have some trouble accessing litellm APIs #13 , and a previous PR to add pricing source #51 is closed. I found a site https://models.dev/ that supports multiple provider and more diverse model IDs.
Summary by CodeRabbit
New Features
Documentation