From ea32dcf863029d5e32497208b0d6eb6104e7e590 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Fri, 17 Jul 2026 10:40:49 -0300 Subject: [PATCH] feat(provider): add Chenzk API OpenAI-compatible gateway (#7246) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(provider): add Chenzk API OpenAI-compatible gateway Registers Chenzk (chenzk.top) as a new API-key gateway provider — an OpenAI-compatible aggregator exposing GPT/Claude/DeepSeek/GLM model groups behind one endpoint. Adapted to OmniRoute's directory-per-provider registry (open-sse/config/providers/registry/) and metadata catalog (src/shared/constants/providers/apikey/gateways.ts), following the same passthrough-models pattern already used for kenari/x5lab/sumopod (live /v1/models catalog resolves the model list instead of a hardcoded array). Co-authored-by: Ahmad Putra Cahyo Inspired-by: https://github.com/decolua/9router/pull/2437 * chore(changelog): fragment for #7246 * test(provider): regen golden snapshot + bump family-count for Chenzk gateway The Chenzk provider added in a616b88c9 registered a new APIKEY_PROVIDERS entry (gateways.ts) but did not update the two characterization tests that assert exact provider counts: the translate-path golden snapshot (missing the chenzk entry) and the 167-entry family-merge count in providers-constants-split.test.ts (now 168, verified as a strict partition sum across the 6 family files, no loss/dup). Co-authored-by: Ahmad Putra Cahyo --------- Co-authored-by: Ahmad Putra Cahyo --- changelog.d/features/7246-chenzk-provider.md | 1 + open-sse/config/providers/index.ts | 2 + .../config/providers/registry/chenzk/index.ts | 15 ++++++ src/shared/constants/config.ts | 1 + .../constants/providers/apikey/gateways.ts | 13 +++++ tests/snapshots/provider/translate-path.json | 23 +++++++++ tests/unit/chenzk-provider-2437.test.ts | 49 +++++++++++++++++++ tests/unit/providers-constants-split.test.ts | 13 ++--- 8 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 changelog.d/features/7246-chenzk-provider.md create mode 100644 open-sse/config/providers/registry/chenzk/index.ts create mode 100644 tests/unit/chenzk-provider-2437.test.ts diff --git a/changelog.d/features/7246-chenzk-provider.md b/changelog.d/features/7246-chenzk-provider.md new file mode 100644 index 0000000000..cc39d66de6 --- /dev/null +++ b/changelog.d/features/7246-chenzk-provider.md @@ -0,0 +1 @@ +- **feat(provider):** add Chenzk API OpenAI-compatible gateway. (thanks @CahyokPutraDev99) diff --git a/open-sse/config/providers/index.ts b/open-sse/config/providers/index.ts index ae083ecf34..8763f6326f 100644 --- a/open-sse/config/providers/index.ts +++ b/open-sse/config/providers/index.ts @@ -151,6 +151,7 @@ import { gigachatProvider } from "./registry/gigachat/index.ts"; import { devin_cliProvider } from "./registry/devin-cli/index.ts"; import { auggieProvider } from "./registry/auggie/index.ts"; import { chutesProvider } from "./registry/chutes/index.ts"; +import { chenzkProvider } from "./registry/chenzk/index.ts"; import { factoryProvider } from "./registry/factory/index.ts"; import { databricksProvider } from "./registry/databricks/index.ts"; import { rekaProvider } from "./registry/reka/index.ts"; @@ -336,6 +337,7 @@ export const REGISTRY: Record = { "devin-cli": devin_cliProvider, auggie: auggieProvider, chutes: chutesProvider, + chenzk: chenzkProvider, factory: factoryProvider, databricks: databricksProvider, reka: rekaProvider, diff --git a/open-sse/config/providers/registry/chenzk/index.ts b/open-sse/config/providers/registry/chenzk/index.ts new file mode 100644 index 0000000000..ee74a4d46f --- /dev/null +++ b/open-sse/config/providers/registry/chenzk/index.ts @@ -0,0 +1,15 @@ +import type { RegistryEntry } from "../../shared.ts"; + +export const chenzkProvider: RegistryEntry = { + id: "chenzk", + alias: "chenzk", + format: "openai", + executor: "default", + baseUrl: "https://chenzk.top/v1/chat/completions", + authType: "apikey", + authHeader: "bearer", + modelsUrl: "https://chenzk.top/v1/models", + defaultContextLength: 128000, + models: [], + passthroughModels: true, +}; diff --git a/src/shared/constants/config.ts b/src/shared/constants/config.ts index 30c3b426cb..100991598f 100644 --- a/src/shared/constants/config.ts +++ b/src/shared/constants/config.ts @@ -23,6 +23,7 @@ export const PROVIDER_ENDPOINTS = { sumopod: "https://ai.sumopod.com/v1/chat/completions", x5lab: "https://api.x5lab.dev/v1/chat/completions", kenari: "https://kenari.id/v1/chat/completions", + chenzk: "https://chenzk.top/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/apikey/gateways.ts b/src/shared/constants/providers/apikey/gateways.ts index 59d1b10fde..69894ff504 100644 --- a/src/shared/constants/providers/apikey/gateways.ts +++ b/src/shared/constants/providers/apikey/gateways.ts @@ -579,6 +579,19 @@ export const APIKEY_PROVIDERS_GATEWAYS = { apiHint: "X5Lab exposes an OpenAI-compatible chat completions endpoint at https://api.x5lab.dev/v1/chat/completions, plus a live /v1/models catalog. OmniRoute uses the OpenAI protocol and lists models via passthrough.", }, + chenzk: { + id: "chenzk", + alias: "chenzk", + name: "Chenzk API", + icon: "hub", + color: "#10B981", + textIcon: "CZ", + passthroughModels: true, + website: "https://chenzk.top", + apiHint: + "Create an API key at https://chenzk.top/token, then paste it here as a Bearer token. " + + "OpenAI-compatible endpoint at https://chenzk.top/v1, with a live /v1/models catalog.", + }, kenari: { id: "kenari", alias: "kenari", diff --git a/tests/snapshots/provider/translate-path.json b/tests/snapshots/provider/translate-path.json index ec208cafd1..d8fc6518c9 100644 --- a/tests/snapshots/provider/translate-path.json +++ b/tests/snapshots/provider/translate-path.json @@ -658,6 +658,29 @@ "stream": "https://chatgpt.com/backend-api/conversation" } }, + "chenzk": { + "format": "openai", + "headers": { + "apiKey": { + "Accept": "text/event-stream", + "Authorization": "Bearer ", + "Content-Type": "application/json" + }, + "nonStream": { + "Authorization": "Bearer ", + "Content-Type": "application/json" + }, + "oauth": { + "Accept": "text/event-stream", + "Authorization": "Bearer ", + "Content-Type": "application/json" + } + }, + "url": { + "nonStream": "https://chenzk.top/v1/chat/completions", + "stream": "https://chenzk.top/v1/chat/completions" + } + }, "chipotle": { "format": "openai", "headers": { diff --git a/tests/unit/chenzk-provider-2437.test.ts b/tests/unit/chenzk-provider-2437.test.ts new file mode 100644 index 0000000000..6c84052569 --- /dev/null +++ b/tests/unit/chenzk-provider-2437.test.ts @@ -0,0 +1,49 @@ +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"); + +const CHENZK_CHAT_URL = "https://chenzk.top/v1/chat/completions"; +const CHENZK_MODELS_URL = "https://chenzk.top/v1/models"; + +// Port of decolua/9router#2437 ("feat: add Chenzk API provider"), adapted to +// OmniRoute's directory-per-provider registry (`open-sse/config/providers/registry/`) +// and the `src/shared/constants/providers/apikey/*` metadata catalog, instead of +// upstream's flat `open-sse/providers/registry/*.js` + hardcoded model array. Chenzk +// exposes a "New API"-style OpenAI-compatible gateway with a live /v1/models catalog, +// so — matching the sibling kenari/x5lab/sumopod gateways already in this catalog — +// models are resolved via passthrough rather than a speculative hardcoded list. +test("Chenzk is registered as an OpenAI-compatible API-key gateway", () => { + const entry = APIKEY_PROVIDERS.chenzk; + assert.ok(entry, "APIKEY_PROVIDERS.chenzk must be defined"); + assert.equal(entry.id, "chenzk"); + assert.equal(entry.alias, "chenzk"); + assert.equal(entry.name, "Chenzk API"); + assert.equal(entry.website, "https://chenzk.top"); + assert.equal(entry.passthroughModels, true); +}); + +test("Chenzk exposes the OpenAI-compatible chat completions endpoint", () => { + assert.equal(PROVIDER_ENDPOINTS.chenzk, CHENZK_CHAT_URL); +}); + +test("Chenzk registry entry uses OpenAI format with bearer API-key auth and passthrough models", () => { + const entry = providerRegistry.chenzk; + assert.ok(entry, "providerRegistry.chenzk must be defined"); + assert.equal(entry.id, "chenzk"); + assert.equal(entry.alias, "chenzk"); + assert.equal(entry.format, "openai"); + assert.equal(entry.executor, "default"); + assert.equal(entry.authType, "apikey"); + assert.equal(entry.authHeader, "bearer"); + assert.equal(entry.baseUrl, CHENZK_CHAT_URL); + assert.equal(entry.modelsUrl, CHENZK_MODELS_URL); + assert.equal(entry.passthroughModels, true); + assert.deepEqual( + entry.models, + [], + "Chenzk ships no speculative seeded models — live catalog via passthrough only" + ); +}); diff --git a/tests/unit/providers-constants-split.test.ts b/tests/unit/providers-constants-split.test.ts index dd7143b53d..6571070027 100644 --- a/tests/unit/providers-constants-split.test.ts +++ b/tests/unit/providers-constants-split.test.ts @@ -1,13 +1,14 @@ // Characterization of the providers.ts catalog split (god-file decomposition): the host became a // barrel that re-exports 10 data catalogs now living under constants/providers/*, and APIKEY is // merged from 6 semantic family files (apikey/.ts). Locks: the public surface (every catalog -// + helpers still exported), the spread-merge integrity (167 APIKEY entries, no loss/dup), and that +// + helpers still exported), the spread-merge integrity (168 APIKEY entries, no loss/dup), and that // load-time Zod validation still runs. Pure-data move → behavior must be identical. // Count was 171 before obsolete provider removals (PR #6675: glhf/kluster/cablyai/inclusionai etc., // 171->167) plus #6126 (ClinePass dual-auth): the API-key-only APIKEY_PROVIDERS_GATEWAYS entry was // removed as a duplicate now that clinepass is OAuth-primary (OAUTH_PROVIDERS.clinepass) with its // BYOK path admitted through the DUAL_AUTH_APIKEY_PROVIDER_IDS gate instead (167->166), then the -// OpenVecta inference-gateway addition brought it back to 167. +// OpenVecta inference-gateway addition brought it back to 167, then #7246 (Chenzk API gateway) +// brought it to 168. import { test } from "node:test"; import assert from "node:assert/strict"; @@ -36,10 +37,10 @@ test("barrel still exports every catalog + key helpers", () => { } }); -test("APIKEY_PROVIDERS merges the 6 family files into 167 entries (no loss / no dup)", async () => { +test("APIKEY_PROVIDERS merges the 6 family files into 168 entries (no loss / no dup)", async () => { const keys = Object.keys((P as Record).APIKEY_PROVIDERS); - assert.equal(keys.length, 167); - assert.equal(new Set(keys).size, 167, "duplicate keys after spread-merge"); + assert.equal(keys.length, 168); + assert.equal(new Set(keys).size, 168, "duplicate keys after spread-merge"); // the merged object's entry-count equals the sum of the 6 semantic family files; families are a // strict partition (every provider in exactly one), so the sum must be exactly 167. const families: [string, string][] = [ @@ -61,7 +62,7 @@ test("APIKEY_PROVIDERS merges the 6 family files into 167 entries (no loss / no seen.add(k); } } - assert.equal(famTotal, 167, "families must partition all 167 providers"); + assert.equal(famTotal, 168, "families must partition all 168 providers"); }); test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => {