Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions open-sse/config/providerRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4318,6 +4318,92 @@ const _REGISTRY_EAGER: Record<string, RegistryEntry> = {
],
},

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,
},
],
Comment on lines +4331 to +4404

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The newly added ZenMux models are missing explicit capability declarations such as supportsVision, toolCalling, and supportsReasoning. Without these flags, the router/executor will gate or strip vision blocks, disable tool calling, or reject reasoning parameters for these models, even though the underlying models (like Gemini 3.1 Pro, Claude Sonnet 4.5, and GPT-5) natively support them. Declaring these capabilities explicitly ensures full feature parity.

    models: [
      {
        id: "google/gemini-3.1-pro-preview",
        name: "Gemini 3.1 Pro Preview (ZenMux)",
        contextLength: 1048576,
        toolCalling: true,
        supportsVision: true,
      },
      {
        id: "google/gemini-3-flash-preview",
        name: "Gemini 3 Flash Preview (ZenMux)",
        contextLength: 1048576,
        toolCalling: true,
        supportsVision: true,
      },
      {
        id: "openai/gpt-5",
        name: "GPT-5 (ZenMux)",
        contextLength: 400000,
        toolCalling: true,
        supportsVision: true,
        supportsReasoning: true,
      },
      {
        id: "anthropic/claude-sonnet-4.5",
        name: "Claude Sonnet 4.5 (ZenMux)",
        contextLength: 200000,
        toolCalling: true,
        supportsVision: true,
      },
      {
        id: "anthropic/claude-opus-4.5",
        name: "Claude Opus 4.5 (ZenMux)",
        contextLength: 200000,
        toolCalling: true,
        supportsVision: true,
      },
      {
        id: "deepseek/deepseek-chat",
        name: "DeepSeek V3.2 Chat (ZenMux)",
        contextLength: 128000,
        toolCalling: true,
      },
      {
        id: "x-ai/grok-4.1-fast",
        name: "Grok 4.1 Fast (ZenMux)",
        contextLength: 131072,
        toolCalling: true,
        supportsVision: true,
      },
      {
        id: "mistralai/mistral-large-2512",
        name: "Mistral Large 2512 (ZenMux)",
        contextLength: 128000,
        toolCalling: true,
      },
      {
        id: "z-ai/glm-4.6v-flash",
        name: "GLM 4.6V Flash (ZenMux)",
        contextLength: 128000,
        toolCalling: true,
        supportsVision: true,
      },
    ],

},

theoldllm: {
id: "theoldllm",
alias: "tllm",
Expand Down
1 change: 1 addition & 0 deletions src/shared/constants/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
16 changes: 16 additions & 0 deletions src/shared/constants/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <key>. 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).
Expand Down
67 changes: 67 additions & 0 deletions tests/unit/zenmux-provider.test.ts
Original file line number Diff line number Diff line change
@@ -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)`
);
}
});