adk_browser/tools/
wait.rs1use crate::session::BrowserSession;
4use adk_core::{Result, Tool, ToolContext};
5use async_trait::async_trait;
6use serde_json::{json, Value};
7use std::sync::Arc;
8use std::time::Duration;
9
10pub struct WaitForElementTool {
12 browser: Arc<BrowserSession>,
13}
14
15impl WaitForElementTool {
16 pub fn new(browser: Arc<BrowserSession>) -> Self {
18 Self { browser }
19 }
20}
21
22#[async_trait]
23impl Tool for WaitForElementTool {
24 fn name(&self) -> &str {
25 "browser_wait_for_element"
26 }
27
28 fn description(&self) -> &str {
29 "Wait for an element to appear on the page. Useful after navigation or dynamic content loading."
30 }
31
32 fn parameters_schema(&self) -> Option<Value> {
33 Some(json!({
34 "type": "object",
35 "properties": {
36 "selector": {
37 "type": "string",
38 "description": "CSS selector for the element to wait for"
39 },
40 "timeout": {
41 "type": "integer",
42 "description": "Maximum wait time in seconds (default: 30)"
43 },
44 "visible": {
45 "type": "boolean",
46 "description": "Wait for element to be visible, not just present (default: false)"
47 }
48 },
49 "required": ["selector"]
50 }))
51 }
52
53 fn response_schema(&self) -> Option<Value> {
54 Some(json!({
55 "type": "object",
56 "properties": {
57 "success": { "type": "boolean" },
58 "found": { "type": "boolean" },
59 "element_text": { "type": "string" }
60 }
61 }))
62 }
63
64 async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
65 let selector = args
66 .get("selector")
67 .and_then(|v| v.as_str())
68 .ok_or_else(|| adk_core::AdkError::Tool("Missing 'selector' parameter".to_string()))?;
69
70 let timeout = args.get("timeout").and_then(|v| v.as_u64()).unwrap_or(30);
71
72 let visible = args.get("visible").and_then(|v| v.as_bool()).unwrap_or(false);
73
74 let element = if visible {
75 self.browser.wait_for_clickable(selector, timeout).await?
76 } else {
77 self.browser.wait_for_element(selector, timeout).await?
78 };
79
80 let text = element.text().await.unwrap_or_default();
81 let text_preview = text.chars().take(100).collect::<String>();
82
83 Ok(json!({
84 "success": true,
85 "found": true,
86 "element_text": text_preview
87 }))
88 }
89}
90
91pub struct WaitTool;
93
94impl WaitTool {
95 pub fn new() -> Self {
96 Self
97 }
98}
99
100impl Default for WaitTool {
101 fn default() -> Self {
102 Self::new()
103 }
104}
105
106#[async_trait]
107impl Tool for WaitTool {
108 fn name(&self) -> &str {
109 "browser_wait"
110 }
111
112 fn description(&self) -> &str {
113 "Wait for a specified duration. Use sparingly - prefer waiting for specific elements."
114 }
115
116 fn parameters_schema(&self) -> Option<Value> {
117 Some(json!({
118 "type": "object",
119 "properties": {
120 "seconds": {
121 "type": "number",
122 "description": "Number of seconds to wait (max: 30)"
123 }
124 },
125 "required": ["seconds"]
126 }))
127 }
128
129 async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
130 let seconds = args
131 .get("seconds")
132 .and_then(|v| v.as_f64())
133 .ok_or_else(|| adk_core::AdkError::Tool("Missing 'seconds' parameter".to_string()))?;
134
135 let seconds = seconds.min(30.0);
137
138 tokio::time::sleep(Duration::from_secs_f64(seconds)).await;
139
140 Ok(json!({
141 "success": true,
142 "waited_seconds": seconds
143 }))
144 }
145}
146
147pub struct WaitForPageLoadTool {
149 browser: Arc<BrowserSession>,
150}
151
152impl WaitForPageLoadTool {
153 pub fn new(browser: Arc<BrowserSession>) -> Self {
154 Self { browser }
155 }
156}
157
158#[async_trait]
159impl Tool for WaitForPageLoadTool {
160 fn name(&self) -> &str {
161 "browser_wait_for_page_load"
162 }
163
164 fn description(&self) -> &str {
165 "Wait for the page to finish loading (document.readyState === 'complete')."
166 }
167
168 fn parameters_schema(&self) -> Option<Value> {
169 Some(json!({
170 "type": "object",
171 "properties": {
172 "timeout": {
173 "type": "integer",
174 "description": "Maximum wait time in seconds (default: 30)"
175 }
176 }
177 }))
178 }
179
180 async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
181 let timeout = args.get("timeout").and_then(|v| v.as_u64()).unwrap_or(30);
182
183 let script = "return document.readyState";
184 let start = std::time::Instant::now();
185
186 loop {
187 let result = self.browser.execute_script(script).await?;
188 if result.as_str() == Some("complete") {
189 break;
190 }
191
192 if start.elapsed().as_secs() > timeout {
193 return Err(adk_core::AdkError::Tool(format!(
194 "Page load timeout after {}s",
195 timeout
196 )));
197 }
198
199 tokio::time::sleep(Duration::from_millis(100)).await;
200 }
201
202 let url = self.browser.current_url().await?;
203 let title = self.browser.title().await?;
204
205 Ok(json!({
206 "success": true,
207 "url": url,
208 "title": title,
209 "ready_state": "complete"
210 }))
211 }
212}
213
214pub struct WaitForTextTool {
216 browser: Arc<BrowserSession>,
217}
218
219impl WaitForTextTool {
220 pub fn new(browser: Arc<BrowserSession>) -> Self {
221 Self { browser }
222 }
223}
224
225#[async_trait]
226impl Tool for WaitForTextTool {
227 fn name(&self) -> &str {
228 "browser_wait_for_text"
229 }
230
231 fn description(&self) -> &str {
232 "Wait for specific text to appear anywhere on the page."
233 }
234
235 fn parameters_schema(&self) -> Option<Value> {
236 Some(json!({
237 "type": "object",
238 "properties": {
239 "text": {
240 "type": "string",
241 "description": "The text to wait for"
242 },
243 "timeout": {
244 "type": "integer",
245 "description": "Maximum wait time in seconds (default: 30)"
246 }
247 },
248 "required": ["text"]
249 }))
250 }
251
252 async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
253 let text = args
254 .get("text")
255 .and_then(|v| v.as_str())
256 .ok_or_else(|| adk_core::AdkError::Tool("Missing 'text' parameter".to_string()))?;
257
258 let timeout = args.get("timeout").and_then(|v| v.as_u64()).unwrap_or(30);
259
260 let script =
261 format!("return document.body.innerText.includes('{}')", text.replace('\'', "\\'"));
262
263 let start = std::time::Instant::now();
264
265 loop {
266 let result = self.browser.execute_script(&script).await?;
267 if result.as_bool() == Some(true) {
268 return Ok(json!({
269 "success": true,
270 "found": true,
271 "text": text
272 }));
273 }
274
275 if start.elapsed().as_secs() > timeout {
276 return Err(adk_core::AdkError::Tool(format!(
277 "Text '{}' not found after {}s",
278 text, timeout
279 )));
280 }
281
282 tokio::time::sleep(Duration::from_millis(100)).await;
283 }
284 }
285}