Compare commits

...

2 Commits

13 changed files with 1019 additions and 23 deletions

View File

@ -1,5 +1,8 @@
pub use crate::protocol::{WsInbound, WsOutbound, serialize_inbound, serialize_outbound};
use crate::command::adapter::InputAdapter;
use crate::command::adapters::cli::CliInputAdapter;
use crate::command::context::AdapterContext;
use futures_util::{SinkExt, StreamExt};
use tokio_tungstenite::{connect_async, tungstenite::Message};
@ -143,10 +146,39 @@ pub async fn run(gateway_url: &str) -> Result<(), Box<dyn std::error::Error>> {
continue;
}
InputEvent::Command(InputCommand::New(title)) => {
let inbound = WsInbound::CreateSession { title };
// 使用新的命令层:通过 CliInputAdapter 构建 Command
let adapter = CliInputAdapter::new();
let ctx = AdapterContext::new("cli")
.with_session_id(current_session_id.as_deref().unwrap_or(""));
// 构建输入字符串
let input = match title {
Some(t) => format!("/new {}", t),
None => "/new".to_string(),
};
// 解析为 Command
match adapter.try_parse(&input, ctx) {
Ok(Some(command)) => {
// 序列化为 JSON 通过 WebSocket 发送
let json = serde_json::to_string(&command).unwrap_or_default();
let inbound = WsInbound::UserInput {
content: json,
channel: None,
chat_id: current_session_id.clone(),
sender_id: None,
};
if let Ok(text) = serialize_inbound(&inbound) {
let _ = sender.send(Message::Text(text.into())).await;
}
}
Ok(None) => {
tracing::warn!("Failed to parse /new command");
}
Err(e) => {
tracing::error!(error = %e, "Error parsing /new command");
}
}
continue;
}
InputEvent::Command(InputCommand::Sessions) => {

89
src/command/adapter.rs Normal file
View File

@ -0,0 +1,89 @@
use crate::command::context::AdapterContext;
use crate::command::response::{CommandError, CommandResponse};
use crate::command::Command;
/// 输入适配器:将渠道特定输入转换为 Command
///
/// 不同渠道CLI、WebSocket、HTTP 等)实现此 trait
/// 将各自的输入格式统一转换为 Command
pub trait InputAdapter: Send + Sync {
/// 尝试将输入解析为 Command
///
/// # Returns
/// - `Ok(Some(Command))`:成功解析为命令
/// - `Ok(None)`:不是命令(如普通聊天消息)
/// - `Err(CommandError)`:解析错误(如缺少参数)
fn try_parse(
&self,
input: &str,
ctx: AdapterContext,
) -> Result<Option<Command>, AdapterError>;
}
/// 输出适配器:将 CommandResponse 转换为渠道特定输出
///
/// 不同渠道CLI、WebSocket、HTTP 等)实现此 trait
/// 将统一的 CommandResponse 转换为自己的输出格式
pub trait OutputAdapter: Send + Sync {
/// 输出类型
type Output;
/// 将 CommandResponse 转换为渠道特定输出
fn adapt(&self, response: CommandResponse) -> Self::Output;
}
/// 适配器错误
#[derive(Debug, Clone)]
pub enum AdapterError {
/// 缺少参数
MissingArgument { expected: String },
/// 解析错误
ParseError { message: String },
/// 不支持的命令
UnsupportedCommand { command: String },
}
impl AdapterError {
/// 创建缺少参数错误
pub fn missing_argument(expected: impl Into<String>) -> Self {
Self::MissingArgument {
expected: expected.into(),
}
}
/// 创建解析错误
pub fn parse_error(message: impl Into<String>) -> Self {
Self::ParseError {
message: message.into(),
}
}
/// 创建不支持命令错误
pub fn unsupported_command(command: impl Into<String>) -> Self {
Self::UnsupportedCommand {
command: command.into(),
}
}
}
impl std::fmt::Display for AdapterError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AdapterError::MissingArgument { expected } => {
write!(f, "Missing argument: expected {}", expected)
}
AdapterError::ParseError { message } => write!(f, "Parse error: {}", message),
AdapterError::UnsupportedCommand { command } => {
write!(f, "Unsupported command: {}", command)
}
}
}
}
impl std::error::Error for AdapterError {}
impl From<AdapterError> for CommandError {
fn from(err: AdapterError) -> Self {
CommandError::new("ADAPTER_ERROR", err.to_string())
}
}

