| 1 | //! Web-search provider configuration types. |
| 2 | //! |
| 3 | //! Self-contained `[search]` table types extracted verbatim from `config.rs`. |
| 4 | //! Re-exported from `crate::config` via `pub use search::*;` so existing |
| 5 | //! `crate::config::SearchProvider` (and sibling) paths resolve unchanged |
| 6 | //! (#3311). |
| 7 | |
| 8 | use serde::{Deserialize, Serialize}; |
| 9 | |
| 10 | /// Search provider enumeration — selects the first backend `web_search` uses. |
| 11 | /// API-backed providers may visibly degrade through the default DuckDuckGo → |
| 12 | /// Bing chain after runtime failure or an empty result. Configuration and |
| 13 | /// network-policy errors fail closed without crossing providers. |
| 14 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)] |
| 15 | #[serde(rename_all = "snake_case")] |
| 16 | pub enum SearchProvider { |
| 17 | /// Bing HTML scraping. No API key needed. |
| 18 | Bing, |
| 19 | /// DuckDuckGo HTML scraping with Bing fallback. No API key needed. |
| 20 | #[default] |
| 21 | #[serde(alias = "duckduckgo")] |
| 22 | DuckDuckGo, |
| 23 | /// Tavily AI Search API (<https://tavily.com>). Requires api_key. |
| 24 | Tavily, |
| 25 | /// Bocha AI Search API (<https://bochaai.com>). Requires api_key. |
| 26 | Bocha, |
| 27 | /// Metaso AI Search API (<https://metaso.cn>). Requires `[search] api_key` |
| 28 | /// or the `METASO_API_KEY` env var. |
| 29 | #[serde(alias = "metaso")] |
| 30 | Metaso, |
| 31 | /// SearXNG JSON search API. Requires a trusted/self-hosted `base_url`. |
| 32 | #[serde(alias = "searx", alias = "searx-ng", alias = "searx_ng")] |
| 33 | Searxng, |
| 34 | /// Baidu AI Search API (<https://qianfan.baidubce.com>). Requires api_key. |
| 35 | #[serde( |
| 36 | alias = "baidu-search", |
| 37 | alias = "baidu_ai_search", |
| 38 | alias = "baidu_search", |
| 39 | alias = "baidu-ai-search" |
| 40 | )] |
| 41 | Baidu, |
| 42 | /// Volcengine Ark web_search via Responses API. Requires api_key. |
| 43 | /// Free tier: 20K queries/month per API key. Falls back to |
| 44 | /// `VOLCENGINE_API_KEY` / `VOLCENGINE_ARK_API_KEY` / `ARK_API_KEY` |
| 45 | /// env vars when `[search] api_key` is not set. |
| 46 | #[serde( |
| 47 | alias = "volcengine", |
| 48 | alias = "ark", |
| 49 | alias = "volc", |
| 50 | alias = "volcengine-ark", |
| 51 | alias = "volcengine_ark", |
| 52 | alias = "volc-ark" |
| 53 | )] |
| 54 | Volcengine, |
| 55 | /// Sofya web search API (<https://sofya.co>). Requires api_key |
| 56 | /// (`ay_live_...`). Returns full extracted page content rather than |
| 57 | /// snippets; falls back to the `SOFYA_API_KEY` env var when |
| 58 | /// `[search] api_key` is not set. |
| 59 | Sofya, |
| 60 | } |
| 61 | |
| 62 | impl SearchProvider { |
| 63 | #[must_use] |
| 64 | pub fn parse(value: &str) -> Option<Self> { |
| 65 | match value.trim().to_ascii_lowercase().as_str() { |
| 66 | "bing" => Some(Self::Bing), |
| 67 | "duckduckgo" | "duck-duck-go" | "duck_duck_go" | "ddg" => Some(Self::DuckDuckGo), |
| 68 | "tavily" => Some(Self::Tavily), |
| 69 | "bocha" => Some(Self::Bocha), |
| 70 | "metaso" => Some(Self::Metaso), |
| 71 | "searxng" | "searx" | "searx-ng" | "searx_ng" => Some(Self::Searxng), |
| 72 | "baidu" | "baidu-search" | "baidu_search" | "baidu-ai-search" | "baidu_ai_search" => { |
| 73 | Some(Self::Baidu) |
| 74 | } |
| 75 | "volcengine" | "ark" | "volc" | "volcengine-ark" => Some(Self::Volcengine), |
| 76 | "sofya" => Some(Self::Sofya), |
| 77 | _ => None, |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | #[must_use] |
| 82 | pub fn as_str(self) -> &'static str { |
| 83 | match self { |
| 84 | Self::Bing => "bing", |
| 85 | Self::DuckDuckGo => "duckduckgo", |
| 86 | Self::Tavily => "tavily", |
| 87 | Self::Bocha => "bocha", |
| 88 | Self::Metaso => "metaso", |
| 89 | Self::Searxng => "searxng", |
| 90 | Self::Baidu => "baidu", |
| 91 | Self::Volcengine => "volcengine", |
| 92 | Self::Sofya => "sofya", |
| 93 | } |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 98 | pub enum SearchProviderSource { |
| 99 | Default, |
| 100 | Config, |
| 101 | EnvOverride, |
| 102 | } |
| 103 | |
| 104 | impl SearchProviderSource { |
| 105 | #[must_use] |
| 106 | pub fn as_str(self) -> &'static str { |
| 107 | match self { |
| 108 | Self::Default => "default", |
| 109 | Self::Config => "config", |
| 110 | Self::EnvOverride => "env override", |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 116 | pub struct SearchProviderResolution { |
| 117 | pub provider: SearchProvider, |
| 118 | pub source: SearchProviderSource, |
| 119 | } |
| 120 | |
| 121 | /// Web search provider configuration (`[search]` table in config.toml). |
| 122 | #[derive(Debug, Clone, Deserialize, Default)] |
| 123 | pub struct SearchConfig { |
| 124 | /// Search provider: `bing` | `duckduckgo` | `tavily` | `bocha` | `metaso` | `searxng` | `baidu` | `volcengine`. Default: `duckduckgo`. |
| 125 | #[serde(default)] |
| 126 | pub provider: Option<SearchProvider>, |
| 127 | /// Optional search endpoint. With `duckduckgo`, this is a |
| 128 | /// DuckDuckGo-compatible HTML endpoint. With `searxng`, this is the trusted |
| 129 | /// SearXNG instance root or `/search` endpoint. |
| 130 | #[serde(default)] |
| 131 | pub base_url: Option<String>, |
| 132 | /// API key for Tavily, Bocha, Metaso, Baidu, or Volcengine. Not required for Bing, DuckDuckGo, or SearXNG. |
| 133 | /// Metaso also falls back to the `METASO_API_KEY` env var. |
| 134 | /// Baidu also falls back to `BAIDU_SEARCH_API_KEY` env var. |
| 135 | /// Volcengine also falls back to `VOLCENGINE_API_KEY` / `VOLCENGINE_ARK_API_KEY` / `ARK_API_KEY` env vars. |
| 136 | #[serde(default)] |
| 137 | pub api_key: Option<String>, |
| 138 | } |
| 139 |