From 65402452d3018b7f1cca432dea38cf1f9433c361 Mon Sep 17 00:00:00 2001 From: argenis de la rosa Date: Thu, 26 Feb 2026 22:33:41 -0500 Subject: [PATCH 1/5] feat(provider): add qwen-coding-plan endpoint alias --- src/onboard/wizard.rs | 53 +++++++++++++++++++++++++++++++++++++++++++ src/providers/mod.rs | 22 +++++++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/onboard/wizard.rs b/src/onboard/wizard.rs index f37446c78..aa6fc1670 100644 --- a/src/onboard/wizard.rs +++ b/src/onboard/wizard.rs @@ -817,6 +817,10 @@ const MINIMAX_ONBOARD_MODELS: [(&str, &str); 7] = [ ]; fn default_model_for_provider(provider: &str) -> String { + if provider == "qwen-coding-plan" { + return "qwen3-coder-plus".into(); + } + match canonical_provider_name(provider) { "anthropic" => "claude-sonnet-4-5-20250929".into(), "openai" => "gpt-5.2".into(), @@ -849,6 +853,23 @@ fn default_model_for_provider(provider: &str) -> String { } fn curated_models_for_provider(provider_name: &str) -> Vec<(String, String)> { + if provider_name == "qwen-coding-plan" { + return vec![ + ( + "qwen3-coder-plus".to_string(), + "Qwen3 Coder Plus (recommended for coding workflows)".to_string(), + ), + ( + "qwen3.5-plus".to_string(), + "Qwen3.5 Plus (reasoning + coding)".to_string(), + ), + ( + "qwen3-max-2026-01-23".to_string(), + "Qwen3 Max (high-capability coding model)".to_string(), + ), + ]; + } + match canonical_provider_name(provider_name) { "openrouter" => vec![ ( @@ -1327,6 +1348,7 @@ fn supports_live_model_fetch(provider_name: &str) -> bool { fn models_endpoint_for_provider(provider_name: &str) -> Option<&'static str> { match provider_name { + "qwen-coding-plan" => Some("https://coding.dashscope.aliyuncs.com/v1/models"), "qwen-intl" => Some("https://dashscope-intl.aliyuncs.com/compatible-mode/v1/models"), "dashscope-us" => Some("https://dashscope-us.aliyuncs.com/compatible-mode/v1/models"), "moonshot-cn" | "kimi-cn" => Some("https://api.moonshot.cn/v1/models"), @@ -2364,6 +2386,10 @@ async fn setup_provider(workspace_dir: &Path) -> Result<(String, String, String, ), ("minimax-cn", "MiniMax — China endpoint (api.minimaxi.com)"), ("qwen", "Qwen — DashScope China endpoint"), + ( + "qwen-coding-plan", + "Qwen — DashScope coding plan endpoint (coding.dashscope.aliyuncs.com)", + ), ("qwen-intl", "Qwen — DashScope international endpoint"), ("qwen-us", "Qwen — DashScope US endpoint"), ("qianfan", "Qianfan — Baidu AI models (China endpoint)"), @@ -6811,6 +6837,10 @@ mod tests { ); assert_eq!(default_model_for_provider("qwen"), "qwen-plus"); assert_eq!(default_model_for_provider("qwen-intl"), "qwen-plus"); + assert_eq!( + default_model_for_provider("qwen-coding-plan"), + "qwen3-coder-plus" + ); assert_eq!(default_model_for_provider("qwen-code"), "qwen3-coder-plus"); assert_eq!(default_model_for_provider("glm-cn"), "glm-5"); assert_eq!(default_model_for_provider("minimax-cn"), "MiniMax-M2.7"); @@ -6856,6 +6886,7 @@ mod tests { fn canonical_provider_name_normalizes_regional_aliases() { assert_eq!(canonical_provider_name("qwen-intl"), "qwen"); assert_eq!(canonical_provider_name("dashscope-us"), "qwen"); + assert_eq!(canonical_provider_name("qwen-coding-plan"), "qwen"); assert_eq!(canonical_provider_name("qwen-code"), "qwen-code"); assert_eq!(canonical_provider_name("qwen-oauth"), "qwen-code"); assert_eq!(canonical_provider_name("codex"), "openai-codex"); @@ -7000,6 +7031,18 @@ mod tests { assert!(ids.contains(&"minimax/minimax-m2.5".to_string())); } + #[test] + fn curated_models_for_qwen_coding_plan_include_coding_models() { + let ids: Vec = curated_models_for_provider("qwen-coding-plan") + .into_iter() + .map(|(id, _)| id) + .collect(); + + assert!(ids.contains(&"qwen3-coder-plus".to_string())); + assert!(ids.contains(&"qwen3.5-plus".to_string())); + assert!(ids.contains(&"qwen3-max-2026-01-23".to_string())); + } + #[test] fn supports_live_model_fetch_for_supported_and_unsupported_providers() { assert!(supports_live_model_fetch("openai")); @@ -7021,6 +7064,7 @@ mod tests { assert!(supports_live_model_fetch("venice")); assert!(supports_live_model_fetch("glm-cn")); assert!(supports_live_model_fetch("qwen-intl")); + assert!(supports_live_model_fetch("qwen-coding-plan")); assert!(!supports_live_model_fetch("minimax-cn")); assert!(!supports_live_model_fetch("unknown-provider")); } @@ -7051,6 +7095,10 @@ mod tests { curated_models_for_provider("qwen"), curated_models_for_provider("dashscope-us") ); + assert_eq!( + curated_models_for_provider("qwen-coding-plan"), + curated_models_for_provider("qwen-code") + ); assert_eq!( curated_models_for_provider("minimax"), curated_models_for_provider("minimax-cn") @@ -7103,6 +7151,10 @@ mod tests { models_endpoint_for_provider("qwen-intl"), Some("https://dashscope-intl.aliyuncs.com/compatible-mode/v1/models") ); + assert_eq!( + models_endpoint_for_provider("qwen-coding-plan"), + Some("https://coding.dashscope.aliyuncs.com/v1/models") + ); } #[test] @@ -7413,6 +7465,7 @@ mod tests { assert_eq!(provider_env_var("qwen"), "DASHSCOPE_API_KEY"); assert_eq!(provider_env_var("qwen-intl"), "DASHSCOPE_API_KEY"); assert_eq!(provider_env_var("dashscope-us"), "DASHSCOPE_API_KEY"); + assert_eq!(provider_env_var("qwen-coding-plan"), "DASHSCOPE_API_KEY"); assert_eq!(provider_env_var("qwen-code"), "QWEN_OAUTH_TOKEN"); assert_eq!(provider_env_var("qwen-oauth"), "QWEN_OAUTH_TOKEN"); assert_eq!(provider_env_var("glm-cn"), "GLM_API_KEY"); diff --git a/src/providers/mod.rs b/src/providers/mod.rs index b2bd68902..7087ed761 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -66,6 +66,7 @@ const MOONSHOT_CN_BASE_URL: &str = "https://api.moonshot.cn/v1"; const QWEN_CN_BASE_URL: &str = "https://dashscope.aliyuncs.com/compatible-mode/v1"; const QWEN_INTL_BASE_URL: &str = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"; const QWEN_US_BASE_URL: &str = "https://dashscope-us.aliyuncs.com/compatible-mode/v1"; +const QWEN_CODING_PLAN_BASE_URL: &str = "https://coding.dashscope.aliyuncs.com/v1"; const QWEN_OAUTH_BASE_FALLBACK_URL: &str = QWEN_CN_BASE_URL; const BAILIAN_BASE_URL: &str = "https://coding.dashscope.aliyuncs.com/v1"; const QWEN_OAUTH_TOKEN_ENDPOINT: &str = "https://chat.qwen.ai/api/v1/oauth2/token"; @@ -155,11 +156,16 @@ pub(crate) fn is_bailian_alias(name: &str) -> bool { matches!(name, "bailian" | "aliyun-bailian" | "aliyun") } +pub(crate) fn is_qwen_coding_plan_alias(name: &str) -> bool { + matches!(name, "qwen-coding-plan") +} + pub(crate) fn is_qwen_alias(name: &str) -> bool { is_qwen_cn_alias(name) || is_qwen_intl_alias(name) || is_qwen_us_alias(name) || is_qwen_oauth_alias(name) + || is_qwen_coding_plan_alias(name) } pub(crate) fn is_zai_global_alias(name: &str) -> bool { @@ -659,7 +665,9 @@ fn moonshot_base_url(name: &str) -> Option<&'static str> { } fn qwen_base_url(name: &str) -> Option<&'static str> { - if is_qwen_cn_alias(name) || is_qwen_oauth_alias(name) { + if is_qwen_coding_plan_alias(name) { + Some(QWEN_CODING_PLAN_BASE_URL) + } else if is_qwen_cn_alias(name) || is_qwen_oauth_alias(name) { Some(QWEN_CN_BASE_URL) } else if is_qwen_intl_alias(name) { Some(QWEN_INTL_BASE_URL) @@ -1917,6 +1925,7 @@ pub fn list_providers() -> Vec { "dashscope-intl", "qwen-us", "dashscope-us", + "qwen-coding-plan", "qwen-code", "qwen-oauth", "qwen_oauth", @@ -2374,6 +2383,7 @@ mod tests { assert!(is_minimax_alias("minimax-portal-cn")); assert!(is_qwen_alias("dashscope")); assert!(is_qwen_alias("qwen-us")); + assert!(is_qwen_alias("qwen-coding-plan")); assert!(is_qwen_alias("qwen-code")); assert!(is_qwen_oauth_alias("qwen-code")); assert!(is_qwen_oauth_alias("qwen_oauth")); @@ -2404,6 +2414,10 @@ mod tests { assert_eq!(canonical_china_provider_name("minimax-cn"), Some("minimax")); assert_eq!(canonical_china_provider_name("qwen"), Some("qwen")); assert_eq!(canonical_china_provider_name("dashscope-us"), Some("qwen")); + assert_eq!( + canonical_china_provider_name("qwen-coding-plan"), + Some("qwen") + ); assert_eq!(canonical_china_provider_name("qwen-code"), Some("qwen")); assert_eq!(canonical_china_provider_name("zai"), Some("zai")); assert_eq!(canonical_china_provider_name("z.ai-cn"), Some("zai")); @@ -2443,6 +2457,10 @@ mod tests { assert_eq!(qwen_base_url("qwen-cn"), Some(QWEN_CN_BASE_URL)); assert_eq!(qwen_base_url("qwen-intl"), Some(QWEN_INTL_BASE_URL)); assert_eq!(qwen_base_url("qwen-us"), Some(QWEN_US_BASE_URL)); + assert_eq!( + qwen_base_url("qwen-coding-plan"), + Some(QWEN_CODING_PLAN_BASE_URL) + ); assert_eq!(qwen_base_url("qwen-code"), Some(QWEN_CN_BASE_URL)); assert_eq!(zai_base_url("zai"), Some(ZAI_GLOBAL_BASE_URL)); @@ -2654,6 +2672,7 @@ mod tests { assert!(create_provider("dashscope-international", Some("key")).is_ok()); assert!(create_provider("qwen-us", Some("key")).is_ok()); assert!(create_provider("dashscope-us", Some("key")).is_ok()); + assert!(create_provider("qwen-coding-plan", Some("key")).is_ok()); assert!(create_provider("qwen-code", Some("key")).is_ok()); assert!(create_provider("qwen-oauth", Some("key")).is_ok()); } @@ -3192,6 +3211,7 @@ mod tests { "qwen-intl", "qwen-cn", "qwen-us", + "qwen-coding-plan", "qwen-code", "lmstudio", "llamacpp", From 13a0f6d423c7a7f10d4f670db67544a28ec764d6 Mon Sep 17 00:00:00 2001 From: khhjoe Date: Wed, 18 Mar 2026 22:52:41 +0800 Subject: [PATCH 2/5] feat(providers): add Qwen Coding provider and enhance Anthropic debugging - Add qwen-coding / qwen-coding-cn aliases using Anthropic Messages API format via ModelStudio /apps/anthropic endpoint - Add MODELSTUDIO_API_KEY env var for credential resolution - Add Qwen Coding to onboard wizard - Add debug logging to Anthropic provider for request/response tracing Co-Authored-By: Claude Opus 4.6 (1M context) --- src/onboard/wizard.rs | 9 +++++- src/providers/anthropic.rs | 65 +++++++++++++++++++++++++++++++++++--- src/providers/mod.rs | 22 ++++++++++++- 3 files changed, 90 insertions(+), 6 deletions(-) diff --git a/src/onboard/wizard.rs b/src/onboard/wizard.rs index aa6fc1670..782dc3edf 100644 --- a/src/onboard/wizard.rs +++ b/src/onboard/wizard.rs @@ -142,6 +142,7 @@ pub async fn run_wizard(force: bool) -> Result { default_temperature: 0.7, provider_timeout_secs: 120, extra_headers: std::collections::HashMap::new(), + provider_env: std::collections::HashMap::new(), observability: ObservabilityConfig::default(), autonomy: AutonomyConfig::default(), backup: crate::config::BackupConfig::default(), @@ -574,6 +575,7 @@ async fn run_quick_setup_with_home( default_temperature: 0.7, provider_timeout_secs: 120, extra_headers: std::collections::HashMap::new(), + provider_env: std::collections::HashMap::new(), observability: ObservabilityConfig::default(), autonomy: AutonomyConfig::default(), backup: crate::config::BackupConfig::default(), @@ -2311,7 +2313,7 @@ async fn setup_provider(workspace_dir: &Path) -> Result<(String, String, String, "⭐ Recommended (OpenRouter, Venice, Anthropic, OpenAI, Gemini)", "⚡ Fast inference (Groq, Fireworks, Together AI, NVIDIA NIM)", "🌐 Gateway / proxy (Vercel AI, Cloudflare AI, Amazon Bedrock)", - "🔬 Specialized (Moonshot/Kimi, GLM/Zhipu, MiniMax, Qwen/DashScope, Qianfan, Z.AI, Synthetic, OpenCode Zen, Cohere)", + "🔬 Specialized (Moonshot/Kimi, GLM/Zhipu, MiniMax, Qwen/DashScope, Qwen Coding, Qianfan, Z.AI, Synthetic, OpenCode Zen, Cohere)", "🏠 Local / private (Ollama, llama.cpp server, vLLM — no API key needed)", "🔧 Custom — bring your own OpenAI-compatible API", ]; @@ -2392,6 +2394,11 @@ async fn setup_provider(workspace_dir: &Path) -> Result<(String, String, String, ), ("qwen-intl", "Qwen — DashScope international endpoint"), ("qwen-us", "Qwen — DashScope US endpoint"), + ( + "qwen-coding", + "Qwen Coding — ModelStudio / coding-intl (Coding Plan)", + ), + ("qwen-coding-cn", "Qwen Coding — ModelStudio China endpoint"), ("qianfan", "Qianfan — Baidu AI models (China endpoint)"), ("zai", "Z.AI — global coding endpoint"), ("zai-cn", "Z.AI — China coding endpoint (open.bigmodel.cn)"), diff --git a/src/providers/anthropic.rs b/src/providers/anthropic.rs index 70e0b45e4..a764f4eb1 100644 --- a/src/providers/anthropic.rs +++ b/src/providers/anthropic.rs @@ -190,6 +190,13 @@ impl AnthropicProvider { } } + /// The user-agent string used in all Anthropic API requests. + /// Some Anthropic-compatible endpoints (e.g. Alibaba Coding Plan) require + /// a non-empty User-Agent header. + fn user_agent() -> &'static str { + "zeroclaw/0.5" + } + fn is_setup_token(token: &str) -> bool { token.starts_with("sk-ant-oat01-") } @@ -549,7 +556,21 @@ impl AnthropicProvider { } fn http_client(&self) -> Client { - crate::config::build_runtime_proxy_client_with_timeouts("provider.anthropic", 120, 10) + // Build via the standard proxy-aware builder, but ensure a non-empty + // User-Agent is always set. Some Anthropic-compatible endpoints + // (e.g. Alibaba Coding Plan) reject requests without one. + reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(120)) + .connect_timeout(std::time::Duration::from_secs(10)) + .user_agent(Self::user_agent()) + .build() + .unwrap_or_else(|_| { + crate::config::build_runtime_proxy_client_with_timeouts( + "provider.anthropic", + 120, + 10, + ) + }) } } @@ -591,18 +612,38 @@ impl Provider for AnthropicProvider { tool_choice: None, }; + let url = format!("{}/v1/messages", self.base_url); + let request_body = serde_json::to_string(&request).unwrap_or_default(); + tracing::debug!( + base_url = %self.base_url, + url = %url, + model = %model, + has_credential = credential.len() > 0, + credential_prefix = %&credential[..credential.len().min(10)], + request_body_len = request_body.len(), + "Anthropic chat_with_system: sending request" + ); + tracing::debug!(request_body = %request_body, "Anthropic request body"); let mut request = self .http_client() - .post(format!("{}/v1/messages", self.base_url)) + .post(&url) .header("anthropic-version", "2023-06-01") .header("content-type", "application/json") - .json(&request); + .body(request_body); request = self.apply_auth(request, credential); let response = request.send().await?; if !response.status().is_success() { + let status = response.status(); + tracing::warn!( + base_url = %self.base_url, + url = %url, + status = %status, + credential_prefix = %&credential[..credential.len().min(10)], + "Anthropic chat_with_system: request failed" + ); return Err(super::api_error("Anthropic", response).await); } @@ -661,15 +702,31 @@ impl Provider for AnthropicProvider { tool_choice, }; + let url = format!("{}/v1/messages", self.base_url); + tracing::debug!( + base_url = %self.base_url, + url = %url, + model = %model, + has_credential = credential.len() > 0, + credential_prefix = %&credential[..credential.len().min(10)], + "Anthropic provider: sending request" + ); let req = self .http_client() - .post(format!("{}/v1/messages", self.base_url)) + .post(&url) .header("anthropic-version", "2023-06-01") .header("content-type", "application/json") .json(&native_request); let response = self.apply_auth(req, credential).send().await?; if !response.status().is_success() { + let status = response.status(); + tracing::warn!( + base_url = %self.base_url, + url = %url, + status = %status, + "Anthropic provider: request failed" + ); return Err(super::api_error("Anthropic", response).await); } diff --git a/src/providers/mod.rs b/src/providers/mod.rs index 7087ed761..257c148e0 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -851,7 +851,9 @@ fn resolve_provider_credential(name: &str, credential_override: Option<&str>) -> if let Some(credential) = resolve_minimax_oauth_refresh_token(name) { return Some(credential); } - } else if name == "anthropic" || name == "openai" || name == "groq" { + } else if name == "anthropic" || name == "openai" || name == "groq" + || is_qwen_coding_alias(name) + { // For well-known providers, prefer provider-specific env vars over the // global api_key override, since the global key may belong to a different // provider (e.g. a custom: gateway). This enables multi-provider setups @@ -860,6 +862,7 @@ fn resolve_provider_credential(name: &str, credential_override: Option<&str>) -> "anthropic" => &["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"], "openai" => &["OPENAI_API_KEY"], "groq" => &["GROQ_API_KEY"], + name if is_qwen_coding_alias(name) => &["MODELSTUDIO_API_KEY", "DASHSCOPE_API_KEY", "BAILIAN_API_KEY"], _ => &[], }; for env_var in env_candidates { @@ -1293,6 +1296,23 @@ fn create_provider_with_url_and_options( true, ) )), + name if is_qwen_coding_alias(name) => { + // Alibaba Coding Plan uses Anthropic Messages API format, + // not OpenAI Chat Completions. + let base_url = if is_qwen_coding_cn_alias(name) { + QWEN_CODING_CN_BASE_URL + } else { + QWEN_CODING_INTL_BASE_URL + }; + // The base URL includes /v1 but AnthropicProvider appends /v1/messages, + // so we need to strip /v1 and use /apps/anthropic as the base. + let anthropic_base = base_url.trim_end_matches("/v1"); + let anthropic_url = format!("{}/apps/anthropic", anthropic_base); + Ok(Box::new(anthropic::AnthropicProvider::with_base_url( + key, + Some(&anthropic_url), + ))) + } name if qwen_base_url(name).is_some() => Ok(compat(OpenAiCompatibleProvider::new_with_vision( "Qwen", qwen_base_url(name).expect("checked in guard"), From fb3d98cc1a76e347009ed3da9b85f29a5f97005f Mon Sep 17 00:00:00 2001 From: khhjoe Date: Sun, 22 Mar 2026 02:57:31 +0800 Subject: [PATCH 3/5] refactor(providers): switch qwen-coding from Anthropic to OpenAI-compatible format The DashScope Coding endpoint supports OpenAI Chat Completions format natively. Using it directly avoids unnecessary Anthropic wrapper overhead and enables extra_request_body pass-through (e.g. enable_thinking=false to suppress chain-of-thought reasoning tokens). Co-Authored-By: Claude Opus 4.6 --- src/providers/mod.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/providers/mod.rs b/src/providers/mod.rs index 257c148e0..d2432d069 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -1297,20 +1297,17 @@ fn create_provider_with_url_and_options( ) )), name if is_qwen_coding_alias(name) => { - // Alibaba Coding Plan uses Anthropic Messages API format, - // not OpenAI Chat Completions. let base_url = if is_qwen_coding_cn_alias(name) { QWEN_CODING_CN_BASE_URL } else { QWEN_CODING_INTL_BASE_URL }; - // The base URL includes /v1 but AnthropicProvider appends /v1/messages, - // so we need to strip /v1 and use /apps/anthropic as the base. - let anthropic_base = base_url.trim_end_matches("/v1"); - let anthropic_url = format!("{}/apps/anthropic", anthropic_base); - Ok(Box::new(anthropic::AnthropicProvider::with_base_url( + Ok(compat(OpenAiCompatibleProvider::new_with_vision( + "Qwen Coding", + base_url, key, - Some(&anthropic_url), + AuthStyle::Bearer, + true, ))) } name if qwen_base_url(name).is_some() => Ok(compat(OpenAiCompatibleProvider::new_with_vision( From ce23e11353f8084c7c23211986811b5a294b504f Mon Sep 17 00:00:00 2001 From: khhjoe Date: Mon, 23 Mar 2026 14:20:24 +0800 Subject: [PATCH 4/5] fix: resolve cherry-pick conflicts and align qwen-coding naming - Replace QWEN_CODING_PLAN_BASE_URL with QWEN_CODING_CN/INTL_BASE_URL - Add is_qwen_coding_alias, is_qwen_coding_cn_alias, is_qwen_coding_intl_alias - Keep qwen-coding-plan as backward-compatible alias - Remove provider_env field (not yet on master) Co-Authored-By: Claude Opus 4.6 (1M context) --- src/onboard/wizard.rs | 6 ++++-- src/providers/mod.rs | 38 ++++++++++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/onboard/wizard.rs b/src/onboard/wizard.rs index 782dc3edf..d2685ae09 100644 --- a/src/onboard/wizard.rs +++ b/src/onboard/wizard.rs @@ -142,7 +142,7 @@ pub async fn run_wizard(force: bool) -> Result { default_temperature: 0.7, provider_timeout_secs: 120, extra_headers: std::collections::HashMap::new(), - provider_env: std::collections::HashMap::new(), + observability: ObservabilityConfig::default(), autonomy: AutonomyConfig::default(), backup: crate::config::BackupConfig::default(), @@ -575,7 +575,7 @@ async fn run_quick_setup_with_home( default_temperature: 0.7, provider_timeout_secs: 120, extra_headers: std::collections::HashMap::new(), - provider_env: std::collections::HashMap::new(), + observability: ObservabilityConfig::default(), autonomy: AutonomyConfig::default(), backup: crate::config::BackupConfig::default(), @@ -7473,6 +7473,8 @@ mod tests { assert_eq!(provider_env_var("qwen-intl"), "DASHSCOPE_API_KEY"); assert_eq!(provider_env_var("dashscope-us"), "DASHSCOPE_API_KEY"); assert_eq!(provider_env_var("qwen-coding-plan"), "DASHSCOPE_API_KEY"); + assert_eq!(provider_env_var("qwen-coding"), "DASHSCOPE_API_KEY"); + assert_eq!(provider_env_var("qwen-coding-cn"), "DASHSCOPE_API_KEY"); assert_eq!(provider_env_var("qwen-code"), "QWEN_OAUTH_TOKEN"); assert_eq!(provider_env_var("qwen-oauth"), "QWEN_OAUTH_TOKEN"); assert_eq!(provider_env_var("glm-cn"), "GLM_API_KEY"); diff --git a/src/providers/mod.rs b/src/providers/mod.rs index d2432d069..bcf68b9aa 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -66,7 +66,8 @@ const MOONSHOT_CN_BASE_URL: &str = "https://api.moonshot.cn/v1"; const QWEN_CN_BASE_URL: &str = "https://dashscope.aliyuncs.com/compatible-mode/v1"; const QWEN_INTL_BASE_URL: &str = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"; const QWEN_US_BASE_URL: &str = "https://dashscope-us.aliyuncs.com/compatible-mode/v1"; -const QWEN_CODING_PLAN_BASE_URL: &str = "https://coding.dashscope.aliyuncs.com/v1"; +const QWEN_CODING_INTL_BASE_URL: &str = "https://coding-intl.dashscope.aliyuncs.com/v1"; +const QWEN_CODING_CN_BASE_URL: &str = "https://coding.dashscope.aliyuncs.com/v1"; const QWEN_OAUTH_BASE_FALLBACK_URL: &str = QWEN_CN_BASE_URL; const BAILIAN_BASE_URL: &str = "https://coding.dashscope.aliyuncs.com/v1"; const QWEN_OAUTH_TOKEN_ENDPOINT: &str = "https://chat.qwen.ai/api/v1/oauth2/token"; @@ -156,8 +157,23 @@ pub(crate) fn is_bailian_alias(name: &str) -> bool { matches!(name, "bailian" | "aliyun-bailian" | "aliyun") } -pub(crate) fn is_qwen_coding_plan_alias(name: &str) -> bool { - matches!(name, "qwen-coding-plan") +pub(crate) fn is_qwen_coding_intl_alias(name: &str) -> bool { + matches!( + name, + "qwen-coding" + | "qwen-coding-plan" + | "modelstudio" + | "qwen-coding-intl" + | "modelstudio-intl" + ) +} + +pub(crate) fn is_qwen_coding_cn_alias(name: &str) -> bool { + matches!(name, "qwen-coding-cn" | "modelstudio-cn") +} + +pub(crate) fn is_qwen_coding_alias(name: &str) -> bool { + is_qwen_coding_intl_alias(name) || is_qwen_coding_cn_alias(name) } pub(crate) fn is_qwen_alias(name: &str) -> bool { @@ -165,7 +181,7 @@ pub(crate) fn is_qwen_alias(name: &str) -> bool { || is_qwen_intl_alias(name) || is_qwen_us_alias(name) || is_qwen_oauth_alias(name) - || is_qwen_coding_plan_alias(name) + || is_qwen_coding_alias(name) } pub(crate) fn is_zai_global_alias(name: &str) -> bool { @@ -665,8 +681,10 @@ fn moonshot_base_url(name: &str) -> Option<&'static str> { } fn qwen_base_url(name: &str) -> Option<&'static str> { - if is_qwen_coding_plan_alias(name) { - Some(QWEN_CODING_PLAN_BASE_URL) + if is_qwen_coding_intl_alias(name) { + Some(QWEN_CODING_INTL_BASE_URL) + } else if is_qwen_coding_cn_alias(name) { + Some(QWEN_CODING_CN_BASE_URL) } else if is_qwen_cn_alias(name) || is_qwen_oauth_alias(name) { Some(QWEN_CN_BASE_URL) } else if is_qwen_intl_alias(name) { @@ -2475,8 +2493,12 @@ mod tests { assert_eq!(qwen_base_url("qwen-intl"), Some(QWEN_INTL_BASE_URL)); assert_eq!(qwen_base_url("qwen-us"), Some(QWEN_US_BASE_URL)); assert_eq!( - qwen_base_url("qwen-coding-plan"), - Some(QWEN_CODING_PLAN_BASE_URL) + qwen_base_url("qwen-coding"), + Some(QWEN_CODING_INTL_BASE_URL) + ); + assert_eq!( + qwen_base_url("qwen-coding-cn"), + Some(QWEN_CODING_CN_BASE_URL) ); assert_eq!(qwen_base_url("qwen-code"), Some(QWEN_CN_BASE_URL)); From 389fbd0ce5282f8245f7718cfafb1750a001f344 Mon Sep 17 00:00:00 2001 From: khhjoe Date: Mon, 23 Mar 2026 14:48:51 +0800 Subject: [PATCH 5/5] style: cargo fmt + fix clippy len_zero warnings --- src/providers/anthropic.rs | 4 ++-- src/providers/mod.rs | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/providers/anthropic.rs b/src/providers/anthropic.rs index a764f4eb1..5b00c1fee 100644 --- a/src/providers/anthropic.rs +++ b/src/providers/anthropic.rs @@ -618,7 +618,7 @@ impl Provider for AnthropicProvider { base_url = %self.base_url, url = %url, model = %model, - has_credential = credential.len() > 0, + has_credential = !credential.is_empty(), credential_prefix = %&credential[..credential.len().min(10)], request_body_len = request_body.len(), "Anthropic chat_with_system: sending request" @@ -707,7 +707,7 @@ impl Provider for AnthropicProvider { base_url = %self.base_url, url = %url, model = %model, - has_credential = credential.len() > 0, + has_credential = !credential.is_empty(), credential_prefix = %&credential[..credential.len().min(10)], "Anthropic provider: sending request" ); diff --git a/src/providers/mod.rs b/src/providers/mod.rs index bcf68b9aa..1ee236724 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -869,7 +869,9 @@ fn resolve_provider_credential(name: &str, credential_override: Option<&str>) -> if let Some(credential) = resolve_minimax_oauth_refresh_token(name) { return Some(credential); } - } else if name == "anthropic" || name == "openai" || name == "groq" + } else if name == "anthropic" + || name == "openai" + || name == "groq" || is_qwen_coding_alias(name) { // For well-known providers, prefer provider-specific env vars over the @@ -880,7 +882,11 @@ fn resolve_provider_credential(name: &str, credential_override: Option<&str>) -> "anthropic" => &["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"], "openai" => &["OPENAI_API_KEY"], "groq" => &["GROQ_API_KEY"], - name if is_qwen_coding_alias(name) => &["MODELSTUDIO_API_KEY", "DASHSCOPE_API_KEY", "BAILIAN_API_KEY"], + name if is_qwen_coding_alias(name) => &[ + "MODELSTUDIO_API_KEY", + "DASHSCOPE_API_KEY", + "BAILIAN_API_KEY", + ], _ => &[], }; for env_var in env_candidates {