From c68b875a363468162bbfd962f9a3ced2be5e9dc6 Mon Sep 17 00:00:00 2001 From: Blade <46746496+Blade096@users.noreply.github.com> Date: Mon, 9 Feb 2026 11:31:38 +0800 Subject: [PATCH] Add GLM Coding (China) provider with OpenAI-compatible API (#83) --- open-sse/config/constants.js | 5 +++ open-sse/config/providerModels.js | 7 ++++ public/providers/glm-cn.png | Bin 0 -> 2548 bytes src/app/api/providers/[id]/test/route.js | 18 +++++++++ src/app/api/providers/validate/route.js | 48 ++++++++++++++++------- src/shared/constants/config.js | 1 + src/shared/constants/providers.js | 1 + 7 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 public/providers/glm-cn.png diff --git a/open-sse/config/constants.js b/open-sse/config/constants.js index 4973962..0e2ddc2 100644 --- a/open-sse/config/constants.js +++ b/open-sse/config/constants.js @@ -104,6 +104,11 @@ export const PROVIDERS = { "Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14" } }, + "glm-cn": { + baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4/chat/completions", + format: "openai", + headers: {} + }, kimi: { baseUrl: "https://api.kimi.com/coding/v1/messages", format: "claude", diff --git a/open-sse/config/providerModels.js b/open-sse/config/providerModels.js index 803d646..7f7cb07 100644 --- a/open-sse/config/providerModels.js +++ b/open-sse/config/providerModels.js @@ -131,6 +131,12 @@ export const PROVIDER_MODELS = { { id: "glm-4.7", name: "GLM 4.7" }, { id: "glm-4.6v", name: "GLM 4.6V (Vision)" }, ], + "glm-cn": [ + { id: "glm-4.7", name: "GLM-4.7" }, + { id: "glm-4.6", name: "GLM-4.6" }, + { id: "glm-4.5", name: "GLM-4.5" }, + { id: "glm-4.5-air", name: "GLM-4.5-Air" }, + ], kimi: [ { id: "kimi-k2.5", name: "Kimi K2.5" }, { id: "kimi-k2.5-thinking", name: "Kimi K2.5 Thinking" }, @@ -188,6 +194,7 @@ export const PROVIDER_ID_TO_ALIAS = { gemini: "gemini", openrouter: "openrouter", glm: "glm", + "glm-cn": "glm-cn", kimi: "kimi", minimax: "minimax", "minimax-cn": "minimax", diff --git a/public/providers/glm-cn.png b/public/providers/glm-cn.png new file mode 100644 index 0000000000000000000000000000000000000000..cee2b24be2c9f03697cdf0cc2fed04b7116a7cb5 GIT binary patch literal 2548 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 X=EL{!GuID{0D&B{w6}PAlpOm%q=9T+ literal 0 HcmV?d00001 diff --git a/src/app/api/providers/[id]/test/route.js b/src/app/api/providers/[id]/test/route.js index 629752b..cf4f90d 100644 --- a/src/app/api/providers/[id]/test/route.js +++ b/src/app/api/providers/[id]/test/route.js @@ -406,6 +406,24 @@ async function testApiKeyConnection(connection) { return { valid, error: valid ? null : "Invalid API key" }; } + case "glm-cn": { + // GLM Coding (China) uses OpenAI-compatible API + const res = await fetch("https://open.bigmodel.cn/api/coding/paas/v4/chat/completions", { + method: "POST", + headers: { + "Authorization": `Bearer ${connection.apiKey}`, + "content-type": "application/json", + }, + body: JSON.stringify({ + model: "glm-4.7", + max_tokens: 1, + messages: [{ role: "user", content: "test" }], + }), + }); + const valid = res.status !== 401 && res.status !== 403; + return { valid, error: valid ? null : "Invalid API key" }; + } + case "minimax": case "minimax-cn": { // MiniMax uses Claude-compatible API diff --git a/src/app/api/providers/validate/route.js b/src/app/api/providers/validate/route.js index b26faaa..c06844a 100644 --- a/src/app/api/providers/validate/route.js +++ b/src/app/api/providers/validate/route.js @@ -99,29 +99,49 @@ export async function POST(request) { break; case "glm": + case "glm-cn": case "kimi": case "minimax": case "minimax-cn": { const claudeBaseUrls = { glm: "https://api.z.ai/api/anthropic/v1/messages", + "glm-cn": "https://open.bigmodel.cn/api/coding/paas/v4/chat/completions", kimi: "https://api.kimi.com/coding/v1/messages", minimax: "https://api.minimax.io/anthropic/v1/messages", "minimax-cn": "https://api.minimaxi.com/anthropic/v1/messages", }; - const claudeRes = await fetch(claudeBaseUrls[provider], { - method: "POST", - headers: { - "x-api-key": apiKey, - "anthropic-version": "2023-06-01", - "content-type": "application/json", - }, - body: JSON.stringify({ - model: "claude-sonnet-4-20250514", - max_tokens: 1, - messages: [{ role: "user", content: "test" }], - }), - }); - isValid = claudeRes.status !== 401; + + // glm-cn uses OpenAI format + if (provider === "glm-cn") { + const glmCnRes = await fetch(claudeBaseUrls[provider], { + method: "POST", + headers: { + "Authorization": `Bearer ${apiKey}`, + "content-type": "application/json", + }, + body: JSON.stringify({ + model: "glm-4.7", + max_tokens: 1, + messages: [{ role: "user", content: "test" }], + }), + }); + isValid = glmCnRes.status !== 401 && glmCnRes.status !== 403; + } else { + const claudeRes = await fetch(claudeBaseUrls[provider], { + method: "POST", + headers: { + "x-api-key": apiKey, + "anthropic-version": "2023-06-01", + "content-type": "application/json", + }, + body: JSON.stringify({ + model: "claude-sonnet-4-20250514", + max_tokens: 1, + messages: [{ role: "user", content: "test" }], + }), + }); + isValid = claudeRes.status !== 401; + } break; } diff --git a/src/shared/constants/config.js b/src/shared/constants/config.js index 9c3bf89..a0bcdb4 100644 --- a/src/shared/constants/config.js +++ b/src/shared/constants/config.js @@ -33,6 +33,7 @@ export const API_ENDPOINTS = { export const PROVIDER_ENDPOINTS = { openrouter: "https://openrouter.ai/api/v1/chat/completions", glm: "https://api.z.ai/api/anthropic/v1/messages", + "glm-cn": "https://open.bigmodel.cn/api/coding/paas/v4/chat/completions", kimi: "https://api.kimi.com/coding/v1/messages", minimax: "https://api.minimax.io/anthropic/v1/messages", "minimax-cn": "https://api.minimaxi.com/anthropic/v1/messages", diff --git a/src/shared/constants/providers.js b/src/shared/constants/providers.js index 57d3652..936504f 100644 --- a/src/shared/constants/providers.js +++ b/src/shared/constants/providers.js @@ -20,6 +20,7 @@ export const OAUTH_PROVIDERS = { export const APIKEY_PROVIDERS = { openrouter: { id: "openrouter", alias: "openrouter", name: "OpenRouter", icon: "router", color: "#6366F1", textIcon: "OR" , passthroughModels: true }, glm: { id: "glm", alias: "glm", name: "GLM Coding", icon: "code", color: "#2563EB", textIcon: "GL" }, + "glm-cn": { id: "glm-cn", alias: "glm-cn", name: "GLM Coding (China)", icon: "code", color: "#DC2626", textIcon: "GC" }, kimi: { id: "kimi", alias: "kimi", name: "Kimi Coding", icon: "psychology", color: "#1E3A8A", textIcon: "KM" }, minimax: { id: "minimax", alias: "minimax", name: "Minimax Coding", icon: "memory", color: "#7C3AED", textIcon: "MM" }, "minimax-cn": { id: "minimax-cn", alias: "minimax-cn", name: "Minimax (China)", icon: "memory", color: "#DC2626", textIcon: "MC" },