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