feat(agent): Bild mit Text einblenden — host_notify kann jetzt image_path

Der Android-Agent konnte schon immer ein Bild MIT Text einblenden
(OverlayView.show nimmt ein imageB64), aber ARIA kam nicht dran: in der
Parameter-Liste von host_notify fehlte das Feld einfach. Die Faehigkeit war
gebaut und unerreichbar.

ARIA gibt jetzt image_path an (ein Pfad unter /shared/uploads/, damit arbeitet
sie sowieso) und das Brain wandelt die Datei nach base64 — statt ARIA
megabyteweise base64 durch den Tool-Call schieben zu lassen. Pfad-Praefix wird
geprueft, und ueber 4 MB wird abgelehnt: fuer eine Einblendung auf einem
Fernseher ist mehr sinnlos und blaeht die WS-Nachricht auf.

Nur Android-Agenten koennen das; die Beschreibung sagt das ausdruecklich.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-03 10:34:04 +02:00
co-authored by Claude Opus 5
parent 734868efab
commit 4c0d8ce3fe
+29 -1
View File
@@ -1599,7 +1599,9 @@ META_TOOLS = [
"das NICHT ueber host_exec/adb (dort gibt es kein passendes Kommando). " "das NICHT ueber host_exec/adb (dort gibt es kein passendes Kommando). "
"mode: 'overlay' (einblenden), 'speech' (vorlesen) oder 'both'; ohne " "mode: 'overlay' (einblenden), 'speech' (vorlesen) oder 'both'; ohne "
"Angabe gilt die Einstellung am Geraet. Braucht dort einmalig die " "Angabe gilt die Einstellung am Geraet. Braucht dort einmalig die "
"Berechtigung 'Ueber anderen Apps anzeigen'." "Berechtigung 'Ueber anderen Apps anzeigen'. Mit image_path kann "
"ein BILD mit Text darueber eingeblendet werden (z.B. ein "
"generiertes Bild oder ein Screenshot) — nur bei Android-Agenten."
), ),
"parameters": { "parameters": {
"type": "object", "type": "object",
@@ -1610,6 +1612,14 @@ META_TOOLS = [
"seconds": {"type": "integer", "description": "Anzeigedauer (Default 12)."}, "seconds": {"type": "integer", "description": "Anzeigedauer (Default 12)."},
"mode": {"type": "string", "description": "overlay | speech | both"}, "mode": {"type": "string", "description": "overlay | speech | both"},
"focus": {"type": "string", "description": "Bei Sprachausgabe: 'duck' (laufende Medien werden leiser, Standard) | 'pause' (Film/Musik anhalten) | 'none'."}, "focus": {"type": "string", "description": "Bei Sprachausgabe: 'duck' (laufende Medien werden leiser, Standard) | 'pause' (Film/Musik anhalten) | 'none'."},
"image_path": {
"type": "string",
"description": (
"Optional: Pfad zu einem Bild (PNG/JPEG) unter "
"/shared/uploads/, das MIT dem Text eingeblendet wird. "
"Nur Android-Agenten koennen das."
),
},
}, },
"required": ["host", "text"], "required": ["host", "text"],
}, },
@@ -3246,6 +3256,24 @@ class Agent:
if name in _UI_ACTIONS: if name in _UI_ACTIONS:
action, keys = _UI_ACTIONS[name] action, keys = _UI_ACTIONS[name]
params = {k: arguments[k] for k in keys if arguments.get(k) is not None} params = {k: arguments[k] for k in keys if arguments.get(k) is not None}
# Bild-Einblendung: ARIA gibt einen Pfad an (damit arbeitet sie
# sowieso), der Agent will base64. Hier umwandeln, statt ARIA
# megabyteweise base64 durch den Tool-Call schieben zu lassen.
img_path = (arguments.get("image_path") or "").strip()
if action == "notify" and img_path:
if not img_path.startswith("/shared/uploads/"):
return "FEHLER: image_path muss unter /shared/uploads/ liegen."
if not os.path.isfile(img_path):
return f"FEHLER: Bild nicht gefunden: {img_path}"
# Overlay-Bilder ueber ~4 MB sind sinnlos gross fuer einen
# Fernseher und blaehen die WS-Nachricht unnoetig auf.
_img_size = os.path.getsize(img_path)
if _img_size > 4 * 1024 * 1024:
return (f"FEHLER: Bild zu gross fuer eine Einblendung "
f"({_img_size // (1024 * 1024)} MB > 4 MB). "
f"Vorher verkleinern.")
with open(img_path, "rb") as _f:
params["image_base64"] = _b64.b64encode(_f.read()).decode("ascii")
# Sprachausgabe braucht laenger: der Agent meldet erst Erfolg, # Sprachausgabe braucht laenger: der Agent meldet erst Erfolg,
# wenn wirklich Ton laeuft (TTS-Render + Uebertragung). Mit den # wenn wirklich Ton laeuft (TTS-Render + Uebertragung). Mit den
# 20s der UI-Aktionen lief die ehrliche Antwort in den Timeout. # 20s der UI-Aktionen lief die ehrliche Antwort in den Timeout.