diff --git a/integrations-service/integrations/models/browserbase.py b/integrations-service/integrations/models/browserbase.py index df683a4ec..52c6c0f51 100644 --- a/integrations-service/integrations/models/browserbase.py +++ b/integrations-service/integrations/models/browserbase.py @@ -1,6 +1,7 @@ from typing import Literal -from browserbase import DebugConnectionURLs, Session +from browserbase.types import Session +from browserbase.types.session_live_urls import SessionLiveURLs from pydantic import AnyUrl, Field from .base_models import BaseOutput @@ -12,6 +13,7 @@ class BrowserbaseListSessionsOutput(BaseOutput): class BrowserbaseCreateSessionOutput(BaseOutput): id: str = Field(..., description="Unique identifier for the session") + connectionUrl: str | None = Field(None, description="The connection URL for the session") createdAt: str | None = Field( None, description="Timestamp indicating when the session was created" ) @@ -90,7 +92,7 @@ class PageInfo(BaseOutput): class BrowserbaseGetSessionLiveUrlsOutput(BaseOutput): - urls: DebugConnectionURLs = Field(..., description="The live URLs for the session") + urls: SessionLiveURLs = Field(..., description="The live URLs for the session") class BrowserbaseContextOutput(BaseOutput): diff --git a/integrations-service/integrations/utils/integrations/browserbase.py b/integrations-service/integrations/utils/integrations/browserbase.py index 6022f40e2..6cbc28862 100644 --- a/integrations-service/integrations/utils/integrations/browserbase.py +++ b/integrations-service/integrations/utils/integrations/browserbase.py @@ -4,13 +4,11 @@ import httpx from beartype import beartype -from browserbase import ( - Browserbase, - BrowserSettings, - CreateSessionOptions, - DebugConnectionURLs, - Session, -) +from browserbase import Browserbase +from browserbase.types.session import Session +from browserbase.types.session_create_params import BrowserSettings +from browserbase.types.session_live_urls import SessionLiveURLs +from pydantic import TypeAdapter from tenacity import retry, stop_after_attempt, wait_exponential from ...autogen.Tools import ( @@ -46,9 +44,6 @@ def get_browserbase_client(setup: BrowserbaseSetup) -> Browserbase: return Browserbase( api_key=setup.api_key, - project_id=setup.project_id, - api_url=setup.api_url, - connect_url=setup.connect_url, ) @@ -63,11 +58,17 @@ async def list_sessions( ) -> BrowserbaseListSessionsOutput: client = get_browserbase_client(setup) - # FIXME: Implement status filter - # Run the list_sessions method - sessions: list[Session] = client.list_sessions() + try: + # Add status filter if provided + params = {} + if hasattr(arguments, "status") and arguments.status: + params["status"] = arguments.status - return BrowserbaseListSessionsOutput(sessions=sessions) + sessions: list[Session] = client.sessions.list(**params) + return BrowserbaseListSessionsOutput(sessions=sessions) + except Exception as e: + print(f"Error listing sessions: {e}") + raise @beartype @@ -84,19 +85,39 @@ async def create_session( if arguments.project_id == "DEMO_PROJECT_ID": arguments.project_id = browserbase_project_id - if arguments.project_id == "DEMO_PROJECT_ID": - arguments.project_id = browserbase_project_id - - options = CreateSessionOptions( - projectId=arguments.project_id or setup.project_id, - extensionId=arguments.extension_id, - browserSettings=BrowserSettings(**arguments.browser_settings), + # Convert browser settings using TypeAdapter + browser_settings = TypeAdapter(BrowserSettings).validate_python(arguments.browser_settings) + + # Create session parameters + create_params = { + "project_id": arguments.project_id or setup.project_id, + "browser_settings": browser_settings, + } + + # Only add extension_id if it's provided and not None/empty + if arguments.extension_id: + create_params["extension_id"] = arguments.extension_id + + # Changed to use sessions.create() with direct parameters + session = client.sessions.create(**create_params) + + # Convert datetime fields to ISO format strings + return BrowserbaseCreateSessionOutput( + id=session.id, + connectionUrl=session.connect_url, + createdAt=session.created_at.isoformat() if session.created_at else None, + projectId=session.project_id, + startedAt=session.started_at.isoformat() if session.started_at else None, + endedAt=session.ended_at.isoformat() if session.ended_at else None, + expiresAt=session.expires_at.isoformat() if session.expires_at else None, + status=session.status, + proxyBytes=session.proxy_bytes, + avgCpuUsage=session.avg_cpu_usage, + memoryUsage=session.memory_usage, + keepAlive=session.keep_alive, + contextId=session.context_id, ) - session = client.create_session(options) - - return BrowserbaseCreateSessionOutput(**session.model_dump()) - @beartype @retry( @@ -109,9 +130,23 @@ async def get_session( ) -> BrowserbaseGetSessionOutput: client = get_browserbase_client(setup) - session = client.get_session(arguments.id) - - return BrowserbaseGetSessionOutput(**session.model_dump()) + # Changed from get_session() to sessions.retrieve() + session = client.sessions.retrieve(id=arguments.id) + + return BrowserbaseGetSessionOutput( + id=session.id, + createdAt=session.created_at.isoformat() if session.created_at else None, + projectId=session.project_id, + startedAt=session.started_at.isoformat() if session.started_at else None, + endedAt=session.ended_at.isoformat() if session.ended_at else None, + expiresAt=session.expires_at.isoformat() if session.expires_at else None, + status=session.status, + proxyBytes=session.proxy_bytes, + avgCpuUsage=session.avg_cpu_usage, + memoryUsage=session.memory_usage, + keepAlive=session.keep_alive, + contextId=session.context_id, + ) @beartype @@ -126,7 +161,10 @@ async def complete_session( client = get_browserbase_client(setup) try: - client.complete_session(arguments.id) + # Changed to use sessions.update() with REQUEST_RELEASE status + client.sessions.update( + id=arguments.id, status="REQUEST_RELEASE", project_id=setup.project_id + ) except Exception: return BrowserbaseCompleteSessionOutput(success=False) @@ -144,8 +182,13 @@ async def get_live_urls( ) -> BrowserbaseGetSessionLiveUrlsOutput: """Get the live URLs for a session.""" client = get_browserbase_client(setup) - urls: DebugConnectionURLs = client.get_debug_connection_urls(arguments.id) - return BrowserbaseGetSessionLiveUrlsOutput(urls=urls) + try: + # Use the debug() method to get live URLs + urls: SessionLiveURLs = client.sessions.debug(id=arguments.id) + return BrowserbaseGetSessionLiveUrlsOutput(urls=urls) + except Exception as e: + print(f"Error getting debug URLs: {e}") + raise @beartype @@ -157,11 +200,17 @@ async def get_live_urls( async def get_connect_url( setup: BrowserbaseSetup, arguments: BrowserbaseGetSessionConnectUrlArguments ) -> BrowserbaseGetSessionConnectUrlOutput: - client = get_browserbase_client(setup) - - url = client.get_connect_url(arguments.id) - - return BrowserbaseGetSessionConnectUrlOutput(url=url) + """Get the connect URL for a session.""" + # TODO: Get a better way to get the connect URL than this + try: + # Get session to access its connect_url + CONNECT_URL = ( + f"wss://connect.browserbase.com?sessionId={arguments.id}&apiKey={setup.api_key}" + ) + return BrowserbaseGetSessionConnectUrlOutput(url=CONNECT_URL) + except Exception as e: + print(f"Error getting connect URL: {e}") + raise @beartype @@ -174,42 +223,47 @@ async def install_extension_from_github( setup: BrowserbaseSetup, arguments: BrowserbaseExtensionArguments ) -> BrowserbaseExtensionOutput: """Download and install an extension from GitHub to the user's Browserbase account.""" + try: + github_url = f"https://github.com/{arguments.repository_name}/archive/refs/tags/{arguments.ref}.zip" - github_url = ( - f"https://github.com/{arguments.repository_name}/archive/refs/tags/{arguments.ref}.zip" - ) - - async with httpx.AsyncClient(timeout=600) as client: - # Download the extension zip - response = await client.get(github_url, follow_redirects=True) - response.raise_for_status() - - with tempfile.NamedTemporaryFile( - delete=True, delete_on_close=False, suffix=".zip" - ) as tmp_file: - tmp_file.write(response.content) - tmp_file_path = tmp_file.name - - # Upload the extension to Browserbase - upload_url = "https://www.browserbase.com/v1/extensions" - headers = { - # NOTE: httpx won't add a boundary if Content-Type header is set when you pass files= - # "Content-Type": "multipart/form-data", - "X-BB-API-Key": setup.api_key, - } - - with open(tmp_file_path, "rb") as f: - files = {"file": f} - upload_response = await client.post(upload_url, headers=headers, files=files) - + async with httpx.AsyncClient(timeout=600) as client: + # Download the extension zip try: - upload_response.raise_for_status() - except httpx.HTTPStatusError: - print(upload_response.text) + response = await client.get(github_url, follow_redirects=True) + response.raise_for_status() + except httpx.HTTPError as e: + print(f"Error downloading extension from GitHub: {e}") raise - # Delete the temporary file - with contextlib.suppress(FileNotFoundError): - os.remove(tmp_file_path) - - return BrowserbaseExtensionOutput(id=upload_response.json()["id"]) + with tempfile.NamedTemporaryFile( + delete=True, delete_on_close=False, suffix=".zip" + ) as tmp_file: + tmp_file.write(response.content) + tmp_file_path = tmp_file.name + + upload_url = "https://api.browserbase.com/v1/extensions" + headers = { + "X-BB-API-Key": setup.api_key, + } + + try: + with open(tmp_file_path, "rb") as f: + files = {"file": f} + upload_response = await client.post( + upload_url, headers=headers, files=files + ) + upload_response.raise_for_status() + except httpx.HTTPError as e: + print(f"Error uploading extension to Browserbase: {e}") + if hasattr(e, "response") and e.response is not None: + print(f"Response content: {e.response.text}") + raise + + # Delete the temporary file + with contextlib.suppress(FileNotFoundError): + os.remove(tmp_file_path) + + return BrowserbaseExtensionOutput(id=upload_response.json()["id"]) + except Exception as e: + print(f"Unexpected error in install_extension_from_github: {e}") + raise diff --git a/integrations-service/pyproject.toml b/integrations-service/pyproject.toml index cc82d9d6c..e0890235c 100644 --- a/integrations-service/pyproject.toml +++ b/integrations-service/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "fire~=0.6.0", "pyowm~=3.3.0", "spider-client~=0.0.70", - "browserbase~=0.3.0", + "browserbase>=1.0.5", "setuptools~=75.1.0", "beartype~=0.19.0", "tenacity>=8.2.0,<8.4.0", diff --git a/integrations-service/uv.lock b/integrations-service/uv.lock index 948c5b748..2f19c25ea 100644 --- a/integrations-service/uv.lock +++ b/integrations-service/uv.lock @@ -163,16 +163,19 @@ wheels = [ [[package]] name = "browserbase" -version = "0.3.6" +version = "1.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "anyio" }, + { name = "distro" }, { name = "httpx" }, - { name = "playwright" }, { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/9d/5542bddca6924f1511f64835dca823b6bb3f943f9f90708885027340ad1c/browserbase-0.3.6.tar.gz", hash = "sha256:1d20d9490304e27c4cf84a9c119b0bce98d87ceaec298acac4a29c47058d367e", size = 5057 } +sdist = { url = "https://files.pythonhosted.org/packages/17/67/683a5f67d01aff89ebb0d0c11141dd21d3c5d9d7513793f74e65cdf45fb3/browserbase-1.0.5.tar.gz", hash = "sha256:1c8256cb8f6d94838067b2f3cc173a3118c102746c68565b8d2fa4cb10a3e4a9", size = 114527 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/f6/6abb69ca84f327f455dcd9a455334d1224c196bd5ab3246c72da63b3a6af/browserbase-0.3.6-py3-none-any.whl", hash = "sha256:da3338302c34ecb6abda0534bc16663c7049a8bef2694db5a93ef05d2ba1d3c3", size = 5373 }, + { url = "https://files.pythonhosted.org/packages/02/92/71db66c57076c067e2c0e5fcc71000e64e80e4e4d5166ad409377ac142ac/browserbase-1.0.5-py3-none-any.whl", hash = "sha256:72c4d8269bd830bedde539de894bc372db46d912b7f832952c6bdd77ae5e2387", size = 94218 }, ] [[package]] @@ -226,7 +229,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ @@ -669,7 +672,7 @@ dev = [ requires-dist = [ { name = "arxiv", specifier = "~=2.1.3" }, { name = "beartype", specifier = "~=0.19.0" }, - { name = "browserbase", specifier = "~=0.3.0" }, + { name = "browserbase", specifier = ">=1.0.5" }, { name = "cloudinary", specifier = "~=1.41.0" }, { name = "duckduckgo-search", specifier = "~=6.2.13" }, { name = "environs", specifier = "~=11.2.1" }, @@ -2086,7 +2089,7 @@ name = "tqdm" version = "4.67.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } wheels = [