Skip to main content

ai_agent/bridge/
env_less_bridge_config.rs

1//! Environment-less bridge configuration.
2//!
3//! Translated from openclaudecode/src/bridge/envLessBridgeConfig.ts
4
5use serde::{Deserialize, Serialize};
6use std::sync::OnceLock;
7
8// =============================================================================
9// TYPES
10// =============================================================================
11
12/// Configuration for the env-less bridge timing and behavior.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub struct EnvLessBridgeConfig {
16    /// Init-phase backoff (createSession, POST /bridge, recovery /bridge)
17    pub init_retry_max_attempts: u32,
18    /// Base delay for init retry in milliseconds
19    pub init_retry_base_delay_ms: u64,
20    /// Jitter fraction for init retry (0.0-1.0)
21    pub init_retry_jitter_fraction: f64,
22    /// Max delay for init retry in milliseconds
23    pub init_retry_max_delay_ms: u64,
24    /// Axios timeout for POST /sessions, POST /bridge, POST /archive
25    pub http_timeout_ms: u64,
26    /// BoundedUUIDSet ring size (echo + re-delivery dedup)
27    pub uuid_dedup_buffer_size: u32,
28    /// CCRClient worker heartbeat cadence in milliseconds
29    pub heartbeat_interval_ms: u64,
30    /// Fraction of interval for per-beat jitter
31    pub heartbeat_jitter_fraction: f64,
32    /// Fire proactive JWT refresh this long before expires_in
33    pub token_refresh_buffer_ms: u64,
34    /// Archive POST timeout in teardown()
35    pub teardown_archive_timeout_ms: u64,
36    /// Deadline for onConnect after transport.connect()
37    pub connect_timeout_ms: u64,
38    /// Semver floor for the env-less bridge path
39    pub min_version: &'static str,
40    /// Whether to show app upgrade message
41    pub should_show_app_upgrade_message: bool,
42}
43
44/// Default configuration for env-less bridge.
45pub const DEFAULT_ENV_LESS_BRIDGE_CONFIG: EnvLessBridgeConfig = EnvLessBridgeConfig {
46    init_retry_max_attempts: 3,
47    init_retry_base_delay_ms: 500,
48    init_retry_jitter_fraction: 0.25,
49    init_retry_max_delay_ms: 4000,
50    http_timeout_ms: 10_000,
51    uuid_dedup_buffer_size: 2000,
52    heartbeat_interval_ms: 20_000,
53    heartbeat_jitter_fraction: 0.1,
54    token_refresh_buffer_ms: 300_000,
55    teardown_archive_timeout_ms: 1500,
56    connect_timeout_ms: 15_000,
57    min_version: &"0.0.0",
58    should_show_app_upgrade_message: false,
59};
60
61// =============================================================================
62// CONFIGURATION FETCHING (STUB - GrowthBook)
63// =============================================================================
64
65static ENV_LESS_BRIDGE_CONFIG: OnceLock<EnvLessBridgeConfig> = OnceLock::new();
66
67/// Get the current version from Cargo.toml
68fn get_current_version() -> String {
69    env!("CARGO_PKG_VERSION").to_string()
70}
71
72/// Compare two semver strings (a < b).
73fn version_lt(a: &str, b: &str) -> bool {
74    let a_parts: Vec<u32> = a.split('.').filter_map(|s| s.parse().ok()).collect();
75    let b_parts: Vec<u32> = b.split('.').filter_map(|s| s.parse().ok()).collect();
76    for (av, bv) in a_parts.iter().zip(b_parts.iter()) {
77        if av < bv {
78            return true;
79        }
80        if av > bv {
81            return false;
82        }
83    }
84    false
85}
86
87/// Fetch the env-less bridge timing config.
88/// Currently returns default config - in production would fetch from GrowthBook.
89pub async fn get_env_less_bridge_config() -> EnvLessBridgeConfig {
90    ENV_LESS_BRIDGE_CONFIG
91        .get_or_init(|| DEFAULT_ENV_LESS_BRIDGE_CONFIG.clone())
92        .clone()
93}
94
95/// Returns an error message if the current CLI version is below the minimum
96/// required for the env-less (v2) bridge path, or None if the version is fine.
97pub async fn check_env_less_bridge_min_version() -> Option<String> {
98    let cfg = get_env_less_bridge_config().await;
99    if !cfg.min_version.is_empty() && version_lt(&get_current_version(), &cfg.min_version) {
100        return Some(format!(
101            "Your version of AI Code ({}) is too old for Remote Control.\n\
102             Version {} or higher is required. Run `ai update` to update.",
103            get_current_version(),
104            cfg.min_version
105        ));
106    }
107    None
108}
109
110/// Whether to nudge users toward upgrading their claude.ai app when a
111/// Remote Control session starts.
112pub async fn should_show_app_upgrade_message() -> bool {
113    // Import from bridge_enabled module
114    if !crate::bridge_enabled::is_env_less_bridge_enabled() {
115        return false;
116    }
117    let cfg = get_env_less_bridge_config().await;
118    cfg.should_show_app_upgrade_message
119}