fix: shrink images to reported provider dimension limit (#45979)

Parse provider-reported image pixel ceilings so many-image Anthropic requests can recover by shrinking Retina screenshots below the stricter limit instead of retrying the same rejected payload.
This commit is contained in:
brooklyn! 2026-06-14 01:07:43 -05:00 committed by GitHub
parent cdf30a7ac6
commit 4b5ba112ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 128 additions and 12 deletions

View file

@ -18,8 +18,11 @@ payload rewriter.
from __future__ import annotations
import base64
import sys
from types import SimpleNamespace
from agent.conversation_loop import _image_error_max_dimension
from agent.error_classifier import FailoverReason, classify_api_error
@ -79,6 +82,21 @@ class TestImageTooLargeClassification:
result = classify_api_error(err, provider="anthropic", model="claude-sonnet-4-6")
assert result.reason == FailoverReason.context_overflow
def test_anthropic_many_image_dimension_limit(self):
"""OpenRouter-wrapped Anthropic many-image limits recover via shrink."""
err = _FakeApiError(
status_code=400,
message=(
"messages.21.content.43.image.source.base64.data: At least one "
"of the image dimensions exceed max allowed size for many-image "
"requests: 2000 pixels"
),
)
result = classify_api_error(err, provider="openrouter", model="anthropic/claude-opus-4.8")
assert result.reason == FailoverReason.image_too_large
assert result.retryable is True
assert _image_error_max_dimension(err) == 2000
# ─── Shrink helper ───────────────────────────────────────────────────────────
@ -90,6 +108,27 @@ def _big_png_data_url(size_kb: int) -> str:
return "data:image/png;base64," + base64.b64encode(raw).decode("ascii")
def _install_fake_pillow(monkeypatch, size: tuple[int, int]) -> None:
"""Install the tiny subset of Pillow used by the shrink preflight."""
class _FakeImage:
def __init__(self):
self.size = size
def __enter__(self):
return self
def __exit__(self, *exc):
return False
class _FakeImageModule:
@staticmethod
def open(_data):
return _FakeImage()
monkeypatch.setitem(sys.modules, "PIL", SimpleNamespace(Image=_FakeImageModule))
monkeypatch.setitem(sys.modules, "PIL.Image", _FakeImageModule)
def _make_agent():
"""Build a bare AIAgent for method-level testing, no provider setup."""
from run_agent import AIAgent
@ -163,6 +202,38 @@ class TestShrinkImagePartsHelper:
assert changed is True
assert msgs[0]["content"][1]["image_url"]["url"] == shrunk
def test_many_image_dimension_limit_rewritten(self, monkeypatch):
"""A 2000px many-image rejection must shrink images below 8000px."""
agent = _make_agent()
_install_fake_pillow(monkeypatch, (2501, 100))
oversized_for_many = _big_png_data_url(100)
shrunk = "data:image/jpeg;base64," + "M" * 1000
seen = {}
def _fake_resize(path, mime_type=None, max_base64_bytes=None, max_dimension=None):
seen["max_dimension"] = max_dimension
return shrunk
monkeypatch.setattr(
"tools.vision_tools._resize_image_for_vision",
_fake_resize,
raising=False,
)
msgs = [{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": oversized_for_many}},
],
}]
changed = agent._try_shrink_image_parts_in_messages(
msgs,
max_dimension=2000,
)
assert changed is True
assert seen["max_dimension"] == 2000
assert msgs[0]["content"][0]["image_url"]["url"] == shrunk
def test_oversized_input_image_string_shape_rewritten(self, monkeypatch):
"""OpenAI Responses shape: {type: input_image, image_url: "data:..."}."""
agent = _make_agent()