Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions packages/typescript/ai-gemini/src/adapters/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,12 @@ export class GeminiTextAdapter<
}
}

// Capture thought signature for Gemini 3.0 compatibility
const metadata =
'thoughtSignature' in part && part.thoughtSignature
? { thoughtSignature: part.thoughtSignature }
Comment on lines +276 to +277
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.
: undefined
Comment on lines +274 to +278
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 +274 to +278
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.

yield {
type: 'tool_call',
id: generateId(this.name),
Expand All @@ -283,6 +289,7 @@ export class GeminiTextAdapter<
name: toolCallData.name,
arguments: toolCallData.args,
},
metadata,
},
index: toolCallData.index,
}
Expand Down Expand Up @@ -323,6 +330,12 @@ export class GeminiTextAdapter<
index: nextToolIndex++,
})

// Capture thought signature for Gemini 3.0 compatibility
const metadata =
'thoughtSignature' in part && part.thoughtSignature
? { thoughtSignature: part.thoughtSignature }
Comment on lines +335 to +336
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.
: undefined

yield {
type: 'tool_call',
id: generateId(this.name),
Expand All @@ -338,6 +351,7 @@ export class GeminiTextAdapter<
? functionArgs
: JSON.stringify(functionArgs),
},
metadata,
},
index: nextToolIndex - 1,
}
Expand Down Expand Up @@ -461,12 +475,24 @@ export class GeminiTextAdapter<
>
}

parts.push({
const part: Part = {
functionCall: {
name: toolCall.function.name,
args: parsedArgs,
},
})
}

// Include thought signature if present for Gemini 3.0 compatibility
if (
toolCall.metadata &&
typeof toolCall.metadata === 'object' &&
'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.
}

parts.push(part)
}
}

Expand Down
15 changes: 7 additions & 8 deletions packages/typescript/ai/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ export interface ToolCall {
name: string
arguments: string // JSON string
}
/**
* Provider-specific metadata associated with this tool call.
* Used by adapters to store additional information needed for API compatibility.
* For example, Gemini stores thought signatures here for Gemini 3.0 models.
*/
metadata?: unknown
}

// ============================================================================
Expand Down Expand Up @@ -675,14 +681,7 @@ export interface ContentStreamChunk extends BaseStreamChunk {

export interface ToolCallStreamChunk extends BaseStreamChunk {
type: 'tool_call'
toolCall: {
id: string
type: 'function'
function: {
name: string
arguments: string // Incremental JSON arguments
}
}
toolCall: ToolCall
index: number
}

Expand Down
Loading