feat(config): harvest provider metadata registry

Add a metadata-only provider registry foundation from #2479. The registry exposes canonical lookup, alias-aware resolution, defaults, config table keys, and API-key env candidates without changing runtime routing or activating fallback providers.

Co-authored-by: sximelon <62371427+sximelon@users.noreply.github.com>
This commit is contained in:
Hunter Bown
2026-06-05 20:40:28 -07:00
committed by GitHub
parent d868a0b96a
commit 5d491bc683
5 changed files with 493 additions and 2 deletions
+5
View File
@@ -66,6 +66,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
were previously unpriced: `mimo-v2.5-pro` / `xiaomi/mimo-v2.5-pro` reuse the
DeepSeek V4-Pro rate table and `mimo-v2.5` / `xiaomi/mimo-v2.5` reuse the
DeepSeek V4-Flash rates. Existing DeepSeek pricing is unchanged (#2731, #2750).
- Added a metadata-only `codewhale-config` provider registry with canonical
lookup, alias-aware resolution, provider defaults, config-table keys, and
API-key env candidates. Runtime routing remains unchanged and fallback
providers stay dormant; this harvests the safe provider-trait foundation from
#2479 toward #2075. Thanks @sximelon.
- Added optional `[search].base_url` / `CODEWHALE_SEARCH_BASE_URL` support for
DuckDuckGo-compatible private search endpoints, while keeping
`DEEPSEEK_SEARCH_BASE_URL` as a legacy alias. Custom endpoints are gated by
+2 -2
View File
@@ -642,8 +642,8 @@ Current v0.9 track credits:
- **[shenjackyuanjie](https://github.com/shenjackyuanjie)** — HarmonyOS /
OpenHarmony porting work and MatePad Edge validation trail (#2634)
- **[sximelon](https://github.com/sximelon)** — saved-session resume footer
hint work and provider-trait direction reviewed for the v0.9 track (#2758,
#2760, #2479)
hint work plus provider-trait metadata registry direction reviewed and
harvested for the v0.9 track (#2758, #2760, #2479)
- **[aboimpinto](https://github.com/aboimpinto)** — sidebar command polish and
pausable custom-command lifecycle direction harvested into the v0.9 track
(#2788, #2732)
+118
View File
@@ -1,3 +1,5 @@
pub mod provider;
use std::collections::BTreeMap;
use std::fs;
#[cfg(unix)]
@@ -134,6 +136,27 @@ pub enum ProviderKind {
}
impl ProviderKind {
pub const ALL: [Self; 18] = [
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,
];
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
@@ -192,6 +215,15 @@ impl ProviderKind {
pub fn is_siliconflow(self) -> bool {
matches!(self, Self::Siliconflow | Self::SiliconflowCN)
}
/// Return the built-in metadata entry for this provider.
///
/// This is a metadata foundation only; runtime routing still resolves
/// through [`ConfigToml::resolve_runtime_options`].
#[must_use]
pub fn provider(self) -> &'static dyn provider::Provider {
provider::provider_for_kind(self)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
@@ -4401,6 +4433,92 @@ unix_socket_path = "/tmp/cw-hooks.sock"
}
}
#[test]
fn provider_metadata_registry_covers_every_provider_kind_once() {
let providers = provider::all_providers();
assert_eq!(providers.len(), ProviderKind::ALL.len());
for (kind, provider) in ProviderKind::ALL.iter().zip(providers.iter()) {
assert_eq!(provider.kind(), *kind);
assert_eq!(provider.id(), kind.as_str());
assert_eq!(kind.provider().id(), kind.as_str());
}
let mut ids = std::collections::BTreeSet::new();
for provider in providers {
assert!(ids.insert(provider.id()), "duplicate provider id");
}
}
#[test]
fn provider_metadata_lookup_does_not_fall_back_to_deepseek() {
assert!(provider::lookup_provider("not-a-provider").is_none());
assert!(provider::resolve_provider("not-a-provider").is_none());
assert!(provider::lookup_provider("deepseek-cn").is_none());
assert_eq!(
provider::resolve_provider("deepseek-cn")
.expect("legacy alias resolves")
.kind(),
ProviderKind::Deepseek
);
}
#[test]
fn provider_metadata_preserves_alias_and_config_key_semantics() {
assert_eq!(
provider::resolve_provider("open_router")
.expect("openrouter alias")
.kind(),
ProviderKind::Openrouter
);
assert_eq!(
provider::resolve_provider("xiaomi")
.expect("xiaomi alias")
.kind(),
ProviderKind::XiaomiMimo
);
assert_eq!(
provider::resolve_provider("kimi")
.expect("kimi alias")
.kind(),
ProviderKind::Moonshot
);
assert_eq!(
provider::resolve_provider("hf")
.expect("huggingface alias")
.kind(),
ProviderKind::Huggingface
);
let siliconflow_cn =
provider::resolve_provider("siliconflow-cn").expect("siliconflow-cn alias resolves");
assert_eq!(siliconflow_cn.kind(), ProviderKind::SiliconflowCN);
assert_eq!(siliconflow_cn.id(), "siliconflow-CN");
assert_eq!(siliconflow_cn.provider_config_key(), "siliconflow");
let config = ProvidersToml::default();
let shared_table = config.for_provider(ProviderKind::SiliconflowCN);
assert!(std::ptr::eq(
shared_table,
config.for_provider(ProviderKind::Siliconflow)
));
}
#[test]
fn provider_metadata_defaults_match_runtime_helpers() {
for kind in ProviderKind::ALL {
let provider = kind.provider();
assert_eq!(provider.default_model(), default_model_for_provider(kind));
assert_eq!(
provider.default_base_url(),
default_base_url_for_provider(kind)
);
assert!(!provider.display_name().trim().is_empty());
assert!(!provider.env_vars().is_empty());
assert_eq!(provider.wire(), provider::WireFormat::ChatCompletions);
}
}
#[test]
fn openrouter_provider_defaults_to_canonical_endpoint_and_model() {
let _lock = env_lock();
+363
View File
@@ -0,0 +1,363 @@
//! Built-in provider metadata.
//!
//! This module is a metadata foundation for collapsing provider drift over
//! time. It deliberately does not mutate request bodies or choose fallback
//! providers; runtime routing remains in `ConfigToml::resolve_runtime_options`.
use super::{
DEFAULT_ARCEE_BASE_URL, DEFAULT_ARCEE_MODEL, DEFAULT_ATLASCLOUD_BASE_URL,
DEFAULT_ATLASCLOUD_MODEL, DEFAULT_DEEPSEEK_BASE_URL, DEFAULT_DEEPSEEK_MODEL,
DEFAULT_FIREWORKS_BASE_URL, DEFAULT_FIREWORKS_MODEL, DEFAULT_HUGGINGFACE_BASE_URL,
DEFAULT_HUGGINGFACE_MODEL, DEFAULT_MOONSHOT_BASE_URL, DEFAULT_MOONSHOT_MODEL,
DEFAULT_NOVITA_BASE_URL, DEFAULT_NOVITA_MODEL, DEFAULT_NVIDIA_NIM_BASE_URL,
DEFAULT_NVIDIA_NIM_MODEL, DEFAULT_OLLAMA_BASE_URL, DEFAULT_OLLAMA_MODEL,
DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, DEFAULT_OPENROUTER_BASE_URL,
DEFAULT_OPENROUTER_MODEL, DEFAULT_SGLANG_BASE_URL, DEFAULT_SGLANG_MODEL,
DEFAULT_SILICONFLOW_BASE_URL, DEFAULT_SILICONFLOW_CN_BASE_URL, DEFAULT_SILICONFLOW_MODEL,
DEFAULT_VLLM_BASE_URL, DEFAULT_VLLM_MODEL, DEFAULT_VOLCENGINE_BASE_URL,
DEFAULT_VOLCENGINE_MODEL, DEFAULT_WANJIE_ARK_BASE_URL, DEFAULT_WANJIE_ARK_MODEL,
DEFAULT_XIAOMI_MIMO_BASE_URL, DEFAULT_XIAOMI_MIMO_MODEL, ProviderKind,
};
/// Wire protocol spoken by a provider.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WireFormat {
/// OpenAI-compatible `/v1/chat/completions` style payloads.
ChatCompletions,
}
/// Static metadata for a built-in model provider.
pub trait Provider: Send + Sync {
/// Provider enum variant represented by this entry.
fn kind(&self) -> ProviderKind;
/// Canonical provider identifier.
fn id(&self) -> &'static str {
self.kind().as_str()
}
/// Human-readable provider label for UIs and diagnostics.
fn display_name(&self) -> &'static str;
/// Default base URL used when no config/env/CLI override is present.
fn default_base_url(&self) -> &'static str;
/// Default model used when no config/env/CLI override is present.
fn default_model(&self) -> &'static str;
/// Environment variable candidates used for this provider's API key.
fn env_vars(&self) -> &'static [&'static str];
/// TOML table key under `[providers.<key>]`.
fn provider_config_key(&self) -> &'static str;
/// Wire format used by the provider.
fn wire(&self) -> WireFormat {
WireFormat::ChatCompletions
}
}
macro_rules! provider {
(
$struct_name:ident,
$kind:ident,
$display_name:literal,
$base_url:ident,
$model:ident,
[$($env_var:literal),* $(,)?],
$config_key:literal
) => {
/// Zero-sized metadata entry for this built-in provider.
pub struct $struct_name;
impl Provider for $struct_name {
fn kind(&self) -> ProviderKind {
ProviderKind::$kind
}
fn display_name(&self) -> &'static str {
$display_name
}
fn default_base_url(&self) -> &'static str {
$base_url
}
fn default_model(&self) -> &'static str {
$model
}
fn env_vars(&self) -> &'static [&'static str] {
&[$($env_var),*]
}
fn provider_config_key(&self) -> &'static str {
$config_key
}
}
};
}
provider!(
Deepseek,
Deepseek,
"DeepSeek",
DEFAULT_DEEPSEEK_BASE_URL,
DEFAULT_DEEPSEEK_MODEL,
["DEEPSEEK_API_KEY"],
"deepseek"
);
provider!(
NvidiaNim,
NvidiaNim,
"NVIDIA NIM",
DEFAULT_NVIDIA_NIM_BASE_URL,
DEFAULT_NVIDIA_NIM_MODEL,
["NVIDIA_API_KEY", "NVIDIA_NIM_API_KEY", "DEEPSEEK_API_KEY"],
"nvidia_nim"
);
provider!(
Openai,
Openai,
"OpenAI-compatible",
DEFAULT_OPENAI_BASE_URL,
DEFAULT_OPENAI_MODEL,
["OPENAI_API_KEY"],
"openai"
);
provider!(
Atlascloud,
Atlascloud,
"AtlasCloud",
DEFAULT_ATLASCLOUD_BASE_URL,
DEFAULT_ATLASCLOUD_MODEL,
["ATLASCLOUD_API_KEY"],
"atlascloud"
);
provider!(
WanjieArk,
WanjieArk,
"Wanjie Ark",
DEFAULT_WANJIE_ARK_BASE_URL,
DEFAULT_WANJIE_ARK_MODEL,
[
"WANJIE_ARK_API_KEY",
"WANJIE_API_KEY",
"WANJIE_MAAS_API_KEY"
],
"wanjie_ark"
);
provider!(
Volcengine,
Volcengine,
"Volcengine Ark",
DEFAULT_VOLCENGINE_BASE_URL,
DEFAULT_VOLCENGINE_MODEL,
[
"VOLCENGINE_API_KEY",
"VOLCENGINE_ARK_API_KEY",
"ARK_API_KEY"
],
"volcengine"
);
provider!(
Openrouter,
Openrouter,
"OpenRouter",
DEFAULT_OPENROUTER_BASE_URL,
DEFAULT_OPENROUTER_MODEL,
["OPENROUTER_API_KEY"],
"openrouter"
);
provider!(
XiaomiMimo,
XiaomiMimo,
"Xiaomi MiMo",
DEFAULT_XIAOMI_MIMO_BASE_URL,
DEFAULT_XIAOMI_MIMO_MODEL,
[
"XIAOMI_MIMO_TOKEN_PLAN_API_KEY",
"MIMO_TOKEN_PLAN_API_KEY",
"XIAOMI_MIMO_API_KEY",
"XIAOMI_API_KEY",
"MIMO_API_KEY",
],
"xiaomi_mimo"
);
provider!(
Novita,
Novita,
"Novita",
DEFAULT_NOVITA_BASE_URL,
DEFAULT_NOVITA_MODEL,
["NOVITA_API_KEY"],
"novita"
);
provider!(
Fireworks,
Fireworks,
"Fireworks",
DEFAULT_FIREWORKS_BASE_URL,
DEFAULT_FIREWORKS_MODEL,
["FIREWORKS_API_KEY"],
"fireworks"
);
provider!(
Siliconflow,
Siliconflow,
"SiliconFlow",
DEFAULT_SILICONFLOW_BASE_URL,
DEFAULT_SILICONFLOW_MODEL,
["SILICONFLOW_API_KEY"],
"siliconflow"
);
provider!(
SiliconflowCN,
SiliconflowCN,
"SiliconFlow CN",
DEFAULT_SILICONFLOW_CN_BASE_URL,
DEFAULT_SILICONFLOW_MODEL,
["SILICONFLOW_API_KEY"],
"siliconflow"
);
provider!(
Arcee,
Arcee,
"Arcee",
DEFAULT_ARCEE_BASE_URL,
DEFAULT_ARCEE_MODEL,
["ARCEE_API_KEY"],
"arcee"
);
provider!(
Moonshot,
Moonshot,
"Moonshot",
DEFAULT_MOONSHOT_BASE_URL,
DEFAULT_MOONSHOT_MODEL,
["MOONSHOT_API_KEY", "KIMI_API_KEY"],
"moonshot"
);
provider!(
Sglang,
Sglang,
"SGLang",
DEFAULT_SGLANG_BASE_URL,
DEFAULT_SGLANG_MODEL,
["SGLANG_API_KEY"],
"sglang"
);
provider!(
Vllm,
Vllm,
"vLLM",
DEFAULT_VLLM_BASE_URL,
DEFAULT_VLLM_MODEL,
["VLLM_API_KEY"],
"vllm"
);
provider!(
Ollama,
Ollama,
"Ollama",
DEFAULT_OLLAMA_BASE_URL,
DEFAULT_OLLAMA_MODEL,
["OLLAMA_API_KEY"],
"ollama"
);
provider!(
Huggingface,
Huggingface,
"Hugging Face",
DEFAULT_HUGGINGFACE_BASE_URL,
DEFAULT_HUGGINGFACE_MODEL,
["HUGGINGFACE_API_KEY", "HF_TOKEN"],
"huggingface"
);
static DEEPSEEK: Deepseek = Deepseek;
static NVIDIA_NIM: NvidiaNim = NvidiaNim;
static OPENAI: Openai = Openai;
static ATLASCLOUD: Atlascloud = Atlascloud;
static WANJIE_ARK: WanjieArk = WanjieArk;
static VOLCENGINE: Volcengine = Volcengine;
static OPENROUTER: Openrouter = Openrouter;
static XIAOMI_MIMO: XiaomiMimo = XiaomiMimo;
static NOVITA: Novita = Novita;
static FIREWORKS: Fireworks = Fireworks;
static SILICONFLOW: Siliconflow = Siliconflow;
static SILICONFLOW_CN: SiliconflowCN = SiliconflowCN;
static ARCEE: Arcee = Arcee;
static MOONSHOT: Moonshot = Moonshot;
static SGLANG: Sglang = Sglang;
static VLLM: Vllm = Vllm;
static OLLAMA: Ollama = Ollama;
static HUGGINGFACE: Huggingface = Huggingface;
static PROVIDER_REGISTRY: [&dyn Provider; 18] = [
&DEEPSEEK,
&NVIDIA_NIM,
&OPENAI,
&ATLASCLOUD,
&WANJIE_ARK,
&VOLCENGINE,
&OPENROUTER,
&XIAOMI_MIMO,
&NOVITA,
&FIREWORKS,
&SILICONFLOW,
&SILICONFLOW_CN,
&ARCEE,
&MOONSHOT,
&SGLANG,
&VLLM,
&OLLAMA,
&HUGGINGFACE,
];
/// Return all built-in provider metadata entries in `ProviderKind::ALL` order.
#[must_use]
pub fn all_providers() -> &'static [&'static dyn Provider] {
&PROVIDER_REGISTRY
}
/// Find a provider by canonical id only.
#[must_use]
pub fn lookup_provider(id: &str) -> Option<&'static dyn Provider> {
let id = id.trim();
all_providers()
.iter()
.copied()
.find(|provider| provider.id() == id)
}
/// Resolve a provider by canonical id or supported legacy alias.
#[must_use]
pub fn resolve_provider(id_or_alias: &str) -> Option<&'static dyn Provider> {
ProviderKind::parse(id_or_alias).map(provider_for_kind)
}
/// 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,
}
}
+5
View File
@@ -66,6 +66,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
were previously unpriced: `mimo-v2.5-pro` / `xiaomi/mimo-v2.5-pro` reuse the
DeepSeek V4-Pro rate table and `mimo-v2.5` / `xiaomi/mimo-v2.5` reuse the
DeepSeek V4-Flash rates. Existing DeepSeek pricing is unchanged (#2731, #2750).
- Added a metadata-only `codewhale-config` provider registry with canonical
lookup, alias-aware resolution, provider defaults, config-table keys, and
API-key env candidates. Runtime routing remains unchanged and fallback
providers stay dormant; this harvests the safe provider-trait foundation from
#2479 toward #2075. Thanks @sximelon.
- Added optional `[search].base_url` / `CODEWHALE_SEARCH_BASE_URL` support for
DuckDuckGo-compatible private search endpoints, while keeping
`DEEPSEEK_SEARCH_BASE_URL` as a legacy alias. Custom endpoints are gated by