173
src/command/adapters/cli.rs Normal file
View File

@ -0,0 +1,173 @@
use crate::command::adapter::{AdapterError, InputAdapter, OutputAdapter};
use crate::command::context::AdapterContext;
use crate::command::response::{CommandResponse, MessageKind};
use crate::command::Command;
/// CLI 输入适配器
///
/// 将 CLI 的 "/new title" 等输入格式转换为 Command
pub struct CliInputAdapter;
impl CliInputAdapter {
/// 创建新的 CLI 输入适配器
pub fn new() -> Self {
Self
}
}
impl Default for CliInputAdapter {
fn default() -> Self {
Self::new()
}
}
impl InputAdapter for CliInputAdapter {
fn try_parse(
&self,
input: &str,
_ctx: AdapterContext,
) -> Result<Option<Command>, AdapterError> {
let trimmed = input.trim();
// 解析 /new 命令
if trimmed == "/new" {
return Ok(Some(Command::CreateSession { title: None }));
}
if let Some(title) = trimmed.strip_prefix("/new ") {
let title = title.trim();
return Ok(Some(Command::CreateSession {
title: Some(title.to_string()),
}));
}
// 不是命令,返回 None
Ok(None)
}
}
/// CLI 输出适配器
///
/// 将 CommandResponse 转换为 CLI 可读的字符串
pub struct CliOutputAdapter;
impl CliOutputAdapter {
/// 创建新的 CLI 输出适配器
pub fn new() -> Self {
Self
}
}
impl Default for CliOutputAdapter {
fn default() -> Self {
Self::new()
}
}
impl OutputAdapter for CliOutputAdapter {
type Output = String;
fn adapt(&self, response: CommandResponse) -> String {
if let Some(error) = response.error {
return format!("Error [{}]: {}", error.code, error.message);
}
if !response.success {
return "Error: Unknown error".to_string();
}
let mut lines = Vec::new();
for msg in &response.messages {
match msg.kind {
MessageKind::Text | MessageKind::Notification => {
lines.push(msg.content.clone());
}
MessageKind::Error => {
lines.push(format!("Error: {}", msg.content));
}
_ => {
// 其他类型在 CLI 中简单显示
lines.push(msg.content.clone());
}
}
}
if lines.is_empty() {
"Success".to_string()
} else {
lines.join("\n")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cli_input_adapter_new_without_title() {
let adapter = CliInputAdapter::new();
let ctx = AdapterContext::new("test");
let result = adapter.try_parse("/new", ctx).unwrap();
assert!(result.is_some());
let cmd = result.unwrap();
assert!(matches!(cmd, Command::CreateSession { title: None }));
}
#[test]
fn test_cli_input_adapter_new_with_title() {
let adapter = CliInputAdapter::new();
let ctx = AdapterContext::new("test");
let result = adapter.try_parse("/new my session", ctx).unwrap();
assert!(result.is_some());
let cmd = result.unwrap();
assert!(matches!(
cmd,
Command::CreateSession {
title: Some(ref t)
} if t == "my session"
));
}
#[test]
fn test_cli_input_adapter_not_command() {
let adapter = CliInputAdapter::new();
let ctx = AdapterContext::new("test");
let result = adapter.try_parse("hello world", ctx).unwrap();
assert!(result.is_none());
}
#[test]
fn test_cli_output_adapter_success() {
let adapter = CliOutputAdapter::new();
let request_id = uuid::Uuid::new_v4();
let response = CommandResponse::success(request_id)
.with_message(MessageKind::Notification, "Session created: abc123");
let output = adapter.adapt(response);
assert!(output.contains("Session created: abc123"));
}
#[test]
fn test_cli_output_adapter_error() {
let adapter = CliOutputAdapter::new();
let request_id = uuid::Uuid::new_v4();
let response = CommandResponse::error(
request_id,
crate::command::response::CommandError::new("TEST_ERROR", "something failed"),
);
let output = adapter.adapt(response);
assert!(output.contains("Error [TEST_ERROR]"));
assert!(output.contains("something failed"));
}
}

View File

@ -0,0 +1,2 @@
pub mod cli;
pub mod websocket;

View File

@ -0,0 +1,160 @@
use crate::command::adapter::{AdapterError, InputAdapter, OutputAdapter};
use crate::command::context::AdapterContext;
use crate::command::response::{CommandResponse, MessageKind};
use crate::command::Command;
use crate::protocol::WsOutbound;
/// WebSocket 输入适配器
///
/// 将 WebSocket 的 JSON 输入直接反序列化为 Command
pub struct WebSocketInputAdapter;
impl WebSocketInputAdapter {
/// 创建新的 WebSocket 输入适配器
pub fn new() -> Self {
Self
}
}
impl Default for WebSocketInputAdapter {
fn default() -> Self {
Self::new()
}
}
impl InputAdapter for WebSocketInputAdapter {
fn try_parse(
&self,
input: &str,
_ctx: AdapterContext,
) -> Result<Option<Command>, AdapterError> {
// 尝试将 JSON 反序列化为 Command
// 如果失败,说明不是 Command 消息,返回 None
match serde_json::from_str(input) {
Ok(cmd) => Ok(Some(cmd)),
Err(_) => Ok(None),
}
}
}
/// WebSocket 输出适配器
///
/// 将 CommandResponse 转换为 WsOutbound 消息列表
pub struct WebSocketOutputAdapter;
impl WebSocketOutputAdapter {
/// 创建新的 WebSocket 输出适配器
pub fn new() -> Self {
Self
}
}
impl Default for WebSocketOutputAdapter {
fn default() -> Self {
Self::new()
}
}
impl OutputAdapter for WebSocketOutputAdapter {
type Output = Vec<WsOutbound>;
fn adapt(&self, response: CommandResponse) -> Vec<WsOutbound> {
let mut outbounds = Vec::new();
// 如果出错,返回错误消息
if let Some(error) = response.error {
outbounds.push(WsOutbound::Error {
code: error.code,
message: error.message,
});
return outbounds;
}
// 转换响应消息为 WsOutbound
for msg in &response.messages {
let outbound = match msg.kind {
MessageKind::Text => WsOutbound::AssistantResponse {
id: response.request_id.to_string(),
content: msg.content.clone(),
role: "assistant".to_string(),
},
MessageKind::Notification => {
// 根据元数据判断具体类型
if let Some(session_id) = response.metadata.get("session_id") {
WsOutbound::SessionCreated {
session_id: session_id.clone(),
title: msg.content.clone(),
}
} else {
// 默认通知
WsOutbound::AssistantResponse {
id: response.request_id.to_string(),
content: msg.content.clone(),
role: "assistant".to_string(),
}
}
}
MessageKind::Error => WsOutbound::Error {
code: "RESPONSE_ERROR".to_string(),
message: msg.content.clone(),
},
_ => WsOutbound::AssistantResponse {
id: response.request_id.to_string(),
content: msg.content.clone(),
role: "assistant".to_string(),
},
};
outbounds.push(outbound);
}
outbounds
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_websocket_input_adapter_valid_command() {
let adapter = WebSocketInputAdapter::new();
let ctx = AdapterContext::new("test");
let json = r#"{"type":"create_session","title":"my session"}"#;
let result = adapter.try_parse(json, ctx).unwrap();
assert!(result.is_some());
let cmd = result.unwrap();
assert!(matches!(
cmd,
Command::CreateSession {
title: Some(ref t)
} if t == "my session"
));
}
#[test]
fn test_websocket_input_adapter_invalid_json() {
let adapter = WebSocketInputAdapter::new();
let ctx = AdapterContext::new("test");
let json = "not a command";
let result = adapter.try_parse(json, ctx).unwrap();
assert!(result.is_none());
}
#[test]
fn test_websocket_output_adapter_session_created() {
let adapter = WebSocketOutputAdapter::new();
let request_id = uuid::Uuid::new_v4();
let response = CommandResponse::success(request_id)
.with_message(MessageKind::Notification, "My Session")
.with_metadata("session_id", "abc123");
let outbounds = adapter.adapt(response);
assert_eq!(outbounds.len(), 1);
assert!(matches!(outbounds[0], WsOutbound::SessionCreated { .. }));
}
}

82
src/command/context.rs Normal file
View File

@ -0,0 +1,82 @@
use std::collections::HashMap;
use uuid::Uuid;
/// 命令执行上下文 - 携带执行所需信息
#[derive(Debug, Clone)]
pub struct CommandContext {
/// 唯一请求ID
pub request_id: Uuid,
/// 当前会话ID
pub session_id: Option<String>,
/// 当前聊天ID
pub chat_id: Option<String>,
/// 发送者ID
pub sender_id: String,
/// 额外元数据
pub metadata: HashMap<String, String>,
}
impl CommandContext {
/// 创建新的命令上下文
pub fn new(sender_id: impl Into<String>) -> Self {
Self {
request_id: Uuid::new_v4(),
session_id: None,
chat_id: None,
sender_id: sender_id.into(),
metadata: HashMap::new(),
}
}
/// 设置会话ID
pub fn with_session_id(mut self, session_id: impl Into<String>) -> Self {
self.session_id = Some(session_id.into());
self
}
/// 设置聊天ID
pub fn with_chat_id(mut self, chat_id: impl Into<String>) -> Self {
self.chat_id = Some(chat_id.into());
self
}
/// 添加元数据
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
}
/// 适配器上下文 - 用于输入解析
#[derive(Debug, Clone)]
pub struct AdapterContext {
/// 当前会话ID
pub session_id: Option<String>,
/// 当前聊天ID
pub chat_id: Option<String>,
/// 发送者ID
pub sender_id: String,
}
impl AdapterContext {
/// 创建新的适配器上下文
pub fn new(sender_id: impl Into<String>) -> Self {
Self {
session_id: None,
chat_id: None,
sender_id: sender_id.into(),
}
}
/// 设置会话ID
pub fn with_session_id(mut self, session_id: impl Into<String>) -> Self {
self.session_id = Some(session_id.into());
self
}
/// 设置聊天ID
pub fn with_chat_id(mut self, chat_id: impl Into<String>) -> Self {
self.chat_id = Some(chat_id.into());
self
}
}

172
src/command/handler.rs Normal file
View File

@ -0,0 +1,172 @@
use crate::command::context::CommandContext;
use crate::command::response::{CommandError, CommandResponse};
use crate::command::Command;
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>;
}
/// 命令路由器
///
/// 负责将命令分发到合适的处理器
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()
}
}
#[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");
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");
let cmd = Command::CreateSession { title: None };
let result = router.dispatch(cmd, ctx).await;
assert!(result.is_err());
}
}

