Skip to main content

stynx_code_tools/infrastructure/
powershell_tool.rs

1use stynx_code_errors::AppResult;
2use stynx_code_types::{InterruptBehavior, PermissionLevel, Tool};
3use serde_json::{Value, json};
4use tokio::process::Command;
5
6pub struct PowerShellTool;
7
8impl PowerShellTool {
9    pub fn new() -> Self {
10        Self
11    }
12}
13
14#[async_trait::async_trait]
15impl Tool for PowerShellTool {
16    fn name(&self) -> &str {
17        "powershell"
18    }
19
20    fn description(&self) -> &str {
21        "Execute a PowerShell command and return its output."
22    }
23
24    fn input_schema(&self) -> Value {
25        json!({
26            "type": "object",
27            "properties": {
28                "command": {
29                    "type": "string",
30                    "description": "The PowerShell command to execute"
31                },
32                "timeout": {
33                    "type": "integer",
34                    "description": "Optional timeout in seconds"
35                }
36            },
37            "required": ["command"]
38        })
39    }
40
41    fn permission_level(&self) -> PermissionLevel {
42        PermissionLevel::Dangerous
43    }
44
45    fn interrupt_behavior(&self) -> InterruptBehavior {
46        InterruptBehavior::Cancel
47    }
48
49    async fn execute(&self, input: Value) -> AppResult<String> {
50        let command = input
51            .get("command")
52            .and_then(|v| v.as_str())
53            .ok_or_else(|| stynx_code_errors::AppError::Tool("missing 'command' field".into()))?;
54
55        let _timeout = input
56            .get("timeout")
57            .and_then(|v| v.as_u64());
58
59        tracing::info!(command, "executing PowerShell");
60
61        let program = if cfg!(target_os = "windows") {
62            "powershell.exe"
63        } else {
64            "pwsh"
65        };
66
67        let output = Command::new(program)
68            .arg("-Command")
69            .arg(command)
70            .output()
71            .await
72            .map_err(|e| stynx_code_errors::AppError::Tool(
73                format!("failed to spawn {program}: {e}")
74            ))?;
75
76        let stdout = String::from_utf8_lossy(&output.stdout);
77        let stderr = String::from_utf8_lossy(&output.stderr);
78
79        let mut result = String::new();
80        if !stdout.is_empty() {
81            result.push_str(&stdout);
82        }
83        if !stderr.is_empty() {
84            if !result.is_empty() {
85                result.push('\n');
86            }
87            result.push_str("STDERR:\n");
88            result.push_str(&stderr);
89        }
90        if result.is_empty() {
91            result.push_str("(no output)");
92        }
93
94        if result.len() > 100_000 {
95            result.truncate(100_000);
96            result.push_str("\n... (truncated)");
97        }
98
99        Ok(result)
100    }
101}