Skip to main content

Channel

Trait Channel 

pub trait Channel:
    Send
    + Sync
    + Attributable {
Show 40 methods // Required methods fn name(&self) -> &str; fn send<'life0, 'life1, 'async_trait>( &'life0 self, message: &'life1 SendMessage, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait; fn listen<'life0, 'async_trait>( &'life0 self, tx: Sender<ChannelMessage>, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, Self: 'async_trait; // Provided methods fn send_final<'life0, 'life1, 'async_trait>( &'life0 self, message: &'life1 SendMessage, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait { ... } fn health_check<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = bool> + Send + 'async_trait>> where 'life0: 'async_trait, Self: 'async_trait { ... } fn listener_health(&self) -> Option<ListenerHealth> { ... } fn send_choice<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, recipient: &'life1 str, prompt: &'life2 str, options: &'life3 [(String, String)], ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait { ... } fn start_typing<'life0, 'life1, 'async_trait>( &'life0 self, _recipient: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait { ... } fn stop_typing<'life0, 'life1, 'async_trait>( &'life0 self, _recipient: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait { ... } fn supports_draft_updates(&self) -> bool { ... } fn supports_outbound_send(&self) -> bool { ... } fn self_handle(&self) -> Option<String> { ... } fn self_addressed_mention(&self) -> Option<String> { ... } fn drop_self_messages(&self, msg: &ChannelMessage) -> bool { ... } fn forge_request<'life0, 'async_trait>( &'life0 self, _request: ForgeApiRequest, ) -> Pin<Box<dyn Future<Output = Result<ForgeApiResponse, Error>> + Send + 'async_trait>> where 'life0: 'async_trait, Self: 'async_trait { ... } fn is_direct_message(&self, _msg: &ChannelMessage) -> bool { ... } fn supports_multi_message_streaming(&self) -> bool { ... } fn multi_message_delay_ms(&self) -> u64 { ... } fn send_draft<'life0, 'life1, 'async_trait>( &'life0 self, _message: &'life1 SendMessage, ) -> Pin<Box<dyn Future<Output = Result<Option<String>, Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait { ... } fn update_draft<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, _recipient: &'life1 str, _message_id: &'life2 str, _text: &'life3 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait { ... } fn update_draft_progress<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, _recipient: &'life1 str, _message_id: &'life2 str, _text: &'life3 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait { ... } fn update_draft_progress_batch<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, recipient: &'life1 str, message_id: &'life2 str, texts: &'life3 [String], ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait { ... } fn update_typed_draft_progress_batch<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, recipient: &'life1 str, message_id: &'life2 str, progress: &'life3 [DraftProgress], ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait { ... } fn update_draft_lifecycle<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _recipient: &'life1 str, _message_id: &'life2 str, _event: ProgressEvent, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait { ... } fn finalize_draft<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, _recipient: &'life1 str, _message_id: &'life2 str, _text: &'life3 str, _suppress_voice: bool, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait { ... } fn cancel_draft<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _recipient: &'life1 str, _message_id: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait { ... } fn add_reaction<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, _channel_id: &'life1 str, _message_id: &'life2 str, _emoji: &'life3 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait { ... } fn remove_reaction<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, _channel_id: &'life1 str, _message_id: &'life2 str, _emoji: &'life3 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait { ... } fn pin_message<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _channel_id: &'life1 str, _message_id: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait { ... } fn unpin_message<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _channel_id: &'life1 str, _message_id: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait { ... } fn redact_message<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _channel_id: &'life1 str, _message_id: &'life2 str, _reason: Option<String>, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait { ... } fn create_room<'life0, 'life1, 'async_trait>( &'life0 self, _options: &'life1 RoomCreationOptions, ) -> Pin<Box<dyn Future<Output = Result<String, Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait { ... } fn invite_user<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _room_id: &'life1 str, _user_id: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait { ... } fn request_approval<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _recipient: &'life1 str, _request: &'life2 ChannelApprovalRequest, ) -> Pin<Box<dyn Future<Output = Result<Option<ChannelApprovalResponse>, Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait { ... } fn request_approval_attributed<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, recipient: &'life1 str, request: &'life2 ChannelApprovalRequest, ) -> Pin<Box<dyn Future<Output = Result<Option<AttributedApprovalResponse>, Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait { ... } fn send_gate_prompt<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _recipient: &'life1 str, _prompt: &'life2 ChannelGatePrompt, ) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait { ... } fn finalize_gate_prompt<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _reference: &'life1 str, _outcome: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait { ... } fn request_choice<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _question: &'life1 str, _choices: &'life2 [String], _timeout: Duration, ) -> Pin<Box<dyn Future<Output = Result<Option<String>, Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait { ... } fn request_multi_choice<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _question: &'life1 str, _choices: &'life2 [String], _min_items: usize, _max_items: usize, _timeout: Duration, ) -> Pin<Box<dyn Future<Output = Result<Option<Vec<String>>, Error>> + Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait { ... } fn supports_free_form_ask(&self) -> bool { ... }
}
Expand description

