refactor: remove _nr_to_assistant_message shim + fix flush_memories guard

NormalizedResponse and ToolCall now have backward-compat properties
so the agent loop can read them directly without the shim:

  ToolCall: .type, .function (returns self), .call_id, .response_item_id
  NormalizedResponse: .reasoning_content, .reasoning_details,
                      .codex_reasoning_items

This eliminates the 35-line shim and its 4 call sites in run_agent.py.

Also changes flush_memories guard from hasattr(response, 'choices')
to self.api_mode in ('chat_completions', 'bedrock_converse') so it
works with raw boto3 dicts too.

WS1 items 3+4 of Cycle 2 (#14418).
This commit is contained in:
kshitijk4poor 2026-04-23 14:06:36 +05:30 committed by Teknium
parent f4612785a4
commit 43de1ca8c2
8 changed files with 233 additions and 157 deletions

View file

@ -6766,42 +6766,6 @@ class AIAgent:
cache[mode] = t
return t
@staticmethod
def _nr_to_assistant_message(nr):
"""Convert a NormalizedResponse to the SimpleNamespace shape downstream expects.
This is the single back-compat shim between the transport layer
(NormalizedResponse) and the agent loop (SimpleNamespace with
.content, .tool_calls, .reasoning, .reasoning_content,
.reasoning_details, .codex_reasoning_items, and per-tool-call
.call_id / .response_item_id).
TODO: Remove when downstream code reads NormalizedResponse directly.
"""
tc_list = None
if nr.tool_calls:
tc_list = []
for tc in nr.tool_calls:
tc_ns = SimpleNamespace(
id=tc.id,
type="function",
function=SimpleNamespace(name=tc.name, arguments=tc.arguments),
)
if tc.provider_data:
for key in ("call_id", "response_item_id"):
if tc.provider_data.get(key):
setattr(tc_ns, key, tc.provider_data[key])
tc_list.append(tc_ns)
pd = nr.provider_data or {}
return SimpleNamespace(
content=nr.content,
tool_calls=tc_list or None,
reasoning=nr.reasoning,
reasoning_content=pd.get("reasoning_content"),
reasoning_details=pd.get("reasoning_details"),
codex_reasoning_items=pd.get("codex_reasoning_items"),
)
def _prepare_anthropic_messages_for_api(self, api_messages: list) -> list:
if not any(
isinstance(msg, dict) and self._content_has_image_parts(msg.get("content"))
@ -7503,20 +7467,25 @@ class AIAgent:
]
elif self.api_mode == "anthropic_messages" and not _aux_available:
_tfn = self._get_transport()
_flush_nr = _tfn.normalize_response(response, strip_tool_prefix=self._is_anthropic_oauth)
if _flush_nr and _flush_nr.tool_calls:
_flush_result = _tfn.normalize_response(response, strip_tool_prefix=self._is_anthropic_oauth)
if _flush_result and _flush_result.tool_calls:
tool_calls = [
SimpleNamespace(
id=tc.id, type="function",
function=SimpleNamespace(name=tc.name, arguments=tc.arguments),
) for tc in _flush_nr.tool_calls
) for tc in _flush_result.tool_calls
]
elif hasattr(response, "choices") and response.choices:
elif self.api_mode in ("chat_completions", "bedrock_converse"):
# chat_completions / bedrock — normalize through transport
_flush_cc_nr = self._get_transport().normalize_response(response)
_flush_msg = self._nr_to_assistant_message(_flush_cc_nr)
if _flush_msg.tool_calls:
tool_calls = _flush_msg.tool_calls
_flush_result = self._get_transport().normalize_response(response)
if _flush_result.tool_calls:
tool_calls = _flush_result.tool_calls
elif _aux_available and hasattr(response, "choices") and response.choices:
# Auxiliary client returned OpenAI-shaped response while main
# api_mode is codex/anthropic — extract tool_calls from .choices
_aux_msg = response.choices[0].message
if hasattr(_aux_msg, "tool_calls") and _aux_msg.tool_calls:
tool_calls = _aux_msg.tool_calls
for tc in tool_calls:
if tc.function.name == "memory":
@ -8582,12 +8551,12 @@ class AIAgent:
is_oauth=self._is_anthropic_oauth,
preserve_dots=self._anthropic_preserve_dots())
summary_response = self._anthropic_messages_create(_ant_kw)
_sum_nr = _tsum.normalize_response(summary_response, strip_tool_prefix=self._is_anthropic_oauth)
final_response = (_sum_nr.content or "").strip()
_summary_result = _tsum.normalize_response(summary_response, strip_tool_prefix=self._is_anthropic_oauth)
final_response = (_summary_result.content or "").strip()
else:
summary_response = self._ensure_primary_openai_client(reason="iteration_limit_summary").chat.completions.create(**summary_kwargs)
_sum_cc_nr = self._get_transport().normalize_response(summary_response)
final_response = (_sum_cc_nr.content or "").strip()
_summary_result = self._get_transport().normalize_response(summary_response)
final_response = (_summary_result.content or "").strip()
if final_response:
if "<think>" in final_response:
@ -8612,8 +8581,8 @@ class AIAgent:
max_tokens=self.max_tokens, reasoning_config=self.reasoning_config,
preserve_dots=self._anthropic_preserve_dots())
retry_response = self._anthropic_messages_create(_ant_kw2)
_retry_nr = _tretry.normalize_response(retry_response, strip_tool_prefix=self._is_anthropic_oauth)
final_response = (_retry_nr.content or "").strip()
_retry_result = _tretry.normalize_response(retry_response, strip_tool_prefix=self._is_anthropic_oauth)
final_response = (_retry_result.content or "").strip()
else:
summary_kwargs = {
"model": self.model,
@ -8627,8 +8596,8 @@ class AIAgent:
summary_kwargs["extra_body"] = summary_extra_body
summary_response = self._ensure_primary_openai_client(reason="iteration_limit_summary_retry").chat.completions.create(**summary_kwargs)
_retry_cc_nr = self._get_transport().normalize_response(summary_response)
final_response = (_retry_cc_nr.content or "").strip()
_retry_result = self._get_transport().normalize_response(summary_response)
final_response = (_retry_result.content or "").strip()
if final_response:
if "<think>" in final_response:
@ -9657,13 +9626,13 @@ class AIAgent:
elif self.api_mode == "bedrock_converse":
# Bedrock response already normalized at dispatch — use transport
_bt_fr = self._get_transport()
_bt_fr_nr = _bt_fr.normalize_response(response)
finish_reason = _bt_fr_nr.finish_reason
_bedrock_result = _bt_fr.normalize_response(response)
finish_reason = _bedrock_result.finish_reason
else:
_cc_fr = self._get_transport()
_cc_fr_nr = _cc_fr.normalize_response(response)
finish_reason = _cc_fr_nr.finish_reason
assistant_message = self._nr_to_assistant_message(_cc_fr_nr)
_finish_result = _cc_fr.normalize_response(response)
finish_reason = _finish_result.finish_reason
assistant_message = _finish_result
if self._should_treat_stop_as_truncated(
finish_reason,
assistant_message,
@ -9688,12 +9657,12 @@ class AIAgent:
_trunc_msg = None
_trunc_transport = self._get_transport()
if self.api_mode == "anthropic_messages":
_trunc_nr = _trunc_transport.normalize_response(
_trunc_result = _trunc_transport.normalize_response(
response, strip_tool_prefix=self._is_anthropic_oauth
)
else:
_trunc_nr = _trunc_transport.normalize_response(response)
_trunc_msg = self._nr_to_assistant_message(_trunc_nr)
_trunc_result = _trunc_transport.normalize_response(response)
_trunc_msg = _trunc_result
_trunc_content = getattr(_trunc_msg, "content", None) if _trunc_msg else None
_trunc_has_tool_calls = bool(getattr(_trunc_msg, "tool_calls", None)) if _trunc_msg else False
@ -10928,9 +10897,9 @@ class AIAgent:
_normalize_kwargs = {}
if self.api_mode == "anthropic_messages":
_normalize_kwargs["strip_tool_prefix"] = self._is_anthropic_oauth
_nr = _transport.normalize_response(response, **_normalize_kwargs)
assistant_message = self._nr_to_assistant_message(_nr)
finish_reason = _nr.finish_reason
normalized = _transport.normalize_response(response, **_normalize_kwargs)
assistant_message = normalized
finish_reason = normalized.finish_reason
# Normalize content to string — some OpenAI-compatible servers
# (llama-server, etc.) return content as a dict or list instead