use serde::{Deserialize, Serialize}; use super::{brave, tavily}; /// Which search backend the AI tool layer should call. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SearchProvider { Tavily, Brave, } impl SearchProvider { /// Parse the `SEARCH_PROVIDER` environment variable into a supported variant. pub fn from_env(value: Option<&str>) -> Result { match value .unwrap_or("tavily") .trim() .to_ascii_lowercase() .as_str() { "tavily" => Ok(Self::Tavily), "brave" => Ok(Self::Brave), other => Err(format!( "Unsupported SEARCH_PROVIDER '{}'. Expected 'tavily' or 'brave'.", other )), } } /// Return the environment variable name required by the selected provider. pub fn required_key_name(self) -> &'static str { match self { Self::Tavily => "TAVILY_API_KEY", Self::Brave => "BRAVE_API_KEY", } } } /// Normalized search result shape shared across providers. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SearchResult { pub title: String, pub url: String, pub description: String, pub age: Option, } /// Dispatch a search request to whichever provider the server is configured to use. pub async fn search( provider: SearchProvider, query: &str, tavily_api_key: Option<&str>, brave_api_key: Option<&str>, count: u8, ) -> Result, String> { match provider { SearchProvider::Tavily => { let api_key = tavily_api_key .filter(|key| !key.is_empty()) .ok_or_else(|| "TAVILY_API_KEY is not configured".to_string())?; tavily::search(query, api_key, count).await } SearchProvider::Brave => { let api_key = brave_api_key .filter(|key| !key.is_empty()) .ok_or_else(|| "BRAVE_API_KEY is not configured".to_string())?; brave::search(query, api_key, count).await } } } /// Turn search results into plain text the AI model can read as tool output. pub fn format_results(results: &[SearchResult]) -> String { if results.is_empty() { return "No search results found.".to_string(); } let mut output = String::new(); for (i, r) in results.iter().enumerate() { output.push_str(&format!("{}. {}\n", i + 1, r.title)); if !r.url.is_empty() { output.push_str(&format!(" URL: {}\n", r.url)); } if let Some(age) = &r.age { output.push_str(&format!(" Age: {}\n", age)); } output.push_str(&format!(" {}\n\n", r.description)); } output }