feat(providers): add CSDN 星图 with Coding Plan billing

CSDN's hosted OpenAI-compatible platform (ai.csdn.net/api/model/v1)
serves the Coding Plan's dedicated model glm_for_coding — the shipped
default — plus pass-through marketplace model ids, all over the one
endpoint and one CSDN_API_KEY slot. Since the URL proves neither
product, billing follows captured credential product MiniMax-style:
plan mode or the plan model id bills as CSDN Coding Plan quota with no
dollar estimates, other models or explicit payg bill metered, and
unrecognized modes or off-platform endpoints stay cost: unknown.

Covers the full spine: ProviderKind + serde aliases (csdn-ai,
csdn-coding-plan, starmap), config/env overrides (CSDN_BASE_URL,
CSDN_MODEL), secrets env lookup, TUI provider/config/runtime surfaces,
agent registry entry for glm_for_coding, route export goldens,
pricing/billing surfaces, EN+ZH provider docs, website facts maps, and
config.example.toml. No invented reasoning dialect — CSDN documents no
provider-owned reasoning controls.
This commit is contained in:
CodeWhale Bot committed 2026-09-18 17:09:45 -07:00
1 parent 77f7582983
commit e2470e9f3f
26 files changed
+418 -42

No files matched your search

+1 -1
View File
@@ -21,7 +21,7 @@
# `api_key` / `base_url` are
# still read as DeepSeek defaults when `[providers.deepseek]` is absent
# (backward compatibility).
provider = "deepseek" # deepseek | deepseek-cn | deepseek-anthropic | nvidia-nim | openai | atlascloud | wanjie-ark | volcengine | openrouter | orcarouter | xiaomi-mimo | novita | fireworks | siliconflow | siliconflow-CN | arcee | moonshot | zai | stepfun | minimax | sglang | vllm | ollama | huggingface | modelscope | together | qianfan | openai-codex | anthropic | openmodel | deepinfra | sakana | longcat | opencode-go | opencode-zen | meta | xai | modelstudio-token-plan | modelstudio-coding-plan | codewhale
provider = "deepseek" # deepseek | deepseek-cn | deepseek-anthropic | nvidia-nim | openai | atlascloud | wanjie-ark | volcengine | openrouter | orcarouter | xiaomi-mimo | novita | fireworks | siliconflow | siliconflow-CN | arcee | moonshot | zai | stepfun | minimax | sglang | vllm | ollama | ollama-cloud | huggingface | modelscope | together | qianfan | openai-codex | anthropic | openmodel | deepinfra | sakana | longcat | opencode-go | opencode-zen | meta | xai | mistral | telecomjs | google | edenai | zenmux | csdn | concentrate | modelstudio-token-plan | modelstudio-coding-plan | codewhale
api_key = "YOUR_DEEPSEEK_API_KEY" # must be non-empty
base_url = "https://api.deepseek.com/beta"
# provider = "deepseek-cn" # legacy alias (official host is still https://api.deepseek.com)
+12
View File
@@ -1006,6 +1006,18 @@ impl Default for ModelRegistry {
supports_tools: true,
supports_reasoning: true,
},
// CSDN 星图 (Starmap) — the Coding Plan's dedicated model id.
// Other CSDN marketplace models resolve through pass-through.
ModelInfo {
id: "glm_for_coding".to_string(),
provider: ProviderKind::Csdn,
aliases: vec![
"glm-for-coding".to_string(),
"csdn-glm-for-coding".to_string(),
],
supports_tools: true,
supports_reasoning: true,
},
// Together AI provider models
ModelInfo {
id: "deepseek-ai/DeepSeek-V4-Pro".to_string(),
+2 -2
View File
@@ -10150,11 +10150,11 @@ verbosity = "project-imported"
.map(|provider| provider.kind())
.collect();
// Full registry keeps legacy dialect/plan kinds; ALL is the catalog surface.
assert_eq!(registry_kinds.len(), 51);
assert_eq!(registry_kinds.len(), 52);
// The tombstone stays in the registry (old config must still parse
// and clear) and left the catalog surface when it stopped being
// selectable.
assert_eq!(ProviderKind::ALL.len(), 45);
assert_eq!(ProviderKind::ALL.len(), 46);
for kind in ProviderKind::ALL {
assert!(
registry_kinds.contains(&kind),
+27
View File
@@ -532,6 +532,17 @@ pub struct ProvidersToml {
alias = "zen_mux"
)]
pub zenmux: ProviderConfigToml,
/// CSDN 星图 — hosted OpenAI-compatible platform and Coding Plan.
#[serde(
default,
skip_serializing_if = "ProviderConfigToml::is_empty",
alias = "csdn-ai",
alias = "csdn_ai",
alias = "csdn-coding-plan",
alias = "csdn_coding_plan",
alias = "starmap"
)]
pub csdn: ProviderConfigToml,
/// Concentrate — OpenAI Responses-compatible AI gateway (aggregator).
#[serde(
default,
@@ -778,6 +789,7 @@ impl ProvidersToml {
ProviderKind::Telecomjs => &self.telecomjs,
ProviderKind::Edenai => &self.edenai,
ProviderKind::Zenmux => &self.zenmux,
ProviderKind::Csdn => &self.csdn,
ProviderKind::Concentrate => &self.concentrate,
ProviderKind::Codewhale => &self.codewhale,
ProviderKind::ModelstudioTokenPlan => &self.modelstudio_token_plan,
@@ -834,6 +846,7 @@ impl ProvidersToml {
ProviderKind::Telecomjs => &mut self.telecomjs,
ProviderKind::Edenai => &mut self.edenai,
ProviderKind::Zenmux => &mut self.zenmux,
ProviderKind::Csdn => &mut self.csdn,
ProviderKind::Concentrate => &mut self.concentrate,
ProviderKind::Codewhale => &mut self.codewhale,
ProviderKind::ModelstudioTokenPlan => &mut self.modelstudio_token_plan,
@@ -3929,6 +3942,7 @@ fn provider_passes_model_through(provider: ProviderKind) -> bool {
| ProviderKind::Telecomjs
| ProviderKind::Edenai
| ProviderKind::Zenmux
| ProviderKind::Csdn
| ProviderKind::Concentrate
| ProviderKind::ModelstudioTokenPlan
| ProviderKind::ModelstudioTokenPlanAnthropic
@@ -4467,6 +4481,7 @@ fn default_model_for_provider(provider: ProviderKind) -> &'static str {
ProviderKind::Telecomjs => DEFAULT_TELECOMJS_MODEL,
ProviderKind::Edenai => DEFAULT_EDENAI_MODEL,
ProviderKind::Zenmux => DEFAULT_ZENMUX_MODEL,
ProviderKind::Csdn => DEFAULT_CSDN_MODEL,
ProviderKind::Concentrate => DEFAULT_CONCENTRATE_MODEL,
ProviderKind::Codewhale => DEFAULT_CODEWHALE_MODEL,
ProviderKind::ModelstudioTokenPlan
@@ -4524,6 +4539,7 @@ fn default_base_url_for_provider(provider: ProviderKind) -> &'static str {
ProviderKind::Telecomjs => DEFAULT_TELECOMJS_BASE_URL,
ProviderKind::Edenai => DEFAULT_EDENAI_BASE_URL,
ProviderKind::Zenmux => DEFAULT_ZENMUX_BASE_URL,
ProviderKind::Csdn => DEFAULT_CSDN_BASE_URL,
ProviderKind::Concentrate => DEFAULT_CONCENTRATE_BASE_URL,
ProviderKind::Codewhale => DEFAULT_CODEWHALE_BASE_URL,
ProviderKind::ModelstudioTokenPlan => DEFAULT_MODELSTUDIO_TOKEN_PLAN_BASE_URL,
@@ -4825,6 +4841,7 @@ pub fn provider_base_url_is_official(provider: ProviderKind, base_url: &str) ->
"https://api.edenai.run/v3" | "https://api.eu.edenai.run/v3"
),
ProviderKind::Zenmux => normalized == DEFAULT_ZENMUX_BASE_URL,
ProviderKind::Csdn => normalized == DEFAULT_CSDN_BASE_URL,
ProviderKind::Concentrate => normalized == DEFAULT_CONCENTRATE_BASE_URL,
// The Codewhale API's official endpoint family is its default base
// plus whatever the operator declared in `CODEWHALE_API_BASE` — the
@@ -6997,6 +7014,8 @@ struct EnvRuntimeOverrides {
edenai_model: Option<String>,
zenmux_base_url: Option<String>,
zenmux_model: Option<String>,
csdn_base_url: Option<String>,
csdn_model: Option<String>,
concentrate_base_url: Option<String>,
concentrate_model: Option<String>,
codewhale_base_url: Option<String>,
@@ -7365,6 +7384,12 @@ impl EnvRuntimeOverrides {
zenmux_model: std::env::var("ZENMUX_MODEL")
.ok()
.filter(|v| !v.trim().is_empty()),
csdn_base_url: std::env::var("CSDN_BASE_URL")
.ok()
.filter(|v| !v.trim().is_empty()),
csdn_model: std::env::var("CSDN_MODEL")
.ok()
.filter(|v| !v.trim().is_empty()),
concentrate_base_url: std::env::var("CONCENTRATE_BASE_URL")
.ok()
.filter(|v| !v.trim().is_empty()),
@@ -7462,6 +7487,7 @@ impl EnvRuntimeOverrides {
ProviderKind::Telecomjs => self.telecomjs_base_url.clone(),
ProviderKind::Edenai => self.edenai_base_url.clone(),
ProviderKind::Zenmux => self.zenmux_base_url.clone(),
ProviderKind::Csdn => self.csdn_base_url.clone(),
ProviderKind::Concentrate => self.concentrate_base_url.clone(),
ProviderKind::Codewhale => self.codewhale_base_url.clone(),
ProviderKind::ModelstudioTokenPlan | ProviderKind::ModelstudioTokenPlanAnthropic => {
@@ -7513,6 +7539,7 @@ impl EnvRuntimeOverrides {
ProviderKind::Telecomjs => self.telecomjs_model.clone(),
ProviderKind::Edenai => self.edenai_model.clone(),
ProviderKind::Zenmux => self.zenmux_model.clone(),
ProviderKind::Csdn => self.csdn_model.clone(),
ProviderKind::Concentrate => self.concentrate_model.clone(),
ProviderKind::Codewhale => self.codewhale_model.clone(),
ProviderKind::ModelstudioTokenPlan | ProviderKind::ModelstudioTokenPlanAnthropic => {
+68 -30
View File
@@ -9,35 +9,36 @@ use super::{
DEFAULT_ANTIGRAVITY_BASE_URL, DEFAULT_ANTIGRAVITY_MODEL, DEFAULT_ARCEE_BASE_URL,
DEFAULT_ARCEE_MODEL, DEFAULT_ATLASCLOUD_BASE_URL, DEFAULT_ATLASCLOUD_MODEL,
DEFAULT_CODEWHALE_BASE_URL, DEFAULT_CODEWHALE_MODEL, DEFAULT_CONCENTRATE_BASE_URL,
DEFAULT_CONCENTRATE_MODEL, DEFAULT_DEEPINFRA_BASE_URL, DEFAULT_DEEPINFRA_MODEL,
DEFAULT_DEEPSEEK_ANTHROPIC_BASE_URL, DEFAULT_DEEPSEEK_ANTHROPIC_MODEL,
DEFAULT_DEEPSEEK_BASE_URL, DEFAULT_DEEPSEEK_MODEL, DEFAULT_EDENAI_BASE_URL,
DEFAULT_EDENAI_MODEL, DEFAULT_FIREWORKS_BASE_URL, DEFAULT_FIREWORKS_MODEL,
DEFAULT_GOOGLE_BASE_URL, DEFAULT_GOOGLE_MODEL, DEFAULT_HUGGINGFACE_BASE_URL,
DEFAULT_HUGGINGFACE_MODEL, DEFAULT_LONGCAT_BASE_URL, DEFAULT_LONGCAT_MODEL,
DEFAULT_META_BASE_URL, DEFAULT_META_MODEL, DEFAULT_MINIMAX_ANTHROPIC_BASE_URL,
DEFAULT_MINIMAX_BASE_URL, DEFAULT_MINIMAX_MODEL, DEFAULT_MISTRAL_BASE_URL,
DEFAULT_MISTRAL_MODEL, DEFAULT_MODELSCOPE_BASE_URL, DEFAULT_MODELSCOPE_MODEL,
DEFAULT_MODELSTUDIO_CODING_PLAN_BASE_URL, DEFAULT_MODELSTUDIO_TOKEN_PLAN_BASE_URL,
DEFAULT_MODELSTUDIO_TOKEN_PLAN_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_CLOUD_BASE_URL,
DEFAULT_OLLAMA_CLOUD_MODEL, DEFAULT_OLLAMA_MODEL, DEFAULT_OPENAI_BASE_URL,
DEFAULT_OPENAI_CODEX_BASE_URL, DEFAULT_OPENAI_CODEX_MODEL, DEFAULT_OPENAI_MODEL,
DEFAULT_OPENCODE_GO_BASE_URL, DEFAULT_OPENCODE_GO_MODEL, DEFAULT_OPENCODE_ZEN_BASE_URL,
DEFAULT_OPENCODE_ZEN_MODEL, DEFAULT_OPENMODEL_BASE_URL, DEFAULT_OPENMODEL_MODEL,
DEFAULT_OPENROUTER_BASE_URL, DEFAULT_OPENROUTER_MODEL, DEFAULT_ORCAROUTER_BASE_URL,
DEFAULT_ORCAROUTER_MODEL, DEFAULT_QIANFAN_BASE_URL, DEFAULT_QIANFAN_MODEL,
DEFAULT_SAKANA_BASE_URL, DEFAULT_SAKANA_MODEL, DEFAULT_SGLANG_BASE_URL, DEFAULT_SGLANG_MODEL,
DEFAULT_SILICONFLOW_BASE_URL, DEFAULT_SILICONFLOW_CN_BASE_URL, DEFAULT_SILICONFLOW_MODEL,
DEFAULT_STEPFUN_BASE_URL, DEFAULT_STEPFUN_MODEL, DEFAULT_TELECOMJS_BASE_URL,
DEFAULT_TELECOMJS_MODEL, DEFAULT_TOGETHER_BASE_URL, DEFAULT_TOGETHER_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_XAI_BASE_URL, DEFAULT_XAI_MODEL, DEFAULT_XIAOMI_MIMO_BASE_URL,
DEFAULT_XIAOMI_MIMO_MODEL, DEFAULT_ZAI_BASE_URL, DEFAULT_ZAI_MODEL, DEFAULT_ZENMUX_BASE_URL,
DEFAULT_ZENMUX_MODEL, MODELSTUDIO_CODING_PLAN_ANTHROPIC_BASE_URL,
MODELSTUDIO_TOKEN_PLAN_ANTHROPIC_BASE_URL, ProviderKind,
DEFAULT_CONCENTRATE_MODEL, DEFAULT_CSDN_BASE_URL, DEFAULT_CSDN_MODEL,
DEFAULT_DEEPINFRA_BASE_URL, DEFAULT_DEEPINFRA_MODEL, DEFAULT_DEEPSEEK_ANTHROPIC_BASE_URL,
DEFAULT_DEEPSEEK_ANTHROPIC_MODEL, DEFAULT_DEEPSEEK_BASE_URL, DEFAULT_DEEPSEEK_MODEL,
DEFAULT_EDENAI_BASE_URL, DEFAULT_EDENAI_MODEL, DEFAULT_FIREWORKS_BASE_URL,
DEFAULT_FIREWORKS_MODEL, DEFAULT_GOOGLE_BASE_URL, DEFAULT_GOOGLE_MODEL,
DEFAULT_HUGGINGFACE_BASE_URL, DEFAULT_HUGGINGFACE_MODEL, DEFAULT_LONGCAT_BASE_URL,
DEFAULT_LONGCAT_MODEL, DEFAULT_META_BASE_URL, DEFAULT_META_MODEL,
DEFAULT_MINIMAX_ANTHROPIC_BASE_URL, DEFAULT_MINIMAX_BASE_URL, DEFAULT_MINIMAX_MODEL,
DEFAULT_MISTRAL_BASE_URL, DEFAULT_MISTRAL_MODEL, DEFAULT_MODELSCOPE_BASE_URL,
DEFAULT_MODELSCOPE_MODEL, DEFAULT_MODELSTUDIO_CODING_PLAN_BASE_URL,
DEFAULT_MODELSTUDIO_TOKEN_PLAN_BASE_URL, DEFAULT_MODELSTUDIO_TOKEN_PLAN_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_CLOUD_BASE_URL, DEFAULT_OLLAMA_CLOUD_MODEL,
DEFAULT_OLLAMA_MODEL, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_CODEX_BASE_URL,
DEFAULT_OPENAI_CODEX_MODEL, DEFAULT_OPENAI_MODEL, DEFAULT_OPENCODE_GO_BASE_URL,
DEFAULT_OPENCODE_GO_MODEL, DEFAULT_OPENCODE_ZEN_BASE_URL, DEFAULT_OPENCODE_ZEN_MODEL,
DEFAULT_OPENMODEL_BASE_URL, DEFAULT_OPENMODEL_MODEL, DEFAULT_OPENROUTER_BASE_URL,
DEFAULT_OPENROUTER_MODEL, DEFAULT_ORCAROUTER_BASE_URL, DEFAULT_ORCAROUTER_MODEL,
DEFAULT_QIANFAN_BASE_URL, DEFAULT_QIANFAN_MODEL, DEFAULT_SAKANA_BASE_URL, DEFAULT_SAKANA_MODEL,
DEFAULT_SGLANG_BASE_URL, DEFAULT_SGLANG_MODEL, DEFAULT_SILICONFLOW_BASE_URL,
DEFAULT_SILICONFLOW_CN_BASE_URL, DEFAULT_SILICONFLOW_MODEL, DEFAULT_STEPFUN_BASE_URL,
DEFAULT_STEPFUN_MODEL, DEFAULT_TELECOMJS_BASE_URL, DEFAULT_TELECOMJS_MODEL,
DEFAULT_TOGETHER_BASE_URL, DEFAULT_TOGETHER_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_XAI_BASE_URL, DEFAULT_XAI_MODEL,
DEFAULT_XIAOMI_MIMO_BASE_URL, DEFAULT_XIAOMI_MIMO_MODEL, DEFAULT_ZAI_BASE_URL,
DEFAULT_ZAI_MODEL, DEFAULT_ZENMUX_BASE_URL, DEFAULT_ZENMUX_MODEL,
MODELSTUDIO_CODING_PLAN_ANTHROPIC_BASE_URL, MODELSTUDIO_TOKEN_PLAN_ANTHROPIC_BASE_URL,
ProviderKind,
};
/// Wire protocol spoken by a provider.
@@ -494,6 +495,12 @@ pub const fn credential_help(kind: ProviderKind) -> CredentialHelp {
docs_url: Some("https://zenmux.ai/docs/"),
guidance: "Create a ZenMux API key from the Pay As You Go management page, then select models by their provider/model namespaced id. The catalog at https://zenmux.ai/api/v1/models is keyless-readable.",
},
ProviderKind::Csdn => CredentialHelp {
acquisition: ApiKey,
credential_url: Some("https://ai.csdn.net/workbench/api-key"),
docs_url: Some("https://ai.csdn.net/coding-plan"),
guidance: "Create an API key in the CSDN console — choose the Coding Plan key type so glm_for_coding calls bill against plan quota; a general key bills metered.",
},
ProviderKind::Codewhale => CredentialHelp {
acquisition: ApiKey,
credential_url: Some(CODEWHALE_API_KEY_URL),
@@ -658,6 +665,18 @@ pub fn is_exact_minimax_anthropic_route(kind: ProviderKind, base_url: &str) -> b
|| is_exact_https_route(base_url, "api.minimaxi.com", "anthropic"))
}
/// Whether a configured route is exactly CSDN 星图's official OpenAI-compatible
/// platform endpoint.
///
/// Coding Plan keys and general marketplace keys share this one endpoint, so
/// the URL proves neither product — only that the route is first-party.
/// Neighboring paths, HTTP downgrades, and lookalike hosts must not inherit
/// CSDN billing or wire semantics.
#[must_use]
pub fn is_exact_csdn_platform_route(kind: ProviderKind, base_url: &str) -> bool {
kind == ProviderKind::Csdn && is_exact_https_route(base_url, "ai.csdn.net", "api/model/v1")
}
/// Return credential help for one concrete provider route.
///
/// This protects non-UI callers such as diagnostics and command surfaces from
@@ -1568,6 +1587,23 @@ provider!(
"zenmux",
aliases: ["zen-mux", "zen_mux"]
);
provider!(
Csdn,
Csdn,
"csdn",
"CSDN",
DEFAULT_CSDN_BASE_URL,
DEFAULT_CSDN_MODEL,
["CSDN_API_KEY"],
"csdn",
aliases: [
"csdn-ai",
"csdn_ai",
"csdn-coding-plan",
"csdn_coding_plan",
"starmap"
]
);
/// Concentrate — OpenAI Responses-compatible AI gateway (aggregator).
///
@@ -1913,6 +1949,7 @@ static ANTIGRAVITY: Antigravity = Antigravity;
static TELECOMJS: Telecomjs = Telecomjs;
static EDENAI: Edenai = Edenai;
static ZENMUX: Zenmux = Zenmux;
static CSDN: Csdn = Csdn;
static CONCENTRATE: Concentrate = Concentrate;
static CODEWHALE: Codewhale = Codewhale;
static MODELSTUDIO_TOKEN_PLAN: ModelstudioTokenPlan = ModelstudioTokenPlan;
@@ -1923,7 +1960,7 @@ static MODELSTUDIO_CODING_PLAN_ANTHROPIC: ModelstudioCodingPlanAnthropic =
ModelstudioCodingPlanAnthropic;
static CUSTOM: Custom = Custom;
static PROVIDER_REGISTRY: [&dyn Provider; 51] = [
static PROVIDER_REGISTRY: [&dyn Provider; 52] = [
&DEEPSEEK,
&DEEPSEEK_ANTHROPIC,
&NVIDIA_NIM,
@@ -1966,6 +2003,7 @@ static PROVIDER_REGISTRY: [&dyn Provider; 51] = [
&TELECOMJS,
&EDENAI,
&ZENMUX,
&CSDN,
&CONCENTRATE,
&CODEWHALE,
&MODELSTUDIO_TOKEN_PLAN,
+2
View File
@@ -213,6 +213,8 @@ pub(crate) const DEFAULT_ZENMUX_BASE_URL: &str = "https://zenmux.ai/api/v1";
// `provider/model` pins a provider and `concentrate/auto` is the gateway router.
pub(crate) const DEFAULT_CONCENTRATE_MODEL: &str = "deepseek-v4-pro";
pub(crate) const DEFAULT_CONCENTRATE_BASE_URL: &str = "https://api.concentrate.ai/v1";
pub(crate) const DEFAULT_CSDN_MODEL: &str = "glm_for_coding";
pub(crate) const DEFAULT_CSDN_BASE_URL: &str = "https://ai.csdn.net/api/model/v1";
// Codewhale API (account-backed model access) defaults. The account control
// plane serves an OpenAI-shaped `GET /v1/models` whose rows carry the wire
// protocol per model, so this route is model-aware and its catalog is the
+17 -1
View File
@@ -233,6 +233,21 @@ pub enum ProviderKind {
/// `https://zenmux.ai/api/v1`. The `/models` catalog is keyless-readable.
#[serde(alias = "zen-mux", alias = "zen_mux")]
Zenmux,
/// CSDN 星图 (Starmap) — CSDN's hosted OpenAI-compatible model platform
/// and Coding Plan subscription.
///
/// The plan model id `glm_for_coding` (GLM-5.2 underneath, 200k context
/// cap) bills against plan quota and requires the dedicated Coding Plan
/// key type; other marketplace model ids and general keys bill metered
/// through the same endpoint.
#[serde(
alias = "csdn-ai",
alias = "csdn_ai",
alias = "csdn-coding-plan",
alias = "csdn_coding_plan",
alias = "starmap"
)]
Csdn,
/// Concentrate — OpenAI Responses-compatible AI gateway (aggregator).
///
/// Serves a broad catalog of upstream models over the OpenAI Responses
@@ -281,7 +296,7 @@ impl ProviderKind {
/// stay on the enum for serde and `provider_for_kind`, but they are not
/// first-class catalog rows. Plan is `mode` / base_url; dialect is
/// `wire = openai|anthropic` on the primary provider config.
pub const ALL: [Self; 45] = [
pub const ALL: [Self; 46] = [
Self::Deepseek,
Self::NvidiaNim,
Self::Openai,
@@ -324,6 +339,7 @@ impl ProviderKind {
Self::Google,
Self::Edenai,
Self::Zenmux,
Self::Csdn,
Self::Concentrate,
Self::Codewhale,
Self::Custom,
@@ -6,6 +6,7 @@ arcee
atlascloud
codewhale
concentrate
csdn
custom
deepinfra
deepseek
@@ -122,6 +122,24 @@
],
"transport": "open-ai-responses"
},
{
"id": "csdn",
"family": "csdn",
"label": "CSDN",
"endpoint": "https://ai.csdn.net/api/model/v1",
"wire": "chat-completions",
"defaultModel": "glm_for_coding",
"envVars": [
"CSDN_API_KEY"
],
"auth": [
{
"kind": "api-key",
"label": "API key"
}
],
"transport": "chat-completions"
},
{
"id": "custom",
"family": "custom",
+72
View File
@@ -5230,6 +5230,78 @@ model = "z-ai/glm-5.3"
assert_eq!(resolved.api_key_source, Some(RuntimeApiKeySource::Env));
}
/// CSDN 星图 (Starmap) is an OpenAI-compatible hosted platform whose default
/// route is the Coding Plan model `glm_for_coding`: aliases collapse onto one
/// catalog identity, the metadata names the official base URL and a distinct
/// `CSDN_API_KEY` slot, the wire policy is fixed on Chat Completions, and
/// env/config overrides resolve exactly like every other provider table.
#[test]
fn csdn_resolves_named_chat_provider_and_environment_overrides() {
let _lock = env_lock();
let _env = EnvGuard::without_deepseek_runtime_overrides();
for alias in [
"csdn",
"csdn-ai",
"csdn_ai",
"csdn-coding-plan",
"csdn_coding_plan",
"starmap",
] {
assert_eq!(ProviderKind::parse(alias), Some(ProviderKind::Csdn));
let parsed: ConfigToml =
toml::from_str(&format!("provider = \"{alias}\"")).expect("CSDN alias");
assert_eq!(parsed.provider, ProviderKind::Csdn);
}
let metadata = provider::resolve_provider("starmap").expect("CSDN metadata");
assert_eq!(metadata.id(), "csdn");
assert_eq!(metadata.display_name(), "CSDN");
assert_eq!(metadata.provider_config_key(), "csdn");
assert_eq!(metadata.default_base_url(), DEFAULT_CSDN_BASE_URL);
assert_eq!(metadata.default_model(), DEFAULT_CSDN_MODEL);
assert_eq!(metadata.env_vars(), &["CSDN_API_KEY"]);
assert_eq!(
metadata.wire_policy(),
provider::WirePolicy::Fixed(provider::WireFormat::ChatCompletions)
);
let config: ConfigToml = toml::from_str(
r#"
provider = "csdn"
[providers.csdn]
api_key = "csdn-config-key"
model = "glm_for_coding"
"#,
)
.expect("CSDN provider table");
let resolved = config.resolve_runtime_options(&CliRuntimeOverrides::default());
assert_eq!(resolved.provider, ProviderKind::Csdn);
assert_eq!(resolved.base_url, DEFAULT_CSDN_BASE_URL);
assert_eq!(resolved.model, "glm_for_coding");
assert_eq!(resolved.api_key.as_deref(), Some("csdn-config-key"));
assert_eq!(
resolved.api_key_source,
Some(RuntimeApiKeySource::ConfigFile)
);
unsafe {
std::env::set_var("CSDN_API_KEY", "csdn-env-key");
std::env::set_var("CSDN_BASE_URL", "https://ai.csdn.net/api/model/v1");
std::env::set_var("CSDN_MODEL", "deepseek-v3.2");
}
let env_config = ConfigToml {
provider: ProviderKind::Csdn,
..ConfigToml::default()
};
let resolved = env_config.resolve_runtime_options(&CliRuntimeOverrides::default());
assert_eq!(resolved.base_url, "https://ai.csdn.net/api/model/v1");
assert_eq!(resolved.model, "deepseek-v3.2");
assert_eq!(resolved.api_key.as_deref(), Some("csdn-env-key"));
assert_eq!(resolved.api_key_source, Some(RuntimeApiKeySource::Env));
}
/// Concentrate is an opt-in, BYOK, Responses-wire gateway: aliases collapse
/// onto one catalog identity, the metadata names the official base URL and a
/// distinct `CONCENTRATE_API_KEY` slot, the wire policy is fixed on the
+25
View File
@@ -1197,6 +1197,7 @@ impl Secrets {
/// | `telecomjs` / `tokenhub` | `TELECOMJS_API_KEY` |
/// | `edenai` / `eden-ai` | `EDENAI_API_KEY` |
/// | `zenmux` / `zen-mux` | `ZENMUX_API_KEY` |
/// | `csdn` / `csdn-ai` / `starmap` | `CSDN_API_KEY` |
/// | `concentrate` / `concentrate-ai` | `CONCENTRATE_API_KEY` |
/// | `codewhale` / `codewhale-api` | `CODEWHALE_API_KEY` |
///
@@ -1255,6 +1256,9 @@ pub fn env_for(name: &str) -> Option<String> {
}
"edenai" | "eden-ai" | "eden_ai" => &["EDENAI_API_KEY"],
"zenmux" | "zen-mux" | "zen_mux" => &["ZENMUX_API_KEY"],
"csdn" | "csdn-ai" | "csdn_ai" | "csdn-coding-plan" | "csdn_coding_plan" | "starmap" => {
&["CSDN_API_KEY"]
}
"concentrate" | "concentrate-ai" | "concentrate_ai" | "concentrateai" => {
&["CONCENTRATE_API_KEY"]
}
@@ -1366,6 +1370,7 @@ mod tests {
"TELECOMJS_API_KEY",
"EDENAI_API_KEY",
"ZENMUX_API_KEY",
"CSDN_API_KEY",
"CONCENTRATE_API_KEY",
"MODELSTUDIO_API_KEY",
"DASHSCOPE_API_KEY",
@@ -1987,6 +1992,26 @@ mod tests {
clear_known_envs();
}
#[test]
fn csdn_env_aliases_resolve() {
let _guard = env_lock();
clear_known_envs();
unsafe { std::env::set_var("CSDN_API_KEY", "csdn-key") };
for alias in [
"csdn",
"csdn-ai",
"csdn_ai",
"csdn-coding-plan",
"csdn_coding_plan",
"starmap",
] {
assert_eq!(env_for(alias).as_deref(), Some("csdn-key"), "{alias}");
}
clear_known_envs();
}
#[test]
fn concentrate_env_aliases_resolve() {
let _guard = env_lock();
+9
View File
@@ -4735,6 +4735,9 @@ pub(super) fn apply_reasoning_effort(
// provider must not inject a model-specific reasoning dialect.
ApiProvider::Edenai => {}
ApiProvider::Zenmux => {}
// CSDN 星图 is OpenAI-compatible but documents no provider-owned
// reasoning dialect; do not invent one.
ApiProvider::Csdn => {}
// The Codewhale API is a passthrough to the account's own
// connected provider; it documents no Codewhale-owned
// reasoning-effort translation, so nothing is invented here.
@@ -4835,6 +4838,9 @@ pub(super) fn apply_reasoning_effort(
ApiProvider::Telecomjs => {}
ApiProvider::Edenai => {}
ApiProvider::Zenmux => {}
// CSDN 星图 is OpenAI-compatible but documents no provider-owned
// reasoning dialect; do not invent one.
ApiProvider::Csdn => {}
// The Codewhale API is a passthrough to the account's own
// connected provider; it documents no Codewhale-owned
// reasoning-effort translation, so nothing is invented here.
@@ -4960,6 +4966,9 @@ pub(super) fn apply_reasoning_effort(
ApiProvider::Telecomjs => {}
ApiProvider::Edenai => {}
ApiProvider::Zenmux => {}
// CSDN 星图 is OpenAI-compatible but documents no provider-owned
// reasoning dialect; do not invent one.
ApiProvider::Csdn => {}
// The Codewhale API is a passthrough to the account's own
// connected provider; it documents no Codewhale-owned
// reasoning-effort translation, so nothing is invented here.
+58 -2
View File
@@ -124,6 +124,8 @@ pub enum ApiProvider {
Edenai,
/// ZenMux — OpenAI-compatible AI gateway (aggregator).
Zenmux,
/// CSDN 星图 — OpenAI-compatible hosted platform and Coding Plan.
Csdn,
/// Concentrate — OpenAI Responses-compatible AI gateway (aggregator; BYOK only).
Concentrate,
/// Codewhale API — account-backed model access over connected provider keys.
@@ -371,7 +373,7 @@ impl ApiProvider {
/// `ApiProvider` discriminant → `ProviderKind` lookup.
/// Index 1 is `None` for the legacy `DeepseekCN` variant.
const KIND_LOOKUP: [Option<codewhale_config::ProviderKind>; 52] = [
const KIND_LOOKUP: [Option<codewhale_config::ProviderKind>; 53] = [
Some(codewhale_config::ProviderKind::Deepseek),
None, // DeepseekCN
Some(codewhale_config::ProviderKind::DeepseekAnthropic),
@@ -417,6 +419,7 @@ impl ApiProvider {
Some(codewhale_config::ProviderKind::Telecomjs),
Some(codewhale_config::ProviderKind::Edenai),
Some(codewhale_config::ProviderKind::Zenmux),
Some(codewhale_config::ProviderKind::Csdn),
Some(codewhale_config::ProviderKind::Concentrate),
Some(codewhale_config::ProviderKind::Codewhale),
Some(codewhale_config::ProviderKind::ModelstudioTokenPlan),
@@ -427,7 +430,7 @@ impl ApiProvider {
];
/// `ProviderKind` discriminant → `ApiProvider` lookup.
const FROM_KIND_LOOKUP: [Self; 51] = [
const FROM_KIND_LOOKUP: [Self; 52] = [
Self::Deepseek,
Self::DeepseekAnthropic,
Self::NvidiaNim,
@@ -476,6 +479,7 @@ impl ApiProvider {
Self::Google,
Self::Edenai,
Self::Zenmux,
Self::Csdn,
Self::Concentrate,
Self::Codewhale,
Self::Custom,
@@ -547,6 +551,10 @@ fn subagent_provider_key_matches(key: &str, provider: ApiProvider) -> bool {
ApiProvider::Orcarouter => matches!(normalized.as_str(), "orcarouter" | "orca_router"),
ApiProvider::Edenai => matches!(normalized.as_str(), "edenai" | "eden_ai"),
ApiProvider::Zenmux => matches!(normalized.as_str(), "zenmux" | "zen_mux"),
ApiProvider::Csdn => matches!(
normalized.as_str(),
"csdn" | "csdn_ai" | "csdn_coding_plan" | "starmap"
),
ApiProvider::Concentrate => matches!(
normalized.as_str(),
"concentrate" | "concentrate_ai" | "concentrateai"
@@ -1713,6 +1721,7 @@ pub fn model_completion_names_for_provider(provider: ApiProvider) -> Vec<&'stati
ApiProvider::Antigravity => Vec::new(),
ApiProvider::Edenai => vec![DEFAULT_EDENAI_MODEL],
ApiProvider::Zenmux => vec![DEFAULT_ZENMUX_MODEL],
ApiProvider::Csdn => vec![DEFAULT_CSDN_MODEL],
ApiProvider::Concentrate => vec![DEFAULT_CONCENTRATE_MODEL],
// Bootstrap rows only. The account's authenticated `GET /v1/models`
// lists exactly the providers this customer connected and replaces
@@ -3924,6 +3933,16 @@ pub struct ProvidersConfig {
/// ZenMux — OpenAI-compatible AI gateway (aggregator).
#[serde(default, alias = "zen-mux", alias = "zen_mux")]
pub zenmux: ProviderConfig,
/// CSDN 星图 — OpenAI-compatible hosted platform and Coding Plan.
#[serde(
default,
alias = "csdn-ai",
alias = "csdn_ai",
alias = "csdn-coding-plan",
alias = "csdn_coding_plan",
alias = "starmap"
)]
pub csdn: ProviderConfig,
/// Concentrate — OpenAI Responses-compatible AI gateway (aggregator).
#[serde(
default,
@@ -5823,6 +5842,7 @@ impl Config {
ApiProvider::Telecomjs => &providers.telecomjs,
ApiProvider::Edenai => &providers.edenai,
ApiProvider::Zenmux => &providers.zenmux,
ApiProvider::Csdn => &providers.csdn,
ApiProvider::Concentrate => &providers.concentrate,
ApiProvider::Codewhale => &providers.codewhale,
ApiProvider::ModelstudioTokenPlan => &providers.modelstudio_token_plan,
@@ -5926,6 +5946,7 @@ impl Config {
ApiProvider::Telecomjs => &mut providers.telecomjs,
ApiProvider::Edenai => &mut providers.edenai,
ApiProvider::Zenmux => &mut providers.zenmux,
ApiProvider::Csdn => &mut providers.csdn,
ApiProvider::Concentrate => &mut providers.concentrate,
ApiProvider::Codewhale => &mut providers.codewhale,
ApiProvider::ModelstudioTokenPlan => &mut providers.modelstudio_token_plan,
@@ -6401,6 +6422,7 @@ impl Config {
ApiProvider::Telecomjs => DEFAULT_TELECOMJS_MODEL,
ApiProvider::Edenai => DEFAULT_EDENAI_MODEL,
ApiProvider::Zenmux => DEFAULT_ZENMUX_MODEL,
ApiProvider::Csdn => DEFAULT_CSDN_MODEL,
ApiProvider::Concentrate => DEFAULT_CONCENTRATE_MODEL,
ApiProvider::Codewhale => DEFAULT_CODEWHALE_MODEL,
ApiProvider::ModelstudioTokenPlan
@@ -6528,6 +6550,7 @@ impl Config {
| ApiProvider::Telecomjs
| ApiProvider::Edenai
| ApiProvider::Zenmux
| ApiProvider::Csdn
| ApiProvider::Concentrate
| ApiProvider::Codewhale
| ApiProvider::ModelstudioTokenPlan
@@ -6642,6 +6665,7 @@ impl Config {
ApiProvider::Telecomjs => DEFAULT_TELECOMJS_BASE_URL,
ApiProvider::Edenai => DEFAULT_EDENAI_BASE_URL,
ApiProvider::Zenmux => DEFAULT_ZENMUX_BASE_URL,
ApiProvider::Csdn => DEFAULT_CSDN_BASE_URL,
ApiProvider::Concentrate => DEFAULT_CONCENTRATE_BASE_URL,
ApiProvider::Codewhale => DEFAULT_CODEWHALE_BASE_URL,
ApiProvider::ModelstudioTokenPlan
@@ -8551,6 +8575,7 @@ fn provider_env_base_url_override(provider: ApiProvider) -> Option<String> {
ApiProvider::Telecomjs => &["TELECOMJS_BASE_URL"],
ApiProvider::Edenai => &["EDENAI_BASE_URL"],
ApiProvider::Zenmux => &["ZENMUX_BASE_URL"],
ApiProvider::Csdn => &["CSDN_BASE_URL"],
ApiProvider::Concentrate => &["CONCENTRATE_BASE_URL"],
ApiProvider::Codewhale => &["CODEWHALE_API_BASE"],
ApiProvider::ModelstudioTokenPlan | ApiProvider::ModelstudioTokenPlanAnthropic => {
@@ -8951,6 +8976,13 @@ fn apply_env_overrides_unlocked(config: &mut Config, policy: ConfigEnvironmentPo
.zenmux
.base_url = Some(value);
}
ApiProvider::Csdn => {
config
.providers
.get_or_insert_with(ProvidersConfig::default)
.csdn
.base_url = Some(value);
}
ApiProvider::Concentrate => {
config
.providers
@@ -9242,6 +9274,16 @@ fn apply_env_overrides_unlocked(config: &mut Config, policy: ConfigEnvironmentPo
.zenmux
.base_url = Some(value);
}
if matches!(config.api_provider(), ApiProvider::Csdn)
&& let Ok(value) = std::env::var("CSDN_BASE_URL")
&& !value.trim().is_empty()
{
config
.providers
.get_or_insert_with(ProvidersConfig::default)
.csdn
.base_url = Some(value);
}
// Concentrate has no inline block here on purpose: CONCENTRATE_BASE_URL
// is already served by `provider_env_base_url_override`, which the route
// resolver consults — a second inline assignment was a duplicate.
@@ -9359,6 +9401,7 @@ fn apply_env_overrides_unlocked(config: &mut Config, policy: ConfigEnvironmentPo
ApiProvider::Telecomjs => &mut providers.telecomjs,
ApiProvider::Edenai => &mut providers.edenai,
ApiProvider::Zenmux => &mut providers.zenmux,
ApiProvider::Csdn => &mut providers.csdn,
ApiProvider::Concentrate => &mut providers.concentrate,
ApiProvider::Codewhale => &mut providers.codewhale,
ApiProvider::ModelstudioTokenPlan => &mut providers.modelstudio_token_plan,
@@ -9661,6 +9704,17 @@ fn apply_env_overrides_unlocked(config: &mut Config, policy: ConfigEnvironmentPo
.model = Some(value);
config.environment_model_applied = true;
}
if matches!(config.api_provider(), ApiProvider::Csdn)
&& let Ok(value) = std::env::var("CSDN_MODEL")
&& !value.trim().is_empty()
{
config
.providers
.get_or_insert_with(ProvidersConfig::default)
.csdn
.model = Some(value);
config.environment_model_applied = true;
}
if matches!(
config.api_provider(),
ApiProvider::ModelstudioTokenPlan | ApiProvider::ModelstudioTokenPlanAnthropic
@@ -10096,6 +10150,7 @@ pub(crate) fn provider_passes_model_through(provider: ApiProvider) -> bool {
| ApiProvider::Telecomjs
| ApiProvider::Edenai
| ApiProvider::Zenmux
| ApiProvider::Csdn
// Concentrate ids are gateway-owned (plain, `provider/model`, or the
// gateway's own `auto`); the resolver strips only `concentrate/`.
| ApiProvider::Concentrate
@@ -11380,6 +11435,7 @@ fn merge_providers(
telecomjs: merge_provider_config(base.telecomjs, override_cfg.telecomjs),
edenai: merge_provider_config(base.edenai, override_cfg.edenai),
zenmux: merge_provider_config(base.zenmux, override_cfg.zenmux),
csdn: merge_provider_config(base.csdn, override_cfg.csdn),
concentrate: merge_provider_config(base.concentrate, override_cfg.concentrate),
codewhale: merge_provider_config(base.codewhale, override_cfg.codewhale),
modelstudio_token_plan: merge_provider_config(
+2
View File
@@ -273,6 +273,8 @@ pub const DEFAULT_EDENAI_MODEL: &str = "deepseek/deepseek-v4-pro";
pub const DEFAULT_EDENAI_BASE_URL: &str = "https://api.edenai.run/v3";
pub const DEFAULT_ZENMUX_MODEL: &str = "deepseek/deepseek-v4.1-flash";
pub const DEFAULT_ZENMUX_BASE_URL: &str = "https://zenmux.ai/api/v1";
pub const DEFAULT_CSDN_MODEL: &str = "glm_for_coding";
pub const DEFAULT_CSDN_BASE_URL: &str = "https://ai.csdn.net/api/model/v1";
// Concentrate (OpenAI Responses-compatible AI gateway) defaults; mirrors
// crates/config/src/provider_defaults.rs for the docs drift check.
pub const DEFAULT_CONCENTRATE_MODEL: &str = "deepseek-v4-pro";
+1
View File
@@ -617,6 +617,7 @@ fn provider_base_url_table_key(provider: ApiProvider) -> anyhow::Result<&'static
ApiProvider::Telecomjs => Ok("telecomjs"),
ApiProvider::Edenai => Ok("edenai"),
ApiProvider::Zenmux => Ok("zenmux"),
ApiProvider::Csdn => Ok("csdn"),
ApiProvider::Concentrate => Ok("concentrate"),
ApiProvider::Codewhale => Ok("codewhale"),
ApiProvider::ModelstudioTokenPlan => Ok("modelstudio_token_plan"),
+19
View File
@@ -267,6 +267,10 @@ pub(crate) const AGGREGATOR_BILLING_SURFACE: &str = "aggregator-payg";
pub(crate) const MODELSTUDIO_TOKEN_PLAN_BILLING_SURFACE: &str = "modelstudio-token-plan";
pub(crate) const MODELSTUDIO_CODING_PLAN_BILLING_SURFACE: &str = "modelstudio-coding-plan";
pub(crate) const VOLCENGINE_CODING_PLAN_BILLING_SURFACE: &str = "volcengine-coding-plan";
/// CSDN 星图's Coding Plan subscription product (the `glm_for_coding` route).
pub(crate) const CSDN_CODING_PLAN_BILLING_SURFACE: &str = "csdn-coding-plan";
/// CSDN 星图's ordinary metered marketplace access on the same endpoint.
pub(crate) const CSDN_PAYG_BILLING_SURFACE: &str = "csdn-payg";
/// A reachable endpoint CodeWhale could not match to any known billing surface.
/// Distinct from "not classified yet": this is a positive statement that the
/// surface is unknown, and it fails closed everywhere it is consumed.
@@ -308,6 +312,7 @@ pub fn endpoint_metering_for_billing_surface(billing_surface: Option<&str>) -> E
(MOONSHOT_PAYG_BILLING_SURFACE, EndpointMetering::Money),
(MINIMAX_PAYG_BILLING_SURFACE, EndpointMetering::Money),
(XIAOMI_PAYG_BILLING_SURFACE, EndpointMetering::Money),
(CSDN_PAYG_BILLING_SURFACE, EndpointMetering::Money),
(FIRST_PARTY_PAYG_BILLING_SURFACE, EndpointMetering::Money),
(AGGREGATOR_BILLING_SURFACE, EndpointMetering::Money),
(
@@ -342,6 +347,10 @@ pub fn endpoint_metering_for_billing_surface(billing_surface: Option<&str>) -> E
XIAOMI_TOKEN_PLAN_BILLING_SURFACE,
EndpointMetering::ExactSubscription,
),
(
CSDN_CODING_PLAN_BILLING_SURFACE,
EndpointMetering::ExactSubscription,
),
(
OAUTH_SUBSCRIPTION_BILLING_SURFACE,
EndpointMetering::ExactSubscription,
@@ -430,6 +439,7 @@ pub(crate) fn billing_surface_for_route(
ApiProvider::Zai => zai_surface(&shape),
ApiProvider::Moonshot => moonshot_surface(&shape),
ApiProvider::Minimax | ApiProvider::MinimaxAnthropic => minimax_surface(&shape),
ApiProvider::Csdn => csdn_surface(&shape),
ApiProvider::XiaomiMimo => xiaomi_surface(&shape),
ApiProvider::ModelstudioTokenPlan
| ApiProvider::ModelstudioTokenPlanAnthropic
@@ -541,6 +551,15 @@ fn minimax_surface(shape: &EndpointShape) -> Option<&'static str> {
None
}
fn csdn_surface(shape: &EndpointShape) -> Option<&'static str> {
// Coding Plan keys and general marketplace keys share the one
// ai.csdn.net/api/model/v1 endpoint, so the URL proves neither product;
// only the captured credential product can produce a concrete surface.
let _is_supported_endpoint = shape.host == "ai.csdn.net"
&& matches!(shape.path.as_str(), "/api/model" | "/api/model/v1");
None
}
fn xiaomi_surface(shape: &EndpointShape) -> Option<&'static str> {
if matches!(
shape.host.as_str(),
@@ -120,7 +120,7 @@ fn shipped_default_routes_have_reviewed_pricing_coverage() {
// A deliberate breadth receipt, including the generic Custom placeholder.
// Inventory still comes from production owners, not these expected counts.
assert_eq!(built_in_count, 50, "review changed shipped-default breadth");
assert_eq!(built_in_count, 51, "review changed shipped-default breadth");
assert_eq!(
defaults.len() - built_in_count,
6,
@@ -60,6 +60,7 @@
"reason": "not_money_metered",
"review": "Exact shipped plan/OAuth/local surfaces are excluded from money coverage before model-price lookup (route_billing.rs:345,470; pricing.rs:398; cost_status.rs:235). Retain exact dialect and plan endpoint distinctions; the same model on PAYG is a different tuple.",
"routes": [
["csdn", "glm_for_coding", "https://ai.csdn.net/api/model/v1", "chat_completions", "subscription"],
["modelstudio-coding-plan", "qwen3.8-max", "https://coding-intl.dashscope.aliyuncs.com/v1", "chat_completions", "subscription"],
["modelstudio-coding-plan-anthropic", "qwen3.8-max", "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic", "anthropic_messages", "subscription"],
["modelstudio-token-plan", "qwen3.8-max", "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1", "chat_completions", "subscription"],
+64 -1
View File
@@ -260,6 +260,11 @@ pub fn capture_product(config: &Config, provider: ApiProvider) -> RouteProduct {
CredentialProduct::Unprovable => RouteProduct::Unproven,
}
}
ApiProvider::Csdn => match csdn_credential_product(provider_config) {
CredentialProduct::Plan => RouteProduct::Subscription("CSDN Coding Plan quota"),
CredentialProduct::PayAsYouGo => RouteProduct::Metered,
CredentialProduct::Unprovable => RouteProduct::Unproven,
},
ApiProvider::XiaomiMimo => {
if xiaomi_is_explicit_pay_as_you_go(provider_config) {
RouteProduct::Metered
@@ -405,6 +410,20 @@ fn classify(
BillingPresentation::Unknown
}
ApiProvider::Minimax | ApiProvider::MinimaxAnthropic => product_billing(product),
// CSDN 星图 sells the Coding Plan (`glm_for_coding`) and metered
// marketplace models over the same endpoint; the URL proves only that
// the route is first-party, so the captured product decides. Anything
// off the supported direct endpoint is Unknown no matter what was
// captured.
ApiProvider::Csdn
if !codewhale_config::provider::is_exact_csdn_platform_route(
codewhale_config::ProviderKind::Csdn,
base_url,
) =>
{
BillingPresentation::Unknown
}
ApiProvider::Csdn => product_billing(product),
ApiProvider::Xai | ApiProvider::Anthropic => product_billing(product),
// A named custom route is billed from the identity and endpoint it
// dispatched on. Without an identity there is no vendor to name, and
@@ -533,6 +552,7 @@ pub fn billing_surface_for_dispatch(
ApiProvider::Minimax | ApiProvider::MinimaxAnthropic => {
crate::pricing::MINIMAX_TOKEN_PLAN_BILLING_SURFACE
}
ApiProvider::Csdn => crate::pricing::CSDN_CODING_PLAN_BILLING_SURFACE,
ApiProvider::OpenaiCodex
| ApiProvider::OpencodeGo
| ApiProvider::Anthropic
@@ -549,6 +569,9 @@ pub fn billing_surface_for_dispatch(
{
return Some(crate::pricing::MINIMAX_PAYG_BILLING_SURFACE);
}
BillingPresentation::Metered if provider == ApiProvider::Csdn => {
return Some(crate::pricing::CSDN_PAYG_BILLING_SURFACE);
}
BillingPresentation::Local => return Some(crate::pricing::LOCAL_BILLING_SURFACE),
BillingPresentation::Unknown | BillingPresentation::Metered => {}
}
@@ -998,6 +1021,42 @@ fn minimax_credential_product(
}
}
/// CSDN 星图 sells the Coding Plan and metered marketplace models over the
/// same `ai.csdn.net/api/model/v1` endpoint and `CSDN_API_KEY` slot, so the
/// product comes from an explicit saved pay mode or the routed model:
/// `glm_for_coding` is the Coding Plan's dedicated model id — the route the
/// plan sells — while every other model on the platform endpoint is ordinary
/// metered marketplace access. An explicit operator-set mode wins over the
/// model in both directions; an unrecognized mode is not a product claim.
fn csdn_credential_product(provider_config: Option<&ProviderConfig>) -> CredentialProduct {
if let Some(mode) = provider_config
.and_then(|config| config.mode.as_deref())
.filter(|mode| !mode.trim().is_empty())
.map(normalized)
{
return match mode.as_str() {
"coding_plan" | "codingplan" | "plan" | "subscription" | "subscription_plan" => {
CredentialProduct::Plan
}
"pay_as_you_go" | "payg" | "paygo" | "pay_as_go" | "metered" | "standard" | "api"
| "api_key" | "default" => CredentialProduct::PayAsYouGo,
_ => CredentialProduct::Unprovable,
};
}
// No table at all still resolves to the plan model: `glm_for_coding` is
// the shipped default for the `csdn` route.
let model = provider_config
.and_then(|entry| entry.model.as_deref())
.map(str::trim)
.filter(|model| !model.is_empty())
.unwrap_or(crate::config::DEFAULT_CSDN_MODEL);
if model.eq_ignore_ascii_case(crate::config::DEFAULT_CSDN_MODEL) {
CredentialProduct::Plan
} else {
CredentialProduct::PayAsYouGo
}
}
/// Whether a MiniMax credential is visible in non-secret-store provenance,
/// and if so whether it carries the Token Plan (`sk-cp…`) product prefix.
///
@@ -3080,6 +3139,10 @@ mod tests {
(ApiProvider::Google, BillingPresentation::Metered),
(ApiProvider::Edenai, BillingPresentation::Metered),
(ApiProvider::Zenmux, BillingPresentation::Metered),
(
ApiProvider::Csdn,
BillingPresentation::Subscription("CSDN Coding Plan quota"),
),
(ApiProvider::Concentrate, BillingPresentation::Metered),
(ApiProvider::Codewhale, BillingPresentation::Metered),
(ApiProvider::Custom, BillingPresentation::Unknown),
@@ -3118,7 +3181,7 @@ mod tests {
}
assert_eq!(
DEFAULT_ROUTE_BILLING_AUDIT.len(),
51,
52,
"the audit covers every provider identity, primary and alternate"
);
+1
View File
@@ -987,6 +987,7 @@ pub(crate) fn mirror_saved_api_key_in_config(
ApiProvider::Telecomjs => &mut providers.telecomjs,
ApiProvider::Edenai => &mut providers.edenai,
ApiProvider::Zenmux => &mut providers.zenmux,
ApiProvider::Csdn => &mut providers.csdn,
ApiProvider::Concentrate => &mut providers.concentrate,
ApiProvider::Codewhale => &mut providers.codewhale,
ApiProvider::ModelstudioTokenPlan => &mut providers.modelstudio_token_plan,
+4 -1
View File
@@ -128,7 +128,7 @@ The canonical provider IDs are the 44 entries of `ProviderKind::ALL`
`together`, `qianfan`, `openai-codex`, `anthropic`, `openmodel`, `zai`,
`stepfun`, `minimax`, `deepinfra`, `sakana`, `longcat`, `opencode-go`,
`opencode-zen`, `meta`, `xai`, `mistral`, `telecomjs`, `modelstudio-token-plan`, `modelscope`,
`google`, `edenai`, `zenmux`, `concentrate`, `codewhale`, and `custom`.
`google`, `edenai`, `zenmux`, `csdn`, `concentrate`, `codewhale`, and `custom`.
`deepseek-anthropic` is *not* on this list — it is a wire dialect of
`deepseek`, reached with `wire = "anthropic"`, not a separate route to select.
@@ -234,6 +234,7 @@ the listed provider env vars.
| `google` | `[providers.google]` | OpenAI Chat Completions (official Gemini OpenAI-compat route; captures and replays thought signatures on tool calls) | `GOOGLE_API_KEY`, `GEMINI_API_KEY` |
| `edenai` | `[providers.edenai]` | OpenAI Chat Completions | `EDENAI_API_KEY` |
| `zenmux` | `[providers.zenmux]` | OpenAI Chat Completions | `ZENMUX_API_KEY` |
| `csdn` | `[providers.csdn]` | OpenAI Chat Completions | `CSDN_API_KEY` |
| `concentrate` | `[providers.concentrate]` | OpenAI Responses (`/v1/responses`) | `CONCENTRATE_API_KEY` |
| `codewhale` | `[providers.codewhale]` | Model-aware: OpenAI Chat Completions (`/v1/chat/completions`) or Anthropic Messages (`/v1/messages`), chosen per model by the account catalog | `CODEWHALE_API_KEY` |
| `modelstudio-token-plan` | `[providers.modelstudio_token_plan]` | OpenAI Chat Completions | `MODELSTUDIO_API_KEY`, `DASHSCOPE_API_KEY` |
@@ -595,6 +596,7 @@ configuration path instead of guessing a vendor page.
| `google` | [Google AI Studio](https://aistudio.google.com/apikey) — Codewhale uses the official Gemini OpenAI-compatible endpoint and never reads Google OAuth files. |
| `edenai` | [Eden AI API keys](https://app.edenai.run/settings/api-keys) |
| `zenmux` | [ZenMux API keys](https://zenmux.ai/platform/pay-as-you-go) |
| `csdn` | [CSDN 星图 console](https://ai.csdn.net/workbench/api-key) — choose the Coding Plan key type for the `glm_for_coding` plan route; a general key bills metered. Docs: [Coding Plan](https://ai.csdn.net/coding-plan). |
| `concentrate` | [Concentrate dashboard](https://concentrate.ai/) → API Keys → Create API Key (a Universal API key); docs: [API introduction](https://concentrate.ai/docs/api-reference/introduction). BYOK only — the key stays in the local secret store. |
| `modelstudio-token-plan`, `modelstudio-token-plan-anthropic`, `modelstudio-coding-plan`, `modelstudio-coding-plan-anthropic` | [Alibaba Cloud Model Studio (Bailian console)](https://bailian.console.aliyun.com/) — create or copy a Model Studio API key. |
| `codewhale` | [Codewhale account settings](https://app.codewhale.net/settings?section=api) — create an API key with the `models:infer` scope, or run `codewhale account api-keys create --name <name> --use`. |
@@ -701,6 +703,7 @@ overlay and lets DSH resolve its own keys.
| `mistral` | `[providers.mistral]` | `MISTRAL_API_KEY` | `MISTRAL_BASE_URL`; default `https://api.mistral.ai/v1` | `mistral-code-latest` (default; `codestral-latest` accepted as alias), `mistral-medium-latest` (aliases: `mistral-medium-3-5`), `mistral-small-latest` (aliases: `mistral-small-2603`), `mistral-large-latest` | Mistral AI (la Plateforme) OpenAI-compatible Chat route. On the documented first-party HTTPS `/v1` hosts, Medium and Small send adjustable `reasoning_effort` (`none` or `high` only), parse Mistral's polymorphic thinking/text blocks, and replay stored thinking in that same wire shape. Deprecated native Magistral IDs remain explicit-configuration compatibility routes: they are always-reasoning and never receive the adjustable effort field. Code and Large are non-reasoning. A custom `MISTRAL_BASE_URL` keeps generic Chat semantics unless it is one of the documented first-party hosts. `MISTRAL_MODEL` is accepted. Provider aliases: `mistral-ai`, `mistralai`, `la-plateforme`. |
| `edenai` | `[providers.edenai]` | `EDENAI_API_KEY` | `EDENAI_BASE_URL`; default `https://api.edenai.run/v3`; EU `https://api.eu.edenai.run/v3` | `deepseek/deepseek-v4-pro` (default); live `/models` catalog of `provider/model` ids | Eden AI OpenAI-compatible aggregation gateway. Catalog rows remain provider-scoped; generic reasoning controls are omitted because supported fields depend on the selected upstream family. `EDENAI_MODEL` is accepted. The default `deepseek/deepseek-v4-pro` is listed on the global catalog only; on the EU endpoint set `EDENAI_MODEL` (or `model`) to a row from the EU `/models` list, for example `qwen/deepseek-v4-pro`. Provider aliases: `eden-ai`, `eden_ai`. |
| `zenmux` | `[providers.zenmux]` | `ZENMUX_API_KEY` | `ZENMUX_BASE_URL`; default `https://zenmux.ai/api/v1` | `deepseek/deepseek-v4.1-flash` (default); live `/models` catalog of `provider/model` ids (keyless-readable) | ZenMux OpenAI-compatible aggregation gateway (~190 models). Catalog rows remain provider-scoped; generic reasoning controls are omitted because supported fields depend on the selected upstream family. `ZENMUX_MODEL` is accepted. Provider aliases: `zen-mux`, `zen_mux`. |
| `csdn` | `[providers.csdn]` | `CSDN_API_KEY` | `CSDN_BASE_URL`; default `https://ai.csdn.net/api/model/v1` | `glm_for_coding` (default; the Coding Plan's dedicated model id); other marketplace model ids pass through | CSDN 星图 (Starmap) OpenAI-compatible hosted platform. Coding Plan keys and general marketplace keys share the one endpoint, so billing follows the credential product, never the URL alone: routing `glm_for_coding` — the shipped default — or setting `mode = "coding_plan"`/`"plan"`/`"subscription"` bills as CSDN Coding Plan quota with no dollar estimates; any other model, or an explicit `pay-as-you-go`/`metered` mode, bills metered; an unrecognized mode or an endpoint off `ai.csdn.net/api/model/v1` reports `cost: unknown`. `CSDN_MODEL` is accepted. Provider aliases: `csdn-ai`, `csdn_ai`, `csdn-coding-plan`, `csdn_coding_plan`, `starmap`. |
| `concentrate` | `[providers.concentrate]` | `CONCENTRATE_API_KEY` | `CONCENTRATE_BASE_URL`; default `https://api.concentrate.ai/v1` | `deepseek-v4-pro` (default; a plain catalog id lets the gateway pick the upstream provider); `provider/model` ids such as `openai/gpt-5.6-sol` pin one upstream; `concentrate/auto` is the gateway's own router; unauthenticated live `/v1/models` catalog | Concentrate OpenAI Responses-compatible gateway (`POST /v1/responses`, bearer Universal API key). Opt-in and BYOK only: your key, your Concentrate bill, zero Codewhale fee, no managed default. Requests carry only documented fields (the system prompt rides as a `system` input item). Contract: [API introduction](https://concentrate.ai/docs/api-reference/introduction), [request parameters](https://concentrate.ai/docs/api-reference/endpoint/request-parameters), [streaming](https://concentrate.ai/docs/api-reference/endpoint/streaming), [errors](https://concentrate.ai/docs/api-reference/endpoint/errors). See [Concentrate Notes](#concentrate-notes). |
| `codewhale` | `[providers.codewhale]` | `CODEWHALE_API_KEY` | `CODEWHALE_API_BASE`; default `https://api.codewhale.net/v1` | `deepseek/deepseek-v4-pro` (default), `anthropic/claude-sonnet-5`, `openai/gpt-5.6` are offline bootstrap rows only; the authenticated `GET /v1/models` listing of the account's connected providers is the catalog authority | Codewhale API: account-backed model access over the provider keys the customer connected to their Codewhale account. One `cwc_key_…` account API key with the `models:infer` scope authenticates every model. Model ids are `provider/model` exactly as the account catalog returns them, and each row states its protocol (`chat-completions` → `/v1/chat/completions`, `anthropic-messages` → `/v1/messages`, `responses` → `/v1/responses`); every protocol uses `Authorization: Bearer`, never `x-api-key`. `CODEWHALE_API_BASE` must be HTTPS except on loopback. Connect provider keys with `codewhale account keys set <provider>`. Provider aliases: `codewhale-api`, `cw-api`, `codewhale-cloud`. |
| `xai` | `[providers.xai]` | `XAI_API_KEY`, Codewhale-owned device OAuth, or explicit read-only Grok CLI consent | `XAI_BASE_URL`; default `https://api.x.ai/v1` | `grok-4.6` (default), `grok-4.5`, `grok-4.3`, `grok-build`, `grok-composer-2.5-fast`, `grok-4.20-0309-reasoning`, `grok-4.20-0309-non-reasoning` | xAI/Grok OpenAI-compatible Chat Completions route. Grok 4.6 has a 500K context window, text/image input, function calls, structured output, server-side web search, and `low`/`medium`/`high`/`xhigh` reasoning (default `high`). Its standard rates double when the prompt reaches 200K tokens; the same 2x long-context rule applies to `grok-4.5` (500K context, $2.00 / $0.30 cached / $6.00) and `grok-4.3` (1M context, $1.25 / $0.20 cached / $2.50) per their [model pages](https://docs.x.ai/docs/models/grok-4.5). There is no documented `latest`/`fast` alias and no published numeric output limit. **API-key** (default): Bearer token from console.x.ai via `XAI_API_KEY` / keyring / `api_key`. **OAuth**: `codewhale auth xai-device` uses SSH-friendly device login and Codewhale-owned storage, which may refresh itself. Existing Grok CLI credentials require `codewhale auth external-consent --provider xai --mode read-only`; the granted external file is never refreshed or rewritten. OAuth may return HTTP 403 on some SuperGrok tiers — keep API-key as the reliable fallback. `XAI_MODEL` is accepted. Provider aliases: `x-ai`, `x_ai`, `grok`. |
+1 -1
View File
@@ -21,7 +21,7 @@
},
"sourceCandidate": {
"version": "0.9.14",
"providerCount": 49,
"providerCount": 50,
"toolCount": 80,
"sandboxBackends": [
"seatbelt (macOS, when available)",
+4 -1
View File
@@ -26,7 +26,7 @@ DeepSeek 仍是默认提供商,但 `ProviderKind::ALL` 中的每个条目都
`together`, `qianfan`, `openai-codex`, `anthropic`, `openmodel`, `zai`,
`stepfun`, `minimax`, `deepinfra`, `sakana`, `longcat`, `opencode-go`,
`opencode-zen`, `meta`, `xai`, `mistral`, `telecomjs`, `modelstudio-token-plan`, `modelscope`,
`google`, `antigravity`, `edenai`, `zenmux`, 和 `custom`。
`google`, `antigravity`, `edenai`, `zenmux`, `csdn`, 和 `custom`。
`deepseek-anthropic` *不在*此列表中——它是 `deepseek` 的线协议方言,通过 `wire = "anthropic"` 访问,而不是一个可单独选择的路由。
@@ -97,6 +97,7 @@ DeepSeek 仍是默认提供商,但 `ProviderKind::ALL` 中的每个条目都
| `antigravity` | `[providers.antigravity]` | 无——请求默认失败关闭;仅凭据导入 | `ANTIGRAVITY_API_KEY`(密钥平面);`AGY_ADC_AUTH`(进程环境变量) |
| `edenai` | `[providers.edenai]` | OpenAI Chat Completions | `EDENAI_API_KEY` |
| `zenmux` | `[providers.zenmux]` | OpenAI Chat Completions | `ZENMUX_API_KEY` |
| `csdn` | `[providers.csdn]` | OpenAI Chat Completions | `CSDN_API_KEY` |
| `modelstudio-token-plan` | `[providers.modelstudio_token_plan]` | OpenAI Chat Completions | `MODELSTUDIO_API_KEY`, `DASHSCOPE_API_KEY` |
| `modelstudio-token-plan-anthropic` | `[providers.modelstudio_token_plan_anthropic]` | Anthropic Messages | `MODELSTUDIO_API_KEY`, `DASHSCOPE_API_KEY` |
| `modelstudio-coding-plan` | `[providers.modelstudio_coding_plan]` | OpenAI Chat Completions | `MODELSTUDIO_API_KEY`, `DASHSCOPE_API_KEY` |
@@ -348,6 +349,7 @@ model = "qwen3:8b" # 默认是 deepseek-v4-flash
| `antigravity` | 用官方 `agy` CLI(1.1.13)登录。在 `codewhale auth external-consent` 之后,Codewhale 可以只读地读取该登录的精确定点 `state.vscdb` 中的 OAuth token;它从不写入或刷新该文件。`ANTIGRAVITY_API_KEY` 或进程的 `AGY_ADC_AUTH` 优先于该文件。云代码线协议未实现:请求失败关闭并给出可操作的消息——Gemini 模型请使用 `google`。 |
| `edenai` | [Eden AI API 密钥](https://app.edenai.run/settings/api-keys) |
| `zenmux` | [ZenMux API 密钥](https://zenmux.ai/platform/pay-as-you-go) |
| `csdn` | [CSDN 星图控制台](https://ai.csdn.net/workbench/api-key)——`glm_for_coding` 套餐路由请选择 Coding Plan 密钥类型;通用密钥按量计费。文档:[Coding Plan](https://ai.csdn.net/coding-plan)。 |
| `modelstudio-token-plan`, `modelstudio-token-plan-anthropic`, `modelstudio-coding-plan`, `modelstudio-coding-plan-anthropic` | [阿里云百炼 Model Studio(百炼控制台)](https://bailian.console.aliyun.com/) —— 创建或复制 Model Studio API 密钥。 |
| `custom` | 设置该具名提供商的 `base_url` 和 `api_key_env` 或 `api_key`;不存在规范的厂商凭据页面。 |
@@ -424,6 +426,7 @@ Kimi 仍然仅支持 API 密钥;对 Kimi 的外部授权被拒绝。
| `mistral` | `[providers.mistral]` | `MISTRAL_API_KEY` | `MISTRAL_BASE_URL`;默认 `https://api.mistral.ai/v1` | `mistral-code-latest`(默认;`codestral-latest` 接受为别名), `mistral-medium-latest`(别名:`mistral-medium-3-5`), `mistral-small-latest`(别名:`mistral-small-2603`), `mistral-large-latest` | Mistral AI(la Plateforme)OpenAI 兼容 Chat 路由。在文档化的第一方 HTTPS `/v1` 主机上,Medium 和 Small 发送可调的 `reasoning_effort`(仅 `none` 或 `high`),解析 Mistral 的多态 thinking/text 块,并以相同线协议形状重放存储的思考。已废弃的原生 Magistral ID 仍是显式配置兼容路由:它们始终思考,从不接收可调 effort 字段。Code 和 Large 不推理。除非是文档化的第一方主机之一,自定义 `MISTRAL_BASE_URL` 保持通用 Chat 语义。接受 `MISTRAL_MODEL`。提供商别名:`mistral-ai`, `mistralai`, `la-plateforme`。 |
| `edenai` | `[providers.edenai]` | `EDENAI_API_KEY` | `EDENAI_BASE_URL`;默认 `https://api.edenai.run/v3`;欧盟 `https://api.eu.edenai.run/v3` | `deepseek/deepseek-v4-pro`(默认);`provider/model` id 的实时 `/models` 目录 | Eden AI OpenAI 兼容聚合网关。目录行保持提供商范围;通用推理控制被省略,因为受支持的字段取决于所选上游家族。接受 `EDENAI_MODEL`。默认的 `deepseek/deepseek-v4-pro` 只列在全球目录上;在欧盟端点上把 `EDENAI_MODEL`(或 `model`)设为欧盟 `/models` 列表中的一行,例如 `qwen/deepseek-v4-pro`。提供商别名:`eden-ai`, `eden_ai`。 |
| `zenmux` | `[providers.zenmux]` | `ZENMUX_API_KEY` | `ZENMUX_BASE_URL`;默认 `https://zenmux.ai/api/v1` | `deepseek/deepseek-v4.1-flash`(默认);`provider/model` id 的实时 `/models` 目录(免密钥可读) | ZenMux OpenAI 兼容聚合网关(约 190 个模型)。目录行保持提供商范围;通用推理控制被省略,因为受支持的字段取决于所选上游家族。接受 `ZENMUX_MODEL`。提供商别名:`zen-mux`, `zen_mux`。 |
| `csdn` | `[providers.csdn]` | `CSDN_API_KEY` | `CSDN_BASE_URL`;默认 `https://ai.csdn.net/api/model/v1` | `glm_for_coding`(默认;Coding Plan 专用模型 id);其他市场模型 id 直通 | CSDN 星图(Starmap)OpenAI 兼容托管平台。Coding Plan 密钥与通用市场密钥共用一个端点,因此计费取决于凭据产品而非 URL 本身:路由 `glm_for_coding`(出厂默认)或设置 `mode = "coding_plan"`/`"plan"`/`"subscription"` 按 CSDN Coding Plan 配额计费,不显示美元估算;任何其他模型或显式 `pay-as-you-go`/`metered` 模式按量计费;无法识别的模式或 `ai.csdn.net/api/model/v1` 之外的端点报告 `cost: unknown`。接受 `CSDN_MODEL`。提供商别名:`csdn-ai`, `csdn_ai`, `csdn-coding-plan`, `csdn_coding_plan`, `starmap`。 |
| `xai` | `[providers.xai]` | `XAI_API_KEY`、Codewhale 自有的设备 OAuth,或显式的只读 Grok CLI 授权 | `XAI_BASE_URL`;默认 `https://api.x.ai/v1` | `grok-4.6`(默认), `grok-4.5`, `grok-4.3`, `grok-build`, `grok-composer-2.5-fast`, `grok-4.20-0309-reasoning`, `grok-4.20-0309-non-reasoning` | xAI/Grok OpenAI 兼容 Chat Completions 路由。Grok 4.6 有 500K 上下文窗口、文本/图像输入、函数调用、结构化输出、服务端 web 搜索和 `low`/`medium`/`high`/`xhigh` 推理(默认 `high`)。提示达到 200K token 时其标准费率翻倍;同样的 2 倍长上下文规则适用于 `grok-4.5`(500K 上下文,$2.00 / $0.30 缓存 / $6.00)和 `grok-4.3`(1M 上下文,$1.25 / $0.20 缓存 / $2.50),按其[模型页](https://docs.x.ai/docs/models/grok-4.5)。没有文档化的 `latest`/`fast` 别名,也没有公布的数字输出上限。**API 密钥**(默认):来自 console.x.ai 的 Bearer token,通过 `XAI_API_KEY` / keyring / `api_key`。**OAuth**:`codewhale auth xai-device` 使用 SSH 友好的设备登录和 Codewhale 自有的存储,它可能自行刷新。现有的 Grok CLI 凭据需要 `codewhale auth external-consent --provider xai --mode read-only`;被授予的外部文件从不刷新或重写。OAuth 在某些 SuperGrok 层级上可能返回 HTTP 403——把 API 密钥作为可靠回退。接受 `XAI_MODEL`。提供商别名:`x-ai`, `x_ai`, `grok`。 |
| `modelstudio-token-plan` | `[providers.modelstudio_token_plan]` | `MODELSTUDIO_API_KEY`, `DASHSCOPE_API_KEY` | `MODELSTUDIO_TOKEN_PLAN_BASE_URL`;默认 `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` | `qwen3.8-max`(默认), `qwen3.8-max-preview`, `qwen3.7-plus`, `qwen3.7-max`, `qwen3.6-flash`, `deepseek-v4-pro`, `deepseek-v4-flash-0731`, `glm-5.2` | 阿里云 Model Studio Token Plan OpenAI 兼容 Chat Completions 路由。Token Plan 个人版和团队版共享该端点。列出的所有模型都是可推理的文本/编码模型。DeepSeek 和 GLM 条目是提供商范围的,不与第一方路由冲突。接受 `MODELSTUDIO_TOKEN_PLAN_MODEL`。提供商别名:`modelstudio-token-plan`, `alibaba-token-plan`, `dashscope-token-plan`。 |
| `modelstudio-token-plan-anthropic` | `[providers.modelstudio_token_plan_anthropic]` | `MODELSTUDIO_API_KEY`, `DASHSCOPE_API_KEY` | 默认 `https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic` | 与 `modelstudio-token-plan` 相同的模型目录 | Token Plan Anthropic 兼容 Messages 路由(`/apps/anthropic`)。与 OpenAI 方言相同的 API 密钥。提供商别名:`modelstudio-token-plan-anthropic`, `alibaba-token-plan-anthropic`。 |
+1
View File
@@ -153,6 +153,7 @@ function deriveProvidersFromConfig(cfg: string): ProviderFact[] {
ModelstudioCodingPlan: { id: "modelstudio-coding-plan", label: "Model Studio Coding Plan", env: "MODELSTUDIO_API_KEY" },
ModelstudioCodingPlanAnthropic: { id: "modelstudio-coding-plan-anthropic", label: "Model Studio Coding Plan (Anthropic-compatible)", env: "MODELSTUDIO_API_KEY" },
Zenmux: { id: "zenmux", label: "ZenMux", env: "ZENMUX_API_KEY" },
Csdn: { id: "csdn", label: "CSDN 星图 (Starmap)", env: "CSDN_API_KEY" },
};
// Log loudly on unmapped variants so a new provider can never be silently
// dropped from the drift-derived facts again. DeepseekCN (#1104), the
+6 -1
View File
@@ -37,7 +37,7 @@ export interface RepoFacts {
}
export const FACTS: RepoFacts = {
"generatedAt": "2026-09-18T18:49:13.339Z",
"generatedAt": "2026-09-19T00:08:11.198Z",
"sourceRevision": null,
"sourceCommittedAt": null,
"version": "0.9.14",
@@ -289,6 +289,11 @@ export const FACTS: RepoFacts = {
"label": "ZenMux",
"env": "ZENMUX_API_KEY"
},
{
"id": "csdn",
"label": "CSDN 星图 (Starmap)",
"env": "CSDN_API_KEY"
},
{
"id": "concentrate",
"label": "Concentrate",
+1
View File
@@ -115,6 +115,7 @@ const PROVIDER_LABEL_MAP = {
ModelstudioCodingPlan: { id: "modelstudio-coding-plan", label: "Model Studio Coding Plan", env: "MODELSTUDIO_API_KEY" },
ModelstudioCodingPlanAnthropic: { id: "modelstudio-coding-plan-anthropic", label: "Model Studio Coding Plan (Anthropic-compatible)", env: "MODELSTUDIO_API_KEY" },
Zenmux: { id: "zenmux", label: "ZenMux", env: "ZENMUX_API_KEY" },
Csdn: { id: "csdn", label: "CSDN 星图 (Starmap)", env: "CSDN_API_KEY" },
};
// DeepseekCN: not wired through shared ProviderKind (#1104).