@@ -184,9 +184,10 @@ class FormatConverter {
184184 *
185185 * @param {Object } obj - The schema object to convert
186186 * @param {boolean } [isResponseSchema=false] - If true, applies stricter rules (e.g. anyOf for unions) for Structured Outputs
187+ * @param {boolean } [isProperties=false] - If true, the current object is a map of property definitions, so keys should not be filtered
187188 * @returns {Object } The converted schema
188189 */
189- _convertSchemaToGemini ( obj , isResponseSchema = false ) {
190+ _convertSchemaToGemini ( obj , isResponseSchema = false , isProperties = false ) {
190191 if ( ! obj || typeof obj !== "object" ) return obj ;
191192
192193 const result = Array . isArray ( obj ) ? [ ] : { } ;
@@ -211,11 +212,44 @@ class FormatConverter {
211212 unsupportedKeys . push ( "title" , "default" , "examples" , "$defs" , "id" ) ;
212213 }
213214
214- if ( unsupportedKeys . includes ( key ) ) {
215+ // ONLY Filter metadata keywords if NOT a property name (isProperties is false)
216+ if ( ! isProperties && unsupportedKeys . includes ( key ) ) {
215217 continue ;
216218 }
217219
218- if ( key === "type" ) {
220+ // Handle anyOf specially (only when it is a schema keyword)
221+ if ( key === "anyOf" && ! isProperties ) {
222+ if ( Array . isArray ( obj [ key ] ) ) {
223+ const variants = obj [ key ] ;
224+ const hasNull = variants . some ( v => v . type === "null" ) ;
225+ const nonNullVariants = variants . filter ( v => v . type !== "null" ) ;
226+
227+ if ( hasNull ) {
228+ result . nullable = true ;
229+ }
230+
231+ if ( nonNullVariants . length === 1 ) {
232+ // Collapse single variant. Reset isProperties to false for the variant's schema.
233+ const converted = this . _convertSchemaToGemini ( nonNullVariants [ 0 ] , isResponseSchema , false ) ;
234+ // Merge converted properties into result
235+ Object . assign ( result , converted ) ;
236+ if ( hasNull ) result . nullable = true ;
237+ continue ; // Skip setting 'anyOf' explicitly
238+ } else if ( nonNullVariants . length > 0 ) {
239+ // Keep anyOf for multiple variants. Reset isProperties for sub-schemas.
240+ result . anyOf = nonNullVariants . map ( v =>
241+ this . _convertSchemaToGemini ( v , isResponseSchema , false )
242+ ) ;
243+ continue ;
244+ } else if ( hasNull ) {
245+ // Only null type? Keep it as nullable without forcing a specific type.
246+ continue ;
247+ }
248+ }
249+ }
250+
251+ // Handle type specially (only when it is a schema keyword)
252+ if ( key === "type" && ! isProperties ) {
219253 if ( Array . isArray ( obj [ key ] ) ) {
220254 // Handle nullable types like ["string", "null"]
221255 const types = obj [ key ] ;
@@ -247,11 +281,12 @@ class FormatConverter {
247281 // Convert lowercase type to uppercase for Gemini
248282 result [ key ] = obj [ key ] . toUpperCase ( ) ;
249283 } else if ( typeof obj [ key ] === "object" && obj [ key ] !== null ) {
250- result [ key ] = this . _convertSchemaToGemini ( obj [ key ] , isResponseSchema ) ;
284+ // Type being an object is a sub-schema definition, not property name mapping
285+ result [ key ] = this . _convertSchemaToGemini ( obj [ key ] , isResponseSchema , false ) ;
251286 } else {
252287 result [ key ] = obj [ key ] ;
253288 }
254- } else if ( key === "enum" ) {
289+ } else if ( key === "enum" && ! isProperties ) {
255290 // 2. Ensure all enum values are strings (Only for Response Schema)
256291 if ( isResponseSchema ) {
257292 if ( Array . isArray ( obj [ key ] ) ) {
@@ -265,7 +300,14 @@ class FormatConverter {
265300 result [ key ] = obj [ key ] ;
266301 }
267302 } else if ( typeof obj [ key ] === "object" && obj [ key ] !== null ) {
268- result [ key ] = this . _convertSchemaToGemini ( obj [ key ] , isResponseSchema ) ;
303+ // Recursion logic:
304+ // - If key is 'properties', next level is a map of property NAMES. Set isProperties = true.
305+ // - Otherwise, if we were currently in a properties map (isProperties is true),
306+ // the value is a schema definition. For its keys, isProperties MUST be false.
307+ const nextIsProperties = key === "properties" ;
308+ const recursionFlag = isProperties ? false : nextIsProperties ;
309+
310+ result [ key ] = this . _convertSchemaToGemini ( obj [ key ] , isResponseSchema , recursionFlag ) ;
269311 } else {
270312 result [ key ] = obj [ key ] ;
271313 }
0 commit comments