adk_browser/tools/
evaluate.rs1use crate::session::BrowserSession;
4use adk_core::{Result, Tool, ToolContext};
5use async_trait::async_trait;
6use serde_json::{Value, json};
7use std::sync::Arc;
8
9pub struct EvaluateJsTool {
11 browser: Arc<BrowserSession>,
12}
13
14impl EvaluateJsTool {
15 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
79pub 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 let escaped = crate::escape::escape_js_string(sel);
129 let script = format!(
130 "document.querySelector('{escaped}').scrollIntoView({{ behavior: 'smooth', block: 'center' }})"
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
161pub 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 let escaped = crate::escape::escape_js_string(selector);
202
203 let script = format!(
205 r#"
206 var element = document.querySelector('{escaped}');
207 if (element) {{
208 element.dispatchEvent(new MouseEvent('mouseenter', {{
209 'view': window, 'bubbles': true, 'cancelable': true
210 }}));
211 element.dispatchEvent(new MouseEvent('mouseover', {{
212 'view': window, 'bubbles': true, 'cancelable': true
213 }}));
214 return true;
215 }}
216 return false;
217 "#,
218 );
219
220 let result = self.browser.execute_script(&script).await?;
221
222 if result.as_bool() == Some(true) {
223 Ok(json!({
224 "success": true,
225 "hovered": selector
226 }))
227 } else {
228 Err(adk_core::AdkError::Tool(format!("Element not found: {selector}")))
229 }
230 }
231}
232
233pub struct AlertTool {
235 browser: Arc<BrowserSession>,
236}
237
238impl AlertTool {
239 pub fn new(browser: Arc<BrowserSession>) -> Self {
240 Self { browser }
241 }
242}
243
244#[async_trait]
245impl Tool for AlertTool {
246 fn name(&self) -> &str {
247 "browser_handle_alert"
248 }
249
250 fn description(&self) -> &str {
251 "Handle JavaScript alerts, confirms, and prompts. Accepts or dismisses the active dialog."
252 }
253
254 fn parameters_schema(&self) -> Option<Value> {
255 Some(json!({
256 "type": "object",
257 "properties": {
258 "action": {
259 "type": "string",
260 "enum": ["accept", "dismiss"],
261 "description": "Action to take on the alert"
262 },
263 "text": {
264 "type": "string",
265 "description": "Text to enter for prompt dialogs"
266 }
267 },
268 "required": ["action"]
269 }))
270 }
271
272 async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
273 let action = args
274 .get("action")
275 .and_then(|v| v.as_str())
276 .ok_or_else(|| adk_core::AdkError::Tool("Missing 'action' parameter".to_string()))?;
277
278 let prompt_text = args.get("text").and_then(|v| v.as_str());
279
280 let real_alert_result = self.browser.execute_script("return 'no_alert';").await;
283
284 let has_real_alert = real_alert_result.is_err();
289
290 if has_real_alert {
291 let handle_script = match action {
295 "accept" => {
296 if let Some(txt) = prompt_text {
297 let escaped = crate::escape::escape_js_string(txt);
298 format!(
299 "window.__adk_prompt_response = '{escaped}'; \
300 window.prompt = function() {{ return window.__adk_prompt_response; }}; \
301 window.confirm = function() {{ return true; }}; \
302 window.alert = function() {{}};"
303 )
304 } else {
305 "window.confirm = function() { return true; }; \
306 window.alert = function() {}; \
307 window.prompt = function() { return ''; };"
308 .to_string()
309 }
310 }
311 "dismiss" => "window.confirm = function() { return false; }; \
312 window.alert = function() {}; \
313 window.prompt = function() { return null; };"
314 .to_string(),
315 _ => return Err(adk_core::AdkError::Tool(format!("Invalid action: {action}"))),
316 };
317
318 let _ = self.browser.execute_script(&handle_script).await;
320
321 Ok(json!({
322 "success": true,
323 "action": action,
324 "had_active_alert": true
325 }))
326 } else {
327 let script = match action {
329 "accept" => {
330 if let Some(txt) = prompt_text {
331 let escaped = crate::escape::escape_js_string(txt);
332 format!(
333 "window.__adk_prompt_response = '{escaped}'; \
334 window.prompt = function() {{ return window.__adk_prompt_response; }}; \
335 window.confirm = function() {{ return true; }}; \
336 window.alert = function() {{}}; \
337 return 'ok';"
338 )
339 } else {
340 "window.confirm = function() { return true; }; \
341 window.alert = function() {}; \
342 window.prompt = function() { return ''; }; \
343 return 'ok';"
344 .to_string()
345 }
346 }
347 "dismiss" => "window.confirm = function() { return false; }; \
348 window.alert = function() {}; \
349 window.prompt = function() { return null; }; \
350 return 'ok';"
351 .to_string(),
352 _ => return Err(adk_core::AdkError::Tool(format!("Invalid action: {action}"))),
353 };
354
355 self.browser.execute_script(&script).await?;
356
357 Ok(json!({
358 "success": true,
359 "action": action,
360 "had_active_alert": false
361 }))
362 }
363 }
364}