{"repo": "anyujia/yode", "files": {"crates/yode-llm/src/registry.rs": {"3c93c410cf91764441efa0ebc2deb425b1cc930a": {"t": 1785768236.4450886, "n": 11369, "h": "59955f7da592361a", "c": "use std::collections::HashMap;\nuse std::sync::Arc;\n\nuse parking_lot::RwLock;\n\nuse crate::provider::LlmProvider;\n\n/// Known provider info for auto-detection.\npub struct ProviderInfo {\n pub name: &'static str,\n pub format: &'static str,\n pub env_keys: &'static [&'static str],\n pub default_base_url: &'static str,\n pub default_models: &'static [&'static str],\n}\n\n/// Built-in provider catalog.\npub const KNOWN_PROVIDERS: &[ProviderInfo] = &[\n ProviderInfo {\n name: \"anthropic\",\n format: \"anthropic\",\n env_keys: &[\"ANTHROPIC_API_KEY\", \"ANTHROPIC_AUTH_TOKEN\"],\n default_base_url: \"https://api.anthropic.com\",\n default_models: &[\n \"claude-sonnet-4-20250514\",\n \"claude-opus-4-20250514\",\n \"claude-haiku-4-20250414\",\n ],\n },\n ProviderInfo {\n name: \"openai\",\n format: \"openai\",\n env_keys: &[\"OPENAI_API_KEY\"],\n default_base_url: \"https://api.openai.com/v1\",\n default_models: &[\n \"gpt-4o\",\n \"gpt-4o-mini\",\n \"gpt-4-turbo\",\n \"o1\",\n \"o1-mini\",\n \"o3-mini\",\n ],\n },\n ProviderInfo {\n name: \"google\",\n format: \"gemini\",\n env_keys: &[\"GOOGLE_API_KEY\", \"GEMINI_API_KEY\"],\n default_base_url: \"https://generativelanguage.googleapis.com/v1beta\",\n default_models: &[\"gemini-2.5-pro\", \"gemini-2.5-flash\", \"gemini-2.0-flash\"],\n },\n ProviderInfo {\n name: \"deepseek\",\n format: \"openai\",\n env_keys: &[\"DEEPSEEK_API_KEY\"],\n default_base_url: \"https://api.deepseek.com/v1\",\n default_models: &[\"deepseek-chat\", \"deepseek-reasoner\"],\n },\n ProviderInfo {\n name: \"groq\",\n format: \"openai\",\n env_keys: &[\"GROQ_API_KEY\"],\n default_base_url: \"https://api.groq.com/openai/v1\",\n default_models: &[\n \"llama-3.3-70b-versatile\",\n \"llama-3.1-8b-instant\",\n \"mixtral-8x7b-32768\",\n ],\n },\n ProviderInfo {\n name: \"mistral\",\n format: \"openai\",\n env_keys: &[\"MISTRAL_API_KEY\"],\n default_base_url: \"https://api.mistral.ai/v1\",\n default_models: &[\n \"mistral-large-latest\",\n \"mistral-medium-latest\",\n \"codestral-latest\",\n ],\n },\n ProviderInfo {\n name: \"xai\",\n format: \"openai\",\n env_keys: &[\"XAI_API_KEY\"],\n default_base_url: \"https://api.x.ai/v1\",\n default_models: &[\"grok-3\", \"grok-3-mini\", \"grok-2\"],\n },\n ProviderInfo {\n name: \"openrouter\",\n format: \"openai\",\n env_keys: &[\"OPENROUTER_API_KEY\"],\n default_base_url: \"https://openrouter.ai/api/v1\",\n default_models: &[],\n },\n ProviderInfo {\n name: \"ollama\",\n format: \"openai\",\n env_keys: &[],\n default_base_url: \"http://localhost:11434/v1\",\n default_models: &[\"llama3.1\", \"qwen2.5-coder\", \"deepseek-coder-v2\"],\n },\n ProviderInfo {\n name: \"together\",\n format: \"openai\",\n env_keys: &[\"TOGETHER_API_KEY\"],\n default_base_url: \"https://api.together.xyz/v1\",\n default_models: &[],\n },\n ProviderInfo {\n name: \"fireworks\",\n format: \"openai\",\n env_keys: &[\"FIREWORKS_API_KEY\"],\n default_base_url: \"https://api.fireworks.ai/inference/v1\",\n default_models: &[],\n },\n ProviderInfo {\n name: \"perplexity\",\n format: \"openai\",\n env_keys: &[\"PERPLEXITY_API_KEY\"],\n default_base_url: \"https://api.perplexity.ai\",\n default_models: &[\"sonar-pro\", \"sonar\"],\n },\n ProviderInfo {\n name: \"cerebras\",\n format: \"openai\",\n env_keys: &[\"CEREBRAS_API_KEY\"],\n default_base_url: \"https://api.cerebras.ai/v1\",\n default_models: &[\"llama-3.3-70b\"],\n },\n ProviderInfo {\n name: \"azure\",\n format: \"openai\",\n env_keys: &[\"AZURE_OPENAI_API_KEY\"],\n default_base_url: \"\",\n default_models: &[],\n },\n // ── 国内提供商 ──\n ProviderInfo {\n name: \"qwen\",\n format: \"openai\",\n env_keys: &[\"DASHSCOPE_API_KEY\", \"QWEN_API_KEY\"],\n default_base_url: \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n default_models: &[\n \"qwen3.5-plus\",\n \"qwen-max\",\n \"qwen-plus\",\n \"qwen-turbo\",\n \"qwen3-coder-30b-a3b-instruct\",\n ],\n },\n ProviderInfo {\n name: \"alibaba-coding\",\n format: \"openai\",\n env_keys: &[\"ALIBABA_CODING_PLAN_API_KEY\"],\n default_base_url: \"https://coding.dashscope.aliyuncs.com/v1\",\n default_models: &[\n \"qwen3.5-plus\",\n \"qwen3-coder-next\",\n \"kimi-k2.5\",\n \"MiniMax-M2.5\",\n \"glm-5\",\n ],\n },\n ProviderInfo {\n name: \"zhipu\",\n format: \"openai\",\n env_keys: &[\"ZHIPU_API_KEY\", \"GLM_API_KEY\"],\n default_base_url: \"https://open.bigmodel.cn/api/paas/v4\",\n default_models: &[\"glm-5\", \"glm-4.7\", \"glm-4.5v\", \"glm-4.5-air\"],\n },\n ProviderInfo {\n name: \"moonshot\",\n format: \"openai\",\n env_keys: &[\"MOONSHOT_API_KEY\", \"KIMI_API_KEY\"],\n default_base_url: \"https://api.moonshot.cn/v1\",\n default_models: &[\"kimi-k2.5\", \"kimi-k2-thinking\", \"kimi-k2-thinking-turbo\"],\n },\n ProviderInfo {\n name: \"kimi-coding\",\n format: \"anthropic\",\n env_keys: &[\"KIMI_API_KEY\"],\n default_base_url: \"https://api.kimi.com/coding/v1\",\n default_models: &[\"kimi-k2-thinking\", \"k2p5\"],\n },\n ProviderInfo {\n name: \"doubao\",\n format: \"openai\",\n env_keys: &[\"ARK_API_KEY\", \"DOUBAO_API_KEY\"],\n default_base_url: \"https://ark.cn-beijing.volces.com/api/v3\",\n default_models: &[\"doubao-pro-256k\", \"doubao-pro-32k\", \"doubao-lite-32k\"],\n },\n ProviderInfo {\n name: \"minimax\",\n format: \"anthropic\",\n env_keys: &[\"MINIMAX_API_KEY\"],\n default_base_url: \"https://api.minimaxi.com/anthropic/v1\",\n default_models: &[\"MiniMax-M2.7\", \"MiniMax-M2.5\", \"MiniMax-M2.1\", \"MiniMax-M2\"],\n },\n ProviderInfo {\n name: \"siliconflow\",\n format: \"openai\",\n env_keys: &[\"SILICONFLOW_API_KEY\", \"SF_API_KEY\"],\n default_base_url: \"https://api.siliconflow.cn/v1\",\n default_models: &[\n \"deepseek-ai/DeepSeek-V3\",\n \"Qwen/Qwen2.5-72B-Instruct\",\n \"THUDM/GLM-4-32B-0414\",\n ],\n },\n ProviderInfo {\n name: \"yi\",\n format: \"openai\",\n env_keys: &[\"YI_API_KEY\", \"LINGYIWANWU_API_KEY\"],\n default_base_url: \"https://api.lingyiwanwu.com/v1\",\n default_models: &[\"yi-lightning\", \"yi-large\", \"yi-medium\"],\n },\n ProviderInfo {\n name: \"baichuan\",\n format: \"openai\",\n env_keys: &[\"BAICHUAN_API_KEY\"],\n default_base_url: \"https://api.baichuan-ai.com/v1\",\n default_models: &[\"Baichuan4\", \"Baichuan3-Turbo\", \"Baichuan3-Turbo-128k\"],\n },\n ProviderInfo {\n name: \"spark\",\n format: \"openai\",\n env_keys: &[\"SPARK_API_KEY\", \"IFLYTEK_API_KEY\"],\n default_base_url: \"https://spark-api-open.xf-yun.com/v1\",\n default_models: &[\"generalv3.5\", \"4.0Ultra\"],\n },\n ProviderInfo {\n name: \"stepfun\",\n format: \"openai\",\n env_keys: &[\"STEPFUN_API_KEY\"],\n default_base_url: \"https://api.stepfun.com/v1\",\n default_models: &[\"step-3.5-flash\", \"step-2-16k\", \"step-1-32k\"],\n },\n ProviderInfo {\n name: \"ernie\",\n format: \"openai\",\n env_keys: &[\"QIANFAN_API_KEY\", \"ERNIE_API_KEY\"],\n default_base_url: \"https://qianfan.baidubce.com/v2\",\n default_models: &[\"ernie-4.0-8k\", \"ernie-3.5-8k\", \"ernie-speed-128k\"],\n },\n ProviderInfo {\n name: \"hunyuan\",\n format: \"openai\",\n env_keys: &[\"HUNYUAN_API_KEY\"],\n default_base_url: \"https://api.hunyuan.cloud.tencent.com/v1\",\n default_models: &[\"hunyuan-pro\", \"hunyuan-standard\", \"hunyuan-lite\"],\n },\n ProviderInfo {\n name: \"tencent-coding\",\n format: \"openai\",\n env_keys: &[\"TENCENT_CODING_PLAN_API_KEY\"],\n default_base_url: \"https://api.lkeap.cloud.tencent.com/coding/v3\",\n default_models: &[\n \"hunyuan-2.0-instruct\",\n \"kimi-k2.5\",\n \"hunyuan-t1\",\n \"tc-code-latest\",\n ],\n },\n ProviderInfo {\n name: \"bailing\",\n format: \"openai\",\n env_keys: &[\"BAILING_API_TOKEN\"],\n default_base_url: \"https://api.tbox.cn/api/llm/v1/chat/completions\",\n default_models: &[\"Ling-1T\", \"Ring-1T\"],\n },\n ProviderInfo {\n name: \"iflow\",\n format: \"openai\",\n env_keys: &[\"IFLOW_API_KEY\"],\n default_base_url: \"https://apis.iflow.cn/v1\",\n default_models: &[\"deepseek-r1\", \"deepseek-v3\", \"kimi-k2\", \"qwen3-max-preview\"],\n },\n];\n\n/// Look up a known provider by name.\npub fn find_provider_info(name: &str) -> Option<&'static ProviderInfo> {\n KNOWN_PROVIDERS.iter().find(|p| p.name == name)\n}\n\n/// Detect available providers from environment variables.\npub fn detect_available_providers() -> Vec<&'static ProviderInfo> {\n KNOWN_PROVIDERS\n .iter()\n .filter(|p| {\n // Ollama is always \"available\" (local)\n if p.name == \"ollama\" {\n return true;\n }\n p.env_keys.iter().any(|key| std::env::var(key).is_ok())\n })\n .collect()\n}\n\npub struct ProviderRegistry {\n providers: RwLock>>,\n}\n\nimpl ProviderRegistry {\n pub fn new() -> Self {\n Self {\n providers: RwLock::new(HashMap::new()),\n }\n }\n\n pub fn register(&self, provider: Arc) {\n let name = provider.name().to_string();\n self.providers.write().insert(name, provider);\n }\n\n pub fn get(&self, name: &str) -> Option> {\n self.providers.read().get(name).cloned()\n }\n\n pub fn list(&self) -> Vec {\n let mut names: Vec = self.providers.read().keys().cloned().collect();\n names.sort();\n names\n }\n\n pub fn remove(&self, name: &str) -> Option> {\n self.providers.write().remove(name)\n }\n\n pub fn contains(&self, name: &str) -> bool {\n self.providers.read().contains_key(name)\n }\n}\n\nimpl Default for ProviderRegistry {\n fn default() -> Self {\n Self::new()\n }\n}\n\n#[cfg(test)]\nmod tests {\n use std::panic::{catch_unwind, AssertUnwindSafe};\n use std::sync::Arc;\n\n use crate::MockProvider;\n\n use super::ProviderRegistry;\n\n #[test]\n fn registry_recovers_from_poisoned_lock() {\n let registry = ProviderRegistry::new();\n\n let poisoned = catch_unwind(AssertUnwindSafe(|| {\n let _guard = registry.providers.write();\n panic!(\"poison provider registry for test\");\n }));\n assert!(poisoned.is_err());\n\n registry.register(Arc::new(MockProvider::new(\"mock\")));\n\n assert!(registry.contains(\"mock\"));\n assert_eq!(registry.list(), vec![\"mock\".to_string()]);\n assert!(registry.get(\"mock\").is_some());\n assert!(registry.remove(\"mock\").is_some());\n assert!(!registry.contains(\"mock\"));\n }\n}\n"}}}}