Skip to main content

agent_engine/tools/
powershell.rs

1//! `powershell` tool — Windows-native shell execution.
2//!
3//! Registered only on Windows. Wraps the shared `run_shell_command` core with
4//! the PowerShell invocation (`-NoProfile -Command`). This gives the model a
5//! native shell on Windows hosts: no quoting translation through bash, no
6//! WSL/Git-Bash path impedance, native cmdlets (`Get-Process`, `Get-ChildItem`),
7//! and direct access to Windows paths.
8
9use super::{bash::ShellSpec, Tool, ToolContext};
10use crate::{Result, RuntimeError};
11use serde_json::{json, Value};
12
13pub struct PowerShellTool;
14
15#[async_trait::async_trait]
16impl Tool for PowerShellTool {
17    fn origin(&self) -> crate::tools::ToolOrigin {
18        crate::tools::ToolOrigin::Builtin
19    }
20
21    /// Same effect class as bash: arbitrary shell side effects.
22    fn effect(&self) -> crate::tools::catalog::ToolEffect {
23        crate::tools::catalog::ToolEffect::NonIdempotent
24    }
25
26    fn name(&self) -> &str {
27        "powershell"
28    }
29
30    fn description(&self) -> &str {
31        "Execute a PowerShell command on the Windows host. Use this for Windows-native tasks: \
32         file system operations on C:\\ paths, registry, services, processes, Windows APIs. \
33         PowerShell quoting rules apply — single quotes are literal, double quotes interpolate \
34         ($var, `n). Native cmdlets like Get-Process, Get-ChildItem, Test-Path are available. \
35         Commands time out after 30 seconds by default; pass a larger timeout when needed."
36    }
37
38    fn parameters(&self) -> Value {
39        json!({
40            "type": "object",
41            "properties": {
42                "command": {
43                    "type": "string",
44                    "description": "The PowerShell command to execute"
45                },
46                "timeout": {
47                    "type": "integer",
48                    "description": "Timeout in seconds (default: 30). Use a larger value for long-running commands."
49                }
50            },
51            "required": ["command"]
52        })
53    }
54
55    async fn execute(&self, params: Value, ctx: ToolContext) -> Result<String> {
56        let command = params["command"]
57            .as_str()
58            .ok_or_else(|| RuntimeError::Tool("Missing command parameter".to_string()))?;
59
60        super::bash::run_shell_command(
61            command,
62            ShellSpec::PowerShell,
63            params["timeout"].as_u64(),
64            ctx,
65        )
66        .await
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn powershell_tool_schema() {
76        let tool = PowerShellTool;
77        assert_eq!(tool.name(), "powershell");
78        let params = tool.parameters();
79        assert!(params["properties"]["command"].is_object());
80        assert!(params["required"]
81            .as_array()
82            .unwrap()
83            .contains(&json!("command")));
84    }
85
86    #[cfg(windows)]
87    #[tokio::test]
88    async fn powershell_executes_simple_command() {
89        let tool = PowerShellTool;
90        let ctx = super::super::test_helpers::create_tool_context();
91        let result = tool
92            .execute(json!({ "command": "Write-Output hello-ps" }), ctx)
93            .await
94            .unwrap();
95        assert!(result.contains("hello-ps"), "got: {result}");
96    }
97}