fix(grok): report the per-turn token usage grok already writes - #1060
Merged
Conversation
grok sessions contributed nothing to the token tally, the lineage turn counts, or the fleet meter — the adapter reported only the context gauge from `signals.json`, on the belief (recorded in its own comment) that grok's streams carry no usage split. They do. Every prompt closes with a `turn_completed` record in the session's `updates.jsonl` carrying a real usage object: `inputTokens`, `outputTokens`, `cachedReadTokens`, and a `modelUsage` map breaking the same figures out per model. The adapter already reads that file for subagent updates and was walking straight past this. `totalTokens` is exactly input + output, which is what establishes that `inputTokens` is the whole prompt side and `cachedReadTokens` a subset of it — the split `Cost` wants. The `modelUsage` keys are spelled exactly as `chat_history.jsonl`'s `model_id`, which is what this adapter's `ModelChanged` carries, so per-model attribution can't disagree with the session's own model reports and split one model into two series. Records belonging to a subagent carry that agent's `sessionId` and are skipped, so a child's usage can't land on the root's tally. The caller's line cursor hands each line over once, so no dedupe is needed, and a resume skips prior lines as it already did for the transcript. `costUsdTicks` is deliberately ignored: it is plainly a scaled integer but nothing states the scale, and a dollar figure wrong by a factor of ten is worse than none. Volume only, as every other wrapper adapter reports. Verified by replaying a real 2-turn session's `updates.jsonl`: two Cost events, 193.5k in / 6.6k out / 122.9k cached. Tests use a verbatim record from that file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
grok sessions contributed nothing to the token tally, the lineage turn counts, or the new fleet meter. They reported only the context gauge.
The cause
The adapter believed grok exposed no per-call usage — it says so in its own comment:
That's not true (any more). Every prompt closes with a
turn_completedrecord in the session'supdates.jsonl:{ "method": "_x.ai/session/update", "params": { "sessionId": "95db93dc-…", "update": { "sessionUpdate": "turn_completed", "usage": { "inputTokens": 98232, "outputTokens": 2785, "totalTokens": 101017, "cachedReadTokens": 65792, "cacheCreationTokens": 0, "reasoningTokens": 924, "modelCalls": 3, "costUsdTicks": 1013276000, "modelUsage": { "grok-4.5-build": { "inputTokens": 98232, "…": "…" } } } } } }The adapter already reads this file for subagent updates and was walking straight past it.
What the shape settles
inputTokensis the whole prompt side,cachedReadTokensa subset of it.totalTokensis exactlyinput + output(98232 + 2785 = 101017), which is what proves cache reads are already insideinputTokensrather than additive — exactly the splitCostrequires (tokens_cached ⊆ tokens_in).modelUsagekeys matchModelChanged. They're spelledgrok-4.5-build, identical tochat_history.jsonl'smodel_id, which is whatgrok_model_changereports. Verified on disk before relying on it — a divergence here would split one model into two colored series in the meter (spec 0167).sessionIdand are skipped, so a child's usage can't land on the root's tally.What I deliberately left out
costUsdTicksis plainly a scaled integer, but nothing states the scale. Reading it as nano-dollars gives $1.01 for that turn; plausible published rates for the model put it nearer $0.34. I'm not shipping a dollar figure that might be off by 3×, so this reports volume only, like every other wrapper adapter. Easy to add once the scale is confirmed.reasoningTokensis not added tooutputTokens—totalTokensaccounting says it's already inside it.Verification
Replayed a real 2-turn session's
updates.jsonlthrough the new parser:Tests use that record verbatim — nothing about the shape is invented. Six new cases: the real split, subagent isolation, fallback when
modelUsageis absent, ordinary chunks ignored (their_meta.totalTokensis a running counter that must not be mistaken for a turn total), zero-usage turns emitting nothing, and multi-model turns splitting per model.Not verified live — that needs a real grok turn against the API, which I didn't run. The parser is exercised against real data; the wiring is a three-line insertion into the existing, already-exercised
updates.jsonlpoll loop. First grok turn after merge should show tokens on the session and agrok-4.5-buildseries in the meter.Also flips
supports_cost: truein the adapter's capabilities, and corrects the stale comment.Binaries
Touches
crates/adapter-grok→ the code ships inconstruct-adapter-grok, spawned by the daemon; run the workspaceconstructas usual.