Core channel trait — implement for any messaging platform.

Every Channel is Attributable: the orchestrator’s spawn site opens attribution_span!(&*ch) so log emissions from within listen() / send() inherit channel = <type>.<alias>.

Required Methods§

Source

fn name(&self) -> &str

Human-readable channel name

Source

fn send<'life0, 'life1, 'async_trait>( &'life0 self, message: &'life1 SendMessage, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait,

Send a message through this channel

Source

fn listen<'life0, 'async_trait>( &'life0 self, tx: Sender<ChannelMessage>, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, Self: 'async_trait,

Start listening for incoming messages (long-running)

Provided Methods§

Source

fn send_final<'life0, 'life1, 'async_trait>( &'life0 self, message: &'life1 SendMessage, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait,

Send the completed assistant response for a turn.

Most channels use the ordinary send path. Channels with delivery policy that applies only to final responses can override this without changing the shared message shape or affecting system and approval sends.

Source

fn health_check<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = bool> + Send + 'async_trait>>
where 'life0: 'async_trait, Self: 'async_trait,

Check if channel is healthy

Source

fn listener_health(&self) -> Option<ListenerHealth>

Listener health as the channel itself last observed it.

health_check is an active probe: implementations reach the remote service to answer it, and some of them send a real message or open a broker connection to do so. That makes it safe to call on demand — from channel doctor — and unsafe to call on a timer, so a recurring caller must not use it.

This is the recurring-safe counterpart. It is synchronous and returns state the channel recorded while it was already talking to the service, so reading it performs no I/O and cannot block, send, or connect.

None — the default — means the channel offers no signal at all, and the caller should fall back to whatever it knew before asking. A channel that does implement this reports a ListenerHealth, which is a three-state answer rather than a boolean: “I have nothing to say yet” is not the same claim as “I am working”, and a caller that collapses them reports a listener as healthy before it has ever connected.

An implementation owns its own freshness rule, because only the channel knows how often a working listener completes an exchange. A success from long enough ago that it is no longer evidence must be reported as ListenerHealth::Unhealthy, not as Healthy forever.

Source

fn send_choice<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, recipient: &'life1 str, prompt: &'life2 str, options: &'life3 [(String, String)], ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait,

Send a discrete-choice prompt with options.

Each (callback_id, label) pair represents one choice. Whether the callback_id round-trips on inbound is channel-specific:

  • WhatsApp Cloud preserves the callback_id exactly — it appears as interactive.button_reply.id / interactive.list_reply.id, surfaced as [choice]<callback_id> in the inbound ChannelMessage.content.
  • Signal uses native polls. Real signal-cli pollVote payloads round-trip selected option indexes, surfaced as [choice-index]N with a 1-based index. Some alternate pollAnswer payloads may include selected titles and surface as [choice]<label>, but callers needing stable callback ids should maintain a side map keyed by poll option index.
  • Default text fallback (Matrix, SMS, IRC, mock) renders a numbered list with a “reply with name or number” hint and relies on the consumer’s own matcher to resolve the user’s text reply against the option list.

options.is_empty() is treated as “send the prompt as plain text with no choices” rather than rendering an empty selection hint; if prompt is also empty the call is a no-op (returns Ok(())).

prompt is the question / title; rendered ABOVE the options.

Source

fn start_typing<'life0, 'life1, 'async_trait>( &'life0 self, _recipient: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait,

Signal that the bot is processing a response (e.g. “typing” indicator).

Source

fn stop_typing<'life0, 'life1, 'async_trait>( &'life0 self, _recipient: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait,

Stop any active typing indicator.

Source

fn supports_draft_updates(&self) -> bool

Whether this channel supports progressive message updates via draft edits.

Source

fn supports_outbound_send(&self) -> bool

Whether send actually delivers a message OUTBOUND on this channel. Default true. An INBOUND-ONLY transport (e.g. an AMQP trigger source whose send is a deliberate no-op that returns Ok) overrides this to false, so a surface that must genuinely deliver - such as the SOP approval route adapter - can refuse to route to it (and report the misconfiguration) instead of silently succeeding without sending anything.

Source

fn self_handle(&self) -> Option<String>

Self-loop guard for multi-agent runs: the bot’s own handle/identity on this channel, so the orchestrator can drop inbound events whose sender matches. A bot must never respond to its own messages, even if a misconfigured peer group lists the bot’s handle as an external peer.

Channels that handle inbound traffic must override this. The default None disables both layers of the self-loop guard (drop_self_messages here and the agent-loop fallback), leaving the channel unprotected from looping on its own outbound. Outbound-only channels can keep the default.

Source

fn self_addressed_mention(&self) -> Option<String>

The exact form the bot expects when addressed by users on this channel (e.g. Discord <@snowflake>, Telegram @bot_username). Returned verbatim into the per-channel system prompt. Default None for channels with no inbound mention concept. Channels that override self_handle should usually override this too.

Source

fn drop_self_messages(&self, msg: &ChannelMessage) -> bool

Whether the orchestrator should drop an inbound message as self-authored (multi-agent self-loop guard). Default compares msg.sender against Self::self_handle case-insensitively after stripping a leading @ from each side. Override only for platforms whose identity comparison is non-string.

Source

fn forge_request<'life0, 'async_trait>( &'life0 self, _request: ForgeApiRequest, ) -> Pin<Box<dyn Future<Output = Result<ForgeApiResponse, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, Self: 'async_trait,

Source

fn is_direct_message(&self, _msg: &ChannelMessage) -> bool

Whether an inbound message is a direct one-to-one conversation with the bot. A DM is definitionally addressed to the bot, so the orchestrator skips the reply-intent classifier and goes straight to the tool-capable agent turn. Default false: channels that cannot prove a one-to-one context keep the classifier precheck.

Source

fn supports_multi_message_streaming(&self) -> bool

Whether this channel supports multi-message streaming delivery.

Source

fn multi_message_delay_ms(&self) -> u64

Minimum delay (ms) between sending each paragraph in multi-message mode.

Source

fn send_draft<'life0, 'life1, 'async_trait>( &'life0 self, _message: &'life1 SendMessage, ) -> Pin<Box<dyn Future<Output = Result<Option<String>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait,

Send an initial draft message. Returns a platform-specific message ID for later edits.

Source

fn update_draft<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, _recipient: &'life1 str, _message_id: &'life2 str, _text: &'life3 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait,

Update a previously sent draft message with new accumulated content.

Source

fn update_draft_progress<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, _recipient: &'life1 str, _message_id: &'life2 str, _text: &'life3 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait,

Show a progress/status update (e.g. tool execution status).

Source

fn update_draft_progress_batch<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, recipient: &'life1 str, message_id: &'life2 str, texts: &'life3 [String], ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait,

Show a batch of progress/status updates as one coalesced draft update.

Channels with expensive edit APIs should override this so an upstream burst does not turn into one awaited network edit per progress item.

Source

fn update_typed_draft_progress_batch<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, recipient: &'life1 str, message_id: &'life2 str, progress: &'life3 [DraftProgress], ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait,

Show a batch of progress updates without discarding their source kind.

The default preserves compatibility for channels that only need text; channels that coalesce semantically different progress should override this method and keep the kind through their local draft buffer.

Source

fn update_draft_lifecycle<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _recipient: &'life1 str, _message_id: &'life2 str, _event: ProgressEvent, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Show localized lifecycle progress produced from a typed runtime event. Unlike raw tool status text, this input is trusted channel chrome.

Source

fn finalize_draft<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, _recipient: &'life1 str, _message_id: &'life2 str, _text: &'life3 str, _suppress_voice: bool, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait,

Finalize a draft with the complete response (e.g. apply Markdown formatting). suppress_voice forces text delivery even on voice-only peers.

Source

fn cancel_draft<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _recipient: &'life1 str, _message_id: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Cancel and remove a previously sent draft message if the channel supports it.

Source

fn add_reaction<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, _channel_id: &'life1 str, _message_id: &'life2 str, _emoji: &'life3 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait,

Add a reaction (emoji) to a message.

Source

fn remove_reaction<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, _channel_id: &'life1 str, _message_id: &'life2 str, _emoji: &'life3 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, Self: 'async_trait,

Remove a reaction (emoji) from a message previously added by this bot.

Source

fn pin_message<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _channel_id: &'life1 str, _message_id: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Pin a message in the channel.

Source

fn unpin_message<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _channel_id: &'life1 str, _message_id: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Unpin a previously pinned message.

Source

fn redact_message<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _channel_id: &'life1 str, _message_id: &'life2 str, _reason: Option<String>, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Redact (delete) a message from the channel.

Source

fn create_room<'life0, 'life1, 'async_trait>( &'life0 self, _options: &'life1 RoomCreationOptions, ) -> Pin<Box<dyn Future<Output = Result<String, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait,

Create a new platform room/conversation when the channel supports it.

Source

fn invite_user<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _room_id: &'life1 str, _user_id: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Invite a user to an existing platform room/conversation.

Source

fn request_approval<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _recipient: &'life1 str, _request: &'life2 ChannelApprovalRequest, ) -> Pin<Box<dyn Future<Output = Result<Option<ChannelApprovalResponse>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Request interactive tool-call approval from the channel operator.

Returns Ok(Some(response)) when the operator answers within the channel’s configured approval_timeout_secs; timeouts surface as Deny. Returns Ok(None) only for channels that do not implement the prompt at all — the caller falls back to its default policy (typically auto-deny).

Source

fn request_approval_attributed<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, recipient: &'life1 str, request: &'life2 ChannelApprovalRequest, ) -> Pin<Box<dyn Future<Output = Result<Option<AttributedApprovalResponse>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Like Channel::request_approval, but also reports WHO produced the decision: which back-channel answered (decided_by) and whether a person answered at all (ApprovalSource).

