Skip to content

Conversation

@amacsmith
Copy link

@amacsmith amacsmith commented Jan 10, 2026

Fixes #216

Gemini 3.0 models require thought signatures to be preserved and sent back during function calling. When the model returns a functionCall with a thoughtSignature, it must be included in subsequent API requests or the API returns a 400 validation error.

Changes:

  • Add optional metadata field to ToolCall type for provider-specific data
  • Update ToolCallStreamChunk to use the ToolCall interface
  • Capture thoughtSignature from Gemini responses in stream processing
  • Include thoughtSignature when formatting messages back to Gemini API

This fix ensures compatibility with Gemini 3.0 Flash and other Gemini 3 models that enforce thought signature validation for tool calls.

🎯 Changes

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • New Features
    • Added Gemini 3.0 compatibility support for AI adapters.
    • Enhanced tool call handling with provider-specific metadata support for improved consistency across streaming operations.

✏️ Tip: You can customize this high-level summary in your review settings.

Fixes TanStack#216

Gemini 3.0 models require thought signatures to be preserved and sent
back during function calling. When the model returns a functionCall
with a thoughtSignature, it must be included in subsequent API requests
or the API returns a 400 validation error.

Changes:
- Add optional `metadata` field to `ToolCall` type for provider-specific data
- Update `ToolCallStreamChunk` to use the `ToolCall` interface
- Capture thoughtSignature from Gemini responses in stream processing
- Include thoughtSignature when formatting messages back to Gemini API

This fix ensures compatibility with Gemini 3.0 Flash and other Gemini 3
models that enforce thought signature validation for tool calls.
Copilot AI review requested due to automatic review settings January 10, 2026 19:53
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 10, 2026

📝 Walkthrough

Walkthrough

The changes add Gemini 3.0 compatibility by introducing a metadata field to ToolCall and implementing thoughtSignature propagation. The text adapter now captures the thoughtSignature from tool call parts and includes it in emitted tool_call metadata, while ToolCallStreamChunk is standardized to directly reference ToolCall instead of inlining the object structure.

Changes

Cohort / File(s) Summary
Type System Updates
packages/typescript/ai/src/types.ts
Added optional metadata?: unknown field to ToolCall interface for provider-specific metadata. Changed ToolCallStreamChunk.toolCall from inline nested object to direct ToolCall type reference for consistency.
Gemini Adapter Implementation
packages/typescript/ai-gemini/src/adapters/text.ts
Captures thoughtSignature from tool call parts and propagates it into the emitted tool_call's metadata. Injects thoughtSignature into formatted Part objects for assistant tool calls. Ensures signature preservation across both streaming emissions and finishReason handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • AlemTuzlak

Poem

🐰 A thought signature now takes flight,
Through Gemini's tools, shining bright!
From parts to metadata it flows,
The thoughtful rabbit knows, it goes!
✨ Three-oh compatibility grows! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: preserving thought signatures for Gemini 3.0 compatibility, which matches the primary objective of the PR.
Description check ✅ Passed The PR description includes the required sections (Changes, Checklist, Release Impact) with substantive details about what was changed and why, though some checklist items remain unchecked.
Linked Issues check ✅ Passed The PR directly addresses issue #216 by implementing the required functionality: capturing thoughtSignature from Gemini responses, storing it in metadata, and including it in subsequent API requests to satisfy Gemini 3.0 validation.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the thought signature issue: adding metadata field to ToolCall, updating ToolCallStreamChunk, and modifying Gemini adapter to capture and propagate thoughtSignature.
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 docstrings

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/typescript/ai-gemini/src/adapters/text.ts (1)

333-354: Consistent thoughtSignature handling for UNEXPECTED_TOOL_CALL.

The same thoughtSignature capture pattern is correctly applied when handling UNEXPECTED_TOOL_CALL finish reasons, ensuring consistent metadata propagation across all tool call scenarios.

Optional: Extract helper to reduce duplication

The thoughtSignature capture logic (lines 274-278 and 333-337) could be extracted to a small helper function:

private captureThoughtSignature(part: Part): { thoughtSignature: string } | undefined {
  return 'thoughtSignature' in part && part.thoughtSignature
    ? { thoughtSignature: part.thoughtSignature }
    : undefined
}

