Skip to content

Channel ABC

Channel

Channel(channel_id)

Bases: ABC

Base class for all channels.

sender_is_participant class-attribute instance-attribute

sender_is_participant = False

Whether this channel's sender_id is a room Participant.id.

An identity resolver maps an address — a number, an email, a handle — to an Identity. Most channels carry one: what arrives on sender_id is how the sender is reachable, and who that is remains to be looked up. A channel that sets this declares the opposite: its senders are named by the room itself, so there is no address to look up and identity resolution (RFC §11) is skipped for its messages.

Declaring it wrongly is not a small mistake: a channel that does carry addresses would stop resolving them, and every sender would stay unidentified.

trace_enabled property

trace_enabled

Whether any trace observers are registered.

provider_name property

provider_name

Provider or backend name for event attribution.

info property

info

Return channel metadata. Override in subclasses.

recent_events_window property

recent_events_window

How many recent room events this channel reads per turn.

Drives how many events the framework loads into RoomContext for a room. Transport-only channels (WebSocket, realtime voice) don't consume room history, so the default is 0; AI channels override it with their memory provider's window.

supports_streaming_delivery property

supports_streaming_delivery

Whether this channel can accept streaming text delivery.

on_trace

on_trace(callback, *, protocols=None)

Register a protocol trace observer.

Parameters:

Name Type Description Default
callback TraceCallback

Called with each :class:ProtocolTrace. May be sync or async (coroutines are scheduled as tasks).

required
protocols list[str] | None

Optional allowlist of protocol names (e.g. ["sip"]). None means all protocols.

None

emit_trace

emit_trace(trace)

Emit a protocol trace to all registered observers.

resolve_trace_room

resolve_trace_room(session_id)

Resolve a room ID for a trace with the given session.

Override in session-based channels (voice, realtime voice) to map session IDs to room IDs. Returns None by default.

handle_inbound abstractmethod async

handle_inbound(message, context)

Process an inbound message into a RoomEvent.

deliver abstractmethod async

deliver(event, binding, context)

Deliver an event to this channel.

on_event async

on_event(event, binding, context)

React to an event. Default: no-op for transport channels.

supports_streaming_delivery_for

supports_streaming_delivery_for(room_id)

Whether this channel can stream into a specific room.

Defaults to the channel-wide answer, which is right for a channel whose capability does not vary by room — a voice session, a terminal. A channel that holds per-room client connections overrides this, so a room whose clients cannot stream does not take the streaming path only to fall back at the end.

deliver_stream async

deliver_stream(text_stream, event, binding, context)

Deliver a streaming text response to this channel.

Default: accumulate text, deliver as complete event.

connect_session async

connect_session(session, room_id, binding)

Accept a long-lived session after inbound processing.

Called by process_inbound when message.session is present and hooks did not block. Override in session-based channels (voice, persistent WebSocket, etc.). Default: no-op.

disconnect_session async

disconnect_session(session, room_id)

Clean up a session on remote disconnect.

Override in session-based channels to release resources. Default: no-op.

update_binding

update_binding(room_id, binding)

Notify the channel that a room's binding has changed.

Called by the framework after mute(), unmute(), or set_access() update the store. Override in session-based channels (voice, realtime voice) to update cached binding state used for audio gating. Default: no-op.

on_room_attached async

on_room_attached(room_id, binding)

Establish whatever the new binding claims exists.

Awaited by attach_channel() after the binding is written and before anything has observed it — no system event, no hook. This is where a channel does the outside-world work an attachment implies: a conference channel creates the SFU room here (RFC §12.10.4 step 1).

Raising cancels the attachment. The framework takes the binding back and re-raises, so the caller learns that the channel refused rather than receiving a binding to something that was never built. Which is why this is not a hook: a lifecycle hook is observation, its errors are logged and never raised, and the room would go on believing it was attached. Default: no-op.

on_room_detached async

on_room_detached(room_id)

Take down what :meth:on_room_attached established.

Awaited by detach_channel() before the ON_CHANNEL_DETACHED hooks, so an integrator's handler runs after the channel has finished letting go rather than alongside it.

Nothing is rolled back if this raises — the binding is already gone and the detach already announced — but the error reaches the caller instead of disappearing into a log. Default: no-op.

capabilities

capabilities()

Return channel capabilities.

close async

close()

Close the channel and its provider.

extract_text staticmethod

extract_text(event)

Extract plain text from an event's content.