optexity-browser-use 0.9.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. browser_use/__init__.py +157 -0
  2. browser_use/actor/__init__.py +11 -0
  3. browser_use/actor/element.py +1175 -0
  4. browser_use/actor/mouse.py +134 -0
  5. browser_use/actor/page.py +561 -0
  6. browser_use/actor/playground/flights.py +41 -0
  7. browser_use/actor/playground/mixed_automation.py +54 -0
  8. browser_use/actor/playground/playground.py +236 -0
  9. browser_use/actor/utils.py +176 -0
  10. browser_use/agent/cloud_events.py +282 -0
  11. browser_use/agent/gif.py +424 -0
  12. browser_use/agent/judge.py +170 -0
  13. browser_use/agent/message_manager/service.py +473 -0
  14. browser_use/agent/message_manager/utils.py +52 -0
  15. browser_use/agent/message_manager/views.py +98 -0
  16. browser_use/agent/prompts.py +413 -0
  17. browser_use/agent/service.py +2316 -0
  18. browser_use/agent/system_prompt.md +185 -0
  19. browser_use/agent/system_prompt_flash.md +10 -0
  20. browser_use/agent/system_prompt_no_thinking.md +183 -0
  21. browser_use/agent/views.py +743 -0
  22. browser_use/browser/__init__.py +41 -0
  23. browser_use/browser/cloud/cloud.py +203 -0
  24. browser_use/browser/cloud/views.py +89 -0
  25. browser_use/browser/events.py +578 -0
  26. browser_use/browser/profile.py +1158 -0
  27. browser_use/browser/python_highlights.py +548 -0
  28. browser_use/browser/session.py +3225 -0
  29. browser_use/browser/session_manager.py +399 -0
  30. browser_use/browser/video_recorder.py +162 -0
  31. browser_use/browser/views.py +200 -0
  32. browser_use/browser/watchdog_base.py +260 -0
  33. browser_use/browser/watchdogs/__init__.py +0 -0
  34. browser_use/browser/watchdogs/aboutblank_watchdog.py +253 -0
  35. browser_use/browser/watchdogs/crash_watchdog.py +335 -0
  36. browser_use/browser/watchdogs/default_action_watchdog.py +2729 -0
  37. browser_use/browser/watchdogs/dom_watchdog.py +817 -0
  38. browser_use/browser/watchdogs/downloads_watchdog.py +1277 -0
  39. browser_use/browser/watchdogs/local_browser_watchdog.py +461 -0
  40. browser_use/browser/watchdogs/permissions_watchdog.py +43 -0
  41. browser_use/browser/watchdogs/popups_watchdog.py +143 -0
  42. browser_use/browser/watchdogs/recording_watchdog.py +126 -0
  43. browser_use/browser/watchdogs/screenshot_watchdog.py +62 -0
  44. browser_use/browser/watchdogs/security_watchdog.py +280 -0
  45. browser_use/browser/watchdogs/storage_state_watchdog.py +335 -0
  46. browser_use/cli.py +2359 -0
  47. browser_use/code_use/__init__.py +16 -0
  48. browser_use/code_use/formatting.py +192 -0
  49. browser_use/code_use/namespace.py +665 -0
  50. browser_use/code_use/notebook_export.py +276 -0
  51. browser_use/code_use/service.py +1340 -0
  52. browser_use/code_use/system_prompt.md +574 -0
  53. browser_use/code_use/utils.py +150 -0
  54. browser_use/code_use/views.py +171 -0
  55. browser_use/config.py +505 -0
  56. browser_use/controller/__init__.py +3 -0
  57. browser_use/dom/enhanced_snapshot.py +161 -0
  58. browser_use/dom/markdown_extractor.py +169 -0
  59. browser_use/dom/playground/extraction.py +312 -0
  60. browser_use/dom/playground/multi_act.py +32 -0
  61. browser_use/dom/serializer/clickable_elements.py +200 -0
  62. browser_use/dom/serializer/code_use_serializer.py +287 -0
  63. browser_use/dom/serializer/eval_serializer.py +478 -0
  64. browser_use/dom/serializer/html_serializer.py +212 -0
  65. browser_use/dom/serializer/paint_order.py +197 -0
  66. browser_use/dom/serializer/serializer.py +1170 -0
  67. browser_use/dom/service.py +825 -0
  68. browser_use/dom/utils.py +129 -0
  69. browser_use/dom/views.py +906 -0
  70. browser_use/exceptions.py +5 -0
  71. browser_use/filesystem/__init__.py +0 -0
  72. browser_use/filesystem/file_system.py +619 -0
  73. browser_use/init_cmd.py +376 -0
  74. browser_use/integrations/gmail/__init__.py +24 -0
  75. browser_use/integrations/gmail/actions.py +115 -0
  76. browser_use/integrations/gmail/service.py +225 -0
  77. browser_use/llm/__init__.py +155 -0
  78. browser_use/llm/anthropic/chat.py +242 -0
  79. browser_use/llm/anthropic/serializer.py +312 -0
  80. browser_use/llm/aws/__init__.py +36 -0
  81. browser_use/llm/aws/chat_anthropic.py +242 -0
  82. browser_use/llm/aws/chat_bedrock.py +289 -0
  83. browser_use/llm/aws/serializer.py +257 -0
  84. browser_use/llm/azure/chat.py +91 -0
  85. browser_use/llm/base.py +57 -0
  86. browser_use/llm/browser_use/__init__.py +3 -0
  87. browser_use/llm/browser_use/chat.py +201 -0
  88. browser_use/llm/cerebras/chat.py +193 -0
  89. browser_use/llm/cerebras/serializer.py +109 -0
  90. browser_use/llm/deepseek/chat.py +212 -0
  91. browser_use/llm/deepseek/serializer.py +109 -0
  92. browser_use/llm/exceptions.py +29 -0
  93. browser_use/llm/google/__init__.py +3 -0
  94. browser_use/llm/google/chat.py +542 -0
  95. browser_use/llm/google/serializer.py +120 -0
  96. browser_use/llm/groq/chat.py +229 -0
  97. browser_use/llm/groq/parser.py +158 -0
  98. browser_use/llm/groq/serializer.py +159 -0
  99. browser_use/llm/messages.py +238 -0
  100. browser_use/llm/models.py +271 -0
  101. browser_use/llm/oci_raw/__init__.py +10 -0
  102. browser_use/llm/oci_raw/chat.py +443 -0
  103. browser_use/llm/oci_raw/serializer.py +229 -0
  104. browser_use/llm/ollama/chat.py +97 -0
  105. browser_use/llm/ollama/serializer.py +143 -0
  106. browser_use/llm/openai/chat.py +264 -0
  107. browser_use/llm/openai/like.py +15 -0
  108. browser_use/llm/openai/serializer.py +165 -0
  109. browser_use/llm/openrouter/chat.py +211 -0
  110. browser_use/llm/openrouter/serializer.py +26 -0
  111. browser_use/llm/schema.py +176 -0
  112. browser_use/llm/views.py +48 -0
  113. browser_use/logging_config.py +330 -0
  114. browser_use/mcp/__init__.py +18 -0
  115. browser_use/mcp/__main__.py +12 -0
  116. browser_use/mcp/client.py +544 -0
  117. browser_use/mcp/controller.py +264 -0
  118. browser_use/mcp/server.py +1114 -0
  119. browser_use/observability.py +204 -0
  120. browser_use/py.typed +0 -0
  121. browser_use/sandbox/__init__.py +41 -0
  122. browser_use/sandbox/sandbox.py +637 -0
  123. browser_use/sandbox/views.py +132 -0
  124. browser_use/screenshots/__init__.py +1 -0
  125. browser_use/screenshots/service.py +52 -0
  126. browser_use/sync/__init__.py +6 -0
  127. browser_use/sync/auth.py +357 -0
  128. browser_use/sync/service.py +161 -0
  129. browser_use/telemetry/__init__.py +51 -0
  130. browser_use/telemetry/service.py +112 -0
  131. browser_use/telemetry/views.py +101 -0
  132. browser_use/tokens/__init__.py +0 -0
  133. browser_use/tokens/custom_pricing.py +24 -0
  134. browser_use/tokens/mappings.py +4 -0
  135. browser_use/tokens/service.py +580 -0
  136. browser_use/tokens/views.py +108 -0
  137. browser_use/tools/registry/service.py +572 -0
  138. browser_use/tools/registry/views.py +174 -0
  139. browser_use/tools/service.py +1675 -0
  140. browser_use/tools/utils.py +82 -0
  141. browser_use/tools/views.py +100 -0
  142. browser_use/utils.py +670 -0
  143. optexity_browser_use-0.9.5.dist-info/METADATA +344 -0
  144. optexity_browser_use-0.9.5.dist-info/RECORD +147 -0
  145. optexity_browser_use-0.9.5.dist-info/WHEEL +4 -0
  146. optexity_browser_use-0.9.5.dist-info/entry_points.txt +3 -0
  147. optexity_browser_use-0.9.5.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,578 @@
