diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index fcf07e90..65dab59e 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -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"], } } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 52ec563e..dc058e08 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -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, anthropic_base_url: Option, anthropic_model: Option, + zai_base_url: Option, + zai_model: Option, + stepfun_base_url: Option, + stepfun_model: Option, } 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(), } } diff --git a/crates/config/src/provider.rs b/crates/config/src/provider.rs index 18d7dc55..b3c4e8c0 100644 --- a/crates/config/src/provider.rs +++ b/crates/config/src/provider.rs @@ -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. diff --git a/crates/tui/src/client.rs b/crates/tui/src/client.rs index 878d328b..0878e99a 100644 --- a/crates/tui/src/client.rs +++ b/crates/tui/src/client.rs @@ -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 => {} }, _ => {} } diff --git a/crates/tui/src/config.rs b/crates/tui/src/config.rs index 135ffa01..94b85ffb 100644 --- a/crates/tui/src/config.rs +++ b/crates/tui/src/config.rs @@ -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; 22] = [ + const KIND_LOOKUP: [Option; 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 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 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"), } } diff --git a/crates/tui/src/config_persistence.rs b/crates/tui/src/config_persistence.rs index 900e0e62..f77a81b9 100644 --- a/crates/tui/src/config_persistence.rs +++ b/crates/tui/src/config_persistence.rs @@ -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"), } } diff --git a/crates/tui/src/core/engine.rs b/crates/tui/src/core/engine.rs index 3f3ac286..7be7bf50 100644 --- a/crates/tui/src/core/engine.rs +++ b/crates/tui/src/core/engine.rs @@ -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", diff --git a/crates/tui/src/main.rs b/crates/tui/src/main.rs index 0724c397..5b1d3522 100644 --- a/crates/tui/src/main.rs +++ b/crates/tui/src/main.rs @@ -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", } ); } diff --git a/crates/tui/src/tui/provider_picker.rs b/crates/tui/src/tui/provider_picker.rs index c26376de..b5008161 100644 --- a/crates/tui/src/tui/provider_picker.rs +++ b/crates/tui/src/tui/provider_picker.rs @@ -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); diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index 434c6123..d0db1c1b 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -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); }