use serde::{Deserialize, Serialize}; // use std::path::PathBuf; use std::collections::HashMap; use std::sync::{OnceLock, RwLock}; // ============================================================================ // 辅助工具函数 // ============================================================================ /// 标准化代理 URL,如果缺失协议则默认补全 http:// pub fn normalize_proxy_url(url: &str) -> String { let url = url.trim(); if url.is_empty() { return String::new(); } if !url.contains("://") { format!("http://{}", url) } else { url.to_string() } } // ============================================================================ // 全局 Thinking Budget 配置存储 // 用于在 request transform 函数中访问配置(无需修改函数签名) // ============================================================================ static GLOBAL_THINKING_BUDGET_CONFIG: OnceLock> = OnceLock::new(); /// 获取当前 Thinking Budget 配置 pub fn get_thinking_budget_config() -> ThinkingBudgetConfig { GLOBAL_THINKING_BUDGET_CONFIG .get() .and_then(|lock| lock.read().ok()) .map(|cfg| cfg.clone()) .unwrap_or_default() } /// 更新全局 Thinking Budget 配置 pub fn update_thinking_budget_config(config: ThinkingBudgetConfig) { if let Some(lock) = GLOBAL_THINKING_BUDGET_CONFIG.get() { if let Ok(mut cfg) = lock.write() { *cfg = config.clone(); tracing::info!( "[Thinking-Budget] Global config updated: mode={:?}, custom_value={}", config.mode, config.custom_value ); } } else { // 首次初始化 let _ = GLOBAL_THINKING_BUDGET_CONFIG.set(RwLock::new(config.clone())); tracing::info!( "[Thinking-Budget] Global config initialized: mode={:?}, custom_value={}", config.mode, config.custom_value ); } } // ============================================================================ // 全局系统提示词配置存储 // 用户可在设置中配置一段全局提示词,自动注入到所有请求的 systemInstruction 中 // ============================================================================ static GLOBAL_SYSTEM_PROMPT_CONFIG: OnceLock> = OnceLock::new(); /// 获取当前全局系统提示词配置 pub fn get_global_system_prompt() -> GlobalSystemPromptConfig { GLOBAL_SYSTEM_PROMPT_CONFIG .get() .and_then(|lock| lock.read().ok()) .map(|cfg| cfg.clone()) .unwrap_or_default() } /// 更新全局系统提示词配置 pub fn update_global_system_prompt_config(config: GlobalSystemPromptConfig) { if let Some(lock) = GLOBAL_SYSTEM_PROMPT_CONFIG.get() { if let Ok(mut cfg) = lock.write() { *cfg = config.clone(); tracing::info!( "[Global-System-Prompt] Config updated: enabled={}, content_len={}", config.enabled, config.content.len() ); } } else { // 首次初始化 let _ = GLOBAL_SYSTEM_PROMPT_CONFIG.set(RwLock::new(config.clone())); tracing::info!( "[Global-System-Prompt] Config initialized: enabled={}, content_len={}", config.enabled, config.content.len() ); } } // ============================================================================ // 全局图像思维模式配置存储 // ============================================================================ static GLOBAL_IMAGE_THINKING_MODE: OnceLock> = OnceLock::new(); pub fn get_image_thinking_mode() -> String { GLOBAL_IMAGE_THINKING_MODE .get() .and_then(|lock| lock.read().ok()) .map(|s| s.clone()) .unwrap_or_else(|| "enabled".to_string()) } pub fn update_image_thinking_mode(mode: Option) { let val = mode.unwrap_or_else(|| "enabled".to_string()); if let Some(lock) = GLOBAL_IMAGE_THINKING_MODE.get() { if let Ok(mut cfg) = lock.write() { if *cfg != val { *cfg = val.clone(); tracing::info!("[Image-Thinking] Global config updated: {}", val); } } } else { let _ = GLOBAL_IMAGE_THINKING_MODE.set(RwLock::new(val.clone())); } } /// 全局系统提示词配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GlobalSystemPromptConfig { /// 是否启用全局系统提示词 #[serde(default)] pub enabled: bool, /// 系统提示词内容 #[serde(default)] pub content: String, } impl Default for GlobalSystemPromptConfig { fn default() -> Self { Self { enabled: false, content: String::new(), } } } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum ProxyAuthMode { Off, Strict, AllExceptHealth, Auto, } impl Default for ProxyAuthMode { fn default() -> Self { Self::Auto } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "snake_case")] pub enum ZaiDispatchMode { /// Never use z.ai. Off, /// Use z.ai for all Anthropic protocol requests. Exclusive, /// Treat z.ai as one additional slot in the shared pool. Pooled, /// Use z.ai only when the Google pool is unavailable. Fallback, } impl Default for ZaiDispatchMode { fn default() -> Self { Self::Off } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ZaiModelDefaults { /// Default model for "opus" family (when the incoming model is a Claude id). #[serde(default = "default_zai_opus_model")] pub opus: String, /// Default model for "sonnet" family (when the incoming model is a Claude id). #[serde(default = "default_zai_sonnet_model")] pub sonnet: String, /// Default model for "haiku" family (when the incoming model is a Claude id). #[serde(default = "default_zai_haiku_model")] pub haiku: String, } impl Default for ZaiModelDefaults { fn default() -> Self { Self { opus: default_zai_opus_model(), sonnet: default_zai_sonnet_model(), haiku: default_zai_haiku_model(), } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ZaiMcpConfig { #[serde(default)] pub enabled: bool, #[serde(default)] pub web_search_enabled: bool, #[serde(default)] pub web_reader_enabled: bool, #[serde(default)] pub vision_enabled: bool, } impl Default for ZaiMcpConfig { fn default() -> Self { Self { enabled: false, web_search_enabled: false, web_reader_enabled: false, vision_enabled: false, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ZaiConfig { #[serde(default)] pub enabled: bool, #[serde(default = "default_zai_base_url")] pub base_url: String, #[serde(default)] pub api_key: String, #[serde(default)] pub dispatch_mode: ZaiDispatchMode, /// Optional per-model mapping overrides for Anthropic/Claude model ids. /// Key: incoming `model` string, Value: upstream z.ai model id (e.g. `glm-4.7`). #[serde(default)] pub model_mapping: HashMap, #[serde(default)] pub models: ZaiModelDefaults, #[serde(default)] pub mcp: ZaiMcpConfig, } impl Default for ZaiConfig { fn default() -> Self { Self { enabled: false, base_url: default_zai_base_url(), api_key: String::new(), dispatch_mode: ZaiDispatchMode::Off, model_mapping: HashMap::new(), models: ZaiModelDefaults::default(), mcp: ZaiMcpConfig::default(), } } } /// 实验性功能配置 (Feature Flags) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExperimentalConfig { /// 启用双层签名缓存 (Signature Cache) #[serde(default = "default_true")] pub enable_signature_cache: bool, /// 启用工具循环自动恢复 (Tool Loop Recovery) #[serde(default = "default_true")] pub enable_tool_loop_recovery: bool, /// 启用跨模型兼容性检查 (Cross-Model Checks) #[serde(default = "default_true")] pub enable_cross_model_checks: bool, /// 启用上下文用量缩放 (Context Usage Scaling) /// 激进模式: 缩放用量并激活自动压缩以突破 200k 限制 /// 默认关闭以保持透明度,让客户端能触发原生压缩指令 #[serde(default = "default_false")] pub enable_usage_scaling: bool, /// 上下文压缩阈值 L1 (Tool Trimming) #[serde(default = "default_threshold_l1")] pub context_compression_threshold_l1: f32, /// 上下文压缩阈值 L2 (Thinking Compression) #[serde(default = "default_threshold_l2")] pub context_compression_threshold_l2: f32, /// 上下文压缩阈值 L3 (Fork + Summary) #[serde(default = "default_threshold_l3")] pub context_compression_threshold_l3: f32, } impl Default for ExperimentalConfig { fn default() -> Self { Self { enable_signature_cache: true, enable_tool_loop_recovery: true, enable_cross_model_checks: true, enable_usage_scaling: false, // 默认关闭,回归透明模式 context_compression_threshold_l1: 0.4, context_compression_threshold_l2: 0.55, context_compression_threshold_l3: 0.7, } } } fn default_threshold_l1() -> f32 { 0.4 } fn default_threshold_l2() -> f32 { 0.55 } fn default_threshold_l3() -> f32 { 0.7 } /// Thinking Budget 模式 /// 控制如何处理调用方传入的 thinking_budget 参数 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "snake_case")] pub enum ThinkingBudgetMode { /// 自动限制:对特定模型(Flash/Thinking)应用 24576 上限 Auto, /// 透传:完全使用调用方传入的值,不做任何修改 Passthrough, /// 自定义:使用用户设定的固定值覆盖所有请求 Custom, /// 自适应:使用 effort 参数控制思考强度 (Claude 4.6+) Adaptive, } impl Default for ThinkingBudgetMode { fn default() -> Self { Self::Auto } } /// Thinking Budget 配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ThinkingBudgetConfig { /// 模式选择 #[serde(default)] pub mode: ThinkingBudgetMode, /// 自定义固定值(仅在 mode=Custom 时生效) #[serde(default = "default_thinking_budget_custom_value")] pub custom_value: u32, /// 思考强度 (仅在 mode=Adaptive 时生效) : low, medium, high #[serde(default, skip_serializing_if = "Option::is_none")] pub effort: Option, } impl Default for ThinkingBudgetConfig { fn default() -> Self { Self { mode: ThinkingBudgetMode::Auto, custom_value: default_thinking_budget_custom_value(), effort: None, } } } fn default_thinking_budget_custom_value() -> u32 { 24576 } fn default_true() -> bool { true } fn default_false() -> bool { false } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DebugLoggingConfig { #[serde(default)] pub enabled: bool, #[serde(default)] pub output_dir: Option, } impl Default for DebugLoggingConfig { fn default() -> Self { Self { enabled: false, output_dir: None, } } } /// IP 黑名单配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IpBlacklistConfig { /// 是否启用黑名单 #[serde(default)] pub enabled: bool, /// 自定义封禁消息 #[serde(default = "default_block_message")] pub block_message: String, } impl Default for IpBlacklistConfig { fn default() -> Self { Self { enabled: false, block_message: default_block_message(), } } } fn default_block_message() -> String { "Access denied".to_string() } /// IP 白名单配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IpWhitelistConfig { /// 是否启用白名单模式 (启用后只允许白名单IP访问) #[serde(default)] pub enabled: bool, /// 白名单优先模式 (白名单IP跳过黑名单检查) #[serde(default = "default_true")] pub whitelist_priority: bool, } impl Default for IpWhitelistConfig { fn default() -> Self { Self { enabled: false, whitelist_priority: true, } } } /// 安全监控配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SecurityMonitorConfig { /// IP 黑名单配置 #[serde(default)] pub blacklist: IpBlacklistConfig, /// IP 白名单配置 #[serde(default)] pub whitelist: IpWhitelistConfig, } impl Default for SecurityMonitorConfig { fn default() -> Self { Self { blacklist: IpBlacklistConfig::default(), whitelist: IpWhitelistConfig::default(), } } } /// 反代服务配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProxyConfig { /// 是否启用反代服务 pub enabled: bool, /// 是否允许局域网访问 /// - false: 仅本机访问 127.0.0.1(默认,隐私优先) /// - true: 允许局域网访问 0.0.0.0 #[serde(default)] pub allow_lan_access: bool, /// Authorization policy for the proxy. /// - off: no auth required /// - strict: auth required for all routes /// - all_except_health: auth required for all routes except `/healthz` /// - auto: recommended defaults (currently: allow_lan_access => all_except_health, else off) #[serde(default)] pub auth_mode: ProxyAuthMode, /// 监听端口 pub port: u16, /// API 密钥 pub api_key: String, /// Web UI 管理后台密码 (可选,如未设置则使用 api_key) pub admin_password: Option, /// 是否自动启动 pub auto_start: bool, /// 自定义精确模型映射表 (key: 原始模型名, value: 目标模型名) #[serde(default)] pub custom_mapping: std::collections::HashMap, /// API 请求超时时间(秒) #[serde(default = "default_request_timeout")] pub request_timeout: u64, /// 是否开启请求日志记录 (监控) #[serde(default)] pub enable_logging: bool, /// 调试日志配置 (保存完整链路) #[serde(default)] pub debug_logging: DebugLoggingConfig, /// 上游代理配置 #[serde(default)] pub upstream_proxy: UpstreamProxyConfig, /// z.ai provider configuration (Anthropic-compatible). #[serde(default)] pub zai: ZaiConfig, /// 自定义 User-Agent 请求头 (可选覆盖) #[serde(default)] pub user_agent_override: Option, /// 账号调度配置 (粘性会话/限流重试) #[serde(default)] pub scheduling: crate::proxy::sticky_config::StickySessionConfig, /// 实验性功能配置 #[serde(default)] pub experimental: ExperimentalConfig, /// 安全监控配置 (IP 黑白名单) #[serde(default)] pub security_monitor: SecurityMonitorConfig, /// 固定账号模式的账号ID (Fixed Account Mode) /// - None: 使用轮询模式 /// - Some(account_id): 固定使用指定账号 #[serde(default)] pub preferred_account_id: Option, /// Saved User-Agent string (persisted even when override is disabled) #[serde(default)] pub saved_user_agent: Option, /// Thinking Budget 配置 /// 控制如何处理 AI 深度思考时的 Token 预算 #[serde(default)] pub thinking_budget: ThinkingBudgetConfig, /// 全局系统提示词配置 /// 自动注入到所有 API 请求的 systemInstruction 中 #[serde(default)] pub global_system_prompt: GlobalSystemPromptConfig, /// 图像思维模式配置 /// - enabled: 保留思维链 (默认) /// - disabled: 移除思维链 (画质优先) #[serde(default)] pub image_thinking_mode: Option, /// 代理池配置 #[serde(default)] pub proxy_pool: ProxyPoolConfig, } /// 上游代理配置 #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct UpstreamProxyConfig { /// 是否启用 pub enabled: bool, /// 代理地址 (http://, https://, socks5://) pub url: String, } impl Default for ProxyConfig { fn default() -> Self { Self { enabled: false, allow_lan_access: false, // 默认仅本机访问,隐私优先 auth_mode: ProxyAuthMode::default(), port: 8045, api_key: format!("sk-{}", uuid::Uuid::new_v4().simple()), admin_password: None, auto_start: false, custom_mapping: std::collections::HashMap::new(), request_timeout: default_request_timeout(), enable_logging: true, // 默认开启,支持 token 统计功能 debug_logging: DebugLoggingConfig::default(), upstream_proxy: UpstreamProxyConfig::default(), zai: ZaiConfig::default(), scheduling: crate::proxy::sticky_config::StickySessionConfig::default(), experimental: ExperimentalConfig::default(), security_monitor: SecurityMonitorConfig::default(), preferred_account_id: None, // 默认使用轮询模式 user_agent_override: None, saved_user_agent: None, thinking_budget: ThinkingBudgetConfig::default(), global_system_prompt: GlobalSystemPromptConfig::default(), proxy_pool: ProxyPoolConfig::default(), image_thinking_mode: None, } } } fn default_request_timeout() -> u64 { 120 // 默认 120 秒,原来 60 秒太短 } fn default_zai_base_url() -> String { "https://api.z.ai/api/anthropic".to_string() } fn default_zai_opus_model() -> String { "glm-4.7".to_string() } fn default_zai_sonnet_model() -> String { "glm-4.7".to_string() } fn default_zai_haiku_model() -> String { "glm-4.5-air".to_string() } impl ProxyConfig { /// 获取实际的监听地址 /// - allow_lan_access = false: 返回 "127.0.0.1"(默认,隐私优先) /// - allow_lan_access = true: 返回 "0.0.0.0"(允许局域网访问) pub fn get_bind_address(&self) -> &str { if self.allow_lan_access { "0.0.0.0" } else { "127.0.0.1" } } } /// 代理认证信息 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProxyAuth { pub username: String, #[serde( serialize_with = "crate::utils::crypto::serialize_password", deserialize_with = "crate::utils::crypto::deserialize_password" )] pub password: String, } /// 单个代理配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProxyEntry { pub id: String, // 唯一标识 pub name: String, // 显示名称 pub url: String, // 代理地址 (http://, https://, socks5://) pub auth: Option, // 认证信息 (可选) pub enabled: bool, // 是否启用 pub priority: i32, // 优先级 (数字越小优先级越高) pub tags: Vec, // 标签 (如 "美国", "住宅IP") pub max_accounts: Option, // 最大绑定账号数 (0 = 无限制) pub health_check_url: Option, // 健康检查 URL pub last_check_time: Option, // 上次检查时间 pub is_healthy: bool, // 健康状态 pub latency: Option, // 延迟 (毫秒) [NEW] } /// 代理池配置 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProxyPoolConfig { pub enabled: bool, // 是否启用代理池 // pub mode: ProxyPoolMode, // [REMOVED] 代理池模式,统一为 Hybrid 逻辑 pub proxies: Vec, // 代理列表 pub health_check_interval: u64, // 健康检查间隔 (秒) pub auto_failover: bool, // 自动故障转移 pub strategy: ProxySelectionStrategy, // 代理选择策略 /// 账号到代理的绑定关系 (account_id -> proxy_id),持久化存储 #[serde(default)] pub account_bindings: HashMap, } impl Default for ProxyPoolConfig { fn default() -> Self { Self { enabled: false, // mode: ProxyPoolMode::Global, proxies: Vec::new(), health_check_interval: 300, auto_failover: true, strategy: ProxySelectionStrategy::Priority, account_bindings: HashMap::new(), } } } /// 代理选择策略 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "snake_case")] pub enum ProxySelectionStrategy { /// 轮询: 依次使用 RoundRobin, /// 随机: 随机选择 Random, /// 优先级: 按 priority 字段排序 Priority, /// 最少连接: 选择当前使用最少的代理 LeastConnections, /// 加权轮询: 根据健康状态和优先级 WeightedRoundRobin, } #[cfg(test)] mod tests { use super::*; #[test] fn test_normalize_proxy_url() { // 测试已有协议 assert_eq!(normalize_proxy_url("http://127.0.0.1:7890"), "http://127.0.0.1:7890"); assert_eq!(normalize_proxy_url("https://proxy.com"), "https://proxy.com"); assert_eq!(normalize_proxy_url("socks5://127.0.0.1:1080"), "socks5://127.0.0.1:1080"); assert_eq!(normalize_proxy_url("socks5h://127.0.0.1:1080"), "socks5h://127.0.0.1:1080"); // 测试缺少协议(默认补全 http://) assert_eq!(normalize_proxy_url("127.0.0.1:7890"), "http://127.0.0.1:7890"); assert_eq!(normalize_proxy_url("localhost:1082"), "http://localhost:1082"); // 测试边缘情况 assert_eq!(normalize_proxy_url(""), ""); assert_eq!(normalize_proxy_url(" "), ""); } }