gephyr 1.16.18

Gephyr is a headless local AI relay/proxy API handling OpenAI, Claude, and Gemini-compatible APIs
Documentation
use serde_json;
use std::fs;

use crate::models::AppConfig;
use crate::modules::auth::account::get_data_dir;

const CONFIG_FILE: &str = "config.json";
pub fn load_app_config() -> Result<AppConfig, String> {
    let data_dir = get_data_dir()?;

    let config_path = data_dir.join(CONFIG_FILE);

    if !config_path.exists() {
        let config = AppConfig::new();
        let _ = save_app_config(&config);
        return Ok(config);
    }

    let content = fs::read_to_string(&config_path)
        .map_err(|e| format!("failed_to_read_config_file: {}", e))?;

    let mut v: serde_json::Value = serde_json::from_str(&content)
        .map_err(|e| format!("failed_to_parse_config_file: {}", e))?;

    let mut modified = false;
    if let Some(proxy) = v.get_mut("proxy") {
        if let Some(auth_mode) = proxy.get("auth_mode").and_then(|m| m.as_str()) {
            if auth_mode.eq_ignore_ascii_case("auto") {
                if let Some(proxy_obj) = proxy.as_object_mut() {
                    proxy_obj.insert(
                        "auth_mode".to_string(),
                        serde_json::Value::String("strict".to_string()),
                    );
                    modified = true;
                    tracing::warn!(
                        "Deprecated proxy.auth_mode='auto' found in config; migrated to 'strict'"
                    );
                }
            }
        }

        let mut custom_mapping = proxy
            .get("custom_mapping")
            .and_then(|m| m.as_object())
            .cloned()
            .unwrap_or_default();
        if let Some(anthropic) = proxy
            .get_mut("anthropic_mapping")
            .and_then(|m| m.as_object_mut())
        {
            for (k, v) in anthropic.iter() {
                if !k.ends_with("-series") && !custom_mapping.contains_key(k) {
                    custom_mapping.insert(k.clone(), v.clone());
                }
            }
            proxy.as_object_mut().unwrap().remove("anthropic_mapping");
            modified = true;
        }
        if let Some(openai) = proxy
            .get_mut("openai_mapping")
            .and_then(|m| m.as_object_mut())
        {
            for (k, v) in openai.iter() {
                if !k.ends_with("-series") && !custom_mapping.contains_key(k) {
                    custom_mapping.insert(k.clone(), v.clone());
                }
            }
            proxy.as_object_mut().unwrap().remove("openai_mapping");
            modified = true;
        }

        if modified {
            proxy.as_object_mut().unwrap().insert(
                "custom_mapping".to_string(),
                serde_json::Value::Object(custom_mapping),
            );
        }
    }

    let config: AppConfig = serde_json::from_value(v)
        .map_err(|e| format!("failed_to_convert_config_after_migration: {}", e))?;
    if modified {
        let _ = save_app_config(&config);
    }

    Ok(config)
}
pub fn save_app_config(config: &AppConfig) -> Result<(), String> {
    let data_dir = get_data_dir()?;
    let config_path = data_dir.join(CONFIG_FILE);

    let content = serde_json::to_string_pretty(config)
        .map_err(|e| format!("failed_to_serialize_config: {}", e))?;

    fs::write(&config_path, content).map_err(|e| format!("failed_to_save_config: {}", e))
}