package cost import "encoding/json" type usagePayload struct { PromptTokens int `json:"prompt_tokens"` CompletionTokens int `json:"completion_tokens"` TotalTokens int `json:"total_tokens"` PromptTokensDetails *struct { CachedTokens int `json:"cached_tokens"` } `json:"prompt_tokens_details,omitempty"` CompletionTokensDetails *struct { ReasoningTokens int `json:"reasoning_tokens"` } `json:"completion_tokens_details,omitempty"` } type chatCompletionResponse struct { Usage *usagePayload `json:"usage,omitempty"` } // ParseUsage extracts token details from an OpenAI-compatible chat completion payload. func ParseUsage(payload []byte) UsageDetail { var parsed chatCompletionResponse if err := json.Unmarshal(payload, &parsed); err != nil || parsed.Usage == nil { return UsageDetail{TotalTokens: 1} } return usageFromPayload(parsed.Usage) } func usageFromPayload(u *usagePayload) UsageDetail { if u == nil { return UsageDetail{TotalTokens: 1} } detail := UsageDetail{ PromptTokens: u.PromptTokens, CompletionTokens: u.CompletionTokens, TotalTokens: u.TotalTokens, } if u.PromptTokensDetails != nil { detail.CachedInputTokens = u.PromptTokensDetails.CachedTokens } if u.CompletionTokensDetails != nil { detail.ReasoningTokens = u.CompletionTokensDetails.ReasoningTokens } if detail.TotalTokens == 0 { detail.TotalTokens = detail.PromptTokens + detail.CompletionTokens } if detail.TotalTokens == 0 { detail.TotalTokens = 1 } return detail } // MergeStreamUsage keeps the latest non-zero usage from streaming chunks. func MergeStreamUsage(acc UsageDetail, chunk []byte) UsageDetail { var parsed chatCompletionResponse if err := json.Unmarshal(chunk, &parsed); err != nil || parsed.Usage == nil { return acc } next := usageFromPayload(parsed.Usage) if next.TotalTokens == 0 && next.PromptTokens == 0 && next.CompletionTokens == 0 { return acc } return next }