11import { defined } from "../../testkit/defined.js" ;
22import { describe , expect , test } from "bun:test" ;
3+ import type { ToolDefinition } from "@intx/types/runtime" ;
34import { presentDefinition } from "./director.js" ;
45import { manageTasksDefinition } from "./tasks.js" ;
56import {
@@ -152,7 +153,6 @@ describe("normalizeToolDefinitionsForProvider", () => {
152153 for ( const ctx of [
153154 { providerName : "anthropic" , model : "claude-sonnet-4" } ,
154155 { providerName : "openai" , model : "gpt-5.6" } ,
155- { providerName : "xai/default" , model : "grok-4.5" } ,
156156 { providerName : "opencode-go" , model : "gpt-5.1" } ,
157157 ] as const ) {
158158 const out = normalizeToolDefinitionsForProvider ( defs , ctx ) ;
@@ -163,6 +163,233 @@ describe("normalizeToolDefinitionsForProvider", () => {
163163 }
164164 } ) ;
165165
166+ test ( "grok sanitizes untyped properties and strips $schema" , ( ) => {
167+ const untyped : ToolDefinition = {
168+ name : "submit_result" ,
169+ description : "test" ,
170+ inputSchema : {
171+ $schema : "https://json-schema.org/draft/2020-12/schema" ,
172+ type : "object" ,
173+ properties : {
174+ turn_token : { type : "string" } ,
175+ result : { description : "The structured result payload." } ,
176+ } ,
177+ required : [ "turn_token" , "result" ] ,
178+ } ,
179+ } ;
180+ const out = normalizeToolDefinitionsForProvider ( [ untyped ] , {
181+ providerName : "xai/default-2" ,
182+ model : "grok-4.6" ,
183+ } ) ;
184+ const schema = defined ( out [ 0 ] ) . inputSchema as {
185+ $schema ?: unknown ;
186+ properties ?: {
187+ result ?: { type ?: string ; additionalProperties ?: boolean } ;
188+ } ;
189+ } ;
190+ expect ( schema . $schema ) . toBeUndefined ( ) ;
191+ expect ( schema . properties ?. result ?. type ) . toBe ( "object" ) ;
192+ expect ( schema . properties ?. result ?. additionalProperties ) . toBe ( true ) ;
193+ } ) ;
194+
195+ test ( "grok sanitizer leaves anthropic identity and typed properties alone" , ( ) => {
196+ const typed : ToolDefinition = {
197+ name : "ask_director" ,
198+ description : "test" ,
199+ inputSchema : {
200+ type : "object" ,
201+ properties : {
202+ question : { type : "string" , description : "q" } ,
203+ } ,
204+ required : [ "question" ] ,
205+ } ,
206+ } ;
207+ const input = [ typed ] ;
208+ const grok = normalizeToolDefinitionsForProvider ( input , {
209+ providerName : "xai/default" ,
210+ model : "grok-4.6" ,
211+ } ) ;
212+ const anthropic = normalizeToolDefinitionsForProvider ( input , {
213+ providerName : "anthropic" ,
214+ model : "claude-sonnet-4" ,
215+ } ) ;
216+ expect ( anthropic ) . toBe ( input ) ;
217+ expect ( grok ) . not . toBe ( input ) ;
218+ const grokQuestion = (
219+ defined ( grok [ 0 ] ) . inputSchema as {
220+ properties ?: { question ?: { type ?: string } } ;
221+ }
222+ ) . properties ?. question ;
223+ expect ( grokQuestion ?. type ) . toBe ( "string" ) ;
224+ } ) ;
225+
226+ test . each ( [
227+ { values : [ "open" , "closed" ] , types : [ "string" ] } ,
228+ { values : [ 1 , 2.5 ] , types : [ "number" ] } ,
229+ { values : [ true , false ] , types : [ "boolean" ] } ,
230+ { values : [ null ] , types : [ "null" ] } ,
231+ { values : [ [ 1 ] , [ 2 ] ] , types : [ "array" ] } ,
232+ { values : [ { state : "open" } ] , types : [ "object" ] } ,
233+ {
234+ values : [ "open" , null , 1 , true , [ ] , { } ] ,
235+ types : [ "string" , "null" , "number" , "boolean" , "array" , "object" ] ,
236+ } ,
237+ ] ) ( "grok preserves enum values with types $types" , ( { values, types } ) => {
238+ const def : ToolDefinition = {
239+ name : "mcp__tracker__set_state" ,
240+ description : "Set state" ,
241+ inputSchema : {
242+ type : "object" ,
243+ properties : { state : { enum : values } } ,
244+ required : [ "state" ] ,
245+ } ,
246+ } ;
247+ const before = structuredClone ( def ) ;
248+ const normalized = defined (
249+ normalizeToolDefinitionsForProvider ( [ def ] , {
250+ providerName : "xai/default" ,
251+ model : "grok-4.6" ,
252+ } ) [ 0 ] ,
253+ ) ;
254+ const schema = normalized . inputSchema as {
255+ properties : {
256+ state : {
257+ enum : unknown [ ] ;
258+ type ?: string ;
259+ anyOf ?: { type : string } [ ] ;
260+ } ;
261+ } ;
262+ } ;
263+ const state = schema . properties . state ;
264+ expect ( state . enum ) . toEqual ( [ ...values ] ) ;
265+ expect ( state . anyOf ?. map ( ( branch ) => branch . type ) ?? [ state . type ] ) . toEqual ( [
266+ ...types ,
267+ ] ) ;
268+ expect ( def ) . toEqual ( before ) ;
269+ expect (
270+ normalizeToolDefinitionsForProvider ( [ def ] , {
271+ providerName : "anthropic" ,
272+ model : "claude-sonnet-4" ,
273+ } ) [ 0 ] ,
274+ ) . toBe ( def ) ;
275+ } ) ;
276+
277+ test . each ( [
278+ { value : "open" , expectedType : "string" } ,
279+ { value : 1.5 , expectedType : "number" } ,
280+ { value : true , expectedType : "boolean" } ,
281+ { value : null , expectedType : "null" } ,
282+ { value : [ "open" ] , expectedType : "array" } ,
283+ { value : { state : "open" } , expectedType : "object" } ,
284+ ] ) (
285+ "grok preserves $expectedType const payloads" ,
286+ ( { value, expectedType } ) => {
287+ const def : ToolDefinition = {
288+ name : "mcp__tracker__set_state" ,
289+ description : "Set state" ,
290+ inputSchema : {
291+ type : "object" ,
292+ properties : { state : { const : value } } ,
293+ required : [ "state" ] ,
294+ } ,
295+ } ;
296+ const before = structuredClone ( def ) ;
297+ const normalized = defined (
298+ normalizeToolDefinitionsForProvider ( [ def ] , {
299+ providerName : "xai/default" ,
300+ model : "grok-4.6" ,
301+ } ) [ 0 ] ,
302+ ) ;
303+ expect ( normalized . inputSchema ) . toMatchObject ( {
304+ properties : { state : { const : value , type : expectedType } } ,
305+ } ) ;
306+ expect ( def ) . toEqual ( before ) ;
307+ } ,
308+ ) ;
309+
310+ test ( "grok sanitizes schema nodes without rewriting literal payloads or property names" , ( ) => {
311+ const literal = {
312+ $schema : "literal schema value" ,
313+ $id : "literal id value" ,
314+ properties : { payload : { description : "literal, not a schema" } } ,
315+ } ;
316+ const def : ToolDefinition = {
317+ name : "mcp__schema__save" ,
318+ description : "Save a schema document" ,
319+ inputSchema : {
320+ type : "object" ,
321+ $schema : "https://json-schema.org/draft/2020-12/schema" ,
322+ $id : "https://example.invalid/tool" ,
323+ properties : {
324+ $schema : { type : "string" } ,
325+ $id : { type : "string" } ,
326+ document : {
327+ enum : [ literal ] ,
328+ const : literal ,
329+ default : literal ,
330+ examples : [ literal ] ,
331+ } ,
332+ entries : {
333+ type : "array" ,
334+ items : {
335+ $id : "https://example.invalid/entry" ,
336+ type : "object" ,
337+ properties : { payload : { description : "untyped payload" } } ,
338+ } ,
339+ } ,
340+ } ,
341+ $defs : {
342+ $schema : {
343+ type : "object" ,
344+ properties : { payload : { description : "untyped definition" } } ,
345+ } ,
346+ } ,
347+ } ,
348+ } ;
349+ const before = structuredClone ( def ) ;
350+ const normalized = defined (
351+ normalizeToolDefinitionsForProvider ( [ def ] , {
352+ providerName : "xai/default" ,
353+ model : "grok-4.6" ,
354+ } ) [ 0 ] ,
355+ ) ;
356+ expect ( normalized . inputSchema ) . toMatchObject ( {
357+ properties : {
358+ $schema : { type : "string" } ,
359+ $id : { type : "string" } ,
360+ document : {
361+ enum : [ literal ] ,
362+ const : literal ,
363+ default : literal ,
364+ examples : [ literal ] ,
365+ } ,
366+ entries : {
367+ items : {
368+ properties : {
369+ payload : { type : "object" , additionalProperties : true } ,
370+ } ,
371+ } ,
372+ } ,
373+ } ,
374+ $defs : {
375+ $schema : {
376+ properties : {
377+ payload : { type : "object" , additionalProperties : true } ,
378+ } ,
379+ } ,
380+ } ,
381+ } ) ;
382+ const schema = normalized . inputSchema as {
383+ $schema ?: unknown ;
384+ $id ?: unknown ;
385+ properties : { entries : { items : { $id ?: unknown } } } ;
386+ } ;
387+ expect ( schema . $schema ) . toBeUndefined ( ) ;
388+ expect ( schema . $id ) . toBeUndefined ( ) ;
389+ expect ( schema . properties . entries . items . $id ) . toBeUndefined ( ) ;
390+ expect ( def ) . toEqual ( before ) ;
391+ } ) ;
392+
166393 test ( "kimi rewrite leaves non-present tools untouched" , ( ) => {
167394 const out = normalizeToolDefinitionsForProvider ( defs , {
168395 providerName : "moonshot" ,
0 commit comments