Skip to main content

adk_browser/tools/
windows.rs

1//! Window and tab management tools.
2
3use crate::session::BrowserSession;
4use crate::tools::navigate::{default_allowed_schemes, validate_url};
5use adk_core::{AdkError, Result, Tool, ToolContext};
6use async_trait::async_trait;
7use serde_json::{Value, json};
8use std::sync::Arc;
9
10/// Tool for listing all windows/tabs.
11pub struct ListWindowsTool {
12    browser: Arc<BrowserSession>,
13}
14
15impl ListWindowsTool {
16    pub fn new(browser: Arc<BrowserSession>) -> Self {
17        Self { browser }
18    }
19}
20
21#[async_trait]
22impl Tool for ListWindowsTool {
23    fn name(&self) -> &str {
24        "browser_list_windows"
25    }
26
27    fn description(&self) -> &str {
28        "List all open browser windows/tabs."
29    }
30
31    fn parameters_schema(&self) -> Option<Value> {
32        Some(json!({
33            "type": "object",
34            "properties": {}
35        }))
36    }
37
38    async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
39        let (windows, current) = self.browser.list_windows().await?;
40
41        Ok(json!({
42            "success": true,
43            "windows": windows,
44            "current_window": current,
45            "count": windows.len()
46        }))
47    }
48}
49
50/// Tool for opening a new tab.
51///
52/// A `url` argument must use a scheme in the allowlist, which defaults to
53/// [`DEFAULT_ALLOWED_SCHEMES`](crate::tools::DEFAULT_ALLOWED_SCHEMES).
54pub struct NewTabTool {
55    browser: Arc<BrowserSession>,
56    allowed_schemes: Vec<String>,
57}
58
59impl NewTabTool {
60    pub fn new(browser: Arc<BrowserSession>) -> Self {
61        Self { browser, allowed_schemes: default_allowed_schemes() }
62    }
63
64    /// Replace the URL schemes accepted for the optional `url` argument.
65    ///
66    /// Schemes are compared case-insensitively. See
67    /// [`NavigateTool::with_allowed_schemes`](crate::tools::NavigateTool::with_allowed_schemes).
68    #[must_use]
69    pub fn with_allowed_schemes<I, S>(mut self, schemes: I) -> Self
70    where
71        I: IntoIterator<Item = S>,
72        S: Into<String>,
73    {
74        self.allowed_schemes = schemes.into_iter().map(Into::into).collect();
75        self
76    }
77}
78
79#[async_trait]
80impl Tool for NewTabTool {
81    fn name(&self) -> &str {
82        "browser_new_tab"
83    }
84
85    fn description(&self) -> &str {
86        "Open a new browser tab and switch to it."
87    }
88
89    fn parameters_schema(&self) -> Option<Value> {
90        Some(json!({
91            "type": "object",
92            "properties": {
93                "url": {
94                    "type": "string",
95                    "description": "Optional URL to navigate to in the new tab"
96                }
97            }
98        }))
99    }
100
101    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
102        let url = args.get("url").and_then(|v| v.as_str());
103        if let Some(url) = url {
104            validate_url(url, &self.allowed_schemes)?;
105        }
106
107        let handle = self.browser.new_tab().await?;
108
109        if let Some(url) = url {
110            self.browser.navigate(url).await?;
111        }
112
113        let current_url = self.browser.current_url().await.unwrap_or_default();
114
115        Ok(json!({
116            "success": true,
117            "window_handle": handle,
118            "url": current_url
119        }))
120    }
121}
122
123/// Tool for opening a new window.
124///
125/// A `url` argument must use a scheme in the allowlist, which defaults to
126/// [`DEFAULT_ALLOWED_SCHEMES`](crate::tools::DEFAULT_ALLOWED_SCHEMES).
127pub struct NewWindowTool {
128    browser: Arc<BrowserSession>,
129    allowed_schemes: Vec<String>,
130}
131
132impl NewWindowTool {
133    pub fn new(browser: Arc<BrowserSession>) -> Self {
134        Self { browser, allowed_schemes: default_allowed_schemes() }
135    }
136
137    /// Replace the URL schemes accepted for the optional `url` argument.
138    ///
139    /// Schemes are compared case-insensitively. See
140    /// [`NavigateTool::with_allowed_schemes`](crate::tools::NavigateTool::with_allowed_schemes).
141    #[must_use]
142    pub fn with_allowed_schemes<I, S>(mut self, schemes: I) -> Self
143    where
144        I: IntoIterator<Item = S>,
145        S: Into<String>,
146    {
147        self.allowed_schemes = schemes.into_iter().map(Into::into).collect();
148        self
149    }
150}
151
152#[async_trait]
153impl Tool for NewWindowTool {
154    fn name(&self) -> &str {
155        "browser_new_window"
156    }
157
158    fn description(&self) -> &str {
159        "Open a new browser window and switch to it."
160    }
161
162    fn parameters_schema(&self) -> Option<Value> {
163        Some(json!({
164            "type": "object",
165            "properties": {
166                "url": {
167                    "type": "string",
168                    "description": "Optional URL to navigate to in the new window"
169                }
170            }
171        }))
172    }
173
174    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
175        let url = args.get("url").and_then(|v| v.as_str());
176        if let Some(url) = url {
177            validate_url(url, &self.allowed_schemes)?;
178        }
179
180        let handle = self.browser.new_window().await?;
181
182        if let Some(url) = url {
183            self.browser.navigate(url).await?;
184        }
185
186        let current_url = self.browser.current_url().await.unwrap_or_default();
187
188        Ok(json!({
189            "success": true,
190            "window_handle": handle,
191            "url": current_url
192        }))
193    }
194}
195
196/// Tool for switching to a window/tab.
197pub struct SwitchWindowTool {
198    browser: Arc<BrowserSession>,
199}
200
201impl SwitchWindowTool {
202    pub fn new(browser: Arc<BrowserSession>) -> Self {
203        Self { browser }
204    }
205}
206
207#[async_trait]
208impl Tool for SwitchWindowTool {
209    fn name(&self) -> &str {
210        "browser_switch_window"
211    }
212
213    fn description(&self) -> &str {
214        "Switch to a different browser window/tab by its handle."
215    }
216
217    fn parameters_schema(&self) -> Option<Value> {
218        Some(json!({
219            "type": "object",
220            "properties": {
221                "handle": {
222                    "type": "string",
223                    "description": "The window handle to switch to"
224                }
225            },
226            "required": ["handle"]
227        }))
228    }
229
230    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
231        let handle = args
232            .get("handle")
233            .and_then(|v| v.as_str())
234            .ok_or_else(|| AdkError::tool("Missing 'handle' parameter"))?;
235
236        self.browser.switch_to_window(handle).await?;
237
238        let url = self.browser.current_url().await.unwrap_or_default();
239        let title = self.browser.title().await.unwrap_or_default();
240
241        Ok(json!({
242            "success": true,
243            "switched_to": handle,
244            "url": url,
245            "title": title
246        }))
247    }
248}
249
250/// Tool for closing the current window/tab.
251pub struct CloseWindowTool {
252    browser: Arc<BrowserSession>,
253}
254
255impl CloseWindowTool {
256    pub fn new(browser: Arc<BrowserSession>) -> Self {
257        Self { browser }
258    }
259}
260
261#[async_trait]
262impl Tool for CloseWindowTool {
263    fn name(&self) -> &str {
264        "browser_close_window"
265    }
266
267    fn description(&self) -> &str {
268        "Close the current browser window/tab."
269    }
270
271    fn parameters_schema(&self) -> Option<Value> {
272        Some(json!({
273            "type": "object",
274            "properties": {}
275        }))
276    }
277
278    async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
279        self.browser.close_window().await?;
280
281        Ok(json!({
282            "success": true,
283            "message": "Window closed"
284        }))
285    }
286}
287
288/// Tool for maximizing the window.
289pub struct MaximizeWindowTool {
290    browser: Arc<BrowserSession>,
291}
292
293impl MaximizeWindowTool {
294    pub fn new(browser: Arc<BrowserSession>) -> Self {
295        Self { browser }
296    }
297}
298
299#[async_trait]
300impl Tool for MaximizeWindowTool {
301    fn name(&self) -> &str {
302        "browser_maximize_window"
303    }
304
305    fn description(&self) -> &str {
306        "Maximize the browser window."
307    }
308
309    fn parameters_schema(&self) -> Option<Value> {
310        Some(json!({
311            "type": "object",
312            "properties": {}
313        }))
314    }
315
316    async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
317        self.browser.maximize_window().await?;
318
319        Ok(json!({
320            "success": true,
321            "message": "Window maximized"
322        }))
323    }
324}
325
326/// Tool for minimizing the window.
327pub struct MinimizeWindowTool {
328    browser: Arc<BrowserSession>,
329}
330
331impl MinimizeWindowTool {
332    pub fn new(browser: Arc<BrowserSession>) -> Self {
333        Self { browser }
334    }
335}
336
337#[async_trait]
338impl Tool for MinimizeWindowTool {
339    fn name(&self) -> &str {
340        "browser_minimize_window"
341    }
342
343    fn description(&self) -> &str {
344        "Minimize the browser window."
345    }
346
347    fn parameters_schema(&self) -> Option<Value> {
348        Some(json!({
349            "type": "object",
350            "properties": {}
351        }))
352    }
353
354    async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
355        self.browser.minimize_window().await?;
356
357        Ok(json!({
358            "success": true,
359            "message": "Window minimized"
360        }))
361    }
362}
363
364/// Tool for setting window size.
365pub struct SetWindowSizeTool {
366    browser: Arc<BrowserSession>,
367}
368
369impl SetWindowSizeTool {
370    pub fn new(browser: Arc<BrowserSession>) -> Self {
371        Self { browser }
372    }
373}
374
375#[async_trait]
376impl Tool for SetWindowSizeTool {
377    fn name(&self) -> &str {
378        "browser_set_window_size"
379    }
380
381    fn description(&self) -> &str {
382        "Set the browser window size and position."
383    }
384
385    fn parameters_schema(&self) -> Option<Value> {
386        Some(json!({
387            "type": "object",
388            "properties": {
389                "width": {
390                    "type": "integer",
391                    "description": "Window width in pixels"
392                },
393                "height": {
394                    "type": "integer",
395                    "description": "Window height in pixels"
396                },
397                "x": {
398                    "type": "integer",
399                    "description": "Window X position (default: 0)"
400                },
401                "y": {
402                    "type": "integer",
403                    "description": "Window Y position (default: 0)"
404                }
405            },
406            "required": ["width", "height"]
407        }))
408    }
409
410    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
411        let width = args
412            .get("width")
413            .and_then(|v| v.as_u64())
414            .ok_or_else(|| AdkError::tool("Missing 'width' parameter"))? as u32;
415
416        let height =
417            args.get("height")
418                .and_then(|v| v.as_u64())
419                .ok_or_else(|| AdkError::tool("Missing 'height' parameter"))? as u32;
420
421        let x = args.get("x").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
422        let y = args.get("y").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
423
424        self.browser.set_window_rect(x, y, width, height).await?;
425
426        Ok(json!({
427            "success": true,
428            "width": width,
429            "height": height,
430            "x": x,
431            "y": y
432        }))
433    }
434}