Skip to main content

telos_agent/tools/builtin/browser/tool_impls/
input.rs

1use 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::manager::BrowserManager;
7use crate::tools::browser::tool_impls::require_session;
8use crate::tools::browser::util::*;
9
10pub struct BrowserClickTool {
11    manager: BrowserManager,
12}
13
14impl BrowserClickTool {
15    pub fn new(manager: BrowserManager) -> Self {
16        Self { manager }
17    }
18}
19
20#[async_trait]
21impl Tool for BrowserClickTool {
22    fn definition(&self) -> ToolDefinition {
23        ToolDefinition {
24            name: "BrowserClick".into(),
25            description:
26                "Click a browser element. Prefer element_id from BrowserState. The selector field is CSS, with text=... and xpath=... accepted as locator shorthands."
27                    .into(),
28            input_schema: selector_schema(json!({})),
29        }
30    }
31
32    async fn check_permission(
33        &self,
34        arguments: &Value,
35        _context: &ToolContext,
36    ) -> Result<PermissionDecision, AgentError> {
37        sensitive_action_permission("browser click", arguments)
38    }
39
40    async fn invoke(
41        &self,
42        arguments: Value,
43        context: ToolContext,
44    ) -> Result<ToolOutput, AgentError> {
45        let key = browser_session_key(&arguments, &context);
46        let session = require_session(&self.manager, &key, "BrowserClick").await?;
47        let mut session = session.lock().await;
48        Ok(ToolOutput::json(session.click(&arguments, &context).await?))
49    }
50}
51
52pub struct BrowserTypeTool {
53    manager: BrowserManager,
54}
55
56impl BrowserTypeTool {
57    pub fn new(manager: BrowserManager) -> Self {
58        Self { manager }
59    }
60}
61
62#[async_trait]
63impl Tool for BrowserTypeTool {
64    fn definition(&self) -> ToolDefinition {
65        ToolDefinition {
66            name: "BrowserType".into(),
67            description:
68                "Type text into a browser input, textarea, or contenteditable element. Prefer element_id from BrowserState. The selector field is CSS, with text=... and xpath=... accepted as locator shorthands."
69                    .into(),
70            input_schema: selector_schema(json!({
71                "text": { "type": "string" },
72                "clear": { "type": "boolean", "default": true }
73            })),
74        }
75    }
76
77    async fn validate(&self, arguments: &Value, _context: &ToolContext) -> Result<(), AgentError> {
78        if arguments.get("text").and_then(Value::as_str).is_none() {
79            return Err(AgentError::Validation("missing string `text`".into()));
80        }
81        Ok(())
82    }
83
84    async fn check_permission(
85        &self,
86        arguments: &Value,
87        _context: &ToolContext,
88    ) -> Result<PermissionDecision, AgentError> {
89        sensitive_action_permission("browser typing", arguments)
90    }
91
92    async fn invoke(
93        &self,
94        arguments: Value,
95        context: ToolContext,
96    ) -> Result<ToolOutput, AgentError> {
97        let key = browser_session_key(&arguments, &context);
98        let session = require_session(&self.manager, &key, "BrowserType").await?;
99        let mut session = session.lock().await;
100        Ok(ToolOutput::json(session.type_text(&arguments, &context).await?))
101    }
102}
103
104pub struct BrowserSelectTool {
105    manager: BrowserManager,
106}
107
108impl BrowserSelectTool {
109    pub fn new(manager: BrowserManager) -> Self {
110        Self { manager }
111    }
112}
113
114#[async_trait]
115impl Tool for BrowserSelectTool {
116    fn definition(&self) -> ToolDefinition {
117        ToolDefinition {
118            name: "BrowserSelect".into(),
119            description:
120                "Select a value in a browser select element. Prefer element_id from BrowserState. The selector field is CSS, with text=... and xpath=... accepted as locator shorthands."
121                    .into(),
122            input_schema: selector_schema(json!({
123                "value": { "type": "string" }
124            })),
125        }
126    }
127
128    async fn validate(&self, arguments: &Value, _context: &ToolContext) -> Result<(), AgentError> {
129        if arguments.get("value").and_then(Value::as_str).is_none() {
130            return Err(AgentError::Validation("missing string `value`".into()));
131        }
132        Ok(())
133    }
134
135    async fn check_permission(
136        &self,
137        arguments: &Value,
138        _context: &ToolContext,
139    ) -> Result<PermissionDecision, AgentError> {
140        sensitive_action_permission("browser select", arguments)
141    }
142
143    async fn invoke(
144        &self,
145        arguments: Value,
146        context: ToolContext,
147    ) -> Result<ToolOutput, AgentError> {
148        let key = browser_session_key(&arguments, &context);
149        let session = require_session(&self.manager, &key, "BrowserSelect").await?;
150        let mut session = session.lock().await;
151        Ok(ToolOutput::json(session.select(&arguments, &context).await?))
152    }
153}