fix(tui): return JSON-RPC errors for invalid request shapes

This commit is contained in:
Yukipukii1 2026-04-30 21:12:09 +03:00 committed by Teknium
parent 5f3f456784
commit 2110a3a0c4
2 changed files with 55 additions and 4 deletions

View File

@ -59,6 +59,28 @@ def test_write_json_returns_false_on_broken_pipe(monkeypatch):
assert server.write_json({"ok": True}) is False
def test_dispatch_rejects_non_object_request():
resp = server.dispatch([])
assert resp == {
"jsonrpc": "2.0",
"id": None,
"error": {"code": -32600, "message": "invalid request: expected an object"},
}
def test_dispatch_rejects_non_object_params():
resp = server.dispatch(
{"id": "1", "method": "session.create", "params": []}
)
assert resp == {
"jsonrpc": "2.0",
"id": "1",
"error": {"code": -32602, "message": "invalid params: expected an object"},
}
def test_load_enabled_toolsets_prefers_tui_env(monkeypatch):
monkeypatch.setenv("HERMES_TUI_TOOLSETS", "web, terminal, ,memory")

View File

@ -417,11 +417,35 @@ def method(name: str):
return dec
def _normalize_request(req: Any) -> tuple[Any, str, dict] | dict:
"""Validate a JSON-RPC request enough for safe local dispatch."""
if not isinstance(req, dict):
return _err(None, -32600, "invalid request: expected an object")
rid = req.get("id")
method = req.get("method")
if not isinstance(method, str) or not method:
return _err(rid, -32600, "invalid request: method must be a non-empty string")
params = req.get("params", {})
if params is None:
params = {}
elif not isinstance(params, dict):
return _err(rid, -32602, "invalid params: expected an object")
return rid, method, params
def handle_request(req: dict) -> dict | None:
fn = _methods.get(req.get("method", ""))
normalized = _normalize_request(req)
if isinstance(normalized, dict):
return normalized
rid, method, params = normalized
fn = _methods.get(method)
if not fn:
return _err(req.get("id"), -32601, f"unknown method: {req.get('method')}")
return fn(req.get("id"), req.get("params", {}))
return _err(rid, -32601, f"unknown method: {method}")
return fn(rid, params)
def dispatch(req: dict, transport: Optional[Transport] = None) -> dict | None:
@ -439,7 +463,12 @@ def dispatch(req: dict, transport: Optional[Transport] = None) -> dict | None:
t = transport or _stdio_transport
token = bind_transport(t)
try:
if req.get("method") not in _LONG_HANDLERS:
normalized = _normalize_request(req)
if isinstance(normalized, dict):
return normalized
_rid, method, _params = normalized
if method not in _LONG_HANDLERS:
return handle_request(req)
# Snapshot the context so the pool worker sees the bound transport.