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