Skip to main content

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

1use async_trait::async_trait;
2use serde_json::{Value, json};
3
4use crate::error::AgentError;
5use crate::tools::api::{Tool, ToolContext, ToolDefinition, ToolOutput};
6use crate::tools::browser::manager::BrowserManager;
7use crate::tools::browser::util::*;
8use crate::tools::display_relative;
9
10pub struct BrowserStartTool {
11    manager: BrowserManager,
12}
13
14impl BrowserStartTool {
15    pub fn new(manager: BrowserManager) -> Self {
16        Self { manager }
17    }
18}
19
20#[async_trait]
21impl Tool for BrowserStartTool {
22    fn definition(&self) -> ToolDefinition {
23        ToolDefinition {
24            name: "BrowserStart".into(),
25            description: "Start or reuse an isolated managed Chromium browser session.".into(),
26            input_schema: json!({
27                "type": "object",
28                "properties": {
29                    "browser_session_id": { "type": "string" },
30                    "headless": { "type": "boolean", "default": true },
31                    "width": { "type": "integer", "default": 1280 },
32                    "height": { "type": "integer", "default": 900 },
33                    "allowed_domains": { "type": "array", "items": { "type": "string" } },
34                    "prohibited_domains": { "type": "array", "items": { "type": "string" } },
35                    "no_sandbox": { "type": "boolean", "description": "Only set when Chromium cannot run in the current sandbox." }
36                }
37            }),
38        }
39    }
40
41    fn prompt_text(&self) -> Option<&'static str> {
42        Some(
43            "Use BrowserStart for dynamic page or full browser automation. Uses isolated profile. \
44Don't bypass CAPTCHA, bot checks, paywalls, or access controls.",
45        )
46    }
47
48    async fn invoke(
49        &self,
50        arguments: Value,
51        context: ToolContext,
52    ) -> Result<ToolOutput, AgentError> {
53        let (key, session, started) = self.manager.start_or_update(&arguments, &context).await?;
54        let session = session.lock().await;
55        Ok(ToolOutput::json(json!({
56            "browser_session_id": key,
57            "started": started,
58            "port": session.port,
59            "artifact_dir": display_relative(&context.cwd, &session.artifact_dir),
60            "headless": optional_bool(&arguments, "headless").unwrap_or(true),
61            "viewport": { "width": session.viewport.width, "height": session.viewport.height },
62            "allowed_domains": session.allowed_domains,
63            "prohibited_domains": session.prohibited_domains
64        })))
65    }
66}
67pub struct BrowserCloseTool {
68    manager: BrowserManager,
69}
70
71impl BrowserCloseTool {
72    pub fn new(manager: BrowserManager) -> Self {
73        Self { manager }
74    }
75}
76
77#[async_trait]
78impl Tool for BrowserCloseTool {
79    fn definition(&self) -> ToolDefinition {
80        ToolDefinition {
81            name: "BrowserClose".into(),
82            description: "Close a managed browser session.".into(),
83            input_schema: json!({
84                "type": "object",
85                "properties": { "browser_session_id": { "type": "string" } }
86            }),
87        }
88    }
89
90    async fn invoke(
91        &self,
92        arguments: Value,
93        context: ToolContext,
94    ) -> Result<ToolOutput, AgentError> {
95        let key = browser_session_key(&arguments, &context);
96        let Some(session) = self.manager.remove(&key).await else {
97            return Ok(ToolOutput::json(json!({
98                "browser_session_id": key,
99                "closed": false,
100                "reason": "session not found"
101            })));
102        };
103        let mut session = session.lock().await;
104        Ok(ToolOutput::json(session.close().await?))
105    }
106}