1use crate::env::VERSION;
8use rustc_version_runtime::version;
9use std::borrow::Cow;
10use std::collections::HashMap;
11use std::io::{self, Write};
12
13#[derive(Debug, Clone)]
15pub struct PackageInfo {
16 pub name: Cow<'static, str>,
18 pub version: Option<&'static str>,
20}
21
22impl PackageInfo {
23 pub fn new_static(name: &'static str, version: Option<&'static str>) -> Self {
25 Self {
26 name: Cow::Borrowed(name),
27 version,
28 }
29 }
30
31 pub fn new_owned(name: String, version: Option<&'static str>) -> Self {
33 Self {
34 name: Cow::Owned(name),
35 version,
36 }
37 }
38}
39
40#[derive(Debug, Clone)]
42pub struct SystemInfo {
43 pub os: &'static str,
45 pub os_family: &'static str,
47 pub arch: &'static str,
49 pub rust_version: String,
51}
52
53impl SystemInfo {
54 pub fn current() -> Self {
56 Self {
57 os: std::env::consts::OS,
58 os_family: std::env::consts::FAMILY,
59 arch: std::env::consts::ARCH,
60 rust_version: version().to_string(),
61 }
62 }
63}
64
65fn get_sub_deps(packages: &[Cow<'static, str>]) -> Vec<PackageInfo> {
70 let all_deps = [
72 ("async-trait", option_env!("DEP_ASYNC_TRAIT_VERSION")),
73 ("futures", option_env!("DEP_FUTURES_VERSION")),
74 ("reqwest", option_env!("DEP_REQWEST_VERSION")),
75 ("serde", option_env!("DEP_SERDE_VERSION")),
76 ("serde_json", option_env!("DEP_SERDE_JSON_VERSION")),
77 ("tokio", option_env!("DEP_TOKIO_VERSION")),
78 ("tracing", option_env!("DEP_TRACING_VERSION")),
79 ];
80
81 let package_set: std::collections::HashSet<&str> =
82 packages.iter().map(|s| s.as_ref()).collect();
83
84 all_deps
85 .iter()
86 .filter(|(name, _)| !package_set.contains(name))
87 .map(|(name, version)| PackageInfo::new_static(name, *version))
88 .collect()
89}
90
91pub fn get_package_info() -> Vec<PackageInfo> {
95 let mut packages = vec![
97 PackageInfo::new_static("agent-chain-core", Some(VERSION)),
98 PackageInfo::new_static("agent-chain", option_env!("DEP_AGENT_CHAIN_VERSION")),
99 PackageInfo::new_static("agent-graph", option_env!("DEP_AGENT_GRAPH_VERSION")),
100 PackageInfo::new_static(
101 "agent-chain-macros",
102 option_env!("DEP_AGENT_CHAIN_MACROS_VERSION"),
103 ),
104 PackageInfo::new_static(
105 "agent-graph-macros",
106 option_env!("DEP_AGENT_GRAPH_MACROS_VERSION"),
107 ),
108 ];
109
110 if packages.len() > 1 {
112 packages[1..].sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
113 }
114
115 packages
116}
117
118pub fn get_sys_info(additional_pkgs: &[&str]) -> (SystemInfo, Vec<PackageInfo>, Vec<PackageInfo>) {
122 let system_info = SystemInfo::current();
123 let mut packages = get_package_info();
124
125 for pkg in additional_pkgs {
127 packages.push(PackageInfo::new_owned((*pkg).to_string(), None));
128 }
129
130 let package_names: Vec<Cow<'static, str>> = packages.iter().map(|p| p.name.clone()).collect();
131 let sub_deps = get_sub_deps(&package_names);
132
133 (system_info, packages, sub_deps)
134}
135
136pub fn print_sys_info(additional_pkgs: &[&str]) {
153 let mut stdout = io::stdout();
154 print_sys_info_to(&mut stdout, additional_pkgs);
155}
156
157pub fn print_sys_info_to<W: Write>(writer: &mut W, additional_pkgs: &[&str]) {
161 let (system_info, packages, sub_deps) = get_sys_info(additional_pkgs);
162
163 writeln!(writer).ok();
165 writeln!(writer, "System Information").ok();
166 writeln!(writer, "------------------").ok();
167 writeln!(writer, "> OS: {}", system_info.os).ok();
168 writeln!(writer, "> OS Family: {}", system_info.os_family).ok();
169 writeln!(writer, "> Architecture: {}", system_info.arch).ok();
170 writeln!(writer, "> Rust Version: {}", system_info.rust_version).ok();
171
172 writeln!(writer).ok();
174 writeln!(writer, "Package Information").ok();
175 writeln!(writer, "-------------------").ok();
176
177 let mut not_installed: Vec<&str> = Vec::new();
178
179 for pkg in &packages {
180 match pkg.version {
181 Some(version) => {
182 writeln!(writer, "> {}: {}", pkg.name, version).ok();
183 }
184 None => {
185 not_installed.push(pkg.name.as_ref());
186 }
187 }
188 }
189
190 if !not_installed.is_empty() {
192 writeln!(writer).ok();
193 writeln!(writer, "Optional packages not installed").ok();
194 writeln!(writer, "-------------------------------").ok();
195 for pkg in not_installed {
196 writeln!(writer, "> {}", pkg).ok();
197 }
198 }
199
200 let deps_with_version: Vec<_> = sub_deps.iter().filter(|d| d.version.is_some()).collect();
202
203 if !deps_with_version.is_empty() {
204 writeln!(writer).ok();
205 writeln!(writer, "Other Dependencies").ok();
206 writeln!(writer, "------------------").ok();
207
208 for dep in deps_with_version {
209 if let Some(version) = dep.version {
210 writeln!(writer, "> {}: {}", dep.name, version).ok();
211 }
212 }
213 }
214}
215
216pub fn get_sys_info_map() -> HashMap<String, String> {
218 let system_info = SystemInfo::current();
219 let packages = get_package_info();
220
221 let mut map = HashMap::new();
222 map.insert("os".to_string(), system_info.os.to_string());
223 map.insert("os_family".to_string(), system_info.os_family.to_string());
224 map.insert("arch".to_string(), system_info.arch.to_string());
225 map.insert(
226 "rust_version".to_string(),
227 system_info.rust_version.to_string(),
228 );
229
230 for pkg in packages {
231 if let Some(version) = pkg.version {
232 map.insert(
233 format!("{}_version", pkg.name.replace('-', "_")),
234 version.to_string(),
235 );
236 }
237 }
238
239 map
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn test_system_info_current() {
248 let info = SystemInfo::current();
249
250 assert!(!info.os.is_empty());
252 assert!(!info.os_family.is_empty());
253 assert!(!info.arch.is_empty());
254 assert!(!info.rust_version.is_empty());
255 }
256
257 #[test]
258 fn test_get_package_info() {
259 let packages = get_package_info();
260
261 assert!(!packages.is_empty());
263
264 assert_eq!(packages[0].name, "agent-chain-core");
266 assert!(packages[0].version.is_some());
267 }
268
269 #[test]
270 fn test_get_sys_info() {
271 let (system_info, packages, _sub_deps) = get_sys_info(&[]);
272
273 assert!(!system_info.os.is_empty());
274 assert!(!packages.is_empty());
275 }
276
277 #[test]
278 fn test_get_sys_info_with_additional_pkgs() {
279 let (_, packages, _) = get_sys_info(&["custom-package"]);
280
281 let has_custom = packages.iter().any(|p| p.name == "custom-package");
283 assert!(has_custom);
284 }
285
286 #[test]
287 fn test_print_sys_info_to_buffer() {
288 let mut buffer = Vec::new();
289 print_sys_info_to(&mut buffer, &[]);
290
291 let output = String::from_utf8(buffer).unwrap();
292
293 assert!(output.contains("System Information"));
295 assert!(output.contains("Package Information"));
296 assert!(output.contains("agent-chain-core"));
297 }
298
299 #[test]
300 fn test_get_sys_info_map() {
301 let map = get_sys_info_map();
302
303 assert!(map.contains_key("os"));
304 assert!(map.contains_key("rust_version"));
305 assert!(map.contains_key("agent_chain_core_version"));
306 }
307}