Summary
otelMiddleware emits only two usage attributes — gen_ai.usage.input_tokens and gen_ai.usage.output_tokens (from usage.promptTokens / usage.completionTokens). The TokenUsage it receives already carries more that never reaches the span.
Available but not emitted
From TokenUsage (@tanstack/ai-event-client):
cost — provider-reported cost for the request
costDetails — upstream input/output cost split
totalTokens
promptTokensDetails — e.g. cached tokens
completionTokensDetails — e.g. reasoning tokens
durationSeconds — duration-based billing (e.g. Whisper transcription)
Impact
Because cost is absent from the span, backends (e.g. PostHog $ai_generation) must re-derive cost from tokens × their own price table. Provider-reported actual cost — cache discounts, gateway markup (OpenRouter), etc. — is therefore lost, and duration-billed activities have no cost signal at all.
Proposal
When present on usage, emit (all optional/guarded so spans stay clean when a provider doesn't report them):
gen_ai.usage.cost ← usage.cost
gen_ai.usage.total_tokens ← usage.totalTokens
- cached/reasoning attributes ←
promptTokensDetails / completionTokensDetails
- duration ←
usage.durationSeconds
- optionally
costDetails (upstream input/output split)
This is independently useful for chat today. It also matters most for the cross-activity case in #720: media activities have no tokens to derive cost from, so without an explicit gen_ai.usage.cost a backend gets nothing.
Observed on @tanstack/ai@0.26.1.
Summary
otelMiddlewareemits only two usage attributes —gen_ai.usage.input_tokensandgen_ai.usage.output_tokens(fromusage.promptTokens/usage.completionTokens). TheTokenUsageit receives already carries more that never reaches the span.Available but not emitted
From
TokenUsage(@tanstack/ai-event-client):cost— provider-reported cost for the requestcostDetails— upstream input/output cost splittotalTokenspromptTokensDetails— e.g. cached tokenscompletionTokensDetails— e.g. reasoning tokensdurationSeconds— duration-based billing (e.g. Whisper transcription)Impact
Because cost is absent from the span, backends (e.g. PostHog
$ai_generation) must re-derive cost fromtokens × their own price table. Provider-reported actual cost — cache discounts, gateway markup (OpenRouter), etc. — is therefore lost, and duration-billed activities have no cost signal at all.Proposal
When present on
usage, emit (all optional/guarded so spans stay clean when a provider doesn't report them):gen_ai.usage.cost←usage.costgen_ai.usage.total_tokens←usage.totalTokenspromptTokensDetails/completionTokensDetailsusage.durationSecondscostDetails(upstream input/output split)This is independently useful for chat today. It also matters most for the cross-activity case in #720: media activities have no tokens to derive cost from, so without an explicit
gen_ai.usage.costa backend gets nothing.Observed on
@tanstack/ai@0.26.1.