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":