11from typing import Optional , Union
22
33from ..handlers .cua_handler import CUAHandler
4+ from ..schemas import (
5+ AgentExecuteResult ,
6+ AgentProvider ,
7+ )
48from ..types .agent import (
59 AgentConfig ,
610 AgentExecuteOptions ,
1620 "claude-3-5-sonnet-latest" : AnthropicCUAClient ,
1721 "claude-3-7-sonnet-latest" : AnthropicCUAClient ,
1822}
23+ MODEL_TO_PROVIDER_MAP : dict [str , AgentProvider ] = {
24+ "computer-use-preview" : AgentProvider .OPENAI ,
25+ "claude-3-5-sonnet-20240620" : AgentProvider .ANTHROPIC ,
26+ "claude-3-7-sonnet-20250219" : AgentProvider .ANTHROPIC ,
27+ # Add more mappings as needed
28+ }
1929
2030AGENT_METRIC_FUNCTION_NAME = "AGENT_EXECUTE_TASK"
2131
@@ -26,6 +36,13 @@ def __init__(self, stagehand_client, **kwargs):
2636 self .stagehand = stagehand_client
2737 self .config = AgentConfig (** kwargs ) if kwargs else AgentConfig ()
2838 self .logger = self .stagehand .logger
39+ if self .config .model in MODEL_TO_PROVIDER_MAP :
40+ self .provider = MODEL_TO_PROVIDER_MAP [self .config .model ]
41+ else :
42+ self .provider = None
43+ self .logger .error (
44+ f"Could not infer provider for model: { self .config .model } "
45+ )
2946
3047 if not hasattr (self .stagehand , "page" ) or not hasattr (
3148 self .stagehand .page , "_page"
@@ -69,7 +86,6 @@ def _get_client(self) -> AgentClient:
6986 async def execute (
7087 self , options_or_instruction : Union [AgentExecuteOptions , str ]
7188 ) -> AgentResult :
72-
7389 options : Optional [AgentExecuteOptions ] = None
7490 instruction : str
7591
@@ -83,56 +99,101 @@ async def execute(
8399 options = options_or_instruction
84100 instruction = options .instruction
85101
86- if not instruction :
87- self .logger .error ("No instruction provided for agent execution." )
88- return AgentResult (
89- message = "No instruction provided." , completed = True , actions = [], usage = {}
102+ if self .stagehand .env == "LOCAL" :
103+ if not instruction :
104+ self .logger .error ("No instruction provided for agent execution." )
105+ return AgentResult (
106+ message = "No instruction provided." ,
107+ completed = True ,
108+ actions = [],
109+ usage = {},
110+ )
111+
112+ self .logger .info (
113+ f"Agent starting execution for instruction: '{ instruction } '" ,
114+ category = "agent" ,
90115 )
91116
92- self .logger .info (
93- f"Agent starting execution for instruction: '{ instruction } '" ,
94- category = "agent" ,
95- )
96-
97- try :
98- agent_result = await self .client .run_task (
99- instruction = instruction ,
100- max_steps = self .config .max_steps ,
101- options = options ,
102- )
103- except Exception as e :
104- self .logger .error (
105- f"Exception during client.run_task: { e } " , category = "agent"
117+ try :
118+ agent_result = await self .client .run_task (
119+ instruction = instruction ,
120+ max_steps = self .config .max_steps ,
121+ options = options ,
122+ )
123+ except Exception as e :
124+ self .logger .error (
125+ f"Exception during client.run_task: { e } " , category = "agent"
126+ )
127+ empty_usage = AgentUsage (
128+ input_tokens = 0 , output_tokens = 0 , inference_time_ms = 0
129+ )
130+ return AgentResult (
131+ message = f"Error: { str (e )} " ,
132+ completed = True ,
133+ actions = [],
134+ usage = empty_usage ,
135+ )
136+
137+ # Update metrics if usage data is available in the result
138+ if agent_result .usage :
139+ # self.stagehand.update_metrics(
140+ # AGENT_METRIC_FUNCTION_NAME,
141+ # agent_result.usage.get("input_tokens", 0),
142+ # agent_result.usage.get("output_tokens", 0),
143+ # agent_result.usage.get("inference_time_ms", 0),
144+ # )
145+ pass # Placeholder if metrics are to be handled differently or not at all
146+
147+ self .logger .info (
148+ f"Agent execution finished. Success: { agent_result .completed } . Message: { agent_result .message } " ,
149+ category = "agent" ,
106150 )
107- empty_usage = AgentUsage (
108- input_tokens = 0 , output_tokens = 0 , inference_time_ms = 0
151+ # To clean up pydantic model output
152+ actions_repr = [action .root for action in agent_result .actions ]
153+ self .logger .debug (
154+ f"Agent actions: { actions_repr } " ,
155+ category = "agent" ,
109156 )
110- return AgentResult (
111- message = f"Error: { str ( e ) } " ,
112- completed = True ,
113- actions = [],
114- usage = empty_usage ,
157+ agent_result . actions = actions_repr
158+ return agent_result
159+ else :
160+ agent_config_payload = self . config . model_dump (
161+ exclude_none = True , by_alias = True
115162 )
116-
117- # Update metrics if usage data is available in the result
118- if agent_result .usage :
119- # self.stagehand.update_metrics(
120- # AGENT_METRIC_FUNCTION_NAME,
121- # agent_result.usage.get("input_tokens", 0),
122- # agent_result.usage.get("output_tokens", 0),
123- # agent_result.usage.get("inference_time_ms", 0),
124- # )
125- pass # Placeholder if metrics are to be handled differently or not at all
126-
127- self .logger .info (
128- f"Agent execution finished. Success: { agent_result .completed } . Message: { agent_result .message } " ,
129- category = "agent" ,
130- )
131- # To clean up pydantic model output
132- actions_repr = [action .root for action in agent_result .actions ]
133- self .logger .debug (
134- f"Agent actions: { actions_repr } " ,
135- category = "agent" ,
136- )
137- agent_result .actions = actions_repr
138- return agent_result
163+ agent_config_payload ["provider" ] = self .provider
164+ payload = {
165+ # Use the stored config
166+ "agentConfig" : agent_config_payload ,
167+ "executeOptions" : options .model_dump (exclude_none = True , by_alias = True ),
168+ }
169+
170+ lock = self .stagehand ._get_lock_for_session ()
171+ async with lock :
172+ result = await self .stagehand ._execute ("agentExecute" , payload )
173+
174+ if isinstance (result , dict ):
175+ # Ensure all expected fields are present
176+ # If not present in result, use defaults from AgentExecuteResult schema
177+ if "success" not in result :
178+ raise ValueError ("Response missing required field 'success'" )
179+
180+ # Ensure completed is set with default if not present
181+ if "completed" not in result :
182+ result ["completed" ] = False
183+
184+ # Add default for message if missing
185+ if "message" not in result :
186+ result ["message" ] = None
187+
188+ return AgentExecuteResult (** result )
189+ elif result is None :
190+ # Handle cases where the server might return None or an empty response
191+ # Return a default failure result or raise an error
192+ return AgentExecuteResult (
193+ success = False ,
194+ completed = False ,
195+ message = "No result received from server" ,
196+ )
197+ else :
198+ # If the result is not a dict and not None, it's unexpected
199+ raise TypeError (f"Unexpected result type from server: { type (result )} " )
0 commit comments