telos_agent/tools/builtin/browser/tool_impls/
find_url.rs1use async_trait::async_trait;
2use serde_json::{Value, json};
3
4use crate::error::AgentError;
5use crate::tools::api::{PermissionDecision, Tool, ToolContext, ToolDefinition, ToolOutput};
6use crate::tools::browser::util::*;
7
8pub struct BrowserFindUrlTool;
9
10#[async_trait]
11impl Tool for BrowserFindUrlTool {
12 fn definition(&self) -> ToolDefinition {
13 ToolDefinition {
14 name: "BrowserFindUrl".into(),
15 description: "Search local browser bookmarks/history metadata for likely URLs. Requires explicit approval.".into(),
16 input_schema: json!({
17 "type": "object",
18 "properties": {
19 "query": { "type": "string" },
20 "limit": { "type": "integer", "default": 10 }
21 },
22 "required": ["query"]
23 }),
24 }
25 }
26
27 async fn check_permission(
28 &self,
29 _arguments: &Value,
30 _context: &ToolContext,
31 ) -> Result<PermissionDecision, AgentError> {
32 Ok(PermissionDecision::Ask {
33 reason: "reading local browser bookmarks/history metadata requires approval".into(),
34 })
35 }
36
37 async fn invoke(
38 &self,
39 arguments: Value,
40 _context: ToolContext,
41 ) -> Result<ToolOutput, AgentError> {
42 let query = arguments
43 .get("query")
44 .and_then(Value::as_str)
45 .ok_or_else(|| AgentError::Validation("missing string `query`".into()))?;
46 let limit = optional_u32(&arguments, "limit").unwrap_or(10).min(50) as usize;
47 let mut results = Vec::new();
48 for path in candidate_bookmark_paths() {
49 if results.len() >= limit {
50 break;
51 }
52 let Ok(content) = tokio::fs::read_to_string(&path).await else {
53 continue;
54 };
55 collect_bookmark_matches(&content, query, limit, &mut results);
56 }
57 Ok(ToolOutput::json(json!({
58 "query": query,
59 "count": results.len(),
60 "results": results,
61 "note": "Only bookmark metadata is read in v1; browser history databases are intentionally not opened yet."
62 })))
63 }
64}