66import asyncio
77import hashlib
88import time
9- from typing import TYPE_CHECKING , Any , Optional
9+ from typing import TYPE_CHECKING , Any , Optional , Union
1010
1111from .action_executor import ActionExecutor
1212from .agent_config import AgentConfig
2525 SnapshotOptions ,
2626 TokenStats ,
2727)
28+ from .protocols import AsyncBrowserProtocol , BrowserProtocol
2829from .snapshot import snapshot , snapshot_async
2930from .trace_event_builder import TraceEventBuilder
3031
3132if TYPE_CHECKING :
3233 from .tracing import Tracer
3334
3435
36+ def _safe_tracer_call (
37+ tracer : Optional ["Tracer" ], method_name : str , verbose : bool , * args , ** kwargs
38+ ) -> None :
39+ """
40+ Safely call tracer method, catching and logging errors without breaking execution.
41+
42+ Args:
43+ tracer: Tracer instance or None
44+ method_name: Name of tracer method to call (e.g., "emit", "emit_error")
45+ verbose: Whether to print error messages
46+ *args: Positional arguments for the tracer method
47+ **kwargs: Keyword arguments for the tracer method
48+ """
49+ if not tracer :
50+ return
51+ try :
52+ method = getattr (tracer , method_name )
53+ if args and kwargs :
54+ method (* args , ** kwargs )
55+ elif args :
56+ method (* args )
57+ elif kwargs :
58+ method (** kwargs )
59+ else :
60+ method ()
61+ except Exception as tracer_error :
62+ # Tracer errors should not break agent execution
63+ if verbose :
64+ print (f"⚠️ Tracer error (non-fatal): { tracer_error } " )
65+
66+
3567class SentienceAgent (BaseAgent ):
3668 """
3769 High-level agent that combines Sentience SDK with any LLM provider.
@@ -58,7 +90,7 @@ class SentienceAgent(BaseAgent):
5890
5991 def __init__ (
6092 self ,
61- browser : SentienceBrowser ,
93+ browser : SentienceBrowser | BrowserProtocol ,
6294 llm : LLMProvider ,
6395 default_snapshot_limit : int = 50 ,
6496 verbose : bool = True ,
@@ -69,7 +101,8 @@ def __init__(
69101 Initialize Sentience Agent
70102
71103 Args:
72- browser: SentienceBrowser instance
104+ browser: SentienceBrowser instance or BrowserProtocol-compatible object
105+ (for testing, can use mock objects that implement BrowserProtocol)
73106 llm: LLM provider (OpenAIProvider, AnthropicProvider, etc.)
74107 default_snapshot_limit: Default maximum elements to include in context (default: 50)
75108 verbose: Print execution logs (default: True)
@@ -157,7 +190,10 @@ def act( # noqa: C901
157190 # Emit step_start trace event if tracer is enabled
158191 if self .tracer :
159192 pre_url = self .browser .page .url if self .browser .page else None
160- self .tracer .emit_step_start (
193+ _safe_tracer_call (
194+ self .tracer ,
195+ "emit_step_start" ,
196+ self .verbose ,
161197 step_id = step_id ,
162198 step_index = self ._step_count ,
163199 goal = goal ,
@@ -226,7 +262,10 @@ def act( # noqa: C901
226262 if snap .screenshot_format :
227263 snapshot_data ["screenshot_format" ] = snap .screenshot_format
228264
229- self .tracer .emit (
265+ _safe_tracer_call (
266+ self .tracer ,
267+ "emit" ,
268+ self .verbose ,
230269 "snapshot" ,
231270 snapshot_data ,
232271 step_id = step_id ,
@@ -252,7 +291,10 @@ def act( # noqa: C901
252291
253292 # Emit LLM query trace event if tracer is enabled
254293 if self .tracer :
255- self .tracer .emit (
294+ _safe_tracer_call (
295+ self .tracer ,
296+ "emit" ,
297+ self .verbose ,
256298 "llm_query" ,
257299 {
258300 "prompt_tokens" : llm_response .prompt_tokens ,
@@ -313,7 +355,10 @@ def act( # noqa: C901
313355 for el in filtered_snap .elements [:50 ]
314356 ]
315357
316- self .tracer .emit (
358+ _safe_tracer_call (
359+ self .tracer ,
360+ "emit" ,
361+ self .verbose ,
317362 "action" ,
318363 {
319364 "action" : result .action ,
@@ -433,14 +478,28 @@ def act( # noqa: C901
433478 verify_data = verify_data ,
434479 )
435480
436- self .tracer .emit ("step_end" , step_end_data , step_id = step_id )
481+ _safe_tracer_call (
482+ self .tracer ,
483+ "emit" ,
484+ self .verbose ,
485+ "step_end" ,
486+ step_end_data ,
487+ step_id = step_id ,
488+ )
437489
438490 return result
439491
440492 except Exception as e :
441493 # Emit error trace event if tracer is enabled
442494 if self .tracer :
443- self .tracer .emit_error (step_id = step_id , error = str (e ), attempt = attempt )
495+ _safe_tracer_call (
496+ self .tracer ,
497+ "emit_error" ,
498+ self .verbose ,
499+ step_id = step_id ,
500+ error = str (e ),
501+ attempt = attempt ,
502+ )
444503
445504 if attempt < max_retries :
446505 if self .verbose :
@@ -666,7 +725,10 @@ async def act( # noqa: C901
666725 # Emit step_start trace event if tracer is enabled
667726 if self .tracer :
668727 pre_url = self .browser .page .url if self .browser .page else None
669- self .tracer .emit_step_start (
728+ _safe_tracer_call (
729+ self .tracer ,
730+ "emit_step_start" ,
731+ self .verbose ,
670732 step_id = step_id ,
671733 step_index = self ._step_count ,
672734 goal = goal ,
@@ -738,7 +800,10 @@ async def act( # noqa: C901
738800 if snap .screenshot_format :
739801 snapshot_data ["screenshot_format" ] = snap .screenshot_format
740802
741- self .tracer .emit (
803+ _safe_tracer_call (
804+ self .tracer ,
805+ "emit" ,
806+ self .verbose ,
742807 "snapshot" ,
743808 snapshot_data ,
744809 step_id = step_id ,
@@ -764,7 +829,10 @@ async def act( # noqa: C901
764829
765830 # Emit LLM query trace event if tracer is enabled
766831 if self .tracer :
767- self .tracer .emit (
832+ _safe_tracer_call (
833+ self .tracer ,
834+ "emit" ,
835+ self .verbose ,
768836 "llm_query" ,
769837 {
770838 "prompt_tokens" : llm_response .prompt_tokens ,
@@ -825,7 +893,10 @@ async def act( # noqa: C901
825893 for el in filtered_snap .elements [:50 ]
826894 ]
827895
828- self .tracer .emit (
896+ _safe_tracer_call (
897+ self .tracer ,
898+ "emit" ,
899+ self .verbose ,
829900 "action" ,
830901 {
831902 "action" : result .action ,
@@ -945,14 +1016,28 @@ async def act( # noqa: C901
9451016 verify_data = verify_data ,
9461017 )
9471018
948- self .tracer .emit ("step_end" , step_end_data , step_id = step_id )
1019+ _safe_tracer_call (
1020+ self .tracer ,
1021+ "emit" ,
1022+ self .verbose ,
1023+ "step_end" ,
1024+ step_end_data ,
1025+ step_id = step_id ,
1026+ )
9491027
9501028 return result
9511029
9521030 except Exception as e :
9531031 # Emit error trace event if tracer is enabled
9541032 if self .tracer :
955- self .tracer .emit_error (step_id = step_id , error = str (e ), attempt = attempt )
1033+ _safe_tracer_call (
1034+ self .tracer ,
1035+ "emit_error" ,
1036+ self .verbose ,
1037+ step_id = step_id ,
1038+ error = str (e ),
1039+ attempt = attempt ,
1040+ )
9561041
9571042 if attempt < max_retries :
9581043 if self .verbose :
0 commit comments