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,150 @@
1
+ """Utility functions for code-use agent."""
2
+
3
+ import re
4
+
5
+
6
+ def truncate_message_content(content: str, max_length: int = 10000) -> str:
7
+ """Truncate message content to max_length characters for history."""
8
+ if len(content) <= max_length:
9
+ return content
10
+ # Truncate and add marker
11
+ return content[:max_length] + f'\n\n[... truncated {len(content) - max_length} characters for history]'
12
+
13
+
14
+ def detect_token_limit_issue(
15
+ completion: str,
16
+ completion_tokens: int | None,
17
+ max_tokens: int | None,
18
+ stop_reason: str | None,
19
+ ) -> tuple[bool, str | None]:
20
+ """
21
+ Detect if the LLM response hit token limits or is repetitive garbage.
22
+
23
+ Returns: (is_problematic, error_message)
24
+ """
25
+ # Check 1: Stop reason indicates max_tokens
26
+ if stop_reason == 'max_tokens':
27
+ return True, f'Response terminated due to max_tokens limit (stop_reason: {stop_reason})'
28
+
29
+ # Check 2: Used 90%+ of max_tokens (if we have both values)
30
+ if completion_tokens is not None and max_tokens is not None and max_tokens > 0:
31
+ usage_ratio = completion_tokens / max_tokens
32
+ if usage_ratio >= 0.9:
33
+ return True, f'Response used {usage_ratio:.1%} of max_tokens ({completion_tokens}/{max_tokens})'
34
+
35
+ # Check 3: Last 6 characters repeat 40+ times (repetitive garbage)
36
+ if len(completion) >= 6:
37
+ last_6 = completion[-6:]
38
+ repetition_count = completion.count(last_6)
39
+ if repetition_count >= 40:
40
+ return True, f'Repetitive output detected: last 6 chars "{last_6}" appears {repetition_count} times'
41
+
42
+ return False, None
43
+
44
+
45
+ def extract_url_from_task(task: str) -> str | None:
46
+ """Extract URL from task string using naive pattern matching."""
47
+ # Remove email addresses from task before looking for URLs
48
+ task_without_emails = re.sub(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', '', task)
49
+
50
+ # Look for common URL patterns
51
+ patterns = [
52
+ r'https?://[^\s<>"\']+', # Full URLs with http/https
53
+ r'(?:www\.)?[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*\.[a-zA-Z]{2,}(?:/[^\s<>"\']*)?', # Domain names with subdomains and optional paths
54
+ ]
55
+
56
+ found_urls = []
57
+ for pattern in patterns:
58
+ matches = re.finditer(pattern, task_without_emails)
59
+ for match in matches:
60
+ url = match.group(0)
61
+
62
+ # Remove trailing punctuation that's not part of URLs
63
+ url = re.sub(r'[.,;:!?()\[\]]+$', '', url)
64
+ # Add https:// if missing
65
+ if not url.startswith(('http://', 'https://')):
66
+ url = 'https://' + url
67
+ found_urls.append(url)
68
+
69
+ unique_urls = list(set(found_urls))
70
+ # If multiple URLs found, skip auto-navigation to avoid ambiguity
71
+ if len(unique_urls) > 1:
72
+ return None
73
+
74
+ # If exactly one URL found, return it
75
+ if len(unique_urls) == 1:
76
+ return unique_urls[0]
77
+
78
+ return None
79
+
80
+
81
+ def extract_code_blocks(text: str) -> dict[str, str]:
82
+ """Extract all code blocks from markdown response.
83
+
84
+ Supports:
85
+ - ```python, ```js, ```javascript, ```bash, ```markdown, ```md
86
+ - Named blocks: ```js variable_name → saved as 'variable_name' in namespace
87
+ - Nested blocks: Use 4+ backticks for outer block when inner content has 3 backticks
88
+
89
+ Returns dict mapping block_name -> content
90
+
91
+ Note: Python blocks are NO LONGER COMBINED. Each python block executes separately
92
+ to allow sequential execution with JS/bash blocks in between.
93
+ """
94
+ # Pattern to match code blocks with language identifier and optional variable name
95
+ # Matches: ```lang\n or ```lang varname\n or ````+lang\n (4+ backticks for nested blocks)
96
+ # Uses non-greedy matching and backreferences to match opening/closing backticks
97
+ pattern = r'(`{3,})(\w+)(?:\s+(\w+))?\n(.*?)\1(?:\n|$)'
98
+ matches = re.findall(pattern, text, re.DOTALL)
99
+
100
+ blocks: dict[str, str] = {}
101
+ python_block_counter = 0
102
+
103
+ for backticks, lang, var_name, content in matches:
104
+ lang = lang.lower()
105
+
106
+ # Normalize language names
107
+ if lang in ('javascript', 'js'):
108
+ lang_normalized = 'js'
109
+ elif lang in ('markdown', 'md'):
110
+ lang_normalized = 'markdown'
111
+ elif lang in ('sh', 'shell'):
112
+ lang_normalized = 'bash'
113
+ elif lang == 'python':
114
+ lang_normalized = 'python'
115
+ else:
116
+ # Unknown language, skip
117
+ continue
118
+
119
+ # Only process supported types
120
+ if lang_normalized in ('python', 'js', 'bash', 'markdown'):
121
+ content = content.rstrip() # Only strip trailing whitespace, preserve leading for indentation
122
+ if content:
123
+ # Determine the key to use
124
+ if var_name:
125
+ # Named block - use the variable name
126
+ block_key = var_name
127
+ blocks[block_key] = content
128
+ elif lang_normalized == 'python':
129
+ # Unnamed Python blocks - give each a unique key to preserve order
130
+ block_key = f'python_{python_block_counter}'
131
+ blocks[block_key] = content
132
+ python_block_counter += 1
133
+ else:
134
+ # Other unnamed blocks (js, bash, markdown) - keep last one only
135
+ blocks[lang_normalized] = content
136
+
137
+ # If we have multiple python blocks, mark the first one as 'python' for backward compat
138
+ if python_block_counter > 0:
139
+ blocks['python'] = blocks['python_0']
140
+
141
+ # Fallback: if no python block but there's generic ``` block, treat as python
142
+ if python_block_counter == 0 and 'python' not in blocks:
143
+ generic_pattern = r'```\n(.*?)```'
144
+ generic_matches = re.findall(generic_pattern, text, re.DOTALL)
145
+ if generic_matches:
146
+ combined = '\n\n'.join(m.strip() for m in generic_matches if m.strip())
147
+ if combined:
148
+ blocks['python'] = combined
149
+
150
+ return blocks
@@ -0,0 +1,171 @@
1
+ """Data models for code-use mode."""
2
+
3
+ from enum import Enum
4
+ from typing import Any
5
+
6
+ from pydantic import BaseModel, ConfigDict, Field
7
+ from uuid_extensions import uuid7str
8
+
9
+
10
+ class CellType(str, Enum):
11
+ """Type of notebook cell."""
12
+
13
+ CODE = 'code'
14
+ MARKDOWN = 'markdown'
15
+
16
+
17
+ class ExecutionStatus(str, Enum):
18
+ """Execution status of a cell."""
19
+
20
+ PENDING = 'pending'
21
+ RUNNING = 'running'
22
+ SUCCESS = 'success'
23
+ ERROR = 'error'
24
+
25
+
26
+ class CodeCell(BaseModel):
27
+ """Represents a code cell in the notebook-like execution."""
28
+
29
+ model_config = ConfigDict(extra='forbid')
30
+
31
+ id: str = Field(default_factory=uuid7str)
32
+ cell_type: CellType = CellType.CODE
33
+ source: str = Field(description='The code to execute')
34
+ output: str | None = Field(default=None, description='The output of the code execution')
35
+ execution_count: int | None = Field(default=None, description='The execution count')
36
+ status: ExecutionStatus = Field(default=ExecutionStatus.PENDING)
37
+ error: str | None = Field(default=None, description='Error message if execution failed')
38
+ browser_state: str | None = Field(default=None, description='Browser state after execution')
39
+
40
+
41
+ class NotebookSession(BaseModel):
42
+ """Represents a notebook-like session."""
43
+
44
+ model_config = ConfigDict(extra='forbid')
45
+
46
+ id: str = Field(default_factory=uuid7str)
47
+ cells: list[CodeCell] = Field(default_factory=list)
48
+ current_execution_count: int = Field(default=0)
49
+ namespace: dict[str, Any] = Field(default_factory=dict, description='Current namespace state')
50
+
51
+ def add_cell(self, source: str) -> CodeCell:
52
+ """Add a new code cell to the session."""
53
+ cell = CodeCell(source=source)
54
+ self.cells.append(cell)
55
+ return cell
56
+
57
+ def get_cell(self, cell_id: str) -> CodeCell | None:
58
+ """Get a cell by ID."""
59
+ for cell in self.cells:
60
+ if cell.id == cell_id:
61
+ return cell
62
+ return None
63
+
64
+ def get_latest_cell(self) -> CodeCell | None:
65
+ """Get the most recently added cell."""
66
+ if self.cells:
67
+ return self.cells[-1]
68
+ return None
69
+
70
+ def increment_execution_count(self) -> int:
71
+ """Increment and return the execution count."""
72
+ self.current_execution_count += 1
73
+ return self.current_execution_count
74
+
75
+
76
+ class NotebookExport(BaseModel):
77
+ """Export format for Jupyter notebook."""
78
+
79
+ model_config = ConfigDict(extra='forbid')
80
+
81
+ nbformat: int = Field(default=4)
82
+ nbformat_minor: int = Field(default=5)
83
+ metadata: dict[str, Any] = Field(default_factory=dict)
84
+ cells: list[dict[str, Any]] = Field(default_factory=list)
85
+
86
+
87
+ class CodeAgentModelOutput(BaseModel):
88
+ """Model output for CodeAgent - contains the code and full LLM response."""
89
+
90
+ model_config = ConfigDict(extra='forbid')
91
+
92
+ model_output: str = Field(description='The extracted code from the LLM response')
93
+ full_response: str = Field(description='The complete LLM response including any text/reasoning')
94
+
95
+
96
+ class CodeAgentResult(BaseModel):
97
+ """Result of executing a code cell in CodeAgent."""
98
+
99
+ model_config = ConfigDict(extra='forbid')
100
+
101
+ extracted_content: str | None = Field(default=None, description='Output from code execution')
102
+ error: str | None = Field(default=None, description='Error message if execution failed')
103
+ is_done: bool = Field(default=False, description='Whether task is marked as done')
104
+ success: bool | None = Field(default=None, description='Self-reported success from done() call')
105
+
106
+
107
+ class CodeAgentState(BaseModel):
108
+ """State information for a CodeAgent step."""
109
+
110
+ model_config = ConfigDict(extra='forbid', arbitrary_types_allowed=True)
111
+
112
+ url: str | None = Field(default=None, description='Current page URL')
113
+ title: str | None = Field(default=None, description='Current page title')
114
+ screenshot_path: str | None = Field(default=None, description='Path to screenshot file')
115
+
116
+ def get_screenshot(self) -> str | None:
117
+ """Load screenshot from disk and return as base64 string."""
118
+ if not self.screenshot_path:
119
+ return None
120
+
121
+ import base64
122
+ from pathlib import Path
123
+
124
+ path_obj = Path(self.screenshot_path)
125
+ if not path_obj.exists():
126
+ return None
127
+
128
+ try:
129
+ with open(path_obj, 'rb') as f:
130
+ screenshot_data = f.read()
131
+ return base64.b64encode(screenshot_data).decode('utf-8')
132
+ except Exception:
133
+ return None
134
+
135
+
136
+ class CodeAgentStepMetadata(BaseModel):
137
+ """Metadata for a single CodeAgent step including timing and token information."""
138
+
139
+ model_config = ConfigDict(extra='forbid')
140
+
141
+ input_tokens: int | None = Field(default=None, description='Number of input tokens used')
142
+ output_tokens: int | None = Field(default=None, description='Number of output tokens used')
143
+ step_start_time: float = Field(description='Step start timestamp (Unix time)')
144
+ step_end_time: float = Field(description='Step end timestamp (Unix time)')
145
+
146
+ @property
147
+ def duration_seconds(self) -> float:
148
+ """Calculate step duration in seconds."""
149
+ return self.step_end_time - self.step_start_time
150
+
151
+
152
+ class CodeAgentHistory(BaseModel):
153
+ """History item for CodeAgent actions."""
154
+
155
+ model_config = ConfigDict(extra='forbid', arbitrary_types_allowed=True)
156
+
157
+ model_output: CodeAgentModelOutput | None = Field(default=None, description='LLM output for this step')
158
+ result: list[CodeAgentResult] = Field(default_factory=list, description='Results from code execution')
159
+ state: CodeAgentState = Field(description='Browser state at this step')
160
+ metadata: CodeAgentStepMetadata | None = Field(default=None, description='Step timing and token metadata')
161
+ screenshot_path: str | None = Field(default=None, description='Legacy field for screenshot path')
162
+
163
+ def model_dump(self, **kwargs) -> dict[str, Any]:
164
+ """Custom serialization for CodeAgentHistory."""
165
+ return {
166
+ 'model_output': self.model_output.model_dump() if self.model_output else None,
167
+ 'result': [r.model_dump() for r in self.result],
168
+ 'state': self.state.model_dump(),
169
+ 'metadata': self.metadata.model_dump() if self.metadata else None,
170
+ 'screenshot_path': self.screenshot_path,
171
+ }