Skip to content

feat(hermes): add Hermes Agent usage adapter - #1026

Merged
ryoppippi merged 14 commits into
mainfrom
codex/hermes-agent-usage-adapter
May 19, 2026
Merged

ryoppippi merged 14 commits into
mainfrom
codex/hermes-agent-usage-adapter

Conversation

@ryoppippi

@ryoppippi ryoppippi commented May 17, 2026 •

Copy link
Copy Markdown
Member

Try This PR

bunx -p https://pkg.pr.new/ryoppippi/ccusage@1026 ccusage hermes daily --help

Summary

  • add a Hermes Agent adapter that reads local SQLite state databases from HERMES_HOME or ~/.hermes
  • expose ccusage hermes daily/monthly/session and include Hermes Agent in all-source detection
  • document the Hermes data source, HERMES_HOME override, and config namespace

Validation

  • pnpm run format
  • pnpm typecheck
  • pnpm run test
  • pnpm --filter docs build

Summary by CodeRabbit

  • New Features

    • Added Hermes Agent as a supported data source with ccusage hermes daily|monthly|session commands.
    • Usage reports now include message counts where available.
    • Configuration schema extended to support Hermes Agent settings.
  • Documentation

    • New Hermes Agent docs with setup, commands, metrics, env vars, and troubleshooting; guides and sidebar updated.
  • Other

    • Added fallback pricing for grok-4.3 (affects cost calculations).

Review Change Stack

ryoppippi added 2 commits May 17, 2026 12:59
Read Hermes Agent local state.db files from HERMES_HOME or ~/.hermes and convert session rows into ccusage daily, monthly, and session reports.

The adapter imports billable sessions from the SQLite sessions table, prefers recorded actual cost over estimated cost, includes reasoning tokens in total tokens, and falls back to LiteLLM pricing when no recorded cost is present.

Wire the hermes namespace into all-source detection, focused CLI commands, config namespaces, generated schema, and fixture-backed CLI coverage so CI does not depend on local Hermes data.
Add Hermes Agent to the README, guide navigation, source overview pages, environment variable reference, and configuration namespace documentation.

Document the local state.db location, HERMES_HOME override, focused commands, token and cost behavior, and troubleshooting guidance for users without requiring local Hermes data in tests.
@coderabbitai

coderabbitai Bot commented May 17, 2026 •

Copy link
Copy Markdown

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

Adds Hermes Agent support: a new SQLite-based adapter parses Hermes session DBs, threads message counts and extra token accounting through summaries, wires CLI/command/help and config schema, adds pricing fallbacks, integrates Hermes into all-agents reports, updates existing adapters, and ships docs and tests.

Changes

Hermes Agent Support

Layer / File(s) Summary
Type system: token and message count extensions
rust/crates/ccusage/src/types.rs
Add extra_total_tokens: u64 to ModelBreakdown (skipped in serialization) and message_count: Option<u64> to LoadedEntry and UsageSummary (conditionally serialized).
Hermes SQLite adapter implementation
rust/crates/ccusage/src/adapter/hermes.rs
Discover state.db from HERMES_HOME, read sessions table, parse tokens/reasoning/message counts/costs, dedupe by session_id, convert to LoadedEntry/UsageSummary, support Daily/Monthly/Session reports, and include unit tests validating parsing and JSON output.
Summary aggregation: message count and extra token propagation
rust/crates/ccusage/src/summary.rs
Extend UsageAccumulator to accumulate message_count and extra_total_tokens; propagate both into UsageSummary and across bucket and per-model aggregation.
Adapter module and all-agents integration
rust/crates/ccusage/src/adapter/mod.rs, rust/crates/ccusage/src/adapter/all.rs, rust/crates/ccusage/src/adapter/opencode/mod.rs
Declare hermes submodule; wire load_hermes_rows into Session and Daily report paths; use total_tokens() for JSON totalTokens and conditionally insert messageCount.
Existing adapters: initialize message_count and extra tokens
rust/crates/ccusage/src/adapter/amp.rs, rust/crates/ccusage/src/adapter/pi.rs, rust/crates/ccusage/src/adapter/copilot.rs, rust/crates/ccusage/src/adapter/gemini.rs, rust/crates/ccusage/src/adapter/kilo.rs, rust/crates/ccusage/src/adapter/opencode/parser.rs, rust/crates/ccusage/src/claude_loader.rs, test fixtures in main.rs
Initialize message_count: None and ensure extra_total_tokens propagation in LoadedEntry/UsageSummary construction across adapters and update tests accordingly.
CLI parsing and help
rust/crates/ccusage/src/cli.rs
Add Hermes(AgentCommandArgs) variant; implement parse_hermes_command (daily
Main dispatch, progress, and config detection
rust/crates/ccusage/src/main.rs, rust/crates/ccusage/src/progress.rs, rust/crates/ccusage/src/config.rs
Route Command::Hermes to adapter::hermes::run; add UsageLoadAgent::Hermes and agent label; include hermes in agent detection/validation.
Configuration schema and JSON schema
rust/crates/ccusage/src/config_schema.rs, apps/ccusage/config-schema.json
Add HermesConfig and HermesCommandsConfig types (defaults, commands.{daily,monthly,session}) and wire optional hermes root property into generated JSON schema with descriptions and validation examples.
Pricing fallbacks: frontier model support
rust/crates/ccusage/src/pricing.rs
Add fallback Pricing entry and context_limit for grok-4.3 and unit test asserting embedded pricing includes frontier models.
User documentation and navigation
docs/guide/hermes/index.md, docs/guide/index.md, docs/.vitepress/config.ts, docs/guide/getting-started.md, apps/ccusage/README.md, docs/index.md
Add Hermes Agent guide, update README and guide index and sidebar, add examples (ccusage hermes daily) and troubleshooting note about ${HERMES_HOME:-~/.hermes}/state.db.

Sequence Diagram(s)

sequenceDiagram
  participant User as CLI
  participant Run as adapter::hermes::run
  participant Loader as load_entries
  participant DB as Hermes state.db
  participant Pricing as PricingMap
  participant Summary as summarize_entries
  participant Reporter as report_from_rows
  User->>Run: ccusage hermes <report> [--since/--until/...]
  Run->>Loader: load_entries(shared, pricing)
  Loader->>DB: open state.db / query sessions
  DB-->>Loader: session rows (tokens, messages, cost)
  Loader->>Pricing: lookup model/provider candidates
  Loader-->>Run: Vec<LoadedEntry>
  Run->>Summary: summarize_entries(entries, kind)
  Summary-->>Reporter: Vec<UsageSummary>
  Reporter-->>User: JSON or formatted table
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • ryoppippi/ccusage#1009: Related VitePress/docs navigation changes that touch the same sidebar area where "Hermes Agent" was added.
  • ryoppippi/ccusage#1058: Refactors schema generation that overlaps with the new hermes config schema additions.
  • ryoppippi/ccusage#977: Foundational CLI/adapter reporting pipeline that this Hermes wiring extends.

Suggested labels

enhancement

"🐰 A rabbit hops in with glee,
Hermes sessions counted, tokens three.
Messages tallied, costs aligned,
Reports unified, neat and signed.
Hooray — a new agent in the tree!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat(hermes): add Hermes Agent usage adapter' accurately and concisely describes the main change: adding a new Hermes Agent adapter to the ccusage tool.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 codex/hermes-agent-usage-adapter

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 and usage tips.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 17, 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 338d94c Commit Preview URL

Branch Preview URL
May 19 2026, 03:42 PM

@pkg-pr-new

pkg-pr-new Bot commented May 17, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

ccusage

npx https://pkg.pr.new/ryoppippi/ccusage@1026

@ccusage/ccusage-darwin-arm64

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

@ccusage/ccusage-darwin-x64

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

@ccusage/ccusage-linux-arm64

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

@ccusage/ccusage-linux-x64

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

@ccusage/ccusage-win32-arm64

npx https://pkg.pr.new/ryoppippi/ccusage/@ccusage/ccusage-win32-arm64@1026

@ccusage/ccusage-win32-x64

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

commit: 338d94c

@github-actions

github-actions Bot commented May 17, 2026 •

Copy link
Copy Markdown
Contributor

ccusage performance comparison

This compares the PR build against the base branch build on the same CI runner.

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)
Runtime: package ccusage bin from apps/ccusage/package.json through bun -b, --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 55.7ms 55.4ms 1.01x 0.03 MiB/s 0.03 MiB/s
claude session --offline --json 0.00 MiB 56.2ms 56.0ms 1.00x 0.03 MiB/s 0.03 MiB/s
codex daily --offline --json 0.00 MiB 55.1ms 55.5ms 0.99x 0.02 MiB/s 0.02 MiB/s
codex session --offline --json 0.00 MiB 55.4ms 55.6ms 1.00x 0.02 MiB/s 0.02 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, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Runtime: package ccusage bin from apps/ccusage/package.json through bun -b, --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 1.392s 1.399s 0.99x 740.86 MiB/s 737.15 MiB/s
codex --offline --json 1.01 GiB 1.734s 1.747s 0.99x 594.46 MiB/s 590.16 MiB/s

