fix(agent): preserve nested API error bodies

This commit is contained in:
LeonSGP43 2026-04-23 09:55:04 +08:00 committed by Teknium
parent 5ab4136631
commit e7c013494d
2 changed files with 46 additions and 13 deletions

View file

@ -1317,19 +1317,25 @@ def _extract_status_code(error: Exception) -> Optional[int]:
def _extract_error_body(error: Exception) -> dict:
"""Extract the structured error body from an SDK exception."""
body = getattr(error, "body", None)
if isinstance(body, dict):
return body
# Some errors have .response.json()
response = getattr(error, "response", None)
if response is not None:
try:
json_body = response.json()
if isinstance(json_body, dict):
return json_body
except Exception:
pass
"""Extract the structured error body from an SDK exception or its cause chain."""
current = error
for _ in range(5): # Match _extract_status_code() traversal depth.
body = getattr(current, "body", None)
if isinstance(body, dict):
return body
# Some errors have .response.json()
response = getattr(current, "response", None)
if response is not None:
try:
json_body = response.json()
if isinstance(json_body, dict):
return json_body
except Exception:
pass
cause = getattr(current, "__cause__", None) or getattr(current, "__context__", None)
if cause is None or cause is current:
break
current = cause
return {}

View file

@ -127,6 +127,18 @@ class TestExtractErrorBody:
e = MockAPIError("fail", body={"error": {"message": "bad"}})
assert _extract_error_body(e) == {"error": {"message": "bad"}}
def test_from_cause_chain_body_attr(self):
inner = MockAPIError(
"inner",
status_code=402,
body={"error": {"message": "Usage limit reached, try again in 5 minutes"}},
)
outer = Exception("outer")
outer.__cause__ = inner
assert _extract_error_body(outer) == {
"error": {"message": "Usage limit reached, try again in 5 minutes"},
}
def test_empty_when_no_body(self):
assert _extract_error_body(Exception("generic")) == {}
@ -300,6 +312,21 @@ class TestClassifyApiError:
assert result.retryable is False
assert result.should_fallback is True
def test_wrapped_402_uses_nested_body_message(self):
inner = MockAPIError(
"inner",
status_code=402,
body={"error": {"message": "Usage limit reached, try again in 5 minutes"}},
)
outer = Exception("outer")
outer.__cause__ = inner
result = classify_api_error(outer)
assert result.reason == FailoverReason.rate_limit
assert result.retryable is True
assert result.message == "Usage limit reached, try again in 5 minutes"
# ── Rate limit ──
def test_429_rate_limit(self):