Merge PR #3005: provider metadata registry

Harvested from PR #3005 by @sximelon

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