ai_agent/utils/shell/
powershell_provider.rs1use super::shell_provider::{ShellError, ShellExecCommand};
4use super::shell_tool_utils::ShellType;
5use crate::constants::env::system;
6use std::collections::HashMap;
7
8pub fn build_powershell_args(cmd: &str) -> Vec<String> {
11 vec![
12 "-NoProfile".to_string(),
13 "-NonInteractive".to_string(),
14 "-Command".to_string(),
15 cmd.to_string(),
16 ]
17}
18
19fn encode_powershell_command(ps_command: &str) -> String {
22 let utf16: Vec<u8> = ps_command
24 .encode_utf16()
25 .flat_map(|c| c.to_le_bytes())
26 .collect();
27
28 base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &utf16)
30}
31
32pub struct PowerShellProvider {
34 shell_path: String,
35 current_sandbox_tmp_dir: Option<String>,
36}
37
38impl PowerShellProvider {
39 pub fn new(shell_path: &str) -> Self {
41 Self {
42 shell_path: shell_path.to_string(),
43 current_sandbox_tmp_dir: None,
44 }
45 }
46
47 pub fn get_type(&self) -> ShellType {
49 ShellType::PowerShell
50 }
51
52 pub fn get_shell_path(&self) -> &str {
54 &self.shell_path
55 }
56
57 pub fn is_detached(&self) -> bool {
59 false
60 }
61
62 pub async fn build_exec_command(
64 &mut self,
65 command: &str,
66 id: usize,
67 sandbox_tmp_dir: Option<&str>,
68 use_sandbox: bool,
69 ) -> Result<ShellExecCommand, ShellError> {
70 self.current_sandbox_tmp_dir = sandbox_tmp_dir.map(|s| s.to_string());
72
73 let cwd_file_path = if use_sandbox && sandbox_tmp_dir.is_some() {
74 format!("{}/claude-pwd-ps-{}", sandbox_tmp_dir.unwrap(), id)
75 } else {
76 let tmpdir = std::env::temp_dir();
77 tmpdir
78 .join(format!("claude-pwd-ps-{}", id))
79 .to_string_lossy()
80 .to_string()
81 };
82
83 let escaped_cwd_file_path = cwd_file_path.replace('\'', "''");
84
85 let cwd_tracking = format!(
88 "\n; $_ec = if ($null -ne $LASTEXITCODE) {{ $LASTEXITCODE }} elseif ($?) {{ 0 }} else {{ 1 }}\n; (Get-Location).Path | Out-File -FilePath '{}' -Encoding utf8 -NoNewline\n; exit $_ec",
89 escaped_cwd_file_path
90 );
91
92 let ps_command = format!("{}{}", command, cwd_tracking);
93
94 let command_string = if use_sandbox {
97 let encoded = encode_powershell_command(&ps_command);
99 format!(
100 "'{}' -NoProfile -NonInteractive -EncodedCommand {}",
101 self.shell_path.replace('\'', "'\\''"),
102 encoded
103 )
104 } else {
105 ps_command
106 };
107
108 Ok(ShellExecCommand {
109 command_string,
110 cwd_file_path,
111 })
112 }
113
114 pub fn get_spawn_args(&self, command_string: &str) -> Vec<String> {
116 build_powershell_args(command_string)
117 }
118
119 pub async fn get_environment_overrides(&self) -> HashMap<String, String> {
121 let mut env = HashMap::new();
122
123 if let Some(ref tmpdir) = self.current_sandbox_tmp_dir {
130 env.insert("TMPDIR".to_string(), tmpdir.clone());
132 env.insert("AI_TMPDIR".to_string(), tmpdir.clone());
133 }
134
135 env
136 }
137}
138
139impl Default for PowerShellProvider {
140 fn default() -> Self {
141 #[cfg(target_os = "windows")]
142 let shell = "powershell.exe".to_string();
143 #[cfg(not(target_os = "windows"))]
144 let shell = std::env::var(system::PATH)
145 .ok()
146 .and_then(|p| {
147 p.split(':')
148 .find(|p| std::path::Path::new(&format!("{}/pwsh", p)).exists())
149 .map(|s| s.to_string())
150 })
151 .unwrap_or_else(|| "pwsh".to_string());
152
153 Self::new(&shell)
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn test_build_powershell_args() {
163 let args = build_powershell_args("echo hello");
164 assert_eq!(args[0], "-NoProfile");
165 assert_eq!(args[1], "-NonInteractive");
166 assert_eq!(args[2], "-Command");
167 assert_eq!(args[3], "echo hello");
168 }
169
170 #[test]
171 fn test_encode_powershell_command() {
172 let encoded = encode_powershell_command("echo hello");
173 assert!(!encoded.is_empty());
175 }
176}