use async_trait::async_trait;
use punch_types::FighterId;
use crate::ChannelPlatform;
use crate::router::ChannelRouter;
#[async_trait]
pub trait ChannelBridgeHandle: Send + Sync {
async fn send_message(&self, fighter_id: FighterId, message: &str) -> Result<String, String>;
async fn find_fighter_by_name(&self, name: &str) -> Result<Option<FighterId>, String>;
async fn list_fighters(&self) -> Result<Vec<(FighterId, String)>, String>;
async fn spawn_fighter_by_name(&self, manifest_name: &str) -> Result<FighterId, String>;
}
pub async fn process_incoming_message(
handle: &dyn ChannelBridgeHandle,
router: &ChannelRouter,
platform: &ChannelPlatform,
user_id: &str,
_display_name: &str,
message_text: &str,
) -> Result<String, String> {
let fighter_id = match router.resolve(platform, user_id) {
Some(id) => id,
None => {
let default_name = router
.channel_default_name(platform)
.unwrap_or_else(|| "assistant".to_string());
match handle.find_fighter_by_name(&default_name).await {
Ok(Some(id)) => {
router.set_direct_route(platform, user_id, id);
id
}
Ok(None) => {
match handle.spawn_fighter_by_name(&default_name).await {
Ok(id) => {
router.set_direct_route(platform, user_id, id);
router.register_fighter(default_name, id);
id
}
Err(e) => {
match handle.list_fighters().await {
Ok(fighters) if !fighters.is_empty() => {
let (id, _) = &fighters[0];
router.set_direct_route(platform, user_id, *id);
*id
}
_ => {
return Err(format!(
"No fighters available and could not spawn '{}': {}",
default_name, e
));
}
}
}
}
}
Err(e) => {
return Err(format!("Error finding fighter: {}", e));
}
}
}
};
handle.send_message(fighter_id, message_text).await
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
struct MockHandle {
agents: Mutex<Vec<(FighterId, String)>>,
responses: Mutex<Vec<(FighterId, String)>>,
}
#[async_trait]
impl ChannelBridgeHandle for MockHandle {
async fn send_message(
&self,
fighter_id: FighterId,
message: &str,
) -> Result<String, String> {
self.responses
.lock()
.unwrap()
.push((fighter_id, message.to_string()));
Ok(format!("Echo: {message}"))
}
async fn find_fighter_by_name(&self, name: &str) -> Result<Option<FighterId>, String> {
let agents = self.agents.lock().unwrap();
Ok(agents.iter().find(|(_, n)| n == name).map(|(id, _)| *id))
}
async fn list_fighters(&self) -> Result<Vec<(FighterId, String)>, String> {
Ok(self.agents.lock().unwrap().clone())
}
async fn spawn_fighter_by_name(&self, _name: &str) -> Result<FighterId, String> {
Err("spawn not implemented in mock".to_string())
}
}
#[tokio::test]
async fn test_process_message_existing_route() {
let fighter_id = FighterId::new();
let handle = MockHandle {
agents: Mutex::new(vec![(fighter_id, "bot".to_string())]),
responses: Mutex::new(Vec::new()),
};
let router = ChannelRouter::new();
router.set_direct_route(&ChannelPlatform::Telegram, "user1", fighter_id);
let result = process_incoming_message(
&handle,
&router,
&ChannelPlatform::Telegram,
"user1",
"Alice",
"Hello!",
)
.await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "Echo: Hello!");
}
#[tokio::test]
async fn test_process_message_auto_routes_to_existing_fighter() {
let fighter_id = FighterId::new();
let handle = MockHandle {
agents: Mutex::new(vec![(fighter_id, "assistant".to_string())]),
responses: Mutex::new(Vec::new()),
};
let router = ChannelRouter::new();
let result = process_incoming_message(
&handle,
&router,
&ChannelPlatform::Telegram,
"user1",
"Alice",
"Hello!",
)
.await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "Echo: Hello!");
assert!(router.has_route(&ChannelPlatform::Telegram, "user1"));
}
#[tokio::test]
async fn test_process_message_no_fighters_available() {
let handle = MockHandle {
agents: Mutex::new(vec![]),
responses: Mutex::new(Vec::new()),
};
let router = ChannelRouter::new();
let result = process_incoming_message(
&handle,
&router,
&ChannelPlatform::Telegram,
"user1",
"Alice",
"Hello!",
)
.await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("No fighters available"));
}
#[tokio::test]
async fn test_conversation_continuity() {
let fighter_id = FighterId::new();
let handle = MockHandle {
agents: Mutex::new(vec![(fighter_id, "assistant".to_string())]),
responses: Mutex::new(Vec::new()),
};
let router = ChannelRouter::new();
let _ = process_incoming_message(
&handle,
&router,
&ChannelPlatform::Telegram,
"user1",
"Alice",
"First message",
)
.await;
let _ = process_incoming_message(
&handle,
&router,
&ChannelPlatform::Telegram,
"user1",
"Alice",
"Second message",
)
.await;
let responses = handle.responses.lock().unwrap();
assert_eq!(responses.len(), 2);
assert_eq!(responses[0].0, responses[1].0);
}
#[tokio::test]
async fn test_different_users_different_routes() {
let fighter_id = FighterId::new();
let handle = MockHandle {
agents: Mutex::new(vec![(fighter_id, "assistant".to_string())]),
responses: Mutex::new(Vec::new()),
};
let router = ChannelRouter::new();
let _ = process_incoming_message(
&handle,
&router,
&ChannelPlatform::Telegram,
"user1",
"Alice",
"Hello from user1",
)
.await;
let _ = process_incoming_message(
&handle,
&router,
&ChannelPlatform::Telegram,
"user2",
"Bob",
"Hello from user2",
)
.await;
assert!(router.has_route(&ChannelPlatform::Telegram, "user1"));
assert!(router.has_route(&ChannelPlatform::Telegram, "user2"));
}
}