fix(vision): cap pixel dimensions proactively at embed time + declare Pillow

Follow-up to the salvaged #37727. That PR fixed the reactive recovery path
(classifier + post-failure shrinker) but left the PROACTIVE embed-time guard
in vision_tools byte-only — a tall small-byte screenshot (e.g. 1200x12000 at
0.06 MB) still baked into immutable history un-resized, relying on a failed
round-trip to trigger reactive shrink.

- vision_tools: add _image_exceeds_dimension() + _EMBED_MAX_DIMENSION (7900px);
  the embed-time cap now fires on bytes OR pixels and passes max_dimension to
  the resizer, so tall small-byte images are shrunk before they're embedded.
- vision_tools: best-effort lazy-install of Pillow (tool.vision) in the resize
  ImportError fallback so the soft dep self-heals (respects allow_lazy_installs).
- error_classifier: add two more Anthropic dimension-cap wording variants.
- pyproject + lazy_deps: declare Pillow as the [vision] extra / tool.vision
  lazy dep (it was undeclared everywhere; without it ALL resize recovery no-ops).
- tests: cover _image_exceeds_dimension (tall/small/edge/no-Pillow/corrupt).

Co-authored-by: kyssta-exe <kyssta-exe@users.noreply.github.com>
This commit is contained in:
teknium1
2026-06-04 06:16:45 -07:00
committed by Teknium
co-authored by kyssta-exe
parent 6bdbe30763
commit dd4ba4c2c4
7 changed files with 203 additions and 11 deletions
+8
View File
@@ -127,6 +127,14 @@ pty = [
# without pulling in extra packages.
]
honcho = ["honcho-ai==2.0.1"]
# Image resize recovery for the vision tools. Pillow is a soft dependency:
# vision_tools / conversation_compression degrade gracefully without it (they
# log and skip the resize), but without it the byte AND pixel-dimension shrink
# paths silently no-op, so an oversized image (>5 MB or >8000px) bakes into
# immutable history and bricks the session on Anthropic's non-retryable 400.
# Declared here so packagers (Nix, Homebrew) ship it with [all] and so
# `pip install hermes-agent[vision]` / the lazy-install path can resolve it.
vision = ["Pillow==12.2.0"]
# CVE-2026-48710 (BadHost): Starlette is pulled transitively by mcp's
# sse-starlette / HTTP-SSE stack (and by fastapi in the `web` extra). Before
# 1.0.1, a malformed Host header makes `request.url.path` desync from the path