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.jsonl
session.start → sessionId, copilotVersion, startTime, context.cwd, repository
session.shutdown → modelMetrics.<model>.usage.{inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, reasoningTokens} and modelMetrics.<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 one CopilotUsageEntry per (sessionId, model) pair from each session.shutdown, with dedup_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/*.jsonl and events.jsonl for the same session, what should win? My instinct: prefer the session.shutdown aggregate (it's authoritative and post-finalized) and drop OTel rows whose gen_ai.conversation.id matches 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
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.
What
Extend the existing
copilotadapter (rust/crates/ccusage/src/adapter/copilot/) so thatccusage copilotactually 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 atCOPILOT_OTEL_FILE_EXPORTER_PATH). The Copilot CLI does not export OpenTelemetry by default —resolveOtelConfig returned nullshows 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 monthlyprintsNo 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.jsonlreader in PR #957 and it worked end-to-end:node ./dist/index.js daily --jsonon 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:
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.jsonland aggregation ofsession.shutdown.modelMetricswas 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:
events.jsonlas a default data source--mode credits)--mode api-equiv)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.jsonlas a default sourceData 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.shutdownwith fullmodelMetrics, across Copilot CLI versions from0.0.419up to1.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 likeclaude-opus-4-7) lives insideadapter/copilot/itself — likely a small lookup inparser.rsnext 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_completeevents carry full per-token-type pricing tables in nano-AIU (10⁻⁹ AI Units):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, inadapter/copilot/, accumulatesnano_aiuper session and converts to credits (/ 1e9) and USD (× $0.01) at the report layer. Forsession.shutdownrows that don't carry the rate table directly, apply the per-token-type rates from the most recent same-modelcompaction_completerate row in the same session, or fall back to the OTel rates (Copilot CLI versions ≥ ~1.0.4x carry it; older sessions just shownullfor 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
--jsonalways emit bothcreditsCostUSDandapiEquivCostUSDfields, so users can compare without re-running.Open question
~/.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
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.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.