Override this whenever the channel can synthesize a response. The Channel::request_approval contract collapses “the operator denied” and “nobody answered in time” into the same Some(Deny), so the default below cannot tell them apart and assumes ApprovalSource::Operator. Any implementation that manufactures a Deny on timeout, on a dropped responder, or on an unreachable approver MUST implement this method and report the real source — otherwise the runtime tells the model a human refused when none was ever asked.

The convention in-tree is to put the real body here and let Channel::request_approval delegate to it, so the logic lives once.

Source

fn send_gate_prompt<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _recipient: &'life1 str, _prompt: &'life2 ChannelGatePrompt, ) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Present a long-lived, out-of-band gate prompt (e.g. a parked SOP approval) on this channel, rendered natively — an embed with one button per choice on Discord, an inline keyboard on Telegram, and so on.

UNLIKE Channel::request_approval this does NOT wait for the answer: the prompt outlives the call (and daemon restarts). The choice comes back through the channel’s normal INBOUND path — a component click (stamped with an internal sop.gate: marker by the channel’s own interaction producer, never derivable from message text) or a plain <choice> <reference> text reply — which the orchestrator resolves against the parked gate.

Default Ok(false) = “no native prompt on this channel”; the caller then falls back to a plain text notice carrying the reply instructions.

Source

