mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 21:02:12 +03:00
feat(providers): add Huancheng Public API (hcnsec) OpenAI-compatible regional provider (#6410)
add Huancheng Public API (hcnsec) OpenAI-compatible provider (port PR #2378) (net +1/-0, tests OK). Integrated into release/v3.8.46.
This commit is contained in:
committed by
GitHub
parent
b67f2c58da
commit
437ca488b0
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
@@ -368,4 +369,5 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
x5lab: x5labProvider,
|
||||
kenari: kenariProvider,
|
||||
requesty: requestyProvider,
|
||||
hcnsec: hcnsecProvider,
|
||||
};
|
||||
|
||||
11
open-sse/config/providers/registry/hcnsec/index.ts
Normal file
11
open-sse/config/providers/registry/hcnsec/index.ts
Normal file
@@ -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,
|
||||
});
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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": {
|
||||
|
||||
44
tests/unit/hcnsec-provider.test.ts
Normal file
44
tests/unit/hcnsec-provider.test.ts
Normal file
@@ -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);
|
||||
});
|
||||
@@ -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";
|
||||
@@ -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"],
|
||||
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user