ai_lib/
config.rs

1use std::time::Duration;
2use crate::circuit_breaker::CircuitBreakerConfig;
3use crate::rate_limiter::RateLimiterConfig;
4use crate::error_handling::ErrorThresholds;
5
6/// Minimal explicit connection/configuration options.
7///
8/// Library users can pass an instance of this struct to `AiClient::with_options` to
9/// explicitly control base URL, proxy, API key and timeout without relying exclusively
10/// on environment variables. Any field left as `None` will fall back to existing
11/// environment variable behavior or library defaults.
12#[derive(Clone, Debug)]
13pub struct ConnectionOptions {
14    pub base_url: Option<String>,
15    pub proxy: Option<String>,
16    pub api_key: Option<String>,
17    pub timeout: Option<Duration>,
18    pub disable_proxy: bool,
19}
20
21impl Default for ConnectionOptions {
22    fn default() -> Self {
23        Self {
24            base_url: None,
25            proxy: None,
26            api_key: None,
27            timeout: None,
28            disable_proxy: false,
29        }
30    }
31}
32
33impl ConnectionOptions {
34    /// Hydrate unset fields from environment variables (lightweight fallback logic).
35    ///
36    /// `provider_env_prefix` may be something like `OPENAI`, `GROQ`, etc., used to look up
37    /// a provider specific API key prior to the generic fallback `AI_API_KEY`.
38    pub fn hydrate_with_env(mut self, provider_env_prefix: &str) -> Self {
39        // API key precedence: explicit > <PROVIDER>_API_KEY > AI_API_KEY
40        if self.api_key.is_none() {
41            let specific = format!("{}_API_KEY", provider_env_prefix);
42            self.api_key = std::env::var(&specific)
43                .ok()
44                .or_else(|| std::env::var("AI_API_KEY").ok());
45        }
46        // Base URL precedence: explicit > AI_BASE_URL (generic) > leave None (caller/adapter handles default)
47        if self.base_url.is_none() {
48            if let Ok(v) = std::env::var("AI_BASE_URL") {
49                self.base_url = Some(v);
50            }
51        }
52        // Proxy precedence: explicit > AI_PROXY_URL
53        if self.proxy.is_none() && !self.disable_proxy {
54            self.proxy = std::env::var("AI_PROXY_URL").ok();
55        }
56        // Timeout precedence: explicit > AI_TIMEOUT_SECS > default handled by caller
57        if self.timeout.is_none() {
58            if let Ok(v) = std::env::var("AI_TIMEOUT_SECS") {
59                if let Ok(secs) = v.parse::<u64>() {
60                    self.timeout = Some(Duration::from_secs(secs));
61                }
62            }
63        }
64        self
65    }
66}
67
68/// Resilience configuration for advanced error handling and rate limiting
69#[derive(Debug, Clone)]
70pub struct ResilienceConfig {
71    pub circuit_breaker: Option<CircuitBreakerConfig>,
72    pub rate_limiter: Option<RateLimiterConfig>,
73    pub backpressure: Option<BackpressureConfig>,
74    pub error_handling: Option<ErrorHandlingConfig>,
75}
76
77/// Backpressure configuration
78#[derive(Debug, Clone)]
79pub struct BackpressureConfig {
80    pub max_concurrent_requests: usize,
81}
82
83/// Error handling configuration
84#[derive(Debug, Clone)]
85pub struct ErrorHandlingConfig {
86    pub enable_recovery: bool,
87    pub enable_monitoring: bool,
88    pub error_thresholds: ErrorThresholds,
89}
90
91impl Default for ResilienceConfig {
92    fn default() -> Self {
93        Self {
94            circuit_breaker: None,
95            rate_limiter: None,
96            backpressure: None,
97            error_handling: None,
98        }
99    }
100}
101
102impl Default for BackpressureConfig {
103    fn default() -> Self {
104        Self {
105            max_concurrent_requests: 100,
106        }
107    }
108}
109
110impl Default for ErrorHandlingConfig {
111    fn default() -> Self {
112        Self {
113            enable_recovery: true,
114            enable_monitoring: true,
115            error_thresholds: ErrorThresholds::default(),
116        }
117    }
118}
119
120impl ResilienceConfig {
121    /// Create smart defaults for production use
122    pub fn smart_defaults() -> Self {
123        Self {
124            circuit_breaker: Some(CircuitBreakerConfig::default()),
125            rate_limiter: Some(RateLimiterConfig::default()),
126            backpressure: Some(BackpressureConfig::default()),
127            error_handling: Some(ErrorHandlingConfig::default()),
128        }
129    }
130
131    /// Create production-ready configuration
132    pub fn production() -> Self {
133        Self {
134            circuit_breaker: Some(CircuitBreakerConfig::production()),
135            rate_limiter: Some(RateLimiterConfig::production()),
136            backpressure: Some(BackpressureConfig {
137                max_concurrent_requests: 50,
138            }),
139            error_handling: Some(ErrorHandlingConfig {
140                enable_recovery: true,
141                enable_monitoring: true,
142                error_thresholds: ErrorThresholds {
143                    error_rate_threshold: 0.05, // 5% error rate
144                    consecutive_errors: 3,
145                    time_window: Duration::from_secs(30),
146                },
147            }),
148        }
149    }
150
151    /// Create development configuration
152    pub fn development() -> Self {
153        Self {
154            circuit_breaker: Some(CircuitBreakerConfig::development()),
155            rate_limiter: Some(RateLimiterConfig::development()),
156            backpressure: Some(BackpressureConfig {
157                max_concurrent_requests: 200,
158            }),
159            error_handling: Some(ErrorHandlingConfig {
160                enable_recovery: false,
161                enable_monitoring: false,
162                error_thresholds: ErrorThresholds::default(),
163            }),
164        }
165    }
166}