From e9d6f447abdc91db9704dfff39f9d0350ad81a58 Mon Sep 17 00:00:00 2001 From: Volodymyr Kasaraba Date: Wed, 13 May 2026 14:51:51 -0400 Subject: [PATCH 1/3] speed up extensioneless lambda --- packages/narada/src/narada/client.py | 35 +++++++++ packages/narada/tests/test_cloud_browser.py | 87 +++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 packages/narada/tests/test_cloud_browser.py diff --git a/packages/narada/src/narada/client.py b/packages/narada/src/narada/client.py index ee72605..e9e53db 100644 --- a/packages/narada/src/narada/client.py +++ b/packages/narada/src/narada/client.py @@ -190,6 +190,41 @@ async def open_and_initialize_cloud_browser_window( "session_name": session_name, "session_timeout": session_timeout, } + + if not require_extension: + endpoint_url = ( + f"{base_url}/cloud-browser/create-and-initialize-cloud-browser-session" + ) + async with aiohttp.ClientSession() as session: + async with session.post( + endpoint_url, + headers=self._auth_headers, + json=request_body, + timeout=aiohttp.ClientTimeout(total=180), + ) as resp: + if not resp.ok: + error_text = await resp.text() + if resp.status == HTTPStatus.FORBIDDEN: + error = ApiErrorPayload.from_error_text(error_text) + err = RuntimeError( + f"Failed to create cloud browser session: {resp.status} {error_text}\n" + f"Endpoint URL: {endpoint_url}" + ) + err.status_code = resp.status # type: ignore[attr-defined] + err.detail = error.detail # type: ignore[attr-defined] + raise err + raise RuntimeError( + f"Failed to create cloud browser session: {resp.status} {error_text}\n" + f"Endpoint URL: {endpoint_url}" + ) + response_data = await resp.json() + + return CloudBrowserWindow( + browser_window_id=response_data["browser_window_id"], + session_id=response_data["session_id"], + auth_headers=self._auth_headers, + ) + endpoint_url = f"{base_url}/cloud-browser/create-cloud-browser-session" async with aiohttp.ClientSession() as session: diff --git a/packages/narada/tests/test_cloud_browser.py b/packages/narada/tests/test_cloud_browser.py new file mode 100644 index 0000000..1949721 --- /dev/null +++ b/packages/narada/tests/test_cloud_browser.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +from typing import Any + +import narada.client as client_module +import pytest +from narada import Narada + + +class _FakeResponse: + ok = True + status = 200 + + def __init__(self, payload: dict[str, Any]) -> None: + self._payload = payload + + async def __aenter__(self) -> "_FakeResponse": + return self + + async def __aexit__(self, *args: Any) -> None: + pass + + async def json(self) -> dict[str, Any]: + return self._payload + + async def text(self) -> str: + return "" + + +class _FakeClientSession: + def __init__(self, payload: dict[str, Any]) -> None: + self.payload = payload + self.posts: list[dict[str, Any]] = [] + + async def __aenter__(self) -> "_FakeClientSession": + return self + + async def __aexit__(self, *args: Any) -> None: + pass + + def post(self, url: str, **kwargs: Any) -> _FakeResponse: + self.posts.append({"url": url, **kwargs}) + return _FakeResponse(self.payload) + + +@pytest.mark.asyncio +async def test_extensionless_cloud_browser_uses_backend_initialization( + monkeypatch: pytest.MonkeyPatch, +) -> None: + fake_session = _FakeClientSession( + { + "session_id": "session-123", + "session_name": "fast-session", + "browser_window_id": "browser-window-123", + } + ) + monkeypatch.setattr(client_module.aiohttp, "ClientSession", lambda: fake_session) + + async def fail_if_client_initializes(*args: Any, **kwargs: Any) -> None: + raise AssertionError( + "extensionless cloud sessions should initialize server-side" + ) + + narada = Narada(auth_headers={"x-api-key": "test-key"}) + monkeypatch.setattr( + narada, "_initialize_cloud_browser_window", fail_if_client_initializes + ) + + window = await narada.open_and_initialize_cloud_browser_window( + session_name="fast-session", + session_timeout=300, + require_extension=False, + ) + + assert window.browser_window_id == "browser-window-123" + assert window.cloud_browser_session_id == "session-123" + assert len(fake_session.posts) == 1 + post = fake_session.posts[0] + assert post["url"].endswith( + "/cloud-browser/create-and-initialize-cloud-browser-session" + ) + assert post["headers"] == {"x-api-key": "test-key"} + assert post["json"] == { + "require_extension": False, + "session_name": "fast-session", + "session_timeout": 300, + } From 70ac2fdc42bf29986ac5f1ea73076563f110b97f Mon Sep 17 00:00:00 2001 From: Volodymyr Kasaraba Date: Wed, 13 May 2026 18:57:21 -0400 Subject: [PATCH 2/3] upd docstring --- packages/narada/src/narada/client.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/narada/src/narada/client.py b/packages/narada/src/narada/client.py index e9e53db..fb9a519 100644 --- a/packages/narada/src/narada/client.py +++ b/packages/narada/src/narada/client.py @@ -177,11 +177,18 @@ async def open_and_initialize_cloud_browser_window( session_timeout: int | None = None, require_extension: bool = True, ) -> CloudBrowserWindow: - """Creates a cloud browser by calling the backend. - - The backend creates a cloud browser session and returns - a CDP WebSocket URL. This method connects to it, initializes the extension, - and returns a CloudBrowserWindow instance. + """Create a cloud browser session and return a ``CloudBrowserWindow``. + + With ``require_extension=True`` (default), calls + ``POST /cloud-browser/create-cloud-browser-session``, then connects local Playwright + over CDP, opens ``login_url``, and waits for ``#narada-browser-window-id`` (extension + install retries apply). ``config`` controls interactive prompts and related behavior. + + With ``require_extension=False``, calls + ``POST /cloud-browser/create-and-initialize-cloud-browser-session`` instead: the API + provisions the browser and runs the same CDP initialization on the server, returning + ``session_id`` and ``browser_window_id`` in the JSON body. Local Playwright is not used + for that path, and ``config`` is ignored. """ config = config or BrowserConfig() base_url = os.getenv("NARADA_API_BASE_URL", "https://api.narada.ai/fast/v2") From e1d05b6e8a64f9249bcfbd2e8583aab189c8c011 Mon Sep 17 00:00:00 2001 From: Volodymyr Kasaraba Date: Thu, 14 May 2026 15:30:56 -0400 Subject: [PATCH 3/3] upd formatting --- examples/human_in_the_loop.py | 3 +-- packages/narada/tests/test_cloud_browser.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/human_in_the_loop.py b/examples/human_in_the_loop.py index 96684cb..3bcf12a 100644 --- a/examples/human_in_the_loop.py +++ b/examples/human_in_the_loop.py @@ -1,8 +1,7 @@ import asyncio -from narada_core.actions.models import PromptForUserInputVariable - from narada import Agent, Narada, UserAbortedError +from narada_core.actions.models import PromptForUserInputVariable async def main() -> None: diff --git a/packages/narada/tests/test_cloud_browser.py b/packages/narada/tests/test_cloud_browser.py index f45a028..db54062 100644 --- a/packages/narada/tests/test_cloud_browser.py +++ b/packages/narada/tests/test_cloud_browser.py @@ -57,6 +57,7 @@ async def test_extensionless_cloud_browser_uses_backend_initialization( monkeypatch: pytest.MonkeyPatch, ) -> None: import narada.client as client_module + fake_session = _FakeClientSession( { "session_id": "session-123",