diff --git a/open-sse/config/providerRegistry.ts b/open-sse/config/providerRegistry.ts index b441fd8251..41eeaebfe0 100644 --- a/open-sse/config/providerRegistry.ts +++ b/open-sse/config/providerRegistry.ts @@ -4318,6 +4318,92 @@ const _REGISTRY_EAGER: Record = { ], }, + zenmux: { + id: "zenmux", + alias: "zm", + format: "openai", + executor: "default", + baseUrl: "https://zenmux.ai/api/v1/chat/completions", + modelsUrl: "https://zenmux.ai/api/v1/models", + authType: "apikey", + authHeader: "bearer", + defaultContextLength: 128000, + models: [ + { + id: "google/gemini-3.1-pro-preview", + name: "Gemini 3.1 Pro Preview (ZenMux)", + contextLength: 1048576, + supportsVision: true, + toolCalling: true, + supportsReasoning: true, + }, + { + id: "google/gemini-3-flash-preview", + name: "Gemini 3 Flash Preview (ZenMux)", + contextLength: 1048576, + supportsVision: true, + toolCalling: true, + supportsReasoning: true, + }, + { + id: "openai/gpt-5", + name: "GPT-5 (ZenMux)", + contextLength: 400000, + supportsVision: true, + toolCalling: true, + supportsReasoning: true, + }, + { + id: "anthropic/claude-sonnet-4.5", + name: "Claude Sonnet 4.5 (ZenMux)", + contextLength: 200000, + supportsVision: true, + toolCalling: true, + supportsReasoning: true, + }, + { + id: "anthropic/claude-opus-4.5", + name: "Claude Opus 4.5 (ZenMux)", + contextLength: 200000, + supportsVision: true, + toolCalling: true, + supportsReasoning: true, + }, + { + id: "deepseek/deepseek-chat", + name: "DeepSeek V3.2 Chat (ZenMux)", + contextLength: 128000, + supportsVision: false, + toolCalling: true, + supportsReasoning: false, + }, + { + id: "x-ai/grok-4.1-fast", + name: "Grok 4.1 Fast (ZenMux)", + contextLength: 131072, + supportsVision: false, + toolCalling: true, + supportsReasoning: true, + }, + { + id: "mistralai/mistral-large-2512", + name: "Mistral Large 2512 (ZenMux)", + contextLength: 128000, + supportsVision: true, + toolCalling: true, + supportsReasoning: false, + }, + { + id: "z-ai/glm-4.6v-flash", + name: "GLM 4.6V Flash (ZenMux)", + contextLength: 128000, + supportsVision: true, + toolCalling: true, + supportsReasoning: false, + }, + ], + }, + theoldllm: { id: "theoldllm", alias: "tllm", diff --git a/src/shared/constants/config.ts b/src/shared/constants/config.ts index 85a93070a1..32a86de86d 100644 --- a/src/shared/constants/config.ts +++ b/src/shared/constants/config.ts @@ -29,6 +29,7 @@ export const PROVIDER_ENDPOINTS = { minimax: "https://api.minimax.io/anthropic/v1/messages", "minimax-cn": "https://api.minimaxi.com/anthropic/v1/messages", crof: "https://crof.ai/v1/chat/completions", + zenmux: "https://zenmux.ai/api/v1/chat/completions", openai: "https://api.openai.com/v1/chat/completions", anthropic: "https://api.anthropic.com/v1/messages", gemini: "https://generativelanguage.googleapis.com/v1beta/models", diff --git a/src/shared/constants/providers.ts b/src/shared/constants/providers.ts index 67df2b560a..de54566eee 100644 --- a/src/shared/constants/providers.ts +++ b/src/shared/constants/providers.ts @@ -2284,6 +2284,22 @@ export const APIKEY_PROVIDERS = { apiHint: "Discounted API proxy for 40+ models including GPT-5, Claude Opus 4.6, Claude Sonnet 4.6, Qwen 3.5. Get your API key at https://freeaiapikey.com/dashboard. Base URL: https://freeaiapikey.com/v1.", }, + zenmux: { + id: "zenmux", + alias: "zm", + name: "ZenMux", + icon: "neurology", + color: "#7C3AED", + textIcon: "ZM", + website: "https://zenmux.ai", + hasFree: true, + freeNote: + "Free tier includes access to Gemini 3 Flash, DeepSeek V3.2, Grok 4.1 Fast, Mistral Large, and more. Get your API key at https://zenmux.ai.", + authHint: + "Use your ZenMux API key in Authorization: Bearer . ZenMux is fully OpenAI-compatible. Base URL: https://zenmux.ai/api/v1.", + apiHint: + "ZenMux exposes an OpenAI-compatible chat completions endpoint at /api/v1/chat/completions, plus Anthropic Messages (/api/anthropic/v1/messages) and Google Gemini (/api/vertex-ai) protocol surfaces. OmniRoute uses the OpenAI protocol.", + }, }; // Sub-categories within APIKEY_PROVIDERS (used by dashboard and catalog views). diff --git a/tests/unit/zenmux-provider.test.ts b/tests/unit/zenmux-provider.test.ts new file mode 100644 index 0000000000..1fd54b9320 --- /dev/null +++ b/tests/unit/zenmux-provider.test.ts @@ -0,0 +1,67 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers.ts"); +const { PROVIDER_ENDPOINTS } = await import("../../src/shared/constants/config.ts"); +const { REGISTRY: providerRegistry } = await import("../../open-sse/config/providerRegistry.ts"); + +test("ZenMux is registered as an API-key provider with the canonical identity", () => { + const zenmux = APIKEY_PROVIDERS.zenmux; + assert.ok(zenmux, "APIKEY_PROVIDERS.zenmux must be defined"); + assert.equal(zenmux.id, "zenmux"); + assert.equal(zenmux.alias, "zm"); + assert.equal(zenmux.name, "ZenMux"); + assert.equal(zenmux.website, "https://zenmux.ai"); + assert.equal(typeof zenmux.textIcon, "string"); + assert.equal(zenmux.hasFree, true); +}); + +test("ZenMux exposes the OpenAI-compatible chat completions URL", () => { + assert.equal( + PROVIDER_ENDPOINTS.zenmux, + "https://zenmux.ai/api/v1/chat/completions" + ); +}); + +test("ZenMux registry entry uses OpenAI format with bearer apikey auth", () => { + const entry = providerRegistry.zenmux; + assert.ok(entry, "providerRegistry.zenmux must be defined"); + assert.equal(entry.id, "zenmux"); + assert.equal(entry.alias, "zm"); + assert.equal(entry.format, "openai"); + assert.equal(entry.executor, "default"); + assert.equal(entry.authType, "apikey"); + assert.equal(entry.authHeader, "bearer"); + assert.equal(entry.baseUrl, "https://zenmux.ai/api/v1/chat/completions"); + assert.equal(entry.modelsUrl, "https://zenmux.ai/api/v1/models"); +}); + +test("ZenMux seed model list includes the headline families and unique ids", () => { + const models = providerRegistry.zenmux.models; + const ids = models.map((m: { id: string }) => m.id); + assert.ok(ids.length >= 8, "expect a non-trivial seed list of 8+ models"); + assert.equal(new Set(ids).size, ids.length, "model ids must be unique"); + // Provider-prefixed family coverage matching ZenMux's multi-protocol router + for (const family of [ + "google/gemini-3", + "openai/gpt-5", + "anthropic/claude-sonnet", + "deepseek/deepseek", + "x-ai/grok-", + "mistralai/mistral", + ]) { + assert.ok( + ids.some((id: string) => id.startsWith(family)), + `seed list must include ${family}* model` + ); + } +}); + +test("ZenMux models use provider/model naming convention", () => { + for (const model of providerRegistry.zenmux.models) { + assert.ok( + model.id.includes("/"), + `model id "${model.id}" must follow provider/model format (e.g. google/gemini-3.1-pro-preview)` + ); + } +});