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 0000000..cee2b24 Binary files /dev/null and b/public/providers/glm-cn.png differ 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" },