Package size

Package artifact Base PR Delta Ratio
packed ccusage-*.tgz 67.58 KiB 68.90 KiB +1.32 KiB 0.98x

Lower medians and smaller packed package sizes 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: 2

🧹 Nitpick comments (1)
apps/ccusage/src/commands/agent.ts (1)

217-219: ⚡ Quick win

Add a focused regression test for messageCount JSON output.

Line 217 adds new payload mapping, but there’s no in-source assertion covering this branch yet. A small session payload test with metadata.messageCount would lock the contract.

🤖 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/agent.ts` around lines 217 - 219, Add a
unit/regression test that exercises the branch which maps
row.metadata.messageCount into the JSON output: create a test that builds a
session-like payload/row with metadata.messageCount set to a numeric value, run
the code path that produces jsonRow (the function that maps row -> jsonRow / the
agent command handler), and assert the resulting JSON includes messageCount with
that numeric value; target the code that reads row.metadata?.messageCount and
assigns jsonRow.messageCount so the branch is locked.
🤖 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 `@apps/ccusage/config-schema.json`:
- Around line 635-781: The schema adds commands["hermes:*"] but never defines a
top-level "hermes" namespace, so validation/autocomplete will reject
hermes.defaults/hermes.commands despite apps/ccusage/src/config-loader-tokens.ts
accepting hermes?: AgentConfigData; add a root-level "hermes" property to the
JSON schema mirroring the existing structure used for
"codex"/"opencode"/"amp"/"pi" (type: object with "defaults" and "commands"
properties, same shapes and additionalProperties rules), ensuring entries like
hermes.defaults and hermes.commands (and the commands["hermes:*"] entries) are
valid.

In `@apps/ccusage/src/adapter/hermes/parser.ts`:
- Around line 19-20: The parser currently coerces missing DB costs to 0 for the
costUSD field which hides the difference between an actual $0 cost and "no
recorded cost"; update the type for costUSD from number to number | null (or
make it optional) in the parsed record, stop assigning 0 when the DB value is
absent (leave it as null/undefined), and propagate this nullability into any
downstream pricing contract functions (e.g., the pricing/computePricing or
priceSession consumer) so they treat null as "no recorded cost" rather than zero
and apply fallback logic only when appropriate.

---

Nitpick comments:
In `@apps/ccusage/src/commands/agent.ts`:
- Around line 217-219: Add a unit/regression test that exercises the branch
which maps row.metadata.messageCount into the JSON output: create a test that
builds a session-like payload/row with metadata.messageCount set to a numeric
value, run the code path that produces jsonRow (the function that maps row ->
jsonRow / the agent command handler), and assert the resulting JSON includes
messageCount with that numeric value; target the code that reads
row.metadata?.messageCount and assigns jsonRow.messageCount so the branch is
locked.
🪄 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: 2348ec9c-5404-422b-a80e-25be04b5444f

📥 Commits

Reviewing files that changed from the base of the PR and between 8bd4e7c and 18ea04f.

📒 Files selected for processing (20)
  • apps/ccusage/README.md
  • apps/ccusage/config-schema.json
  • apps/ccusage/src/adapter/hermes/index.ts
  • apps/ccusage/src/adapter/hermes/parser.ts
  • apps/ccusage/src/adapter/hermes/paths.ts
  • apps/ccusage/src/adapter/hermes/pricing.ts
  • apps/ccusage/src/adapter/index.ts
  • apps/ccusage/src/adapter/types.ts
  • apps/ccusage/src/commands/agent.ts
  • apps/ccusage/src/commands/index.ts
  • apps/ccusage/src/config-loader-tokens.ts
  • apps/ccusage/test/cli-output.test.ts
  • docs/.vitepress/config.ts
  • docs/guide/all-reports.md
  • docs/guide/config-files.md
  • docs/guide/configuration.md
  • docs/guide/environment-variables.md
  • docs/guide/getting-started.md
  • docs/guide/hermes/index.md
  • docs/guide/index.md

Comment thread apps/ccusage/config-schema.json Outdated
Comment thread apps/ccusage/src/adapter/hermes/parser.ts Outdated
ryoppippi added 2 commits May 17, 2026 13:09
Add the Hermes top-level configuration namespace to the JSON schema generator so hermes.defaults and hermes.commands validate alongside the command-specific entries.

Preserve the difference between a recorded zero-dollar Hermes session and a missing recorded cost. Recorded costs now short-circuit pricing fallback even when the value is 0, while missing costs continue to use token pricing.

Share the read-only SQLite loading wrapper with OpenCode so both SQLite-backed adapters use the same failure handling, and add focused regression coverage for messageCount JSON output and recorded-cost behavior.
Centralize the adapter-level SQLite availability check so SQLite-backed agents do not duplicate runtime probing logic.

Hermes now requires a readable SQLite runtime before reporting a state database as detected. OpenCode keeps its JSON fallback path while only treating database files as usable when SQLite support is present.
@ryoppippi

Copy link
Copy Markdown
Member Author

@cubic-dev-ai review it

@cubic-dev-ai

cubic-dev-ai Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review it

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

@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review it

@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
cat apps/ccusage/src/adapter/hermes/parser.ts
echo "---FILE_BOUNDARY---"
cat apps/ccusage/src/adapter/hermes/pricing.ts
echo "---FILE_BOUNDARY---"
cat apps/ccusage/src/adapter/hermes/paths.ts
echo "---FILE_BOUNDARY---"
cat apps/ccusage/src/adapter/hermes/index.ts
echo "---FILE_BOUNDARY---"
cat apps/ccusage/src/adapter/sqlite.ts
echo "---FILE_BOUNDARY---"
cat apps/ccusage/src/adapter/opencode/paths.ts
echo "---FILE_BOUNDARY---"
cat apps/ccusage/src/adapter/opencode/loader.ts
echo "---FILE_BOUNDARY---"
cat apps/ccusage/src/adapter/index.ts
echo "---FILE_BOUNDARY---"
cat apps/ccusage/src/adapter/types.ts
echo "---FILE_BOUNDARY---"
cat apps/ccusage/src/commands/index.ts
echo "---FILE_BOUNDARY---"
cat apps/ccusage/src/config-loader-tokens.ts
echo "---FILE_BOUNDARY---"
cat apps/ccusage/test/cli-output.test.ts

Length of output: 110794


@ryoppippi Great PR! The Hermes adapter follows the existing patterns well and the SQLite helper extraction is a nice improvement. Here are my findings:


Review: feat(hermes): add Hermes Agent usage adapter

🔴 Issues

1. createHermesStateDb duplicated in three places

The same schema-creation helper appears independently in:

  • apps/ccusage/src/adapter/hermes/parser.ts (in the import.meta.vitest block)
  • apps/ccusage/src/adapter/hermes/index.ts (in the import.meta.vitest block)
  • apps/ccusage/test/cli-output.test.ts

If the sessions table schema ever gains a new column (e.g., cost_currency), all three copies need updating. Consider extracting this into a single shared test fixture helper (e.g., src/adapter/hermes/_test-helpers.ts or a test/helpers/hermes.ts).


2. PROVIDER_ALIASES is narrow — unknown providers fall back to 'hermes'

apps/ccusage/src/adapter/hermes/parser.ts

const PROVIDER_ALIASES = new Map<string, string>([
  ['anthropic', 'anthropic'],
  ['claude', 'anthropic'],
  ['openai', 'openai'],
  ...
]);

Common providers like mistral, cohere, deepseek, bedrock, together, and fireworks are absent. When a session has one of these as billing_provider, normalizeProvider falls through to inferProviderFromModel, and if the model name doesn't match any known pattern, provider is set to 'hermes'.

The consequence is in createModelCandidates:

if (entry.provider !== 'hermes') {
  candidates.push(`${entry.provider}/${entry.model}`);
}

When provider is 'hermes', the provider/model candidate is never tried, so LiteLLM lookup falls back to 0 silently.

Consider either expanding the aliases map or always adding the raw billing_provider/model candidate when billing_provider is non-null:

function createModelCandidates(entry: HermesUsageEntry, rawProvider?: string): string[] {
  const candidates = [entry.model];
  if (entry.provider !== 'hermes') {
    candidates.push(`${entry.provider}/${entry.model}`);
  }
  // Also try the raw billing_provider value if it wasn't recognized
  if (rawProvider != null && rawProvider !== entry.provider) {
    candidates.push(`${rawProvider}/${entry.model}`);
  }
  return Array.from(new Set(candidates));
}

3. Silent $0 fallback in calculateHermesCost with no warning

apps/ccusage/src/adapter/hermes/pricing.ts

for (const candidate of createModelCandidates(entry)) {
  const result = await fetcher.calculateCostFromTokens(tokens, candidate);
  if (Result.isSuccess(result) && Number.isFinite(result.value) && result.value >= 0) {
    return result.value;
  }
}
return 0; // <-- silent zero

When LiteLLM has no pricing info for any candidate, costs are silently reported as $0.00. This is especially confusing because the user will see a session with tokens but zero cost. Adding a logger.warn (similar to how loadReadonlySqliteRows logs failures) would make this diagnosable without the docs:

logger.warn(`No pricing found for model '${entry.model}' (provider: '${entry.provider}'); cost reported as $0`);
return 0;

🟡 Warnings

4. o\d regex won't match future multi-digit OpenAI o-series models

apps/ccusage/src/adapter/hermes/parser.ts

/^(?:gpt|chatgpt)(?:[-/]|$)|^o\d(?:[-/]|$)/u

o1, o3, o4 all match, but o10-mini (hypothetical) would not because \d matches exactly one digit. Consider \d+:

/^(?:gpt|chatgpt)(?:[-/]|$)|^o\d+(?:[-/]|$)/u

5. Hermes integration test is conditionally skipped

apps/ccusage/test/cli-output.test.ts

it.skipIf(getSqliteDatabaseFactory(() => {}) == null)(
  'renders Hermes Agent direct JSON from state.db',

This is the only end-to-end test for the Hermes adapter. In CI environments without native SQLite bindings, this test is a no-op. Since the Hermes adapter is SQLite-only (unlike OpenCode which has a JSON fallback), consider documenting the CI requirement or adding a non-SQLite-gated test that exercises the parser directly with fixture data.


6. Negative cost values are silently clamped to zero

apps/ccusage/src/adapter/hermes/parser.ts

const costUSD =
  toNonNegativeNumber(row.actual_cost_usd) ?? toNonNegativeNumber(row.estimated_cost_usd);

toNonNegativeNumber uses Math.max(number, 0), so a negative value (e.g., a credit/refund in actual_cost_usd) is silently treated as 0. If this is intentional, a code comment explaining the decision would help future readers.


🟢 Nits

  • loadReadonlySqliteRows return typing: The result.value ?? [] fallback is safe but slightly odd since readRows is typed as (db) => T[] — it should never return null/undefined. A comment explaining this covers the edge case from withSqliteDatabase's return type would clarify intent.

  • Missing hermes weekly is clearly intentional and properly guarded by getUnsupportedAgentReportError. 👍

  • detectHermes vs detectHermesStateDb: The two-function split (one pure path check, one with SQLite support gate) is clean and consistent with the OpenCode pattern. 👍

  • Deduplication by sessionId first-seen in loadHermesUsageEntries is consistent with OpenCode's approach. 👍

✅ Actions performed

Full review triggered.

@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: 2

🤖 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 `@apps/ccusage/src/adapter/hermes/parser.ts`:
- Around line 93-102: normalizeProvider currently discards unknown
billing_provider strings and falls back to inferProviderFromModel(model); change
it so that when value is a non-empty string you first compute the normalized
form (value.trim().toLowerCase().replaceAll('-', '_')), check PROVIDER_ALIASES
for a mapped provider and return that if present, but if not present return the
normalized original string (preserving the explicit billing_provider); only call
inferProviderFromModel(model) when value is not a non-empty string. Update the
implementation in normalizeProvider and keep references to PROVIDER_ALIASES and
inferProviderFromModel.

In `@apps/ccusage/src/adapter/hermes/pricing.ts`:
- Around line 30-38: When all model candidates from createModelCandidates(entry)
fail and you are about to return 0, add a clear warning log so the silent
fallback is detectable: before returning 0, log a message (prefer using the
existing logger if available, e.g., processLogger or a module-level logger;
otherwise use console.warn) that includes identifying info such as the entry (or
entry.id) and the tokens value, and note that fetcher.calculateCostFromTokens
calls (and Result.isSuccess checks) for all candidates failed; this ensures
missing model pricing is visible when the loop exhausts candidates.
🪄 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: 03645fd2-49c0-4f3c-a226-7dda0d2b2f97

📥 Commits

Reviewing files that changed from the base of the PR and between 8bd4e7c and 1047bca.

📒 Files selected for processing (24)
  • apps/ccusage/README.md
  • apps/ccusage/config-schema.json
  • apps/ccusage/scripts/generate-json-schema.ts
  • apps/ccusage/src/adapter/hermes/index.ts
  • apps/ccusage/src/adapter/hermes/parser.ts
  • apps/ccusage/src/adapter/hermes/paths.ts
  • apps/ccusage/src/adapter/hermes/pricing.ts
  • apps/ccusage/src/adapter/index.ts
  • apps/ccusage/src/adapter/opencode/loader.ts
  • apps/ccusage/src/adapter/opencode/paths.ts
  • apps/ccusage/src/adapter/sqlite.ts
  • apps/ccusage/src/adapter/types.ts
  • apps/ccusage/src/commands/agent.ts
  • apps/ccusage/src/commands/index.ts
  • apps/ccusage/src/config-loader-tokens.ts
  • apps/ccusage/test/cli-output.test.ts
  • docs/.vitepress/config.ts
  • docs/guide/all-reports.md
  • docs/guide/config-files.md
  • docs/guide/configuration.md
  • docs/guide/environment-variables.md
  • docs/guide/getting-started.md
  • docs/guide/hermes/index.md
  • docs/guide/index.md

Comment thread apps/ccusage/src/adapter/hermes/parser.ts Outdated
Comment thread apps/ccusage/src/adapter/hermes/pricing.ts Outdated
Replace the inline agent label conditional in the schema generator with a small exhaustive switch helper. This keeps future agent-specific labels out of nested ternaries and makes the config schema label rules easier to extend.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review it

@ryoppippi

Copy link
Copy Markdown
Member Author

@cubic-dev-ai review it

@cubic-dev-ai

cubic-dev-ai Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review it

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

@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown

@ryoppippi Sure, I'll kick off a fresh review of PR #1026 now!

✅ Actions performed

Full review triggered.

Keep unknown Hermes billing_provider values instead of replacing them with model inference, so downstream pricing can try provider-qualified candidates. Also log when all Hermes pricing candidates miss before returning zero, making missing pricing visible during reports.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review it

@ryoppippi

Copy link
Copy Markdown
Member Author

@cubic-dev-ai review it

@cubic-dev-ai

cubic-dev-ai Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review it

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

@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown

@ryoppippi Sure! Triggering a fresh review of PR #1026 now.

✅ Actions performed

Full review triggered.

Merge the current main Rust CLI migration into the Hermes Agent PR and replace the old TypeScript Hermes adapter with a Rust implementation.

The Rust adapter discovers state.db from HERMES_HOME or ~/.hermes, reads session rows read-only from SQLite, preserves recorded message counts and costs, and includes reasoning tokens in total-token accounting and output-side cost calculation.

Also wires Hermes into direct reports, all-agent reports, progress labels, config schema, CLI parsing, README, and docs.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review

Updated this PR to merge current main and port Hermes Agent support to the Rust CLI implementation.

This now wires Hermes into direct reports and all-agent reports, reads state.db from HERMES_HOME/default ~/.hermes, preserves recorded costs/messageCount, and includes reasoning tokens in total-token accounting and cost calculation.

Local validation passed:

  • nix develop --command cargo test --manifest-path rust/Cargo.toml --workspace hermes
  • nix develop --command pnpm run format
  • nix develop --command cargo test --manifest-path rust/Cargo.toml --workspace
  • nix develop --command pnpm typecheck
  • nix develop --command pnpm run test

@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown
🧩 Analysis chain

@ryoppippi Sure! Let me examine the Rust implementation before giving you the full review.


🏁 Script executed:

#!/bin/bash
# Find all Rust files related to Hermes
fd -e rs hermes rust/
echo "---FILE_BOUNDARY---"
# Look at Rust adapter structure
tree rust/ --gitignore 2>/dev/null | head -80
echo "---FILE_BOUNDARY---"
# Find Rust source files related to Hermes
rg -rn --type=rust "hermes" rust/ -l

Length of output: 1583


🏁 Script executed:

#!/bin/bash
cat rust/crates/ccusage/src/adapter/hermes.rs
echo "---FILE_BOUNDARY---"
cat rust/crates/ccusage/src/adapter/mod.rs
echo "---FILE_BOUNDARY---"
cat rust/crates/ccusage/src/adapter/all.rs
echo "---FILE_BOUNDARY---"
cat rust/crates/ccusage/src/cli.rs
echo "---FILE_BOUNDARY---"
cat rust/crates/ccusage/src/config.rs
echo "---FILE_BOUNDARY---"
cat rust/crates/ccusage/src/config_schema.rs
echo "---FILE_BOUNDARY---"
cat rust/crates/ccusage/src/main.rs
echo "---FILE_BOUNDARY---"
# Check if there are any test files for Hermes in Rust
rg -rn --type=rust "hermes" rust/ -C 3

Length of output: 207334


@ryoppippi Thanks for the update — the Rust core logic in hermes.rs looks solid (SQLite loading, cost calculation, reasoning token accounting, deduplication, test coverage). However, the rg scan reveals a critical rename artifact across several files where an intermediate identifier "n" was used instead of "hermes". These would break the Rust CLI silently or noisily at runtime.


Review: Rust CLI port — Hermes Agent

🔴 Critical: Stale "n" identifier scattered across 7 files

It looks like "hermes" was partially renamed from an intermediate name "n" and the rename didn't propagate consistently. Here's the full audit:

rust/crates/ccusage/src/cli.rs

Location Current Should be
parse_command dispatch "n" => parse_n_command(...) "hermes" => parse_hermes_command(...)
Function name fn parse_n_command(...) fn parse_hermes_command(...)
Error message "Unknown n command '{command}'" "Unknown hermes command '{command}'"
is_command match | "n" | "hermes"
is_agent_command match "claude" | ... | "n" | "pi" ... | "hermes" | ...
agent_report_supported "amp" | "n" | "pi" "amp" | "hermes" | "pi"
agent_display_name "n" => "Hermes" "hermes" => "Hermes"
Help text lists " n\n" should list " hermes\n"
Test parses_n_session_options parses ["ccusage", "n", "session", ...] should parse ["ccusage", "hermes", "session", ...]
Help test assert!(help.contains("\n n\n")) assert!(help.contains("\n hermes\n"))

The is_command mismatch alone means ccusage hermes session would fail with "Unknown command 'hermes'" at runtime, while ccusage n session would accidentally work.


rust/crates/ccusage/src/config.rs

// Current (broken):
fn is_agent_command(command: &str) -> bool {
    matches!(command, "claude" | "codex" | "opencode" | "amp" | "n" | "pi")
}

// Should be:
fn is_agent_command(command: &str) -> bool {
    matches!(command, "claude" | "codex" | "opencode" | "amp" | "hermes" | "pi")
}

rust/crates/ccusage/src/config_schema.rs

// Current (broken) — config key "hermes" block silently ignored:
pub(crate) n: Option<HermesConfig>,

// Should be:
pub(crate) hermes: Option<HermesConfig>,

The schema test asserting "ccusage-config" properties also checks for "n" instead of "hermes", and the config example uses "n" as the key.


rust/crates/ccusage/src/main.rs

// Current (broken):
Some(Command::Hermes(args)) => adapter::n::run(args),

// Should be:
Some(Command::Hermes(args)) => adapter::hermes::run(args),

rust/crates/ccusage/src/adapter/all.rs

// Current (broken) — wrong module alias + wrong agent label string:
use crate::{
    adapter::{amp, codex, n, opencode, pi},
    ...
};
// ...
append_agent_rows(&mut rows, &mut detected_agents, "n", load_n_rows(...));
// ...
fn load_n_rows(...) -> Result<AgentRows> {
    let mut entries = n::load_entries(shared, pricing)?;
    let summaries = n::summarize_entries(&entries, kind)?;
    Ok(AgentRows { rows: summary_rows("n", summaries), ... })
}

This means the all-sources report would label Hermes data as "n" (no matching arm in agent_label), showing raw "n" in the terminal table.

Also note agent_label has no "hermes" arm — it would fall through to _ => agent, displaying the raw string. Add:

"hermes" => "Hermes Agent",

rust/crates/ccusage/src/adapter/hermes.rs (internal inconsistencies)

// Default home directory:
vec![home.join(".n")]          // should be: home.join(".hermes")

// Fallback provider:
} else { "n" }                 // should be: "hermes"

// cost function name:
fn calculate_n_cost(...)       // should be: calculate_hermes_cost

// message ID prefix:
id: Some(format!("n:{}", ...)) // should be: format!("hermes:{}", ...)

// project arc:
project: Arc::from("n")        // should be: Arc::from("hermes")

// model_candidates guard:
if entry.provider != "n" {...} // should be: entry.provider != "hermes"

// path discovery:
fn n_state_db_paths()          // should be: hermes_state_db_paths()

// test temp dir:
format!("ccusage-n-{name}-...")  // cosmetic, should be: "ccusage-hermes-..."

// test name:
fn loads_billable_n_sessions_from_state_db()  // cosmetic

// test assertion:
id: Some("n:session-1".to_string())  // should be: "hermes:session-1"
project: Arc::from("n")              // should be: Arc::from("hermes")

The .n default path is the most impactful here: with HERMES_HOME unset and ~/.hermes being the correct location, the binary would look in ~/.n and find nothing.


🟡 Previously raised issues (TypeScript side — still applicable)

The earlier findings from the prior reviews remain open:

  1. createHermesStateDb duplicated in 3 places — schema drift risk
  2. PROVIDER_ALIASES too narrow — mistral, cohere, deepseek, etc. fall back to "hermes" and miss the provider/model pricing candidate
  3. Silent $0 fallback in calculateHermesCost when no pricing is found
  4. o\d regex should be o\d+ for future multi-digit o-series model names

✅ What looks good in the Rust port

  • SQLite loading with open_with_flags(...with_read_only()) — correct isolation
  • read_u64/read_f64/read_non_negative_f64 helpers are well-structured
  • Session deduplication via HashSet is consistent with other adapters
  • Reasoning tokens wired into extra_total_tokens / cost candidates correctly
  • messageCount propagated into LoadedEntry
  • Two real unit tests (loads_billable_hermes_sessions_from_state_db, report_includes_message_count_and_reasoning_total) cover the happy path
✅ Actions performed

Full review triggered.

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/crates/ccusage/src/adapter/all.rs (1)

355-362: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate messageCount through aggregated all-report rows.

summary_metadata() now captures messageCount, but daily/monthly/weekly all reports discard row.metadata in AllAccumulator::into_row(), so row_json() only emits { "agents": ... } for the final rows. In practice the new field only survives session mode, which skips aggregation.

Also applies to: 461-476, 500-506

🤖 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/src/adapter/all.rs` around lines 355 - 362,
AllAccumulator::into_row is dropping per-row metadata (so summary_metadata's
messageCount is lost during aggregation); modify AllAccumulator::into_row (and
any aggregation path that builds final rows, e.g., where row_json() is produced)
to merge/preserve row.metadata into the resulting Row/Value instead of
discarding it—specifically propagate the serde_json::Map from each row.metadata
into the accumulator's metadata field (merge counts/overwrite consistently) and
ensure row_json() includes that metadata object (so fields like messageCount
from summary_metadata() survive daily/weekly/monthly aggregation). Also apply
the same metadata-preservation change to the other aggregation branches
referenced (the analogous code paths around the other AllAccumulator
conversions) so all-report rows retain messageCount.
🤖 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/src/adapter/all.rs`:
- Around line 79-84: The all-agent output uses "hermes" as the agent key (see
append_agent_rows and load_hermes_rows) but agent_label() lacks a branch for
Hermes, so the UI shows lowercase "hermes"; update agent_label() to handle the
"hermes" key and return the proper branded display label (e.g., "Hermes" or the
project's official name) so rows inserted by append_agent_rows(..., "hermes",
load_hermes_rows(...)) render with the correct title; apply the same addition
wherever agent_label() maps agent keys (also affects the other occurrence around
lines 126-131).

In `@rust/crates/ccusage/src/adapter/hermes.rs`:
- Around line 388-392: The model_candidates function currently places the bare
model name first which can match the wrong provider price; update
model_candidates (and refer to HermesEntry.model and HermesEntry.provider) to
yield the provider-qualified candidate ("{provider}/{model}") before the bare
model when entry.provider != "hermes" so that pricing lookups try
"provider/model" first and fall back to "model" second; ensure the default for
hermes still only returns the bare model.

In `@rust/crates/ccusage/src/output.rs`:
- Around line 85-92: The table total row is recomputing totalTokens from
input/output/cache fields and thus undercounts extra_total_tokens; update the
construction of the non-compact totals JSON (the `value` object) to use the
already-aggregated `extra_total` and the precomputed `totalTokens` expression
instead of recalculating only input+output+cache fields — specifically replace
the manual sum in the `"totalTokens"` field with the aggregated sum (input +
output + cache_create + cache_read + extra_total) or the existing `totalTokens`
variable so the `"totalTokens"` and `"totalCost"` entries in `value` reflect
extra_total_tokens correctly.

---

Outside diff comments:
In `@rust/crates/ccusage/src/adapter/all.rs`:
- Around line 355-362: AllAccumulator::into_row is dropping per-row metadata (so
summary_metadata's messageCount is lost during aggregation); modify
AllAccumulator::into_row (and any aggregation path that builds final rows, e.g.,
where row_json() is produced) to merge/preserve row.metadata into the resulting
Row/Value instead of discarding it—specifically propagate the serde_json::Map
from each row.metadata into the accumulator's metadata field (merge
counts/overwrite consistently) and ensure row_json() includes that metadata
object (so fields like messageCount from summary_metadata() survive
daily/weekly/monthly aggregation). Also apply the same metadata-preservation
change to the other aggregation branches referenced (the analogous code paths
around the other AllAccumulator conversions) so all-report rows retain
messageCount.
🪄 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: 6d1e1e51-a75d-48d8-ad66-ac3bf17d806a

📥 Commits

Reviewing files that changed from the base of the PR and between bef1c8b and d164e46.

📒 Files selected for processing (22)
  • apps/ccusage/README.md
  • apps/ccusage/config-schema.json
  • docs/.vitepress/config.ts
  • docs/guide/environment-variables.md
  • docs/guide/getting-started.md
  • docs/guide/index.md
  • rust/crates/ccusage/src/adapter/all.rs
  • rust/crates/ccusage/src/adapter/amp.rs
  • rust/crates/ccusage/src/adapter/hermes.rs
  • rust/crates/ccusage/src/adapter/mod.rs
  • rust/crates/ccusage/src/adapter/opencode/mod.rs
  • rust/crates/ccusage/src/adapter/opencode/parser.rs
  • rust/crates/ccusage/src/adapter/pi.rs
  • rust/crates/ccusage/src/claude_loader.rs
  • rust/crates/ccusage/src/cli.rs
  • rust/crates/ccusage/src/config.rs
  • rust/crates/ccusage/src/config_schema.rs
  • rust/crates/ccusage/src/main.rs
  • rust/crates/ccusage/src/output.rs
  • rust/crates/ccusage/src/progress.rs
  • rust/crates/ccusage/src/summary.rs
  • rust/crates/ccusage/src/types.rs
✅ Files skipped from review due to trivial changes (5)
  • docs/.vitepress/config.ts
  • docs/guide/environment-variables.md
  • docs/guide/getting-started.md
  • docs/guide/index.md
  • apps/ccusage/README.md

Comment thread rust/crates/ccusage/src/adapter/all.rs
Comment thread rust/crates/ccusage/src/adapter/hermes.rs
Comment thread rust/crates/ccusage/src/output.rs Outdated

@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.

♻️ Duplicate comments (2)
rust/crates/ccusage/src/output.rs (1)

85-92: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use aggregated totalTokens for the non-compact total row.

Lines [85]-[92] now aggregate extra tokens, but Line [293] still prints input + output + cache_create + cache_read, which undercounts totals when extra_total_tokens is non-zero.

Suggested fix
     let total_cost = totals
         .get("totalCost")
         .and_then(Value::as_f64)
         .unwrap_or_default();
+    let total_tokens = totals
+        .get("totalTokens")
+        .and_then(Value::as_u64)
+        .unwrap_or(input + output + cache_create + cache_read);
@@
             color(
                 shared,
-                format_number(input + output + cache_create + cache_read),
+                format_number(total_tokens),
                 Color::Yellow,
             ),
🤖 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/src/output.rs` around lines 85 - 92, The non-compact
total row currently computes "totalTokens" using only input + output +
cache_create + cache_read and thus omits extra_total_tokens; update the code
that builds the total row (the json! construction used for the non-compact
summary) to include the aggregated extra_total (the variable extra_total or
recompute via rows.iter().map(|r| r.extra_total_tokens).sum::<u64>()) so
totalTokens becomes input + output + cache_create + cache_read + extra_total and
the reported total matches the compact aggregation; update the value
construction that sets "totalTokens" accordingly.
rust/crates/ccusage/src/adapter/all.rs (1)

