mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-11 09:42:15 +03:00
Compare commits
13 Commits
fix/9981-i
...
feat/free-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85b1208add | ||
|
|
a4ec487e16 | ||
|
|
3a1e595325 | ||
|
|
4d38295bab | ||
|
|
e7b5e1870f | ||
|
|
8b55e61bdb | ||
|
|
85e8fd8a19 | ||
|
|
89ae04867b | ||
|
|
72e5bc9cb6 | ||
|
|
58eb441307 | ||
|
|
ddc4c827a7 | ||
|
|
c7d9e9c4c7 | ||
|
|
f3c2e30879 |
@@ -225,6 +225,23 @@ import { digitaloceanProvider } from "./registry/digitalocean/index.ts";
|
||||
import { hcnsecProvider } from "./registry/hcnsec/index.ts";
|
||||
import { promptqlProvider } from "./registry/promptql/index.ts";
|
||||
import { hyperagentProvider } from "./registry/hyperagent/index.ts";
|
||||
import { zyloApiProvider } from "./registry/zylo-api/index.ts";
|
||||
import { unorouterProvider } from "./registry/unorouter/index.ts";
|
||||
import { poolsideProvider } from "./registry/poolside/index.ts";
|
||||
import { fastrouterProvider } from "./registry/fastrouter/index.ts";
|
||||
import { anyapiProvider } from "./registry/anyapi/index.ts";
|
||||
import { electronhubProvider } from "./registry/electronhub/index.ts";
|
||||
import { llmgatewayProvider } from "./registry/llmgateway/index.ts";
|
||||
import { llmKiwiProvider } from "./registry/llm-kiwi/index.ts";
|
||||
import { literouterProvider } from "./registry/literouter/index.ts";
|
||||
import { mnnAiProvider } from "./registry/mnn-ai/index.ts";
|
||||
import { meganovaAiProvider } from "./registry/meganova-ai/index.ts";
|
||||
import { mixlayerProvider } from "./registry/mixlayer/index.ts";
|
||||
import { spekaProvider } from "./registry/speka/index.ts";
|
||||
import { tokenreplyProvider } from "./registry/tokenreply/index.ts";
|
||||
import { yoloAutoProvider } from "./registry/yolo-auto/index.ts";
|
||||
import { dxntProvider } from "./registry/dxnt/index.ts";
|
||||
import { cloudcodeOneProvider } from "./registry/cloudcode-one/index.ts";
|
||||
|
||||
export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
aimlapi: aimlapiProvider,
|
||||
@@ -451,5 +468,21 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
hcnsec: hcnsecProvider,
|
||||
promptql: promptqlProvider,
|
||||
hyperagent: hyperagentProvider,
|
||||
"zylo-api": zyloApiProvider,
|
||||
unorouter: unorouterProvider,
|
||||
poolside: poolsideProvider,
|
||||
fastrouter: fastrouterProvider,
|
||||
anyapi: anyapiProvider,
|
||||
electronhub: electronhubProvider,
|
||||
llmgateway: llmgatewayProvider,
|
||||
"llm-kiwi": llmKiwiProvider,
|
||||
literouter: literouterProvider,
|
||||
"mnn-ai": mnnAiProvider,
|
||||
"meganova-ai": meganovaAiProvider,
|
||||
mixlayer: mixlayerProvider,
|
||||
speka: spekaProvider,
|
||||
tokenreply: tokenreplyProvider,
|
||||
"yolo-auto": yoloAutoProvider,
|
||||
dxnt: dxntProvider,
|
||||
"cloudcode-one": cloudcodeOneProvider,
|
||||
};
|
||||
|
||||
12
open-sse/config/providers/registry/chat-oripe/index.ts
Normal file
12
open-sse/config/providers/registry/chat-oripe/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
// The upstream brand and hostname are ambiguous, so avoid unverified quota claims.
|
||||
export const chatOripeProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "chat-oripe",
|
||||
alias: "chat-oripe",
|
||||
baseUrl: "https://api.oriper.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.oriper.com/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
20
open-sse/config/providers/registry/cloudcode-one/index.ts
Normal file
20
open-sse/config/providers/registry/cloudcode-one/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
/**
|
||||
* CloudCode.ONE - OpenAI-compatible API with published free model aliases.
|
||||
*
|
||||
* The Anthropic-compatible endpoint is documented separately; this registry
|
||||
* covers the OpenAI-compatible API surface audited for this migration.
|
||||
*/
|
||||
export const cloudcodeOneProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "cloudcode-one",
|
||||
alias: "cloudcode-one",
|
||||
baseUrl: "https://api.cloudcode.one/v1/chat/completions",
|
||||
modelsUrl: "https://api.cloudcode.one/v1/models",
|
||||
models: [
|
||||
{ id: "glm-4.7-flash", name: "GLM 4.7 Flash" },
|
||||
{ id: "glm-4.6v-flash", name: "GLM 4.6V Flash" },
|
||||
],
|
||||
passthroughModels: true,
|
||||
});
|
||||
17
open-sse/config/providers/registry/dxnt/index.ts
Normal file
17
open-sse/config/providers/registry/dxnt/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
/**
|
||||
* DXNT - OpenAI-compatible API with a free account quota.
|
||||
*
|
||||
* Models are discovered from the provider's authenticated catalog rather than
|
||||
* copied into a static list, so account-specific availability remains intact.
|
||||
*/
|
||||
export const dxntProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "dxnt",
|
||||
alias: "dxnt",
|
||||
baseUrl: "https://www.dxnt.com/v1/chat/completions",
|
||||
modelsUrl: "https://www.dxnt.com/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
11
open-sse/config/providers/registry/literouter/index.ts
Normal file
11
open-sse/config/providers/registry/literouter/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const literouterProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "literouter",
|
||||
alias: "literouter",
|
||||
baseUrl: "https://api.literouter.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.literouter.com/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
11
open-sse/config/providers/registry/meganova-ai/index.ts
Normal file
11
open-sse/config/providers/registry/meganova-ai/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const meganovaAiProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "meganova-ai",
|
||||
alias: "meganova-ai",
|
||||
baseUrl: "https://api.meganova.ai/v1/chat/completions",
|
||||
modelsUrl: "https://api.meganova.ai/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
11
open-sse/config/providers/registry/mixlayer/index.ts
Normal file
11
open-sse/config/providers/registry/mixlayer/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const mixlayerProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "mixlayer",
|
||||
alias: "mixlayer",
|
||||
baseUrl: "https://models.mixlayer.ai/v1/chat/completions",
|
||||
modelsUrl: "https://models.mixlayer.ai/v1/models",
|
||||
models: [{ id: "qwen/qwen3.5-4b-free", name: "Qwen 3.5 4B (free)" }],
|
||||
passthroughModels: true,
|
||||
});
|
||||
11
open-sse/config/providers/registry/mnn-ai/index.ts
Normal file
11
open-sse/config/providers/registry/mnn-ai/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const mnnAiProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "mnn-ai",
|
||||
alias: "mnn-ai",
|
||||
baseUrl: "https://api.mnnai.ru/v1/chat/completions",
|
||||
modelsUrl: "https://api.mnnai.ru/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
12
open-sse/config/providers/registry/naga-ai/index.ts
Normal file
12
open-sse/config/providers/registry/naga-ai/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
// Free access terms may permit data collection or training use; discover models dynamically.
|
||||
export const nagaAiProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "naga-ai",
|
||||
alias: "naga-ai",
|
||||
baseUrl: "https://api.naga.ac/v1/chat/completions",
|
||||
modelsUrl: "https://api.naga.ac/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
11
open-sse/config/providers/registry/poolside/index.ts
Normal file
11
open-sse/config/providers/registry/poolside/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const poolsideProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "poolside",
|
||||
alias: "poolside",
|
||||
baseUrl: "https://inference.poolside.ai/v1/chat/completions",
|
||||
modelsUrl: "https://inference.poolside.ai/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
11
open-sse/config/providers/registry/speka/index.ts
Normal file
11
open-sse/config/providers/registry/speka/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const spekaProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "speka",
|
||||
alias: "speka",
|
||||
baseUrl: "https://speka.me/v1/chat/completions",
|
||||
modelsUrl: "https://speka.me/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
11
open-sse/config/providers/registry/tokenreply/index.ts
Normal file
11
open-sse/config/providers/registry/tokenreply/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const tokenreplyProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "tokenreply",
|
||||
alias: "tokenreply",
|
||||
baseUrl: "https://api.tokenreply.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.tokenreply.com/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
17
open-sse/config/providers/registry/yolo-auto/index.ts
Normal file
17
open-sse/config/providers/registry/yolo-auto/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
/**
|
||||
* Yolo-Auto - OpenAI-compatible API with a request-limited free tier.
|
||||
*
|
||||
* The catalog is kept intentionally small: the documented free-tier model is
|
||||
* seeded while passthrough discovery allows the service to publish updates.
|
||||
*/
|
||||
export const yoloAutoProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "yolo-auto",
|
||||
alias: "yolo-auto",
|
||||
baseUrl: "https://yolo-auto.com/v1/chat/completions",
|
||||
modelsUrl: "https://yolo-auto.com/v1/models",
|
||||
models: [{ id: "qwen3.6-35b-a3b", name: "Qwen 3.6 35B A3B" }],
|
||||
passthroughModels: true,
|
||||
});
|
||||
11
open-sse/config/providers/registry/zylo-api/index.ts
Normal file
11
open-sse/config/providers/registry/zylo-api/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const zyloApiProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "zylo-api",
|
||||
alias: "zylo",
|
||||
baseUrl: "https://api.zyloai.net/v1/chat/completions",
|
||||
modelsUrl: "https://api.zyloai.net/v1/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -7,6 +7,23 @@ export const PROVIDER_ENDPOINTS = {
|
||||
dgrid: "https://api.dgrid.ai/v1/chat/completions",
|
||||
bai: "https://api.b.ai/v1/chat/completions",
|
||||
qiniu: "https://api.qnaigc.com/v1/chat/completions",
|
||||
"zylo-api": "https://api.zyloai.net/v1/chat/completions",
|
||||
unorouter: "https://api.unorouter.com/v1/chat/completions",
|
||||
poolside: "https://inference.poolside.ai/v1/chat/completions",
|
||||
fastrouter: "https://api.fastrouter.ai/api/v1/chat/completions",
|
||||
anyapi: "https://api.anyapi.ai/v1/chat/completions",
|
||||
electronhub: "https://api.electronhub.ai/v1/chat/completions",
|
||||
llmgateway: "https://api.llmgateway.io/v1/chat/completions",
|
||||
"llm-kiwi": "https://api.llm.kiwi/v1/chat/completions",
|
||||
literouter: "https://api.literouter.com/v1/chat/completions",
|
||||
"mnn-ai": "https://api.mnnai.ru/v1/chat/completions",
|
||||
"meganova-ai": "https://api.meganova.ai/v1/chat/completions",
|
||||
mixlayer: "https://models.mixlayer.ai/v1/chat/completions",
|
||||
speka: "https://speka.me/v1/chat/completions",
|
||||
tokenreply: "https://api.tokenreply.com/v1/chat/completions",
|
||||
"yolo-auto": "https://yolo-auto.com/v1/chat/completions",
|
||||
dxnt: "https://www.dxnt.com/v1/chat/completions",
|
||||
"cloudcode-one": "https://api.cloudcode.one/v1/chat/completions",
|
||||
glm: "https://api.z.ai/api/anthropic/v1/messages",
|
||||
glmt: "https://api.z.ai/api/anthropic/v1/messages",
|
||||
"bailian-coding-plan": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1/messages",
|
||||
|
||||
@@ -21,7 +21,10 @@ import { PROVIDER_MODELS as MODELS } from "@omniroute/open-sse/config/providerMo
|
||||
const PASSTHROUGH_PROVIDERS = new Set(
|
||||
Object.entries(AI_PROVIDERS)
|
||||
.filter(([, p]) => (p as any).passthroughModels)
|
||||
.map(([key]) => key)
|
||||
.flatMap(([key, provider]) => {
|
||||
const alias = (provider as { alias?: unknown }).alias;
|
||||
return typeof alias === "string" && alias.length > 0 ? [key, alias] : [key];
|
||||
})
|
||||
);
|
||||
|
||||
// Wrap isValidModel with passthrough providers
|
||||
|
||||
@@ -92,6 +92,22 @@ export const AGGREGATOR_PROVIDER_IDS = new Set([
|
||||
"g4f-pollinations",
|
||||
"g4f-ollama",
|
||||
"g4f-nvidia",
|
||||
"zylo-api",
|
||||
"unorouter",
|
||||
"fastrouter",
|
||||
"anyapi",
|
||||
"electronhub",
|
||||
"llmgateway",
|
||||
"llm-kiwi",
|
||||
"literouter",
|
||||
"mnn-ai",
|
||||
"meganova-ai",
|
||||
"mixlayer",
|
||||
"speka",
|
||||
"tokenreply",
|
||||
"yolo-auto",
|
||||
"dxnt",
|
||||
"cloudcode-one",
|
||||
]);
|
||||
|
||||
export const ENTERPRISE_CLOUD_PROVIDER_IDS = new Set([
|
||||
|
||||
@@ -95,6 +95,230 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
|
||||
"Create an API key at https://app.requesty.ai, then paste it here as a Bearer token. " +
|
||||
"OpenAI-compatible endpoint at https://router.requesty.ai/v1, with a live /v1/models catalog.",
|
||||
},
|
||||
"zylo-api": {
|
||||
id: "zylo-api",
|
||||
alias: "zylo",
|
||||
name: "Zylo API",
|
||||
icon: "hub",
|
||||
color: "#2563EB",
|
||||
textIcon: "ZY",
|
||||
passthroughModels: true,
|
||||
website: "https://zyloai.net",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Basic plan: 10 RPM, 7,200 requests/day and 200,000 tokens/day; limited to Basic text models.",
|
||||
apiHint:
|
||||
"Create a free Zylo API key at https://zyloai.net, then use https://api.zyloai.net/v1 as the OpenAI-compatible base URL.",
|
||||
},
|
||||
fastrouter: {
|
||||
id: "fastrouter",
|
||||
alias: "fastrouter",
|
||||
name: "FastRouter",
|
||||
icon: "speed",
|
||||
color: "#F97316",
|
||||
textIcon: "FR",
|
||||
passthroughModels: true,
|
||||
website: "https://fastrouter.ai",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Models with the :free suffix allow 10 requests/day per organization and model; availability may change.",
|
||||
apiHint:
|
||||
"Create a FastRouter API key, then use https://api.fastrouter.ai/api/v1 as the OpenAI-compatible base URL.",
|
||||
},
|
||||
anyapi: {
|
||||
id: "anyapi",
|
||||
alias: "anyapi",
|
||||
name: "AnyAPI AI",
|
||||
icon: "hub",
|
||||
color: "#0EA5E9",
|
||||
textIcon: "AA",
|
||||
passthroughModels: true,
|
||||
website: "https://anyapi.ai",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Free plan: 100,000 ANY Tokens/day and 100 RPM for eligible Free/Basic models; no credit card required.",
|
||||
apiHint:
|
||||
"Create and verify an AnyAPI account, then use https://api.anyapi.ai/v1 as the OpenAI-compatible base URL.",
|
||||
},
|
||||
electronhub: {
|
||||
id: "electronhub",
|
||||
alias: "electronhub",
|
||||
name: "Electron Hub",
|
||||
icon: "hub",
|
||||
color: "#22C55E",
|
||||
textIcon: "EH",
|
||||
passthroughModels: true,
|
||||
website: "https://www.electronhub.ai",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Free plan: 5 RPM, $0.25 weekly credits and 10 Neutrinos/day for :free models; family budgets also apply.",
|
||||
apiHint:
|
||||
"Create a free API key at https://app.electronhub.ai, then use https://api.electronhub.ai/v1 as the OpenAI-compatible base URL.",
|
||||
},
|
||||
llmgateway: {
|
||||
id: "llmgateway",
|
||||
alias: "llmgateway",
|
||||
name: "LLM Gateway",
|
||||
icon: "router",
|
||||
color: "#6366F1",
|
||||
textIcon: "LG",
|
||||
passthroughModels: true,
|
||||
website: "https://llmgateway.io",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Hosted Free plan: free-priced models are limited to 5 requests per 10 minutes when the account has no credits.",
|
||||
apiHint:
|
||||
"Create an LLM Gateway API key, then use https://api.llmgateway.io/v1 as the OpenAI-compatible base URL.",
|
||||
},
|
||||
"llm-kiwi": {
|
||||
id: "llm-kiwi",
|
||||
alias: "llmkiwi",
|
||||
name: "LLM.Kiwi",
|
||||
icon: "hub",
|
||||
color: "#84CC16",
|
||||
textIcon: "LK",
|
||||
passthroughModels: true,
|
||||
website: "https://llm.kiwi",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Free plan exposes auto and hrLLM; the published 40 requests/hour limit applies to hrLLM.",
|
||||
apiHint:
|
||||
"Create a free LLM.Kiwi key, then use https://api.llm.kiwi/v1 as the OpenAI-compatible base URL.",
|
||||
},
|
||||
literouter: {
|
||||
id: "literouter",
|
||||
alias: "literouter",
|
||||
name: "LiteRouter",
|
||||
icon: "router",
|
||||
color: "#2563EB",
|
||||
textIcon: "LR",
|
||||
passthroughModels: true,
|
||||
website: "https://literouter.com",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Free model variants use the :free suffix; daily credit limits vary by model and free input is capped at 5,000 tokens.",
|
||||
apiHint:
|
||||
"Create a LiteRouter API key, then use https://api.literouter.com/v1 as the OpenAI-compatible base URL.",
|
||||
},
|
||||
"mnn-ai": {
|
||||
id: "mnn-ai",
|
||||
alias: "mnn-ai",
|
||||
name: "MNN AI",
|
||||
icon: "hub",
|
||||
color: "#0F766E",
|
||||
textIcon: "MNN",
|
||||
passthroughModels: true,
|
||||
website: "https://mnnai.ru",
|
||||
hasFree: true,
|
||||
freeNote: "Free plan: $1 monthly credits, 10 RPM and access only to models marked Free.",
|
||||
apiHint:
|
||||
"Create an MNN AI API key, then use the primary https://api.mnnai.ru/v1 OpenAI-compatible endpoint. Review jurisdiction, privacy and regional data-transfer requirements before use.",
|
||||
},
|
||||
"meganova-ai": {
|
||||
id: "meganova-ai",
|
||||
alias: "meganova-ai",
|
||||
name: "MegaNova AI",
|
||||
icon: "router",
|
||||
color: "#7C3AED",
|
||||
textIcon: "MN",
|
||||
passthroughModels: true,
|
||||
website: "https://meganova.ai",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Free signup without a card. Published Tier 1 per-model quotas total 550 requests/day; they are not a shared global pool, and paid overage can apply if enabled.",
|
||||
apiHint:
|
||||
"Create a MegaNova API key, then use https://api.meganova.ai/v1 as the OpenAI-compatible base URL.",
|
||||
},
|
||||
mixlayer: {
|
||||
id: "mixlayer",
|
||||
alias: "mixlayer",
|
||||
name: "Mixlayer",
|
||||
icon: "router",
|
||||
color: "#0EA5E9",
|
||||
textIcon: "MX",
|
||||
passthroughModels: true,
|
||||
website: "https://www.mixlayer.com",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"The qwen/qwen3.5-4b-free model is free for prototyping and rate-limited; no fixed public RPM or daily quota is confirmed.",
|
||||
apiHint:
|
||||
"Create a Mixlayer API key, then use https://models.mixlayer.ai/v1 as the OpenAI-compatible base URL.",
|
||||
},
|
||||
speka: {
|
||||
id: "speka",
|
||||
alias: "speka",
|
||||
name: "Speka AI",
|
||||
icon: "router",
|
||||
color: "#DB2777",
|
||||
textIcon: "SP",
|
||||
passthroughModels: true,
|
||||
website: "https://speka.me",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Free plan: $1 monthly usage, 10 RPM, one API key and access to open models and the playground; no card required.",
|
||||
apiHint:
|
||||
"Create a Speka API key, then use https://speka.me/v1 as the OpenAI-compatible base URL. Confirm current model availability and overage settings before use.",
|
||||
},
|
||||
tokenreply: {
|
||||
id: "tokenreply",
|
||||
alias: "tokenreply",
|
||||
name: "TokenReply",
|
||||
icon: "router",
|
||||
color: "#3B82F6",
|
||||
textIcon: "TR",
|
||||
passthroughModels: true,
|
||||
website: "https://www.tokenreply.com",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Free-tagged models have model- and campaign-specific daily limits; no fixed global free quota is published.",
|
||||
apiHint:
|
||||
"Create a TokenReply token, then use https://api.tokenreply.com/v1 as the OpenAI-compatible base URL and confirm the selected model's current limit.",
|
||||
},
|
||||
"yolo-auto": {
|
||||
id: "yolo-auto",
|
||||
alias: "yolo-auto",
|
||||
name: "Yolo-Auto",
|
||||
icon: "auto_awesome",
|
||||
color: "#F59E0B",
|
||||
textIcon: "YA",
|
||||
passthroughModels: true,
|
||||
website: "https://yolo-auto.com",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Free API access is request-limited and intended for testing; no numeric daily quota is published and free access is not promised indefinitely.",
|
||||
apiHint:
|
||||
"Create a yolo_ API key, then use https://yolo-auto.com/v1 as the OpenAI-compatible base URL.",
|
||||
},
|
||||
dxnt: {
|
||||
id: "dxnt",
|
||||
alias: "dxnt",
|
||||
name: "DXNT / DX Token",
|
||||
icon: "hub",
|
||||
color: "#111827",
|
||||
textIcon: "DX",
|
||||
passthroughModels: true,
|
||||
website: "https://www.dxnt.com",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Free accounts are documented at 100 calls/day; the quota may increase through invitations and can vary by account.",
|
||||
apiHint:
|
||||
"Create a DXNT API key, then use https://www.dxnt.com/v1 as the OpenAI-compatible base URL.",
|
||||
},
|
||||
"cloudcode-one": {
|
||||
id: "cloudcode-one",
|
||||
alias: "cloudcode-one",
|
||||
name: "CloudCode.ONE",
|
||||
icon: "router",
|
||||
color: "#6366F1",
|
||||
textIcon: "CC",
|
||||
passthroughModels: true,
|
||||
website: "https://cloudcode.one",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Published free models include glm-4.7-flash and glm-4.6v-flash; no numeric quota is published, and key creation may require credit or a coupon.",
|
||||
apiHint:
|
||||
"Create a CloudCode.ONE key, then use https://api.cloudcode.one/v1 as the OpenAI-compatible base URL. Key issuance may require credit or a coupon.",
|
||||
},
|
||||
dgrid: {
|
||||
id: "dgrid",
|
||||
alias: "dgrid",
|
||||
|
||||
@@ -32,6 +32,21 @@ export const APIKEY_PROVIDERS_INFERENCE = {
|
||||
freeNote:
|
||||
"Free credits on signup for OpenAI-compatible inference across LLMs, embeddings, and reasoning models",
|
||||
},
|
||||
poolside: {
|
||||
id: "poolside",
|
||||
alias: "poolside",
|
||||
name: "Poolside",
|
||||
icon: "memory",
|
||||
color: "#111827",
|
||||
textIcon: "PS",
|
||||
passthroughModels: true,
|
||||
website: "https://poolside.ai",
|
||||
hasFree: true,
|
||||
freeNote:
|
||||
"Laguna S 2.1 and XS 2.1 are free during Preview; no public numeric quota is published.",
|
||||
apiHint:
|
||||
"Create a free developer API key, then use https://inference.poolside.ai/v1 as the OpenAI-compatible base URL.",
|
||||
},
|
||||
fireworks: {
|
||||
id: "fireworks",
|
||||
alias: "fireworks",
|
||||
|
||||
55
tests/unit/free-tier-providers-phase3-integration.test.ts
Normal file
55
tests/unit/free-tier-providers-phase3-integration.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts");
|
||||
const { DefaultExecutor, getExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
const { PROVIDER_ENDPOINTS } = await import("../../src/shared/constants/config.ts");
|
||||
const { isValidModel } = await import("../../src/shared/constants/models.ts");
|
||||
const { AGGREGATOR_PROVIDER_IDS } = await import("../../src/shared/constants/providers.ts");
|
||||
const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers/apikey/index.ts");
|
||||
|
||||
const providers = [
|
||||
["zylo-api", "zylo", "https://api.zyloai.net/v1/chat/completions"],
|
||||
["unorouter", "unorouter", "https://api.unorouter.com/v1/chat/completions"],
|
||||
["poolside", "poolside", "https://inference.poolside.ai/v1/chat/completions"],
|
||||
["fastrouter", "fastrouter", "https://api.fastrouter.ai/api/v1/chat/completions"],
|
||||
["anyapi", "anyapi", "https://api.anyapi.ai/v1/chat/completions"],
|
||||
["electronhub", "electronhub", "https://api.electronhub.ai/v1/chat/completions"],
|
||||
["llmgateway", "llmgateway", "https://api.llmgateway.io/v1/chat/completions"],
|
||||
["llm-kiwi", "llmkiwi", "https://api.llm.kiwi/v1/chat/completions"],
|
||||
] as const;
|
||||
|
||||
for (const [id, alias, endpoint] of providers) {
|
||||
test(`${id} is wired through registry, metadata, endpoint and default executor`, () => {
|
||||
const registry = REGISTRY[id];
|
||||
const metadata = APIKEY_PROVIDERS[id];
|
||||
|
||||
assert.ok(registry);
|
||||
assert.ok(metadata);
|
||||
assert.equal(registry.id, id);
|
||||
assert.equal(registry.alias, alias);
|
||||
assert.equal(registry.baseUrl, endpoint);
|
||||
assert.equal(PROVIDER_ENDPOINTS[id], endpoint);
|
||||
assert.equal(metadata.id, id);
|
||||
assert.equal(metadata.alias, alias);
|
||||
assert.equal(metadata.hasFree, true);
|
||||
assert.equal(metadata.passthroughModels, true);
|
||||
assert.ok(typeof metadata.freeNote === "string" && metadata.freeNote.length > 0);
|
||||
assert.ok(getExecutor(id) instanceof DefaultExecutor);
|
||||
assert.equal(isValidModel(id, "future/live-catalog-model"), true);
|
||||
assert.equal(isValidModel(alias, "future/live-catalog-model"), true);
|
||||
});
|
||||
}
|
||||
|
||||
test("gateway providers are classified as aggregators while direct Poolside is not", () => {
|
||||
const gatewayIds = providers.map(([id]) => id).filter((id) => id !== "poolside");
|
||||
for (const id of gatewayIds) assert.equal(AGGREGATOR_PROVIDER_IDS.has(id), true);
|
||||
assert.equal(AGGREGATOR_PROVIDER_IDS.has("poolside"), false);
|
||||
});
|
||||
|
||||
test("LLM.Kiwi statically exposes only the confirmed Free plan models", () => {
|
||||
assert.deepEqual(REGISTRY["llm-kiwi"].models, [
|
||||
{ id: "auto", name: "Auto" },
|
||||
{ id: "hrLLM", name: "hrLLM" },
|
||||
]);
|
||||
});
|
||||
55
tests/unit/free-tier-providers-wave1-a.test.ts
Normal file
55
tests/unit/free-tier-providers-wave1-a.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { poolsideProvider } from "../../open-sse/config/providers/registry/poolside/index.ts";
|
||||
import { unorouterProvider } from "../../open-sse/config/providers/registry/unorouter/index.ts";
|
||||
import { zyloApiProvider } from "../../open-sse/config/providers/registry/zylo-api/index.ts";
|
||||
import type { RegistryEntry } from "../../open-sse/config/providers/shared.ts";
|
||||
|
||||
const providers: Array<{
|
||||
entry: RegistryEntry;
|
||||
id: string;
|
||||
alias: string;
|
||||
chatUrl: string;
|
||||
modelsUrl: string;
|
||||
}> = [
|
||||
{
|
||||
entry: zyloApiProvider,
|
||||
id: "zylo-api",
|
||||
alias: "zylo",
|
||||
chatUrl: "https://api.zyloai.net/v1/chat/completions",
|
||||
modelsUrl: "https://api.zyloai.net/v1/models",
|
||||
},
|
||||
{
|
||||
entry: unorouterProvider,
|
||||
id: "unorouter",
|
||||
alias: "unorouter",
|
||||
chatUrl: "https://api.unorouter.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.unorouter.com/v1/models",
|
||||
},
|
||||
{
|
||||
entry: poolsideProvider,
|
||||
id: "poolside",
|
||||
alias: "poolside",
|
||||
chatUrl: "https://inference.poolside.ai/v1/chat/completions",
|
||||
modelsUrl: "https://inference.poolside.ai/v1/models",
|
||||
},
|
||||
];
|
||||
|
||||
for (const { entry, id, alias, chatUrl, modelsUrl } of providers) {
|
||||
test(`${id} uses the standard OpenAI-compatible API-key registry shape`, () => {
|
||||
assert.equal(entry.id, id);
|
||||
assert.equal(entry.alias, alias);
|
||||
assert.equal(entry.format, "openai");
|
||||
assert.equal(entry.executor, "default");
|
||||
assert.equal(entry.authType, "apikey");
|
||||
assert.equal(entry.authHeader, "bearer");
|
||||
assert.equal(entry.baseUrl, chatUrl);
|
||||
assert.equal(entry.modelsUrl, modelsUrl);
|
||||
assert.equal(entry.passthroughModels, true);
|
||||
});
|
||||
|
||||
test(`${id} relies on its live catalog without invented static model ids`, () => {
|
||||
assert.deepEqual(entry.models, []);
|
||||
});
|
||||
}
|
||||
51
tests/unit/free-tier-providers-wave2-a.test.ts
Normal file
51
tests/unit/free-tier-providers-wave2-a.test.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { literouterProvider } from "../../open-sse/config/providers/registry/literouter/index.ts";
|
||||
import { meganovaAiProvider } from "../../open-sse/config/providers/registry/meganova-ai/index.ts";
|
||||
import { mnnAiProvider } from "../../open-sse/config/providers/registry/mnn-ai/index.ts";
|
||||
import type { RegistryEntry } from "../../open-sse/config/providers/shared.ts";
|
||||
|
||||
const providers: Array<{
|
||||
entry: RegistryEntry;
|
||||
id: string;
|
||||
chatUrl: string;
|
||||
modelsUrl: string;
|
||||
}> = [
|
||||
{
|
||||
entry: literouterProvider,
|
||||
id: "literouter",
|
||||
chatUrl: "https://api.literouter.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.literouter.com/v1/models",
|
||||
},
|
||||
{
|
||||
entry: mnnAiProvider,
|
||||
id: "mnn-ai",
|
||||
chatUrl: "https://api.mnnai.ru/v1/chat/completions",
|
||||
modelsUrl: "https://api.mnnai.ru/v1/models",
|
||||
},
|
||||
{
|
||||
entry: meganovaAiProvider,
|
||||
id: "meganova-ai",
|
||||
chatUrl: "https://api.meganova.ai/v1/chat/completions",
|
||||
modelsUrl: "https://api.meganova.ai/v1/models",
|
||||
},
|
||||
];
|
||||
|
||||
for (const { entry, id, chatUrl, modelsUrl } of providers) {
|
||||
test(`${id} uses an OpenAI-compatible Bearer registry entry`, () => {
|
||||
assert.equal(entry.id, id);
|
||||
assert.equal(entry.alias, id);
|
||||
assert.equal(entry.format, "openai");
|
||||
assert.equal(entry.executor, "default");
|
||||
assert.equal(entry.authType, "apikey");
|
||||
assert.equal(entry.authHeader, "bearer");
|
||||
assert.equal(entry.baseUrl, chatUrl);
|
||||
assert.equal(entry.modelsUrl, modelsUrl);
|
||||
assert.equal(entry.passthroughModels, true);
|
||||
});
|
||||
|
||||
test(`${id} leaves model discovery to the live upstream catalog`, () => {
|
||||
assert.deepEqual(entry.models, []);
|
||||
});
|
||||
}
|
||||
60
tests/unit/free-tier-providers-wave2-b.test.ts
Normal file
60
tests/unit/free-tier-providers-wave2-b.test.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { mixlayerProvider } from "../../open-sse/config/providers/registry/mixlayer/index.ts";
|
||||
import { spekaProvider } from "../../open-sse/config/providers/registry/speka/index.ts";
|
||||
import { tokenreplyProvider } from "../../open-sse/config/providers/registry/tokenreply/index.ts";
|
||||
import type { RegistryEntry } from "../../open-sse/config/providers/shared.ts";
|
||||
|
||||
const providers: Array<{
|
||||
entry: RegistryEntry;
|
||||
id: string;
|
||||
chatUrl: string;
|
||||
modelsUrl: string;
|
||||
}> = [
|
||||
{
|
||||
entry: mixlayerProvider,
|
||||
id: "mixlayer",
|
||||
chatUrl: "https://models.mixlayer.ai/v1/chat/completions",
|
||||
modelsUrl: "https://models.mixlayer.ai/v1/models",
|
||||
},
|
||||
{
|
||||
entry: spekaProvider,
|
||||
id: "speka",
|
||||
chatUrl: "https://speka.me/v1/chat/completions",
|
||||
modelsUrl: "https://speka.me/v1/models",
|
||||
},
|
||||
{
|
||||
entry: tokenreplyProvider,
|
||||
id: "tokenreply",
|
||||
chatUrl: "https://api.tokenreply.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.tokenreply.com/v1/models",
|
||||
},
|
||||
];
|
||||
|
||||
test("Wave 2-B providers expose OpenAI-compatible Bearer registries", () => {
|
||||
for (const { entry, id, chatUrl, modelsUrl } of providers) {
|
||||
assert.equal(entry.id, id);
|
||||
assert.equal(entry.alias, id);
|
||||
assert.equal(entry.format, "openai");
|
||||
assert.equal(entry.executor, "default");
|
||||
assert.equal(entry.authType, "apikey");
|
||||
assert.equal(entry.authHeader, "bearer");
|
||||
assert.equal(entry.baseUrl, chatUrl);
|
||||
assert.equal(entry.modelsUrl, modelsUrl);
|
||||
assert.equal(entry.passthroughModels, true);
|
||||
assert.ok(Array.isArray(entry.models));
|
||||
}
|
||||
});
|
||||
|
||||
test("Mixlayer seeds only its documented free model", () => {
|
||||
assert.deepEqual(
|
||||
mixlayerProvider.models.map((model) => model.id),
|
||||
["qwen/qwen3.5-4b-free"]
|
||||
);
|
||||
});
|
||||
|
||||
test("Speka and TokenReply rely on live model catalogs without invented models", () => {
|
||||
assert.deepEqual(spekaProvider.models, []);
|
||||
assert.deepEqual(tokenreplyProvider.models, []);
|
||||
});
|
||||
58
tests/unit/free-tier-providers-wave2-c.test.ts
Normal file
58
tests/unit/free-tier-providers-wave2-c.test.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { cloudcodeOneProvider } from "../../open-sse/config/providers/registry/cloudcode-one/index.ts";
|
||||
import { dxntProvider } from "../../open-sse/config/providers/registry/dxnt/index.ts";
|
||||
import { yoloAutoProvider } from "../../open-sse/config/providers/registry/yolo-auto/index.ts";
|
||||
import type { RegistryEntry } from "../../open-sse/config/providers/shared.ts";
|
||||
|
||||
const providers: Array<{
|
||||
entry: RegistryEntry;
|
||||
id: string;
|
||||
chatUrl: string;
|
||||
modelsUrl: string;
|
||||
modelIds: string[];
|
||||
}> = [
|
||||
{
|
||||
entry: yoloAutoProvider,
|
||||
id: "yolo-auto",
|
||||
chatUrl: "https://yolo-auto.com/v1/chat/completions",
|
||||
modelsUrl: "https://yolo-auto.com/v1/models",
|
||||
modelIds: ["qwen3.6-35b-a3b"],
|
||||
},
|
||||
{
|
||||
entry: dxntProvider,
|
||||
id: "dxnt",
|
||||
chatUrl: "https://www.dxnt.com/v1/chat/completions",
|
||||
modelsUrl: "https://www.dxnt.com/v1/models",
|
||||
modelIds: [],
|
||||
},
|
||||
{
|
||||
entry: cloudcodeOneProvider,
|
||||
id: "cloudcode-one",
|
||||
chatUrl: "https://api.cloudcode.one/v1/chat/completions",
|
||||
modelsUrl: "https://api.cloudcode.one/v1/models",
|
||||
modelIds: ["glm-4.7-flash", "glm-4.6v-flash"],
|
||||
},
|
||||
];
|
||||
|
||||
for (const { entry, id, chatUrl, modelsUrl, modelIds } of providers) {
|
||||
test(`${id} uses the standard OpenAI-compatible API-key registry shape`, () => {
|
||||
assert.equal(entry.id, id);
|
||||
assert.equal(entry.alias, id);
|
||||
assert.equal(entry.format, "openai");
|
||||
assert.equal(entry.executor, "default");
|
||||
assert.equal(entry.authType, "apikey");
|
||||
assert.equal(entry.authHeader, "bearer");
|
||||
assert.equal(entry.baseUrl, chatUrl);
|
||||
assert.equal(entry.modelsUrl, modelsUrl);
|
||||
assert.equal(entry.passthroughModels, true);
|
||||
});
|
||||
|
||||
test(`${id} seeds only the audited model identifiers`, () => {
|
||||
assert.deepEqual(
|
||||
(entry.models ?? []).map((model) => model.id),
|
||||
modelIds
|
||||
);
|
||||
});
|
||||
}
|
||||
60
tests/unit/free-tier-providers-wave2-integration.test.ts
Normal file
60
tests/unit/free-tier-providers-wave2-integration.test.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts");
|
||||
const { DefaultExecutor, getExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
const { PROVIDER_ENDPOINTS } = await import("../../src/shared/constants/config.ts");
|
||||
const { isValidModel } = await import("../../src/shared/constants/models.ts");
|
||||
const { AGGREGATOR_PROVIDER_IDS } = await import("../../src/shared/constants/providers.ts");
|
||||
const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers/apikey/index.ts");
|
||||
|
||||
const providers = [
|
||||
["literouter", "https://api.literouter.com/v1/chat/completions", []],
|
||||
["mnn-ai", "https://api.mnnai.ru/v1/chat/completions", []],
|
||||
["meganova-ai", "https://api.meganova.ai/v1/chat/completions", []],
|
||||
["mixlayer", "https://models.mixlayer.ai/v1/chat/completions", ["qwen/qwen3.5-4b-free"]],
|
||||
["speka", "https://speka.me/v1/chat/completions", []],
|
||||
["tokenreply", "https://api.tokenreply.com/v1/chat/completions", []],
|
||||
["yolo-auto", "https://yolo-auto.com/v1/chat/completions", ["qwen3.6-35b-a3b"]],
|
||||
["dxnt", "https://www.dxnt.com/v1/chat/completions", []],
|
||||
[
|
||||
"cloudcode-one",
|
||||
"https://api.cloudcode.one/v1/chat/completions",
|
||||
["glm-4.7-flash", "glm-4.6v-flash"],
|
||||
],
|
||||
] as const;
|
||||
|
||||
for (const [id, endpoint, modelIds] of providers) {
|
||||
test(`${id} is wired through registry, metadata, endpoint and default executor`, () => {
|
||||
const registry = REGISTRY[id];
|
||||
const metadata = APIKEY_PROVIDERS[id];
|
||||
|
||||
assert.ok(registry);
|
||||
assert.ok(metadata);
|
||||
assert.equal(registry.id, id);
|
||||
assert.equal(registry.alias, id);
|
||||
assert.equal(registry.baseUrl, endpoint);
|
||||
assert.equal(PROVIDER_ENDPOINTS[id], endpoint);
|
||||
assert.equal(metadata.id, id);
|
||||
assert.equal(metadata.alias, id);
|
||||
assert.equal(metadata.hasFree, true);
|
||||
assert.equal(metadata.passthroughModels, true);
|
||||
assert.ok(typeof metadata.freeNote === "string" && metadata.freeNote.length > 0);
|
||||
assert.equal(AGGREGATOR_PROVIDER_IDS.has(id), true);
|
||||
assert.ok(getExecutor(id) instanceof DefaultExecutor);
|
||||
assert.equal(isValidModel(id, "future/live-catalog-model"), true);
|
||||
assert.deepEqual(
|
||||
registry.models.map((model) => model.id),
|
||||
modelIds
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
test("risk-sensitive metadata preserves the audited quota qualifications", () => {
|
||||
assert.match(APIKEY_PROVIDERS["mnn-ai"].apiHint ?? "", /jurisdiction.*privacy/i);
|
||||
assert.match(APIKEY_PROVIDERS["meganova-ai"].freeNote ?? "", /per-model quotas/i);
|
||||
assert.match(APIKEY_PROVIDERS["meganova-ai"].freeNote ?? "", /paid overage/i);
|
||||
assert.match(APIKEY_PROVIDERS.tokenreply.freeNote ?? "", /no fixed global free quota/i);
|
||||
assert.match(APIKEY_PROVIDERS["yolo-auto"].freeNote ?? "", /no numeric daily quota/i);
|
||||
assert.match(APIKEY_PROVIDERS["cloudcode-one"].freeNote ?? "", /credit or a coupon/i);
|
||||
});
|
||||
41
tests/unit/free-tier-providers-wave3-c.test.ts
Normal file
41
tests/unit/free-tier-providers-wave3-c.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { chatOripeProvider } from "../../open-sse/config/providers/registry/chat-oripe/index.ts";
|
||||
import { nagaAiProvider } from "../../open-sse/config/providers/registry/naga-ai/index.ts";
|
||||
import type { RegistryEntry } from "../../open-sse/config/providers/shared.ts";
|
||||
|
||||
const providers: Array<{
|
||||
entry: RegistryEntry;
|
||||
id: string;
|
||||
chatUrl: string;
|
||||
modelsUrl: string;
|
||||
}> = [
|
||||
{
|
||||
entry: nagaAiProvider,
|
||||
id: "naga-ai",
|
||||
chatUrl: "https://api.naga.ac/v1/chat/completions",
|
||||
modelsUrl: "https://api.naga.ac/v1/models",
|
||||
},
|
||||
{
|
||||
entry: chatOripeProvider,
|
||||
id: "chat-oripe",
|
||||
chatUrl: "https://api.oriper.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.oriper.com/v1/models",
|
||||
},
|
||||
];
|
||||
|
||||
test("Wave 3-C providers expose OpenAI-compatible Bearer registries", () => {
|
||||
for (const { entry, id, chatUrl, modelsUrl } of providers) {
|
||||
assert.equal(entry.id, id);
|
||||
assert.equal(entry.alias, id);
|
||||
assert.equal(entry.format, "openai");
|
||||
assert.equal(entry.executor, "default");
|
||||
assert.equal(entry.authType, "apikey");
|
||||
assert.equal(entry.authHeader, "bearer");
|
||||
assert.equal(entry.baseUrl, chatUrl);
|
||||
assert.equal(entry.modelsUrl, modelsUrl);
|
||||
assert.equal(entry.passthroughModels, true);
|
||||
assert.deepEqual(entry.models, []);
|
||||
}
|
||||
});
|
||||
15
tests/unit/passthrough-provider-aliases.test.ts
Normal file
15
tests/unit/passthrough-provider-aliases.test.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
const { isValidModel } = await import("../../src/shared/constants/models.ts");
|
||||
|
||||
test("passthrough providers accept live model ids through both provider id and alias", () => {
|
||||
for (const [id, alias] of [
|
||||
["zylo-api", "zylo"],
|
||||
["llm-kiwi", "llmkiwi"],
|
||||
["freetheai", "fta"],
|
||||
] as const) {
|
||||
assert.equal(isValidModel(id, "future/live-catalog-model"), true);
|
||||
assert.equal(isValidModel(alias, "future/live-catalog-model"), true);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user