| use std::sync::LazyLock; |
| use regex::Regex; |
|
|
| |
| const VERSION_URL: &str = "https://antigravity-auto-updater-974169037036.us-central1.run.app"; |
|
|
| |
| const CHANGELOG_URL: &str = "https://antigravity.google/changelog"; |
|
|
|
|
|
|
| |
| |
| const KNOWN_STABLE_VERSION: &str = "4.1.32"; |
| const KNOWN_STABLE_ELECTRON: &str = "39.2.3"; |
| const KNOWN_STABLE_CHROME: &str = "132.0.6834.160"; |
|
|
| |
| static VERSION_REGEX: LazyLock<Regex> = LazyLock::new(|| { |
| Regex::new(r"\d+\.\d+\.\d+").expect("Invalid version regex") |
| }); |
|
|
| |
| |
| fn parse_version(text: &str) -> Option<String> { |
| VERSION_REGEX.find(text).map(|m| m.as_str().to_string()) |
| } |
|
|
| |
| |
| fn compare_semver(v1: &str, v2: &str) -> std::cmp::Ordering { |
| let parse = |v: &str| -> Vec<u32> { |
| v.split('.').filter_map(|s| s.parse().ok()).collect() |
| }; |
| let p1 = parse(v1); |
| let p2 = parse(v2); |
| for i in 0..p1.len().max(p2.len()) { |
| let a = p1.get(i).copied().unwrap_or(0); |
| let b = p2.get(i).copied().unwrap_or(0); |
| match a.cmp(&b) { |
| std::cmp::Ordering::Equal => continue, |
| other => return other, |
| } |
| } |
| std::cmp::Ordering::Equal |
| } |
|
|
| |
| #[derive(Debug, PartialEq)] |
| enum VersionSource { |
| LocalInstallation, |
| KnownStableFallback, |
| RemoteAPI, |
| #[allow(dead_code)] |
| ChangelogWeb, |
| #[allow(dead_code)] |
| CargoToml, |
| } |
|
|
| |
| struct VersionConfig { |
| version: String, |
| electron: String, |
| chrome: String, |
| } |
|
|
| |
| |
| |
| fn try_fetch_remote_version() -> Option<String> { |
| |
| |
| |
| let (tx, rx) = std::sync::mpsc::channel::<Option<String>>(); |
|
|
| std::thread::spawn(move || { |
| let result = (|| -> Option<String> { |
| let client = reqwest::blocking::Client::builder() |
| .timeout(std::time::Duration::from_secs(5)) |
| .build() |
| .ok()?; |
|
|
| |
| if let Ok(resp) = client.get(VERSION_URL).send() { |
| if let Ok(text) = resp.text() { |
| if let Some(ver) = parse_version(&text) { |
| tracing::debug!(remote_version = %ver, "Fetched remote version from VERSION_URL"); |
| return Some(ver); |
| } |
| } |
| } |
|
|
| |
| if let Ok(resp) = client.get(CHANGELOG_URL).send() { |
| if let Ok(text) = resp.text() { |
| if let Some(ver) = parse_version(&text) { |
| tracing::debug!(remote_version = %ver, "Fetched remote version from CHANGELOG_URL"); |
| return Some(ver); |
| } |
| } |
| } |
|
|
| tracing::debug!("Unable to fetch remote version; will rely on local/stable floor"); |
| None |
| })(); |
|
|
| let _ = tx.send(result); |
| }); |
|
|
| |
| rx.recv_timeout(std::time::Duration::from_secs(6)) |
| .unwrap_or(None) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| fn resolve_version_config() -> (VersionConfig, VersionSource) { |
| |
| let mut best_version = KNOWN_STABLE_VERSION.to_string(); |
| let mut source = VersionSource::KnownStableFallback; |
|
|
| |
| if let Ok(local_ver) = crate::modules::version::get_antigravity_version() { |
| let local_parsed = parse_version(&local_ver.short_version) |
| .or_else(|| parse_version(&local_ver.bundle_version)); |
|
|
| if let Some(local_v) = local_parsed { |
| if compare_semver(&local_v, &best_version) > std::cmp::Ordering::Equal { |
| |
| tracing::debug!( |
| local_version = %local_v, |
| "Local installation version is newer than known-stable floor; using local" |
| ); |
| best_version = local_v; |
| source = VersionSource::LocalInstallation; |
| } else { |
| |
| tracing::info!( |
| local_version = %local_v, |
| floor_version = %best_version, |
| "Local Antigravity version is older than known-stable floor; \ |
| using floor to avoid upstream model rejection" |
| ); |
| |
| } |
| } |
| } |
|
|
| |
| if let Some(remote_v) = try_fetch_remote_version() { |
| if compare_semver(&remote_v, &best_version) > std::cmp::Ordering::Equal { |
| tracing::info!( |
| remote_version = %remote_v, |
| previous_best = %best_version, |
| "Remote version is newer than current best; upgrading fingerprint version" |
| ); |
| best_version = remote_v; |
| source = VersionSource::RemoteAPI; |
| } |
| } |
|
|
| ( |
| VersionConfig { |
| version: best_version, |
| electron: KNOWN_STABLE_ELECTRON.to_string(), |
| chrome: KNOWN_STABLE_CHROME.to_string(), |
| }, |
| source, |
| ) |
| } |
|
|
| |
| |
| pub static CURRENT_VERSION: LazyLock<String> = LazyLock::new(|| { |
| let (config, _) = resolve_version_config(); |
| config.version |
| }); |
|
|
| |
| pub static NATIVE_OAUTH_USER_AGENT: LazyLock<String> = LazyLock::new(|| { |
| format!("vscode/1.X.X (Antigravity/{})", CURRENT_VERSION.as_str()) |
| }); |
|
|
| |
| pub fn get_current_version() -> String { |
| env!("CARGO_PKG_VERSION").to_string() |
| } |
|
|
| |
| |
| pub fn get_default_user_agent() -> String { |
| format!("Antigravity/{} (Macintosh; Intel Mac OS X 10_15_7) Chrome/132.0.6834.160 Electron/39.2.3", env!("CARGO_PKG_VERSION")) |
| } |
|
|
| |
| pub static SESSION_ID: LazyLock<String> = LazyLock::new(|| { |
| uuid::Uuid::new_v4().to_string() |
| }); |
|
|
| |
| |
| |
| pub static USER_AGENT: LazyLock<String> = LazyLock::new(|| { |
| let (config, source) = resolve_version_config(); |
|
|
| tracing::info!( |
| version = %config.version, |
| source = ?source, |
| "User-Agent initialized" |
| ); |
|
|
| let platform_info = match std::env::consts::OS { |
| "macos" => "Macintosh; Intel Mac OS X 10_15_7", |
| "windows" => "Windows NT 10.0; Win64; x64", |
| "linux" => "X11; Linux x86_64", |
| _ => "X11; Linux x86_64", |
| }; |
|
|
| format!( |
| "Antigravity/{} ({}) Chrome/{} Electron/{}", |
| config.version, |
| platform_info, |
| config.chrome, |
| config.electron |
| ) |
| }); |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
|
|
| #[test] |
| fn test_parse_version_from_updater_response() { |
| let text = "Auto updater is running. Stable Version: 1.15.8-5724687216017408"; |
| assert_eq!(parse_version(text), Some("1.15.8".to_string())); |
| } |
|
|
| #[test] |
| fn test_parse_version_simple() { |
| assert_eq!(parse_version("1.15.8"), Some("1.15.8".to_string())); |
| assert_eq!(parse_version("Version: 2.0.0"), Some("2.0.0".to_string())); |
| assert_eq!(parse_version("v1.2.3"), Some("1.2.3".to_string())); |
| } |
|
|
| #[test] |
| fn test_parse_version_invalid() { |
| assert_eq!(parse_version("no version here"), None); |
| assert_eq!(parse_version(""), None); |
| assert_eq!(parse_version("1.2"), None); |
| } |
|
|
| #[test] |
| fn test_parse_version_with_suffix() { |
| |
| let text = "antigravity/1.15.8 windows/amd64"; |
| assert_eq!(parse_version(text), Some("1.15.8".to_string())); |
| } |
|
|
| #[test] |
| fn test_compare_semver() { |
| assert_eq!(compare_semver("4.1.32", "4.1.22"), std::cmp::Ordering::Greater); |
| assert_eq!(compare_semver("4.1.22", "4.1.32"), std::cmp::Ordering::Less); |
| assert_eq!(compare_semver("4.1.32", "4.1.32"), std::cmp::Ordering::Equal); |
| assert_eq!(compare_semver("5.0.0", "4.9.9"), std::cmp::Ordering::Greater); |
| assert_eq!(compare_semver("1.16.5", "1.16.4"), std::cmp::Ordering::Greater); |
| } |
|
|
| #[test] |
| fn test_known_stable_floor_is_up_to_date() { |
| |
| |
| assert!( |
| compare_semver(KNOWN_STABLE_VERSION, "4.1.22") > std::cmp::Ordering::Equal, |
| "KNOWN_STABLE_VERSION ({}) must be > 4.1.22; please sync with Cargo.toml", |
| KNOWN_STABLE_VERSION |
| ); |
| } |
|
|
| #[test] |
| fn test_old_local_version_uses_floor() { |
| |
| |
| let local = "4.1.20"; |
| let floor = KNOWN_STABLE_VERSION; |
| let best = if compare_semver(local, floor) > std::cmp::Ordering::Equal { |
| local |
| } else { |
| floor |
| }; |
| assert_eq!(best, KNOWN_STABLE_VERSION); |
| } |
|
|
| #[test] |
| fn test_newer_local_version_takes_priority() { |
| |
| |
| let local = "4.1.32"; |
| let floor = KNOWN_STABLE_VERSION; |
| let best = if compare_semver(local, floor) >= std::cmp::Ordering::Equal { |
| local |
| } else { |
| floor |
| }; |
| assert_eq!(best, "4.1.32"); |
| } |
| } |
|
|