fn finalize_gate_prompt<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _reference: &'life1 str, _outcome: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Mark a previously sent gate prompt as resolved: strip its interactive controls and replace the body with outcome (e.g. “Approved by @user — run resumed”), so a decided gate cannot be clicked again and the decision is visible in place. reference is the same correlation key the prompt was sent with. Best-effort: Ok(false) when this channel has nothing to finalize (no native prompt, or the mapping was lost to a restart) — the gate state itself is never affected.

Source

fn request_choice<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _question: &'life1 str, _choices: &'life2 [String], _timeout: Duration, ) -> Pin<Box<dyn Future<Output = Result<Option<String>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Ask the user a multiple-choice question and return the chosen option’s text.

Returns Ok(Some(answer)) if the channel handled the question natively (e.g. ACP elicitation/create with a single-select enum schema, or the legacy session/request_permission fallback for older ACP clients; Telegram inline keyboard; etc.). Returns Ok(None) to signal the caller should fall back to the generic send + listen flow. Default impl returns None.

Free-form (no-choices) questions are not modeled by this method. Multiple-choice support landed via ACP elicitation/create (see the ACP elicitation RFD: https://agentclientprotocol.com/rfds/elicitation); free-form text is tracked under that spec’s Phase 2.

Source

fn request_multi_choice<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _question: &'life1 str, _choices: &'life2 [String], _min_items: usize, _max_items: usize, _timeout: Duration, ) -> Pin<Box<dyn Future<Output = Result<Option<Vec<String>>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Source

