Changes to src/agents/agui_adapter.py:
- Extract _execute_backend_tool / _run_backend_tool helpers so the
oai_tool_calls path and the content-JSON path share one sanitization
contract. The _try_parse_tool backend-tool path no longer runs
.handle() barefoot — it goes through _execute_backend_tool which
catches the narrowed (pydantic.ValidationError, ValueError), logs
the traceback server-side, and returns a sanitized JSON error payload.
- Narrow tool-execution except from
(RuntimeError, ValueError, TypeError, KeyError, pydantic.ValidationError)
to (pydantic.ValidationError, ValueError). RuntimeError/KeyError from
unrelated libraries or real config drift now propagate as real bugs
rather than being misclassified as user-facing tool failures.
- Replace dict | None three-way return of _parse_tool_args with a
ParsedArgs dataclass (args + status: 'ok' / 'empty' / 'malformed' +
.usable helper). Call sites pattern-match on status explicitly.
- Treat empty-string raw_args (legacy function_call path) as 'malformed'
instead of 'ok with {}' — firing a tool with empty args produces a
meaningless UI card, same rationale as the oai path.
- Drop TypeError from the json.loads except in _try_parse_tool (it
signals a programmer bug, not plain text). Add an
isinstance(content, (str, bytes, bytearray)) guard that logs a
WARNING rather than silencing the case.
- Drop pydantic.errors.PydanticUserError from tool-instantiation except
lists — it signals malformed model *definitions*, not runtime data
errors, and must surface loudly at startup.
- Freeze _TOOL_BY_NAME behind types.MappingProxyType so post-import
mutation raises TypeError (e.g. accidental monkeypatch / shadowing).
Changes to tests/python/conftest.py:
- Tighten the docstring cross-reference to src/agent_server.py (the
real entry point confirmed to import agents.agui_adapter identically).
Changes to tests/python/test_agui_adapter.py:
- Update helper tests to assert on ParsedArgs.status / .usable / .args.
- Flip empty-string expectation to 'malformed' per the new contract.
- Add: bytes-input parse, unknown-type 'empty' status, MappingProxyType
frozen-ness of _TOOL_BY_NAME, unified-helper happy path, helper-level
sanitization + traceback capture, helper propagates non-narrowed
RuntimeError, _try_parse_tool non-str/bytes guard warns and returns
None, and str(exc) never appears anywhere in the SSE stream bytes.
- Switch sanitized-error test's raise to ValueError (narrowed except
covers it) — previously RuntimeError leaked through by accident.
24/24 tests pass (16 existing + 8 new).
- agui_adapter: compute thread_id once so RUN_STARTED/RUN_FINISHED never
disagree when the caller omits it.
- Narrow silent `except Exception: pass` sites, log parse/tool failures
with sanitized context, and drop the dead `agent.agent_response` path
(returns ChatDocument, not ToolMessage).
- Extract `_emit_text_block` helper DRYing the empty-delta guard, and a
`_parse_tool_args` helper with explicit warning on malformed input.
- Assert tool-class request-name uniqueness at import so collisions fail
loudly instead of silently shadowing.
- Replace `str(response)` fallback with documented empty default.
- Add unit tests covering the OAI tool-calls happy path, malformed args,
legacy function_call shape, empty-content skip, thread_id stability,
and tool-class uniqueness (red-green verified).