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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

### ✨ New Features

- **feat(providers):** add **Huancheng Public API** (`hcnsec`) as an OpenAI-compatible regional provider — Xinjiang Huancheng Cybersecurity's public LLM platform (base `https://api.hcnsec.cn/v1`, free credits via daily check-ins), wired through the shared OpenAI-compatible registry with full model passthrough (`open-sse/config/providers/registry/hcnsec/`, `src/shared/constants/providers/apikey/regional.ts`). Regression guard: `tests/unit/hcnsec-provider.test.ts`. (thanks @UnrealAryan)
- **feat(dashboard):** the web-session credential guide now shows an **"Open {host}" link** to the provider's sign-in site (derived from the provider `website` via `getProviderWebsiteHost`), so you can jump straight to the page where the cookie/session must be captured. Regression guard: `tests/unit/web-session-provider-link-6316.test.ts`. (thanks @jordansilly77-stack)
- **feat(cerebras):** add the **Gemma 4 31B** model (`gemma-4-31b`) to the Cerebras registry + pricing table. Regression guard extends `tests/unit/t28-model-catalog-updates.test.ts`. (thanks @backryun)
- **feat(providers):** add **Yuanbao (web)** as a cookie-session provider ([#6196](https://github.com/diegosouzapw/OmniRoute/issues/6196)) — `yuanbao-web` (Tencent Yuanbao, `yuanbao.tencent.com`) with cookie-only auth (`hy_user`/`hy_token` + public agent id), SSE→OpenAI translation incl. `reasoning_content`, exposing DeepSeek V3/R1 + Hunyuan / Hunyuan-T1. Regression guard: `tests/unit/providers-yuanbao-web.test.ts`. `together-web` was **deferred** (no verifiable web-session endpoint — needs a captured request) and `huggingchat-web` **dropped** (the existing `huggingchat` already is a web-cookie provider). (thanks @chirag127)
Expand Down
2 changes: 2 additions & 0 deletions open-sse/config/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ import { sumopodProvider } from "./registry/sumopod/index.ts";
import { x5labProvider } from "./registry/x5lab/index.ts";
import { kenariProvider } from "./registry/kenari/index.ts";
import { requestyProvider } from "./registry/requesty/index.ts";
import { hcnsecProvider } from "./registry/hcnsec/index.ts";

export const REGISTRY: Record<string, RegistryEntry> = {
aimlapi: aimlapiProvider,
Expand Down Expand Up @@ -368,4 +369,5 @@ export const REGISTRY: Record<string, RegistryEntry> = {
x5lab: x5labProvider,
kenari: kenariProvider,
requesty: requestyProvider,
hcnsec: hcnsecProvider,
};
11 changes: 11 additions & 0 deletions open-sse/config/providers/registry/hcnsec/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { RegistryEntry } from "../../shared.ts";
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";

export const hcnsecProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
id: "hcnsec",
alias: "hcnsec",
baseUrl: "https://api.hcnsec.cn/v1/chat/completions",
modelsUrl: "https://api.hcnsec.cn/v1/models",
models: [],
passthroughModels: true,
});
14 changes: 14 additions & 0 deletions src/shared/constants/providers/apikey/regional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,4 +355,18 @@ export const APIKEY_PROVIDERS_REGIONAL = {
deprecationReason:
"api.inclusionai.tech no longer resolves (sweep 2026-06-19); the inference API appears discontinued.",
},
hcnsec: {
id: "hcnsec",
alias: "hcnsec",
name: "Huancheng Public API",
icon: "security",
color: "#0EA5E9",
textIcon: "HC",
website: "https://api.hcnsec.cn",
hasFree: true,
freeNote:
"Xinjiang Huancheng Cybersecurity public LLM API platform: free credits with daily check-ins.",
passthroughModels: true,
authHint: "Get API key at api.hcnsec.cn",
},
};
23 changes: 23 additions & 0 deletions tests/snapshots/provider/translate-path.json
Original file line number Diff line number Diff line change
Expand Up @@ -2018,6 +2018,29 @@
"stream": "https://api.haiper.ai/v1"
}
},
"hcnsec": {
"format": "openai",
"headers": {
"apiKey": {
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json"
},
"nonStream": {
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json"
},
"oauth": {
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json"
}
},
"url": {
"nonStream": "https://api.hcnsec.cn/v1/chat/completions",
"stream": "https://api.hcnsec.cn/v1/chat/completions"
}
},
"heroku": {
"format": "openai",
"headers": {
Expand Down
44 changes: 44 additions & 0 deletions tests/unit/hcnsec-provider.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import test from "node:test";
import assert from "node:assert/strict";

const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers.ts");
const { REGISTRY: providerRegistry } = await import("../../open-sse/config/providerRegistry.ts");
const { isValidModel } = await import("../../src/shared/constants/models.ts");

const HCNSEC_CHAT_URL = "https://api.hcnsec.cn/v1/chat/completions";
const HCNSEC_MODELS_URL = "https://api.hcnsec.cn/v1/models";

test("hcnsec is registered as an API-key regional provider", () => {
const entry = APIKEY_PROVIDERS.hcnsec;
assert.ok(entry, "APIKEY_PROVIDERS.hcnsec must be defined");
assert.equal(entry.id, "hcnsec");
assert.equal(entry.alias, "hcnsec");
assert.equal(entry.name, "Huancheng Public API");
assert.equal(entry.website, "https://api.hcnsec.cn");
assert.equal(entry.passthroughModels, true);
assert.equal(entry.hasFree, true);
});

test("hcnsec registry entry uses OpenAI format with bearer API-key auth", () => {
const entry = providerRegistry.hcnsec;
assert.ok(entry, "providerRegistry.hcnsec must be defined");
assert.equal(entry.id, "hcnsec");
assert.equal(entry.alias, "hcnsec");
assert.equal(entry.format, "openai");
assert.equal(entry.executor, "default");
assert.equal(entry.authType, "apikey");
assert.equal(entry.authHeader, "bearer");
assert.equal(entry.baseUrl, HCNSEC_CHAT_URL);
assert.equal(entry.modelsUrl, HCNSEC_MODELS_URL);
assert.equal(entry.passthroughModels, true);
});

test("hcnsec ships no static model seed — relies fully on passthrough + live catalog", () => {
assert.deepEqual(providerRegistry.hcnsec.models, []);
});

test("hcnsec accepts any model id via passthrough", () => {
assert.equal(isValidModel("hcnsec", "gpt-4o-mini"), true);
assert.equal(isValidModel("hcnsec", "deepseek-v3"), true);
assert.equal(isValidModel("hcnsec", "qwen-max"), true);
});
12 changes: 6 additions & 6 deletions tests/unit/providers-constants-split.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// 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/<family>.ts). Locks: the public surface (every catalog
// + helpers still exported), the spread-merge integrity (168 APIKEY entries, no loss/dup), and that
// + helpers still exported), the spread-merge integrity (169 APIKEY entries, no loss/dup), and that
// load-time Zod validation still runs. Pure-data move → behavior must be identical.
import { test } from "node:test";
import assert from "node:assert/strict";
Expand Down Expand Up @@ -31,12 +31,12 @@ test("barrel still exports every catalog + key helpers", () => {
}
});

test("APIKEY_PROVIDERS merges the 6 family files into 168 entries (no loss / no dup)", async () => {
test("APIKEY_PROVIDERS merges the 6 family files into 169 entries (no loss / no dup)", async () => {
const keys = Object.keys((P as Record<string, object>).APIKEY_PROVIDERS);
assert.equal(keys.length, 168);
assert.equal(new Set(keys).size, 168, "duplicate keys after spread-merge");
assert.equal(keys.length, 169);
assert.equal(new Set(keys).size, 169, "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 168.
// strict partition (every provider in exactly one), so the sum must be exactly 169.
const families: [string, string][] = [
["gateways", "APIKEY_PROVIDERS_GATEWAYS"],
["frontier-labs", "APIKEY_PROVIDERS_FRONTIER"],
Expand All @@ -56,7 +56,7 @@ test("APIKEY_PROVIDERS merges the 6 family files into 168 entries (no loss / no
seen.add(k);
}
}
assert.equal(famTotal, 168, "families must partition all 168 providers");
assert.equal(famTotal, 169, "families must partition all 169 providers");
});

test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => {
Expand Down
Loading