mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-19 02:12:46 +08:00
refactor: 迁移 Rust 核心代码至 rust/ 目录
- 将 Rust 核心从 astrbot/rust/ 迁移至 rust/ - 新增 a2a.rs: Agent-to-Agent 通信协议 - 新增 abp.rs: ABP 插件协议客户端 - 新增 server.rs: WebSocket/HTTP 服务器 - 更新 Cargo.toml 依赖 (futures-util) - 重构 config.rs, orchestrator.rs, protocol.rs 等核心模块
This commit is contained in:
@@ -1,119 +1,67 @@
|
||||
//! Protocol client implementations for ACP and ABP
|
||||
//! Protocol client implementations for LSP, MCP, ACP, and ABP
|
||||
|
||||
use crate::error::AstrBotError;
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::net::UnixStream;
|
||||
use tokio::process::{Child, Command};
|
||||
|
||||
/// Protocol client trait
|
||||
#[async_trait]
|
||||
pub trait ProtocolClient: Send + Sync {
|
||||
async fn connect(&mut self) -> Result<(), AstrBotError>;
|
||||
async fn disconnect(&mut self) -> Result<(), AstrBotError>;
|
||||
fn is_connected(&self) -> bool;
|
||||
fn name(&self) -> &str;
|
||||
// ============================================================================
|
||||
// Protocol Status
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProtocolStatus {
|
||||
pub connected: bool,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl ProtocolStatus {
|
||||
pub fn new(name: &str, connected: bool) -> Self {
|
||||
Self {
|
||||
connected,
|
||||
name: name.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// LSP Client
|
||||
// Protocol Client Trait
|
||||
// ============================================================================
|
||||
|
||||
#[async_trait]
|
||||
pub trait ProtocolClient: Send + Sync {
|
||||
fn name(&self) -> &str;
|
||||
fn is_connected(&self) -> bool;
|
||||
async fn connect(&mut self) -> Result<(), AstrBotError>;
|
||||
async fn disconnect(&mut self) -> Result<(), AstrBotError>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// LSP Client - Language Server Protocol
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LspClient {
|
||||
connected: bool,
|
||||
child: Option<Child>,
|
||||
stdin: Option<tokio::io::BufWriter<tokio::process::ChildStdin>>,
|
||||
stdout: Option<tokio::io::BufReader<tokio::process::ChildStdout>>,
|
||||
pending_requests: HashMap<i64, tokio::sync::oneshot::Sender<String>>,
|
||||
request_id: i64,
|
||||
}
|
||||
|
||||
impl LspClient {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self { connected: false }
|
||||
}
|
||||
|
||||
pub fn set_connected(&mut self, connected: bool) {
|
||||
self.connected = connected;
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ProtocolClient for LspClient {
|
||||
async fn connect(&mut self) -> Result<(), AstrBotError> {
|
||||
tracing::debug!("LSP client connecting");
|
||||
self.connected = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn disconnect(&mut self) -> Result<(), AstrBotError> {
|
||||
tracing::debug!("LSP client disconnecting");
|
||||
self.connected = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_connected(&self) -> bool {
|
||||
self.connected
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"lsp-client"
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MCP Client
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct McpClient {
|
||||
connected: bool,
|
||||
}
|
||||
|
||||
impl McpClient {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self { connected: false }
|
||||
}
|
||||
|
||||
pub fn set_connected(&mut self, connected: bool) {
|
||||
self.connected = connected;
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ProtocolClient for McpClient {
|
||||
async fn connect(&mut self) -> Result<(), AstrBotError> {
|
||||
tracing::debug!("MCP client connecting");
|
||||
self.connected = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn disconnect(&mut self) -> Result<(), AstrBotError> {
|
||||
tracing::debug!("MCP client disconnecting");
|
||||
self.connected = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_connected(&self) -> bool {
|
||||
self.connected
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"mcp-client"
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ACP Client - AstrBot Communication Protocol
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AcpClient {
|
||||
connected: bool,
|
||||
server_url: Option<String>,
|
||||
}
|
||||
|
||||
impl AcpClient {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
connected: false,
|
||||
server_url: None,
|
||||
child: None,
|
||||
stdin: None,
|
||||
stdout: None,
|
||||
pending_requests: HashMap::new(),
|
||||
request_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,17 +69,512 @@ impl AcpClient {
|
||||
self.connected = connected;
|
||||
}
|
||||
|
||||
pub async fn connect_to_server(&mut self, host: &str, port: u16) -> Result<(), AstrBotError> {
|
||||
self.server_url = Some(format!("{}:{}", host, port));
|
||||
/// Connect to an LSP server subprocess
|
||||
pub async fn connect_to_server(
|
||||
&mut self,
|
||||
command: Vec<String>,
|
||||
workspace_uri: &str,
|
||||
) -> Result<(), AstrBotError> {
|
||||
if command.is_empty() {
|
||||
return Err(AstrBotError::InvalidState("LSP command is empty".into()));
|
||||
}
|
||||
|
||||
let mut cmd = Command::new(&command[0]);
|
||||
if command.len() > 1 {
|
||||
cmd.args(&command[1..]);
|
||||
}
|
||||
|
||||
cmd.stdin(std::process::Stdio::piped());
|
||||
cmd.stdout(std::process::Stdio::piped());
|
||||
cmd.stderr(std::process::Stdio::piped());
|
||||
|
||||
let mut child = cmd.spawn().map_err(|e| {
|
||||
AstrBotError::ConnectionFailed(format!("Failed to spawn LSP server: {e}"))
|
||||
})?;
|
||||
|
||||
let stdin = child
|
||||
.stdin
|
||||
.take()
|
||||
.ok_or_else(|| AstrBotError::ConnectionFailed("Failed to capture stdin".into()))?;
|
||||
|
||||
let stdout = child
|
||||
.stdout
|
||||
.take()
|
||||
.ok_or_else(|| AstrBotError::ConnectionFailed("Failed to capture stdout".into()))?;
|
||||
|
||||
self.stdin = Some(tokio::io::BufWriter::new(stdin));
|
||||
self.stdout = Some(tokio::io::BufReader::new(stdout));
|
||||
self.child = Some(child);
|
||||
self.connected = true;
|
||||
tracing::debug!("ACP client connecting to {}:{}", host, port);
|
||||
|
||||
// Send initialize request
|
||||
let initialize_params = serde_json::json!({
|
||||
"processId": std::process::id(),
|
||||
"rootUri": workspace_uri,
|
||||
"capabilities": {}
|
||||
});
|
||||
|
||||
self.send_request("initialize", Some(initialize_params))
|
||||
.await?;
|
||||
|
||||
// Send initialized notification
|
||||
self.send_notification("initialized", None).await?;
|
||||
|
||||
tracing::info!("LSP client connected to server");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn connect_to_unix_socket(&mut self, socket_path: &str) -> Result<(), AstrBotError> {
|
||||
self.server_url = Some(format!("unix://{}", socket_path));
|
||||
/// Send a request and wait for response
|
||||
pub async fn send_request(
|
||||
&mut self,
|
||||
method: &str,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<String, AstrBotError> {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
let id = self.request_id;
|
||||
self.request_id += 1;
|
||||
|
||||
self.pending_requests.insert(id, tx);
|
||||
|
||||
let request = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": id,
|
||||
"method": method,
|
||||
"params": params.unwrap_or(serde_json::Value::Null)
|
||||
});
|
||||
|
||||
self.send_json(&request).await?;
|
||||
|
||||
rx.await
|
||||
.map_err(|_| AstrBotError::Timeout("LSP request timed out".into()))
|
||||
}
|
||||
|
||||
/// Send a notification (no response expected)
|
||||
pub async fn send_notification(
|
||||
&mut self,
|
||||
method: &str,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<(), AstrBotError> {
|
||||
let notification = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": method,
|
||||
"params": params.unwrap_or(serde_json::Value::Null)
|
||||
});
|
||||
|
||||
self.send_json(¬ification).await
|
||||
}
|
||||
|
||||
async fn send_json(&mut self, msg: &serde_json::Value) -> Result<(), AstrBotError> {
|
||||
let content = serde_json::to_string(msg).map_err(|e| AstrBotError::Json(e))?;
|
||||
let header = format!("Content-Length: {}\r\n\r\n", content.len());
|
||||
|
||||
if let Some(stdin) = &mut self.stdin {
|
||||
stdin.write_all(header.as_bytes()).await?;
|
||||
stdin.write_all(content.as_bytes()).await?;
|
||||
stdin.flush().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read responses from LSP server
|
||||
pub async fn read_responses(&mut self) -> Result<(), AstrBotError> {
|
||||
if let Some(stdout) = &mut self.stdout {
|
||||
let mut header_buf = Vec::new();
|
||||
|
||||
loop {
|
||||
// Read header line
|
||||
header_buf.clear();
|
||||
stdout.read_until(b'\n', &mut header_buf).await?;
|
||||
|
||||
let header_str = String::from_utf8_lossy(&header_buf);
|
||||
let content_length = header_str
|
||||
.strip_prefix("Content-Length: ")
|
||||
.and_then(|s| s.trim().parse::<usize>().ok())
|
||||
.unwrap_or(0);
|
||||
|
||||
// Skip blank line (\r\n)
|
||||
if header_buf.len() >= 2 && header_buf[0] == b'\r' && header_buf[1] == b'\n' {
|
||||
// already at content
|
||||
} else {
|
||||
// Read the \r\n after Content-Length
|
||||
let mut crlf = [0u8; 2];
|
||||
stdout.read_exact(&mut crlf).await?;
|
||||
}
|
||||
|
||||
// Read content
|
||||
let mut content = vec![0u8; content_length];
|
||||
stdout.read_exact(&mut content).await?;
|
||||
|
||||
let response: serde_json::Value =
|
||||
serde_json::from_slice(&content).map_err(|e| AstrBotError::Json(e))?;
|
||||
|
||||
// Handle response
|
||||
if let Some(id) = response.get("id").and_then(|v| v.as_i64()) {
|
||||
if let Some(tx) = self.pending_requests.remove(&id) {
|
||||
let _ = tx.send(response.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LspClient {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ProtocolClient for LspClient {
|
||||
fn name(&self) -> &'static str {
|
||||
"lsp"
|
||||
}
|
||||
|
||||
fn is_connected(&self) -> bool {
|
||||
self.connected
|
||||
}
|
||||
|
||||
async fn connect(&mut self) -> Result<(), AstrBotError> {
|
||||
self.connected = true;
|
||||
tracing::debug!("ACP client connecting to unix socket:{}", socket_path);
|
||||
tracing::debug!("LSP client connected");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn disconnect(&mut self) -> Result<(), AstrBotError> {
|
||||
self.connected = false;
|
||||
|
||||
// Try graceful shutdown first
|
||||
if let Err(_) = self.send_notification("shutdown", None).await {
|
||||
// Ignore errors during shutdown
|
||||
}
|
||||
if let Err(_) = self.send_notification("exit", None).await {
|
||||
// Ignore errors during exit
|
||||
}
|
||||
|
||||
// Terminate LSP server process
|
||||
if let Some(mut child) = self.child.take() {
|
||||
let _ = child.kill().await;
|
||||
}
|
||||
|
||||
self.stdin = None;
|
||||
self.stdout = None;
|
||||
tracing::info!("LSP client disconnected");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MCP Client - Model Context Protocol
|
||||
// ============================================================================
|
||||
|
||||
pub struct McpClient {
|
||||
connected: bool,
|
||||
server_url: Option<String>,
|
||||
transport: McpTransport,
|
||||
}
|
||||
|
||||
enum McpTransport {
|
||||
Stdio {
|
||||
child: Option<Child>,
|
||||
},
|
||||
Http {
|
||||
client: Option<reqwest::Client>,
|
||||
base_url: String,
|
||||
},
|
||||
#[allow(dead_code)]
|
||||
Sse {
|
||||
client: Option<reqwest::Client>,
|
||||
base_url: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl McpClient {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
connected: false,
|
||||
server_url: None,
|
||||
transport: McpTransport::Stdio { child: None },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_connected(&mut self, connected: bool) {
|
||||
self.connected = connected;
|
||||
}
|
||||
|
||||
/// Connect to MCP server via stdio
|
||||
pub async fn connect_to_stdio_server(
|
||||
&mut self,
|
||||
command: Vec<String>,
|
||||
) -> Result<(), AstrBotError> {
|
||||
if command.is_empty() {
|
||||
return Err(AstrBotError::InvalidState("MCP command is empty".into()));
|
||||
}
|
||||
|
||||
let mut cmd = Command::new(&command[0]);
|
||||
if command.len() > 1 {
|
||||
cmd.args(&command[1..]);
|
||||
}
|
||||
|
||||
cmd.stdin(std::process::Stdio::piped());
|
||||
cmd.stdout(std::process::Stdio::piped());
|
||||
cmd.stderr(std::process::Stdio::piped());
|
||||
|
||||
let child = cmd.spawn().map_err(|e| {
|
||||
AstrBotError::ConnectionFailed(format!("Failed to spawn MCP server: {e}"))
|
||||
})?;
|
||||
|
||||
self.transport = McpTransport::Stdio { child: Some(child) };
|
||||
self.connected = true;
|
||||
self.server_url = Some("stdio".to_string());
|
||||
|
||||
tracing::info!("MCP client connected via stdio");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Connect to MCP server via HTTP/SSE
|
||||
pub async fn connect_to_http_server(&mut self, base_url: &str) -> Result<(), AstrBotError> {
|
||||
let client = reqwest::Client::new();
|
||||
self.transport = McpTransport::Http {
|
||||
client: Some(client),
|
||||
base_url: base_url.to_string(),
|
||||
};
|
||||
self.connected = true;
|
||||
self.server_url = Some(base_url.to_string());
|
||||
|
||||
tracing::info!("MCP client connected via HTTP: {}", base_url);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List available tools on MCP server
|
||||
pub async fn list_tools(&self) -> Result<Vec<McpTool>, AstrBotError> {
|
||||
match &self.transport {
|
||||
McpTransport::Http { client, base_url } => {
|
||||
if let Some(client) = client {
|
||||
let url = format!("{}/tools", base_url);
|
||||
let response = client
|
||||
.get(&url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| AstrBotError::ConnectionFailed(e.to_string()))?;
|
||||
|
||||
let tools: Vec<McpTool> = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| AstrBotError::Protocol(e.to_string()))?;
|
||||
|
||||
return Ok(tools);
|
||||
}
|
||||
}
|
||||
McpTransport::Stdio { .. } => {
|
||||
// Stdio-based tool listing would require JSON-RPC communication
|
||||
}
|
||||
McpTransport::Sse { .. } => {}
|
||||
}
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
/// Call a tool on MCP server
|
||||
pub async fn call_tool(
|
||||
&self,
|
||||
tool_name: &str,
|
||||
arguments: serde_json::Value,
|
||||
) -> Result<serde_json::Value, AstrBotError> {
|
||||
match &self.transport {
|
||||
McpTransport::Http { client, base_url } => {
|
||||
if let Some(client) = client {
|
||||
let url = format!("{}/call", base_url);
|
||||
let request = serde_json::json!({
|
||||
"tool": tool_name,
|
||||
"arguments": arguments
|
||||
});
|
||||
|
||||
let response = client
|
||||
.post(&url)
|
||||
.json(&request)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| AstrBotError::ConnectionFailed(e.to_string()))?;
|
||||
|
||||
let result: serde_json::Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| AstrBotError::Protocol(e.to_string()))?;
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
McpTransport::Stdio { child: _ } => {
|
||||
// Stdio-based tool calling would require JSON-RPC communication
|
||||
tracing::debug!("MCP stdio tool call: {} with {:?}", tool_name, arguments);
|
||||
}
|
||||
McpTransport::Sse { .. } => {}
|
||||
}
|
||||
|
||||
Err(AstrBotError::NotConnected("MCP transport not ready".into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct McpTool {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub input_schema: serde_json::Value,
|
||||
}
|
||||
|
||||
impl Default for McpClient {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ProtocolClient for McpClient {
|
||||
fn name(&self) -> &'static str {
|
||||
"mcp"
|
||||
}
|
||||
|
||||
fn is_connected(&self) -> bool {
|
||||
self.connected
|
||||
}
|
||||
|
||||
async fn connect(&mut self) -> Result<(), AstrBotError> {
|
||||
self.connected = true;
|
||||
tracing::debug!("MCP client connected");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn disconnect(&mut self) -> Result<(), AstrBotError> {
|
||||
self.connected = false;
|
||||
|
||||
if let McpTransport::Stdio { child } = &mut self.transport {
|
||||
if let Some(mut child) = child.take() {
|
||||
let _ = child.kill().await;
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!("MCP client disconnected");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ACP Client - Agent Communication Protocol (e.g., Google A2A)
|
||||
// ============================================================================
|
||||
|
||||
pub struct AcpClient {
|
||||
connected: bool,
|
||||
server_url: Option<String>,
|
||||
tcp_stream: Option<TcpStream>,
|
||||
pending_requests: HashMap<String, tokio::sync::oneshot::Sender<serde_json::Value>>,
|
||||
request_id: u64,
|
||||
}
|
||||
|
||||
impl AcpClient {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
connected: false,
|
||||
server_url: None,
|
||||
tcp_stream: None,
|
||||
pending_requests: HashMap::new(),
|
||||
request_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_connected(&mut self, connected: bool) {
|
||||
self.connected = connected;
|
||||
}
|
||||
|
||||
/// Connect to ACP server via TCP
|
||||
pub async fn connect_to_tcp(&mut self, host: &str, port: u16) -> Result<(), AstrBotError> {
|
||||
let addr = format!("{}:{}", host, port);
|
||||
let stream = TcpStream::connect(&addr)
|
||||
.await
|
||||
.map_err(|e| AstrBotError::ConnectionFailed(format!("TCP connect failed: {e}")))?;
|
||||
|
||||
self.tcp_stream = Some(stream);
|
||||
self.connected = true;
|
||||
self.server_url = Some(addr.clone());
|
||||
|
||||
tracing::info!("ACP client connected via TCP: {}", addr);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Connect to ACP server via Unix socket
|
||||
/// Note: For now, Unix socket support is simplified - just mark as connected
|
||||
pub async fn connect_to_unix_socket(&mut self, socket_path: &str) -> Result<(), AstrBotError> {
|
||||
let _stream = UnixStream::connect(socket_path).await.map_err(|e| {
|
||||
AstrBotError::ConnectionFailed(format!("Unix socket connect failed: {e}"))
|
||||
})?;
|
||||
|
||||
self.connected = true;
|
||||
self.server_url = Some(socket_path.to_string());
|
||||
|
||||
tracing::info!("ACP client connected via Unix socket: {}", socket_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a request and wait for response
|
||||
pub async fn call_tool(
|
||||
&mut self,
|
||||
server_name: &str,
|
||||
tool_name: &str,
|
||||
arguments: serde_json::Value,
|
||||
) -> Result<serde_json::Value, AstrBotError> {
|
||||
let request_id = format!("{}-{}", self.request_id, server_name);
|
||||
self.request_id += 1;
|
||||
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
self.pending_requests.insert(request_id.clone(), tx);
|
||||
|
||||
let request = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": request_id,
|
||||
"method": format!("{}/{}", server_name, tool_name),
|
||||
"params": arguments
|
||||
});
|
||||
|
||||
self.send_json(&request).await?;
|
||||
|
||||
rx.await
|
||||
.map_err(|_| AstrBotError::Timeout("ACP request timed out".into()))
|
||||
}
|
||||
|
||||
/// Send a notification
|
||||
pub async fn send_notification(
|
||||
&mut self,
|
||||
method: &str,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<(), AstrBotError> {
|
||||
let notification = serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": method,
|
||||
"params": params.unwrap_or(serde_json::Value::Null)
|
||||
});
|
||||
|
||||
self.send_json(¬ification).await
|
||||
}
|
||||
|
||||
async fn send_json(&mut self, msg: &serde_json::Value) -> Result<(), AstrBotError> {
|
||||
let content = serde_json::to_string(msg).map_err(|e| AstrBotError::Json(e))?;
|
||||
let header = serde_json::to_string(&serde_json::json!({
|
||||
"content-length": content.len()
|
||||
}))? + "\n";
|
||||
|
||||
if let Some(stream) = &mut self.tcp_stream {
|
||||
stream.write_all(header.as_bytes()).await?;
|
||||
stream.write_all(content.as_bytes()).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read messages from ACP server
|
||||
pub async fn read_messages(&mut self) -> Result<(), AstrBotError> {
|
||||
// Simplified - actual implementation would use BufReader
|
||||
tracing::debug!("ACP read_messages called");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -144,79 +587,25 @@ impl Default for AcpClient {
|
||||
|
||||
#[async_trait]
|
||||
impl ProtocolClient for AcpClient {
|
||||
async fn connect(&mut self) -> Result<(), AstrBotError> {
|
||||
self.connected = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn disconnect(&mut self) -> Result<(), AstrBotError> {
|
||||
self.connected = false;
|
||||
self.server_url = None;
|
||||
Ok(())
|
||||
fn name(&self) -> &'static str {
|
||||
"acp"
|
||||
}
|
||||
|
||||
fn is_connected(&self) -> bool {
|
||||
self.connected
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"acp-client"
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ABP Client - AstrBot Protocol (internal stars)
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AbpClient {
|
||||
connected: bool,
|
||||
stars: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl AbpClient {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn set_connected(&mut self, connected: bool) {
|
||||
self.connected = connected;
|
||||
}
|
||||
|
||||
pub fn register_star(&mut self, name: &str, _handler: &str) {
|
||||
self.stars.insert(name.to_string(), name.to_string());
|
||||
tracing::debug!("Star '{}' registered with ABP client", name);
|
||||
}
|
||||
|
||||
pub fn unregister_star(&mut self, name: &str) {
|
||||
self.stars.remove(name);
|
||||
tracing::debug!("Star '{}' unregistered from ABP client", name);
|
||||
}
|
||||
|
||||
pub fn list_stars(&self) -> Vec<String> {
|
||||
self.stars.keys().cloned().collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ProtocolClient for AbpClient {
|
||||
async fn connect(&mut self) -> Result<(), AstrBotError> {
|
||||
self.connected = true;
|
||||
tracing::debug!("ABP client connecting to internal stars");
|
||||
tracing::debug!("ACP client connected");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn disconnect(&mut self) -> Result<(), AstrBotError> {
|
||||
self.connected = false;
|
||||
self.stars.clear();
|
||||
self.tcp_stream = None;
|
||||
tracing::info!("ACP client disconnected");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_connected(&self) -> bool {
|
||||
self.connected
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"abp-client"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user