137 lines
4.0 KiB
Rust
137 lines
4.0 KiB
Rust
use async_trait::async_trait;
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use crate::bus::{BusError, CommittedTurnDelta, InboundMessage, MessageBus, OutboundMessage};
|
|
use crate::delivery::PresentationPolicy;
|
|
use crate::session::TurnSnapshot;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum LivePolicy {
|
|
FinalOnly,
|
|
Snapshot { min_interval: Duration },
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct TurnTarget {
|
|
pub channel: String,
|
|
pub chat_id: String,
|
|
pub session_id: String,
|
|
pub reply_to: Option<String>,
|
|
pub metadata: HashMap<String, String>,
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait TurnSink: Send {
|
|
async fn update(&mut self, snapshot: &TurnSnapshot) -> Result<(), ChannelError>;
|
|
async fn finish(&mut self, snapshot: &TurnSnapshot) -> Result<(), ChannelError>;
|
|
async fn abort(&mut self, snapshot: &TurnSnapshot) -> Result<(), ChannelError>;
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum ChannelError {
|
|
ConfigError(String),
|
|
ConnectionError(String),
|
|
SendError(String),
|
|
BusError(String),
|
|
Other(String),
|
|
}
|
|
|
|
impl std::fmt::Display for ChannelError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
ChannelError::ConfigError(s) => write!(f, "Config error: {}", s),
|
|
ChannelError::ConnectionError(s) => write!(f, "Connection error: {}", s),
|
|
ChannelError::SendError(s) => write!(f, "Send error: {}", s),
|
|
ChannelError::BusError(s) => write!(f, "Bus error: {}", s),
|
|
ChannelError::Other(s) => write!(f, "Error: {}", s),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for ChannelError {}
|
|
|
|
impl ChannelError {
|
|
pub fn is_transient(&self) -> bool {
|
|
matches!(self, Self::ConnectionError(_) | Self::SendError(_))
|
|
}
|
|
}
|
|
|
|
impl From<BusError> for ChannelError {
|
|
fn from(e: BusError) -> Self {
|
|
ChannelError::BusError(e.to_string())
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait Channel: Send + Sync + 'static {
|
|
fn name(&self) -> &str;
|
|
fn is_running(&self) -> bool;
|
|
|
|
/// Start the channel with a reference to the MessageBus
|
|
async fn start(&self, bus: Arc<MessageBus>) -> Result<(), ChannelError>;
|
|
|
|
/// Stop the channel
|
|
async fn stop(&self) -> Result<(), ChannelError>;
|
|
|
|
fn live_policy(&self) -> LivePolicy {
|
|
LivePolicy::FinalOnly
|
|
}
|
|
|
|
fn presentation_policy(&self) -> PresentationPolicy {
|
|
PresentationPolicy::external(matches!(self.live_policy(), LivePolicy::Snapshot { .. }))
|
|
}
|
|
|
|
async fn open_turn(&self, _target: TurnTarget) -> Result<Box<dyn TurnSink>, ChannelError> {
|
|
Err(ChannelError::Other(format!(
|
|
"channel {} does not support turn delivery",
|
|
self.name()
|
|
)))
|
|
}
|
|
|
|
/// Deliver a durable history delta after a Turn commit. Channels without
|
|
/// local history views intentionally ignore this event.
|
|
async fn commit_turn(
|
|
&self,
|
|
_target: &TurnTarget,
|
|
_delta: CommittedTurnDelta,
|
|
) -> Result<(), ChannelError> {
|
|
Ok(())
|
|
}
|
|
|
|
/// Whether `commit_turn` presents media references from the committed
|
|
/// assistant message to the user. Channels that return false receive a
|
|
/// separate media-only outbound delivery after the durable commit.
|
|
fn commit_turn_presents_media(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
/// Send a message to the channel (called by OutboundDispatcher)
|
|
async fn send(&self, msg: OutboundMessage) -> Result<(), ChannelError>;
|
|
|
|
/// Check if a sender is allowed to use this channel
|
|
fn is_allowed(&self, _sender_id: &str) -> bool {
|
|
true
|
|
}
|
|
|
|
/// Handle an inbound message: check permissions and publish to bus
|
|
async fn handle_and_publish(
|
|
&self,
|
|
bus: &Arc<MessageBus>,
|
|
msg: &InboundMessage,
|
|
) -> Result<(), ChannelError> {
|
|
if !self.is_allowed(&msg.sender_id) {
|
|
tracing::warn!(
|
|
channel = %self.name(),
|
|
sender = %msg.sender_id,
|
|
"Access denied"
|
|
);
|
|
return Ok(());
|
|
}
|
|
|
|
bus.publish_inbound(msg.clone()).await?;
|
|
Ok(())
|
|
}
|
|
}
|