use std::time::Duration;
use async_trait::async_trait;
use serde_json::Value;
use crate::core::tools::ToolError;
use crate::BaseTool;
use super::actions::{ComputerMode, ComputerUseInput, ComputerUseOutput};
pub struct ComputerUseTool {
pub(super) mode: ComputerMode,
pub(super) api_key: String,
pub(super) base_url: String,
pub(super) display_width: u32,
pub(super) display_height: u32,
pub(super) client: reqwest::Client,
}
impl ComputerUseTool {
pub fn new_anthropic(
api_key: impl Into<String>,
display_width: u32,
display_height: u32,
) -> Self {
Self {
mode: ComputerMode::AnthropicApi,
api_key: api_key.into(),
base_url: "https://api.anthropic.com".to_string(),
display_width,
display_height,
client: reqwest::Client::builder()
.timeout(Duration::from_secs(60))
.build()
.unwrap_or_else(|_| reqwest::Client::new()),
}
}
pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
self.base_url = url.into();
self
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.client = reqwest::Client::builder()
.timeout(timeout)
.build()
.unwrap_or_else(|_| reqwest::Client::new());
self
}
#[cfg(feature = "native-computer")]
pub fn new_native(display_width: u32, display_height: u32) -> Self {
Self {
mode: ComputerMode::Native,
api_key: String::new(),
base_url: String::new(),
display_width,
display_height,
client: reqwest::Client::new(),
}
}
pub fn mode(&self) -> &ComputerMode {
&self.mode
}
}
impl Default for ComputerUseTool {
fn default() -> Self {
Self::new_anthropic(String::new(), 1024, 768)
}
}
#[async_trait]
impl BaseTool for ComputerUseTool {
fn name(&self) -> &str {
"computer_use"
}
fn description(&self) -> &str {
"Computer use tool for screen interaction. \
Input JSON: {\"action\": \"screenshot|click|type|scroll|key_press|wait\", \
\"coordinate\": [x, y], \"text\": \"...\", \"keys\": [\"...\"], \
\"direction\": \"up|down|left|right\", \"amount\": N, \"duration_ms\": N}. \
- screenshot: capture the current screen. \
- click: click at (x, y). \
- type: type text string. \
- scroll: scroll at (x, y) in direction by amount. \
- key_press: press key combination. \
- wait: wait for duration_ms milliseconds."
}
async fn run(&self, input: String) -> Result<String, ToolError> {
let parsed: ComputerUseInput =
serde_json::from_str(&input).map_err(|e| ToolError::InvalidInput(e.to_string()))?;
let output = self.dispatch(&parsed).await?;
serde_json::to_string(&output).map_err(|e| ToolError::ExecutionFailed(e.to_string()))
}
fn args_schema(&self) -> Option<Value> {
use schemars::schema_for;
serde_json::to_value(schema_for!(ComputerUseInput)).ok()
}
}
impl ComputerUseTool {
pub(super) async fn dispatch(
&self,
input: &ComputerUseInput,
) -> Result<ComputerUseOutput, ToolError> {
match self.mode {
ComputerMode::AnthropicApi => self.execute_anthropic(input).await,
#[cfg(feature = "native-computer")]
ComputerMode::Native => self.execute_native(input).await,
}
}
}