From 2a074e5ec3fc1b8f2cf2c9f71def5dd1dd959560 Mon Sep 17 00:00:00 2001 From: bowen628 Date: Sun, 26 Jul 2026 19:15:59 +0800 Subject: [PATCH 1/7] feat(models): refresh preset provider catalog and model lists Align built-in provider presets with current provider offerings (referencing opencode's models.dev catalog): - Update preset model names to current generations (Claude 5 family, GLM-5.x, Kimi K3, Qwen 3.7, MiniMax M3, DeepSeek V4, Doubao Seed 2.1, Gemini 3.5/3.6) - Aggregate China/international endpoints and API/coding-plan endpoints under each provider via baseUrlOptions (Zhipu+Z.AI, DashScope+intl, MiniMax cn/io, Moonshot cn/ai + Kimi For Coding, SiliconFlow cn/com, Volcengine + BytePlus + coding plan, DeepSeek Anthropic endpoint) - Extend provider URL catalog for template inference from new endpoints - Localize new base URL option notes (zh-CN, zh-TW, en-US; installer synced) - Recognize Claude 5 family adaptive reasoning (web-ui gating and Anthropic adapter thinking capability for claude-fable) - Extend URL quirks to intl endpoints (dashscope-intl, siliconflow.com, api.z.ai tool_stream) - Update CLI provider selector presets --- BitFun-Installer/src/data/modelProviders.ts | 112 ++++++++++++++++-- BitFun-Installer/src/i18n/locales/en.json | 68 ++++++++--- BitFun-Installer/src/i18n/locales/zh-TW.json | 50 ++++++-- BitFun-Installer/src/i18n/locales/zh.json | 68 ++++++++--- src/apps/cli/src/ui/provider_selector.rs | 33 +++--- .../adapters/ai-adapters/src/client/quirks.rs | 6 +- .../src/providers/anthropic/request.rs | 2 +- .../config/services/modelConfigs.ts | 58 ++++++--- .../config/services/providerCatalog.ts | 24 ++++ .../infrastructure/config/utils/reasoning.ts | 5 + .../src/locales/en-US/settings/ai-model.json | 53 +++++++-- .../src/locales/zh-CN/settings/ai-model.json | 53 +++++++-- .../src/locales/zh-TW/settings/ai-model.json | 53 +++++++-- 13 files changed, 468 insertions(+), 117 deletions(-) diff --git a/BitFun-Installer/src/data/modelProviders.ts b/BitFun-Installer/src/data/modelProviders.ts index ab07d121a2..cb689ee6ae 100644 --- a/BitFun-Installer/src/data/modelProviders.ts +++ b/BitFun-Installer/src/data/modelProviders.ts @@ -51,7 +51,7 @@ export const PROVIDER_TEMPLATES: Record = { descriptionKey: 'model.providers.gemini.description', baseUrl: 'https://generativelanguage.googleapis.com', format: 'gemini', - models: ['gemini-3.1-pro-preview', 'gemini-3.1-flash-lite-preview'], + models: ['gemini-3.1-pro-preview', 'gemini-3.6-flash', 'gemini-3.5-flash-lite'], helpUrl: 'https://aistudio.google.com/app/apikey', }, anthropic: { @@ -60,7 +60,7 @@ export const PROVIDER_TEMPLATES: Record = { descriptionKey: 'model.providers.anthropic.description', baseUrl: 'https://api.anthropic.com', format: 'anthropic', - models: ['claude-opus-4-6', 'claude-sonnet-4-6'], + models: ['claude-opus-5', 'claude-fable-5', 'claude-sonnet-5', 'claude-haiku-4-5'], helpUrl: 'https://console.anthropic.com/', }, minimax: { @@ -69,7 +69,7 @@ export const PROVIDER_TEMPLATES: Record = { descriptionKey: 'model.providers.minimax.description', baseUrl: 'https://api.minimaxi.com/anthropic', format: 'anthropic', - models: ['MiniMax-M2.7-highspeed', 'MiniMax-M2.5-highspeed'], + models: ['MiniMax-M3', 'MiniMax-M2.7', 'MiniMax-M2.7-highspeed'], helpUrl: 'https://platform.minimax.io/', baseUrlOptions: [ { @@ -82,6 +82,16 @@ export const PROVIDER_TEMPLATES: Record = { format: 'openai', noteKey: 'model.providers.minimax.urlOptions.openai', }, + { + url: 'https://api.minimax.io/anthropic', + format: 'anthropic', + noteKey: 'model.providers.minimax.urlOptions.intl', + }, + { + url: 'https://api.minimax.io/v1', + format: 'openai', + noteKey: 'model.providers.minimax.urlOptions.intlOpenai', + }, ], }, moonshot: { @@ -90,8 +100,35 @@ export const PROVIDER_TEMPLATES: Record = { descriptionKey: 'model.providers.moonshot.description', baseUrl: 'https://api.moonshot.cn/v1', format: 'openai', - models: ['kimi-k2.5', 'kimi-k2', 'kimi-k2-thinking'], + models: ['kimi-k3', 'kimi-k2.7-code', 'kimi-k2.6', 'kimi-for-coding'], helpUrl: 'https://platform.moonshot.ai/console', + baseUrlOptions: [ + { + url: 'https://api.moonshot.cn/v1', + format: 'openai', + noteKey: 'model.providers.moonshot.urlOptions.default', + }, + { + url: 'https://api.moonshot.cn/anthropic', + format: 'anthropic', + noteKey: 'model.providers.moonshot.urlOptions.anthropic', + }, + { + url: 'https://api.moonshot.ai/v1', + format: 'openai', + noteKey: 'model.providers.moonshot.urlOptions.intl', + }, + { + url: 'https://api.moonshot.ai/anthropic', + format: 'anthropic', + noteKey: 'model.providers.moonshot.urlOptions.intlAnthropic', + }, + { + url: 'https://api.kimi.com/coding', + format: 'anthropic', + noteKey: 'model.providers.moonshot.urlOptions.kimiForCoding', + }, + ], }, deepseek: { id: 'deepseek', @@ -101,6 +138,18 @@ export const PROVIDER_TEMPLATES: Record = { format: 'openai', models: ['deepseek-v4-flash', 'deepseek-v4-pro'], helpUrl: 'https://platform.deepseek.com/api_keys', + baseUrlOptions: [ + { + url: 'https://api.deepseek.com/v1', + format: 'openai', + noteKey: 'model.providers.deepseek.urlOptions.default', + }, + { + url: 'https://api.deepseek.com/anthropic', + format: 'anthropic', + noteKey: 'model.providers.deepseek.urlOptions.anthropic', + }, + ], }, zhipu: { id: 'zhipu', @@ -108,7 +157,7 @@ export const PROVIDER_TEMPLATES: Record = { descriptionKey: 'model.providers.zhipu.description', baseUrl: 'https://open.bigmodel.cn/api/paas/v4', format: 'openai', - models: ['glm-5', 'glm-4.7'], + models: ['glm-5.2', 'glm-5.1', 'glm-5'], helpUrl: 'https://open.bigmodel.cn/usercenter/apikeys', baseUrlOptions: [ { @@ -126,6 +175,21 @@ export const PROVIDER_TEMPLATES: Record = { format: 'openai', noteKey: 'model.providers.zhipu.urlOptions.codingPlan', }, + { + url: 'https://api.z.ai/api/paas/v4', + format: 'openai', + noteKey: 'model.providers.zhipu.urlOptions.intl', + }, + { + url: 'https://api.z.ai/api/anthropic', + format: 'anthropic', + noteKey: 'model.providers.zhipu.urlOptions.intlAnthropic', + }, + { + url: 'https://api.z.ai/api/coding/paas/v4', + format: 'openai', + noteKey: 'model.providers.zhipu.urlOptions.intlCodingPlan', + }, ], }, qwen: { @@ -134,7 +198,7 @@ export const PROVIDER_TEMPLATES: Record = { descriptionKey: 'model.providers.qwen.description', baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', format: 'openai', - models: ['Qwen3.5-Plus', 'Qwen3.5-Flash'], + models: ['qwen3.7-plus', 'qwen3.7-max', 'qwen3.6-flash'], helpUrl: 'https://dashscope.console.aliyun.com/apiKey', baseUrlOptions: [ { @@ -142,6 +206,11 @@ export const PROVIDER_TEMPLATES: Record = { format: 'openai', noteKey: 'model.providers.qwen.urlOptions.default', }, + { + url: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1', + format: 'openai', + noteKey: 'model.providers.qwen.urlOptions.intl', + }, { url: 'https://coding.dashscope.aliyuncs.com/v1', format: 'openai', @@ -152,6 +221,11 @@ export const PROVIDER_TEMPLATES: Record = { format: 'anthropic', noteKey: 'model.providers.qwen.urlOptions.codingPlanAnthropic', }, + { + url: 'https://coding-intl.dashscope.aliyuncs.com/v1', + format: 'openai', + noteKey: 'model.providers.qwen.urlOptions.intlCodingPlan', + }, ], }, volcengine: { @@ -160,8 +234,25 @@ export const PROVIDER_TEMPLATES: Record = { descriptionKey: 'model.providers.volcengine.description', baseUrl: 'https://ark.cn-beijing.volces.com/api/v3', format: 'openai', - models: ['doubao-seed-2-0-code-preview-260215', 'doubao-seed-2-0-pro-260215'], + models: ['doubao-seed-2-1-pro-260628', 'doubao-seed-2-0-code-preview-260215', 'doubao-seed-2-0-pro-260215'], helpUrl: 'https://console.volcengine.com/ark/', + baseUrlOptions: [ + { + url: 'https://ark.cn-beijing.volces.com/api/v3', + format: 'openai', + noteKey: 'model.providers.volcengine.urlOptions.default', + }, + { + url: 'https://ark.cn-beijing.volces.com/api/coding/v3', + format: 'openai', + noteKey: 'model.providers.volcengine.urlOptions.codingPlan', + }, + { + url: 'https://ark.ap-southeast.bytepluses.com/api/v3', + format: 'openai', + noteKey: 'model.providers.volcengine.urlOptions.intl', + }, + ], }, siliconflow: { id: 'siliconflow', @@ -169,7 +260,7 @@ export const PROVIDER_TEMPLATES: Record = { descriptionKey: 'model.providers.siliconflow.description', baseUrl: 'https://api.siliconflow.cn/v1', format: 'openai', - models: [], + models: ['zai-org/GLM-5.2', 'deepseek-ai/DeepSeek-V4-Pro', 'deepseek-ai/DeepSeek-V4-Flash'], helpUrl: 'https://cloud.siliconflow.cn/account/ak', baseUrlOptions: [ { @@ -182,6 +273,11 @@ export const PROVIDER_TEMPLATES: Record = { format: 'anthropic', noteKey: 'model.providers.siliconflow.urlOptions.anthropic', }, + { + url: 'https://api.siliconflow.com/v1', + format: 'openai', + noteKey: 'model.providers.siliconflow.urlOptions.intl', + }, ], }, nvidia: { diff --git a/BitFun-Installer/src/i18n/locales/en.json b/BitFun-Installer/src/i18n/locales/en.json index 7a442f200a..4f8ee0e8de 100644 --- a/BitFun-Installer/src/i18n/locales/en.json +++ b/BitFun-Installer/src/i18n/locales/en.json @@ -70,46 +70,70 @@ "name": "MiniMax", "description": "MiniMax series models", "urlOptions": { - "default": "Anthropic Format - Default", - "openai": "OpenAI Compatible Format" + "default": "China - Anthropic Format - Default", + "openai": "China - OpenAI Compatible", + "intl": "Global - Anthropic Format", + "intlOpenai": "Global - OpenAI Compatible" } }, "moonshot": { "name": "Moonshot AI", - "description": "Moonshot Kimi series models" + "description": "Moonshot Kimi series models", + "urlOptions": { + "default": "China - OpenAI Format - Default", + "anthropic": "China - Anthropic Format", + "intl": "Global - OpenAI Format", + "intlAnthropic": "Global - Anthropic Format", + "kimiForCoding": "Kimi For Coding Plan" + } }, "deepseek": { "name": "DeepSeek", - "description": "DeepSeek series models" + "description": "DeepSeek series models", + "urlOptions": { + "default": "OpenAI Format - Default", + "anthropic": "Anthropic Format" + } }, "zhipu": { "name": "Zhipu AI", "description": "Zhipu AI GLM series models", "urlOptions": { - "default": "OpenAI Format - Default", - "anthropic": "Anthropic Format", - "codingPlan": "OpenAI Format - CodingPlan" + "default": "China - OpenAI Format - Default", + "anthropic": "China - Anthropic Format", + "codingPlan": "China - Coding Plan", + "intl": "Global (Z.AI) - OpenAI Format", + "intlAnthropic": "Global (Z.AI) - Anthropic Format", + "intlCodingPlan": "Global (Z.AI) - Coding Plan" } }, "qwen": { "name": "Qwen", "description": "Alibaba Cloud Bailian Model Platform", "urlOptions": { - "default": "OpenAI Format - Default", - "codingPlan": "OpenAI Format - Coding Plan", - "codingPlanAnthropic": "Anthropic Format - Coding Plan" + "default": "China - OpenAI Format - Default", + "codingPlan": "China - Coding Plan", + "codingPlanAnthropic": "China - Coding Plan - Anthropic Format", + "intl": "Global (International) - OpenAI Format", + "intlCodingPlan": "Global - Coding Plan" } }, "volcengine": { "name": "Volcano Engine", - "description": "ByteDance Volcano Engine Model Platform" + "description": "ByteDance Volcano Engine Model Platform", + "urlOptions": { + "default": "China - Default", + "codingPlan": "China - Coding Plan", + "intl": "Global (BytePlus)" + } }, "siliconflow": { "name": "SiliconFlow", "description": "SiliconFlow Model Platform", "urlOptions": { - "default": "OpenAI Format - Default", - "anthropic": "Anthropic Format" + "default": "China - OpenAI Format - Default", + "anthropic": "China - Anthropic Format", + "intl": "Global - OpenAI Format" } }, "nvidia": { @@ -139,7 +163,8 @@ "provider": "Request Format", "providerPlaceholder": "Select request format", "modelSelection": "Select Models", - "resolvedUrlLabel": "Request URL: " + "resolvedUrlLabel": "Request URL: ", + "modelName": "Model Name" }, "formats": { "openaiCompatible": "OpenAI Compatible", @@ -148,7 +173,20 @@ "geminiApi": "Gemini GenerateContent API" }, "showSecret": "Show", - "hideSecret": "Hide" + "hideSecret": "Hide", + "getApiKey": "Get API Key", + "modelNoResults": "No matching models", + "customRequestBodyPlaceholder": "Example:\n{\n \"temperature\": 0.8,\n \"top_p\": 0.9\n}", + "jsonValid": "JSON format is valid", + "jsonInvalid": "Invalid JSON format, please check syntax", + "skipSslVerify": "Skip SSL Certificate Verification", + "customHeadersModeMerge": "Merge", + "customHeadersModeReplace": "Replace", + "addHeader": "Add Field", + "headerKey": "key", + "headerValue": "value", + "advancedShow": "Show advanced settings", + "advancedHide": "Hide advanced settings" }, "progress": { "title": "Installing", diff --git a/BitFun-Installer/src/i18n/locales/zh-TW.json b/BitFun-Installer/src/i18n/locales/zh-TW.json index e09da22422..1e493e5bd8 100644 --- a/BitFun-Installer/src/i18n/locales/zh-TW.json +++ b/BitFun-Installer/src/i18n/locales/zh-TW.json @@ -70,46 +70,70 @@ "name": "MiniMax", "description": "MiniMax 系列模型", "urlOptions": { - "default": "Anthropic 格式-默認", - "openai": "OpenAI 相容格式" + "default": "國內-Anthropic格式-默認", + "openai": "國內-OpenAI兼容格式", + "intl": "海外-Anthropic格式", + "intlOpenai": "海外-OpenAI兼容格式" } }, "moonshot": { "name": "月之暗面", - "description": "月之暗面 Kimi 系列模型" + "description": "月之暗面 Kimi 系列模型", + "urlOptions": { + "default": "國內-OpenAI格式-默認", + "anthropic": "國內-Anthropic格式", + "intl": "海外-OpenAI格式", + "intlAnthropic": "海外-Anthropic格式", + "kimiForCoding": "Kimi For Coding 套餐" + } }, "deepseek": { "name": "DeepSeek", - "description": "DeepSeek 系列模型" + "description": "DeepSeek 系列模型", + "urlOptions": { + "default": "OpenAI格式-默認", + "anthropic": "Anthropic格式" + } }, "zhipu": { "name": "智譜AI", "description": "智譜AI GLM 系列模型", "urlOptions": { - "default": "OpenAI 格式-默認", - "anthropic": "Anthropic 格式", - "codingPlan": "OpenAI 格式-CodingPlan" + "default": "國內-OpenAI格式-默認", + "anthropic": "國內-Anthropic格式", + "codingPlan": "國內-Coding Plan", + "intl": "海外(Z.AI)-OpenAI格式", + "intlAnthropic": "海外(Z.AI)-Anthropic格式", + "intlCodingPlan": "海外(Z.AI)-Coding Plan" } }, "qwen": { "name": "通義千問", "description": "阿里雲百鍊大模型平臺", "urlOptions": { - "default": "OpenAI 格式-默認", - "codingPlan": "OpenAI 格式-Coding Plan", - "codingPlanAnthropic": "Anthropic 格式-Coding Plan" + "default": "國內-OpenAI格式-默認", + "intl": "海外(國際站)-OpenAI格式", + "codingPlan": "國內-Coding Plan", + "codingPlanAnthropic": "國內-Coding Plan-Anthropic格式", + "intlCodingPlan": "海外-Coding Plan" } }, "volcengine": { "name": "火山引擎", - "description": "字節跳動火山引擎大模型平臺" + "description": "字節跳動火山引擎大模型平臺", + "urlOptions": { + "default": "國內-默認", + "codingPlan": "國內-Coding Plan", + "intl": "海外(BytePlus)" + } }, "siliconflow": { "name": "硅基流動", "description": "硅基流動大模型平臺", "urlOptions": { - "default": "OpenAI 格式-默認", - "anthropic": "Anthropic 格式" + "default": "國內-OpenAI格式-默認", + "anthropic": "國內-Anthropic格式", + "intl": "海外-OpenAI格式" } }, "nvidia": { diff --git a/BitFun-Installer/src/i18n/locales/zh.json b/BitFun-Installer/src/i18n/locales/zh.json index e5fd928f34..da21260b05 100644 --- a/BitFun-Installer/src/i18n/locales/zh.json +++ b/BitFun-Installer/src/i18n/locales/zh.json @@ -70,46 +70,70 @@ "name": "MiniMax", "description": "MiniMax 系列模型", "urlOptions": { - "default": "Anthropic格式-默认", - "openai": "OpenAI兼容格式" + "default": "国内-Anthropic格式-默认", + "openai": "国内-OpenAI兼容格式", + "intl": "海外-Anthropic格式", + "intlOpenai": "海外-OpenAI兼容格式" } }, "moonshot": { "name": "月之暗面", - "description": "月之暗面 Kimi 系列模型" + "description": "月之暗面 Kimi 系列模型", + "urlOptions": { + "default": "国内-OpenAI格式-默认", + "anthropic": "国内-Anthropic格式", + "intl": "海外-OpenAI格式", + "intlAnthropic": "海外-Anthropic格式", + "kimiForCoding": "Kimi For Coding 套餐" + } }, "deepseek": { "name": "DeepSeek", - "description": "DeepSeek 系列模型" + "description": "DeepSeek 系列模型", + "urlOptions": { + "default": "OpenAI格式-默认", + "anthropic": "Anthropic格式" + } }, "zhipu": { "name": "智谱AI", "description": "智谱AI GLM 系列模型", "urlOptions": { - "default": "OpenAI格式-默认", - "anthropic": "Anthropic格式", - "codingPlan": "OpenAI格式-CodingPlan" + "default": "国内-OpenAI格式-默认", + "anthropic": "国内-Anthropic格式", + "codingPlan": "国内-Coding Plan", + "intl": "海外(Z.AI)-OpenAI格式", + "intlAnthropic": "海外(Z.AI)-Anthropic格式", + "intlCodingPlan": "海外(Z.AI)-Coding Plan" } }, "qwen": { "name": "通义千问", "description": "阿里云百炼大模型平台", "urlOptions": { - "default": "OpenAI格式-默认", - "codingPlan": "OpenAI格式-Coding Plan", - "codingPlanAnthropic": "Anthropic格式-Coding Plan" + "default": "国内-OpenAI格式-默认", + "codingPlan": "国内-Coding Plan", + "codingPlanAnthropic": "国内-Coding Plan-Anthropic格式", + "intl": "海外(国际站)-OpenAI格式", + "intlCodingPlan": "海外-Coding Plan" } }, "volcengine": { "name": "火山引擎", - "description": "字节跳动火山引擎大模型平台" + "description": "字节跳动火山引擎大模型平台", + "urlOptions": { + "default": "国内-默认", + "codingPlan": "国内-Coding Plan", + "intl": "海外(BytePlus)" + } }, "siliconflow": { "name": "硅基流动", "description": "硅基流动大模型平台", "urlOptions": { - "default": "OpenAI格式-默认", - "anthropic": "Anthropic格式" + "default": "国内-OpenAI格式-默认", + "anthropic": "国内-Anthropic格式", + "intl": "海外-OpenAI格式" } }, "nvidia": { @@ -139,7 +163,8 @@ "provider": "请求格式", "providerPlaceholder": "选择请求格式", "modelSelection": "选择模型", - "resolvedUrlLabel": "实际请求地址:" + "resolvedUrlLabel": "实际请求地址:", + "modelName": "模型名称" }, "formats": { "openaiCompatible": "OpenAI 兼容", @@ -148,7 +173,20 @@ "geminiApi": "Gemini GenerateContent API" }, "showSecret": "显示", - "hideSecret": "隐藏" + "hideSecret": "隐藏", + "getApiKey": "获取 API Key", + "modelNoResults": "没有匹配的模型", + "customRequestBodyPlaceholder": "例如:\n{\n \"temperature\": 0.8,\n \"top_p\": 0.9\n}", + "jsonValid": "JSON格式有效", + "jsonInvalid": "JSON格式错误,请检查语法", + "skipSslVerify": "跳过SSL证书验证", + "customHeadersModeMerge": "合并", + "customHeadersModeReplace": "替换", + "addHeader": "添加字段", + "headerKey": "key", + "headerValue": "value", + "advancedShow": "Show advanced settings", + "advancedHide": "Hide advanced settings" }, "progress": { "title": "安装中", diff --git a/src/apps/cli/src/ui/provider_selector.rs b/src/apps/cli/src/ui/provider_selector.rs index aaddaf390c..216939890c 100644 --- a/src/apps/cli/src/ui/provider_selector.rs +++ b/src/apps/cli/src/ui/provider_selector.rs @@ -43,7 +43,7 @@ fn builtin_provider_templates() -> Vec { name: "ZhiPu AI".into(), base_url: "https://open.bigmodel.cn/api/paas/v4/chat/completions".into(), format: "openai".into(), - models: vec!["glm-4.7".into(), "glm-4.7-flash".into(), "glm-4.6".into()], + models: vec!["glm-5.2".into(), "glm-5.1".into(), "glm-5".into()], description: "ZhiPu GLM series".into(), }, ProviderTemplate { @@ -51,9 +51,9 @@ fn builtin_provider_templates() -> Vec { base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions".into(), format: "openai".into(), models: vec![ - "qwen3-max".into(), - "qwen3-coder-plus".into(), - "qwen3-coder-flash".into(), + "qwen3.7-plus".into(), + "qwen3.7-max".into(), + "qwen3.6-flash".into(), ], description: "Alibaba Qwen series".into(), }, @@ -61,14 +61,17 @@ fn builtin_provider_templates() -> Vec { name: "DeepSeek".into(), base_url: "https://api.deepseek.com/chat/completions".into(), format: "openai".into(), - models: vec!["deepseek-chat".into(), "deepseek-reasoner".into()], + models: vec!["deepseek-v4-pro".into(), "deepseek-v4-flash".into()], description: "DeepSeek AI models".into(), }, ProviderTemplate { name: "Volcengine".into(), base_url: "https://ark.cn-beijing.volces.com/api/v3/chat/completions".into(), format: "openai".into(), - models: vec!["doubao-seed-1-8-251228".into(), "glm-4-7-251222".into()], + models: vec![ + "doubao-seed-2-1-pro-260628".into(), + "doubao-seed-2-0-pro-260215".into(), + ], description: "ByteDance Volcengine".into(), }, ProviderTemplate { @@ -76,9 +79,9 @@ fn builtin_provider_templates() -> Vec { base_url: "https://api.minimaxi.com/anthropic/v1/messages".into(), format: "anthropic".into(), models: vec![ - "MiniMax-M2.1".into(), - "MiniMax-M2.1-lightning".into(), - "MiniMax-M2".into(), + "MiniMax-M3".into(), + "MiniMax-M2.7".into(), + "MiniMax-M2.7-highspeed".into(), ], description: "MiniMax AI models".into(), }, @@ -87,9 +90,9 @@ fn builtin_provider_templates() -> Vec { base_url: "https://api.moonshot.cn/v1/chat/completions".into(), format: "openai".into(), models: vec![ - "kimi-k2.5".into(), - "kimi-k2".into(), - "kimi-k2-thinking".into(), + "kimi-k3".into(), + "kimi-k2.7-code".into(), + "kimi-k2.6".into(), ], description: "Moonshot Kimi series".into(), }, @@ -98,9 +101,9 @@ fn builtin_provider_templates() -> Vec { base_url: "https://api.anthropic.com/v1/messages".into(), format: "anthropic".into(), models: vec![ - "claude-opus-4-6".into(), - "claude-sonnet-4-5-20250929".into(), - "claude-haiku-4-5-20251001".into(), + "claude-opus-5".into(), + "claude-sonnet-5".into(), + "claude-haiku-4-5".into(), ], description: "Anthropic Claude series".into(), }, diff --git a/src/crates/adapters/ai-adapters/src/client/quirks.rs b/src/crates/adapters/ai-adapters/src/client/quirks.rs index d6042fe79b..325ddaa5a9 100644 --- a/src/crates/adapters/ai-adapters/src/client/quirks.rs +++ b/src/crates/adapters/ai-adapters/src/client/quirks.rs @@ -1,11 +1,11 @@ use crate::types::ReasoningMode; pub(crate) fn is_dashscope_url(url: &str) -> bool { - url.contains("dashscope.aliyuncs.com") + url.contains("dashscope.aliyuncs.com") || url.contains("dashscope-intl.aliyuncs.com") } pub(crate) fn is_siliconflow_url(url: &str) -> bool { - url.contains("api.siliconflow.cn") + url.contains("api.siliconflow.cn") || url.contains("api.siliconflow.com") } pub(crate) fn is_deepseek_url(url: &str) -> bool { @@ -48,7 +48,7 @@ pub(crate) fn parse_glm_major_minor(model_name: &str) -> Option<(u32, u32)> { } pub(crate) fn should_append_tool_stream(url: &str, model_name: &str) -> bool { - if url.contains("bigmodel.cn") { + if url.contains("bigmodel.cn") || url.contains("api.z.ai") { return true; } diff --git a/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs b/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs index bdbbd509c9..37696e30b9 100644 --- a/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs +++ b/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs @@ -62,7 +62,7 @@ fn parse_claude_model_version(model_name: &str, family: &str) -> Option AnthropicThinkingCapability { - if model_name.starts_with("claude-mythos") { + if model_name.starts_with("claude-mythos") || model_name.starts_with("claude-fable") { return AnthropicThinkingCapability::AdaptiveDefaultNoDisabled; } diff --git a/src/web-ui/src/infrastructure/config/services/modelConfigs.ts b/src/web-ui/src/infrastructure/config/services/modelConfigs.ts index 484e6f3807..98a18c1054 100644 --- a/src/web-ui/src/infrastructure/config/services/modelConfigs.ts +++ b/src/web-ui/src/infrastructure/config/services/modelConfigs.ts @@ -107,7 +107,7 @@ export const PROVIDER_TEMPLATES: Record = { name: t('settings/ai-model:providers.gemini.name'), baseUrl: 'https://generativelanguage.googleapis.com', format: 'gemini', - models: ['gemini-3.1-pro-preview', 'gemini-3.1-flash-lite-preview'], + models: ['gemini-3.1-pro-preview', 'gemini-3.6-flash', 'gemini-3.5-flash-lite'], requiresApiKey: true, description: t('settings/ai-model:providers.gemini.description'), helpUrl: 'https://aistudio.google.com/app/apikey' @@ -118,7 +118,7 @@ export const PROVIDER_TEMPLATES: Record = { name: t('settings/ai-model:providers.anthropic.name'), baseUrl: 'https://api.anthropic.com', format: 'anthropic', - models: ['claude-opus-4-6', 'claude-sonnet-4-6'], + models: ['claude-opus-5', 'claude-fable-5', 'claude-sonnet-5', 'claude-haiku-4-5'], requiresApiKey: true, description: t('settings/ai-model:providers.anthropic.description'), helpUrl: 'https://console.anthropic.com/' @@ -129,13 +129,15 @@ export const PROVIDER_TEMPLATES: Record = { name: t('settings/ai-model:providers.minimax.name'), baseUrl: 'https://api.minimaxi.com/anthropic', format: 'anthropic', - models: ['MiniMax-M2.7-highspeed', 'MiniMax-M2.5-highspeed'], + models: ['MiniMax-M3', 'MiniMax-M2.7', 'MiniMax-M2.7-highspeed'], requiresApiKey: true, description: t('settings/ai-model:providers.minimax.description'), helpUrl: 'https://platform.minimax.io/', baseUrlOptions: [ { url: 'https://api.minimaxi.com/anthropic', format: 'anthropic', note: 'default' }, - { url: 'https://api.minimaxi.com/v1', format: 'openai', note: 'OpenAI Compatible' }, + { url: 'https://api.minimaxi.com/v1', format: 'openai', note: 'openai' }, + { url: 'https://api.minimax.io/anthropic', format: 'anthropic', note: 'intl' }, + { url: 'https://api.minimax.io/v1', format: 'openai', note: 'intlOpenai' }, ] }, @@ -144,10 +146,17 @@ export const PROVIDER_TEMPLATES: Record = { name: t('settings/ai-model:providers.moonshot.name'), baseUrl: 'https://api.moonshot.cn/v1', format: 'openai', - models: ['kimi-k2.5', 'kimi-k2', 'kimi-k2-thinking'], + models: ['kimi-k3', 'kimi-k2.7-code', 'kimi-k2.6', 'kimi-for-coding'], requiresApiKey: true, description: t('settings/ai-model:providers.moonshot.description'), - helpUrl: 'https://platform.moonshot.ai/console' + helpUrl: 'https://platform.moonshot.ai/console', + baseUrlOptions: [ + { url: 'https://api.moonshot.cn/v1', format: 'openai', note: 'default' }, + { url: 'https://api.moonshot.cn/anthropic', format: 'anthropic', note: 'anthropic' }, + { url: 'https://api.moonshot.ai/v1', format: 'openai', note: 'intl' }, + { url: 'https://api.moonshot.ai/anthropic', format: 'anthropic', note: 'intlAnthropic' }, + { url: 'https://api.kimi.com/coding', format: 'anthropic', note: 'kimiForCoding' }, + ] }, deepseek: { @@ -158,7 +167,11 @@ export const PROVIDER_TEMPLATES: Record = { models: ['deepseek-v4-flash', 'deepseek-v4-pro'], requiresApiKey: true, description: t('settings/ai-model:providers.deepseek.description'), - helpUrl: 'https://platform.deepseek.com/api_keys' + helpUrl: 'https://platform.deepseek.com/api_keys', + baseUrlOptions: [ + { url: 'https://api.deepseek.com/v1', format: 'openai', note: 'default' }, + { url: 'https://api.deepseek.com/anthropic', format: 'anthropic', note: 'anthropic' }, + ] }, zhipu: { @@ -166,14 +179,17 @@ export const PROVIDER_TEMPLATES: Record = { name: t('settings/ai-model:providers.zhipu.name'), baseUrl: 'https://open.bigmodel.cn/api/paas/v4', format: 'openai', - models: ['glm-5', 'glm-4.7'], + models: ['glm-5.2', 'glm-5.1', 'glm-5'], requiresApiKey: true, description: t('settings/ai-model:providers.zhipu.description'), helpUrl: 'https://open.bigmodel.cn/usercenter/apikeys', baseUrlOptions: [ { url: 'https://open.bigmodel.cn/api/paas/v4', format: 'openai', note: 'default' }, - { url: 'https://open.bigmodel.cn/api/anthropic', format: 'anthropic', note: 'Coding Plan' }, - { url: 'https://open.bigmodel.cn/api/coding/paas/v4', format: 'openai', note: 'Coding Plan' }, + { url: 'https://open.bigmodel.cn/api/anthropic', format: 'anthropic', note: 'anthropic' }, + { url: 'https://open.bigmodel.cn/api/coding/paas/v4', format: 'openai', note: 'codingPlan' }, + { url: 'https://api.z.ai/api/paas/v4', format: 'openai', note: 'intl' }, + { url: 'https://api.z.ai/api/anthropic', format: 'anthropic', note: 'intlAnthropic' }, + { url: 'https://api.z.ai/api/coding/paas/v4', format: 'openai', note: 'intlCodingPlan' }, ] }, @@ -182,14 +198,16 @@ export const PROVIDER_TEMPLATES: Record = { name: t('settings/ai-model:providers.qwen.name'), baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', format: 'openai', - models: ['Qwen3.5-Plus', 'Qwen3.5-Flash', 'qwen3-asr-flash'], + models: ['qwen3.7-plus', 'qwen3.7-max', 'qwen3.6-flash', 'qwen3-asr-flash'], requiresApiKey: true, description: t('settings/ai-model:providers.qwen.description'), helpUrl: 'https://dashscope.console.aliyun.com/apiKey', baseUrlOptions: [ { url: 'https://dashscope.aliyuncs.com/compatible-mode/v1', format: 'openai', note: 'default' }, - { url: 'https://coding.dashscope.aliyuncs.com/v1', format: 'openai', note: 'Coding Plan' }, - { url: 'https://coding.dashscope.aliyuncs.com/apps/anthropic', format: 'anthropic', note: 'Coding Plan' }, + { url: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1', format: 'openai', note: 'intl' }, + { url: 'https://coding.dashscope.aliyuncs.com/v1', format: 'openai', note: 'codingPlan' }, + { url: 'https://coding.dashscope.aliyuncs.com/apps/anthropic', format: 'anthropic', note: 'codingPlanAnthropic' }, + { url: 'https://coding-intl.dashscope.aliyuncs.com/v1', format: 'openai', note: 'intlCodingPlan' }, ] }, @@ -198,10 +216,15 @@ export const PROVIDER_TEMPLATES: Record = { name: t('settings/ai-model:providers.volcengine.name'), baseUrl: 'https://ark.cn-beijing.volces.com/api/v3', format: 'openai', - models: ['doubao-seed-2-0-code-preview-260215', 'doubao-seed-2-0-pro-260215'], + models: ['doubao-seed-2-1-pro-260628', 'doubao-seed-2-0-code-preview-260215', 'doubao-seed-2-0-pro-260215'], requiresApiKey: true, description: t('settings/ai-model:providers.volcengine.description'), - helpUrl: 'https://console.volcengine.com/ark/' + helpUrl: 'https://console.volcengine.com/ark/', + baseUrlOptions: [ + { url: 'https://ark.cn-beijing.volces.com/api/v3', format: 'openai', note: 'default' }, + { url: 'https://ark.cn-beijing.volces.com/api/coding/v3', format: 'openai', note: 'codingPlan' }, + { url: 'https://ark.ap-southeast.bytepluses.com/api/v3', format: 'openai', note: 'intl' }, + ] }, siliconflow: { @@ -209,13 +232,14 @@ export const PROVIDER_TEMPLATES: Record = { name: t('settings/ai-model:providers.siliconflow.name'), baseUrl: 'https://api.siliconflow.cn/v1', format: 'openai', - models: [], + models: ['zai-org/GLM-5.2', 'deepseek-ai/DeepSeek-V4-Pro', 'deepseek-ai/DeepSeek-V4-Flash'], requiresApiKey: true, description: t('settings/ai-model:providers.siliconflow.description'), helpUrl: 'https://cloud.siliconflow.cn/account/ak', baseUrlOptions: [ { url: 'https://api.siliconflow.cn/v1', format: 'openai', note: 'default' }, - { url: 'https://api.siliconflow.cn/v1/messages', format: 'anthropic', note: 'Anthropic' }, + { url: 'https://api.siliconflow.cn/v1/messages', format: 'anthropic', note: 'anthropic' }, + { url: 'https://api.siliconflow.com/v1', format: 'openai', note: 'intl' }, ] }, diff --git a/src/web-ui/src/infrastructure/config/services/providerCatalog.ts b/src/web-ui/src/infrastructure/config/services/providerCatalog.ts index 45d6a1511d..d8aca102ee 100644 --- a/src/web-ui/src/infrastructure/config/services/providerCatalog.ts +++ b/src/web-ui/src/infrastructure/config/services/providerCatalog.ts @@ -23,15 +23,28 @@ export const PROVIDER_URL_CATALOG: ProviderUrlCatalogItem[] = [ baseUrlOptions: [ 'https://api.minimaxi.com/anthropic', 'https://api.minimaxi.com/v1', + 'https://api.minimax.io/anthropic', + 'https://api.minimax.io/v1', ], }, { id: 'moonshot', baseUrl: 'https://api.moonshot.cn/v1', + baseUrlOptions: [ + 'https://api.moonshot.cn/v1', + 'https://api.moonshot.cn/anthropic', + 'https://api.moonshot.ai/v1', + 'https://api.moonshot.ai/anthropic', + 'https://api.kimi.com/coding', + ], }, { id: 'deepseek', baseUrl: 'https://api.deepseek.com/v1', + baseUrlOptions: [ + 'https://api.deepseek.com/v1', + 'https://api.deepseek.com/anthropic', + ], }, { id: 'zhipu', @@ -40,6 +53,9 @@ export const PROVIDER_URL_CATALOG: ProviderUrlCatalogItem[] = [ 'https://open.bigmodel.cn/api/paas/v4', 'https://open.bigmodel.cn/api/anthropic', 'https://open.bigmodel.cn/api/coding/paas/v4', + 'https://api.z.ai/api/paas/v4', + 'https://api.z.ai/api/anthropic', + 'https://api.z.ai/api/coding/paas/v4', ], }, { @@ -47,13 +63,20 @@ export const PROVIDER_URL_CATALOG: ProviderUrlCatalogItem[] = [ baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', baseUrlOptions: [ 'https://dashscope.aliyuncs.com/compatible-mode/v1', + 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1', 'https://coding.dashscope.aliyuncs.com/v1', 'https://coding.dashscope.aliyuncs.com/apps/anthropic', + 'https://coding-intl.dashscope.aliyuncs.com/v1', ], }, { id: 'volcengine', baseUrl: 'https://ark.cn-beijing.volces.com/api/v3', + baseUrlOptions: [ + 'https://ark.cn-beijing.volces.com/api/v3', + 'https://ark.cn-beijing.volces.com/api/coding/v3', + 'https://ark.ap-southeast.bytepluses.com/api/v3', + ], }, { id: 'siliconflow', @@ -61,6 +84,7 @@ export const PROVIDER_URL_CATALOG: ProviderUrlCatalogItem[] = [ baseUrlOptions: [ 'https://api.siliconflow.cn/v1', 'https://api.siliconflow.cn/v1/messages', + 'https://api.siliconflow.com/v1', ], }, { diff --git a/src/web-ui/src/infrastructure/config/utils/reasoning.ts b/src/web-ui/src/infrastructure/config/utils/reasoning.ts index 8a69dcb341..1cde229ee0 100644 --- a/src/web-ui/src/infrastructure/config/utils/reasoning.ts +++ b/src/web-ui/src/infrastructure/config/utils/reasoning.ts @@ -29,7 +29,12 @@ export function supportsAnthropicReasoning(provider?: string): boolean { export function supportsAnthropicAdaptive(modelName?: string): boolean { const normalized = (modelName || '').trim().toLowerCase(); return normalized.startsWith('claude-opus-4-6') + || normalized.startsWith('claude-opus-4-7') + || normalized.startsWith('claude-opus-4-8') + || normalized.startsWith('claude-opus-5') || normalized.startsWith('claude-sonnet-4-6') + || normalized.startsWith('claude-sonnet-5') + || normalized.startsWith('claude-fable') || normalized.startsWith('claude-mythos'); } diff --git a/src/web-ui/src/locales/en-US/settings/ai-model.json b/src/web-ui/src/locales/en-US/settings/ai-model.json index c76dc712e0..f47e93f3c7 100644 --- a/src/web-ui/src/locales/en-US/settings/ai-model.json +++ b/src/web-ui/src/locales/en-US/settings/ai-model.json @@ -147,39 +147,72 @@ }, "minimax": { "name": "MiniMax", - "description": "MiniMax series models" + "description": "MiniMax series models", + "urlOptions": { + "default": "China - Anthropic Format - Default", + "openai": "China - OpenAI Compatible", + "intl": "Global - Anthropic Format", + "intlOpenai": "Global - OpenAI Compatible" + } }, "moonshot": { "name": "Moonshot AI", - "description": "Moonshot Kimi series models" + "description": "Moonshot Kimi series models", + "urlOptions": { + "default": "China - OpenAI Format - Default", + "anthropic": "China - Anthropic Format", + "intl": "Global - OpenAI Format", + "intlAnthropic": "Global - Anthropic Format", + "kimiForCoding": "Kimi For Coding Plan" + } }, "deepseek": { "name": "DeepSeek", - "description": "DeepSeek series models" + "description": "DeepSeek series models", + "urlOptions": { + "default": "OpenAI Format - Default", + "anthropic": "Anthropic Format" + } }, "zhipu": { "name": "Zhipu AI", "description": "Zhipu AI GLM series models", "urlOptions": { - "default": "OpenAI Format - Default", - "anthropic": "Anthropic Format", - "codingPlan": "OpenAI Format - CodingPlan" + "default": "China - OpenAI Format - Default", + "anthropic": "China - Anthropic Format", + "codingPlan": "China - Coding Plan", + "intl": "Global (Z.AI) - OpenAI Format", + "intlAnthropic": "Global (Z.AI) - Anthropic Format", + "intlCodingPlan": "Global (Z.AI) - Coding Plan" } }, "qwen": { "name": "Qwen", - "description": "Alibaba Cloud Bailian Model Platform" + "description": "Alibaba Cloud Bailian Model Platform", + "urlOptions": { + "default": "China - OpenAI Format - Default", + "intl": "Global (International) - OpenAI Format", + "codingPlan": "China - Coding Plan", + "codingPlanAnthropic": "China - Coding Plan - Anthropic Format", + "intlCodingPlan": "Global - Coding Plan" + } }, "volcengine": { "name": "Volcano Engine", - "description": "ByteDance Volcano Engine Model Platform" + "description": "ByteDance Volcano Engine Model Platform", + "urlOptions": { + "default": "China - Default", + "codingPlan": "China - Coding Plan", + "intl": "Global (BytePlus)" + } }, "siliconflow": { "name": "SiliconFlow", "description": "SiliconFlow Model Platform", "urlOptions": { - "default": "OpenAI Format - Default", - "anthropic": "Anthropic Format" + "default": "China - OpenAI Format - Default", + "anthropic": "China - Anthropic Format", + "intl": "Global - OpenAI Format" } }, "nvidia": { diff --git a/src/web-ui/src/locales/zh-CN/settings/ai-model.json b/src/web-ui/src/locales/zh-CN/settings/ai-model.json index f58fe265bd..69a40c4edf 100644 --- a/src/web-ui/src/locales/zh-CN/settings/ai-model.json +++ b/src/web-ui/src/locales/zh-CN/settings/ai-model.json @@ -147,39 +147,72 @@ }, "minimax": { "name": "MiniMax", - "description": "MiniMax 系列模型" + "description": "MiniMax 系列模型", + "urlOptions": { + "default": "国内-Anthropic格式-默认", + "openai": "国内-OpenAI兼容格式", + "intl": "海外-Anthropic格式", + "intlOpenai": "海外-OpenAI兼容格式" + } }, "moonshot": { "name": "月之暗面", - "description": "月之暗面 Kimi 系列模型" + "description": "月之暗面 Kimi 系列模型", + "urlOptions": { + "default": "国内-OpenAI格式-默认", + "anthropic": "国内-Anthropic格式", + "intl": "海外-OpenAI格式", + "intlAnthropic": "海外-Anthropic格式", + "kimiForCoding": "Kimi For Coding 套餐" + } }, "deepseek": { "name": "DeepSeek", - "description": "DeepSeek 系列模型" + "description": "DeepSeek 系列模型", + "urlOptions": { + "default": "OpenAI格式-默认", + "anthropic": "Anthropic格式" + } }, "zhipu": { "name": "智谱AI", "description": "智谱AI GLM 系列模型", "urlOptions": { - "default": "OpenAI格式-默认", - "anthropic": "Anthropic格式", - "codingPlan": "OpenAI格式-CodingPlan" + "default": "国内-OpenAI格式-默认", + "anthropic": "国内-Anthropic格式", + "codingPlan": "国内-Coding Plan", + "intl": "海外(Z.AI)-OpenAI格式", + "intlAnthropic": "海外(Z.AI)-Anthropic格式", + "intlCodingPlan": "海外(Z.AI)-Coding Plan" } }, "qwen": { "name": "通义千问", - "description": "阿里云百炼大模型平台" + "description": "阿里云百炼大模型平台", + "urlOptions": { + "default": "国内-OpenAI格式-默认", + "intl": "海外(国际站)-OpenAI格式", + "codingPlan": "国内-Coding Plan", + "codingPlanAnthropic": "国内-Coding Plan-Anthropic格式", + "intlCodingPlan": "海外-Coding Plan" + } }, "volcengine": { "name": "火山引擎", - "description": "字节跳动火山引擎大模型平台" + "description": "字节跳动火山引擎大模型平台", + "urlOptions": { + "default": "国内-默认", + "codingPlan": "国内-Coding Plan", + "intl": "海外(BytePlus)" + } }, "siliconflow": { "name": "硅基流动", "description": "硅基流动大模型平台", "urlOptions": { - "default": "OpenAI格式-默认", - "anthropic": "Anthropic格式" + "default": "国内-OpenAI格式-默认", + "anthropic": "国内-Anthropic格式", + "intl": "海外-OpenAI格式" } }, "nvidia": { diff --git a/src/web-ui/src/locales/zh-TW/settings/ai-model.json b/src/web-ui/src/locales/zh-TW/settings/ai-model.json index 9b937b0da2..ae0df56ec9 100644 --- a/src/web-ui/src/locales/zh-TW/settings/ai-model.json +++ b/src/web-ui/src/locales/zh-TW/settings/ai-model.json @@ -147,39 +147,72 @@ }, "minimax": { "name": "MiniMax", - "description": "MiniMax 系列模型" + "description": "MiniMax 系列模型", + "urlOptions": { + "default": "國內-Anthropic格式-默認", + "openai": "國內-OpenAI兼容格式", + "intl": "海外-Anthropic格式", + "intlOpenai": "海外-OpenAI兼容格式" + } }, "moonshot": { "name": "月之暗面", - "description": "月之暗面 Kimi 系列模型" + "description": "月之暗面 Kimi 系列模型", + "urlOptions": { + "default": "國內-OpenAI格式-默認", + "anthropic": "國內-Anthropic格式", + "intl": "海外-OpenAI格式", + "intlAnthropic": "海外-Anthropic格式", + "kimiForCoding": "Kimi For Coding 套餐" + } }, "deepseek": { "name": "DeepSeek", - "description": "DeepSeek 系列模型" + "description": "DeepSeek 系列模型", + "urlOptions": { + "default": "OpenAI格式-默認", + "anthropic": "Anthropic格式" + } }, "zhipu": { "name": "智譜AI", "description": "智譜AI GLM 系列模型", "urlOptions": { - "default": "OpenAI格式-默認", - "anthropic": "Anthropic格式", - "codingPlan": "OpenAI格式-CodingPlan" + "default": "國內-OpenAI格式-默認", + "anthropic": "國內-Anthropic格式", + "codingPlan": "國內-Coding Plan", + "intl": "海外(Z.AI)-OpenAI格式", + "intlAnthropic": "海外(Z.AI)-Anthropic格式", + "intlCodingPlan": "海外(Z.AI)-Coding Plan" } }, "qwen": { "name": "通義千問", - "description": "阿里雲百鍊大模型平臺" + "description": "阿里雲百鍊大模型平臺", + "urlOptions": { + "default": "國內-OpenAI格式-默認", + "intl": "海外(國際站)-OpenAI格式", + "codingPlan": "國內-Coding Plan", + "codingPlanAnthropic": "國內-Coding Plan-Anthropic格式", + "intlCodingPlan": "海外-Coding Plan" + } }, "volcengine": { "name": "火山引擎", - "description": "字節跳動火山引擎大模型平臺" + "description": "字節跳動火山引擎大模型平臺", + "urlOptions": { + "default": "國內-默認", + "codingPlan": "國內-Coding Plan", + "intl": "海外(BytePlus)" + } }, "siliconflow": { "name": "硅基流動", "description": "硅基流動大模型平臺", "urlOptions": { - "default": "OpenAI格式-默認", - "anthropic": "Anthropic格式" + "default": "國內-OpenAI格式-默認", + "anthropic": "國內-Anthropic格式", + "intl": "海外-OpenAI格式" } }, "nvidia": { From fb33921f8681bb076a6036797a3f3752ab76e515 Mon Sep 17 00:00:00 2001 From: bowen628 Date: Sun, 26 Jul 2026 19:34:49 +0800 Subject: [PATCH 2/7] fix(i18n): backfill installer zh-TW keys added by model i18n sync The sync-model-i18n script only writes en.json and zh.json; the new keys it introduced broke locale key parity with zh-TW.json in the i18n audit. Add the 14 missing keys, sourced from the web-ui zh-TW locale where available. --- BitFun-Installer/src/i18n/locales/zh-TW.json | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/BitFun-Installer/src/i18n/locales/zh-TW.json b/BitFun-Installer/src/i18n/locales/zh-TW.json index 1e493e5bd8..19dba767ed 100644 --- a/BitFun-Installer/src/i18n/locales/zh-TW.json +++ b/BitFun-Installer/src/i18n/locales/zh-TW.json @@ -163,7 +163,8 @@ "provider": "請求格式", "providerPlaceholder": "選擇請求格式", "modelSelection": "選擇模型", - "resolvedUrlLabel": "實際請求地址:" + "resolvedUrlLabel": "實際請求地址:", + "modelName": "模型名稱" }, "formats": { "openaiCompatible": "OpenAI 兼容", @@ -172,7 +173,20 @@ "geminiApi": "Gemini GenerateContent API" }, "showSecret": "顯示", - "hideSecret": "隱藏" + "hideSecret": "隱藏", + "addHeader": "新增字段", + "advancedShow": "Show advanced settings", + "advancedHide": "Hide advanced settings", + "customHeadersModeMerge": "合併", + "customHeadersModeReplace": "替換", + "customRequestBodyPlaceholder": "例如:\n{\n \"temperature\": 0.8,\n \"top_p\": 0.9\n}", + "getApiKey": "獲取 API Key", + "headerKey": "key", + "headerValue": "value", + "jsonInvalid": "JSON格式錯誤,請檢查語法", + "jsonValid": "JSON格式有效", + "modelNoResults": "沒有匹配的模型", + "skipSslVerify": "跳過SSL證書驗證" }, "progress": { "title": "安裝中", From 9dc52a8792e4a0fd7a4de127f15ab0b8680022ca Mon Sep 17 00:00:00 2001 From: bowen628 Date: Sun, 26 Jul 2026 19:39:59 +0800 Subject: [PATCH 3/7] fix(models): address review findings on new provider endpoints - Send Authorization: Bearer to Anthropic-compatible endpoints whose vendors document ANTHROPIC_AUTH_TOKEN auth: Z.AI, Moonshot /anthropic gateway, Kimi For Coding (previously only bigmodel.cn; the new Z.AI preset would have failed auth with x-api-key) - Drop kimi-for-coding from the shared Moonshot model list; it only exists on the api.kimi.com/coding endpoint and 404s on the default moonshot.cn API - Recognize dashscope-intl in the Qwen ASR base-url detection - Localize installer advancedShow/advancedHide (was hardcoded English in zh via the sync script) and refresh the stale glm placeholder --- BitFun-Installer/scripts/sync-model-i18n.cjs | 4 ++-- BitFun-Installer/src/data/modelProviders.ts | 2 +- BitFun-Installer/src/i18n/locales/zh-TW.json | 4 ++-- BitFun-Installer/src/i18n/locales/zh.json | 4 ++-- .../ai-adapters/src/providers/anthropic/request.rs | 13 ++++++++++++- .../config/components/AIModelConfig.tsx | 2 +- .../config/components/VoiceInputConfig.tsx | 3 ++- .../infrastructure/config/services/modelConfigs.ts | 2 +- 8 files changed, 23 insertions(+), 11 deletions(-) diff --git a/BitFun-Installer/scripts/sync-model-i18n.cjs b/BitFun-Installer/scripts/sync-model-i18n.cjs index 8002b896ec..8ec08546ef 100644 --- a/BitFun-Installer/scripts/sync-model-i18n.cjs +++ b/BitFun-Installer/scripts/sync-model-i18n.cjs @@ -163,8 +163,8 @@ function buildModelPatch(settingsAiModel, languageTag, components) { testing: isZh ? '测试中...' : 'Testing...', testSuccess: get(settingsAiModel, 'messages.testSuccess', 'Connection successful'), testFailed: get(settingsAiModel, 'messages.testFailed', 'Connection failed'), - advancedShow: 'Show advanced settings', - advancedHide: 'Hide advanced settings', + advancedShow: isZh ? '显示高级设置' : 'Show advanced settings', + advancedHide: isZh ? '隐藏高级设置' : 'Hide advanced settings', providers: buildProviderPatch(settingsAiModel), }; } diff --git a/BitFun-Installer/src/data/modelProviders.ts b/BitFun-Installer/src/data/modelProviders.ts index cb689ee6ae..17bb03aa42 100644 --- a/BitFun-Installer/src/data/modelProviders.ts +++ b/BitFun-Installer/src/data/modelProviders.ts @@ -100,7 +100,7 @@ export const PROVIDER_TEMPLATES: Record = { descriptionKey: 'model.providers.moonshot.description', baseUrl: 'https://api.moonshot.cn/v1', format: 'openai', - models: ['kimi-k3', 'kimi-k2.7-code', 'kimi-k2.6', 'kimi-for-coding'], + models: ['kimi-k3', 'kimi-k2.7-code', 'kimi-k2.6'], helpUrl: 'https://platform.moonshot.ai/console', baseUrlOptions: [ { diff --git a/BitFun-Installer/src/i18n/locales/zh-TW.json b/BitFun-Installer/src/i18n/locales/zh-TW.json index 19dba767ed..cb97e2d9db 100644 --- a/BitFun-Installer/src/i18n/locales/zh-TW.json +++ b/BitFun-Installer/src/i18n/locales/zh-TW.json @@ -175,8 +175,8 @@ "showSecret": "顯示", "hideSecret": "隱藏", "addHeader": "新增字段", - "advancedShow": "Show advanced settings", - "advancedHide": "Hide advanced settings", + "advancedShow": "顯示高級設定", + "advancedHide": "隱藏高級設定", "customHeadersModeMerge": "合併", "customHeadersModeReplace": "替換", "customRequestBodyPlaceholder": "例如:\n{\n \"temperature\": 0.8,\n \"top_p\": 0.9\n}", diff --git a/BitFun-Installer/src/i18n/locales/zh.json b/BitFun-Installer/src/i18n/locales/zh.json index da21260b05..3c6c3464a5 100644 --- a/BitFun-Installer/src/i18n/locales/zh.json +++ b/BitFun-Installer/src/i18n/locales/zh.json @@ -185,8 +185,8 @@ "addHeader": "添加字段", "headerKey": "key", "headerValue": "value", - "advancedShow": "Show advanced settings", - "advancedHide": "Hide advanced settings" + "advancedShow": "显示高级设置", + "advancedHide": "隐藏高级设置" }, "progress": { "title": "安装中", diff --git a/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs b/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs index 37696e30b9..f7b5a3cb4d 100644 --- a/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs +++ b/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs @@ -28,6 +28,17 @@ struct ClaudeModelVersion { minor: u32, } +/// Anthropic-compatible endpoints whose vendors document `Authorization: Bearer` +/// (ANTHROPIC_AUTH_TOKEN) instead of `x-api-key`: Zhipu bigmodel.cn / Z.AI, +/// Moonshot's /anthropic gateway, and Kimi For Coding. +fn wants_bearer_auth(url: &str) -> bool { + url.contains("bigmodel.cn") + || url.contains("api.z.ai") + || url.contains("api.kimi.com") + || ((url.contains("api.moonshot.cn") || url.contains("api.moonshot.ai")) + && url.contains("/anthropic")) +} + pub(crate) fn apply_headers( client: &AIClient, builder: RequestBuilder, @@ -36,7 +47,7 @@ pub(crate) fn apply_headers( shared::apply_header_policy(client, builder, |mut builder| { builder = builder.header("Content-Type", "application/json"); - if url.contains("bigmodel.cn") { + if wants_bearer_auth(url) { builder = builder.header("Authorization", format!("Bearer {}", client.config.api_key)); } else { builder = builder diff --git a/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx b/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx index 4d9628c5d1..e15328d88e 100644 --- a/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx +++ b/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx @@ -2572,7 +2572,7 @@ const AIModelConfig: React.FC = () => { : [String(value)]; syncSelectedModelDrafts(nextModelNames, editingConfig, !!editingConfig.id); }} - placeholder="glm-4.7" + placeholder="glm-5.2" options={availableModelOptions} searchable multiple={!editingConfig.id} diff --git a/src/web-ui/src/infrastructure/config/components/VoiceInputConfig.tsx b/src/web-ui/src/infrastructure/config/components/VoiceInputConfig.tsx index 009e84660a..84ff0d42f2 100644 --- a/src/web-ui/src/infrastructure/config/components/VoiceInputConfig.tsx +++ b/src/web-ui/src/infrastructure/config/components/VoiceInputConfig.tsx @@ -88,7 +88,8 @@ function isQwenAsrConfig(model?: AIModelConfig | null): boolean { if (!model) return true; return ( model.model_name === QWEN_ASR_FLASH_MODEL_ID || - model.base_url.includes('dashscope.aliyuncs.com/compatible-mode') + model.base_url.includes('dashscope.aliyuncs.com/compatible-mode') || + model.base_url.includes('dashscope-intl.aliyuncs.com/compatible-mode') ); } diff --git a/src/web-ui/src/infrastructure/config/services/modelConfigs.ts b/src/web-ui/src/infrastructure/config/services/modelConfigs.ts index 98a18c1054..6210556f23 100644 --- a/src/web-ui/src/infrastructure/config/services/modelConfigs.ts +++ b/src/web-ui/src/infrastructure/config/services/modelConfigs.ts @@ -146,7 +146,7 @@ export const PROVIDER_TEMPLATES: Record = { name: t('settings/ai-model:providers.moonshot.name'), baseUrl: 'https://api.moonshot.cn/v1', format: 'openai', - models: ['kimi-k3', 'kimi-k2.7-code', 'kimi-k2.6', 'kimi-for-coding'], + models: ['kimi-k3', 'kimi-k2.7-code', 'kimi-k2.6'], requiresApiKey: true, description: t('settings/ai-model:providers.moonshot.description'), helpUrl: 'https://platform.moonshot.ai/console', From e2baaf4a7c5180054cd6f1392ec206c57549b462 Mon Sep 17 00:00:00 2001 From: bowen628 Date: Sun, 26 Jul 2026 19:52:12 +0800 Subject: [PATCH 4/7] fix(models): keep anthropic-version on new bearer gateways; sync zh-TW installer i18n Second-pass review follow-ups: - The bearer-auth branch previously dropped the anthropic-version header for all bearer endpoints; keep sending it to Z.AI / Moonshot / Kimi gateways (Anthropic protocol requires it) while leaving bigmodel.cn's shipped behavior untouched - Tighten the Kimi match to api.kimi.com/coding - Teach sync-model-i18n.cjs to generate zh-TW.json (with zh-TW literals for installer-only strings) so installer locale parity cannot silently break again; re-synced zh-TW from the web-ui source of truth --- BitFun-Installer/scripts/sync-model-i18n.cjs | 49 +++++++++++++++---- BitFun-Installer/src/i18n/locales/zh-TW.json | 4 +- .../src/providers/anthropic/request.rs | 7 ++- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/BitFun-Installer/scripts/sync-model-i18n.cjs b/BitFun-Installer/scripts/sync-model-i18n.cjs index 8ec08546ef..6a0640d22c 100644 --- a/BitFun-Installer/scripts/sync-model-i18n.cjs +++ b/BitFun-Installer/scripts/sync-model-i18n.cjs @@ -78,8 +78,33 @@ function buildFormatsPatch(formats) { }; } +/** Hardcoded installer-only strings that have no web-ui source key. */ +const LITERALS = { + en: { + modelNoResults: 'No matching models', + baseUrlPlaceholder: 'e.g., https://open.bigmodel.cn/api/paas/v4/chat/completions', + testing: 'Testing...', + advancedShow: 'Show advanced settings', + advancedHide: 'Hide advanced settings', + }, + zh: { + modelNoResults: '没有匹配的模型', + baseUrlPlaceholder: '示例:https://open.bigmodel.cn/api/paas/v4/chat/completions', + testing: '测试中...', + advancedShow: '显示高级设置', + advancedHide: '隐藏高级设置', + }, + 'zh-TW': { + modelNoResults: '沒有匹配的模型', + baseUrlPlaceholder: '範例:https://open.bigmodel.cn/api/paas/v4/chat/completions', + testing: '測試中...', + advancedShow: '顯示高級設定', + advancedHide: '隱藏高級設定', + }, +}; + function buildModelPatch(settingsAiModel, languageTag, components) { - const isZh = languageTag === 'zh'; + const literals = LITERALS[languageTag] || LITERALS.en; const form = get(settingsAiModel, 'form', {}); const formats = get(settingsAiModel, 'formats', {}); const input = get(components, 'input', {}); @@ -120,16 +145,14 @@ function buildModelPatch(settingsAiModel, languageTag, components) { get(settingsAiModel, 'form.modelName', 'Enter model name...') ), modelNameSelectPlaceholder: get(settingsAiModel, 'providerSelection.selectModel', 'Select a model...'), - modelNoResults: isZh ? '没有匹配的模型' : 'No matching models', + modelNoResults: literals.modelNoResults, /** Installer: use addCustomModel (not useCustomModel / "Press Enter") for the extra dropdown option */ addCustomModel: get(settingsAiModel, 'providerSelection.addCustomModel', 'Add Custom Model'), form: buildFormPatch(form), formats: buildFormatsPatch(formats), showSecret: get(input, 'show', 'Show'), hideSecret: get(input, 'hide', 'Hide'), - baseUrlPlaceholder: isZh - ? '示例:https://open.bigmodel.cn/api/paas/v4/chat/completions' - : 'e.g., https://open.bigmodel.cn/api/paas/v4/chat/completions', + baseUrlPlaceholder: literals.baseUrlPlaceholder, customRequestBodyPlaceholder: get( settingsAiModel, 'advancedSettings.customRequestBody.placeholder', @@ -160,18 +183,23 @@ function buildModelPatch(settingsAiModel, languageTag, components) { headerKey: get(settingsAiModel, 'advancedSettings.customHeaders.keyPlaceholder', 'key'), headerValue: get(settingsAiModel, 'advancedSettings.customHeaders.valuePlaceholder', 'value'), testConnection: get(settingsAiModel, 'actions.test', 'Test Connection'), - testing: isZh ? '测试中...' : 'Testing...', + testing: literals.testing, testSuccess: get(settingsAiModel, 'messages.testSuccess', 'Connection successful'), testFailed: get(settingsAiModel, 'messages.testFailed', 'Connection failed'), - advancedShow: isZh ? '显示高级设置' : 'Show advanced settings', - advancedHide: isZh ? '隐藏高级设置' : 'Hide advanced settings', + advancedShow: literals.advancedShow, + advancedHide: literals.advancedHide, providers: buildProviderPatch(settingsAiModel), }; } +const LOCALE_MAP = { + en: { localeDir: 'en-US', installerLocale: 'en.json' }, + zh: { localeDir: 'zh-CN', installerLocale: 'zh.json' }, + 'zh-TW': { localeDir: 'zh-TW', installerLocale: 'zh-TW.json' }, +}; + function syncOne(languageTag) { - const localeDir = languageTag === 'zh' ? 'zh-CN' : 'en-US'; - const installerLocale = languageTag === 'zh' ? 'zh.json' : 'en.json'; + const { localeDir, installerLocale } = LOCALE_MAP[languageTag]; const sourceAiModelPath = path.join( PROJECT_ROOT, @@ -213,6 +241,7 @@ function syncOne(languageTag) { function main() { syncOne('en'); syncOne('zh'); + syncOne('zh-TW'); console.log('[sync-model-i18n] Synced installer model i18n from web-ui locales.'); } diff --git a/BitFun-Installer/src/i18n/locales/zh-TW.json b/BitFun-Installer/src/i18n/locales/zh-TW.json index cb97e2d9db..064789849f 100644 --- a/BitFun-Installer/src/i18n/locales/zh-TW.json +++ b/BitFun-Installer/src/i18n/locales/zh-TW.json @@ -151,13 +151,13 @@ "testSuccess": "測試成功", "testFailed": "測試失敗", "fillApiKeyBeforeFetch": "請先填寫 API Key 再獲取模型列表", - "fetchingModels": "正在擷取模型清單...", + "fetchingModels": "正在拉取模型列表...", "fetchFailedFallback": "拉取模型列表失敗,已回退到常用預設模型", "fetchEmptyFallback": "供應商未返回可用模型,已回退到常用預設模型", "usingPresetModels": "目前顯示的是常用預設模型", "addCustomModel": "新增自定義模型", "form": { - "baseUrl": "API 位址", + "baseUrl": "API地址", "apiKey": "API密鑰", "apiKeyPlaceholder": "輸入您的 API Key", "provider": "請求格式", diff --git a/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs b/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs index f7b5a3cb4d..fff5419042 100644 --- a/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs +++ b/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs @@ -34,7 +34,7 @@ struct ClaudeModelVersion { fn wants_bearer_auth(url: &str) -> bool { url.contains("bigmodel.cn") || url.contains("api.z.ai") - || url.contains("api.kimi.com") + || url.contains("api.kimi.com/coding") || ((url.contains("api.moonshot.cn") || url.contains("api.moonshot.ai")) && url.contains("/anthropic")) } @@ -49,6 +49,11 @@ pub(crate) fn apply_headers( if wants_bearer_auth(url) { builder = builder.header("Authorization", format!("Bearer {}", client.config.api_key)); + // Keep bigmodel.cn exactly as it shipped (no version header); the other + // bearer gateways follow the Anthropic protocol, which requires it. + if !url.contains("bigmodel.cn") { + builder = builder.header("anthropic-version", "2023-06-01"); + } } else { builder = builder .header("x-api-key", &client.config.api_key) From 4f079f97807563aa538fca10df5eb7022b7d4cc5 Mon Sep 17 00:00:00 2001 From: bowen628 Date: Sun, 26 Jul 2026 20:01:22 +0800 Subject: [PATCH 5/7] test(models): lock provider preset invariants The presets are duplicated across the web UI, the installer, the URL catalog and three locale bundles, and every drift between them has shipped either a request-time failure or an untranslated label. Assert the invariants instead of re-checking them by hand: catalog coverage both ways, note keys resolving to distinct labels in every locale, base URL shape matching what each adapter appends, and no URL owned by two providers. Verified by mutation: dropping a catalog URL, deleting a locale key and appending /v1 to an Anthropic base URL each fail a distinct assertion. --- .../config/services/providerPresets.test.ts | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 src/web-ui/src/infrastructure/config/services/providerPresets.test.ts diff --git a/src/web-ui/src/infrastructure/config/services/providerPresets.test.ts b/src/web-ui/src/infrastructure/config/services/providerPresets.test.ts new file mode 100644 index 0000000000..e33a7e5dc8 --- /dev/null +++ b/src/web-ui/src/infrastructure/config/services/providerPresets.test.ts @@ -0,0 +1,109 @@ +import { describe, expect, it, vi } from 'vitest'; + +import enUS from '@/locales/en-US/settings/ai-model.json'; +import zhCN from '@/locales/zh-CN/settings/ai-model.json'; +import zhTW from '@/locales/zh-TW/settings/ai-model.json'; + +vi.mock('@/infrastructure/i18n', () => ({ + i18nService: { + t: (key: string) => key, + }, +})); + +const LOCALES = { 'en-US': enUS, 'zh-CN': zhCN, 'zh-TW': zhTW } as Record< + string, + { providers: Record }> } +>; + +async function loadPresets() { + const [{ PROVIDER_TEMPLATES }, { PROVIDER_URL_CATALOG }] = await Promise.all([ + import('./modelConfigs'), + import('./providerCatalog'), + ]); + return { PROVIDER_TEMPLATES, PROVIDER_URL_CATALOG }; +} + +/** + * These presets are duplicated across the web UI, the installer, the URL catalog and + * three locale bundles, so a drift between any two of them ships a provider entry that + * silently fails at request time or renders an untranslated key. Assert the invariants. + */ +describe('provider presets', () => { + it('offers every preset URL through the provider URL catalog', async () => { + const { PROVIDER_TEMPLATES, PROVIDER_URL_CATALOG } = await loadPresets(); + + for (const template of Object.values(PROVIDER_TEMPLATES)) { + const catalogItem = PROVIDER_URL_CATALOG.find(item => item.id === template.id); + expect(catalogItem, `provider ${template.id} is missing from PROVIDER_URL_CATALOG`).toBeDefined(); + + const catalogUrls = new Set([catalogItem!.baseUrl, ...(catalogItem!.baseUrlOptions ?? [])]); + const presetUrls = new Set([ + template.baseUrl, + ...(template.baseUrlOptions ?? []).map(option => option.url), + ]); + + expect([...presetUrls].filter(url => !catalogUrls.has(url))).toEqual([]); + expect([...catalogUrls].filter(url => !presetUrls.has(url))).toEqual([]); + } + }); + + it('resolves every base URL option note in all locales, with distinct labels', async () => { + const { PROVIDER_TEMPLATES } = await loadPresets(); + + for (const template of Object.values(PROVIDER_TEMPLATES)) { + const options = template.baseUrlOptions ?? []; + if (options.length === 0) continue; + + const notes = options.map(option => option.note); + expect(new Set(notes).size, `${template.id} reuses a note key`).toBe(notes.length); + + for (const [tag, bundle] of Object.entries(LOCALES)) { + const urlOptions = bundle.providers[template.id]?.urlOptions ?? {}; + const labels = notes.map(note => urlOptions[note]); + + expect( + labels.filter(label => !label), + `${tag} is missing providers.${template.id}.urlOptions entries`, + ).toEqual([]); + // Two endpoints sharing a label leaves the user unable to tell them apart. + expect(new Set(labels).size, `${tag} renders duplicate labels for ${template.id}`).toBe(labels.length); + expect(Object.keys(urlOptions).filter(key => !notes.includes(key))).toEqual([]); + } + } + }); + + it('shapes base URLs so the adapter appends the right suffix', async () => { + const { PROVIDER_TEMPLATES } = await loadPresets(); + + for (const template of Object.values(PROVIDER_TEMPLATES)) { + const entries = [ + { url: template.baseUrl, format: template.format }, + ...(template.baseUrlOptions ?? []), + ]; + + for (const { url, format } of entries) { + if (format === 'anthropic') { + // The Anthropic adapter appends /v1/messages itself. + expect(url.endsWith('/v1'), `${template.id}: ${url} would request /v1/v1/messages`).toBe(false); + } + if (format === 'openai') { + expect(url.endsWith('/messages'), `${template.id}: ${url} is not an OpenAI path`).toBe(false); + } + } + } + }); + + it('never maps one URL to two providers', async () => { + const { PROVIDER_TEMPLATES } = await loadPresets(); + const owner = new Map(); + + for (const template of Object.values(PROVIDER_TEMPLATES)) { + const urls = [template.baseUrl, ...(template.baseUrlOptions ?? []).map(option => option.url)]; + for (const url of urls) { + const previous = owner.get(url); + expect(previous ?? template.id, `${url} is claimed by ${previous} and ${template.id}`).toBe(template.id); + owner.set(url, template.id); + } + } + }); +}); From 2789f192b62fad8eb2ef74d94c515b3a893a79ba Mon Sep 17 00:00:00 2001 From: bowen628 Date: Sun, 26 Jul 2026 20:12:24 +0800 Subject: [PATCH 6/7] fix(models): correct verified endpoint facts and format-switch handling Third review pass (endpoint fact-check + Rust request tracing + frontend flow), all endpoints live-probed: - Note the Kimi For Coding model IDs on that base URL option; the plan serves k3 / kimi-for-coding, which are disjoint from the Moonshot API model list the provider block otherwise offers - Point the Moonshot and Anthropic help links at the hosts they now redirect to (platform.kimi.ai, platform.claude.com) - Normalize per-model reasoning drafts when the base URL dropdown changes the wire format, matching what the request-format dropdown already did; otherwise an adaptive mode or thinking budget could persist against a format that cannot express it - Installer: only a preset URL implies its format. A hand-typed proxy URL no longer snaps the format back to the template default, which would write an OpenAI config against an Anthropic-only endpoint - Installer: fall back to a note key's last segment instead of printing the whole dotted path when a translation is missing Cover the auth split with tests: Z.AI / Moonshot / Kimi document bearer tokens, while DeepSeek and MiniMax document x-api-key, so the two groups must not be unified. Also pin the Claude 5 thinking capabilities and assert vendor model names never take the Claude-specific paths. --- BitFun-Installer/src/data/modelProviders.ts | 4 +- BitFun-Installer/src/i18n/locales/en.json | 2 +- BitFun-Installer/src/i18n/locales/zh-TW.json | 2 +- BitFun-Installer/src/i18n/locales/zh.json | 2 +- BitFun-Installer/src/pages/ModelSetup.tsx | 11 ++- .../src/providers/anthropic/request.rs | 71 +++++++++++++++++++ .../config/components/AIModelConfig.tsx | 9 +++ .../config/services/modelConfigs.ts | 4 +- .../src/locales/en-US/settings/ai-model.json | 2 +- .../src/locales/zh-CN/settings/ai-model.json | 2 +- .../src/locales/zh-TW/settings/ai-model.json | 2 +- 11 files changed, 98 insertions(+), 13 deletions(-) diff --git a/BitFun-Installer/src/data/modelProviders.ts b/BitFun-Installer/src/data/modelProviders.ts index 17bb03aa42..4b0986e2d5 100644 --- a/BitFun-Installer/src/data/modelProviders.ts +++ b/BitFun-Installer/src/data/modelProviders.ts @@ -61,7 +61,7 @@ export const PROVIDER_TEMPLATES: Record = { baseUrl: 'https://api.anthropic.com', format: 'anthropic', models: ['claude-opus-5', 'claude-fable-5', 'claude-sonnet-5', 'claude-haiku-4-5'], - helpUrl: 'https://console.anthropic.com/', + helpUrl: 'https://platform.claude.com/', }, minimax: { id: 'minimax', @@ -101,7 +101,7 @@ export const PROVIDER_TEMPLATES: Record = { baseUrl: 'https://api.moonshot.cn/v1', format: 'openai', models: ['kimi-k3', 'kimi-k2.7-code', 'kimi-k2.6'], - helpUrl: 'https://platform.moonshot.ai/console', + helpUrl: 'https://platform.kimi.ai/console', baseUrlOptions: [ { url: 'https://api.moonshot.cn/v1', diff --git a/BitFun-Installer/src/i18n/locales/en.json b/BitFun-Installer/src/i18n/locales/en.json index 4f8ee0e8de..61fbc6e5e5 100644 --- a/BitFun-Installer/src/i18n/locales/en.json +++ b/BitFun-Installer/src/i18n/locales/en.json @@ -84,7 +84,7 @@ "anthropic": "China - Anthropic Format", "intl": "Global - OpenAI Format", "intlAnthropic": "Global - Anthropic Format", - "kimiForCoding": "Kimi For Coding Plan" + "kimiForCoding": "Kimi For Coding plan (models: k3 / kimi-for-coding)" } }, "deepseek": { diff --git a/BitFun-Installer/src/i18n/locales/zh-TW.json b/BitFun-Installer/src/i18n/locales/zh-TW.json index 064789849f..ddcadab10f 100644 --- a/BitFun-Installer/src/i18n/locales/zh-TW.json +++ b/BitFun-Installer/src/i18n/locales/zh-TW.json @@ -84,7 +84,7 @@ "anthropic": "國內-Anthropic格式", "intl": "海外-OpenAI格式", "intlAnthropic": "海外-Anthropic格式", - "kimiForCoding": "Kimi For Coding 套餐" + "kimiForCoding": "Kimi For Coding 套餐(模型 k3 / kimi-for-coding)" } }, "deepseek": { diff --git a/BitFun-Installer/src/i18n/locales/zh.json b/BitFun-Installer/src/i18n/locales/zh.json index 3c6c3464a5..a0e852a011 100644 --- a/BitFun-Installer/src/i18n/locales/zh.json +++ b/BitFun-Installer/src/i18n/locales/zh.json @@ -84,7 +84,7 @@ "anthropic": "国内-Anthropic格式", "intl": "海外-OpenAI格式", "intlAnthropic": "海外-Anthropic格式", - "kimiForCoding": "Kimi For Coding 套餐" + "kimiForCoding": "Kimi For Coding 套餐(模型 k3 / kimi-for-coding)" } }, "deepseek": { diff --git a/BitFun-Installer/src/pages/ModelSetup.tsx b/BitFun-Installer/src/pages/ModelSetup.tsx index 07cf321776..a91a556931 100644 --- a/BitFun-Installer/src/pages/ModelSetup.tsx +++ b/BitFun-Installer/src/pages/ModelSetup.tsx @@ -320,7 +320,8 @@ export function ModelSetup({ options, setOptions, onSkip, onNext, onTestConnecti return template.baseUrlOptions.map((opt) => ({ value: opt.url, label: opt.url, - description: `${opt.format.toUpperCase()} · ${opt.noteKey ? t(opt.noteKey) : ''}`, + // Degrade to the key's last segment rather than the whole dotted path. + description: `${opt.format.toUpperCase()} · ${opt.noteKey ? t(opt.noteKey, { defaultValue: opt.noteKey.split('.').pop() }) : ''}`, })); }, [template, t]); @@ -440,8 +441,12 @@ export function ModelSetup({ options, setOptions, onSkip, onNext, onTestConnecti setBaseUrl(e.target.value); resetTestState(); resetRemoteDiscovery(); - if (template && !isCustomProvider) { - setApiFormat(resolveProviderFormat(template, e.target.value)); + // Only a preset URL implies its format. A hand-typed proxy URL + // keeps whatever the user picked, instead of snapping back to + // the template default and mismatching the endpoint. + const preset = template?.baseUrlOptions?.find((o) => o.url === e.target.value.trim()); + if (preset && !isCustomProvider) { + setApiFormat(preset.format); } }} /> diff --git a/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs b/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs index fff5419042..37dcaef67f 100644 --- a/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs +++ b/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs @@ -379,3 +379,74 @@ pub(crate) async fn send_stream( ) .await } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bearer_auth_matches_each_gateway_documented_scheme() { + // Vendors that document ANTHROPIC_AUTH_TOKEN for their Claude-compatible gateway. + for url in [ + "https://open.bigmodel.cn/api/anthropic/v1/messages", + "https://api.z.ai/api/anthropic/v1/messages", + "https://api.kimi.com/coding/v1/messages", + "https://api.moonshot.cn/anthropic/v1/messages", + "https://api.moonshot.ai/anthropic/v1/messages", + ] { + assert!(wants_bearer_auth(url), "{url} should authenticate with a bearer token"); + } + + // These document ANTHROPIC_API_KEY (x-api-key) instead, so they must stay on the + // default branch even though their paths look similar. + for url in [ + "https://api.deepseek.com/anthropic/v1/messages", + "https://api.minimaxi.com/anthropic/v1/messages", + "https://api.minimax.io/anthropic/v1/messages", + "https://api.siliconflow.cn/v1/messages", + "https://api.anthropic.com/v1/messages", + ] { + assert!(!wants_bearer_auth(url), "{url} should authenticate with x-api-key"); + } + } + + #[test] + fn bearer_auth_holds_for_model_discovery_urls() { + // Discovery hits /v1/models on the same base, so both paths must agree. + assert!(wants_bearer_auth("https://api.kimi.com/coding/v1/models")); + assert!(wants_bearer_auth("https://api.moonshot.cn/anthropic/v1/models")); + assert!(!wants_bearer_auth("https://api.moonshot.cn/v1/models")); + } + + #[test] + fn moonshot_bearer_auth_is_limited_to_the_anthropic_gateway() { + // The OpenAI-compatible Moonshot base never reaches this module, but keep the + // qualifier honest so a future caller cannot pick up the wrong scheme. + assert!(!wants_bearer_auth("https://api.moonshot.cn/v1/chat/completions")); + assert!(!wants_bearer_auth("https://api.moonshot.ai/v1/chat/completions")); + } + + #[test] + fn claude_5_family_thinking_capabilities() { + use AnthropicThinkingCapability::*; + + // Fable rejects an explicit thinking.type=disabled, so it must omit the field. + assert_eq!(anthropic_thinking_capability("claude-fable-5"), AdaptiveDefaultNoDisabled); + assert_eq!(anthropic_thinking_capability("claude-mythos-preview"), AdaptiveDefaultNoDisabled); + assert_eq!(anthropic_thinking_capability("claude-opus-5"), AdaptivePreferred); + assert_eq!(anthropic_thinking_capability("claude-sonnet-5"), AdaptivePreferred); + // Haiku 4.5 takes budget_tokens and errors on effort. + assert_eq!(anthropic_thinking_capability("claude-haiku-4-5"), ManualOnly); + } + + #[test] + fn non_claude_models_never_take_the_claude_thinking_paths() { + use AnthropicThinkingCapability::*; + + // These reach the Anthropic builder through vendor gateways; emitting Claude's + // adaptive fields to them would be rejected. + for model in ["minimax-m3", "glm-5.2", "kimi-k3", "kimi-k2.7-code"] { + assert_eq!(anthropic_thinking_capability(model), ManualOnly, "{model}"); + } + } +} diff --git a/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx b/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx index e15328d88e..9cfd4ba7c7 100644 --- a/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx +++ b/src/web-ui/src/infrastructure/config/components/AIModelConfig.tsx @@ -2373,6 +2373,15 @@ const AIModelConfig: React.FC = () => { const selectedOption = currentTemplate.baseUrlOptions!.find(opt => opt.url === value); const newProvider = selectedOption?.format || editingConfig.provider || 'openai'; resetRemoteModelDiscovery(); + if (newProvider !== editingConfig.provider) { + setSelectedModelDrafts(prevDrafts => + prevDrafts.map(draft => normalizeDraftReasoningForProvider(draft, { + name: editingConfig?.name, + provider: newProvider, + base_url: value as string, + })) + ); + } setEditingConfig(prev => ({ ...prev, base_url: value as string, diff --git a/src/web-ui/src/infrastructure/config/services/modelConfigs.ts b/src/web-ui/src/infrastructure/config/services/modelConfigs.ts index 6210556f23..2413315bc2 100644 --- a/src/web-ui/src/infrastructure/config/services/modelConfigs.ts +++ b/src/web-ui/src/infrastructure/config/services/modelConfigs.ts @@ -121,7 +121,7 @@ export const PROVIDER_TEMPLATES: Record = { models: ['claude-opus-5', 'claude-fable-5', 'claude-sonnet-5', 'claude-haiku-4-5'], requiresApiKey: true, description: t('settings/ai-model:providers.anthropic.description'), - helpUrl: 'https://console.anthropic.com/' + helpUrl: 'https://platform.claude.com/' }, minimax: { @@ -149,7 +149,7 @@ export const PROVIDER_TEMPLATES: Record = { models: ['kimi-k3', 'kimi-k2.7-code', 'kimi-k2.6'], requiresApiKey: true, description: t('settings/ai-model:providers.moonshot.description'), - helpUrl: 'https://platform.moonshot.ai/console', + helpUrl: 'https://platform.kimi.ai/console', baseUrlOptions: [ { url: 'https://api.moonshot.cn/v1', format: 'openai', note: 'default' }, { url: 'https://api.moonshot.cn/anthropic', format: 'anthropic', note: 'anthropic' }, diff --git a/src/web-ui/src/locales/en-US/settings/ai-model.json b/src/web-ui/src/locales/en-US/settings/ai-model.json index f47e93f3c7..8acb8f71f2 100644 --- a/src/web-ui/src/locales/en-US/settings/ai-model.json +++ b/src/web-ui/src/locales/en-US/settings/ai-model.json @@ -163,7 +163,7 @@ "anthropic": "China - Anthropic Format", "intl": "Global - OpenAI Format", "intlAnthropic": "Global - Anthropic Format", - "kimiForCoding": "Kimi For Coding Plan" + "kimiForCoding": "Kimi For Coding plan (models: k3 / kimi-for-coding)" } }, "deepseek": { diff --git a/src/web-ui/src/locales/zh-CN/settings/ai-model.json b/src/web-ui/src/locales/zh-CN/settings/ai-model.json index 69a40c4edf..47c72ca259 100644 --- a/src/web-ui/src/locales/zh-CN/settings/ai-model.json +++ b/src/web-ui/src/locales/zh-CN/settings/ai-model.json @@ -163,7 +163,7 @@ "anthropic": "国内-Anthropic格式", "intl": "海外-OpenAI格式", "intlAnthropic": "海外-Anthropic格式", - "kimiForCoding": "Kimi For Coding 套餐" + "kimiForCoding": "Kimi For Coding 套餐(模型 k3 / kimi-for-coding)" } }, "deepseek": { diff --git a/src/web-ui/src/locales/zh-TW/settings/ai-model.json b/src/web-ui/src/locales/zh-TW/settings/ai-model.json index ae0df56ec9..feac1c186a 100644 --- a/src/web-ui/src/locales/zh-TW/settings/ai-model.json +++ b/src/web-ui/src/locales/zh-TW/settings/ai-model.json @@ -163,7 +163,7 @@ "anthropic": "國內-Anthropic格式", "intl": "海外-OpenAI格式", "intlAnthropic": "海外-Anthropic格式", - "kimiForCoding": "Kimi For Coding 套餐" + "kimiForCoding": "Kimi For Coding 套餐(模型 k3 / kimi-for-coding)" } }, "deepseek": { From 121bbf5d175a0b26e3bacefd4e33f00ac73114d4 Mon Sep 17 00:00:00 2001 From: bowen628 Date: Sun, 26 Jul 2026 20:19:29 +0800 Subject: [PATCH 7/7] style(models): rustfmt the new anthropic request tests --- .../src/providers/anthropic/request.rs | 47 +++++++++++++++---- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs b/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs index 37dcaef67f..c8f57bb6d0 100644 --- a/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs +++ b/src/crates/adapters/ai-adapters/src/providers/anthropic/request.rs @@ -394,7 +394,10 @@ mod tests { "https://api.moonshot.cn/anthropic/v1/messages", "https://api.moonshot.ai/anthropic/v1/messages", ] { - assert!(wants_bearer_auth(url), "{url} should authenticate with a bearer token"); + assert!( + wants_bearer_auth(url), + "{url} should authenticate with a bearer token" + ); } // These document ANTHROPIC_API_KEY (x-api-key) instead, so they must stay on the @@ -406,7 +409,10 @@ mod tests { "https://api.siliconflow.cn/v1/messages", "https://api.anthropic.com/v1/messages", ] { - assert!(!wants_bearer_auth(url), "{url} should authenticate with x-api-key"); + assert!( + !wants_bearer_auth(url), + "{url} should authenticate with x-api-key" + ); } } @@ -414,7 +420,9 @@ mod tests { fn bearer_auth_holds_for_model_discovery_urls() { // Discovery hits /v1/models on the same base, so both paths must agree. assert!(wants_bearer_auth("https://api.kimi.com/coding/v1/models")); - assert!(wants_bearer_auth("https://api.moonshot.cn/anthropic/v1/models")); + assert!(wants_bearer_auth( + "https://api.moonshot.cn/anthropic/v1/models" + )); assert!(!wants_bearer_auth("https://api.moonshot.cn/v1/models")); } @@ -422,8 +430,12 @@ mod tests { fn moonshot_bearer_auth_is_limited_to_the_anthropic_gateway() { // The OpenAI-compatible Moonshot base never reaches this module, but keep the // qualifier honest so a future caller cannot pick up the wrong scheme. - assert!(!wants_bearer_auth("https://api.moonshot.cn/v1/chat/completions")); - assert!(!wants_bearer_auth("https://api.moonshot.ai/v1/chat/completions")); + assert!(!wants_bearer_auth( + "https://api.moonshot.cn/v1/chat/completions" + )); + assert!(!wants_bearer_auth( + "https://api.moonshot.ai/v1/chat/completions" + )); } #[test] @@ -431,12 +443,27 @@ mod tests { use AnthropicThinkingCapability::*; // Fable rejects an explicit thinking.type=disabled, so it must omit the field. - assert_eq!(anthropic_thinking_capability("claude-fable-5"), AdaptiveDefaultNoDisabled); - assert_eq!(anthropic_thinking_capability("claude-mythos-preview"), AdaptiveDefaultNoDisabled); - assert_eq!(anthropic_thinking_capability("claude-opus-5"), AdaptivePreferred); - assert_eq!(anthropic_thinking_capability("claude-sonnet-5"), AdaptivePreferred); + assert_eq!( + anthropic_thinking_capability("claude-fable-5"), + AdaptiveDefaultNoDisabled + ); + assert_eq!( + anthropic_thinking_capability("claude-mythos-preview"), + AdaptiveDefaultNoDisabled + ); + assert_eq!( + anthropic_thinking_capability("claude-opus-5"), + AdaptivePreferred + ); + assert_eq!( + anthropic_thinking_capability("claude-sonnet-5"), + AdaptivePreferred + ); // Haiku 4.5 takes budget_tokens and errors on effort. - assert_eq!(anthropic_thinking_capability("claude-haiku-4-5"), ManualOnly); + assert_eq!( + anthropic_thinking_capability("claude-haiku-4-5"), + ManualOnly + ); } #[test]