You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
V2: align Anthropic streaming usage aggregation with upstream SDKs #40713
The native Anthropic Messages parser currently normalizes usage independently on message_start and message_delta, then merges normalized Usage values. Anthropic usage is split across those events, and newer responses can include usage.iterations for compaction, message, advisor, and fallback inference.
This makes correct aggregation harder than it needs to be and risks losing or undercounting partial usage details. Vercel AI SDK instead accumulates raw Anthropic usage across stream events and normalizes once when message_stop arrives.
Follow-up scope
Accumulate typed raw Anthropic usage in parser state across message_start and message_delta.
Normalize once at message_stop rather than merging partially normalized Usage objects.
Match Anthropic and AI SDK nullability for streamed usage fields.
Define and test usage.iterations accounting:
include executor compaction and message work
keep advisor_message separately attributable
avoid double-counting fallback responses
handle cache-read and cache-creation counters consistently
Preserve the complete provider payload in providerMetadata.anthropic.
Keep normalized Usage invariants intact when only partial counters are available.
Acceptance criteria
Fixtures cover split start/delta usage, compaction, advisor, and fallback cases.
Normalized totals match the current Anthropic/AI SDK semantics.
No provider metadata has to be reparsed to reconstruct normalized usage.
The last chunk that carries any usage object wins outright. A stream that emits prompt_tokens_details on one chunk and bare totals on the next drops the cache read/write. Every OpenAI-compatible provider reuses this protocol, so litellm / DeepSeek-compat / GLM-compat all have the same exposure.
Anthropic is the better of the two — it folds per-field:
But it still merges already-normalized Usage, which is exactly the gap this issue describes. Accumulating the raw events and normalizing once at message_stop / the final Chat chunk fixes both parsers the same way.
mapUsage also stashes providerMetadata: { openai: usage }. That object is not on the persisted message (105k assistant rows, none carry the key), so a later replay cannot recover the dropped details. The fold has to happen while the stream is still in hand. #34022 is the “cache read is zero” report this replace would produce on a multi-usage stream.
Partly done: #46431 added typed AnthropicUsage with iterations. mapUsage sums the iterations and takes context tokens from the last one. The parser still merges usage normalized separately at message_start and message_delta, advisor usage isn't counted separately, and nothing de-duplicates fallback responses. Closing this tracker.
Activity
Zaczero commentedon Aug 18, 2026
The Anthropic merge-normalized-
Usagerisk named here already has a worse sibling on the OpenAI Chat path: usage is replaced, not merged.opencode/packages/ai/src/protocols/openai-chat.ts
Line 692 in 9e22c40
The last chunk that carries any usage object wins outright. A stream that emits
prompt_tokens_detailson one chunk and bare totals on the next drops the cache read/write. Every OpenAI-compatible provider reuses this protocol, so litellm / DeepSeek-compat / GLM-compat all have the same exposure.Anthropic is the better of the two — it folds per-field:
opencode/packages/ai/src/protocols/anthropic-messages.ts
Lines 709 to 716 in 9e22c40
But it still merges already-normalized
Usage, which is exactly the gap this issue describes. Accumulating the raw events and normalizing once atmessage_stop/ the final Chat chunk fixes both parsers the same way.mapUsagealso stashesproviderMetadata: { openai: usage }. That object is not on the persisted message (105k assistant rows, none carry the key), so a later replay cannot recover the dropped details. The fold has to happen while the stream is still in hand. #34022 is the “cache read is zero” report this replace would produce on a multi-usage stream.neriousy commentedon Oct 21, 2026
Partly done: #46431 added typed
AnthropicUsagewithiterations.mapUsagesums the iterations and takes context tokens from the last one. The parser still merges usage normalized separately at message_start and message_delta, advisor usage isn't counted separately, and nothing de-duplicates fallback responses. Closing this tracker.