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 @@
+
\ 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");