mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 22:32:12 +03:00
feat(providers): add Charm Hyper OpenAI-compatible provider (#5961)
* feat(providers): add Charm Hyper OpenAI-compatible provider Registers Charm Hyper (hyper.charm.land) as a new API-key gateway provider: OpenAI-compatible chat completions format, bearer auth, free tier (100 monthly Hypercredits). Models are resolved via passthrough (modelsUrl + live /v1/models import) instead of a hardcoded upstream model list, since the specific model catalog is not publicly documented. Co-authored-by: whale <admin@dyntech.cc> Inspired-by: https://github.com/decolua/9router/pull/2006 * test(golden): regenerate translate-path for charm-hyper provider * test(providers): bump APIKEY count 163→164 for charm-hyper --------- Co-authored-by: whale <admin@dyntech.cc>
This commit is contained in:
committed by
GitHub
parent
50f770583d
commit
7658a7da1e
@@ -24,6 +24,7 @@
|
||||
- **feat(providers):** add Qiniu as an OpenAI-compatible (API-key) provider. (thanks @JackChiang233)
|
||||
- **feat(providers):** add b.ai as an OpenAI-compatible (API-key) provider. (thanks @DEYLNN)
|
||||
- **feat(providers):** add Nube.sh as an OpenAI-compatible (API-key) provider. (thanks @whale9820)
|
||||
- **feat(providers):** add Charm Hyper as an OpenAI-compatible (API-key) provider. (thanks @whale9820)
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ import { groqProvider } from "./registry/groq/index.ts";
|
||||
import { inference_netProvider } from "./registry/inference-net/index.ts";
|
||||
import { llm7Provider } from "./registry/llm7/index.ts";
|
||||
import { cerebrasProvider } from "./registry/cerebras/index.ts";
|
||||
import { charmHyperProvider } from "./registry/charm-hyper/index.ts";
|
||||
import { nubeProvider } from "./registry/nube/index.ts";
|
||||
import { clinepassProvider } from "./registry/clinepass/index.ts";
|
||||
import { sparkdeskProvider } from "./registry/sparkdesk/index.ts";
|
||||
@@ -228,6 +229,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
"inference-net": inference_netProvider,
|
||||
llm7: llm7Provider,
|
||||
cerebras: cerebrasProvider,
|
||||
"charm-hyper": charmHyperProvider,
|
||||
nube: nubeProvider,
|
||||
clinepass: clinepassProvider,
|
||||
sparkdesk: sparkdeskProvider,
|
||||
|
||||
14
open-sse/config/providers/registry/charm-hyper/index.ts
Normal file
14
open-sse/config/providers/registry/charm-hyper/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const charmHyperProvider: RegistryEntry = {
|
||||
id: "charm-hyper",
|
||||
alias: "charm-hyper",
|
||||
format: "openai",
|
||||
executor: "default",
|
||||
baseUrl: "https://hyper.charm.land/v1/chat/completions",
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
modelsUrl: "https://hyper.charm.land/v1/models",
|
||||
models: [{ id: "hyper/auto", name: "Charm Hyper Auto" }],
|
||||
passthroughModels: true,
|
||||
};
|
||||
@@ -3,6 +3,20 @@
|
||||
* Pure data; merged by apikey/index.ts via spread (god-file decomposition; semantic split).
|
||||
*/
|
||||
export const APIKEY_PROVIDERS_GATEWAYS = {
|
||||
"charm-hyper": {
|
||||
id: "charm-hyper",
|
||||
alias: "charm-hyper",
|
||||
name: "Charm Hyper",
|
||||
icon: "router",
|
||||
color: "#7C3AED",
|
||||
textIcon: "CH",
|
||||
passthroughModels: true,
|
||||
website: "https://hyper.charm.land",
|
||||
hasFree: true,
|
||||
freeNote: "100 free monthly Hypercredits on signup",
|
||||
apiHint:
|
||||
"Create an API key at https://hyper.charm.land, then paste it here as a Bearer token.",
|
||||
},
|
||||
agentrouter: {
|
||||
id: "agentrouter",
|
||||
alias: "agentrouter",
|
||||
|
||||
@@ -611,6 +611,29 @@
|
||||
"stream": "https://api.cerebras.ai/v1/chat/completions"
|
||||
}
|
||||
},
|
||||
"charm-hyper": {
|
||||
"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://hyper.charm.land/v1/chat/completions",
|
||||
"stream": "https://hyper.charm.land/v1/chat/completions"
|
||||
}
|
||||
},
|
||||
"chatgpt-web": {
|
||||
"format": "openai",
|
||||
"headers": {
|
||||
|
||||
33
tests/unit/charm-hyper-provider.test.ts
Normal file
33
tests/unit/charm-hyper-provider.test.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
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 CHARM_HYPER_CHAT_URL = "https://hyper.charm.land/v1/chat/completions";
|
||||
const CHARM_HYPER_MODELS_URL = "https://hyper.charm.land/v1/models";
|
||||
|
||||
test("Charm Hyper is registered as a free API-key provider", () => {
|
||||
const entry = APIKEY_PROVIDERS["charm-hyper"];
|
||||
assert.ok(entry, "APIKEY_PROVIDERS['charm-hyper'] must be defined");
|
||||
assert.equal(entry.id, "charm-hyper");
|
||||
assert.equal(entry.alias, "charm-hyper");
|
||||
assert.equal(entry.name, "Charm Hyper");
|
||||
assert.equal(entry.website, "https://hyper.charm.land");
|
||||
assert.equal(entry.hasFree, true);
|
||||
assert.equal(entry.passthroughModels, true);
|
||||
});
|
||||
|
||||
test("Charm Hyper registry entry uses OpenAI format with bearer API-key auth", () => {
|
||||
const entry = providerRegistry["charm-hyper"];
|
||||
assert.ok(entry, "providerRegistry['charm-hyper'] must be defined");
|
||||
assert.equal(entry.id, "charm-hyper");
|
||||
assert.equal(entry.alias, "charm-hyper");
|
||||
assert.equal(entry.format, "openai");
|
||||
assert.equal(entry.executor, "default");
|
||||
assert.equal(entry.authType, "apikey");
|
||||
assert.equal(entry.authHeader, "bearer");
|
||||
assert.equal(entry.baseUrl, CHARM_HYPER_CHAT_URL);
|
||||
assert.equal(entry.modelsUrl, CHARM_HYPER_MODELS_URL);
|
||||
assert.equal(entry.passthroughModels, 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 (163 APIKEY entries, no loss/dup), and that
|
||||
// + helpers still exported), the spread-merge integrity (164 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 163 entries (no loss / no dup)", async () => {
|
||||
test("APIKEY_PROVIDERS merges the 6 family files into 164 entries (no loss / no dup)", async () => {
|
||||
const keys = Object.keys((P as Record<string, object>).APIKEY_PROVIDERS);
|
||||
assert.equal(keys.length, 163);
|
||||
assert.equal(new Set(keys).size, 163, "duplicate keys after spread-merge");
|
||||
assert.equal(keys.length, 164);
|
||||
assert.equal(new Set(keys).size, 164, "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 163.
|
||||
// strict partition (every provider in exactly one), so the sum must be exactly 164.
|
||||
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 163 entries (no loss / no
|
||||
seen.add(k);
|
||||
}
|
||||
}
|
||||
assert.equal(famTotal, 163, "families must partition all 163 providers");
|
||||
assert.equal(famTotal, 164, "families must partition all 164 providers");
|
||||
});
|
||||
|
||||
test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => {
|
||||
|
||||
Reference in New Issue
Block a user