From 88d1b6ea3429a43642e3110acbe9af427b834b05 Mon Sep 17 00:00:00 2001 From: cryptonahue Date: Sat, 7 Mar 2026 07:56:24 +0000 Subject: [PATCH 1/5] Add --no-sandbox flag when Chromium runs as root Chromium refuses to launch without --no-sandbox when the process is running as UID 0. This causes the browser hand to fail immediately with 'Chromium exited before printing DevTools URL' on any server-based OpenFang installation that runs as root (the default install). Added is_running_as_root() which reads /proc/self/status on Linux to detect UID 0 without requiring a libc dependency, with a fallback to the HOME env var for other Unix systems. When root is detected, --no-sandbox is appended to the Chromium launch args automatically. --- crates/openfang-runtime/src/browser.rs | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/crates/openfang-runtime/src/browser.rs b/crates/openfang-runtime/src/browser.rs index 4bb0f2e79..a16074104 100644 --- a/crates/openfang-runtime/src/browser.rs +++ b/crates/openfang-runtime/src/browser.rs @@ -258,6 +258,12 @@ impl BrowserSession { args.insert(0, "--headless=new".to_string()); args.push("--disable-gpu".to_string()); } + // Chromium refuses to run as root without --no-sandbox. Detect this + // without adding a libc dependency by reading the effective UID from + // /proc/self/status (Linux) or falling back to the HOME env var. + if is_running_as_root() { + args.push("--no-sandbox".to_string()); + } let mut cmd = tokio::process::Command::new(&chrome_path); cmd.args(&args); @@ -1154,6 +1160,36 @@ const EXTRACT_CONTENT_JS: &str = r#"(() => { return JSON.stringify({title, url, content}); })()"#; +// ── Root detection ───────────────────────────────────────────────────────── + +/// Returns true if the current process is running as root (UID 0). +/// +/// On Linux, reads `/proc/self/status` to get the effective UID without +/// requiring a `libc` dependency. Falls back to checking the `HOME` env var +/// on systems where `/proc` is not available. +fn is_running_as_root() -> bool { + #[cfg(unix)] + { + // Primary: read effective UID from /proc/self/status (Linux) + if let Ok(status) = std::fs::read_to_string("/proc/self/status") { + for line in status.lines() { + if let Some(rest) = line.strip_prefix("Uid:") { + // Format: "Uid: " + if let Some(euid_str) = rest.split_whitespace().nth(1) { + return euid_str == "0"; + } + } + } + } + // Fallback: HOME=/root is a reliable indicator on most Unix systems + std::env::var("HOME").map(|h| h == "/root").unwrap_or(false) + } + #[cfg(not(unix))] + { + false + } +} + // ── Tests ────────────────────────────────────────────────────────────────── #[cfg(test)] @@ -1290,6 +1326,12 @@ mod tests { assert!(mgr.sessions.is_empty()); } + #[test] + fn test_is_running_as_root_returns_bool() { + // Just verify it doesn't panic and returns a bool. + let _ = is_running_as_root(); + } + #[test] fn test_chromium_candidates_not_empty() { let paths = chromium_candidates(); From f6f521d1f62e025e7a14504e8cb96f92f2402a55 Mon Sep 17 00:00:00 2001 From: cryptonahue Date: Sat, 7 Mar 2026 22:17:32 +0000 Subject: [PATCH 2/5] feat: add DashScope Coding Plan providers (qwen_intl, qwen_coding, qwen_coding_intl) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds native support for Alibaba Cloud's DashScope Coding Plan, which provides access to Qwen, GLM, Kimi, and MiniMax models via a single DASHSCOPE_API_KEY. Three new providers: - qwen_intl: DashScope international standard endpoint - qwen_coding: DashScope Coding Plan (China) endpoint - qwen_coding_intl: DashScope Coding Plan (International) endpoint Eight new model catalog entries under qwen_coding_intl: - qwen3.5-plus (1M ctx, 65k output, vision+tools) - qwen3-max-2026-01-23 (131k ctx, Frontier) - qwen3-coder-plus (131k ctx, Smart) - qwen3-coder-next (131k ctx, Frontier) - qwen_coding_intl/glm-5 (Zhipu GLM-5 via coding plan) - qwen_coding_intl/glm-4.7 (Zhipu GLM-4.7 via coding plan) - qwen_coding_intl/kimi-k2.5 (Moonshot Kimi K2.5 via coding plan) - qwen_coding_intl/MiniMax-M2.5 (MiniMax M2.5 via coding plan) Multi-brand models use the "provider/model" prefix convention (same pattern as OpenRouter). The existing strip_provider_prefix() in agent_loop strips the prefix before the API call, so the API receives the bare model name ("glm-5", "kimi-k2.5", etc.) as expected by the DashScope endpoint. Native providers (zhipu, moonshot, minimax) are unchanged — users with separate API keys for those providers are not affected. Also adds wizard support for all three new providers and fixes qwen3-235b-a22b max_output_tokens (8192 → 32768). Co-Authored-By: Claude Sonnet 4.6 --- Cargo.lock | 28 +-- crates/openfang-cli/src/tui/screens/wizard.rs | 18 ++ crates/openfang-runtime/src/drivers/mod.rs | 27 ++- crates/openfang-runtime/src/model_catalog.rs | 167 +++++++++++++++++- crates/openfang-types/src/model_catalog.rs | 9 + 5 files changed, 228 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8d466d71..8874f76a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3875,7 +3875,7 @@ dependencies = [ [[package]] name = "openfang-api" -version = "0.3.29" +version = "0.3.26" dependencies = [ "async-trait", "axum", @@ -3912,7 +3912,7 @@ dependencies = [ [[package]] name = "openfang-channels" -version = "0.3.29" +version = "0.3.26" dependencies = [ "async-trait", "axum", @@ -3944,7 +3944,7 @@ dependencies = [ [[package]] name = "openfang-cli" -version = "0.3.29" +version = "0.3.26" dependencies = [ "clap", "clap_complete", @@ -3971,7 +3971,7 @@ dependencies = [ [[package]] name = "openfang-desktop" -version = "0.3.29" +version = "0.3.26" dependencies = [ "axum", "open", @@ -3997,7 +3997,7 @@ dependencies = [ [[package]] name = "openfang-extensions" -version = "0.3.29" +version = "0.3.26" dependencies = [ "aes-gcm", "argon2", @@ -4025,7 +4025,7 @@ dependencies = [ [[package]] name = "openfang-hands" -version = "0.3.29" +version = "0.3.26" dependencies = [ "chrono", "dashmap", @@ -4042,7 +4042,7 @@ dependencies = [ [[package]] name = "openfang-kernel" -version = "0.3.29" +version = "0.3.26" dependencies = [ "async-trait", "chrono", @@ -4078,7 +4078,7 @@ dependencies = [ [[package]] name = "openfang-memory" -version = "0.3.29" +version = "0.3.26" dependencies = [ "async-trait", "chrono", @@ -4097,7 +4097,7 @@ dependencies = [ [[package]] name = "openfang-migrate" -version = "0.3.29" +version = "0.3.26" dependencies = [ "chrono", "dirs 6.0.0", @@ -4116,7 +4116,7 @@ dependencies = [ [[package]] name = "openfang-runtime" -version = "0.3.29" +version = "0.3.26" dependencies = [ "anyhow", "async-trait", @@ -4148,7 +4148,7 @@ dependencies = [ [[package]] name = "openfang-skills" -version = "0.3.29" +version = "0.3.26" dependencies = [ "chrono", "hex", @@ -4171,7 +4171,7 @@ dependencies = [ [[package]] name = "openfang-types" -version = "0.3.29" +version = "0.3.26" dependencies = [ "async-trait", "chrono", @@ -4190,7 +4190,7 @@ dependencies = [ [[package]] name = "openfang-wire" -version = "0.3.29" +version = "0.3.26" dependencies = [ "async-trait", "chrono", @@ -8818,7 +8818,7 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xtask" -version = "0.3.29" +version = "0.3.26" [[package]] name = "yoke" diff --git a/crates/openfang-cli/src/tui/screens/wizard.rs b/crates/openfang-cli/src/tui/screens/wizard.rs index 4ca4a136f..344dfe5ae 100644 --- a/crates/openfang-cli/src/tui/screens/wizard.rs +++ b/crates/openfang-cli/src/tui/screens/wizard.rs @@ -85,6 +85,24 @@ const PROVIDERS: &[ProviderInfo] = &[ default_model: "qwen-plus", needs_key: true, }, + ProviderInfo { + name: "qwen_intl", + env_var: "DASHSCOPE_API_KEY", + default_model: "qwen-plus", + needs_key: true, + }, + ProviderInfo { + name: "qwen_coding", + env_var: "DASHSCOPE_API_KEY", + default_model: "qwen3-coder-plus", + needs_key: true, + }, + ProviderInfo { + name: "qwen_coding_intl", + env_var: "DASHSCOPE_API_KEY", + default_model: "qwen3.5-plus", + needs_key: true, + }, ProviderInfo { name: "perplexity", env_var: "PERPLEXITY_API_KEY", diff --git a/crates/openfang-runtime/src/drivers/mod.rs b/crates/openfang-runtime/src/drivers/mod.rs index 9285a3aab..33c563915 100644 --- a/crates/openfang-runtime/src/drivers/mod.rs +++ b/crates/openfang-runtime/src/drivers/mod.rs @@ -18,7 +18,8 @@ use openfang_types::model_catalog::{ LMSTUDIO_BASE_URL, MINIMAX_BASE_URL, MISTRAL_BASE_URL, MOONSHOT_BASE_URL, OLLAMA_BASE_URL, OPENAI_BASE_URL, OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL, QIANFAN_BASE_URL, QWEN_BASE_URL, - REPLICATE_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VENICE_BASE_URL, VLLM_BASE_URL, + QWEN_CODING_BASE_URL, QWEN_CODING_INTL_BASE_URL, QWEN_INTL_BASE_URL, REPLICATE_BASE_URL, + SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VENICE_BASE_URL, VLLM_BASE_URL, VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL, ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL, }; @@ -160,6 +161,22 @@ fn provider_defaults(provider: &str) -> Option { api_key_env: "DASHSCOPE_API_KEY", key_required: true, }), + "qwen_intl" | "dashscope_intl" => Some(ProviderDefaults { + base_url: QWEN_INTL_BASE_URL, + api_key_env: "DASHSCOPE_API_KEY", + key_required: true, + }), + // DashScope Coding Plan — multi-brand (Qwen, Zhipu/GLM, Kimi, MiniMax) + "qwen_coding" | "dashscope_coding" => Some(ProviderDefaults { + base_url: QWEN_CODING_BASE_URL, + api_key_env: "DASHSCOPE_API_KEY", + key_required: true, + }), + "qwen_coding_intl" | "dashscope_coding_intl" => Some(ProviderDefaults { + base_url: QWEN_CODING_INTL_BASE_URL, + api_key_env: "DASHSCOPE_API_KEY", + key_required: true, + }), "minimax" => Some(ProviderDefaults { base_url: MINIMAX_BASE_URL, api_key_env: "MINIMAX_API_KEY", @@ -420,6 +437,9 @@ pub fn known_providers() -> &'static [&'static str] { "github-copilot", "moonshot", "qwen", + "qwen_intl", + "qwen_coding", + "qwen_coding_intl", "minimax", "zhipu", "zhipu_coding", @@ -517,6 +537,9 @@ mod tests { assert!(providers.contains(&"github-copilot")); assert!(providers.contains(&"moonshot")); assert!(providers.contains(&"qwen")); + assert!(providers.contains(&"qwen_intl")); + assert!(providers.contains(&"qwen_coding")); + assert!(providers.contains(&"qwen_coding_intl")); assert!(providers.contains(&"minimax")); assert!(providers.contains(&"zhipu")); assert!(providers.contains(&"zhipu_coding")); @@ -524,7 +547,7 @@ mod tests { assert!(providers.contains(&"volcengine")); assert!(providers.contains(&"codex")); assert!(providers.contains(&"claude-code")); - assert_eq!(providers.len(), 31); + assert_eq!(providers.len(), 34); } #[test] diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index 1df4eb3c2..847974fb4 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -9,7 +9,8 @@ use openfang_types::model_catalog::{ GEMINI_BASE_URL, GITHUB_COPILOT_BASE_URL, GROQ_BASE_URL, HUGGINGFACE_BASE_URL, LMSTUDIO_BASE_URL, MINIMAX_BASE_URL, MISTRAL_BASE_URL, MOONSHOT_BASE_URL, OLLAMA_BASE_URL, LEMONADE_BASE_URL, OPENAI_BASE_URL, OPENROUTER_BASE_URL, PERPLEXITY_BASE_URL, - QIANFAN_BASE_URL, QWEN_BASE_URL, + QIANFAN_BASE_URL, QWEN_BASE_URL, QWEN_CODING_BASE_URL, QWEN_CODING_INTL_BASE_URL, + QWEN_INTL_BASE_URL, REPLICATE_BASE_URL, SAMBANOVA_BASE_URL, TOGETHER_BASE_URL, VENICE_BASE_URL, VLLM_BASE_URL, VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL, ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL, @@ -602,16 +603,45 @@ fn builtin_providers() -> Vec { auth_status: AuthStatus::Missing, model_count: 0, }, - // ── Chinese providers (5) ──────────────────────────────────── + // ── DashScope / Qwen (Alibaba Cloud) ──────────────────────── ProviderInfo { id: "qwen".into(), - display_name: "Qwen (Alibaba)".into(), + display_name: "Qwen — DashScope (China)".into(), api_key_env: "DASHSCOPE_API_KEY".into(), base_url: QWEN_BASE_URL.into(), key_required: true, auth_status: AuthStatus::Missing, model_count: 0, }, + ProviderInfo { + id: "qwen_intl".into(), + display_name: "Qwen — DashScope (International)".into(), + api_key_env: "DASHSCOPE_API_KEY".into(), + base_url: QWEN_INTL_BASE_URL.into(), + key_required: true, + auth_status: AuthStatus::Missing, + model_count: 0, + }, + // DashScope Coding Plan: single API key, multi-brand models + // (Qwen, Zhipu/GLM, Kimi, MiniMax — all via Alibaba Cloud) + ProviderInfo { + id: "qwen_coding".into(), + display_name: "DashScope Coding Plan (China)".into(), + api_key_env: "DASHSCOPE_API_KEY".into(), + base_url: QWEN_CODING_BASE_URL.into(), + key_required: true, + auth_status: AuthStatus::Missing, + model_count: 0, + }, + ProviderInfo { + id: "qwen_coding_intl".into(), + display_name: "DashScope Coding Plan (International)".into(), + api_key_env: "DASHSCOPE_API_KEY".into(), + base_url: QWEN_CODING_INTL_BASE_URL.into(), + key_required: true, + auth_status: AuthStatus::Missing, + model_count: 0, + }, ProviderInfo { id: "minimax".into(), display_name: "MiniMax".into(), @@ -2597,7 +2627,7 @@ fn builtin_models() -> Vec { provider: "qwen".into(), tier: ModelTier::Frontier, context_window: 131_072, - max_output_tokens: 8_192, + max_output_tokens: 32_768, input_cost_per_m: 4.00, output_cost_per_m: 12.00, supports_tools: true, @@ -2662,6 +2692,133 @@ fn builtin_models() -> Vec { aliases: vec![], }, // ══════════════════════════════════════════════════════════════ + // DashScope Coding Plan — International + // All accessed via DASHSCOPE_API_KEY on qwen_coding_intl provider. + // Model IDs use "qwen_coding_intl/" format — the provider prefix is + // stripped automatically by strip_provider_prefix() before the API call, + // so the API receives the bare model name (e.g. "glm-5", "kimi-k2.5"). + // This mirrors the OpenRouter pattern and avoids collisions with native providers. + // ══════════════════════════════════════════════════════════════ + // ── Qwen (4) ──────────────────────────────────────────────── + ModelCatalogEntry { + id: "qwen3.5-plus".into(), + display_name: "Qwen 3.5 Plus".into(), + provider: "qwen_coding_intl".into(), + tier: ModelTier::Smart, + context_window: 1_000_000, + max_output_tokens: 65_536, + input_cost_per_m: 0.40, + output_cost_per_m: 2.40, + supports_tools: true, + supports_vision: true, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "qwen3-max-2026-01-23".into(), + display_name: "Qwen 3 Max (2026-01-23)".into(), + provider: "qwen_coding_intl".into(), + tier: ModelTier::Frontier, + context_window: 131_072, + max_output_tokens: 32_768, + input_cost_per_m: 4.00, + output_cost_per_m: 16.00, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "qwen3-coder-plus".into(), + display_name: "Qwen 3 Coder Plus".into(), + provider: "qwen_coding_intl".into(), + tier: ModelTier::Smart, + context_window: 131_072, + max_output_tokens: 32_768, + input_cost_per_m: 0.80, + output_cost_per_m: 3.20, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec!["qwen3-coder".into()], + }, + ModelCatalogEntry { + id: "qwen3-coder-next".into(), + display_name: "Qwen 3 Coder Next".into(), + provider: "qwen_coding_intl".into(), + tier: ModelTier::Frontier, + context_window: 131_072, + max_output_tokens: 32_768, + input_cost_per_m: 4.00, + output_cost_per_m: 16.00, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + // ── Zhipu / GLM via Coding Plan (2) ───────────────────────── + // API receives "glm-5" / "glm-4.7" after prefix stripping. + ModelCatalogEntry { + id: "qwen_coding_intl/glm-5".into(), + display_name: "GLM-5 (Coding Plan)".into(), + provider: "qwen_coding_intl".into(), + tier: ModelTier::Frontier, + context_window: 128_000, + max_output_tokens: 32_768, + input_cost_per_m: 4.00, + output_cost_per_m: 16.00, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "qwen_coding_intl/glm-4.7".into(), + display_name: "GLM-4.7 (Coding Plan)".into(), + provider: "qwen_coding_intl".into(), + tier: ModelTier::Smart, + context_window: 128_000, + max_output_tokens: 32_768, + input_cost_per_m: 0.80, + output_cost_per_m: 3.20, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + // ── Moonshot / Kimi via Coding Plan (1) ───────────────────── + // API receives "kimi-k2.5" after prefix stripping. + ModelCatalogEntry { + id: "qwen_coding_intl/kimi-k2.5".into(), + display_name: "Kimi K2.5 (Coding Plan)".into(), + provider: "qwen_coding_intl".into(), + tier: ModelTier::Smart, + context_window: 128_000, + max_output_tokens: 32_768, + input_cost_per_m: 0.80, + output_cost_per_m: 3.20, + supports_tools: true, + supports_vision: true, + supports_streaming: true, + aliases: vec![], + }, + // ── MiniMax via Coding Plan (1) ────────────────────────────── + // API receives "MiniMax-M2.5" after prefix stripping. + ModelCatalogEntry { + id: "qwen_coding_intl/MiniMax-M2.5".into(), + display_name: "MiniMax M2.5 (Coding Plan)".into(), + provider: "qwen_coding_intl".into(), + tier: ModelTier::Smart, + context_window: 1_000_000, + max_output_tokens: 32_768, + input_cost_per_m: 0.80, + output_cost_per_m: 3.20, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + // ══════════════════════════════════════════════════════════════ // MiniMax (4) // ══════════════════════════════════════════════════════════════ ModelCatalogEntry { @@ -3253,7 +3410,7 @@ mod tests { #[test] fn test_catalog_has_providers() { let catalog = ModelCatalog::new(); - assert_eq!(catalog.list_providers().len(), 36); + assert_eq!(catalog.list_providers().len(), 39); } #[test] diff --git a/crates/openfang-types/src/model_catalog.rs b/crates/openfang-types/src/model_catalog.rs index 92019518d..f7427b946 100644 --- a/crates/openfang-types/src/model_catalog.rs +++ b/crates/openfang-types/src/model_catalog.rs @@ -36,6 +36,15 @@ pub const GITHUB_COPILOT_BASE_URL: &str = "https://api.githubcopilot.com"; // ── Chinese providers ───────────────────────────────────────────── pub const QWEN_BASE_URL: &str = "https://dashscope.aliyuncs.com/compatible-mode/v1"; +/// DashScope international endpoint (ap-southeast-1 and other international regions). +pub const QWEN_INTL_BASE_URL: &str = + "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"; +/// DashScope Coding Plan — China endpoint. Multi-brand: Qwen, Zhipu, Kimi, MiniMax. +pub const QWEN_CODING_BASE_URL: &str = + "https://coding.dashscope.aliyuncs.com/compatible-mode/v1"; +/// DashScope Coding Plan — International endpoint. Multi-brand: Qwen, Zhipu, Kimi, MiniMax. +pub const QWEN_CODING_INTL_BASE_URL: &str = + "https://coding-intl.dashscope.aliyuncs.com/compatible-mode/v1"; pub const MINIMAX_BASE_URL: &str = "https://api.minimax.io/v1"; pub const ZHIPU_BASE_URL: &str = "https://open.bigmodel.cn/api/paas/v4"; pub const ZHIPU_CODING_BASE_URL: &str = "https://open.bigmodel.cn/api/coding/paas/v4"; From 30b275c3aecbd0c5748d5948e0ceed6e451a222a Mon Sep 17 00:00:00 2001 From: cryptonahue Date: Sun, 8 Mar 2026 00:51:14 +0000 Subject: [PATCH 3/5] =?UTF-8?q?refactor:=20simplify=20DashScope=20provider?= =?UTF-8?q?s=20=E2=80=94=20keep=20qwen=20+=20qwen=5Fcoding=5Fintl=20only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove qwen_intl and qwen_coding (China/International variants) that added confusion without providing distinct model sets. Keep: - qwen: standard DashScope endpoint (existing 11 models) - qwen_coding_intl: DashScope Coding Plan, renamed to "DashScope Coding Plan" Co-Authored-By: Claude Sonnet 4.6 --- crates/openfang-cli/src/tui/screens/wizard.rs | 12 ---------- crates/openfang-runtime/src/drivers/mod.rs | 18 ++------------- crates/openfang-runtime/src/model_catalog.rs | 22 ++----------------- 3 files changed, 4 insertions(+), 48 deletions(-) diff --git a/crates/openfang-cli/src/tui/screens/wizard.rs b/crates/openfang-cli/src/tui/screens/wizard.rs index 344dfe5ae..d11e32bc6 100644 --- a/crates/openfang-cli/src/tui/screens/wizard.rs +++ b/crates/openfang-cli/src/tui/screens/wizard.rs @@ -85,18 +85,6 @@ const PROVIDERS: &[ProviderInfo] = &[ default_model: "qwen-plus", needs_key: true, }, - ProviderInfo { - name: "qwen_intl", - env_var: "DASHSCOPE_API_KEY", - default_model: "qwen-plus", - needs_key: true, - }, - ProviderInfo { - name: "qwen_coding", - env_var: "DASHSCOPE_API_KEY", - default_model: "qwen3-coder-plus", - needs_key: true, - }, ProviderInfo { name: "qwen_coding_intl", env_var: "DASHSCOPE_API_KEY", diff --git a/crates/openfang-runtime/src/drivers/mod.rs b/crates/openfang-runtime/src/drivers/mod.rs index 33c563915..5f52fae40 100644 --- a/crates/openfang-runtime/src/drivers/mod.rs +++ b/crates/openfang-runtime/src/drivers/mod.rs @@ -161,18 +161,8 @@ fn provider_defaults(provider: &str) -> Option { api_key_env: "DASHSCOPE_API_KEY", key_required: true, }), - "qwen_intl" | "dashscope_intl" => Some(ProviderDefaults { - base_url: QWEN_INTL_BASE_URL, - api_key_env: "DASHSCOPE_API_KEY", - key_required: true, - }), // DashScope Coding Plan — multi-brand (Qwen, Zhipu/GLM, Kimi, MiniMax) - "qwen_coding" | "dashscope_coding" => Some(ProviderDefaults { - base_url: QWEN_CODING_BASE_URL, - api_key_env: "DASHSCOPE_API_KEY", - key_required: true, - }), - "qwen_coding_intl" | "dashscope_coding_intl" => Some(ProviderDefaults { + "qwen_coding_intl" | "dashscope_coding" | "dashscope_coding_intl" => Some(ProviderDefaults { base_url: QWEN_CODING_INTL_BASE_URL, api_key_env: "DASHSCOPE_API_KEY", key_required: true, @@ -437,8 +427,6 @@ pub fn known_providers() -> &'static [&'static str] { "github-copilot", "moonshot", "qwen", - "qwen_intl", - "qwen_coding", "qwen_coding_intl", "minimax", "zhipu", @@ -537,8 +525,6 @@ mod tests { assert!(providers.contains(&"github-copilot")); assert!(providers.contains(&"moonshot")); assert!(providers.contains(&"qwen")); - assert!(providers.contains(&"qwen_intl")); - assert!(providers.contains(&"qwen_coding")); assert!(providers.contains(&"qwen_coding_intl")); assert!(providers.contains(&"minimax")); assert!(providers.contains(&"zhipu")); @@ -547,7 +533,7 @@ mod tests { assert!(providers.contains(&"volcengine")); assert!(providers.contains(&"codex")); assert!(providers.contains(&"claude-code")); - assert_eq!(providers.len(), 34); + assert_eq!(providers.len(), 32); } #[test] diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index 847974fb4..96650d922 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -613,29 +613,11 @@ fn builtin_providers() -> Vec { auth_status: AuthStatus::Missing, model_count: 0, }, - ProviderInfo { - id: "qwen_intl".into(), - display_name: "Qwen — DashScope (International)".into(), - api_key_env: "DASHSCOPE_API_KEY".into(), - base_url: QWEN_INTL_BASE_URL.into(), - key_required: true, - auth_status: AuthStatus::Missing, - model_count: 0, - }, // DashScope Coding Plan: single API key, multi-brand models // (Qwen, Zhipu/GLM, Kimi, MiniMax — all via Alibaba Cloud) - ProviderInfo { - id: "qwen_coding".into(), - display_name: "DashScope Coding Plan (China)".into(), - api_key_env: "DASHSCOPE_API_KEY".into(), - base_url: QWEN_CODING_BASE_URL.into(), - key_required: true, - auth_status: AuthStatus::Missing, - model_count: 0, - }, ProviderInfo { id: "qwen_coding_intl".into(), - display_name: "DashScope Coding Plan (International)".into(), + display_name: "DashScope Coding Plan".into(), api_key_env: "DASHSCOPE_API_KEY".into(), base_url: QWEN_CODING_INTL_BASE_URL.into(), key_required: true, @@ -3410,7 +3392,7 @@ mod tests { #[test] fn test_catalog_has_providers() { let catalog = ModelCatalog::new(); - assert_eq!(catalog.list_providers().len(), 39); + assert_eq!(catalog.list_providers().len(), 37); } #[test] From c790229003e3d2f12af1695d510c9ab71ee361a6 Mon Sep 17 00:00:00 2001 From: cryptonahue Date: Sun, 8 Mar 2026 01:12:47 +0000 Subject: [PATCH 4/5] fix: restore original qwen provider display name "Qwen (Alibaba)" Co-Authored-By: Claude Sonnet 4.6 --- crates/openfang-runtime/src/model_catalog.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index 96650d922..4255d48fd 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -606,7 +606,7 @@ fn builtin_providers() -> Vec { // ── DashScope / Qwen (Alibaba Cloud) ──────────────────────── ProviderInfo { id: "qwen".into(), - display_name: "Qwen — DashScope (China)".into(), + display_name: "Qwen (Alibaba)".into(), api_key_env: "DASHSCOPE_API_KEY".into(), base_url: QWEN_BASE_URL.into(), key_required: true, From 21da8719b4a0c2d71839dcb18652b39b9bc7f7ee Mon Sep 17 00:00:00 2001 From: cryptonahue Date: Sun, 8 Mar 2026 06:38:55 +0000 Subject: [PATCH 5/5] fix: qwen_coding_intl sends User-Agent OpenClaw/1.0 natively Add special-case driver path for qwen_coding_intl that injects the required User-Agent: OpenClaw/1.0 header, eliminating the need for the proxy. Also fix base URL from /compatible-mode/v1 to /v1. Co-Authored-By: Claude Sonnet 4.6 --- crates/openfang-runtime/src/drivers/mod.rs | 26 ++++++++++++++++++++++ crates/openfang-types/src/model_catalog.rs | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/openfang-runtime/src/drivers/mod.rs b/crates/openfang-runtime/src/drivers/mod.rs index 5f52fae40..619706cf3 100644 --- a/crates/openfang-runtime/src/drivers/mod.rs +++ b/crates/openfang-runtime/src/drivers/mod.rs @@ -325,6 +325,32 @@ pub fn create_driver(config: &DriverConfig) -> Result, LlmErr ))); } + // DashScope Coding Plan — requires User-Agent: OpenClaw/1.0 + if provider == "qwen_coding_intl" || provider == "dashscope_coding" || provider == "dashscope_coding_intl" { + let api_key = config + .api_key + .clone() + .or_else(|| std::env::var("DASHSCOPE_API_KEY").ok()) + .or_else(|| std::env::var("QWEN_API_KEY").ok()) + .unwrap_or_default(); + + if api_key.is_empty() { + return Err(LlmError::MissingApiKey( + "Set DASHSCOPE_API_KEY environment variable for DashScope Coding Plan".to_string(), + )); + } + + let base_url = config + .base_url + .clone() + .unwrap_or_else(|| QWEN_CODING_INTL_BASE_URL.to_string()); + + return Ok(Arc::new( + openai::OpenAIDriver::new(api_key, base_url) + .with_extra_headers(vec![("User-Agent".to_string(), "OpenClaw/1.0".to_string())]), + )); + } + // All other providers use OpenAI-compatible format if let Some(defaults) = provider_defaults(provider) { let api_key = config diff --git a/crates/openfang-types/src/model_catalog.rs b/crates/openfang-types/src/model_catalog.rs index f7427b946..0dc89154a 100644 --- a/crates/openfang-types/src/model_catalog.rs +++ b/crates/openfang-types/src/model_catalog.rs @@ -44,7 +44,7 @@ pub const QWEN_CODING_BASE_URL: &str = "https://coding.dashscope.aliyuncs.com/compatible-mode/v1"; /// DashScope Coding Plan — International endpoint. Multi-brand: Qwen, Zhipu, Kimi, MiniMax. pub const QWEN_CODING_INTL_BASE_URL: &str = - "https://coding-intl.dashscope.aliyuncs.com/compatible-mode/v1"; + "https://coding-intl.dashscope.aliyuncs.com/v1"; pub const MINIMAX_BASE_URL: &str = "https://api.minimax.io/v1"; pub const ZHIPU_BASE_URL: &str = "https://open.bigmodel.cn/api/paas/v4"; pub const ZHIPU_CODING_BASE_URL: &str = "https://open.bigmodel.cn/api/coding/paas/v4";