use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProxyConfig {
pub listen_address: String,
pub listen_port: u16,
pub max_retries: u8,
pub request_timeout: u64,
pub enable_logging: bool,
#[serde(default)]
pub live_takeover_active: bool,
#[serde(default = "default_streaming_first_byte_timeout")]
pub streaming_first_byte_timeout: u64,
#[serde(default = "default_streaming_idle_timeout")]
pub streaming_idle_timeout: u64,
#[serde(default = "default_non_streaming_timeout")]
pub non_streaming_timeout: u64,
}
fn default_streaming_first_byte_timeout() -> u64 {
60
}
fn default_streaming_idle_timeout() -> u64 {
120
}
fn default_non_streaming_timeout() -> u64 {
600
}
impl Default for ProxyConfig {
fn default() -> Self {
Self {
listen_address: "127.0.0.1".to_string(),
listen_port: 15721, max_retries: 3,
request_timeout: 600,
enable_logging: true,
live_takeover_active: false,
streaming_first_byte_timeout: 60,
streaming_idle_timeout: 120,
non_streaming_timeout: 600,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ProxyStatus {
pub running: bool,
pub address: String,
pub port: u16,
pub active_connections: usize,
pub total_requests: u64,
#[serde(default)]
pub estimated_input_tokens_total: u64,
#[serde(default)]
pub estimated_output_tokens_total: u64,
pub success_requests: u64,
pub failed_requests: u64,
pub success_rate: f32,
pub uptime_seconds: u64,
pub current_provider: Option<String>,
pub current_provider_id: Option<String>,
pub last_request_at: Option<String>,
pub last_error: Option<String>,
pub failover_count: u64,
#[serde(default)]
pub managed_session_token: Option<String>,
#[serde(default)]
pub active_targets: Vec<ActiveTarget>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActiveTarget {
pub app_type: String, pub provider_name: String,
pub provider_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProxyServerInfo {
pub address: String,
pub port: u16,
pub started_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ProxyTakeoverStatus {
pub claude: bool,
pub codex: bool,
pub gemini: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)]
pub enum ApiFormat {
Claude,
OpenAI,
Gemini,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderHealth {
pub provider_id: String,
pub app_type: String,
pub is_healthy: bool,
pub consecutive_failures: u32,
pub last_success_at: Option<String>,
pub last_failure_at: Option<String>,
pub last_error: Option<String>,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiveBackup {
pub app_type: String,
pub original_config: String,
pub backed_up_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GlobalProxyConfig {
pub proxy_enabled: bool,
pub listen_address: String,
pub listen_port: u16,
pub enable_logging: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AppProxyConfig {
pub app_type: String,
pub enabled: bool,
pub auto_failover_enabled: bool,
pub max_retries: u32,
pub streaming_first_byte_timeout: u32,
pub streaming_idle_timeout: u32,
pub non_streaming_timeout: u32,
pub circuit_failure_threshold: u32,
pub circuit_success_threshold: u32,
pub circuit_timeout_seconds: u32,
pub circuit_error_rate_threshold: f64,
pub circuit_min_requests: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RectifierConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_true")]
pub request_thinking_signature: bool,
#[serde(default = "default_true")]
pub request_thinking_budget: bool,
}
fn default_true() -> bool {
true
}
fn default_log_level() -> String {
"info".to_string()
}
impl Default for RectifierConfig {
fn default() -> Self {
Self {
enabled: true,
request_thinking_signature: true,
request_thinking_budget: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OptimizerConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_true")]
pub thinking_optimizer: bool,
#[serde(default = "default_true")]
pub cache_injection: bool,
#[serde(default = "default_cache_ttl")]
pub cache_ttl: String,
}
fn default_cache_ttl() -> String {
"1h".to_string()
}
impl Default for OptimizerConfig {
fn default() -> Self {
Self {
enabled: false,
thinking_optimizer: true,
cache_injection: true,
cache_ttl: "1h".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LogConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_log_level")]
pub level: String,
}
impl Default for LogConfig {
fn default() -> Self {
Self {
enabled: true,
level: "info".to_string(),
}
}
}
impl LogConfig {
pub fn to_level_filter(&self) -> log::LevelFilter {
if !self.enabled {
return log::LevelFilter::Off;
}
match self.level.to_lowercase().as_str() {
"error" => log::LevelFilter::Error,
"warn" => log::LevelFilter::Warn,
"info" => log::LevelFilter::Info,
"debug" => log::LevelFilter::Debug,
"trace" => log::LevelFilter::Trace,
_ => log::LevelFilter::Info,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rectifier_config_default_enabled() {
let config = RectifierConfig::default();
assert!(config.enabled, "整流器总开关默认应为 true");
assert!(
config.request_thinking_signature,
"thinking 签名整流器默认应为 true"
);
assert!(
config.request_thinking_budget,
"thinking budget 整流器默认应为 true"
);
}
#[test]
fn test_rectifier_config_serde_default() {
let json = "{}";
let config: RectifierConfig = serde_json::from_str(json).unwrap();
assert!(config.enabled);
assert!(config.request_thinking_signature);
assert!(config.request_thinking_budget);
}
#[test]
fn test_rectifier_config_serde_explicit_true() {
let json =
r#"{"enabled": true, "requestThinkingSignature": true, "requestThinkingBudget": true}"#;
let config: RectifierConfig = serde_json::from_str(json).unwrap();
assert!(config.enabled);
assert!(config.request_thinking_signature);
assert!(config.request_thinking_budget);
}
#[test]
fn test_rectifier_config_serde_partial_fields() {
let json = r#"{"enabled": true, "requestThinkingSignature": false}"#;
let config: RectifierConfig = serde_json::from_str(json).unwrap();
assert!(config.enabled);
assert!(!config.request_thinking_signature);
assert!(config.request_thinking_budget);
}
#[test]
fn test_rectifier_config_roundtrip_preserves_budget_flag() {
let config = RectifierConfig {
enabled: false,
request_thinking_signature: true,
request_thinking_budget: false,
};
let encoded = serde_json::to_string(&config).unwrap();
let decoded: RectifierConfig = serde_json::from_str(&encoded).unwrap();
assert!(!decoded.enabled);
assert!(decoded.request_thinking_signature);
assert!(!decoded.request_thinking_budget);
}
#[test]
fn test_log_config_default() {
let config = LogConfig::default();
assert!(config.enabled);
assert_eq!(config.level, "info");
}
#[test]
fn test_optimizer_config_default() {
let config = OptimizerConfig::default();
assert!(!config.enabled);
assert!(config.thinking_optimizer);
assert!(config.cache_injection);
assert_eq!(config.cache_ttl, "1h");
}
#[test]
fn test_optimizer_config_serde_default() {
let json = "{}";
let config: OptimizerConfig = serde_json::from_str(json).unwrap();
assert!(!config.enabled);
assert!(config.thinking_optimizer);
assert!(config.cache_injection);
assert_eq!(config.cache_ttl, "1h");
}
#[test]
fn test_log_config_serde_default() {
let json = "{}";
let config: LogConfig = serde_json::from_str(json).unwrap();
assert!(config.enabled);
assert_eq!(config.level, "info");
}
#[test]
fn test_log_config_to_level_filter() {
let config = LogConfig {
level: "error".to_string(),
..Default::default()
};
assert_eq!(config.to_level_filter(), log::LevelFilter::Error);
let config = LogConfig {
level: "warn".to_string(),
..Default::default()
};
assert_eq!(config.to_level_filter(), log::LevelFilter::Warn);
let config = LogConfig {
level: "info".to_string(),
..Default::default()
};
assert_eq!(config.to_level_filter(), log::LevelFilter::Info);
let config = LogConfig {
level: "debug".to_string(),
..Default::default()
};
assert_eq!(config.to_level_filter(), log::LevelFilter::Debug);
let config = LogConfig {
level: "trace".to_string(),
..Default::default()
};
assert_eq!(config.to_level_filter(), log::LevelFilter::Trace);
let config = LogConfig {
level: "invalid".to_string(),
..Default::default()
};
assert_eq!(config.to_level_filter(), log::LevelFilter::Info);
let config = LogConfig {
enabled: false,
level: "debug".to_string(),
};
assert_eq!(config.to_level_filter(), log::LevelFilter::Off);
}
#[test]
fn test_log_config_serde_roundtrip() {
let config = LogConfig {
enabled: true,
level: "debug".to_string(),
};
let json = serde_json::to_string(&config).unwrap();
let parsed: LogConfig = serde_json::from_str(&json).unwrap();
assert!(parsed.enabled);
assert_eq!(parsed.level, "debug");
}
}