fix(codex-responses): let final_answer complete top-level incomplete responses

This commit is contained in:
briandevans 2026-06-13 13:31:26 -07:00 committed by Teknium
parent 7c0605bf22
commit 7d11fa4e9e
2 changed files with 107 additions and 1 deletions

View file

@ -154,6 +154,22 @@ def _codex_ack_message_response(text: str):
)
def _codex_final_answer_with_top_level_incomplete_response(text: str):
return SimpleNamespace(
output=[
SimpleNamespace(
type="message",
phase="final_answer",
status="completed",
content=[SimpleNamespace(type="output_text", text=text)],
)
],
usage=SimpleNamespace(input_tokens=4, output_tokens=2, total_tokens=6),
status="incomplete",
model="gpt-5.4",
)
class _FakeCreateStream:
"""Iterable-only fake for ``responses.create(stream=True)`` outputs.
@ -1351,6 +1367,92 @@ def test_normalize_codex_response_marks_commentary_only_message_as_incomplete(mo
assert "inspect the repository" in (assistant_message.content or "")
def test_normalize_codex_response_final_answer_overrides_top_level_incomplete(monkeypatch):
from agent.codex_responses_adapter import _normalize_codex_response
assistant_message, finish_reason = _normalize_codex_response(
_codex_final_answer_with_top_level_incomplete_response(
"Briefly:\n\n- I'm Ramsay, your assistant."
)
)
assert finish_reason == "stop"
assert "Ramsay" in (assistant_message.content or "")
def test_normalize_codex_response_top_level_incomplete_without_final_answer_stays_incomplete(monkeypatch):
from agent.codex_responses_adapter import _normalize_codex_response
response = SimpleNamespace(
output=[
SimpleNamespace(
type="message",
status="completed",
content=[SimpleNamespace(type="output_text", text="Partial...")],
)
],
usage=SimpleNamespace(input_tokens=4, output_tokens=2, total_tokens=6),
status="incomplete",
model="gpt-5.4",
)
_, finish_reason = _normalize_codex_response(response)
assert finish_reason == "incomplete"
@pytest.mark.parametrize("top_level_status", ["queued", "in_progress"])
def test_normalize_codex_response_final_answer_does_not_override_streaming_status(
monkeypatch, top_level_status
):
from agent.codex_responses_adapter import _normalize_codex_response
response = SimpleNamespace(
output=[
SimpleNamespace(
type="message",
phase="final_answer",
status="completed",
content=[SimpleNamespace(type="output_text", text="Interim answer.")],
)
],
usage=SimpleNamespace(input_tokens=4, output_tokens=2, total_tokens=6),
status=top_level_status,
model="gpt-5.4",
)
_, finish_reason = _normalize_codex_response(response)
assert finish_reason == "incomplete"
def test_normalize_codex_response_final_answer_does_not_override_per_item_in_progress(monkeypatch):
from agent.codex_responses_adapter import _normalize_codex_response
response = SimpleNamespace(
output=[
SimpleNamespace(
type="message",
phase="final_answer",
status="completed",
content=[SimpleNamespace(type="output_text", text="Partial final.")],
),
SimpleNamespace(
type="message",
status="in_progress",
content=[SimpleNamespace(type="output_text", text="")],
),
],
usage=SimpleNamespace(input_tokens=4, output_tokens=2, total_tokens=6),
status="completed",
model="gpt-5.4",
)
_, finish_reason = _normalize_codex_response(response)
assert finish_reason == "incomplete"
def test_normalize_codex_response_preserves_message_status_for_replay(monkeypatch):
"""Incomplete Codex output messages must not be replayed as completed."""
agent = _build_agent(monkeypatch)