From 55cb23cd62b14c093d04b1a66d89416db66735fc Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Sat, 1 Aug 2026 23:27:36 -0300 Subject: [PATCH] feat(providers): integrate audited free-tier gateways --- open-sse/config/providers/index.ts | 15 +++ src/shared/constants/config.ts | 8 ++ src/shared/constants/models.ts | 5 +- src/shared/constants/providers.ts | 7 ++ .../constants/providers/apikey/gateways.ts | 105 ++++++++++++++++++ .../providers/apikey/inference-hosts.ts | 16 +++ ...-tier-providers-phase3-integration.test.ts | 55 +++++++++ .../unit/passthrough-provider-aliases.test.ts | 15 +++ 8 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 tests/unit/free-tier-providers-phase3-integration.test.ts create mode 100644 tests/unit/passthrough-provider-aliases.test.ts diff --git a/open-sse/config/providers/index.ts b/open-sse/config/providers/index.ts index 80357cf981..edf51cda68 100644 --- a/open-sse/config/providers/index.ts +++ b/open-sse/config/providers/index.ts @@ -229,6 +229,14 @@ import { hcnsecProvider } from "./registry/hcnsec/index.ts"; import { promptqlProvider } from "./registry/promptql/index.ts"; import { hyperagentProvider } from "./registry/hyperagent/index.ts"; import { muse_codeProvider } from "./registry/muse-code/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"; export const REGISTRY: Record = { aimlapi: aimlapiProvider, @@ -459,5 +467,12 @@ export const REGISTRY: Record = { promptql: promptqlProvider, hyperagent: hyperagentProvider, "muse-code": muse_codeProvider, + "zylo-api": zyloApiProvider, unorouter: unorouterProvider, + poolside: poolsideProvider, + fastrouter: fastrouterProvider, + anyapi: anyapiProvider, + electronhub: electronhubProvider, + llmgateway: llmgatewayProvider, + "llm-kiwi": llmKiwiProvider, }; diff --git a/src/shared/constants/config.ts b/src/shared/constants/config.ts index d80b94f4ef..7ed0827a01 100644 --- a/src/shared/constants/config.ts +++ b/src/shared/constants/config.ts @@ -7,6 +7,14 @@ 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", 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/models.ts b/src/shared/constants/models.ts index 7595280708..f78d64858f 100644 --- a/src/shared/constants/models.ts +++ b/src/shared/constants/models.ts @@ -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 diff --git a/src/shared/constants/providers.ts b/src/shared/constants/providers.ts index bdebdc4753..78a091f191 100644 --- a/src/shared/constants/providers.ts +++ b/src/shared/constants/providers.ts @@ -93,6 +93,13 @@ export const AGGREGATOR_PROVIDER_IDS = new Set([ "g4f-pollinations", "g4f-ollama", "g4f-nvidia", + "zylo-api", + "unorouter", + "fastrouter", + "anyapi", + "electronhub", + "llmgateway", + "llm-kiwi", ]); 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 24a0567efd..460dc7347b 100644 --- a/src/shared/constants/providers/apikey/gateways.ts +++ b/src/shared/constants/providers/apikey/gateways.ts @@ -95,6 +95,111 @@ 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.", + }, + unorouter: { + id: "unorouter", + alias: "unorouter", + name: "UnoRouter", + icon: "router", + color: "#7C3AED", + textIcon: "UR", + passthroughModels: true, + website: "https://unorouter.com", + hasFree: true, + freeNote: + "Models with the :free suffix do not debit balance; limit is 1 request/minute per free model per user.", + apiHint: + "Create an UnoRouter token, then use https://api.unorouter.com/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.", + }, dgrid: { id: "dgrid", alias: "dgrid", diff --git a/src/shared/constants/providers/apikey/inference-hosts.ts b/src/shared/constants/providers/apikey/inference-hosts.ts index 7f14a46061..f081897f9f 100644 --- a/src/shared/constants/providers/apikey/inference-hosts.ts +++ b/src/shared/constants/providers/apikey/inference-hosts.ts @@ -45,6 +45,22 @@ export const APIKEY_PROVIDERS_INFERENCE = { hasFree: true, freeNote: "Free plan: 3-day trial with open-source models — no credit card required", }, + 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", diff --git a/tests/unit/free-tier-providers-phase3-integration.test.ts b/tests/unit/free-tier-providers-phase3-integration.test.ts new file mode 100644 index 0000000000..89c3141d8b --- /dev/null +++ b/tests/unit/free-tier-providers-phase3-integration.test.ts @@ -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" }, + ]); +}); diff --git a/tests/unit/passthrough-provider-aliases.test.ts b/tests/unit/passthrough-provider-aliases.test.ts new file mode 100644 index 0000000000..7ea409663e --- /dev/null +++ b/tests/unit/passthrough-provider-aliases.test.ts @@ -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); + } +});