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.rsplit(|c| c == '\\' || c == '/').next().unwrap_or(unquoted);
128 basename.to_lowercase().trim_end_matches(".exe").to_string()
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_grep_success() {
138 let result = interpret_command_result("grep pattern file.txt", 0, "match\n", "");
139 assert!(!result.is_error);
140 }
141
142 #[test]
143 fn test_grep_no_match() {
144 let result = interpret_command_result("grep pattern file.txt", 1, "", "");
145 assert!(!result.is_error);
146 assert_eq!(result.message, Some("No matches found".to_string()));
147 }
148
149 #[test]
150 fn test_robocopy_success() {
151 let result = interpret_command_result("robocopy src dst", 1, "", "");
152 assert!(!result.is_error);
153 assert_eq!(result.message, Some("Files copied successfully".to_string()));
154 }
155
156 #[test]
157 fn test_robocopy_failure() {
158 let result = interpret_command_result("robocopy src dst", 16, "", "");
159 assert!(result.is_error);
160 }
161}