Skip to content

fix(usage,sessions): resolve token/subscription usage integration disparities - #760

Merged
junhoyeo merged 3 commits into
mainfrom
fix/usage-disparities
Jun 22, 2026
Merged

fix(usage,sessions): resolve token/subscription usage integration disparities#760
junhoyeo merged 3 commits into
mainfrom
fix/usage-disparities

Conversation

@junhoyeo

@junhoyeo junhoyeo commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the actionable disparities found in an audit of all token-usage + subscription-usage integrations (11 subscription providers in usage/*.rs, 34 session parsers in sessions/*.rs). The unifying theme: omit or fall back, never fabricate — several integrations emitted confident-looking empty/zero/100% data or silently dropped real records.

Subscription-usage providers (fix(usage))

  • amp (high — confirmed live): fetch() returned Ok with no metrics when its $X/$Y display-text scrape didn't match, rendering a bare "Amp" header with no data. Now bails so fetch_all drops it. Also made the byte-offset slicing char-boundary-safe (it could panic on multibyte display_text).
  • copilot, minimax_tokenplan: same empty-metrics guard / skip empty rows.
  • warp: the "Spend" and no-limit "requests" metrics set 0% remaining → a false "exhausted" bar; now read full with the figure in the label. Dropped the placeholder "Aggregate API cache" plan string.
  • zai: a missing percentage rendered as "100% left"; now skipped.
  • kimi: dedup key now includes resets_at so distinct windows with equal numbers don't collapse.

Token-usage session parsers (fix(sessions))

  • Provider canonicalization (opencode, micode, kilo, mux, antigravity, jcode): these stored a raw provider field verbatim, so aliases (fireworks, vertex, gemini, azure) split into separate aggregation buckets and undercounted per-provider totals. Now routed through provider_identity::canonical_provider, matching the other parsers.
  • codex: provider defaulted to "openai" when session meta was absent; now infers from the model first, keeping "openai" only as the final fallback.
  • gjc, pi (high — data loss): dropped messages that had valid tokens but no provider field; now recover via model inference (fallback "gjc"/"pi") instead of discarding the spend.
  • droid: dropped a whole session when its timestamp resolved to 0; now falls back to file mtime.
  • qwen, mux: had no dedup key (double-count risk on incremental re-parse); now keyed stably.
  • utils / kiro / goose: seconds→ms conversion uses saturating_mul; timestamp paths hardened against overflow.
  • kiro / goose / crush: documented source limitations (estimated tokens / heuristic reasoning / cost-only) so 0/estimated values aren't mistaken for bugs.

Deliberately NOT changed

  • roocode / cline / kilocode intentionally preserve raw apiProtocol (has a test) — not canonicalized.
  • grok input-only, codex API-specific credit fields, amp parser's Anthropic-first default — documented source behavior.

Testing

  • cargo build --workspace clean; cargo test --workspace 1850 passed / 0 failed; cargo clippy --workspace --all-targets clean; cargo fmt --all -- --check passes.
  • New unit tests for the amp (empty/multibyte) and warp (full-not-exhausted) behavior changes.
  • Test updates were limited to ones encoding the old behavior: fireworks → fireworks_ai canonicalization and gjc now keeping a previously-dropped record.
  • Live-validated on a real machine: the Amp ghost-row is gone (Amp drops cleanly instead of rendering an empty header); Claude/Codex/Copilot render full data.
  • Also swept 3 pre-existing clippy useless-vec warnings in report.rs test helpers to keep the gate green (style(report) commit).

Summary by cubic

Fixes incorrect or missing usage data across subscription providers and session parsers by failing soft on empty responses, canonicalizing providers, and adding safe fallbacks. This removes empty headers, corrects Warp bars, prevents double counts, and improves per‑provider totals.

  • Bug Fixes

    • Subscription usage: bail on empty metrics (Amp, Copilot, MiniMax); make Amp parsing char‑boundary safe; show Warp "Spend" and unlimited "Requests" as full with value in label; skip Zai limits without percentage; include resets_at in Kimi dedup.
    • Sessions: canonicalize providers (opencode, micode, kilo, mux, antigravity, jcode); Codex infers provider from model before "openai"; GJC/PI keep messages with missing provider via model inference (fallback to "gjc"/"pi"); Droid falls back to file mtime when timestamp is 0.
    • Data integrity: add stable dedup keys (Qwen, Mux, Zed); overflow‑safe seconds→ms conversions (utils, Kiro, Goose).
  • Refactors

    • Documented source limits: Kiro/Goose use estimated tokens; Crush is cost‑only (zero tokens expected).
    • Dropped Warp placeholder plan; minor test updates and clippy cleanups.

Written for commit fbd2bd6. Summary will update on new commits.

Review in cubic

junhoyeo added 3 commits June 22, 2026 17:46
Audit found several subscription-usage providers emitting confident-looking
empty/zero data instead of omitting it:

- amp: fetch() returned Ok with no metrics when its $X/$Y display-text scrape
  didn't match, rendering a bare "Amp" header. Now bails so it's dropped. Also
  made the byte-offset slicing char-boundary-safe (could panic on multibyte
  display_text).
- copilot, minimax_tokenplan: same empty-metrics guard / skip empty rows.
- warp: "Spend" and no-limit "requests" metrics set 0% remaining, rendering a
  false "exhausted" bar; now read full with the figure kept in the label.
  Dropped the placeholder "Aggregate API cache" plan string.
- zai: a missing percentage was rendered as "100% left"; now skipped.
- kimi: include resets_at in the metric dedup key so distinct windows with
  equal numbers don't collapse.

Adds unit tests for the amp and warp behavior changes.

Confidence: high
Scope-risk: narrow
Cross-parser audit found inconsistent provider attribution and silent data loss:

- Provider canonicalization (opencode, micode, kilo, mux, antigravity, jcode):
  these stored a raw provider field verbatim, so aliases (fireworks, vertex,
  gemini, azure) split into separate aggregation buckets. Now routed through
  provider_identity::canonical_provider like the other parsers.
- codex: provider defaulted to "openai" when session meta was absent; now infers
  from the model first, keeping "openai" only as the final fallback.
- gjc, pi: dropped messages with valid tokens but no provider field; now recover
  via model inference (fallback "gjc"/"pi") instead of discarding the spend.
- droid: dropped a whole session when its timestamp resolved to 0; now falls
  back to file mtime.
- qwen, mux: had no dedup key (double-count risk on re-parse); now keyed stably.
- utils: seconds->ms conversion uses saturating_mul; kiro/goose timestamp paths
  hardened against overflow.
- kiro (estimated tokens), goose (heuristic reasoning), crush (cost-only, zero
  tokens): documented these source limitations so 0/estimated values aren't
  mistaken for bugs.

Updates tests that encoded the OLD behavior: fireworks->fireworks_ai
canonicalization, and gjc now keeping a previously-dropped record.

Confidence: high
Scope-risk: moderate
Directive: roocode/cline/kilocode intentionally preserve raw apiProtocol — do NOT canonicalize them
Pre-existing clippy useless-vec warnings in report.rs test helpers, swept to keep the clippy gate clean.
@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
tokscale Ignored Ignored Preview Jun 22, 2026 8:48am

Request Review

@junhoyeo
junhoyeo merged commit 5ff7bf4 into main Jun 22, 2026
14 checks passed
@junhoyeo
junhoyeo deleted the fix/usage-disparities branch June 22, 2026 08:48

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fbd2bd6070

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// "0% used / 100% left" from a missing field.
let pct = match limit.percentage {
Some(p) => p.clamp(0.0, 100.0),
None => continue,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Drop Z.ai output when all quota rows are skipped

When Z.ai returns limits that omit percentage (the exact case handled here), every row can be skipped but fetch() still returns Ok(UsageOutput { metrics: vec![] }), so fetch_all() includes it and the renderer shows a bare Z.ai card instead of failing soft like the Amp/Copilot/MiniMax guards added in this commit. Add an empty-metrics bail after assembling metrics (or compute from other fields) so unparseable quota responses are dropped rather than rendered as an empty provider.

Useful? React with 👍 / 👎.

pinion05 added a commit to pinion05/tokscale that referenced this pull request Jun 23, 2026
…parities (junhoyeo#760)

* fix(usage): make subscription providers fail-soft

Audit found several subscription-usage providers emitting confident-looking
empty/zero data instead of omitting it:

- amp: fetch() returned Ok with no metrics when its $X/$Y display-text scrape
  didn't match, rendering a bare "Amp" header. Now bails so it's dropped. Also
  made the byte-offset slicing char-boundary-safe (could panic on multibyte
  display_text).
- copilot, minimax_tokenplan: same empty-metrics guard / skip empty rows.
- warp: "Spend" and no-limit "requests" metrics set 0% remaining, rendering a
  false "exhausted" bar; now read full with the figure kept in the label.
  Dropped the placeholder "Aggregate API cache" plan string.
- zai: a missing percentage was rendered as "100% left"; now skipped.
- kimi: include resets_at in the metric dedup key so distinct windows with
  equal numbers don't collapse.

Adds unit tests for the amp and warp behavior changes.

Confidence: high
Scope-risk: narrow

* fix(sessions): canonicalize providers and harden token parsers

Cross-parser audit found inconsistent provider attribution and silent data loss:

- Provider canonicalization (opencode, micode, kilo, mux, antigravity, jcode):
  these stored a raw provider field verbatim, so aliases (fireworks, vertex,
  gemini, azure) split into separate aggregation buckets. Now routed through
  provider_identity::canonical_provider like the other parsers.
- codex: provider defaulted to "openai" when session meta was absent; now infers
  from the model first, keeping "openai" only as the final fallback.
- gjc, pi: dropped messages with valid tokens but no provider field; now recover
  via model inference (fallback "gjc"/"pi") instead of discarding the spend.
- droid: dropped a whole session when its timestamp resolved to 0; now falls
  back to file mtime.
- qwen, mux: had no dedup key (double-count risk on re-parse); now keyed stably.
- utils: seconds->ms conversion uses saturating_mul; kiro/goose timestamp paths
  hardened against overflow.
- kiro (estimated tokens), goose (heuristic reasoning), crush (cost-only, zero
  tokens): documented these source limitations so 0/estimated values aren't
  mistaken for bugs.

Updates tests that encoded the OLD behavior: fireworks->fireworks_ai
canonicalization, and gjc now keeping a previously-dropped record.

Confidence: high
Scope-risk: moderate
Directive: roocode/cline/kilocode intentionally preserve raw apiProtocol — do NOT canonicalize them

* style(report): drop useless vec! in test helpers

Pre-existing clippy useless-vec warnings in report.rs test helpers, swept to keep the clippy gate clean.
makoMakoGo added a commit to makoMakoGo/tokscale that referenced this pull request Jun 23, 2026
ported from upstream junhoyeo#735
ported from upstream junhoyeo#737
ported from upstream junhoyeo#747
ported from upstream junhoyeo#750
ported from upstream junhoyeo#752
ported from upstream junhoyeo#760
ported from upstream junhoyeo#766
makoMakoGo added a commit to makoMakoGo/tokscale that referenced this pull request Jun 23, 2026
ported from upstream junhoyeo#760
ported from upstream junhoyeo#762
ported from upstream junhoyeo#763
ported from upstream junhoyeo#764
makoMakoGo added a commit to makoMakoGo/tokscale that referenced this pull request Jun 23, 2026
ported from upstream junhoyeo#728
ported from upstream junhoyeo#757
ported from upstream junhoyeo#759
ported from upstream junhoyeo#760
t1000040 pushed a commit to tmobi-internal/tokscale that referenced this pull request Jun 30, 2026
…parities (junhoyeo#760)

* fix(usage): make subscription providers fail-soft

Audit found several subscription-usage providers emitting confident-looking
empty/zero data instead of omitting it:

- amp: fetch() returned Ok with no metrics when its $X/$Y display-text scrape
  didn't match, rendering a bare "Amp" header. Now bails so it's dropped. Also
  made the byte-offset slicing char-boundary-safe (could panic on multibyte
  display_text).
- copilot, minimax_tokenplan: same empty-metrics guard / skip empty rows.
- warp: "Spend" and no-limit "requests" metrics set 0% remaining, rendering a
  false "exhausted" bar; now read full with the figure kept in the label.
  Dropped the placeholder "Aggregate API cache" plan string.
- zai: a missing percentage was rendered as "100% left"; now skipped.
- kimi: include resets_at in the metric dedup key so distinct windows with
  equal numbers don't collapse.

Adds unit tests for the amp and warp behavior changes.

Confidence: high
Scope-risk: narrow

* fix(sessions): canonicalize providers and harden token parsers

Cross-parser audit found inconsistent provider attribution and silent data loss:

- Provider canonicalization (opencode, micode, kilo, mux, antigravity, jcode):
  these stored a raw provider field verbatim, so aliases (fireworks, vertex,
  gemini, azure) split into separate aggregation buckets. Now routed through
  provider_identity::canonical_provider like the other parsers.
- codex: provider defaulted to "openai" when session meta was absent; now infers
  from the model first, keeping "openai" only as the final fallback.
- gjc, pi: dropped messages with valid tokens but no provider field; now recover
  via model inference (fallback "gjc"/"pi") instead of discarding the spend.
- droid: dropped a whole session when its timestamp resolved to 0; now falls
  back to file mtime.
- qwen, mux: had no dedup key (double-count risk on re-parse); now keyed stably.
- utils: seconds->ms conversion uses saturating_mul; kiro/goose timestamp paths
  hardened against overflow.
- kiro (estimated tokens), goose (heuristic reasoning), crush (cost-only, zero
  tokens): documented these source limitations so 0/estimated values aren't
  mistaken for bugs.

Updates tests that encoded the OLD behavior: fireworks->fireworks_ai
canonicalization, and gjc now keeping a previously-dropped record.

Confidence: high
Scope-risk: moderate
Directive: roocode/cline/kilocode intentionally preserve raw apiProtocol — do NOT canonicalize them

* style(report): drop useless vec! in test helpers

Pre-existing clippy useless-vec warnings in report.rs test helpers, swept to keep the clippy gate clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant