chore: import upstream snapshot with attribution
Test Browser Use CLI Install / uv pip install (ubuntu-latest) (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use from local wheel (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use[cli] from PyPI (push) Failing after 1s
package / pip-install-on-macos-latest-py-3.11 (push) Has been skipped
package / pip-install-on-macos-latest-py-3.13 (push) Has been skipped
package / pip-install-on-ubuntu-latest-py-3.11 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.13 (push) Has been skipped
cloud_evals / trigger_cloud_eval_image_build (push) Failing after 1s
docker / build_publish_image (push) Failing after 1s
Test Browser Use CLI Install / browser-use skill sync (push) Failing after 1s
lint / code-style (push) Failing after 0s
lint / type-checker (push) Failing after 1s
package / pip-build (push) Failing after 1s
lint / syntax-errors (push) Failing after 3s
package / pip-install-on-ubuntu-latest-py-3.13 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.11 (push) Has been skipped
test / ${{ matrix.test_filename }} (push) Has been skipped
test / evaluate-tasks (push) Has been skipped
test / setup-chromium (push) Failing after 2s
test / find_tests (push) Failing after 2s
Test Browser Use CLI Install / uv pip install (windows-latest) (push) Has been cancelled
Test Browser Use CLI Install / uv pip install (macos-latest) (push) Has been cancelled
Test Browser Use CLI Install / uv pip install (ubuntu-latest) (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use from local wheel (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use[cli] from PyPI (push) Failing after 1s
package / pip-install-on-macos-latest-py-3.11 (push) Has been skipped
package / pip-install-on-macos-latest-py-3.13 (push) Has been skipped
package / pip-install-on-ubuntu-latest-py-3.11 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.13 (push) Has been skipped
cloud_evals / trigger_cloud_eval_image_build (push) Failing after 1s
docker / build_publish_image (push) Failing after 1s
Test Browser Use CLI Install / browser-use skill sync (push) Failing after 1s
lint / code-style (push) Failing after 0s
lint / type-checker (push) Failing after 1s
package / pip-build (push) Failing after 1s
lint / syntax-errors (push) Failing after 3s
package / pip-install-on-ubuntu-latest-py-3.13 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.11 (push) Has been skipped
test / ${{ matrix.test_filename }} (push) Has been skipped
test / evaluate-tasks (push) Has been skipped
test / setup-chromium (push) Failing after 2s
test / find_tests (push) Failing after 2s
Test Browser Use CLI Install / uv pip install (windows-latest) (push) Has been cancelled
Test Browser Use CLI Install / uv pip install (macos-latest) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
|
||||
from agentmail import AsyncAgentMail # type: ignore
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
from browser_use import Agent, Browser, ChatBrowserUse
|
||||
from examples.integrations.agentmail.email_tools import EmailTools
|
||||
|
||||
TASK = """
|
||||
Go to reddit.com, create a new account (use the get_email_address), make up password and all other information, confirm the 2fa with get_latest_email, and like latest post on r/elon subreddit.
|
||||
"""
|
||||
|
||||
|
||||
async def main():
|
||||
# Create email inbox
|
||||
# Get an API key from https://agentmail.to/
|
||||
email_client = AsyncAgentMail()
|
||||
inbox = await email_client.inboxes.create()
|
||||
print(f'Your email address is: {inbox.inbox_id}\n\n')
|
||||
|
||||
# Initialize the tools for browser-use agent
|
||||
tools = EmailTools(email_client=email_client, inbox=inbox)
|
||||
|
||||
# Initialize the LLM for browser-use agent
|
||||
llm = ChatBrowserUse(model='bu-2-0')
|
||||
|
||||
# Set your local browser path
|
||||
browser = Browser(executable_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome')
|
||||
|
||||
agent = Agent(task=TASK, tools=tools, llm=llm, browser=browser)
|
||||
|
||||
await agent.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
Email management to enable 2fa.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
# run `pip install agentmail` to install the library
|
||||
from agentmail import AsyncAgentMail, Message, MessageReceivedEvent, Subscribe # type: ignore
|
||||
from agentmail.inboxes.types.inbox import Inbox # type: ignore
|
||||
from agentmail.inboxes.types.inbox_id import InboxId # type: ignore
|
||||
|
||||
from browser_use import Tools
|
||||
|
||||
# Configure basic logging if not already configured
|
||||
if not logging.getLogger().handlers:
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(name)s - %(message)s')
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EmailTools(Tools):
|
||||
def __init__(
|
||||
self,
|
||||
email_client: AsyncAgentMail | None = None,
|
||||
email_timeout: int = 30,
|
||||
inbox: Inbox | None = None,
|
||||
):
|
||||
super().__init__()
|
||||
self.email_client = email_client or AsyncAgentMail()
|
||||
|
||||
self.email_timeout = email_timeout
|
||||
|
||||
self.register_email_tools()
|
||||
|
||||
self.inbox: Inbox | None = inbox
|
||||
|
||||
def _serialize_message_for_llm(self, message: Message) -> str:
|
||||
"""
|
||||
Serialize a message for the LLM
|
||||
"""
|
||||
# Use text if available, otherwise convert HTML to simple text
|
||||
body_content = message.text
|
||||
if not body_content and message.html:
|
||||
body_content = self._html_to_text(message.html)
|
||||
|
||||
msg = f'From: {message.from_}\nTo: {message.to}\nTimestamp: {message.timestamp.isoformat()}\nSubject: {message.subject}\nBody: {body_content}'
|
||||
return msg
|
||||
|
||||
def _html_to_text(self, html: str) -> str:
|
||||
"""
|
||||
Simple HTML to text conversion
|
||||
"""
|
||||
import re
|
||||
|
||||
# Remove script and style elements - handle spaces in closing tags
|
||||
html = re.sub(r'<script\b[^>]*>.*?</script\s*>', '', html, flags=re.DOTALL | re.IGNORECASE)
|
||||
html = re.sub(r'<style\b[^>]*>.*?</style\s*>', '', html, flags=re.DOTALL | re.IGNORECASE)
|
||||
|
||||
# Remove HTML tags
|
||||
html = re.sub(r'<[^>]+>', '', html)
|
||||
|
||||
# Decode HTML entities
|
||||
html = html.replace(' ', ' ')
|
||||
html = html.replace('&', '&')
|
||||
html = html.replace('<', '<')
|
||||
html = html.replace('>', '>')
|
||||
html = html.replace('"', '"')
|
||||
html = html.replace(''', "'")
|
||||
|
||||
# Clean up whitespace
|
||||
html = re.sub(r'\s+', ' ', html)
|
||||
html = html.strip()
|
||||
|
||||
return html
|
||||
|
||||
async def get_or_create_inbox_client(self) -> Inbox:
|
||||
"""
|
||||
Create a default inbox profile for this API key (assume that agent is on free tier)
|
||||
|
||||
If you are not on free tier it is recommended to create 1 inbox per agent.
|
||||
"""
|
||||
if self.inbox:
|
||||
return self.inbox
|
||||
|
||||
return await self.create_inbox_client()
|
||||
|
||||
async def create_inbox_client(self) -> Inbox:
|
||||
"""
|
||||
Create a default inbox profile for this API key (assume that agent is on free tier)
|
||||
|
||||
If you are not on free tier it is recommended to create 1 inbox per agent.
|
||||
"""
|
||||
inbox = await self.email_client.inboxes.create()
|
||||
self.inbox = inbox
|
||||
return inbox
|
||||
|
||||
async def wait_for_message(self, inbox_id: InboxId) -> Message:
|
||||
"""
|
||||
Wait for a message to be received in the inbox
|
||||
"""
|
||||
async with self.email_client.websockets.connect() as ws:
|
||||
await ws.send_subscribe(message=Subscribe(inbox_ids=[inbox_id]))
|
||||
|
||||
try:
|
||||
while True:
|
||||
data = await asyncio.wait_for(ws.recv(), timeout=self.email_timeout)
|
||||
if isinstance(data, MessageReceivedEvent):
|
||||
await self.email_client.inboxes.messages.update(
|
||||
inbox_id=inbox_id, message_id=data.message.message_id, remove_labels=['unread']
|
||||
)
|
||||
msg = data.message
|
||||
logger.info(f'Received new message from: {msg.from_} with subject: {msg.subject}')
|
||||
return msg
|
||||
# If not MessageReceived, continue waiting for the next event
|
||||
except TimeoutError:
|
||||
raise TimeoutError(f'No email received in the inbox in {self.email_timeout}s')
|
||||
|
||||
def register_email_tools(self):
|
||||
"""Register all email-related controller actions"""
|
||||
|
||||
@self.action('Get email address for login. You can use this email to login to any service with email and password')
|
||||
async def get_email_address() -> str:
|
||||
"""
|
||||
Get the email address of the inbox
|
||||
"""
|
||||
inbox = await self.get_or_create_inbox_client()
|
||||
logger.info(f'Email address: {inbox.inbox_id}')
|
||||
return inbox.inbox_id
|
||||
|
||||
@self.action(
|
||||
'Get the latest unread email from the inbox from the last max_age_minutes (default 5 minutes). Waits some seconds for new emails if none found. Use for 2FA codes.'
|
||||
)
|
||||
async def get_latest_email(max_age_minutes: int = 5) -> str:
|
||||
"""
|
||||
1. Check for unread emails within the last max_age_minutes
|
||||
2. If no recent unread email, wait 30 seconds for new email via websocket
|
||||
"""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
inbox = await self.get_or_create_inbox_client()
|
||||
|
||||
# Get unread emails
|
||||
emails = await self.email_client.inboxes.messages.list(inbox_id=inbox.inbox_id, labels=['unread'])
|
||||
# Filter unread emails by time window - use UTC timezone to match email timestamps
|
||||
time_cutoff = datetime.now(timezone.utc) - timedelta(minutes=max_age_minutes)
|
||||
logger.debug(f'Time cutoff: {time_cutoff}')
|
||||
logger.info(f'Found {len(emails.messages)} unread emails for inbox {inbox.inbox_id}')
|
||||
recent_unread_emails = []
|
||||
|
||||
for i, email_summary in enumerate(emails.messages):
|
||||
# Get full email details to check timestamp
|
||||
full_email = await self.email_client.inboxes.messages.get(
|
||||
inbox_id=inbox.inbox_id, message_id=email_summary.message_id
|
||||
)
|
||||
# Handle timezone comparison properly
|
||||
email_timestamp = full_email.timestamp
|
||||
if email_timestamp.tzinfo is None:
|
||||
# If email timestamp is naive, assume UTC
|
||||
email_timestamp = email_timestamp.replace(tzinfo=timezone.utc)
|
||||
|
||||
if email_timestamp >= time_cutoff:
|
||||
recent_unread_emails.append(full_email)
|
||||
|
||||
# If we have recent unread emails, return the latest one
|
||||
if recent_unread_emails:
|
||||
# Sort by timestamp and get the most recent
|
||||
recent_unread_emails.sort(key=lambda x: x.timestamp, reverse=True)
|
||||
logger.info(f'Found {len(recent_unread_emails)} recent unread emails for inbox {inbox.inbox_id}')
|
||||
|
||||
latest_email = recent_unread_emails[0]
|
||||
|
||||
# Mark as read
|
||||
await self.email_client.inboxes.messages.update(
|
||||
inbox_id=inbox.inbox_id, message_id=latest_email.message_id, remove_labels=['unread']
|
||||
)
|
||||
logger.info(f'Latest email from: {latest_email.from_} with subject: {latest_email.subject}')
|
||||
return self._serialize_message_for_llm(latest_email)
|
||||
else:
|
||||
logger.info('No recent unread emails, waiting for a new one')
|
||||
# No recent unread emails, wait for new one
|
||||
try:
|
||||
latest_message = await self.wait_for_message(inbox_id=inbox.inbox_id)
|
||||
except TimeoutError:
|
||||
return f'No email received in the inbox in {self.email_timeout}s'
|
||||
# logger.info(f'Latest message: {latest_message}')
|
||||
return self._serialize_message_for_llm(latest_message)
|
||||
Reference in New Issue
Block a user