1use crate::error::Result;
4use log::{debug, warn};
5use std::process::Command;
6use std::sync::Once;
7
8const TESTED_VERSION: &str = "0.104.0";
10
11static VERSION_CHECK: Once = Once::new();
13
14pub fn check_codex_version() -> Result<()> {
18 VERSION_CHECK.call_once(|| {
19 if let Err(e) = check_version_impl() {
20 debug!("Failed to check Codex CLI version: {}", e);
21 }
22 });
23 Ok(())
24}
25
26fn check_version_impl() -> Result<()> {
27 let output = Command::new("codex")
28 .arg("--version")
29 .output()
30 .map_err(crate::error::Error::Io)?;
31
32 if !output.status.success() {
33 debug!("Failed to check Codex CLI version - command failed");
34 return Ok(());
35 }
36
37 let version_str = String::from_utf8_lossy(&output.stdout);
38 let version_line = version_str.lines().next().unwrap_or("");
39
40 if let Some(version) = version_line.split_whitespace().last() {
42 if is_version_newer(version, TESTED_VERSION) {
43 warn!(
44 "Codex CLI version {} is newer than tested version {}. \
45 Please report compatibility at: https://github.com/meawoppl/rust-code-agent-sdks/issues",
46 version, TESTED_VERSION
47 );
48 } else {
49 debug!(
50 "Codex CLI version {} is compatible (tested: {})",
51 version, TESTED_VERSION
52 );
53 }
54 } else {
55 warn!(
56 "Could not parse Codex CLI version from output: '{}'. \
57 Please report compatibility at: https://github.com/meawoppl/rust-code-agent-sdks/issues",
58 version_line
59 );
60 }
61
62 Ok(())
63}
64
65fn is_version_newer(version: &str, tested: &str) -> bool {
67 let v_parts: Vec<u32> = version.split('.').filter_map(|s| s.parse().ok()).collect();
68 let t_parts: Vec<u32> = tested.split('.').filter_map(|s| s.parse().ok()).collect();
69
70 use std::cmp::Ordering;
71
72 for i in 0..v_parts.len().min(t_parts.len()) {
73 match v_parts[i].cmp(&t_parts[i]) {
74 Ordering::Greater => return true,
75 Ordering::Less => return false,
76 Ordering::Equal => continue,
77 }
78 }
79
80 v_parts.len() > t_parts.len()
81}
82
83#[cfg(feature = "async-client")]
85pub async fn check_codex_version_async() -> Result<()> {
86 use tokio::sync::OnceCell;
87
88 static ASYNC_VERSION_CHECK: OnceCell<()> = OnceCell::const_new();
89
90 ASYNC_VERSION_CHECK
91 .get_or_init(|| async {
92 if let Err(e) = check_version_impl_async().await {
93 debug!("Failed to check Codex CLI version: {}", e);
94 }
95 })
96 .await;
97
98 Ok(())
99}
100
101#[cfg(feature = "async-client")]
102async fn check_version_impl_async() -> Result<()> {
103 use tokio::process::Command;
104
105 let output = Command::new("codex")
106 .arg("--version")
107 .output()
108 .await
109 .map_err(crate::error::Error::Io)?;
110
111 if !output.status.success() {
112 debug!("Failed to check Codex CLI version - command failed");
113 return Ok(());
114 }
115
116 let version_str = String::from_utf8_lossy(&output.stdout);
117 let version_line = version_str.lines().next().unwrap_or("");
118
119 if let Some(version) = version_line.split_whitespace().last() {
121 if is_version_newer(version, TESTED_VERSION) {
122 warn!(
123 "Codex CLI version {} is newer than tested version {}. \
124 Please report compatibility at: https://github.com/meawoppl/rust-code-agent-sdks/issues",
125 version, TESTED_VERSION
126 );
127 } else {
128 debug!(
129 "Codex CLI version {} is compatible (tested: {})",
130 version, TESTED_VERSION
131 );
132 }
133 } else {
134 warn!(
135 "Could not parse Codex CLI version from output: '{}'. \
136 Please report compatibility at: https://github.com/meawoppl/rust-code-agent-sdks/issues",
137 version_line
138 );
139 }
140
141 Ok(())
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_version_comparison() {
150 assert!(is_version_newer("0.105.0", "0.104.0"));
151 assert!(!is_version_newer("0.104.0", "0.104.0"));
152 assert!(!is_version_newer("0.103.0", "0.104.0"));
153
154 assert!(is_version_newer("1.0.0", "0.104.0"));
155 assert!(!is_version_newer("0.0.1", "0.104.0"));
156 assert!(is_version_newer("0.104.1", "0.104.0"));
157 }
158}