fix(agent): surface Codex commentary items as interim messages

This commit is contained in:
David Robertson 2026-07-07 19:37:49 +00:00 committed by Teknium
parent 73ad9136b7
commit 136ade2ed5
2 changed files with 86 additions and 0 deletions

View file

@ -4754,6 +4754,45 @@ class AIAgent:
)
return bool(streamed) and streamed == visible_content
def _extract_codex_interim_visible_text(self, assistant_msg: Dict[str, Any]) -> str:
"""Extract visible Codex Responses commentary text.
Codex Responses can keep user-facing mid-turn narration as structured
``phase=commentary`` message items while final answer text remains in
assistant ``content``. Non-streaming gateway surfaces need that
commentary through the interim assistant callback before tool calls run.
``phase=analysis`` remains hidden because it is provider scratchpad.
"""
items = assistant_msg.get("codex_message_items")
if not isinstance(items, list):
return ""
parts: List[str] = []
for item in items:
if not isinstance(item, dict):
continue
if item.get("type") != "message":
continue
phase = item.get("phase")
if not isinstance(phase, str) or phase.strip().lower() != "commentary":
continue
content_parts = item.get("content")
if not isinstance(content_parts, list):
continue
for part in content_parts:
if not isinstance(part, dict):
continue
if part.get("type") != "output_text":
continue
text = part.get("text")
if isinstance(text, str) and text.strip():
parts.append(text.strip())
visible = "\n\n".join(parts).strip()
if visible:
visible = self._strip_think_blocks(visible).strip()
return visible
def _emit_interim_assistant_message(self, assistant_msg: Dict[str, Any]) -> None:
"""Surface a real mid-turn assistant commentary message to the UI layer."""
cb = getattr(self, "interim_assistant_callback", None)
@ -4761,6 +4800,8 @@ class AIAgent:
return
content = assistant_msg.get("content")
visible = self._strip_think_blocks(content or "").strip()
if not visible or visible == "(empty)":
visible = self._extract_codex_interim_visible_text(assistant_msg)
if not visible or visible == "(empty)":
return
already_streamed = self._interim_content_was_streamed(visible)

View file

@ -2100,6 +2100,51 @@ def test_interim_commentary_preserves_assistant_content(monkeypatch):
assert "I'll inspect the repo structure first." in observed["text"]
def test_interim_commentary_uses_codex_commentary_items_when_content_is_empty(monkeypatch):
"""Codex Responses stores commentary phase text outside content.
The gateway still needs that visible mid-turn narration to flow through the
interim assistant callback; otherwise Discord with streaming disabled gets
no natural progress commentary before tools run. Analysis/final-answer items
stay hidden from interim delivery.
"""
agent = _build_agent(monkeypatch)
observed = {}
agent.interim_assistant_callback = lambda text, *, already_streamed=False: observed.update(
{"text": text, "already_streamed": already_streamed}
)
agent._emit_interim_assistant_message({
"role": "assistant",
"content": "",
"codex_message_items": [
{
"type": "message",
"role": "assistant",
"phase": "analysis",
"content": [{"type": "output_text", "text": "Need inspect files."}],
},
{
"type": "message",
"role": "assistant",
"phase": "commentary",
"content": [{"type": "output_text", "text": "I'll inspect the repo first."}],
},
{
"type": "message",
"role": "assistant",
"phase": "final_answer",
"content": [{"type": "output_text", "text": "Done."}],
},
],
})
assert observed == {
"text": "I'll inspect the repo first.",
"already_streamed": False,
}
def test_stream_delta_strips_leaked_memory_context(monkeypatch):
agent = _build_agent(monkeypatch)
observed = []