use anyhow::{anyhow, Result}; use reqwest::Client; use serde_json::Value; use std::time::Duration; /// HTTP client for inter-service communication pub struct ServiceClient { client: Client, } impl ServiceClient { pub fn new() -> Self { let client = Client::builder() .timeout(Duration::from_secs(30)) .build() .expect("Failed to create HTTP client"); Self { client } } /// Check if a service is available pub async fn check_service_status(&self, base_url: &str) -> Result { let url = format!("{}/health", base_url.trim_end_matches('/')); match self.client.get(&url).send().await { Ok(response) => { if response.status().is_success() { Ok(ServiceStatus::Available) } else { Ok(ServiceStatus::Error(format!("HTTP {}", response.status()))) } } Err(e) => Ok(ServiceStatus::Unavailable(e.to_string())), } } /// Make a GET request to a service pub async fn get_request(&self, url: &str) -> Result { let response = self.client .get(url) .send() .await?; if !response.status().is_success() { return Err(anyhow!("Request failed with status: {}", response.status())); } let json: Value = response.json().await?; Ok(json) } /// Make a POST request to a service pub async fn post_request(&self, url: &str, body: &Value) -> Result { let response = self.client .post(url) .header("Content-Type", "application/json") .json(body) .send() .await?; if !response.status().is_success() { return Err(anyhow!("Request failed with status: {}", response.status())); } let json: Value = response.json().await?; Ok(json) } /// Get user's card collection from ai.card service pub async fn get_user_cards(&self, user_did: &str) -> Result { let url = format!("http://localhost:8000/api/v1/cards/collection?did={}", user_did); self.get_request(&url).await } /// Draw a card for user from ai.card service pub async fn draw_card(&self, user_did: &str, is_paid: bool) -> Result { let payload = serde_json::json!({ "user_did": user_did, "is_paid": is_paid }); self.post_request("http://localhost:8000/api/v1/cards/draw", &payload).await } } /// Service status enum #[derive(Debug, Clone)] pub enum ServiceStatus { Available, Unavailable(String), Error(String), } impl ServiceStatus { pub fn is_available(&self) -> bool { matches!(self, ServiceStatus::Available) } } /// Service detector for ai ecosystem services pub struct ServiceDetector { client: ServiceClient, } impl ServiceDetector { pub fn new() -> Self { Self { client: ServiceClient::new(), } } /// Check all ai ecosystem services pub async fn detect_services(&self) -> ServiceMap { let mut services = ServiceMap::default(); // Check ai.card service if let Ok(status) = self.client.check_service_status("http://localhost:8000").await { services.ai_card = Some(ServiceInfo { base_url: "http://localhost:8000".to_string(), status, }); } // Check ai.log service if let Ok(status) = self.client.check_service_status("http://localhost:8001").await { services.ai_log = Some(ServiceInfo { base_url: "http://localhost:8001".to_string(), status, }); } // Check ai.bot service if let Ok(status) = self.client.check_service_status("http://localhost:8002").await { services.ai_bot = Some(ServiceInfo { base_url: "http://localhost:8002".to_string(), status, }); } services } /// Get available services only pub async fn get_available_services(&self) -> Vec { let services = self.detect_services().await; let mut available = Vec::new(); if let Some(card) = &services.ai_card { if card.status.is_available() { available.push("ai.card".to_string()); } } if let Some(log) = &services.ai_log { if log.status.is_available() { available.push("ai.log".to_string()); } } if let Some(bot) = &services.ai_bot { if bot.status.is_available() { available.push("ai.bot".to_string()); } } available } /// Get card collection statistics pub async fn get_card_stats(&self) -> Result> { match self.client.get_request("http://localhost:8000/api/v1/cards/gacha-stats").await { Ok(stats) => Ok(stats), Err(e) => Err(e.into()), } } /// Draw a card for user pub async fn draw_card(&self, user_did: &str, is_paid: bool) -> Result> { let payload = serde_json::json!({ "user_did": user_did, "is_paid": is_paid }); match self.client.post_request("http://localhost:8000/api/v1/cards/draw", &payload).await { Ok(card) => Ok(card), Err(e) => Err(e.into()), } } /// Get user's card collection pub async fn get_user_cards(&self, user_did: &str) -> Result> { let url = format!("http://localhost:8000/api/v1/cards/collection?did={}", user_did); match self.client.get_request(&url).await { Ok(collection) => Ok(collection), Err(e) => Err(e.into()), } } /// Get contextual memories for conversation mode pub async fn get_contextual_memories(&self, _user_id: &str, _limit: usize) -> Result, Box> { // This is a simplified version - in a real implementation this would call the MCP server // For now, we'll return an empty vec to make compilation work Ok(Vec::new()) } /// Search memories by query pub async fn search_memories(&self, _query: &str, _limit: usize) -> Result, Box> { // This is a simplified version - in a real implementation this would call the MCP server // For now, we'll return an empty vec to make compilation work Ok(Vec::new()) } /// Create context summary pub async fn create_summary(&self, user_id: &str) -> Result> { // This is a simplified version - in a real implementation this would call the MCP server // For now, we'll return a placeholder summary Ok(format!("Context summary for user: {}", user_id)) } } /// Service information #[derive(Debug, Clone)] pub struct ServiceInfo { pub base_url: String, pub status: ServiceStatus, } /// Map of all ai ecosystem services #[derive(Debug, Clone, Default)] pub struct ServiceMap { pub ai_card: Option, pub ai_log: Option, pub ai_bot: Option, } impl ServiceMap { /// Get service info by name pub fn get_service(&self, name: &str) -> Option<&ServiceInfo> { match name { "ai.card" => self.ai_card.as_ref(), "ai.log" => self.ai_log.as_ref(), "ai.bot" => self.ai_bot.as_ref(), _ => None, } } /// Check if a service is available pub fn is_service_available(&self, name: &str) -> bool { self.get_service(name) .map(|info| info.status.is_available()) .unwrap_or(false) } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_service_client_creation() { let _client = ServiceClient::new(); // Basic test to ensure client can be created assert!(true); } #[test] fn test_service_status() { let status = ServiceStatus::Available; assert!(status.is_available()); let status = ServiceStatus::Unavailable("Connection refused".to_string()); assert!(!status.is_available()); } #[test] fn test_service_map() { let mut map = ServiceMap::default(); assert!(!map.is_service_available("ai.card")); map.ai_card = Some(ServiceInfo { base_url: "http://localhost:8000".to_string(), status: ServiceStatus::Available, }); assert!(map.is_service_available("ai.card")); assert!(!map.is_service_available("ai.log")); } }