diff --git a/packages/llm/src/protocols/anthropic-messages.ts b/packages/llm/src/protocols/anthropic-messages.ts index f9b7ef523a..4f02248a9c 100644 --- a/packages/llm/src/protocols/anthropic-messages.ts +++ b/packages/llm/src/protocols/anthropic-messages.ts @@ -384,7 +384,7 @@ const mapUsage = (usage: AnthropicUsage | undefined): Usage | undefined => { cacheReadInputTokens: cacheRead, cacheWriteInputTokens: cacheWrite, totalTokens: ProviderShared.totalTokens(inputTokens, usage.output_tokens, undefined), - native: usage, + providerMetadata: { anthropic: usage }, }) } @@ -408,7 +408,12 @@ const mergeUsage = (left: Usage | undefined, right: Usage | undefined) => { cacheReadInputTokens, cacheWriteInputTokens, totalTokens: ProviderShared.totalTokens(inputTokens, outputTokens, undefined), - native: { ...left.native, ...right.native }, + providerMetadata: { + anthropic: { + ...(left.providerMetadata?.["anthropic"] ?? {}), + ...(right.providerMetadata?.["anthropic"] ?? {}), + }, + }, }) } diff --git a/packages/llm/src/protocols/bedrock-converse.ts b/packages/llm/src/protocols/bedrock-converse.ts index 8385c7fe51..eadfda3a0b 100644 --- a/packages/llm/src/protocols/bedrock-converse.ts +++ b/packages/llm/src/protocols/bedrock-converse.ts @@ -378,7 +378,7 @@ const mapUsage = (usage: BedrockUsageSchema | undefined): Usage | undefined => { cacheReadInputTokens: usage.cacheReadInputTokens, cacheWriteInputTokens: usage.cacheWriteInputTokens, totalTokens: ProviderShared.totalTokens(usage.inputTokens, usage.outputTokens, usage.totalTokens), - native: usage, + providerMetadata: { bedrock: usage }, }) } diff --git a/packages/llm/src/protocols/gemini.ts b/packages/llm/src/protocols/gemini.ts index f78a6c9e87..ff6f3f83ec 100644 --- a/packages/llm/src/protocols/gemini.ts +++ b/packages/llm/src/protocols/gemini.ts @@ -304,7 +304,7 @@ const mapUsage = (usage: GeminiUsage | undefined) => { cacheReadInputTokens: cached, reasoningTokens: usage.thoughtsTokenCount, totalTokens: ProviderShared.totalTokens(usage.promptTokenCount, outputTokens, usage.totalTokenCount), - native: usage, + providerMetadata: { google: usage }, }) } diff --git a/packages/llm/src/protocols/openai-chat.ts b/packages/llm/src/protocols/openai-chat.ts index 6633f1bfed..133adb503b 100644 --- a/packages/llm/src/protocols/openai-chat.ts +++ b/packages/llm/src/protocols/openai-chat.ts @@ -307,7 +307,7 @@ const mapUsage = (usage: OpenAIChatEvent["usage"]): Usage | undefined => { cacheReadInputTokens: cached, reasoningTokens: reasoning, totalTokens: ProviderShared.totalTokens(usage.prompt_tokens, usage.completion_tokens, usage.total_tokens), - native: usage, + providerMetadata: { openai: usage }, }) } diff --git a/packages/llm/src/protocols/openai-responses.ts b/packages/llm/src/protocols/openai-responses.ts index a90a5d32c7..035cc07713 100644 --- a/packages/llm/src/protocols/openai-responses.ts +++ b/packages/llm/src/protocols/openai-responses.ts @@ -292,7 +292,7 @@ const mapUsage = (usage: OpenAIResponsesUsage | null | undefined) => { cacheReadInputTokens: cached, reasoningTokens: reasoning, totalTokens: ProviderShared.totalTokens(usage.input_tokens, usage.output_tokens, usage.total_tokens), - native: usage, + providerMetadata: { openai: usage }, }) } diff --git a/packages/llm/src/schema/events.ts b/packages/llm/src/schema/events.ts index 5c34a01b5c..6e6bb1541b 100644 --- a/packages/llm/src/schema/events.ts +++ b/packages/llm/src/schema/events.ts @@ -42,7 +42,10 @@ import { ToolResultValue } from "./messages" * `reasoningTokens` is `undefined` and `outputTokens` carries the * combined total — a documented limitation of the Anthropic API. * - * `native` always carries the provider's raw usage payload for debugging. + * `providerMetadata` always carries the provider's raw usage payload — + * keyed by provider name (`{ openai: ... }`, `{ anthropic: ... }`, etc.) + * — for fields we don't normalize and for billing-level audit trails. + * Matches the same escape-hatch field on `LLMEvent`. */ export class Usage extends Schema.Class("LLM.Usage")({ inputTokens: Schema.optional(Schema.Number), @@ -52,7 +55,7 @@ export class Usage extends Schema.Class("LLM.Usage")({ cacheWriteInputTokens: Schema.optional(Schema.Number), reasoningTokens: Schema.optional(Schema.Number), totalTokens: Schema.optional(Schema.Number), - native: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), + providerMetadata: Schema.optional(ProviderMetadata), }) { /** * Visible output tokens — `outputTokens` minus `reasoningTokens`, clamped diff --git a/packages/llm/test/provider/anthropic-messages.test.ts b/packages/llm/test/provider/anthropic-messages.test.ts index eb867530c3..0005ae7dfe 100644 --- a/packages/llm/test/provider/anthropic-messages.test.ts +++ b/packages/llm/test/provider/anthropic-messages.test.ts @@ -158,7 +158,7 @@ describe("Anthropic Messages route", () => { outputTokens: 1, nonCachedInputTokens: 5, totalTokens: 6, - native: { input_tokens: 5, output_tokens: 1 }, + providerMetadata: { anthropic: { input_tokens: 5, output_tokens: 1 } }, }), }, ]) diff --git a/packages/llm/test/provider/gemini.test.ts b/packages/llm/test/provider/gemini.test.ts index 50f597b429..e0b3864a26 100644 --- a/packages/llm/test/provider/gemini.test.ts +++ b/packages/llm/test/provider/gemini.test.ts @@ -218,12 +218,14 @@ describe("Gemini route", () => { cacheReadInputTokens: 1, reasoningTokens: 1, totalTokens: 7, - native: { - promptTokenCount: 5, - candidatesTokenCount: 2, - totalTokenCount: 7, - thoughtsTokenCount: 1, - cachedContentTokenCount: 1, + providerMetadata: { + google: { + promptTokenCount: 5, + candidatesTokenCount: 2, + totalTokenCount: 7, + thoughtsTokenCount: 1, + cachedContentTokenCount: 1, + }, }, }), }, @@ -264,7 +266,7 @@ describe("Gemini route", () => { outputTokens: 1, nonCachedInputTokens: 5, totalTokens: 6, - native: { promptTokenCount: 5, candidatesTokenCount: 1 }, + providerMetadata: { google: { promptTokenCount: 5, candidatesTokenCount: 1 } }, }), }, ]) diff --git a/packages/llm/test/provider/openai-chat.test.ts b/packages/llm/test/provider/openai-chat.test.ts index 1bac72ba64..2c692dcd7d 100644 --- a/packages/llm/test/provider/openai-chat.test.ts +++ b/packages/llm/test/provider/openai-chat.test.ts @@ -237,12 +237,14 @@ describe("OpenAI Chat route", () => { cacheReadInputTokens: 1, reasoningTokens: 0, totalTokens: 7, - native: { - prompt_tokens: 5, - completion_tokens: 2, - total_tokens: 7, - prompt_tokens_details: { cached_tokens: 1 }, - completion_tokens_details: { reasoning_tokens: 0 }, + providerMetadata: { + openai: { + prompt_tokens: 5, + completion_tokens: 2, + total_tokens: 7, + prompt_tokens_details: { cached_tokens: 1 }, + completion_tokens_details: { reasoning_tokens: 0 }, + }, }, }), }, diff --git a/packages/llm/test/provider/openai-responses.test.ts b/packages/llm/test/provider/openai-responses.test.ts index 3cdb3e070b..2319857ed1 100644 --- a/packages/llm/test/provider/openai-responses.test.ts +++ b/packages/llm/test/provider/openai-responses.test.ts @@ -349,12 +349,14 @@ describe("OpenAI Responses route", () => { cacheReadInputTokens: 1, reasoningTokens: 0, totalTokens: 7, - native: { - input_tokens: 5, - output_tokens: 2, - total_tokens: 7, - input_tokens_details: { cached_tokens: 1 }, - output_tokens_details: { reasoning_tokens: 0 }, + providerMetadata: { + openai: { + input_tokens: 5, + output_tokens: 2, + total_tokens: 7, + input_tokens_details: { cached_tokens: 1 }, + output_tokens_details: { reasoning_tokens: 0 }, + }, }, }), }, @@ -417,7 +419,7 @@ describe("OpenAI Responses route", () => { outputTokens: 1, nonCachedInputTokens: 5, totalTokens: 6, - native: { input_tokens: 5, output_tokens: 1 }, + providerMetadata: { openai: { input_tokens: 5, output_tokens: 1 } }, }), }, ])