1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct ProxyConfig {
6 pub listen_address: String,
8 pub listen_port: u16,
10 pub max_retries: u8,
12 pub request_timeout: u64,
14 pub enable_logging: bool,
16 #[serde(default)]
18 pub live_takeover_active: bool,
19 #[serde(default = "default_streaming_first_byte_timeout")]
21 pub streaming_first_byte_timeout: u64,
22 #[serde(default = "default_streaming_idle_timeout")]
24 pub streaming_idle_timeout: u64,
25 #[serde(default = "default_non_streaming_timeout")]
27 pub non_streaming_timeout: u64,
28}
29
30fn default_streaming_first_byte_timeout() -> u64 {
31 60
32}
33
34fn default_streaming_idle_timeout() -> u64 {
35 120
36}
37
38fn default_non_streaming_timeout() -> u64 {
39 600
40}
41
42impl Default for ProxyConfig {
43 fn default() -> Self {
44 Self {
45 listen_address: "127.0.0.1".to_string(),
46 listen_port: 15721, max_retries: 3,
48 request_timeout: 600,
49 enable_logging: true,
50 live_takeover_active: false,
51 streaming_first_byte_timeout: 60,
52 streaming_idle_timeout: 120,
53 non_streaming_timeout: 600,
54 }
55 }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, Default)]
60pub struct ProxyStatus {
61 pub running: bool,
63 pub address: String,
65 pub port: u16,
67 pub active_connections: usize,
69 pub total_requests: u64,
71 #[serde(default)]
73 pub estimated_input_tokens_total: u64,
74 #[serde(default)]
76 pub estimated_output_tokens_total: u64,
77 pub success_requests: u64,
79 pub failed_requests: u64,
81 pub success_rate: f32,
83 pub uptime_seconds: u64,
85 pub current_provider: Option<String>,
87 pub current_provider_id: Option<String>,
89 pub last_request_at: Option<String>,
91 pub last_error: Option<String>,
93 pub failover_count: u64,
95 #[serde(default)]
97 pub managed_session_token: Option<String>,
98 #[serde(default)]
100 pub active_targets: Vec<ActiveTarget>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct ActiveTarget {
106 pub app_type: String, pub provider_name: String,
108 pub provider_id: String,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct ProxyServerInfo {
114 pub address: String,
115 pub port: u16,
116 pub started_at: String,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize, Default)]
121pub struct ProxyTakeoverStatus {
122 pub claude: bool,
123 pub codex: bool,
124 pub gemini: bool,
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129#[allow(dead_code)]
130pub enum ApiFormat {
131 Claude,
132 OpenAI,
133 Gemini,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct ProviderHealth {
139 pub provider_id: String,
140 pub app_type: String,
141 pub is_healthy: bool,
142 pub consecutive_failures: u32,
143 pub last_success_at: Option<String>,
144 pub last_failure_at: Option<String>,
145 pub last_error: Option<String>,
146 pub updated_at: String,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct LiveBackup {
152 pub app_type: String,
154 pub original_config: String,
156 pub backed_up_at: String,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(rename_all = "camelCase")]
163pub struct GlobalProxyConfig {
164 pub proxy_enabled: bool,
166 pub listen_address: String,
168 pub listen_port: u16,
170 pub enable_logging: bool,
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
176#[serde(rename_all = "camelCase")]
177pub struct AppProxyConfig {
178 pub app_type: String,
180 pub enabled: bool,
182 pub auto_failover_enabled: bool,
184 pub max_retries: u32,
186 pub streaming_first_byte_timeout: u32,
188 pub streaming_idle_timeout: u32,
190 pub non_streaming_timeout: u32,
192 pub circuit_failure_threshold: u32,
194 pub circuit_success_threshold: u32,
196 pub circuit_timeout_seconds: u32,
198 pub circuit_error_rate_threshold: f64,
200 pub circuit_min_requests: u32,
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize)]
208#[serde(rename_all = "camelCase")]
209pub struct RectifierConfig {
210 #[serde(default = "default_true")]
212 pub enabled: bool,
213 #[serde(default = "default_true")]
215 pub request_thinking_signature: bool,
216 #[serde(default = "default_true")]
218 pub request_thinking_budget: bool,
219}
220
221fn default_true() -> bool {
222 true
223}
224
225fn default_log_level() -> String {
226 "info".to_string()
227}
228
229impl Default for RectifierConfig {
230 fn default() -> Self {
231 Self {
232 enabled: true,
233 request_thinking_signature: true,
234 request_thinking_budget: true,
235 }
236 }
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize)]
244#[serde(rename_all = "camelCase")]
245pub struct OptimizerConfig {
246 #[serde(default)]
247 pub enabled: bool,
248 #[serde(default = "default_true")]
249 pub thinking_optimizer: bool,
250 #[serde(default = "default_true")]
251 pub cache_injection: bool,
252 #[serde(default = "default_cache_ttl")]
253 pub cache_ttl: String,
254}
255
256fn default_cache_ttl() -> String {
257 "1h".to_string()
258}
259
260impl Default for OptimizerConfig {
261 fn default() -> Self {
262 Self {
263 enabled: false,
264 thinking_optimizer: true,
265 cache_injection: true,
266 cache_ttl: "1h".to_string(),
267 }
268 }
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
275#[serde(rename_all = "camelCase")]
276pub struct LogConfig {
277 #[serde(default = "default_true")]
279 pub enabled: bool,
280 #[serde(default = "default_log_level")]
282 pub level: String,
283}
284
285impl Default for LogConfig {
286 fn default() -> Self {
287 Self {
288 enabled: true,
289 level: "info".to_string(),
290 }
291 }
292}
293
294impl LogConfig {
295 pub fn to_level_filter(&self) -> log::LevelFilter {
297 if !self.enabled {
298 return log::LevelFilter::Off;
299 }
300 match self.level.to_lowercase().as_str() {
301 "error" => log::LevelFilter::Error,
302 "warn" => log::LevelFilter::Warn,
303 "info" => log::LevelFilter::Info,
304 "debug" => log::LevelFilter::Debug,
305 "trace" => log::LevelFilter::Trace,
306 _ => log::LevelFilter::Info,
307 }
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 #[test]
316 fn test_rectifier_config_default_enabled() {
317 let config = RectifierConfig::default();
318 assert!(config.enabled, "整流器总开关默认应为 true");
319 assert!(
320 config.request_thinking_signature,
321 "thinking 签名整流器默认应为 true"
322 );
323 assert!(
324 config.request_thinking_budget,
325 "thinking budget 整流器默认应为 true"
326 );
327 }
328
329 #[test]
330 fn test_rectifier_config_serde_default() {
331 let json = "{}";
332 let config: RectifierConfig = serde_json::from_str(json).unwrap();
333 assert!(config.enabled);
334 assert!(config.request_thinking_signature);
335 assert!(config.request_thinking_budget);
336 }
337
338 #[test]
339 fn test_rectifier_config_serde_explicit_true() {
340 let json =
341 r#"{"enabled": true, "requestThinkingSignature": true, "requestThinkingBudget": true}"#;
342 let config: RectifierConfig = serde_json::from_str(json).unwrap();
343 assert!(config.enabled);
344 assert!(config.request_thinking_signature);
345 assert!(config.request_thinking_budget);
346 }
347
348 #[test]
349 fn test_rectifier_config_serde_partial_fields() {
350 let json = r#"{"enabled": true, "requestThinkingSignature": false}"#;
351 let config: RectifierConfig = serde_json::from_str(json).unwrap();
352 assert!(config.enabled);
353 assert!(!config.request_thinking_signature);
354 assert!(config.request_thinking_budget);
355 }
356
357 #[test]
358 fn test_rectifier_config_roundtrip_preserves_budget_flag() {
359 let config = RectifierConfig {
360 enabled: false,
361 request_thinking_signature: true,
362 request_thinking_budget: false,
363 };
364
365 let encoded = serde_json::to_string(&config).unwrap();
366 let decoded: RectifierConfig = serde_json::from_str(&encoded).unwrap();
367 assert!(!decoded.enabled);
368 assert!(decoded.request_thinking_signature);
369 assert!(!decoded.request_thinking_budget);
370 }
371
372 #[test]
373 fn test_log_config_default() {
374 let config = LogConfig::default();
375 assert!(config.enabled);
376 assert_eq!(config.level, "info");
377 }
378
379 #[test]
380 fn test_optimizer_config_default() {
381 let config = OptimizerConfig::default();
382 assert!(!config.enabled);
383 assert!(config.thinking_optimizer);
384 assert!(config.cache_injection);
385 assert_eq!(config.cache_ttl, "1h");
386 }
387
388 #[test]
389 fn test_optimizer_config_serde_default() {
390 let json = "{}";
391 let config: OptimizerConfig = serde_json::from_str(json).unwrap();
392 assert!(!config.enabled);
393 assert!(config.thinking_optimizer);
394 assert!(config.cache_injection);
395 assert_eq!(config.cache_ttl, "1h");
396 }
397
398 #[test]
399 fn test_log_config_serde_default() {
400 let json = "{}";
401 let config: LogConfig = serde_json::from_str(json).unwrap();
402 assert!(config.enabled);
403 assert_eq!(config.level, "info");
404 }
405
406 #[test]
407 fn test_log_config_to_level_filter() {
408 let config = LogConfig {
409 level: "error".to_string(),
410 ..Default::default()
411 };
412 assert_eq!(config.to_level_filter(), log::LevelFilter::Error);
413
414 let config = LogConfig {
415 level: "warn".to_string(),
416 ..Default::default()
417 };
418 assert_eq!(config.to_level_filter(), log::LevelFilter::Warn);
419
420 let config = LogConfig {
421 level: "info".to_string(),
422 ..Default::default()
423 };
424 assert_eq!(config.to_level_filter(), log::LevelFilter::Info);
425
426 let config = LogConfig {
427 level: "debug".to_string(),
428 ..Default::default()
429 };
430 assert_eq!(config.to_level_filter(), log::LevelFilter::Debug);
431
432 let config = LogConfig {
433 level: "trace".to_string(),
434 ..Default::default()
435 };
436 assert_eq!(config.to_level_filter(), log::LevelFilter::Trace);
437
438 let config = LogConfig {
440 level: "invalid".to_string(),
441 ..Default::default()
442 };
443 assert_eq!(config.to_level_filter(), log::LevelFilter::Info);
444
445 let config = LogConfig {
447 enabled: false,
448 level: "debug".to_string(),
449 };
450 assert_eq!(config.to_level_filter(), log::LevelFilter::Off);
451 }
452
453 #[test]
454 fn test_log_config_serde_roundtrip() {
455 let config = LogConfig {
456 enabled: true,
457 level: "debug".to_string(),
458 };
459 let json = serde_json::to_string(&config).unwrap();
460 let parsed: LogConfig = serde_json::from_str(&json).unwrap();
461 assert!(parsed.enabled);
462 assert_eq!(parsed.level, "debug");
463 }
464}