Files
hermes-agent/gateway/relay/transport.py
T
Teknium 3c7069bdcb refactor(gateway): dead-code removal, helper unification, defensive-layer collapse and rationale-preserving comment compaction across 40 modules
authz_mixin, browser_control_broker, delivery, delivery_ledger, display_config, drain_control,
hosted_room_links/peer/policy_checkpoint, hosted_rooms, platform_registry, relay/__init__,
relay/ws_transport, run.py and slash_commands.py (comments), session_context, session_state,
streaming_tts_consumer, turn_lease and small modules.

- HostedRoomPolicyCheckpoint._apply_event -> per-kind handler table
- WebSocketRelayTransport._handle_frame -> frame-handler table
- GatewayAuthorizationMixin: unified adapter setting/flag/extra readers
- dead symbols removed (verified zero references): RoomLinkProbe/select_room_link,
  relay_bot_username, is_restart_loop_tripped, debug_rows, DeadTargetRegistry.all_dead,
  BrowserControlBroker.detach_owner/_prune_tickets, StreamingTTSConsumer.started/_enqueue_done/
  _iter_stream_chunks/_next_stream_chunk, RecoverableHandleCache.status_for, _auth_env,
  _copy_default_catalog, _parse_timestamp_prefix, _present_* helpers, _send_result_error_kind,
  _truthy_env, SessionFieldView/TurnLeaseTokenView dunder shims, and their orphaned tests.
- lost WHY/invariant text from the earlier compaction restored compactly (541 hunks audited)
2026-09-02 13:30:50 -07:00

115 lines
5.1 KiB
Python

"""Relay transport protocol — the gateway<->connector wire contract. EXPERIMENTAL.
The ``RelayAdapter`` (gateway side) delegates all wire I/O to a ``RelayTransport``.
The gateway dials OUT to the connector, so a production transport is a WebSocket
client (``ws_transport.py``); in tests it is an in-memory stub
(``tests/gateway/relay/stub_connector.py``). This module defines the protocol
surface only: lifecycle (connect/disconnect), handshake (the advertised
``CapabilityDescriptor``), inbound (``set_inbound_handler``) and outbound
(``send_outbound`` / ``get_chat_info`` / ``send_interrupt``).
EXPERIMENTAL: may change without a deprecation cycle until >=2 Class-1 platforms
validate it. See docs/relay-connector-contract.md.
"""
from __future__ import annotations
from typing import Any, Awaitable, Callable, Dict, Optional, Protocol, runtime_checkable
from gateway.platforms.base import MessageEvent
from gateway.relay.descriptor import CapabilityDescriptor
# Callback the transport invokes for each inbound normalized event.
InboundHandler = Callable[[MessageEvent], Awaitable[None]]
# Callback for each forwarded passthrough request (§5.1): ``(forward, buffer_id)``.
# ``forward`` is a ws_transport.PassthroughForward, typed Any here because
# ws_transport imports FROM this module; ``buffer_id`` (§5.3 buffered flip) is
# acked by the handler after durable handoff.
PassthroughHandler = Callable[[Any, Optional[str]], Awaitable[None]]
@runtime_checkable
class RelayTransport(Protocol):
"""Full gateway<->connector transport contract."""
async def connect(self) -> bool:
"""Open the connection to the connector; return True on success."""
...
async def disconnect(self) -> None:
"""Close the connection."""
...
async def handshake(self) -> CapabilityDescriptor:
"""Return the capability descriptor the connector advertises."""
...
def set_inbound_handler(self, handler: InboundHandler) -> None:
"""Register the callback invoked with each inbound MessageEvent."""
...
def set_passthrough_handler(self, handler: "PassthroughHandler") -> None:
"""Register the callback for each forwarded passthrough request (§5.1).
The connector answers the provider's edge ACK itself, then forwards the
real request over this same outbound socket (a hosted gateway has no
public inbound port). Optional on a transport (a stub may not implement it).
"""
...
async def send_outbound(
self, action: Dict[str, Any], *, platform: Optional[str] = None
) -> Dict[str, Any]:
"""Carry an outbound action (send/edit/typing) to the connector.
Returns a result dict; for ``op == "send"`` it carries ``success`` and
optionally ``message_id`` / ``error``. ``platform`` tags WHICH fronted
platform this reply targets (the transport resolves the matching botId);
omitted ⇒ the connector uses the session's default platform.
"""
...
async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
"""Proxy a chat-info lookup to the connector."""
...
async def send_interrupt(self, session_key: str, reason: Optional[str] = None) -> None:
"""Route a mid-turn /stop to the connector for ``session_key``.
The connector forwards it down the socket owned by the gateway instance
running that session. This is the OUTBOUND direction; the actual task
cancellation happens when the connector echoes an interrupt inbound.
"""
...
async def go_idle(self, timeout_s: float = 10.0) -> bool:
"""Ask the connector to flip this instance to buffered-only (§5.3).
Sends ``going_idle`` and awaits the connector-authoritative
``going_idle_ack`` (live delivery stopped; inbound now buffers for replay
on reconnect). Returns True on ack, False on timeout / not-connected —
the caller closes regardless. Optional on a transport. Emitted as part of
the gateway's EXISTING drain transition -- not a new idle path.
"""
...
async def send_follow_up(
self, action: Dict[str, Any], *, platform: Optional[str] = None
) -> Dict[str, Any]:
"""Act on a shared-identity capability bound to a session (A2 outbound).
Some platforms hand the connector a credential acting on the SHARED bot
identity (e.g. a Discord interaction follow-up token). Under A2 it NEVER
reaches the gateway: the connector binds it in its capability vault keyed
by session, and the gateway issues a SEMANTIC action against that session.
Action dict: ``op == "follow_up"``, ``session_key``, ``kind`` (e.g.
``"discord.interaction_token"``), ``content``, optional ``metadata``.
The connector resolves the capability, enforces the tenant match, and
egresses. Returns ``{success, message_id?, error?}``; ``success`` is False
when the capability is absent/expired or the tenant mismatches — nothing to
retry with (by design: a leaked gateway holds zero capability material).
"""
...