Skip to main content

virtuoso_cli/
version.rs

1use crate::client::bridge::VirtuosoClient;
2use crate::error::Result;
3
4/// Virtuoso IC version family — determines Maestro SKILL API signatures.
5///
6/// API 实测结果(2026-04-20, IC25.1 ISR4):
7/// - `maeGetSetup` 仍然返回 list `("setupName")`,`car()` 有效
8/// - `maeSetAnalysis` 仍然使用 positional `(setupName type)` 签名
9/// - `maeGetEnabledAnalysis` 仍然使用 positional `(setupName)` 签名
10///
11/// 目前 IC23/IC25 Maestro API 签名完全一致。
12/// 版本检测留作基础设施,等未来真正出现不兼容时再启用分支。
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum VirtuosoVersion {
15    /// IC23.1 / IC25.1 ISR4 — positional API(当前实测兼容)
16    IC23,
17    /// 未来的 IC25+ 变体 — 如果 Maestro API 签名真的发生变化
18    IC25,
19    /// 版本无法确定
20    Unknown,
21}
22
23impl VirtuosoVersion {
24    /// Returns true if this version uses the IC25+ Maestro API signatures.
25    /// 当前 IC25.1 ISR4 实测与 IC23 签名一致,返回 false。
26    /// 只有真正检测到 API 变化时才返回 true。
27    pub fn is_ic25(&self) -> bool {
28        // 当前 IC25.1 ISR4 签名与 IC23 一致,不做分支。
29        // 如果未来版本出现真正的不兼容,修改这里的判断逻辑。
30        false
31    }
32}
33
34/// Parse the major IC version from a version string like "IC23.1-64b.500" or "IC25.1 ISR1".
35pub fn parse_ic_version(version_str: &str) -> VirtuosoVersion {
36    // Look for "IC" followed by digits — IC618 = 618, IC23 = 23, IC25 = 25
37    let lower = version_str.to_lowercase();
38    if let Some(pos) = lower.find("ic") {
39        let digits: String = lower[pos + 2..]
40            .chars()
41            .take_while(|c| c.is_ascii_digit())
42            .collect();
43        if let Ok(major) = digits.parse::<u32>() {
44            if major >= 25 {
45                return VirtuosoVersion::IC25;
46            }
47            if major >= 23 {
48                return VirtuosoVersion::IC23;
49            }
50            // IC6.1.x etc. — treat as IC23 (pre-25 API)
51            return VirtuosoVersion::IC23;
52        }
53    }
54    VirtuosoVersion::Unknown
55}
56
57/// Detect Virtuoso version by querying the daemon.
58/// Tries getVersion(t) first (IC23/IC25), falls back to getVersionString() (if available).
59pub fn detect_version(client: &VirtuosoClient) -> Result<VirtuosoVersion> {
60    // getVersion(t) returns e.g. "sub-version  IC25.1-64b.ISR4.49 "
61    let result = client.execute_skill("getVersion(t)", None)?;
62    if result.ok() {
63        let version_str = result.output.trim().trim_matches('"');
64        if !version_str.is_empty() && version_str != "nil" {
65            return Ok(parse_ic_version(version_str));
66        }
67    }
68    // Fallback: some builds may have getVersionString()
69    let result2 = client.execute_skill("getVersionString()", None)?;
70    if result2.ok() {
71        let version_str = result2.output.trim().trim_matches('"');
72        if !version_str.is_empty() && version_str != "nil" {
73            return Ok(parse_ic_version(version_str));
74        }
75    }
76    Ok(VirtuosoVersion::Unknown)
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn ic23_1_parses_as_ic23() {
85        assert_eq!(parse_ic_version("IC23.1-64b.500"), VirtuosoVersion::IC23);
86    }
87
88    #[test]
89    fn ic25_1_parses_as_ic25() {
90        assert_eq!(parse_ic_version("IC25.1 ISR1"), VirtuosoVersion::IC25);
91    }
92
93    #[test]
94    fn ic618_parses_as_ic23() {
95        assert_eq!(parse_ic_version("IC6.1.8-64b.500"), VirtuosoVersion::IC23);
96    }
97
98    #[test]
99    fn ic24_parses_as_ic23() {
100        assert_eq!(parse_ic_version("IC24.1"), VirtuosoVersion::IC23);
101    }
102
103    #[test]
104    fn empty_string_is_unknown() {
105        assert_eq!(parse_ic_version(""), VirtuosoVersion::Unknown);
106    }
107
108    #[test]
109    fn garbage_is_unknown() {
110        assert_eq!(parse_ic_version("foo bar"), VirtuosoVersion::Unknown);
111    }
112}