ai_agent/tools/powershell/
command_semantics.rs1use once_cell::sync::Lazy;
17use std::collections::HashMap;
18
19#[derive(Debug, Clone)]
21pub struct CommandResult {
22 pub is_error: bool,
23 pub message: Option<String>,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum CommandType {
29 Default,
30 Grep,
31 Robocopy,
32}
33
34fn get_command_type(base_command: &str) -> CommandType {
36 match base_command {
37 "grep" | "rg" | "findstr" => CommandType::Grep,
38 "robocopy" => CommandType::Robocopy,
39 _ => CommandType::Default,
40 }
41}
42
43pub fn interpret_command_result(
45 command: &str,
46 exit_code: i32,
47 stdout: &str,
48 stderr: &str,
49) -> CommandResult {
50 let base_command = extract_base_command(command);
51 let cmd_type = get_command_type(&base_command);
52
53 match cmd_type {
54 CommandType::Grep => {
55 if exit_code >= 2 {
56 CommandResult {
57 is_error: true,
58 message: Some(format!("Command failed with exit code {}", exit_code)),
59 }
60 } else if exit_code == 1 {
61 CommandResult {
62 is_error: false,
63 message: Some("No matches found".to_string()),
64 }
65 } else {
66 CommandResult {
67 is_error: false,
68 message: None,
69 }
70 }
71 }
72 CommandType::Robocopy => {
73 if exit_code >= 8 {
74 CommandResult {
75 is_error: true,
76 message: Some(format!("Robocopy failed with exit code {}", exit_code)),
77 }
78 } else if exit_code == 0 {
79 CommandResult {
80 is_error: false,
81 message: Some("No files copied (already in sync)".to_string()),
82 }
83 } else {
84 let message = if exit_code & 1 != 0 {
86 "Files copied successfully".to_string()
87 } else {
88 "Robocopy completed (no errors)".to_string()
89 };
90 CommandResult {
91 is_error: false,
92 message: Some(message),
93 }
94 }
95 }
96 CommandType::Default => {
97 if exit_code == 0 {
98 CommandResult {
99 is_error: false,
100 message: None,
101 }
102 } else {
103 CommandResult {
104 is_error: true,
105 message: Some(format!("Command failed with exit code {}", exit_code)),
106 }
107 }
108 }
109 }
110}
111
112fn extract_base_command(command: &str) -> String {
115 let segments: Vec<&str> = command
116 .split(|c| c == ';' || c == '|')
117 .filter(|s| !s.trim().is_empty())
118 .collect();
119 let last = segments.last().unwrap_or(&command);
120
121 let stripped = last.trim().trim_start_matches(&['&', '.'][..]).trim();
123 let first_token = stripped.split_whitespace().next().unwrap_or("");
124 let unquoted = first_token.trim_matches('"').trim_matches('\'');
126 let basename = unquoted
128 .rsplit(|c| c == '\\' || c == '/')
129 .next()
130 .unwrap_or(unquoted);
131 basename.to_lowercase().trim_end_matches(".exe").to_string()
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_grep_success() {
141 let result = interpret_command_result("grep pattern file.txt", 0, "match\n", "");
142 assert!(!result.is_error);
143 }
144
145 #[test]
146 fn test_grep_no_match() {
147 let result = interpret_command_result("grep pattern file.txt", 1, "", "");
148 assert!(!result.is_error);
149 assert_eq!(result.message, Some("No matches found".to_string()));
150 }
151
152 #[test]
153 fn test_robocopy_success() {
154 let result = interpret_command_result("robocopy src dst", 1, "", "");
155 assert!(!result.is_error);
156 assert_eq!(
157 result.message,
158 Some("Files copied successfully".to_string())
159 );
160 }
161
162 #[test]
163 fn test_robocopy_failure() {
164 let result = interpret_command_result("robocopy src dst", 16, "", "");
165 assert!(result.is_error);
166 }
167}