adk_browser/tools/
evaluate.rs1use crate::session::BrowserSession;
4use adk_core::{Result, Tool, ToolContext};
5use async_trait::async_trait;
6use serde_json::{json, Value};
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 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
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 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
232pub 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 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}