From b013ed03e539e94466a0aaafc4d67e40cdeb888d Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Tue, 14 Jul 2026 05:31:07 -0700 Subject: [PATCH] fix(moa): scope the non-text placeholder to structured content only Follow-up to the cherry-picked empty-user-turn drop: the placeholder introduced in 8582f35d9 fired for whitespace-only STRING turns too (content=' ' flattens to non-stripping text but isn't in the (None, '', []) exclusion set), fabricating an attachment note for a turn that carried nothing. Gate the placeholder on isinstance(content, list) so only genuinely structured (e.g. image-only) turns get it; empty and whitespace-only string turns now fall through to the drop path. Edge cases verified: trailing empty user turn still ends the view on the synthetic advisory marker; an all-empty transcript degenerates to []. --- agent/moa_loop.py | 6 ++++-- tests/run_agent/test_moa_loop_mode.py | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/agent/moa_loop.py b/agent/moa_loop.py index d8461e921dc2..28707b2a4f14 100644 --- a/agent/moa_loop.py +++ b/agent/moa_loop.py @@ -491,7 +491,7 @@ def _reference_messages(messages: list[dict[str, Any]]) -> list[dict[str, Any]]: if role == "system": continue if role == "user": - if not text.strip() and content not in (None, "", []): + if not text.strip() and isinstance(content, list) and content: # Structured content with no extractable text (e.g. an # image-only turn). Emitting an empty user message would be # dropped/rejected by strict providers (Anthropic 400s on @@ -499,7 +499,9 @@ def _reference_messages(messages: list[dict[str, Any]]) -> list[dict[str, Any]]: # mode), and silently skipping the turn would break # user/assistant alternation in the advisory view. Substitute # a placeholder so the reference knows a non-text turn - # happened. + # happened. Only structured content qualifies — an empty or + # whitespace-only STRING turn carries nothing and is dropped + # below instead. text = "[user sent non-text content (e.g. an image attachment)]" if not text.strip(): # Genuinely empty user turn (content="" / None). It carries diff --git a/tests/run_agent/test_moa_loop_mode.py b/tests/run_agent/test_moa_loop_mode.py index 659f64da7a94..1c1ab8f71eb7 100644 --- a/tests/run_agent/test_moa_loop_mode.py +++ b/tests/run_agent/test_moa_loop_mode.py @@ -1241,3 +1241,27 @@ def test_reference_guidance_appends_text_part_to_decorated_trailing_user(): assert content[0] == marked_part # The guidance rides as a trailing text part outside the cached span. assert content[1] == {"type": "text", "text": "\n\nREFERENCE BLOCK"} + + +def test_reference_messages_drops_whitespace_only_string_user_turn(): + """A whitespace-only STRING user turn is dropped, not placeholdered. + + The non-text placeholder exists for structured content (image-only turns) + where a real turn happened that the reference should know about. A bare + whitespace string carries nothing — emitting it would 400 strict + providers (Kimi/Moonshot 'role user must not be empty'), and + placeholdering it would fabricate an attachment that never existed. + """ + from agent.moa_loop import _reference_messages + + messages = [ + {"role": "user", "content": " "}, + {"role": "assistant", "content": "a"}, + {"role": "user", "content": "real"}, + ] + + view = _reference_messages(messages) + + assert view[0] == {"role": "assistant", "content": "a"} + assert view[-1] == {"role": "user", "content": "real"} + assert all(str(m["content"]).strip() for m in view)