adk_browser/tools/
click.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 ClickTool {
11 browser: Arc<BrowserSession>,
12}
13
14impl ClickTool {
15 pub fn new(browser: Arc<BrowserSession>) -> Self {
17 Self { browser }
18 }
19}
20
21#[async_trait]
22impl Tool for ClickTool {
23 fn name(&self) -> &str {
24 "browser_click"
25 }
26
27 fn description(&self) -> &str {
28 "Click on an element on the page. Use CSS selectors to identify the element."
29 }
30
31 fn parameters_schema(&self) -> Option<Value> {
32 Some(json!({
33 "type": "object",
34 "properties": {
35 "selector": {
36 "type": "string",
37 "description": "CSS selector for the element to click (e.g., '#submit-btn', '.nav-link', 'button[type=submit]')"
38 },
39 "wait_timeout": {
40 "type": "integer",
41 "description": "Optional timeout in seconds to wait for element to be clickable (default: 10)"
42 }
43 },
44 "required": ["selector"]
45 }))
46 }
47
48 fn response_schema(&self) -> Option<Value> {
49 Some(json!({
50 "type": "object",
51 "properties": {
52 "success": { "type": "boolean" },
53 "clicked_element": { "type": "string" }
54 }
55 }))
56 }
57
58 async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
59 let selector = args
60 .get("selector")
61 .and_then(|v| v.as_str())
62 .ok_or_else(|| adk_core::AdkError::Tool("Missing 'selector' parameter".to_string()))?;
63
64 let wait_timeout = args.get("wait_timeout").and_then(|v| v.as_u64()).unwrap_or(10);
65
66 let element = self.browser.wait_for_clickable(selector, wait_timeout).await?;
68
69 element
70 .click()
71 .await
72 .map_err(|e| adk_core::AdkError::Tool(format!("Click failed: {}", e)))?;
73
74 let tag_name = element.tag_name().await.unwrap_or_else(|_| "unknown".to_string());
76
77 let text = element.text().await.unwrap_or_default();
78 let element_info = if text.is_empty() {
79 tag_name
80 } else {
81 format!("{}: {}", tag_name, text.chars().take(50).collect::<String>())
82 };
83
84 let context = self.browser.page_context().await.unwrap_or_default();
86
87 Ok(json!({
88 "success": true,
89 "clicked_element": element_info,
90 "page": context
91 }))
92 }
93}
94
95pub struct DoubleClickTool {
97 browser: Arc<BrowserSession>,
98}
99
100impl DoubleClickTool {
101 pub fn new(browser: Arc<BrowserSession>) -> Self {
102 Self { browser }
103 }
104}
105
106#[async_trait]
107impl Tool for DoubleClickTool {
108 fn name(&self) -> &str {
109 "browser_double_click"
110 }
111
112 fn description(&self) -> &str {
113 "Double-click on an element on the page."
114 }
115
116 fn parameters_schema(&self) -> Option<Value> {
117 Some(json!({
118 "type": "object",
119 "properties": {
120 "selector": {
121 "type": "string",
122 "description": "CSS selector for the element to double-click"
123 }
124 },
125 "required": ["selector"]
126 }))
127 }
128
129 async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
130 let selector = args
131 .get("selector")
132 .and_then(|v| v.as_str())
133 .ok_or_else(|| adk_core::AdkError::Tool("Missing 'selector' parameter".to_string()))?;
134
135 let escaped = crate::escape::escape_js_string(selector);
136
137 let result = self.browser
139 .execute_script(&format!(
140 "var el = document.querySelector('{escaped}'); if (!el) return null; el.dispatchEvent(new MouseEvent('dblclick', {{'view': window, 'bubbles': true, 'cancelable': true}})); return el.tagName.toLowerCase();"
141 ))
142 .await?;
143
144 let tag_name = result.as_str().unwrap_or("unknown");
145 if tag_name == "unknown" && result.is_null() {
146 return Err(adk_core::AdkError::Tool(format!("Element not found: {selector}")));
147 }
148
149 Ok(json!({
150 "success": true,
151 "double_clicked_element": tag_name
152 }))
153 }
154}