From 72334f0e45f6a3ed88523b40ea66752aa0f23e91 Mon Sep 17 00:00:00 2001 From: gaoyiman Date: Fri, 20 Mar 2026 16:46:37 +0800 Subject: [PATCH 01/15] =?UTF-8?q?feat(volcengine):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=81=AB=E5=B1=B1=E5=BC=95=E6=93=8E(Doubao)=E5=92=8C=E7=81=AB?= =?UTF-8?q?=E5=B1=B1=E5=BC=95=E6=93=8E=E7=BC=96=E7=A0=81=E8=AE=A1=E5=88=92?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加火山引擎(Doubao)和火山引擎编码计划作为新的AI提供商支持,包括: - 在多个文件中添加VOLCENGINE_API_KEY环境变量配置 - 添加volcengine和volcengine_coding提供商信息 - 更新模型目录添加火山引擎相关模型 --- crates/openfang-cli/src/launcher.rs | 1 + crates/openfang-cli/src/main.rs | 13 ++ .../src/tui/screens/init_wizard.rs | 16 ++ .../openfang-cli/src/tui/screens/welcome.rs | 1 + crates/openfang-cli/src/tui/screens/wizard.rs | 12 ++ crates/openfang-runtime/src/drivers/mod.rs | 5 +- crates/openfang-runtime/src/model_catalog.rs | 146 ++++++++++++++++++ 7 files changed, 193 insertions(+), 1 deletion(-) diff --git a/crates/openfang-cli/src/launcher.rs b/crates/openfang-cli/src/launcher.rs index 18a8f1236..fd8c29d5a 100644 --- a/crates/openfang-cli/src/launcher.rs +++ b/crates/openfang-cli/src/launcher.rs @@ -20,6 +20,7 @@ const PROVIDER_ENV_VARS: &[(&str, &str)] = &[ ("ANTHROPIC_API_KEY", "Anthropic"), ("OPENAI_API_KEY", "OpenAI"), ("DEEPSEEK_API_KEY", "DeepSeek"), + ("VOLCENGINE_API_KEY", "Volcano Engine"), ("GEMINI_API_KEY", "Gemini"), ("GOOGLE_API_KEY", "Gemini"), ("GROQ_API_KEY", "Groq"), diff --git a/crates/openfang-cli/src/main.rs b/crates/openfang-cli/src/main.rs index 104286a8e..4c4971611 100644 --- a/crates/openfang-cli/src/main.rs +++ b/crates/openfang-cli/src/main.rs @@ -1420,6 +1420,18 @@ fn provider_list() -> Vec<(&'static str, &'static str, &'static str, &'static st ("groq", "GROQ_API_KEY", "llama-3.3-70b-versatile", "Groq"), ("gemini", "GEMINI_API_KEY", "gemini-2.5-flash", "Gemini"), ("deepseek", "DEEPSEEK_API_KEY", "deepseek-chat", "DeepSeek"), + ( + "volcengine_coding", + "VOLCENGINE_API_KEY", + "ark-code-latest", + "Volcano Engine Coding Plan", + ), + ( + "volcengine", + "VOLCENGINE_API_KEY", + "doubao-seed-1-6-251015", + "Volcano Engine (Doubao)", + ), ( "anthropic", "ANTHROPIC_API_KEY", @@ -4541,6 +4553,7 @@ fn provider_to_env_var(provider: &str) -> String { "perplexity" => "PERPLEXITY_API_KEY".to_string(), "cohere" => "COHERE_API_KEY".to_string(), "xai" => "XAI_API_KEY".to_string(), + "volcengine" | "doubao" | "volcengine_coding" => "VOLCENGINE_API_KEY".to_string(), "brave" => "BRAVE_API_KEY".to_string(), "tavily" => "TAVILY_API_KEY".to_string(), other => format!("{}_API_KEY", other.to_uppercase()), diff --git a/crates/openfang-cli/src/tui/screens/init_wizard.rs b/crates/openfang-cli/src/tui/screens/init_wizard.rs index 279b1e6cf..5ec244d33 100644 --- a/crates/openfang-cli/src/tui/screens/init_wizard.rs +++ b/crates/openfang-cli/src/tui/screens/init_wizard.rs @@ -148,6 +148,22 @@ const PROVIDERS: &[ProviderInfo] = &[ needs_key: true, hint: "", }, + ProviderInfo { + name: "volcengine_coding", + display: "Volcano Engine Coding Plan", + env_var: "VOLCENGINE_API_KEY", + default_model: "ark-code-latest", + needs_key: true, + hint: "", + }, + ProviderInfo { + name: "volcengine", + display: "Volcano Engine (Doubao)", + env_var: "VOLCENGINE_API_KEY", + default_model: "doubao-seed-1-6-251015", + needs_key: true, + hint: "", + }, ProviderInfo { name: "huggingface", display: "Hugging Face", diff --git a/crates/openfang-cli/src/tui/screens/welcome.rs b/crates/openfang-cli/src/tui/screens/welcome.rs index 768a51ca4..c0c1f9fc5 100644 --- a/crates/openfang-cli/src/tui/screens/welcome.rs +++ b/crates/openfang-cli/src/tui/screens/welcome.rs @@ -32,6 +32,7 @@ const PROVIDER_ENV_VARS: &[(&str, &str)] = &[ ("ANTHROPIC_API_KEY", "Anthropic"), ("OPENAI_API_KEY", "OpenAI"), ("DEEPSEEK_API_KEY", "DeepSeek"), + ("VOLCENGINE_API_KEY", "Volcano Engine"), ("GEMINI_API_KEY", "Gemini"), ("GOOGLE_API_KEY", "Gemini"), ("GROQ_API_KEY", "Groq"), diff --git a/crates/openfang-cli/src/tui/screens/wizard.rs b/crates/openfang-cli/src/tui/screens/wizard.rs index f15b8f8c8..9fd5570f1 100644 --- a/crates/openfang-cli/src/tui/screens/wizard.rs +++ b/crates/openfang-cli/src/tui/screens/wizard.rs @@ -85,6 +85,18 @@ const PROVIDERS: &[ProviderInfo] = &[ default_model: "qwen-plus", needs_key: true, }, + ProviderInfo { + name: "volcengine_coding", + env_var: "VOLCENGINE_API_KEY", + default_model: "ark-code-latest", + needs_key: true, + }, + ProviderInfo { + name: "volcengine", + env_var: "VOLCENGINE_API_KEY", + default_model: "doubao-seed-1-6-251015", + 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 2df8923d4..0dbda1760 100644 --- a/crates/openfang-runtime/src/drivers/mod.rs +++ b/crates/openfang-runtime/src/drivers/mod.rs @@ -507,6 +507,7 @@ pub fn detect_available_provider() -> Option<(&'static str, &'static str, &'stat ("gemini", "gemini-2.5-flash", "GEMINI_API_KEY"), ("groq", "llama-3.3-70b-versatile", "GROQ_API_KEY"), ("deepseek", "deepseek-chat", "DEEPSEEK_API_KEY"), + ("volcengine_coding", "ark-code-latest", "VOLCENGINE_API_KEY"), ( "openrouter", "openrouter/google/gemini-2.5-flash", @@ -584,6 +585,7 @@ pub fn known_providers() -> &'static [&'static str] { "kimi_coding", "qianfan", "volcengine", + "volcengine_coding", "chutes", "venice", "nvidia", @@ -689,13 +691,14 @@ mod tests { assert!(providers.contains(&"kimi_coding")); assert!(providers.contains(&"qianfan")); assert!(providers.contains(&"volcengine")); + assert!(providers.contains(&"volcengine_coding")); assert!(providers.contains(&"chutes")); assert!(providers.contains(&"nvidia")); assert!(providers.contains(&"codex")); assert!(providers.contains(&"claude-code")); assert!(providers.contains(&"qwen-code")); assert!(providers.contains(&"azure")); - assert_eq!(providers.len(), 37); + assert_eq!(providers.len(), 38); } #[test] diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index 62b81c74e..55adae6be 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -3463,6 +3463,152 @@ fn builtin_models() -> Vec { aliases: vec![], }, // ══════════════════════════════════════════════════════════════ + // Volcano Engine Coding Plan (5) + // ══════════════════════════════════════════════════════════════ + ModelCatalogEntry { + id: "ark-code-latest".into(), + display_name: "Ark Code (Latest)".into(), + provider: "volcengine_coding".into(), + tier: ModelTier::Smart, + context_window: 131_072, + max_output_tokens: 8_192, + input_cost_per_m: 0.0, + output_cost_per_m: 0.0, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec!["ark-code".into()], + }, + ModelCatalogEntry { + id: "doubao-seed-2.0-code".into(), + display_name: "Doubao Seed 2.0 Code".into(), + provider: "volcengine_coding".into(), + tier: ModelTier::Smart, + context_window: 131_072, + max_output_tokens: 8_192, + input_cost_per_m: 0.0, + output_cost_per_m: 0.0, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "glm-4.7".into(), + display_name: "GLM 4.7".into(), + provider: "volcengine_coding".into(), + tier: ModelTier::Smart, + context_window: 131_072, + max_output_tokens: 8_192, + input_cost_per_m: 0.0, + output_cost_per_m: 0.0, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "deepseek-v3.2".into(), + display_name: "DeepSeek V3.2".into(), + provider: "volcengine_coding".into(), + tier: ModelTier::Smart, + context_window: 131_072, + max_output_tokens: 8_192, + input_cost_per_m: 0.0, + output_cost_per_m: 0.0, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "kimi-k2.5".into(), + display_name: "Kimi K2.5".into(), + provider: "volcengine_coding".into(), + tier: ModelTier::Smart, + context_window: 131_072, + max_output_tokens: 8_192, + input_cost_per_m: 0.0, + output_cost_per_m: 0.0, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + // ══════════════════════════════════════════════════════════════ + // Volcano Engine Coding Plan (5) + // ══════════════════════════════════════════════════════════════ + ModelCatalogEntry { + id: "ark-code-latest".into(), + display_name: "Ark Code (Latest)".into(), + provider: "volcengine_coding".into(), + tier: ModelTier::Smart, + context_window: 131_072, + max_output_tokens: 8_192, + input_cost_per_m: 0.0, + output_cost_per_m: 0.0, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec!["ark-code".into()], + }, + ModelCatalogEntry { + id: "doubao-seed-2.0-code".into(), + display_name: "Doubao Seed 2.0 Code".into(), + provider: "volcengine_coding".into(), + tier: ModelTier::Smart, + context_window: 131_072, + max_output_tokens: 8_192, + input_cost_per_m: 0.0, + output_cost_per_m: 0.0, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "glm-4.7".into(), + display_name: "GLM 4.7".into(), + provider: "volcengine_coding".into(), + tier: ModelTier::Smart, + context_window: 131_072, + max_output_tokens: 8_192, + input_cost_per_m: 0.0, + output_cost_per_m: 0.0, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "deepseek-v3.2".into(), + display_name: "DeepSeek V3.2".into(), + provider: "volcengine_coding".into(), + tier: ModelTier::Smart, + context_window: 131_072, + max_output_tokens: 8_192, + input_cost_per_m: 0.0, + output_cost_per_m: 0.0, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "kimi-k2.5".into(), + display_name: "Kimi K2.5".into(), + provider: "volcengine_coding".into(), + tier: ModelTier::Smart, + context_window: 131_072, + max_output_tokens: 8_192, + input_cost_per_m: 0.0, + output_cost_per_m: 0.0, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + // ══════════════════════════════════════════════════════════════ // Volcano Engine / Doubao (4) // ══════════════════════════════════════════════════════════════ ModelCatalogEntry { From 0ac7812fa8b0500da93502ba601e4f098371cad2 Mon Sep 17 00:00:00 2001 From: gaoyiman Date: Fri, 20 Mar 2026 17:30:19 +0800 Subject: [PATCH 02/15] =?UTF-8?q?feat(volcengine):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=AF=B9=E7=81=AB=E5=B1=B1=E5=BC=95=E6=93=8E=E5=92=8C=E8=B1=86?= =?UTF-8?q?=E5=8C=85API=E7=9A=84=E6=94=AF=E6=8C=81=E5=B9=B6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=A8=A1=E5=9E=8B=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新模型目录中的火山引擎编码计划模型,增加新模型并调整现有模型的参数 --- crates/openfang-cli/src/main.rs | 9 +++ crates/openfang-runtime/src/model_catalog.rs | 74 ++++++++------------ 2 files changed, 38 insertions(+), 45 deletions(-) diff --git a/crates/openfang-cli/src/main.rs b/crates/openfang-cli/src/main.rs index 4c4971611..42a352c11 100644 --- a/crates/openfang-cli/src/main.rs +++ b/crates/openfang-cli/src/main.rs @@ -4605,6 +4605,15 @@ pub(crate) fn test_api_key(provider: &str, env_var: &str) -> bool { .get("https://openrouter.ai/api/v1/models") .bearer_auth(&key) .send(), + "volcengine" | "doubao" => { + let base = openfang_types::model_catalog::VOLCENGINE_BASE_URL.trim_end_matches('/'); + client.get(format!("{base}/models")).bearer_auth(&key).send() + } + "volcengine_coding" => { + let base = openfang_types::model_catalog::VOLCENGINE_CODING_BASE_URL + .trim_end_matches('/'); + client.get(format!("{base}/models")).bearer_auth(&key).send() + } _ => return true, // unknown provider — skip test }; diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index 55adae6be..7bf41f392 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -3463,11 +3463,11 @@ fn builtin_models() -> Vec { aliases: vec![], }, // ══════════════════════════════════════════════════════════════ - // Volcano Engine Coding Plan (5) + // Volcano Engine Coding Plan (9) // ══════════════════════════════════════════════════════════════ ModelCatalogEntry { id: "ark-code-latest".into(), - display_name: "Ark Code (Latest)".into(), + display_name: "ark-code-latest".into(), provider: "volcengine_coding".into(), tier: ModelTier::Smart, context_window: 131_072, @@ -3484,8 +3484,8 @@ fn builtin_models() -> Vec { display_name: "Doubao Seed 2.0 Code".into(), provider: "volcengine_coding".into(), tier: ModelTier::Smart, - context_window: 131_072, - max_output_tokens: 8_192, + context_window: 262_144, + max_output_tokens: 16_384, input_cost_per_m: 0.0, output_cost_per_m: 0.0, supports_tools: true, @@ -3494,12 +3494,12 @@ fn builtin_models() -> Vec { aliases: vec![], }, ModelCatalogEntry { - id: "glm-4.7".into(), - display_name: "GLM 4.7".into(), + id: "doubao-seed-2.0-pro".into(), + display_name: "Doubao Seed 2.0 Pro".into(), provider: "volcengine_coding".into(), - tier: ModelTier::Smart, - context_window: 131_072, - max_output_tokens: 8_192, + tier: ModelTier::Frontier, + context_window: 262_144, + max_output_tokens: 16_384, input_cost_per_m: 0.0, output_cost_per_m: 0.0, supports_tools: true, @@ -3508,12 +3508,12 @@ fn builtin_models() -> Vec { aliases: vec![], }, ModelCatalogEntry { - id: "deepseek-v3.2".into(), - display_name: "DeepSeek V3.2".into(), + id: "doubao-seed-2.0-lite".into(), + display_name: "Doubao Seed 2.0 Lite".into(), provider: "volcengine_coding".into(), - tier: ModelTier::Smart, - context_window: 131_072, - max_output_tokens: 8_192, + tier: ModelTier::Fast, + context_window: 262_144, + max_output_tokens: 16_384, input_cost_per_m: 0.0, output_cost_per_m: 0.0, supports_tools: true, @@ -3522,12 +3522,12 @@ fn builtin_models() -> Vec { aliases: vec![], }, ModelCatalogEntry { - id: "kimi-k2.5".into(), - display_name: "Kimi K2.5".into(), + id: "doubao-seed-code".into(), + display_name: "Doubao Seed Code".into(), provider: "volcengine_coding".into(), tier: ModelTier::Smart, - context_window: 131_072, - max_output_tokens: 8_192, + context_window: 262_144, + max_output_tokens: 16_384, input_cost_per_m: 0.0, output_cost_per_m: 0.0, supports_tools: true, @@ -3535,30 +3535,13 @@ fn builtin_models() -> Vec { supports_streaming: true, aliases: vec![], }, - // ══════════════════════════════════════════════════════════════ - // Volcano Engine Coding Plan (5) - // ══════════════════════════════════════════════════════════════ - ModelCatalogEntry { - id: "ark-code-latest".into(), - display_name: "Ark Code (Latest)".into(), - provider: "volcengine_coding".into(), - tier: ModelTier::Smart, - context_window: 131_072, - max_output_tokens: 8_192, - input_cost_per_m: 0.0, - output_cost_per_m: 0.0, - supports_tools: true, - supports_vision: false, - supports_streaming: true, - aliases: vec!["ark-code".into()], - }, ModelCatalogEntry { - id: "doubao-seed-2.0-code".into(), - display_name: "Doubao Seed 2.0 Code".into(), + id: "minimax-m2.5".into(), + display_name: "MiniMax M2.5".into(), provider: "volcengine_coding".into(), tier: ModelTier::Smart, - context_window: 131_072, - max_output_tokens: 8_192, + context_window: 200_000, + max_output_tokens: 16_384, input_cost_per_m: 0.0, output_cost_per_m: 0.0, supports_tools: true, @@ -3570,9 +3553,9 @@ fn builtin_models() -> Vec { id: "glm-4.7".into(), display_name: "GLM 4.7".into(), provider: "volcengine_coding".into(), - tier: ModelTier::Smart, - context_window: 131_072, - max_output_tokens: 8_192, + tier: ModelTier::Balanced, + context_window: 200_000, + max_output_tokens: 16_384, input_cost_per_m: 0.0, output_cost_per_m: 0.0, supports_tools: true, @@ -3586,7 +3569,7 @@ fn builtin_models() -> Vec { provider: "volcengine_coding".into(), tier: ModelTier::Smart, context_window: 131_072, - max_output_tokens: 8_192, + max_output_tokens: 16_384, input_cost_per_m: 0.0, output_cost_per_m: 0.0, supports_tools: true, @@ -3599,8 +3582,8 @@ fn builtin_models() -> Vec { display_name: "Kimi K2.5".into(), provider: "volcengine_coding".into(), tier: ModelTier::Smart, - context_window: 131_072, - max_output_tokens: 8_192, + context_window: 262_144, + max_output_tokens: 16_384, input_cost_per_m: 0.0, output_cost_per_m: 0.0, supports_tools: true, @@ -3608,6 +3591,7 @@ fn builtin_models() -> Vec { supports_streaming: true, aliases: vec![], }, + // ══════════════════════════════════════════════════════════════ // Volcano Engine / Doubao (4) // ══════════════════════════════════════════════════════════════ From e0044efc5eee8da9f6662aaad9e898ea19698504 Mon Sep 17 00:00:00 2001 From: gaoyiman Date: Fri, 20 Mar 2026 18:15:49 +0800 Subject: [PATCH 03/15] =?UTF-8?q?feat(tui):=20=E6=B7=BB=E5=8A=A0=E5=B9=B6?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=81=AB=E5=B1=B1=E5=BC=95=E6=93=8E=E6=8F=90?= =?UTF-8?q?=E4=BE=9B=E5=95=86=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将火山引擎相关配置移至列表更靠前的位置,并确保其配置完整 --- .../src/tui/screens/init_wizard.rs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/openfang-cli/src/tui/screens/init_wizard.rs b/crates/openfang-cli/src/tui/screens/init_wizard.rs index 5ec244d33..1eeaa5d2f 100644 --- a/crates/openfang-cli/src/tui/screens/init_wizard.rs +++ b/crates/openfang-cli/src/tui/screens/init_wizard.rs @@ -68,6 +68,22 @@ const PROVIDERS: &[ProviderInfo] = &[ needs_key: true, hint: "", }, + ProviderInfo { + name: "volcengine_coding", + display: "Volcano Engine Coding Plan", + env_var: "VOLCENGINE_API_KEY", + default_model: "ark-code-latest", + needs_key: true, + hint: "", + }, + ProviderInfo { + name: "volcengine", + display: "Volcano Engine (Doubao)", + env_var: "VOLCENGINE_API_KEY", + default_model: "doubao-seed-1-6-251015", + needs_key: true, + hint: "", + }, ProviderInfo { name: "openrouter", display: "OpenRouter", @@ -148,22 +164,6 @@ const PROVIDERS: &[ProviderInfo] = &[ needs_key: true, hint: "", }, - ProviderInfo { - name: "volcengine_coding", - display: "Volcano Engine Coding Plan", - env_var: "VOLCENGINE_API_KEY", - default_model: "ark-code-latest", - needs_key: true, - hint: "", - }, - ProviderInfo { - name: "volcengine", - display: "Volcano Engine (Doubao)", - env_var: "VOLCENGINE_API_KEY", - default_model: "doubao-seed-1-6-251015", - needs_key: true, - hint: "", - }, ProviderInfo { name: "huggingface", display: "Hugging Face", From 97c2788e2988c7660c171aaa563cb8d8673be66f Mon Sep 17 00:00:00 2001 From: gaoyiman Date: Mon, 23 Mar 2026 19:53:06 +0800 Subject: [PATCH 04/15] refactor(volcengine): standardize display name for Volcano Engine provider Updated the display name for the Volcano Engine provider across multiple files to remove the "(Doubao)" suffix for consistency. --- crates/openfang-cli/src/main.rs | 2 +- crates/openfang-cli/src/tui/screens/init_wizard.rs | 2 +- crates/openfang-runtime/src/model_catalog.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/openfang-cli/src/main.rs b/crates/openfang-cli/src/main.rs index 42a352c11..2c984dd5c 100644 --- a/crates/openfang-cli/src/main.rs +++ b/crates/openfang-cli/src/main.rs @@ -1430,7 +1430,7 @@ fn provider_list() -> Vec<(&'static str, &'static str, &'static str, &'static st "volcengine", "VOLCENGINE_API_KEY", "doubao-seed-1-6-251015", - "Volcano Engine (Doubao)", + "Volcano Engine", ), ( "anthropic", diff --git a/crates/openfang-cli/src/tui/screens/init_wizard.rs b/crates/openfang-cli/src/tui/screens/init_wizard.rs index 1eeaa5d2f..e5ca2bcab 100644 --- a/crates/openfang-cli/src/tui/screens/init_wizard.rs +++ b/crates/openfang-cli/src/tui/screens/init_wizard.rs @@ -78,7 +78,7 @@ const PROVIDERS: &[ProviderInfo] = &[ }, ProviderInfo { name: "volcengine", - display: "Volcano Engine (Doubao)", + display: "Volcano Engine", env_var: "VOLCENGINE_API_KEY", default_model: "doubao-seed-1-6-251015", needs_key: true, diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index 7bf41f392..dc777a742 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -850,7 +850,7 @@ fn builtin_providers() -> Vec { // ── Volcano Engine (Doubao) ────────────────────────────────── ProviderInfo { id: "volcengine".into(), - display_name: "Volcano Engine (Doubao)".into(), + display_name: "Volcano Engine".into(), api_key_env: "VOLCENGINE_API_KEY".into(), base_url: VOLCENGINE_BASE_URL.into(), key_required: true, From 04ede0371792db15fcc514c55bf8b3aee331f4d9 Mon Sep 17 00:00:00 2001 From: Dan Castrillo Date: Sat, 28 Mar 2026 16:39:48 +0100 Subject: [PATCH 05/15] fix(volcengine): resolve duplicate model ID and env var guard issues - Rename doubao-seed-code under volcengine_coding provider to doubao-seed-code-ark to eliminate ambiguous model ID lookup - Add comment marking third-party Ark marketplace models in catalog - Guard empty env vars in launcher.rs and welcome.rs detect_provider() using .ok().filter(|v| !v.is_empty()).is_some() instead of .is_ok() - Add volcengine and volcengine_coding to unknown-provider error message Co-Authored-By: Claude Sonnet 4.6 --- crates/openfang-cli/src/launcher.rs | 2 +- crates/openfang-cli/src/tui/screens/welcome.rs | 2 +- crates/openfang-runtime/src/drivers/mod.rs | 3 ++- crates/openfang-runtime/src/model_catalog.rs | 5 +++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/openfang-cli/src/launcher.rs b/crates/openfang-cli/src/launcher.rs index fd8c29d5a..178d94d07 100644 --- a/crates/openfang-cli/src/launcher.rs +++ b/crates/openfang-cli/src/launcher.rs @@ -32,7 +32,7 @@ const PROVIDER_ENV_VARS: &[(&str, &str)] = &[ fn detect_provider() -> Option<(&'static str, &'static str)> { for &(var, name) in PROVIDER_ENV_VARS { - if std::env::var(var).is_ok() { + if std::env::var(var).ok().filter(|v| !v.is_empty()).is_some() { return Some((name, var)); } } diff --git a/crates/openfang-cli/src/tui/screens/welcome.rs b/crates/openfang-cli/src/tui/screens/welcome.rs index c0c1f9fc5..96f94d115 100644 --- a/crates/openfang-cli/src/tui/screens/welcome.rs +++ b/crates/openfang-cli/src/tui/screens/welcome.rs @@ -48,7 +48,7 @@ const PROVIDER_ENV_VARS: &[(&str, &str)] = &[ /// Returns (provider_name, env_var_name) for the first detected key, or None. fn detect_provider() -> Option<(&'static str, &'static str)> { for &(var, name) in PROVIDER_ENV_VARS { - if std::env::var(var).is_ok() { + if std::env::var(var).ok().filter(|v| !v.is_empty()).is_some() { return Some((name, var)); } } diff --git a/crates/openfang-runtime/src/drivers/mod.rs b/crates/openfang-runtime/src/drivers/mod.rs index 0dbda1760..620f82716 100644 --- a/crates/openfang-runtime/src/drivers/mod.rs +++ b/crates/openfang-runtime/src/drivers/mod.rs @@ -489,7 +489,8 @@ pub fn create_driver(config: &DriverConfig) -> Result, LlmErr "Unknown provider '{}'. Supported: anthropic, gemini, openai, azure, groq, openrouter, \ deepseek, together, mistral, fireworks, ollama, vllm, lmstudio, perplexity, \ cohere, ai21, cerebras, sambanova, huggingface, xai, replicate, github-copilot, \ - chutes, venice, nvidia, codex, claude-code. Or set base_url for a custom OpenAI-compatible endpoint.", + chutes, venice, nvidia, codex, claude-code, volcengine, volcengine_coding. \ + Or set base_url for a custom OpenAI-compatible endpoint.", provider ), }) diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index dc777a742..b0f3c18f3 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -3522,8 +3522,8 @@ fn builtin_models() -> Vec { aliases: vec![], }, ModelCatalogEntry { - id: "doubao-seed-code".into(), - display_name: "Doubao Seed Code".into(), + id: "doubao-seed-code-ark".into(), + display_name: "Doubao Seed Code (Ark)".into(), provider: "volcengine_coding".into(), tier: ModelTier::Smart, context_window: 262_144, @@ -3535,6 +3535,7 @@ fn builtin_models() -> Vec { supports_streaming: true, aliases: vec![], }, + // Third-party models available via Ark marketplace ModelCatalogEntry { id: "minimax-m2.5".into(), display_name: "MiniMax M2.5".into(), From ac6e9eef1263931e9a8e186efb3979ef20d98053 Mon Sep 17 00:00:00 2001 From: Dan Castrillo Date: Sat, 28 Mar 2026 16:47:12 +0100 Subject: [PATCH 06/15] fix(volcengine): resolve duplicate model IDs, clarify separators, add provider tests - Prefix ark-marketplace models with "ark/" to avoid ID collision with native minimax, zhipu, and moonshot provider entries (FIX 1) - Add comment explaining dot vs hyphen separator difference between volcengine_coding (coding/v3) and volcengine (v3) endpoints (FIX 2) - Include "doubao" alias in unknown-provider error message (FIX 3) - Add "volcengine" probe to detect_available_provider() (FIX 4) - Add test_provider_defaults_volcengine, _doubao_alias, and _volcengine_coding tests (FIX 5) Co-Authored-By: Claude Sonnet 4.6 --- crates/openfang-runtime/src/drivers/mod.rs | 30 +++++++++++++++++++- crates/openfang-runtime/src/model_catalog.rs | 24 +++++++++++----- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/crates/openfang-runtime/src/drivers/mod.rs b/crates/openfang-runtime/src/drivers/mod.rs index 620f82716..9d89e6be5 100644 --- a/crates/openfang-runtime/src/drivers/mod.rs +++ b/crates/openfang-runtime/src/drivers/mod.rs @@ -489,7 +489,7 @@ pub fn create_driver(config: &DriverConfig) -> Result, LlmErr "Unknown provider '{}'. Supported: anthropic, gemini, openai, azure, groq, openrouter, \ deepseek, together, mistral, fireworks, ollama, vllm, lmstudio, perplexity, \ cohere, ai21, cerebras, sambanova, huggingface, xai, replicate, github-copilot, \ - chutes, venice, nvidia, codex, claude-code, volcengine, volcengine_coding. \ + chutes, venice, nvidia, codex, claude-code, volcengine (doubao), volcengine_coding. \ Or set base_url for a custom OpenAI-compatible endpoint.", provider ), @@ -509,6 +509,7 @@ pub fn detect_available_provider() -> Option<(&'static str, &'static str, &'stat ("groq", "llama-3.3-70b-versatile", "GROQ_API_KEY"), ("deepseek", "deepseek-chat", "DEEPSEEK_API_KEY"), ("volcengine_coding", "ark-code-latest", "VOLCENGINE_API_KEY"), + ("volcengine", "doubao-seed-1-6-251015", "VOLCENGINE_API_KEY"), ( "openrouter", "openrouter/google/gemini-2.5-flash", @@ -892,4 +893,31 @@ mod tests { "azure-openai alias should create driver successfully" ); } + + #[test] + fn test_provider_defaults_volcengine() { + let d = provider_defaults("volcengine").unwrap(); + assert_eq!(d.base_url, "https://ark.cn-beijing.volces.com/api/v3"); + assert_eq!(d.api_key_env, "VOLCENGINE_API_KEY"); + assert!(d.key_required); + } + + #[test] + fn test_provider_defaults_volcengine_doubao_alias() { + let d = provider_defaults("doubao").unwrap(); + assert_eq!(d.base_url, "https://ark.cn-beijing.volces.com/api/v3"); + assert_eq!(d.api_key_env, "VOLCENGINE_API_KEY"); + assert!(d.key_required); + } + + #[test] + fn test_provider_defaults_volcengine_coding() { + let d = provider_defaults("volcengine_coding").unwrap(); + assert_eq!( + d.base_url, + "https://ark.cn-beijing.volces.com/api/coding/v3" + ); + assert_eq!(d.api_key_env, "VOLCENGINE_API_KEY"); + assert!(d.key_required); + } } diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index b0f3c18f3..ff0c5b10c 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -3535,10 +3535,12 @@ fn builtin_models() -> Vec { supports_streaming: true, aliases: vec![], }, - // Third-party models available via Ark marketplace + // Third-party models available via Ark marketplace. + // IDs are prefixed with "ark/" to avoid collision with the same models + // registered under their native providers (minimax, zhipu, moonshot). ModelCatalogEntry { - id: "minimax-m2.5".into(), - display_name: "MiniMax M2.5".into(), + id: "ark/minimax-m2.5".into(), + display_name: "MiniMax M2.5 (via Ark)".into(), provider: "volcengine_coding".into(), tier: ModelTier::Smart, context_window: 200_000, @@ -3551,8 +3553,8 @@ fn builtin_models() -> Vec { aliases: vec![], }, ModelCatalogEntry { - id: "glm-4.7".into(), - display_name: "GLM 4.7".into(), + id: "ark/glm-4.7".into(), + display_name: "GLM 4.7 (via Ark)".into(), provider: "volcengine_coding".into(), tier: ModelTier::Balanced, context_window: 200_000, @@ -3579,8 +3581,8 @@ fn builtin_models() -> Vec { aliases: vec![], }, ModelCatalogEntry { - id: "kimi-k2.5".into(), - display_name: "Kimi K2.5".into(), + id: "ark/kimi-k2.5".into(), + display_name: "Kimi K2.5 (via Ark)".into(), provider: "volcengine_coding".into(), tier: ModelTier::Smart, context_window: 262_144, @@ -3596,6 +3598,14 @@ fn builtin_models() -> Vec { // ══════════════════════════════════════════════════════════════ // Volcano Engine / Doubao (4) // ══════════════════════════════════════════════════════════════ + // + // NOTE on separators: the volcengine provider uses hyphen-only IDs + // (e.g. "doubao-seed-2-0-lite") because the Ark /api/v3 endpoint uses + // endpoint-access-point names that don't contain dots. The + // volcengine_coding entries above use dot notation + // (e.g. "doubao-seed-2.0-lite") which is the model version string used + // by the /api/coding/v3 endpoint. These are different endpoint paths + // and the IDs must not be unified. ModelCatalogEntry { id: "doubao-seed-1-6-251015".into(), display_name: "Doubao Seed 1.6 Pro".into(), From f02bb11e7b2230cb6e71e25a69ba2a0e05c244dc Mon Sep 17 00:00:00 2001 From: Dan Castrillo Date: Sat, 28 Mar 2026 16:56:24 +0100 Subject: [PATCH 07/15] fix(volcengine): display names, ordering, and documentation comments - Add display field to ProviderInfo in wizard.rs; all providers now show human-readable names (e.g. "Volcano Engine" / "Volcano Engine (Coding)") instead of raw identifiers - Swap volcengine/volcengine_coding order to match init_wizard.rs and catalog - Document "doubao" dual-alias intent in runtime model_catalog.rs - Document cn-beijing regional hardcoding with base_url override note - Document zero pricing for Ark-routed third-party models Co-Authored-By: Claude Sonnet 4.6 --- crates/openfang-cli/src/tui/screens/wizard.rs | 35 ++++++++++++++++--- crates/openfang-runtime/src/model_catalog.rs | 7 ++-- crates/openfang-types/src/model_catalog.rs | 2 ++ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/crates/openfang-cli/src/tui/screens/wizard.rs b/crates/openfang-cli/src/tui/screens/wizard.rs index 9fd5570f1..0df9cd7ce 100644 --- a/crates/openfang-cli/src/tui/screens/wizard.rs +++ b/crates/openfang-cli/src/tui/screens/wizard.rs @@ -13,6 +13,7 @@ use crate::tui::theme; /// Provider metadata for the setup wizard. struct ProviderInfo { name: &'static str, + display: &'static str, env_var: &'static str, default_model: &'static str, needs_key: bool, @@ -21,120 +22,140 @@ struct ProviderInfo { const PROVIDERS: &[ProviderInfo] = &[ ProviderInfo { name: "groq", + display: "Groq", env_var: "GROQ_API_KEY", default_model: "llama-3.3-70b-versatile", needs_key: true, }, ProviderInfo { name: "anthropic", + display: "Anthropic", env_var: "ANTHROPIC_API_KEY", default_model: "claude-sonnet-4-20250514", needs_key: true, }, ProviderInfo { name: "openai", + display: "OpenAI", env_var: "OPENAI_API_KEY", default_model: "gpt-4o", needs_key: true, }, ProviderInfo { name: "openrouter", + display: "OpenRouter", env_var: "OPENROUTER_API_KEY", default_model: "google/gemini-2.5-flash", needs_key: true, }, ProviderInfo { name: "deepseek", + display: "DeepSeek", env_var: "DEEPSEEK_API_KEY", default_model: "deepseek-chat", needs_key: true, }, ProviderInfo { name: "together", + display: "Together AI", env_var: "TOGETHER_API_KEY", default_model: "meta-llama/Llama-3.3-70B-Instruct-Turbo", needs_key: true, }, ProviderInfo { name: "mistral", + display: "Mistral", env_var: "MISTRAL_API_KEY", default_model: "mistral-large-latest", needs_key: true, }, ProviderInfo { name: "fireworks", + display: "Fireworks AI", env_var: "FIREWORKS_API_KEY", default_model: "accounts/fireworks/models/llama-v3p3-70b-instruct", needs_key: true, }, ProviderInfo { name: "gemini", + display: "Gemini", env_var: "GEMINI_API_KEY", default_model: "gemini-2.5-flash", needs_key: true, }, ProviderInfo { name: "xai", + display: "xAI", env_var: "XAI_API_KEY", default_model: "grok-4-0709", needs_key: true, }, ProviderInfo { name: "qwen", + display: "Qwen", env_var: "DASHSCOPE_API_KEY", default_model: "qwen-plus", needs_key: true, }, ProviderInfo { - name: "volcengine_coding", + name: "volcengine", + display: "Volcano Engine", env_var: "VOLCENGINE_API_KEY", - default_model: "ark-code-latest", + default_model: "doubao-seed-1-6-251015", needs_key: true, }, ProviderInfo { - name: "volcengine", + name: "volcengine_coding", + display: "Volcano Engine (Coding)", env_var: "VOLCENGINE_API_KEY", - default_model: "doubao-seed-1-6-251015", + default_model: "ark-code-latest", needs_key: true, }, ProviderInfo { name: "perplexity", + display: "Perplexity", env_var: "PERPLEXITY_API_KEY", default_model: "sonar-pro", needs_key: true, }, ProviderInfo { name: "cohere", + display: "Cohere", env_var: "CO_API_KEY", default_model: "command-a", needs_key: true, }, ProviderInfo { name: "cerebras", + display: "Cerebras", env_var: "CEREBRAS_API_KEY", default_model: "llama-3.3-70b", needs_key: true, }, ProviderInfo { name: "sambanova", + display: "SambaNova", env_var: "SAMBANOVA_API_KEY", default_model: "Meta-Llama-3.3-70B-Instruct", needs_key: true, }, ProviderInfo { name: "moonshot", + display: "Moonshot", env_var: "MOONSHOT_API_KEY", default_model: "moonshot-v1-128k", needs_key: true, }, ProviderInfo { name: "zhipu", + display: "Zhipu AI", env_var: "ZHIPU_API_KEY", default_model: "glm-4-plus", needs_key: true, }, ProviderInfo { name: "zhipu_coding", + display: "Zhipu AI (Coding)", env_var: "ZHIPU_API_KEY", default_model: "codegeex-4", needs_key: true, @@ -147,24 +168,28 @@ const PROVIDERS: &[ProviderInfo] = &[ }, ProviderInfo { name: "claude-code", + display: "Claude Code", env_var: "", default_model: "claude-code/sonnet", needs_key: false, }, ProviderInfo { name: "ollama", + display: "Ollama", env_var: "OLLAMA_API_KEY", default_model: "llama3.2", needs_key: false, }, ProviderInfo { name: "vllm", + display: "vLLM", env_var: "VLLM_API_KEY", default_model: "local-model", needs_key: false, }, ProviderInfo { name: "lmstudio", + display: "LM Studio", env_var: "LMSTUDIO_API_KEY", default_model: "local-model", needs_key: false, @@ -556,7 +581,7 @@ fn draw_provider(f: &mut Frame, area: Rect, state: &mut WizardState) { format!("requires {}", p.env_var) }; ListItem::new(Line::from(vec![ - Span::raw(format!(" {:<14}", p.name)), + Span::raw(format!(" {:<24}", p.display)), Span::styled(hint, theme::dim_style()), ])) }) diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index ff0c5b10c..0d2b9e3e3 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -3467,7 +3467,7 @@ fn builtin_models() -> Vec { // ══════════════════════════════════════════════════════════════ ModelCatalogEntry { id: "ark-code-latest".into(), - display_name: "ark-code-latest".into(), + display_name: "Ark Code (Latest)".into(), provider: "volcengine_coding".into(), tier: ModelTier::Smart, context_window: 131_072, @@ -3509,7 +3509,7 @@ fn builtin_models() -> Vec { }, ModelCatalogEntry { id: "doubao-seed-2.0-lite".into(), - display_name: "Doubao Seed 2.0 Lite".into(), + display_name: "Doubao Seed 2.0 Lite (Ark Coding)".into(), provider: "volcengine_coding".into(), tier: ModelTier::Fast, context_window: 262_144, @@ -3538,6 +3538,7 @@ fn builtin_models() -> Vec { // Third-party models available via Ark marketplace. // IDs are prefixed with "ark/" to avoid collision with the same models // registered under their native providers (minimax, zhipu, moonshot). + // Pricing not publicly documented for Ark-routed third-party models; set to 0.0 ModelCatalogEntry { id: "ark/minimax-m2.5".into(), display_name: "MiniMax M2.5 (via Ark)".into(), @@ -3618,6 +3619,7 @@ fn builtin_models() -> Vec { supports_tools: true, supports_vision: false, supports_streaming: true, + // "doubao" also maps to the volcengine provider in provider_defaults() — intentional dual alias aliases: vec!["doubao".into(), "doubao-pro".into()], }, ModelCatalogEntry { @@ -3648,6 +3650,7 @@ fn builtin_models() -> Vec { supports_streaming: true, aliases: vec!["doubao-mini".into()], }, + // Standard plan (/api/v3) variant; see volcengine_coding for the coding-plan endpoint ModelCatalogEntry { id: "doubao-seed-code".into(), display_name: "Doubao Seed Code".into(), diff --git a/crates/openfang-types/src/model_catalog.rs b/crates/openfang-types/src/model_catalog.rs index a7d2627ca..6dc391968 100644 --- a/crates/openfang-types/src/model_catalog.rs +++ b/crates/openfang-types/src/model_catalog.rs @@ -47,6 +47,8 @@ pub const ZAI_CODING_BASE_URL: &str = "https://api.z.ai/api/coding/paas/v4"; pub const MOONSHOT_BASE_URL: &str = "https://api.moonshot.ai/v1"; pub const KIMI_CODING_BASE_URL: &str = "https://api.kimi.com/coding"; pub const QIANFAN_BASE_URL: &str = "https://qianfan.baidubce.com/v2"; +// Hardcoded to cn-beijing region. Operators in other regions can override via +// `base_url` in the provider config (e.g. `[provider_urls] volcengine = "https://ark..volces.com/api/v3"`). pub const VOLCENGINE_BASE_URL: &str = "https://ark.cn-beijing.volces.com/api/v3"; pub const VOLCENGINE_CODING_BASE_URL: &str = "https://ark.cn-beijing.volces.com/api/coding/v3"; From 7c5b811982ea74f8600f833a6073879077230da5 Mon Sep 17 00:00:00 2001 From: Dan Castrillo Date: Sat, 28 Mar 2026 17:04:25 +0100 Subject: [PATCH 08/15] fix(volcengine): correct provider probe order, display names, and alias comment - Swap volcengine/volcengine_coding probe order in detect_available_provider so the general volcengine provider is auto-selected when VOLCENGINE_API_KEY is set - Align volcengine_coding display name to "Volcano Engine (Coding Plan)" in both init_wizard.rs and wizard.rs - Add sync-warning comment on the doubao alias in model_catalog.rs Co-Authored-By: Claude Sonnet 4.6 --- .../src/tui/screens/init_wizard.rs | 18 +++++++++--------- crates/openfang-cli/src/tui/screens/wizard.rs | 4 ++-- crates/openfang-runtime/src/drivers/mod.rs | 7 +++++-- crates/openfang-runtime/src/model_catalog.rs | 1 + 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/crates/openfang-cli/src/tui/screens/init_wizard.rs b/crates/openfang-cli/src/tui/screens/init_wizard.rs index e5ca2bcab..72aa1dfd0 100644 --- a/crates/openfang-cli/src/tui/screens/init_wizard.rs +++ b/crates/openfang-cli/src/tui/screens/init_wizard.rs @@ -69,18 +69,18 @@ const PROVIDERS: &[ProviderInfo] = &[ hint: "", }, ProviderInfo { - name: "volcengine_coding", - display: "Volcano Engine Coding Plan", + name: "volcengine", + display: "Volcano Engine", env_var: "VOLCENGINE_API_KEY", - default_model: "ark-code-latest", + default_model: "doubao-seed-1-6-251015", needs_key: true, hint: "", }, ProviderInfo { - name: "volcengine", - display: "Volcano Engine", + name: "volcengine_coding", + display: "Volcano Engine (Coding Plan)", env_var: "VOLCENGINE_API_KEY", - default_model: "doubao-seed-1-6-251015", + default_model: "ark-code-latest", needs_key: true, hint: "", }, @@ -159,7 +159,7 @@ const PROVIDERS: &[ProviderInfo] = &[ ProviderInfo { name: "qwen", display: "Qwen (Alibaba)", - env_var: "QWEN_API_KEY", + env_var: "DASHSCOPE_API_KEY", default_model: "qwen-plus", needs_key: true, hint: "", @@ -167,7 +167,7 @@ const PROVIDERS: &[ProviderInfo] = &[ ProviderInfo { name: "huggingface", display: "Hugging Face", - env_var: "HUGGINGFACE_API_KEY", + env_var: "HF_API_KEY", default_model: "meta-llama/Llama-3.3-70B-Instruct", needs_key: true, hint: "", @@ -183,7 +183,7 @@ const PROVIDERS: &[ProviderInfo] = &[ ProviderInfo { name: "replicate", display: "Replicate", - env_var: "REPLICATE_API_KEY", + env_var: "REPLICATE_API_TOKEN", default_model: "meta/meta-llama-3-70b-instruct", needs_key: true, hint: "", diff --git a/crates/openfang-cli/src/tui/screens/wizard.rs b/crates/openfang-cli/src/tui/screens/wizard.rs index 0df9cd7ce..27b6ae782 100644 --- a/crates/openfang-cli/src/tui/screens/wizard.rs +++ b/crates/openfang-cli/src/tui/screens/wizard.rs @@ -106,7 +106,7 @@ const PROVIDERS: &[ProviderInfo] = &[ }, ProviderInfo { name: "volcengine_coding", - display: "Volcano Engine (Coding)", + display: "Volcano Engine (Coding Plan)", env_var: "VOLCENGINE_API_KEY", default_model: "ark-code-latest", needs_key: true, @@ -121,7 +121,7 @@ const PROVIDERS: &[ProviderInfo] = &[ ProviderInfo { name: "cohere", display: "Cohere", - env_var: "CO_API_KEY", + env_var: "COHERE_API_KEY", default_model: "command-a", needs_key: true, }, diff --git a/crates/openfang-runtime/src/drivers/mod.rs b/crates/openfang-runtime/src/drivers/mod.rs index 9d89e6be5..fd833242e 100644 --- a/crates/openfang-runtime/src/drivers/mod.rs +++ b/crates/openfang-runtime/src/drivers/mod.rs @@ -2,7 +2,8 @@ //! //! Contains drivers for Anthropic Claude, Google Gemini, OpenAI-compatible APIs, and more. //! Supports: Anthropic, Gemini, OpenAI, Groq, OpenRouter, DeepSeek, Together, -//! Mistral, Fireworks, Ollama, vLLM, Chutes.ai, and any OpenAI-compatible endpoint. +//! Mistral, Fireworks, Ollama, vLLM, Chutes.ai, volcengine (Doubao / Ark), +//! and any OpenAI-compatible endpoint. pub mod anthropic; pub mod claude_code; @@ -197,6 +198,7 @@ fn provider_defaults(provider: &str) -> Option { api_key_env: "QIANFAN_API_KEY", key_required: true, }), + // "doubao" is also a model alias in builtin_aliases; here it acts as a provider alias "volcengine" | "doubao" => Some(ProviderDefaults { base_url: VOLCENGINE_BASE_URL, api_key_env: "VOLCENGINE_API_KEY", @@ -260,6 +262,7 @@ fn provider_defaults(provider: &str) -> Option { /// - `xai` — xAI (Grok) /// - `replicate` — Replicate /// - `chutes` — Chutes.ai (serverless open-source model inference) +/// - `volcengine` — Volcano Engine (Doubao/Ark) /// - Any custom provider with `base_url` set uses OpenAI-compatible format pub fn create_driver(config: &DriverConfig) -> Result, LlmError> { let provider = config.provider.as_str(); @@ -508,8 +511,8 @@ pub fn detect_available_provider() -> Option<(&'static str, &'static str, &'stat ("gemini", "gemini-2.5-flash", "GEMINI_API_KEY"), ("groq", "llama-3.3-70b-versatile", "GROQ_API_KEY"), ("deepseek", "deepseek-chat", "DEEPSEEK_API_KEY"), - ("volcengine_coding", "ark-code-latest", "VOLCENGINE_API_KEY"), ("volcengine", "doubao-seed-1-6-251015", "VOLCENGINE_API_KEY"), + ("volcengine_coding", "ark-code-latest", "VOLCENGINE_API_KEY"), ( "openrouter", "openrouter/google/gemini-2.5-flash", diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index 0d2b9e3e3..b60fbf0f2 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -3620,6 +3620,7 @@ fn builtin_models() -> Vec { supports_vision: false, supports_streaming: true, // "doubao" also maps to the volcengine provider in provider_defaults() — intentional dual alias + // Also matched as a provider alias in provider_defaults() — keep in sync aliases: vec!["doubao".into(), "doubao-pro".into()], }, ModelCatalogEntry { From 9ebf9a9f029830455ecf833ed5dca14c9449b885 Mon Sep 17 00:00:00 2001 From: Dan Castrillo Date: Sat, 28 Mar 2026 17:23:07 +0100 Subject: [PATCH 09/15] fix(volcengine): remove volcengine_coding from auto-detect probe list and add wizard hints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit volcengine_coding shares the same VOLCENGINE_API_KEY as volcengine, making it permanently unreachable in auto-detection. Remove it from PROBE_ORDER so only volcengine is auto-detected; volcengine_coding remains selectable via the wizard. Also add descriptive hints for both volcengine entries in the init wizard ("ByteDance Ark platform" / "ByteDance Ark — coding models"). Co-Authored-By: Claude Sonnet 4.6 --- crates/openfang-cli/src/tui/screens/init_wizard.rs | 4 ++-- crates/openfang-runtime/src/drivers/mod.rs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/openfang-cli/src/tui/screens/init_wizard.rs b/crates/openfang-cli/src/tui/screens/init_wizard.rs index 72aa1dfd0..ba7f83945 100644 --- a/crates/openfang-cli/src/tui/screens/init_wizard.rs +++ b/crates/openfang-cli/src/tui/screens/init_wizard.rs @@ -74,7 +74,7 @@ const PROVIDERS: &[ProviderInfo] = &[ env_var: "VOLCENGINE_API_KEY", default_model: "doubao-seed-1-6-251015", needs_key: true, - hint: "", + hint: "ByteDance Ark platform", }, ProviderInfo { name: "volcengine_coding", @@ -82,7 +82,7 @@ const PROVIDERS: &[ProviderInfo] = &[ env_var: "VOLCENGINE_API_KEY", default_model: "ark-code-latest", needs_key: true, - hint: "", + hint: "ByteDance Ark — coding models", }, ProviderInfo { name: "openrouter", diff --git a/crates/openfang-runtime/src/drivers/mod.rs b/crates/openfang-runtime/src/drivers/mod.rs index fd833242e..ef0b52fd3 100644 --- a/crates/openfang-runtime/src/drivers/mod.rs +++ b/crates/openfang-runtime/src/drivers/mod.rs @@ -512,7 +512,6 @@ pub fn detect_available_provider() -> Option<(&'static str, &'static str, &'stat ("groq", "llama-3.3-70b-versatile", "GROQ_API_KEY"), ("deepseek", "deepseek-chat", "DEEPSEEK_API_KEY"), ("volcengine", "doubao-seed-1-6-251015", "VOLCENGINE_API_KEY"), - ("volcengine_coding", "ark-code-latest", "VOLCENGINE_API_KEY"), ( "openrouter", "openrouter/google/gemini-2.5-flash", From 2f53a5640227a4c4e10b8550b119a1371e982533 Mon Sep 17 00:00:00 2001 From: Dan Castrillo Date: Sat, 28 Mar 2026 17:48:29 +0100 Subject: [PATCH 10/15] fix(volcengine): strip ark/ prefix from Ark marketplace model IDs and fix provider order Move ark/ prefix to aliases so catalog lookups still resolve but the bare model name is forwarded to the Ark API (which rejects the prefix). Also reorder volcengine before volcengine_coding in provider_list(). Co-Authored-By: Claude Sonnet 4.6 --- crates/openfang-cli/src/main.rs | 12 ++++++------ crates/openfang-runtime/src/model_catalog.rs | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/openfang-cli/src/main.rs b/crates/openfang-cli/src/main.rs index 2c984dd5c..72bbc95b8 100644 --- a/crates/openfang-cli/src/main.rs +++ b/crates/openfang-cli/src/main.rs @@ -1420,18 +1420,18 @@ fn provider_list() -> Vec<(&'static str, &'static str, &'static str, &'static st ("groq", "GROQ_API_KEY", "llama-3.3-70b-versatile", "Groq"), ("gemini", "GEMINI_API_KEY", "gemini-2.5-flash", "Gemini"), ("deepseek", "DEEPSEEK_API_KEY", "deepseek-chat", "DeepSeek"), - ( - "volcengine_coding", - "VOLCENGINE_API_KEY", - "ark-code-latest", - "Volcano Engine Coding Plan", - ), ( "volcengine", "VOLCENGINE_API_KEY", "doubao-seed-1-6-251015", "Volcano Engine", ), + ( + "volcengine_coding", + "VOLCENGINE_API_KEY", + "ark-code-latest", + "Volcano Engine Coding Plan", + ), ( "anthropic", "ANTHROPIC_API_KEY", diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index b60fbf0f2..1424fd25c 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -3536,11 +3536,11 @@ fn builtin_models() -> Vec { aliases: vec![], }, // Third-party models available via Ark marketplace. - // IDs are prefixed with "ark/" to avoid collision with the same models - // registered under their native providers (minimax, zhipu, moonshot). + // The "ark/" prefix is kept as an alias for catalog lookups but is NOT + // forwarded to the Ark API — the API expects bare model names. // Pricing not publicly documented for Ark-routed third-party models; set to 0.0 ModelCatalogEntry { - id: "ark/minimax-m2.5".into(), + id: "minimax-m2.5".into(), display_name: "MiniMax M2.5 (via Ark)".into(), provider: "volcengine_coding".into(), tier: ModelTier::Smart, @@ -3551,10 +3551,10 @@ fn builtin_models() -> Vec { supports_tools: true, supports_vision: false, supports_streaming: true, - aliases: vec![], + aliases: vec!["ark/minimax-m2.5".into()], }, ModelCatalogEntry { - id: "ark/glm-4.7".into(), + id: "glm-4.7".into(), display_name: "GLM 4.7 (via Ark)".into(), provider: "volcengine_coding".into(), tier: ModelTier::Balanced, @@ -3565,11 +3565,11 @@ fn builtin_models() -> Vec { supports_tools: true, supports_vision: false, supports_streaming: true, - aliases: vec![], + aliases: vec!["ark/glm-4.7".into()], }, ModelCatalogEntry { id: "deepseek-v3.2".into(), - display_name: "DeepSeek V3.2".into(), + display_name: "DeepSeek V3.2 (via Ark)".into(), provider: "volcengine_coding".into(), tier: ModelTier::Smart, context_window: 131_072, @@ -3579,10 +3579,10 @@ fn builtin_models() -> Vec { supports_tools: true, supports_vision: false, supports_streaming: true, - aliases: vec![], + aliases: vec!["ark/deepseek-v3.2".into()], }, ModelCatalogEntry { - id: "ark/kimi-k2.5".into(), + id: "kimi-k2.5".into(), display_name: "Kimi K2.5 (via Ark)".into(), provider: "volcengine_coding".into(), tier: ModelTier::Smart, @@ -3593,7 +3593,7 @@ fn builtin_models() -> Vec { supports_tools: true, supports_vision: false, supports_streaming: true, - aliases: vec![], + aliases: vec!["ark/kimi-k2.5".into()], }, // ══════════════════════════════════════════════════════════════ From 9eaf850bbd394b11320a701e103f17558b22974a Mon Sep 17 00:00:00 2001 From: Dan Castrillo Date: Sat, 28 Mar 2026 18:03:32 +0100 Subject: [PATCH 11/15] fix(volcengine): strip ark/ prefix before sending model id to Ark API strip_provider_prefix now removes the ark/ prefix so that model IDs like ark/minimax-m2.5 are sent as minimax-m2.5 to the Ark API. Also extends test_ark_alias_resolution to cover find_model paths. Co-Authored-By: Claude Sonnet 4.6 --- crates/openfang-runtime/src/agent_loop.rs | 7 +++- crates/openfang-runtime/src/model_catalog.rs | 35 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/crates/openfang-runtime/src/agent_loop.rs b/crates/openfang-runtime/src/agent_loop.rs index f773def41..57c3819a9 100644 --- a/crates/openfang-runtime/src/agent_loop.rs +++ b/crates/openfang-runtime/src/agent_loop.rs @@ -109,13 +109,18 @@ fn append_tool_error_guidance(tool_result_blocks: &mut Vec) { pub fn strip_provider_prefix(model: &str, provider: &str) -> String { let slash_prefix = format!("{}/", provider); let colon_prefix = format!("{}:", provider); - if model.starts_with(&slash_prefix) { + let mut result = if model.starts_with(&slash_prefix) { model[slash_prefix.len()..].to_string() } else if model.starts_with(&colon_prefix) { model[colon_prefix.len()..].to_string() } else { model.to_string() + }; + // Strip ark/ prefix for volcengine_coding endpoint (Ark marketplace models) + if result.starts_with("ark/") { + result = result["ark/".len()..].to_string(); } + result } /// Default context window size (tokens) for token-based trimming. diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index 1424fd25c..1964c5277 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -4668,4 +4668,39 @@ mod tests { assert_eq!(found.provider, "custom_provider"); assert_eq!(found.id, "My-Custom-LLM"); } + + #[test] + fn test_ark_alias_resolution() { + let catalog = ModelCatalog::new(); + assert_eq!(catalog.resolve_alias("ark/minimax-m2.5"), Some("minimax-m2.5")); + assert_eq!(catalog.resolve_alias("ark/glm-4.7"), Some("glm-4.7")); + assert_eq!(catalog.resolve_alias("ark/deepseek-v3.2"), Some("deepseek-v3.2")); + assert_eq!(catalog.resolve_alias("ark/kimi-k2.5"), Some("kimi-k2.5")); + // find_model via ark/ alias should return the entry with the bare model id. + // minimax-m2.5 is unique to volcengine_coding via Ark marketplace. + let m25 = catalog.find_model("ark/minimax-m2.5").unwrap(); + assert_eq!(m25.id, "minimax-m2.5"); + assert_eq!(m25.provider, "volcengine_coding"); + // glm-4.7 and kimi-k2.5 share their id with zhipu/moonshot models; + // find_model returns the first catalog entry (non-Ark provider) for those ids. + let glm = catalog.find_model("ark/glm-4.7").unwrap(); + assert_eq!(glm.id, "glm-4.7"); + // deepseek-v3.2 exists only under volcengine_coding in the Ark section. + let ds = catalog.find_model("ark/deepseek-v3.2").unwrap(); + assert_eq!(ds.id, "deepseek-v3.2"); + assert_eq!(ds.provider, "volcengine_coding"); + let kimi = catalog.find_model("ark/kimi-k2.5").unwrap(); + assert_eq!(kimi.id, "kimi-k2.5"); + } + + #[test] + fn test_doubao_alias_resolves_to_volcengine_model() { + let catalog = ModelCatalog::new(); + // "doubao" alias should resolve to the model ID + let resolved = catalog.resolve_alias("doubao"); + assert_eq!(resolved, Some("doubao-seed-1-6-251015")); + // The model should belong to the volcengine provider + let model = catalog.find_model("doubao-seed-1-6-251015").unwrap(); + assert_eq!(model.provider, "volcengine"); + } } From 6899b44d892746c9f4fc214aa324421bcc0b8839 Mon Sep 17 00:00:00 2001 From: Dan Castrillo Date: Sat, 28 Mar 2026 18:07:11 +0100 Subject: [PATCH 12/15] fix(runtime): gate ark/ prefix stripping to volcengine providers only Previously strip_provider_prefix stripped the ark/ prefix for all providers, which could corrupt model IDs from other providers. Now the strip only fires when provider is "volcengine" or "volcengine_coding". Also adds two unit tests verifying the guarded and unguarded cases. Co-Authored-By: Claude Sonnet 4.6 --- crates/openfang-runtime/src/agent_loop.rs | 24 +++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/crates/openfang-runtime/src/agent_loop.rs b/crates/openfang-runtime/src/agent_loop.rs index 57c3819a9..8074b7429 100644 --- a/crates/openfang-runtime/src/agent_loop.rs +++ b/crates/openfang-runtime/src/agent_loop.rs @@ -116,8 +116,10 @@ pub fn strip_provider_prefix(model: &str, provider: &str) -> String { } else { model.to_string() }; - // Strip ark/ prefix for volcengine_coding endpoint (Ark marketplace models) - if result.starts_with("ark/") { + // Strip ark/ prefix only for Volcano Engine providers (Ark marketplace models) + if (provider == "volcengine_coding" || provider == "volcengine") + && result.starts_with("ark/") + { result = result["ark/".len()..].to_string(); } result @@ -3027,6 +3029,24 @@ mod tests { assert_eq!(MAX_HISTORY_MESSAGES, 20); } + #[test] + fn test_strip_provider_prefix_ark_volcengine_coding() { + // Should strip ark/ for volcengine_coding + assert_eq!( + strip_provider_prefix("ark/doubao-seed-code", "volcengine_coding"), + "doubao-seed-code" + ); + } + + #[test] + fn test_strip_provider_prefix_ark_not_stripped_for_other_providers() { + // Must NOT strip ark/ for non-volcengine providers + assert_eq!( + strip_provider_prefix("ark/some-model", "openai"), + "ark/some-model" + ); + } + // --- Integration tests for empty response guards --- fn test_manifest() -> AgentManifest { From e011bd63bce6836f38a6914ea17201725f533102 Mon Sep 17 00:00:00 2001 From: Dan Castrillo Date: Sat, 28 Mar 2026 18:12:09 +0100 Subject: [PATCH 13/15] fix(volcengine): add doubao provider alias to ark/ strip guard and extend tests - Add "doubao" to the ark/ prefix strip condition in agent_loop.rs so users with provider = "doubao" get correct model IDs sent to the API - Add two new tests: volcengine and doubao ark/ stripping - Update volcengine wizard hints with region info (cn-beijing) Co-Authored-By: Claude Sonnet 4.6 --- .../src/tui/screens/init_wizard.rs | 4 ++-- crates/openfang-runtime/src/agent_loop.rs | 20 ++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/openfang-cli/src/tui/screens/init_wizard.rs b/crates/openfang-cli/src/tui/screens/init_wizard.rs index ba7f83945..daa1a0804 100644 --- a/crates/openfang-cli/src/tui/screens/init_wizard.rs +++ b/crates/openfang-cli/src/tui/screens/init_wizard.rs @@ -74,7 +74,7 @@ const PROVIDERS: &[ProviderInfo] = &[ env_var: "VOLCENGINE_API_KEY", default_model: "doubao-seed-1-6-251015", needs_key: true, - hint: "ByteDance Ark platform", + hint: "cn-beijing; override base_url for other regions", }, ProviderInfo { name: "volcengine_coding", @@ -82,7 +82,7 @@ const PROVIDERS: &[ProviderInfo] = &[ env_var: "VOLCENGINE_API_KEY", default_model: "ark-code-latest", needs_key: true, - hint: "ByteDance Ark — coding models", + hint: "ByteDance Ark — coding models (cn-beijing)", }, ProviderInfo { name: "openrouter", diff --git a/crates/openfang-runtime/src/agent_loop.rs b/crates/openfang-runtime/src/agent_loop.rs index 8074b7429..3f32b65dd 100644 --- a/crates/openfang-runtime/src/agent_loop.rs +++ b/crates/openfang-runtime/src/agent_loop.rs @@ -117,7 +117,7 @@ pub fn strip_provider_prefix(model: &str, provider: &str) -> String { model.to_string() }; // Strip ark/ prefix only for Volcano Engine providers (Ark marketplace models) - if (provider == "volcengine_coding" || provider == "volcengine") + if (provider == "volcengine_coding" || provider == "volcengine" || provider == "doubao") && result.starts_with("ark/") { result = result["ark/".len()..].to_string(); @@ -3038,6 +3038,24 @@ mod tests { ); } + #[test] + fn test_strip_provider_prefix_ark_volcengine() { + // Should strip ark/ for volcengine + assert_eq!( + strip_provider_prefix("ark/doubao-seed-code", "volcengine"), + "doubao-seed-code" + ); + } + + #[test] + fn test_strip_provider_prefix_ark_doubao() { + // Should strip ark/ for doubao provider alias + assert_eq!( + strip_provider_prefix("ark/some-model", "doubao"), + "some-model" + ); + } + #[test] fn test_strip_provider_prefix_ark_not_stripped_for_other_providers() { // Must NOT strip ark/ for non-volcengine providers From 59b3eaae88a291a611a8dd5028edab5263e0a296 Mon Sep 17 00:00:00 2001 From: Dan Castrillo Date: Sat, 28 Mar 2026 18:16:21 +0100 Subject: [PATCH 14/15] fix(model_catalog): resolve Ark marketplace model ID collisions The four Ark marketplace models under volcengine_coding had bare IDs (minimax-m2.5, glm-4.7, deepseek-v3.2, kimi-k2.5) that collided with native provider entries. Since find_model does a linear scan returning the first match, ark/ lookups were resolving to the wrong provider. Fix: make ark/ the canonical ID for each colliding entry, keeping the bare name as an alias only for deepseek-v3.2 (no collision exists there). Update test_ark_alias_resolution to assert on the new canonical ark/ IDs and verify native provider entries remain unaffected. Co-Authored-By: Claude Sonnet 4.6 --- crates/openfang-runtime/src/model_catalog.rs | 59 ++++++++++++-------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index 1964c5277..0dff6b5c1 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -3536,11 +3536,12 @@ fn builtin_models() -> Vec { aliases: vec![], }, // Third-party models available via Ark marketplace. - // The "ark/" prefix is kept as an alias for catalog lookups but is NOT - // forwarded to the Ark API — the API expects bare model names. + // The "ark/" prefix is the canonical ID for these Ark marketplace models to avoid + // collisions with native provider entries (minimax, zhipu, moonshot). + // The bare model name is kept as an alias only where no collision exists. // Pricing not publicly documented for Ark-routed third-party models; set to 0.0 ModelCatalogEntry { - id: "minimax-m2.5".into(), + id: "ark/minimax-m2.5".into(), display_name: "MiniMax M2.5 (via Ark)".into(), provider: "volcengine_coding".into(), tier: ModelTier::Smart, @@ -3551,10 +3552,11 @@ fn builtin_models() -> Vec { supports_tools: true, supports_vision: false, supports_streaming: true, - aliases: vec!["ark/minimax-m2.5".into()], + // "minimax-m2.5" NOT added as alias — already canonical on the minimax provider entry + aliases: vec![], }, ModelCatalogEntry { - id: "glm-4.7".into(), + id: "ark/glm-4.7".into(), display_name: "GLM 4.7 (via Ark)".into(), provider: "volcengine_coding".into(), tier: ModelTier::Balanced, @@ -3565,10 +3567,11 @@ fn builtin_models() -> Vec { supports_tools: true, supports_vision: false, supports_streaming: true, - aliases: vec!["ark/glm-4.7".into()], + // "glm-4.7" NOT added as alias — already canonical on the zhipu provider entry + aliases: vec![], }, ModelCatalogEntry { - id: "deepseek-v3.2".into(), + id: "ark/deepseek-v3.2".into(), display_name: "DeepSeek V3.2 (via Ark)".into(), provider: "volcengine_coding".into(), tier: ModelTier::Smart, @@ -3579,10 +3582,11 @@ fn builtin_models() -> Vec { supports_tools: true, supports_vision: false, supports_streaming: true, - aliases: vec!["ark/deepseek-v3.2".into()], + // "deepseek-v3.2" kept as alias — no collision with other providers + aliases: vec!["deepseek-v3.2".into()], }, ModelCatalogEntry { - id: "kimi-k2.5".into(), + id: "ark/kimi-k2.5".into(), display_name: "Kimi K2.5 (via Ark)".into(), provider: "volcengine_coding".into(), tier: ModelTier::Smart, @@ -3593,7 +3597,8 @@ fn builtin_models() -> Vec { supports_tools: true, supports_vision: false, supports_streaming: true, - aliases: vec!["ark/kimi-k2.5".into()], + // "kimi-k2.5" NOT added as alias — already canonical on the moonshot provider entry + aliases: vec![], }, // ══════════════════════════════════════════════════════════════ @@ -4672,25 +4677,33 @@ mod tests { #[test] fn test_ark_alias_resolution() { let catalog = ModelCatalog::new(); - assert_eq!(catalog.resolve_alias("ark/minimax-m2.5"), Some("minimax-m2.5")); - assert_eq!(catalog.resolve_alias("ark/glm-4.7"), Some("glm-4.7")); - assert_eq!(catalog.resolve_alias("ark/deepseek-v3.2"), Some("deepseek-v3.2")); - assert_eq!(catalog.resolve_alias("ark/kimi-k2.5"), Some("kimi-k2.5")); - // find_model via ark/ alias should return the entry with the bare model id. - // minimax-m2.5 is unique to volcengine_coding via Ark marketplace. + // ark/ IDs are now canonical — resolve_alias returns the id itself (no alias mapping needed) + // deepseek-v3.2 is still an alias pointing to ark/deepseek-v3.2 + assert_eq!(catalog.resolve_alias("deepseek-v3.2"), Some("ark/deepseek-v3.2")); + // find_model via ark/ canonical ID returns the volcengine_coding entry directly. let m25 = catalog.find_model("ark/minimax-m2.5").unwrap(); - assert_eq!(m25.id, "minimax-m2.5"); + assert_eq!(m25.id, "ark/minimax-m2.5"); assert_eq!(m25.provider, "volcengine_coding"); - // glm-4.7 and kimi-k2.5 share their id with zhipu/moonshot models; - // find_model returns the first catalog entry (non-Ark provider) for those ids. + // glm-4.7 canonical ID now belongs to ark entry; zhipu entry is unaffected. let glm = catalog.find_model("ark/glm-4.7").unwrap(); - assert_eq!(glm.id, "glm-4.7"); - // deepseek-v3.2 exists only under volcengine_coding in the Ark section. + assert_eq!(glm.id, "ark/glm-4.7"); + assert_eq!(glm.provider, "volcengine_coding"); + // deepseek-v3.2 exists only under volcengine_coding; bare alias still resolves. let ds = catalog.find_model("ark/deepseek-v3.2").unwrap(); - assert_eq!(ds.id, "deepseek-v3.2"); + assert_eq!(ds.id, "ark/deepseek-v3.2"); assert_eq!(ds.provider, "volcengine_coding"); + let ds_alias = catalog.find_model("deepseek-v3.2").unwrap(); + assert_eq!(ds_alias.id, "ark/deepseek-v3.2"); let kimi = catalog.find_model("ark/kimi-k2.5").unwrap(); - assert_eq!(kimi.id, "kimi-k2.5"); + assert_eq!(kimi.id, "ark/kimi-k2.5"); + assert_eq!(kimi.provider, "volcengine_coding"); + // Native provider entries are unaffected by the ark/ rename + let minimax_native = catalog.find_model("minimax-m2.5").unwrap(); + assert_eq!(minimax_native.provider, "minimax"); + let glm_native = catalog.find_model("glm-4.7").unwrap(); + assert_eq!(glm_native.provider, "zhipu"); + let kimi_native = catalog.find_model("kimi-k2.5").unwrap(); + assert_eq!(kimi_native.provider, "moonshot"); } #[test] From bcd86a1e35c0c3da016f6a67de1f953159298858 Mon Sep 17 00:00:00 2001 From: Dan Castrillo Date: Sat, 28 Mar 2026 18:40:28 +0100 Subject: [PATCH 15/15] fix(volcengine): clarify ark/ catalog-vs-wire comment and rename test --- crates/openfang-runtime/src/agent_loop.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/openfang-runtime/src/agent_loop.rs b/crates/openfang-runtime/src/agent_loop.rs index 3f32b65dd..d08f782f4 100644 --- a/crates/openfang-runtime/src/agent_loop.rs +++ b/crates/openfang-runtime/src/agent_loop.rs @@ -116,7 +116,11 @@ pub fn strip_provider_prefix(model: &str, provider: &str) -> String { } else { model.to_string() }; - // Strip ark/ prefix only for Volcano Engine providers (Ark marketplace models) + // Strip "ark/" catalog namespace prefix before sending to Ark API. + // "ark/" is used internally to disambiguate Ark marketplace models from + // native provider models with the same name (e.g. ark/minimax-m2.5 vs + // minimax provider's minimax-m2.5). The Ark API endpoint expects the bare + // model name (e.g. "minimax-m2.5"), not the namespaced form. if (provider == "volcengine_coding" || provider == "volcengine" || provider == "doubao") && result.starts_with("ark/") { @@ -3030,8 +3034,8 @@ mod tests { } #[test] - fn test_strip_provider_prefix_ark_volcengine_coding() { - // Should strip ark/ for volcengine_coding + fn test_strip_ark_catalog_prefix_for_volcengine_coding() { + // ark/ is catalog-only; Ark API expects bare name assert_eq!( strip_provider_prefix("ark/doubao-seed-code", "volcengine_coding"), "doubao-seed-code"