fn supports_free_form_ask(&self) -> bool

Whether this channel can answer free-form (no-choices) ask_user questions via the standard send + listen flow. Channels that only handle structured choices return false so callers fail fast with a useful error instead of timing out on listen.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementations on Foreign Types§

Source§

impl Channel for AcpChannel

Source§

fn request_approval<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, recipient: &'life1 str, request: &'life2 ChannelApprovalRequest, ) -> Pin<Box<dyn Future<Output = Result<Option<ChannelApprovalResponse>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, AcpChannel: 'async_trait,

Delegates to Self::request_approval_attributed and drops the provenance, so the prompt logic lives in exactly one place.

Source§

fn start_typing<'life0, 'life1, 'async_trait>( &'life0 self, _recipient: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, AcpChannel: 'async_trait,

Source§

fn stop_typing<'life0, 'life1, 'async_trait>( &'life0 self, _recipient: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, AcpChannel: 'async_trait,

Source§

fn name(&self) -> &str

Source§

fn send<'life0, 'life1, 'async_trait>( &'life0 self, message: &'life1 SendMessage, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, AcpChannel: 'async_trait,

Source§

fn listen<'life0, 'async_trait>( &'life0 self, _tx: Sender<ChannelMessage>, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, AcpChannel: 'async_trait,

Source§

fn supports_free_form_ask(&self) -> bool

Source§

fn add_reaction<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, _channel_id: &'life1 str, _message_id: &'life2 str, _emoji: &'life3 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, AcpChannel: 'async_trait,

Source§

fn remove_reaction<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, _channel_id: &'life1 str, _message_id: &'life2 str, _emoji: &'life3 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, AcpChannel: 'async_trait,

Source§

fn request_choice<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, question: &'life1 str, choices: &'life2 [String], timeout: Duration, ) -> Pin<Box<dyn Future<Output = Result<Option<String>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, AcpChannel: 'async_trait,