1
+ """Event definitions for browser communication."""
2
+
3
+ import inspect
4
+ import os
5
+ from typing import Any, Literal
6
+
7
+ from bubus import BaseEvent
8
+ from bubus.models import T_EventResultType
9
+ from cdp_use.cdp.target import TargetID
10
+ from pydantic import BaseModel, Field, field_validator
11
+
12
+ from browser_use.browser.views import BrowserStateSummary
13
+ from browser_use.dom.views import EnhancedDOMTreeNode
14
+
15
+
16
+ def _get_timeout(env_var: str, default: float) -> float | None:
17
+ """
18
+ Safely parse environment variable timeout values with robust error handling.
19
+
20
+ Args:
21
+ env_var: Environment variable name (e.g. 'TIMEOUT_NavigateToUrlEvent')
22
+ default: Default timeout value as float (e.g. 15.0)
23
+
24
+ Returns:
25
+ Parsed float value or the default if parsing fails
26
+
27
+ Raises:
28
+ ValueError: Only if both env_var and default are invalid (should not happen with valid defaults)
29
+ """
30
+ # Try environment variable first
31
+ env_value = os.getenv(env_var)
32
+ if env_value:
33
+ try:
34
+ parsed = float(env_value)
35
+ if parsed < 0:
36
+ print(f'Warning: {env_var}={env_value} is negative, using default {default}')
37
+ return default
38
+ return parsed
39
+ except (ValueError, TypeError):
40
+ print(f'Warning: {env_var}={env_value} is not a valid number, using default {default}')
41
+
42
+ # Fall back to default
43
+ return default
44
+
45
+
46
+ # ============================================================================
47
+ # Agent/Tools -> BrowserSession Events (High-level browser actions)
48
+ # ============================================================================
49
+
50
+
51
+ class ElementSelectedEvent(BaseEvent[T_EventResultType]):
52
+ """An element was selected."""
53
+
54
+ node: EnhancedDOMTreeNode
55
+
56
+ @field_validator('node', mode='before')
57
+ @classmethod
58
+ def serialize_node(cls, data: EnhancedDOMTreeNode | None) -> EnhancedDOMTreeNode | None:
59
+ if data is None:
60
+ return None
61
+ return EnhancedDOMTreeNode(
62
+ node_id=data.node_id,
63
+ backend_node_id=data.backend_node_id,
64
+ session_id=data.session_id,
65
+ frame_id=data.frame_id,
66
+ target_id=data.target_id,
67
+ node_type=data.node_type,
68
+ node_name=data.node_name,
69
+ node_value=data.node_value,
70
+ attributes=data.attributes,
71
+ is_scrollable=data.is_scrollable,
72
+ is_visible=data.is_visible,
73
+ absolute_position=data.absolute_position,
74
+ # override the circular reference fields in EnhancedDOMTreeNode as they cant be serialized and aren't needed by event handlers
75
+ # only used internally by the DOM service during DOM tree building process, not intended public API use
76
+ content_document=None,
77
+ shadow_root_type=None,
78
+ shadow_roots=[],
79
+ parent_node=None,
80
+ children_nodes=[],
81
+ ax_node=None,
82
+ snapshot_node=None,
83
+ )
84
+
85
+
86
+ # TODO: add page handle to events
87
+ # class PageHandle(share a base with browser.session.CDPSession?):
88
+ # url: str
89
+ # target_id: TargetID
90
+ # @classmethod
91
+ # def from_target_id(cls, target_id: TargetID) -> Self:
92
+ # return cls(target_id=target_id)
93
+ # @classmethod
94
+ # def from_target_id(cls, target_id: TargetID) -> Self:
95
+ # return cls(target_id=target_id)
96
+ # @classmethod
97
+ # def from_url(cls, url: str) -> Self:
98
+ # @property
99
+ # def root_frame_id(self) -> str:
100
+ # return self.target_id
101
+ # @property
102
+ # def session_id(self) -> str:
103
+ # return browser_session.get_or_create_cdp_session(self.target_id).session_id
104
+
105
+ # class PageSelectedEvent(BaseEvent[T_EventResultType]):
106
+ # """An event like SwitchToTabEvent(page=PageHandle) or CloseTabEvent(page=PageHandle)"""
107
+ # page: PageHandle
108
+
109
+
110
+ class NavigateToUrlEvent(BaseEvent[None]):
111
+ """Navigate to a specific URL."""
112
+
113
+ url: str
114
+ wait_until: Literal['load', 'domcontentloaded', 'networkidle', 'commit'] = 'load'
115
+ timeout_ms: int | None = None
116
+ new_tab: bool = Field(
117
+ default=False, description='Set True to leave the current tab alone and open a new tab in the foreground for the new URL'
118
+ )
119
+ # existing_tab: PageHandle | None = None # TODO
120
+
121
+ # time limits enforced by bubus, not exposed to LLM:
122
+ event_timeout: float | None = _get_timeout('TIMEOUT_NavigateToUrlEvent', 15.0) # seconds
123
+
124
+
125
+ class ClickElementEvent(ElementSelectedEvent[dict[str, Any] | None]):
126
+ """Click an element."""
127
+
128
+ node: 'EnhancedDOMTreeNode'
129
+ button: Literal['left', 'right', 'middle'] = 'left'
130
+ # click_count: int = 1 # TODO
131
+ # expect_download: bool = False # moved to downloads_watchdog.py
132
+
133
+ event_timeout: float | None = _get_timeout('TIMEOUT_ClickElementEvent', 15.0) # seconds
134
+
135
+
136
+ class TypeTextEvent(ElementSelectedEvent[dict | None]):
137
+ """Type text into an element."""
138
+
139
+ node: 'EnhancedDOMTreeNode'
140
+ text: str
141
+ clear: bool = True
142
+ is_sensitive: bool = False # Flag to indicate if text contains sensitive data
143
+ sensitive_key_name: str | None = None # Name of the sensitive key being typed (e.g., 'username', 'password')
144
+
145
+ event_timeout: float | None = _get_timeout('TIMEOUT_TypeTextEvent', 15.0) # seconds
146
+
147
+
148
+ class ScrollEvent(ElementSelectedEvent[None]):
149
+ """Scroll the page or element."""
150
+
151
+ direction: Literal['up', 'down', 'left', 'right']
152
+ amount: int # pixels
153
+ node: 'EnhancedDOMTreeNode | None' = None # None means scroll page
154
+
155
+ event_timeout: float | None = _get_timeout('TIMEOUT_ScrollEvent', 8.0) # seconds
156
+
157
+
158
+ class SwitchTabEvent(BaseEvent[TargetID]):
159
+ """Switch to a different tab."""
160
+
161
+ target_id: TargetID | None = Field(default=None, description='None means switch to the most recently opened tab')
162
+
163
+ event_timeout: float | None = _get_timeout('TIMEOUT_SwitchTabEvent', 10.0) # seconds
164
+
165
+
166
+ class CloseTabEvent(BaseEvent[None]):
167
+ """Close a tab."""
168
+
169
+ target_id: TargetID
170
+
171
+ event_timeout: float | None = _get_timeout('TIMEOUT_CloseTabEvent', 10.0) # seconds
172
+
173
+
174
+ class ScreenshotEvent(BaseEvent[str]):
175
+ """Request to take a screenshot."""
176
+
177
+ full_page: bool = False
178
+ clip: dict[str, float] | None = None # {x, y, width, height}
179
+
180
+ event_timeout: float | None = _get_timeout('TIMEOUT_ScreenshotEvent', 8.0) # seconds
181
+
182
+
183
+ class BrowserStateRequestEvent(BaseEvent[BrowserStateSummary]):
184
+ """Request current browser state."""
185
+
186
+ include_dom: bool = True
187
+ include_screenshot: bool = True
188
+ include_recent_events: bool = False
189
+
190
+ event_timeout: float | None = _get_timeout('TIMEOUT_BrowserStateRequestEvent', 30.0) # seconds
191
+
192
+
193
+ # class WaitForConditionEvent(BaseEvent):
194
+ # """Wait for a condition."""
195
+
196
+ # condition: Literal['navigation', 'selector', 'timeout', 'load_state']
197
+ # timeout: float = 30000
198
+ # selector: str | None = None
199
+ # state: Literal['attached', 'detached', 'visible', 'hidden'] | None = None
200
+
201
+
202
+ class GoBackEvent(BaseEvent[None]):
203
+ """Navigate back in browser history."""
204
+
205
+ event_timeout: float | None = _get_timeout('TIMEOUT_GoBackEvent', 15.0) # seconds
206
+
207
+
208
+ class GoForwardEvent(BaseEvent[None]):
209
+ """Navigate forward in browser history."""
210
+
211
+ event_timeout: float | None = _get_timeout('TIMEOUT_GoForwardEvent', 15.0) # seconds
212
+
213
+
214
+ class RefreshEvent(BaseEvent[None]):
215
+ """Refresh/reload the current page."""
216
+
217
+ event_timeout: float | None = _get_timeout('TIMEOUT_RefreshEvent', 15.0) # seconds
218
+
219
+
220
+ class WaitEvent(BaseEvent[None]):
221
+ """Wait for a specified number of seconds."""
222
+
223
+ seconds: float = 3.0
224
+ max_seconds: float = 10.0 # Safety cap
225
+
226
+ event_timeout: float | None = _get_timeout('TIMEOUT_WaitEvent', 60.0) # seconds
227
+
228
+
229
+ class SendKeysEvent(BaseEvent[None]):
230
+ """Send keyboard keys/shortcuts."""
231
+
232
+ keys: str # e.g., "ctrl+a", "cmd+c", "Enter"
233
+
234
+ event_timeout: float | None = _get_timeout('TIMEOUT_SendKeysEvent', 15.0) # seconds
235
+
236
+
237
+ class UploadFileEvent(ElementSelectedEvent[None]):
238
+ """Upload a file to an element."""
239
+
240
+ node: 'EnhancedDOMTreeNode'
241
+ file_path: str
242
+
243
+ event_timeout: float | None = _get_timeout('TIMEOUT_UploadFileEvent', 30.0) # seconds
244
+
245
+
246
+ class GetDropdownOptionsEvent(ElementSelectedEvent[dict[str, str]]):
247
+ """Get all options from any dropdown (native <select>, ARIA menus, or custom dropdowns).
248
+
249
+ Returns a dict containing dropdown type, options list, and element metadata."""
250
+
251
+ node: 'EnhancedDOMTreeNode'
252
+
253
+ event_timeout: float | None = _get_timeout(
254
+ 'TIMEOUT_GetDropdownOptionsEvent',
255
+ 15.0,
256
+ ) # some dropdowns lazy-load the list of options on first interaction, so we need to wait for them to load (e.g. table filter lists can have thousands of options)
257
+
258
+
259
+ class SelectDropdownOptionEvent(ElementSelectedEvent[dict[str, str]]):
260
+ """Select a dropdown option by exact text from any dropdown type.
261
+
262
+ Returns a dict containing success status and selection details."""
263
+
264
+ node: 'EnhancedDOMTreeNode'
265
+ text: str # The option text to select
266
+
267
+ event_timeout: float | None = _get_timeout('TIMEOUT_SelectDropdownOptionEvent', 8.0) # seconds
268
+
269
+
270
+ class ScrollToTextEvent(BaseEvent[None]):
271
+ """Scroll to specific text on the page. Raises exception if text not found."""
272
+
273
+ text: str
274
+ direction: Literal['up', 'down'] = 'down'
275
+
276
+ event_timeout: float | None = _get_timeout('TIMEOUT_ScrollToTextEvent', 15.0) # seconds
277
+
278
+
279
+ # ============================================================================
280
+
281
+
282
+ class BrowserStartEvent(BaseEvent):
283
+ """Start/connect to browser."""
284
+
285
+ cdp_url: str | None = None
286
+ launch_options: dict[str, Any] = Field(default_factory=dict)
287
+
288
+ event_timeout: float | None = _get_timeout('TIMEOUT_BrowserStartEvent', 30.0) # seconds
289
+
290
+
291
+ class BrowserStopEvent(BaseEvent):
292
+ """Stop/disconnect from browser."""
293
+
294
+ force: bool = False
295
+
296
+ event_timeout: float | None = _get_timeout('TIMEOUT_BrowserStopEvent', 45.0) # seconds
297
+
298
+
299
+ class BrowserLaunchResult(BaseModel):
300
+ """Result of launching a browser."""
301
+
302
+ # TODO: add browser executable_path, pid, version, latency, user_data_dir, X11 $DISPLAY, host IP address, etc.
303
+ cdp_url: str
304
+
305
+
306
+ class BrowserLaunchEvent(BaseEvent[BrowserLaunchResult]):
307
+ """Launch a local browser process."""
308
+
309
+ # TODO: add executable_path, proxy settings, preferences, extra launch args, etc.
310
+
311
+ event_timeout: float | None = _get_timeout('TIMEOUT_BrowserLaunchEvent', 30.0) # seconds
312
+
313
+
314
+ class BrowserKillEvent(BaseEvent):
315
+ """Kill local browser subprocess."""
316
+
317
+ event_timeout: float | None = _get_timeout('TIMEOUT_BrowserKillEvent', 30.0) # seconds
318
+
319
+
320
+ # TODO: replace all Runtime.evaluate() calls with this event
321
+ # class ExecuteJavaScriptEvent(BaseEvent):
322
+ # """Execute JavaScript in page context."""
323
+
324
+ # target_id: TargetID
325
+ # expression: str
326
+ # await_promise: bool = True
327
+
328
+ # event_timeout: float | None = 60.0 # seconds
329
+
330
+ # TODO: add this and use the old BrowserProfile.viewport options to set it
331
+ # class SetViewportEvent(BaseEvent):
332
+ # """Set the viewport size."""
333
+
334
+ # width: int
335
+ # height: int
336
+ # device_scale_factor: float = 1.0
337
+
338
+ # event_timeout: float | None = 15.0 # seconds
339
+
340
+
341
+ # Moved to storage state
342
+ # class SetCookiesEvent(BaseEvent):
343
+ # """Set browser cookies."""
344
+
345
+ # cookies: list[dict[str, Any]]
346
+
347
+ # event_timeout: float | None = (
348
+ # 30.0 # only long to support the edge case of restoring a big localStorage / on many origins (has to O(n) visit each origin to restore)
349
+ # )
350
+
351
+
352
+ # class GetCookiesEvent(BaseEvent):
353
+ # """Get browser cookies."""
354
+
355
+ # urls: list[str] | None = None
356
+
357
+ # event_timeout: float | None = 30.0 # seconds
358
+
359
+
360
+ # ============================================================================
361
+ # DOM-related Events
362
+ # ============================================================================
363
+
364
+
365
+ class BrowserConnectedEvent(BaseEvent):
366
+ """Browser has started/connected."""
367
+
368
+ cdp_url: str
369
+
370
+ event_timeout: float | None = _get_timeout('TIMEOUT_BrowserConnectedEvent', 30.0) # seconds
371
+
372
+
373
+ class BrowserStoppedEvent(BaseEvent):
374
+ """Browser has stopped/disconnected."""
375
+
376
+ reason: str | None = None
377
+
378
+ event_timeout: float | None = _get_timeout('TIMEOUT_BrowserStoppedEvent', 30.0) # seconds
379
+
380
+
381
+ class TabCreatedEvent(BaseEvent):
382
+ """A new tab was created."""
383
+
384
+ target_id: TargetID
385
+ url: str
386
+
387
+ event_timeout: float | None = _get_timeout('TIMEOUT_TabCreatedEvent', 30.0) # seconds
388
+
389
+
390
+ class TabClosedEvent(BaseEvent):
391
+ """A tab was closed."""
392
+
393
+ target_id: TargetID
394
+
395
+ # TODO:
396
+ # new_focus_target_id: int | None = None
397
+ # new_focus_url: str | None = None
398
+
399
+ event_timeout: float | None = _get_timeout('TIMEOUT_TabClosedEvent', 10.0) # seconds
400
+
401
+
402
+ # TODO: emit this when DOM changes significantly, inner frame navigates, form submits, history.pushState(), etc.
403
+ # class TabUpdatedEvent(BaseEvent):
404
+ # """Tab information updated (URL changed, etc.)."""
405
+
406
+ # target_id: TargetID
407
+ # url: str
408
+
409
+
410
+ class AgentFocusChangedEvent(BaseEvent):
411
+ """Agent focus changed to a different tab."""
412
+
413
+ target_id: TargetID
414
+ url: str
415
+
416
+ event_timeout: float | None = _get_timeout('TIMEOUT_AgentFocusChangedEvent', 10.0) # seconds
417
+
418
+
419
+ class TargetCrashedEvent(BaseEvent):
420
+ """A target has crashed."""
421
+
422
+ target_id: TargetID
423
+ error: str
424
+
425
+ event_timeout: float | None = _get_timeout('TIMEOUT_TargetCrashedEvent', 10.0) # seconds
426
+
427
+
428
+ class NavigationStartedEvent(BaseEvent):
429
+ """Navigation started."""
430
+
431
+ target_id: TargetID
432
+ url: str
433
+
434
+ event_timeout: float | None = _get_timeout('TIMEOUT_NavigationStartedEvent', 30.0) # seconds
435
+
436
+
437
+ class NavigationCompleteEvent(BaseEvent):
438
+ """Navigation completed."""
439
+
440
+ target_id: TargetID
441
+ url: str
442
+ status: int | None = None
443
+ error_message: str | None = None # Error/timeout message if navigation had issues
444
+ loading_status: str | None = None # Detailed loading status (e.g., network timeout info)
445
+
446
+ event_timeout: float | None = _get_timeout('TIMEOUT_NavigationCompleteEvent', 30.0) # seconds
447
+
448
+
449
+ # ============================================================================
450
+ # Error Events
451
+ # ============================================================================
452
+
453
+
454
+ class BrowserErrorEvent(BaseEvent):
455
+ """An error occurred in the browser layer."""
456
+
457
+ error_type: str
458
+ message: str
459
+ details: dict[str, Any] = Field(default_factory=dict)
460
+
461
+ event_timeout: float | None = _get_timeout('TIMEOUT_BrowserErrorEvent', 30.0) # seconds
462
+
463
+
464
+ # ============================================================================
465
+ # Storage State Events
466
+ # ============================================================================
467
+
468
+
469
+ class SaveStorageStateEvent(BaseEvent):
470
+ """Request to save browser storage state."""
471
+
472
+ path: str | None = None # Optional path, uses profile default if not provided
473
+
474
+ event_timeout: float | None = _get_timeout('TIMEOUT_SaveStorageStateEvent', 45.0) # seconds
475
+
476
+
477
+ class StorageStateSavedEvent(BaseEvent):
478
+ """Notification that storage state was saved."""
479
+
480
+ path: str
481
+ cookies_count: int
482
+ origins_count: int
483
+
484
+ event_timeout: float | None = _get_timeout('TIMEOUT_StorageStateSavedEvent', 30.0) # seconds
485
+
486
+
487
+ class LoadStorageStateEvent(BaseEvent):
488
+ """Request to load browser storage state."""
489
+
490
+ path: str | None = None # Optional path, uses profile default if not provided
491
+
492
+ event_timeout: float | None = _get_timeout('TIMEOUT_LoadStorageStateEvent', 45.0) # seconds
493
+
494
+
495
+ # TODO: refactor this to:
496
+ # - on_BrowserConnectedEvent() -> dispatch(LoadStorageStateEvent()) -> _copy_storage_state_from_json_to_browser(json_file, new_cdp_session) + return storage_state from handler
497
+ # - on_BrowserStopEvent() -> dispatch(SaveStorageStateEvent()) -> _copy_storage_state_from_browser_to_json(new_cdp_session, json_file)
498
+ # and get rid of StorageStateSavedEvent and StorageStateLoadedEvent, have the original events + provide handler return values for any results
499
+ class StorageStateLoadedEvent(BaseEvent):
500
+ """Notification that storage state was loaded."""
501
+
502
+ path: str
503
+ cookies_count: int
504
+ origins_count: int
505
+
506
+ event_timeout: float | None = _get_timeout('TIMEOUT_StorageStateLoadedEvent', 30.0) # seconds
507
+
508
+
509
+ # ============================================================================
510
+ # File Download Events
511
+ # ============================================================================
512
+
513
+
514
+ class FileDownloadedEvent(BaseEvent):
515
+ """A file has been downloaded."""
516
+
517
+ url: str
518
+ path: str
519
+ file_name: str
520
+ file_size: int
521
+ file_type: str | None = None # e.g., 'pdf', 'zip', 'docx', etc.
522
+ mime_type: str | None = None # e.g., 'application/pdf'
523
+ from_cache: bool = False
524
+ auto_download: bool = False # Whether this was an automatic download (e.g., PDF auto-download)
525
+
526
+ event_timeout: float | None = _get_timeout('TIMEOUT_FileDownloadedEvent', 30.0) # seconds
527
+
528
+
529
+ class AboutBlankDVDScreensaverShownEvent(BaseEvent):
530
+ """AboutBlankWatchdog has shown DVD screensaver animation on an about:blank tab."""
531
+
532
+ target_id: TargetID
533
+ error: str | None = None
534
+
535
+
536
+ class DialogOpenedEvent(BaseEvent):
537
+ """Event dispatched when a JavaScript dialog is opened and handled."""
538
+
539
+ dialog_type: str # 'alert', 'confirm', 'prompt', or 'beforeunload'
540
+ message: str
541
+ url: str
542
+ frame_id: str | None = None # Can be None when frameId is not provided by CDP
543
+ # target_id: TargetID # TODO: add this to avoid needing target_id_from_frame() later
544
+
545
+
546
+ # Note: Model rebuilding for forward references is handled in the importing modules
547
+ # Events with 'EnhancedDOMTreeNode' forward references (ClickElementEvent, TypeTextEvent,
548
+ # ScrollEvent, UploadFileEvent) need model_rebuild() called after imports are complete
549
+
550
+
551
+ def _check_event_names_dont_overlap():
552
+ """
553
+ check that event names defined in this file are valid and non-overlapping
554
+ (naiively n^2 so it's pretty slow but ok for now, optimize when >20 events)
555
+ """
556
+ event_names = {
557
+ name.split('[')[0]
558
+ for name in globals().keys()
559
+ if not name.startswith('_')
560
+ and inspect.isclass(globals()[name])
561
+ and issubclass(globals()[name], BaseEvent)
562
+ and name != 'BaseEvent'
563
+ }
564
+ for name_a in event_names:
565
+ assert name_a.endswith('Event'), f'Event with name {name_a} does not end with "Event"'
566
+ for name_b in event_names:
567
+ if name_a != name_b: # Skip self-comparison
568
+ assert name_a not in name_b, (
569
+ f'Event with name {name_a} is a substring of {name_b}, all events must be completely unique to avoid find-and-replace accidents'
570
+ )
571
+
572
+
573
+ # overlapping event names are a nightmare to trace and rename later, dont do it!
574
+ # e.g. prevent ClickEvent and FailedClickEvent are terrible names because one is a substring of the other,
575
+ # must be ClickEvent and ClickFailedEvent to preserve the usefulnes of codebase grep/sed/awk as refactoring tools.
576
+ # at import time, we do a quick check that all event names defined above are valid and non-overlapping.
577
+ # this is hand written in blood by a human! not LLM slop. feel free to optimize but do not remove it without a good reason.
578
+ _check_event_names_dont_overlap()