Skip to content

Commit cfd6449

Browse files
committed
Normalize Python trace agent types
1 parent 278e1a0 commit cfd6449

8 files changed

Lines changed: 144 additions & 10 deletions

File tree

packages/narada-core/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "narada-core"
3-
version = "0.0.22"
3+
version = "0.0.23"
44
description = "Code shared by the `narada` and `narada-pyodide` packages."
55
license = "Apache-2.0"
66
readme = "README.md"

packages/narada-core/src/narada_core/models.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,66 @@ class ObjectSetPropertiesTrace(TypedDict):
292292
description: str
293293

294294

295+
class PythonStdoutEvent(TypedDict):
296+
kind: Literal["stdout"]
297+
ts: int
298+
text: str
299+
300+
301+
class PythonStderrEvent(TypedDict):
302+
kind: Literal["stderr"]
303+
ts: int
304+
text: str
305+
306+
307+
class PythonSubAgentCallEvent(TypedDict):
308+
kind: Literal["subAgentCall"]
309+
ts_start: int
310+
ts_end: int
311+
agent_type: str
312+
prompt: str
313+
status: Literal["success", "error", "timeout"]
314+
request_id: NotRequired[str]
315+
error_message: NotRequired[str]
316+
action_trace: NotRequired[ActionTrace]
317+
318+
319+
class PythonExtensionActionEvent(TypedDict):
320+
kind: Literal["extensionAction"]
321+
ts_start: int
322+
ts_end: int
323+
action_name: str
324+
request_summary: dict[str, object]
325+
result_summary: NotRequired[dict[str, object]]
326+
status: Literal["success", "error", "timeout"]
327+
error_message: NotRequired[str]
328+
329+
330+
class PythonSideEffectEvent(TypedDict):
331+
kind: Literal["sideEffect"]
332+
ts: int
333+
effect_type: Literal["download_file", "render_html"]
334+
description: str
335+
336+
337+
type PythonTraceEvent = (
338+
PythonStdoutEvent
339+
| PythonStderrEvent
340+
| PythonSubAgentCallEvent
341+
| PythonExtensionActionEvent
342+
| PythonSideEffectEvent
343+
)
344+
345+
346+
class PythonAgentRunTrace(TypedDict):
347+
step_type: Literal["pythonAgentRun"]
348+
url: str
349+
status: Literal["success", "error", "aborted"]
350+
duration_ms: int
351+
events: list[PythonTraceEvent]
352+
error_message: NotRequired[str]
353+
354+
295355
ApaStepTrace = (
296356
GoToUrlTrace
297357
| GetUrlTrace
@@ -324,6 +384,7 @@ class ObjectSetPropertiesTrace(TypedDict):
324384
| DataTableInsertRowTrace
325385
| DataTableUpdateCellValueTrace
326386
| ObjectSetPropertiesTrace
387+
| PythonAgentRunTrace
327388
)
328389

329390

packages/narada-core/src/narada_core/tracing/model.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,24 @@
88
NonNegativeInt,
99
TypeAdapter,
1010
ValidationError,
11+
field_validator,
1112
model_validator,
1213
)
1314

1415

16+
def _normalize_agent_type(agent_type: object) -> str:
17+
"""Normalize legacy SDK enum values into the frontend trace contract."""
18+
match agent_type:
19+
case 1 | "1":
20+
return "generalist"
21+
case 2 | "2":
22+
return "operator"
23+
case 3 | "3":
24+
return "coreAgent"
25+
case _:
26+
return str(agent_type)
27+
28+
1529
class OperatorActionTraceItem(BaseModel):
1630
url: str
1731
action: str
@@ -42,6 +56,11 @@ class AgentTrace(BaseModel):
4256
action_trace: ActionTrace | None = None
4357
text: str | None = None
4458

59+
@field_validator("agent_type", mode="before")
60+
@classmethod
61+
def _normalize_agent_type(cls, value: object) -> str:
62+
return _normalize_agent_type(value)
63+
4564

4665
class ForLoopTrace(BaseModel):
4766
step_type: Literal["for"]
@@ -244,6 +263,11 @@ class PythonSubAgentCallEvent(BaseModel):
244263
error_message: str | None = None
245264
action_trace: ActionTrace | None = None
246265

266+
@field_validator("agent_type", mode="before")
267+
@classmethod
268+
def _normalize_agent_type(cls, value: object) -> str:
269+
return _normalize_agent_type(value)
270+
247271
@model_validator(mode="after")
248272
def _check_ts_ordering(self) -> PythonSubAgentCallEvent:
249273
if self.ts_end < self.ts_start:

packages/narada-pyodide/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11

