| name | api-server-media-display |
| description | Diagnose and fix images not displaying in Open WebUI / API server frontends. |
| version | 1.1.0 |
| author | Hermes Agent |
| license | MIT |
| metadata | {"hermes":{"tags":["api-server","open-webui","media","images","devops"]}} |
Goal
Ensure images generated by the agent render correctly in Open WebUI and other API server frontends.
Root Cause
The on_media_deliver hook only fires for Telegram, Discord, and other messaging platform sessions. The API server has its own media path and does NOT trigger hooks — it relies on _convert_media_to_http_urls() to convert image references into http://host:port/media/<hash> URLs that the _handle_media route can serve.
If an image doesn't display in Open WebUI, the conversion pipeline likely missed the image format.
Conversion Pipeline Order (in _convert_media_to_http_urls)
DATA_URI_RE — matches 
MEDIA_TAG_RE — matches MEDIA:/path/to/file
LOCAL_PATH_MD_RE — matches  ← often missing
Diagnose
Check gateway/platforms/api_server.py:
- Does
LOCAL_PATH_MD_RE exist? (regex for )
- Is
_convert_local_path_image handler present?
- Is
LOCAL_PATH_MD_RE.sub(...) called in the pipeline?
- Is
self._app.router.add_get("/media/{filename}", self._handle_media) called?
- Are
host and _convert_media_to_http_urls called in both the chat completion handler and the SSE streaming handler?
Common symptom: Open WebUI shows a broken image icon or the raw markdown text  instead of rendering the image.
Fix Template
Add to the imports/patterns section (after existing regexes):
Diagnose and fix images not displaying in Open WebUI / API server frontends.
This skill contains a reusable operational workflow. Follow the existing task-specific steps and examples in the sections below.
LOCAL_PATH_MD_RE = re.compile(
r'!\\[([^\\]]*)\\]\\(\\s*(/[\\w./\\-]+\\.[\\w]{2,4})\\s*\\)'
)
Add handler inside _convert_media_to_http_urls() (after existing handlers):
def _convert_local_path_image(match):
"""Convert standard markdown with local file path to HTTP URL."""
alt_text = match.group(1)
file_path = match.group(2)
if not file_path.startswith("/"):
return match.group(0)
ext = os.path.splitext(file_path)[1].lower()
if ext not in ALLOWED_MEDIA_EXTENSIONS:
return match.group(0)
if not os.path.isfile(file_path):
return match.group(0)
try:
with open(file_path, "rb") as f:
file_bytes = f.read()
except Exception:
return match.group(0)
content_hash = hashlib.sha256(file_bytes).hexdigest()[:16]
filename = f"{content_hash}{ext}"
dest_path = os.path.join(media_dir, filename)
if not os.path.exists(dest_path):
import shutil
shutil.copy2(file_path, dest_path)
return f""
Add to pipeline (after other conversions):
result = LOCAL_PATH_MD_RE.sub(_convert_local_path_image, result)
Verification
-
Restart the gateway — changes to api_server.py require a restart:
kill $(cat ~/.hermes/gateway.pid) 2>/dev/null || pkill -f "hermes gateway"
hermes gateway
-
Check syntax before restarting:
python3 -m py_compile ~/.hermes/hermes-agent/gateway/platforms/api_server.py
-
Test via curl (if gateway is running):
curl http://localhost:8642/v1/chat/completions \
-H "Authorization: Bearer <key>" \
-H "Content-Type: application/json" \
-d '{"model": "hermes-agent", "messages": [{"role": "user", "content": "Generate a simple plot"}], "stream": false}'
-
Inspect the response — verify it contains http://127.0.0.1:8642/media/<hash>.png not /tmp/... local paths.
-
Open WebUI — navigate to the conversation and verify the image renders inline.
Pitfalls
- Gateway must be restarted for code changes to take effect
- The fix is defensive: if the file doesn't exist or can't be read, the markdown is left unchanged rather than breaking
- Content-hashed filenames (16 hex chars) prevent collisions but make debugging harder — check
/tmp/hermes-api-media/ to map hashes back
- Both the chat completion path and the SSE streaming path need the conversion call (check
host = request.host and _convert_media_to_http_urls(item, host))
- When pushing skills to the AstroAgentAssistant repo, always regenerate the README inventory from actual git-tracked files — the README claims often diverge significantly from reality
Related Skills
skills-repo-maintenance — for publishing skills to the AstroAgentAssistant GitHub repo