From 538173f679f7d182bc530dc073a9ae2f6d93d2a9 Mon Sep 17 00:00:00 2001 From: annguyenNous Date: Sun, 7 Jun 2026 21:50:35 +0700 Subject: [PATCH] fix(codex): route commentary-phase preamble text to reasoning channel (fixes #41293) GPT-5.x models on the Codex Responses API emit short pre-tool-call "preamble" text as message items with phase="commentary". Previously, _normalize_codex_response() added ALL message items to content_parts regardless of phase, causing commentary text to leak as visible assistant content on chat gateways. Fix: when normalized_phase is "commentary" or "analysis", route the message text to reasoning_parts instead of content_parts. This keeps preamble/internal planning in the reasoning channel where it belongs. Fixes NousResearch/hermes-agent#41293 --- agent/codex_responses_adapter.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/agent/codex_responses_adapter.py b/agent/codex_responses_adapter.py index cda1f87be12..4d138ce6e63 100644 --- a/agent/codex_responses_adapter.py +++ b/agent/codex_responses_adapter.py @@ -1176,12 +1176,17 @@ def _normalize_codex_response( saw_final_answer_phase = True message_text = _extract_responses_message_text(item) if message_text: - # Responses ``commentary``/``analysis`` phase text is scratch/tool - # preamble for the model/provider protocol, not user-visible final - # answer text. Preserve the exact message item for replay/cache - # continuity, but do not expose it as assistant content where the - # gateway/CLI/interim callbacks can leak it to the user. - if not is_commentary_phase: + # Responses ``commentary``/``analysis`` phase text is mid-turn + # preamble/progress narration, never the turn's final answer + # (Codex CLI excludes it from last-message extraction; issues + # #24933 / #41293). Keep it out of assistant content so it + # can't be concatenated into — or leak as — the final response, + # but surface it through the reasoning channel so the CLI/ + # gateway display it like thinking text. The exact message + # item is still preserved below for replay/cache continuity. + if is_commentary_phase: + reasoning_parts.append(message_text) + else: content_parts.append(message_text) raw_message_item: Dict[str, Any] = { "type": "message",