use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;
use async_trait::async_trait;
use crate::internal::channels::channel::ConversationKind;
#[async_trait]
pub trait ChannelAdapter: Send + Sync {
fn name(&self) -> &str;
fn chat_type_routes(&self) -> Vec<(&str, ConversationKind)>;
fn is_local_only(&self) -> bool {
false
}
fn allows_cross_chat(&self) -> bool {
true
}
async fn send_text(&self, external_chat_id: &str, text: &str) -> Result<(), String>;
fn text_chunk_limit_bytes(&self) -> Option<usize> {
None
}
fn text_chunk_delay(&self) -> Option<std::time::Duration> {
None
}
async fn send_text_chunk(
&self,
external_chat_id: &str,
text: &str,
_idempotency_key: &str,
) -> Result<(), String> {
self.send_text(external_chat_id, text).await
}
async fn send_attachment(
&self,
_external_chat_id: &str,
_file_path: &Path,
_caption: Option<&str>,
) -> Result<String, String> {
Err(format!("attachments not supported for {}", self.name()))
}
}
#[derive(Default)]
pub struct ChannelRegistry {
adapters: HashMap<String, Arc<dyn ChannelAdapter>>,
type_to_channel: HashMap<String, String>,
type_to_conversation: HashMap<String, ConversationKind>,
output_guardrail: microclaw_core::redact::OutputGuardrailMode,
}
impl ChannelRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn set_output_guardrail(&mut self, mode: microclaw_core::redact::OutputGuardrailMode) {
self.output_guardrail = mode;
}
pub fn output_guardrail(&self) -> microclaw_core::redact::OutputGuardrailMode {
self.output_guardrail
}
pub fn register(&mut self, adapter: Arc<dyn ChannelAdapter>) {
let name = adapter.name().to_string();
for (chat_type, kind) in adapter.chat_type_routes() {
self.type_to_channel
.insert(chat_type.to_string(), name.clone());
self.type_to_conversation
.insert(chat_type.to_string(), kind);
}
self.adapters.insert(name, adapter);
}
pub fn get(&self, name: &str) -> Option<&Arc<dyn ChannelAdapter>> {
self.adapters.get(name)
}
pub fn resolve(
&self,
db_chat_type: &str,
) -> Option<(&Arc<dyn ChannelAdapter>, ConversationKind)> {
let channel_name = self.type_to_channel.get(db_chat_type)?;
let adapter = self.adapters.get(channel_name)?;
let kind = self.type_to_conversation.get(db_chat_type)?;
Some((adapter, *kind))
}
pub fn resolve_routing(&self, db_chat_type: &str) -> Option<(&str, ConversationKind)> {
let channel_name = self.type_to_channel.get(db_chat_type)?;
let kind = self.type_to_conversation.get(db_chat_type)?;
Some((channel_name.as_str(), *kind))
}
pub fn has_any(&self) -> bool {
!self.adapters.is_empty()
}
}