use serde::{Deserialize, Serialize};
use std::env;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
#[serde(skip_serializing_if = "Option::is_none")]
pub openai: Option<ProviderConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub anthropic: Option<ProviderConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deepseek: Option<ProviderConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub glm: Option<ProviderConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub qwen: Option<ProviderConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub kimi: Option<ProviderConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderConfig {
pub api_key: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_url: Option<String>,
#[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 {
pub fn from_env() -> Self {
let mut config = Config::default();
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()),
});
}
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()),
});
}
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()),
});
}
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()),
});
}
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()),
});
}
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
}
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,
}
}
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 {
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,
}
}
pub fn effective_base_url(&self, provider: &str) -> Option<String> {
self.base_url.clone().or_else(|| Self::default_base_url(provider))
}
pub fn effective_timeout_ms(&self) -> u64 {
self.timeout_ms.unwrap_or(30000) }
}