1use 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
9pub 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
68pub 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
117pub 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
166pub 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
219pub 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
286pub 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
345pub 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}