@@ -12,6 +12,8 @@ import {
1212 spillBlobKey ,
1313 truncateToolResultContent ,
1414 wrapAgentToolResultTruncation ,
15+ wrapAgentToolsWithResultTruncation ,
16+ type SpillBlobWriter ,
1517} from "./result-truncation-plugin.js" ;
1618import { toolOutputAbsolutePath } from "./tool-result-materialize.js" ;
1719import { CREDENTIAL_REDACTION } from "./tool-result-secret-scrub.js" ;
@@ -387,7 +389,9 @@ describe("resultTruncationPlugin", () => {
387389 const minified = JSON . stringify ( obj ) ;
388390 expect ( minified . length ) . toBeGreaterThan ( MAX_RESULT_CHARS ) ;
389391 const pretty = JSON . stringify ( obj , null , 2 ) ;
390- const plugin = resultTruncationPlugin ( { getBlobWriter : ( ) => store . writeBlob } ) ;
392+ const plugin = resultTruncationPlugin ( {
393+ getBlobWriter : ( ) => store . writeBlob ,
394+ } ) ;
391395 if ( plugin . middleware === undefined ) throw new Error ( "expected middleware" ) ;
392396 const middleware = plugin . middleware ( async ( call ) => ( {
393397 callId : call . id ,
@@ -401,10 +405,14 @@ describe("resultTruncationPlugin", () => {
401405 new AbortController ( ) . signal ,
402406 ) ;
403407 expect ( typeof result . content ) . toBe ( "string" ) ;
404- expect ( String ( result . content ) . length ) . toBeLessThanOrEqual ( MAX_RESULT_CHARS ) ;
408+ expect ( String ( result . content ) . length ) . toBeLessThanOrEqual (
409+ MAX_RESULT_CHARS ,
410+ ) ;
405411 const uri = `tool-output:///${ spillBlobKey ( callId ) } ` ;
406412 expect ( String ( result . content ) ) . toContain ( uri ) ;
407- const recovered = new TextDecoder ( ) . decode ( await createBlobReader ( store ) . read ( uri ) ) ;
413+ const recovered = new TextDecoder ( ) . decode (
414+ await createBlobReader ( store ) . read ( uri ) ,
415+ ) ;
408416 expect ( recovered ) . toBe ( pretty ) ;
409417 }
410418 } ) ;
@@ -413,7 +421,9 @@ describe("resultTruncationPlugin", () => {
413421 const store = fakeBlobStore ( ) ;
414422 const original = `Matching agent profiles:\n\n${ "body " . repeat ( MAX_RESULT_CHARS ) } ` ;
415423 expect ( original . length ) . toBeGreaterThan ( MAX_RESULT_CHARS ) ;
416- const plugin = resultTruncationPlugin ( { getBlobWriter : ( ) => store . writeBlob } ) ;
424+ const plugin = resultTruncationPlugin ( {
425+ getBlobWriter : ( ) => store . writeBlob ,
426+ } ) ;
417427 if ( plugin . middleware === undefined ) throw new Error ( "expected middleware" ) ;
418428 const middleware = plugin . middleware ( async ( call ) => ( {
419429 callId : call . id ,
@@ -426,14 +436,18 @@ describe("resultTruncationPlugin", () => {
426436 expect ( String ( result . content ) . length ) . toBeLessThanOrEqual ( MAX_RESULT_CHARS ) ;
427437 const uri = `tool-output:///${ spillBlobKey ( "call-search" ) } ` ;
428438 expect ( String ( result . content ) ) . toContain ( uri ) ;
429- const recovered = new TextDecoder ( ) . decode ( await createBlobReader ( store ) . read ( uri ) ) ;
439+ const recovered = new TextDecoder ( ) . decode (
440+ await createBlobReader ( store ) . read ( uri ) ,
441+ ) ;
430442 expect ( recovered ) . toBe ( original ) ;
431443 } ) ;
432444
433445 test ( "does not truncate isError results even when over the gate" , async ( ) => {
434446 const store = fakeBlobStore ( ) ;
435447 const original = `Error: ${ "x" . repeat ( MAX_RESULT_CHARS + 500 ) } ` ;
436- const plugin = resultTruncationPlugin ( { getBlobWriter : ( ) => store . writeBlob } ) ;
448+ const plugin = resultTruncationPlugin ( {
449+ getBlobWriter : ( ) => store . writeBlob ,
450+ } ) ;
437451 if ( plugin . middleware === undefined ) throw new Error ( "expected middleware" ) ;
438452 const middleware = plugin . middleware ( async ( call ) => ( {
439453 callId : call . id ,
@@ -453,7 +467,9 @@ describe("resultTruncationPlugin", () => {
453467describe ( "wrapAgentToolResultTruncation" , ( ) => {
454468 test ( "spills oversized wait_agents JSON from a kind:full handler" , async ( ) => {
455469 const store = fakeBlobStore ( ) ;
456- const payload = { results : [ { report : "x" . repeat ( MAX_RESULT_CHARS + 500 ) } ] } ;
470+ const payload = {
471+ results : [ { report : "x" . repeat ( MAX_RESULT_CHARS + 500 ) } ] ,
472+ } ;
457473 const minified = JSON . stringify ( payload ) ;
458474 expect ( minified . length ) . toBeGreaterThan ( MAX_RESULT_CHARS ) ;
459475 const pretty = JSON . stringify ( payload , null , 2 ) ;
@@ -477,7 +493,9 @@ describe("wrapAgentToolResultTruncation", () => {
477493 expect ( String ( result . content ) . length ) . toBeLessThanOrEqual ( MAX_RESULT_CHARS ) ;
478494 const uri = `tool-output:///${ spillBlobKey ( "call-wrap-wait" ) } ` ;
479495 expect ( String ( result . content ) ) . toContain ( uri ) ;
480- const recovered = new TextDecoder ( ) . decode ( await createBlobReader ( store ) . read ( uri ) ) ;
496+ const recovered = new TextDecoder ( ) . decode (
497+ await createBlobReader ( store ) . read ( uri ) ,
498+ ) ;
481499 expect ( recovered ) . toBe ( pretty ) ;
482500 } ) ;
483501
@@ -496,15 +514,18 @@ describe("wrapAgentToolResultTruncation", () => {
496514 } ,
497515 { getBlobWriter : ( ) => store . writeBlob } ,
498516 ) ;
499- if ( wrapped . kind !== "full" ) throw new Error ( "expected full wrapper so spill can use callId" ) ;
517+ if ( wrapped . kind !== "full" )
518+ throw new Error ( "expected full wrapper so spill can use callId" ) ;
500519 const result = await wrapped . handler (
501520 { id : "call-wrap-search" , name : "search_agents" , arguments : { } } ,
502521 new AbortController ( ) . signal ,
503522 ) ;
504523 expect ( String ( result . content ) . length ) . toBeLessThanOrEqual ( MAX_RESULT_CHARS ) ;
505524 const uri = `tool-output:///${ spillBlobKey ( "call-wrap-search" ) } ` ;
506525 expect ( String ( result . content ) ) . toContain ( uri ) ;
507- const recovered = new TextDecoder ( ) . decode ( await createBlobReader ( store ) . read ( uri ) ) ;
526+ const recovered = new TextDecoder ( ) . decode (
527+ await createBlobReader ( store ) . read ( uri ) ,
528+ ) ;
508529 expect ( recovered ) . toBe ( original ) ;
509530 } ) ;
510531
@@ -519,7 +540,11 @@ describe("wrapAgentToolResultTruncation", () => {
519540 description : "wait" ,
520541 inputSchema : { type : "object" } ,
521542 } ,
522- handler : async ( call ) => ( { callId : call . id , content : original , isError : true } ) ,
543+ handler : async ( call ) => ( {
544+ callId : call . id ,
545+ content : original ,
546+ isError : true ,
547+ } ) ,
523548 } ,
524549 { getBlobWriter : ( ) => store . writeBlob } ,
525550 ) ;
@@ -534,6 +559,58 @@ describe("wrapAgentToolResultTruncation", () => {
534559 } ) ;
535560} ) ;
536561
562+ describe ( "wrapAgentToolsWithResultTruncation" , ( ) => {
563+ test ( "late-binds a blob writer after wrap so oversized wait_agents JSON spills to a readable URI" , async ( ) => {
564+ const payload = {
565+ results : [ { report : "n" . repeat ( MAX_RESULT_CHARS + 500 ) } ] ,
566+ } ;
567+ const minified = JSON . stringify ( payload ) ;
568+ expect ( minified . length ) . toBeGreaterThan ( MAX_RESULT_CHARS ) ;
569+ const pretty = JSON . stringify ( payload , null , 2 ) ;
570+
571+ const childSpill : { writer ?: SpillBlobWriter } = { } ;
572+ const [ wrapped ] = wrapAgentToolsWithResultTruncation (
573+ [
574+ {
575+ kind : "full" ,
576+ definition : {
577+ name : "wait_agents" ,
578+ description : "wait" ,
579+ inputSchema : { type : "object" } ,
580+ } ,
581+ handler : async ( call ) => ( { callId : call . id , content : minified } ) ,
582+ } ,
583+ ] ,
584+ { getBlobWriter : ( ) => childSpill . writer } ,
585+ ) ;
586+ if ( wrapped === undefined || wrapped . kind !== "full" ) {
587+ throw new Error ( "expected full wrapped tool" ) ;
588+ }
589+
590+ const before = await wrapped . handler (
591+ { id : "call-nested-before" , name : "wait_agents" , arguments : { } } ,
592+ new AbortController ( ) . signal ,
593+ ) ;
594+ expect ( String ( before . content ) ) . toContain ( "NOT retrievable" ) ;
595+ expect ( String ( before . content ) ) . not . toContain ( "tool-output:///" ) ;
596+
597+ const store = fakeBlobStore ( ) ;
598+ childSpill . writer = store . writeBlob ;
599+ const result = await wrapped . handler (
600+ { id : "call-nested-wait" , name : "wait_agents" , arguments : { } } ,
601+ new AbortController ( ) . signal ,
602+ ) ;
603+ expect ( String ( result . content ) . length ) . toBeLessThanOrEqual ( MAX_RESULT_CHARS ) ;
604+ expect ( String ( result . content ) ) . not . toContain ( "NOT retrievable" ) ;
605+ const uri = `tool-output:///${ spillBlobKey ( "call-nested-wait" ) } ` ;
606+ expect ( String ( result . content ) ) . toContain ( uri ) ;
607+ const recovered = new TextDecoder ( ) . decode (
608+ await createBlobReader ( store ) . read ( uri ) ,
609+ ) ;
610+ expect ( recovered ) . toBe ( pretty ) ;
611+ } ) ;
612+ } ) ;
613+
537614describe ( "scrub-before-spill" , ( ) => {
538615 test ( "secret scrub runs on the full content before truncation spills" , async ( ) => {
539616 const store = fakeBlobStore ( ) ;
0 commit comments