From d79d5f20357abb6ce962e0d3d65c7e705524b1bb Mon Sep 17 00:00:00 2001 From: xTRam1 Date: Thu, 7 May 2026 15:24:28 -0700 Subject: [PATCH] Fix Cloud Browser trace agent type formatting --- packages/narada-core/pyproject.toml | 2 +- .../src/narada_core/tracing/model.py | 6 + packages/narada-pyodide/pyproject.toml | 4 +- packages/narada-pyodide/src/narada/window.py | 14 ++- .../tests/test_cloud_browser.py | 111 +++++++++++++++++- packages/narada/pyproject.toml | 4 +- uv.lock | 6 +- 7 files changed, 137 insertions(+), 10 deletions(-) diff --git a/packages/narada-core/pyproject.toml b/packages/narada-core/pyproject.toml index edadabf..8bce01f 100644 --- a/packages/narada-core/pyproject.toml +++ b/packages/narada-core/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "narada-core" -version = "0.0.21" +version = "0.0.22" description = "Code shared by the `narada` and `narada-pyodide` packages." license = "Apache-2.0" readme = "README.md" diff --git a/packages/narada-core/src/narada_core/tracing/model.py b/packages/narada-core/src/narada_core/tracing/model.py index 0eb60e3..1cd5b1e 100644 --- a/packages/narada-core/src/narada_core/tracing/model.py +++ b/packages/narada-core/src/narada_core/tracing/model.py @@ -8,6 +8,7 @@ NonNegativeInt, TypeAdapter, ValidationError, + field_validator, model_validator, ) @@ -244,6 +245,11 @@ class PythonSubAgentCallEvent(BaseModel): error_message: str | None = None action_trace: ActionTrace | None = None + @field_validator("agent_type", mode="before") + @classmethod + def _coerce_agent_type(cls, agent_type: Any) -> str: + return str(agent_type) + @model_validator(mode="after") def _check_ts_ordering(self) -> PythonSubAgentCallEvent: if self.ts_end < self.ts_start: diff --git a/packages/narada-pyodide/pyproject.toml b/packages/narada-pyodide/pyproject.toml index 66e29bf..76a0ac2 100644 --- a/packages/narada-pyodide/pyproject.toml +++ b/packages/narada-pyodide/pyproject.toml @@ -1,14 +1,14 @@ [project] name = "narada-pyodide" -version = "0.0.49" +version = "0.0.50" description = "Pyodide-compatible Python client SDK for Narada" license = "Apache-2.0" readme = "README.md" authors = [{ name = "Narada", email = "support@narada.ai" }] requires-python = ">=3.12" dependencies = [ - "narada-core==0.0.21", + "narada-core==0.0.22", # Must be a supported version in https://pyodide.org/en/stable/usage/packages-in-pyodide.html "packaging==24.2", ] diff --git a/packages/narada-pyodide/src/narada/window.py b/packages/narada-pyodide/src/narada/window.py index b389c42..43b8cac 100644 --- a/packages/narada-pyodide/src/narada/window.py +++ b/packages/narada-pyodide/src/narada/window.py @@ -106,6 +106,18 @@ async def _narada_get_id_token() -> str: ... _ResponseModel = TypeVar("_ResponseModel", bound=BaseModel) +_TRACE_AGENT_TYPE_BY_AGENT = { + Agent.PRODUCTIVITY: "generalist", + Agent.OPERATOR: "operator", + Agent.CORE_AGENT: "coreAgent", +} + + +def _agent_type_for_trace(agent: Agent | str) -> str: + if isinstance(agent, Agent): + return _TRACE_AGENT_TYPE_BY_AGENT[agent] + return str(agent) + def _normalize_narada_env(env: str | None) -> Literal["prod", "dev", None]: if env is not None and env not in ("prod", "dev"): @@ -352,7 +364,7 @@ async def dispatch_request( # exit (successful return, timeout, or non-timeout failure) produces a # ``subAgentCall`` trace event with matching status. See `_trace.py`. trace_start_ms = _trace.now_ms() - agent_type_str = agent.value if isinstance(agent, Agent) else str(agent) + agent_type_str = _agent_type_for_trace(agent) deadline = time.monotonic() + timeout diff --git a/packages/narada-pyodide/tests/test_cloud_browser.py b/packages/narada-pyodide/tests/test_cloud_browser.py index 02cf5a7..4b601ab 100644 --- a/packages/narada-pyodide/tests/test_cloud_browser.py +++ b/packages/narada-pyodide/tests/test_cloud_browser.py @@ -9,7 +9,7 @@ import pytest from packaging.version import InvalidVersion -PROJECT_ROOT = Path("/Users/zizheng/Projects/narada-python-sdk") +PROJECT_ROOT = Path(__file__).resolve().parents[3] PYODIDE_SRC = PROJECT_ROOT / "packages" / "narada-pyodide" / "src" CORE_SRC = PROJECT_ROOT / "packages" / "narada-core" / "src" @@ -277,6 +277,115 @@ async def test_cloud_browser_window_dispatch_request_omits_parent_run_ids( assert "parentRunIds" not in payload +@pytest.mark.asyncio +async def test_cloud_browser_window_trace_uses_string_agent_type_for_builtin_agent( + monkeypatch: pytest.MonkeyPatch, +) -> None: + pyfetch = AsyncMock( + side_effect=[ + _FakeResponse(json_data={"requestId": "req-123"}), + _FakeResponse(json_data={"status": "success", "response": None}), + ] + ) + _, _, window_module = _import_pyodide_narada(monkeypatch, pyfetch=pyfetch) + emitted_events: list[dict[str, object]] = [] + monkeypatch.setattr( + window_module._trace, + "_narada_emit_trace_event", + lambda event_json: emitted_events.append(json.loads(event_json)), + raising=False, + ) + + window = window_module.CloudBrowserWindow( + browser_window_id="browser-window-123", + session_id="session-123", + api_key="test-api-key", + ) + await window.dispatch_request(prompt="hello from cloud browser") + + trace_event = emitted_events[0] + assert trace_event["kind"] == "subAgentCall" + assert trace_event["agent_type"] == "operator" + + +def test_parse_action_trace_accepts_historical_numeric_sub_agent_type( + monkeypatch: pytest.MonkeyPatch, +) -> None: + _, _, window_module = _import_pyodide_narada(monkeypatch, pyfetch=AsyncMock()) + + trace = window_module.parse_action_trace( + [ + { + "step_type": "pythonAgentRun", + "url": "", + "status": "success", + "duration_ms": 10, + "events": [ + { + "kind": "subAgentCall", + "ts_start": 1, + "ts_end": 2, + "agent_type": 2, + "prompt": "legacy operator call", + "status": "success", + } + ], + } + ] + ) + + assert trace[0].events[0].agent_type == "2" + + +def test_parse_action_trace_accepts_historical_numeric_nested_sub_agent_type( + monkeypatch: pytest.MonkeyPatch, +) -> None: + _, _, window_module = _import_pyodide_narada(monkeypatch, pyfetch=AsyncMock()) + + trace = window_module.parse_action_trace( + [ + { + "step_type": "pythonAgentRun", + "url": "", + "status": "success", + "duration_ms": 10, + "events": [ + { + "kind": "subAgentCall", + "ts_start": 1, + "ts_end": 2, + "agent_type": "/$USER/childWorkflow", + "prompt": "child workflow call", + "status": "success", + "action_trace": [ + { + "step_type": "pythonAgentRun", + "url": "", + "status": "success", + "duration_ms": 5, + "events": [ + { + "kind": "subAgentCall", + "ts_start": 3, + "ts_end": 4, + "agent_type": 2, + "prompt": "nested operator call", + "status": "success", + } + ], + } + ], + } + ], + } + ] + ) + + nested_trace = trace[0].events[0].action_trace + assert nested_trace is not None + assert nested_trace[0].events[0].agent_type == "2" + + @pytest.mark.asyncio async def test_cloud_browser_window_dispatch_request_preserves_current_file_variable_shape( monkeypatch: pytest.MonkeyPatch, diff --git a/packages/narada/pyproject.toml b/packages/narada/pyproject.toml index a715c14..e8f3aad 100644 --- a/packages/narada/pyproject.toml +++ b/packages/narada/pyproject.toml @@ -1,13 +1,13 @@ [project] name = "narada" -version = "0.1.51" +version = "0.1.52" description = "Python client SDK for Narada" license = "Apache-2.0" readme = "README.md" authors = [{ name = "Narada", email = "support@narada.ai" }] requires-python = ">=3.12" dependencies = [ - "narada-core==0.0.21", + "narada-core==0.0.22", "aiohttp>=3.12.13", "playwright>=1.53.0", "rich>=14.0.0", diff --git a/uv.lock b/uv.lock index de076e1..1906416 100644 --- a/uv.lock +++ b/uv.lock @@ -312,7 +312,7 @@ wheels = [ [[package]] name = "narada" -version = "0.1.51" +version = "0.1.52" source = { editable = "packages/narada" } dependencies = [ { name = "aiohttp" }, @@ -345,7 +345,7 @@ dev = [ [[package]] name = "narada-core" -version = "0.0.21" +version = "0.0.22" source = { editable = "packages/narada-core" } dependencies = [ { name = "pydantic" }, @@ -356,7 +356,7 @@ requires-dist = [{ name = "pydantic", specifier = "==2.12.5" }] [[package]] name = "narada-pyodide" -version = "0.0.49" +version = "0.0.50" source = { editable = "packages/narada-pyodide" } dependencies = [ { name = "narada-core" },