use crate::config::ModelConfig;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[allow(dead_code)]
pub enum ConfigFormat {
Json,
Toml,
Yaml,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Backup {
pub agent_name: String,
pub original_config_path: PathBuf,
pub backup_path: PathBuf,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(dead_code)]
pub struct AgentConfigState {
pub agent_name: String,
pub model_name: Option<String>,
pub config_path: PathBuf,
pub last_switched: Option<String>,
pub is_installed: bool,
pub config_exists: bool,
}
pub trait AgentAdapter: Send + Sync {
fn name(&self) -> &str;
fn detect(&self) -> Result<bool>;
fn config_path(&self) -> Result<PathBuf>;
fn backup(&self) -> Result<Backup>;
fn apply(&self, model_config: &ModelConfig) -> Result<()>;
fn restore(&self, backup: &Backup) -> Result<()>;
fn current_model(&self) -> Result<Option<String>>;
fn validate_compatibility(&self, _fields: &HashSet<String>) -> Vec<FieldWarning> {
vec![] }
}
#[derive(Debug, Clone)]
pub struct FieldWarning {
pub field_name: String,
pub level: WarningLevel,
pub message: String,
}
#[derive(Debug, Clone, PartialEq)]
pub enum WarningLevel {
Info,
Warning,
Error,
}
pub struct IncompatibleFieldDetector {
incompatible_fields: HashSet<String>,
}
impl IncompatibleFieldDetector {
pub fn new() -> Self {
let mut incompatible_fields = HashSet::new();
incompatible_fields.insert("custom_headers".to_string());
incompatible_fields.insert("custom_proxy".to_string());
incompatible_fields.insert("proxy_url".to_string());
incompatible_fields.insert("timeout_ms".to_string());
incompatible_fields.insert("max_retries".to_string());
incompatible_fields.insert("retry_delay".to_string());
incompatible_fields.insert("debug_mode".to_string());
incompatible_fields.insert("verbose_logging".to_string());
Self {
incompatible_fields,
}
}
pub fn detect(&self, fields: &HashSet<String>, agent_name: &str) -> Vec<FieldWarning> {
let mut warnings = Vec::new();
for field in fields {
if self.incompatible_fields.contains(field) {
warnings.push(FieldWarning {
field_name: field.clone(),
level: WarningLevel::Warning,
message: format!(
"字段 '{}' 可能不被 {} 支持。切换后该字段可能被忽略或导致意外行为。",
field, agent_name
),
});
}
}
warnings
}
pub fn detect_for_agent(
&self,
fields: &HashSet<String>,
agent_name: &str,
) -> Vec<FieldWarning> {
let mut warnings = self.detect(fields, agent_name);
let agent_specific = self.get_agent_specific_incompatible_fields(agent_name);
for field in fields {
if agent_specific.contains(field) {
warnings.push(FieldWarning {
field_name: field.clone(),
level: WarningLevel::Warning,
message: format!(
"字段 '{}' 是 {} 特有的配置,切换到其他工具时可能丢失。",
field, agent_name
),
});
}
}
warnings
}
fn get_agent_specific_incompatible_fields(&self, agent_name: &str) -> HashSet<String> {
match agent_name {
"claude-code" => {
let mut fields = HashSet::new();
fields.insert("includeCoAuthoredBy".to_string());
fields.insert("anthropicVersion".to_string());
fields
}
"codex" => {
let mut fields = HashSet::new();
fields.insert("model_provider".to_string());
fields.insert("wire_api".to_string());
fields
}
"gemini-cli" => {
let mut fields = HashSet::new();
fields.insert("defaultModel".to_string());
fields.insert("project_id".to_string());
fields
}
_ => HashSet::new(),
}
}
}
impl Default for IncompatibleFieldDetector {
fn default() -> Self {
Self::new()
}
}
pub fn format_field_warnings(warnings: &[FieldWarning]) -> String {
if warnings.is_empty() {
return String::new();
}
let mut output = String::from("⚠️ 配置兼容性警告:\n");
for warning in warnings {
let icon = match warning.level {
WarningLevel::Info => "ℹ️",
WarningLevel::Warning => "⚠️",
WarningLevel::Error => "❌",
};
output.push_str(&format!(
" {} {}: {}\n",
icon, warning.field_name, warning.message
));
}
output.pop(); output
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field_detection() {
let detector = IncompatibleFieldDetector::new();
let mut fields = HashSet::new();
fields.insert("custom_headers".to_string());
fields.insert("base_url".to_string());
let warnings = detector.detect(&fields, "claude-code");
assert_eq!(warnings.len(), 1);
assert_eq!(warnings[0].field_name, "custom_headers");
assert_eq!(warnings[0].level, WarningLevel::Warning);
}
#[test]
fn test_agent_specific_fields() {
let detector = IncompatibleFieldDetector::new();
let mut fields = HashSet::new();
fields.insert("includeCoAuthoredBy".to_string());
fields.insert("base_url".to_string());
let warnings = detector.detect_for_agent(&fields, "claude-code");
assert_eq!(warnings.len(), 1);
assert_eq!(warnings[0].field_name, "includeCoAuthoredBy");
}
#[test]
fn test_format_warnings() {
let warnings = vec![FieldWarning {
field_name: "custom_headers".to_string(),
level: WarningLevel::Warning,
message: "可能不被支持".to_string(),
}];
let formatted = format_field_warnings(&warnings);
assert!(formatted.contains("配置兼容性警告"));
assert!(formatted.contains("custom_headers"));
}
}