diff --git a/open-sse/config/providers/index.ts b/open-sse/config/providers/index.ts index edf51cda68..bf895b4585 100644 --- a/open-sse/config/providers/index.ts +++ b/open-sse/config/providers/index.ts @@ -237,6 +237,15 @@ 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 = { aimlapi: aimlapiProvider, @@ -475,4 +484,13 @@ export const REGISTRY: Record = { 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, }; diff --git a/src/shared/constants/config.ts b/src/shared/constants/config.ts index 7ed0827a01..0b5a7c4b14 100644 --- a/src/shared/constants/config.ts +++ b/src/shared/constants/config.ts @@ -15,6 +15,15 @@ export const PROVIDER_ENDPOINTS = { 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", diff --git a/src/shared/constants/providers.ts b/src/shared/constants/providers.ts index 78a091f191..8a6cc6decf 100644 --- a/src/shared/constants/providers.ts +++ b/src/shared/constants/providers.ts @@ -100,6 +100,15 @@ export const AGGREGATOR_PROVIDER_IDS = new Set([ "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([ diff --git a/src/shared/constants/providers/apikey/gateways.ts b/src/shared/constants/providers/apikey/gateways.ts index 460dc7347b..f3121a67d9 100644 --- a/src/shared/constants/providers/apikey/gateways.ts +++ b/src/shared/constants/providers/apikey/gateways.ts @@ -200,6 +200,140 @@ export const APIKEY_PROVIDERS_GATEWAYS = { 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", diff --git a/tests/unit/free-tier-providers-wave2-integration.test.ts b/tests/unit/free-tier-providers-wave2-integration.test.ts new file mode 100644 index 0000000000..83128bd3c0 --- /dev/null +++ b/tests/unit/free-tier-providers-wave2-integration.test.ts @@ -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); +});