mermaid_cli/providers/tool/computer_use/
mouse_move.rs1use std::sync::Arc;
5use std::time::Instant;
6
7use async_trait::async_trait;
8use serde_json::Value;
9
10use crate::domain::{ToolDefinition, ToolOutcome};
11use crate::providers::ctx::ExecContext;
12
13use super::super::ToolExecutor;
14use super::computer_use_success;
15use super::driver::ComputerUseDriver;
16
17pub struct MouseMoveTool {
18 driver: Arc<ComputerUseDriver>,
19}
20
21impl MouseMoveTool {
22 pub fn new(driver: Arc<ComputerUseDriver>) -> Self {
23 Self { driver }
24 }
25}
26
27#[async_trait]
28impl ToolExecutor for MouseMoveTool {
29 fn name(&self) -> &'static str {
30 "mouse_move"
31 }
32
33 fn schema(&self) -> ToolDefinition {
34 ToolDefinition {
35 name: "mouse_move".to_string(),
36 description: "Move the mouse cursor to model-space (x, y) without clicking. Same \
37 `screenshot_id` semantics as click: pass the id from the screenshot \
38 whose coordinates you're using, or omit to use the most recent."
39 .to_string(),
40 input_schema: serde_json::json!({
41 "type": "object",
42 "properties": {
43 "x": { "type": "integer" },
44 "y": { "type": "integer" },
45 "screenshot_id": { "type": "integer" }
46 },
47 "required": ["x", "y"]
48 }),
49 }
50 }
51
52 async fn execute(&self, args: Value, ctx: ExecContext) -> ToolOutcome {
53 let started = Instant::now();
54 if let Err(error) = self.driver.ensure_alive() {
55 return ToolOutcome::error(error, started.elapsed().as_secs_f64());
56 }
57
58 let x = args.get("x").and_then(|v| v.as_i64()).map(|n| n as i32);
59 let y = args.get("y").and_then(|v| v.as_i64()).map(|n| n as i32);
60 let (x, y) = match (x, y) {
61 (Some(x), Some(y)) => (x, y),
62 _ => {
63 return ToolOutcome::error(
64 "mouse_move requires integer `x` and `y`",
65 started.elapsed().as_secs_f64(),
66 );
67 },
68 };
69 let screenshot_id = args.get("screenshot_id").and_then(|v| v.as_u64());
70 let (sx, sy) = match self.driver.scale_coords(x, y, screenshot_id) {
71 Ok(p) => p,
72 Err(e) => return ToolOutcome::error(e, started.elapsed().as_secs_f64()),
73 };
74
75 let res = tokio::select! {
76 biased;
77 _ = ctx.token.cancelled() => return ToolOutcome::cancelled(),
78 r = self.driver.mouse_move(sx, sy, &ctx.token) => r,
79 };
80 if let Err(e) = res {
81 return ToolOutcome::error(
82 format!("mouse_move failed: {}", e),
83 started.elapsed().as_secs_f64(),
84 );
85 }
86
87 computer_use_success(
88 "mouse_move",
89 args,
90 format!("Moved to ({}, {}) [screen: ({}, {})]", x, y, sx, sy),
91 started.elapsed().as_secs_f64(),
92 )
93 }
94}