View File

@ -0,0 +1 @@
pub mod session;

View File

@ -0,0 +1,115 @@
use crate::command::context::CommandContext;
use crate::command::handler::CommandHandler;
use crate::command::response::{CommandError, CommandResponse, MessageKind};
use crate::command::Command;
use crate::gateway::cli_session::CliSessionService;
use async_trait::async_trait;
/// 会话命令处理器
///
/// 处理与会话管理相关的命令
pub struct SessionCommandHandler {
cli_sessions: CliSessionService,
}
impl SessionCommandHandler {
/// 创建新的会话命令处理器
///
/// # Arguments
/// * `cli_sessions` - CLI 会话服务
pub(crate) fn new(cli_sessions: CliSessionService) -> Self {
Self { cli_sessions }
}
}
#[async_trait]
impl CommandHandler for SessionCommandHandler {
fn can_handle(&self, cmd: &Command) -> bool {
matches!(cmd, Command::CreateSession { .. })
}
async fn handle(
&self,
cmd: Command,
ctx: CommandContext,
) -> Result<CommandResponse, CommandError> {
match cmd {
Command::CreateSession { title } => handle_create_session(self, title, ctx).await,
}
}
}
/// 处理创建会话命令
async fn handle_create_session(
handler: &SessionCommandHandler,
title: Option<String>,
ctx: CommandContext,
) -> Result<CommandResponse, CommandError> {
let record = handler
.cli_sessions
.create(title.as_deref())
.map_err(|e| CommandError::new("CREATE_SESSION_ERROR", e.to_string()))?;
Ok(CommandResponse::success(ctx.request_id)
.with_message(MessageKind::Notification, &record.title)
.with_metadata("session_id", &record.id)
.with_metadata("channel_name", &record.channel_name)
.with_metadata("message_count", &record.message_count.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::command::response::MessageKind;
use crate::storage::{SessionRecord, SessionStore};
use std::sync::Arc;
fn create_test_service() -> CliSessionService {
let store = Arc::new(SessionStore::in_memory().unwrap());
CliSessionService::new(store)
}
#[tokio::test]
async fn test_create_session_with_title() {
let service = create_test_service();
let handler = SessionCommandHandler::new(service);
let ctx = CommandContext::new("test");
let cmd = Command::CreateSession {
title: Some("my session".to_string()),
};
let result = handler.handle(cmd, ctx).await;
assert!(result.is_ok());
let resp = result.unwrap();
assert!(resp.success);
assert_eq!(resp.messages.len(), 1);
assert_eq!(resp.messages[0].content, "my session");
assert!(resp.metadata.contains_key("session_id"));
}
#[tokio::test]
async fn test_create_session_without_title() {
let service = create_test_service();
let handler = SessionCommandHandler::new(service);
let ctx = CommandContext::new("test");
let cmd = Command::CreateSession { title: None };
let result = handler.handle(cmd, ctx).await;
assert!(result.is_ok());
let resp = result.unwrap();
assert!(resp.success);
assert_eq!(resp.messages.len(), 1);
// 自动生成的标题
assert!(!resp.messages[0].content.is_empty());
}
#[test]
fn test_can_handle() {
let service = create_test_service();
let handler = SessionCommandHandler::new(service);
assert!(handler.can_handle(&Command::CreateSession { title: None }));
}
}

25
src/command/mod.rs Normal file
View File

@ -0,0 +1,25 @@
pub mod adapter;
pub mod adapters;
pub mod context;
pub mod handler;
pub mod handlers;
pub mod response;
use serde::{Deserialize, Serialize};
/// 统一命令枚举 - 与渠道无关
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Command {
/// 目前仅实现 /new 命令
CreateSession { title: Option<String> },
}
impl Command {
/// 获取命令名称(用于日志和调试)
pub fn name(&self) -> &'static str {
match self {
Command::CreateSession { .. } => "create_session",
}
}
}

122
src/command/response.rs Normal file
View File

@ -0,0 +1,122 @@
use std::collections::HashMap;
use uuid::Uuid;
/// 命令响应 - 与渠道无关的统一响应格式
#[derive(Debug, Clone)]
pub struct CommandResponse {
/// 对应的请求ID
pub request_id: Uuid,
/// 是否成功
pub success: bool,
/// 响应数据(可选)
pub data: Option<serde_json::Value>,
/// 响应消息列表
pub messages: Vec<ResponseMessage>,
/// 错误信息(如果有)
pub error: Option<CommandError>,
/// 元数据
pub metadata: HashMap<String, String>,
}
impl CommandResponse {
/// 创建成功的响应
pub fn success(request_id: Uuid) -> Self {
Self {
request_id,
success: true,
data: None,
messages: Vec::new(),
error: None,
metadata: HashMap::new(),
}
}
/// 创建失败的响应
pub fn error(request_id: Uuid, error: CommandError) -> Self {
Self {
request_id,
success: false,
data: None,
messages: Vec::new(),
error: Some(error),
metadata: HashMap::new(),
}
}
/// 添加响应消息
pub fn with_message(mut self, kind: MessageKind, content: impl Into<String>) -> Self {
self.messages.push(ResponseMessage {
kind,
content: content.into(),
metadata: HashMap::new(),
});
self
}
/// 添加元数据
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
/// 设置响应数据
pub fn with_data(mut self, data: serde_json::Value) -> Self {
self.data = Some(data);
self
}
}
/// 响应消息
#[derive(Debug, Clone)]
pub struct ResponseMessage {
/// 消息类型
pub kind: MessageKind,
/// 消息内容
pub content: String,
/// 消息元数据
pub metadata: HashMap<String, String>,
}
/// 消息类型
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageKind {
/// 普通文本
Text,
/// 通知
Notification,
/// 错误
Error,
/// 工具调用
ToolCall,
/// 工具结果
ToolResult,
/// 工具等待
ToolPending,
}
/// 命令错误
#[derive(Debug, Clone)]
pub struct CommandError {
/// 错误代码
pub code: String,
/// 错误消息
pub message: String,
}
impl CommandError {
/// 创建新的命令错误
pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
code: code.into(),
message: message.into(),
}
}
}
impl std::fmt::Display for CommandError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] {}", self.code, self.message)
}
}
impl std::error::Error for CommandError {}

