claude_code_agent_sdk/
version.rs1use std::sync::OnceLock;
4
5pub const SDK_VERSION: &str = env!("CARGO_PKG_VERSION");
7
8pub const MIN_CLI_VERSION: &str = "2.0.0";
10
11pub const CLI_VERSION: &str = "2.1.38";
13
14pub(crate) const BUNDLED_CLI_DIR: &str = ".claude/sdk/bundled";
16
17pub fn bundled_cli_path() -> Option<std::path::PathBuf> {
22 dirs::home_dir().map(|home| {
23 let cli_name = if cfg!(target_os = "windows") {
24 "claude.exe"
25 } else {
26 "claude"
27 };
28 home.join(BUNDLED_CLI_DIR).join(CLI_VERSION).join(cli_name)
29 })
30}
31
32pub const SKIP_VERSION_CHECK_ENV: &str = "CLAUDE_AGENT_SDK_SKIP_VERSION_CHECK";
34
35pub const ENTRYPOINT: &str = "sdk-rs";
37
38pub fn parse_version(version: &str) -> Option<(u32, u32, u32)> {
40 let parts: Vec<&str> = version.trim_start_matches('v').split('.').collect();
41 if parts.len() < 3 {
42 return None;
43 }
44
45 let major = parts[0].parse().ok()?;
46 let minor = parts[1].parse().ok()?;
47 let patch = parts[2].parse().ok()?;
48
49 Some((major, minor, patch))
50}
51
52pub fn check_version(cli_version: &str) -> bool {
54 let Some((cli_maj, cli_min, cli_patch)) = parse_version(cli_version) else {
55 return false;
56 };
57
58 let Some((req_maj, req_min, req_patch)) = parse_version(MIN_CLI_VERSION) else {
59 return false;
60 };
61
62 if cli_maj > req_maj {
63 return true;
64 }
65 if cli_maj < req_maj {
66 return false;
67 }
68
69 if cli_min > req_min {
71 return true;
72 }
73 if cli_min < req_min {
74 return false;
75 }
76
77 cli_patch >= req_patch
79}
80
81static CLAUDE_CODE_VERSION: OnceLock<Option<String>> = OnceLock::new();
83
84pub fn get_claude_code_version() -> Option<&'static str> {
89 CLAUDE_CODE_VERSION
90 .get_or_init(|| {
91 std::process::Command::new("claude")
92 .arg("--version")
93 .output()
94 .ok()
95 .filter(|output| output.status.success())
96 .and_then(|output| {
97 let version_output = String::from_utf8_lossy(&output.stdout);
98 version_output
99 .lines()
100 .next()
101 .and_then(|line| line.split_whitespace().next())
102 .map(|v| v.trim().to_string())
103 })
104 })
105 .as_deref()
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_parse_version() {
114 assert_eq!(parse_version("1.2.3"), Some((1, 2, 3)));
115 assert_eq!(parse_version("v1.2.3"), Some((1, 2, 3)));
116 assert_eq!(parse_version("10.20.30"), Some((10, 20, 30)));
117 assert_eq!(parse_version("1.2"), None);
118 assert_eq!(parse_version("invalid"), None);
119 }
120
121 #[test]
122 fn test_check_version() {
123 assert!(check_version("2.0.0"));
124 assert!(check_version("2.0.1"));
125 assert!(check_version("2.1.0"));
126 assert!(check_version("3.0.0"));
127 assert!(!check_version("1.9.9"));
128 assert!(!check_version("1.99.99"));
129 }
130
131 #[test]
132 fn test_cli_version_format() {
133 assert!(
134 parse_version(CLI_VERSION).is_some(),
135 "CLI_VERSION must be a valid semver string"
136 );
137 }
138
139 #[test]
140 fn test_cli_version_meets_minimum() {
141 assert!(
142 check_version(CLI_VERSION),
143 "CLI_VERSION ({}) must meet MIN_CLI_VERSION ({})",
144 CLI_VERSION,
145 MIN_CLI_VERSION
146 );
147 }
148
149 #[test]
150 fn test_bundled_cli_path_format() {
151 if let Some(path) = bundled_cli_path() {
152 let path_str = path.to_string_lossy();
153 assert!(
154 path_str.contains(".claude/sdk/bundled"),
155 "bundled path must contain '.claude/sdk/bundled': {}",
156 path_str
157 );
158 assert!(
159 path_str.contains(CLI_VERSION),
160 "bundled path must contain CLI_VERSION ({}): {}",
161 CLI_VERSION,
162 path_str
163 );
164 }
165 }
166}