@@ -25,6 +25,7 @@ import {
2525 executeFunctionToolCalls ,
2626 executeComputerActions ,
2727 executeHandoffCalls ,
28+ executeToolsAndSideEffects ,
2829} from '../src/runImplementation' ;
2930import { FunctionTool , FunctionToolResult , tool } from '../src/tool' ;
3031import { handoff } from '../src/handoff' ;
@@ -78,6 +79,7 @@ describe('processModelResponse', () => {
7879 expect ( result . newItems [ 1 ] . rawItem ) . toEqual (
7980 TEST_MODEL_RESPONSE_WITH_FUNCTION . output [ 1 ] ,
8081 ) ;
82+ expect ( result . hasToolsOrApprovalsToRun ( ) ) . toBe ( true ) ;
8183 } ) ;
8284} ) ;
8385
@@ -438,6 +440,7 @@ describe('processModelResponse edge cases', () => {
438440 expect ( result . computerActions [ 0 ] ?. toolCall ) . toBe ( compCall ) ;
439441 expect ( result . handoffs [ 0 ] ?. toolCall ) . toBe ( handCall ) ;
440442 expect ( result . toolsUsed ) . toEqual ( [ 'test' , 'computer_use' , h . toolName ] ) ;
443+ expect ( result . hasToolsOrApprovalsToRun ( ) ) . toBe ( true ) ;
441444 expect ( result . newItems [ 3 ] ) . toBeInstanceOf ( MessageOutputItem ) ;
442445 } ) ;
443446} ) ;
@@ -790,3 +793,133 @@ describe('empty execution helpers', () => {
790793 expect ( comp ) . toEqual ( [ ] ) ;
791794 } ) ;
792795} ) ;
796+
797+ describe ( 'hasToolsOrApprovalsToRun method' , ( ) => {
798+ it ( 'returns true when handoffs are pending' , ( ) => {
799+ const target = new Agent ( { name : 'Target' } ) ;
800+ const h = handoff ( target ) ;
801+ const response : ModelResponse = {
802+ output : [ { ...TEST_MODEL_FUNCTION_CALL , name : h . toolName } ] ,
803+ usage : new Usage ( ) ,
804+ } as any ;
805+
806+ const result = processModelResponse ( response , TEST_AGENT , [ ] , [ h ] ) ;
807+ expect ( result . hasToolsOrApprovalsToRun ( ) ) . toBe ( true ) ;
808+ } ) ;
809+
810+ it ( 'returns true when function calls are pending' , ( ) => {
811+ const result = processModelResponse (
812+ TEST_MODEL_RESPONSE_WITH_FUNCTION ,
813+ TEST_AGENT ,
814+ [ TEST_TOOL ] ,
815+ [ ] ,
816+ ) ;
817+ expect ( result . hasToolsOrApprovalsToRun ( ) ) . toBe ( true ) ;
818+ } ) ;
819+
820+ it ( 'returns true when computer actions are pending' , ( ) => {
821+ const computer = computerTool ( {
822+ computer : {
823+ environment : 'mac' ,
824+ dimensions : [ 10 , 10 ] ,
825+ screenshot : vi . fn ( async ( ) => 'img' ) ,
826+ click : vi . fn ( async ( ) => { } ) ,
827+ doubleClick : vi . fn ( async ( ) => { } ) ,
828+ drag : vi . fn ( async ( ) => { } ) ,
829+ keypress : vi . fn ( async ( ) => { } ) ,
830+ move : vi . fn ( async ( ) => { } ) ,
831+ scroll : vi . fn ( async ( ) => { } ) ,
832+ type : vi . fn ( async ( ) => { } ) ,
833+ wait : vi . fn ( async ( ) => { } ) ,
834+ } ,
835+ } ) ;
836+ const compCall : protocol . ComputerUseCallItem = {
837+ id : 'c1' ,
838+ type : 'computer_call' ,
839+ callId : 'c1' ,
840+ status : 'completed' ,
841+ action : { type : 'screenshot' } ,
842+ } ;
843+ const response : ModelResponse = {
844+ output : [ compCall ] ,
845+ usage : new Usage ( ) ,
846+ } as any ;
847+
848+ const result = processModelResponse ( response , TEST_AGENT , [ computer ] , [ ] ) ;
849+ expect ( result . hasToolsOrApprovalsToRun ( ) ) . toBe ( true ) ;
850+ } ) ;
851+
852+ it ( 'returns false when no tools or approvals are pending' , ( ) => {
853+ const response : ModelResponse = {
854+ output : [ TEST_MODEL_MESSAGE ] ,
855+ usage : new Usage ( ) ,
856+ } as any ;
857+
858+ const result = processModelResponse ( response , TEST_AGENT , [ ] , [ ] ) ;
859+ expect ( result . hasToolsOrApprovalsToRun ( ) ) . toBe ( false ) ;
860+ } ) ;
861+ } ) ;
862+
863+ describe ( 'executeToolsAndSideEffects' , ( ) => {
864+ let runner : Runner ;
865+ let state : RunState < any , any > ;
866+
867+ beforeEach ( ( ) => {
868+ runner = new Runner ( { tracingDisabled : true } ) ;
869+ state = new RunState ( new RunContext ( ) , 'test input' , TEST_AGENT , 1 ) ;
870+ } ) ;
871+
872+ it ( 'continues execution when text agent has tools pending' , async ( ) => {
873+ const textAgent = new Agent ( { name : 'TextAgent' , outputType : 'text' } ) ;
874+ const processedResponse = processModelResponse (
875+ TEST_MODEL_RESPONSE_WITH_FUNCTION ,
876+ textAgent ,
877+ [ TEST_TOOL ] ,
878+ [ ] ,
879+ ) ;
880+
881+ expect ( processedResponse . hasToolsOrApprovalsToRun ( ) ) . toBe ( true ) ;
882+
883+ const result = await withTrace ( 'test' , ( ) =>
884+ executeToolsAndSideEffects (
885+ textAgent ,
886+ 'test input' ,
887+ [ ] ,
888+ TEST_MODEL_RESPONSE_WITH_FUNCTION ,
889+ processedResponse ,
890+ runner ,
891+ state ,
892+ ) ,
893+ ) ;
894+
895+ expect ( result . nextStep . type ) . toBe ( 'next_step_run_again' ) ;
896+ } ) ;
897+
898+ it ( 'returns final output when text agent has no tools pending' , async ( ) => {
899+ const textAgent = new Agent ( { name : 'TextAgent' , outputType : 'text' } ) ;
900+ const response : ModelResponse = {
901+ output : [ TEST_MODEL_MESSAGE ] ,
902+ usage : new Usage ( ) ,
903+ } as any ;
904+ const processedResponse = processModelResponse ( response , textAgent , [ ] , [ ] ) ;
905+
906+ expect ( processedResponse . hasToolsOrApprovalsToRun ( ) ) . toBe ( false ) ;
907+
908+ const result = await withTrace ( 'test' , ( ) =>
909+ executeToolsAndSideEffects (
910+ textAgent ,
911+ 'test input' ,
912+ [ ] ,
913+ response ,
914+ processedResponse ,
915+ runner ,
916+ state ,
917+ ) ,
918+ ) ;
919+
920+ expect ( result . nextStep . type ) . toBe ( 'next_step_final_output' ) ;
921+ if ( result . nextStep . type === 'next_step_final_output' ) {
922+ expect ( result . nextStep . output ) . toBe ( 'Hello World' ) ;
923+ }
924+ } ) ;
925+ } ) ;
0 commit comments