@@ -49,14 +49,37 @@ const UNSUPPORTED_SCHEMA_FIELDS = new Set([
4949 "maxContains" ,
5050] ) ;
5151
52- export function toGeminiSchema ( schema : unknown ) : unknown {
52+ const NUMERIC_SCHEMA_CONSTRAINTS = new Set ( [
53+ "minLength" ,
54+ "maxLength" ,
55+ "minItems" ,
56+ "maxItems" ,
57+ "minProperties" ,
58+ "maxProperties" ,
59+ "minimum" ,
60+ "maximum" ,
61+ "exclusiveMinimum" ,
62+ "exclusiveMaximum" ,
63+ "multipleOf" ,
64+ ] )
65+
66+ export interface GeminiSchemaOptions {
67+ /**
68+ * AGY's GPT bridge re-encodes protobuf numeric constraints as strings before
69+ * OpenAI JSON Schema validation. Move them to descriptions instead.
70+ */
71+ moveNumericConstraintsToDescription ?: boolean
72+ }
73+
74+ export function toGeminiSchema ( schema : unknown , options : GeminiSchemaOptions = { } ) : unknown {
5375 // Return primitives and arrays as-is
5476 if ( ! schema || typeof schema !== "object" || Array . isArray ( schema ) ) {
5577 return schema ;
5678 }
5779
5880 const inputSchema = schema as Record < string , unknown > ;
5981 const result : Record < string , unknown > = { } ;
82+ const numericConstraintHints : string [ ] = [ ] ;
6083
6184 // First pass: collect all property names for required validation
6285 const propertyNames = new Set < string > ( ) ;
@@ -79,18 +102,25 @@ export function toGeminiSchema(schema: unknown): unknown {
79102 // Recursively transform nested property schemas
80103 const props : Record < string , unknown > = { } ;
81104 for ( const [ propName , propSchema ] of Object . entries ( value as Record < string , unknown > ) ) {
82- props [ propName ] = toGeminiSchema ( propSchema ) ;
105+ props [ propName ] = toGeminiSchema ( propSchema , options ) ;
83106 }
84107 result [ key ] = props ;
85108 } else if ( key === "items" && typeof value === "object" ) {
86109 // Transform array items schema
87- result [ key ] = toGeminiSchema ( value ) ;
110+ result [ key ] = toGeminiSchema ( value , options ) ;
88111 } else if ( ( key === "anyOf" || key === "oneOf" || key === "allOf" ) && Array . isArray ( value ) ) {
89112 // Transform union type schemas
90- result [ key ] = value . map ( ( item ) => toGeminiSchema ( item ) ) ;
113+ result [ key ] = value . map ( ( item ) => toGeminiSchema ( item , options ) ) ;
91114 } else if ( key === "enum" && Array . isArray ( value ) ) {
92115 // Keep enum values as-is
93116 result [ key ] = value ;
117+ } else if (
118+ options . moveNumericConstraintsToDescription &&
119+ NUMERIC_SCHEMA_CONSTRAINTS . has ( key )
120+ ) {
121+ if ( typeof value === "string" || typeof value === "number" ) {
122+ numericConstraintHints . push ( `${ key } : ${ value } ` )
123+ }
94124 } else if ( key === "default" || key === "examples" ) {
95125 // Keep default and examples as-is
96126 result [ key ] = value ;
@@ -114,6 +144,13 @@ export function toGeminiSchema(schema: unknown): unknown {
114144 }
115145 }
116146
147+ if ( numericConstraintHints . length > 0 ) {
148+ const hint = numericConstraintHints . join ( ", " )
149+ result . description = typeof result . description === "string" && result . description
150+ ? `${ result . description } (${ hint } )`
151+ : hint
152+ }
153+
117154 // Issue #80: Ensure array schemas have an 'items' field
118155 // Gemini API requires: "parameters.properties[X].items: missing field"
119156 if ( result . type === "ARRAY" && ! result . items ) {
@@ -229,6 +266,7 @@ export function buildImageGenerationConfig(): ImageConfig {
229266 */
230267export function normalizeGeminiTools (
231268 payload : RequestPayload ,
269+ schemaOptions : GeminiSchemaOptions = { } ,
232270) : { toolDebugMissing : number ; toolDebugSummaries : string [ ] } {
233271 let toolDebugMissing = 0 ;
234272 const toolDebugSummaries : string [ ] = [ ] ;
@@ -245,6 +283,22 @@ export function normalizeGeminiTools(
245283 return t ;
246284 }
247285
286+ if ( Array . isArray ( t . functionDeclarations ) ) {
287+ return {
288+ ...t ,
289+ functionDeclarations : t . functionDeclarations . map ( ( declaration ) => {
290+ const normalized = { ...( declaration as Record < string , unknown > ) }
291+ const rawSchema = normalized . parameters ?? normalized . parametersJsonSchema ?? {
292+ type : "OBJECT" ,
293+ properties : { } ,
294+ }
295+ normalized . parameters = toGeminiSchema ( rawSchema , schemaOptions )
296+ delete normalized . parametersJsonSchema
297+ return normalized
298+ } ) ,
299+ }
300+ }
301+
248302 const newTool = { ...t } ;
249303
250304 const schemaCandidates = [
@@ -276,7 +330,7 @@ export function normalizeGeminiTools(
276330 toolDebugMissing += 1 ;
277331 } else {
278332 // Transform existing schema to Gemini-compatible format
279- schema = toGeminiSchema ( schema ) as Record < string , unknown > ;
333+ schema = toGeminiSchema ( schema , schemaOptions ) as Record < string , unknown > ;
280334 }
281335
282336 const nameCandidate =
@@ -417,7 +471,9 @@ export function applyGeminiTransforms(
417471 }
418472
419473 // 3. Normalize tools
420- const result = normalizeGeminiTools ( payload ) ;
474+ const result = normalizeGeminiTools ( payload , {
475+ moveNumericConstraintsToDescription : model . toLowerCase ( ) . startsWith ( "gpt-oss-" ) ,
476+ } ) ;
421477
422478 // 4. Wrap tools in functionDeclarations format (fixes #203, #206)
423479 // Antigravity strict protobuf validation rejects wrapper-level 'parameters' field
0 commit comments