adk_browser/tools/
evaluate.rs

1//! JavaScript evaluation tool.
2
3use crate::session::BrowserSession;
4use adk_core::{Result, Tool, ToolContext};
5use async_trait::async_trait;
6use serde_json::{json, Value};
7use std::sync::Arc;
8
9/// Tool for executing JavaScript in the browser.
10pub struct EvaluateJsTool {
11    browser: Arc<BrowserSession>,
12}
13
14impl EvaluateJsTool {
15    /// Create a new JavaScript evaluation tool with a shared browser session.
16    pub fn new(browser: Arc<BrowserSession>) -> Self {
17        Self { browser }
18    }
19}
20
21#[async_trait]
22impl Tool for EvaluateJsTool {
23    fn name(&self) -> &str {
24        "browser_evaluate_js"
25    }
26
27    fn description(&self) -> &str {
28        "Execute JavaScript code in the browser and return the result. Use for complex interactions or data extraction."
29    }
30
31    fn parameters_schema(&self) -> Option<Value> {
32        Some(json!({
33            "type": "object",
34            "properties": {
35                "script": {
36                    "type": "string",
37                    "description": "JavaScript code to execute. Use 'return' to get a value back."
38                },
39                "async": {
40                    "type": "boolean",
41                    "description": "Whether the script is async (uses a callback). Default: false"
42                }
43            },
44            "required": ["script"]
45        }))
46    }
47
48    fn response_schema(&self) -> Option<Value> {
49        Some(json!({
50            "type": "object",
51            "properties": {
52                "success": { "type": "boolean" },
53                "result": {}
54            }
55        }))
56    }
57
58    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
59        let script = args
60            .get("script")
61            .and_then(|v| v.as_str())
62            .ok_or_else(|| adk_core::AdkError::Tool("Missing 'script' parameter".to_string()))?;
63
64        let is_async = args.get("async").and_then(|v| v.as_bool()).unwrap_or(false);
65
66        let result = if is_async {
67            self.browser.execute_async_script(script).await?
68        } else {
69            self.browser.execute_script(script).await?
70        };
71
72        Ok(json!({
73            "success": true,
74            "result": result
75        }))
76    }
77}
78
79/// Tool for scrolling the page.
80pub struct ScrollTool {
81    browser: Arc<BrowserSession>,
82}
83
84impl ScrollTool {
85    pub fn new(browser: Arc<BrowserSession>) -> Self {
86        Self { browser }
87    }
88}
89
90#[async_trait]
91impl Tool for ScrollTool {
92    fn name(&self) -> &str {
93        "browser_scroll"
94    }
95
96    fn description(&self) -> &str {
97        "Scroll the page in a direction or to a specific element."
98    }
99
100    fn parameters_schema(&self) -> Option<Value> {
101        Some(json!({
102            "type": "object",
103            "properties": {
104                "direction": {
105                    "type": "string",
106                    "enum": ["up", "down", "top", "bottom"],
107                    "description": "Direction to scroll"
108                },
109                "selector": {
110                    "type": "string",
111                    "description": "CSS selector of element to scroll into view"
112                },
113                "amount": {
114                    "type": "integer",
115                    "description": "Pixels to scroll (for up/down). Default: 500"
116                }
117            }
118        }))
119    }
120
121    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
122        let direction = args.get("direction").and_then(|v| v.as_str());
123        let selector = args.get("selector").and_then(|v| v.as_str());
124        let amount = args.get("amount").and_then(|v| v.as_i64()).unwrap_or(500);
125
126        if let Some(sel) = selector {
127            // Scroll element into view
128            let script = format!(
129                "document.querySelector('{}').scrollIntoView({{ behavior: 'smooth', block: 'center' }})",
130                sel.replace('\'', "\\'")
131            );
132            self.browser.execute_script(&script).await?;
133
134            return Ok(json!({
135                "success": true,
136                "scrolled_to": sel
137            }));
138        }
139
140        if let Some(dir) = direction {
141            let script = match dir {
142                "up" => format!("window.scrollBy(0, -{})", amount),
143                "down" => format!("window.scrollBy(0, {})", amount),
144                "top" => "window.scrollTo(0, 0)".to_string(),
145                "bottom" => "window.scrollTo(0, document.body.scrollHeight)".to_string(),
146                _ => return Err(adk_core::AdkError::Tool(format!("Invalid direction: {}", dir))),
147            };
148
149            self.browser.execute_script(&script).await?;
150
151            return Ok(json!({
152                "success": true,
153                "scrolled": dir
154            }));
155        }
156
157        Err(adk_core::AdkError::Tool("Must specify either 'direction' or 'selector'".to_string()))
158    }
159}
160
161/// Tool for hovering over elements.
162pub struct HoverTool {
163    browser: Arc<BrowserSession>,
164}
165
166impl HoverTool {
167    pub fn new(browser: Arc<BrowserSession>) -> Self {
168        Self { browser }
169    }
170}
171
172#[async_trait]
173impl Tool for HoverTool {
174    fn name(&self) -> &str {
175        "browser_hover"
176    }
177
178    fn description(&self) -> &str {
179        "Hover over an element to trigger hover effects or tooltips."
180    }
181
182    fn parameters_schema(&self) -> Option<Value> {
183        Some(json!({
184            "type": "object",
185            "properties": {
186                "selector": {
187                    "type": "string",
188                    "description": "CSS selector for the element to hover over"
189                }
190            },
191            "required": ["selector"]
192        }))
193    }
194
195    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
196        let selector = args
197            .get("selector")
198            .and_then(|v| v.as_str())
199            .ok_or_else(|| adk_core::AdkError::Tool("Missing 'selector' parameter".to_string()))?;
200
201        // Use JavaScript to trigger hover events
202        let script = format!(
203            r#"
204            var element = document.querySelector('{}');
205            if (element) {{
206                var event = new MouseEvent('mouseover', {{
207                    'view': window,
208                    'bubbles': true,
209                    'cancelable': true
210                }});
211                element.dispatchEvent(event);
212                return true;
213            }}
214            return false;
215            "#,
216            selector.replace('\'', "\\'")
217        );
218
219        let result = self.browser.execute_script(&script).await?;
220
221        if result.as_bool() == Some(true) {
222            Ok(json!({
223                "success": true,
224                "hovered": selector
225            }))
226        } else {
227            Err(adk_core::AdkError::Tool(format!("Element not found: {}", selector)))
228        }
229    }
230}
231
232/// Tool for handling alerts/dialogs.
233pub struct AlertTool {
234    browser: Arc<BrowserSession>,
235}
236
237impl AlertTool {
238    pub fn new(browser: Arc<BrowserSession>) -> Self {
239        Self { browser }
240    }
241}
242
243#[async_trait]
244impl Tool for AlertTool {
245    fn name(&self) -> &str {
246        "browser_handle_alert"
247    }
248
249    fn description(&self) -> &str {
250        "Handle JavaScript alerts, confirms, and prompts."
251    }
252
253    fn parameters_schema(&self) -> Option<Value> {
254        Some(json!({
255            "type": "object",
256            "properties": {
257                "action": {
258                    "type": "string",
259                    "enum": ["accept", "dismiss"],
260                    "description": "Action to take on the alert"
261                },
262                "text": {
263                    "type": "string",
264                    "description": "Text to enter for prompt dialogs"
265                }
266            },
267            "required": ["action"]
268        }))
269    }
270
271    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
272        let action = args
273            .get("action")
274            .and_then(|v| v.as_str())
275            .ok_or_else(|| adk_core::AdkError::Tool("Missing 'action' parameter".to_string()))?;
276
277        let _text = args.get("text").and_then(|v| v.as_str());
278
279        // Note: Full alert handling requires thirtyfour's alert API
280        // For now, provide a JavaScript-based approach
281        let script = match action {
282            "accept" => {
283                r#"
284                window.alert = function() { return true; };
285                window.confirm = function() { return true; };
286                window.prompt = function() { return ''; };
287                return 'ok';
288                "#
289            }
290            "dismiss" => {
291                r#"
292                window.alert = function() { return false; };
293                window.confirm = function() { return false; };
294                window.prompt = function() { return null; };
295                return 'ok';
296                "#
297            }
298            _ => return Err(adk_core::AdkError::Tool(format!("Invalid action: {}", action))),
299        };
300
301        self.browser.execute_script(script).await?;
302
303        Ok(json!({
304            "success": true,
305            "action": action
306        }))
307    }
308}