diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index e50f1e1a..0c07eb1e 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -173,8 +173,8 @@ impl ProviderKind { Self::Novita, Self::Fireworks, Self::Siliconflow, - Self::SiliconflowCN, Self::Arcee, + Self::SiliconflowCN, Self::Moonshot, Self::Sglang, Self::Vllm, @@ -187,63 +187,19 @@ impl ProviderKind { #[must_use] pub fn as_str(self) -> &'static str { - match self { - Self::Deepseek => "deepseek", - Self::NvidiaNim => "nvidia-nim", - Self::Openai => "openai", - Self::Atlascloud => "atlascloud", - Self::WanjieArk => "wanjie-ark", - Self::Volcengine => "volcengine", - Self::Openrouter => "openrouter", - Self::XiaomiMimo => "xiaomi-mimo", - Self::Novita => "novita", - Self::Fireworks => "fireworks", - Self::Siliconflow => "siliconflow", - Self::SiliconflowCN => "siliconflow-CN", - Self::Arcee => "arcee", - Self::Moonshot => "moonshot", - Self::Sglang => "sglang", - Self::Vllm => "vllm", - Self::Ollama => "ollama", - Self::Huggingface => "huggingface", - Self::Together => "together", - Self::OpenaiCodex => "openai-codex", - Self::Anthropic => "anthropic", - } + self.provider().id() } #[must_use] pub fn parse(value: &str) -> Option { - match value.trim().to_ascii_lowercase().as_str() { - "deepseek" | "deep-seek" | "deepseek-cn" | "deepseek_china" | "deepseekcn" - | "deepseek-china" => Some(Self::Deepseek), - "nvidia" | "nvidia-nim" | "nvidia_nim" | "nim" => Some(Self::NvidiaNim), - "openai" | "open-ai" => Some(Self::Openai), - "atlascloud" | "atlas-cloud" | "atlas_cloud" | "atlas" => Some(Self::Atlascloud), - "wanjie" | "wanjie-ark" | "wanjie_ark" | "ark-wanjie" | "ark_wanjie" | "wanjieark" - | "wanjie-maas" | "wanjie_maas" | "wanjiemaas" => Some(Self::WanjieArk), - "volcengine" | "volcengine-ark" | "volcengine_ark" | "ark" | "volc-ark" - | "volcengineark" => Some(Self::Volcengine), - "openrouter" | "open_router" => Some(Self::Openrouter), - "xiaomi-mimo" | "xiaomi_mimo" | "xiaomimimo" | "mimo" | "xiaomi" => { - Some(Self::XiaomiMimo) - } - "novita" => Some(Self::Novita), - "fireworks" | "fireworks-ai" => Some(Self::Fireworks), - "siliconflow" | "silicon-flow" | "silicon_flow" => Some(Self::Siliconflow), - "siliconflow-cn" | "siliconflow-CN" => Some(Self::SiliconflowCN), - "arcee" | "arcee-ai" | "arcee_ai" => Some(Self::Arcee), - "moonshot" | "moonshot-ai" | "kimi" | "kimi-k2" => Some(Self::Moonshot), - "sglang" | "sg-lang" => Some(Self::Sglang), - "vllm" | "v-llm" => Some(Self::Vllm), - "ollama" | "ollama-local" => Some(Self::Ollama), - "huggingface" | "hugging-face" | "hugging_face" | "hf" => Some(Self::Huggingface), - "together" | "together-ai" | "together_ai" => Some(Self::Together), - "anthropic" | "claude" => Some(Self::Anthropic), - "openai-codex" | "openai_codex" | "openaicodex" | "codex" | "chatgpt" - | "chatgpt-codex" | "chatgpt_codex" | "chatgptcodex" => Some(Self::OpenaiCodex), - _ => None, - } + let trimmed = value.trim(); + provider::all_providers() + .iter() + .find(|p| { + trimmed.eq_ignore_ascii_case(p.id()) + || p.aliases().iter().any(|a| trimmed.eq_ignore_ascii_case(a)) + }) + .map(|p| p.kind()) } #[must_use] diff --git a/crates/config/src/provider.rs b/crates/config/src/provider.rs index e8312a27..18d7dc55 100644 --- a/crates/config/src/provider.rs +++ b/crates/config/src/provider.rs @@ -56,6 +56,11 @@ pub trait Provider: Send + Sync { /// TOML table key under `[providers.]`. fn provider_config_key(&self) -> &'static str; + /// Alternate names accepted during provider resolution. + fn aliases(&self) -> &'static [&'static str] { + &[] + } + /// Wire format used by the provider. fn wire(&self) -> WireFormat { WireFormat::ChatCompletions @@ -66,16 +71,22 @@ macro_rules! provider { ( $struct_name:ident, $kind:ident, + $id:literal, $display_name:literal, $base_url:ident, $model:ident, [$($env_var:literal),* $(,)?], - $config_key:literal + $config_key:literal, + aliases: [$($alias:literal),* $(,)?] ) => { /// Zero-sized metadata entry for this built-in provider. pub struct $struct_name; impl Provider for $struct_name { + fn id(&self) -> &'static str { + $id + } + fn kind(&self) -> ProviderKind { ProviderKind::$kind } @@ -99,6 +110,10 @@ macro_rules! provider { fn provider_config_key(&self) -> &'static str { $config_key } + + fn aliases(&self) -> &'static [&'static str] { + &[$($alias),*] + } } }; } @@ -106,42 +121,51 @@ macro_rules! provider { provider!( Deepseek, Deepseek, + "deepseek", "DeepSeek", DEFAULT_DEEPSEEK_BASE_URL, DEFAULT_DEEPSEEK_MODEL, ["DEEPSEEK_API_KEY"], - "deepseek" + "deepseek", + aliases: ["deep-seek", "deepseek-cn", "deepseek_china", "deepseekcn", "deepseek-china"] ); provider!( NvidiaNim, NvidiaNim, + "nvidia-nim", "NVIDIA NIM", DEFAULT_NVIDIA_NIM_BASE_URL, DEFAULT_NVIDIA_NIM_MODEL, ["NVIDIA_API_KEY", "NVIDIA_NIM_API_KEY", "DEEPSEEK_API_KEY"], - "nvidia_nim" + "nvidia_nim", + aliases: ["nvidia", "nvidia_nim", "nim"] ); provider!( Openai, Openai, + "openai", "OpenAI-compatible", DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, ["OPENAI_API_KEY"], - "openai" + "openai", + aliases: ["open-ai"] ); provider!( Atlascloud, Atlascloud, + "atlascloud", "AtlasCloud", DEFAULT_ATLASCLOUD_BASE_URL, DEFAULT_ATLASCLOUD_MODEL, ["ATLASCLOUD_API_KEY"], - "atlascloud" + "atlascloud", + aliases: ["atlas-cloud", "atlas_cloud", "atlas"] ); provider!( WanjieArk, WanjieArk, + "wanjie-ark", "Wanjie Ark", DEFAULT_WANJIE_ARK_BASE_URL, DEFAULT_WANJIE_ARK_MODEL, @@ -150,11 +174,13 @@ provider!( "WANJIE_API_KEY", "WANJIE_MAAS_API_KEY" ], - "wanjie_ark" + "wanjie_ark", + aliases: ["wanjie", "wanjie_ark", "ark-wanjie", "ark_wanjie", "wanjieark", "wanjie-maas", "wanjie_maas", "wanjiemaas"] ); provider!( Volcengine, Volcengine, + "volcengine", "Volcengine Ark", DEFAULT_VOLCENGINE_BASE_URL, DEFAULT_VOLCENGINE_MODEL, @@ -163,20 +189,24 @@ provider!( "VOLCENGINE_ARK_API_KEY", "ARK_API_KEY" ], - "volcengine" + "volcengine", + aliases: ["volcengine-ark", "volcengine_ark", "ark", "volc-ark", "volcengineark"] ); provider!( Openrouter, Openrouter, + "openrouter", "OpenRouter", DEFAULT_OPENROUTER_BASE_URL, DEFAULT_OPENROUTER_MODEL, ["OPENROUTER_API_KEY"], - "openrouter" + "openrouter", + aliases: ["open_router"] ); provider!( XiaomiMimo, XiaomiMimo, + "xiaomi-mimo", "Xiaomi MiMo", DEFAULT_XIAOMI_MIMO_BASE_URL, DEFAULT_XIAOMI_MIMO_MODEL, @@ -187,112 +217,145 @@ provider!( "XIAOMI_API_KEY", "MIMO_API_KEY", ], - "xiaomi_mimo" + "xiaomi_mimo", + aliases: ["xiaomi_mimo", "xiaomimimo", "mimo", "xiaomi"] ); provider!( Novita, Novita, - "Novita", + "novita", + "Novita AI", DEFAULT_NOVITA_BASE_URL, DEFAULT_NOVITA_MODEL, ["NOVITA_API_KEY"], - "novita" + "novita", + aliases: [] ); provider!( Fireworks, Fireworks, - "Fireworks", + "fireworks", + "Fireworks AI", DEFAULT_FIREWORKS_BASE_URL, DEFAULT_FIREWORKS_MODEL, ["FIREWORKS_API_KEY"], - "fireworks" + "fireworks", + aliases: ["fireworks-ai"] ); provider!( Siliconflow, Siliconflow, + "siliconflow", "SiliconFlow", DEFAULT_SILICONFLOW_BASE_URL, DEFAULT_SILICONFLOW_MODEL, ["SILICONFLOW_API_KEY"], - "siliconflow" + "siliconflow", + aliases: ["silicon-flow", "silicon_flow"] ); provider!( SiliconflowCN, SiliconflowCN, - "SiliconFlow CN", + "siliconflow-CN", + "SiliconFlow (China)", DEFAULT_SILICONFLOW_CN_BASE_URL, DEFAULT_SILICONFLOW_MODEL, ["SILICONFLOW_API_KEY"], - "siliconflow_cn" + "siliconflow_cn", + aliases: [ + "silicon-flow-cn", + "silicon-flow-CN", + "silicon_flow_cn", + "silicon_flow_CN", + "siliconflow-china", + ] ); provider!( Arcee, Arcee, - "Arcee", + "arcee", + "Arcee AI", DEFAULT_ARCEE_BASE_URL, DEFAULT_ARCEE_MODEL, ["ARCEE_API_KEY"], - "arcee" + "arcee", + aliases: ["arcee-ai", "arcee_ai"] ); provider!( Moonshot, Moonshot, - "Moonshot", + "moonshot", + "Moonshot/Kimi", DEFAULT_MOONSHOT_BASE_URL, DEFAULT_MOONSHOT_MODEL, ["MOONSHOT_API_KEY", "KIMI_API_KEY"], - "moonshot" + "moonshot", + aliases: ["moonshot-ai", "kimi", "kimi-k2"] ); provider!( Sglang, Sglang, + "sglang", "SGLang", DEFAULT_SGLANG_BASE_URL, DEFAULT_SGLANG_MODEL, ["SGLANG_API_KEY"], - "sglang" + "sglang", + aliases: ["sg-lang"] ); provider!( Vllm, Vllm, + "vllm", "vLLM", DEFAULT_VLLM_BASE_URL, DEFAULT_VLLM_MODEL, ["VLLM_API_KEY"], - "vllm" + "vllm", + aliases: ["v-llm"] ); provider!( Ollama, Ollama, + "ollama", "Ollama", DEFAULT_OLLAMA_BASE_URL, DEFAULT_OLLAMA_MODEL, ["OLLAMA_API_KEY"], - "ollama" + "ollama", + aliases: ["ollama-local"] ); provider!( Huggingface, Huggingface, + "huggingface", "Hugging Face", DEFAULT_HUGGINGFACE_BASE_URL, DEFAULT_HUGGINGFACE_MODEL, ["HUGGINGFACE_API_KEY", "HF_TOKEN"], - "huggingface" + "huggingface", + aliases: ["hugging-face", "hugging_face", "hf"] ); provider!( Together, Together, + "together", "Together AI", DEFAULT_TOGETHER_BASE_URL, DEFAULT_TOGETHER_MODEL, ["TOGETHER_API_KEY"], - "together" + "together", + aliases: ["together-ai", "together_ai"] ); /// OpenAI Codex / ChatGPT OAuth provider using the Responses API. pub struct OpenaiCodex; impl Provider for OpenaiCodex { + fn id(&self) -> &'static str { + "openai-codex" + } + fn kind(&self) -> ProviderKind { ProviderKind::OpenaiCodex } @@ -317,6 +380,18 @@ impl Provider for OpenaiCodex { "openai_codex" } + fn aliases(&self) -> &'static [&'static str] { + &[ + "openai_codex", + "openaicodex", + "codex", + "chatgpt", + "chatgpt-codex", + "chatgpt_codex", + "chatgptcodex", + ] + } + fn wire(&self) -> WireFormat { WireFormat::Responses } @@ -326,6 +401,10 @@ impl Provider for OpenaiCodex { pub struct Anthropic; impl Provider for Anthropic { + fn id(&self) -> &'static str { + "anthropic" + } + fn kind(&self) -> ProviderKind { ProviderKind::Anthropic } @@ -389,8 +468,8 @@ static PROVIDER_REGISTRY: [&dyn Provider; 21] = [ &NOVITA, &FIREWORKS, &SILICONFLOW, - &SILICONFLOW_CN, &ARCEE, + &SILICONFLOW_CN, &MOONSHOT, &SGLANG, &VLLM, @@ -426,27 +505,9 @@ pub fn resolve_provider(id_or_alias: &str) -> Option<&'static dyn Provider> { /// Return metadata for a known provider kind. #[must_use] pub fn provider_for_kind(kind: ProviderKind) -> &'static dyn Provider { - match kind { - ProviderKind::Deepseek => &DEEPSEEK, - ProviderKind::NvidiaNim => &NVIDIA_NIM, - ProviderKind::Openai => &OPENAI, - ProviderKind::Atlascloud => &ATLASCLOUD, - ProviderKind::WanjieArk => &WANJIE_ARK, - ProviderKind::Volcengine => &VOLCENGINE, - ProviderKind::Openrouter => &OPENROUTER, - ProviderKind::XiaomiMimo => &XIAOMI_MIMO, - ProviderKind::Novita => &NOVITA, - ProviderKind::Fireworks => &FIREWORKS, - ProviderKind::Siliconflow => &SILICONFLOW, - ProviderKind::SiliconflowCN => &SILICONFLOW_CN, - ProviderKind::Arcee => &ARCEE, - ProviderKind::Moonshot => &MOONSHOT, - ProviderKind::Sglang => &SGLANG, - ProviderKind::Vllm => &VLLM, - ProviderKind::Ollama => &OLLAMA, - ProviderKind::Huggingface => &HUGGINGFACE, - ProviderKind::Together => &TOGETHER, - ProviderKind::OpenaiCodex => &OPENAI_CODEX, - ProviderKind::Anthropic => &ANTHROPIC, - } + PROVIDER_REGISTRY + .iter() + .find(|p| p.kind() == kind) + .copied() + .expect("ProviderKind variant missing from PROVIDER_REGISTRY") } diff --git a/crates/tui/src/config.rs b/crates/tui/src/config.rs index 2e0dfdb1..56d29dec 100644 --- a/crates/tui/src/config.rs +++ b/crates/tui/src/config.rs @@ -220,126 +220,105 @@ impl ApiProvider { #[must_use] pub fn parse(value: &str) -> Option { - match value.trim().to_ascii_lowercase().as_str() { - "deepseek" | "deep-seek" => Some(Self::Deepseek), - "deepseek-cn" | "deepseek_china" | "deepseekcn" | "deepseek-china" => { - Some(Self::DeepseekCN) - } - "nvidia" | "nvidia-nim" | "nvidia_nim" | "nim" => Some(Self::NvidiaNim), - "openai" | "open-ai" => Some(Self::Openai), - "atlascloud" | "atlas-cloud" | "atlas_cloud" | "atlas" => Some(Self::Atlascloud), - "wanjie" | "wanjie-ark" | "wanjie_ark" | "ark-wanjie" | "ark_wanjie" | "wanjieark" - | "wanjie-maas" | "wanjie_maas" | "wanjiemaas" => Some(Self::WanjieArk), - "volcengine" | "volcengine-ark" | "volcengine_ark" | "ark" | "volc-ark" - | "volcengineark" => Some(Self::Volcengine), - "openrouter" | "open_router" => Some(Self::Openrouter), - "xiaomi-mimo" | "xiaomi_mimo" | "xiaomimimo" | "mimo" | "xiaomi" => { - Some(Self::XiaomiMimo) - } - "novita" => Some(Self::Novita), - "fireworks" | "fireworks-ai" => Some(Self::Fireworks), - "siliconflow" | "silicon-flow" | "silicon_flow" => Some(Self::Siliconflow), - "siliconflow-cn" | "siliconflow-CN" | "silicon-flow-cn" | "silicon-flow-CN" - | "silicon_flow_cn" | "silicon_flow_CN" | "siliconflow-china" => { - Some(Self::SiliconflowCn) - } - "arcee" | "arcee-ai" | "arcee_ai" => Some(Self::Arcee), - "moonshot" | "moonshot-ai" | "kimi" | "kimi-k2" => Some(Self::Moonshot), - "sglang" | "sg-lang" => Some(Self::Sglang), - "vllm" | "v-llm" => Some(Self::Vllm), - "ollama" | "ollama-local" => Some(Self::Ollama), - "huggingface" | "hugging-face" | "hugging_face" | "hf" => Some(Self::Huggingface), - "together" | "together-ai" | "together_ai" => Some(Self::Together), - "anthropic" | "claude" => Some(Self::Anthropic), - "openai-codex" | "openai_codex" | "openaicodex" | "codex" | "chatgpt" - | "chatgpt-codex" | "chatgpt_codex" | "chatgptcodex" => Some(Self::OpenaiCodex), - _ => None, + let trimmed = value.trim(); + // ApiProvider-specific: "deepseek-cn" is a legacy variant here, + // while ProviderKind treats it as a Deepseek alias. + if trimmed.eq_ignore_ascii_case("deepseek-cn") + || trimmed.eq_ignore_ascii_case("deepseek_china") + || trimmed.eq_ignore_ascii_case("deepseekcn") + || trimmed.eq_ignore_ascii_case("deepseek-china") + { + return Some(Self::DeepseekCN); } + codewhale_config::ProviderKind::parse(value).map(Self::from_kind) } #[must_use] pub fn as_str(self) -> &'static str { - match self { - Self::Deepseek => "deepseek", - Self::DeepseekCN => "deepseek-cn", - Self::NvidiaNim => "nvidia-nim", - Self::Openai => "openai", - Self::Atlascloud => "atlascloud", - Self::WanjieArk => "wanjie-ark", - Self::Volcengine => "volcengine", - Self::Openrouter => "openrouter", - Self::XiaomiMimo => "xiaomi-mimo", - Self::Novita => "novita", - Self::Fireworks => "fireworks", - Self::Siliconflow => "siliconflow", - Self::SiliconflowCn => "siliconflow-CN", - Self::Arcee => "arcee", - Self::Moonshot => "moonshot", - Self::Sglang => "sglang", - Self::Vllm => "vllm", - Self::Ollama => "ollama", - Self::Huggingface => "huggingface", - Self::Together => "together", - Self::OpenaiCodex => "openai-codex", - Self::Anthropic => "anthropic", + match self.kind() { + Some(kind) => kind.as_str(), + None => "deepseek-cn", } } /// Human-friendly label for picker UIs / status chips. #[must_use] pub fn display_name(self) -> &'static str { - match self { - Self::Deepseek => "DeepSeek", - Self::DeepseekCN => "DeepSeek (legacy alias)", - Self::NvidiaNim => "NVIDIA NIM", - Self::Openai => "OpenAI-compatible", - Self::Atlascloud => "AtlasCloud", - Self::WanjieArk => "Wanjie Ark", - Self::Volcengine => "Volcengine Ark", - Self::Openrouter => "OpenRouter", - Self::XiaomiMimo => "Xiaomi MiMo", - Self::Novita => "Novita AI", - Self::Fireworks => "Fireworks AI", - Self::Siliconflow => "SiliconFlow", - Self::SiliconflowCn => "SiliconFlow (China)", - Self::Arcee => "Arcee AI", - Self::Moonshot => "Moonshot/Kimi", - Self::Sglang => "SGLang", - Self::Vllm => "vLLM", - Self::Ollama => "Ollama", - Self::Huggingface => "Hugging Face", - Self::Together => "Together AI", - Self::OpenaiCodex => "OpenAI Codex (ChatGPT)", - Self::Anthropic => "Anthropic", + match self.kind() { + Some(kind) => kind.provider().display_name(), + None => "DeepSeek (legacy alias)", } } /// All providers, in the order shown in the picker. #[must_use] pub fn all() -> &'static [Self] { - &[ - Self::Deepseek, - Self::NvidiaNim, - Self::Openai, - Self::Atlascloud, - Self::WanjieArk, - Self::Volcengine, - Self::Openrouter, - Self::XiaomiMimo, - Self::Novita, - Self::Fireworks, - Self::Siliconflow, - Self::SiliconflowCn, - Self::Arcee, - Self::Moonshot, - Self::Sglang, - Self::Vllm, - Self::Ollama, - Self::Huggingface, - Self::Together, - Self::OpenaiCodex, - Self::Anthropic, - ] + &Self::FROM_KIND_LOOKUP + } + + /// `ApiProvider` discriminant → `ProviderKind` lookup. + /// Index 1 is `None` for the legacy `DeepseekCN` variant. + const KIND_LOOKUP: [Option; 22] = [ + Some(codewhale_config::ProviderKind::Deepseek), + None, // DeepseekCN + Some(codewhale_config::ProviderKind::NvidiaNim), + Some(codewhale_config::ProviderKind::Openai), + Some(codewhale_config::ProviderKind::Atlascloud), + Some(codewhale_config::ProviderKind::WanjieArk), + Some(codewhale_config::ProviderKind::Volcengine), + Some(codewhale_config::ProviderKind::Openrouter), + Some(codewhale_config::ProviderKind::XiaomiMimo), + Some(codewhale_config::ProviderKind::Novita), + Some(codewhale_config::ProviderKind::Fireworks), + Some(codewhale_config::ProviderKind::Siliconflow), + Some(codewhale_config::ProviderKind::SiliconflowCN), + Some(codewhale_config::ProviderKind::Arcee), + Some(codewhale_config::ProviderKind::Moonshot), + Some(codewhale_config::ProviderKind::Sglang), + Some(codewhale_config::ProviderKind::Vllm), + Some(codewhale_config::ProviderKind::Ollama), + Some(codewhale_config::ProviderKind::Huggingface), + Some(codewhale_config::ProviderKind::Together), + Some(codewhale_config::ProviderKind::OpenaiCodex), + Some(codewhale_config::ProviderKind::Anthropic), + ]; + + /// `ProviderKind` discriminant → `ApiProvider` lookup. + const FROM_KIND_LOOKUP: [Self; 21] = [ + Self::Deepseek, + Self::NvidiaNim, + Self::Openai, + Self::Atlascloud, + Self::WanjieArk, + Self::Volcengine, + Self::Openrouter, + Self::XiaomiMimo, + Self::Novita, + Self::Fireworks, + Self::Siliconflow, + Self::Arcee, + Self::SiliconflowCn, + Self::Moonshot, + Self::Sglang, + Self::Vllm, + Self::Ollama, + Self::Huggingface, + Self::Together, + Self::OpenaiCodex, + Self::Anthropic, + ]; + + /// Map to the config-level `ProviderKind`. + /// Returns `None` for the legacy `DeepseekCN` variant. + #[must_use] + pub fn kind(self) -> Option { + Self::KIND_LOOKUP[self as usize] + } + + /// Construct from a config-level `ProviderKind`. + #[must_use] + pub fn from_kind(kind: codewhale_config::ProviderKind) -> Self { + Self::FROM_KIND_LOOKUP[kind as usize] } } diff --git a/crates/tui/src/tui/provider_picker.rs b/crates/tui/src/tui/provider_picker.rs index 6887e84f..c26376de 100644 --- a/crates/tui/src/tui/provider_picker.rs +++ b/crates/tui/src/tui/provider_picker.rs @@ -505,8 +505,8 @@ mod tests { "Novita AI", "Fireworks AI", "SiliconFlow", - "SiliconFlow (China)", "Arcee AI", + "SiliconFlow (China)", "Moonshot/Kimi", "SGLang", "vLLM", diff --git a/scripts/check-provider-registry.py b/scripts/check-provider-registry.py index 0caa5a71..8880997d 100644 --- a/scripts/check-provider-registry.py +++ b/scripts/check-provider-registry.py @@ -21,6 +21,7 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] CONFIG_RS = ROOT / "crates" / "config" / "src" / "lib.rs" +PROVIDER_RS = ROOT / "crates" / "config" / "src" / "provider.rs" TUI_CONFIG_RS = ROOT / "crates" / "tui" / "src" / "config.rs" AGENT_RS = ROOT / "crates" / "agent" / "src" / "lib.rs" PROVIDERS_MD = ROOT / "docs" / "PROVIDERS.md" @@ -69,35 +70,37 @@ def extract_match_block( def provider_kind_ids(config_rs: str) -> dict[str, str]: - impl_start = require_index( - config_rs, "impl ProviderKind", "crates/config/src/lib.rs" + provider_rs = read(PROVIDER_RS) + pairs = re.findall( + r"provider!\(\s*\n\s*\w+,\s*\n\s*(\w+),\s*\n\s*\"([^\"]+)\"", + provider_rs, ) - block = extract_match_block( - config_rs, - "pub fn as_str(self) -> &'static str", - "crates/config/src/lib.rs", - impl_start, - ) - pairs = re.findall(r"Self::(\w+)\s*=>\s*\"([^\"]+)\"", block) - if not pairs: - raise ValueError("ProviderKind::as_str returned no providers") - return {variant: provider_id for variant, provider_id in pairs} + ids: dict[str, str] = {variant: provider_id for variant, provider_id in pairs} + # OpenaiCodex and Anthropic use manual impls rather than the provider!() macro + for variant_name, id_literal in [ + ("OpenaiCodex", "openai-codex"), + ("Anthropic", "anthropic"), + ]: + match = re.search( + rf'impl\s+Provider\s+for\s+{variant_name}.*?fn\s+id.*?\"({id_literal})\"', + provider_rs, re.DOTALL, + ) + if match: + ids[variant_name] = match.group(1) + if not ids: + raise ValueError("provider!() invocations returned no providers") + return ids def api_provider_ids(tui_config_rs: str) -> dict[str, str]: - impl_start = require_index( - tui_config_rs, "impl ApiProvider", "crates/tui/src/config.rs" - ) - block = extract_match_block( - tui_config_rs, - "pub fn as_str(self) -> &'static str", - "crates/tui/src/config.rs", - impl_start, - ) - pairs = re.findall(r"Self::(\w+)\s*=>\s*\"([^\"]+)\"", block) - if not pairs: - raise ValueError("ApiProvider::as_str returned no providers") - return {variant: provider_id for variant, provider_id in pairs} + # ApiProvider ids derive from ProviderKind ids (via delegation to .kind().as_str()) + # plus the legacy "deepseek-cn" variant that exists only in ApiProvider. + variant_to_id = provider_kind_ids("") + # ApiProvider::SiliconflowCn maps to ProviderKind::SiliconflowCN + if "SiliconflowCN" in variant_to_id: + variant_to_id["SiliconflowCn"] = variant_to_id["SiliconflowCN"] + variant_to_id["DeepseekCN"] = "deepseek-cn" + return variant_to_id def provider_tables(config_rs: str) -> set[str]: @@ -253,4 +256,4 @@ def main() -> int: if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file