Repository navigation
Description
What
Extend the existing copilot adapter (rust/crates/ccusage/src/adapter/copilot/) so that ccusage copilot actually returns data on a normal Copilot CLI install — and, while we're there, report cost in the units that match Copilot's new AI-credits billing model going live June 1, 2026.
The proposal has three composable pieces (more on that below) so each can land — or not — independently.
This is not a proposal to reintroduce a standalone wrapper package — everything stays inside the current unified Rust adapter, which is what was invited in #956 ("a fresh adapter-focused proposal can be opened if this source is still needed").
Why
Today the adapter only reads ~/.copilot/otel/*.jsonl (or the file at COPILOT_OTEL_FILE_EXPORTER_PATH). The Copilot CLI does not export OpenTelemetry by default — resolveOtelConfig returned null shows up in its own logs unless the user sets that env var before every session.
Result: on a machine where I have ~500 Copilot CLI sessions on disk, bunx ccusage copilot monthly prints No usage data found. This is also what #1169 was reporting, and the same pain point that motivated #956.
Meanwhile the CLI already writes complete per-session token + cost aggregates by default — they're just not in the format the adapter currently looks at. The CLI is also already credit-aware ahead of the June 1 cutover (concrete numbers below).
Why I'm confident the data path works
I already implemented an events.jsonl reader in PR #957 and it worked end-to-end: node ./dist/index.js daily --json on real local data returned 46 sessions and 828M input tokens, with passing tests.
That PR was not closed because the approach was wrong or the code was bad. On #958 the maintainer was explicit about the reason:
I am sorry that the Rust migration made the original TypeScript implementation no longer mergeable as-is. The production CLI has moved from the TypeScript implementation to the Rust implementation under
rust/crates/ccusage. The related TypeScript PR has been closed as obsolete… — @ryoppippi, #958
In other words: right thing, wrong time — the repo was mid-rewrite from TypeScript to Rust, and at the same time it was deprecating standalone wrapper packages in favor of the unified adapter model (the framing in #956). PR #957 happened to land squarely in the wrong spot on both axes. The actual reading of events.jsonl and aggregation of session.shutdown.modelMetrics was sound and verified against real data — that's the part I want to bring forward, ported into the current Rust adapter where it now belongs.
Composability (please read this before evaluating scope)
The three pieces below are independent merges. Pick any subset:
| Piece | Depends on | Risk if skipped |
|---|---|---|
A. Read events.jsonl as a default data source |
nothing | adapter stays broken for users without OTel — i.e. most users |
B. AI-credits cost mode (--mode credits) |
A | costs continue to be shown only in token-derived USD; OK if you'd rather not commit to credits yet |
C. API-equivalent cost mode (--mode api-equiv) |
A | no comparison view between Copilot subscription cost and raw API cost; OK if out of scope |
A is the only one that fixes the "no data" bug. B and C are independent of each other; either can be added later without touching A.
Piece A — read events.jsonl as a default source
Data the CLI already persists, per session:
~/.copilot/session-state/<uuid>/events.jsonlsession.start→sessionId,copilotVersion,startTime,context.cwd,repositorysession.shutdown→modelMetrics.<model>.usage.{inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, reasoningTokens}andmodelMetrics.<model>.requests.{count, cost}
Example from one of my sessions:
{
"type": "session.shutdown",
"timestamp": "2026-05-07T10:57:19.746Z",
"data": {
"modelMetrics": {
"claude-opus-4.7": {
"usage": {"inputTokens": 23399, "outputTokens": 2994,
"cacheReadTokens": 10069, "cacheWriteTokens": 13324,
"reasoningTokens": 0},
"requests": {"count": 1, "cost": 15}
}
}
}
}I scanned the last 80 sessions on my machine — 70/80 have at least one session.shutdown with full modelMetrics, across Copilot CLI versions from 0.0.419 up to 1.0.54.
Sketch of the change in adapter/copilot/:
paths.rs: also enumerate~/.copilot/session-state/*/events.jsonl.parser.rs: add a second parser that emits oneCopilotUsageEntryper(sessionId, model)pair from eachsession.shutdown, withdedup_key = "shutdown:{sessionId}:{model}"and timestamp from the shutdown event.loader.rs: merge both sources, sort, return.
Model name normalization (Copilot uses claude-opus-4.7-1m-internal, gpt-5.4-mini, etc., while LiteLLM keys use dashes like claude-opus-4-7) lives inside adapter/copilot/ itself — likely a small lookup in parser.rs next to where the model field is read — rather than leaking Copilot-specific quirks into the central pricing layer.
PR #957's TypeScript loader did the equivalent of these three steps; the port to Rust is mechanical because the data shape doesn't change.
Piece B — AI-credits cost mode (--mode credits)
Starting June 1, 2026, Copilot replaces "premium requests" with GitHub AI Credits, where 1 AI Credit = $0.01 USD and cost depends on tokens × model rate (blog).
The Copilot CLI is already credit-aware. session.compaction_complete events carry full per-token-type pricing tables in nano-AIU (10⁻⁹ AI Units):
"copilotUsage": {
"tokenDetails": [
{"tokenType": "input", "batchSize": 1000000, "costPerBatch": 500000000000, "tokenCount": 6},
{"tokenType": "cache_read", "batchSize": 1000000, "costPerBatch": 50000000000, "tokenCount": 127386},
{"tokenType": "cache_write", "batchSize": 1000000, "costPerBatch": 625000000000, "tokenCount": 2220},
{"tokenType": "output", "batchSize": 1000000, "costPerBatch": 2500000000000, "tokenCount": 6210}
],
"totalNanoAiu": 23284800000
}So we get the rate table from the CLI itself — no need for ccusage to ship a separate credit-pricing source.
Sketch: a --mode credits (or extending the existing cost mode) that, in adapter/copilot/, accumulates nano_aiu per session and converts to credits (/ 1e9) and USD (× $0.01) at the report layer. For session.shutdown rows that don't carry the rate table directly, apply the per-token-type rates from the most recent same-model compaction_complete rate row in the same session, or fall back to the OTel rates (Copilot CLI versions ≥ ~1.0.4x carry it; older sessions just show null for credits).
Piece C — API-equivalent cost mode (--mode api-equiv)
A second mode that answers: "how much would this have cost if I'd hit Anthropic/OpenAI directly?" — useful for justifying the Copilot subscription or comparing across vendors.
This is just LiteLLM pricing applied to the same (model, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, reasoningTokens) tuple — exactly what the OTel branch of the existing adapter already does. The only new bit is the normalization map from Piece A so Copilot's model names look up correctly in LiteLLM.
The two modes are not mutually exclusive — happy to have --json always emit both creditsCostUSD and apiEquivCostUSD fields, so users can compare without re-running.
Open question
- Coexistence with OTel. If a user has both
~/.copilot/otel/*.jsonlandevents.jsonlfor the same session, what should win? My instinct: prefer thesession.shutdownaggregate (it's authoritative and post-finalized) and drop OTel rows whosegen_ai.conversation.idmatches a shutdown sessionId. Open to other approaches.
Why it matters
The adapter is currently dead in practice for everyone who hasn't manually enabled OTel — which, as far as I can tell, is basically everyone. And with AI-credits billing going live next week, "how much have I actually spent / saved" is about to become the single most-asked question by Copilot CLI users. Piece A unblocks the first question; B and C answer the second.
Prior art
- feat: Add GitHub Copilot CLI usage tracking (@ccusage/copilot) #956 — original feature request (closed, but invited an adapter-focused proposal).
- feat(copilot): add GitHub Copilot CLI usage tracking app #957 — my closed PR. Implemented the
events.jsonldata path and verified it on real local data. Per the maintainer on feat: support human-readable token count display (K/M/B suffixes) #958, closed because of the TypeScript→Rust migration (and the parallel move away from wrapper packages), not because the reader was wrong. - Tool says it has copilot support, it doesn't #1169 — user-side bug report symptom of the same gap.
I'm happy to open the PR(s) once the design question above is settled — one PR per piece, in A → B → C order so each can be reviewed in isolation.
Activity
github-actions commentedon May 28, 2026
This issue was auto-closed. Issues from new contributors are auto-closed by default.
Maintainers review auto-closed issues and reopen worthwhile ones. Issues that do not meet the quality bar in CONTRIBUTING.md may not be reopened or receive a reply.
Keep the issue short, concrete, and written in your own voice.
If a maintainer replies
lgtmi, your future issues will stay open. If a maintainer replieslgtm, your future issues and PRs will stay open.See CONTRIBUTING.md.
nickcox commentedon Jun 1, 2026
I'd love to see this land.
ryoppippi commentedon Jun 4, 2026
thanks.
feat(adapter/copilot): read session-state events.jsonl as default dat…
feat(adapter/copilot): add `--mode credits` for GitHub AI Credits
feat(adapter/copilot): add `--mode api-equiv` as alias for `calculate`
feat(adapter/copilot): make default mode reflect Copilot's true bill
feat(adapter/copilot): make default mode era-aware across billing cut…
refactor(adapter/copilot): stop reading OpenTelemetry data
refactor(cli): remove `--mode credits` in favor of era-aware `auto`
refactor(cli): rename `--mode api-equiv` to `--mode api`
fix(adapter/copilot): accept fractional `requests.cost` and clean up …
fix(adapter/copilot): tighten parser dedup key and address round-2 re…
189 remaining items
feat(adapter/copilot): switch default source to per-session events.js…
feat(adapter/copilot): switch default source to per-session events.js…
feat(adapter/copilot): switch default source to per-session events.js…
feat(adapter/copilot): switch default source to per-session events.js…
feat(adapter/copilot): switch default source to per-session events.js…
feat(adapter/copilot): switch default source to per-session events.js…
feat(adapter/copilot): switch default source to per-session events.js…
jay-tau commentedon Jul 4, 2026
@ryoppippi The PR linked to this is ready for merge, pending your review
feat(adapter/copilot): switch default source to per-session events.js…
appleflyerv3 commentedon Jul 13, 2026
please land this fix
feat(adapter/copilot): switch default source to per-session events.js…
feat(adapter/copilot): switch default source to per-session events.js…
ryoppippi commentedon Aug 31, 2026
Historical audit: this discussion was auto-closed by the legacy contributor gate. That closure did not assess technical importance.
Audit result: resolved. A later merged change or the current
mainimplementation covers this request. This item is kept for history and does not need to be reopened.