Skip to content

fix(ai): preserve providerMetadata through client-side UIMessage pipeline#404

Open
houmark wants to merge 1 commit intoTanStack:mainfrom
houmark:fix/provider-metadata-roundtrip
Open

fix(ai): preserve providerMetadata through client-side UIMessage pipeline#404
houmark wants to merge 1 commit intoTanStack:mainfrom
houmark:fix/provider-metadata-roundtrip

Conversation

@houmark
Copy link
Copy Markdown

@houmark houmark commented Mar 28, 2026

Summary

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 PR completes the round-trip by carrying providerMetadata through every step of the client-side pipeline.

Fixes #403 (follow-up to #216, #401)

Changes (5 files, 26 lines added)

File Change
types.ts Add optional providerMetadata to ToolCallPart interface
stream/types.ts Add optional providerMetadata to InternalToolCallState
stream/message-updaters.ts Accept and carry forward providerMetadata in updateToolCallPart()
stream/processor.ts Pass chunk.providerMetadata into InternalToolCallState and updateToolCallPart() in handleToolCallStartEvent(); include it in getCompletedToolCalls()
messages.ts Include providerMetadata on toolCalls in buildAssistantMessages() (UIMessage → ModelMessage); preserve it in modelMessageToUIMessage() (ModelMessage → UIMessage)

How it works

The data flow is now:

Gemini API response
  → GeminiTextAdapter.processStreamChunks() captures thoughtSignature
  → TOOL_CALL_START event with providerMetadata: { thoughtSignature }
  → StreamProcessor.handleToolCallStartEvent() stores on InternalToolCallState + ToolCallPart
  → UIMessage.parts[].providerMetadata preserved
  → uiMessageToModelMessages() → buildAssistantMessages() includes providerMetadata on toolCalls
  → ModelMessage.toolCalls[].providerMetadata
  → GeminiTextAdapter.formatMessages() reads toolCall.providerMetadata?.thoughtSignature
  → functionCall part includes thoughtSignature
  → Gemini API accepts ✅

Test plan

Summary by CodeRabbit

  • New Features
    • Tool calls now support optional provider-specific metadata that is preserved throughout their complete lifecycle during streaming and message processing.

…line

PR TanStack#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 TanStack#403
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7b8f7417-fa49-4b77-9e5f-d33e6484cfcc

📥 Commits

Reviewing files that changed from the base of the PR and between a8a4465 and 60ea7ab.

📒 Files selected for processing (5)
  • packages/typescript/ai/src/activities/chat/messages.ts
  • packages/typescript/ai/src/activities/chat/stream/message-updaters.ts
  • packages/typescript/ai/src/activities/chat/stream/processor.ts
  • packages/typescript/ai/src/activities/chat/stream/types.ts
  • packages/typescript/ai/src/types.ts

📝 Walkthrough

Walkthrough

This PR threads providerMetadata through the client-side UIMessage processing pipeline. The metadata is now preserved when StreamProcessor converts streamed tool-call chunks into UIMessage parts, and when UIMessages are converted back to ModelMessages for server submission. This ensures provider-specific metadata (e.g., Gemini's thoughtSignature) survives multi-turn conversations.

Changes

Cohort / File(s) Summary
Type Definitions
packages/typescript/ai/src/types.ts, packages/typescript/ai/src/activities/chat/stream/types.ts
Added optional providerMetadata?: Record<string, unknown> field to ToolCallPart and InternalToolCallState interfaces to carry provider-specific metadata through the tool-call lifecycle.
Stream Processing
packages/typescript/ai/src/activities/chat/stream/processor.ts, packages/typescript/ai/src/activities/chat/stream/message-updaters.ts
Propagated providerMetadata from streamed tool-call chunks into internal state on creation and into UIMessage parts; updateToolCallPart() now accepts and preserves metadata with fallback to existing part's metadata.
Message Conversion
packages/typescript/ai/src/activities/chat/messages.ts
Conditionally attached providerMetadata when converting assistant tool-call UI parts to ModelMessage tool calls and back, ensuring metadata survives round-trip conversions.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 Through streams and messages we hop,
providerMetadata won't drop!
From chunk to part to message bright,
Thoughtsignatures take flight.
Each turn preserves what came before,
The rabbit's pipeline evermore! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: preserving providerMetadata through the client-side UIMessage pipeline, which is the primary objective of the PR.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering summary, changes, data flow, and test plan. However, the linked template checklist items (Contributing guide, testing with pnpm, and changeset generation) are not mentioned.
Linked Issues check ✅ Passed The PR directly addresses all five root causes identified in issue #403: adds providerMetadata to ToolCallPart and InternalToolCallState types [#403], updates updateToolCallPart() to accept and forward metadata [#403], passes chunk.providerMetadata in handleToolCallStartEvent() [#403], and includes providerMetadata in buildAssistantMessages() and modelMessageToUIMessage() [#403].
Out of Scope Changes check ✅ Passed All five modified files directly address the root causes of issue #403 and fit the scope of preserving providerMetadata through the client-side UIMessage pipeline. No unrelated changes are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

providerMetadata lost in client-side UIMessage pipeline — breaks Gemini 3 thoughtSignature on multi-turn tool calls

1 participant