From 8b5c81f21f28ac72c932fc8e4d4ddf0a46c1e67d Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 29 Jan 2026 00:06:51 +0100 Subject: [PATCH] fix(ai): preserve input token counts from message_start in Anthropic provider Proxies like Portkey omit input_tokens in message_delta events (it's nullable per the SDK). The previous code unconditionally overwrote usage fields, causing input token counts to reset to 0. Now only updates usage fields when they are present (not null), preserving the correct input_tokens value captured from message_start. Fixes #1045 --- packages/ai/CHANGELOG.md | 1 + packages/ai/src/providers/anthropic.ts | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 61502e6b..1e11168f 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -6,6 +6,7 @@ - Fixed Anthropic provider to handle `sensitive` stop_reason returned by API ([#978](https://github.com/badlogic/pi-mono/issues/978)) - Fixed DeepSeek API compatibility by detecting `deepseek.com` URLs and disabling unsupported `developer` role ([#1048](https://github.com/badlogic/pi-mono/issues/1048)) +- Fixed Anthropic provider to preserve input token counts when proxies omit them in `message_delta` events ([#1045](https://github.com/badlogic/pi-mono/issues/1045)) ## [0.50.1] - 2026-01-26 diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index da167024..6e99e852 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -304,10 +304,20 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti if (event.delta.stop_reason) { output.stopReason = mapStopReason(event.delta.stop_reason); } - output.usage.input = event.usage.input_tokens || 0; - output.usage.output = event.usage.output_tokens || 0; - output.usage.cacheRead = event.usage.cache_read_input_tokens || 0; - output.usage.cacheWrite = event.usage.cache_creation_input_tokens || 0; + // Only update usage fields if present (not null). + // Preserves input_tokens from message_start when proxies omit it in message_delta. + if (event.usage.input_tokens != null) { + output.usage.input = event.usage.input_tokens; + } + if (event.usage.output_tokens != null) { + output.usage.output = event.usage.output_tokens; + } + if (event.usage.cache_read_input_tokens != null) { + output.usage.cacheRead = event.usage.cache_read_input_tokens; + } + if (event.usage.cache_creation_input_tokens != null) { + output.usage.cacheWrite = event.usage.cache_creation_input_tokens; + } // Anthropic doesn't provide total_tokens, compute from components output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;