263 lines
7.0 KiB
Rust
263 lines
7.0 KiB
Rust
use crate::bus::InboundMessage;
|
||
use crate::command::context::CommandContext;
|
||
use crate::command::response::{CommandError, CommandResponse};
|
||
use crate::command::Command;
|
||
use crate::agent::AgentError;
|
||
use crate::gateway::session::SessionManager;
|
||
use async_trait::async_trait;
|
||
|
||
/// 命令处理器 trait
|
||
///
|
||
/// 实现此 trait 来处理特定类型的命令
|
||
/// 处理器是渠道无关的,只关心 Command 本身
|
||
#[async_trait]
|
||
pub trait CommandHandler: Send + Sync {
|
||
/// 是否可以处理此命令
|
||
fn can_handle(&self, cmd: &Command) -> bool;
|
||
|
||
/// 执行命令
|
||
///
|
||
/// # Arguments
|
||
/// * `cmd` - 要执行的命令
|
||
/// * `ctx` - 命令执行上下文
|
||
///
|
||
/// # Returns
|
||
/// * `Ok(CommandResponse)` - 命令执行成功
|
||
/// * `Err(CommandError)` - 命令执行失败
|
||
async fn handle(
|
||
&self,
|
||
cmd: Command,
|
||
ctx: CommandContext,
|
||
) -> Result<CommandResponse, CommandError>;
|
||
}
|
||
|
||
/// InChat 命令处理器 trait
|
||
///
|
||
/// 用于处理在聊天中直接输入的命令(如 Feishu/WeChat 等通道)
|
||
/// 接收 InboundMessage 和 SessionManager
|
||
#[async_trait]
|
||
pub trait InChatCommandHandler: Send + Sync {
|
||
/// 是否可以处理此命令
|
||
fn can_handle(&self, cmd: &Command) -> bool;
|
||
|
||
/// 执行命令
|
||
///
|
||
/// # Arguments
|
||
/// * `cmd` - 要执行的命令
|
||
/// * `inbound` - 入站消息(包含通道信息)
|
||
/// * `session_manager` - 会话管理器(用于获取 session)
|
||
///
|
||
/// # Returns
|
||
/// * `Ok(Some(msg))` - 命令执行成功,返回要发送给用户的消息
|
||
/// * `Ok(None)` - 命令执行成功,无需发送消息
|
||
/// * `Err(AgentError)` - 命令执行失败
|
||
async fn handle(
|
||
&self,
|
||
cmd: Command,
|
||
inbound: &InboundMessage,
|
||
session_manager: &SessionManager,
|
||
) -> Result<Option<String>, AgentError>;
|
||
}
|
||
|
||
/// 命令路由器
|
||
///
|
||
/// 负责将命令分发到合适的处理器
|
||
pub struct CommandRouter {
|
||
handlers: Vec<Box<dyn CommandHandler>>,
|
||
}
|
||
|
||
impl CommandRouter {
|
||
/// 创建新的命令路由器
|
||
pub fn new() -> Self {
|
||
Self {
|
||
handlers: Vec::new(),
|
||
}
|
||
}
|
||
|
||
/// 注册命令处理器
|
||
///
|
||
/// # Arguments
|
||
/// * `handler` - 要注册的处理器
|
||
pub fn register(&mut self, handler: Box<dyn CommandHandler>) {
|
||
self.handlers.push(handler);
|
||
}
|
||
|
||
/// 分发命令到合适的处理器
|
||
///
|
||
/// # Arguments
|
||
/// * `cmd` - 要执行的命令
|
||
/// * `ctx` - 命令执行上下文
|
||
///
|
||
/// # Returns
|
||
/// * `Ok(CommandResponse)` - 命令执行成功
|
||
/// * `Err(CommandError)` - 没有合适的处理器或执行失败
|
||
pub async fn dispatch(
|
||
&self,
|
||
cmd: Command,
|
||
ctx: CommandContext,
|
||
) -> Result<CommandResponse, CommandError> {
|
||
// 查找能处理此命令的处理器
|
||
for handler in &self.handlers {
|
||
if handler.can_handle(&cmd) {
|
||
return handler.handle(cmd, ctx).await;
|
||
}
|
||
}
|
||
|
||
// 没有找到合适的处理器
|
||
Err(CommandError::new(
|
||
"NO_HANDLER",
|
||
format!("No handler found for command: {}", cmd.name()),
|
||
))
|
||
}
|
||
|
||
/// 分发命令,返回响应(如果失败则返回错误响应)
|
||
///
|
||
/// 与 `dispatch` 不同,此方法不会返回 Err,
|
||
/// 而是将错误包装在 CommandResponse 中
|
||
pub async fn dispatch_with_response(
|
||
&self,
|
||
cmd: Command,
|
||
ctx: CommandContext,
|
||
) -> CommandResponse {
|
||
let request_id = ctx.request_id;
|
||
match self.dispatch(cmd, ctx).await {
|
||
Ok(response) => response,
|
||
Err(err) => CommandResponse::error(request_id, err),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Default for CommandRouter {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
/// InChat 命令路由器
|
||
///
|
||
/// 负责将在聊天中输入的命令分发到合适的处理器
|
||
pub struct InChatCommandRouter {
|
||
handlers: Vec<Box<dyn InChatCommandHandler>>,
|
||
}
|
||
|
||
impl InChatCommandRouter {
|
||
/// 创建新的 InChat 命令路由器
|
||
pub fn new() -> Self {
|
||
Self {
|
||
handlers: Vec::new(),
|
||
}
|
||
}
|
||
|
||
/// 注册 InChat 命令处理器
|
||
///
|
||
/// # Arguments
|
||
/// * `handler` - 要注册的处理器
|
||
pub fn register(&mut self, handler: Box<dyn InChatCommandHandler>) {
|
||
self.handlers.push(handler);
|
||
}
|
||
|
||
/// 分发命令到合适的处理器
|
||
///
|
||
/// # Arguments
|
||
/// * `cmd` - 要执行的命令
|
||
/// * `inbound` - 入站消息
|
||
/// * `session_manager` - 会话管理器
|
||
///
|
||
/// # Returns
|
||
/// * `Ok(Some(msg))` - 命令被处理,返回成功消息
|
||
/// * `Ok(None)` - 没有合适的处理器
|
||
/// * `Err(AgentError)` - 执行失败
|
||
pub async fn dispatch(
|
||
&self,
|
||
cmd: Command,
|
||
inbound: &InboundMessage,
|
||
session_manager: &SessionManager,
|
||
) -> Result<Option<String>, AgentError> {
|
||
// 查找能处理此命令的处理器
|
||
for handler in &self.handlers {
|
||
if handler.can_handle(&cmd) {
|
||
let result = handler.handle(cmd, inbound, session_manager).await?;
|
||
return Ok(result);
|
||
}
|
||
}
|
||
|
||
// 没有找到合适的处理器
|
||
Ok(None)
|
||
}
|
||
}
|
||
|
||
impl Default for InChatCommandRouter {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use uuid::Uuid;
|
||
|
||
struct TestHandler;
|
||
|
||
#[async_trait]
|
||
impl CommandHandler for TestHandler {
|
||
fn can_handle(&self, cmd: &Command) -> bool {
|
||
matches!(cmd, Command::CreateSession { .. })
|
||
}
|
||
|
||
async fn handle(
|
||
&self,
|
||
_cmd: Command,
|
||
ctx: CommandContext,
|
||
) -> Result<CommandResponse, CommandError> {
|
||
Ok(CommandResponse::success(ctx.request_id)
|
||
.with_message(crate::command::response::MessageKind::Notification, "ok"))
|
||
}
|
||
}
|
||
|
||
struct NoOpHandler;
|
||
|
||
#[async_trait]
|
||
impl CommandHandler for NoOpHandler {
|
||
fn can_handle(&self, _cmd: &Command) -> bool {
|
||
false
|
||
}
|
||
|
||
async fn handle(
|
||
&self,
|
||
_cmd: Command,
|
||
_ctx: CommandContext,
|
||
) -> Result<CommandResponse, CommandError> {
|
||
unreachable!()
|
||
}
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_router_finds_handler() {
|
||
let mut router = CommandRouter::new();
|
||
router.register(Box::new(TestHandler));
|
||
router.register(Box::new(NoOpHandler));
|
||
|
||
let ctx = CommandContext::new("test", "test");
|
||
let cmd = Command::CreateSession { title: None };
|
||
|
||
let result = router.dispatch(cmd, ctx).await;
|
||
|
||
assert!(result.is_ok());
|
||
let resp = result.unwrap();
|
||
assert!(resp.success);
|
||
}
|
||
|
||
#[tokio::test]
|
||
async fn test_router_no_handler() {
|
||
let router = CommandRouter::new();
|
||
|
||
let ctx = CommandContext::new("test", "test");
|
||
let cmd = Command::CreateSession { title: None };
|
||
|
||
let result = router.dispatch(cmd, ctx).await;
|
||
|
||
assert!(result.is_err());
|
||
}
|
||
}
|