@@ -29,12 +29,66 @@ export function anthropicToolsToOpenAI(
2929 function : {
3030 name,
3131 description,
32- parameters : inputSchema || { type : 'object' , properties : { } } ,
32+ parameters : sanitizeJsonSchema ( inputSchema || { type : 'object' , properties : { } } ) ,
3333 } ,
3434 } satisfies ChatCompletionTool
3535 } )
3636}
3737
38+ /**
39+ * Recursively sanitize a JSON Schema for OpenAI-compatible providers.
40+ *
41+ * Many OpenAI-compatible endpoints (Ollama, DeepSeek, vLLM, etc.) do not
42+ * support the `const` keyword in JSON Schema. Convert it to `enum` with a
43+ * single-element array, which is semantically equivalent.
44+ */
45+ function sanitizeJsonSchema ( schema : Record < string , unknown > ) : Record < string , unknown > {
46+ if ( ! schema || typeof schema !== 'object' ) return schema
47+
48+ const result = { ...schema }
49+
50+ // Convert `const` → `enum: [value]`
51+ if ( 'const' in result ) {
52+ result . enum = [ result . const ]
53+ delete result . const
54+ }
55+
56+ // Recursively process nested schemas
57+ const objectKeys = [ 'properties' , 'definitions' , '$defs' , 'patternProperties' ] as const
58+ for ( const key of objectKeys ) {
59+ const nested = result [ key ]
60+ if ( nested && typeof nested === 'object' ) {
61+ const sanitized : Record < string , unknown > = { }
62+ for ( const [ k , v ] of Object . entries ( nested as Record < string , unknown > ) ) {
63+ sanitized [ k ] = v && typeof v === 'object' ? sanitizeJsonSchema ( v as Record < string , unknown > ) : v
64+ }
65+ result [ key ] = sanitized
66+ }
67+ }
68+
69+ // Recursively process single-schema keys
70+ const singleKeys = [ 'items' , 'additionalProperties' , 'not' , 'if' , 'then' , 'else' , 'contains' , 'propertyNames' ] as const
71+ for ( const key of singleKeys ) {
72+ const nested = result [ key ]
73+ if ( nested && typeof nested === 'object' && ! Array . isArray ( nested ) ) {
74+ result [ key ] = sanitizeJsonSchema ( nested as Record < string , unknown > )
75+ }
76+ }
77+
78+ // Recursively process array-of-schemas keys
79+ const arrayKeys = [ 'anyOf' , 'oneOf' , 'allOf' ] as const
80+ for ( const key of arrayKeys ) {
81+ const nested = result [ key ]
82+ if ( Array . isArray ( nested ) ) {
83+ result [ key ] = nested . map ( item =>
84+ item && typeof item === 'object' ? sanitizeJsonSchema ( item as Record < string , unknown > ) : item
85+ )
86+ }
87+ }
88+
89+ return result
90+ }
91+
3892/**
3993 * Map Anthropic tool_choice to OpenAI tool_choice format.
4094 *
0 commit comments