Skip to content
yottacode v0.4.0 is out! 🎉 See the release notes ↗

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/usage

On 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 — subscription

The 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

ProviderWhat /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”:

ProviderBilling dashboard
Anthropichttps://console.anthropic.com/settings/billing
OpenAI APIhttps://platform.openai.com/usage
ChatGPT (openai-auth)https://chatgpt.com/account
Copilothttps://github.com/settings/billing/summary
Geminihttps://aistudio.google.com/app/billing
xAIhttps://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_delta for Anthropic, response.completed for the OpenAI Responses APIs, the empty-choices chunk for Chat Completions with stream_options.include_usage: true, and usageMetadata for Gemini).
  • The neutral adapter.Message.Usage field carries normalized counts: input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens, reasoning_tokens.
  • session.Session.AddUsage(model, u) sums each turn into TotalUsage plus a per-model breakdown. Sessions persist these alongside the message log in ~/.yottacode/sessions/<id>.json.
  • The /usage daily 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.go maps 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.