tui_gateway: surface tool failure as payload.error (result convention)
This commit is contained in:
parent
076aebc7e6
commit
6e62489d9e
@ -6118,3 +6118,43 @@ def test_sniff_image_ext_magic_and_filename():
|
|||||||
assert server._sniff_image_ext(b"unknown") == ".png" # fallback
|
assert server._sniff_image_ext(b"unknown") == ".png" # fallback
|
||||||
# filename hint wins over magic bytes
|
# filename hint wins over magic bytes
|
||||||
assert server._sniff_image_ext(b"\x89PNG", "photo.jpeg") == ".jpeg"
|
assert server._sniff_image_ext(b"\x89PNG", "photo.jpeg") == ".jpeg"
|
||||||
|
|
||||||
|
|
||||||
|
def test_tool_complete_derives_error_from_result_convention(monkeypatch):
|
||||||
|
# The repo convention (agent.display._result_succeeded): a JSON-object
|
||||||
|
# result with a truthy string "error" — or success:false — IS a failure.
|
||||||
|
# The gateway surfaces it as payload["error"] so clients (OpenTUI ✗ state,
|
||||||
|
# Ink trail ✗) don't sniff the convention themselves.
|
||||||
|
events: list[tuple[str, str, dict]] = []
|
||||||
|
monkeypatch.setattr(
|
||||||
|
server, "_emit", lambda event_type, sid, payload: events.append((event_type, sid, payload))
|
||||||
|
)
|
||||||
|
monkeypatch.setitem(
|
||||||
|
server._sessions,
|
||||||
|
"err-test",
|
||||||
|
{"tool_progress_mode": "verbose", "tool_started_at": {}, "edit_snapshots": {}},
|
||||||
|
)
|
||||||
|
|
||||||
|
# 1) error-string result → flattened, capped error on the payload
|
||||||
|
server._on_tool_complete(
|
||||||
|
"err-test", "t1", "read_file", {"path": "/nope"},
|
||||||
|
json.dumps({"error": "File not found:\n /nope"}),
|
||||||
|
)
|
||||||
|
assert events[-1][2]["error"] == "File not found: /nope"
|
||||||
|
|
||||||
|
# 2) success:false without error string → generic failure marker
|
||||||
|
server._on_tool_complete(
|
||||||
|
"err-test", "t2", "patch", {"path": "x"}, json.dumps({"success": False})
|
||||||
|
)
|
||||||
|
assert events[-1][2]["error"] == "tool reported failure"
|
||||||
|
|
||||||
|
# 3) plain-text result → NEVER a failure
|
||||||
|
server._on_tool_complete("err-test", "t3", "terminal", {"command": "ls"}, "file-a\nfile-b")
|
||||||
|
assert "error" not in events[-1][2]
|
||||||
|
|
||||||
|
# 4) JSON result with error: null / no error key → success
|
||||||
|
server._on_tool_complete(
|
||||||
|
"err-test", "t4", "web_search", {"q": "x"},
|
||||||
|
json.dumps({"results": [1, 2], "error": None}),
|
||||||
|
)
|
||||||
|
assert "error" not in events[-1][2]
|
||||||
|
|||||||
@ -2100,8 +2100,37 @@ def _on_tool_start(sid: str, tool_call_id: str, name: str, args: dict):
|
|||||||
_emit("tool.start", sid, payload)
|
_emit("tool.start", sid, payload)
|
||||||
|
|
||||||
|
|
||||||
|
def _tool_error_from_result(result: str) -> str | None:
|
||||||
|
"""Derive a tool-failure message from the repo's result convention.
|
||||||
|
|
||||||
|
Canon (same as ``agent.display._result_succeeded``): a JSON-object result
|
||||||
|
with a truthy string ``error`` key — or an explicit ``success: false`` —
|
||||||
|
means the tool failed. Returned flattened + capped for a one-line header.
|
||||||
|
Conservative on purpose: non-JSON / non-dict results are NEVER failures
|
||||||
|
(plain-text output is normal success output).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
data = json.loads(result)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
return None
|
||||||
|
err = data.get("error")
|
||||||
|
if isinstance(err, str) and err.strip():
|
||||||
|
return " ".join(err.split())[:300]
|
||||||
|
if data.get("success") is False:
|
||||||
|
return "tool reported failure"
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _on_tool_complete(sid: str, tool_call_id: str, name: str, args: dict, result: str):
|
def _on_tool_complete(sid: str, tool_call_id: str, name: str, args: dict, result: str):
|
||||||
payload = {"tool_id": tool_call_id, "name": name, "args": args}
|
payload = {"tool_id": tool_call_id, "name": name, "args": args}
|
||||||
|
# Failure surfaced explicitly so clients don't have to sniff the result
|
||||||
|
# convention themselves (the TUI's ✗ state and Ink's trail ✗ both key off
|
||||||
|
# payload["error"], which was previously never set on this path).
|
||||||
|
_tool_err = _tool_error_from_result(result)
|
||||||
|
if _tool_err:
|
||||||
|
payload["error"] = _tool_err
|
||||||
session = _sessions.get(sid)
|
session = _sessions.get(sid)
|
||||||
snapshot = None
|
snapshot = None
|
||||||
started_at = None
|
started_at = None
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user