fix(codex): keep GitHub/Copilot Responses on the reasoning-only continuation path

Follow-up to the salvaged #64449: the status-trusting branch flipped
github_responses to 'stop' alongside unknown relays. Copilot fronts the
same OpenAI model family as codex_backend and shows the same
reasoning-only 'still thinking' degeneration, so it stays on the
continuation path. Only unrecognized (other:*) backends trust
response.status='completed' as terminal.
This commit is contained in:
Teknium 2026-07-14 21:47:41 -07:00
parent 306a3774b6
commit 01b7609252
2 changed files with 30 additions and 6 deletions

View file

@ -1382,10 +1382,11 @@ def _normalize_codex_response(
# Response contains only reasoning (encrypted thinking state and/or
# human-readable summary) with no visible content or tool calls.
#
# For known Codex/xAI backends, reasoning-only with status="completed"
# means "the model is still thinking and needs another turn" — treat
# it as incomplete so the Codex continuation path retries instead of
# falling into the empty-content retry loop.
# For the specially-handled backends (Codex, xAI, GitHub/Copilot),
# reasoning-only with status="completed" means "the model is still
# thinking and needs another turn" — treat it as incomplete so the
# Codex continuation path retries instead of falling into the
# empty-content retry loop.
#
# For all other backends (other:<base_url>, etc.), trust the provider's
# own response.status signal. When status == "completed" and no items
@ -1393,7 +1394,11 @@ def _normalize_codex_response(
# state — forcing "incomplete" causes multi-minute stalls as the
# continuation path re-issues calls (3 retries × up to 240s each).
# See https://github.com/NousResearch/hermes-agent/issues/64434
if response_status == "completed" and issuer_kind not in ("codex_backend", "xai_responses"):
if response_status == "completed" and issuer_kind not in (
"codex_backend",
"xai_responses",
"github_responses",
):
finish_reason = "stop"
else:
finish_reason = "incomplete"

View file

@ -2414,7 +2414,7 @@ def test_normalize_codex_response_reasoning_only_completed_is_stop_without_issue
assert assistant_message.content == ""
def test_normalize_codex_response_reasoning_only_is_stop_for_xai_backend(monkeypatch):
def test_normalize_codex_response_reasoning_only_stays_incomplete_for_xai_backend(monkeypatch):
"""xAI backend also preserves incomplete for reasoning-only (same as Codex)."""
agent = _build_agent(monkeypatch)
from agent.codex_responses_adapter import _normalize_codex_response
@ -2427,6 +2427,25 @@ def test_normalize_codex_response_reasoning_only_is_stop_for_xai_backend(monkeyp
assert assistant_message.content == ""
def test_normalize_codex_response_reasoning_only_stays_incomplete_for_github_backend(monkeypatch):
"""GitHub/Copilot Responses backend preserves incomplete for reasoning-only.
Copilot fronts the same OpenAI model family as codex_backend and exhibits
the same reasoning-only "still thinking" degeneration mode, so it must
stay on the continuation path only unrecognized (other:*) backends
trust response.status='completed' as terminal.
"""
agent = _build_agent(monkeypatch)
from agent.codex_responses_adapter import _normalize_codex_response
response = _codex_reasoning_only_response()
assistant_message, finish_reason = _normalize_codex_response(
response, issuer_kind="github_responses"
)
assert finish_reason == "incomplete"
assert assistant_message.content == ""
def test_normalize_codex_response_reasoning_with_content_is_stop(monkeypatch):
"""If a response has both reasoning and message content, it should still be 'stop'."""
agent = _build_agent(monkeypatch)