377 lines
11 KiB
Rust

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()),
}));
}
// 解析 /save 命令 - 保存当前话题
if trimmed == "/save" {
return Ok(Some(Command::SaveTopic {
filepath: None,
include_subagents: false,
}));
}
if let Some(args) = trimmed.strip_prefix("/save ") {
let args = args.trim();
let parts: Vec<&str> = args.split_whitespace().collect();
// 解析参数
let mut include_subagents = false;
let mut filepath = None;
for part in parts {
if part == "+sub" {
include_subagents = true;
} else if !part.is_empty() {
// 非特殊参数视为文件路径
filepath = Some(part.to_string());
}
}
return Ok(Some(Command::SaveTopic {
filepath,
include_subagents,
}));
}
// 解析 /save-session 命令 - 保存整个会话
if trimmed == "/save-session" {
return Ok(Some(Command::SaveSession {
filepath: None,
include_all: false,
include_subagents: false,
}));
}
if let Some(args) = trimmed.strip_prefix("/save-session ") {
let args = args.trim();
let parts: Vec<&str> = args.split_whitespace().collect();
// 解析参数
let mut include_all = false;
let mut include_subagents = false;
let mut filepath = None;
for part in parts {
if part == "all" {
include_all = true;
} else if part == "+sub" {
include_subagents = true;
} else if !part.is_empty() {
// 非特殊参数视为文件路径
filepath = Some(part.to_string());
}
}
return Ok(Some(Command::SaveSession {
filepath,
include_all,
include_subagents,
}));
}
// 解析 /list 命令
if trimmed == "/list" {
return Ok(Some(Command::ListSessions {
include_archived: false,
}));
}
if trimmed == "/list all" {
return Ok(Some(Command::ListSessions {
include_archived: true,
}));
}
// 解析 /use 命令 - 切换会话(支持 session_id 或序号)
if let Some(session_id) = trimmed.strip_prefix("/use ") {
let session_id = session_id.trim();
return Ok(Some(Command::SwitchSession {
session_id: session_id.to_string(),
}));
}
// 解析 /current 命令 - 获取当前会话信息
if trimmed == "/current" {
return Ok(Some(Command::GetCurrentSession));
}
// 解析 /help 命令 - 显示所有支持的命令
if trimmed == "/help" {
return Ok(Some(Command::Help));
}
// 不是命令,返回 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"));
}
#[test]
fn test_cli_input_adapter_save_topic_without_path() {
let adapter = CliInputAdapter::new();
let ctx = AdapterContext::new("test");
let result = adapter.try_parse("/save", ctx).unwrap();
assert!(result.is_some());
let cmd = result.unwrap();
assert!(matches!(cmd, Command::SaveTopic { filepath: None, .. }));
}
#[test]
fn test_cli_input_adapter_save_topic_with_path() {
let adapter = CliInputAdapter::new();
let ctx = AdapterContext::new("test");
let result = adapter.try_parse("/save ./debug/topic.md", ctx).unwrap();
assert!(result.is_some());
let cmd = result.unwrap();
assert!(matches!(
cmd,
Command::SaveTopic {
filepath: Some(ref p),
..
} if p == "./debug/topic.md"
));
}
#[test]
fn test_cli_input_adapter_save_session_without_path() {
let adapter = CliInputAdapter::new();
let ctx = AdapterContext::new("test");
let result = adapter.try_parse("/save-session", ctx).unwrap();
assert!(result.is_some());
let cmd = result.unwrap();
assert!(matches!(cmd, Command::SaveSession { filepath: None, include_all: false, .. }));
}
#[test]
fn test_cli_input_adapter_save_session_with_path() {
let adapter = CliInputAdapter::new();
let ctx = AdapterContext::new("test");
let result = adapter.try_parse("/save-session ./debug/session.md", ctx).unwrap();
assert!(result.is_some());
let cmd = result.unwrap();
assert!(matches!(
cmd,
Command::SaveSession {
filepath: Some(ref p),
include_all: false,
..
} if p == "./debug/session.md"
));
}
#[test]
fn test_cli_input_adapter_save_session_all() {
let adapter = CliInputAdapter::new();
let ctx = AdapterContext::new("test");
let result = adapter.try_parse("/save-session all", ctx).unwrap();
assert!(result.is_some());
let cmd = result.unwrap();
assert!(matches!(cmd, Command::SaveSession { filepath: None, include_all: true, .. }));
}
#[test]
fn test_cli_input_adapter_save_session_all_with_path() {
let adapter = CliInputAdapter::new();
let ctx = AdapterContext::new("test");
let result = adapter.try_parse("/save-session all ./debug/session.md", ctx).unwrap();
assert!(result.is_some());
let cmd = result.unwrap();
assert!(matches!(
cmd,
Command::SaveSession {
filepath: Some(ref p),
include_all: true,
..
} if p == "./debug/session.md"
));
}
#[test]
fn test_cli_output_adapter_save_success() {
let adapter = CliOutputAdapter::new();
let request_id = uuid::Uuid::new_v4();
let response = CommandResponse::success(request_id)
.with_message(MessageKind::Notification, "Topic saved to: topic.md")
.with_metadata("filepath", "topic.md");
let output = adapter.adapt(response);
assert!(output.contains("Topic saved to: topic.md"));
}
}