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_async().await {
55 return ToolOutcome::error(error, started.elapsed().as_secs_f64());
56 }
57 if let Some(blocked) = super::super::policy_gate::gate_external(
58 &ctx,
59 "mouse_move",
60 crate::runtime::ToolCategory::ComputerUse,
61 "computer-use: mouse_move".to_string(),
62 &args,
63 )
64 .await
65 {
66 return blocked;
67 }
68
69 let x = args.get("x").and_then(|v| v.as_i64()).map(|n| n as i32);
70 let y = args.get("y").and_then(|v| v.as_i64()).map(|n| n as i32);
71 let (x, y) = match (x, y) {
72 (Some(x), Some(y)) => (x, y),
73 _ => {
74 return ToolOutcome::error(
75 "mouse_move requires integer `x` and `y`",
76 started.elapsed().as_secs_f64(),
77 );
78 },
79 };
80 let screenshot_id = args.get("screenshot_id").and_then(|v| v.as_u64());
81 let (sx, sy) = match self.driver.scale_coords(x, y, screenshot_id) {
82 Ok(p) => p,
83 Err(e) => return ToolOutcome::error(e, started.elapsed().as_secs_f64()),
84 };
85
86 let res = tokio::select! {
87 biased;
88 _ = ctx.token.cancelled() => return ToolOutcome::cancelled(),
89 r = self.driver.mouse_move(sx, sy, &ctx.token) => r,
90 };
91 if let Err(e) = res {
92 return ToolOutcome::error(
93 format!("mouse_move failed: {}", e),
94 started.elapsed().as_secs_f64(),
95 );
96 }
97
98 computer_use_success(
99 "mouse_move",
100 args,
101 format!("Moved to ({}, {}) [screen: ({}, {})]", x, y, sx, sy),
102 started.elapsed().as_secs_f64(),
103 )
104 }
105}