hermes(chat): make cluster_read robust to large node listings
Node .status.images (every cached image on the node) overflowed the size cap and left json.loads parsing a truncated blob, so a nodes query came back as a non-JSON error. The de-noise pass now summarizes that list, and genuine truncation returns the readable prefix with a narrow-your-query hint instead of an error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
This commit is contained in:
parent
4ccf2066ca
commit
ed0bc30d7c
@ -119,6 +119,11 @@ def _strip_noise(payload: Any) -> Any:
|
||||
if isinstance(annotations, dict):
|
||||
for key in [k for k in annotations if "last-applied" in k]:
|
||||
annotations.pop(key, None)
|
||||
status = payload.get("status")
|
||||
if isinstance(status, dict) and isinstance(status.get("images"), list):
|
||||
# Node .status.images is a huge blob of every cached image; it
|
||||
# never helps the model and routinely overflows the size cap.
|
||||
status["images"] = f"[{len(status['images'])} cached images omitted]"
|
||||
for value in payload.values():
|
||||
_strip_noise(value)
|
||||
elif isinstance(payload, list):
|
||||
@ -149,17 +154,20 @@ def _handle_cluster_read(args: dict[str, Any] | None = None, **kwargs: Any) -> s
|
||||
return json.dumps({"error": f"cluster API returned {error.code}", "detail": detail[:400]})
|
||||
except Exception as error: # noqa: BLE001 - tool result, never an exception into the loop
|
||||
return json.dumps({"error": f"cluster API unavailable: {type(error).__name__}"})
|
||||
truncated = len(raw) > MAX_RESPONSE_BYTES
|
||||
over_wire = len(raw) > MAX_RESPONSE_BYTES
|
||||
if over_wire:
|
||||
# A truncated wire body is not parseable JSON; hand back the readable
|
||||
# prefix rather than failing, so a large list still yields something.
|
||||
return json.dumps({"truncated": True, "reason": "response exceeded size cap; narrow with namespace/name/label_selector",
|
||||
"partial": raw[: MAX_RESPONSE_BYTES - 96].decode("utf-8", "replace")})
|
||||
try:
|
||||
payload = _strip_noise(json.loads(raw[:MAX_RESPONSE_BYTES] if truncated else raw))
|
||||
payload = _strip_noise(json.loads(raw))
|
||||
except ValueError:
|
||||
return json.dumps({"error": "cluster API returned non-JSON data"})
|
||||
body = json.dumps(payload, separators=(",", ":"))
|
||||
if len(body) > MAX_RESPONSE_BYTES:
|
||||
body = body[:MAX_RESPONSE_BYTES]
|
||||
truncated = True
|
||||
if truncated:
|
||||
return json.dumps({"truncated": True, "partial": body[: MAX_RESPONSE_BYTES - 64]})
|
||||
return json.dumps({"truncated": True, "reason": "result exceeded size cap after cleanup; narrow the query",
|
||||
"partial": body[: MAX_RESPONSE_BYTES - 96]})
|
||||
return body
|
||||
|
||||
|
||||
|
||||
@ -86,7 +86,17 @@ def test_handler_is_get_only_and_bounded(monkeypatch, tmp_path):
|
||||
|
||||
monkeypatch.setattr(mod.urllib.request, "urlopen", huge_open)
|
||||
out = json.loads(mod._handle_cluster_read({"resource": "pods"}))
|
||||
assert out.get("truncated") is True or out.get("error")
|
||||
assert out.get("truncated") is True and "partial" in out
|
||||
|
||||
def node_open(request, timeout=None, context=None):
|
||||
payload = {"kind": "NodeList", "items": [{"metadata": {"name": "n"},
|
||||
"status": {"images": [{"names": ["x"], "sizeBytes": 1} for _ in range(500)]}}]}
|
||||
return FakeResponse(json.dumps(payload).encode())
|
||||
|
||||
monkeypatch.setattr(mod.urllib.request, "urlopen", node_open)
|
||||
out = json.loads(mod._handle_cluster_read({"resource": "nodes"}))
|
||||
assert out["kind"] == "NodeList"
|
||||
assert "cached images omitted" in json.dumps(out)
|
||||
|
||||
|
||||
def test_errors_never_raise(monkeypatch, tmp_path):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user