@@ -80,7 +80,7 @@ describe("AgentRuntimePlan tool policy helpers", () => {
8080 runtimePlan,
8181 tools,
8282 provider : "openai" ,
83- onPreNormalizationSchemaDiagnostics : ( entries ) => diagnostics . push ( [ ...entries ] ) ,
83+ onToolSchemaDiagnostics : ( entries ) => diagnostics . push ( [ ...entries ] ) ,
8484 } ) ,
8585 ) . toEqual ( [ healthy ] ) ;
8686 expect ( normalize ) . toHaveBeenCalledWith ( [ healthy ] , {
@@ -112,7 +112,7 @@ describe("AgentRuntimePlan tool policy helpers", () => {
112112 normalizeAgentRuntimeTools ( {
113113 tools : [ arraySchema , healthy ] ,
114114 provider : "openai" ,
115- onPreNormalizationSchemaDiagnostics : ( entries ) => diagnostics . push ( [ ...entries ] ) ,
115+ onToolSchemaDiagnostics : ( entries ) => diagnostics . push ( [ ...entries ] ) ,
116116 } ) ,
117117 ) . toEqual ( [ healthy ] ) ;
118118 expect ( mocks . normalizeProviderToolSchemas ) . toHaveBeenCalledWith (
@@ -132,6 +132,53 @@ describe("AgentRuntimePlan tool policy helpers", () => {
132132 ] ) ;
133133 } ) ;
134134
135+ it ( "quarantines provider-normalized tools that remain runtime-incompatible" , ( ) => {
136+ const source = {
137+ ...createParameterFreeTool ( "fuzzplugin_dynamic_ref" ) ,
138+ } as unknown as AgentTool ;
139+ setPluginToolMeta ( source , {
140+ pluginId : "fuzzplugin" ,
141+ optional : false ,
142+ } ) ;
143+ const dynamicSchema = {
144+ ...source ,
145+ parameters : { type : "object" , $dynamicRef : "#fuzz" } ,
146+ } as unknown as AgentTool ;
147+ const healthy = { ...createParameterFreeTool ( ) , name : "healthy" } as AgentTool ;
148+ const diagnostics : RuntimeToolSchemaDiagnostic [ ] [ ] = [ ] ;
149+ const diagnosticSources : ( readonly AgentTool [ ] ) [ ] = [ ] ;
150+ mocks . normalizeProviderToolSchemas . mockReturnValueOnce ( [ dynamicSchema , healthy ] ) ;
151+
152+ expect (
153+ normalizeAgentRuntimeTools ( {
154+ tools : [ source , healthy ] ,
155+ provider : "openai" ,
156+ onToolSchemaDiagnostics : ( entries , sourceTools ) => {
157+ diagnostics . push ( [ ...entries ] ) ;
158+ diagnosticSources . push ( sourceTools ) ;
159+ } ,
160+ } ) ,
161+ ) . toEqual ( [ healthy ] ) ;
162+ expect ( mocks . normalizeProviderToolSchemas ) . toHaveBeenCalledWith (
163+ expect . objectContaining ( {
164+ tools : [ source , healthy ] ,
165+ provider : "openai" ,
166+ } ) ,
167+ ) ;
168+ expect ( diagnostics ) . toEqual ( [
169+ [
170+ {
171+ toolName : "fuzzplugin_dynamic_ref" ,
172+ toolIndex : 0 ,
173+ violations : [ "fuzzplugin_dynamic_ref.parameters.$dynamicRef" ] ,
174+ } ,
175+ ] ,
176+ ] ) ;
177+ expect ( getPluginToolMeta ( diagnosticSources [ 0 ] ?. [ 0 ] ) ) . toMatchObject ( {
178+ pluginId : "fuzzplugin" ,
179+ } ) ;
180+ } ) ;
181+
135182 it ( "accepts legacy optional model fields while normalizing RuntimePlan context" , ( ) => {
136183 const tools = [ createParameterFreeTool ( ) ] as AgentTool [ ] ;
137184 const normalize = vi . fn ( ( ) => tools ) ;
@@ -253,7 +300,60 @@ describe("AgentRuntimePlan tool policy helpers", () => {
253300 const result = normalizeAgentRuntimeTools ( {
254301 tools : [ unreadableName , healthy ] ,
255302 provider : "openai" ,
256- onPreNormalizationSchemaDiagnostics : ( entries ) => diagnostics . push ( [ ...entries ] ) ,
303+ onToolSchemaDiagnostics : ( entries ) => diagnostics . push ( [ ...entries ] ) ,
304+ } ) ;
305+
306+ expect ( result ) . toEqual ( [ normalized ] ) ;
307+ expect ( getPluginToolMeta ( result [ 0 ] ) ) . toMatchObject ( {
308+ pluginId : "bundle-mcp" ,
309+ mcp : {
310+ serverName : "fixture" ,
311+ toolName : "lookup_note" ,
312+ } ,
313+ } ) ;
314+ expect ( diagnostics ) . toEqual ( [
315+ [
316+ {
317+ toolName : "tool[0]" ,
318+ toolIndex : 0 ,
319+ violations : [ "tool[0].name is unreadable" ] ,
320+ } ,
321+ ] ,
322+ ] ) ;
323+ } ) ;
324+
325+ it ( "quarantines unreadable provider-normalized tools before preserving metadata" , ( ) => {
326+ const source = createParameterFreeTool ( "fixture__lookup_note" ) as AgentTool ;
327+ setPluginToolMeta ( source , {
328+ pluginId : "bundle-mcp" ,
329+ optional : false ,
330+ mcp : {
331+ serverName : "fixture" ,
332+ safeServerName : "fixture" ,
333+ toolName : "lookup_note" ,
334+ operation : "tool" ,
335+ } ,
336+ } ) ;
337+ const unreadableNormalized = {
338+ ...createParameterFreeTool ( "fuzzplugin_normalized_unreadable" ) ,
339+ } as AgentTool ;
340+ Object . defineProperty ( unreadableNormalized , "name" , {
341+ enumerable : true ,
342+ get ( ) {
343+ throw new Error ( "fuzzplugin normalized name getter exploded" ) ;
344+ } ,
345+ } ) ;
346+ const normalized = {
347+ ...source ,
348+ parameters : normalizedParameterFreeSchema ( ) ,
349+ } ;
350+ const diagnostics : RuntimeToolSchemaDiagnostic [ ] [ ] = [ ] ;
351+ mocks . normalizeProviderToolSchemas . mockReturnValueOnce ( [ unreadableNormalized , normalized ] ) ;
352+
353+ const result = normalizeAgentRuntimeTools ( {
354+ tools : [ source ] ,
355+ provider : "openai" ,
356+ onToolSchemaDiagnostics : ( entries ) => diagnostics . push ( [ ...entries ] ) ,
257357 } ) ;
258358
259359 expect ( result ) . toEqual ( [ normalized ] ) ;
@@ -275,6 +375,58 @@ describe("AgentRuntimePlan tool policy helpers", () => {
275375 ] ) ;
276376 } ) ;
277377
378+ it ( "quarantines unreadable provider-normalized array entries before preserving metadata" , ( ) => {
379+ const source = createParameterFreeTool ( "fixture__lookup_note" ) as AgentTool ;
380+ setPluginToolMeta ( source , {
381+ pluginId : "bundle-mcp" ,
382+ optional : false ,
383+ mcp : {
384+ serverName : "fixture" ,
385+ safeServerName : "fixture" ,
386+ toolName : "lookup_note" ,
387+ operation : "tool" ,
388+ } ,
389+ } ) ;
390+ const normalized = {
391+ ...source ,
392+ parameters : normalizedParameterFreeSchema ( ) ,
393+ } ;
394+ const normalizedTools = new Proxy ( [ undefined , normalized ] as unknown as AgentTool [ ] , {
395+ get ( target , property , receiver ) {
396+ if ( property === "0" ) {
397+ throw new Error ( "fuzzplugin normalized array slot exploded" ) ;
398+ }
399+ return Reflect . get ( target , property , receiver ) ;
400+ } ,
401+ } ) ;
402+ const diagnostics : RuntimeToolSchemaDiagnostic [ ] [ ] = [ ] ;
403+ mocks . normalizeProviderToolSchemas . mockReturnValueOnce ( normalizedTools ) ;
404+
405+ const result = normalizeAgentRuntimeTools ( {
406+ tools : [ source ] ,
407+ provider : "openai" ,
408+ onToolSchemaDiagnostics : ( entries ) => diagnostics . push ( [ ...entries ] ) ,
409+ } ) ;
410+
411+ expect ( result ) . toEqual ( [ normalized ] ) ;
412+ expect ( getPluginToolMeta ( result [ 0 ] ) ) . toMatchObject ( {
413+ pluginId : "bundle-mcp" ,
414+ mcp : {
415+ serverName : "fixture" ,
416+ toolName : "lookup_note" ,
417+ } ,
418+ } ) ;
419+ expect ( diagnostics ) . toEqual ( [
420+ [
421+ {
422+ toolName : "tool[0]" ,
423+ toolIndex : 0 ,
424+ violations : [ "tool[0] is unreadable" ] ,
425+ } ,
426+ ] ,
427+ ] ) ;
428+ } ) ;
429+
278430 it ( "quarantines unreadable tools before provider schema normalization" , ( ) => {
279431 const healthy = { ...createParameterFreeTool ( ) , name : "healthy" } as AgentTool ;
280432 const unreadable = { ...createParameterFreeTool ( ) , name : "fuzzplugin_unreadable" } as AgentTool ;
@@ -292,7 +444,7 @@ describe("AgentRuntimePlan tool policy helpers", () => {
292444 normalizeAgentRuntimeTools ( {
293445 tools,
294446 provider : "openai" ,
295- onPreNormalizationSchemaDiagnostics : ( entries ) => diagnostics . push ( [ ...entries ] ) ,
447+ onToolSchemaDiagnostics : ( entries ) => diagnostics . push ( [ ...entries ] ) ,
296448 } ) ,
297449 ) . toEqual ( [ healthy ] ) ;
298450 expect ( mocks . normalizeProviderToolSchemas ) . toHaveBeenCalledWith (
0 commit comments