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,376 @@
1
+ """
2
+ Standalone init command for browser-use template generation.
3
+
4
+ This module provides a minimal command-line interface for generating
5
+ browser-use templates without requiring heavy TUI dependencies.
6
+ """
7
+
8
+ import json
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Any
12
+ from urllib import request
13
+ from urllib.error import URLError
14
+
15
+ import click
16
+ from InquirerPy.base.control import Choice
17
+ from InquirerPy.prompts.list import ListPrompt
18
+ from InquirerPy.utils import InquirerPyStyle
19
+ from rich.console import Console
20
+ from rich.panel import Panel
21
+ from rich.text import Text
22
+
23
+ # Rich console for styled output
24
+ console = Console()
25
+
26
+ # GitHub template repository URL (for runtime fetching)
27
+ TEMPLATE_REPO_URL = 'https://raw.githubusercontent.com/browser-use/template-library/main'
28
+
29
+ # Export for backward compatibility with cli.py
30
+ # Templates are fetched at runtime via _get_template_list()
31
+ INIT_TEMPLATES: dict[str, Any] = {}
32
+
33
+
34
+ def _fetch_template_list() -> dict[str, Any] | None:
35
+ """
36
+ Fetch template list from GitHub templates.json.
37
+
38
+ Returns template dict if successful, None if failed.
39
+ """
40
+ try:
41
+ url = f'{TEMPLATE_REPO_URL}/templates.json'
42
+ with request.urlopen(url, timeout=5) as response:
43
+ data = response.read().decode('utf-8')
44
+ return json.loads(data)
45
+ except (URLError, TimeoutError, json.JSONDecodeError, Exception):
46
+ return None
47
+
48
+
49
+ def _get_template_list() -> dict[str, Any]:
50
+ """
51
+ Get template list from GitHub.
52
+
53
+ Raises FileNotFoundError if GitHub fetch fails.
54
+ """
55
+ templates = _fetch_template_list()
56
+ if templates is not None:
57
+ return templates
58
+ raise FileNotFoundError('Could not fetch templates from GitHub. Check your internet connection.')
59
+
60
+
61
+ def _fetch_from_github(file_path: str) -> str | None:
62
+ """
63
+ Fetch template file from GitHub.
64
+
65
+ Returns file content if successful, None if failed.
66
+ """
67
+ try:
68
+ url = f'{TEMPLATE_REPO_URL}/{file_path}'
69
+ with request.urlopen(url, timeout=5) as response:
70
+ return response.read().decode('utf-8')
71
+ except (URLError, TimeoutError, Exception):
72
+ return None
73
+
74
+
75
+ def _fetch_binary_from_github(file_path: str) -> bytes | None:
76
+ """
77
+ Fetch binary file from GitHub.
78
+
79
+ Returns file content if successful, None if failed.
80
+ """
81
+ try:
82
+ url = f'{TEMPLATE_REPO_URL}/{file_path}'
83
+ with request.urlopen(url, timeout=5) as response:
84
+ return response.read()
85
+ except (URLError, TimeoutError, Exception):
86
+ return None
87
+
88
+
89
+ def _get_template_content(file_path: str) -> str:
90
+ """
91
+ Get template file content from GitHub.
92
+
93
+ Raises exception if fetch fails.
94
+ """
95
+ content = _fetch_from_github(file_path)
96
+
97
+ if content is not None:
98
+ return content
99
+
100
+ raise FileNotFoundError(f'Could not fetch template from GitHub: {file_path}')
101
+
102
+
103
+ # InquirerPy style for template selection (browser-use orange theme)
104
+ inquirer_style = InquirerPyStyle(
105
+ {
106
+ 'pointer': '#fe750e bold',
107
+ 'highlighted': '#fe750e bold',
108
+ 'question': 'bold',
109
+ 'answer': '#fe750e bold',
110
+ 'questionmark': '#fe750e bold',
111
+ }
112
+ )
113
+
114
+
115
+ def _write_init_file(output_path: Path, content: str, force: bool = False) -> bool:
116
+ """Write content to a file, with safety checks."""
117
+ # Check if file already exists
118
+ if output_path.exists() and not force:
119
+ console.print(f'[yellow]⚠[/yellow] File already exists: [cyan]{output_path}[/cyan]')
120
+ if not click.confirm('Overwrite?', default=False):
121
+ console.print('[red]✗[/red] Cancelled')
122
+ return False
123
+
124
+ # Ensure parent directory exists
125
+ output_path.parent.mkdir(parents=True, exist_ok=True)
126
+
127
+ # Write file
128
+ try:
129
+ output_path.write_text(content, encoding='utf-8')
130
+ return True
131
+ except Exception as e:
132
+ console.print(f'[red]✗[/red] Error writing file: {e}')
133
+ return False
134
+
135
+
136
+ @click.command('browser-use-init')
137
+ @click.option(
138
+ '--template',
139
+ '-t',
140
+ type=str,
141
+ help='Template to use',
142
+ )
143
+ @click.option(
144
+ '--output',
145
+ '-o',
146
+ type=click.Path(),
147
+ help='Output file path (default: browser_use_<template>.py)',
148
+ )
149
+ @click.option(
150
+ '--force',
151
+ '-f',
152
+ is_flag=True,
153
+ help='Overwrite existing files without asking',
154
+ )
155
+ @click.option(
156
+ '--list',
157
+ '-l',
158
+ 'list_templates',
159
+ is_flag=True,
160
+ help='List available templates',
161
+ )
162
+ def main(
163
+ template: str | None,
164
+ output: str | None,
165
+ force: bool,
166
+ list_templates: bool,
167
+ ):
168
+ """
169
+ Generate a browser-use template file to get started quickly.
170
+
171
+ Examples:
172
+
173
+ \b
174
+ # Interactive mode - prompts for template selection
175
+ uvx browser-use init
176
+ uvx browser-use init --template
177
+
178
+ \b
179
+ # Generate default template
180
+ uvx browser-use init --template default
181
+
182
+ \b
183
+ # Generate advanced template with custom filename
184
+ uvx browser-use init --template advanced --output my_script.py
185
+
186
+ \b
187
+ # List available templates
188
+ uvx browser-use init --list
189
+ """
190
+
191
+ # Fetch template list at runtime
192
+ try:
193
+ INIT_TEMPLATES = _get_template_list()
194
+ except FileNotFoundError as e:
195
+ console.print(f'[red]✗[/red] {e}')
196
+ sys.exit(1)
197
+
198
+ # Handle --list flag
199
+ if list_templates:
200
+ console.print('\n[bold]Available templates:[/bold]\n')
201
+ for name, info in INIT_TEMPLATES.items():
202
+ console.print(f' [#fe750e]{name:12}[/#fe750e] - {info["description"]}')
203
+ console.print()
204
+ return
205
+
206
+ # Interactive template selection if not provided
207
+ if not template:
208
+ # Create choices with numbered display
209
+ template_list = list(INIT_TEMPLATES.keys())
210
+ choices = [
211
+ Choice(
212
+ name=f'{i}. {name:12} - {info["description"]}',
213
+ value=name,
214
+ )
215
+ for i, (name, info) in enumerate(INIT_TEMPLATES.items(), 1)
216
+ ]
217
+
218
+ # Create the prompt
219
+ prompt = ListPrompt(
220
+ message='Select a template:',
221
+ choices=choices,
222
+ default='default',
223
+ style=inquirer_style,
224
+ )
225
+
226
+ # Register custom keybindings for instant selection with number keys
227
+ @prompt.register_kb('1')
228
+ def _(event):
229
+ event.app.exit(result=template_list[0])
230
+
231
+ @prompt.register_kb('2')
232
+ def _(event):
233
+ event.app.exit(result=template_list[1])
234
+
235
+ @prompt.register_kb('3')
236
+ def _(event):
237
+ event.app.exit(result=template_list[2])
238
+
239
+ @prompt.register_kb('4')
240
+ def _(event):
241
+ event.app.exit(result=template_list[3])
242
+
243
+ @prompt.register_kb('5')
244
+ def _(event):
245
+ event.app.exit(result=template_list[4])
246
+
247
+ template = prompt.execute()
248
+
249
+ # Handle user cancellation (Ctrl+C)
250
+ if template is None:
251
+ console.print('\n[red]✗[/red] Cancelled')
252
+ sys.exit(1)
253
+
254
+ # Template is guaranteed to be set at this point (either from option or prompt)
255
+ assert template is not None
256
+
257
+ # Create template directory
258
+ template_dir = Path.cwd() / template
259
+ if template_dir.exists() and not force:
260
+ console.print(f'[yellow]⚠[/yellow] Directory already exists: [cyan]{template_dir}[/cyan]')
261
+ if not click.confirm('Continue and overwrite files?', default=False):
262
+ console.print('[red]✗[/red] Cancelled')
263
+ sys.exit(1)
264
+
265
+ # Create directory
266
+ template_dir.mkdir(parents=True, exist_ok=True)
267
+
268
+ # Determine output path
269
+ if output:
270
+ output_path = template_dir / Path(output)
271
+ else:
272
+ output_path = template_dir / 'main.py'
273
+
274
+ # Read template file from GitHub
275
+ try:
276
+ template_file = INIT_TEMPLATES[template]['file']
277
+ content = _get_template_content(template_file)
278
+ except Exception as e:
279
+ console.print(f'[red]✗[/red] Error reading template: {e}')
280
+ sys.exit(1)
281
+
282
+ # Write file
283
+ if _write_init_file(output_path, content, force):
284
+ console.print(f'\n[green]✓[/green] Created [cyan]{output_path}[/cyan]')
285
+
286
+ # Generate additional files if template has a manifest
287
+ if 'files' in INIT_TEMPLATES[template]:
288
+ import stat
289
+
290
+ for file_spec in INIT_TEMPLATES[template]['files']:
291
+ source_path = file_spec['source']
292
+ dest_name = file_spec['dest']
293
+ dest_path = output_path.parent / dest_name
294
+ is_binary = file_spec.get('binary', False)
295
+ is_executable = file_spec.get('executable', False)
296
+
297
+ # Skip if we already wrote this file (main.py)
298
+ if dest_path == output_path:
299
+ continue
300
+
301
+ # Fetch and write file
302
+ try:
303
+ if is_binary:
304
+ file_content = _fetch_binary_from_github(source_path)
305
+ if file_content:
306
+ if not dest_path.exists() or force:
307
+ dest_path.write_bytes(file_content)
308
+ console.print(f'[green]✓[/green] Created [cyan]{dest_name}[/cyan]')
309
+ else:
310
+ console.print(f'[yellow]⚠[/yellow] Could not fetch [cyan]{dest_name}[/cyan] from GitHub')
311
+ else:
312
+ file_content = _get_template_content(source_path)
313
+ if _write_init_file(dest_path, file_content, force):
314
+ console.print(f'[green]✓[/green] Created [cyan]{dest_name}[/cyan]')
315
+ # Make executable if needed
316
+ if is_executable and sys.platform != 'win32':
317
+ dest_path.chmod(dest_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
318
+ except Exception as e:
319
+ console.print(f'[yellow]⚠[/yellow] Error generating [cyan]{dest_name}[/cyan]: {e}')
320
+
321
+ # Create a nice panel for next steps
322
+ next_steps = Text()
323
+
324
+ # Display next steps from manifest if available
325
+ if 'next_steps' in INIT_TEMPLATES[template]:
326
+ steps = INIT_TEMPLATES[template]['next_steps']
327
+ for i, step in enumerate(steps, 1):
328
+ # Handle footer separately (no numbering)
329
+ if 'footer' in step:
330
+ next_steps.append(f'{step["footer"]}\n', style='dim italic')
331
+ continue
332
+
333
+ # Step title
334
+ next_steps.append(f'\n{i}. {step["title"]}:\n', style='bold')
335
+
336
+ # Step commands
337
+ for cmd in step.get('commands', []):
338
+ # Replace placeholders
339
+ cmd = cmd.replace('{template}', template)
340
+ cmd = cmd.replace('{output}', output_path.name)
341
+ next_steps.append(f' {cmd}\n', style='dim')
342
+
343
+ # Optional note
344
+ if 'note' in step:
345
+ next_steps.append(f' {step["note"]}\n', style='dim italic')
346
+
347
+ next_steps.append('\n')
348
+ else:
349
+ # Default workflow for templates without custom next_steps
350
+ next_steps.append('\n1. Navigate to project directory:\n', style='bold')
351
+ next_steps.append(f' cd {template}\n\n', style='dim')
352
+ next_steps.append('2. Initialize uv project:\n', style='bold')
353
+ next_steps.append(' uv init\n\n', style='dim')
354
+ next_steps.append('3. Install browser-use:\n', style='bold')
355
+ next_steps.append(' uv add browser-use\n\n', style='dim')
356
+ next_steps.append('4. Set up your API key in .env file or environment:\n', style='bold')
357
+ next_steps.append(' BROWSER_USE_API_KEY=your-key\n', style='dim')
358
+ next_steps.append(
359
+ ' (Get your key at https://cloud.browser-use.com/dashboard/settings?tab=api-keys&new)\n\n',
360
+ style='dim italic',
361
+ )
362
+ next_steps.append('5. Run your script:\n', style='bold')
363
+ next_steps.append(f' uv run {output_path.name}\n', style='dim')
364
+
365
+ console.print(
366
+ Panel(
367
+ next_steps,
368
+ title='[bold]Next steps[/bold]',
369
+ border_style='#fe750e',
370
+ padding=(1, 2),
371
+ )
372
+ )
373
+
374
+
375
+ if __name__ == '__main__':
376
+ main()
@@ -0,0 +1,24 @@
1
+ """
2
+ Gmail Integration for Browser Use
3
+ Provides Gmail API integration for email reading and verification code extraction.
4
+ This integration enables agents to read email content and extract verification codes themselves.
5
+ Usage:
6
+ from browser_use.integrations.gmail import GmailService, register_gmail_actions
7
+ # Option 1: Register Gmail actions with file-based authentication
8
+ tools = Tools()
9
+ register_gmail_actions(tools)
10
+ # Option 2: Register Gmail actions with direct access token (recommended for production)
11
+ tools = Tools()
12
+ register_gmail_actions(tools, access_token="your_access_token_here")
13
+ # Option 3: Use the service directly
14
+ gmail = GmailService(access_token="your_access_token_here")
15
+ await gmail.authenticate()
16
+ emails = await gmail.get_recent_emails()
17
+ """
18
+
19
+ # @file purpose: Gmail integration for 2FA email authentication and email reading
20
+
21
+ from .actions import register_gmail_actions
22
+ from .service import GmailService
23
+
24
+ __all__ = ['GmailService', 'register_gmail_actions']
@@ -0,0 +1,115 @@
1
+ """
2
+ Gmail Actions for Browser Use
3
+ Defines agent actions for Gmail integration including 2FA code retrieval,
4
+ email reading, and authentication management.
5
+ """
6
+
7
+ import logging
8
+
9
+ from pydantic import BaseModel, Field
10
+
11
+ from browser_use.agent.views import ActionResult
12
+ from browser_use.tools.service import Tools
13
+
14
+ from .service import GmailService
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # Global Gmail service instance - initialized when actions are registered
19
+ _gmail_service: GmailService | None = None
20
+
21
+
22
+ class GetRecentEmailsParams(BaseModel):
23
+ """Parameters for getting recent emails"""
24
+
25
+ keyword: str = Field(default='', description='A single keyword for search, e.g. github, airbnb, etc.')
26
+ max_results: int = Field(default=3, ge=1, le=50, description='Maximum number of emails to retrieve (1-50, default: 3)')
27
+
28
+
29
+ def register_gmail_actions(tools: Tools, gmail_service: GmailService | None = None, access_token: str | None = None) -> Tools:
30
+ """
31
+ Register Gmail actions with the provided tools
32
+ Args:
33
+ tools: The browser-use tools to register actions with
34
+ gmail_service: Optional pre-configured Gmail service instance
35
+ access_token: Optional direct access token (alternative to file-based auth)
36
+ """
37
+ global _gmail_service
38
+
39
+ # Use provided service or create a new one with access token if provided
40
+ if gmail_service:
41
+ _gmail_service = gmail_service
42
+ elif access_token:
43
+ _gmail_service = GmailService(access_token=access_token)
44
+ else:
45
+ _gmail_service = GmailService()
46
+
47
+ @tools.registry.action(
48
+ description='Get recent emails from the mailbox with a keyword to retrieve verification codes, OTP, 2FA tokens, magic links, or any recent email content. Keep your query a single keyword.',
49
+ param_model=GetRecentEmailsParams,
50
+ )
51
+ async def get_recent_emails(params: GetRecentEmailsParams) -> ActionResult:
52
+ """Get recent emails from the last 5 minutes with full content"""
53
+ try:
54
+ if _gmail_service is None:
55
+ raise RuntimeError('Gmail service not initialized')
56
+
57
+ # Ensure authentication
58
+ if not _gmail_service.is_authenticated():
59
+ logger.info('📧 Gmail not authenticated, attempting authentication...')
60
+ authenticated = await _gmail_service.authenticate()
61
+ if not authenticated:
62
+ return ActionResult(
63
+ extracted_content='Failed to authenticate with Gmail. Please ensure Gmail credentials are set up properly.',
64
+ long_term_memory='Gmail authentication failed',
65
+ )
66
+
67
+ # Use specified max_results (1-50, default 10), last 5 minutes
68
+ max_results = params.max_results
69
+ time_filter = '5m'
70
+
71
+ # Build query with time filter and optional user query
72
+ query_parts = [f'newer_than:{time_filter}']
73
+ if params.keyword.strip():
74
+ query_parts.append(params.keyword.strip())
75
+
76
+ query = ' '.join(query_parts)
77
+ logger.info(f'🔍 Gmail search query: {query}')
78
+
79
+ # Get emails
80
+ emails = await _gmail_service.get_recent_emails(max_results=max_results, query=query, time_filter=time_filter)
81
+
82
+ if not emails:
83
+ query_info = f" matching '{params.keyword}'" if params.keyword.strip() else ''
84
+ memory = f'No recent emails found from last {time_filter}{query_info}'
85
+ return ActionResult(
86
+ extracted_content=memory,
87
+ long_term_memory=memory,
88
+ )
89
+
90
+ # Format with full email content for large display
91
+ content = f'Found {len(emails)} recent email{"s" if len(emails) > 1 else ""} from the last {time_filter}:\n\n'
92
+
93
+ for i, email in enumerate(emails, 1):
94
+ content += f'Email {i}:\n'
95
+ content += f'From: {email["from"]}\n'
96
+ content += f'Subject: {email["subject"]}\n'
97
+ content += f'Date: {email["date"]}\n'
98
+ content += f'Content:\n{email["body"]}\n'
99
+ content += '-' * 50 + '\n\n'
100
+
101
+ logger.info(f'📧 Retrieved {len(emails)} recent emails')
102
+ return ActionResult(
103
+ extracted_content=content,
104
+ include_extracted_content_only_once=True,
105
+ long_term_memory=f'Retrieved {len(emails)} recent emails from last {time_filter} for query {query}.',
106
+ )
107
+
108
+ except Exception as e:
109
+ logger.error(f'Error getting recent emails: {e}')
110
+ return ActionResult(
111
+ error=f'Error getting recent emails: {str(e)}',
112
+ long_term_memory='Failed to get recent emails due to error',
113
+ )
114
+
115
+ return tools