invoke-with-response-stream wraps each model event as {"bytes":"<base64>"} inside event-stream frames, so the usage fields never appear in clear text. extractUsage returned null and the pipeline fell back to estimating tokens over the base64, losing real counts and cache tokens. Decode the chunks before scanning.
extractOpenAiResponses only took response.usage from response.completed, so a
stream cut by max_output_tokens (response.incomplete) lost its usage and fell
back to estimateUsage over the whole SSE text, over-counting cost and budget.
Read response.usage from any event that carries it.
Gemini reports the tokens of tool results (Google Search, URL context) in usageMetadata.toolUsePromptTokenCount, separate from promptTokenCount. The extractor ignored it, so usage and cost were under-counted.
With prompt caching enabled, Bedrock reports inputTokens as the non-cached input only; cacheReadInputTokens and cacheWriteInputTokens are separate counters. Subtracting the cache reads again undercounted promptTokens (clamped to 0) in both the JSON and the stream-scan paths.