@@ -27,7 +27,13 @@ import {
2727 executeHandoffCalls ,
2828 executeToolsAndSideEffects ,
2929} from '../src/runImplementation' ;
30- import { FunctionTool , FunctionToolResult , tool } from '../src/tool' ;
30+ import {
31+ FunctionTool ,
32+ FunctionToolResult ,
33+ tool ,
34+ computerTool ,
35+ hostedMcpTool ,
36+ } from '../src/tool' ;
3137import { handoff } from '../src/handoff' ;
3238import { ModelBehaviorError , UserError } from '../src/errors' ;
3339import { Computer } from '../src/computer' ;
@@ -43,7 +49,6 @@ import {
4349 FakeModelProvider ,
4450 fakeModelMessage ,
4551} from './stubs' ;
46- import { computerTool } from '../src/tool' ;
4752import * as protocol from '../src/types/protocol' ;
4853import { Runner } from '../src/run' ;
4954import { RunContext } from '../src/runContext' ;
@@ -1027,4 +1032,196 @@ describe('executeToolsAndSideEffects', () => {
10271032 expect ( result . nextStep . output ) . toBe ( '' ) ;
10281033 }
10291034 } ) ;
1035+
1036+ it ( 'returns final output after computer actions complete in the same turn' , async ( ) => {
1037+ const computerAgent = new Agent ( {
1038+ name : 'ComputerAgent' ,
1039+ outputType : 'text' ,
1040+ } ) ;
1041+ const fakeComputer = {
1042+ environment : 'mac' ,
1043+ dimensions : [ 1 , 1 ] as [ number , number ] ,
1044+ screenshot : vi . fn ( ) . mockResolvedValue ( 'img' ) ,
1045+ click : vi . fn ( ) ,
1046+ doubleClick : vi . fn ( ) ,
1047+ drag : vi . fn ( ) ,
1048+ keypress : vi . fn ( ) ,
1049+ move : vi . fn ( ) ,
1050+ scroll : vi . fn ( ) ,
1051+ type : vi . fn ( ) ,
1052+ wait : vi . fn ( ) ,
1053+ } ;
1054+ const computer = computerTool ( {
1055+ computer : fakeComputer as unknown as Computer ,
1056+ } ) ;
1057+ const computerCall : protocol . ComputerUseCallItem = {
1058+ type : 'computer_call' ,
1059+ id : 'comp1' ,
1060+ callId : 'comp1' ,
1061+ status : 'completed' ,
1062+ action : { type : 'screenshot' } ,
1063+ } as protocol . ComputerUseCallItem ;
1064+
1065+ const computerResponse : ModelResponse = {
1066+ output : [ computerCall , { ...TEST_MODEL_MESSAGE } ] ,
1067+ usage : new Usage ( ) ,
1068+ } as any ;
1069+
1070+ const processedResponse = processModelResponse (
1071+ computerResponse ,
1072+ computerAgent ,
1073+ [ computer ] ,
1074+ [ ] ,
1075+ ) ;
1076+
1077+ const computerState = new RunState (
1078+ new RunContext ( ) ,
1079+ 'test input' ,
1080+ computerAgent ,
1081+ 1 ,
1082+ ) ;
1083+
1084+ const result = await withTrace ( 'test' , ( ) =>
1085+ executeToolsAndSideEffects (
1086+ computerAgent ,
1087+ 'test input' ,
1088+ [ ] ,
1089+ computerResponse ,
1090+ processedResponse ,
1091+ runner ,
1092+ computerState ,
1093+ ) ,
1094+ ) ;
1095+
1096+ expect ( result . nextStep . type ) . toBe ( 'next_step_final_output' ) ;
1097+ if ( result . nextStep . type === 'next_step_final_output' ) {
1098+ expect ( result . nextStep . output ) . toBe ( 'Hello World' ) ;
1099+ }
1100+ } ) ;
1101+
1102+ it ( 'returns final output when hosted MCP approval resolves immediately' , async ( ) => {
1103+ const approvalAgent = new Agent ( { name : 'MCPAgent' , outputType : 'text' } ) ;
1104+ const mcpTool = hostedMcpTool ( {
1105+ serverLabel : 'demo_server' ,
1106+ serverUrl : 'https://example.com' ,
1107+ requireApproval : {
1108+ always : { toolNames : [ 'demo_tool' ] } ,
1109+ } ,
1110+ onApproval : async ( ) => ( { approve : true , reason : 'approved in test' } ) ,
1111+ } ) ;
1112+
1113+ const approvalCall : protocol . HostedToolCallItem = {
1114+ type : 'hosted_tool_call' ,
1115+ id : 'approval1' ,
1116+ name : 'mcp_approval_request' ,
1117+ status : 'completed' ,
1118+ providerData : {
1119+ type : 'mcp_approval_request' ,
1120+ server_label : 'demo_server' ,
1121+ name : 'demo_tool' ,
1122+ id : 'approval1' ,
1123+ arguments : '{}' ,
1124+ } ,
1125+ } as protocol . HostedToolCallItem ;
1126+
1127+ const approvalResponse : ModelResponse = {
1128+ output : [ approvalCall , { ...TEST_MODEL_MESSAGE } ] ,
1129+ usage : new Usage ( ) ,
1130+ } as any ;
1131+
1132+ const processedResponse = processModelResponse (
1133+ approvalResponse ,
1134+ approvalAgent ,
1135+ [ mcpTool ] ,
1136+ [ ] ,
1137+ ) ;
1138+
1139+ const approvalState = new RunState (
1140+ new RunContext ( ) ,
1141+ 'test input' ,
1142+ approvalAgent ,
1143+ 1 ,
1144+ ) ;
1145+
1146+ const result = await withTrace ( 'test' , ( ) =>
1147+ executeToolsAndSideEffects (
1148+ approvalAgent ,
1149+ 'test input' ,
1150+ [ ] ,
1151+ approvalResponse ,
1152+ processedResponse ,
1153+ runner ,
1154+ approvalState ,
1155+ ) ,
1156+ ) ;
1157+
1158+ expect ( result . nextStep . type ) . toBe ( 'next_step_final_output' ) ;
1159+ if ( result . nextStep . type === 'next_step_final_output' ) {
1160+ expect ( result . nextStep . output ) . toBe ( 'Hello World' ) ;
1161+ }
1162+ } ) ;
1163+
1164+ it ( 'returns interruption when hosted MCP approval requires user input' , async ( ) => {
1165+ const approvalAgent = new Agent ( { name : 'MCPAgent' , outputType : 'text' } ) ;
1166+ const mcpTool = hostedMcpTool ( {
1167+ serverLabel : 'demo_server' ,
1168+ serverUrl : 'https://example.com' ,
1169+ requireApproval : {
1170+ always : { toolNames : [ 'demo_tool' ] } ,
1171+ } ,
1172+ } ) ;
1173+
1174+ const approvalCall : protocol . HostedToolCallItem = {
1175+ type : 'hosted_tool_call' ,
1176+ id : 'approval1' ,
1177+ name : 'mcp_approval_request' ,
1178+ status : 'completed' ,
1179+ providerData : {
1180+ type : 'mcp_approval_request' ,
1181+ server_label : 'demo_server' ,
1182+ name : 'demo_tool' ,
1183+ id : 'approval1' ,
1184+ arguments : '{}' ,
1185+ } ,
1186+ } as protocol . HostedToolCallItem ;
1187+
1188+ const approvalResponse : ModelResponse = {
1189+ output : [ approvalCall , { ...TEST_MODEL_MESSAGE } ] ,
1190+ usage : new Usage ( ) ,
1191+ } as any ;
1192+
1193+ const processedResponse = processModelResponse (
1194+ approvalResponse ,
1195+ approvalAgent ,
1196+ [ mcpTool ] ,
1197+ [ ] ,
1198+ ) ;
1199+
1200+ const approvalState = new RunState (
1201+ new RunContext ( ) ,
1202+ 'test input' ,
1203+ approvalAgent ,
1204+ 1 ,
1205+ ) ;
1206+
1207+ const result = await withTrace ( 'test' , ( ) =>
1208+ executeToolsAndSideEffects (
1209+ approvalAgent ,
1210+ 'test input' ,
1211+ [ ] ,
1212+ approvalResponse ,
1213+ processedResponse ,
1214+ runner ,
1215+ approvalState ,
1216+ ) ,
1217+ ) ;
1218+
1219+ expect ( result . nextStep . type ) . toBe ( 'next_step_interruption' ) ;
1220+ if ( result . nextStep . type === 'next_step_interruption' ) {
1221+ expect ( result . nextStep . data . interruptions ) . toHaveLength ( 1 ) ;
1222+ expect ( result . nextStep . data . interruptions [ 0 ] . rawItem ) . toMatchObject ( {
1223+ providerData : { id : 'approval1' , type : 'mcp_approval_request' } ,
1224+ } ) ;
1225+ }
1226+ } ) ;
10301227} ) ;
0 commit comments