79-84: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add Hermes display-name mapping in agent_label().

Lines [79]-[84] and [126]-[131] add "hermes" rows, but Line [795] has no "hermes" arm, so table/title output shows lowercase hermes instead of branded Hermes.

Suggested fix
 fn agent_label(agent: &str) -> &str {
     match agent {
         "all" => "All",
         "claude" => "Claude",
         "codex" => "Codex",
+        "hermes" => "Hermes",
         "opencode" => "OpenCode",
         "amp" => "Amp",
         "pi" => "pi-agent",
         _ => agent,
     }
 }

Also applies to: 126-131

🤖 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/src/adapter/all.rs` around lines 79 - 84, The table shows
lowercase "hermes" because agent_label() lacks a mapping for the "hermes" key;
add a branch/arm in the agent_label function to map the identifier "hermes" to
the branded display name "Hermes" (similar to other agents), so rows appended by
append_agent_rows(..., "hermes", load_hermes_rows(...)) and the other hermes
usage at the second call render the correct title; locate and update agent_label
to include the "hermes" arm and return the proper display string.
🤖 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.

Duplicate comments:
In `@rust/crates/ccusage/src/adapter/all.rs`:
- Around line 79-84: The table shows lowercase "hermes" because agent_label()
lacks a mapping for the "hermes" key; add a branch/arm in the agent_label
function to map the identifier "hermes" to the branded display name "Hermes"
(similar to other agents), so rows appended by append_agent_rows(..., "hermes",
load_hermes_rows(...)) and the other hermes usage at the second call render the
correct title; locate and update agent_label to include the "hermes" arm and
return the proper display string.

In `@rust/crates/ccusage/src/output.rs`:
- Around line 85-92: The non-compact total row currently computes "totalTokens"
using only input + output + cache_create + cache_read and thus omits
extra_total_tokens; update the code that builds the total row (the json!
construction used for the non-compact summary) to include the aggregated
extra_total (the variable extra_total or recompute via rows.iter().map(|r|
r.extra_total_tokens).sum::<u64>()) so totalTokens becomes input + output +
cache_create + cache_read + extra_total and the reported total matches the
compact aggregation; update the value construction that sets "totalTokens"
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bc5a3d8f-a3d2-4cfd-aa24-0040655b4fbe

📥 Commits

Reviewing files that changed from the base of the PR and between 7971969 and d164e46.

📒 Files selected for processing (26)
  • apps/ccusage/README.md
  • apps/ccusage/config-schema.json
  • docs/.vitepress/config.ts
  • docs/guide/all-reports.md
  • docs/guide/config-files.md
  • docs/guide/configuration.md
  • docs/guide/environment-variables.md
  • docs/guide/getting-started.md
  • docs/guide/hermes/index.md
  • docs/guide/index.md
  • rust/crates/ccusage/src/adapter/all.rs
  • rust/crates/ccusage/src/adapter/amp.rs
  • rust/crates/ccusage/src/adapter/hermes.rs
  • rust/crates/ccusage/src/adapter/mod.rs
  • rust/crates/ccusage/src/adapter/opencode/mod.rs
  • rust/crates/ccusage/src/adapter/opencode/parser.rs
  • rust/crates/ccusage/src/adapter/pi.rs
  • rust/crates/ccusage/src/claude_loader.rs
  • rust/crates/ccusage/src/cli.rs
  • rust/crates/ccusage/src/config.rs
  • rust/crates/ccusage/src/config_schema.rs
  • rust/crates/ccusage/src/main.rs
  • rust/crates/ccusage/src/output.rs
  • rust/crates/ccusage/src/progress.rs
  • rust/crates/ccusage/src/summary.rs
  • rust/crates/ccusage/src/types.rs

…age-adapter

# Conflicts:
#	apps/ccusage/README.md
#	apps/ccusage/config-schema.json
#	docs/.vitepress/config.ts
#	docs/guide/all-reports.md
#	docs/guide/config-files.md
#	docs/guide/configuration.md
#	docs/guide/environment-variables.md
#	docs/guide/getting-started.md
#	docs/guide/index.md
#	rust/crates/ccusage/src/adapter/all.rs
#	rust/crates/ccusage/src/adapter/mod.rs
#	rust/crates/ccusage/src/cli.rs
#	rust/crates/ccusage/src/config.rs
#	rust/crates/ccusage/src/config_schema.rs
#	rust/crates/ccusage/src/output.rs
#	rust/crates/ccusage/src/types.rs
@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 6b46b1ddbfb8
Base SHA: 1727a60f7df5

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

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 1727a60f7df5 580.7ms 561.8ms 33.2ms 3
PR pkg.pr.new 6b46b1ddbfb8 751.6ms 754.0ms 33.8ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 1727a60f7df5; PR package: 6b46b1ddbfb8. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 701.7ms 696.7ms 1.01x 1.43 GiB/s 1.45 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 895.7ms 842.8ms 1.06x 1.12 GiB/s 1.19 GiB/s

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, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 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 671.3ms 1.50 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 673.4ms 1.50 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 715.6ms 1.41 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 829.4ms 1.21 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 802.5ms 1.25 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 816.3ms 1.23 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.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 31.6ms 31.6ms 1.00x 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 31.7ms 31.4ms 1.01x 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 31.4ms 31.7ms 0.99x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 30.8ms 30.4ms 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, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 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.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 697.9ms 674.5ms 1.03x 1.44 GiB/s 1.49 GiB/s
codex --offline --json 1.01 GiB 855.0ms 843.2ms 1.01x 1.18 GiB/s 1.19 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 8.96 KiB 9.01 KiB +0.05 KiB 0.99x
installed native package binary 3160.24 KiB 3160.24 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2827.68 KiB - -

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

ryoppippi added 2 commits May 19, 2026 16:37
…age-adapter

# Conflicts:
#	apps/ccusage/README.md
#	apps/ccusage/config-schema.json
#	docs/guide/all-reports.md
#	docs/guide/config-files.md
#	docs/guide/configuration.md
#	docs/guide/environment-variables.md
#	docs/guide/index.md
#	rust/crates/ccusage/src/adapter/all.rs
#	rust/crates/ccusage/src/adapter/mod.rs
#	rust/crates/ccusage/src/cli.rs
#	rust/crates/ccusage/src/config.rs
#	rust/crates/ccusage/src/config_schema.rs
Keep the generated config schema formatting in sync with the pre-commit generator after merging origin/main into the Hermes adapter branch.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review

Pushed the Hermes branch again after #1032 Kilo was merged into main.

Changes in this push:

  • merged the latest origin/main into the Hermes adapter branch
  • kept the Hermes Rust adapter wired into CLI/all-report/config schema/progress code alongside Kilo
  • kept Hermes documented as an experimental data source
  • added Kilo compatibility for the Hermes branch's message_count field on LoadedEntry
  • regenerated and formatted the config schema

Hermes-specific fix still included:

  • bundled fallback pricing covers gpt-5.5 and grok-4.3, so Hermes rows for those models no longer collapse to $0.00 just because LiteLLM returns zero loaded pricing

Local validation:

  • nix develop --command cargo test --manifest-path rust/Cargo.toml -p ccusage hermes
  • nix develop --command pnpm run format
  • nix develop --command cargo test --manifest-path rust/Cargo.toml --workspace
  • nix develop --command pnpm typecheck
  • nix develop --command pnpm run test

Pre-push validation also passed clippy/oxfmt/eslint hooks.

@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check calculate_hermes_cost for any debug/warn logging
rg -n -A 15 "fn calculate_hermes_cost" rust/crates/ccusage/src/adapter/hermes.rs
echo "---FILE_BOUNDARY---"
# Check agent_label in all.rs
rg -n -A 25 'fn agent_label' rust/crates/ccusage/src/adapter/all.rs
echo "---FILE_BOUNDARY---"
# Check Kilo adapter for message_count compatibility
rg -n "message_count" rust/crates/ccusage/src/adapter/kilo.rs 2>/dev/null || echo "kilo.rs not found or no message_count"
echo "---FILE_BOUNDARY---"
# Check all "n" artifacts remain gone
rg -n '"n"' rust/crates/ccusage/src/ --type=rust | grep -v "//\|test\|\"n/a\"\|\"n \"\|n_count\|file_n\|spawn\|stdin\|return\|\"\\\\n\"" | head -30
echo "---FILE_BOUNDARY---"
# Check progress.rs for Hermes and Kilo
rg -n "Hermes\|Kilo\|agent_label" rust/crates/ccusage/src/progress.rs
echo "---FILE_BOUNDARY---"
# Check config_schema.rs for hermes field
rg -n "hermes\|HermesConfig" rust/crates/ccusage/src/config_schema.rs | head -20
echo "---FILE_BOUNDARY---"
# Check load_hermes_rows in all.rs
rg -n -A 20 "fn load_hermes_rows" rust/crates/ccusage/src/adapter/all.rs
echo "---FILE_BOUNDARY---"
# Check for Kilo in all.rs to confirm both are wired
rg -n "kilo\|hermes" rust/crates/ccusage/src/adapter/all.rs | head -30
echo "---FILE_BOUNDARY---"
# Check cli.rs for both hermes and kilo commands
rg -n "hermes\|kilo\|Hermes\|Kilo" rust/crates/ccusage/src/cli.rs | head -30

Length of output: 2409


🏁 Script executed:

#!/bin/bash
# Get the tail of calculate_hermes_cost to see the return 0.0 path and any logging
sed -n '375,410p' rust/crates/ccusage/src/adapter/hermes.rs
echo "---FILE_BOUNDARY---"
# Check progress.rs directly
cat rust/crates/ccusage/src/progress.rs
echo "---FILE_BOUNDARY---"
# Check config_schema.rs for hermes entries
grep -n "hermes\|HermesConfig\|Hermes" rust/crates/ccusage/src/config_schema.rs | head -30
echo "---FILE_BOUNDARY---"
# Check cli.rs for hermes command
grep -n "hermes\|Hermes" rust/crates/ccusage/src/cli.rs | head -30
echo "---FILE_BOUNDARY---"
# Check all.rs for both hermes and kilo imports
grep -n "use crate\|hermes\|kilo" rust/crates/ccusage/src/adapter/all.rs | head -20

Length of output: 13877


@ryoppippi The Kilo merge looks clean — all wiring is intact and consistent. Here's the updated status:


✅ Everything looks good

Check Status
All "n" → "hermes" artifacts ✅ Clean
cli.rs — dispatch, function name, help text, is_command, is_agent_command, agent_display_name ✅ All "hermes"
config_schema.rs — hermes: Option<HermesConfig> field ✅ Correct
progress.rs — UsageLoadAgent::Hermes + agent_label arm ✅ Present, alongside Kilo
adapter/all.rs — hermes imported, wired in Session + Daily, agent_label arm ✅ Correct
adapter/kilo.rs — message_count: None compatibility ✅ Added
Embedded fallback pricing for gpt-5.5 / grok-4.3 ✅ From previous push

🟡 One persistent minor nit

calculate_hermes_cost returns 0.0 silently (lines 393–396):

    for candidate in model_candidates(entry) {
        // ...
        if cost >= 0.0 && cost.is_finite() && cost > 0.0 { return cost; }
    }
    0.0  // ← still no diagnostic

The embedded pricing now covers the main frontier models, so this is low-impact in practice. If you'd like to make unknown future models diagnosable without users needing to know about LOG_LEVEL, a single call consistent with the rest of the file would do it:

    crate::debug_log(|| format!("hermes: no pricing found for model '{}' (provider: '{}'); cost=0", entry.model, entry.provider));
    0.0

Otherwise the PR is in great shape and ready to go. Up to you whether to add the diagnostic before merging.

Try provider-qualified Hermes pricing keys before bare model names so routed providers cannot be accidentally priced against a generic model entry. Add a regression for the candidate ordering requested in review.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review

Pushed a follow-up for the remaining Hermes review thread.

Changes in this push:

  • now tries provider-qualified Hermes pricing keys before the bare model
  • added as a regression

Validation:

  • nix develop --command cargo test --manifest-path rust/Cargo.toml -p ccusage hermes
  • pre-push clippy/oxfmt hooks passed

@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review

Correcting the previous review request formatting: pushed a follow-up for the remaining Hermes review thread.

Changes in this push:

  • model_candidates() now tries provider-qualified Hermes pricing keys before the bare model
  • added tries_provider_qualified_model_candidate_first as a regression

Validation:

  • nix develop --command cargo test --manifest-path rust/Cargo.toml -p ccusage hermes
  • pre-push clippy/oxfmt hooks passed

@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check calculate_hermes_cost and model_candidates
rg -n -A 40 "fn calculate_hermes_cost\|fn model_candidates" rust/crates/ccusage/src/adapter/hermes.rs
echo "---FILE_BOUNDARY---"
# Check for regression test
rg -n -A 30 "provider_qualified\|regression\|provider.*model.*candidate\|candidate.*provider" rust/crates/ccusage/src/adapter/hermes.rs | head -80
echo "---FILE_BOUNDARY---"
# Check debug_log calls in hermes.rs
rg -n "debug_log\|warn\|eprintln" rust/crates/ccusage/src/adapter/hermes.rs
echo "---FILE_BOUNDARY---"
# See the full bottom of hermes.rs (where tests and cost functions usually are)
wc -l rust/crates/ccusage/src/adapter/hermes.rs
tail -120 rust/crates/ccusage/src/adapter/hermes.rs

Length of output: 4857


@ryoppippi The fix looks correct and the regression test is solid. Here's the final status:


✅ All issues resolved

Issue Status
model_candidates puts provider/model first ✅ Fixed — tries_provider_qualified_model_candidate_first confirms ["openai/gpt-5.5", "gpt-5.5"] ordering
Unknown provider falls back to bare model ✅ Correct — embedded pricing still covers gpt-5.5/grok-4.3 for provider="hermes"
All "n" → "hermes" rename artifacts ✅ Fixed in prior pushes
agent_label missing "hermes" arm ✅ Fixed in prior push
Embedded fallback pricing coverage ✅ Fixed in prior push

🟡 One persistent minor nit (acknowledged, not blocking)

calculate_hermes_cost still returns 0.0 silently for truly unknown future models — no debug_log call before the zero return. You've consistently chosen not to add this diagnostic, which is totally fine given the embedded pricing now covers the main frontier models. Just flagging it one last time in case you want to add it before merge:

crate::debug_log(|| format!("hermes: no pricing found for '{}'/'{}'", entry.provider, entry.model));
0.0

Otherwise, the PR is in great shape — the implementation is clean, all renames are correct, tests cover happy paths and regression cases, and Kilo/main parity is maintained. 🐇

✅ Actions performed

Full review triggered.

@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the updated model_candidates function
rg -n -A 20 "fn model_candidates" rust/crates/ccusage/src/adapter/hermes.rs
echo "---FILE_BOUNDARY---"
# Check calculate_hermes_cost for any debug logging
rg -n -A 20 "fn calculate_hermes_cost" rust/crates/ccusage/src/adapter/hermes.rs
echo "---FILE_BOUNDARY---"
# Check the new regression test
rg -n -A 30 "tries_provider_qualified_model_candidate_first" rust/crates/ccusage/src/adapter/hermes.rs
echo "---FILE_BOUNDARY---"
# Check all tests in hermes.rs to understand coverage
rg -n "#\[test\]\|fn test\|fn loads_\|fn report_\|fn calculates_\|fn embedded_\|fn tries_" rust/crates/ccusage/src/adapter/hermes.rs

Length of output: 2196


@ryoppippi The model_candidates() fix looks correct and the regression test is well-targeted. Here's the verification:


✅ model_candidates() ordering — FIXED

fn model_candidates(entry: &HermesEntry) -> Vec<String> {
    let mut candidates = Vec::new();
    if entry.provider != "hermes" {
        candidates.push(format!("{}/{}", entry.provider, entry.model));  // ← tried first
    }
    candidates.push(entry.model.clone());  // ← bare model as fallback
    // dedup via HashSet
}

Provider-qualified form is now the first candidate for known providers, which means openai/gpt-5.5 gets looked up before gpt-5.5. The HashSet deduplication correctly handles the edge case where provider and model prefix coincide.


✅ tries_provider_qualified_model_candidate_first — correct regression test

assert_eq!(
    model_candidates(&entry),
    vec!["openai/gpt-5.5".to_string(), "gpt-5.5".to_string()]
);

Exactly the right assertion — verifies both ordering and completeness.


🟡 One persistent minor nit (same as prior reviews)

calculate_hermes_cost returns 0.0 silently at line 395. With embedded pricing now covering the main frontier models and provider-qualified lookup now tried first, the practical impact is low. But a crate::debug_log(...) before the 0.0 would make future unknown-model cases diagnosable via LOG_LEVEL:

    crate::debug_log(|| format!("hermes: no pricing found for model '{}' (provider: '{}'); cost=0", entry.model, entry.provider));
    0.0

Entirely up to you — the PR is in great shape otherwise.

✅ Actions performed

Full review triggered.

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 6a2c6e02d790
Base SHA: 35cb05e38459

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

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 35cb05e38459 741.4ms 527.1ms 34.7ms 3
PR pkg.pr.new 6a2c6e02d790 710.1ms 518.0ms 35.6ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 35cb05e38459; PR package: 6a2c6e02d790. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 701.7ms 689.8ms 1.02x 1.43 GiB/s 1.46 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 876.2ms 896.1ms 0.98x 1.15 GiB/s 1.12 GiB/s

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, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 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 691.9ms 1.46 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 657.4ms 1.53 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 745.6ms 1.35 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 905.8ms 1.11 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 833.4ms 1.21 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 814.1ms 1.24 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.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 32.7ms 32.4ms 1.01x 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 32.7ms 32.8ms 1.00x 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 32.0ms 31.5ms 1.02x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 31.0ms 30.9ms 1.00x 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, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 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.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 686.3ms 683.8ms 1.00x 1.47 GiB/s 1.47 GiB/s
codex --offline --json 1.01 GiB 850.4ms 872.0ms 0.98x 1.18 GiB/s 1.15 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 9.00 KiB 9.05 KiB +0.05 KiB 0.99x
installed native package binary 3160.24 KiB 3160.24 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2827.68 KiB - -

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: 6a2c6e02d790
Base SHA: 35cb05e38459

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

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 35cb05e38459 480.7ms 585.9ms 34.8ms 3
PR pkg.pr.new 6a2c6e02d790 533.3ms 486.2ms 34.4ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 35cb05e38459; PR package: 6a2c6e02d790. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 698.7ms 707.4ms 0.99x 1.44 GiB/s 1.42 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 877.1ms 884.2ms 0.99x 1.15 GiB/s 1.14 GiB/s

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, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 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 685.4ms 1.47 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 654.5ms 1.54 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 735.3ms 1.37 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 885.6ms 1.14 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 850.3ms 1.18 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 835.2ms 1.21 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 rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 32.4ms 4.8ms 6.80x 0.05 MiB/s 0.32 MiB/s
claude session --offline --json 0.00 MiB 32.4ms 4.9ms 6.64x 0.05 MiB/s 0.32 MiB/s
codex daily --offline --json 0.00 MiB 32.2ms 4.6ms 7.07x 0.03 MiB/s 0.19 MiB/s
codex session --offline --json 0.00 MiB 32.0ms 4.5ms 7.08x 0.03 MiB/s 0.19 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, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 696.0ms 738.1ms 0.94x 1.45 GiB/s 1.36 GiB/s
codex --offline --json 1.01 GiB 871.7ms 827.3ms 1.05x 1.15 GiB/s 1.22 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 9.00 KiB 9.05 KiB +0.05 KiB 0.99x
installed native package binary 3160.24 KiB 3160.24 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2827.68 KiB - -

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

🧹 Nitpick comments (1)
docs/guide/getting-started.md (1)

176-194: ⚡ Quick win

Add comma-separated HERMES_HOME example for consistency.

The Hermes Agent documentation at docs/guide/hermes/index.md:59 states that HERMES_HOME supports "comma-separated roots," but this section omits HERMES_HOME from the comma-separated examples while including CODEX_HOME, OPENCODE_DATA_DIR, AMP_DATA_DIR, PI_AGENT_DIR, and KILO_DATA_DIR.

📝 Proposed addition

Add this line after line 183:

 export AMP_DATA_DIR="/path/to/amp"
+export HERMES_HOME="/path/to/hermes"
 export PI_AGENT_DIR="/path/to/pi/sessions"

And this line after line 193:

 export AMP_DATA_DIR="/path/to/amp,/archive/amp"
+export HERMES_HOME="/path/to/hermes,/archive/hermes"
 export PI_AGENT_DIR="/path/to/pi/sessions,/archive/pi/sessions"
🤖 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 `@docs/guide/getting-started.md` around lines 176 - 194, The docs omit a
comma-separated example for HERMES_HOME; update the getting-started snippet to
include HERMES_HOME alongside the other environment variables and add a second
example showing HERMES_HOME supporting comma-separated roots (consistent with
HERMES_HOME behavior documented in docs/guide/hermes/index.md:59); edit the
block that lists single-path exports to add HERMES_HOME and the block showing
comma-separated directories to add a HERMES_HOME entry, ensuring the variable
name is exactly HERMES_HOME to match existing references.
🤖 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/src/adapter/hermes.rs`:
- Around line 375-396: In calculate_hermes_cost, add a diagnostic log right
before the silent fallback return 0.0 so we record why no pricing candidate
resolved; capture identifying info from the HermesEntry (e.g.,
entry.model/name/id, entry.usage.output_tokens and reasoning_tokens, and
entry.cost_usd), the list of model_candidates tried and the pricing reference,
and emit a debug/warn via your logger indicating "no pricing candidate matched,
falling back to zero" along with those details; keep the loop logic
(model_candidates + calculate_cost_for_usage) unchanged and only add this single
diagnostic before the final 0.0 return.