Source§

fn request_multi_choice<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, question: &'life1 str, choices: &'life2 [String], min_items: usize, max_items: usize, timeout: Duration, ) -> Pin<Box<dyn Future<Output = Result<Option<Vec<String>>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, AcpChannel: 'async_trait,

Source§

fn request_approval_attributed<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, _recipient: &'life1 str, request: &'life2 ChannelApprovalRequest, ) -> Pin<Box<dyn Future<Output = Result<Option<AttributedApprovalResponse>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, AcpChannel: 'async_trait,

Source§

impl Channel for PacedChannel

Source§

fn listener_health(&self) -> Option<ListenerHealth>

Forward the inner channel’s passive observation.

Without this the trait default (None) answers for the wrapper, and a supervisor reads “no signal” for a channel that does have one. Pacing is a delivery concern; it says nothing about whether the listener is reaching the service.

Source§

fn request_approval_attributed<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, recipient: &'life1 str, request: &'life2 ChannelApprovalRequest, ) -> Pin<Box<dyn Future<Output = Result<Option<AttributedApprovalResponse>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, PacedChannel: 'async_trait,

Must be forwarded explicitly: without this the trait default would call Channel::request_approval on the wrapper and relabel the inner channel’s synthesized timeout deny as an operator decision.

Source§

fn name(&self) -> &str

Source§

fn send<'life0, 'life1, 'async_trait>( &'life0 self, message: &'life1 SendMessage, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, PacedChannel: 'async_trait,

Source§

fn send_final<'life0, 'life1, 'async_trait>( &'life0 self, message: &'life1 SendMessage, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, PacedChannel: 'async_trait,

Source§

fn listen<'life0, 'async_trait>( &'life0 self, tx: Sender<ChannelMessage>, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, PacedChannel: 'async_trait,

Source§

fn health_check<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = bool> + Send + 'async_trait>>
where 'life0: 'async_trait, PacedChannel: 'async_trait,

Source§

fn start_typing<'life0, 'life1, 'async_trait>( &'life0 self, recipient: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, PacedChannel: 'async_trait,

Source§

fn stop_typing<'life0, 'life1, 'async_trait>( &'life0 self, recipient: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, PacedChannel: 'async_trait,

Source§

fn supports_draft_updates(&self) -> bool

Source§

fn supports_multi_message_streaming(&self) -> bool

Source§

fn multi_message_delay_ms(&self) -> u64

Source§

fn send_draft<'life0, 'life1, 'async_trait>( &'life0 self, message: &'life1 SendMessage, ) -> Pin<Box<dyn Future<Output = Result<Option<String>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, PacedChannel: 'async_trait,

Source§

fn update_draft<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, recipient: &'life1 str, message_id: &'life2 str, text: &'life3 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, PacedChannel: 'async_trait,

Source§

fn update_draft_progress<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, recipient: &'life1 str, message_id: &'life2 str, text: &'life3 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, PacedChannel: 'async_trait,

Source§

fn update_draft_progress_batch<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, recipient: &'life1 str, message_id: &'life2 str, texts: &'life3 [String], ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, PacedChannel: 'async_trait,

Source§

fn update_typed_draft_progress_batch<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, recipient: &'life1 str, message_id: &'life2 str, progress: &'life3 [DraftProgress], ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, PacedChannel: 'async_trait,

Source§

fn update_draft_lifecycle<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, recipient: &'life1 str, message_id: &'life2 str, event: ProgressEvent, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, PacedChannel: 'async_trait,

Source§

fn finalize_draft<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, recipient: &'life1 str, message_id: &'life2 str, text: &'life3 str, suppress_voice: bool, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, PacedChannel: 'async_trait,

Source§

fn cancel_draft<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, recipient: &'life1 str, message_id: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, PacedChannel: 'async_trait,

Source§

fn add_reaction<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, channel_id: &'life1 str, message_id: &'life2 str, emoji: &'life3 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, PacedChannel: 'async_trait,

Source§

fn remove_reaction<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, channel_id: &'life1 str, message_id: &'life2 str, emoji: &'life3 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, PacedChannel: 'async_trait,

Source§

fn pin_message<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, channel_id: &'life1 str, message_id: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, PacedChannel: 'async_trait,

Source§

fn unpin_message<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, channel_id: &'life1 str, message_id: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, PacedChannel: 'async_trait,

Source§

fn redact_message<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, channel_id: &'life1 str, message_id: &'life2 str, reason: Option<String>, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, PacedChannel: 'async_trait,

Source§

fn create_room<'life0, 'life1, 'async_trait>( &'life0 self, options: &'life1 RoomCreationOptions, ) -> Pin<Box<dyn Future<Output = Result<String, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, PacedChannel: 'async_trait,

Source§

fn invite_user<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, room_id: &'life1 str, user_id: &'life2 str, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, PacedChannel: 'async_trait,

Source§

fn request_approval<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, recipient: &'life1 str, request: &'life2 ChannelApprovalRequest, ) -> Pin<Box<dyn Future<Output = Result<Option<ChannelApprovalResponse>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, PacedChannel: 'async_trait,

