From cfd6449aea619cf92eeebd111b4bba615e5bbe05 Mon Sep 17 00:00:00 2001 From: xTRam1 Date: Tue, 12 May 2026 15:21:14 -0700 Subject: [PATCH] Normalize Python trace agent types --- packages/narada-core/pyproject.toml | 2 +- .../narada-core/src/narada_core/models.py | 61 +++++++++++++++++++ .../src/narada_core/tracing/model.py | 24 ++++++++ packages/narada-pyodide/pyproject.toml | 4 +- packages/narada-pyodide/src/narada/window.py | 14 ++++- .../tests/test_cloud_browser.py | 39 +++++++++++- packages/narada/pyproject.toml | 4 +- uv.lock | 6 +- 8 files changed, 144 insertions(+), 10 deletions(-) diff --git a/packages/narada-core/pyproject.toml b/packages/narada-core/pyproject.toml index 8bce01f..0966dab 100644 --- a/packages/narada-core/pyproject.toml +++ b/packages/narada-core/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "narada-core" -version = "0.0.22" +version = "0.0.23" 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/models.py b/packages/narada-core/src/narada_core/models.py index 307c7a9..18f183f 100644 --- a/packages/narada-core/src/narada_core/models.py +++ b/packages/narada-core/src/narada_core/models.py @@ -292,6 +292,66 @@ class ObjectSetPropertiesTrace(TypedDict): description: str +class PythonStdoutEvent(TypedDict): + kind: Literal["stdout"] + ts: int + text: str + + +class PythonStderrEvent(TypedDict): + kind: Literal["stderr"] + ts: int + text: str + + +class PythonSubAgentCallEvent(TypedDict): + kind: Literal["subAgentCall"] + ts_start: int + ts_end: int + agent_type: str + prompt: str + status: Literal["success", "error", "timeout"] + request_id: NotRequired[str] + error_message: NotRequired[str] + action_trace: NotRequired[ActionTrace] + + +class PythonExtensionActionEvent(TypedDict): + kind: Literal["extensionAction"] + ts_start: int + ts_end: int + action_name: str + request_summary: dict[str, object] + result_summary: NotRequired[dict[str, object]] + status: Literal["success", "error", "timeout"] + error_message: NotRequired[str] + + +class PythonSideEffectEvent(TypedDict): + kind: Literal["sideEffect"] + ts: int + effect_type: Literal["download_file", "render_html"] + description: str + + +type PythonTraceEvent = ( + PythonStdoutEvent + | PythonStderrEvent + | PythonSubAgentCallEvent + | PythonExtensionActionEvent + | PythonSideEffectEvent +) + + +class PythonAgentRunTrace(TypedDict): + step_type: Literal["pythonAgentRun"] + url: str + status: Literal["success", "error", "aborted"] + duration_ms: int + events: list[PythonTraceEvent] + error_message: NotRequired[str] + + ApaStepTrace = ( GoToUrlTrace | GetUrlTrace @@ -324,6 +384,7 @@ class ObjectSetPropertiesTrace(TypedDict): | DataTableInsertRowTrace | DataTableUpdateCellValueTrace | ObjectSetPropertiesTrace + | PythonAgentRunTrace ) diff --git a/packages/narada-core/src/narada_core/tracing/model.py b/packages/narada-core/src/narada_core/tracing/model.py index 0eb60e3..d2c6245 100644 --- a/packages/narada-core/src/narada_core/tracing/model.py +++ b/packages/narada-core/src/narada_core/tracing/model.py @@ -8,10 +8,24 @@ NonNegativeInt, TypeAdapter, ValidationError, + field_validator, model_validator, ) +def _normalize_agent_type(agent_type: object) -> str: + """Normalize legacy SDK enum values into the frontend trace contract.""" + match agent_type: + case 1 | "1": + return "generalist" + case 2 | "2": + return "operator" + case 3 | "3": + return "coreAgent" + case _: + return str(agent_type) + + class OperatorActionTraceItem(BaseModel): url: str action: str @@ -42,6 +56,11 @@ class AgentTrace(BaseModel): action_trace: ActionTrace | None = None text: str | None = None + @field_validator("agent_type", mode="before") + @classmethod + def _normalize_agent_type(cls, value: object) -> str: + return _normalize_agent_type(value) + class ForLoopTrace(BaseModel): step_type: Literal["for"] @@ -244,6 +263,11 @@ class PythonSubAgentCallEvent(BaseModel): error_message: str | None = None action_trace: ActionTrace | None = None + @field_validator("agent_type", mode="before") + @classmethod + def _normalize_agent_type(cls, value: object) -> str: + return _normalize_agent_type(value) + @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 595c4d4..84f6dce 100644 --- a/packages/narada-pyodide/pyproject.toml +++ b/packages/narada-pyodide/pyproject.toml @@ -1,14 +1,14 @@ [project] name = "narada-pyodide" -version = "0.0.51" +version = "0.0.52" 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.22", + "narada-core==0.0.23", # 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 4c6809c..9d9b6c1 100644 --- a/packages/narada-pyodide/src/narada/window.py +++ b/packages/narada-pyodide/src/narada/window.py @@ -109,6 +109,18 @@ async def _narada_get_id_token() -> str: ... _ResponseModel = TypeVar("_ResponseModel", bound=BaseModel) +def _trace_agent_type(agent: Agent | str) -> str: + match agent: + case Agent.PRODUCTIVITY: + return "generalist" + case Agent.OPERATOR: + return "operator" + case Agent.CORE_AGENT: + return "coreAgent" + case _: + 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"): raise ValueError(f"Invalid environment: {env!r}") @@ -354,7 +366,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 = _trace_agent_type(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..2342cab 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,43 @@ async def test_cloud_browser_window_dispatch_request_omits_parent_run_ids( assert "parentRunIds" not in payload +@pytest.mark.asyncio +async def test_dispatch_request_emits_string_trace_agent_type_for_sdk_enum( + monkeypatch: pytest.MonkeyPatch, +) -> None: + pyfetch = AsyncMock( + side_effect=[ + _FakeResponse(json_data={"requestId": "req-123"}), + _FakeResponse(json_data={"status": "success", "response": None}), + ] + ) + narada_pkg, _, window_module = _import_pyodide_narada(monkeypatch, pyfetch=pyfetch) + emitted_events: list[str] = [] + monkeypatch.setattr( + sys.modules["narada._trace"], + "_narada_emit_trace_event", + emitted_events.append, + raising=False, + ) + + window = window_module.CloudBrowserWindow( + browser_window_id="browser-window-123", + session_id="session-123", + api_key="test-api-key", + ) + response = await window.dispatch_request( + prompt="hello from cloud browser", + agent=narada_pkg.Agent.OPERATOR, + ) + + assert response["status"] == "success" + assert json.loads(pyfetch.await_args_list[0].kwargs["body"])["prompt"] == ( + "/Operator hello from cloud browser" + ) + assert len(emitted_events) == 1 + assert json.loads(emitted_events[0])["agent_type"] == "operator" + + @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 b624cb7..b47e034 100644 --- a/packages/narada/pyproject.toml +++ b/packages/narada/pyproject.toml @@ -1,13 +1,13 @@ [project] name = "narada" -version = "0.1.53a1" +version = "0.1.53a2" 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.22", + "narada-core==0.0.23", "aiohttp>=3.12.13", "playwright>=1.53.0", "rich>=14.0.0", diff --git a/uv.lock b/uv.lock index 7857505..47dabed 100644 --- a/uv.lock +++ b/uv.lock @@ -312,7 +312,7 @@ wheels = [ [[package]] name = "narada" -version = "0.1.53a1" +version = "0.1.53a2" source = { editable = "packages/narada" } dependencies = [ { name = "aiohttp" }, @@ -345,7 +345,7 @@ dev = [ [[package]] name = "narada-core" -version = "0.0.22" +version = "0.0.23" 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.51" +version = "0.0.52" source = { editable = "packages/narada-pyodide" } dependencies = [ { name = "narada-core" },