pub mod config;
pub mod health;
pub mod ipc_client;
pub mod ipc_server;
pub mod logger;
pub mod manager;
pub mod pool;
pub mod signals;
pub use config::{ServerConfigEntry, ServerRegistry};
pub use health::{DaemonHealth, HealthMonitor, HealthStatus};
pub use ipc_client::IpcClient;
pub use ipc_server::{DaemonServer, IpcMessage, ToolInfo};
pub use logger::DaemonLogger;
pub use manager::{DaemonManager, DaemonStatus};
pub use pool::{ConnectionPool, PoolStats};
pub use signals::setup_signal_handlers;
use crate::error::{Error, Result};
use crate::mcp::tools::ToolResult;
use std::sync::OnceLock;
use tokio::sync::RwLock;
static SERVER_REGISTRY: OnceLock<RwLock<ServerRegistry>> = OnceLock::new();
fn get_registry() -> &'static RwLock<ServerRegistry> {
SERVER_REGISTRY.get_or_init(|| {
RwLock::new(ServerRegistry::load().unwrap_or_else(|e| {
tracing::warn!(error = %e, "Failed to load server registry, using defaults");
ServerRegistry::new()
}))
})
}
static CONNECTION_POOL: OnceLock<ConnectionPool> = OnceLock::new();
fn get_pool() -> &'static ConnectionPool {
CONNECTION_POOL.get_or_init(ConnectionPool::new)
}
pub async fn daemon_is_running() -> Result<bool> {
let manager = DaemonManager::new()?;
Ok(matches!(
manager.status().await,
DaemonStatus::Running { .. }
))
}
pub async fn call_tool(name: &str, args: serde_json::Value) -> Result<ToolResult> {
if daemon_is_running().await? {
daemon_call_tool(name, args).await
} else {
pooled_call_tool(name, args).await
}
}
async fn daemon_call_tool(name: &str, args: serde_json::Value) -> Result<ToolResult> {
let mut client = IpcClient::connect().await?;
client.call_tool(name, args).await
}
async fn pooled_call_tool(name: &str, args: serde_json::Value) -> Result<ToolResult> {
let pool = get_pool();
let registry = get_registry().read().await;
let config = registry
.get_client_config(name)
.ok_or_else(|| Error::network(format!("Unknown tool '{}' - no server configured", name)))?;
pool.call_tool(&config, name, args).await
}
pub async fn direct_call_tool(name: &str, args: serde_json::Value) -> Result<ToolResult> {
use crate::mcp::{McpClient, McpClientTrait};
let registry = get_registry().read().await;
let config = registry
.get_client_config(name)
.ok_or_else(|| Error::network(format!("Unknown tool '{}' - no server configured", name)))?;
let mut client = McpClient::new(config);
client.connect().await?;
let result = client.call_tool(name, args).await?;
client.disconnect().await?;
Ok(result)
}
pub async fn list_available_tools() -> Vec<String> {
let registry = get_registry().read().await;
registry
.list_tools()
.iter()
.map(|s| s.to_string())
.collect()
}
pub async fn list_servers() -> Vec<String> {
let registry = get_registry().read().await;
registry
.list_servers()
.iter()
.map(|s| s.to_string())
.collect()
}
pub async fn reload_config() -> Result<()> {
let mut registry = get_registry().write().await;
*registry = ServerRegistry::load()?;
tracing::info!("Server registry reloaded");
Ok(())
}
pub fn clear_pool() {
get_pool().clear();
tracing::info!("Connection pool cleared");
}
pub fn pool_stats() -> pool::PoolStats {
get_pool().stats()
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_daemon_detection() {
let running = daemon_is_running().await.unwrap();
assert!(!running);
}
#[tokio::test]
async fn test_registry_loading() {
let registry = get_registry().read().await;
assert!(registry.get_server_for_tool("gigathink").is_some());
assert!(registry.get_server_for_tool("think").is_some());
}
#[tokio::test]
async fn test_list_available_tools() {
let tools = list_available_tools().await;
assert!(tools.contains(&"gigathink".to_string()));
assert!(tools.contains(&"laserlogic".to_string()));
assert!(tools.contains(&"think".to_string()));
}
#[tokio::test]
async fn test_unknown_tool_error() {
let result = direct_call_tool("nonexistent_tool", serde_json::json!({})).await;
assert!(result.is_err());
let error = result.unwrap_err().to_string();
assert!(error.contains("Unknown tool"));
}
#[test]
fn test_pool_stats() {
let stats = pool_stats();
assert_eq!(stats.active_connections, 0);
assert_eq!(stats.total_calls, 0);
}
}