@@ -17,11 +17,12 @@ export interface Tool {
1717/**
1818 * Cleans up tool definitions to ensure strict JSON Schema compliance.
1919 *
20- * Implements "require" logic:
20+ * Implements "require" logic and advanced normalization :
2121 * 1. Filters 'required' array to remove properties that don't exist in 'properties'.
22- * (Fixes "property is not defined" errors from strict validators)
23- * 2. Injects a placeholder property for empty parameter objects if needed.
24- * 3. Handles nested object schemas recursively.
22+ * 2. Injects a placeholder property for empty parameter objects.
23+ * 3. Flattens 'anyOf' with 'const' values into 'enum'.
24+ * 4. Normalizes nullable types (array types) to single type + description.
25+ * 5. Removes unsupported keywords (additionalProperties, const, etc.).
2526 *
2627 * @param tools - Array of tool definitions
2728 * @returns Cleaned array of tool definitions
@@ -50,7 +51,45 @@ export function cleanupToolDefinitions(tools: unknown): unknown {
5051function cleanupSchema ( schema : Record < string , unknown > ) : void {
5152 if ( ! schema || typeof schema !== "object" ) return ;
5253
53- // 1. Filter 'required' array
54+ // 1. Flatten Unions (anyOf -> enum)
55+ if ( Array . isArray ( schema . anyOf ) ) {
56+ const anyOf = schema . anyOf as Record < string , unknown > [ ] ;
57+ const allConst = anyOf . every ( ( opt ) => "const" in opt ) ;
58+ if ( allConst && anyOf . length > 0 ) {
59+ const enumValues = anyOf . map ( ( opt ) => opt . const ) ;
60+ schema . enum = enumValues ;
61+ delete schema . anyOf ;
62+
63+ // Infer type from first value if missing
64+ if ( ! schema . type ) {
65+ const firstVal = enumValues [ 0 ] ;
66+ if ( typeof firstVal === "string" ) schema . type = "string" ;
67+ else if ( typeof firstVal === "number" ) schema . type = "number" ;
68+ else if ( typeof firstVal === "boolean" ) schema . type = "boolean" ;
69+ }
70+ }
71+ }
72+
73+ // 2. Flatten Nullable Types (["string", "null"] -> "string")
74+ if ( Array . isArray ( schema . type ) ) {
75+ const types = schema . type as string [ ] ;
76+ const isNullable = types . includes ( "null" ) ;
77+ const nonNullTypes = types . filter ( ( t ) => t !== "null" ) ;
78+
79+ if ( nonNullTypes . length > 0 ) {
80+ // Use the first non-null type (most strict models expect a single string type)
81+ schema . type = nonNullTypes [ 0 ] ;
82+ if ( isNullable ) {
83+ const desc = ( schema . description as string ) || "" ;
84+ // Only append if not already present
85+ if ( ! desc . toLowerCase ( ) . includes ( "nullable" ) ) {
86+ schema . description = desc ? `${ desc } (nullable)` : "(nullable)" ;
87+ }
88+ }
89+ }
90+ }
91+
92+ // 3. Filter 'required' array
5493 if (
5594 Array . isArray ( schema . required ) &&
5695 schema . properties &&
@@ -70,9 +109,7 @@ function cleanupSchema(schema: Record<string, unknown>): void {
70109 }
71110 }
72111
73- // 2. Handle empty object parameters (Claude/Gemini compatibility)
74- // If properties is empty but type is object, some models fail.
75- // We inject a placeholder to make it a valid non-empty object.
112+ // 4. Handle empty object parameters
76113 if (
77114 schema . type === "object" &&
78115 ( ! schema . properties || Object . keys ( schema . properties as object ) . length === 0 )
@@ -83,19 +120,23 @@ function cleanupSchema(schema: Record<string, unknown>): void {
83120 description : "This property is a placeholder and should be ignored." ,
84121 } ,
85122 } ;
86- // Ideally we shouldn't make it required unless necessary, but some validators want it.
87- // For now, we'll leave it optional to avoid forcing the model to generate it.
88123 }
89124
90- // 3. Recurse into properties
125+ // 5. Remove unsupported keywords
126+ delete schema . additionalProperties ;
127+ delete schema . const ;
128+ delete schema . title ;
129+ delete schema . $schema ;
130+
131+ // 6. Recurse into properties
91132 if ( schema . properties && typeof schema . properties === "object" ) {
92133 const props = schema . properties as Record < string , Record < string , unknown > > ;
93134 for ( const key in props ) {
94135 cleanupSchema ( props [ key ] ) ;
95136 }
96137 }
97138
98- // 4 . Recurse into array items
139+ // 7 . Recurse into array items
99140 if ( schema . items && typeof schema . items === "object" ) {
100141 cleanupSchema ( schema . items as Record < string , unknown > ) ;
101142 }
0 commit comments