fix(run_agent): use safe attribute extraction instead of vars() on response objects

vars() raises TypeError on objects that don't expose __dict__ — this
includes pydantic v2 models and certain OpenAI SDK response types. The
crash occurred in the debug logging path when provider detection fell
back to scanning response attributes.

Replace vars(response) with a three-way fallback: check __dict__ first,
then model_dump() for pydantic models, then an empty dict, wrapped in a
try/except for any remaining edge cases.

Fixes #6133
This commit is contained in:
UGBOMEH OGOCHUKWU WILLIAMS 2026-04-19 15:07:37 +01:00
parent a521005fe5
commit c1191e78cb
2 changed files with 46 additions and 1 deletions

View file

@ -9499,7 +9499,16 @@ class AIAgent:
# Check for x-openrouter-provider or similar metadata
if provider_name == "Unknown" and response:
# Log all response attributes for debugging
resp_attrs = {k: str(v)[:100] for k, v in vars(response).items() if not k.startswith('_')}
try:
if hasattr(response, "__dict__"):
_resp_dict = response.__dict__
elif hasattr(response, "model_dump"):
_resp_dict = response.model_dump()
else:
_resp_dict = {}
resp_attrs = {k: str(v)[:100] for k, v in _resp_dict.items() if not k.startswith('_')}
except (TypeError, AttributeError):
resp_attrs = {}
if self.verbose_logging:
logging.debug(f"Response attributes for invalid response: {resp_attrs}")

View file

@ -1310,3 +1310,39 @@ def test_preflight_codex_input_deduplicates_reasoning_ids(monkeypatch):
# IDs must be stripped — with store=False the API 404s on id lookups.
for it in reasoning_items:
assert "id" not in it
# ---------------------------------------------------------------------------
# Issue #6133 — vars() on response objects that lack __dict__ raises TypeError.
# The fix must use a safe fallback (hasattr __dict__ / model_dump) instead.
# ---------------------------------------------------------------------------
def test_run_conversation_slots_response_does_not_raise(monkeypatch):
"""Response objects that define __slots__ (and therefore have no __dict__)
must not crash the invalid-response debug logging path."""
class _SlotsResponse:
__slots__ = ("output", "output_text", "status")
def __init__(self):
self.output = []
self.output_text = None
self.status = "completed"
agent = _build_agent(monkeypatch)
calls = {"n": 0}
def _fake_api(api_kwargs):
calls["n"] += 1
if calls["n"] == 1:
return _SlotsResponse()
return _codex_message_response("Recovered")
monkeypatch.setattr(agent, "_interruptible_api_call", _fake_api)
result = agent.run_conversation("ping")
assert calls["n"] >= 2
assert result["completed"] is True
assert result["final_response"] == "Recovered"