adk_browser/tools/
actions.rs

1//! Advanced action tools (drag-drop, context-click, focus, etc.)
2
3use crate::session::BrowserSession;
4use adk_core::{AdkError, Result, Tool, ToolContext};
5use async_trait::async_trait;
6use serde_json::{json, Value};
7use std::sync::Arc;
8
9/// Tool for drag and drop operations.
10pub struct DragAndDropTool {
11    browser: Arc<BrowserSession>,
12}
13
14impl DragAndDropTool {
15    pub fn new(browser: Arc<BrowserSession>) -> Self {
16        Self { browser }
17    }
18}
19
20#[async_trait]
21impl Tool for DragAndDropTool {
22    fn name(&self) -> &str {
23        "browser_drag_and_drop"
24    }
25
26    fn description(&self) -> &str {
27        "Drag an element and drop it onto another element."
28    }
29
30    fn parameters_schema(&self) -> Option<Value> {
31        Some(json!({
32            "type": "object",
33            "properties": {
34                "source_selector": {
35                    "type": "string",
36                    "description": "CSS selector for the element to drag"
37                },
38                "target_selector": {
39                    "type": "string",
40                    "description": "CSS selector for the drop target"
41                }
42            },
43            "required": ["source_selector", "target_selector"]
44        }))
45    }
46
47    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
48        let source = args
49            .get("source_selector")
50            .and_then(|v| v.as_str())
51            .ok_or_else(|| AdkError::Tool("Missing 'source_selector' parameter".to_string()))?;
52
53        let target = args
54            .get("target_selector")
55            .and_then(|v| v.as_str())
56            .ok_or_else(|| AdkError::Tool("Missing 'target_selector' parameter".to_string()))?;
57
58        self.browser.drag_and_drop(source, target).await?;
59
60        Ok(json!({
61            "success": true,
62            "dragged_from": source,
63            "dropped_on": target
64        }))
65    }
66}
67
68/// Tool for right-click (context click).
69pub struct RightClickTool {
70    browser: Arc<BrowserSession>,
71}
72
73impl RightClickTool {
74    pub fn new(browser: Arc<BrowserSession>) -> Self {
75        Self { browser }
76    }
77}
78
79#[async_trait]
80impl Tool for RightClickTool {
81    fn name(&self) -> &str {
82        "browser_right_click"
83    }
84
85    fn description(&self) -> &str {
86        "Right-click (context click) on an element to open context menu."
87    }
88
89    fn parameters_schema(&self) -> Option<Value> {
90        Some(json!({
91            "type": "object",
92            "properties": {
93                "selector": {
94                    "type": "string",
95                    "description": "CSS selector for the element to right-click"
96                }
97            },
98            "required": ["selector"]
99        }))
100    }
101
102    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
103        let selector = args
104            .get("selector")
105            .and_then(|v| v.as_str())
106            .ok_or_else(|| AdkError::Tool("Missing 'selector' parameter".to_string()))?;
107
108        self.browser.right_click(selector).await?;
109
110        Ok(json!({
111            "success": true,
112            "right_clicked": selector
113        }))
114    }
115}
116
117/// Tool for focusing an element.
118pub struct FocusTool {
119    browser: Arc<BrowserSession>,
120}
121
122impl FocusTool {
123    pub fn new(browser: Arc<BrowserSession>) -> Self {
124        Self { browser }
125    }
126}
127
128#[async_trait]
129impl Tool for FocusTool {
130    fn name(&self) -> &str {
131        "browser_focus"
132    }
133
134    fn description(&self) -> &str {
135        "Focus on an element (useful for inputs before typing)."
136    }
137
138    fn parameters_schema(&self) -> Option<Value> {
139        Some(json!({
140            "type": "object",
141            "properties": {
142                "selector": {
143                    "type": "string",
144                    "description": "CSS selector for the element to focus"
145                }
146            },
147            "required": ["selector"]
148        }))
149    }
150
151    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
152        let selector = args
153            .get("selector")
154            .and_then(|v| v.as_str())
155            .ok_or_else(|| AdkError::Tool("Missing 'selector' parameter".to_string()))?;
156
157        self.browser.focus_element(selector).await?;
158
159        Ok(json!({
160            "success": true,
161            "focused": selector
162        }))
163    }
164}
165
166/// Tool for checking element state (visible, enabled, selected).
167pub struct ElementStateTool {
168    browser: Arc<BrowserSession>,
169}
170
171impl ElementStateTool {
172    pub fn new(browser: Arc<BrowserSession>) -> Self {
173        Self { browser }
174    }
175}
176
177#[async_trait]
178impl Tool for ElementStateTool {
179    fn name(&self) -> &str {
180        "browser_element_state"
181    }
182
183    fn description(&self) -> &str {
184        "Check the state of an element (displayed, enabled, selected, clickable)."
185    }
186
187    fn parameters_schema(&self) -> Option<Value> {
188        Some(json!({
189            "type": "object",
190            "properties": {
191                "selector": {
192                    "type": "string",
193                    "description": "CSS selector for the element"
194                }
195            },
196            "required": ["selector"]
197        }))
198    }
199
200    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
201        let selector = args
202            .get("selector")
203            .and_then(|v| v.as_str())
204            .ok_or_else(|| AdkError::Tool("Missing 'selector' parameter".to_string()))?;
205
206        let state = self.browser.get_element_state(selector).await?;
207
208        Ok(json!({
209            "success": true,
210            "selector": selector,
211            "is_displayed": state.is_displayed,
212            "is_enabled": state.is_enabled,
213            "is_selected": state.is_selected,
214            "is_clickable": state.is_clickable
215        }))
216    }
217}
218
219/// Tool for pressing keyboard keys.
220pub struct PressKeyTool {
221    browser: Arc<BrowserSession>,
222}
223
224impl PressKeyTool {
225    pub fn new(browser: Arc<BrowserSession>) -> Self {
226        Self { browser }
227    }
228}
229
230#[async_trait]
231impl Tool for PressKeyTool {
232    fn name(&self) -> &str {
233        "browser_press_key"
234    }
235
236    fn description(&self) -> &str {
237        "Press a keyboard key (Enter, Escape, Tab, etc.) optionally on a specific element."
238    }
239
240    fn parameters_schema(&self) -> Option<Value> {
241        Some(json!({
242            "type": "object",
243            "properties": {
244                "key": {
245                    "type": "string",
246                    "description": "Key to press: Enter, Escape, Tab, Backspace, Delete, ArrowUp, ArrowDown, ArrowLeft, ArrowRight, etc."
247                },
248                "selector": {
249                    "type": "string",
250                    "description": "Optional CSS selector for the target element"
251                },
252                "modifiers": {
253                    "type": "array",
254                    "items": { "type": "string" },
255                    "description": "Optional modifier keys: Ctrl, Alt, Shift, Meta"
256                }
257            },
258            "required": ["key"]
259        }))
260    }
261
262    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
263        let key = args
264            .get("key")
265            .and_then(|v| v.as_str())
266            .ok_or_else(|| AdkError::Tool("Missing 'key' parameter".to_string()))?;
267
268        let selector = args.get("selector").and_then(|v| v.as_str());
269        let modifiers: Vec<&str> = args
270            .get("modifiers")
271            .and_then(|v| v.as_array())
272            .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
273            .unwrap_or_default();
274
275        self.browser.press_key(key, selector, &modifiers).await?;
276
277        Ok(json!({
278            "success": true,
279            "key_pressed": key,
280            "modifiers": modifiers,
281            "target": selector
282        }))
283    }
284}
285
286/// Tool for uploading files.
287pub struct FileUploadTool {
288    browser: Arc<BrowserSession>,
289}
290
291impl FileUploadTool {
292    pub fn new(browser: Arc<BrowserSession>) -> Self {
293        Self { browser }
294    }
295}
296
297#[async_trait]
298impl Tool for FileUploadTool {
299    fn name(&self) -> &str {
300        "browser_file_upload"
301    }
302
303    fn description(&self) -> &str {
304        "Upload a file to a file input element."
305    }
306
307    fn parameters_schema(&self) -> Option<Value> {
308        Some(json!({
309            "type": "object",
310            "properties": {
311                "selector": {
312                    "type": "string",
313                    "description": "CSS selector for the file input element"
314                },
315                "file_path": {
316                    "type": "string",
317                    "description": "Absolute path to the file to upload"
318                }
319            },
320            "required": ["selector", "file_path"]
321        }))
322    }
323
324    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
325        let selector = args
326            .get("selector")
327            .and_then(|v| v.as_str())
328            .ok_or_else(|| AdkError::Tool("Missing 'selector' parameter".to_string()))?;
329
330        let file_path = args
331            .get("file_path")
332            .and_then(|v| v.as_str())
333            .ok_or_else(|| AdkError::Tool("Missing 'file_path' parameter".to_string()))?;
334
335        self.browser.upload_file(selector, file_path).await?;
336
337        Ok(json!({
338            "success": true,
339            "uploaded_file": file_path,
340            "to_element": selector
341        }))
342    }
343}
344
345/// Tool for printing page to PDF.
346pub struct PrintToPdfTool {
347    browser: Arc<BrowserSession>,
348}
349
350impl PrintToPdfTool {
351    pub fn new(browser: Arc<BrowserSession>) -> Self {
352        Self { browser }
353    }
354}
355
356#[async_trait]
357impl Tool for PrintToPdfTool {
358    fn name(&self) -> &str {
359        "browser_print_to_pdf"
360    }
361
362    fn description(&self) -> &str {
363        "Print the current page to PDF and return as base64."
364    }
365
366    fn parameters_schema(&self) -> Option<Value> {
367        Some(json!({
368            "type": "object",
369            "properties": {
370                "landscape": {
371                    "type": "boolean",
372                    "description": "Print in landscape orientation (default: false)"
373                },
374                "scale": {
375                    "type": "number",
376                    "description": "Scale factor (default: 1.0)"
377                }
378            }
379        }))
380    }
381
382    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
383        let landscape = args.get("landscape").and_then(|v| v.as_bool()).unwrap_or(false);
384        let scale = args.get("scale").and_then(|v| v.as_f64()).unwrap_or(1.0);
385
386        let pdf_base64 = self.browser.print_to_pdf(landscape, scale).await?;
387
388        Ok(json!({
389            "success": true,
390            "pdf_base64": pdf_base64
391        }))
392    }
393}