Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions agent/conversation_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -1981,6 +1981,10 @@ def _stop_spinner():
# "unknown variant `image_url`, expected `text`".
"unknown variant `image_url`, expected `text`",
"unknown variant image_url, expected text",
# Xiaomi MiMo: rejects requests where text content part is missing
"text is not set",
# Xiaomi MiMo variant with backticks around text
"`text` is not set",
)
_err_lower = _err_body.lower()
_looks_like_image_rejection = any(
Expand Down
6 changes: 4 additions & 2 deletions gateway/platforms/weixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1388,10 +1388,12 @@ async def _process_message(self, message: Dict[str, Any]) -> None:
if message_id and self._dedup.is_duplicate(message_id):
return

# Secondary content-fingerprint dedup for text messages
# Secondary content-fingerprint dedup for text messages.
# Commands (prefixed with /) are always processed — deduplicating them
# silently drops intentionally-repeated slash commands like /approve.
item_list = message.get("item_list") or []
text = _extract_text(item_list)
if text:
if text and not text.strip().startswith("/"):
content_key = f"content:{sender_id}:{hashlib.md5(text.encode()).hexdigest()}"
if self._dedup.is_duplicate(content_key):
logger.debug("[%s] Content-dedup: skipping duplicate message from %s", self.name, sender_id)
Expand Down
14 changes: 14 additions & 0 deletions tests/run_agent/test_image_rejection_fallback.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ class TestImageRejectionPhraseIsolation:
"does not support vision",
"model does not support image",
"image_url'. expected",
# Xiaomi MiMo: rejects requests where text content part is missing
"text is not set",
"`text` is not set",
)

def _matches(self, body: str) -> bool:
Expand Down Expand Up @@ -265,3 +268,14 @@ def test_codex_data_url_rejection_does_not_false_match_other_url_errors(self):
]
for body in bodies:
assert self._matches(body) is False, f"false positive on: {body}"

def test_mimo_text_is_not_set_trips(self):
"""Xiaomi MiMo 'text is not set' — model requires text content part
but the request only has image_url parts. Should trigger image
rejection recovery (strip all images from user messages)."""
bodies = [
"Error code: 400 - {'error': {'code': '400', 'message': 'Param Incorrect', 'param': 'text is not set', 'type': ''}}",
"{'error': {'code': '400', 'message': 'Param Incorrect', 'param': '`text` is not set', 'type': ''}}",
]
for body in bodies:
assert self._matches(body) is True, f"false negative on: {body}"