lc/search/
providers.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
5#[serde(rename_all = "lowercase")]
6pub enum SearchProviderType {
7    Brave,
8    Exa,
9    Serper,
10    SerpApi,
11    DuckDuckGo,
12    Jina,
13}
14
15#[derive(Debug, Serialize, Deserialize, Clone)]
16pub struct SearchProviderConfig {
17    pub url: String,
18    pub provider_type: SearchProviderType,
19    #[serde(default)]
20    pub headers: HashMap<String, String>,
21}
22
23impl SearchProviderConfig {
24    #[allow(dead_code)]
25    pub fn new(url: String, provider_type: SearchProviderType) -> Self {
26        Self {
27            url,
28            provider_type,
29            headers: HashMap::new(),
30        }
31    }
32}
33
34impl SearchProviderType {
35    /// Auto-detect provider type from URL
36    pub fn detect_from_url(url: &str) -> anyhow::Result<Self> {
37        let url_lower = url.to_lowercase();
38
39        if url_lower.contains("api.search.brave.com") {
40            Ok(SearchProviderType::Brave)
41        } else if url_lower.contains("api.exa.ai") || url_lower.contains("exa.ai") {
42            Ok(SearchProviderType::Exa)
43        } else if url_lower.contains("google.serper.dev") || url_lower.contains("serper.dev") {
44            Ok(SearchProviderType::Serper)
45        } else if url_lower.contains("serpapi.com") {
46            Ok(SearchProviderType::SerpApi)
47        } else if url_lower.contains("duckduckgo.com") || url_lower.contains("api.duckduckgo.com") {
48            Ok(SearchProviderType::DuckDuckGo)
49        } else if url_lower.contains("jina.ai") || url_lower.contains("s.jina.ai") {
50            Ok(SearchProviderType::Jina)
51        } else {
52            anyhow::bail!(
53                "Cannot auto-detect provider type from URL '{}'. \
54                Supported providers:\n\
55                - Brave: api.search.brave.com\n\
56                - Exa: api.exa.ai\n\
57                - Serper: google.serper.dev\n\
58                - SerpApi: serpapi.com\n\
59                - DuckDuckGo: api.duckduckgo.com\n\
60                - Jina: s.jina.ai",
61                url
62            )
63        }
64    }
65
66    /// Get the correct API key header name for this provider type
67    pub fn api_key_header(&self) -> &'static str {
68        match self {
69            SearchProviderType::Brave => "X-Subscription-Token",
70            SearchProviderType::Exa => "x-api-key",
71            SearchProviderType::Serper => "X-API-KEY",
72            SearchProviderType::SerpApi => "api_key",
73            SearchProviderType::DuckDuckGo => "", // No API key required
74            SearchProviderType::Jina => "Authorization",
75        }
76    }
77}
78
79#[allow(dead_code)]
80pub trait SearchProvider {
81    fn name(&self) -> &str;
82    fn search(
83        &self,
84        query: &str,
85        count: Option<usize>,
86    ) -> impl std::future::Future<Output = anyhow::Result<super::SearchResults>> + Send;
87}