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 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 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 => "", 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}