-
Notifications
You must be signed in to change notification settings - Fork 0
feat: added cloud_browser feature #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: eval-pr-3139-target-1762452059715
Are you sure you want to change the base?
Changes from all commits
85a2386
7e409e2
f208e9e
348ca05
e0eb26e
52ed7e2
0a13273
c1381b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -15,20 +15,20 @@ | |||||||||||||||||
| [](https://app.workweave.ai/reports/repository/org_T5Pvn3UBswTHIsN1dWS3voPg/881458615) | ||||||||||||||||||
|
|
||||||||||||||||||
| <!-- Keep these links. Translations will automatically update with the README. --> | ||||||||||||||||||
| [Deutsch](https://www.readme-i18n.com/browser-use/browser-use?lang=de) | | ||||||||||||||||||
| [Español](https://www.readme-i18n.com/browser-use/browser-use?lang=es) | | ||||||||||||||||||
| [français](https://www.readme-i18n.com/browser-use/browser-use?lang=fr) | | ||||||||||||||||||
| [日本語](https://www.readme-i18n.com/browser-use/browser-use?lang=ja) | | ||||||||||||||||||
| [한국어](https://www.readme-i18n.com/browser-use/browser-use?lang=ko) | | ||||||||||||||||||
| [Português](https://www.readme-i18n.com/browser-use/browser-use?lang=pt) | | ||||||||||||||||||
| [Русский](https://www.readme-i18n.com/browser-use/browser-use?lang=ru) | | ||||||||||||||||||
| [Deutsch](https://www.readme-i18n.com/browser-use/browser-use?lang=de) | | ||||||||||||||||||
| [Español](https://www.readme-i18n.com/browser-use/browser-use?lang=es) | | ||||||||||||||||||
| [français](https://www.readme-i18n.com/browser-use/browser-use?lang=fr) | | ||||||||||||||||||
| [日本語](https://www.readme-i18n.com/browser-use/browser-use?lang=ja) | | ||||||||||||||||||
| [한국어](https://www.readme-i18n.com/browser-use/browser-use?lang=ko) | | ||||||||||||||||||
| [Português](https://www.readme-i18n.com/browser-use/browser-use?lang=pt) | | ||||||||||||||||||
| [Русский](https://www.readme-i18n.com/browser-use/browser-use?lang=ru) | | ||||||||||||||||||
| [中文](https://www.readme-i18n.com/browser-use/browser-use?lang=zh) | ||||||||||||||||||
|
|
||||||||||||||||||
| 🌤️ Want to skip the setup? Use our <b>[cloud](https://cloud.browser-use.com)</b> for faster, scalable, stealth-enabled browser automation! | ||||||||||||||||||
|
|
||||||||||||||||||
| **🚀 Use the latest version!** | ||||||||||||||||||
| **🚀 Use the latest version!** | ||||||||||||||||||
|
|
||||||||||||||||||
| > We ship every day improvements for **speed**, **accuracy**, and **UX**. | ||||||||||||||||||
| > We ship every day improvements for **speed**, **accuracy**, and **UX**. | ||||||||||||||||||
| > ```bash | ||||||||||||||||||
| > uv pip install --upgrade browser-use | ||||||||||||||||||
| > ``` | ||||||||||||||||||
|
|
@@ -74,6 +74,22 @@ OPENAI_API_KEY= | |||||||||||||||||
|
|
||||||||||||||||||
| For other settings, models, and more, check out the [documentation 📕](https://docs.browser-use.com). | ||||||||||||||||||
|
|
||||||||||||||||||
| **🌤️ Want to use cloud browsers?** Simply add `cloud_browser=True` to your Browser config: | ||||||||||||||||||
|
|
||||||||||||||||||
| ```python | ||||||||||||||||||
| from browser_use import Agent, Browser, ChatOpenAI | ||||||||||||||||||
|
|
||||||||||||||||||
| agent = Agent( | ||||||||||||||||||
| task="Find the number of stars of the browser-use repo", | ||||||||||||||||||
| llm=ChatOpenAI(model="gpt-4.1-mini"), | ||||||||||||||||||
| browser=Browser(cloud_browser=True), # Uses Browser-Use cloud service | ||||||||||||||||||
| ) | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| First Set BROWSER_USE_API_KEY environment variable. You can get your API key from [here](https://cloud.browser-use.com). | ||||||||||||||||||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new sentence has a grammatical error ( Prompt for AI agents[internal] Confidence score: 8/10 [internal] Posted by: General AI Review Agent
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new sentence has incorrect capitalization and is missing an article, which makes the documentation harder to read. Please rewrite it for proper grammar. Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: General AI Review Agent
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Grammar issue: "Set" should be lowercase "set". Consider rephrasing to "First, set the BROWSER_USE_API_KEY environment variable." Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: General AI Review Agent
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Incorrect capitalization: 'Set' should be lowercase in 'First set' Prompt for AI agents[internal] Confidence score: 10/10 [internal] Posted by: General AI Review Agent Fix confidence: 10/10
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| For other settings, models, and more, check out the [Cloud documentation 📕](https://docs.cloud.browser-use.com). | ||||||||||||||||||
|
|
||||||||||||||||||
| # Demos | ||||||||||||||||||
|
|
||||||||||||||||||
| <br/><br/> | ||||||||||||||||||
|
|
@@ -254,11 +270,11 @@ If you use Browser Use in your research or project, please cite: | |||||||||||||||||
| } | ||||||||||||||||||
| ``` | ||||||||||||||||||
|
|
||||||||||||||||||
| <div align="center"> <img src="https://github.com/user-attachments/assets/06fa3078-8461-4560-b434-445510c1766f" width="400"/> | ||||||||||||||||||
| <div align="center"> <img src="https://github.com/user-attachments/assets/06fa3078-8461-4560-b434-445510c1766f" width="400"/> | ||||||||||||||||||
|
|
||||||||||||||||||
| [](https://x.com/intent/user?screen_name=gregpr07) | ||||||||||||||||||
| [](https://x.com/intent/user?screen_name=mamagnus00) | ||||||||||||||||||
|
|
||||||||||||||||||
| </div> | ||||||||||||||||||
|
|
||||||||||||||||||
| <div align="center"> | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,286 @@ | ||||||||||||||||||
| """Cloud browser service integration for browser-use. | ||||||||||||||||||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: [Orchestrator] Concurrent cloud browser sessions cannot be tracked independently. The global Prompt for AI agents[internal] Confidence score: 8/10 [internal] Posted by: Orchestrator AI Review Agent |
||||||||||||||||||
|
|
||||||||||||||||||
| This module provides integration with the browser-use cloud browser service. | ||||||||||||||||||
| When cloud_browser=True, it automatically creates a cloud browser instance | ||||||||||||||||||
| and returns the CDP URL for connection. | ||||||||||||||||||
| """ | ||||||||||||||||||
|
|
||||||||||||||||||
| import logging | ||||||||||||||||||
| import os | ||||||||||||||||||
|
|
||||||||||||||||||
| import httpx | ||||||||||||||||||
| from pydantic import BaseModel, Field | ||||||||||||||||||
|
|
||||||||||||||||||
| from browser_use.sync.auth import CloudAuthConfig | ||||||||||||||||||
|
|
||||||||||||||||||
| logger = logging.getLogger(__name__) | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class CloudBrowserResponse(BaseModel): | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: CloudBrowserResponse lacks Reasoning: Prompt for AI agents[internal] Confidence score: 7/10 [internal] Posted by: General AI Review Agent |
||||||||||||||||||
| """Response from cloud browser API.""" | ||||||||||||||||||
|
|
||||||||||||||||||
| id: str | ||||||||||||||||||
| status: str | ||||||||||||||||||
| liveUrl: str = Field(alias='liveUrl') | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Redundant Field aliases where alias equals field name. Pydantic uses field names by default, so DEV MODE: This violation would have been filtered out by screening filters. Failing filters: functionalImpact. Reasoning: Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: General AI Review Agent |
||||||||||||||||||
| cdpUrl: str = Field(alias='cdpUrl') | ||||||||||||||||||
| timeoutAt: str = Field(alias='timeoutAt') | ||||||||||||||||||
| startedAt: str = Field(alias='startedAt') | ||||||||||||||||||
| finishedAt: str | None = Field(alias='finishedAt', default=None) | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class CloudBrowserError(Exception): | ||||||||||||||||||
| """Exception raised when cloud browser operations fail.""" | ||||||||||||||||||
|
|
||||||||||||||||||
| pass | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class CloudBrowserAuthError(CloudBrowserError): | ||||||||||||||||||
| """Exception raised when cloud browser authentication fails.""" | ||||||||||||||||||
|
|
||||||||||||||||||
| pass | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class CloudBrowserClient: | ||||||||||||||||||
| """Client for browser-use cloud browser service.""" | ||||||||||||||||||
|
|
||||||||||||||||||
| def __init__(self, api_base_url: str = 'https://api.browser-use.com'): | ||||||||||||||||||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hard-coding the cloud API base URL here ignores the existing Prompt for AI agents[internal] Confidence score: 8/10 [internal] Posted by: General AI Review Agent |
||||||||||||||||||
| self.api_base_url = api_base_url | ||||||||||||||||||
| self.client = httpx.AsyncClient(timeout=30.0) | ||||||||||||||||||
| self.current_session_id: str | None = None | ||||||||||||||||||
|
|
||||||||||||||||||
| async def create_browser(self) -> CloudBrowserResponse: | ||||||||||||||||||
| """Create a new cloud browser instance. | ||||||||||||||||||
|
|
||||||||||||||||||
| Returns: | ||||||||||||||||||
| CloudBrowserResponse: Contains CDP URL and other browser info | ||||||||||||||||||
|
|
||||||||||||||||||
| Raises: | ||||||||||||||||||
| CloudBrowserAuthError: If authentication fails | ||||||||||||||||||
| CloudBrowserError: If browser creation fails | ||||||||||||||||||
| """ | ||||||||||||||||||
| url = f'{self.api_base_url}/api/v2/browsers' | ||||||||||||||||||
|
|
||||||||||||||||||
| # Try to get API key from environment variable first, then auth config | ||||||||||||||||||
| api_token = os.getenv('BROWSER_USE_API_KEY') | ||||||||||||||||||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. API token retrieval logic duplicates within CloudBrowserClient. This authentication pattern should be centralized into a single helper method (e.g., Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: Duplicate Detection Agent There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method for managing the cloud browser API key is inconsistent with the established configuration pattern for other credentials in the project. This new feature introduces a separate configuration file ( DEV MODE: This violation would have been filtered out by screening filters. Failing filters: functionalImpact. Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: System Design Agent
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. API token retrieval logic is duplicated within the Prompt for AI agents[internal] Confidence score: 10/10 [internal] Posted by: Duplicate Detection Agent
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. API token retrieval logic duplicates itself within the CloudBrowserClient class in browser_use/browser/cloud.py. This critical authentication pattern should be centralized into a shared helper method. Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: Duplicate Detection Agent
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Authentication token retrieval logic is duplicated within Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: Duplicate Detection Agent
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. API token retrieval logic duplicates in Prompt for AI agents[internal] Confidence score: 10/10 [internal] Posted by: Duplicate Detection Agent
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Authentication token retrieval logic is duplicated within CloudBrowserClient.create_browser() and CloudBrowserClient.stop_browser(). This logic should be extracted into a shared helper method. Prompt for AI agents[internal] Confidence score: 9.5/10 [internal] Posted by: Duplicate Detection Agent
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. API token retrieval logic is duplicated in Reasoning: Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: Duplicate Detection Agent |
||||||||||||||||||
|
|
||||||||||||||||||
| if not api_token: | ||||||||||||||||||
| # Fallback to auth config file | ||||||||||||||||||
| try: | ||||||||||||||||||
| auth_config = CloudAuthConfig.load_from_file() | ||||||||||||||||||
| api_token = auth_config.api_token | ||||||||||||||||||
| except Exception: | ||||||||||||||||||
| pass | ||||||||||||||||||
|
|
||||||||||||||||||
| if not api_token: | ||||||||||||||||||
| raise CloudBrowserAuthError( | ||||||||||||||||||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. API token retrieval and validation logic is duplicated between Prompt for AI agents[internal] Confidence score: 10/10 [internal] Posted by: Duplicate Detection Agent |
||||||||||||||||||
| 'No authentication token found. Please set BROWSER_USE_API_KEY environment variable to authenticate with the cloud service.' | ||||||||||||||||||
| ) | ||||||||||||||||||
|
|
||||||||||||||||||
| headers = {'X-Browser-Use-API-Key': api_token, 'Content-Type': 'application/json'} | ||||||||||||||||||
|
|
||||||||||||||||||
| # Empty request body as per API specification | ||||||||||||||||||
| request_body = {} | ||||||||||||||||||
|
|
||||||||||||||||||
| try: | ||||||||||||||||||
| logger.info('🌤️ Creating cloud browser instance...') | ||||||||||||||||||
|
|
||||||||||||||||||
| response = await self.client.post(url, headers=headers, json=request_body) | ||||||||||||||||||
|
|
||||||||||||||||||
| if response.status_code == 401: | ||||||||||||||||||
| raise CloudBrowserAuthError( | ||||||||||||||||||
| 'Authentication failed. Please make sure you have set BROWSER_USE_API_KEY environment variable to authenticate with the cloud service.' | ||||||||||||||||||
| ) | ||||||||||||||||||
| elif response.status_code == 403: | ||||||||||||||||||
| raise CloudBrowserAuthError('Access forbidden. Please check your browser-use cloud subscription status.') | ||||||||||||||||||
| elif not response.is_success: | ||||||||||||||||||
| error_msg = f'Failed to create cloud browser: HTTP {response.status_code}' | ||||||||||||||||||
| try: | ||||||||||||||||||
| error_data = response.json() | ||||||||||||||||||
| if 'detail' in error_data: | ||||||||||||||||||
| error_msg += f' - {error_data["detail"]}' | ||||||||||||||||||
| except Exception: | ||||||||||||||||||
| pass | ||||||||||||||||||
| raise CloudBrowserError(error_msg) | ||||||||||||||||||
|
|
||||||||||||||||||
| browser_data = response.json() | ||||||||||||||||||
| browser_response = CloudBrowserResponse(**browser_data) | ||||||||||||||||||
|
|
||||||||||||||||||
| # Store session ID for cleanup | ||||||||||||||||||
| self.current_session_id = browser_response.id | ||||||||||||||||||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Storing the cloud session id on the shared client means only the newest session is tracked. If two BrowserSession instances start cloud browsers, the second call overwrites Prompt for AI agents[internal] Confidence score: 8/10 [internal] Posted by: General AI Review Agent |
||||||||||||||||||
|
|
||||||||||||||||||
| logger.info(f'🌤️ Cloud browser created successfully: {browser_response.id}') | ||||||||||||||||||
| logger.debug(f'🌤️ CDP URL: {browser_response.cdpUrl}') | ||||||||||||||||||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sensitive data exposure: cdpUrl logged at debug allows session hijacking via logs. Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: Security Vulnerabilities Agent
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Logging the CDP URL exposes the authenticated remote debugging endpoint for the live cloud browser session; anyone with log access could hijack the browser. Please drop or redact this log line so the URL never leaves process memory. Prompt for AI agents[internal] Confidence score: 7/10 [internal] Posted by: General AI Review Agent
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sensitive data exposure: CDP URL is logged, enabling unauthorized remote control if logs are accessed. Prompt for AI agents[internal] Confidence score: 9/10 [internal] Posted by: Security Vulnerabilities Agent
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid logging the CDP endpoint; it exposes the remote-debugging URL and would let anyone with log access hijack the session. Prompt for AI agents[internal] Confidence score: 8/10 [internal] Posted by: General AI Review Agent
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: [Orchestrator] Sensitive CDP and Live URLs containing session identifiers are logged at DEBUG level. These URLs effectively act as credentials for accessing cloud browser sessions. Consider logging only a masked portion (e.g., session ID prefix) or removing these debug statements to prevent potential session hijacking via log exposure. Prompt for AI agents[internal] Confidence score: 8/10 [internal] Posted by: Orchestrator AI Review Agent |
||||||||||||||||||
| logger.debug(f'🌤️ Live URL: {browser_response.liveUrl}') | ||||||||||||||||||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid logging the live-view URL; publishing it to logs shares a sensitive link that grants access to the running cloud browser session. Prompt for AI agents[internal] Confidence score: 8/10 [internal] Posted by: General AI Review Agent
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| return browser_response | ||||||||||||||||||
|
|
||||||||||||||||||
| except httpx.TimeoutException: | ||||||||||||||||||
| raise CloudBrowserError('Timeout while creating cloud browser. Please try again.') | ||||||||||||||||||
| except httpx.ConnectError: | ||||||||||||||||||
| raise CloudBrowserError('Failed to connect to cloud browser service. Please check your internet connection.') | ||||||||||||||||||
| except Exception as e: | ||||||||||||||||||
| if isinstance(e, (CloudBrowserError, CloudBrowserAuthError)): | ||||||||||||||||||
| raise | ||||||||||||||||||
| raise CloudBrowserError(f'Unexpected error creating cloud browser: {e}') | ||||||||||||||||||
|
|
||||||||||||||||||
| async def stop_browser(self, session_id: str | None = None) -> CloudBrowserResponse: | ||||||||||||||||||
| """Stop a cloud browser session. | ||||||||||||||||||
|
|
||||||||||||||||||
| Args: | ||||||||||||||||||
| session_id: Session ID to stop. If None, uses current session. | ||||||||||||||||||
|
|
||||||||||||||||||
| Returns: | ||||||||||||||||||
| CloudBrowserResponse: Updated browser info with stopped status | ||||||||||||||||||
|
|
||||||||||||||||||
| Raises: | ||||||||||||||||||
| CloudBrowserAuthError: If authentication fails | ||||||||||||||||||
| CloudBrowserError: If stopping fails | ||||||||||||||||||
| """ | ||||||||||||||||||
| if session_id is None: | ||||||||||||||||||
| session_id = self.current_session_id | ||||||||||||||||||
|
|
||||||||||||||||||
| if not session_id: | ||||||||||||||||||
| raise CloudBrowserError('No session ID provided and no current session available') | ||||||||||||||||||
|
|
||||||||||||||||||
| url = f'{self.api_base_url}/api/v2/browsers/{session_id}' | ||||||||||||||||||
|
|
||||||||||||||||||
| # Try to get API key from environment variable first, then auth config | ||||||||||||||||||
| api_token = os.getenv('BROWSER_USE_API_KEY') | ||||||||||||||||||
|
|
||||||||||||||||||
| if not api_token: | ||||||||||||||||||
| # Fallback to auth config file | ||||||||||||||||||
| try: | ||||||||||||||||||
| auth_config = CloudAuthConfig.load_from_file() | ||||||||||||||||||
| api_token = auth_config.api_token | ||||||||||||||||||
| except Exception: | ||||||||||||||||||
| pass | ||||||||||||||||||
|
|
||||||||||||||||||
| if not api_token: | ||||||||||||||||||
| raise CloudBrowserAuthError( | ||||||||||||||||||
| 'No authentication token found. Please set BROWSER_USE_API_KEY environment variable to authenticate with the cloud service.' | ||||||||||||||||||
| ) | ||||||||||||||||||
|
|
||||||||||||||||||
| headers = {'X-Browser-Use-API-Key': api_token, 'Content-Type': 'application/json'} | ||||||||||||||||||
|
|
||||||||||||||||||
| request_body = {'action': 'stop'} | ||||||||||||||||||
|
|
||||||||||||||||||
| try: | ||||||||||||||||||
| logger.info(f'🌤️ Stopping cloud browser session: {session_id}') | ||||||||||||||||||
|
|
||||||||||||||||||
| response = await self.client.patch(url, headers=headers, json=request_body) | ||||||||||||||||||
|
|
||||||||||||||||||
| if response.status_code == 401: | ||||||||||||||||||
| raise CloudBrowserAuthError( | ||||||||||||||||||
| 'Authentication failed. Please make sure you have set the BROWSER_USE_API_KEY environment variable to authenticate with the cloud service.' | ||||||||||||||||||
| ) | ||||||||||||||||||
| elif response.status_code == 404: | ||||||||||||||||||
| # Session already stopped or doesn't exist - treating as error and clearing session | ||||||||||||||||||
| logger.debug(f'🌤️ Cloud browser session {session_id} not found (already stopped)') | ||||||||||||||||||
| # Clear current session if it was this one | ||||||||||||||||||
| if session_id == self.current_session_id: | ||||||||||||||||||
| self.current_session_id = None | ||||||||||||||||||
| raise CloudBrowserError(f'Cloud browser session {session_id} not found') | ||||||||||||||||||
| elif not response.is_success: | ||||||||||||||||||
| error_msg = f'Failed to stop cloud browser: HTTP {response.status_code}' | ||||||||||||||||||
| try: | ||||||||||||||||||
| error_data = response.json() | ||||||||||||||||||
| if 'detail' in error_data: | ||||||||||||||||||
| error_msg += f' - {error_data["detail"]}' | ||||||||||||||||||
| except Exception: | ||||||||||||||||||
| pass | ||||||||||||||||||
| raise CloudBrowserError(error_msg) | ||||||||||||||||||
|
|
||||||||||||||||||
| browser_data = response.json() | ||||||||||||||||||
| browser_response = CloudBrowserResponse(**browser_data) | ||||||||||||||||||
|
|
||||||||||||||||||
| # Clear current session if it was this one | ||||||||||||||||||
| if session_id == self.current_session_id: | ||||||||||||||||||
| self.current_session_id = None | ||||||||||||||||||
|
|
||||||||||||||||||
| logger.info(f'🌤️ Cloud browser session stopped: {browser_response.id}') | ||||||||||||||||||
| logger.debug(f'🌤️ Status: {browser_response.status}') | ||||||||||||||||||
|
|
||||||||||||||||||
| return browser_response | ||||||||||||||||||
|
|
||||||||||||||||||
| except httpx.TimeoutException: | ||||||||||||||||||
| raise CloudBrowserError('Timeout while stopping cloud browser. Please try again.') | ||||||||||||||||||
| except httpx.ConnectError: | ||||||||||||||||||
| raise CloudBrowserError('Failed to connect to cloud browser service. Please check your internet connection.') | ||||||||||||||||||
| except Exception as e: | ||||||||||||||||||
| if isinstance(e, (CloudBrowserError, CloudBrowserAuthError)): | ||||||||||||||||||
| raise | ||||||||||||||||||
| raise CloudBrowserError(f'Unexpected error stopping cloud browser: {e}') | ||||||||||||||||||
|
|
||||||||||||||||||
| async def close(self): | ||||||||||||||||||
| """Close the HTTP client and cleanup any active sessions.""" | ||||||||||||||||||
| # Try to stop current session if active | ||||||||||||||||||
| if self.current_session_id: | ||||||||||||||||||
| try: | ||||||||||||||||||
| await self.stop_browser() | ||||||||||||||||||
| except Exception as e: | ||||||||||||||||||
| logger.debug(f'Failed to stop cloud browser session during cleanup: {e}') | ||||||||||||||||||
|
|
||||||||||||||||||
| await self.client.aclose() | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| # Global client instance | ||||||||||||||||||
| _cloud_client: CloudBrowserClient | None = None | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of a global Prompt for AI agents[internal] Confidence score: 10/10 [internal] Posted by: System Design Agent
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of a global singleton for Prompt for AI agents[internal] Confidence score: 10/10 [internal] Posted by: System Design Agent
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of a global stateful client for managing cloud browsers is not thread-safe and will cause race conditions. If multiple cloud Prompt for AI agents[internal] Confidence score: 10/10 [internal] Posted by: System Design Agent
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of a global Prompt for AI agents[internal] Confidence score: 10/10 [internal] Posted by: System Design Agent
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of a global singleton ( Prompt for AI agents[internal] Confidence score: 10/10 [internal] Posted by: System Design Agent
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The introduction of a global Prompt for AI agents[internal] Confidence score: 10/10 [internal] Posted by: System Design Agent |
||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| async def get_cloud_browser_cdp_url() -> str: | ||||||||||||||||||
| """Get a CDP URL for a new cloud browser instance. | ||||||||||||||||||
|
|
||||||||||||||||||
| Returns: | ||||||||||||||||||
| str: CDP URL for connecting to the cloud browser | ||||||||||||||||||
|
|
||||||||||||||||||
| Raises: | ||||||||||||||||||
| CloudBrowserAuthError: If authentication fails | ||||||||||||||||||
| CloudBrowserError: If browser creation fails | ||||||||||||||||||
| """ | ||||||||||||||||||
| global _cloud_client | ||||||||||||||||||
|
|
||||||||||||||||||
| if _cloud_client is None: | ||||||||||||||||||
| _cloud_client = CloudBrowserClient() | ||||||||||||||||||
|
|
||||||||||||||||||
| try: | ||||||||||||||||||
| browser_response = await _cloud_client.create_browser() | ||||||||||||||||||
| return browser_response.cdpUrl | ||||||||||||||||||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Returning only the CDP URL drops the session id, so creating multiple cloud sessions before stopping leaks earlier sessions because they can no longer be stopped. Prompt for AI agents[internal] Confidence score: 8/10 [internal] Posted by: Functional Bugs Agent |
||||||||||||||||||
| except Exception: | ||||||||||||||||||
| # Clean up client on error | ||||||||||||||||||
| if _cloud_client: | ||||||||||||||||||
| await _cloud_client.close() | ||||||||||||||||||
| _cloud_client = None | ||||||||||||||||||
| raise | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| async def stop_cloud_browser_session(session_id: str | None = None) -> CloudBrowserResponse: | ||||||||||||||||||
| """Stop a cloud browser session. | ||||||||||||||||||
|
|
||||||||||||||||||
| Args: | ||||||||||||||||||
| session_id: Session ID to stop. If None, uses current session from global client. | ||||||||||||||||||
|
|
||||||||||||||||||
| Returns: | ||||||||||||||||||
| CloudBrowserResponse: Updated browser info with stopped status | ||||||||||||||||||
|
|
||||||||||||||||||
| Raises: | ||||||||||||||||||
| CloudBrowserAuthError: If authentication fails | ||||||||||||||||||
| CloudBrowserError: If stopping fails | ||||||||||||||||||
| """ | ||||||||||||||||||
| global _cloud_client | ||||||||||||||||||
|
|
||||||||||||||||||
| if _cloud_client is None: | ||||||||||||||||||
| _cloud_client = CloudBrowserClient() | ||||||||||||||||||
|
|
||||||||||||||||||
| try: | ||||||||||||||||||
| return await _cloud_client.stop_browser(session_id) | ||||||||||||||||||
| except Exception: | ||||||||||||||||||
| # Don't clean up client on stop errors - session might still be valid | ||||||||||||||||||
| raise | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| async def cleanup_cloud_client(): | ||||||||||||||||||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of a stateful global client for managing cloud browser sessions will cause incorrect session termination when multiple cloud browsers are used concurrently. The session lifecycle management should be handled within each Prompt for AI agents[internal] Confidence score: 10/10 [internal] Posted by: System Design Agent |
||||||||||||||||||
| """Clean up the global cloud client.""" | ||||||||||||||||||
| global _cloud_client | ||||||||||||||||||
| if _cloud_client: | ||||||||||||||||||
| await _cloud_client.close() | ||||||||||||||||||
| _cloud_client = None | ||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please adjust the sentence to clearly instruct users to set the environment variable, e.g., “First set the
BROWSER_USE_API_KEYenvironment variable. You can get your API key from here.” This improves readability and reduces confusion.Prompt for AI agents
[internal] Confidence score: 8/10
[internal] Posted by: General AI Review Agent