hermes-agent/gateway/relay
Ben Barclay 689b51bef6
feat(relay): Phase 2 media parity — send_media egress + inbound media localization (#71363)
- gateway/relay/media.py: RelayMediaClient for the connector's /relay/media
  plane (upload local files → re-host reference for send_media; download
  re-hosted inbound attachments → local temp paths). Same connector base URL
  the WS dials, same per-gateway signed bearer as the upgrade (auth.py) —
  no new configuration. stdlib urllib in a thread executor (no new deps);
  25MB cap mirroring the connector's MEDIA_MAX_BYTES.
- RelayAdapter: send_image / send_image_file / send_voice / send_video /
  send_document overrides route through ONE send_media op (media by
  reference: local paths upload first, public URLs pass through). Gated on
  supported_ops advertising send_media — legacy connectors keep today's
  text fallbacks; connector declines/failed uploads degrade the same way.
  Scope/user egress discriminators ride metadata exactly like send.
- Inbound: _localize_inbound_media downloads each media_urls entry to a
  local temp path (native-adapter parity — vision/file tools consume
  paths); dead re-host refs are dropped, public URLs survive a missing
  client. Best-effort, never blocks handle_message.
- docs/relay-connector-contract.md §4: send_media op row + media
  ingress/egress semantics (replaces the 'deferred to a later revision'
  note). Additive within contract_version 1.
- tests: tests/gateway/relay/test_relay_media.py (15) — kind mapping,
  upload-first path handling, op gating (explicit + legacy-empty),
  decline/upload-failure fallbacks, scope metadata, inbound localization
  matrix, client URL derivation/credential gating. Stub connector grew a
  canned send_media result.

Cross-repo pair: gateway-gateway 'Phase 2 media parity' PR (re-host plane +
four ingress lanes + four platform send_media senders).
2026-07-25 23:15:52 +10:00
..
__init__.py feat(relay): Phase 1 parity — supported_ops discovery, wire identity fields, /handoff aliasing, provision displayName (#71300) 2026-07-25 20:36:16 +10:00
adapter.py feat(relay): Phase 2 media parity — send_media egress + inbound media localization (#71363) 2026-07-25 23:15:52 +10:00
auth.py feat(relay): connector⇄gateway channel auth + signed-HTTP inbound receiver + enroll CLI (#48147) 2026-06-18 12:01:54 +10:00
descriptor.py feat(relay): Phase 1 parity — supported_ops discovery, wire identity fields, /handoff aliasing, provision displayName (#71300) 2026-07-25 20:36:16 +10:00
media.py feat(relay): Phase 2 media parity — send_media egress + inbound media localization (#71363) 2026-07-25 23:15:52 +10:00
transport.py feat(relay): multi-platform-per-agent — list identity, provision-loop, N-hello, per-frame egress (Phase 1.5) (#52830) 2026-06-26 17:32:46 +10:00
ws_transport.py feat(relay): Phase 1 parity — supported_ops discovery, wire identity fields, /handoff aliasing, provision displayName (#71300) 2026-07-25 20:36:16 +10:00