feat(qwen): add Qwen usage adapter#1030
Conversation
Add a Qwen adapter for local chat JSONL files under QWEN_DATA_DIR, including path discovery, parsing, LiteLLM pricing candidates, focused commands, all-source detection, docs, schema, and CLI output coverage. Qwen exposes reasoning tokens separately from ordinary candidate output, so table rendering now accepts adapter-provided totalTokens instead of recomputing totals from visible token columns.
|
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 Qwen as a local agent data source: discovers Qwen chat JSONL files, parses token/cost data, summarizes into daily/monthly/session reports, wires CLI/config/schema/help/tests, integrates into the multi-agent loader and progress, updates output totals, and adds documentation. ChangesQwen Agent Support
Sequence Diagram: sequenceDiagram
participant User
participant CLI as ccusage CLI
participant Adapter as adapter::qwen::run
participant Paths as qwen::paths
participant Parser as qwen::parser
User->>CLI: ccusage qwen daily --json
CLI->>Adapter: Command::Qwen(args)
Adapter->>Paths: discover_chat_files()
Paths->>Adapter: list of chat files
Adapter->>Parser: load_entries(shared)
Parser->>Adapter: LoadedEntry[]
Adapter->>Adapter: summarize_entries -> rows/json
Adapter->>User: print JSON/table
Estimated code review effort: 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 | b957810 | Commit Preview URL Branch Preview URL |
May 19 2026, 07:25 PM |
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/test/snapshots/cli-output/qwen-direct-daily-json.txt (1)
1-24: ⚡ Quick winConsider adding a reasoningTokens field for transparency.
The JSON output shows
totalTokens: 165while the sum of component fields is only 155 (100+50+0+5). The 10-token difference represents reasoning tokens, which per the PR design are included in the adapter-providedtotalTokensbut not broken out separately.For user clarity and transparency, consider adding an explicit
reasoningTokensfield to the output schema so users can track reasoning token usage independently.💡 Suggested enhancement
{ "daily": [ { "date": "2026-01-02", "inputTokens": 100, "outputTokens": 50, "cacheCreationTokens": 0, "cacheReadTokens": 5, + "reasoningTokens": 10, "totalTokens": 165, "totalCost": 0, "modelsUsed": [ "qwen3-coder-plus" ] } ], "totals": { "inputTokens": 100, "outputTokens": 50, "cacheCreationTokens": 0, "cacheReadTokens": 5, + "reasoningTokens": 10, "totalTokens": 165, "totalCost": 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 `@apps/ccusage/test/snapshots/cli-output/qwen-direct-daily-json.txt` around lines 1 - 24, Add an explicit reasoningTokens field computed as totalTokens - (inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens) to both each object in the "daily" array and the top-level "totals" object (e.g., update the code that builds daily entries and the totals aggregation to set reasoningTokens = Math.max(0, totalTokens - sumOfComponents) to avoid negatives), and update the output schema/snapshots accordingly so consumers see reasoningTokens alongside inputTokens/outputTokens/cacheCreationTokens/cacheReadTokens and 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.
Inline comments:
In `@apps/ccusage/config-schema.json`:
- Around line 635-781: The schema is missing a top-level "qwen" object while
runtime accepts config.qwen (see AGENT_CONFIG_KEYS and ConfigData), causing
validation to reject commands like "qwen:daily"/"qwen:monthly"/"qwen:session";
add a top-level "qwen" schema object mirroring other agent namespaces that
includes "defaults" and "commands" (with properties referencing the existing
"qwen:daily", "qwen:monthly", "qwen:session" definitions or duplicating their
structure) and ensure additionalProperties is set consistently so
autocomplete/validation accepts config.qwen and its subcommands.
In `@apps/ccusage/src/adapter/qwen/paths.ts`:
- Around line 20-35: isQwenChatFile is too permissive (matches any parent named
"chats"); update it to only accept files that follow projects/*/chats/*.jsonl by
checking that path.basename(path.dirname(filePath)) === QWEN_CHATS_DIR_NAME and
that the ancestor three levels up has basename === QWEN_PROJECTS_DIR_NAME (i.e.,
verify path.basename(path.dirname(path.dirname(path.dirname(filePath)))) ===
QWEN_PROJECTS_DIR_NAME); keep discoverQwenChatFiles, getQwenPaths,
QWEN_PROJECTS_DIR_NAME and QWEN_CHATS_DIR_NAME intact and use the revised
isQwenChatFile for filtering.
---
Nitpick comments:
In `@apps/ccusage/test/snapshots/cli-output/qwen-direct-daily-json.txt`:
- Around line 1-24: Add an explicit reasoningTokens field computed as
totalTokens - (inputTokens + outputTokens + cacheCreationTokens +
cacheReadTokens) to both each object in the "daily" array and the top-level
"totals" object (e.g., update the code that builds daily entries and the totals
aggregation to set reasoningTokens = Math.max(0, totalTokens - sumOfComponents)
to avoid negatives), and update the output schema/snapshots accordingly so
consumers see reasoningTokens alongside
inputTokens/outputTokens/cacheCreationTokens/cacheReadTokens and totalTokens.
🪄 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: 832d95f5-09d1-490e-a469-95c83ed6eea6
📒 Files selected for processing (31)
apps/ccusage/README.mdapps/ccusage/config-schema.jsonapps/ccusage/src/adapter/index.tsapps/ccusage/src/adapter/qwen/index.tsapps/ccusage/src/adapter/qwen/parser.tsapps/ccusage/src/adapter/qwen/paths.tsapps/ccusage/src/adapter/qwen/pricing.tsapps/ccusage/src/adapter/qwen/types.tsapps/ccusage/src/adapter/types.tsapps/ccusage/src/commands/agent.tsapps/ccusage/src/commands/all.tsapps/ccusage/src/commands/index.tsapps/ccusage/src/config-loader-tokens.tsapps/ccusage/test/cli-output.test.tsapps/ccusage/test/snapshots/cli-output/all-agent-daily-json.txtapps/ccusage/test/snapshots/cli-output/all-agent-daily-table.txtapps/ccusage/test/snapshots/cli-output/qwen-direct-daily-json.txtdocs/.vitepress/config.tsdocs/guide/all-reports.mddocs/guide/claude/index.mddocs/guide/codex/index.mddocs/guide/config-files.mddocs/guide/configuration.mddocs/guide/daily-reports.mddocs/guide/environment-variables.mddocs/guide/getting-started.mddocs/guide/index.mddocs/guide/monthly-reports.mddocs/guide/qwen/index.mddocs/guide/session-reports.mdpackages/terminal/src/table.ts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/ccusage/scripts/generate-json-schema.ts (1)
468-468: ⚡ Quick winStrengthen the “expected properties” test to cover all agent namespaces.
This check now includes
qwen, but it still misses assertions for existing namespaces (opencode,amp,pi). Adding them keeps this test from allowing accidental regressions.Suggested test expansion
expect(properties).toHaveProperty('claude'); expect(properties).toHaveProperty('codex'); + expect(properties).toHaveProperty('opencode'); + expect(properties).toHaveProperty('amp'); + expect(properties).toHaveProperty('pi'); expect(properties).toHaveProperty('qwen');🤖 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/scripts/generate-json-schema.ts` at line 468, The test currently only asserts that properties includes 'qwen' (expect(properties).toHaveProperty('qwen');) and should be expanded to assert all agent namespaces to prevent regressions; update the test in generate-json-schema.ts where properties is checked to also include toHaveProperty('opencode'), toHaveProperty('amp'), and toHaveProperty('pi') (in addition to the existing 'qwen') so the suite verifies all expected agent namespaces are present.
🤖 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.
Nitpick comments:
In `@apps/ccusage/scripts/generate-json-schema.ts`:
- Line 468: The test currently only asserts that properties includes 'qwen'
(expect(properties).toHaveProperty('qwen');) and should be expanded to assert
all agent namespaces to prevent regressions; update the test in
generate-json-schema.ts where properties is checked to also include
toHaveProperty('opencode'), toHaveProperty('amp'), and toHaveProperty('pi') (in
addition to the existing 'qwen') so the suite verifies all expected agent
namespaces are present.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b2b40de5-f46f-4268-9e39-de0dfa307e89
📒 Files selected for processing (3)
apps/ccusage/config-schema.jsonapps/ccusage/scripts/generate-json-schema.tsapps/ccusage/src/adapter/qwen/paths.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/ccusage/src/adapter/qwen/paths.ts
- apps/ccusage/config-schema.json
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: |
|
@coderabbitai review it |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 99164 🏁 Script executed: Length of output: 30222
🔴 Dedup key separator collision
const key = [
'qwen',
entry.sessionId,
entry.timestamp,
entry.model, // e.g. "qwen3:latest" or "qwen3.5-coder:instruct"
entry.inputTokens,
entry.outputTokens,
entry.cacheReadTokens,
entry.reasoningTokens,
].join(':');Model names from Ollama/local Qwen registries often use the Suggested fix — use a separator that cannot appear in any of the components, or serialize with const key = JSON.stringify([
'qwen',
entry.sessionId,
entry.timestamp,
entry.model,
entry.inputTokens,
entry.outputTokens,
entry.cacheReadTokens,
entry.reasoningTokens,
]);🟡
|
|
@cubic-dev-ai review it |
@ryoppippi I have started the AI code review. It will take a few minutes to complete. |
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. |
Merge origin/main into the Qwen adapter PR and replace the previous TypeScript adapter wiring with Rust CLI support. Add Rust Qwen chat discovery, JSONL parsing, report aggregation, command/config schema integration, and documentation updates. Preserve Qwen reasoning tokens in total token counts without inflating displayed output tokens. Validation: nix develop --command pnpm run format; nix develop --command pnpm typecheck; nix develop --command pnpm run test.
Keep the Qwen Rust adapter PR focused by reverting local pre-commit sync changes to .claude/skills that are unrelated to this adapter port.
|
@coderabbitai review Updated this PR to merge current main and port Qwen support to the Rust CLI implementation. Local validation passed:
|
|
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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 334-335: The table total calculation is recomputing totals from
input/output/cache and thus misses summary.extra totals; change the table/JSON
total path to use the precomputed total on each row (row.total_tokens) instead
of summing row.input_tokens + row.output_tokens + row.cache_tokens (affecting
the block that currently calls summary.total_tokens() and the downstream
aggregation near the table total logic around the previous lines ~612-613).
Locate usages of summary.total_tokens() and the manual sum of row fields and
replace them with aggregating row.total_tokens so table and JSON outputs remain
consistent when extra_total_tokens are present.
In `@rust/crates/ccusage/src/adapter/qwen/mod.rs`:
- Around line 121-145: The test mutates the process-global QWEN_DATA_DIR which
causes races when tests run in parallel; update the test(s) (e.g.,
loads_qwen_jsonl_usage_entries and the similar test at 157-179) to set
QWEN_DATA_DIR in a safe, per-test way: capture the current env var, set
QWEN_DATA_DIR to the temp_qwen_dir value before calling load_entries, then
always restore the original value and remove the temp dir in a
panic-safe/finally manner (or use a helper like temp_env or the serial_test
attribute) so SharedArgs::default()/load_entries see the intended directory
without leaking or racing with other tests. Ensure the same pattern is applied
to the other test(s) referencing QWEN_DATA_DIR.
In `@rust/crates/ccusage/src/adapter/qwen/parser.rs`:
- Around line 165-172: The file_timestamp function currently falls back to
TimestampMs::UNIX_EPOCH on any metadata/mtime error which yields misleading 1970
dates; change the fallback to use the current time instead and emit a warning
when the metadata lookup fails. Specifically, update the error path in
file_timestamp (replace the final unwrap_or(TimestampMs::UNIX_EPOCH)) so that
failed metadata/modified/duration_since cases convert SystemTime::now() (or call
TimestampMs::now() if available) into a TimestampMs and call log::warn! (or the
crate's logger) to record the failure and filepath.
- Around line 174-185: entry_id currently concatenates user-controlled fields
with ":" in the entry_id function, which can collide if session_id, model or
timestamp contain ":"; change entry_id to produce an unambiguous key by safely
encoding or delimiting components (e.g., JSON-serialize the tuple, use
length-prefixed fields, escape delimiters, or base64-encode
session_id/model/timestamp) instead of raw ":" joining; update the entry_id
function to encode entry.session_id, entry.data.timestamp,
entry.model.as_deref().unwrap_or_default(), usage.input_tokens,
usage.output_tokens, usage.cache_read_input_tokens, and entry.extra_total_tokens
into a single non-ambiguous string (keeping other field names and logic intact).
🪄 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: 3b8d96a1-d054-4cc6-b2cb-a3f7b4afc179
📒 Files selected for processing (23)
apps/ccusage/README.mdapps/ccusage/config-schema.jsondocs/.vitepress/config.tsdocs/guide/environment-variables.mddocs/guide/getting-started.mdrust/crates/ccusage/src/adapter/all.rsrust/crates/ccusage/src/adapter/amp.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/adapter/qwen/mod.rsrust/crates/ccusage/src/adapter/qwen/parser.rsrust/crates/ccusage/src/adapter/qwen/paths.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 (4)
- rust/crates/ccusage/src/progress.rs
- docs/.vitepress/config.ts
- apps/ccusage/README.md
- docs/guide/getting-started.md
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/guide/environment-variables.md
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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/guide/environment-variables.md (1)
16-25:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winInclude
QWENin the debug grep example.After documenting
QWEN_DATA_DIR, the debug command should also match it (currently it filters onlyCLAUDE|CODEX|OPENCODE|AMP|PI_AGENT|CCUSAGE|LOG_LEVEL), otherwise users can miss Qwen env diagnostics.🤖 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/environment-variables.md` around lines 16 - 25, The debug grep example is missing QWEN so add QWEN to the diagnostic filter: update the grep/regex that currently contains CLAUDE|CODEX|OPENCODE|AMP|PI_AGENT|CCUSAGE|LOG_LEVEL to also include QWEN (and ensure it will match QWEN_DATA_DIR); locate the debug command in the docs and modify the pattern string to include the token "QWEN" so QWEN_DATA_DIR appears in the debug output.
♻️ Duplicate comments (3)
rust/crates/ccusage/src/adapter/qwen/mod.rs (1)
121-145:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winSerialize tests that mutate
QWEN_DATA_DIRand restore env panic-safely.Both tests mutate the same process-global env var without synchronization, so they can race under parallel test execution and produce flaky results.
Suggested direction
+use std::sync::{Mutex, OnceLock}; + +fn qwen_env_lock() -> &'static Mutex<()> { + static LOCK: OnceLock<Mutex<()>> = OnceLock::new(); + LOCK.get_or_init(|| Mutex::new(())) +} + #[test] fn loads_qwen_jsonl_usage_entries() { + let _guard = qwen_env_lock().lock().unwrap(); + let prev = env::var_os("QWEN_DATA_DIR"); // ... env::set_var("QWEN_DATA_DIR", &qwen_dir); @@ - env::remove_var("QWEN_DATA_DIR"); + match prev { + Some(v) => env::set_var("QWEN_DATA_DIR", v), + None => env::remove_var("QWEN_DATA_DIR"), + } }Apply the same pattern to the second test.
Also applies to: 157-179
🤖 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/qwen/mod.rs` around lines 121 - 145, The test loads_qwen_jsonl_usage_entries (and the second test at lines 157-179) mutates the global QWEN_DATA_DIR via env::set_var and then env::remove_var, which can race when tests run in parallel; change both tests to capture the previous env::var("QWEN_DATA_DIR") state, set the new value, and install a panic-safe guard (a small RAII Drop guard or use temp_env/test-env crate) that restores the original value (either by env::set_var(prev) or env::remove_var if None) when the test scope exits, and use the same pattern for temp_qwen_dir cleanup so both environment and temp directory are always cleaned up even on panic.rust/crates/ccusage/src/adapter/qwen/parser.rs (2)
174-185:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMake dedup keys unambiguous.
Building the key with
:separators can collide when any component contains:(session/model/timestamp), causing distinct entries to be dropped.Suggested patch
fn entry_id(entry: &LoadedEntry) -> String { let usage = entry.data.message.usage; - format!( - "{}:{}:{}:{}:{}:{}:{}", - entry.session_id, - entry.data.timestamp, - entry.model.as_deref().unwrap_or_default(), - usage.input_tokens, - usage.output_tokens, - usage.cache_read_input_tokens, - entry.extra_total_tokens - ) + serde_json::json!([ + entry.session_id.as_ref(), + entry.data.timestamp.as_str(), + entry.model.as_deref().unwrap_or_default(), + usage.input_tokens, + usage.output_tokens, + usage.cache_read_input_tokens, + entry.extra_total_tokens + ]) + .to_string() }🤖 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/qwen/parser.rs` around lines 174 - 185, The dedup key built in entry_id can collide because components like session_id/model/timestamp may contain ":"; update the entry_id function to produce an unambiguous key by serializing the identifying components into a single canonical representation (e.g., use serde_json::to_string on a tuple/array of (entry.session_id, entry.data.timestamp, entry.model.as_deref().unwrap_or_default(), usage.input_tokens, usage.output_tokens, usage.cache_read_input_tokens, entry.extra_total_tokens) or alternatively base64-encode/escape the string components) so separators cannot collide; modify the entry_id function to construct the key via that serialization and return the serialized string (referencing entry_id, LoadedEntry, entry.session_id, entry.data.timestamp, entry.model, usage fields).
165-172:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUse a non-epoch fallback when file mtime lookup fails.
Falling back to Unix epoch can silently push records into 1970 buckets and distort daily/monthly summaries. Use current time (and log a warning) instead.
Suggested patch
use std::{ collections::HashSet, fs::{self, File}, io::{BufRead, BufReader}, path::Path, sync::Arc, - time::UNIX_EPOCH, + time::{SystemTime, UNIX_EPOCH}, }; @@ fn file_timestamp(file: &Path) -> TimestampMs { fs::metadata(file) .and_then(|metadata| metadata.modified()) .ok() .and_then(|modified| modified.duration_since(UNIX_EPOCH).ok()) .map(|duration| TimestampMs::from_millis(duration.as_millis().min(i64::MAX as u128) as i64)) - .unwrap_or(TimestampMs::UNIX_EPOCH) + .unwrap_or_else(|| { + log::warn!("failed to read mtime for {:?}; falling back to current time", file); + SystemTime::now() + .duration_since(UNIX_EPOCH) + .ok() + .map(|duration| { + TimestampMs::from_millis(duration.as_millis().min(i64::MAX as u128) as i64) + }) + .unwrap_or(TimestampMs::UNIX_EPOCH) + }) }🤖 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/qwen/parser.rs` around lines 165 - 172, file_timestamp currently falls back to TimestampMs::UNIX_EPOCH on any metadata/mtime error which skews time-based aggregations; change the fallback to use the current system time (SystemTime::now()) converted to TimestampMs instead and emit a warning when the metadata or modified() call fails. Update the file_timestamp function to attempt fs::metadata(...).and_then(...).map(...) as before but on the final unwrap_or call compute TimestampMs from SystemTime::now(), and use the crate's logging facility (e.g., log::warn! or tracing::warn!) to log the original error or a short warning message indicating mtime lookup failed and current time was used.
🧹 Nitpick comments (2)
rust/crates/ccusage/src/output.rs (1)
69-84: ⚡ Quick winDerive aggregated
totalTokensfromUsageSummary::total_tokens()directly.
totals_jsoncurrently re-implements token total math. Reusingrow.total_tokens()avoids drift if token fields evolve again.♻️ Proposed refactor
pub(crate) fn totals_json(rows: &[UsageSummary]) -> Value { let input = rows.iter().map(|row| row.input_tokens).sum::<u64>(); let output = rows.iter().map(|row| row.output_tokens).sum::<u64>(); let cache_create = rows .iter() .map(|row| row.cache_creation_tokens) .sum::<u64>(); let cache_read = rows.iter().map(|row| row.cache_read_tokens).sum::<u64>(); - let extra = rows.iter().map(|row| row.extra_total_tokens).sum::<u64>(); + let total_tokens = rows.iter().map(|row| row.total_tokens()).sum::<u64>(); let mut value = json!({ "inputTokens": input, "outputTokens": output, "cacheCreationTokens": cache_create, "cacheReadTokens": cache_read, - "totalTokens": input + output + cache_create + cache_read + extra, + "totalTokens": total_tokens, "totalCost": rows.iter().map(|row| row.total_cost).sum::<f64>(), });🤖 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 69 - 84, In totals_json, replace the manual aggregation used to set "totalTokens" (currently input + output + cache_create + cache_read + extra) by summing each row's UsageSummary::total_tokens() to avoid duplicating token arithmetic; update the totals_json function to compute totalTokens as rows.iter().map(|row| row.total_tokens()).sum::<u64>() and remove the duplicated calculation (you can keep the individual input/output/cache sums for separate fields but derive the aggregate only from UsageSummary::total_tokens()).rust/crates/ccusage/src/main.rs (1)
810-810: ⚡ Quick winAdd at least one non-zero
extra_total_tokensfixture assertion.All updated fixtures set
extra_total_tokensto0, so the new total-token aggregation path is not actually exercised.🧪 Minimal test hardening example
let entry = LoadedEntry { data: UsageEntry { session_id: Some("thread-a".to_string()), @@ model: Some("claude-sonnet-4-20250514".to_string()), usage_limit_reset_time: None, - extra_total_tokens: 0, + extra_total_tokens: 5, }; @@ - assert_eq!(report["daily"][0]["totalTokens"], 180); + assert_eq!(report["daily"][0]["totalTokens"], 185);Also applies to: 895-895, 942-942
🤖 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/main.rs` at line 810, One or more test fixtures currently set extra_total_tokens to 0 so the total-token aggregation path isn't exercised; pick at least one fixture where extra_total_tokens is assigned (the fields named extra_total_tokens in the test data) and change it to a non-zero value (e.g., 1 or 5), then update the corresponding expected aggregated total assertions to include that extra amount so the aggregation logic (where totals are computed) is actually validated; ensure you update every related fixture/assertion pair you modify so tests remain consistent.
🤖 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/qwen/parser.rs`:
- Around line 152-160: The loop currently treats any cost <= 0.0 as a miss and
keeps trying aliases; change the lookup contract and loop to return the first
successful lookup even if it yields 0.0. Update calculate_cost_for_usage to
return an Option<f64> (or otherwise signal success vs miss), then in the
candidate loop call calculate_cost_for_usage(Some(&candidate), usage, None,
mode, pricing) and if it returns Some(cost) immediately return that cost (do not
gate on cost > 0.0); only continue the loop on None (lookup miss).
---
Outside diff comments:
In `@docs/guide/environment-variables.md`:
- Around line 16-25: The debug grep example is missing QWEN so add QWEN to the
diagnostic filter: update the grep/regex that currently contains
CLAUDE|CODEX|OPENCODE|AMP|PI_AGENT|CCUSAGE|LOG_LEVEL to also include QWEN (and
ensure it will match QWEN_DATA_DIR); locate the debug command in the docs and
modify the pattern string to include the token "QWEN" so QWEN_DATA_DIR appears
in the debug output.
---
Duplicate comments:
In `@rust/crates/ccusage/src/adapter/qwen/mod.rs`:
- Around line 121-145: The test loads_qwen_jsonl_usage_entries (and the second
test at lines 157-179) mutates the global QWEN_DATA_DIR via env::set_var and
then env::remove_var, which can race when tests run in parallel; change both
tests to capture the previous env::var("QWEN_DATA_DIR") state, set the new
value, and install a panic-safe guard (a small RAII Drop guard or use
temp_env/test-env crate) that restores the original value (either by
env::set_var(prev) or env::remove_var if None) when the test scope exits, and
use the same pattern for temp_qwen_dir cleanup so both environment and temp
directory are always cleaned up even on panic.
In `@rust/crates/ccusage/src/adapter/qwen/parser.rs`:
- Around line 174-185: The dedup key built in entry_id can collide because
components like session_id/model/timestamp may contain ":"; update the entry_id
function to produce an unambiguous key by serializing the identifying components
into a single canonical representation (e.g., use serde_json::to_string on a
tuple/array of (entry.session_id, entry.data.timestamp,
entry.model.as_deref().unwrap_or_default(), usage.input_tokens,
usage.output_tokens, usage.cache_read_input_tokens, entry.extra_total_tokens) or
alternatively base64-encode/escape the string components) so separators cannot
collide; modify the entry_id function to construct the key via that
serialization and return the serialized string (referencing entry_id,
LoadedEntry, entry.session_id, entry.data.timestamp, entry.model, usage fields).
- Around line 165-172: file_timestamp currently falls back to
TimestampMs::UNIX_EPOCH on any metadata/mtime error which skews time-based
aggregations; change the fallback to use the current system time
(SystemTime::now()) converted to TimestampMs instead and emit a warning when the
metadata or modified() call fails. Update the file_timestamp function to attempt
fs::metadata(...).and_then(...).map(...) as before but on the final unwrap_or
call compute TimestampMs from SystemTime::now(), and use the crate's logging
facility (e.g., log::warn! or tracing::warn!) to log the original error or a
short warning message indicating mtime lookup failed and current time was used.
---
Nitpick comments:
In `@rust/crates/ccusage/src/main.rs`:
- Line 810: One or more test fixtures currently set extra_total_tokens to 0 so
the total-token aggregation path isn't exercised; pick at least one fixture
where extra_total_tokens is assigned (the fields named extra_total_tokens in the
test data) and change it to a non-zero value (e.g., 1 or 5), then update the
corresponding expected aggregated total assertions to include that extra amount
so the aggregation logic (where totals are computed) is actually validated;
ensure you update every related fixture/assertion pair you modify so tests
remain consistent.
In `@rust/crates/ccusage/src/output.rs`:
- Around line 69-84: In totals_json, replace the manual aggregation used to set
"totalTokens" (currently input + output + cache_create + cache_read + extra) by
summing each row's UsageSummary::total_tokens() to avoid duplicating token
arithmetic; update the totals_json function to compute totalTokens as
rows.iter().map(|row| row.total_tokens()).sum::<u64>() and remove the duplicated
calculation (you can keep the individual input/output/cache sums for separate
fields but derive the aggregate only from UsageSummary::total_tokens()).
🪄 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: 5096d0ec-546d-4907-b0fd-a173ba4c8616
📒 Files selected for processing (33)
apps/ccusage/README.mdapps/ccusage/config-schema.jsondocs/.vitepress/config.tsdocs/guide/all-reports.mddocs/guide/claude/index.mddocs/guide/codex/index.mddocs/guide/config-files.mddocs/guide/configuration.mddocs/guide/daily-reports.mddocs/guide/environment-variables.mddocs/guide/getting-started.mddocs/guide/index.mddocs/guide/monthly-reports.mddocs/guide/qwen/index.mddocs/guide/session-reports.mdrust/crates/ccusage/src/adapter/all.rsrust/crates/ccusage/src/adapter/amp.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/adapter/qwen/mod.rsrust/crates/ccusage/src/adapter/qwen/parser.rsrust/crates/ccusage/src/adapter/qwen/paths.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
# Conflicts: # apps/ccusage/README.md # docs/.vitepress/config.ts # docs/guide/all-reports.md # docs/guide/claude/index.md # docs/guide/codex/index.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/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/progress.rs # rust/crates/ccusage/src/types.rs
Keep the generated config schema formatting in sync with the pre-commit generator after merging origin/main into the Qwen adapter branch.
|
@coderabbitai review Pushed the Qwen branch after merging the latest origin/main and resolving conflicts against current native Rust all-agent, Copilot/Gemini, config schema, pricing, and docs changes. Changes in this push:
Local validation:
Pre-push validation also passed clippy/oxfmt/eslint hooks. |
|
✅ Actions performedFull review triggered. |
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. |
|
@coderabbitai review Pushed a CI fix for the Qwen branch: restored the generated schema property expectation to the complete latest-main set plus qwen. Local checks:
|
|
✅ Actions performedFull review triggered. |
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
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)
145-152:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix duplicate loader index for Gemini/Kimi ordering.
Line 151 reuses index
10already assigned on Line 145. That makes Gemini vs Kimi load/error ordering nondeterministic.🔧 Proposed fix
AgentLoadSpec { - index: 10, + index: 11, agent: "kimi", progress_agent: crate::progress::UsageLoadAgent::Kimi, load: Box::new(|| load_kimi_rows(load_kind, shared, &pricing)), },🤖 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 145 - 152, AgentLoadSpec entries for agent "gemini" and "kimi" currently both use index 10 which breaks deterministic ordering; update the duplicate index on the "kimi" AgentLoadSpec to the next unique value (e.g., 11) so ordering/errors are deterministic. Locate the AgentLoadSpec block that sets agent: "gemini" (uses load: Box::new(|| load_gemini_rows(...))) and the following AgentLoadSpec with agent: "kimi" and change the index field there to a unique value (match adjacent indices sequence) so each AgentLoadSpec has a distinct index.
🤖 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/qwen/mod.rs`:
- Around line 105-115: The comparison in filter_session_summaries currently
compares the normalized row date (YYYYMMDD) to raw shared.since/shared.until
which may still be YYYY-MM-DD; update filter_session_summaries (the rows.retain
closure using row.last_activity) to normalize shared.since and shared.until to
the same format as the row date (remove '-' or otherwise produce YYYYMMDD)
before doing the >= / <= comparisons so that valid inputs like "YYYY-MM-DD" are
correctly compared against the row date.
---
Outside diff comments:
In `@rust/crates/ccusage/src/adapter/all.rs`:
- Around line 145-152: AgentLoadSpec entries for agent "gemini" and "kimi"
currently both use index 10 which breaks deterministic ordering; update the
duplicate index on the "kimi" AgentLoadSpec to the next unique value (e.g., 11)
so ordering/errors are deterministic. Locate the AgentLoadSpec block that sets
agent: "gemini" (uses load: Box::new(|| load_gemini_rows(...))) and the
following AgentLoadSpec with agent: "kimi" and change the index field there to a
unique value (match adjacent indices sequence) so each AgentLoadSpec has a
distinct index.
🪄 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: 0e3c49a2-16ed-4427-a50e-030ff9bc6d70
📒 Files selected for processing (27)
apps/ccusage/README.mdapps/ccusage/config-schema.jsondocs/.vitepress/config.tsdocs/guide/all-reports.mddocs/guide/claude/index.mddocs/guide/codex/index.mddocs/guide/config-files.mddocs/guide/configuration.mddocs/guide/daily-reports.mddocs/guide/environment-variables.mddocs/guide/getting-started.mddocs/guide/index.mddocs/guide/monthly-reports.mddocs/guide/qwen/index.mddocs/guide/session-reports.mddocs/index.mdrust/crates/ccusage/src/adapter/all.rsrust/crates/ccusage/src/adapter/mod.rsrust/crates/ccusage/src/adapter/qwen/mod.rsrust/crates/ccusage/src/adapter/qwen/parser.rsrust/crates/ccusage/src/adapter/qwen/paths.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.rs
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. |
# Conflicts: # apps/ccusage/README.md # docs/guide/all-reports.md # docs/guide/config-files.md # docs/guide/configuration.md # docs/guide/getting-started.md # docs/guide/index.md # docs/index.md # rust/crates/ccusage/src/adapter/all.rs # rust/crates/ccusage/src/cli.rs # rust/crates/ccusage/src/config_schema.rs
|
@coderabbitai review Synced this Qwen PR with the latest main after #1019 landed, resolved the OpenClaw/Kimi integration conflicts, kept the Qwen env grep/schema fixes, regenerated/formatted the config schema, and pushed the updates. Local checks run:
|
|
✅ Actions performedFull review triggered. |
Normalize since/until bounds before comparing Qwen session summaries so ISO date inputs compare against the YYYYMMDD row key consistently. Adds a regression test for filtering Qwen session rows with matching ISO date bounds.
|
@coderabbitai review Addressed the Qwen session date-filter review thread and pushed a follow-up fix. Local checks run:
|
|
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
rust/crates/ccusage/src/adapter/all.rs (1)
770-770:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUse
row.total_tokensfor table totals to keep table/JSON consistent.The table path recomputes totals from input/output/cache fields, which can undercount adapters that provide additional token components via
total_tokens. Userow.total_tokensdirectly for both row and footer totals.Proposed fix
- let table_total_tokens = rows.iter().map(table_total_tokens).sum::<u64>(); + let table_total_tokens = rows.iter().map(|row| row.total_tokens).sum::<u64>(); @@ - format_number(table_total_tokens(row)), + format_number(row.total_tokens), @@ fn table_total_tokens(row: &AllRow) -> u64 { - row.input_tokens - .saturating_add(row.output_tokens) - .saturating_add(row.cache_creation_tokens) - .saturating_add(row.cache_read_tokens) + row.total_tokens }Also applies to: 923-933
🤖 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` at line 770, The table total calculation currently recomputes totals from input/output/cache fields (using the table_total_tokens mapper), which can undercount adapters that already provide a combined total in row.total_tokens; change the table/footer aggregation to use row.total_tokens directly (i.e., map rows.iter().map(|row| row.total_tokens).sum::<u64>()) and similarly ensure any footer/summary logic that sums per-row components (the code referenced by table_total_tokens and the footer totals logic) uses row.total_tokens so table and JSON outputs remain consistent.
🤖 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 `@docs/guide/claude/index.md`:
- Line 3: Update the peer sources lists in the paragraph that begins "ccusage
can read Claude Code usage data..." and the repeated peer list later in the file
to include the missing adapters "Hermes Agent", "Goose", and "OpenClaw" so the
list matches supported namespaces/env-var coverage; ensure both occurrences (the
initial description of supported local data sources and the later peer sources
list) are edited to add these three names in the same style and punctuation as
the existing entries (e.g., after "Kimi" or before "GitHub Copilot CLI") so the
documentation remains consistent.
In `@docs/guide/codex/index.md`:
- Line 7: Update the sentence that lists peer tools for Codex support so it
includes the missing peers; specifically edit the line containing "Codex uses
the same unified and focused report model as Claude Code, OpenCode, Amp,
pi-agent, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI" to also include
"Hermes Agent, Goose, and OpenClaw" so the peer-source list is complete and
consistent with the support matrix.
In `@rust/crates/ccusage/src/adapter/qwen/parser.rs`:
- Around line 114-116: The TokenUsageRaw construction for billable_usage
currently adds output_tokens and reasoning_tokens directly which can overflow;
change the arithmetic to use saturating_add() when computing output_tokens
(e.g., set output_tokens: output_tokens.saturating_add(reasoning_tokens)) and
keep the rest of the struct spread from display_usage so the resulting
TokenUsageRaw uses saturating addition like the pattern in goose.rs to avoid
integer wraparound.
---
Duplicate comments:
In `@rust/crates/ccusage/src/adapter/all.rs`:
- Line 770: The table total calculation currently recomputes totals from
input/output/cache fields (using the table_total_tokens mapper), which can
undercount adapters that already provide a combined total in row.total_tokens;
change the table/footer aggregation to use row.total_tokens directly (i.e., map
rows.iter().map(|row| row.total_tokens).sum::<u64>()) and similarly ensure any
footer/summary logic that sums per-row components (the code referenced by
table_total_tokens and the footer totals logic) uses row.total_tokens so table
and JSON outputs remain consistent.
🪄 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: 7c723558-5c7a-4426-ab49-498aec56fbdb
📒 Files selected for processing (27)
apps/ccusage/README.mdapps/ccusage/config-schema.jsondocs/.vitepress/config.tsdocs/guide/all-reports.mddocs/guide/claude/index.mddocs/guide/codex/index.mddocs/guide/config-files.mddocs/guide/configuration.mddocs/guide/daily-reports.mddocs/guide/environment-variables.mddocs/guide/getting-started.mddocs/guide/index.mddocs/guide/monthly-reports.mddocs/guide/qwen/index.mddocs/guide/session-reports.mddocs/index.mdrust/crates/ccusage/src/adapter/all.rsrust/crates/ccusage/src/adapter/mod.rsrust/crates/ccusage/src/adapter/qwen/mod.rsrust/crates/ccusage/src/adapter/qwen/parser.rsrust/crates/ccusage/src/adapter/qwen/paths.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.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. |
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.
♻️ Duplicate comments (2)
docs/guide/claude/index.md (1)
3-3:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winComplete the source lists to avoid excluding supported adapters.
Lines 3 and 78 still omit Hermes Agent, Goose, and OpenClaw from the peer source lists, which can mislead readers about supported namespaces.
📝 Suggested doc fix
-ccusage can read Claude Code usage data as one of its supported local data sources. Claude Code is no longer treated as the only ccusage target; it uses the same unified and focused report model as Codex, OpenCode, Amp, pi-agent, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI. +ccusage can read Claude Code usage data as one of its supported local data sources. Claude Code is no longer treated as the only ccusage target; it uses the same unified and focused report model as Codex, OpenCode, Amp, Hermes Agent, pi-agent, Goose, OpenClaw, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI.Also applies to Line 78.
🤖 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/claude/index.md` at line 3, Update the peer source lists in the Claude guide where the sentence starting "ccusage can read Claude Code usage data..." (and the repeated peer-source list later in the document) omits supported adapters; add "Hermes Agent", "Goose", and "OpenClaw" to those comma-separated source lists so the examples list all supported namespaces consistently (match the style and punctuation used for existing entries like Codex, OpenCode, Amp, pi-agent, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI).docs/guide/codex/index.md (1)
7-7:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winKeep the peer-source list complete for consistency.
Line 7 still omits Hermes Agent, Goose, and OpenClaw from the peer source list, which makes the support matrix appear narrower than it is.
📝 Suggested doc fix
-ccusage can read OpenAI Codex CLI session logs as one of its supported local data sources. Codex uses the same unified and focused report model as Claude Code, OpenCode, Amp, pi-agent, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI. +ccusage can read OpenAI Codex CLI session logs as one of its supported local data sources. Codex uses the same unified and focused report model as Claude Code, OpenCode, Amp, Hermes Agent, pi-agent, Goose, OpenClaw, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI.🤖 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/codex/index.md` at line 7, Update the peer-source list in the sentence that starts "ccusage can read OpenAI Codex CLI session logs..." (the line listing "Claude Code, OpenCode, Amp, pi-agent, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI") to also include "Hermes Agent", "Goose", and "OpenClaw" so the supported local data sources list is complete and consistent with the support matrix.
🤖 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 `@docs/guide/claude/index.md`:
- Line 3: Update the peer source lists in the Claude guide where the sentence
starting "ccusage can read Claude Code usage data..." (and the repeated
peer-source list later in the document) omits supported adapters; add "Hermes
Agent", "Goose", and "OpenClaw" to those comma-separated source lists so the
examples list all supported namespaces consistently (match the style and
punctuation used for existing entries like Codex, OpenCode, Amp, pi-agent, Kilo,
Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI).
In `@docs/guide/codex/index.md`:
- Line 7: Update the peer-source list in the sentence that starts "ccusage can
read OpenAI Codex CLI session logs..." (the line listing "Claude Code, OpenCode,
Amp, pi-agent, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI") to also
include "Hermes Agent", "Goose", and "OpenClaw" so the supported local data
sources list is complete and consistent with the support matrix.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2f9b91f9-a340-48db-917a-47e8d055d129
📒 Files selected for processing (27)
apps/ccusage/README.mdapps/ccusage/config-schema.jsondocs/.vitepress/config.tsdocs/guide/all-reports.mddocs/guide/claude/index.mddocs/guide/codex/index.mddocs/guide/config-files.mddocs/guide/configuration.mddocs/guide/daily-reports.mddocs/guide/environment-variables.mddocs/guide/getting-started.mddocs/guide/index.mddocs/guide/monthly-reports.mddocs/guide/qwen/index.mddocs/guide/session-reports.mddocs/index.mdrust/crates/ccusage/src/adapter/all.rsrust/crates/ccusage/src/adapter/mod.rsrust/crates/ccusage/src/adapter/qwen/mod.rsrust/crates/ccusage/src/adapter/qwen/parser.rsrust/crates/ccusage/src/adapter/qwen/paths.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.rs
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. |
# Conflicts: # apps/ccusage/README.md # docs/guide/all-reports.md # docs/guide/claude/index.md # docs/guide/codex/index.md # docs/guide/config-files.md # docs/guide/configuration.md # docs/guide/environment-variables.md # docs/guide/index.md # docs/index.md # rust/crates/ccusage/src/adapter/all.rs # rust/crates/ccusage/src/cli.rs # rust/crates/ccusage/src/config_schema.rs
Remove the unused duplicate total_tokens binding exposed by the latest main merge so clippy passes with -D warnings.
|
@coderabbitai review Synced this Qwen PR again after #1028 and #1029 landed, resolved the Droid/Codebuff/OpenClaw/Kimi integration conflicts, addressed the latest CodeRabbit doc/parser threads, regenerated/formatted schema output, and pushed the updates. Local checks run:
|
|
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
♻️ Duplicate comments (1)
rust/crates/ccusage/src/adapter/all.rs (1)
491-508:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUse
row.total_tokensin table totals to keep all-source table and JSON consistent.With Qwen rows,
summary.total_tokens()can include extra totals, but non-compact table output still recomputes totals from input/output/cache (Line 815 and Line 973), which can underreport displayed totals.💡 Minimal fix
- let table_total_tokens = rows.iter().map(table_total_tokens).sum::<u64>(); + let table_total_tokens = rows.iter().map(|row| row.total_tokens).sum::<u64>(); @@ - format_number(table_total_tokens(row)), + format_number(row.total_tokens), @@ fn table_total_tokens(row: &AllRow) -> u64 { - row.input_tokens - .saturating_add(row.output_tokens) - .saturating_add(row.cache_creation_tokens) - .saturating_add(row.cache_read_tokens) + row.total_tokens }🤖 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 491 - 508, Some table totals recompute tokens from input/output/cache and underreport Qwen totals; update the table/JSON totals logic to use each row's precomputed total (row.total_tokens) instead of summing input/output/cache so it matches summary.total_tokens(), e.g. change the totals accumulation in the table rendering code (the code path that builds rows via summary_rows and later computes totals for non-compact output) to use row.total_tokens for the tokens column and overall totals.
🤖 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 491-508: Some table totals recompute tokens from
input/output/cache and underreport Qwen totals; update the table/JSON totals
logic to use each row's precomputed total (row.total_tokens) instead of summing
input/output/cache so it matches summary.total_tokens(), e.g. change the totals
accumulation in the table rendering code (the code path that builds rows via
summary_rows and later computes totals for non-compact output) to use
row.total_tokens for the tokens column and overall totals.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b728da17-1643-4130-9685-56b39e6e0e81
📒 Files selected for processing (27)
apps/ccusage/README.mdapps/ccusage/config-schema.jsondocs/.vitepress/config.tsdocs/guide/all-reports.mddocs/guide/claude/index.mddocs/guide/codex/index.mddocs/guide/config-files.mddocs/guide/configuration.mddocs/guide/daily-reports.mddocs/guide/environment-variables.mddocs/guide/getting-started.mddocs/guide/index.mddocs/guide/monthly-reports.mddocs/guide/qwen/index.mddocs/guide/session-reports.mddocs/index.mdrust/crates/ccusage/src/adapter/all.rsrust/crates/ccusage/src/adapter/mod.rsrust/crates/ccusage/src/adapter/qwen/mod.rsrust/crates/ccusage/src/adapter/qwen/parser.rsrust/crates/ccusage/src/adapter/qwen/paths.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.rs
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. |
Try This PR
Summary
Validation
Summary by cubic
Add a Rust-based Qwen usage adapter that reads local Qwen Code JSONL chats and plugs into focused and unified reports. Totals include reasoning tokens; tables now prefer adapter-provided
totalTokensand fall back to a computed sum when missing so counts and costs match Qwen logs and LiteLLM pricing.New Features
QWEN_DATA_DIRor~/.qwenatprojects/*/chats/*.jsonl.ccusage qwen daily|monthly|session; included in unified reports and CLI help; supports--offline.usageMetadata(prompt, candidates, thoughts, cache reads); include reasoning tokens intotalTokens.qwen/model, andalibaba/model; config schema addsqwennamespace; docs add a Qwen guide and env var updates.Bug Fixes
qwen; restore full property set in tests.qwen sessionsosince/untilfiltering works correctly.totalTokens; fall back to input+output+cache sums; remove a duplicate binding to satisfy linting.Written for commit b957810. Summary will update on new commits. Review in cubic
Summary by CodeRabbit
New Features
Documentation