@@ -38,6 +38,7 @@ interface ToolCallback {
3838 readonly id : string ;
3939 readonly index : number ;
4040 args : string ;
41+ thoughtSignature ?: string ;
4142}
4243const convertMessageToPart = ( message : LanguageModelMessage ) : Part [ ] | undefined => {
4344 if ( LanguageModelMessage . isTextMessage ( message ) && message . text . length > 0 ) {
@@ -52,7 +53,7 @@ const convertMessageToPart = (message: LanguageModelMessage): Part[] | undefined
5253 return [ { functionResponse : { id : message . tool_use_id , name : message . name , response : { output : message . content } } } ] ;
5354
5455 } else if ( LanguageModelMessage . isThinkingMessage ( message ) ) {
55- return [ { thought : true } , { text : message . thinking } ] ;
56+ return [ { thought : true , text : message . thinking } ] ;
5657 } else if ( LanguageModelMessage . isImageMessage ( message ) && ImageContent . isBase64 ( message . image ) ) {
5758 return [ { inlineData : { data : message . image . base64data , mimeType : message . image . mimeType } } ] ;
5859 }
@@ -76,31 +77,61 @@ function transformToGeminiMessages(
7677 continue ; // Skip system messages as they're handled separately
7778 }
7879 const resultParts = convertMessageToPart ( message ) ;
79- if ( resultParts === undefined ) {
80+ if ( resultParts === undefined || resultParts . length === 0 ) {
8081 continue ;
8182 }
8283
8384 const role = toGoogleRole ( message ) ;
84- const lastContent = contents . pop ( ) ;
85+ const lastContent = contents . length > 0 ? contents [ contents . length - 1 ] : undefined ;
8586
8687 if ( ! lastContent ) {
8788 contents . push ( { role, parts : resultParts } ) ;
8889 } else if ( lastContent . role !== role ) {
89- contents . push ( lastContent ) ;
9090 contents . push ( { role, parts : resultParts } ) ;
9191 } else {
92- lastContent ?. parts ?. push ( ...resultParts ) ;
93- contents . push ( lastContent ) ;
92+ if ( lastContent . parts ) {
93+ lastContent . parts . push ( ...resultParts ) ;
94+ } else {
95+ lastContent . parts = resultParts ;
96+ }
9497 }
9598
9699 }
97100
101+ // Ensure we have at least one content
102+ if ( contents . length === 0 ) {
103+ throw new Error ( 'No valid messages to send to Gemini API' ) ;
104+ }
105+
98106 return {
99107 contents : contents ,
100108 systemMessage,
101109 } ;
102110}
103111
112+ /**
113+ * Validates that all items in the array are proper Content objects with role and parts
114+ * @param contents Array to validate
115+ * @throws Error if validation fails
116+ */
117+ function validateContents ( contents : Content [ ] ) : void {
118+ for ( let i = 0 ; i < contents . length ; i ++ ) {
119+ const content = contents [ i ] ;
120+ if ( ! content || typeof content !== 'object' ) {
121+ throw new Error ( `Invalid content at index ${ i } : not an object` ) ;
122+ }
123+ if ( ! content . role || ( content . role !== 'user' && content . role !== 'model' ) ) {
124+ throw new Error ( `Invalid content at index ${ i } : missing or invalid role (got ${ content . role } )` ) ;
125+ }
126+ if ( ! content . parts || ! Array . isArray ( content . parts ) ) {
127+ throw new Error ( `Invalid content at index ${ i } : missing or invalid parts array` ) ;
128+ }
129+ if ( content . parts . length === 0 ) {
130+ throw new Error ( `Invalid content at index ${ i } : parts array is empty` ) ;
131+ }
132+ }
133+ }
134+
104135export const GoogleModelIdentifier = Symbol ( 'GoogleModelIdentifier' ) ;
105136
106137/**
@@ -171,6 +202,12 @@ export class GoogleModel implements LanguageModel {
171202 } ;
172203 }
173204
205+ // Merge contents and tool messages
206+ const allContents = [ ...contents , ...( toolMessages ?? [ ] ) ] ;
207+
208+ // Validate all contents before making API call
209+ validateContents ( allContents ) ;
210+
174211 // Wrap the API call in the retry mechanism
175212 const stream = await this . withRetry ( async ( ) =>
176213 genAI . models . generateContentStream ( {
@@ -187,7 +224,7 @@ export class GoogleModel implements LanguageModel {
187224 temperature : 1 ,
188225 ...settings
189226 } ,
190- contents : [ ... contents , ... ( toolMessages ?? [ ] ) ]
227+ contents : allContents
191228 } ) ) ;
192229
193230 const that = this ;
@@ -228,11 +265,16 @@ export class GoogleModel implements LanguageModel {
228265 const callId = functionCall . id ?? crypto . randomUUID ( ) . replace ( / - / g, '' ) ;
229266 let toolCall = toolCallMap [ callId ] ;
230267 if ( toolCall === undefined ) {
268+ const candidateParts = chunk . candidates ?. [ 0 ] ?. content ?. parts ;
269+ const matchingPart = candidateParts ?. find ( p =>
270+ p . functionCall ?. id === callId && p . thoughtSignature
271+ ) ;
231272 toolCall = {
232273 name : functionCall . name ?? '' ,
233274 args : functionCall . args ? JSON . stringify ( functionCall . args ) : '{}' ,
234275 id : callId ,
235- index : functionIndex ++
276+ index : functionIndex ++ ,
277+ thoughtSignature : matchingPart ?. thoughtSignature
236278 } ;
237279 toolCallMap [ callId ] = toolCall ;
238280
@@ -310,18 +352,33 @@ export class GoogleModel implements LanguageModel {
310352 yield { tool_calls : calls } ;
311353
312354 // Format tool responses for Gemini
313- const toolResponses : Part [ ] = toolResult . map ( call => ( {
314- functionResponse : {
315- id : call . id ,
316- name : call . name ,
317- response : that . formatToolCallResult ( call . result )
355+ const toolResponses : Part [ ] = toolResult . map ( call => {
356+ const toolCall = toolCallMap [ call . id ] ;
357+ const part : Part = {
358+ functionResponse : {
359+ id : call . id ,
360+ name : call . name ,
361+ response : that . formatToolCallResult ( call . result )
362+ }
363+ } ;
364+ if ( toolCall ?. thoughtSignature ) {
365+ part . thoughtSignature = toolCall . thoughtSignature ;
318366 }
319- } ) ) ;
367+ return part ;
368+ } ) ;
320369 const responseMessage : Content = { role : 'user' , parts : toolResponses } ;
321370
322371 const messages = [ ...( toolMessages ?? [ ] ) ] ;
323372 if ( currentContent ) {
324- messages . push ( currentContent ) ;
373+ // Ensure currentContent has proper structure
374+ if ( ! currentContent . role ) {
375+ currentContent . role = 'model' ;
376+ }
377+ if ( ! currentContent . parts || currentContent . parts . length === 0 ) {
378+ console . warn ( 'currentContent has no parts, skipping' ) ;
379+ } else {
380+ messages . push ( currentContent ) ;
381+ }
325382 }
326383 messages . push ( responseMessage ) ;
327384 // Continue the conversation with tool results
@@ -374,6 +431,9 @@ export class GoogleModel implements LanguageModel {
374431 const { contents, systemMessage } = transformToGeminiMessages ( request . messages ) ;
375432 const functionDeclarations = this . createFunctionDeclarations ( request ) ;
376433
434+ // Validate contents before making API call
435+ validateContents ( contents ) ;
436+
377437 // Wrap the API call in the retry mechanism
378438 const model = await this . withRetry ( async ( ) => genAI . models . generateContent ( {
379439 model : this . model ,
0 commit comments