@@ -29,12 +29,12 @@ describe("box.agent.run", () => {
2929
3030 it ( "calls onToolUse callback" , async ( ) => {
3131 const { box, fetchMock } = await createTestBox ( ) ;
32- const tools : Array < { name : string ; input : Record < string , unknown > } > = [ ] ;
32+ const tools : Array < { toolCallId ?: string ; name : string ; input : Record < string , unknown > } > = [ ] ;
3333
3434 fetchMock . mockResolvedValueOnce (
3535 mockSSEResponse ( [
3636 { event : "run_start" , data : { run_id : "r1" } } ,
37- { event : "tool" , data : { name : "Read" , input : { path : "/test" } } } ,
37+ { event : "tool" , data : { id : "tool-1" , name : "Read" , input : { path : "/test" } } } ,
3838 { event : "done" , data : { } } ,
3939 ] ) ,
4040 ) ;
@@ -45,9 +45,30 @@ describe("box.agent.run", () => {
4545 } ) ;
4646
4747 expect ( tools ) . toHaveLength ( 1 ) ;
48+ expect ( tools [ 0 ] ! . toolCallId ) . toBe ( "tool-1" ) ;
4849 expect ( tools [ 0 ] ! . name ) . toBe ( "Read" ) ;
4950 } ) ;
5051
52+ it ( "calls onToolResult callback" , async ( ) => {
53+ const { box, fetchMock } = await createTestBox ( ) ;
54+ const results : Array < { toolCallId ?: string ; output : unknown } > = [ ] ;
55+
56+ fetchMock . mockResolvedValueOnce (
57+ mockSSEResponse ( [
58+ { event : "run_start" , data : { run_id : "r1" } } ,
59+ { event : "tool_result" , data : { toolCallId : "tool-1" , output : { ok : true } } } ,
60+ { event : "done" , data : { } } ,
61+ ] ) ,
62+ ) ;
63+
64+ await box . agent . run ( {
65+ prompt : "test" ,
66+ onToolResult : ( result ) => results . push ( result ) ,
67+ } ) ;
68+
69+ expect ( results ) . toEqual ( [ { toolCallId : "tool-1" , output : { ok : true } } ] ) ;
70+ } ) ;
71+
5172 it ( "parses structured output with responseSchema" , async ( ) => {
5273 const { box, fetchMock } = await createTestBox ( ) ;
5374
@@ -437,12 +458,12 @@ describe("box.agent.stream", () => {
437458
438459 it ( "yields tool-call chunks and calls onToolUse" , async ( ) => {
439460 const { box, fetchMock } = await createTestBox ( ) ;
440- const tools : Array < { name : string ; input : Record < string , unknown > } > = [ ] ;
461+ const tools : Array < { toolCallId ?: string ; name : string ; input : Record < string , unknown > } > = [ ] ;
441462
442463 fetchMock . mockResolvedValueOnce (
443464 mockSSEResponse ( [
444465 { event : "run_start" , data : { run_id : "r1" } } ,
445- { event : "tool" , data : { name : "Write" , input : { path : "/x" } } } ,
466+ { event : "tool" , data : { id : "tool-2" , name : "Write" , input : { path : "/x" } } } ,
446467 { event : "text" , data : { text : "done" } } ,
447468 { event : "done" , data : { } } ,
448469 ] ) ,
@@ -458,15 +479,97 @@ describe("box.agent.stream", () => {
458479 }
459480
460481 expect ( tools ) . toHaveLength ( 1 ) ;
482+ expect ( tools [ 0 ] ! . toolCallId ) . toBe ( "tool-2" ) ;
461483 expect ( tools [ 0 ] ! . name ) . toBe ( "Write" ) ;
462484 const toolChunks = chunks . filter ( ( c ) => c . type === "tool-call" ) ;
463485 expect ( toolChunks ) . toHaveLength ( 1 ) ;
486+ expect ( toolChunks [ 0 ] ) . toEqual ( {
487+ type : "tool-call" ,
488+ toolCallId : "tool-2" ,
489+ toolName : "Write" ,
490+ input : { path : "/x" } ,
491+ } ) ;
464492 const textChunks = chunks . filter (
465493 ( c ) : c is Extract < Chunk , { type : "text-delta" } > => c . type === "text-delta" ,
466494 ) ;
467495 expect ( textChunks . map ( ( c ) => c . text ) ) . toEqual ( [ "done" ] ) ;
468496 } ) ;
469497
498+ it ( "yields tool-result chunks and calls onToolResult" , async ( ) => {
499+ const { box, fetchMock } = await createTestBox ( ) ;
500+ const results : Array < { toolCallId ?: string ; output : unknown } > = [ ] ;
501+
502+ fetchMock . mockResolvedValueOnce (
503+ mockSSEResponse ( [
504+ { event : "run_start" , data : { run_id : "r1" } } ,
505+ { event : "tool_result" , data : { tool_use_id : "tool-3" , output : "ok" } } ,
506+ { event : "done" , data : { } } ,
507+ ] ) ,
508+ ) ;
509+
510+ const run = await box . agent . stream ( {
511+ prompt : "test" ,
512+ onToolResult : ( result ) => results . push ( result ) ,
513+ } ) ;
514+ const chunks : Chunk [ ] = [ ] ;
515+ for await ( const chunk of run ) {
516+ chunks . push ( chunk ) ;
517+ }
518+
519+ expect ( results ) . toEqual ( [ { toolCallId : "tool-3" , output : "ok" } ] ) ;
520+ expect ( chunks ) . toContainEqual ( {
521+ type : "tool-result" ,
522+ toolCallId : "tool-3" ,
523+ output : "ok" ,
524+ } ) ;
525+ } ) ;
526+
527+ it ( "prefers explicit tool call identifiers over generic ids" , async ( ) => {
528+ const { box, fetchMock } = await createTestBox ( ) ;
529+
530+ fetchMock . mockResolvedValueOnce (
531+ mockSSEResponse ( [
532+ { event : "run_start" , data : { run_id : "r1" } } ,
533+ {
534+ event : "tool" ,
535+ data : {
536+ id : "event-id" ,
537+ tool_use_id : "tool-use-id" ,
538+ name : "Read" ,
539+ input : { path : "/x" } ,
540+ } ,
541+ } ,
542+ {
543+ event : "tool_result" ,
544+ data : {
545+ id : "result-event-id" ,
546+ toolCallId : "tool-call-id" ,
547+ output : "ok" ,
548+ } ,
549+ } ,
550+ { event : "done" , data : { } } ,
551+ ] ) ,
552+ ) ;
553+
554+ const run = await box . agent . stream ( { prompt : "test" } ) ;
555+ const chunks : Chunk [ ] = [ ] ;
556+ for await ( const chunk of run ) {
557+ chunks . push ( chunk ) ;
558+ }
559+
560+ expect ( chunks ) . toContainEqual ( {
561+ type : "tool-call" ,
562+ toolCallId : "tool-use-id" ,
563+ toolName : "Read" ,
564+ input : { path : "/x" } ,
565+ } ) ;
566+ expect ( chunks ) . toContainEqual ( {
567+ type : "tool-result" ,
568+ toolCallId : "tool-call-id" ,
569+ output : "ok" ,
570+ } ) ;
571+ } ) ;
572+
470573 it ( "yields all chunk types in order" , async ( ) => {
471574 const { box, fetchMock } = await createTestBox ( ) ;
472575
@@ -475,7 +578,8 @@ describe("box.agent.stream", () => {
475578 { event : "run_start" , data : { run_id : "r1" } } ,
476579 { event : "text" , data : { text : "Hello " } } ,
477580 { event : "thinking" , data : { text : "trace" } } ,
478- { event : "tool" , data : { name : "Write" , input : { path : "/x" } } } ,
581+ { event : "tool" , data : { toolCallId : "tool-4" , name : "Write" , input : { path : "/x" } } } ,
582+ { event : "tool_result" , data : { tool_use_id : "tool-4" , output : "done" } } ,
479583 {
480584 event : "done" ,
481585 data : { output : "Hello world" , input_tokens : 7 , output_tokens : 9 , session_id : "s1" } ,
@@ -495,6 +599,7 @@ describe("box.agent.stream", () => {
495599 "text-delta" ,
496600 "reasoning" ,
497601 "tool-call" ,
602+ "tool-result" ,
498603 "finish" ,
499604 "stats" ,
500605 ] ) ;
0 commit comments