diff --git a/crates/openfang-runtime/src/drivers/anthropic.rs b/crates/openfang-runtime/src/drivers/anthropic.rs index 6f1179acc..3c76ee3a9 100644 --- a/crates/openfang-runtime/src/drivers/anthropic.rs +++ b/crates/openfang-runtime/src/drivers/anthropic.rs @@ -19,6 +19,7 @@ pub struct AnthropicDriver { api_key: Zeroizing, base_url: String, client: reqwest::Client, + extra_headers: Vec<(String, String)>, } impl AnthropicDriver { @@ -31,8 +32,14 @@ impl AnthropicDriver { .user_agent(crate::USER_AGENT) .build() .unwrap_or_default(), + extra_headers: Vec::new(), } } + + pub fn with_extra_headers(mut self, headers: Vec<(String, String)>) -> Self { + self.extra_headers = headers; + self + } } /// Anthropic Messages API request body. @@ -204,12 +211,16 @@ impl LlmDriver for AnthropicDriver { let url = format!("{}/v1/messages", self.base_url); debug!(url = %url, attempt, "Sending Anthropic API request"); - let resp = self + let mut req_builder = self .client .post(&url) .header("x-api-key", self.api_key.as_str()) .header("anthropic-version", "2023-06-01") - .header("content-type", "application/json") + .header("content-type", "application/json"); + for (k, v) in &self.extra_headers { + req_builder = req_builder.header(k, v); + } + let resp = req_builder .json(&api_request) .send() .await @@ -311,12 +322,16 @@ impl LlmDriver for AnthropicDriver { let url = format!("{}/v1/messages", self.base_url); debug!(url = %url, attempt, "Sending Anthropic streaming request"); - let resp = self + let mut req_builder = self .client .post(&url) .header("x-api-key", self.api_key.as_str()) .header("anthropic-version", "2023-06-01") - .header("content-type", "application/json") + .header("content-type", "application/json"); + for (k, v) in &self.extra_headers { + req_builder = req_builder.header(k, v); + } + let resp = req_builder .json(&api_request) .send() .await diff --git a/crates/openfang-runtime/src/drivers/mod.rs b/crates/openfang-runtime/src/drivers/mod.rs index fdd62aaf9..d32efee75 100644 --- a/crates/openfang-runtime/src/drivers/mod.rs +++ b/crates/openfang-runtime/src/drivers/mod.rs @@ -13,14 +13,14 @@ pub mod openai; use crate::llm_driver::{DriverConfig, LlmDriver, LlmError}; use openfang_types::model_catalog::{ - AI21_BASE_URL, ANTHROPIC_BASE_URL, CEREBRAS_BASE_URL, CHUTES_BASE_URL, COHERE_BASE_URL, + AI21_BASE_URL, ANTHROPIC_BASE_URL, BAILIAN_CODING_ANTHROPIC_BASE_URL, + BAILIAN_CODING_OPENAI_BASE_URL, CEREBRAS_BASE_URL, CHUTES_BASE_URL, COHERE_BASE_URL, DEEPSEEK_BASE_URL, FIREWORKS_BASE_URL, GEMINI_BASE_URL, GROQ_BASE_URL, HUGGINGFACE_BASE_URL, - KIMI_CODING_BASE_URL, LEMONADE_BASE_URL, 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, - VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL, - ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL, + KIMI_CODING_BASE_URL, LEMONADE_BASE_URL, 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, VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, + ZAI_BASE_URL, ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL, }; use std::sync::Arc; @@ -205,6 +205,11 @@ fn provider_defaults(provider: &str) -> Option { api_key_env: "VOLCENGINE_API_KEY", key_required: true, }), + "bailian_coding_openai" => Some(ProviderDefaults { + base_url: BAILIAN_CODING_OPENAI_BASE_URL, + api_key_env: "BAILIAN_API_KEY", + key_required: true, + }), "chutes" => Some(ProviderDefaults { base_url: CHUTES_BASE_URL, api_key_env: "CHUTES_API_KEY", @@ -329,6 +334,44 @@ pub fn create_driver(config: &DriverConfig) -> Result, LlmErr ))); } + // Bailian Coding Plan — Anthropic-compatible endpoint. + // Requires DashScope-specific headers just like the OpenAI endpoint. + // Base URL does NOT include /v1 — AnthropicDriver appends /v1/messages automatically. + if provider == "bailian_coding_anthropic" { + let api_key = config + .api_key + .clone() + .or_else(|| std::env::var("BAILIAN_API_KEY").ok()) + .ok_or_else(|| { + LlmError::MissingApiKey( + "Set BAILIAN_API_KEY environment variable for Bailian Coding Anthropic" + .to_string(), + ) + })?; + let base_url = config + .base_url + .clone() + .unwrap_or_else(|| BAILIAN_CODING_ANTHROPIC_BASE_URL.to_string()); + let user_agent = format!( + "OpenFang/{} ({}; {})", + env!("CARGO_PKG_VERSION"), + std::env::consts::OS, + std::env::consts::ARCH + ); + let dashscope_headers = vec![ + ("User-Agent".to_string(), user_agent.clone()), + ("X-DashScope-UserAgent".to_string(), user_agent), + ("X-DashScope-AuthType".to_string(), "qwen-oauth".to_string()), + ( + "X-DashScope-CacheControl".to_string(), + "enable".to_string(), + ), + ]; + return Ok(Arc::new( + anthropic::AnthropicDriver::new(api_key, base_url).with_extra_headers(dashscope_headers), + )); + } + // Kimi for Code — Anthropic-compatible endpoint if provider == "kimi_coding" { let api_key = config @@ -365,6 +408,28 @@ pub fn create_driver(config: &DriverConfig) -> Result, LlmErr .clone() .unwrap_or_else(|| defaults.base_url.to_string()); + // Bailian Coding Plan (OpenAI endpoint) requires specific headers to identify + // the caller as a "Coding Agent"; without them the API returns HTTP 405. + if provider == "bailian_coding_openai" { + let user_agent = format!( + "OpenFang/{} ({}; {})", + env!("CARGO_PKG_VERSION"), + std::env::consts::OS, + std::env::consts::ARCH, + ); + return Ok(Arc::new( + openai::OpenAIDriver::new(api_key, base_url).with_extra_headers(vec![ + ("User-Agent".to_string(), user_agent.clone()), + ("X-DashScope-UserAgent".to_string(), user_agent), + ("X-DashScope-AuthType".to_string(), "qwen-oauth".to_string()), + ( + "X-DashScope-CacheControl".to_string(), + "enable".to_string(), + ), + ]), + )); + } + return Ok(Arc::new(openai::OpenAIDriver::new(api_key, base_url))); } @@ -408,7 +473,7 @@ pub fn create_driver(config: &DriverConfig) -> Result, LlmErr "Unknown provider '{}'. Supported: anthropic, gemini, openai, groq, openrouter, \ deepseek, together, mistral, fireworks, ollama, vllm, lmstudio, perplexity, \ cohere, ai21, cerebras, sambanova, huggingface, xai, replicate, github-copilot, \ - chutes, venice, codex, claude-code. Or set base_url for a custom OpenAI-compatible endpoint.", + chutes, venice, bailian_coding_openai, bailian_coding_anthropic, codex, claude-code. Or set base_url for a custom OpenAI-compatible endpoint.", provider ), }) @@ -479,6 +544,8 @@ pub fn known_providers() -> &'static [&'static str] { "kimi_coding", "qianfan", "volcengine", + "bailian_coding_openai", + "bailian_coding_anthropic", "chutes", "venice", "codex", @@ -579,10 +646,12 @@ mod tests { assert!(providers.contains(&"kimi_coding")); assert!(providers.contains(&"qianfan")); assert!(providers.contains(&"volcengine")); + assert!(providers.contains(&"bailian_coding_openai")); + assert!(providers.contains(&"bailian_coding_anthropic")); assert!(providers.contains(&"chutes")); assert!(providers.contains(&"codex")); assert!(providers.contains(&"claude-code")); - assert_eq!(providers.len(), 34); + assert_eq!(providers.len(), 36); } #[test] diff --git a/crates/openfang-runtime/src/model_catalog.rs b/crates/openfang-runtime/src/model_catalog.rs index 8822c460e..60caedd03 100644 --- a/crates/openfang-runtime/src/model_catalog.rs +++ b/crates/openfang-runtime/src/model_catalog.rs @@ -5,14 +5,14 @@ use openfang_types::model_catalog::{ AuthStatus, ModelCatalogEntry, ModelTier, ProviderInfo, AI21_BASE_URL, ANTHROPIC_BASE_URL, - BEDROCK_BASE_URL, CEREBRAS_BASE_URL, CHUTES_BASE_URL, COHERE_BASE_URL, DEEPSEEK_BASE_URL, - FIREWORKS_BASE_URL, GEMINI_BASE_URL, GITHUB_COPILOT_BASE_URL, GROQ_BASE_URL, - HUGGINGFACE_BASE_URL, KIMI_CODING_BASE_URL, LEMONADE_BASE_URL, 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, - VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, ZAI_BASE_URL, - ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL, + BAILIAN_CODING_ANTHROPIC_BASE_URL, BAILIAN_CODING_OPENAI_BASE_URL, BEDROCK_BASE_URL, + CEREBRAS_BASE_URL, CHUTES_BASE_URL, COHERE_BASE_URL, DEEPSEEK_BASE_URL, FIREWORKS_BASE_URL, + GEMINI_BASE_URL, GITHUB_COPILOT_BASE_URL, GROQ_BASE_URL, HUGGINGFACE_BASE_URL, + KIMI_CODING_BASE_URL, LEMONADE_BASE_URL, 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, VOLCENGINE_BASE_URL, VOLCENGINE_CODING_BASE_URL, XAI_BASE_URL, + ZAI_BASE_URL, ZAI_CODING_BASE_URL, ZHIPU_BASE_URL, ZHIPU_CODING_BASE_URL, }; use std::collections::HashMap; @@ -59,12 +59,11 @@ impl ModelCatalog { // Claude Code is special: no API key needed, but we probe for CLI // installation so the dashboard shows "Configured" vs "Not Installed". if provider.id == "claude-code" { - provider.auth_status = - if crate::drivers::claude_code::claude_code_available() { - AuthStatus::Configured - } else { - AuthStatus::Missing - }; + provider.auth_status = if crate::drivers::claude_code::claude_code_available() { + AuthStatus::Configured + } else { + AuthStatus::Missing + }; continue; } @@ -80,8 +79,7 @@ impl ModelCatalog { let has_fallback = match provider.id.as_str() { "gemini" => std::env::var("GOOGLE_API_KEY").is_ok(), "codex" => { - std::env::var("OPENAI_API_KEY").is_ok() - || read_codex_credential().is_some() + std::env::var("OPENAI_API_KEY").is_ok() || read_codex_credential().is_some() } // claude-code is handled above (before key_required check) _ => false, @@ -725,6 +723,25 @@ fn builtin_providers() -> Vec { auth_status: AuthStatus::Missing, model_count: 0, }, + // ── Bailian Coding Plan ─────────────────────────────────── + ProviderInfo { + id: "bailian_coding_openai".into(), + display_name: "Bailian Coding Plan (OpenAI)".into(), + api_key_env: "BAILIAN_API_KEY".into(), + base_url: BAILIAN_CODING_OPENAI_BASE_URL.into(), + key_required: true, + auth_status: AuthStatus::Missing, + model_count: 0, + }, + ProviderInfo { + id: "bailian_coding_anthropic".into(), + display_name: "Bailian Coding Plan (Anthropic)".into(), + api_key_env: "BAILIAN_API_KEY".into(), + base_url: BAILIAN_CODING_ANTHROPIC_BASE_URL.into(), + key_required: true, + auth_status: AuthStatus::Missing, + model_count: 0, + }, // ── AWS Bedrock ────────────────────────────────────────────── ProviderInfo { id: "bedrock".into(), @@ -3533,6 +3550,236 @@ fn builtin_models() -> Vec { supports_streaming: true, aliases: vec![], }, + // ══════════════════════════════════════════════════════════════ + // Bailian Coding Plan — OpenAI Protocol (8) + // ══════════════════════════════════════════════════════════════ + ModelCatalogEntry { + id: "bailian_coding_openai/kimi-k2.5".into(), + display_name: "Kimi K2.5 (Bailian OpenAI)".into(), + provider: "bailian_coding_openai".into(), + tier: ModelTier::Frontier, + context_window: 262_144, + max_output_tokens: 32_768, + input_cost_per_m: 2.00, + output_cost_per_m: 8.00, + supports_tools: true, + supports_vision: true, + supports_streaming: true, + aliases: vec!["bailian-kimi".into()], + }, + ModelCatalogEntry { + id: "bailian_coding_openai/qwen3.5-plus".into(), + display_name: "Qwen 3.5 Plus (Bailian OpenAI)".into(), + provider: "bailian_coding_openai".into(), + tier: ModelTier::Smart, + context_window: 1_000_000, + max_output_tokens: 65_536, + input_cost_per_m: 1.00, + output_cost_per_m: 4.00, + supports_tools: true, + supports_vision: true, + supports_streaming: true, + aliases: vec!["bailian-qwen".into()], + }, + ModelCatalogEntry { + id: "bailian_coding_openai/qwen3-max-2026-01-23".into(), + display_name: "Qwen3 Max 2026-01-23 (Bailian OpenAI)".into(), + provider: "bailian_coding_openai".into(), + tier: ModelTier::Smart, + context_window: 262_144, + max_output_tokens: 32_768, + input_cost_per_m: 1.00, + output_cost_per_m: 4.00, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec!["bailian-qwen3-max".into()], + }, + ModelCatalogEntry { + id: "bailian_coding_openai/qwen3-coder-next".into(), + display_name: "Qwen3 Coder Next (Bailian OpenAI)".into(), + provider: "bailian_coding_openai".into(), + tier: ModelTier::Smart, + context_window: 262_144, + max_output_tokens: 65_536, + input_cost_per_m: 1.00, + output_cost_per_m: 4.00, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec!["bailian-coder-next".into()], + }, + ModelCatalogEntry { + id: "bailian_coding_openai/qwen3-coder-plus".into(), + display_name: "Qwen3 Coder Plus (Bailian OpenAI)".into(), + provider: "bailian_coding_openai".into(), + tier: ModelTier::Smart, + context_window: 1_000_000, + max_output_tokens: 65_536, + input_cost_per_m: 1.00, + output_cost_per_m: 4.00, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec!["bailian-coder".into()], + }, + ModelCatalogEntry { + id: "bailian_coding_openai/MiniMax-M2.5".into(), + display_name: "MiniMax M2.5 (Bailian OpenAI)".into(), + provider: "bailian_coding_openai".into(), + tier: ModelTier::Smart, + context_window: 196_608, + max_output_tokens: 24_576, + input_cost_per_m: 1.00, + output_cost_per_m: 4.00, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec!["bailian-minimax".into()], + }, + ModelCatalogEntry { + id: "bailian_coding_openai/glm-5".into(), + display_name: "GLM-5 (Bailian OpenAI)".into(), + provider: "bailian_coding_openai".into(), + tier: ModelTier::Smart, + context_window: 202_752, + max_output_tokens: 16_384, + input_cost_per_m: 1.50, + output_cost_per_m: 6.00, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec!["bailian-glm".into()], + }, + ModelCatalogEntry { + id: "bailian_coding_openai/glm-4.7".into(), + display_name: "GLM-4.7 (Bailian OpenAI)".into(), + provider: "bailian_coding_openai".into(), + tier: ModelTier::Smart, + context_window: 202_752, + max_output_tokens: 16_384, + input_cost_per_m: 1.00, + output_cost_per_m: 4.00, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec!["bailian-glm4".into()], + }, + // ══════════════════════════════════════════════════════════════ + // Bailian Coding Plan — Anthropic Protocol (8) + // ══════════════════════════════════════════════════════════════ + ModelCatalogEntry { + id: "bailian_coding_anthropic/kimi-k2.5".into(), + display_name: "Kimi K2.5 (Bailian Anthropic)".into(), + provider: "bailian_coding_anthropic".into(), + tier: ModelTier::Frontier, + context_window: 262_144, + max_output_tokens: 32_768, + input_cost_per_m: 2.00, + output_cost_per_m: 8.00, + supports_tools: true, + supports_vision: true, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "bailian_coding_anthropic/qwen3.5-plus".into(), + display_name: "Qwen 3.5 Plus (Bailian Anthropic)".into(), + provider: "bailian_coding_anthropic".into(), + tier: ModelTier::Smart, + context_window: 1_000_000, + max_output_tokens: 65_536, + input_cost_per_m: 1.00, + output_cost_per_m: 4.00, + supports_tools: true, + supports_vision: true, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "bailian_coding_anthropic/qwen3-max-2026-01-23".into(), + display_name: "Qwen3 Max 2026-01-23 (Bailian Anthropic)".into(), + provider: "bailian_coding_anthropic".into(), + tier: ModelTier::Smart, + context_window: 262_144, + max_output_tokens: 32_768, + input_cost_per_m: 1.00, + output_cost_per_m: 4.00, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "bailian_coding_anthropic/qwen3-coder-next".into(), + display_name: "Qwen3 Coder Next (Bailian Anthropic)".into(), + provider: "bailian_coding_anthropic".into(), + tier: ModelTier::Smart, + context_window: 262_144, + max_output_tokens: 65_536, + input_cost_per_m: 1.00, + output_cost_per_m: 4.00, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "bailian_coding_anthropic/qwen3-coder-plus".into(), + display_name: "Qwen3 Coder Plus (Bailian Anthropic)".into(), + provider: "bailian_coding_anthropic".into(), + tier: ModelTier::Smart, + context_window: 1_000_000, + max_output_tokens: 65_536, + input_cost_per_m: 1.00, + output_cost_per_m: 4.00, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "bailian_coding_anthropic/MiniMax-M2.5".into(), + display_name: "MiniMax M2.5 (Bailian Anthropic)".into(), + provider: "bailian_coding_anthropic".into(), + tier: ModelTier::Smart, + context_window: 196_608, + max_output_tokens: 24_576, + input_cost_per_m: 1.00, + output_cost_per_m: 4.00, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "bailian_coding_anthropic/glm-5".into(), + display_name: "GLM-5 (Bailian Anthropic)".into(), + provider: "bailian_coding_anthropic".into(), + tier: ModelTier::Smart, + context_window: 202_752, + max_output_tokens: 16_384, + input_cost_per_m: 1.50, + output_cost_per_m: 6.00, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, + ModelCatalogEntry { + id: "bailian_coding_anthropic/glm-4.7".into(), + display_name: "GLM-4.7 (Bailian Anthropic)".into(), + provider: "bailian_coding_anthropic".into(), + tier: ModelTier::Smart, + context_window: 202_752, + max_output_tokens: 16_384, + input_cost_per_m: 1.00, + output_cost_per_m: 4.00, + supports_tools: true, + supports_vision: false, + supports_streaming: true, + aliases: vec![], + }, ] } @@ -3549,7 +3796,7 @@ mod tests { #[test] fn test_catalog_has_providers() { let catalog = ModelCatalog::new(); - assert_eq!(catalog.list_providers().len(), 38); + assert_eq!(catalog.list_providers().len(), 40); } #[test] @@ -3584,10 +3831,7 @@ mod tests { #[test] fn test_resolve_alias() { let catalog = ModelCatalog::new(); - assert_eq!( - catalog.resolve_alias("sonnet"), - Some("claude-sonnet-4-6") - ); + assert_eq!(catalog.resolve_alias("sonnet"), Some("claude-sonnet-4-6")); assert_eq!( catalog.resolve_alias("haiku"), Some("claude-haiku-4-5-20251001") diff --git a/crates/openfang-types/src/model_catalog.rs b/crates/openfang-types/src/model_catalog.rs index b38e51b6c..3060e1414 100644 --- a/crates/openfang-types/src/model_catalog.rs +++ b/crates/openfang-types/src/model_catalog.rs @@ -44,6 +44,13 @@ pub const ZAI_BASE_URL: &str = "https://api.z.ai/api/paas/v4"; 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 BAILIAN_CODING_OPENAI_BASE_URL: &str = "https://coding.dashscope.aliyuncs.com/v1"; +pub const BAILIAN_CODING_OPENAI_INTL_BASE_URL: &str = + "https://coding-intl.dashscope.aliyuncs.com/v1"; +pub const BAILIAN_CODING_ANTHROPIC_BASE_URL: &str = + "https://coding.dashscope.aliyuncs.com/apps/anthropic"; +pub const BAILIAN_CODING_ANTHROPIC_INTL_BASE_URL: &str = + "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic"; pub const QIANFAN_BASE_URL: &str = "https://qianfan.baidubce.com/v2"; 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";