From 0bfb1035e447b071eeb1947fc6937c7d880d6402 Mon Sep 17 00:00:00 2001 From: U1 <56990620+CHNHJF@users.noreply.github.com> Date: Sat, 12 Sep 2026 09:29:04 +0800 Subject: [PATCH] feat(ai): add Zhipu provider preset --- apps/desktop/public/icons/ai/zhipu.svg | 1 + .../stores/__tests__/settingsStore.spec.ts | 19 ++++++++++++++++ apps/desktop/src/stores/settingsStore.ts | 14 ++++++++++++ apps/desktop/src/types/ai.ts | 22 ++++++++++++++++++- crates/dbx-core/src/ai.rs | 7 ++++++ crates/dbx-core/src/ai_effort.rs | 6 ++--- crates/dbx-core/src/ai_model_filter.rs | 1 + packages/app-tests/settingsStore.test.ts | 6 +++++ 8 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 apps/desktop/public/icons/ai/zhipu.svg diff --git a/apps/desktop/public/icons/ai/zhipu.svg b/apps/desktop/public/icons/ai/zhipu.svg new file mode 100644 index 0000000000..ac526a3bf8 --- /dev/null +++ b/apps/desktop/public/icons/ai/zhipu.svg @@ -0,0 +1 @@ +Zhipu \ No newline at end of file diff --git a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts index 48f68870ae..9006ffc56a 100644 --- a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts +++ b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts @@ -772,6 +772,19 @@ describe("settingsStore AI API key normalization", () => { expect(normalizeAiConfig({ endpoint: "https://api.moonshot.cn/v1", model: "kimi-k2.5" }).provider).toBe("kimi"); }); + it("provides Zhipu defaults and recognizes legacy Zhipu configurations", () => { + expect(AI_PROVIDER_PRESETS.zhipu).toMatchObject({ + provider: "zhipu", + endpoint: "https://open.bigmodel.cn/api/paas/v4", + model: "glm-5.3", + apiStyle: "completions", + authMethod: "bearer", + requiresApiKey: true, + }); + expect(normalizeAiConfig({ endpoint: "https://open.bigmodel.cn/api/paas/v4", model: "glm-4.6" }).provider).toBe("zhipu"); + expect(normalizeAiConfig({ endpoint: "https://api.z.ai/api/paas/v4", model: "glm-5.2" }).provider).toBe("zhipu"); + }); + it("uses the mainland MiniMax endpoint only for new zh-CN presets", () => { expect(getAiProviderPresetDefaultEndpoint(AI_PROVIDER_PRESETS.minimax, "zh-CN")).toBe("https://api.minimaxi.com/v1"); expect(getAiProviderPresetDefaultEndpoint(AI_PROVIDER_PRESETS.minimax, "zh-TW")).toBe("https://api.minimax.io/v1"); @@ -779,6 +792,12 @@ describe("settingsStore AI API key normalization", () => { expect(getAiProviderPresetDefaultEndpoint(AI_PROVIDER_PRESETS.openai, "zh-CN")).toBe(AI_PROVIDER_PRESETS.openai.endpoint); }); + it("uses the international Zhipu endpoint for non-zh-CN presets", () => { + expect(getAiProviderPresetDefaultEndpoint(AI_PROVIDER_PRESETS.zhipu, "zh-CN")).toBe("https://open.bigmodel.cn/api/paas/v4"); + expect(getAiProviderPresetDefaultEndpoint(AI_PROVIDER_PRESETS.zhipu, "zh-TW")).toBe("https://api.z.ai/api/paas/v4"); + expect(getAiProviderPresetDefaultEndpoint(AI_PROVIDER_PRESETS.zhipu, "en")).toBe("https://api.z.ai/api/paas/v4"); + }); + it("preserves saved MiniMax endpoints during normalization", () => { expect(normalizeAiConfig({ provider: "minimax", endpoint: "https://api.minimaxi.com/v1" }).endpoint).toBe("https://api.minimaxi.com/v1"); expect(normalizeAiConfig({ provider: "minimax", endpoint: "https://minimax.example.com/v1" }).endpoint).toBe("https://minimax.example.com/v1"); diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index eebe0e9b95..440ded9860 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -301,6 +301,16 @@ export const AI_PROVIDER_PRESETS: Record = { authMethod: "bearer", requiresApiKey: true, }, + zhipu: { + label: "Zhipu", + iconSlug: "zhipu", + provider: "zhipu", + endpoint: "https://open.bigmodel.cn/api/paas/v4", + model: "glm-5.3", + apiStyle: "completions", + authMethod: "bearer", + requiresApiKey: true, + }, minimax: { label: "MiniMax", iconSlug: "minimax", @@ -490,6 +500,9 @@ export function getAiProviderPresetDefaultEndpoint(preset: AiProviderPreset | Ai if (preset.provider === "minimax" && locale === "zh-CN") { return "https://api.minimaxi.com/v1"; } + if (preset.provider === "zhipu" && locale !== "zh-CN") { + return "https://api.z.ai/api/paas/v4"; + } return preset.endpoint; } @@ -588,6 +601,7 @@ function inferAiProviderFromConfig(config: Partial | null | undefined) if (endpoint.includes("deepseek") || model.includes("deepseek")) return "deepseek"; if (endpoint.includes("moonshot") || endpoint.includes("kimi.com") || model.includes("kimi")) return "kimi"; if (endpoint.includes("dashscope") || endpoint.includes("aliyuncs") || model.includes("qwen")) return "qwen"; + if (endpoint.includes("bigmodel.cn") || endpoint.includes("api.z.ai") || model.startsWith("glm-")) return "zhipu"; if (endpoint.includes("generativelanguage.googleapis.com") || model.includes("gemini")) return "gemini"; if (endpoint.includes("minimax.io") || endpoint.includes("minimaxi.com") || model.includes("minimax")) return "minimax"; if (endpoint.includes("localhost:11434") || endpoint.includes("127.0.0.1:11434")) return "ollama"; diff --git a/apps/desktop/src/types/ai.ts b/apps/desktop/src/types/ai.ts index 6eace4fedb..abc56304e3 100644 --- a/apps/desktop/src/types/ai.ts +++ b/apps/desktop/src/types/ai.ts @@ -1,4 +1,24 @@ -export type AiProvider = "claude" | "openai" | "gemini" | "deepseek" | "kimi" | "qwen" | "minimax" | "ollama" | "anthropic-compatible" | "openai-compatible" | "claude-code-cli" | "pi-agent-cli" | "codex-cli" | "opencode-cli" | "cursor-cli" | "grok-cli" | "codebuddy-cli" | "qoder-cli" | "custom"; +export type AiProvider = + | "claude" + | "openai" + | "gemini" + | "deepseek" + | "kimi" + | "qwen" + | "zhipu" + | "minimax" + | "ollama" + | "anthropic-compatible" + | "openai-compatible" + | "claude-code-cli" + | "pi-agent-cli" + | "codex-cli" + | "opencode-cli" + | "cursor-cli" + | "grok-cli" + | "codebuddy-cli" + | "qoder-cli" + | "custom"; export type AiApiStyle = "completions" | "responses" | "anthropic-messages"; export type AiAuthMethod = "api-key" | "bearer"; export type AiEffortLevel = "low" | "medium" | "high" | "xhigh" | "max"; diff --git a/crates/dbx-core/src/ai.rs b/crates/dbx-core/src/ai.rs index 321c785d27..044f042b2d 100644 --- a/crates/dbx-core/src/ai.rs +++ b/crates/dbx-core/src/ai.rs @@ -88,6 +88,7 @@ pub enum AiProvider { Deepseek, Kimi, Qwen, + Zhipu, MiniMax, Ollama, #[serde(rename = "openai-compatible")] @@ -121,6 +122,7 @@ impl AiProvider { AiProvider::Deepseek => "deepseek", AiProvider::Kimi => "kimi", AiProvider::Qwen => "qwen", + AiProvider::Zhipu => "zhipu", AiProvider::MiniMax => "minimax", AiProvider::Ollama => "ollama", AiProvider::OpenaiCompatible => "openai-compatible", @@ -887,6 +889,7 @@ pub fn resolve_endpoint(config: &AiConfig) -> String { | AiProvider::Deepseek | AiProvider::Kimi | AiProvider::Qwen + | AiProvider::Zhipu | AiProvider::MiniMax | AiProvider::Ollama | AiProvider::OpenaiCompatible @@ -1689,6 +1692,7 @@ fn provider_requires_api_key(provider: &AiProvider) -> bool { | AiProvider::Deepseek | AiProvider::Kimi | AiProvider::Qwen + | AiProvider::Zhipu | AiProvider::MiniMax ) } @@ -2179,6 +2183,7 @@ pub async fn list_models_core(config: &AiConfig) -> Result, Str | AiProvider::Deepseek | AiProvider::Kimi | AiProvider::Qwen + | AiProvider::Zhipu | AiProvider::MiniMax | AiProvider::OpenaiCompatible => list_openai_compatible_models(&client, config).await?, AiProvider::Custom => { @@ -3614,6 +3619,7 @@ pub async fn complete(request: &AiCompletionRequest) -> Result { | AiProvider::Deepseek | AiProvider::Kimi | AiProvider::Qwen + | AiProvider::Zhipu | AiProvider::MiniMax | AiProvider::Ollama | AiProvider::OpenaiCompatible => { @@ -3676,6 +3682,7 @@ pub async fn stream( | AiProvider::Deepseek | AiProvider::Kimi | AiProvider::Qwen + | AiProvider::Zhipu | AiProvider::MiniMax | AiProvider::Ollama | AiProvider::OpenaiCompatible => { diff --git a/crates/dbx-core/src/ai_effort.rs b/crates/dbx-core/src/ai_effort.rs index 312bc65728..dd35712180 100644 --- a/crates/dbx-core/src/ai_effort.rs +++ b/crates/dbx-core/src/ai_effort.rs @@ -78,7 +78,7 @@ pub fn static_effort_capability(config: &AiConfig, model_id: &str) -> Option deepseek_capability(&model, source), AiProvider::Kimi => kimi_capability(&model, source), AiProvider::Qwen => qwen_capability(&model, source), - AiProvider::Ollama => ollama_capability(&model, source), + AiProvider::Zhipu | AiProvider::Ollama => ollama_capability(&model, source), AiProvider::MiniMax if matches_family(&model, "minimax-m3") => Some(boolean_capability(source)), AiProvider::MiniMax => None, AiProvider::AnthropicCompatible | AiProvider::OpenaiCompatible | AiProvider::Custom => { @@ -208,7 +208,7 @@ pub fn registry_source_url(provider: &AiProvider) -> Option<&'static str> { AiProvider::Deepseek => Some(DEEPSEEK_THINKING_DOCS), AiProvider::Kimi => Some(KIMI_REASONING_DOCS), AiProvider::Qwen => Some(QWEN_THINKING_DOCS), - AiProvider::Ollama => Some(OLLAMA_THINKING_DOCS), + AiProvider::Zhipu | AiProvider::Ollama => Some(OLLAMA_THINKING_DOCS), AiProvider::MiniMax => Some(MINIMAX_THINKING_DOCS), AiProvider::Claude | AiProvider::AnthropicCompatible @@ -287,7 +287,7 @@ pub fn apply_runtime_effort(body: &mut Value, config: &AiConfig) { AiProvider::Deepseek => apply_deepseek_effort(object, selection), AiProvider::Kimi => apply_kimi_effort(object, &config.model, selection), AiProvider::Qwen => apply_qwen_effort(object, selection), - AiProvider::Ollama => apply_openai_effort(object, &config.api_style, selection), + AiProvider::Zhipu | AiProvider::Ollama => apply_openai_effort(object, &config.api_style, selection), AiProvider::MiniMax => apply_minimax_effort(object, selection), AiProvider::Openai | AiProvider::OpenaiCompatible => apply_openai_effort(object, &config.api_style, selection), AiProvider::Custom => { diff --git a/crates/dbx-core/src/ai_model_filter.rs b/crates/dbx-core/src/ai_model_filter.rs index 94f5bde2cb..60aeb2ac03 100644 --- a/crates/dbx-core/src/ai_model_filter.rs +++ b/crates/dbx-core/src/ai_model_filter.rs @@ -92,6 +92,7 @@ pub(crate) fn model_is_assistant_compatible(provider: &AiProvider, model_id: &st | AiProvider::AnthropicCompatible | AiProvider::Deepseek | AiProvider::Kimi + | AiProvider::Zhipu | AiProvider::Ollama | AiProvider::OpenaiCompatible | AiProvider::CodexCli diff --git a/packages/app-tests/settingsStore.test.ts b/packages/app-tests/settingsStore.test.ts index 8e820f50c1..bba7aaf33d 100644 --- a/packages/app-tests/settingsStore.test.ts +++ b/packages/app-tests/settingsStore.test.ts @@ -892,6 +892,12 @@ test("AI provider presets include common hosted and local providers", () => { assert.equal(AI_PROVIDER_PRESETS.minimax.requiresApiKey, true); assert.equal(AI_PROVIDER_PRESETS.minimax.iconSlug, "minimax"); assert.equal(AI_PROVIDER_PRESETS.qwen.endpoint, "https://dashscope.aliyuncs.com/compatible-mode/v1"); + assert.equal(AI_PROVIDER_PRESETS.zhipu.endpoint, "https://open.bigmodel.cn/api/paas/v4"); + assert.equal(AI_PROVIDER_PRESETS.zhipu.model, "glm-5.3"); + assert.equal(AI_PROVIDER_PRESETS.zhipu.apiStyle, "completions"); + assert.equal(AI_PROVIDER_PRESETS.zhipu.authMethod, "bearer"); + assert.equal(AI_PROVIDER_PRESETS.zhipu.requiresApiKey, true); + assert.equal(AI_PROVIDER_PRESETS.zhipu.iconSlug, "zhipu"); assert.equal(AI_PROVIDER_PRESETS.ollama.endpoint, "http://localhost:11434/v1"); assert.equal(AI_PROVIDER_PRESETS.ollama.requiresApiKey, false); assert.equal(AI_PROVIDER_PRESETS.claude.authMethod, "api-key");