Skip to main content

lc_tools/extended/computer/
screen.rs

1//! Computer Use tool — struct, constructors, and [`BaseTool`] implementation.
2//!
3//! Supports actions: screenshot, click, type, scroll, key_press, wait.
4//! The `AnthropicApi` mode constructs tool-call payloads compatible with
5//! Anthropic's computer-use beta.
6
7use std::time::Duration;
8
9use async_trait::async_trait;
10use serde_json::Value;
11
12use lc_core::tools::ToolError;
13use lc_core::BaseTool;
14
15use super::actions::{ComputerMode, ComputerUseInput, ComputerUseOutput};
16
17// ---------------------------------------------------------------------------
18// Tool struct
19// ---------------------------------------------------------------------------
20
21/// Computer Use tool that can be registered with `ToolRegistry` and used by
22/// any agent.
23pub struct ComputerUseTool {
24    pub(super) mode: ComputerMode,
25    pub(super) api_key: String,
26    pub(super) base_url: String,
27    pub(super) display_width: u32,
28    pub(super) display_height: u32,
29    pub(super) client: reqwest::Client,
30}
31
32// ---------------------------------------------------------------------------
33// Constructors
34// ---------------------------------------------------------------------------
35
36impl ComputerUseTool {
37    /// Create a new tool in `AnthropicApi` mode.
38    pub fn new_anthropic(
39        api_key: impl Into<String>,
40        display_width: u32,
41        display_height: u32,
42    ) -> Self {
43        Self {
44            mode: ComputerMode::AnthropicApi,
45            api_key: api_key.into(),
46            base_url: "https://api.anthropic.com".to_string(),
47            display_width,
48            display_height,
49            client: reqwest::Client::builder()
50                .timeout(Duration::from_secs(60))
51                .build()
52                .unwrap_or_else(|_| reqwest::Client::new()),
53        }
54    }
55
56    /// Create with a custom Anthropic-compatible base URL.
57    pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
58        self.base_url = url.into();
59        self
60    }
61
62    /// Create with a custom HTTP timeout.
63    pub fn with_timeout(mut self, timeout: Duration) -> Self {
64        self.client = reqwest::Client::builder()
65            .timeout(timeout)
66            .build()
67            .unwrap_or_else(|_| reqwest::Client::new());
68        self
69    }
70
71    /// Return the current mode.
72    pub fn mode(&self) -> &ComputerMode {
73        &self.mode
74    }
75}
76
77impl Default for ComputerUseTool {
78    fn default() -> Self {
79        Self::new_anthropic(String::new(), 1024, 768)
80    }
81}
82
83// ---------------------------------------------------------------------------
84// BaseTool implementation
85// ---------------------------------------------------------------------------
86
87#[async_trait]
88impl BaseTool for ComputerUseTool {
89    fn name(&self) -> &str {
90        "computer_use"
91    }
92
93    fn description(&self) -> &str {
94        "Computer use tool for screen interaction. \
95         Input JSON: {\"action\": \"screenshot|click|type|scroll|key_press|wait\", \
96         \"coordinate\": [x, y], \"text\": \"...\", \"keys\": [\"...\"], \
97         \"direction\": \"up|down|left|right\", \"amount\": N, \"duration_ms\": N}. \
98         - screenshot: capture the current screen. \
99         - click: click at (x, y). \
100         - type: type text string. \
101         - scroll: scroll at (x, y) in direction by amount. \
102         - key_press: press key combination. \
103         - wait: wait for duration_ms milliseconds."
104    }
105
106    async fn run(&self, input: String) -> Result<String, ToolError> {
107        let parsed: ComputerUseInput =
108            serde_json::from_str(&input).map_err(|e| ToolError::InvalidInput(e.to_string()))?;
109
110        let output = self.dispatch(&parsed).await?;
111
112        serde_json::to_string(&output).map_err(|e| ToolError::ExecutionFailed(e.to_string()))
113    }
114
115    fn args_schema(&self) -> Option<Value> {
116        use schemars::schema_for;
117        serde_json::to_value(schema_for!(ComputerUseInput)).ok()
118    }
119}
120
121// ---------------------------------------------------------------------------
122// Dispatch
123// ---------------------------------------------------------------------------
124
125impl ComputerUseTool {
126    /// Dispatch to the appropriate mode handler.
127    pub(super) async fn dispatch(
128        &self,
129        input: &ComputerUseInput,
130    ) -> Result<ComputerUseOutput, ToolError> {
131        match self.mode {
132            ComputerMode::AnthropicApi => self.execute_anthropic(input).await,
133        }
134    }
135}