---

Nitpick comments:
In `@docs/guide/getting-started.md`:
- Around line 176-194: The docs omit a comma-separated example for HERMES_HOME;
update the getting-started snippet to include HERMES_HOME alongside the other
environment variables and add a second example showing HERMES_HOME supporting
comma-separated roots (consistent with HERMES_HOME behavior documented in
docs/guide/hermes/index.md:59); edit the block that lists single-path exports to
add HERMES_HOME and the block showing comma-separated directories to add a
HERMES_HOME entry, ensuring the variable name is exactly HERMES_HOME to match
existing references.
🪄 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: f0a04c6c-7283-4e4f-b691-bc89fb5e3e73

📥 Commits

Reviewing files that changed from the base of the PR and between 35cb05e and 338d94c.

📒 Files selected for processing (26)
  • apps/ccusage/README.md
  • apps/ccusage/config-schema.json
  • docs/.vitepress/config.ts
  • docs/guide/getting-started.md
  • docs/guide/hermes/index.md
  • docs/guide/index.md
  • docs/index.md
  • rust/crates/ccusage/src/adapter/all.rs
  • rust/crates/ccusage/src/adapter/amp.rs
  • rust/crates/ccusage/src/adapter/copilot.rs
  • rust/crates/ccusage/src/adapter/gemini.rs
  • rust/crates/ccusage/src/adapter/hermes.rs
  • rust/crates/ccusage/src/adapter/kilo.rs
  • rust/crates/ccusage/src/adapter/mod.rs
  • rust/crates/ccusage/src/adapter/opencode/mod.rs
  • rust/crates/ccusage/src/adapter/opencode/parser.rs
  • rust/crates/ccusage/src/adapter/pi.rs
  • rust/crates/ccusage/src/claude_loader.rs
  • rust/crates/ccusage/src/cli.rs
  • rust/crates/ccusage/src/config.rs
  • rust/crates/ccusage/src/config_schema.rs
  • rust/crates/ccusage/src/main.rs
  • rust/crates/ccusage/src/pricing.rs
  • rust/crates/ccusage/src/progress.rs
  • rust/crates/ccusage/src/summary.rs
  • rust/crates/ccusage/src/types.rs

