PicoBot/src/bus/mod.rs

133 lines
5.0 KiB
Rust

pub mod dispatcher;
pub mod message;
pub use dispatcher::OutboundDispatcher;
pub use message::{
ChannelContext, ChatMessage, CommittedMessage, CommittedTurnDelta, CompletionStatus,
ContentBlock, ControlMessage, InboundMessage, MediaItem, MediaRef, MessageSource,
OutboundMessage, ProviderReasoningState, SourceKind,
};
use std::sync::Arc;
use tokio::sync::{Mutex, mpsc};
// ============================================================================
// MessageBus - Async message queue for Channel <-> Agent communication
// ============================================================================
pub struct MessageBus {
inbound_tx: mpsc::Sender<InboundMessage>,
outbound_tx: mpsc::Sender<OutboundMessage>,
inbound_rx: Mutex<mpsc::Receiver<InboundMessage>>,
outbound_rx: Mutex<mpsc::Receiver<OutboundMessage>>,
// Control channel for session management operations
control_tx: mpsc::Sender<ControlMessage>,
control_rx: Mutex<mpsc::Receiver<ControlMessage>>,
}
impl MessageBus {
/// Create a new MessageBus with the given channel capacity
pub fn new(capacity: usize) -> Arc<Self> {
let (inbound_tx, inbound_rx) = mpsc::channel(capacity);
let (outbound_tx, outbound_rx) = mpsc::channel(capacity);
let (control_tx, control_rx) = mpsc::channel(capacity);
Arc::new(Self {
inbound_tx,
outbound_tx,
inbound_rx: Mutex::new(inbound_rx),
outbound_rx: Mutex::new(outbound_rx),
control_tx,
control_rx: Mutex::new(control_rx),
})
}
/// Publish an inbound message (Channel -> Bus)
pub async fn publish_inbound(&self, msg: InboundMessage) -> Result<(), BusError> {
#[cfg(debug_assertions)]
tracing::debug!(channel = %msg.channel, sender = %msg.sender_id, chat = %msg.chat_id, content_len = %msg.content.len(), media_count = %msg.media.len(), "Bus: publishing inbound message");
self.inbound_tx
.send(msg)
.await
.map_err(|_| BusError::Closed)
}
/// Consume an inbound message (Agent -> Bus)
pub async fn consume_inbound(&self) -> Option<InboundMessage> {
let msg = self.inbound_rx.lock().await.recv().await?;
#[cfg(debug_assertions)]
tracing::debug!(channel = %msg.channel, sender = %msg.sender_id, chat = %msg.chat_id, "Bus: consuming inbound message");
Some(msg)
}
/// Publish an outbound message (Agent -> Bus)
pub async fn publish_outbound(&self, msg: OutboundMessage) -> Result<(), BusError> {
#[cfg(debug_assertions)]
tracing::debug!(channel = %msg.channel, chat_id = %msg.chat_id, content_len = %msg.content.len(), "Bus: publishing outbound message");
self.outbound_tx
.send(msg)
.await
.map_err(|_| BusError::Closed)
}
/// Publish an outbound message and wait for the dispatcher to report the
/// actual channel delivery result.
pub async fn deliver_outbound(&self, mut msg: OutboundMessage) -> Result<(), BusError> {
let (delivery_tx, mut delivery_rx) = tokio::sync::watch::channel(None);
msg.delivery = Some(delivery_tx);
self.publish_outbound(msg).await?;
tokio::time::timeout(std::time::Duration::from_secs(120), async {
loop {
delivery_rx.changed().await.map_err(|_| BusError::Closed)?;
if let Some(result) = delivery_rx.borrow().clone() {
return result.map_err(BusError::DeliveryFailed);
}
}
})
.await
.map_err(|_| BusError::DeliveryTimedOut)?
}
/// Consume an outbound message (Dispatcher -> Bus)
pub async fn consume_outbound(&self) -> Option<OutboundMessage> {
self.outbound_rx.lock().await.recv().await
}
/// Publish a control message (Channel -> Bus for session management)
pub async fn publish_control(&self, msg: ControlMessage) -> Result<(), BusError> {
tracing::debug!(op = ?msg.op, "Bus: publishing control message");
self.control_tx
.send(msg)
.await
.map_err(|_| BusError::Closed)
}
/// Consume a control message (ControlProcessor -> Bus)
pub async fn consume_control(&self) -> Option<ControlMessage> {
self.control_rx.lock().await.recv().await
}
}
// ============================================================================
// BusError
// ============================================================================
#[derive(Debug)]
pub enum BusError {
Closed,
DeliveryFailed(String),
DeliveryTimedOut,
}
impl std::fmt::Display for BusError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BusError::Closed => write!(f, "Bus channel closed"),
BusError::DeliveryFailed(error) => write!(f, "Outbound delivery failed: {error}"),
BusError::DeliveryTimedOut => write!(f, "Outbound delivery confirmation timed out"),
}
}
}
impl std::error::Error for BusError {}