use crate::error::AgentError;
use crate::tools::powershell::prompt::get_default_timeout_ms;
use crate::tools::powershell::powershell_security::powershell_command_is_safe;
use crate::tools::powershell::tool_name::POWERSHELL_TOOL_NAME;
use crate::types::*;
pub struct PowerShellTool;
impl PowerShellTool {
pub fn new() -> Self {
Self
}
pub fn name(&self) -> &str {
POWERSHELL_TOOL_NAME
}
pub fn description(&self) -> String {
"Execute a PowerShell command. Windows-only tool for PowerShell cmdlets and native executable execution"
.to_string()
}
pub fn user_facing_name(&self, _input: Option<&serde_json::Value>) -> String {
"PowerShell".to_string()
}
pub fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
input.and_then(|inp| inp["command"].as_str().map(String::from))
}
pub fn render_tool_result_message(
&self,
content: &serde_json::Value,
) -> Option<String> {
let content_str = content["content"].as_str()?;
if content_str.is_empty() {
Some("No output".to_string())
} else {
let line_count = content_str.lines().count();
Some(format!(
"{} {}",
line_count,
if line_count == 1 { "line" } else { "lines" }
))
}
}
pub fn input_schema(&self) -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"command": {
"type": "string",
"description": "PowerShell command to execute"
},
"timeout": {
"type": "number",
"description": "Optional timeout in milliseconds (default: 120000, max: 600000)"
},
"description": {
"type": "string",
"description": "Brief description of what this command does"
},
"run_in_background": {
"type": "boolean",
"description": "Run the command in the background (default: false)"
}
}),
required: Some(vec!["command".to_string()]),
}
}
pub async fn execute(
&self,
input: serde_json::Value,
context: &ToolContext,
) -> Result<ToolResult, AgentError> {
let command = input["command"]
.as_str()
.ok_or_else(|| AgentError::Tool("command is required".to_string()))?
.to_string();
let security = powershell_command_is_safe(&command);
if security.behavior
== crate::tools::powershell::powershell_security::SecurityBehavior::Ask
{
let _warning = security.message;
}
let cwd = context.cwd.clone();
let output = tokio::task::spawn_blocking(move || {
let mut cmd = if let Ok(output) = std::process::Command::new("pwsh")
.arg("-c")
.arg("--version")
.output()
{
if output.status.success() {
let mut c = std::process::Command::new("pwsh");
c.arg("-NoProfile").arg("-Command").arg(&command);
c
} else {
let mut c = std::process::Command::new("powershell");
c.arg("-NoProfile")
.arg("-NonInteractive")
.arg("-Command")
.arg(&command);
c
}
} else {
let mut c = std::process::Command::new("powershell");
c.arg("-NoProfile")
.arg("-NonInteractive")
.arg("-Command")
.arg(&command);
c
};
if !cwd.is_empty() {
cmd.current_dir(&cwd);
}
cmd.output()
})
.await
.map_err(|e| AgentError::Tool(e.to_string()))?
.map_err(|e| {
AgentError::Tool(format!(
"Failed to execute PowerShell command: {}. Make sure PowerShell is installed.",
e
))
})?;
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let content = if !stdout.trim().is_empty() {
stdout.to_string()
} else if !stderr.trim().is_empty() {
stderr.to_string()
} else {
"".to_string()
};
let is_error = !output.status.success();
Ok(ToolResult {
result_type: "tool_result".to_string(),
tool_use_id: "".to_string(),
content,
is_error: Some(is_error),
was_persisted: None,
})
}
}
impl Default for PowerShellTool {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_powershell_tool_name() {
let tool = PowerShellTool::new();
assert_eq!(tool.name(), POWERSHELL_TOOL_NAME);
}
#[test]
fn test_powershell_tool_schema() {
let tool = PowerShellTool::new();
let schema = tool.input_schema();
assert_eq!(schema.schema_type, "object");
assert!(schema.properties["command"].is_object());
assert!(schema.required.is_some());
}
#[test]
fn test_powershell_tool_user_facing_name() {
let tool = PowerShellTool::new();
assert_eq!(tool.user_facing_name(None), "PowerShell");
}
#[test]
fn test_powershell_tool_summary() {
let tool = PowerShellTool::new();
let input = serde_json::json!({"command": "Get-ChildItem"});
let summary = tool.get_tool_use_summary(Some(&input));
assert_eq!(summary, Some("Get-ChildItem".to_string()));
}
#[test]
fn test_powershell_tool_description_not_empty() {
let tool = PowerShellTool::new();
assert!(!tool.description().is_empty());
}
}