feat(providers): add UnoRouter provider (#8978) (#9009)

Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates green: static + changed tests + vitest — only pre-existing audit.test.ts flake). Evidence: /home/diegosouzapw/dev/proxys/OmniRoute/.claude/worktrees/merge-train-20260805-213228-suite.log
This commit is contained in:
Jeevan M
2026-08-06 06:12:35 +05:30
committed by GitHub
parent 68cb678780
commit 2cfd672f79
10 changed files with 86 additions and 10 deletions

View File

@@ -0,0 +1 @@
- **feat(providers): add UnoRouter provider** — UnoRouter is an OpenAI-compatible routing gateway supporting hundreds of models. It is now registered as an API-key provider. ([#8978](https://github.com/diegosouzapw/OmniRoute/issues/8978))

View File

@@ -1,4 +1,5 @@
import type { RegistryEntry } from "./shared.ts";
import { unorouterProvider } from "./registry/unorouter/index.ts";
import { aimlapiProvider } from "./registry/aimlapi/index.ts";
import { byteplusProvider } from "./registry/byteplus/index.ts";
@@ -444,4 +445,5 @@ export const REGISTRY: Record<string, RegistryEntry> = {
hcnsec: hcnsecProvider,
promptql: promptqlProvider,
hyperagent: hyperagentProvider,
unorouter: unorouterProvider,
};

View File

@@ -0,0 +1,14 @@
import type { RegistryEntry } from "../../shared.ts";
export const unorouterProvider: RegistryEntry = {
id: "unorouter",
alias: "unorouter",
format: "openai",
executor: "default",
baseUrl: "https://api.unorouter.ai/v1/chat/completions",
authType: "apikey",
authHeader: "bearer",
defaultContextLength: 128000,
models: [{ id: "auto", name: "Auto (Best Available)" }],
passthroughModels: true,
};

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>UnoRouter</title><circle cx="12" cy="12" r="12" fill="#8B5CF6"/><text x="12" y="16" text-anchor="middle" fill="white" font-size="14" font-weight="bold" font-family="system-ui, sans-serif">U</text></svg>

After

Width:  |  Height:  |  Size: 270 B

View File

@@ -42,6 +42,7 @@ export const NAMED_OPENAI_STYLE_PROVIDERS = new Set([
"ovhcloud",
"sambanova",
"orcarouter",
"unorouter",
"uncloseai",
"opencode-go",
"baseten",

View File

@@ -5769,6 +5769,7 @@
"onboardingProviderDescriptions": {
"360ai": "Get API key at ai.360.cn",
"agentrouter": "Get $200 free credits at https://agentrouter.org/register — no credit card required.",
"unorouter": "Create an API key at https://unorouter.ai, then paste it here as a Bearer token.",
"agnes": "Get API key at agnes-ai.com",
"aimlapi": "Free tier paused (2026) — AI/ML API is now pay-as-you-go only (min $20 top-up); no recurring free credits.",
"ai21": "$10 trial credits on signup (valid 3 months), no credit card required",

View File

@@ -216,6 +216,7 @@ const KNOWN_SVGS = new Set([
"trae",
"udio",
"uncloseai",
"unorouter",
"upstage",
"v0",
"veoaifree-web",

View File

@@ -74,6 +74,7 @@ export const AGGREGATOR_PROVIDER_IDS = new Set([
"getgoapi",
"laozhang",
"vercel-ai-gateway",
"unorouter",
"agentrouter",
"thebai",
"fenayai",

View File

@@ -45,6 +45,17 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
freeNote: "$200 free credits on signup - multi-model routing gateway",
apiHint: "Get $200 free credits at https://agentrouter.org/register — no credit card required.",
},
unorouter: {
id: "unorouter",
alias: "unorouter",
name: "UnoRouter",
icon: "unorouter",
color: "#8B5CF6",
textIcon: "UR",
passthroughModels: true,
website: "https://unorouter.ai",
apiHint: "Create an API key at https://unorouter.ai, then paste it here as a Bearer token.",
},
"command-code": {
id: "command-code",
alias: "cmd",
@@ -295,8 +306,7 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
textIcon: "G4F",
website: "https://g4f.space",
hasFree: true,
freeNote:
"Free no-key reverse proxy to Groq (gpt4free project) — rate-limited to 5 req/min.",
freeNote: "Free no-key reverse proxy to Groq (gpt4free project) — rate-limited to 5 req/min.",
passthroughModels: true,
authHint:
"No auth required. Free tier is limited to 5 requests/minute — sign up at g4f.dev/members.html for higher limits.",
@@ -310,8 +320,7 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
textIcon: "G4F",
website: "https://g4f.space",
hasFree: true,
freeNote:
"Free no-key reverse proxy to Gemini (gpt4free project) — rate-limited to 5 req/min.",
freeNote: "Free no-key reverse proxy to Gemini (gpt4free project) — rate-limited to 5 req/min.",
passthroughModels: true,
authHint:
"No auth required. Free tier is limited to 5 requests/minute — sign up at g4f.dev/members.html for higher limits.",
@@ -340,8 +349,7 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
textIcon: "G4F",
website: "https://g4f.space",
hasFree: true,
freeNote:
"Free no-key hosted Ollama gateway (gpt4free project) — rate-limited to 5 req/min.",
freeNote: "Free no-key hosted Ollama gateway (gpt4free project) — rate-limited to 5 req/min.",
passthroughModels: true,
authHint:
"No auth required. Free tier is limited to 5 requests/minute — sign up at g4f.dev/members.html for higher limits.",
@@ -759,7 +767,8 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
website: "https://ainative.studio",
hasFree: true,
freeNote: "Free tier ~10M tokens/month (claimed) across Qwen3, Llama 4, DeepSeek R1 and more.",
authHint: "Create a free API key at ainative.studio (no card), then paste it here as a Bearer token.",
authHint:
"Create a free API key at ainative.studio (no card), then paste it here as a Bearer token.",
apiHint:
"OpenAI-compatible endpoint at https://api.ainative.studio/api/v1 with a public /models catalog (84 models). OmniRoute lists models via passthrough.",
},
@@ -774,7 +783,8 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
website: "https://www.aionlabs.ai",
hasFree: true,
freeNote: "Free tier ~20k tokens/day across the Aion reasoning models.",
authHint: "Create a free API key at aionlabs.ai (no card), then paste it here as a Bearer token.",
authHint:
"Create a free API key at aionlabs.ai (no card), then paste it here as a Bearer token.",
apiHint:
"OpenAI-compatible endpoint at https://api.aionlabs.ai/v1 with a public /models catalog carrying context and pricing.",
},
@@ -788,7 +798,8 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
passthroughModels: true,
website: "https://routeway.ai",
hasFree: true,
freeNote: "Free models (:free suffix) at ~5 RPM / 200 RPD across Llama, Nemotron, Step and Laguna.",
freeNote:
"Free models (:free suffix) at ~5 RPM / 200 RPD across Llama, Nemotron, Step and Laguna.",
authHint: "Create a free API key at routeway.ai, then paste it here as a Bearer token.",
apiHint:
"OpenAI-compatible endpoint at https://api.routeway.ai/v1 with a public /models catalog (236 models). Cloudflare fronts the API and requires a browser-style User-Agent.",
@@ -804,7 +815,8 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
website: "https://bynara.id",
hasFree: true,
freeNote: "Free tier is a shared 5M tokens/day pool; some models are gated behind credit/plan.",
authHint: "Get a free API key via NaraRouter's Telegram channel, then paste it here as a Bearer token.",
authHint:
"Get a free API key via NaraRouter's Telegram channel, then paste it here as a Bearer token.",
apiHint:
"OpenAI-compatible endpoint at https://router.bynara.id/v1. Free-tier models are pinned; others need credit.",
},

View File

@@ -0,0 +1,42 @@
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 UNOROUTER_CHAT_URL = "https://api.unorouter.ai/v1/chat/completions";
test("unorouter is registered as an API-key gateway provider", () => {
const entry = APIKEY_PROVIDERS.unorouter;
assert.ok(entry, "APIKEY_PROVIDERS.unorouter must be defined");
assert.equal(entry.id, "unorouter");
assert.equal(entry.alias, "unorouter");
assert.equal(entry.name, "UnoRouter");
assert.equal(entry.website, "https://unorouter.ai");
assert.equal(entry.passthroughModels, true);
});
test("unorouter registry entry uses OpenAI format with bearer API-key auth", () => {
const entry = providerRegistry.unorouter;
assert.ok(entry, "providerRegistry.unorouter must be defined");
assert.equal(entry.id, "unorouter");
assert.equal(entry.alias, "unorouter");
assert.equal(entry.format, "openai");
assert.equal(entry.executor, "default");
assert.equal(entry.authType, "apikey");
assert.equal(entry.authHeader, "bearer");
assert.equal(entry.baseUrl, UNOROUTER_CHAT_URL);
assert.equal(entry.passthroughModels, true);
});
test("unorouter ships with auto model", () => {
assert.deepEqual(providerRegistry.unorouter.models, [
{ id: "auto", name: "Auto (Best Available)" },
]);
});
test("unorouter accepts any model id via passthrough", () => {
assert.equal(isValidModel("unorouter", "openai/gpt-4o"), true);
assert.equal(isValidModel("unorouter", "anthropic/claude-3-5-sonnet"), true);
});