@@ -191,44 +191,48 @@ export const GoogleErrorResponseTransform: (
191
191
return undefined ;
192
192
} ;
193
193
194
- const getDefFromRef = ( ref : string ) => {
195
- const refParts = ref . split ( '/' ) ;
196
- return refParts . at ( - 1 ) ;
194
+ // Extract definition key from a JSON Schema $ref string
195
+ const getDefFromRef = ( ref : string ) : string | null => {
196
+ const match = ref . match ( / ^ # \/ \$ d e f s \/ ( .+ ) $ / ) ;
197
+ return match ? match [ 1 ] : null ;
197
198
} ;
198
199
199
- const getRefParts = ( spec : Record < string , any > , ref : string ) => {
200
- return spec ?. [ ref ] ;
201
- } ;
202
-
203
- export const derefer = ( spec : Record < string , any > , defs = null ) => {
204
- const original = { ...spec } ;
205
-
206
- const finalDefs = defs ?? original ?. [ '$defs' ] ;
207
- const entries = Object . entries ( original ) ;
208
-
209
- for ( let [ key , object ] of entries ) {
210
- if ( key === '$defs' ) {
211
- continue ;
212
- }
213
- if (
214
- object === null ||
215
- typeof object !== 'object' ||
216
- Array . isArray ( object )
217
- ) {
218
- continue ;
219
- }
220
- const ref = object ?. [ '$ref' ] ;
221
- if ( ref ) {
222
- const def = getDefFromRef ( ref ) ;
223
- const defData = getRefParts ( finalDefs , def ?? '' ) ;
224
- const newValue = derefer ( defData , finalDefs ) ;
225
- original [ key ] = newValue ;
226
- } else {
227
- const newValue = derefer ( object , finalDefs ) ;
228
- original [ key ] = newValue ;
200
+ const getDefObject = (
201
+ defs : Record < string , any > | undefined | null ,
202
+ key : string | null
203
+ ) : any => ( key && defs ? defs [ key ] : undefined ) ;
204
+
205
+ // Recursively expands $ref nodes in a JSON Schema object tree
206
+ export const derefer = (
207
+ schema : any ,
208
+ defs : Record < string , any > | null = null ,
209
+ stack : Set < string > = new Set ( )
210
+ ) : any => {
211
+ if ( schema === null || typeof schema !== 'object' ) return schema ;
212
+ if ( Array . isArray ( schema ) )
213
+ return schema . map ( ( item ) => derefer ( item , defs , stack ) ) ;
214
+ const node = { ...schema } ;
215
+ const activeDefs =
216
+ defs ?? ( node . $defs as Record < string , any > | undefined ) ?? null ;
217
+ if ( '$ref' in node && typeof node . $ref === 'string' ) {
218
+ const defKey = getDefFromRef ( node . $ref ) ;
219
+ const target = getDefObject ( activeDefs , defKey ) ;
220
+ if ( defKey && target ) {
221
+ if ( stack . has ( defKey ) ) return node ;
222
+ stack . add ( defKey ) ;
223
+ const resolved = derefer ( target , activeDefs , stack ) ;
224
+ stack . delete ( defKey ) ;
225
+ const keys = Object . keys ( node ) ;
226
+ if ( keys . length === 1 ) return resolved ;
227
+ const { $ref : _ , ...siblings } = node ;
228
+ return derefer ( { ...resolved , ...siblings } , activeDefs , stack ) ;
229
229
}
230
230
}
231
- return original ;
231
+ for ( const [ k , v ] of Object . entries ( node ) ) {
232
+ if ( k === '$defs' ) continue ;
233
+ node [ k ] = derefer ( v , activeDefs , stack ) ;
234
+ }
235
+ return node ;
232
236
} ;
233
237
234
238
export const transformGeminiToolParameters = (
@@ -248,6 +252,9 @@ export const transformGeminiToolParameters = (
248
252
delete schema . $defs ;
249
253
}
250
254
255
+ const isNullTypeNode = ( node : any ) : boolean =>
256
+ node && typeof node === 'object' && node . type === 'null' ;
257
+
251
258
const transformNode = ( node : JsonSchema ) : JsonSchema => {
252
259
if ( Array . isArray ( node ) ) {
253
260
return node . map ( transformNode ) ;
@@ -260,21 +267,14 @@ export const transformGeminiToolParameters = (
260
267
if ( key === 'enum' && Array . isArray ( value ) ) {
261
268
transformed . enum = value ;
262
269
transformed . format = 'enum' ;
263
- } else if (
264
- key === 'anyOf' &&
265
- Array . isArray ( value ) &&
266
- value . length === 2
267
- ) {
268
- // Convert anyOf with null type to nullable which is a supported param
269
- const nonNullItems = value . filter (
270
- ( item ) => ! ( typeof item === 'object' && item ?. type === 'null' )
271
- ) ;
272
- if ( nonNullItems . length === 1 ) {
273
- Object . assign ( transformed , transformNode ( nonNullItems [ 0 ] ) ) ;
270
+ } else if ( ( key === 'anyOf' || key === 'oneOf' ) && Array . isArray ( value ) ) {
271
+ const nonNullItems = value . filter ( ( item ) => ! isNullTypeNode ( item ) ) ;
272
+ if ( nonNullItems . length < value . length ) {
273
+ // remove `null` type in schema and set nullable: true
274
+ transformed [ key ] = transformNode ( nonNullItems ) ;
274
275
transformed . nullable = true ;
275
276
} else {
276
- // leave true unions as-is which is not supported by Google, let Google raise an error
277
- transformed . anyOf = transformNode ( value ) ;
277
+ transformed [ key ] = transformNode ( value ) ;
278
278
}
279
279
} else {
280
280
transformed [ key ] = transformNode ( value ) ;
0 commit comments