From e4452ffb8a4986343a7b256c3f7469a73fc9fc54 Mon Sep 17 00:00:00 2001 From: Gille <4317663+helix4u@users.noreply.github.com> Date: Thu, 18 Jun 2026 16:49:14 -0600 Subject: [PATCH] fix(agent): summarize structured provider error messages --- run_agent.py | 30 +++++++++++++++++++ .../test_codex_xai_oauth_recovery.py | 29 ++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/run_agent.py b/run_agent.py index 331ff2c66ab..65b95483e54 100644 --- a/run_agent.py +++ b/run_agent.py @@ -1840,6 +1840,35 @@ class AIAgent: return detail return f"{detail}{hint}" + @staticmethod + def _coerce_api_error_detail(value: Any) -> str: + """Return a display-safe string for structured provider error fields.""" + if isinstance(value, str): + return value + if isinstance(value, dict): + for key in ("message", "detail", "error", "code", "type"): + nested = value.get(key) + if isinstance(nested, str) and nested.strip(): + return nested + for key in ("message", "detail", "error", "code", "type"): + if key in value: + nested_detail = AIAgent._coerce_api_error_detail(value[key]) + if nested_detail: + return nested_detail + try: + return json.dumps(value, ensure_ascii=False, sort_keys=True) + except TypeError: + return str(value) + if isinstance(value, (list, tuple)): + parts = [ + AIAgent._coerce_api_error_detail(item) + for item in value + ] + return "; ".join(part for part in parts if part) + if value is None: + return "" + return str(value) + @staticmethod def _summarize_api_error(error: Exception) -> str: """Extract a human-readable one-liner from an API error. @@ -1879,6 +1908,7 @@ class AIAgent: if msg: status_code = getattr(error, "status_code", None) prefix = f"HTTP {status_code}: " if status_code else "" + msg = AIAgent._coerce_api_error_detail(msg) return AIAgent._decorate_xai_entitlement_error(f"{prefix}{msg[:300]}") # Fallback: truncate the raw string but give more room than 200 chars diff --git a/tests/run_agent/test_codex_xai_oauth_recovery.py b/tests/run_agent/test_codex_xai_oauth_recovery.py index 8a2ce564193..2bc31686e75 100644 --- a/tests/run_agent/test_codex_xai_oauth_recovery.py +++ b/tests/run_agent/test_codex_xai_oauth_recovery.py @@ -252,6 +252,35 @@ def test_summarize_api_error_decorates_xai_body_message(): assert "X Premium+ does NOT include" in summary +def test_summarize_api_error_handles_nested_provider_message(): + """HF router may put a structured object in error.message.""" + from run_agent import AIAgent + + class _NestedProviderErr(Exception): + status_code = 400 + body = { + "error": { + "message": { + "type": "Bad Request", + "code": "context_length_exceeded", + "message": ( + "This model's maximum context length is 262144 tokens. " + "Please reduce the length of the messages." + ), + "param": None, + }, + "type": "invalid_request_error", + "param": None, + "code": None, + } + } + + summary = AIAgent._summarize_api_error(_NestedProviderErr("400")) + assert "HTTP 400" in summary + assert "maximum context length is 262144 tokens" in summary + assert "context_length_exceeded" not in summary + + def test_summarize_api_error_idempotent_for_entitlement_hint(): """Decorating twice must not double up the hint.""" from run_agent import AIAgent