Skip to content

fix(amp): fall back to messages[].usage when usageLedger is absent#1145

Merged
ryoppippi merged 2 commits into
mainfrom
claude/github-issue-1138-Fkbf1
May 25, 2026
Merged

fix(amp): fall back to messages[].usage when usageLedger is absent#1145
ryoppippi merged 2 commits into
mainfrom
claude/github-issue-1138-Fkbf1

Conversation

@ryoppippi
Copy link
Copy Markdown
Owner

@ryoppippi ryoppippi commented May 24, 2026

Summary

  • The Rust Amp parser only emitted entries when usageLedger.events was present, so current Amp installs that store usage directly on each assistant message reported No Amp usage data found.
  • After this change, when usageLedger.events is missing, the parser reads each assistant message's usage object (model, timestamp, inputTokens, outputTokens, cacheCreationInputTokens, cacheReadInputTokens).
  • Legacy usageLedger.events data still takes precedence when present; the messages-only fallback only kicks in when the ledger is absent.

Closes #1138

Test plan

  • cargo test -p ccusage adapter::amp — 4 tests pass (legacy fallback, new schema, precedence, empty-tokens skip).
  • cargo test -p ccusage — 215 tests pass, no regressions.
  • cargo clippy -p ccusage --tests clean.
  • Sanity-check on real Amp data under ~/.local/share/amp/threads (per the issue, 94/121 files use the new schema).

Generated by Claude Code


Summary by cubic

Fixes Amp usage parsing in ccusage by reading messages[].usage when usageLedger.events is missing and falling back to totalTokens when split fields are absent, so newer Amp installs report usage correctly. Legacy ledger data still takes precedence. Closes #1138.

  • Bug Fixes
    • Read model, timestamp, and token fields from messages[].usage when no ledger is present.
    • Use totalTokens as a fallback in the messages path; still skip zero-token entries.
    • Keep usageLedger.events as the source of truth when available.

Written for commit f52ff3b. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • Documentation

    • Clarified how token usage is derived from stored data, including when event-ledgers vs. message-level usage apply and how token fields are interpreted.
  • Bug Fixes

    • Improved parsing to prefer ledger events when present, fall back to message usage otherwise, skip entries with zero tokens, and use total-token fallback when split fields are absent.

Review Change Stack

The Rust Amp parser only emitted entries when usageLedger.events was
present, so current Amp installs that store usage directly on each
assistant message (model, timestamp, token fields inside messages[].usage)
reported "No Amp usage data found." Now we read those usage objects when
the ledger is missing.

Closes #1138
@ryoppippi ryoppippi requested a review from Copilot May 24, 2026 22:58
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

📝 Walkthrough

Walkthrough

The Amp parser now prefers usageLedger.events when present; otherwise it falls back to extracting assistant token usage from messages[].usage. Session ID construction was unified for ledger entries; message parsing adds token-field splitting, total-token fallback, zero-token filtering, cost calculation, tests, and README updates.

Changes

Amp usage parser fallback to messages array

Layer / File(s) Summary
Entry point routing and session ID normalization
rust/crates/ccusage/src/adapter/amp/parser.rs
read_thread_file checks for usageLedger.events and branches to parse_ledger_events or parse_message_usage; UsageEntry.session_id and LoadedEntry.session_id are built directly from thread_id.
Message-based usage parsing
rust/crates/ccusage/src/adapter/amp/parser.rs
New parse_message_usage extracts timestamp and model, constructs TokenUsageRaw from input/output/cache fields, applies totalTokens fallback, skips entries with all zero relevant tokens, normalizes messageId, computes cost, and emits LoadedEntry items.
Documentation and test validation
rust/crates/ccusage/src/adapter/amp/README.md, rust/crates/ccusage/src/adapter/amp/parser.rs
README clarifies when to use ledger events vs messages usage and totalTokens fallback; tests validate message-based parsing, ledger precedence, zero-token skipping, and fallback behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • ryoppippi/ccusage#1071: Overlapping Amp parser changes around tokens.total handling and extra_total_tokens/cost behavior, including ledger vs messages precedence.
  • ryoppippi/ccusage#977: Earlier Amp adapter implementation and parser work that introduced the AMP adapter this PR extends.

Poem

