llm-connector 0.1.0

A lightweight Rust library for protocol adaptation across multiple LLM providers. Focuses solely on converting between different LLM provider APIs and providing a unified OpenAI-compatible interface.
Documentation
//! Configuration management for llm-connector

use serde::{Deserialize, Serialize};
use std::env;

/// Main configuration for llm-connector
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    /// OpenAI configuration
    #[serde(skip_serializing_if = "Option::is_none")]
    pub openai: Option<ProviderConfig>,
    
    /// Anthropic configuration
    #[serde(skip_serializing_if = "Option::is_none")]
    pub anthropic: Option<ProviderConfig>,
    
    /// DeepSeek configuration
    #[serde(skip_serializing_if = "Option::is_none")]
    pub deepseek: Option<ProviderConfig>,
    
    /// GLM (Zhipu) configuration
    #[serde(skip_serializing_if = "Option::is_none")]
    pub glm: Option<ProviderConfig>,
    
    /// Qwen (Alibaba) configuration
    #[serde(skip_serializing_if = "Option::is_none")]
    pub qwen: Option<ProviderConfig>,
    
    /// Kimi (Moonshot) configuration
    #[serde(skip_serializing_if = "Option::is_none")]
    pub kimi: Option<ProviderConfig>,
}

/// Configuration for a specific provider
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderConfig {
    /// API key for the provider
    pub api_key: String,
    
    /// Base URL for the provider API
    #[serde(skip_serializing_if = "Option::is_none")]
    pub base_url: Option<String>,
    
    /// Request timeout in milliseconds
    #[serde(skip_serializing_if = "Option::is_none")]
    pub timeout_ms: Option<u64>,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            openai: None,
            anthropic: None,
            deepseek: None,
            glm: None,
            qwen: None,
            kimi: None,
        }
    }
}

impl Config {
    /// Create configuration from environment variables
    pub fn from_env() -> Self {
        let mut config = Config::default();
        
        // OpenAI
        if let Ok(api_key) = env::var("OPENAI_API_KEY") {
            config.openai = Some(ProviderConfig {
                api_key,
                base_url: env::var("OPENAI_BASE_URL").ok(),
                timeout_ms: env::var("OPENAI_TIMEOUT_MS")
                    .ok()
                    .and_then(|s| s.parse().ok()),
            });
        }
        
        // Anthropic
        if let Ok(api_key) = env::var("ANTHROPIC_API_KEY") {
            config.anthropic = Some(ProviderConfig {
                api_key,
                base_url: env::var("ANTHROPIC_BASE_URL").ok(),
                timeout_ms: env::var("ANTHROPIC_TIMEOUT_MS")
                    .ok()
                    .and_then(|s| s.parse().ok()),
            });
        }
        
        // DeepSeek
        if let Ok(api_key) = env::var("DEEPSEEK_API_KEY") {
            config.deepseek = Some(ProviderConfig {
                api_key,
                base_url: env::var("DEEPSEEK_BASE_URL").ok(),
                timeout_ms: env::var("DEEPSEEK_TIMEOUT_MS")
                    .ok()
                    .and_then(|s| s.parse().ok()),
            });
        }
        
        // GLM (Zhipu)
        if let (Ok(api_key), _) = (
            env::var("GLM_API_KEY").or_else(|_| env::var("ZHIPU_API_KEY")),
            (),
        ) {
            config.glm = Some(ProviderConfig {
                api_key,
                base_url: env::var("GLM_BASE_URL")
                    .or_else(|_| env::var("ZHIPU_BASE_URL"))
                    .ok(),
                timeout_ms: env::var("GLM_TIMEOUT_MS")
                    .or_else(|_| env::var("ZHIPU_TIMEOUT_MS"))
                    .ok()
                    .and_then(|s| s.parse().ok()),
            });
        }
        
        // Qwen (Alibaba)
        if let (Ok(api_key), _) = (
            env::var("QWEN_API_KEY").or_else(|_| env::var("ALIBABA_QWEN_API_KEY")),
            (),
        ) {
            config.qwen = Some(ProviderConfig {
                api_key,
                base_url: env::var("QWEN_BASE_URL")
                    .or_else(|_| env::var("ALIBABA_QWEN_BASE_URL"))
                    .ok(),
                timeout_ms: env::var("QWEN_TIMEOUT_MS")
                    .or_else(|_| env::var("ALIBABA_QWEN_TIMEOUT_MS"))
                    .ok()
                    .and_then(|s| s.parse().ok()),
            });
        }
        
        // Kimi (Moonshot)
        if let (Ok(api_key), _) = (
            env::var("KIMI_API_KEY").or_else(|_| env::var("MOONSHOT_API_KEY")),
            (),
        ) {
            config.kimi = Some(ProviderConfig {
                api_key,
                base_url: env::var("KIMI_BASE_URL")
                    .or_else(|_| env::var("MOONSHOT_BASE_URL"))
                    .ok(),
                timeout_ms: env::var("KIMI_TIMEOUT_MS")
                    .or_else(|_| env::var("MOONSHOT_TIMEOUT_MS"))
                    .ok()
                    .and_then(|s| s.parse().ok()),
            });
        }
        
        config
    }
    
    /// Get provider configuration by name
    pub fn get_provider(&self, name: &str) -> Option<&ProviderConfig> {
        match name {
            "openai" => self.openai.as_ref(),
            "anthropic" => self.anthropic.as_ref(),
            "deepseek" => self.deepseek.as_ref(),
            "glm" | "zhipu" => self.glm.as_ref(),
            "qwen" | "alibaba" => self.qwen.as_ref(),
            "kimi" | "moonshot" => self.kimi.as_ref(),
            _ => None,
        }
    }
    
    /// List all configured providers
    pub fn list_providers(&self) -> Vec<String> {
        let mut providers = Vec::new();
        
        if self.openai.is_some() {
            providers.push("openai".to_string());
        }
        if self.anthropic.is_some() {
            providers.push("anthropic".to_string());
        }
        if self.deepseek.is_some() {
            providers.push("deepseek".to_string());
        }
        if self.glm.is_some() {
            providers.push("glm".to_string());
        }
        if self.qwen.is_some() {
            providers.push("qwen".to_string());
        }
        if self.kimi.is_some() {
            providers.push("kimi".to_string());
        }
        
        providers
    }
}

impl ProviderConfig {
    /// Get the default base URL for a provider
    pub fn default_base_url(provider: &str) -> Option<String> {
        match provider {
            "openai" => Some("https://api.openai.com/v1".to_string()),
            "anthropic" => Some("https://api.anthropic.com".to_string()),
            "deepseek" => Some("https://api.deepseek.com/v1".to_string()),
            "glm" | "zhipu" => Some("https://open.bigmodel.cn/api/paas/v4".to_string()),
            "qwen" | "alibaba" => Some("https://dashscope.aliyuncs.com/compatible-mode/v1".to_string()),
            "kimi" | "moonshot" => Some("https://api.moonshot.cn/v1".to_string()),
            _ => None,
        }
    }
    
    /// Get the effective base URL (configured or default)
    pub fn effective_base_url(&self, provider: &str) -> Option<String> {
        self.base_url.clone().or_else(|| Self::default_base_url(provider))
    }
    
    /// Get the effective timeout (configured or default)
    pub fn effective_timeout_ms(&self) -> u64 {
        self.timeout_ms.unwrap_or(30000) // 30 seconds default
    }
}