fix(provider): split CN/global endpoints for Chinese provider variants (#542)
* fix(providers): add CN/global endpoint variants for Chinese vendors * fix(onboard): deduplicate provider key-url match arms * chore(i18n): normalize non-English literals to English
This commit is contained in:
parent
93d9d0de06
commit
85de9b5625
6 changed files with 373 additions and 45 deletions
|
|
@ -1085,7 +1085,7 @@ mod tests {
|
||||||
"sender": { "sender_id": { "open_id": "ou_user" } },
|
"sender": { "sender_id": { "open_id": "ou_user" } },
|
||||||
"message": {
|
"message": {
|
||||||
"message_type": "text",
|
"message_type": "text",
|
||||||
"content": "{\"text\":\"你好世界 🌍\"}",
|
"content": "{\"text\":\"Hello world 🌍\"}",
|
||||||
"chat_id": "oc_chat",
|
"chat_id": "oc_chat",
|
||||||
"create_time": "1000"
|
"create_time": "1000"
|
||||||
}
|
}
|
||||||
|
|
@ -1094,7 +1094,7 @@ mod tests {
|
||||||
|
|
||||||
let msgs = ch.parse_event_payload(&payload);
|
let msgs = ch.parse_event_payload(&payload);
|
||||||
assert_eq!(msgs.len(), 1);
|
assert_eq!(msgs.len(), 1);
|
||||||
assert_eq!(msgs[0].content, "你好世界 🌍");
|
assert_eq!(msgs[0].content, "Hello world 🌍");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -1620,7 +1620,7 @@ impl Default for AuditConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// DingTalk (钉钉) configuration for Stream Mode messaging
|
/// DingTalk configuration for Stream Mode messaging
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct DingTalkConfig {
|
pub struct DingTalkConfig {
|
||||||
/// Client ID (AppKey) from DingTalk developer console
|
/// Client ID (AppKey) from DingTalk developer console
|
||||||
|
|
@ -1827,10 +1827,19 @@ impl Config {
|
||||||
self.api_key = Some(key);
|
self.api_key = Some(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// API Key: GLM_API_KEY overrides when provider is glm (provider-specific)
|
// API Key: GLM_API_KEY overrides when provider is a GLM/Zhipu variant.
|
||||||
if self.default_provider.as_deref() == Some("glm")
|
if matches!(
|
||||||
|| self.default_provider.as_deref() == Some("zhipu")
|
self.default_provider.as_deref(),
|
||||||
{
|
Some(
|
||||||
|
"glm"
|
||||||
|
| "zhipu"
|
||||||
|
| "glm-global"
|
||||||
|
| "zhipu-global"
|
||||||
|
| "glm-cn"
|
||||||
|
| "zhipu-cn"
|
||||||
|
| "bigmodel"
|
||||||
|
)
|
||||||
|
) {
|
||||||
if let Ok(key) = std::env::var("GLM_API_KEY") {
|
if let Ok(key) = std::env::var("GLM_API_KEY") {
|
||||||
if !key.is_empty() {
|
if !key.is_empty() {
|
||||||
self.api_key = Some(key);
|
self.api_key = Some(key);
|
||||||
|
|
@ -3086,6 +3095,21 @@ default_temperature = 0.7
|
||||||
std::env::remove_var("PROVIDER");
|
std::env::remove_var("PROVIDER");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn env_override_glm_api_key_for_regional_aliases() {
|
||||||
|
let _env_guard = env_override_test_guard();
|
||||||
|
let mut config = Config {
|
||||||
|
default_provider: Some("glm-cn".to_string()),
|
||||||
|
..Config::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
std::env::set_var("GLM_API_KEY", "glm-regional-key");
|
||||||
|
config.apply_env_overrides();
|
||||||
|
assert_eq!(config.api_key.as_deref(), Some("glm-regional-key"));
|
||||||
|
|
||||||
|
std::env::remove_var("GLM_API_KEY");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn env_override_model() {
|
fn env_override_model() {
|
||||||
let _env_guard = env_override_test_guard();
|
let _env_guard = env_override_test_guard();
|
||||||
|
|
|
||||||
|
|
@ -1318,7 +1318,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn whatsapp_signature_unicode_body() {
|
fn whatsapp_signature_unicode_body() {
|
||||||
let app_secret = "test_secret_key_12345";
|
let app_secret = "test_secret_key_12345";
|
||||||
let body = "Hello 🦀 世界".as_bytes();
|
let body = "Hello 🦀 World".as_bytes();
|
||||||
|
|
||||||
let signature_header = compute_whatsapp_signature_header(app_secret, body);
|
let signature_header = compute_whatsapp_signature_header(app_secret, body);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ pub fn all_integrations() -> Vec<IntegrationEntry> {
|
||||||
},
|
},
|
||||||
IntegrationEntry {
|
IntegrationEntry {
|
||||||
name: "DingTalk",
|
name: "DingTalk",
|
||||||
description: "DingTalk Stream Mode (钉钉)",
|
description: "DingTalk Stream Mode",
|
||||||
category: IntegrationCategory::Chat,
|
category: IntegrationCategory::Chat,
|
||||||
status_fn: |c| {
|
status_fn: |c| {
|
||||||
if c.channels_config.dingtalk.is_some() {
|
if c.channels_config.dingtalk.is_some() {
|
||||||
|
|
@ -317,7 +317,19 @@ pub fn all_integrations() -> Vec<IntegrationEntry> {
|
||||||
description: "Kimi & Kimi Coding",
|
description: "Kimi & Kimi Coding",
|
||||||
category: IntegrationCategory::AiModel,
|
category: IntegrationCategory::AiModel,
|
||||||
status_fn: |c| {
|
status_fn: |c| {
|
||||||
if c.default_provider.as_deref() == Some("moonshot") {
|
if matches!(
|
||||||
|
c.default_provider.as_deref(),
|
||||||
|
Some(
|
||||||
|
"moonshot"
|
||||||
|
| "kimi"
|
||||||
|
| "moonshot-intl"
|
||||||
|
| "moonshot-global"
|
||||||
|
| "moonshot-cn"
|
||||||
|
| "kimi-intl"
|
||||||
|
| "kimi-global"
|
||||||
|
| "kimi-cn"
|
||||||
|
)
|
||||||
|
) {
|
||||||
IntegrationStatus::Active
|
IntegrationStatus::Active
|
||||||
} else {
|
} else {
|
||||||
IntegrationStatus::Available
|
IntegrationStatus::Available
|
||||||
|
|
@ -365,7 +377,18 @@ pub fn all_integrations() -> Vec<IntegrationEntry> {
|
||||||
description: "ChatGLM / Zhipu models",
|
description: "ChatGLM / Zhipu models",
|
||||||
category: IntegrationCategory::AiModel,
|
category: IntegrationCategory::AiModel,
|
||||||
status_fn: |c| {
|
status_fn: |c| {
|
||||||
if c.default_provider.as_deref() == Some("glm") {
|
if matches!(
|
||||||
|
c.default_provider.as_deref(),
|
||||||
|
Some(
|
||||||
|
"glm"
|
||||||
|
| "zhipu"
|
||||||
|
| "glm-global"
|
||||||
|
| "zhipu-global"
|
||||||
|
| "glm-cn"
|
||||||
|
| "zhipu-cn"
|
||||||
|
| "bigmodel"
|
||||||
|
)
|
||||||
|
) {
|
||||||
IntegrationStatus::Active
|
IntegrationStatus::Active
|
||||||
} else {
|
} else {
|
||||||
IntegrationStatus::Available
|
IntegrationStatus::Available
|
||||||
|
|
@ -377,7 +400,43 @@ pub fn all_integrations() -> Vec<IntegrationEntry> {
|
||||||
description: "MiniMax AI models",
|
description: "MiniMax AI models",
|
||||||
category: IntegrationCategory::AiModel,
|
category: IntegrationCategory::AiModel,
|
||||||
status_fn: |c| {
|
status_fn: |c| {
|
||||||
if c.default_provider.as_deref() == Some("minimax") {
|
if matches!(
|
||||||
|
c.default_provider.as_deref(),
|
||||||
|
Some(
|
||||||
|
"minimax"
|
||||||
|
| "minimax-intl"
|
||||||
|
| "minimax-io"
|
||||||
|
| "minimax-global"
|
||||||
|
| "minimax-cn"
|
||||||
|
| "minimaxi"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
IntegrationStatus::Active
|
||||||
|
} else {
|
||||||
|
IntegrationStatus::Available
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
IntegrationEntry {
|
||||||
|
name: "Qwen",
|
||||||
|
description: "Alibaba DashScope Qwen models",
|
||||||
|
category: IntegrationCategory::AiModel,
|
||||||
|
status_fn: |c| {
|
||||||
|
if matches!(
|
||||||
|
c.default_provider.as_deref(),
|
||||||
|
Some(
|
||||||
|
"qwen"
|
||||||
|
| "dashscope"
|
||||||
|
| "qwen-cn"
|
||||||
|
| "dashscope-cn"
|
||||||
|
| "qwen-intl"
|
||||||
|
| "dashscope-intl"
|
||||||
|
| "qwen-international"
|
||||||
|
| "dashscope-international"
|
||||||
|
| "qwen-us"
|
||||||
|
| "dashscope-us"
|
||||||
|
)
|
||||||
|
) {
|
||||||
IntegrationStatus::Active
|
IntegrationStatus::Active
|
||||||
} else {
|
} else {
|
||||||
IntegrationStatus::Available
|
IntegrationStatus::Available
|
||||||
|
|
@ -905,4 +964,40 @@ mod tests {
|
||||||
"Expected 5+ AI model integrations, got {ai_count}"
|
"Expected 5+ AI model integrations, got {ai_count}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn regional_provider_aliases_activate_expected_ai_integrations() {
|
||||||
|
let entries = all_integrations();
|
||||||
|
let mut config = Config {
|
||||||
|
default_provider: Some("minimax-cn".to_string()),
|
||||||
|
..Config::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let minimax = entries.iter().find(|e| e.name == "MiniMax").unwrap();
|
||||||
|
assert!(matches!(
|
||||||
|
(minimax.status_fn)(&config),
|
||||||
|
IntegrationStatus::Active
|
||||||
|
));
|
||||||
|
|
||||||
|
config.default_provider = Some("glm-cn".to_string());
|
||||||
|
let glm = entries.iter().find(|e| e.name == "GLM").unwrap();
|
||||||
|
assert!(matches!(
|
||||||
|
(glm.status_fn)(&config),
|
||||||
|
IntegrationStatus::Active
|
||||||
|
));
|
||||||
|
|
||||||
|
config.default_provider = Some("moonshot-intl".to_string());
|
||||||
|
let moonshot = entries.iter().find(|e| e.name == "Moonshot").unwrap();
|
||||||
|
assert!(matches!(
|
||||||
|
(moonshot.status_fn)(&config),
|
||||||
|
IntegrationStatus::Active
|
||||||
|
));
|
||||||
|
|
||||||
|
config.default_provider = Some("qwen-intl".to_string());
|
||||||
|
let qwen = entries.iter().find(|e| e.name == "Qwen").unwrap();
|
||||||
|
assert!(matches!(
|
||||||
|
(qwen.status_fn)(&config),
|
||||||
|
IntegrationStatus::Active
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -448,6 +448,20 @@ fn canonical_provider_name(provider_name: &str) -> &str {
|
||||||
"grok" => "xai",
|
"grok" => "xai",
|
||||||
"together" => "together-ai",
|
"together" => "together-ai",
|
||||||
"google" | "google-gemini" => "gemini",
|
"google" | "google-gemini" => "gemini",
|
||||||
|
"dashscope"
|
||||||
|
| "qwen-cn"
|
||||||
|
| "dashscope-cn"
|
||||||
|
| "qwen-intl"
|
||||||
|
| "dashscope-intl"
|
||||||
|
| "qwen-international"
|
||||||
|
| "dashscope-international"
|
||||||
|
| "qwen-us"
|
||||||
|
| "dashscope-us" => "qwen",
|
||||||
|
"zhipu" | "glm-global" | "zhipu-global" | "glm-cn" | "zhipu-cn" | "bigmodel" => "glm",
|
||||||
|
"kimi" | "moonshot-intl" | "moonshot-global" | "moonshot-cn" | "kimi-intl"
|
||||||
|
| "kimi-global" | "kimi-cn" => "moonshot",
|
||||||
|
"minimax-intl" | "minimax-io" | "minimax-global" | "minimax-cn" | "minimaxi" => "minimax",
|
||||||
|
"baidu" => "qianfan",
|
||||||
_ => provider_name,
|
_ => provider_name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -467,6 +481,7 @@ fn default_model_for_provider(provider: &str) -> String {
|
||||||
"openai" => "gpt-5.2".into(),
|
"openai" => "gpt-5.2".into(),
|
||||||
"glm" | "zhipu" | "zai" | "z.ai" => "glm-5".into(),
|
"glm" | "zhipu" | "zai" | "z.ai" => "glm-5".into(),
|
||||||
"minimax" => "MiniMax-M2.5".into(),
|
"minimax" => "MiniMax-M2.5".into(),
|
||||||
|
"qwen" => "qwen-plus".into(),
|
||||||
"ollama" => "llama3.2".into(),
|
"ollama" => "llama3.2".into(),
|
||||||
"groq" => "llama-3.3-70b-versatile".into(),
|
"groq" => "llama-3.3-70b-versatile".into(),
|
||||||
"deepseek" => "deepseek-chat".into(),
|
"deepseek" => "deepseek-chat".into(),
|
||||||
|
|
@ -702,6 +717,20 @@ fn curated_models_for_provider(provider_name: &str) -> Vec<(String, String)> {
|
||||||
"MiniMax M2.1 Lightning (fast)".to_string(),
|
"MiniMax M2.1 Lightning (fast)".to_string(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
"qwen" => vec![
|
||||||
|
(
|
||||||
|
"qwen-max".to_string(),
|
||||||
|
"Qwen Max (highest quality)".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"qwen-plus".to_string(),
|
||||||
|
"Qwen Plus (balanced default)".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"qwen-turbo".to_string(),
|
||||||
|
"Qwen Turbo (fast and cost-efficient)".to_string(),
|
||||||
|
),
|
||||||
|
],
|
||||||
"ollama" => vec![
|
"ollama" => vec![
|
||||||
(
|
(
|
||||||
"llama3.2".to_string(),
|
"llama3.2".to_string(),
|
||||||
|
|
@ -1306,7 +1335,7 @@ fn setup_provider(workspace_dir: &Path) -> Result<(String, String, String)> {
|
||||||
"⭐ Recommended (OpenRouter, Venice, Anthropic, OpenAI, Gemini)",
|
"⭐ Recommended (OpenRouter, Venice, Anthropic, OpenAI, Gemini)",
|
||||||
"⚡ Fast inference (Groq, Fireworks, Together AI, NVIDIA NIM)",
|
"⚡ Fast inference (Groq, Fireworks, Together AI, NVIDIA NIM)",
|
||||||
"🌐 Gateway / proxy (Vercel AI, Cloudflare AI, Amazon Bedrock)",
|
"🌐 Gateway / proxy (Vercel AI, Cloudflare AI, Amazon Bedrock)",
|
||||||
"🔬 Specialized (Moonshot/Kimi, GLM/Zhipu, MiniMax, Qianfan, Z.AI, Synthetic, OpenCode Zen, Cohere)",
|
"🔬 Specialized (Moonshot/Kimi, GLM/Zhipu, MiniMax, Qwen/DashScope, Qianfan, Z.AI, Synthetic, OpenCode Zen, Cohere)",
|
||||||
"🏠 Local / private (Ollama — no API key needed)",
|
"🏠 Local / private (Ollama — no API key needed)",
|
||||||
"🔧 Custom — bring your own OpenAI-compatible API",
|
"🔧 Custom — bring your own OpenAI-compatible API",
|
||||||
];
|
];
|
||||||
|
|
@ -1347,9 +1376,21 @@ fn setup_provider(workspace_dir: &Path) -> Result<(String, String, String)> {
|
||||||
("bedrock", "Amazon Bedrock — AWS managed models"),
|
("bedrock", "Amazon Bedrock — AWS managed models"),
|
||||||
],
|
],
|
||||||
3 => vec![
|
3 => vec![
|
||||||
("moonshot", "Moonshot — Kimi & Kimi Coding"),
|
("moonshot", "Moonshot — Kimi API (China endpoint)"),
|
||||||
("glm", "GLM — ChatGLM / Zhipu models"),
|
(
|
||||||
("minimax", "MiniMax — MiniMax AI models"),
|
"moonshot-intl",
|
||||||
|
"Moonshot — Kimi API (international endpoint)",
|
||||||
|
),
|
||||||
|
("glm", "GLM — ChatGLM / Zhipu (international endpoint)"),
|
||||||
|
("glm-cn", "GLM — ChatGLM / Zhipu (China endpoint)"),
|
||||||
|
(
|
||||||
|
"minimax",
|
||||||
|
"MiniMax — international endpoint (api.minimax.io)",
|
||||||
|
),
|
||||||
|
("minimax-cn", "MiniMax — China endpoint (api.minimaxi.com)"),
|
||||||
|
("qwen", "Qwen — DashScope China endpoint"),
|
||||||
|
("qwen-intl", "Qwen — DashScope international endpoint"),
|
||||||
|
("qwen-us", "Qwen — DashScope US endpoint"),
|
||||||
("qianfan", "Qianfan — Baidu AI models"),
|
("qianfan", "Qianfan — Baidu AI models"),
|
||||||
("zai", "Z.AI — Z.AI inference"),
|
("zai", "Z.AI — Z.AI inference"),
|
||||||
("synthetic", "Synthetic — Synthetic AI models"),
|
("synthetic", "Synthetic — Synthetic AI models"),
|
||||||
|
|
@ -1512,10 +1553,30 @@ fn setup_provider(workspace_dir: &Path) -> Result<(String, String, String)> {
|
||||||
"perplexity" => "https://www.perplexity.ai/settings/api",
|
"perplexity" => "https://www.perplexity.ai/settings/api",
|
||||||
"xai" => "https://console.x.ai",
|
"xai" => "https://console.x.ai",
|
||||||
"cohere" => "https://dashboard.cohere.com/api-keys",
|
"cohere" => "https://dashboard.cohere.com/api-keys",
|
||||||
"moonshot" => "https://platform.moonshot.cn/console/api-keys",
|
"moonshot" | "moonshot-intl" | "moonshot-global" | "moonshot-cn" | "kimi"
|
||||||
"glm" | "zhipu" => "https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys",
|
| "kimi-intl" | "kimi-global" | "kimi-cn" => {
|
||||||
"zai" | "z.ai" => "https://platform.z.ai/",
|
"https://platform.moonshot.cn/console/api-keys"
|
||||||
"minimax" => "https://www.minimaxi.com/user-center/basic-information",
|
}
|
||||||
|
"glm" | "zhipu" | "glm-global" | "zhipu-global" | "zai" | "z.ai" => {
|
||||||
|
"https://platform.z.ai/"
|
||||||
|
}
|
||||||
|
"glm-cn" | "zhipu-cn" | "bigmodel" => {
|
||||||
|
"https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys"
|
||||||
|
}
|
||||||
|
"minimax" | "minimax-intl" | "minimax-io" | "minimax-global" | "minimax-cn"
|
||||||
|
| "minimaxi" => "https://www.minimaxi.com/user-center/basic-information",
|
||||||
|
"qwen"
|
||||||
|
| "dashscope"
|
||||||
|
| "qwen-cn"
|
||||||
|
| "dashscope-cn"
|
||||||
|
| "qwen-intl"
|
||||||
|
| "dashscope-intl"
|
||||||
|
| "qwen-international"
|
||||||
|
| "dashscope-international"
|
||||||
|
| "qwen-us"
|
||||||
|
| "dashscope-us" => {
|
||||||
|
"https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key"
|
||||||
|
}
|
||||||
"vercel" => "https://vercel.com/account/tokens",
|
"vercel" => "https://vercel.com/account/tokens",
|
||||||
"cloudflare" => "https://dash.cloudflare.com/profile/api-tokens",
|
"cloudflare" => "https://dash.cloudflare.com/profile/api-tokens",
|
||||||
"nvidia" | "nvidia-nim" | "build.nvidia.com" => "https://build.nvidia.com/",
|
"nvidia" | "nvidia-nim" | "build.nvidia.com" => "https://build.nvidia.com/",
|
||||||
|
|
@ -1551,7 +1612,8 @@ fn setup_provider(workspace_dir: &Path) -> Result<(String, String, String)> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Model selection ──
|
// ── Model selection ──
|
||||||
let models: Vec<(&str, &str)> = match provider_name {
|
let canonical_provider = canonical_provider_name(provider_name);
|
||||||
|
let models: Vec<(&str, &str)> = match canonical_provider {
|
||||||
"openrouter" => vec![
|
"openrouter" => vec![
|
||||||
(
|
(
|
||||||
"anthropic/claude-sonnet-4",
|
"anthropic/claude-sonnet-4",
|
||||||
|
|
@ -1629,7 +1691,7 @@ fn setup_provider(workspace_dir: &Path) -> Result<(String, String, String)> {
|
||||||
"Mixtral 8x22B",
|
"Mixtral 8x22B",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
"together" => vec![
|
"together-ai" => vec![
|
||||||
(
|
(
|
||||||
"meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
|
"meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
|
||||||
"Llama 3.1 70B Turbo",
|
"Llama 3.1 70B Turbo",
|
||||||
|
|
@ -1660,6 +1722,11 @@ fn setup_provider(workspace_dir: &Path) -> Result<(String, String, String)> {
|
||||||
("glm-4-flash", "GLM-4 Flash (fast)"),
|
("glm-4-flash", "GLM-4 Flash (fast)"),
|
||||||
],
|
],
|
||||||
"minimax" => MINIMAX_ONBOARD_MODELS.to_vec(),
|
"minimax" => MINIMAX_ONBOARD_MODELS.to_vec(),
|
||||||
|
"qwen" => vec![
|
||||||
|
("qwen-plus", "Qwen Plus (balanced default)"),
|
||||||
|
("qwen-max", "Qwen Max (highest quality)"),
|
||||||
|
("qwen-turbo", "Qwen Turbo (fast and cost-efficient)"),
|
||||||
|
],
|
||||||
"ollama" => vec![
|
"ollama" => vec![
|
||||||
("llama3.2", "Llama 3.2 (recommended local)"),
|
("llama3.2", "Llama 3.2 (recommended local)"),
|
||||||
("mistral", "Mistral 7B"),
|
("mistral", "Mistral 7B"),
|
||||||
|
|
@ -1861,6 +1928,7 @@ fn provider_env_var(name: &str) -> &'static str {
|
||||||
"moonshot" | "kimi" => "MOONSHOT_API_KEY",
|
"moonshot" | "kimi" => "MOONSHOT_API_KEY",
|
||||||
"glm" | "zhipu" => "GLM_API_KEY",
|
"glm" | "zhipu" => "GLM_API_KEY",
|
||||||
"minimax" => "MINIMAX_API_KEY",
|
"minimax" => "MINIMAX_API_KEY",
|
||||||
|
"qwen" | "dashscope" => "DASHSCOPE_API_KEY",
|
||||||
"qianfan" | "baidu" => "QIANFAN_API_KEY",
|
"qianfan" | "baidu" => "QIANFAN_API_KEY",
|
||||||
"zai" | "z.ai" => "ZAI_API_KEY",
|
"zai" | "z.ai" => "ZAI_API_KEY",
|
||||||
"synthetic" => "SYNTHETIC_API_KEY",
|
"synthetic" => "SYNTHETIC_API_KEY",
|
||||||
|
|
@ -2384,7 +2452,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
|
||||||
if config.dingtalk.is_some() {
|
if config.dingtalk.is_some() {
|
||||||
"✅ connected"
|
"✅ connected"
|
||||||
} else {
|
} else {
|
||||||
"— 钉钉 Stream Mode"
|
"— DingTalk Stream Mode"
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
"Done — finish setup".to_string(),
|
"Done — finish setup".to_string(),
|
||||||
|
|
@ -3111,7 +3179,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
|
||||||
println!(
|
println!(
|
||||||
" {} {}",
|
" {} {}",
|
||||||
style("DingTalk Setup").white().bold(),
|
style("DingTalk Setup").white().bold(),
|
||||||
style("— 钉钉 Stream Mode").dim()
|
style("— DingTalk Stream Mode").dim()
|
||||||
);
|
);
|
||||||
print_bullet("1. Go to DingTalk developer console (open.dingtalk.com)");
|
print_bullet("1. Go to DingTalk developer console (open.dingtalk.com)");
|
||||||
print_bullet("2. Create an app and enable the Stream Mode bot");
|
print_bullet("2. Create an app and enable the Stream Mode bot");
|
||||||
|
|
@ -4313,6 +4381,10 @@ mod tests {
|
||||||
default_model_for_provider("anthropic"),
|
default_model_for_provider("anthropic"),
|
||||||
"claude-sonnet-4-5-20250929"
|
"claude-sonnet-4-5-20250929"
|
||||||
);
|
);
|
||||||
|
assert_eq!(default_model_for_provider("qwen"), "qwen-plus");
|
||||||
|
assert_eq!(default_model_for_provider("qwen-intl"), "qwen-plus");
|
||||||
|
assert_eq!(default_model_for_provider("glm-cn"), "glm-5");
|
||||||
|
assert_eq!(default_model_for_provider("minimax-cn"), "MiniMax-M2.5");
|
||||||
assert_eq!(default_model_for_provider("gemini"), "gemini-2.5-pro");
|
assert_eq!(default_model_for_provider("gemini"), "gemini-2.5-pro");
|
||||||
assert_eq!(default_model_for_provider("google"), "gemini-2.5-pro");
|
assert_eq!(default_model_for_provider("google"), "gemini-2.5-pro");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -4321,6 +4393,17 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn canonical_provider_name_normalizes_regional_aliases() {
|
||||||
|
assert_eq!(canonical_provider_name("qwen-intl"), "qwen");
|
||||||
|
assert_eq!(canonical_provider_name("dashscope-us"), "qwen");
|
||||||
|
assert_eq!(canonical_provider_name("moonshot-intl"), "moonshot");
|
||||||
|
assert_eq!(canonical_provider_name("kimi-cn"), "moonshot");
|
||||||
|
assert_eq!(canonical_provider_name("glm-cn"), "glm");
|
||||||
|
assert_eq!(canonical_provider_name("bigmodel"), "glm");
|
||||||
|
assert_eq!(canonical_provider_name("minimax-cn"), "minimax");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn curated_models_for_openai_include_latest_choices() {
|
fn curated_models_for_openai_include_latest_choices() {
|
||||||
let ids: Vec<String> = curated_models_for_provider("openai")
|
let ids: Vec<String> = curated_models_for_provider("openai")
|
||||||
|
|
@ -4372,6 +4455,18 @@ mod tests {
|
||||||
curated_models_for_provider("gemini"),
|
curated_models_for_provider("gemini"),
|
||||||
curated_models_for_provider("google-gemini")
|
curated_models_for_provider("google-gemini")
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
curated_models_for_provider("qwen"),
|
||||||
|
curated_models_for_provider("qwen-intl")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
curated_models_for_provider("qwen"),
|
||||||
|
curated_models_for_provider("dashscope-us")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
curated_models_for_provider("minimax"),
|
||||||
|
curated_models_for_provider("minimax-cn")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -4527,6 +4622,12 @@ mod tests {
|
||||||
assert_eq!(provider_env_var("google"), "GEMINI_API_KEY"); // alias
|
assert_eq!(provider_env_var("google"), "GEMINI_API_KEY"); // alias
|
||||||
assert_eq!(provider_env_var("google-gemini"), "GEMINI_API_KEY"); // alias
|
assert_eq!(provider_env_var("google-gemini"), "GEMINI_API_KEY"); // alias
|
||||||
assert_eq!(provider_env_var("gemini"), "GEMINI_API_KEY");
|
assert_eq!(provider_env_var("gemini"), "GEMINI_API_KEY");
|
||||||
|
assert_eq!(provider_env_var("qwen"), "DASHSCOPE_API_KEY");
|
||||||
|
assert_eq!(provider_env_var("qwen-intl"), "DASHSCOPE_API_KEY");
|
||||||
|
assert_eq!(provider_env_var("dashscope-us"), "DASHSCOPE_API_KEY");
|
||||||
|
assert_eq!(provider_env_var("glm-cn"), "GLM_API_KEY");
|
||||||
|
assert_eq!(provider_env_var("minimax-cn"), "MINIMAX_API_KEY");
|
||||||
|
assert_eq!(provider_env_var("moonshot-intl"), "MOONSHOT_API_KEY");
|
||||||
assert_eq!(provider_env_var("nvidia"), "NVIDIA_API_KEY");
|
assert_eq!(provider_env_var("nvidia"), "NVIDIA_API_KEY");
|
||||||
assert_eq!(provider_env_var("nvidia-nim"), "NVIDIA_API_KEY"); // alias
|
assert_eq!(provider_env_var("nvidia-nim"), "NVIDIA_API_KEY"); // alias
|
||||||
assert_eq!(provider_env_var("build.nvidia.com"), "NVIDIA_API_KEY"); // alias
|
assert_eq!(provider_env_var("build.nvidia.com"), "NVIDIA_API_KEY"); // alias
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,52 @@ use compatible::{AuthStyle, OpenAiCompatibleProvider};
|
||||||
use reliable::ReliableProvider;
|
use reliable::ReliableProvider;
|
||||||
|
|
||||||
const MAX_API_ERROR_CHARS: usize = 200;
|
const MAX_API_ERROR_CHARS: usize = 200;
|
||||||
|
const MINIMAX_INTL_BASE_URL: &str = "https://api.minimax.io/v1";
|
||||||
|
const MINIMAX_CN_BASE_URL: &str = "https://api.minimaxi.com/v1";
|
||||||
|
const GLM_GLOBAL_BASE_URL: &str = "https://api.z.ai/api/paas/v4";
|
||||||
|
const GLM_CN_BASE_URL: &str = "https://open.bigmodel.cn/api/paas/v4";
|
||||||
|
const MOONSHOT_INTL_BASE_URL: &str = "https://api.moonshot.ai/v1";
|
||||||
|
const MOONSHOT_CN_BASE_URL: &str = "https://api.moonshot.cn/v1";
|
||||||
|
const QWEN_CN_BASE_URL: &str = "https://dashscope.aliyuncs.com/compatible-mode/v1";
|
||||||
|
const QWEN_INTL_BASE_URL: &str = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
|
||||||
|
const QWEN_US_BASE_URL: &str = "https://dashscope-us.aliyuncs.com/compatible-mode/v1";
|
||||||
|
|
||||||
|
fn minimax_base_url(name: &str) -> Option<&'static str> {
|
||||||
|
match name {
|
||||||
|
"minimax" | "minimax-intl" | "minimax-io" | "minimax-global" => Some(MINIMAX_INTL_BASE_URL),
|
||||||
|
"minimax-cn" | "minimaxi" => Some(MINIMAX_CN_BASE_URL),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn glm_base_url(name: &str) -> Option<&'static str> {
|
||||||
|
match name {
|
||||||
|
"glm" | "zhipu" | "glm-global" | "zhipu-global" => Some(GLM_GLOBAL_BASE_URL),
|
||||||
|
"glm-cn" | "zhipu-cn" | "bigmodel" => Some(GLM_CN_BASE_URL),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn moonshot_base_url(name: &str) -> Option<&'static str> {
|
||||||
|
match name {
|
||||||
|
"moonshot-intl" | "moonshot-global" | "kimi-intl" | "kimi-global" => {
|
||||||
|
Some(MOONSHOT_INTL_BASE_URL)
|
||||||
|
}
|
||||||
|
"moonshot" | "kimi" | "moonshot-cn" | "kimi-cn" => Some(MOONSHOT_CN_BASE_URL),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn qwen_base_url(name: &str) -> Option<&'static str> {
|
||||||
|
match name {
|
||||||
|
"qwen" | "dashscope" | "qwen-cn" | "dashscope-cn" => Some(QWEN_CN_BASE_URL),
|
||||||
|
"qwen-intl" | "dashscope-intl" | "qwen-international" | "dashscope-international" => {
|
||||||
|
Some(QWEN_INTL_BASE_URL)
|
||||||
|
}
|
||||||
|
"qwen-us" | "dashscope-us" => Some(QWEN_US_BASE_URL),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_secret_char(c: char) -> bool {
|
fn is_secret_char(c: char) -> bool {
|
||||||
c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.' | ':')
|
c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.' | ':')
|
||||||
|
|
@ -135,13 +181,24 @@ fn resolve_provider_credential(name: &str, credential_override: Option<&str>) ->
|
||||||
"fireworks" | "fireworks-ai" => vec!["FIREWORKS_API_KEY"],
|
"fireworks" | "fireworks-ai" => vec!["FIREWORKS_API_KEY"],
|
||||||
"perplexity" => vec!["PERPLEXITY_API_KEY"],
|
"perplexity" => vec!["PERPLEXITY_API_KEY"],
|
||||||
"cohere" => vec!["COHERE_API_KEY"],
|
"cohere" => vec!["COHERE_API_KEY"],
|
||||||
"moonshot" | "kimi" => vec!["MOONSHOT_API_KEY"],
|
"moonshot" | "kimi" | "moonshot-intl" | "moonshot-global" | "moonshot-cn" | "kimi-intl"
|
||||||
"glm" | "zhipu" => vec!["GLM_API_KEY"],
|
| "kimi-global" | "kimi-cn" => vec!["MOONSHOT_API_KEY"],
|
||||||
"minimax" => vec!["MINIMAX_API_KEY"],
|
"glm" | "zhipu" | "glm-global" | "zhipu-global" | "glm-cn" | "zhipu-cn" | "bigmodel" => {
|
||||||
"qianfan" | "baidu" => vec!["QIANFAN_API_KEY"],
|
vec!["GLM_API_KEY"]
|
||||||
"qwen" | "dashscope" | "qwen-intl" | "dashscope-intl" | "qwen-us" | "dashscope-us" => {
|
|
||||||
vec!["DASHSCOPE_API_KEY"]
|
|
||||||
}
|
}
|
||||||
|
"minimax" | "minimax-intl" | "minimax-io" | "minimax-global" | "minimax-cn"
|
||||||
|
| "minimaxi" => vec!["MINIMAX_API_KEY"],
|
||||||
|
"qianfan" | "baidu" => vec!["QIANFAN_API_KEY"],
|
||||||
|
"qwen"
|
||||||
|
| "dashscope"
|
||||||
|
| "qwen-cn"
|
||||||
|
| "dashscope-cn"
|
||||||
|
| "qwen-intl"
|
||||||
|
| "dashscope-intl"
|
||||||
|
| "qwen-international"
|
||||||
|
| "dashscope-international"
|
||||||
|
| "qwen-us"
|
||||||
|
| "dashscope-us" => vec!["DASHSCOPE_API_KEY"],
|
||||||
"zai" | "z.ai" => vec!["ZAI_API_KEY"],
|
"zai" | "z.ai" => vec!["ZAI_API_KEY"],
|
||||||
"nvidia" | "nvidia-nim" | "build.nvidia.com" => vec!["NVIDIA_API_KEY"],
|
"nvidia" | "nvidia-nim" | "build.nvidia.com" => vec!["NVIDIA_API_KEY"],
|
||||||
"synthetic" => vec!["SYNTHETIC_API_KEY"],
|
"synthetic" => vec!["SYNTHETIC_API_KEY"],
|
||||||
|
|
@ -235,8 +292,11 @@ pub fn create_provider_with_url(
|
||||||
key,
|
key,
|
||||||
AuthStyle::Bearer,
|
AuthStyle::Bearer,
|
||||||
))),
|
))),
|
||||||
"moonshot" | "kimi" => Ok(Box::new(OpenAiCompatibleProvider::new(
|
name if moonshot_base_url(name).is_some() => Ok(Box::new(OpenAiCompatibleProvider::new(
|
||||||
"Moonshot", "https://api.moonshot.cn", key, AuthStyle::Bearer,
|
"Moonshot",
|
||||||
|
moonshot_base_url(name).expect("checked in guard"),
|
||||||
|
key,
|
||||||
|
AuthStyle::Bearer,
|
||||||
))),
|
))),
|
||||||
"synthetic" => Ok(Box::new(OpenAiCompatibleProvider::new(
|
"synthetic" => Ok(Box::new(OpenAiCompatibleProvider::new(
|
||||||
"Synthetic", "https://api.synthetic.com", key, AuthStyle::Bearer,
|
"Synthetic", "https://api.synthetic.com", key, AuthStyle::Bearer,
|
||||||
|
|
@ -247,12 +307,17 @@ pub fn create_provider_with_url(
|
||||||
"zai" | "z.ai" => Ok(Box::new(OpenAiCompatibleProvider::new(
|
"zai" | "z.ai" => Ok(Box::new(OpenAiCompatibleProvider::new(
|
||||||
"Z.AI", "https://api.z.ai/api/coding/paas/v4", key, AuthStyle::Bearer,
|
"Z.AI", "https://api.z.ai/api/coding/paas/v4", key, AuthStyle::Bearer,
|
||||||
))),
|
))),
|
||||||
"glm" | "zhipu" => Ok(Box::new(OpenAiCompatibleProvider::new_no_responses_fallback(
|
name if glm_base_url(name).is_some() => {
|
||||||
"GLM", "https://api.z.ai/api/paas/v4", key, AuthStyle::Bearer,
|
Ok(Box::new(OpenAiCompatibleProvider::new_no_responses_fallback(
|
||||||
))),
|
"GLM",
|
||||||
"minimax" => Ok(Box::new(OpenAiCompatibleProvider::new(
|
glm_base_url(name).expect("checked in guard"),
|
||||||
|
key,
|
||||||
|
AuthStyle::Bearer,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
name if minimax_base_url(name).is_some() => Ok(Box::new(OpenAiCompatibleProvider::new(
|
||||||
"MiniMax",
|
"MiniMax",
|
||||||
"https://api.minimaxi.com/v1",
|
minimax_base_url(name).expect("checked in guard"),
|
||||||
key,
|
key,
|
||||||
AuthStyle::Bearer,
|
AuthStyle::Bearer,
|
||||||
))),
|
))),
|
||||||
|
|
@ -265,14 +330,11 @@ pub fn create_provider_with_url(
|
||||||
"qianfan" | "baidu" => Ok(Box::new(OpenAiCompatibleProvider::new(
|
"qianfan" | "baidu" => Ok(Box::new(OpenAiCompatibleProvider::new(
|
||||||
"Qianfan", "https://aip.baidubce.com", key, AuthStyle::Bearer,
|
"Qianfan", "https://aip.baidubce.com", key, AuthStyle::Bearer,
|
||||||
))),
|
))),
|
||||||
"qwen" | "dashscope" => Ok(Box::new(OpenAiCompatibleProvider::new(
|
name if qwen_base_url(name).is_some() => Ok(Box::new(OpenAiCompatibleProvider::new(
|
||||||
"Qwen", "https://dashscope.aliyuncs.com/compatible-mode/v1", key, AuthStyle::Bearer,
|
"Qwen",
|
||||||
))),
|
qwen_base_url(name).expect("checked in guard"),
|
||||||
"qwen-intl" | "dashscope-intl" => Ok(Box::new(OpenAiCompatibleProvider::new(
|
key,
|
||||||
"Qwen", "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", key, AuthStyle::Bearer,
|
AuthStyle::Bearer,
|
||||||
))),
|
|
||||||
"qwen-us" | "dashscope-us" => Ok(Box::new(OpenAiCompatibleProvider::new(
|
|
||||||
"Qwen", "https://dashscope-us.aliyuncs.com/compatible-mode/v1", key, AuthStyle::Bearer,
|
|
||||||
))),
|
))),
|
||||||
|
|
||||||
// ── Extended ecosystem (community favorites) ─────────
|
// ── Extended ecosystem (community favorites) ─────────
|
||||||
|
|
@ -492,6 +554,31 @@ mod tests {
|
||||||
assert_eq!(resolved, Some("explicit-key".to_string()));
|
assert_eq!(resolved, Some("explicit-key".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn regional_endpoint_aliases_map_to_expected_urls() {
|
||||||
|
assert_eq!(minimax_base_url("minimax"), Some(MINIMAX_INTL_BASE_URL));
|
||||||
|
assert_eq!(
|
||||||
|
minimax_base_url("minimax-intl"),
|
||||||
|
Some(MINIMAX_INTL_BASE_URL)
|
||||||
|
);
|
||||||
|
assert_eq!(minimax_base_url("minimax-cn"), Some(MINIMAX_CN_BASE_URL));
|
||||||
|
|
||||||
|
assert_eq!(glm_base_url("glm"), Some(GLM_GLOBAL_BASE_URL));
|
||||||
|
assert_eq!(glm_base_url("glm-cn"), Some(GLM_CN_BASE_URL));
|
||||||
|
assert_eq!(glm_base_url("bigmodel"), Some(GLM_CN_BASE_URL));
|
||||||
|
|
||||||
|
assert_eq!(moonshot_base_url("moonshot"), Some(MOONSHOT_CN_BASE_URL));
|
||||||
|
assert_eq!(
|
||||||
|
moonshot_base_url("moonshot-intl"),
|
||||||
|
Some(MOONSHOT_INTL_BASE_URL)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(qwen_base_url("qwen"), Some(QWEN_CN_BASE_URL));
|
||||||
|
assert_eq!(qwen_base_url("qwen-cn"), Some(QWEN_CN_BASE_URL));
|
||||||
|
assert_eq!(qwen_base_url("qwen-intl"), Some(QWEN_INTL_BASE_URL));
|
||||||
|
assert_eq!(qwen_base_url("qwen-us"), Some(QWEN_US_BASE_URL));
|
||||||
|
}
|
||||||
|
|
||||||
// ── Primary providers ────────────────────────────────────
|
// ── Primary providers ────────────────────────────────────
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -550,6 +637,10 @@ mod tests {
|
||||||
fn factory_moonshot() {
|
fn factory_moonshot() {
|
||||||
assert!(create_provider("moonshot", Some("key")).is_ok());
|
assert!(create_provider("moonshot", Some("key")).is_ok());
|
||||||
assert!(create_provider("kimi", Some("key")).is_ok());
|
assert!(create_provider("kimi", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("moonshot-intl", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("moonshot-cn", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("kimi-intl", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("kimi-cn", Some("key")).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -573,11 +664,19 @@ mod tests {
|
||||||
fn factory_glm() {
|
fn factory_glm() {
|
||||||
assert!(create_provider("glm", Some("key")).is_ok());
|
assert!(create_provider("glm", Some("key")).is_ok());
|
||||||
assert!(create_provider("zhipu", Some("key")).is_ok());
|
assert!(create_provider("zhipu", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("glm-cn", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("zhipu-cn", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("glm-global", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("bigmodel", Some("key")).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn factory_minimax() {
|
fn factory_minimax() {
|
||||||
assert!(create_provider("minimax", Some("key")).is_ok());
|
assert!(create_provider("minimax", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("minimax-intl", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("minimax-io", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("minimax-cn", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("minimaxi", Some("key")).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -596,8 +695,12 @@ mod tests {
|
||||||
fn factory_qwen() {
|
fn factory_qwen() {
|
||||||
assert!(create_provider("qwen", Some("key")).is_ok());
|
assert!(create_provider("qwen", Some("key")).is_ok());
|
||||||
assert!(create_provider("dashscope", Some("key")).is_ok());
|
assert!(create_provider("dashscope", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("qwen-cn", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("dashscope-cn", Some("key")).is_ok());
|
||||||
assert!(create_provider("qwen-intl", Some("key")).is_ok());
|
assert!(create_provider("qwen-intl", Some("key")).is_ok());
|
||||||
assert!(create_provider("dashscope-intl", Some("key")).is_ok());
|
assert!(create_provider("dashscope-intl", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("qwen-international", Some("key")).is_ok());
|
||||||
|
assert!(create_provider("dashscope-international", Some("key")).is_ok());
|
||||||
assert!(create_provider("qwen-us", Some("key")).is_ok());
|
assert!(create_provider("qwen-us", Some("key")).is_ok());
|
||||||
assert!(create_provider("dashscope-us", Some("key")).is_ok());
|
assert!(create_provider("dashscope-us", Some("key")).is_ok());
|
||||||
}
|
}
|
||||||
|
|
@ -860,15 +963,20 @@ mod tests {
|
||||||
"vercel",
|
"vercel",
|
||||||
"cloudflare",
|
"cloudflare",
|
||||||
"moonshot",
|
"moonshot",
|
||||||
|
"moonshot-intl",
|
||||||
|
"moonshot-cn",
|
||||||
"synthetic",
|
"synthetic",
|
||||||
"opencode",
|
"opencode",
|
||||||
"zai",
|
"zai",
|
||||||
"glm",
|
"glm",
|
||||||
|
"glm-cn",
|
||||||
"minimax",
|
"minimax",
|
||||||
|
"minimax-cn",
|
||||||
"bedrock",
|
"bedrock",
|
||||||
"qianfan",
|
"qianfan",
|
||||||
"qwen",
|
"qwen",
|
||||||
"qwen-intl",
|
"qwen-intl",
|
||||||
|
"qwen-cn",
|
||||||
"qwen-us",
|
"qwen-us",
|
||||||
"lmstudio",
|
"lmstudio",
|
||||||
"groq",
|
"groq",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue