Defined in acp/session.go, this is the single source of truth for usage data within a session:
| Field | Type | Description | Semantics |
|---|---|---|---|
InputTokens |
int64 | Tokens consumed by user input | Cumulative across all prompts |
OutputTokens |
int64 | Tokens generated by model | Cumulative across all prompts |
CacheCreationInputTokens |
int64 | Tokens used to create prompt cache | Cumulative |
CacheReadInputTokens |
int64 | Tokens read from cached prompts | Cumulative |
ContextWindow |
int64 | Model's context window size | Gauge (last known value) |
MaxOutputTokens |
int64 | Model's max output token limit | Gauge (last known value) |
WebSearchRequests |
int64 | Number of web searches performed | Cumulative |
CostUSD |
float64 | Total cost in USD | Cumulative |
PromptCount |
int64 | Number of prompts sent | Cumulative |
Model identification is stored separately as lastModel string on each session — only the most recently seen model ID is kept.
SDK version is extracted once during initialization from InitializeResponse.AgentInfo._meta.{claudeCode,rai}.sdkVersion.
Source: _meta field on PromptResponse and AgentMessageChunk events.
Structure (JSON):
{
"_meta": {
"claudeCode": {
"model": "claude-opus-4-6",
"sdkVersion": "1.0.0",
"totalCostUsd": 0.1234,
"modelUsage": {
"claude-opus-4-6": {
"inputTokens": 1000,
"outputTokens": 500,
"cacheCreationInputTokens": 200,
"cacheReadInputTokens": 800,
"contextWindow": 200000,
"maxOutputTokens": 16384,
"webSearchRequests": 2,
"costUSD": 0.1234
}
}
}
}
}Key "claudeCode", "rai", "codex", or "gemini" — all use the same inner structure. modelUsage is a map keyed by model name; values are summed across all models into a single UsageInfo.
When updated:
- Streaming — each
AgentMessageChunkwith_metareplaces the current usage (values are cumulative from the agent). - Prompt response —
PromptResponse.Metais used as fallback if streaming didn't provide usage. PromptCountis incremented by ACPP on eachPrompt()call (not from the agent).
Accumulation model: The agent provides cumulative values. ACPP replaces (not adds) on each update, preserving only PromptCount from the previous state.
Source: Synchronous POST /session/:id/message response.
Structure (JSON):
{
"info": {
"id": "msg_123",
"sessionID": "sess_abc",
"role": "assistant",
"modelID": "claude-opus-4-6",
"cost": 0.05,
"tokens": {
"input": 500,
"output": 200,
"reasoning": 0,
"cache": {
"read": 100,
"write": 50
}
}
}
}When updated:
- Prompt response only —
updateUsageFromMessage()after eachPrompt()call. - SSE
message.updated— used only forlastModeltracking, not usage.
Accumulation model: The API provides per-prompt deltas. ACPP adds each response's values to the running total.
| Issue | ACP Backend | OpenCode Backend |
|---|---|---|
| Accumulation | Replace (agent gives cumulative) | Add (API gives per-prompt deltas) |
ContextWindow |
Populated from modelUsage |
Never populated |
MaxOutputTokens |
Populated from modelUsage |
Never populated |
WebSearchRequests |
Populated from modelUsage |
Never populated |
SDKVersion |
From InitializeResponse |
Never populated |
reasoning tokens |
Not tracked | Available in API (ocTokens.Reasoning) but ignored |
| Model history | Last model only | Last model only |
CostUSD source |
totalCostUsd at section level, fallback to per-model costUSD |
Per-message cost field |
Persisted columns:
| Column | Type | Source field |
|---|---|---|
model |
TEXT | StatusInfo.Model (last model) |
sdk_version |
TEXT | StatusInfo.SDKVersion |
input_tokens |
BIGINT | UsageInfo.InputTokens |
output_tokens |
BIGINT | UsageInfo.OutputTokens |
cache_creation_input_tokens |
BIGINT | UsageInfo.CacheCreationInputTokens |
cache_read_input_tokens |
BIGINT | UsageInfo.CacheReadInputTokens |
cost_usd |
DOUBLE PRECISION | UsageInfo.CostUSD |
prompt_count |
BIGINT | UsageInfo.PromptCount |
Not stored in DB: ContextWindow, MaxOutputTokens, WebSearchRequests.
Written at two points:
UpdateSession()— after each prompt completesFinishSession()— when session closes (setsfinished_at)
Per-prompt snapshots: {timestamp, prompt_text, usage_snapshot}. Written after each Prompt() call. Contains full UsageInfo at that point in time.
Raw ACP SessionUpdate JSON objects. Contains the original _meta payloads with full model-level breakdown.
Same events as log files, indexed by session_id and event_type.
| Consumer | Fields shown | Fields missing |
|---|---|---|
| Web UI session view | cost, input/output tokens, cache creation, prompts, model, SDK | cache read, context window, max output, web searches |
| Web UI daily stats | sessions, prompts, input/output, cache create/read, cost | model breakdown |
| Web UI monthly stats | same as daily, grouped by directory | model breakdown |
Console summary (FormatUsageSummary) |
prompts, input/output, cache create+read, cost | model, context window, web searches |
| Prometheus metrics | all UsageInfo fields + context window + max output + session status | per-model breakdown |
JSON API (/api/sessions.json) |
prompts, input/output, cache create/read, cost, model, SDK | context window, max output, web searches |
Currently only a single lastModel string is tracked per session. This means:
- If a session uses multiple models (e.g. Haiku for tool calls, Opus for reasoning), only the last one seen is recorded.
- The ACP
_meta.modelUsagemap contains per-model breakdowns, but ACPP sums them into a flat total and discards the per-model detail (except in raw logs). - OpenCode provides
modelIDper message, but again only the last is kept.
There is no per-model usage breakdown in the database schema or any display layer.