Skip to main content

adk_browser/tools/
navigate.rs

1//! Navigate tool for browser navigation.
2
3use crate::session::BrowserSession;
4use adk_core::{Result, Tool, ToolContext};
5use async_trait::async_trait;
6use serde_json::{Value, json};
7use std::sync::Arc;
8
9/// URL schemes the navigation tools accept unless configured otherwise.
10///
11/// `file:`, `javascript:`, `data:`, and browser-internal schemes such as `chrome:`
12/// are excluded: they read the host filesystem, run script, or reach browser
13/// settings rather than load a web page.
14pub const DEFAULT_ALLOWED_SCHEMES: &[&str] = &["http", "https"];
15
16/// The default scheme allowlist as owned strings.
17pub(crate) fn default_allowed_schemes() -> Vec<String> {
18    DEFAULT_ALLOWED_SCHEMES.iter().map(|scheme| (*scheme).to_string()).collect()
19}
20
21/// Rejects a model-supplied URL that does not parse or whose scheme is not in `allowed`.
22pub(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
36/// Tool for navigating to URLs.
37///
38/// Only URLs whose scheme is in the allowlist are opened; the default is
39/// [`DEFAULT_ALLOWED_SCHEMES`] (`http` and `https`).
40pub struct NavigateTool {
41    browser: Arc<BrowserSession>,
42    allowed_schemes: Vec<String>,
43}
44
45impl NavigateTool {
46    /// Create a new navigate tool with a shared browser session.
47    ///
48    /// The tool accepts [`DEFAULT_ALLOWED_SCHEMES`] until
49    /// [`with_allowed_schemes`](Self::with_allowed_schemes) replaces them.
50    pub fn new(browser: Arc<BrowserSession>) -> Self {
51        Self { browser, allowed_schemes: default_allowed_schemes() }
52    }
53
54    /// Replace the URL schemes this tool accepts.
55    ///
56    /// Schemes are compared case-insensitively. Allowing `file` exposes the host
57    /// filesystem to the model, and allowing `javascript` or `data` lets it run
58    /// script outside `browser_evaluate_js`.
59    ///
60    /// # Example
61    ///
62    /// ```rust,ignore
63    /// use adk_browser::{BrowserSession, NavigateTool};
64    /// use std::sync::Arc;
65    ///
66    /// let browser = Arc::new(BrowserSession::with_defaults());
67    /// let tool = NavigateTool::new(browser).with_allowed_schemes(["https"]);
68    /// ```
69    #[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        // Navigate
124        self.browser.navigate(url).await?;
125
126        // Get result info
127        let current_url = self.browser.current_url().await.unwrap_or_default();
128        let title = self.browser.title().await.unwrap_or_default();
129
130        // Include page context like interaction tools do
131        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
148/// Tool for going back in browser history.
149pub 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        // Include page context like interaction tools do
183        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
200/// Tool for going forward in browser history.
201pub 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        // Include page context like interaction tools do
235        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
252/// Tool for refreshing the current page.
253pub 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        // Include page context like interaction tools do
287        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}