Skip to main content

codex_codes/
version.rs

1//! Version checking utilities for Codex CLI compatibility.
2
3use crate::error::Result;
4use log::{debug, warn};
5use std::process::Command;
6use std::sync::Once;
7
8/// The latest Codex CLI version we've tested against.
9const TESTED_VERSION: &str = "0.146.0";
10
11/// Ensures version warning is only shown once per session.
12static VERSION_CHECK: Once = Once::new();
13
14/// Check the Codex CLI version and warn if newer than tested.
15///
16/// This will only issue a warning once per program execution.
17pub 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 mut command = Command::new("codex");
28    command.arg("--version");
29    crate::process::configure_no_window(&mut command);
30    let output = command.output().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    // Format: "codex-cli X.Y.Z"
41    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
65/// Compare two version strings (e.g., "0.104.0" vs "0.103.0").
66fn 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/// Async version check for tokio-based clients.
84#[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 mut command = Command::new("codex");
106    command.arg("--version");
107    crate::process::configure_no_window(command.as_std_mut());
108    let output = command.output().await.map_err(crate::error::Error::Io)?;
109
110    if !output.status.success() {
111        debug!("Failed to check Codex CLI version - command failed");
112        return Ok(());
113    }
114
115    let version_str = String::from_utf8_lossy(&output.stdout);
116    let version_line = version_str.lines().next().unwrap_or("");
117
118    // Format: "codex-cli X.Y.Z"
119    if let Some(version) = version_line.split_whitespace().last() {
120        if is_version_newer(version, TESTED_VERSION) {
121            warn!(
122                "Codex CLI version {} is newer than tested version {}. \
123                 Please report compatibility at: https://github.com/meawoppl/rust-code-agent-sdks/issues",
124                version, TESTED_VERSION
125            );
126        } else {
127            debug!(
128                "Codex CLI version {} is compatible (tested: {})",
129                version, TESTED_VERSION
130            );
131        }
132    } else {
133        warn!(
134            "Could not parse Codex CLI version from output: '{}'. \
135             Please report compatibility at: https://github.com/meawoppl/rust-code-agent-sdks/issues",
136            version_line
137        );
138    }
139
140    Ok(())
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_version_comparison() {
149        assert!(is_version_newer("0.105.0", "0.104.0"));
150        assert!(!is_version_newer("0.104.0", "0.104.0"));
151        assert!(!is_version_newer("0.103.0", "0.104.0"));
152
153        assert!(is_version_newer("1.0.0", "0.104.0"));
154        assert!(!is_version_newer("0.0.1", "0.104.0"));
155        assert!(is_version_newer("0.104.1", "0.104.0"));
156    }
157}