Skip to main content

a3s_code_core/config/
search.rs

1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3
4// ============================================================================
5// Search / Browser / Document Configuration
6// ============================================================================
7
8/// Search engine configuration (a3s-search integration)
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct SearchConfig {
12    /// Default timeout in seconds for all engines
13    #[serde(default = "default_search_timeout")]
14    pub timeout: u64,
15
16    /// Health monitor configuration
17    #[serde(default, skip_serializing_if = "Option::is_none")]
18    pub health: Option<SearchHealthConfig>,
19
20    /// Engine configurations
21    #[serde(default, rename = "engine")]
22    pub engines: std::collections::HashMap<String, SearchEngineConfig>,
23
24    /// Headless browser configuration for JS-rendered engines (google and baidu).
25    /// When enabled, an existing system or explicitly managed runtime is discovered.
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub headless: Option<HeadlessConfig>,
28}
29
30/// Browser backend for JS-rendered search engines.
31#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(rename_all = "lowercase")]
33pub enum BrowserBackend {
34    /// Chrome/Chromium headless browser.
35    #[default]
36    Chrome,
37    /// Lightpanda headless browser.
38    Lightpanda,
39}
40
41/// Headless browser configuration for JS-rendered engines.
42/// Uses a3s-search's browser pool, backed by Chrome/Chromium or Lightpanda.
43#[derive(Debug, Clone, Serialize, Deserialize)]
44#[serde(rename_all = "camelCase")]
45pub struct HeadlessConfig {
46    /// Browser backend to use.
47    #[serde(default)]
48    pub backend: BrowserBackend,
49
50    /// Maximum number of concurrent browser tabs.
51    #[serde(default = "default_headless_max_tabs")]
52    pub max_tabs: usize,
53
54    /// Path to the browser executable. If None, an existing runtime is discovered.
55    #[serde(
56        default,
57        alias = "chromePath",
58        alias = "lightpandaPath",
59        alias = "obscuraPath",
60        alias = "playwrightPath",
61        skip_serializing_if = "Option::is_none"
62    )]
63    pub browser_path: Option<String>,
64
65    /// Additional browser launch arguments.
66    #[serde(default, skip_serializing_if = "Vec::is_empty")]
67    pub launch_args: Vec<String>,
68
69    /// Proxy URL for the browser to use.
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    pub proxy_url: Option<String>,
72}
73
74impl BrowserBackend {
75    pub fn is_lightpanda(self) -> bool {
76        matches!(self, Self::Lightpanda)
77    }
78}
79
80impl Default for HeadlessConfig {
81    fn default() -> Self {
82        Self {
83            backend: BrowserBackend::Chrome,
84            max_tabs: 4,
85            browser_path: None,
86            launch_args: Vec::new(),
87            proxy_url: None,
88        }
89    }
90}
91
92/// Default configuration for built-in document context extraction.
93#[derive(Debug, Clone, Serialize, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub struct DocumentParserConfig {
96    /// Whether the default document extraction stack is registered in the parser registry.
97    #[serde(default = "default_enabled")]
98    pub enabled: bool,
99
100    /// Maximum file size accepted by the parser, in MiB.
101    #[serde(default = "default_document_parser_max_file_size_mb")]
102    pub max_file_size_mb: u64,
103
104    /// Optional cache settings for parsed / normalized document context.
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub cache: Option<DocumentCacheConfig>,
107}
108
109impl Default for DocumentParserConfig {
110    fn default() -> Self {
111        Self {
112            enabled: true,
113            max_file_size_mb: default_document_parser_max_file_size_mb(),
114            cache: Some(DocumentCacheConfig::default()),
115        }
116    }
117}
118
119impl DocumentParserConfig {
120    pub fn normalized(&self) -> Self {
121        Self {
122            enabled: self.enabled,
123            max_file_size_mb: self.max_file_size_mb.clamp(1, 1024),
124            cache: self.cache.as_ref().map(DocumentCacheConfig::normalized),
125        }
126    }
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct DocumentCacheConfig {
132    #[serde(default = "default_enabled")]
133    pub enabled: bool,
134
135    #[serde(default, skip_serializing_if = "Option::is_none")]
136    pub directory: Option<PathBuf>,
137}
138
139impl Default for DocumentCacheConfig {
140    fn default() -> Self {
141        Self {
142            enabled: true,
143            directory: None,
144        }
145    }
146}
147
148impl DocumentCacheConfig {
149    pub fn normalized(&self) -> Self {
150        Self {
151            enabled: self.enabled,
152            directory: self.directory.clone(),
153        }
154    }
155}
156
157/// Search health monitor configuration
158#[derive(Debug, Clone, Serialize, Deserialize)]
159#[serde(rename_all = "camelCase")]
160pub struct SearchHealthConfig {
161    /// Number of consecutive failures before suspending
162    #[serde(default = "default_max_failures")]
163    pub max_failures: u32,
164
165    /// Suspension duration in seconds
166    #[serde(default = "default_suspend_seconds")]
167    pub suspend_seconds: u64,
168}
169
170/// Per-engine search configuration
171#[derive(Debug, Clone, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub struct SearchEngineConfig {
174    /// Whether the engine is enabled
175    #[serde(default = "default_enabled")]
176    pub enabled: bool,
177
178    /// Weight for ranking (higher = more influence)
179    #[serde(default = "default_weight")]
180    pub weight: f64,
181
182    /// Per-engine timeout override in seconds
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub timeout: Option<u64>,
185}
186
187pub(crate) fn default_search_timeout() -> u64 {
188    10
189}
190
191pub(crate) fn default_headless_max_tabs() -> usize {
192    4
193}
194
195fn default_max_failures() -> u32 {
196    3
197}
198
199fn default_suspend_seconds() -> u64 {
200    60
201}
202
203pub(crate) fn default_enabled() -> bool {
204    true
205}
206
207fn default_weight() -> f64 {
208    1.0
209}
210
211pub(crate) fn default_document_parser_max_file_size_mb() -> u64 {
212    50
213}