mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-21 16:18:55 +00:00
fix(agent): surface Codex commentary items as interim messages
This commit is contained in:
parent
73ad9136b7
commit
136ade2ed5
2 changed files with 86 additions and 0 deletions
41
run_agent.py
41
run_agent.py
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue