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