From 60ea7abb41930a2cd370da8a7aa020a2f3106695 Mon Sep 17 00:00:00 2001 From: Lars Houmark Date: Sat, 28 Mar 2026 05:00:17 -0500 Subject: [PATCH] fix(ai): preserve providerMetadata through client-side UIMessage pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #401 added providerMetadata to ToolCall and ToolCallStartEvent and wired it through the server-side ToolCallManager. However, the client-side pipeline (StreamProcessor → UIMessage → ModelMessage) drops providerMetadata entirely, breaking Gemini 3+ thinking models on multi-turn tool calls. This commit carries providerMetadata through every step: - types.ts: add providerMetadata to ToolCallPart - stream/types.ts: add providerMetadata to InternalToolCallState - stream/message-updaters.ts: accept and forward providerMetadata - stream/processor.ts: pass chunk.providerMetadata into state and parts - messages.ts: include providerMetadata in buildAssistantMessages() and modelMessageToUIMessage() Fixes #403 --- packages/typescript/ai/src/activities/chat/messages.ts | 7 +++++++ .../ai/src/activities/chat/stream/message-updaters.ts | 6 ++++++ .../ai/src/activities/chat/stream/processor.ts | 9 +++++++++ .../typescript/ai/src/activities/chat/stream/types.ts | 2 ++ packages/typescript/ai/src/types.ts | 2 ++ 5 files changed, 26 insertions(+) diff --git a/packages/typescript/ai/src/activities/chat/messages.ts b/packages/typescript/ai/src/activities/chat/messages.ts index b7f97b880..67fa86d52 100644 --- a/packages/typescript/ai/src/activities/chat/messages.ts +++ b/packages/typescript/ai/src/activities/chat/messages.ts @@ -138,6 +138,7 @@ interface AssistantSegment { id: string type: 'function' function: { name: string; arguments: string } + providerMetadata?: Record }> } @@ -205,6 +206,9 @@ function buildAssistantMessages(uiMessage: UIMessage): Array { name: part.name, arguments: part.arguments, }, + ...(part.providerMetadata && { + providerMetadata: part.providerMetadata, + }), }) } break @@ -340,6 +344,9 @@ export function modelMessageToUIMessage( name: toolCall.function.name, arguments: toolCall.function.arguments, state: 'input-complete', // Model messages have complete arguments + ...(toolCall.providerMetadata && { + providerMetadata: toolCall.providerMetadata, + }), }) } } diff --git a/packages/typescript/ai/src/activities/chat/stream/message-updaters.ts b/packages/typescript/ai/src/activities/chat/stream/message-updaters.ts index 80b94d59a..6a18bdee2 100644 --- a/packages/typescript/ai/src/activities/chat/stream/message-updaters.ts +++ b/packages/typescript/ai/src/activities/chat/stream/message-updaters.ts @@ -55,6 +55,7 @@ export function updateToolCallPart( name: string arguments: string state: ToolCallState + providerMetadata?: Record }, ): Array { return messages.map((msg) => { @@ -67,6 +68,10 @@ export function updateToolCallPart( (p): p is ToolCallPart => p.type === 'tool-call' && p.id === toolCall.id, ) + // Carry forward providerMetadata from either the new toolCall or the existing part + const providerMetadata = + toolCall.providerMetadata ?? existing?.providerMetadata + const toolCallPart: ToolCallPart = { type: 'tool-call', id: toolCall.id, @@ -76,6 +81,7 @@ export function updateToolCallPart( // Carry forward approval and output from the existing part ...(existing?.approval && { approval: { ...existing.approval } }), ...(existing?.output !== undefined && { output: existing.output }), + ...(providerMetadata && { providerMetadata }), } if (existing) { diff --git a/packages/typescript/ai/src/activities/chat/stream/processor.ts b/packages/typescript/ai/src/activities/chat/stream/processor.ts index dc5330f17..f83b980d5 100644 --- a/packages/typescript/ai/src/activities/chat/stream/processor.ts +++ b/packages/typescript/ai/src/activities/chat/stream/processor.ts @@ -856,6 +856,9 @@ export class StreamProcessor { state: initialState, parsedArguments: undefined, index: chunk.index ?? state.toolCalls.size, + ...(chunk.providerMetadata && { + providerMetadata: chunk.providerMetadata, + }), } state.toolCalls.set(toolCallId, newToolCall) @@ -870,6 +873,9 @@ export class StreamProcessor { name: chunk.toolName, arguments: '', state: initialState, + ...(chunk.providerMetadata && { + providerMetadata: chunk.providerMetadata, + }), }) this.emitMessagesChange() @@ -1367,6 +1373,9 @@ export class StreamProcessor { name: tc.name, arguments: tc.arguments, }, + ...(tc.providerMetadata && { + providerMetadata: tc.providerMetadata, + }), }) } } diff --git a/packages/typescript/ai/src/activities/chat/stream/types.ts b/packages/typescript/ai/src/activities/chat/stream/types.ts index c1806238f..374572505 100644 --- a/packages/typescript/ai/src/activities/chat/stream/types.ts +++ b/packages/typescript/ai/src/activities/chat/stream/types.ts @@ -25,6 +25,8 @@ export interface InternalToolCallState { state: ToolCallState parsedArguments?: any index: number + /** Provider-specific metadata (e.g. Gemini thoughtSignature) */ + providerMetadata?: Record } /** diff --git a/packages/typescript/ai/src/types.ts b/packages/typescript/ai/src/types.ts index 984e15125..5a4ef502b 100644 --- a/packages/typescript/ai/src/types.ts +++ b/packages/typescript/ai/src/types.ts @@ -295,6 +295,8 @@ export interface ToolCallPart { } /** Tool execution output (for client tools or after approval) */ output?: any + /** Provider-specific metadata carried through the tool call lifecycle (e.g. Gemini thoughtSignature) */ + providerMetadata?: Record } export interface ToolResultPart {