Skip to content

fix(vision): convert SVG/unsupported image formats to PNG before embedding into history - #52688

Closed
JAlmanzarMint wants to merge 1 commit into
NousResearch:mainfrom
JAlmanzarMint:fix/vision-svg-unsupported-media-type
Closed

fix(vision): convert SVG/unsupported image formats to PNG before embedding into history#52688
JAlmanzarMint wants to merge 1 commit into
NousResearch:mainfrom
JAlmanzarMint:fix/vision-svg-unsupported-media-type

Conversation

@JAlmanzarMint

Copy link
Copy Markdown
Contributor

Summary

vision_analyze could embed an SVG (or BMP/TIFF) image into conversation history with media_type: image/svg+xml. Anthropic's vision API only accepts image/jpeg, image/png, image/gif, or image/webp, so the request fails with a non-retryable 400:

messages.N.content.0.tool_result.content.1.image.source.base64.media_type:
Input should be 'image/jpeg', 'image/png', 'image/gif' or 'image/webp'

Because a vision tool-result is baked into immutable conversation history and re-sent on every subsequent turn, this permanently wedges the sessionhermes --resume <id> re-sends the same poison payload and fails identically every time. Retries cannot clear bytes already in the request.

Root cause

_detect_image_mime_type() correctly returns image/svg+xml (and image/bmp) for those file types, but both consumption sites — the native-vision fast path (_native_vision_fast_path) and the auxiliary-API path (vision_analyze_tool) — then base64-embedded the image with that unsupported media_type and shipped it to the provider.

Fix

Add _normalize_to_supported_image(), called before any image is base64-embedded:

  • SVG → rasterized to PNG, best-effort via cairosvgsvglib+reportlabrsvg-convertinkscape (all soft deps).
  • Other non-supported raster formats (BMP, TIFF, …) → re-encoded to PNG via Pillow.
  • If conversion is impossible → returns an actionable error (e.g. "install cairosvg or convert to PNG") instead of embedding a session-wedging payload.

Wired into both call sites so the whole bug class is fixed, not just the one site the symptom surfaced at. Temp PNGs are cleaned up on the same path as downloaded images.

Test Plan

  • All 99 existing vision tests pass (tests/tools/test_vision_native_fast_path.py, tests/tools/test_vision_tools.py).
  • Verified end-to-end on a real SVG: image/svg+xml → normalized to image/png, embeds cleanly.
  • Reproduced the original wedge from a real session (media_type 400 on every resume); confirmed the fix prevents an unsupported media_type from ever entering history.

…dding

vision_analyze embedded SVG (and BMP/TIFF) tool-results into conversation
history with media_type image/svg+xml. Anthropic only accepts jpeg/png/
gif/webp, so the request fails with a non-retryable 400. Because the image
is baked into immutable history and re-sent every turn, the session is
permanently wedged on resume — retries re-send the same bad bytes.

Add _normalize_to_supported_image(): SVG is rasterized to PNG (best-effort
via cairosvg/svglib/rsvg-convert/inkscape), other non-supported raster
formats are re-encoded to PNG via Pillow, and if conversion is impossible
the tool returns an actionable error instead of a session-wedging payload.
Wired into both the native-vision fast path and the auxiliary-API path so
the whole bug class is covered, not just the one call site.

All 99 existing vision tests pass.
@alt-glitch alt-glitch added type/bug Something isn't working tool/vision Vision analysis and image generation comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists labels Jun 25, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related (competing approaches, not a duplicate) to the native-image-format cluster: #47299 (Codex data-URL downgrade to text placeholder), #47523 (reject corrupt/unsupported native inputs — same tools/vision_tools.py file but reject vs transcode), and #25936 (gateway-side transcode of uncommon formats). This PR is the transcode-in-vision_tools.py approach wired into both the native fast path and vision_analyze. Flagging the cluster so a maintainer can pick a single mechanism.

teknium1 added a commit that referenced this pull request Jul 4, 2026
… 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).
teknium1 added a commit that referenced this pull request Jul 4, 2026
The salvaged #52688 rasterizer shell-out predates the TUI subprocess
stdin= guard; a rasterizer that prompts on stdin could hang the tool
under prompt_toolkit. DEVNULL it.
teknium1 added a commit that referenced this pull request Jul 4, 2026
… 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).
teknium1 added a commit that referenced this pull request Jul 4, 2026
The salvaged #52688 rasterizer shell-out predates the TUI subprocess
stdin= guard; a rasterizer that prompts on stdin could hang the tool
under prompt_toolkit. DEVNULL it.
teknium1 added a commit that referenced this pull request Jul 4, 2026
… 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).
teknium1 added a commit that referenced this pull request Jul 4, 2026
The salvaged #52688 rasterizer shell-out predates the TUI subprocess
stdin= guard; a rasterizer that prompts on stdin could hang the tool
under prompt_toolkit. DEVNULL it.
@teknium1

teknium1 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Merged via PR #57890 — your commit was cherry-picked onto current main with your authorship preserved in git history (ac94f2c). Your SVG rasterization ladder (cairosvg → svglib → rsvg-convert → inkscape) and the format-normalization step now run behind the new unified image-source resolver, so SVG/BMP/TIFF sources are auto-converted to PNG before embedding exactly as you designed. Small follow-ups added on top: mkdir for the converted-PNG output dir on fresh installs, stdin=DEVNULL on the rasterizer shell-out (a subprocess guard that landed after your PR was written), and thread-offloading the conversion. Thanks!

@teknium1 teknium1 closed this Jul 4, 2026
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
… rasterizer; AUTHOR_MAP for NousResearch#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 NousResearch#52688 salvage).
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
The salvaged NousResearch#52688 rasterizer shell-out predates the TUI subprocess
stdin= guard; a rasterizer that prompts on stdin could hang the tool
under prompt_toolkit. DEVNULL it.
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
… rasterizer; AUTHOR_MAP for NousResearch#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 NousResearch#52688 salvage).
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
The salvaged NousResearch#52688 rasterizer shell-out predates the TUI subprocess
stdin= guard; a rasterizer that prompts on stdin could hang the tool
under prompt_toolkit. DEVNULL it.
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
… rasterizer; AUTHOR_MAP for NousResearch#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 NousResearch#52688 salvage).
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
The salvaged NousResearch#52688 rasterizer shell-out predates the TUI subprocess
stdin= guard; a rasterizer that prompts on stdin could hang the tool
under prompt_toolkit. DEVNULL it.
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
… rasterizer; AUTHOR_MAP for NousResearch#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 NousResearch#52688 salvage).
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
The salvaged NousResearch#52688 rasterizer shell-out predates the TUI subprocess
stdin= guard; a rasterizer that prompts on stdin could hang the tool
under prompt_toolkit. DEVNULL it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists tool/vision Vision analysis and image generation type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants