use super::agent::{AIAgent, AgentContext};
use crate::protocol::{Message, MessageType, Channel};
use crate::{Error, Result};
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Debug, Clone)]
pub struct HandlerContext {
pub channel: Option<Channel>,
pub is_dm: bool,
pub recent_messages: Vec<Message>,
pub mentions: Vec<String>,
}
impl HandlerContext {
pub fn from_message(msg: &Message, channel: Option<Channel>) -> Self {
let is_dm = channel
.as_ref()
.map(|c| c.channel_type == crate::protocol::ChannelType::Direct)
.unwrap_or(true);
Self {
channel,
is_dm,
recent_messages: Vec::new(),
mentions: msg.metadata.mentions.clone(),
}
}
}
#[derive(Debug)]
pub enum HandlerResult {
Handled(Option<Message>),
NotHandled,
Error(Error),
}
#[async_trait::async_trait]
pub trait MessageHandler: Send + Sync {
async fn handle(&self, msg: &Message, ctx: &HandlerContext) -> HandlerResult;
fn name(&self) -> &str;
fn priority(&self) -> i32 {
0
}
}
pub struct AIAgentHandler {
agent: Arc<AIAgent>,
}
impl AIAgentHandler {
pub fn new(agent: Arc<AIAgent>) -> Self {
Self { agent }
}
}
#[async_trait::async_trait]
impl MessageHandler for AIAgentHandler {
async fn handle(&self, msg: &Message, ctx: &HandlerContext) -> HandlerResult {
let agent_ctx = AgentContext {
channel: ctx.channel.clone(),
recent_messages: ctx.recent_messages.clone(),
mentions: ctx.mentions.clone(),
is_dm: ctx.is_dm,
};
match self.agent.process_message(msg, agent_ctx).await {
Ok(Some(response)) => HandlerResult::Handled(Some(response)),
Ok(None) => HandlerResult::NotHandled,
Err(e) => HandlerResult::Error(e),
}
}
fn name(&self) -> &str {
self.agent.name()
}
fn priority(&self) -> i32 {
-10 }
}
pub struct CommandHandler {
prefix: String,
agent: Arc<AIAgent>,
}
impl CommandHandler {
pub fn new(prefix: impl Into<String>, agent: Arc<AIAgent>) -> Self {
Self {
prefix: prefix.into(),
agent,
}
}
fn parse_command(&self, text: &str) -> Option<(String, Vec<String>)> {
if !text.starts_with(&self.prefix) {
return None;
}
let without_prefix = &text[self.prefix.len()..];
let parts: Vec<&str> = without_prefix.split_whitespace().collect();
if parts.is_empty() {
return None;
}
let command = parts[0].to_lowercase();
let args: Vec<String> = parts[1..].iter().map(|s| s.to_string()).collect();
Some((command, args))
}
}
#[async_trait::async_trait]
impl MessageHandler for CommandHandler {
async fn handle(&self, msg: &Message, _ctx: &HandlerContext) -> HandlerResult {
let text = match &msg.content {
crate::protocol::message::MessageContent::Text(t) => t,
_ => return HandlerResult::NotHandled,
};
let (command, args) = match self.parse_command(text) {
Some(cmd) => cmd,
None => return HandlerResult::NotHandled,
};
let args_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
match self.agent.handle_command(&command, &args_refs).await {
Ok(response) => {
let reply = Message::text(
&self.agent.fingerprint().to_hex(),
&msg.sender,
response,
);
HandlerResult::Handled(Some(reply))
}
Err(e) => HandlerResult::Error(e),
}
}
fn name(&self) -> &str {
"CommandHandler"
}
fn priority(&self) -> i32 {
100 }
}
pub struct HandlerChain {
handlers: Vec<Box<dyn MessageHandler>>,
}
impl HandlerChain {
pub fn new() -> Self {
Self {
handlers: Vec::new(),
}
}
pub fn add<H: MessageHandler + 'static>(&mut self, handler: H) {
self.handlers.push(Box::new(handler));
self.handlers.sort_by(|a, b| b.priority().cmp(&a.priority()));
}
pub async fn process(&self, msg: &Message, ctx: &HandlerContext) -> Option<Message> {
for handler in &self.handlers {
match handler.handle(msg, ctx).await {
HandlerResult::Handled(response) => {
tracing::debug!("Message handled by {}", handler.name());
return response;
}
HandlerResult::NotHandled => continue,
HandlerResult::Error(e) => {
tracing::error!("Handler {} error: {}", handler.name(), e);
continue;
}
}
}
None
}
}
impl Default for HandlerChain {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_command_parsing() {
let agent = Arc::new(AIAgent::new(Default::default()).unwrap());
let handler = CommandHandler::new("/", agent);
let (cmd, args) = handler.parse_command("/help").unwrap();
assert_eq!(cmd, "help");
assert!(args.is_empty());
let (cmd, args) = handler.parse_command("/status arg1 arg2").unwrap();
assert_eq!(cmd, "status");
assert_eq!(args, vec!["arg1", "arg2"]);
assert!(handler.parse_command("no prefix").is_none());
}
}