Then use: const metadata = this.captureThoughtSignature(part)

This is optional given the simplicity and limited scope of the duplication.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e37d8b and 8eb1c7c.

📒 Files selected for processing (2)
  • packages/typescript/ai-gemini/src/adapters/text.ts
  • packages/typescript/ai/src/types.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use tree-shakeable adapter architecture for provider implementations - export specialized adapters (text, embedding, summarize, image) as separate imports from /adapters subpath rather than monolithic adapters
Use Zod for runtime schema validation and type inference, particularly for tool input/output definitions with toolDefinition() and Zod schema inference
Implement isomorphic tool system using toolDefinition() with .server() and .client() implementations for dual-environment execution
Use type-safe per-model configuration with provider options typed based on selected model to ensure compile-time safety
Implement stream processing with StreamProcessor for handling chunked responses and support partial JSON parsing for streaming AI responses

Files:

  • packages/typescript/ai/src/types.ts
  • packages/typescript/ai-gemini/src/adapters/text.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use camelCase for function and variable names throughout the codebase

Files:

  • packages/typescript/ai/src/types.ts
  • packages/typescript/ai-gemini/src/adapters/text.ts
packages/typescript/*/src/adapters/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Create individual adapter implementations for each provider capability (text, embed, summarize, image) with separate exports to enable tree-shaking

Files:

  • packages/typescript/ai-gemini/src/adapters/text.ts
🧠 Learnings (4)
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to packages/typescript/*/src/model-meta.ts : Maintain model metadata files that define provider options and capabilities per model for per-model type safety

Applied to files:

  • packages/typescript/ai/src/types.ts
  • packages/typescript/ai-gemini/src/adapters/text.ts
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to **/*.{ts,tsx} : Implement stream processing with StreamProcessor for handling chunked responses and support partial JSON parsing for streaming AI responses

Applied to files:

  • packages/typescript/ai/src/types.ts
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to packages/typescript/*/src/adapters/*.ts : Create individual adapter implementations for each provider capability (text, embed, summarize, image) with separate exports to enable tree-shaking

Applied to files:

  • packages/typescript/ai-gemini/src/adapters/text.ts
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to packages/typescript/*/src/index.ts : Export tree-shakeable adapters with clear subpath exports in package.json (e.g., `tanstack/ai/adapters`, `tanstack/ai-openai/adapters`) to minimize bundle size

Applied to files:

  • packages/typescript/ai-gemini/src/adapters/text.ts
