From b3f4f50771c74d16bbf0e6dfad4d547a9226c327 Mon Sep 17 00:00:00 2001 From: Koduri Mahesh Bhushan Chowdary Date: Sat, 1 Aug 2026 11:15:03 +0200 Subject: [PATCH] fix(agent): classify "media exceeds size limit" as image_too_large MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MiniMax's Anthropic-compatible endpoint rejects an oversized native image part with "media exceeds size limit: max 10485760 bytes (2013)" — no occurrence of the word "image", so none of _IMAGE_TOO_LARGE_PATTERNS matched. The 400 fell through to _REQUEST_VALIDATION_PATTERNS (the body is type: invalid_request_error) and classified as format_error / non-retryable. That skipped the image-shrink recovery in conversation_loop, which is gated on FailoverReason.image_too_large. Because the oversized part is already baked into history as a tool_result image block, and the context compressor rewrites text but not image data, every later turn re-sent the same bytes and failed identically — the session stayed dead until the user forked it. Match on the "media" fragment, mirroring the existing "image exceeds" entry so reworded vendor variants are caught too. A non-image media rejection routed here is safe: the shrink pass finds no image parts, returns False, and the caller surfaces the original error unchanged. Fixes #76039 --- agent/error_classifier.py | 10 ++++++ tests/run_agent/test_image_shrink_recovery.py | 31 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/agent/error_classifier.py b/agent/error_classifier.py index b6358161bb..7bcfbaf0cb 100644 --- a/agent/error_classifier.py +++ b/agent/error_classifier.py @@ -284,6 +284,16 @@ _IMAGE_TOO_LARGE_PATTERNS = [ "image dimensions exceed", # Anthropic: "image dimensions exceed max allowed size: 8000 pixels" "dimensions exceed max allowed size", # Anthropic dimension-cap (wording variant) "max allowed size: 8000", # Anthropic dimension-cap (explicit pixel ceiling) + # Vendors that reject the same oversized image without using the word + # "image". MiniMax's Anthropic-compatible endpoint returns + # "media exceeds size limit: max 10485760 bytes (2013)" for a native + # image part above its 10 MB ceiling (#76039). Matched on the "media" + # fragment to mirror "image exceeds" above and catch reworded variants. + # A non-image media rejection (audio/video) that lands here is safe: the + # shrink pass finds no image parts, returns False, and the caller + # surfaces the original error unchanged. + "media exceeds", + "media too large", # "request_too_large" on a request known to contain an image → image is # the likely culprit; we still try the shrink path before giving up. ] diff --git a/tests/run_agent/test_image_shrink_recovery.py b/tests/run_agent/test_image_shrink_recovery.py index 2ff4519273..0c2984b4e2 100644 --- a/tests/run_agent/test_image_shrink_recovery.py +++ b/tests/run_agent/test_image_shrink_recovery.py @@ -53,8 +53,39 @@ class TestImageTooLargeClassification: assert result.reason == FailoverReason.image_too_large assert result.retryable is True + def test_minimax_400_media_exceeds_message(self): + """A vendor that rejects an oversized image without the word "image". + MiniMax's Anthropic-compatible endpoint returns this for a native + image part over its 10 MB ceiling. It used to fall through to + _REQUEST_VALIDATION_PATTERNS and classify as format_error / + non-retryable, which skipped shrink recovery and left the oversized + part baked into replayed history — every later turn re-sent the same + bytes and failed identically (#76039). + """ + err = _FakeApiError( + status_code=400, + message="media exceeds size limit: max 10485760 bytes (2013)", + body={ + "type": "error", + "error": { + "type": "invalid_request_error", + "message": "media exceeds size limit: max 10485760 bytes (2013)", + }, + }, + ) + result = classify_api_error(err, provider="minimax", model="MiniMax-M3") + assert result.reason == FailoverReason.image_too_large + assert result.retryable is True + def test_unrelated_400_still_not_image_too_large(self): + """The new "media" patterns must not widen into ordinary 400s.""" + err = _FakeApiError( + status_code=400, + message="messages: unexpected role \"tool\" at index 3", + ) + result = classify_api_error(err, provider="minimax", model="MiniMax-M3") + assert result.reason != FailoverReason.image_too_large