Usage and cost
/usage shows how many tokens the current session has spent — per
model, plus a rolling total across every session created today — along
with live rate-limit headroom and a provider-aware account block.
It does not show a dollar estimate. Token counts are reported by
the provider and are exact, but a dollar figure would require a price
per model, and no provider exposes per-model pricing through the
inference API. The only way to price tokens is a hand-maintained table
that drifts the moment a provider changes rates or ships a new model —
so rather than print a number we can’t stand behind, /usage links
each provider’s billing dashboard, the authoritative source for spend.
What /usage shows
Usage
session 3:30 PM today (20260530-153012.482917)
metrics 42 turns · 17 tools · 6 subagents · 2 models
largest turn turn 31 · 231,902 tokens (input 37,401 · output 812 · cache read 193,689)
compaction compacted 2x · reclaimed ~418k tokens
tools 3 distinct
read_file 6 calls 14,802 tokens
git_diff_files 2 calls 5,104 tokens
efficiency avg 18K tokens/turn · 1 low-signal turn
repeated call read_file({"path":"docs/cost.md"}) × 2
strategy guidance fired apply_diff (1×)
waste estimate ~4K tokens (repeated calls + failed-retry guidance)
context top retained context
tool:read_file 14,802 tokens ⚠ retained
tool:git_diff_files 5,104 tokens
claude-opus-4-7 input 265
output 103,432
cache read 22,503,118
cache write 457,012
─────────────────────
total 23,063,827
claude-haiku-4-5 input 1,200
output 10,712
cache read 1,310,484
cache write 117,933
────────────────────
total 1,440,329
session total 24,504,156 tokens
rate limits live, from last response
tokens 1,824,000 / 2,000,000 remaining · resets in 41s
requests 3,998 / 4,000 remaining · resets in 41s
today 15 sessions · 20.8M tokens
account openai (pay-per-use API key)
billing → platform.openai.com/usageOn a ChatGPT-subscription (openai-auth) session the account block
instead reports the Codex quota windows, refreshed off every response:
account openai-auth (chatgpt prolite plan)
5h window 12% used · resets in 3h 41m
weekly 97% used · resets in 6d 12h
✓ no per-request cost — subscriptionThe block renders in an inline overlay above the cmdline (the same surface the cheatsheet and the pickers use), not in chat scrollback — token tallies are transient inspection, not part of the conversation, so they never bloat the history the model re-reads. Press any key to dismiss it. The panel is read-only and safe to invoke mid-turn — it doesn’t cancel a streaming response.
The per-model breakdown is sorted by total tokens (highest first) and
reuses the session’s ModelUsage map. Sessions that mixed providers
or models (Claude for code review, Gemini for grep) show each model’s
tokens separately. Each model renders as a small ledger: input and
output always appear, cache read/write and reasoning appear only when
non-zero, and the session total line sums all model rows. A lightweight
metrics row shows the current session’s assistant turns, tool calls,
subagents, and model count when those counts are non-zero. The
largest turn row calls out the single highest-spend assistant turn and
splits it into input, output, cache-read, cache-write, and reasoning
components so a spike is visible as fresh context, cache replay, or
model output instead of one opaque total.
The optional efficiency block calls out progress-cost smells that are
more concrete than a generic outlier flag: average tokens per assistant
turn, low-signal turns (large input with tiny output), exact-duplicate
tool calls with the same arguments, repeated tool failures where the
agent loop injected strategy guidance, and a floor waste estimate for
repeated-call results plus failed-retry guidance. This is intentionally
a conservative signal; ordinary high-token rows are no longer labelled
as anomalies just because they are larger than the table mean.
The optional context block explains why future turns may stay high:
it estimates the largest retained transcript messages with the same
4-chars-per-token heuristic used for tool-output stats, labels tool
results by tool name, and marks messages above the retained-context
warning threshold. Large retained tool outputs — full file reads, diffs,
PR templates, or command logs — are resent on later turns until the
session is compacted or the transcript is otherwise shortened.
One-model sessions with only input/output rows skip the per-model
total separator so the explicit session total is not repeated
back-to-back.
The daily rollup prints each session’s short id. Pass that id to
/inspect <id> to open a read-only turn-by-turn replay for a past
session without resuming or replacing the live conversation.
Live rate limits
OpenAI, Anthropic, and xAI return per-minute rate-limit headers on
every successful response — no admin key, no extra request. A
client middleware (internal/adapter/ratelimit.go) snapshots them off
each turn and /usage surfaces the latest as a “rate limits (live)”
block: remaining/limit token and request headroom for the current
window, with a reset countdown. The snapshot is in-memory and reflects
the most recent response, so the block only appears after the first
turn of a session and disappears on restart until the next turn.
ChatGPT-subscription sessions (openai-auth) get the same treatment
through a different header family. The Codex backend enforces two
quota windows concurrently — a short rolling one and a weekly one — and
reports each as x-codex-{primary,secondary}-*
(used-percent, window-minutes, reset-at / reset-after-seconds).
The adapter snapshots both off every response in runOnce, so /usage
shows standing headroom rather than a post-mortem of the last 429.
Two rules keep that block honest. Each window is labelled from its own
window-minutes, never a hardcoded guess — otherwise a user bounced off
the short window would read the weekly number and conclude they were
locked out for days. And a window whose reset instant has already
passed is dropped, not shown as resets in now: the quota it described
has since refilled, so displaying it would assert a limit that no longer
applies.
Only the primary family is confirmed against a live backend;
secondary is inferred from the same shape. If the real names differ,
parsing simply finds nothing and the verbatim header dump on the next 429
surfaces the actual fields.
This is the one quota signal the providers do return on the
inference key. The per-account cost / spend APIs (OpenAI’s
/v1/organization/costs, Anthropic’s /v1/organizations/cost_report)
need a separate admin/org key and report org-wide month-to-date
totals with a lag — they can’t give per-session cost — so /usage
doesn’t call them; the dashboard link covers that need.
Why no dollar figure
Pricing the tokens we count would mean shipping a per-model rate table and maintaining it by hand:
- No pricing API. OpenAI, Anthropic, Gemini, and xAI publish rates on web pages, not through an endpoint the inference key can read.
- It drifts. A bundled table is stale the moment a price changes, and it has no entry at all for a model released after the last update (and new models ship constantly).
- The list price isn’t the invoice anyway. Promotional credits, committed-use and enterprise discounts, batch pricing, and context-length tiers all move the real number in ways the inference API can’t see.
So a computed “≈$X” would be an unverifiable guess wearing the costume of an exact figure. The honest surface is exact token counts (which we have) plus a link to where the real dollars live.
Per-provider behavior
| Provider | What /usage shows |
|---|---|
anthropic, openai, gemini, xai, openai-compatible (OpenRouter, Groq, …) | Per-model token counts + billing-dashboard link |
openai-auth (ChatGPT subscription) | Per-model token counts + plan (best-effort /backend-api/me probe) + both Codex quota windows, percent-used and reset, captured live off every response |
copilot (GitHub Copilot subscription) | Per-model token counts; no public quota endpoint |
ollama (local) | Token counts when the runtime reports them; no billing dashboard |
openai-compatible → NVIDIA NIM (integrate.api.nvidia.com) | Token counts only — local / credit-based |
Billing dashboards
/usage’s account block links each provider’s public billing surface —
the authoritative answer to “what did this cost”:
| Provider | Billing dashboard |
|---|---|
| Anthropic | https://console.anthropic.com/settings/billing |
| OpenAI API | https://platform.openai.com/usage |
ChatGPT (openai-auth) | https://chatgpt.com/account |
| Copilot | https://github.com/settings/billing/summary |
| Gemini | https://aistudio.google.com/app/billing |
| xAI | https://console.x.ai/team |
For ChatGPT subscription accounts the /backend-api/me probe adds plan
- email when the endpoint cooperates. It’s undocumented and may change without notice; we cache the result for 5 minutes per process and silently fall back if a subsequent probe fails.
Account identity is sourced from each provider’s own API, never from
config. Only openai-auth exposes one today (email + plan, via the
probe above); API-key providers don’t surface the key holder’s
name/email on the inference key, so /usage shows none for them.
Where the data comes from
- Each cloud adapter parses the provider’s usage field on its final
stream event (
message_deltafor Anthropic,response.completedfor the OpenAI Responses APIs, the empty-choiceschunk for Chat Completions withstream_options.include_usage: true, andusageMetadatafor Gemini). - The neutral
adapter.Message.Usagefield carries normalized counts:input_tokens,output_tokens,cache_creation_tokens,cache_read_tokens,reasoning_tokens. session.Session.AddUsage(model, u)sums each turn intoTotalUsageplus a per-model breakdown. Sessions persist these alongside the message log in~/.yottacode/sessions/<id>.json.- The
/usagedaily rollup scans the sessions directory and decodes only the metadata + usage fields (Messages stay on disk) so the command stays cheap to run. internal/cost/dashboards.gomaps each provider to its billing dashboard URL — the only piece of the former price catalog still in the tree.
Backward compatibility
The Usage field on adapter.Message is a pointer with omitempty;
Session.TotalUsage uses omitzero and Session.ModelUsage uses
omitempty. Session files written before the usage fields landed
continue to load unchanged, and sessions that haven’t recorded a turn
yet stay byte-identical to the old shape on disk.