22
[project]
33
name = "narada-pyodide"
4-
version = "0.0.51"
4+
version = "0.0.52"
55
description = "Pyodide-compatible Python client SDK for Narada"
66
license = "Apache-2.0"
77
readme = "README.md"
88
authors = [{ name = "Narada", email = "support@narada.ai" }]
99
requires-python = ">=3.12"
1010
dependencies = [
11-
"narada-core==0.0.22",
11+
"narada-core==0.0.23",
1212
# Must be a supported version in https://pyodide.org/en/stable/usage/packages-in-pyodide.html
1313
"packaging==24.2",
1414
]

packages/narada-pyodide/src/narada/window.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,18 @@ async def _narada_get_id_token() -> str: ...
109109
_ResponseModel = TypeVar("_ResponseModel", bound=BaseModel)
110110

111111

112+
def _trace_agent_type(agent: Agent | str) -> str:
113+
match agent:
114+
case Agent.PRODUCTIVITY:
115+
return "generalist"
116+
case Agent.OPERATOR:
117+
return "operator"
118+
case Agent.CORE_AGENT:
119+
return "coreAgent"
120+
case _:
121+
return str(agent)
122+
123+
112124
def _normalize_narada_env(env: str | None) -> Literal["prod", "dev", None]:
113125
if env is not None and env not in ("prod", "dev"):
114126
raise ValueError(f"Invalid environment: {env!r}")
@@ -354,7 +366,7 @@ async def dispatch_request(
354366
# exit (successful return, timeout, or non-timeout failure) produces a
355367
# ``subAgentCall`` trace event with matching status. See `_trace.py`.
356368
trace_start_ms = _trace.now_ms()
357-
agent_type_str = agent.value if isinstance(agent, Agent) else str(agent)
369+
agent_type_str = _trace_agent_type(agent)
358370

359371
deadline = time.monotonic() + timeout
360372

packages/narada-pyodide/tests/test_cloud_browser.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import pytest
1010
from packaging.version import InvalidVersion
1111

12-
PROJECT_ROOT = Path("/Users/zizheng/Projects/narada-python-sdk")
12+
PROJECT_ROOT = Path(__file__).resolve().parents[3]
1313
PYODIDE_SRC = PROJECT_ROOT / "packages" / "narada-pyodide" / "src"
1414
CORE_SRC = PROJECT_ROOT / "packages" / "narada-core" / "src"
1515

@@ -277,6 +277,43 @@ async def test_cloud_browser_window_dispatch_request_omits_parent_run_ids(
277277
assert "parentRunIds" not in payload
278278

279279

280+
@pytest.mark.asyncio
281+
async def test_dispatch_request_emits_string_trace_agent_type_for_sdk_enum(
282+
monkeypatch: pytest.MonkeyPatch,
283+
) -> None:
284+
pyfetch = AsyncMock(
285+
side_effect=[
286+
_FakeResponse(json_data={"requestId": "req-123"}),
287+
_FakeResponse(json_data={"status": "success", "response": None}),
288+
]
289+
)
290+
narada_pkg, _, window_module = _import_pyodide_narada(monkeypatch, pyfetch=pyfetch)
291+
emitted_events: list[str] = []
292+
monkeypatch.setattr(
293+
sys.modules["narada._trace"],
294+
"_narada_emit_trace_event",
295+
emitted_events.append,
296+
raising=False,
297+
)
298+
299+
window = window_module.CloudBrowserWindow(
300+
browser_window_id="browser-window-123",
301+
session_id="session-123",
302+
api_key="test-api-key",
303+
)
304+
response = await window.dispatch_request(
305+
prompt="hello from cloud browser",
306+
agent=narada_pkg.Agent.OPERATOR,
307+
)
308+
309+
assert response["status"] == "success"
310+
assert json.loads(pyfetch.await_args_list[0].kwargs["body"])["prompt"] == (
311+
"/Operator hello from cloud browser"
312+
)
313+
assert len(emitted_events) == 1
314+
assert json.loads(emitted_events[0])["agent_type"] == "operator"
315+
316+
280317
@pytest.mark.asyncio
281318
async def test_cloud_browser_window_dispatch_request_preserves_current_file_variable_shape(
282319
monkeypatch: pytest.MonkeyPatch,

packages/narada/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
[project]
22
name = "narada"
3-
version = "0.1.53a1"
3+
version = "0.1.53a2"
44
description = "Python client SDK for Narada"
55
license = "Apache-2.0"
66
readme = "README.md"
77
authors = [{ name = "Narada", email = "support@narada.ai" }]
88
requires-python = ">=3.12"
99
dependencies = [
10-
"narada-core==0.0.22",
10+
"narada-core==0.0.23",
1111
"aiohttp>=3.12.13",
1212
"playwright>=1.53.0",
1313
"rich>=14.0.0",

uv.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)