1use super::action::BrowserAction;
6use super::client::BrowserClient;
7use super::config::{BrowserConfig, BrowserEngine};
8use super::executor::{BrowserExecutor, CliExecutor};
9use super::handlers;
10use async_trait::async_trait;
11use limit_agent::error::AgentError;
12use limit_agent::Tool;
13use serde_json::Value;
14use std::str::FromStr;
15use std::sync::Arc;
16
17pub struct BrowserTool {
19 client: BrowserClient,
20}
21
22impl BrowserTool {
23 pub fn new() -> Self {
25 Self::with_config(BrowserConfig::default())
26 }
27
28 pub fn with_config(config: BrowserConfig) -> Self {
30 let executor = Arc::new(CliExecutor::new(config));
31 Self {
32 client: BrowserClient::new(executor),
33 }
34 }
35
36 pub fn with_executor(executor: Arc<dyn BrowserExecutor>) -> Self {
38 Self {
39 client: BrowserClient::new(executor),
40 }
41 }
42
43 #[allow(dead_code)]
45 fn parse_engine(s: &str) -> Option<BrowserEngine> {
46 match s.to_lowercase().as_str() {
47 "chrome" => Some(BrowserEngine::Chrome),
48 "lightpanda" => Some(BrowserEngine::Lightpanda),
49 _ => None,
50 }
51 }
52}
53
54impl Default for BrowserTool {
55 fn default() -> Self {
56 Self::new()
57 }
58}
59
60impl std::fmt::Debug for BrowserTool {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 f.debug_struct("BrowserTool").finish()
63 }
64}
65
66#[async_trait]
67impl Tool for BrowserTool {
68 fn name(&self) -> &str {
69 "browser"
70 }
71
72 async fn execute(&self, args: Value) -> Result<Value, AgentError> {
73 let action = args
74 .get("action")
75 .and_then(|v| v.as_str())
76 .ok_or_else(|| AgentError::ToolError("Missing 'action' argument".to_string()))?;
77
78 match BrowserAction::from_str(action)? {
79 BrowserAction::Open => handlers::navigation::open(&self.client, &args).await,
80 BrowserAction::Close => handlers::navigation::close(&self.client, &args).await,
81 BrowserAction::Snapshot => handlers::navigation::snapshot(&self.client, &args).await,
82 BrowserAction::Screenshot => {
83 handlers::navigation::screenshot(&self.client, &args).await
84 }
85 BrowserAction::Back => handlers::navigation::back(&self.client, &args).await,
86 BrowserAction::Forward => handlers::navigation::forward(&self.client, &args).await,
87 BrowserAction::Reload => handlers::navigation::reload(&self.client, &args).await,
88 BrowserAction::Click => handlers::interaction::click(&self.client, &args).await,
89 BrowserAction::Fill => handlers::interaction::fill(&self.client, &args).await,
90 BrowserAction::Type => handlers::interaction::type_(&self.client, &args).await,
91 BrowserAction::Press => handlers::interaction::press(&self.client, &args).await,
92 BrowserAction::Hover => handlers::interaction::hover(&self.client, &args).await,
93 BrowserAction::Select => handlers::interaction::select(&self.client, &args).await,
94 BrowserAction::Dblclick => handlers::interaction::dblclick(&self.client, &args).await,
95 BrowserAction::Focus => handlers::interaction::focus(&self.client, &args).await,
96 BrowserAction::Check => handlers::interaction::check(&self.client, &args).await,
97 BrowserAction::Uncheck => handlers::interaction::uncheck(&self.client, &args).await,
98 BrowserAction::Scrollintoview => {
99 handlers::interaction::scrollintoview(&self.client, &args).await
100 }
101 BrowserAction::Drag => handlers::interaction::drag(&self.client, &args).await,
102 BrowserAction::Upload => handlers::interaction::upload(&self.client, &args).await,
103 BrowserAction::Pdf => handlers::interaction::pdf(&self.client, &args).await,
104 BrowserAction::Get => handlers::query::get(&self.client, &args).await,
105 BrowserAction::GetAttr => handlers::query::get_attr(&self.client, &args).await,
106 BrowserAction::GetCount => handlers::query::get_count(&self.client, &args).await,
107 BrowserAction::GetBox => handlers::query::get_box(&self.client, &args).await,
108 BrowserAction::GetStyles => handlers::query::get_styles(&self.client, &args).await,
109 BrowserAction::Wait => handlers::wait::wait(&self.client, &args).await,
110 BrowserAction::WaitForText => handlers::wait::wait_for_text(&self.client, &args).await,
111 BrowserAction::WaitForUrl => handlers::wait::wait_for_url(&self.client, &args).await,
112 BrowserAction::WaitForLoad => handlers::wait::wait_for_load(&self.client, &args).await,
113 BrowserAction::WaitForDownload => {
114 handlers::wait::wait_for_download(&self.client, &args).await
115 }
116 BrowserAction::WaitForFn => handlers::wait::wait_for_fn(&self.client, &args).await,
117 BrowserAction::WaitForState => {
118 handlers::wait::wait_for_state(&self.client, &args).await
119 }
120 BrowserAction::Find => handlers::state::find(&self.client, &args).await,
121 BrowserAction::Scroll => handlers::state::scroll(&self.client, &args).await,
122 BrowserAction::Is => handlers::state::is(&self.client, &args).await,
123 BrowserAction::Download => handlers::state::download(&self.client, &args).await,
124 BrowserAction::TabList => handlers::tabs::tab_list(&self.client, &args).await,
125 BrowserAction::TabNew => handlers::tabs::tab_new(&self.client, &args).await,
126 BrowserAction::TabClose => handlers::tabs::tab_close(&self.client, &args).await,
127 BrowserAction::TabSelect => handlers::tabs::tab_select(&self.client, &args).await,
128 BrowserAction::DialogAccept => {
129 handlers::dialog::dialog_accept(&self.client, &args).await
130 }
131 BrowserAction::DialogDismiss => {
132 handlers::dialog::dialog_dismiss(&self.client, &args).await
133 }
134 BrowserAction::Cookies => handlers::storage::cookies(&self.client, &args).await,
135 BrowserAction::CookiesSet => handlers::storage::cookies_set(&self.client, &args).await,
136 BrowserAction::StorageGet => handlers::storage::storage_get(&self.client, &args).await,
137 BrowserAction::StorageSet => handlers::storage::storage_set(&self.client, &args).await,
138 BrowserAction::NetworkRequests => {
139 handlers::storage::network_requests(&self.client, &args).await
140 }
141 BrowserAction::SetViewport => {
142 handlers::settings::set_viewport(&self.client, &args).await
143 }
144 BrowserAction::SetDevice => handlers::settings::set_device(&self.client, &args).await,
145 BrowserAction::SetGeo => handlers::settings::set_geo(&self.client, &args).await,
146 BrowserAction::Eval => handlers::query::eval(&self.client, &args).await,
147 }
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_browser_tool_name() {
157 let tool = BrowserTool::new();
158 assert_eq!(tool.name(), "browser");
159 }
160
161 #[test]
162 fn test_browser_tool_default() {
163 let tool = BrowserTool::default();
164 assert_eq!(tool.name(), "browser");
165 }
166
167 #[test]
168 fn test_parse_engine() {
169 assert_eq!(
170 BrowserTool::parse_engine("chrome"),
171 Some(BrowserEngine::Chrome)
172 );
173 assert_eq!(
174 BrowserTool::parse_engine("CHROME"),
175 Some(BrowserEngine::Chrome)
176 );
177 assert_eq!(
178 BrowserTool::parse_engine("lightpanda"),
179 Some(BrowserEngine::Lightpanda)
180 );
181 assert_eq!(
182 BrowserTool::parse_engine("LightPanda"),
183 Some(BrowserEngine::Lightpanda)
184 );
185 assert_eq!(BrowserTool::parse_engine("invalid"), None);
186 }
187
188 #[tokio::test]
189 async fn test_browser_tool_missing_action() {
190 let tool = BrowserTool::new();
191 let args = serde_json::json!({
192 "url": "https://example.com"
193 });
194
195 let result = tool.execute(args).await;
196 assert!(result.is_err());
197 let err = result.unwrap_err();
198 assert!(err.to_string().contains("Missing 'action'"));
199 }
200
201 #[tokio::test]
202 async fn test_browser_tool_unknown_action() {
203 let tool = BrowserTool::new();
204 let args = serde_json::json!({
205 "action": "unknown"
206 });
207
208 let result = tool.execute(args).await;
209 assert!(result.is_err());
210 let err = result.unwrap_err();
211 assert!(err.to_string().contains("Unknown browser action"));
212 }
213
214 #[tokio::test]
215 async fn test_browser_tool_open_missing_url() {
216 let tool = BrowserTool::new();
217 let args = serde_json::json!({
218 "action": "open"
219 });
220
221 let result = tool.execute(args).await;
222 assert!(result.is_err());
223 let err = result.unwrap_err();
224 assert!(err.to_string().contains("Missing 'url'"));
225 }
226
227 #[tokio::test]
228 async fn test_browser_tool_click_missing_selector() {
229 let tool = BrowserTool::new();
230 let args = serde_json::json!({
231 "action": "click"
232 });
233
234 let result = tool.execute(args).await;
235 assert!(result.is_err());
236 let err = result.unwrap_err();
237 assert!(err.to_string().contains("Missing 'selector'"));
238 }
239
240 #[tokio::test]
241 async fn test_browser_tool_screenshot_missing_path() {
242 let tool = BrowserTool::new();
243 let args = serde_json::json!({
244 "action": "screenshot"
245 });
246
247 let result = tool.execute(args).await;
248 assert!(result.is_err());
249 let err = result.unwrap_err();
250 assert!(err.to_string().contains("Missing 'path'"));
251 }
252}