Skip to main content

mermaid_cli/providers/tool/computer_use/
mouse_move.rs

1//! `mouse_move` — move the cursor without clicking. Same
2//! `screenshot_id` coord-translation semantics as `click`.
3
4use 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}