🧬 Code graph analysis (1)
packages/typescript/ai/src/types.ts (1)
packages/typescript/ai-devtools/src/store/ai-context.tsx (1)
  • ToolCall (17-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent
🔇 Additional comments (4)
packages/typescript/ai/src/types.ts (2)

94-99: LGTM! Well-documented provider metadata field.

The optional metadata field with unknown type provides the necessary flexibility for adapters to attach provider-specific data like Gemini's thoughtSignature. The documentation clearly explains the purpose and usage.


684-684: LGTM! Improved type consistency.

Standardizing ToolCallStreamChunk to directly reference the ToolCall interface eliminates type duplication and ensures automatic consistency when the ToolCall interface is extended (like the new metadata field).

packages/typescript/ai-gemini/src/adapters/text.ts (2)

478-495: LGTM! Completes thoughtSignature round-trip for Gemini 3.0.

The code correctly extracts thoughtSignature from toolCall.metadata and injects it back into the Part object when formatting messages for Gemini. The type guards are thorough, and the as any cast is necessary since the Part type definition from @google/genai doesn't include thoughtSignature despite the API requiring it.

This completes the round-trip: capture from Gemini response → store in metadata → inject back to Gemini request, ensuring Gemini 3.0 validation passes.


274-292: Correct implementation of required Gemini 3.0 thoughtSignature handling.

The code properly captures thoughtSignature from function call parts and stores it in the emitted tool call's metadata field. This is critical for Gemini 3.0 API compatibility—thought signatures are strictly enforced and must be preserved exactly and returned in follow-up requests to avoid 400 errors. The implementation correctly uses existence checks and properly integrates with the ToolCall.metadata field, which is documented as supporting provider-specific metadata for this purpose.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for Gemini 3.0 thought signatures in function calling. Gemini 3.0 models require thought signatures to be preserved and sent back during function calling to avoid API validation errors.

Changes:

  • Added optional metadata field to ToolCall type for provider-specific data storage
  • Updated ToolCallStreamChunk to reuse the ToolCall interface instead of inline type definition
  • Implemented thought signature capture from Gemini API responses and preservation when formatting messages

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
packages/typescript/ai/src/types.ts Added optional metadata field to ToolCall interface; refactored ToolCallStreamChunk to use ToolCall type
packages/typescript/ai-gemini/src/adapters/text.ts Captured thought signatures from streaming responses; preserved thought signatures when formatting tool calls back to API

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

'thoughtSignature' in toolCall.metadata &&
typeof toolCall.metadata.thoughtSignature === 'string'
) {
;(part as any).thoughtSignature = toolCall.metadata.thoughtSignature
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

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

Using a type assertion to any to bypass TypeScript's type checking when assigning thoughtSignature. This makes the code less type-safe and harder to maintain. Consider defining a more specific type or interface that includes the thoughtSignature property, or use a type-safe approach to add the property.

Copilot uses AI. Check for mistakes.
Comment on lines +274 to +278
// Capture thought signature for Gemini 3.0 compatibility
const metadata =
'thoughtSignature' in part && part.thoughtSignature
? { thoughtSignature: part.thoughtSignature }
: undefined
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

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

The logic for capturing thoughtSignature from parts is duplicated in multiple locations (lines 274-278, 333-337, and the similar pattern at line 485-492). Consider extracting this into a helper function to reduce duplication and improve maintainability. For example, a function like extractThoughtSignatureMetadata(part) could be reused across all these locations.

Copilot uses AI. Check for mistakes.
Comment on lines +276 to +277
'thoughtSignature' in part && part.thoughtSignature
? { thoughtSignature: part.thoughtSignature }
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

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

The code accesses part.thoughtSignature without a type assertion, which will cause a TypeScript compilation error if thoughtSignature is not a property of the Part type from @google/genai. While the runtime check using the in operator is correct, TypeScript doesn't narrow the type based on this check. Consider using a type assertion like (part as any).thoughtSignature consistently, or define a type guard function that properly narrows the type.

Suggested change
'thoughtSignature' in part && part.thoughtSignature
? { thoughtSignature: part.thoughtSignature }
'thoughtSignature' in (part as any) && (part as any).thoughtSignature
? { thoughtSignature: (part as any).thoughtSignature }

Copilot uses AI. Check for mistakes.
Comment on lines +335 to +336
'thoughtSignature' in part && part.thoughtSignature
? { thoughtSignature: part.thoughtSignature }
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

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

The code accesses part.thoughtSignature without a type assertion, which will cause a TypeScript compilation error if thoughtSignature is not a property of the Part type from @google/genai. While the runtime check using the in operator is correct, TypeScript doesn't narrow the type based on this check. Consider using a type assertion like (part as any).thoughtSignature consistently, or define a type guard function that properly narrows the type.

Suggested change
'thoughtSignature' in part && part.thoughtSignature
? { thoughtSignature: part.thoughtSignature }
'thoughtSignature' in part && (part as any).thoughtSignature
? { thoughtSignature: (part as any).thoughtSignature }

Copilot uses AI. Check for mistakes.
Comment on lines +274 to +278
// Capture thought signature for Gemini 3.0 compatibility
const metadata =
'thoughtSignature' in part && part.thoughtSignature
? { thoughtSignature: part.thoughtSignature }
: undefined
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

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

The new thought signature capture and preservation logic is not covered by tests. Consider adding test cases that verify:

  1. Thought signatures are correctly captured from Gemini responses when present
  2. The metadata field is properly populated in ToolCall objects
  3. Thought signatures are correctly included when formatting messages back to the Gemini API
  4. The behavior is correct when thoughtSignature is absent

This would ensure the Gemini 3.0 compatibility fix works as expected and prevent regressions.

Copilot uses AI. Check for mistakes.
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.

Gemini 3 Flash: Function call is missing a thought_signature in functionCall parts

2 participants