Repository navigation
feat(hermes): add Hermes Agent usage adapter - #1026
Conversation
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.
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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. ChangesHermes Agent Support
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| 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 |
ccusage
@ccusage/ccusage-darwin-arm64
@ccusage/ccusage-darwin-x64
@ccusage/ccusage-linux-arm64
@ccusage/ccusage-linux-x64
@ccusage/ccusage-win32-arm64
@ccusage/ccusage-win32-x64
commit: |
ccusage performance comparisonThis compares the PR build against the base branch build on the same CI runner. Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated 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
Package size
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. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
apps/ccusage/src/commands/agent.ts (1)
217-219: ⚡ Quick winAdd a focused regression test for
messageCountJSON output.Line 217 adds new payload mapping, but there’s no in-source assertion covering this branch yet. A small
sessionpayload test withmetadata.messageCountwould 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
📒 Files selected for processing (20)
apps/ccusage/README.mdapps/ccusage/config-schema.jsonapps/ccusage/src/adapter/hermes/index.tsapps/ccusage/src/adapter/hermes/parser.tsapps/ccusage/src/adapter/hermes/paths.tsapps/ccusage/src/adapter/hermes/pricing.tsapps/ccusage/src/adapter/index.tsapps/ccusage/src/adapter/types.tsapps/ccusage/src/commands/agent.tsapps/ccusage/src/commands/index.tsapps/ccusage/src/config-loader-tokens.tsapps/ccusage/test/cli-output.test.tsdocs/.vitepress/config.tsdocs/guide/all-reports.mddocs/guide/config-files.mddocs/guide/configuration.mddocs/guide/environment-variables.mddocs/guide/getting-started.mddocs/guide/hermes/index.mddocs/guide/index.md
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.
|
@cubic-dev-ai review it |
@ryoppippi I have started the AI code review. It will take a few minutes to complete. |
|
@coderabbitai review it |
🧩 Analysis chain🏁 Script executed: Length of output: 110794
Review:
|
There was a problem hiding this comment.
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
📒 Files selected for processing (24)
apps/ccusage/README.mdapps/ccusage/config-schema.jsonapps/ccusage/scripts/generate-json-schema.tsapps/ccusage/src/adapter/hermes/index.tsapps/ccusage/src/adapter/hermes/parser.tsapps/ccusage/src/adapter/hermes/paths.tsapps/ccusage/src/adapter/hermes/pricing.tsapps/ccusage/src/adapter/index.tsapps/ccusage/src/adapter/opencode/loader.tsapps/ccusage/src/adapter/opencode/paths.tsapps/ccusage/src/adapter/sqlite.tsapps/ccusage/src/adapter/types.tsapps/ccusage/src/commands/agent.tsapps/ccusage/src/commands/index.tsapps/ccusage/src/config-loader-tokens.tsapps/ccusage/test/cli-output.test.tsdocs/.vitepress/config.tsdocs/guide/all-reports.mddocs/guide/config-files.mddocs/guide/configuration.mddocs/guide/environment-variables.mddocs/guide/getting-started.mddocs/guide/hermes/index.mddocs/guide/index.md
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.
|
@coderabbitai review it |
|
@cubic-dev-ai review it |
@ryoppippi I have started the AI code review. It will take a few minutes to complete. |
|
✅ Actions performedFull 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.
|
@coderabbitai review it |
|
@cubic-dev-ai review it |
@ryoppippi I have started the AI code review. It will take a few minutes to complete. |
|
✅ Actions performedFull 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.
|
@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:
|
🧩 Analysis chain
🏁 Script executed: Length of output: 1583 🏁 Script executed: Length of output: 207334
Review: Rust CLI port — Hermes Agent🔴 Critical: Stale
|
| 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:
createHermesStateDbduplicated in 3 places — schema drift riskPROVIDER_ALIASEStoo narrow —mistral,cohere,deepseek, etc. fall back to"hermes"and miss theprovider/modelpricing candidate- Silent
$0fallback incalculateHermesCostwhen no pricing is found o\dregex should beo\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_f64helpers are well-structured- Session deduplication via
HashSetis consistent with other adapters - Reasoning tokens wired into
extra_total_tokens/ cost candidates correctly messageCountpropagated intoLoadedEntry- 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.
There was a problem hiding this comment.
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 winPropagate
messageCountthrough aggregated all-report rows.
summary_metadata()now capturesmessageCount, but daily/monthly/weeklyallreports discardrow.metadatainAllAccumulator::into_row(), sorow_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
📒 Files selected for processing (22)
apps/ccusage/README.mdapps/ccusage/config-schema.jsondocs/.vitepress/config.tsdocs/guide/environment-variables.mddocs/guide/getting-started.mddocs/guide/index.mdrust/crates/ccusage/src/adapter/all.rsrust/crates/ccusage/src/adapter/amp.rsrust/crates/ccusage/src/adapter/hermes.rsrust/crates/ccusage/src/adapter/mod.rsrust/crates/ccusage/src/adapter/opencode/mod.rsrust/crates/ccusage/src/adapter/opencode/parser.rsrust/crates/ccusage/src/adapter/pi.rsrust/crates/ccusage/src/claude_loader.rsrust/crates/ccusage/src/cli.rsrust/crates/ccusage/src/config.rsrust/crates/ccusage/src/config_schema.rsrust/crates/ccusage/src/main.rsrust/crates/ccusage/src/output.rsrust/crates/ccusage/src/progress.rsrust/crates/ccusage/src/summary.rsrust/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
There was a problem hiding this comment.
♻️ Duplicate comments (2)
rust/crates/ccusage/src/output.rs (1)
85-92:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUse aggregated
totalTokensfor 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 whenextra_total_tokensis 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 winAdd 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 lowercasehermesinstead of brandedHermes.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
📒 Files selected for processing (26)
apps/ccusage/README.mdapps/ccusage/config-schema.jsondocs/.vitepress/config.tsdocs/guide/all-reports.mddocs/guide/config-files.mddocs/guide/configuration.mddocs/guide/environment-variables.mddocs/guide/getting-started.mddocs/guide/hermes/index.mddocs/guide/index.mdrust/crates/ccusage/src/adapter/all.rsrust/crates/ccusage/src/adapter/amp.rsrust/crates/ccusage/src/adapter/hermes.rsrust/crates/ccusage/src/adapter/mod.rsrust/crates/ccusage/src/adapter/opencode/mod.rsrust/crates/ccusage/src/adapter/opencode/parser.rsrust/crates/ccusage/src/adapter/pi.rsrust/crates/ccusage/src/claude_loader.rsrust/crates/ccusage/src/cli.rsrust/crates/ccusage/src/config.rsrust/crates/ccusage/src/config_schema.rsrust/crates/ccusage/src/main.rsrust/crates/ccusage/src/output.rsrust/crates/ccusage/src/progress.rsrust/crates/ccusage/src/summary.rsrust/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
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runner startupExecution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one
Cached bunx execution performanceRuns the same large fixture through Fixtures: Claude
Package runtime diagnosticsCompares 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
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated 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
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
…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.
|
@coderabbitai review Pushed the Hermes branch again after #1032 Kilo was merged into main. Changes in this push:
Hermes-specific fix still included:
Local validation:
Pre-push validation also passed clippy/oxfmt/eslint hooks. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 2409 🏁 Script executed: Length of output: 13877
✅ Everything looks good
🟡 One persistent minor nit
for candidate in model_candidates(entry) {
// ...
if cost >= 0.0 && cost.is_finite() && cost > 0.0 { return cost; }
}
0.0 // ← still no diagnosticThe 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 crate::debug_log(|| format!("hermes: no pricing found for model '{}' (provider: '{}'); cost=0", entry.model, entry.provider));
0.0Otherwise 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.
|
@coderabbitai review Pushed a follow-up for the remaining Hermes review thread. Changes in this push:
Validation:
|
|
@coderabbitai review Correcting the previous review request formatting: pushed a follow-up for the remaining Hermes review thread. Changes in this push:
Validation:
|
🧩 Analysis chain🏁 Script executed: Length of output: 4857
✅ All issues resolved
🟡 One persistent minor nit (acknowledged, not blocking)
crate::debug_log(|| format!("hermes: no pricing found for '{}'/'{}'", entry.provider, entry.model));
0.0Otherwise, 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 performedFull review triggered. |
🧩 Analysis chain🏁 Script executed: Length of output: 2196
✅
|
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runner startupExecution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one
Cached bunx execution performanceRuns the same large fixture through Fixtures: Claude
Package runtime diagnosticsCompares 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
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated 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
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runner startupExecution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one
Cached bunx execution performanceRuns the same large fixture through Fixtures: Claude
Package runtime diagnosticsCompares 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
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated 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
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
docs/guide/getting-started.md (1)
176-194: ⚡ Quick winAdd comma-separated HERMES_HOME example for consistency.
The Hermes Agent documentation at
docs/guide/hermes/index.md:59states thatHERMES_HOMEsupports "comma-separated roots," but this section omitsHERMES_HOMEfrom 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
📒 Files selected for processing (26)
apps/ccusage/README.mdapps/ccusage/config-schema.jsondocs/.vitepress/config.tsdocs/guide/getting-started.mddocs/guide/hermes/index.mddocs/guide/index.mddocs/index.mdrust/crates/ccusage/src/adapter/all.rsrust/crates/ccusage/src/adapter/amp.rsrust/crates/ccusage/src/adapter/copilot.rsrust/crates/ccusage/src/adapter/gemini.rsrust/crates/ccusage/src/adapter/hermes.rsrust/crates/ccusage/src/adapter/kilo.rsrust/crates/ccusage/src/adapter/mod.rsrust/crates/ccusage/src/adapter/opencode/mod.rsrust/crates/ccusage/src/adapter/opencode/parser.rsrust/crates/ccusage/src/adapter/pi.rsrust/crates/ccusage/src/claude_loader.rsrust/crates/ccusage/src/cli.rsrust/crates/ccusage/src/config.rsrust/crates/ccusage/src/config_schema.rsrust/crates/ccusage/src/main.rsrust/crates/ccusage/src/pricing.rsrust/crates/ccusage/src/progress.rsrust/crates/ccusage/src/summary.rsrust/crates/ccusage/src/types.rs
| 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 | ||
| } |
There was a problem hiding this comment.
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.
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runner startupExecution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one
Cached bunx execution performanceRuns the same large fixture through Fixtures: Claude
Package runtime diagnosticsCompares 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
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated 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
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runner startupExecution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one
Cached bunx execution performanceRuns the same large fixture through Fixtures: Claude
Package runtime diagnosticsCompares 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
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated 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
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
There was a problem hiding this comment.
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
📒 Files selected for processing (26)
apps/ccusage/README.mdapps/ccusage/config-schema.jsondocs/.vitepress/config.tsdocs/guide/getting-started.mddocs/guide/hermes/index.mddocs/guide/index.mddocs/index.mdrust/crates/ccusage/src/adapter/all.rsrust/crates/ccusage/src/adapter/amp.rsrust/crates/ccusage/src/adapter/copilot.rsrust/crates/ccusage/src/adapter/gemini.rsrust/crates/ccusage/src/adapter/hermes.rsrust/crates/ccusage/src/adapter/kilo.rsrust/crates/ccusage/src/adapter/mod.rsrust/crates/ccusage/src/adapter/opencode/mod.rsrust/crates/ccusage/src/adapter/opencode/parser.rsrust/crates/ccusage/src/adapter/pi.rsrust/crates/ccusage/src/claude_loader.rsrust/crates/ccusage/src/cli.rsrust/crates/ccusage/src/config.rsrust/crates/ccusage/src/config_schema.rsrust/crates/ccusage/src/main.rsrust/crates/ccusage/src/pricing.rsrust/crates/ccusage/src/progress.rsrust/crates/ccusage/src/summary.rsrust/crates/ccusage/src/types.rs
| 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, | ||
| }, | ||
| ); |
There was a problem hiding this comment.
🧩 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.
Try This PR
Summary
Validation
Summary by CodeRabbit
New Features
Documentation
Other