A rabbit reads the JSON stream,
Ledger present? Ledger gleam.
When ledgers hide, I softly peek,
Messages tell the tokens' speak.
Hoppy parser, small and neat! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% 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 title clearly and concisely describes the main change: adding fallback support for messages[].usage when usageLedger is absent in the Amp parser.
Linked Issues check ✅ Passed The changes fully implement the requirements from #1138: the parser now falls back to messages[].usage when usageLedger.events is absent, extracts the correct fields (model, timestamp, token counts), preserves legacy behavior where usageLedger takes precedence, and skips entries with zero tokens.
Out of Scope Changes check ✅ Passed All changes are directly scoped to #1138: README updates document the dual-path parsing behavior, parser.rs implements the fallback logic, and tests cover both legacy and new schema paths; no unrelated modifications detected.

✏️ 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 claude/github-issue-1138-Fkbf1

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
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 24, 2026

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 f52ff3b Commit Preview URL

Branch Preview URL
May 24 2026, 11:23 PM

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes Amp usage parsing in the Rust ccusage adapter by supporting the current Amp thread schema where token usage is stored on messages[].usage when usageLedger.events is absent, while keeping the legacy ledger path as the preferred source when available (closes #1138).

Changes:

  • Parse usageLedger.events[] when present (existing behavior), including cache breakdown via messages[].usage keyed by toMessageId.
  • Add a fallback path to emit entries from assistant messages[].usage when the ledger/events array is missing.
  • Add tests covering the new messages-based schema, precedence of ledger events, and skipping zero-token usage.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
rust/crates/ccusage/src/adapter/amp/parser.rs Adds messages-based usage parsing fallback and supporting tests while preserving ledger precedence.
rust/crates/ccusage/src/adapter/amp/README.md Updates Amp adapter docs to describe the ledger-vs-messages usage sources.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +15 to +17
- `messages[].usage` directly when `usageLedger.events` is not present (current
Amp schema). Each assistant message's `usage` object carries `model`,
`timestamp`, and the token fields below.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Updated the README in f52ff3b. The token-field list now spells out which keys belong to each schema variant: legacy usageLedger.events[].tokens uses input/output/total, and the current messages[].usage schema uses inputTokens/outputTokens/cacheCreationInputTokens/cacheReadInputTokens/totalTokens, with totalTokens documented as a fallback for the split fields.


Generated by Claude Code

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
rust/crates/ccusage/src/adapter/amp/parser.rs (1)

275-401: ⚡ Quick win

Move new parser cases to fixture-backed tests.

These cases are good, but the inline JSON blobs make schema-variant maintenance harder. Please move them to fixtures and keep tests focused on assertions.

As per coding guidelines, **/*.rs: "prefer fixture-backed parser/loader tests".

🤖 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/amp/parser.rs` around lines 275 - 401, Move
the inline JSON blobs in tests reads_usage_from_messages_when_ledger_is_missing,
ledger_events_take_precedence_over_messages_usage, and
skips_messages_with_no_usage_tokens into dedicated fixture files and update the
tests to load those fixtures instead of embedding the strings; specifically,
create fixture files containing each JSON payload and change the tests to call
read_thread_file on the fixture path (reusing the existing read_thread_file
function) and keep the existing assertions unchanged so the parser behavior is
verified against file-backed inputs rather than inline literals.
🤖 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/amp/parser.rs`:
- Around line 167-179: The zero-token filter currently checks TokenUsageRaw
fields (input_tokens, output_tokens, cache_creation_input_tokens,
cache_read_input_tokens) and continues before applying fallbacks from aggregate
fields like totalTokens/totalInputTokens, causing valid AMP records to be
dropped; update the parsing in parser.rs (around TokenUsageRaw construction
where json_value_u64 is used) to first populate/merge aggregate token fields
into the detailed fields (e.g., use totalTokens or totalInputTokens to set
input_tokens/output_tokens when the detailed fields are missing or zero), then
perform the zero-token check, and apply the same change to the analogous block
around the later code (lines ~199-208) so both branches handle aggregates before
skipping.

---

Nitpick comments:
In `@rust/crates/ccusage/src/adapter/amp/parser.rs`:
- Around line 275-401: Move the inline JSON blobs in tests
reads_usage_from_messages_when_ledger_is_missing,
ledger_events_take_precedence_over_messages_usage, and
skips_messages_with_no_usage_tokens into dedicated fixture files and update the
tests to load those fixtures instead of embedding the strings; specifically,
create fixture files containing each JSON payload and change the tests to call
read_thread_file on the fixture path (reusing the existing read_thread_file
function) and keep the existing assertions unchanged so the parser behavior is
verified against file-backed inputs rather than inline literals.
🪄 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: 6666019a-732d-4735-9566-1fb8b1c86424

📥 Commits

Reviewing files that changed from the base of the PR and between 366eb0c and faa83e8.

📒 Files selected for processing (2)
  • rust/crates/ccusage/src/adapter/amp/README.md
  • rust/crates/ccusage/src/adapter/amp/parser.rs

Comment thread rust/crates/ccusage/src/adapter/amp/parser.rs
Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

1 issue found across 2 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread rust/crates/ccusage/src/adapter/amp/parser.rs
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 24, 2026

Open in StackBlitz

ccusage

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

@ccusage/ccusage-darwin-arm64

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

@ccusage/ccusage-darwin-x64

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

@ccusage/ccusage-linux-arm64

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

@ccusage/ccusage-linux-x64

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

@ccusage/ccusage-win32-arm64

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

@ccusage/ccusage-win32-x64

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

commit: f52ff3b

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: faa83e83fc38
Base SHA: 366eb0c4cc6c

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 366eb0c4cc6c 762.6ms 742.7ms 34.9ms 3
PR pkg.pr.new faa83e83fc38 633.5ms 484.0ms 34.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: 366eb0c4cc6c; PR package: faa83e83fc38. 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.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 498.9ms 491.8ms 1.01x 265.58 MiB 266.95 MiB 1.01x 2.02 GiB/s 2.05 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 371.3ms 368.5ms 1.01x 57.33 MiB 51.95 MiB 0.91x 2.71 GiB/s 2.73 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 491.7ms 2.05 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 460.5ms 2.19 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 361.4ms 2.79 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 335.3ms 3.00 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.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 31.1ms 4.1ms 7.53x 43.61 MiB 2.83 MiB 0.06x 0.05 MiB/s 0.37 MiB/s
claude session --offline --json 0.00 MiB 31.1ms 4.3ms 7.28x 43.48 MiB 2.83 MiB 0.07x 0.05 MiB/s 0.36 MiB/s
codex daily --offline --json 0.00 MiB 31.3ms 4.0ms 7.89x 43.61 MiB 2.83 MiB 0.06x 0.03 MiB/s 0.22 MiB/s
codex session --offline --json 0.00 MiB 31.0ms 3.9ms 7.92x 43.48 MiB 2.83 MiB 0.07x 0.03 MiB/s 0.22 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.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 498.1ms 468.4ms 1.06x 255.95 MiB 266.70 MiB 1.04x 2.02 GiB/s 2.15 GiB/s
codex --offline --json 1.01 GiB 401.1ms 330.4ms 1.21x 66.83 MiB 57.45 MiB 0.86x 2.51 GiB/s 3.05 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 14.25 KiB 14.25 KiB +0.00 KiB 1.00x
installed native package binary 3225.49 KiB 3225.49 KiB +0.00 KiB 1.00x

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

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: faa83e83fc38
Base SHA: 366eb0c4cc6c

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 366eb0c4cc6c 793.5ms 632.6ms 36.7ms 3
PR pkg.pr.new faa83e83fc38 671.4ms 886.5ms 36.2ms 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: 366eb0c4cc6c; PR package: faa83e83fc38. 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.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 506.9ms 508.1ms 1.00x 272.33 MiB 257.83 MiB 0.95x 1.99 GiB/s 1.98 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 384.0ms 383.5ms 1.00x 58.95 MiB 52.20 MiB 0.89x 2.62 GiB/s 2.62 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 497.5ms 2.02 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 474.3ms 2.12 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 370.8ms 2.71 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 349.4ms 2.88 GiB/s 1

Committed fixture performance

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

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

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 33.0ms 32.8ms 1.01x 43.48 MiB - - 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 33.2ms 33.0ms 1.01x - - - 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 33.0ms 32.8ms 1.01x 43.48 MiB 43.61 MiB 1.00x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 33.3ms 32.6ms 1.02x 43.61 MiB 43.61 MiB 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.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 513.1ms 507.0ms 1.01x 266.83 MiB 267.33 MiB 1.00x 1.96 GiB/s 1.99 GiB/s
codex --offline --json 1.01 GiB 373.5ms 371.5ms 1.01x 64.20 MiB - - 2.70 GiB/s 2.71 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 14.25 KiB 14.25 KiB +0.00 KiB 1.00x
installed native package binary 3225.49 KiB 3225.49 KiB +0.00 KiB 1.00x

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

Mirrors the legacy ledger-event behavior so a `messages[].usage` object
that reports only `totalTokens` (without the split input/output/cache
fields) still yields a usage entry instead of being filtered out by the
zero-token check. Also clarifies the README to spell out the field names
each schema variant uses and that `totalTokens` is only a fallback.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
rust/crates/ccusage/src/adapter/amp/parser.rs (1)

174-176: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Support totalInputTokens fallback in the messages path too.

The fallback only reads usage.totalTokens. Amp data can also provide aggregate usage as totalInputTokens; in that case this path still gets filtered as zero usage.

Suggested patch
-        let total_tokens = json_value_u64(usage.get("totalTokens"));
+        let total_tokens = json_value_u64(usage.get("totalTokens"))
+            .max(json_value_u64(usage.get("totalInputTokens")));
         let (usage_raw, extra_total_tokens) =
             apply_total_token_fallback(usage_raw, 0, total_tokens);
+    #[test]
+    fn falls_back_to_total_input_tokens_in_messages_path() {
+        let nanos = SystemTime::now()
+            .duration_since(std::time::UNIX_EPOCH)
+            .unwrap()
+            .as_nanos();
+        let dir = env::temp_dir().join(format!("ccusage-amp-messages-total-input-{nanos}"));
+        fs::create_dir_all(&dir).unwrap();
+        let file = dir.join("thread.json");
+        fs::write(
+            &file,
+            r#"{
+                "id":"T-thread-a",
+                "messages":[
+                    {"role":"assistant","usage":{
+                        "model":"claude-haiku-4-5-20251001",
+                        "totalInputTokens":345,
+                        "timestamp":"2026-01-19T11:42:10.652Z"
+                    }}
+                ]
+            }"#,
+        )
+        .unwrap();
+
+        let entries = read_thread_file(&file, None, CostMode::Auto, None).unwrap();
+        fs::remove_dir_all(&dir).unwrap();
+
+        assert_eq!(entries.len(), 1);
+        assert_eq!(entries[0].data.message.usage.output_tokens, 345);
+        assert_eq!(entries[0].extra_total_tokens, 0);
+    }

As per coding guidelines, "Add Rust fixture-backed tests for path discovery, parser behavior, aggregation totals, and important legacy compatibility."

Also applies to: 181-181, 421-451

🤖 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/amp/parser.rs` around lines 174 - 176, The
messages-path fallback is only reading usage.totalTokens so aggregate counts
provided as totalInputTokens are ignored; update the parser code in parser.rs
where total_tokens is computed (currently via
json_value_u64(usage.get("totalTokens"))) to also check
usage.get("totalInputTokens") and prefer that value when present (e.g., try
totalInputTokens then totalTokens) before calling
apply_total_token_fallback(usage_raw, 0, total_tokens); ensure the same change
is applied at the other occurrences noted (around lines 181 and 421-451) and add
Rust fixture-backed tests that cover path discovery, parser behavior,
aggregation totals and legacy compatibility for totalInputTokens vs totalTokens.
🤖 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/amp/parser.rs`:
- Around line 174-176: The messages-path fallback is only reading
usage.totalTokens so aggregate counts provided as totalInputTokens are ignored;
update the parser code in parser.rs where total_tokens is computed (currently
via json_value_u64(usage.get("totalTokens"))) to also check
usage.get("totalInputTokens") and prefer that value when present (e.g., try
totalInputTokens then totalTokens) before calling
apply_total_token_fallback(usage_raw, 0, total_tokens); ensure the same change
is applied at the other occurrences noted (around lines 181 and 421-451) and add
Rust fixture-backed tests that cover path discovery, parser behavior,
aggregation totals and legacy compatibility for totalInputTokens vs totalTokens.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e2d5aba2-416f-4508-88e0-df0c7722d054

📥 Commits

Reviewing files that changed from the base of the PR and between faa83e8 and f52ff3b.

📒 Files selected for processing (2)
  • rust/crates/ccusage/src/adapter/amp/README.md
  • rust/crates/ccusage/src/adapter/amp/parser.rs
✅ Files skipped from review due to trivial changes (1)
  • rust/crates/ccusage/src/adapter/amp/README.md

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: f52ff3b7c608
Base SHA: 366eb0c4cc6c

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 366eb0c4cc6c 594.2ms 449.3ms 33.3ms 3
PR pkg.pr.new f52ff3b7c608 584.3ms 447.7ms 33.5ms 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: 366eb0c4cc6c; PR package: f52ff3b7c608. 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.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 495.5ms 500.9ms 0.99x 259.95 MiB 246.58 MiB 0.95x 2.03 GiB/s 2.01 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 363.9ms 363.5ms 1.00x 51.45 MiB 59.33 MiB 1.15x 2.77 GiB/s 2.77 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 491.1ms 2.05 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 456.8ms 2.20 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 353.3ms 2.85 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 331.7ms 3.04 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.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 30.7ms 3.9ms 7.78x 43.48 MiB 2.83 MiB 0.07x 0.05 MiB/s 0.39 MiB/s
claude session --offline --json 0.00 MiB 30.4ms 4.0ms 7.64x 43.61 MiB 2.83 MiB 0.06x 0.05 MiB/s 0.39 MiB/s
codex daily --offline --json 0.00 MiB 29.6ms 3.8ms 7.86x 43.48 MiB 2.83 MiB 0.07x 0.03 MiB/s 0.23 MiB/s
codex session --offline --json 0.00 MiB 29.6ms 3.7ms 7.95x 43.61 MiB 2.83 MiB 0.06x 0.03 MiB/s 0.23 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.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 489.9ms 456.3ms 1.07x 261.95 MiB 261.58 MiB 1.00x 2.06 GiB/s 2.21 GiB/s
codex --offline --json 1.01 GiB 357.8ms 324.0ms 1.10x 65.58 MiB 57.83 MiB 0.88x 2.81 GiB/s 3.11 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 14.25 KiB 14.25 KiB -0.00 KiB 1.00x
installed native package binary 3225.49 KiB 3225.49 KiB +0.00 KiB 1.00x

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

@github-actions
Copy link
Copy Markdown

ccusage performance comparison

PR SHA: f52ff3b7c608
Base SHA: 366eb0c4cc6c

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 366eb0c4cc6c 922.5ms 678.3ms 31.3ms 3
PR pkg.pr.new f52ff3b7c608 693.0ms 502.9ms 30.3ms 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: 366eb0c4cc6c; PR package: f52ff3b7c608. 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.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 476.9ms 489.9ms 0.97x 242.20 MiB 267.20 MiB 1.10x 2.11 GiB/s 2.06 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 355.1ms 355.5ms 1.00x 65.45 MiB 54.83 MiB 0.84x 2.84 GiB/s 2.83 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 475.8ms 2.12 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 443.8ms 2.27 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 350.6ms 2.87 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 314.3ms 3.20 GiB/s 1

Committed fixture performance

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

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

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 28.4ms 28.6ms 0.99x 43.73 MiB 43.48 MiB 0.99x 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 28.5ms 29.0ms 0.98x 43.48 MiB 43.73 MiB 1.01x 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 28.3ms 28.3ms 1.00x 43.48 MiB 43.48 MiB 1.00x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 28.3ms 27.8ms 1.02x 43.48 MiB 43.48 MiB 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.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 482.1ms 469.6ms 1.03x 264.83 MiB 253.20 MiB 0.96x 2.09 GiB/s 2.14 GiB/s
codex --offline --json 1.01 GiB 348.4ms 345.6ms 1.01x 56.95 MiB 64.20 MiB 1.13x 2.89 GiB/s 2.91 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 14.25 KiB 14.25 KiB -0.00 KiB 1.00x
installed native package binary 3225.49 KiB 3225.49 KiB +0.00 KiB 1.00x

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

@ryoppippi ryoppippi merged commit 4176e95 into main May 25, 2026
40 checks passed
@ryoppippi ryoppippi deleted the claude/github-issue-1138-Fkbf1 branch May 25, 2026 18:01
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.

Amp usage parser misses current messages[].usage schema

3 participants