mixtape_tools/process/
kill_process.rs1use crate::prelude::*;
2use sysinfo::{Pid, Signal, System};
3
4#[derive(Debug, Deserialize, JsonSchema)]
6pub struct KillProcessInput {
7 pub pid: u32,
9}
10
11pub struct KillProcessTool;
13
14impl Tool for KillProcessTool {
15 type Input = KillProcessInput;
16
17 fn name(&self) -> &str {
18 "kill_process"
19 }
20
21 fn description(&self) -> &str {
22 "Terminate a running process by its PID. Use with caution as this forcefully kills the process."
23 }
24
25 async fn execute(&self, input: Self::Input) -> std::result::Result<ToolResult, ToolError> {
26 let mut sys = System::new();
27 let pid = Pid::from_u32(input.pid);
28
29 sys.refresh_processes(sysinfo::ProcessesToUpdate::All);
31
32 if let Some(process) = sys.process(pid) {
33 let name = process.name().to_string_lossy().to_string();
34
35 if process.kill_with(Signal::Term).is_some() {
37 Ok(format!(
38 "Successfully sent termination signal to process {} (PID: {})",
39 name, input.pid
40 )
41 .into())
42 } else {
43 Err(format!(
44 "Failed to terminate process {} (PID: {}). Permission denied or process already terminated.",
45 name, input.pid
46 ).into())
47 }
48 } else {
49 Err(format!("Process with PID {} not found", input.pid).into())
50 }
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[tokio::test]
59 async fn test_kill_nonexistent_process() {
60 let tool = KillProcessTool;
61 let input = KillProcessInput { pid: 99999999 };
62
63 let result = tool.execute(input).await;
64 assert!(result.is_err());
65 assert!(result.unwrap_err().to_string().contains("not found"));
66 }
67
68 #[tokio::test]
69 async fn test_kill_process_success() {
70 use crate::process::start_process::{StartProcessInput, StartProcessTool};
71
72 let start_tool = StartProcessTool;
74 let start_input = StartProcessInput {
75 command: "sleep 30".to_string(),
76 timeout_ms: Some(60000),
77 shell: None,
78 };
79
80 let start_result = start_tool.execute(start_input).await;
81 if start_result.is_err() {
82 return;
84 }
85
86 let start_output = start_result.unwrap().as_text();
87 if let Some(pid_line) = start_output.lines().find(|l| l.contains("PID:")) {
89 if let Some(pid_str) = pid_line.split(':').nth(1) {
90 if let Ok(pid) = pid_str.trim().parse::<u32>() {
91 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
93
94 let kill_tool = KillProcessTool;
96 let kill_input = KillProcessInput { pid };
97
98 let result = kill_tool.execute(kill_input).await;
99 if result.is_err() {
100 eprintln!("Kill failed: {:?}", result.as_ref().err());
101 return;
103 }
104 let output = result.unwrap().as_text();
105 assert!(output.contains("Successfully sent termination signal"));
106 return;
107 }
108 }
109 }
110 }
111
112 #[test]
115 fn test_tool_name() {
116 let tool = KillProcessTool;
117 assert_eq!(tool.name(), "kill_process");
118 }
119
120 #[test]
121 fn test_tool_description() {
122 let tool = KillProcessTool;
123 assert!(!tool.description().is_empty());
124 assert!(tool.description().contains("Terminate") || tool.description().contains("kill"));
125 }
126
127 #[test]
130 fn test_kill_process_input_debug() {
131 let input = KillProcessInput { pid: 12345 };
132 let debug_str = format!("{:?}", input);
133 assert!(debug_str.contains("12345"));
134 }
135}