From 4ac8c7546be281a87113debafd7ab5ceb9058c5f Mon Sep 17 00:00:00 2001
From: teknium1 <127238744+teknium1@users.noreply.github.com>
Date: Sat, 4 Jul 2026 14:11:30 -0700
Subject: [PATCH] fix(vision): mkdir converted-PNG output dir; wire SVG
pass-through to rasterizer; AUTHOR_MAP for #52688
- _normalize_to_supported_image: ensure cache/vision exists before writing
the converted PNG (fresh HERMES_HOME had no dir -> FileNotFoundError).
- resolver: SVG passes through as image/svg+xml instead of erroring; the
call sites rasterize to PNG via the salvaged normalize step (cairosvg /
svglib / rsvg-convert / inkscape, best-effort with actionable error).
- normalization offloaded via asyncio.to_thread at both call sites.
- tests: resolver pass-through + rasterization + no-converter error paths.
- AUTHOR_MAP: jonathan@mintrx.com -> JAlmanzarMint (PR #52688 salvage).
---
scripts/release.py | 1 +
tests/tools/test_image_source.py | 37 ++++++++++++++++++++++++++++++++
tools/vision_tools.py | 7 +++---
3 files changed, 41 insertions(+), 4 deletions(-)
diff --git a/scripts/release.py b/scripts/release.py
index a4823fad15a..e0d4d4fb10d 100755
--- a/scripts/release.py
+++ b/scripts/release.py
@@ -45,6 +45,7 @@ ACP_REGISTRY_MANIFEST = REPO_ROOT / "acp_registry" / "agent.json"
# Auto-extracted from noreply emails + manual overrides
AUTHOR_MAP = {
+ "jonathan@mintrx.com": "JAlmanzarMint", # PR #52688 salvage (vision: rasterize SVG / re-encode unsupported raster formats to PNG before embedding), folded into #57890
"al3060388206@gmail.com": "ooiuuii", # PR #58466/#58377 salvage (redact: fireworks fw-/fpk_ prefixes; telegram: redact bot tokens out of transport error strings)
"yingwaizhiying@gmail.com": "msh01", # PR #58250 salvage (telegram: wall-clock init timeout via daemon-thread deadline + abandon the shielded initialize task on timeout so the retry ladder advances instead of hanging on attempt 1/8 under s6 supervision; #58236). Also covers PR #58276 salvage (compression: preserve a real user turn after compaction; #55677).
"danilo@falcao.org": "danilofalcao", # PR #56674 salvage (update: skip unsupported platform.matrix lazy refresh on native Windows — python-olm has no Windows wheel)
diff --git a/tests/tools/test_image_source.py b/tests/tools/test_image_source.py
index b54f8103659..e59ec9e98f3 100644
--- a/tests/tools/test_image_source.py
+++ b/tests/tools/test_image_source.py
@@ -223,3 +223,40 @@ class TestExecReadSafety:
with pytest.raises(isrc.SourceNotFound):
await isrc.resolve_image_source(
"/workspace/nope.png", isrc.ResolveContext(task_id="t1"))
+
+
+class TestSvgNormalization:
+ """SVG resolves end-to-end: the resolver passes it through as
+ image/svg+xml and the vision call sites rasterize it to PNG via
+ _normalize_to_supported_image (PR #52688, folded in)."""
+
+ @pytest.mark.asyncio
+ async def test_svg_rasterized_when_converter_available(self, tmp_path, monkeypatch):
+ from tools import vision_tools as vt
+ isrc = _reload(monkeypatch, tmp_path / "hermes")
+ monkeypatch.setenv("TERMINAL_ENV", "local")
+ svg = tmp_path / "art.svg"
+ svg.write_bytes(b'')
+
+ def fake_rasterize(svg_path, out_path):
+ out_path.write_bytes(PNG)
+ return True
+
+ with patch.object(vt, "_rasterize_svg_to_png", side_effect=fake_rasterize):
+ res = await isrc.resolve_image_source(str(svg), isrc.ResolveContext())
+ assert res.mime == "image/svg+xml"
+ path, mime, err = vt._normalize_to_supported_image(svg, "image/svg+xml")
+ assert err is None
+ assert mime == "image/png"
+ assert path.read_bytes() == PNG
+ path.unlink()
+
+ def test_svg_actionable_error_when_no_converter(self, tmp_path, monkeypatch):
+ from tools import vision_tools as vt
+ _reload(monkeypatch, tmp_path / "hermes")
+ svg = tmp_path / "art.svg"
+ svg.write_bytes(b'')
+ with patch.object(vt, "_rasterize_svg_to_png", return_value=False):
+ path, mime, err = vt._normalize_to_supported_image(svg, "image/svg+xml")
+ assert path is None
+ assert "rasterizer" in err
diff --git a/tools/vision_tools.py b/tools/vision_tools.py
index 83b8b12b2ac..b5e1a16119f 100644
--- a/tools/vision_tools.py
+++ b/tools/vision_tools.py
@@ -316,10 +316,9 @@ def _normalize_to_supported_image(
if detected_mime in _ANTHROPIC_SUPPORTED_MEDIA_TYPES:
return image_path, detected_mime, None
- out_path = (
- get_hermes_dir("cache/vision", "temp_vision_images")
- / f"converted_{uuid.uuid4()}.png"
- )
+ out_dir = get_hermes_dir("cache/vision", "temp_vision_images")
+ out_dir.mkdir(parents=True, exist_ok=True)
+ out_path = out_dir / f"converted_{uuid.uuid4()}.png"
# SVG: needs a rasterizer (Pillow cannot render SVG).
if detected_mime == "image/svg+xml":