Source§

fn request_choice<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, question: &'life1 str, choices: &'life2 [String], timeout: Duration, ) -> Pin<Box<dyn Future<Output = Result<Option<String>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, PacedChannel: 'async_trait,

Source§

fn request_multi_choice<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, question: &'life1 str, choices: &'life2 [String], min_items: usize, max_items: usize, timeout: Duration, ) -> Pin<Box<dyn Future<Output = Result<Option<Vec<String>>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, PacedChannel: 'async_trait,

Source§

fn supports_free_form_ask(&self) -> bool

Source§

impl Channel for RpcApprovalChannel

Source§

fn supports_outbound_send(&self) -> bool

send above is a deliberate no-op: the Code tab renders agent output from the RPC turn stream, not from generic channel sends. Declaring it here lets surfaces that must genuinely deliver (poll’s text fallback, escalate_to_human) refuse this channel instead of reporting success for a message the user never saw.

Source§

fn supports_free_form_ask(&self) -> bool

Free-form text elicitation is Phase 2 of the elicitation rollout — the same answer as AcpChannel. Until that lands, tools like ask_user (with no choices) and escalate_to_human (with wait_for_response) fail fast on the Code tab.

Source§

fn name(&self) -> &str

Source§

fn send<'life0, 'life1, 'async_trait>( &'life0 self, _message: &'life1 SendMessage, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, RpcApprovalChannel: 'async_trait,

Source§

fn listen<'life0, 'async_trait>( &'life0 self, _tx: Sender<ChannelMessage>, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, RpcApprovalChannel: 'async_trait,

Source§

fn request_approval<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, recipient: &'life1 str, request: &'life2 ChannelApprovalRequest, ) -> Pin<Box<dyn Future<Output = Result<Option<ChannelApprovalResponse>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, RpcApprovalChannel: 'async_trait,

Source§

fn request_approval_attributed<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, recipient: &'life1 str, request: &'life2 ChannelApprovalRequest, ) -> Pin<Box<dyn Future<Output = Result<Option<AttributedApprovalResponse>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, RpcApprovalChannel: 'async_trait,

Source§

fn request_choice<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, question: &'life1 str, choices: &'life2 [String], timeout: Duration, ) -> Pin<Box<dyn Future<Output = Result<Option<String>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, RpcApprovalChannel: 'async_trait,

Source§

fn request_multi_choice<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, question: &'life1 str, choices: &'life2 [String], min_items: usize, max_items: usize, timeout: Duration, ) -> Pin<Box<dyn Future<Output = Result<Option<Vec<String>>, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, RpcApprovalChannel: 'async_trait,

Implementors§