feat(config): add first-party Z.ai and StepFlash/StepFun provider routes (#3187) (#3191)

* feat(config): add first-party Z.ai and StepFlash/StepFun provider routes (#3187)

Add Z.ai (GLM Coding Plan) and StepFun/StepFlash as first-party providers:

Provider: Z.ai
- Default model: GLM-5.1 (200K context, 128K output, thinking, function calling)
- Base URL: https://api.z.ai/api/coding/paas/v4
- Auth: ZAI_API_KEY / Z_AI_API_KEY
- Config key: [providers.zai]

Provider: StepFun
- Default model: step-3.7-flash (256K context, 256K output, 3-level reasoning)
- Base URL: https://api.stepfun.ai/v1
- Auth: STEPFUN_API_KEY / STEP_API_KEY
- Config key: [providers.stepfun]

Both providers use OpenAI-compatible Chat Completions transport.

Includes:
- ProviderKind enum variants with serde aliases
- ProvidersToml config fields with aliases
- Provider trait implementations in provider.rs
- PROVIDER_REGISTRY, ALL, KIND_LOOKUP, FROM_KIND_LOOKUP updates
- EnvRuntimeOverrides fields and env-var loading
- TUI ApiProvider enum and lookup table updates
- CLI provider_slot and provider_env_vars additions
- Exhaustive match arms across client.rs, config.rs, engine.rs, main.rs,
  provider_picker.rs, ui.rs, and config_persistence.rs

Verified: cargo check passes, config provider tests (67/67) pass.

* test(tui): update provider picker tests for Zai and Stepfun additions
This commit is contained in:
Hunter Bown
2026-06-12 19:58:57 -07:00
committed by GitHub
parent 12d3053a15
commit e6005eb9ac
10 changed files with 223 additions and 11 deletions
+4
View File
@@ -780,6 +780,8 @@ fn provider_slot(provider: ProviderKind) -> &'static str {
ProviderKind::Together => "together",
ProviderKind::OpenaiCodex => "openai-codex",
ProviderKind::Anthropic => "anthropic",
ProviderKind::Zai => "zai",
ProviderKind::Stepfun => "stepfun",
}
}
@@ -915,6 +917,8 @@ fn provider_env_vars(provider: ProviderKind) -> &'static [&'static str] {
ProviderKind::Together => &["TOGETHER_API_KEY"],
ProviderKind::OpenaiCodex => &["OPENAI_CODEX_ACCESS_TOKEN", "CODEX_ACCESS_TOKEN"],
ProviderKind::Anthropic => &["ANTHROPIC_API_KEY"],
ProviderKind::Zai => &["ZAI_API_KEY", "Z_AI_API_KEY"],
ProviderKind::Stepfun => &["STEPFUN_API_KEY", "STEP_API_KEY"],
}
}
+79 -1
View File
@@ -102,6 +102,13 @@ const DEFAULT_VLLM_BASE_URL: &str = "http://localhost:8000/v1";
const DEFAULT_OLLAMA_MODEL: &str = "deepseek-coder:1.3b";
const DEFAULT_OLLAMA_BASE_URL: &str = "http://localhost:11434/v1";
// Z.ai (GLM Coding Plan) defaults
const DEFAULT_ZAI_MODEL: &str = "GLM-5.1";
const DEFAULT_ZAI_BASE_URL: &str = "https://api.z.ai/api/coding/paas/v4";
// StepFun / StepFlash defaults
const DEFAULT_STEPFUN_MODEL: &str = "step-3.7-flash";
const DEFAULT_STEPFUN_BASE_URL: &str = "https://api.stepfun.ai/v1";
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "kebab-case")]
pub enum ProviderKind {
@@ -158,10 +165,25 @@ pub enum ProviderKind {
OpenaiCodex,
#[serde(alias = "claude")]
Anthropic,
#[serde(
alias = "z-ai",
alias = "z_ai",
alias = "z.ai"
)]
Zai,
#[serde(
alias = "step-fun",
alias = "step_fun",
alias = "stepfun",
alias = "stepflash",
alias = "step-flash",
alias = "step_flash"
)]
Stepfun,
}
impl ProviderKind {
pub const ALL: [Self; 21] = [
pub const ALL: [Self; 23] = [
Self::Deepseek,
Self::NvidiaNim,
Self::Openai,
@@ -183,6 +205,8 @@ impl ProviderKind {
Self::Together,
Self::OpenaiCodex,
Self::Anthropic,
Self::Zai,
Self::Stepfun,
];
#[must_use]
@@ -206,6 +230,11 @@ impl ProviderKind {
Self::Vllm,
Self::Ollama,
Self::Huggingface,
Self::Together,
Self::OpenaiCodex,
Self::Anthropic,
Self::Zai,
Self::Stepfun,
]
}
@@ -314,6 +343,23 @@ pub struct ProvidersToml {
pub openai_codex: ProviderConfigToml,
#[serde(default)]
pub anthropic: ProviderConfigToml,
#[serde(
default,
alias = "z-ai",
alias = "z_ai",
alias = "z.ai"
)]
pub zai: ProviderConfigToml,
#[serde(
default,
alias = "step-fun",
alias = "step_fun",
alias = "stepfun",
alias = "stepflash",
alias = "step-flash",
alias = "step_flash"
)]
pub stepfun: ProviderConfigToml,
}
/// Sibling `permissions.toml` schema.
@@ -365,6 +411,8 @@ impl ProvidersToml {
ProviderKind::Together => &self.together,
ProviderKind::OpenaiCodex => &self.openai_codex,
ProviderKind::Anthropic => &self.anthropic,
ProviderKind::Zai => &self.zai,
ProviderKind::Stepfun => &self.stepfun,
}
}
@@ -391,6 +439,8 @@ impl ProvidersToml {
ProviderKind::Together => &mut self.together,
ProviderKind::OpenaiCodex => &mut self.openai_codex,
ProviderKind::Anthropic => &mut self.anthropic,
ProviderKind::Zai => &mut self.zai,
ProviderKind::Stepfun => &mut self.stepfun,
}
}
}
@@ -2114,6 +2164,8 @@ impl ConfigToml {
ProviderKind::Together => DEFAULT_TOGETHER_BASE_URL.to_string(),
ProviderKind::OpenaiCodex => DEFAULT_OPENAI_CODEX_BASE_URL.to_string(),
ProviderKind::Anthropic => DEFAULT_ANTHROPIC_BASE_URL.to_string(),
ProviderKind::Zai => DEFAULT_ZAI_BASE_URL.to_string(),
ProviderKind::Stepfun => DEFAULT_STEPFUN_BASE_URL.to_string(),
})
};
// CLI flag wins outright. Otherwise: config-file → injected secrets/env.
@@ -2580,6 +2632,8 @@ fn default_model_for_provider(provider: ProviderKind) -> &'static str {
ProviderKind::Together => DEFAULT_TOGETHER_MODEL,
ProviderKind::OpenaiCodex => DEFAULT_OPENAI_CODEX_MODEL,
ProviderKind::Anthropic => DEFAULT_ANTHROPIC_MODEL,
ProviderKind::Zai => DEFAULT_ZAI_MODEL,
ProviderKind::Stepfun => DEFAULT_STEPFUN_MODEL,
}
}
@@ -2606,6 +2660,8 @@ fn default_base_url_for_provider(provider: ProviderKind) -> &'static str {
ProviderKind::Together => DEFAULT_TOGETHER_BASE_URL,
ProviderKind::OpenaiCodex => DEFAULT_OPENAI_CODEX_BASE_URL,
ProviderKind::Anthropic => DEFAULT_ANTHROPIC_BASE_URL,
ProviderKind::Zai => DEFAULT_ZAI_BASE_URL,
ProviderKind::Stepfun => DEFAULT_STEPFUN_BASE_URL,
}
}
@@ -3387,6 +3443,10 @@ struct EnvRuntimeOverrides {
openai_codex_model: Option<String>,
anthropic_base_url: Option<String>,
anthropic_model: Option<String>,
zai_base_url: Option<String>,
zai_model: Option<String>,
stepfun_base_url: Option<String>,
stepfun_model: Option<String>,
}
impl EnvRuntimeOverrides {
@@ -3558,6 +3618,22 @@ impl EnvRuntimeOverrides {
anthropic_model: std::env::var("ANTHROPIC_MODEL")
.ok()
.filter(|v| !v.trim().is_empty()),
zai_base_url: std::env::var("ZAI_BASE_URL")
.or_else(|_| std::env::var("Z_AI_BASE_URL"))
.ok()
.filter(|v| !v.trim().is_empty()),
zai_model: std::env::var("ZAI_MODEL")
.or_else(|_| std::env::var("Z_AI_MODEL"))
.ok()
.filter(|v| !v.trim().is_empty()),
stepfun_base_url: std::env::var("STEPFUN_BASE_URL")
.or_else(|_| std::env::var("STEP_BASE_URL"))
.ok()
.filter(|v| !v.trim().is_empty()),
stepfun_model: std::env::var("STEPFUN_MODEL")
.or_else(|_| std::env::var("STEP_MODEL"))
.ok()
.filter(|v| !v.trim().is_empty()),
}
}
@@ -3601,6 +3677,8 @@ impl EnvRuntimeOverrides {
ProviderKind::Together => self.together_base_url.clone(),
ProviderKind::OpenaiCodex => self.openai_codex_base_url.clone(),
ProviderKind::Anthropic => self.anthropic_base_url.clone(),
ProviderKind::Zai => self.zai_base_url.clone(),
ProviderKind::Stepfun => self.stepfun_base_url.clone(),
}
}
+34 -4
View File
@@ -14,10 +14,12 @@ use super::{
DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_CODEX_BASE_URL, DEFAULT_OPENAI_CODEX_MODEL,
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_TOGETHER_BASE_URL,
DEFAULT_TOGETHER_MODEL, DEFAULT_VLLM_BASE_URL, DEFAULT_VLLM_MODEL, DEFAULT_VOLCENGINE_BASE_URL,
DEFAULT_SILICONFLOW_CN_BASE_URL, DEFAULT_SILICONFLOW_MODEL, DEFAULT_STEPFUN_BASE_URL,
DEFAULT_STEPFUN_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_XIAOMI_MIMO_BASE_URL, DEFAULT_XIAOMI_MIMO_MODEL, ProviderKind,
DEFAULT_XIAOMI_MIMO_BASE_URL, DEFAULT_XIAOMI_MIMO_MODEL, DEFAULT_ZAI_BASE_URL,
DEFAULT_ZAI_MODEL, ProviderKind,
};
/// Wire protocol spoken by a provider.
@@ -434,6 +436,30 @@ impl Provider for Anthropic {
}
}
provider!(
Zai,
Zai,
"zai",
"Z.ai (GLM Coding)",
DEFAULT_ZAI_BASE_URL,
DEFAULT_ZAI_MODEL,
["ZAI_API_KEY", "Z_AI_API_KEY"],
"zai",
aliases: ["z-ai", "z_ai", "z.ai"]
);
provider!(
Stepfun,
Stepfun,
"stepfun",
"StepFun / StepFlash",
DEFAULT_STEPFUN_BASE_URL,
DEFAULT_STEPFUN_MODEL,
["STEPFUN_API_KEY", "STEP_API_KEY"],
"stepfun",
aliases: ["step-fun", "step_fun", "stepflash", "step-flash", "step_flash"]
);
static DEEPSEEK: Deepseek = Deepseek;
static NVIDIA_NIM: NvidiaNim = NvidiaNim;
static OPENAI: Openai = Openai;
@@ -455,8 +481,10 @@ static HUGGINGFACE: Huggingface = Huggingface;
static TOGETHER: Together = Together;
static OPENAI_CODEX: OpenaiCodex = OpenaiCodex;
static ANTHROPIC: Anthropic = Anthropic;
static ZAI: Zai = Zai;
static STEPFUN: Stepfun = Stepfun;
static PROVIDER_REGISTRY: [&dyn Provider; 21] = [
static PROVIDER_REGISTRY: [&dyn Provider; 23] = [
&DEEPSEEK,
&NVIDIA_NIM,
&OPENAI,
@@ -478,6 +506,8 @@ static PROVIDER_REGISTRY: [&dyn Provider; 21] = [
&TOGETHER,
&OPENAI_CODEX,
&ANTHROPIC,
&ZAI,
&STEPFUN,
];
/// Return all built-in provider metadata entries in `ProviderKind::ALL` order.
+3
View File
@@ -1294,6 +1294,7 @@ pub(super) fn apply_reasoning_effort(
"thinking": false,
});
}
ApiProvider::Zai | ApiProvider::Stepfun => {}
},
"low" | "minimal" | "medium" | "mid" | "high" | "" => match provider {
// DeepSeek compatibility: low/medium both map to high
@@ -1367,6 +1368,7 @@ pub(super) fn apply_reasoning_effort(
"reasoning_effort": "high",
});
}
ApiProvider::Zai | ApiProvider::Stepfun => {}
},
"xhigh" | "max" | "highest" => match provider {
ApiProvider::Deepseek
@@ -1420,6 +1422,7 @@ pub(super) fn apply_reasoning_effort(
"reasoning_effort": "max",
});
}
ApiProvider::Zai | ApiProvider::Stepfun => {}
},
_ => {}
}
+79 -3
View File
@@ -171,6 +171,10 @@ pub const COMMON_DEEPSEEK_MODELS: &[&str] = &[
"deepseek/deepseek-v4-flash",
];
pub const OFFICIAL_DEEPSEEK_MODELS: &[&str] = &["deepseek-v4-pro", "deepseek-v4-flash"];
pub const DEFAULT_ZAI_MODEL: &str = "GLM-5.1";
pub const DEFAULT_ZAI_BASE_URL: &str = "https://api.z.ai/api/coding/paas/v4";
pub const DEFAULT_STEPFUN_MODEL: &str = "step-3.7-flash";
pub const DEFAULT_STEPFUN_BASE_URL: &str = "https://api.stepfun.ai/v1";
pub const DEFAULT_ANTHROPIC_MODEL: &str = "claude-sonnet-4-6";
pub const ANTHROPIC_OPUS_MODEL: &str = "claude-opus-4-8";
pub const ANTHROPIC_HAIKU_MODEL: &str = "claude-haiku-4-5";
@@ -201,6 +205,8 @@ pub enum ApiProvider {
Together,
OpenaiCodex,
Anthropic,
Zai,
Stepfun,
}
impl ApiProvider {
@@ -258,7 +264,7 @@ impl ApiProvider {
/// `ApiProvider` discriminant → `ProviderKind` lookup.
/// Index 1 is `None` for the legacy `DeepseekCN` variant.
const KIND_LOOKUP: [Option<codewhale_config::ProviderKind>; 22] = [
const KIND_LOOKUP: [Option<codewhale_config::ProviderKind>; 24] = [
Some(codewhale_config::ProviderKind::Deepseek),
None, // DeepseekCN
Some(codewhale_config::ProviderKind::NvidiaNim),
@@ -281,10 +287,12 @@ impl ApiProvider {
Some(codewhale_config::ProviderKind::Together),
Some(codewhale_config::ProviderKind::OpenaiCodex),
Some(codewhale_config::ProviderKind::Anthropic),
Some(codewhale_config::ProviderKind::Zai),
Some(codewhale_config::ProviderKind::Stepfun),
];
/// `ProviderKind` discriminant → `ApiProvider` lookup.
const FROM_KIND_LOOKUP: [Self; 21] = [
const FROM_KIND_LOOKUP: [Self; 23] = [
Self::Deepseek,
Self::NvidiaNim,
Self::Openai,
@@ -306,6 +314,8 @@ impl ApiProvider {
Self::Together,
Self::OpenaiCodex,
Self::Anthropic,
Self::Zai,
Self::Stepfun,
];
/// Map to the config-level `ProviderKind`.
@@ -889,6 +899,8 @@ pub fn model_completion_names_for_provider(provider: ApiProvider) -> Vec<&'stati
ApiProvider::Openai | ApiProvider::Atlascloud => OFFICIAL_DEEPSEEK_MODELS.to_vec(),
ApiProvider::Together => vec![DEFAULT_TOGETHER_MODEL],
ApiProvider::OpenaiCodex => vec![DEFAULT_OPENAI_CODEX_MODEL],
ApiProvider::Zai => vec![DEFAULT_ZAI_MODEL],
ApiProvider::Stepfun => vec![DEFAULT_STEPFUN_MODEL],
ApiProvider::Anthropic => vec![
ANTHROPIC_OPUS_MODEL,
DEFAULT_ANTHROPIC_MODEL,
@@ -2050,6 +2062,10 @@ pub struct ProvidersConfig {
pub openai_codex: ProviderConfig,
#[serde(default, alias = "claude")]
pub anthropic: ProviderConfig,
#[serde(default)]
pub zai: ProviderConfig,
#[serde(default)]
pub stepfun: ProviderConfig,
}
#[derive(Debug, Clone, Deserialize, Default)]
@@ -2217,6 +2233,8 @@ impl Config {
ApiProvider::Together => "providers.together",
ApiProvider::OpenaiCodex => "providers.openai_codex",
ApiProvider::Anthropic => "providers.anthropic",
ApiProvider::Zai => "providers.zai",
ApiProvider::Stepfun => "providers.stepfun",
ApiProvider::Deepseek | ApiProvider::DeepseekCN => return,
};
tracing::warn!(
@@ -2374,6 +2392,8 @@ impl Config {
ApiProvider::Together => &providers.together,
ApiProvider::OpenaiCodex => &providers.openai_codex,
ApiProvider::Anthropic => &providers.anthropic,
ApiProvider::Zai => &providers.zai,
ApiProvider::Stepfun => &providers.stepfun,
})
}
@@ -2402,6 +2422,8 @@ impl Config {
ApiProvider::Together => &mut providers.together,
ApiProvider::OpenaiCodex => &mut providers.openai_codex,
ApiProvider::Anthropic => &mut providers.anthropic,
ApiProvider::Zai => &mut providers.zai,
ApiProvider::Stepfun => &mut providers.stepfun,
}
}
@@ -2543,6 +2565,8 @@ impl Config {
ApiProvider::Huggingface => DEFAULT_HUGGINGFACE_MODEL,
ApiProvider::Together => DEFAULT_TOGETHER_MODEL,
ApiProvider::OpenaiCodex => DEFAULT_OPENAI_CODEX_MODEL,
ApiProvider::Zai => DEFAULT_ZAI_MODEL,
ApiProvider::Stepfun => DEFAULT_STEPFUN_MODEL,
ApiProvider::Anthropic => DEFAULT_ANTHROPIC_MODEL,
}
.to_string()
@@ -2583,7 +2607,9 @@ impl Config {
| ApiProvider::Volcengine
| ApiProvider::Huggingface
| ApiProvider::Together
| ApiProvider::OpenaiCodex => None,
| ApiProvider::OpenaiCodex
| ApiProvider::Zai
| ApiProvider::Stepfun => None,
};
let configured_base_url = provider_base.or(root_base);
let base = if provider == ApiProvider::XiaomiMimo {
@@ -2630,6 +2656,8 @@ impl Config {
ApiProvider::Huggingface => DEFAULT_HUGGINGFACE_BASE_URL,
ApiProvider::Together => DEFAULT_TOGETHER_BASE_URL,
ApiProvider::OpenaiCodex => DEFAULT_OPENAI_CODEX_BASE_URL,
ApiProvider::Zai => DEFAULT_ZAI_BASE_URL,
ApiProvider::Stepfun => DEFAULT_STEPFUN_BASE_URL,
ApiProvider::Anthropic => DEFAULT_ANTHROPIC_BASE_URL,
}
.to_string()
@@ -2680,6 +2708,8 @@ impl Config {
ApiProvider::Huggingface => "huggingface",
ApiProvider::Together => "together",
ApiProvider::OpenaiCodex => "openai_codex",
ApiProvider::Zai => "zai",
ApiProvider::Stepfun => "stepfun",
ApiProvider::Anthropic => "anthropic",
};
@@ -2851,6 +2881,14 @@ impl Config {
"Together AI API key not found. Run 'codewhale auth set --provider together', \
set TOGETHER_API_KEY, or add [providers.together] api_key in ~/.codewhale/config.toml."
),
ApiProvider::Zai => anyhow::bail!(
"Z.ai (GLM Coding) API key not found. Run 'codewhale auth set --provider zai', \
set ZAI_API_KEY, or add [providers.zai] api_key in ~/.codewhale/config.toml."
),
ApiProvider::Stepfun => anyhow::bail!(
"StepFun API key not found. Run 'codewhale auth set --provider stepfun', \
set STEPFUN_API_KEY, or add [providers.stepfun] api_key in ~/.codewhale/config.toml."
),
ApiProvider::Anthropic => anyhow::bail!(
"Anthropic API key not found. Run 'codewhale auth set --provider anthropic', \
set ANTHROPIC_API_KEY, or add [providers.anthropic] api_key in ~/.codewhale/config.toml. \
@@ -3707,6 +3745,20 @@ fn apply_env_overrides(config: &mut Config) {
.openai_codex
.base_url = Some(value);
}
ApiProvider::Zai => {
config
.providers
.get_or_insert_with(ProvidersConfig::default)
.zai
.base_url = Some(value);
}
ApiProvider::Stepfun => {
config
.providers
.get_or_insert_with(ProvidersConfig::default)
.stepfun
.base_url = Some(value);
}
}
}
if matches!(config.api_provider(), ApiProvider::NvidiaNim)
@@ -3914,6 +3966,8 @@ fn apply_env_overrides(config: &mut Config) {
ApiProvider::Together => &mut providers.together,
ApiProvider::OpenaiCodex => &mut providers.openai_codex,
ApiProvider::Anthropic => &mut providers.anthropic,
ApiProvider::Zai => &mut providers.zai,
ApiProvider::Stepfun => &mut providers.stepfun,
};
let mut provider_headers = entry.http_headers.clone().unwrap_or_default();
provider_headers.extend(headers);
@@ -4109,6 +4163,8 @@ fn apply_env_overrides(config: &mut Config) {
ApiProvider::Together => &mut providers.together,
ApiProvider::OpenaiCodex => &mut providers.openai_codex,
ApiProvider::Anthropic => &mut providers.anthropic,
ApiProvider::Zai => &mut providers.zai,
ApiProvider::Stepfun => &mut providers.stepfun,
};
entry.model = Some(value);
}
@@ -4448,6 +4504,8 @@ fn default_base_url_for_provider(provider: ApiProvider) -> &'static str {
ApiProvider::Huggingface => DEFAULT_HUGGINGFACE_BASE_URL,
ApiProvider::Together => DEFAULT_TOGETHER_BASE_URL,
ApiProvider::OpenaiCodex => DEFAULT_OPENAI_CODEX_BASE_URL,
ApiProvider::Zai => DEFAULT_ZAI_BASE_URL,
ApiProvider::Stepfun => DEFAULT_STEPFUN_BASE_URL,
ApiProvider::Anthropic => DEFAULT_ANTHROPIC_BASE_URL,
}
}
@@ -4894,6 +4952,8 @@ fn merge_providers(
huggingface: merge_provider_config(base.huggingface, override_cfg.huggingface),
together: merge_provider_config(base.together, override_cfg.together),
openai_codex: merge_provider_config(base.openai_codex, override_cfg.openai_codex),
zai: merge_provider_config(base.zai, override_cfg.zai),
stepfun: merge_provider_config(base.stepfun, override_cfg.stepfun),
}),
}
}
@@ -5400,6 +5460,14 @@ pub fn active_provider_has_env_api_key(config: &Config) -> bool {
std::env::var("OPENAI_CODEX_ACCESS_TOKEN").is_ok_and(|k| !k.trim().is_empty())
|| std::env::var("CODEX_ACCESS_TOKEN").is_ok_and(|k| !k.trim().is_empty())
}
ApiProvider::Zai => {
std::env::var("ZAI_API_KEY").is_ok_and(|k| !k.trim().is_empty())
|| std::env::var("Z_AI_API_KEY").is_ok_and(|k| !k.trim().is_empty())
}
ApiProvider::Stepfun => {
std::env::var("STEPFUN_API_KEY").is_ok_and(|k| !k.trim().is_empty())
|| std::env::var("STEP_API_KEY").is_ok_and(|k| !k.trim().is_empty())
}
}
}
@@ -5434,6 +5502,8 @@ pub fn has_api_key_for(config: &Config, provider: ApiProvider) -> bool {
ApiProvider::Vllm => "VLLM_API_KEY",
ApiProvider::Ollama => "OLLAMA_API_KEY",
ApiProvider::Volcengine => "VOLCENGINE_API_KEY",
ApiProvider::Zai => "ZAI_API_KEY",
ApiProvider::Stepfun => "STEPFUN_API_KEY",
};
if std::env::var(env_var).is_ok_and(|k| !k.trim().is_empty()) {
return true;
@@ -5561,6 +5631,8 @@ pub fn save_api_key_for(provider: ApiProvider, api_key: &str) -> Result<PathBuf>
ApiProvider::Volcengine => "providers.volcengine",
ApiProvider::Together => "providers.together",
ApiProvider::OpenaiCodex => "providers.openai_codex",
ApiProvider::Zai => "providers.zai",
ApiProvider::Stepfun => "providers.stepfun",
};
// Parse existing TOML (or start fresh) so we can edit the right table
@@ -5607,6 +5679,8 @@ pub fn save_api_key_for(provider: ApiProvider, api_key: &str) -> Result<PathBuf>
ApiProvider::Volcengine => "volcengine",
ApiProvider::Together => "together",
ApiProvider::OpenaiCodex => "openai_codex",
ApiProvider::Zai => "zai",
ApiProvider::Stepfun => "stepfun",
};
let entry = providers
.entry(key_inside.to_string())
@@ -5705,6 +5779,8 @@ fn provider_config_key(provider: ApiProvider) -> Result<&'static str> {
ApiProvider::Ollama => Ok("ollama"),
ApiProvider::Together => Ok("together"),
ApiProvider::OpenaiCodex => Ok("openai_codex"),
ApiProvider::Zai => Ok("zai"),
ApiProvider::Stepfun => Ok("stepfun"),
}
}
+2
View File
@@ -221,6 +221,8 @@ fn provider_base_url_table_key(provider: ApiProvider) -> anyhow::Result<&'static
ApiProvider::Ollama => Ok("ollama"),
ApiProvider::Together => Ok("together"),
ApiProvider::OpenaiCodex => Ok("openai_codex"),
ApiProvider::Zai => Ok("zai"),
ApiProvider::Stepfun => Ok("stepfun"),
}
}
+1 -1
View File
@@ -614,7 +614,7 @@ impl Engine {
let env_var = match provider {
ApiProvider::Deepseek | ApiProvider::DeepseekCN => "DEEPSEEK_API_KEY",
ApiProvider::NvidiaNim => "NVIDIA_API_KEY/NVIDIA_NIM_API_KEY",
ApiProvider::Openai => "OPENAI_API_KEY",
ApiProvider::Openai | ApiProvider::Zai | ApiProvider::Stepfun => "OPENAI_API_KEY",
ApiProvider::Anthropic => "ANTHROPIC_API_KEY",
ApiProvider::Atlascloud => "ATLASCLOUD_API_KEY",
ApiProvider::WanjieArk => "WANJIE_ARK_API_KEY/WANJIE_API_KEY/WANJIE_MAAS_API_KEY",
+10
View File
@@ -2600,6 +2600,14 @@ fn run_setup_status(config: &Config, workspace: &Path) -> Result<()> {
crate::config::ApiProvider::Deepseek | crate::config::ApiProvider::DeepseekCN => {
("DEEPSEEK_API_KEY", "codewhale auth set --provider deepseek")
}
crate::config::ApiProvider::Zai => (
"OPENAI_API_KEY",
"codewhale auth set --provider zai --api-key \"...\"",
),
crate::config::ApiProvider::Stepfun => (
"OPENAI_API_KEY",
"codewhale auth set --provider stepfun --api-key \"...\"",
),
};
println!(
" {} api_key: missing (set {env_var} or `[providers.{}].api_key` in ~/.codewhale/config.toml; or run `{login_hint}`)",
@@ -2627,6 +2635,8 @@ fn run_setup_status(config: &Config, workspace: &Path) -> Result<()> {
crate::config::ApiProvider::OpenaiCodex => "openai_codex",
crate::config::ApiProvider::Deepseek
| crate::config::ApiProvider::DeepseekCN => "deepseek",
crate::config::ApiProvider::Zai => "zai",
crate::config::ApiProvider::Stepfun => "stepfun",
}
);
}
+5 -2
View File
@@ -121,6 +121,7 @@ impl ProviderPickerView {
ApiProvider::Huggingface => "HUGGINGFACE_API_KEY / HF_TOKEN",
ApiProvider::Together => "TOGETHER_API_KEY",
ApiProvider::OpenaiCodex => "OPENAI_CODEX_ACCESS_TOKEN / CODEX_ACCESS_TOKEN",
ApiProvider::Zai | ApiProvider::Stepfun => "OPENAI_API_KEY",
}
}
@@ -514,7 +515,9 @@ mod tests {
"Hugging Face",
"Together AI",
"OpenAI Codex (ChatGPT)",
"Anthropic"
"Anthropic",
"Z.ai (GLM Coding)",
"StepFun / StepFlash"
]
);
}
@@ -549,7 +552,7 @@ mod tests {
let mut picker = ProviderPickerView::new(ApiProvider::Deepseek, &config);
picker.handle_key(key(KeyCode::Up));
assert_eq!(picker.selected_provider(), ApiProvider::Anthropic);
assert_eq!(picker.selected_provider(), ApiProvider::Stepfun);
picker.handle_key(key(KeyCode::Down));
assert_eq!(picker.selected_provider(), ApiProvider::Deepseek);
+6
View File
@@ -7593,6 +7593,8 @@ fn render(f: &mut Frame, app: &mut App) {
crate::config::ApiProvider::Huggingface => Some("HF"),
crate::config::ApiProvider::Together => Some("Together"),
crate::config::ApiProvider::OpenaiCodex => Some("Codex"),
crate::config::ApiProvider::Zai => Some("Z.ai"),
crate::config::ApiProvider::Stepfun => Some("StepFun"),
};
let status_indicator_started_at = if app.low_motion {
None
@@ -8651,6 +8653,8 @@ async fn apply_provider_picker_api_key(
ApiProvider::Together => &mut providers.together,
ApiProvider::OpenaiCodex => &mut providers.openai_codex,
ApiProvider::Anthropic => &mut providers.anthropic,
ApiProvider::Zai => &mut providers.zai,
ApiProvider::Stepfun => &mut providers.stepfun,
};
entry.api_key = Some(api_key);
}
@@ -8711,6 +8715,8 @@ fn set_provider_auth_mode_in_memory(config: &mut Config, provider: ApiProvider,
ApiProvider::Together => &mut providers.together,
ApiProvider::OpenaiCodex => &mut providers.openai_codex,
ApiProvider::Anthropic => &mut providers.anthropic,
ApiProvider::Zai => &mut providers.zai,
ApiProvider::Stepfun => &mut providers.stepfun,
};
entry.auth_mode = Some(auth_mode);
}