Comment on lines +375 to +396
fn calculate_hermes_cost(entry: &HermesEntry, pricing: &PricingMap) -> f64 {
if let Some(cost) = entry.cost_usd {
return cost;
}
let usage = TokenUsageRaw {
output_tokens: entry.usage.output_tokens + entry.reasoning_tokens,
..entry.usage
};
for candidate in model_candidates(entry) {
let cost = calculate_cost_for_usage(
Some(&candidate),
usage,
None,
CostMode::Calculate,
Some(pricing),
);
if cost >= 0.0 && cost.is_finite() && cost > 0.0 {
return cost;
}
}
0.0
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a debug diagnostic before zero-cost fallback in Hermes pricing.

When no pricing candidate resolves, this returns 0.0 silently. That makes future model/pricing drift hard to detect in production diagnostics.

Suggested patch
 fn calculate_hermes_cost(entry: &HermesEntry, pricing: &PricingMap) -> f64 {
     if let Some(cost) = entry.cost_usd {
         return cost;
@@
     for candidate in model_candidates(entry) {
         let cost = calculate_cost_for_usage(
             Some(&candidate),
             usage,
             None,
             CostMode::Calculate,
             Some(pricing),
         );
         if cost >= 0.0 && cost.is_finite() && cost > 0.0 {
             return cost;
         }
     }
+    crate::debug_log(
+        &SharedArgs::default(),
+        format!(
+            "Hermes pricing not found for provider='{}' model='{}'; returning 0.0",
+            entry.provider, entry.model
+        ),
+    );
     0.0
 }
🤖 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/src/adapter/hermes.rs` around lines 375 - 396, In
calculate_hermes_cost, add a diagnostic log right before the silent fallback
return 0.0 so we record why no pricing candidate resolved; capture identifying
info from the HermesEntry (e.g., entry.model/name/id, entry.usage.output_tokens
and reasoning_tokens, and entry.cost_usd), the list of model_candidates tried
and the pricing reference, and emit a debug/warn via your logger indicating "no
pricing candidate matched, falling back to zero" along with those details; keep
the loop logic (model_candidates + calculate_cost_for_usage) unchanged and only
add this single diagnostic before the final 0.0 return.

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 338d94cf1345
Base SHA: 35cb05e38459

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

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 35cb05e38459 737.8ms 468.6ms 32.1ms 3
PR pkg.pr.new 338d94cf1345 849.1ms 578.1ms 32.4ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 35cb05e38459; PR package: 338d94cf1345. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 676.2ms 681.4ms 0.99x 1.49 GiB/s 1.48 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 817.7ms 830.4ms 0.98x 1.23 GiB/s 1.21 GiB/s

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, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 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 672.6ms 1.50 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 644.4ms 1.56 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 687.7ms 1.46 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 817.7ms 1.23 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 786.5ms 1.28 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 787.5ms 1.28 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 rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 29.9ms 4.4ms 6.76x 0.05 MiB/s 0.35 MiB/s
claude session --offline --json 0.00 MiB 29.7ms 4.4ms 6.72x 0.05 MiB/s 0.35 MiB/s
codex daily --offline --json 0.00 MiB 29.1ms 4.2ms 6.95x 0.03 MiB/s 0.20 MiB/s
codex session --offline --json 0.00 MiB 29.3ms 4.1ms 7.11x 0.03 MiB/s 0.21 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, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 672.7ms 692.1ms 0.97x 1.50 GiB/s 1.45 GiB/s
codex --offline --json 1.01 GiB 811.2ms 799.5ms 1.01x 1.24 GiB/s 1.26 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 9.00 KiB 9.06 KiB +0.05 KiB 0.99x
installed native package binary 3160.24 KiB 3160.24 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2827.68 KiB - -

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: 338d94cf1345
Base SHA: 35cb05e38459

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

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new 35cb05e38459 961.3ms 625.2ms 34.4ms 3
PR pkg.pr.new 338d94cf1345 951.5ms 579.4ms 34.9ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: 35cb05e38459; PR package: 338d94cf1345. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.

Command Input Base median PR median PR vs base Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 685.8ms 676.5ms 1.01x 1.47 GiB/s 1.49 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 846.6ms 861.3ms 0.98x 1.19 GiB/s 1.17 GiB/s

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, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 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 671.9ms 1.50 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 646.2ms 1.56 GiB/s 1
claude --offline --json Workspace release binary 1.01 GiB 704.4ms 1.43 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 862.5ms 1.17 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 824.7ms 1.22 GiB/s 1
codex --offline --json Workspace release binary 1.01 GiB 827.4ms 1.22 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.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude daily --offline --json 0.00 MiB 31.8ms 31.1ms 1.02x 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 31.4ms 31.0ms 1.01x 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 30.9ms 31.0ms 1.00x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 30.7ms 31.3ms 0.98x 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, 2,597 files), Codex /home/runner/work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 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.

Command Input Base median PR median PR vs base Base throughput PR throughput
claude --offline --json 1.01 GiB 679.1ms 675.9ms 1.00x 1.48 GiB/s 1.49 GiB/s
codex --offline --json 1.01 GiB 847.1ms 847.5ms 1.00x 1.19 GiB/s 1.19 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 9.00 KiB 9.06 KiB +0.05 KiB 0.99x
installed native package binary 3160.24 KiB 3160.24 KiB +0.00 KiB 1.00x
Rust release binary rust/target/release/ccusage - 2827.68 KiB - -

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/src/pricing.rs`:
- Around line 409-423: Update the Grok 4.3 pricing entry created in the
entries.insert call for "grok-4.3": change the Pricing.cache_read value from
0.125e-6 to 0.20e-6 to match xAI's Grok 4.3 docs; keep input (1.25e-6) and
output (2.5e-6) as-is. While editing, optionally mirror the gpt-5.5 pattern by
checking/applying fast_multiplier_overrides (the same logic used around the
gpt-5.5 entry) to the grok-4.3 block if you intend to support fast variants,
otherwise leave fast_multiplier at 1.0.
🪄 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: b32bc1b3-4347-4ece-b1a0-4fc9e9b1a65f

📥 Commits

Reviewing files that changed from the base of the PR and between 35cb05e and 338d94c.

📒 Files selected for processing (26)
  • apps/ccusage/README.md
  • apps/ccusage/config-schema.json
  • docs/.vitepress/config.ts
  • docs/guide/getting-started.md
  • docs/guide/hermes/index.md
  • docs/guide/index.md
  • docs/index.md
  • rust/crates/ccusage/src/adapter/all.rs
  • rust/crates/ccusage/src/adapter/amp.rs
  • rust/crates/ccusage/src/adapter/copilot.rs
  • rust/crates/ccusage/src/adapter/gemini.rs
  • rust/crates/ccusage/src/adapter/hermes.rs
  • rust/crates/ccusage/src/adapter/kilo.rs
  • rust/crates/ccusage/src/adapter/mod.rs
  • rust/crates/ccusage/src/adapter/opencode/mod.rs
  • rust/crates/ccusage/src/adapter/opencode/parser.rs
  • rust/crates/ccusage/src/adapter/pi.rs
  • rust/crates/ccusage/src/claude_loader.rs
  • rust/crates/ccusage/src/cli.rs
  • rust/crates/ccusage/src/config.rs
  • rust/crates/ccusage/src/config_schema.rs
  • rust/crates/ccusage/src/main.rs
  • rust/crates/ccusage/src/pricing.rs
  • rust/crates/ccusage/src/progress.rs
  • rust/crates/ccusage/src/summary.rs
  • rust/crates/ccusage/src/types.rs

Comment on lines +409 to +423
self.entries.insert(
"grok-4.3".to_string(),
Pricing {
input: 1.25e-6,
output: 2.5e-6,
cache_create: 1.25e-6,
cache_read: 0.125e-6,
cache_read_explicit: false,
input_above_200k: None,
output_above_200k: None,
cache_create_above_200k: None,
cache_read_above_200k: None,
fast_multiplier: 1.0,
},
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What is the Grok 4.3 API pricing per token?

💡 Result:

Grok 4.3 API pricing per token (xAI): - Input: $1.25 per 1M tokens → $0.00000125 per token [1] - Cached input: $0.20 per 1M tokens → $0.00000020 per token [1] - Output: $2.50 per 1M tokens → $0.00000250 per token [1] (These are the published per-1M token rates in xAI’s Grok 4.3 model docs.) [1]

Citations:


Fix the cache_read pricing rate for Grok 4.3.

The hardcoded pricing has an error: cache_read: 0.125e-6 should be 0.20e-6 according to xAI's official Grok 4.3 API documentation. The input (1.25e-6) and output (2.5e-6) rates are correct, but the cached token read rate is ~37% too low. This affects cost reporting accuracy for Hermes users relying on these fallback values.

Additionally, consider whether grok-4.3 should follow the gpt-5.5 pattern (lines 404-406) of checking fast_multiplier_overrides for consistency, though this is optional if no fast variants are planned.

🤖 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/src/pricing.rs` around lines 409 - 423, Update the Grok
4.3 pricing entry created in the entries.insert call for "grok-4.3": change the
Pricing.cache_read value from 0.125e-6 to 0.20e-6 to match xAI's Grok 4.3 docs;
keep input (1.25e-6) and output (2.5e-6) as-is. While editing, optionally mirror
the gpt-5.5 pattern by checking/applying fast_multiplier_overrides (the same
logic used around the gpt-5.5 entry) to the grok-4.3 block if you intend to
support fast variants, otherwise leave fast_multiplier at 1.0.

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.

1 participant