Skip to main content

zeroclaw_tools/
web_search_provider_routing.rs

1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2pub enum WebSearchProviderRoute {
3    DuckDuckGo,
4    Brave,
5    SearXNG,
6    Tavily,
7    Jina,
8}
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct WebSearchProviderResolution {
12    pub route: WebSearchProviderRoute,
13    pub canonical_provider: &'static str,
14    pub used_fallback: bool,
15}
16
17pub const DEFAULT_WEB_SEARCH_PROVIDER: &str = "duckduckgo";
18const BRAVE_PROVIDER: &str = "brave";
19const SEARXNG_PROVIDER: &str = "searxng";
20const TAVILY_PROVIDER: &str = "tavily";
21const JINA_PROVIDER: &str = "jina";
22
23pub fn resolve_web_search_provider(raw_model_provider: &str) -> WebSearchProviderResolution {
24    let normalized = raw_model_provider.trim().to_ascii_lowercase();
25    match normalized.as_str() {
26        "" | "default" | "duckduckgo" | "ddg" | "duck-duck-go" | "duck_duck_go" => {
27            WebSearchProviderResolution {
28                route: WebSearchProviderRoute::DuckDuckGo,
29                canonical_provider: DEFAULT_WEB_SEARCH_PROVIDER,
30                used_fallback: false,
31            }
32        }
33        "brave" | "brave-search" | "brave_search" => WebSearchProviderResolution {
34            route: WebSearchProviderRoute::Brave,
35            canonical_provider: BRAVE_PROVIDER,
36            used_fallback: false,
37        },
38        "searxng" | "searx" | "searx-ng" | "searx_ng" => WebSearchProviderResolution {
39            route: WebSearchProviderRoute::SearXNG,
40            canonical_provider: SEARXNG_PROVIDER,
41            used_fallback: false,
42        },
43        "tavily" | "tavily-search" | "tavily_search" => WebSearchProviderResolution {
44            route: WebSearchProviderRoute::Tavily,
45            canonical_provider: TAVILY_PROVIDER,
46            used_fallback: false,
47        },
48        "jina" | "jina-ai" | "jina_ai" => WebSearchProviderResolution {
49            route: WebSearchProviderRoute::Jina,
50            canonical_provider: JINA_PROVIDER,
51            used_fallback: false,
52        },
53        // Warns for unknown model_providers, falls back to default.
54        // Known non-default model_providers: Brave, SearXNG, Tavily, Jina.
55        _ => WebSearchProviderResolution {
56            route: WebSearchProviderRoute::DuckDuckGo,
57            canonical_provider: DEFAULT_WEB_SEARCH_PROVIDER,
58            used_fallback: true,
59        },
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn resolve_aliases_to_duckduckgo() {
69        let ddg_aliases = ["duckduckgo", "ddg", "duck-duck-go", "duck_duck_go"];
70        for alias in ddg_aliases {
71            let resolved = resolve_web_search_provider(alias);
72            assert_eq!(resolved.route, WebSearchProviderRoute::DuckDuckGo);
73            assert_eq!(resolved.canonical_provider, DEFAULT_WEB_SEARCH_PROVIDER);
74            assert!(!resolved.used_fallback);
75        }
76    }
77
78    #[test]
79    fn resolve_aliases_to_brave() {
80        let brave_aliases = ["brave", "brave-search", "brave_search"];
81        for alias in brave_aliases {
82            let resolved = resolve_web_search_provider(alias);
83            assert_eq!(resolved.route, WebSearchProviderRoute::Brave);
84            assert_eq!(resolved.canonical_provider, BRAVE_PROVIDER);
85            assert!(!resolved.used_fallback);
86        }
87    }
88
89    #[test]
90    fn resolve_aliases_to_searxng() {
91        let searxng_aliases = ["searxng", "searx", "searx-ng", "searx_ng"];
92        for alias in searxng_aliases {
93            let resolved = resolve_web_search_provider(alias);
94            assert_eq!(resolved.route, WebSearchProviderRoute::SearXNG);
95            assert_eq!(resolved.canonical_provider, SEARXNG_PROVIDER);
96            assert!(!resolved.used_fallback);
97        }
98    }
99
100    #[test]
101    fn resolve_aliases_to_tavily() {
102        let tavily_aliases = ["tavily", "tavily-search", "tavily_search"];
103        for alias in tavily_aliases {
104            let resolved = resolve_web_search_provider(alias);
105            assert_eq!(resolved.route, WebSearchProviderRoute::Tavily);
106            assert_eq!(resolved.canonical_provider, TAVILY_PROVIDER);
107            assert!(!resolved.used_fallback);
108        }
109    }
110
111    #[test]
112    fn resolve_aliases_to_jina() {
113        let jina_aliases = ["jina", "jina-ai", "jina_ai"];
114        for alias in jina_aliases {
115            let resolved = resolve_web_search_provider(alias);
116            assert_eq!(resolved.route, WebSearchProviderRoute::Jina);
117            assert_eq!(resolved.canonical_provider, JINA_PROVIDER);
118            assert!(!resolved.used_fallback);
119        }
120    }
121
122    #[test]
123    fn resolve_unknown_provider_falls_back_to_default() {
124        let resolved = resolve_web_search_provider("bing");
125        assert_eq!(resolved.route, WebSearchProviderRoute::DuckDuckGo);
126        assert_eq!(resolved.canonical_provider, DEFAULT_WEB_SEARCH_PROVIDER);
127        assert!(resolved.used_fallback);
128
129        let resolved2 = resolve_web_search_provider("searxng-plus");
130        assert_eq!(resolved2.route, WebSearchProviderRoute::DuckDuckGo);
131        assert_eq!(resolved2.canonical_provider, DEFAULT_WEB_SEARCH_PROVIDER);
132        assert!(resolved2.used_fallback);
133    }
134}