Skip to main content

limit_cli/tools/browser/
config.rs

1//! Browser configuration types
2//!
3//! Provides configuration options for browser automation.
4
5use serde::{Deserialize, Serialize};
6use std::path::PathBuf;
7
8/// Supported browser engines
9#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
10pub enum BrowserEngine {
11    /// Google Chrome (default)
12    #[default]
13    Chrome,
14    /// Lightpanda - lightweight browser
15    Lightpanda,
16}
17
18impl BrowserEngine {
19    /// Get the CLI argument value for this engine
20    pub fn as_arg(&self) -> &'static str {
21        match self {
22            BrowserEngine::Chrome => "chrome",
23            BrowserEngine::Lightpanda => "lightpanda",
24        }
25    }
26}
27
28/// Configuration for browser automation
29#[derive(Clone, Debug, Serialize, Deserialize)]
30pub struct BrowserConfig {
31    /// Path to agent-browser binary (defaults to "agent-browser" in PATH)
32    pub binary_path: Option<PathBuf>,
33    /// Browser engine to use
34    pub engine: BrowserEngine,
35    /// Run in headless mode (no visible window)
36    pub headless: bool,
37    /// Timeout for operations in milliseconds
38    pub timeout_ms: u64,
39}
40
41/// Convert from config file section to BrowserConfig
42impl From<&limit_llm::BrowserConfigSection> for BrowserConfig {
43    fn from(section: &limit_llm::BrowserConfigSection) -> Self {
44        let engine = match section.engine.to_lowercase().as_str() {
45            "lightpanda" => BrowserEngine::Lightpanda,
46            _ => BrowserEngine::Chrome,
47        };
48
49        Self {
50            binary_path: section.binary_path.clone(),
51            engine,
52            headless: section.headless,
53            timeout_ms: section.timeout_ms,
54        }
55    }
56}
57
58impl Default for BrowserConfig {
59    fn default() -> Self {
60        Self {
61            binary_path: None,
62            engine: BrowserEngine::default(),
63            headless: true,
64            timeout_ms: 30_000,
65        }
66    }
67}
68
69impl BrowserConfig {
70    /// Create a new configuration with defaults
71    pub fn new() -> Self {
72        Self::default()
73    }
74
75    /// Set the binary path
76    pub fn with_binary_path(mut self, path: PathBuf) -> Self {
77        self.binary_path = Some(path);
78        self
79    }
80
81    /// Set the browser engine
82    pub fn with_engine(mut self, engine: BrowserEngine) -> Self {
83        self.engine = engine;
84        self
85    }
86
87    /// Set headless mode
88    pub fn with_headless(mut self, headless: bool) -> Self {
89        self.headless = headless;
90        self
91    }
92
93    /// Set timeout in milliseconds
94    pub fn with_timeout_ms(mut self, timeout_ms: u64) -> Self {
95        self.timeout_ms = timeout_ms;
96        self
97    }
98
99    /// Get the binary name/path to execute
100    pub fn binary(&self) -> &std::path::Path {
101        self.binary_path
102            .as_deref()
103            .unwrap_or(std::path::Path::new("agent-browser"))
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_browser_engine_default() {
113        let engine = BrowserEngine::default();
114        assert_eq!(engine, BrowserEngine::Chrome);
115    }
116
117    #[test]
118    fn test_browser_engine_as_arg() {
119        assert_eq!(BrowserEngine::Chrome.as_arg(), "chrome");
120        assert_eq!(BrowserEngine::Lightpanda.as_arg(), "lightpanda");
121    }
122
123    #[test]
124    fn test_browser_config_default() {
125        let config = BrowserConfig::default();
126        assert!(config.binary_path.is_none());
127        assert_eq!(config.engine, BrowserEngine::Chrome);
128        assert!(config.headless);
129        assert_eq!(config.timeout_ms, 30_000);
130    }
131
132    #[test]
133    fn test_browser_config_builder() {
134        let config = BrowserConfig::new()
135            .with_engine(BrowserEngine::Lightpanda)
136            .with_headless(false)
137            .with_timeout_ms(60_000);
138
139        assert_eq!(config.engine, BrowserEngine::Lightpanda);
140        assert!(!config.headless);
141        assert_eq!(config.timeout_ms, 60_000);
142    }
143
144    #[test]
145    fn test_browser_config_binary() {
146        let config = BrowserConfig::default();
147        assert_eq!(config.binary().to_str().unwrap(), "agent-browser");
148
149        let config =
150            BrowserConfig::new().with_binary_path(PathBuf::from("/usr/local/bin/agent-browser"));
151        assert_eq!(
152            config.binary().to_str().unwrap(),
153            "/usr/local/bin/agent-browser"
154        );
155    }
156
157    #[test]
158    fn test_from_browser_config_section() {
159        let section = limit_llm::BrowserConfigSection {
160            enabled: true,
161            binary_path: Some(PathBuf::from("/custom/agent-browser")),
162            engine: "lightpanda".to_string(),
163            headless: false,
164            timeout_ms: 60_000,
165        };
166
167        let config = BrowserConfig::from(&section);
168        assert_eq!(
169            config.binary_path,
170            Some(PathBuf::from("/custom/agent-browser"))
171        );
172        assert_eq!(config.engine, BrowserEngine::Lightpanda);
173        assert!(!config.headless);
174        assert_eq!(config.timeout_ms, 60_000);
175    }
176
177    #[test]
178    fn test_from_browser_config_section_default() {
179        let section = limit_llm::BrowserConfigSection::default();
180        let config = BrowserConfig::from(&section);
181        assert!(config.binary_path.is_none());
182        assert_eq!(config.engine, BrowserEngine::Chrome);
183        assert!(config.headless);
184        assert_eq!(config.timeout_ms, 30_000);
185    }
186}