View File

@ -1,6 +1,11 @@
use super::GatewayState;
use crate::agent::AgentError;
use crate::bus::InboundMessage;
use crate::command::adapter::OutputAdapter;
use crate::command::adapters::websocket::{WebSocketInputAdapter, WebSocketOutputAdapter};
use crate::command::context::CommandContext;
use crate::command::handler::CommandRouter;
use crate::command::handlers::session::SessionCommandHandler;
use crate::protocol::{SessionSummary, WsInbound, WsOutbound, parse_inbound, serialize_outbound};
use axum::extract::State;
use axum::extract::ws::{Message as WsMessage, WebSocket, WebSocketUpgrade};
@ -195,27 +200,44 @@ async fn handle_inbound(
Ok(())
}
WsInbound::CreateSession { title } => {
let record = state
.session_manager
.cli_sessions()
.create(title.as_deref())?;
*current_session_id = record.id.clone();
// 使用新的命令层处理
let _input_adapter = WebSocketInputAdapter::new();
let output_adapter = WebSocketOutputAdapter::new();
let cli_sessions = state.session_manager.cli_sessions();
let handler = SessionCommandHandler::new(cli_sessions);
let router = {
let mut r = CommandRouter::new();
r.register(Box::new(handler));
r
};
// 构建命令
let cmd = crate::command::Command::CreateSession { title };
let cmd_ctx = CommandContext::new("websocket")
.with_session_id(current_session_id.as_str());
// 执行命令
let response = router.dispatch_with_response(cmd, cmd_ctx).await;
// 适配输出
let outbounds = output_adapter.adapt(response);
// 处理响应
for msg in outbounds {
if let WsOutbound::SessionCreated { session_id, title: _ } = &msg {
*current_session_id = session_id.clone();
state
.channel_manager
.cli_channel()
.register_connection(
record.id.clone(),
session_id.clone(),
runtime_session_id.to_string(),
sender.clone(),
)
.await;
let _ = sender
.send(WsOutbound::SessionCreated {
session_id: record.id,
title: record.title,
})
.await;
}
let _ = sender.send(msg).await;
}
Ok(())
}
WsInbound::ListSessions { include_archived } => {

View File

@ -4,6 +4,7 @@ pub mod bus;
pub mod channels;
pub mod cli;
pub mod client;
pub mod command;
pub mod config;
pub mod domain;
pub mod gateway;