diff --git a/agent/codex_runtime.py b/agent/codex_runtime.py index 38ba98f5835..1cf48ec1705 100644 --- a/agent/codex_runtime.py +++ b/agent/codex_runtime.py @@ -605,9 +605,10 @@ def _consume_codex_event_stream( _raise_stream_error(event) # Track the phase of the active streamed message item. Codex/Harmony - # ``commentary``/``analysis`` text is protocol/tool preamble, not the - # final answer. We still collect completed output items for replay, but - # suppress those deltas from live user-visible callbacks. + # ``commentary``/``analysis`` text is mid-turn preamble/progress + # narration, never the final answer. We still collect completed output + # items for replay, but route those deltas to the reasoning callback so + # they display like thinking text instead of assistant content. if event_type == "response.output_item.added": item = _event_field(event, "item") item_type = _item_field(item, "type", "") @@ -623,7 +624,15 @@ def _consume_codex_event_stream( if "output_text.delta" in event_type or event_type == "response.output_text.delta": delta_text = _event_field(event, "delta", "") is_commentary_delta = active_message_phase in {"commentary", "analysis"} - if delta_text and not is_commentary_delta: + if delta_text and is_commentary_delta: + # Commentary streams through the reasoning channel, not the + # visible answer stream (and stays out of output_text). + if on_reasoning_delta is not None: + try: + on_reasoning_delta(delta_text) + except Exception: + logger.debug("Codex stream on_reasoning_delta raised", exc_info=True) + elif delta_text: collected_text_deltas.append(delta_text) if not has_tool_calls: if not first_delta_fired: diff --git a/scripts/release.py b/scripts/release.py index d6f0d1b3085..a908eb92d82 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -489,6 +489,7 @@ AUTHOR_MAP = { "Rivuza@users.noreply.github.com": "Rivuza", "annguyenNous@users.noreply.github.com": "annguyenNous", "285874597+annguyenNous@users.noreply.github.com": "annguyenNous", + "perkintahmaz50@gmail.com": "devatnull", "kylekahraman@users.noreply.github.com": "kylekahraman", "130975919+kylekahraman@users.noreply.github.com": "kylekahraman", "seppe@fushia.be": "seppegadeyne", diff --git a/tests/run_agent/test_run_agent_codex_responses.py b/tests/run_agent/test_run_agent_codex_responses.py index 0b9f7daa06f..0c24adc4ed6 100644 --- a/tests/run_agent/test_run_agent_codex_responses.py +++ b/tests/run_agent/test_run_agent_codex_responses.py @@ -629,7 +629,7 @@ def test_run_codex_stream_returns_collected_items_when_stream_ends_without_termi assert response.output == [output_item] -def test_consume_codex_stream_suppresses_commentary_phase_deltas(monkeypatch): +def test_consume_codex_stream_routes_commentary_phase_deltas_to_reasoning(monkeypatch): from agent.codex_runtime import _consume_codex_event_stream commentary_item = SimpleNamespace( @@ -646,6 +646,7 @@ def test_consume_codex_stream_suppresses_commentary_phase_deltas(monkeypatch): arguments="{}", ) streamed = [] + reasoning_streamed = [] response = _consume_codex_event_stream( _FakeCreateStream([ @@ -665,9 +666,11 @@ def test_consume_codex_stream_suppresses_commentary_phase_deltas(monkeypatch): ]), model="gpt-5-codex", on_text_delta=streamed.append, + on_reasoning_delta=reasoning_streamed.append, ) assert streamed == [] + assert reasoning_streamed == ["I’ll call the tool now."] assert response.output == [commentary_item, function_item] assert response.output_text == "" @@ -1635,6 +1638,7 @@ def test_normalize_codex_response_marks_commentary_only_message_as_incomplete(mo assert finish_reason == "incomplete" assert (assistant_message.content or "") == "" + assert "inspect the repository" in (assistant_message.reasoning or "") assert assistant_message.codex_message_items assert assistant_message.codex_message_items[0]["phase"] == "commentary" assert "inspect the repository" in assistant_message.codex_message_items[0]["content"][0]["text"] @@ -1651,6 +1655,7 @@ def test_normalize_codex_response_does_not_fallback_to_output_text_for_commentar assert finish_reason == "incomplete" assert (assistant_message.content or "") == "" + assert "call the tool" in (assistant_message.reasoning or "") assert assistant_message.codex_message_items[0]["phase"] == "commentary" def test_normalize_codex_response_final_answer_overrides_top_level_incomplete(monkeypatch):