Skip to content

fix(ai-gemini): read/write thoughtSignature at Part level for Gemini 3.x#459

Open
pemontto wants to merge 3 commits intoTanStack:mainfrom
pemontto:fix/gemini-thought-signature-part-level
Open

fix(ai-gemini): read/write thoughtSignature at Part level for Gemini 3.x#459
pemontto wants to merge 3 commits intoTanStack:mainfrom
pemontto:fix/gemini-thought-signature-part-level

Conversation

@pemontto
Copy link
Copy Markdown

@pemontto pemontto commented Apr 16, 2026

Summary

Gemini 3.x models emit thoughtSignature as a Part-level sibling of functionCall (per the @google/genai Part type definition), not nested inside functionCall. The FunctionCall interface has no thoughtSignature property at all.

The adapter was:

  • Reading from functionCall.thoughtSignature (wrong location, doesn't exist in SDK types)
  • Writing it back nested inside functionCall (wrong location, API ignores it there)

This causes Gemini 3.x to reject subsequent tool-call turns with:

400 INVALID_ARGUMENT: "Function call is missing a thought_signature"

The @google/genai Part type (for reference)

export declare interface Part {
    functionCall?: FunctionCall;
    thoughtSignature?: string;  // <-- Part-level sibling
    // ...
}

export declare interface FunctionCall {
    id?: string;
    args?: Record<string, unknown>;
    name?: string;
    // no thoughtSignature here
}

Changes

  • Read side (processStreamChunks): reads part.thoughtSignature first, falls back to functionCall.thoughtSignature for Gemini 2.x compatibility
  • Write side (formatMessages): emits thoughtSignature as a Part-level sibling of functionCall instead of nesting it inside

Test plan

  • Existing tests pass (66/66)
  • Added test: reads Part-level thoughtSignature from Gemini 3.x streaming response and round-trips it at the Part level
  • Added test: falls back to functionCall.thoughtSignature for Gemini 2.x wire format
  • Verified fix against live gemini-3.1-pro-preview and gemini-3.1-flash-lite-preview sessions (multi-turn tool calling with thinking enabled)

Closes #403
Related: #218, #401, #404

Summary by CodeRabbit

  • Bug Fixes
    • Fixed Gemini adapter to read and emit tool-call signature at the correct part level for Gemini 3.x while preserving Gemini 2.x fallback, preventing API validation errors on subsequent tool calls.
  • Tests
    • Updated and added tests to validate part-level signature handling for Gemini 3.x and nested fallback behavior for Gemini 2.x.
  • Chores
    • Added a changeset entry documenting the adapter behavior change.

Gemini 3.x models emit thoughtSignature as a Part-level sibling of
functionCall (per @google/genai Part type), not nested inside
functionCall. The adapter was reading from functionCall.thoughtSignature
(which does not exist in the SDK types) and writing it back nested,
causing the API to reject subsequent tool-call turns with
400 INVALID_ARGUMENT: "Function call is missing a thought_signature".

Read side: check part.thoughtSignature first, fall back to
functionCall.thoughtSignature for Gemini 2.x compatibility.

Write side: emit thoughtSignature as a Part-level sibling of
functionCall instead of nesting it inside.

Closes TanStack#403
Related: TanStack#218, TanStack#401, TanStack#404
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 16, 2026

Caution

Review failed

An error occurred during the review process. Please try again later.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a68eebf-2355-4984-92d3-ac306c005239

📥 Commits

Reviewing files that changed from the base of the PR and between 37fb4c1 and 48cf4a2.

📒 Files selected for processing (1)
  • .changeset/fix-gemini-thought-signature-part-level.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • .changeset/fix-gemini-thought-signature-part-level.md

📝 Walkthrough

Walkthrough

Updates Gemini adapter and tests to read thoughtSignature from the Gemini Part-level (part.thoughtSignature) for Gemini 3.x, fall back to functionCall.thoughtSignature for Gemini 2.x, and emit thoughtSignature as a Part-level sibling to functionCall when formatting requests.

Changes

Cohort / File(s) Summary
Changesets
\.changeset/fix-gemini-thought-signature-part-level.md
Adds a patch changeset documenting the Gemini adapter fix: part-level thoughtSignature handling and request emission change.
Gemini Adapter Implementation
packages/typescript/ai-gemini/src/adapters/text.ts
Read streamed part.thoughtSignature first (fallback to functionCall.thoughtSignature), only backfill when unset, and emit thoughtSignature as a Part-level sibling to functionCall in formatted request messages.
Tests
packages/typescript/ai-gemini/tests/gemini-adapter.test.ts
Adjusts Gemini 3.x expectations to read Part-level thoughtSignature, adds Gemini 2.x test where thoughtSignature is nested under functionCall, and verifies adapter emits Part-level thoughtSignature for subsequent tool-call turns.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nudged a thought from nested gloom,

now it hops and lives at Part-level room.
Sibling to calls, it bounces free—
a tidy stream, a clearer me. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive This PR addresses the adapter-level implementation of thoughtSignature handling in Gemini 3.x (fixing the read/write paths in the adapter code). However, issue #403 identifies that the root cause also requires core framework changes to preserve providerMetadata through the client-side UIMessage pipeline. Verify whether core framework changes from issue #403 (ToolCallPart, InternalToolCallState, updateToolCallPart, handleToolCallStartEvent, buildAssistantMessages updates) are included or planned separately, as this adapter fix alone may not fully resolve the client-side pipeline issue.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: fixing how thoughtSignature is read/written at the Part level for Gemini 3.x models.
Description check ✅ Passed The description provides comprehensive context with the Part type definition, clear explanation of the read/write changes, test coverage, and verification against live sessions. All required checklist items are addressed.
Out of Scope Changes check ✅ Passed All changes are narrowly scoped to fixing the Gemini adapter's read/write behavior for thoughtSignature at the Part level: the changeset file, text.ts adapter logic, and gemini-adapter.test.ts. No unrelated changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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.

@pemontto
Copy link
Copy Markdown
Author

Re: the pre-merge check about #403 scope.

This PR fixes the server-side adapter (the GeminiTextAdapter read/write paths), which is the direct cause of the 400 INVALID_ARGUMENT: "Function call is missing a thought_signature" errors on Gemini 3.x models.

The client-side providerMetadata pipeline issue (threading through ToolCallPart, InternalToolCallState, StreamProcessor, etc.) is a separate concern tracked in #404. That PR addresses whether providerMetadata survives the client-side UIMessage round-trip. Both fixes are needed for a complete solution, but they're independent: this adapter fix stops the API rejections, and #404 ensures the metadata persists through the client layer.

@mattsoltani
Copy link
Copy Markdown

mattsoltani commented Apr 17, 2026

Can confirm this also fixes the issue we were experiencing missing thought signature

Comment on lines +359 to +360
(part as any).thoughtSignature ||
(functionCall as any).thoughtSignature ||
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are these cast to any? can we update the sdk or whatever else is required to make this typesafe (I'm aware it was cast before as well below)

@nx-cloud
Copy link
Copy Markdown

nx-cloud bot commented Apr 20, 2026

View your CI Pipeline Execution ↗ for commit 48cf4a2

Command Status Duration Result
nx run-many --targets=build --exclude=examples/** ✅ Succeeded 48s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-20 15:23:42 UTC

@nx-cloud
Copy link
Copy Markdown

nx-cloud bot commented Apr 20, 2026

View your CI Pipeline Execution ↗ for commit 48cf4a2

Command Status Duration Result
nx run-many --targets=build --exclude=examples/** ✅ Succeeded 48s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-20 15:22:47 UTC

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

3 participants