@@ -65,6 +65,7 @@ class BrowserService(EventEmitter):
6565
6666 stagehand_config : StagehandConfig
6767 stagehand : Stagehand
68+ default_timeout : float
6869 artifacts_dir : str
6970 provider : str
7071 model : str
@@ -109,6 +110,7 @@ def __init__(
109110 "No artifacts_dir provided. Browser will not persist network traffic, profile, downloads to artifacts."
110111 )
111112
113+ self .default_timeout = float (os .getenv ("BROWSER_DEFAULT_TIMEOUT" , "120000" ))
112114 self .stagehand_config = StagehandConfig (
113115 env = "LOCAL" ,
114116 modelName = model ,
@@ -120,6 +122,11 @@ def __init__(
120122 )
121123 self .stagehand = Stagehand (self .stagehand_config )
122124
125+ @asynccontextmanager
126+ async def timeout (self ):
127+ async with asyncio .timeout ((self .default_timeout / 1000 ) + 5 ):
128+ yield
129+
123130 @property
124131 def page_domain (self ):
125132 """
@@ -167,9 +174,16 @@ async def ensure_init(self):
167174 if self ._initialized :
168175 return
169176
177+ logger .info ("Initializing browser" )
170178 await self .stagehand .init ()
171179 self ._initialized = True
172180
181+ self .context .set_default_timeout (self .default_timeout )
182+ self .context .set_default_navigation_timeout (self .default_timeout )
183+
184+ self .page .set_default_timeout (self .default_timeout )
185+ self .page .set_default_navigation_timeout (self .default_timeout )
186+
173187 async def handle_dialog (dialog : Dialog ):
174188 """Auto accept all dialogs"""
175189 await dialog .accept ()
@@ -582,11 +596,9 @@ async def get_browser():
582596 """
583597 if not _BROWSER :
584598 raise ValueError ("Browser not initialized. Please call initialize_browser first." )
585- logger . info ( "[BROWSER] ENtering get_browser context manager." )
599+
586600 async with _BROWSER_LOCK :
587- logger .info ("[BROWSER] ENtering get_browser context manager lock." )
588601 await _BROWSER .ensure_init ()
589- logger .info ("[BROWSER] Done get_browser context manager ensure_init." )
590602 yield _BROWSER
591603
592604
@@ -686,20 +698,22 @@ async def browser_goto_url(url: str):
686698 async with browser .interaction_context_capture (
687699 only_domains = [browser .page_domain , extract_domain (url )]
688700 ) as interaction_context :
689- await browser .page .goto (url )
701+ async with browser .timeout ():
702+ await browser .page .goto (url )
690703 interaction_context = interaction_context
691704
692705 # Eagerly returning relevant observations to reduce agent tool calls
693- observations = "\n " .join (
694- map (
695- lambda obs : obs .description ,
696- await browser .page .observe (
697- f"{ url } was just opened. "
698- "give all important elements on the page that might be relevant to the next action. "
699- "observe the overall state of the page to understand the purpose of the page."
700- ),
706+ async with browser .timeout ():
707+ observations = "\n " .join (
708+ map (
709+ lambda obs : obs .description ,
710+ await browser .page .observe (
711+ f"{ url } was just opened. "
712+ "give all important elements on the page that might be relevant to the next action. "
713+ "observe the overall state of the page to understand the purpose of the page."
714+ ),
715+ )
701716 )
702- )
703717 return f"<observations>\n { observations } \n </observations>\n { interaction_context } "
704718
705719
@@ -714,7 +728,8 @@ async def browser_get_page_html() -> str:
714728 The path of the downloaded HTML file artifact.
715729 """
716730 async with get_browser () as browser :
717- page_html = await browser .page .content ()
731+ async with browser .timeout ():
732+ page_html = await browser .page .content ()
718733 html_artifact_file = os .path .join (browser .artifacts_dir , f"browser_page_{ time .time_ns ()} .html" )
719734 with open (html_artifact_file , "w" ) as f :
720735 f .write (page_html )
@@ -740,7 +755,8 @@ async def browser_evaluate_js(expression: str):
740755 The result of the javascript expression.
741756 """
742757 async with get_browser () as browser :
743- return await browser .page .evaluate (expression )
758+ async with browser .timeout ():
759+ return await browser .page .evaluate (expression )
744760
745761
746762@tool
@@ -757,7 +773,8 @@ async def browser_get_cookies():
757773 a message string indicating this is returned.
758774 """
759775 async with get_browser () as browser :
760- cookies = await browser .context .cookies ()
776+ async with browser .timeout ():
777+ cookies = await browser .context .cookies ()
761778 if len (cookies ) == 0 :
762779 return "No cookies found"
763780
@@ -798,22 +815,24 @@ async def browser_perform_action(action: str):
798815 """
799816 async with get_browser () as browser :
800817 async with browser .interaction_context_capture (only_domains = [browser .page_domain ]) as interaction_context :
801- await browser .page .act (action )
818+ async with browser .timeout ():
819+ await browser .page .act (action )
802820 with contextlib .suppress (TimeoutError ):
803821 await browser .page .wait_for_load_state ("networkidle" , timeout = 60000 )
804822 interaction_context = interaction_context
805823
806824 # Eagerly returning relevant observations to reduce agent tool calls
807- observations = "\n " .join (
808- map (
809- lambda obs : obs .description ,
810- await browser .page .observe (
811- f"`{ action } ` action was just performed. "
812- "give all important elements on the page that might be relevant to the next action."
813- "observe the overall state of the page to understand the purpose of the page."
814- ),
825+ async with browser .timeout ():
826+ observations = "\n " .join (
827+ map (
828+ lambda obs : obs .description ,
829+ await browser .page .observe (
830+ f"`{ action } ` action was just performed. "
831+ "give all important elements on the page that might be relevant to the next action."
832+ "observe the overall state of the page to understand the purpose of the page."
833+ ),
834+ )
815835 )
816- )
817836 return f"<observations>\n { observations } \n </observations>\n { interaction_context } "
818837
819838
@@ -840,5 +859,6 @@ async def browser_observe_page(instruction: Optional[str] = None) -> list[str]:
840859 on the browser page.
841860 """
842861 async with get_browser () as browser :
843- observations = await browser .page .observe (instruction )
862+ async with browser .timeout ():
863+ observations = await browser .page .observe (instruction )
844864 return [observation .description for observation in observations ]
0 commit comments