OpenCode rows read from its own data (opencode.db or storage/) had 请求模型 and 推理强度 as —. OpenCode keeps both on each reply (packages/schema/src/v1/session.ts, AssistantMessage): variant, the effort picked in its model menu for the prompt (high, max, low, xhigh, none, thinking … as the provider's variants name them; prompt.ts copies lastUser.model.variant onto every reply), and modelID, the model the reply was sent to. OpenCode 2 (session_message) keeps the variant in model.variant (Model.Ref). It records no model the response named, so modelID is what was asked for, as a Codex rollout's model is.
Now a call's Effort is the reply's variant ("default" or none is no effort picked); a compaction in OpenCode 2 takes the effort last replied with, as it takes the model. In the ledger an OpenCode call is told as a Codex one: requested = its model, served left empty rather than claimed. The call cache moves to calls-v5 so calls read before carry the effort.
TestOpenCodeCallsJSON/DB/V2 (fixtures with variant high, default, xhigh and none) and TestOpenCodeSessionCallIsWhatItAskedFor fail on the old code and pass. go vet ./internal/..., builds (nogui, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried against a real OpenCode database with variants (this machine's OpenCode data predates them).
Usage → Sessions and the stats now list Grok Build's and WorkBuddy's local sessions next to the other agents'. The GUI and CLI filters already name them from agent.Clients(), so nothing else needed changing.
Grok Build: the reader looks under $GROK_HOME/sessions (else ~/.grok/sessions), at <url-encoded cwd>/<id>/, reading summary.json and updates.jsonl.
- Tokens come from each turn_completed's usage.modelUsage, per model. Input is counted without the cache it read (inputTokens already includes cachedReadTokens), so the cache isn't counted twice.
- A subagent's session (session_kind "subagent…") counts once, inside the session it ran in.
- A fork's copied updates count only in the session they came from. The fork itself counts from its created_at on.
- The title is the first prompt typed, else Grok's generated_title.
- The hooks that fire on start and end don't count as time at work, so a session closed the next day doesn't run overnight.
- It resumes with `cd <cwd> && grok --resume <id>`.
WorkBuddy: the reader looks at $WORKBUDDY_CONFIG_DIR/projects/**/*.jsonl (else ~/.workbuddy).
- Tokens come from providerData.usage and count once per reply (providerData.messageId), however many of its lines carry it. Cached tokens (inputTokensDetails) are taken out of the input.
- The older snake_case input_tokens/output_tokens are read as well, with the cwd from <id>.meta.json for sessions migrated from its old history.
- The title is custom-title, then ai-title, then the first prompt (without WorkBuddy's <system-reminder> and @scene: blocks).
- There is no resume command, since it is a desktop app.
Tested:
- go test -tags nogui ./... passes. New sanitized fixtures cover both readers: a Grok main session with a subagent, a fork and a hooks-only session, plus a WorkBuddy session with repeated usage lines and a migrated one.
- go build -tags nogui passes for darwin, linux and windows (CGO_ENABLED=0).
- A read-only run against real ~/.grok and ~/.workbuddy: 7 Grok sessions (an 8th, hooks only, left out) and 16 WorkBuddy sessions. Token totals match a raw jq tally of the same files: Grok 293910 in / 4396 out / 610560 cache read, WorkBuddy 13548053 / 621856 / 32321668.