adk_browser/tools/
navigate.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 const DEFAULT_ALLOWED_SCHEMES: &[&str] = &["http", "https"];
15
16pub(crate) fn default_allowed_schemes() -> Vec<String> {
18 DEFAULT_ALLOWED_SCHEMES.iter().map(|scheme| (*scheme).to_string()).collect()
19}
20
21pub(crate) fn validate_url(url: &str, allowed: &[String]) -> Result<()> {
23 let parsed = url::Url::parse(url)
24 .map_err(|e| adk_core::AdkError::tool(format!("Invalid URL '{url}': {e}")))?;
25 let scheme = parsed.scheme();
26 if allowed.iter().any(|candidate| candidate.eq_ignore_ascii_case(scheme)) {
27 return Ok(());
28 }
29 Err(adk_core::AdkError::tool(format!(
30 "URL scheme '{scheme}' is not allowed; allowed schemes: {}. Configure \
31 BrowserToolset::with_allowed_schemes to permit others.",
32 allowed.join(", ")
33 )))
34}
35
36pub struct NavigateTool {
41 browser: Arc<BrowserSession>,
42 allowed_schemes: Vec<String>,
43}
44
45impl NavigateTool {
46 pub fn new(browser: Arc<BrowserSession>) -> Self {
51 Self { browser, allowed_schemes: default_allowed_schemes() }
52 }
53
54 #[must_use]
70 pub fn with_allowed_schemes<I, S>(mut self, schemes: I) -> Self
71 where
72 I: IntoIterator<Item = S>,
73 S: Into<String>,
74 {
75 self.allowed_schemes = schemes.into_iter().map(Into::into).collect();
76 self
77 }
78}
79
80#[async_trait]
81impl Tool for NavigateTool {
82 fn name(&self) -> &str {
83 "browser_navigate"
84 }
85
86 fn description(&self) -> &str {
87 "Navigate the browser to a specified URL. Use this to open web pages \
88 (http and https URLs by default)."
89 }
90
91 fn parameters_schema(&self) -> Option<Value> {
92 Some(json!({
93 "type": "object",
94 "properties": {
95 "url": {
96 "type": "string",
97 "description": "The URL to navigate to (e.g., 'https://example.com')"
98 }
99 },
100 "required": ["url"]
101 }))
102 }
103
104 fn response_schema(&self) -> Option<Value> {
105 Some(json!({
106 "type": "object",
107 "properties": {
108 "success": { "type": "boolean" },
109 "url": { "type": "string" },
110 "title": { "type": "string" }
111 }
112 }))
113 }
114
115 async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
116 let url = args
117 .get("url")
118 .and_then(|v| v.as_str())
119 .ok_or_else(|| adk_core::AdkError::tool("Missing 'url' parameter"))?;
120
121 validate_url(url, &self.allowed_schemes)?;
122
123 self.browser.navigate(url).await?;
125
126 let current_url = self.browser.current_url().await.unwrap_or_default();
128 let title = self.browser.title().await.unwrap_or_default();
129
130 match self.browser.page_context().await {
132 Ok(page) => Ok(json!({
133 "success": true,
134 "url": current_url,
135 "title": title,
136 "page": page
137 })),
138 Err(e) => Ok(json!({
139 "success": true,
140 "url": current_url,
141 "title": title,
142 "page_context_error": e.to_string()
143 })),
144 }
145 }
146}
147
148pub struct BackTool {
150 browser: Arc<BrowserSession>,
151}
152
153impl BackTool {
154 pub fn new(browser: Arc<BrowserSession>) -> Self {
155 Self { browser }
156 }
157}
158
159#[async_trait]
160impl Tool for BackTool {
161 fn name(&self) -> &str {
162 "browser_back"
163 }
164
165 fn description(&self) -> &str {
166 "Go back to the previous page in browser history."
167 }
168
169 fn parameters_schema(&self) -> Option<Value> {
170 Some(json!({
171 "type": "object",
172 "properties": {}
173 }))
174 }
175
176 async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
177 self.browser.back().await?;
178
179 let url = self.browser.current_url().await.unwrap_or_default();
180 let title = self.browser.title().await.unwrap_or_default();
181
182 match self.browser.page_context().await {
184 Ok(page) => Ok(json!({
185 "success": true,
186 "url": url,
187 "title": title,
188 "page": page
189 })),
190 Err(e) => Ok(json!({
191 "success": true,
192 "url": url,
193 "title": title,
194 "page_context_error": e.to_string()
195 })),
196 }
197 }
198}
199
200pub struct ForwardTool {
202 browser: Arc<BrowserSession>,
203}
204
205impl ForwardTool {
206 pub fn new(browser: Arc<BrowserSession>) -> Self {
207 Self { browser }
208 }
209}
210
211#[async_trait]
212impl Tool for ForwardTool {
213 fn name(&self) -> &str {
214 "browser_forward"
215 }
216
217 fn description(&self) -> &str {
218 "Go forward to the next page in browser history."
219 }
220
221 fn parameters_schema(&self) -> Option<Value> {
222 Some(json!({
223 "type": "object",
224 "properties": {}
225 }))
226 }
227
228 async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
229 self.browser.forward().await?;
230
231 let url = self.browser.current_url().await.unwrap_or_default();
232 let title = self.browser.title().await.unwrap_or_default();
233
234 match self.browser.page_context().await {
236 Ok(page) => Ok(json!({
237 "success": true,
238 "url": url,
239 "title": title,
240 "page": page
241 })),
242 Err(e) => Ok(json!({
243 "success": true,
244 "url": url,
245 "title": title,
246 "page_context_error": e.to_string()
247 })),
248 }
249 }
250}
251
252pub struct RefreshTool {
254 browser: Arc<BrowserSession>,
255}
256
257impl RefreshTool {
258 pub fn new(browser: Arc<BrowserSession>) -> Self {
259 Self { browser }
260 }
261}
262
263#[async_trait]
264impl Tool for RefreshTool {
265 fn name(&self) -> &str {
266 "browser_refresh"
267 }
268
269 fn description(&self) -> &str {
270 "Refresh the current page."
271 }
272
273 fn parameters_schema(&self) -> Option<Value> {
274 Some(json!({
275 "type": "object",
276 "properties": {}
277 }))
278 }
279
280 async fn execute(&self, _ctx: Arc<dyn ToolContext>, _args: Value) -> Result<Value> {
281 self.browser.refresh().await?;
282
283 let url = self.browser.current_url().await.unwrap_or_default();
284 let title = self.browser.title().await.unwrap_or_default();
285
286 match self.browser.page_context().await {
288 Ok(page) => Ok(json!({
289 "success": true,
290 "url": url,
291 "title": title,
292 "page": page
293 })),
294 Err(e) => Ok(json!({
295 "success": true,
296 "url": url,
297 "title": title,
298 "page_context_error": e.to_string()
299 })),
300 }
301 }
302}