@@ -181,7 +181,18 @@ export const prepare = Effect.fn("LLMRequestPrep.prepare")(function* (input: Pre
181181 return {
182182 system,
183183 messages,
184- tools : Object . fromEntries ( Object . entries ( tools ) . toSorted ( ( [ a ] , [ b ] ) => a . localeCompare ( b ) ) ) ,
184+ tools : Object . fromEntries (
185+ Object . entries ( tools )
186+ . toSorted ( ( [ a ] , [ b ] ) => a . localeCompare ( b ) )
187+ . map ( ( [ name , tool ] ) => {
188+ if ( input . model . api . npm === "@ai-sdk/google" || input . model . api . npm === "@ai-sdk/google-vertex" ) {
189+ const schema = tool . inputSchema as unknown
190+ const plain = isRecord ( schema ) && isRecord ( schema . jsonSchema ) ? schema . jsonSchema : isRecord ( schema ) ? schema : undefined
191+ if ( plain ) foldArrayItems ( plain )
192+ }
193+ return [ name , tool ]
194+ } ) ,
195+ ) ,
185196 params,
186197 messageTransformOptions : options ,
187198 headers : {
@@ -213,6 +224,49 @@ function resolveTools(input: Pick<PrepareInput, "tools" | "agent" | "permission"
213224 return Record . filter ( input . tools , ( _ , k ) => input . user . tools ?. [ k ] !== false && ! disabled . has ( k ) )
214225}
215226
227+ const isRecord = ( value : unknown ) : value is Record < string , unknown > =>
228+ typeof value === "object" && value !== null && ! Array . isArray ( value )
229+
230+ // @ai -sdk/google's convertJSONSchemaToOpenAPISchema splits a nullable array
231+ // written as `type: ["null", "array"]` into `anyOf: [{ type: "array" }]` but
232+ // leaves a sibling `items` dangling at the parent, which Gemini rejects. Fold
233+ // `items` into the array-typed branches of any union so the generated
234+ // function declaration carries `items` inside the array branch. Mutates the
235+ // plain schema object in place so the surrounding jsonSchema() wrapper (whose
236+ // `jsonSchema` getter returns this same reference) keeps working.
237+ const foldArrayItems = ( schema : unknown ) : void => {
238+ if ( Array . isArray ( schema ) ) {
239+ for ( const item of schema ) foldArrayItems ( item )
240+ return
241+ }
242+ if ( ! isRecord ( schema ) ) return
243+ for ( const value of Object . values ( schema ) ) foldArrayItems ( value )
244+ if ( schema . items === undefined ) return
245+ const type = schema . type
246+ if ( Array . isArray ( type ) && type . includes ( "array" ) ) {
247+ const arrayBranch : Record < string , unknown > = { type : "array" , items : schema . items }
248+ schema . anyOf = type . includes ( "null" ) ? [ arrayBranch , { type : "null" } ] : [ arrayBranch ]
249+ delete schema . type
250+ delete schema . items
251+ return
252+ }
253+ const combiner = [ "anyOf" , "oneOf" , "allOf" ] . find ( ( key ) => Array . isArray ( schema [ key ] ) )
254+ if ( ! combiner ) return
255+ const branches = schema [ combiner ]
256+ if ( ! Array . isArray ( branches ) ) return
257+ for ( const branch of branches ) {
258+ if ( ! isRecord ( branch ) ) continue
259+ const branchType = branch . type
260+ if (
261+ ( branchType === "array" || ( Array . isArray ( branchType ) && branchType . includes ( "array" ) ) ) &&
262+ branch . items === undefined
263+ ) {
264+ branch . items = schema . items
265+ }
266+ }
267+ delete schema . items
268+ }
269+
216270export function hasToolCalls ( messages : ModelMessage [ ] ) : boolean {
217271 for ( const msg of messages ) {
218272 if ( ! Array . isArray ( msg . content ) ) continue
0 commit comments