mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-11 01:32:22 +03:00
Compare commits
8 Commits
feat/9544-
...
feat/free-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
252098cbd9 | ||
|
|
7f09814a2f | ||
|
|
9db12119ea | ||
|
|
f2ba68022a | ||
|
|
58eb441307 | ||
|
|
ddc4c827a7 | ||
|
|
c7d9e9c4c7 | ||
|
|
f3c2e30879 |
@@ -1 +0,0 @@
|
||||
- feat(providers): add Muse Code CLI provider preset (#9544)
|
||||
@@ -225,7 +225,14 @@ 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 { 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<string, RegistryEntry> = {
|
||||
aimlapi: aimlapiProvider,
|
||||
@@ -452,6 +459,12 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
hcnsec: hcnsecProvider,
|
||||
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,
|
||||
};
|
||||
|
||||
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/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,
|
||||
});
|
||||
@@ -1,106 +0,0 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
/**
|
||||
* Muse Code CLI — Meta's agentic coding tool.
|
||||
*
|
||||
* Wire format: OpenAI Responses API (POST /responses).
|
||||
* Auth: Bearer token from META_API_KEY env var.
|
||||
* Reasoning efforts: xhigh/ultra -> high (handled generically).
|
||||
*
|
||||
* @see https://github.com/joymadhu49/muse-openrouter-shim
|
||||
*/
|
||||
export const muse_codeProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "muse-code",
|
||||
alias: "mc",
|
||||
passthroughModels: true,
|
||||
defaultContextLength: 200000,
|
||||
models: [
|
||||
{
|
||||
id: "llama-4-maverick",
|
||||
name: "Llama 4 Maverick",
|
||||
contextLength: 1048576,
|
||||
maxOutputTokens: 131072,
|
||||
supportsReasoning: true,
|
||||
supportsXHighEffort: true,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
targetFormat: "openai-responses",
|
||||
unsupportedParams: ["logprobs", "topLogprobs", "logitBias"],
|
||||
},
|
||||
{
|
||||
id: "llama-4-scout",
|
||||
name: "Llama 4 Scout",
|
||||
contextLength: 1048576,
|
||||
maxOutputTokens: 131072,
|
||||
supportsReasoning: true,
|
||||
supportsXHighEffort: true,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
targetFormat: "openai-responses",
|
||||
unsupportedParams: ["logprobs", "topLogprobs", "logitBias"],
|
||||
},
|
||||
{
|
||||
id: "llama-3.3-70b",
|
||||
name: "Llama 3.3 70B",
|
||||
contextLength: 131072,
|
||||
maxOutputTokens: 32768,
|
||||
supportsReasoning: false,
|
||||
toolCalling: true,
|
||||
targetFormat: "openai-responses",
|
||||
unsupportedParams: ["logprobs", "topLogprobs"],
|
||||
},
|
||||
{
|
||||
id: "llama-3.1-405b",
|
||||
name: "Llama 3.1 405B",
|
||||
contextLength: 131072,
|
||||
maxOutputTokens: 32768,
|
||||
supportsReasoning: false,
|
||||
toolCalling: true,
|
||||
targetFormat: "openai-responses",
|
||||
unsupportedParams: ["logprobs", "topLogprobs"],
|
||||
},
|
||||
{
|
||||
id: "llama-3.1-70b",
|
||||
name: "Llama 3.1 70B",
|
||||
contextLength: 131072,
|
||||
maxOutputTokens: 32768,
|
||||
supportsReasoning: false,
|
||||
toolCalling: true,
|
||||
targetFormat: "openai-responses",
|
||||
unsupportedParams: ["logprobs", "topLogprobs"],
|
||||
},
|
||||
{
|
||||
id: "llama-3.1-8b",
|
||||
name: "Llama 3.1 8B",
|
||||
contextLength: 131072,
|
||||
maxOutputTokens: 32768,
|
||||
supportsReasoning: false,
|
||||
toolCalling: true,
|
||||
targetFormat: "openai-responses",
|
||||
unsupportedParams: ["logprobs", "topLogprobs"],
|
||||
},
|
||||
{
|
||||
id: "llama-3.2-90b-vision",
|
||||
name: "Llama 3.2 90B Vision",
|
||||
contextLength: 131072,
|
||||
maxOutputTokens: 32768,
|
||||
supportsReasoning: false,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
targetFormat: "openai-responses",
|
||||
unsupportedParams: ["logprobs", "topLogprobs"],
|
||||
},
|
||||
{
|
||||
id: "llama-3.2-11b-vision",
|
||||
name: "Llama 3.2 11B Vision",
|
||||
contextLength: 131072,
|
||||
maxOutputTokens: 32768,
|
||||
supportsReasoning: false,
|
||||
toolCalling: true,
|
||||
supportsVision: true,
|
||||
targetFormat: "openai-responses",
|
||||
unsupportedParams: ["logprobs", "topLogprobs"],
|
||||
},
|
||||
],
|
||||
});
|
||||
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,
|
||||
});
|
||||
@@ -1,14 +1,11 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const unorouterProvider: RegistryEntry = {
|
||||
export const unorouterProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
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)" }],
|
||||
baseUrl: "https://api.unorouter.com/v1/chat/completions",
|
||||
modelsUrl: "https://api.unorouter.com/v1/models",
|
||||
models: [],
|
||||
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,
|
||||
});
|
||||
@@ -1,87 +0,0 @@
|
||||
/**
|
||||
* Muse Code CLI proprietary model catalog endpoint.
|
||||
*
|
||||
* Muse CLI calls GET /muse-code/models (or --base-url/muse-code/models)
|
||||
* to discover available models. Returns the proprietary Muse format:
|
||||
*
|
||||
* { object: "list", data: [{ id, object, created, owned_by, metadata }] }
|
||||
*
|
||||
* Each model's metadata includes: name, family, reasoning, tool_call,
|
||||
* modalities, limit, cost.
|
||||
*/
|
||||
|
||||
import { muse_codeProvider } from "@omniroute/open-sse/config/providers/registry/muse-code/index.ts";
|
||||
|
||||
const MUSECODE_TIMESTAMP = Math.floor(Date.now() / 1000);
|
||||
|
||||
interface MuseCodeModel {
|
||||
id: string;
|
||||
object: "model";
|
||||
created: number;
|
||||
owned_by: string;
|
||||
metadata: {
|
||||
name: string;
|
||||
family: string;
|
||||
reasoning: boolean;
|
||||
tool_call: boolean;
|
||||
modalities: string[];
|
||||
limit: number;
|
||||
cost: number;
|
||||
};
|
||||
}
|
||||
|
||||
function buildModelCatalog(): MuseCodeModel[] {
|
||||
const data: MuseCodeModel[] = [];
|
||||
|
||||
for (const model of muse_codeProvider.models) {
|
||||
let family = "llama";
|
||||
if (model.id.includes("llama-4")) family = "llama-4";
|
||||
else if (model.id.includes("llama-3.3")) family = "llama-3.3";
|
||||
else if (model.id.includes("llama-3.2")) family = "llama-3.2";
|
||||
else if (model.id.includes("llama-3.1")) family = "llama-3.1";
|
||||
|
||||
const modalities: string[] = ["text"];
|
||||
if (model.supportsVision) modalities.push("image");
|
||||
|
||||
data.push({
|
||||
id: model.id,
|
||||
object: "model",
|
||||
created: MUSECODE_TIMESTAMP,
|
||||
owned_by: "meta",
|
||||
metadata: {
|
||||
name: model.name,
|
||||
family,
|
||||
reasoning: !!model.supportsReasoning,
|
||||
tool_call: !!model.toolCalling,
|
||||
modalities,
|
||||
limit: model.contextLength ?? 200_000,
|
||||
cost: model.id.includes("maverick") || model.id.includes("405b") ? 3 : 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Cache the catalog for the lifetime of the process — model list is static.
|
||||
const CATALOG = buildModelCatalog();
|
||||
const CATALOG_PAYLOAD = JSON.stringify({ object: "list", data: CATALOG }, null, 2);
|
||||
|
||||
export async function OPTIONS() {
|
||||
return new Response(null, {
|
||||
headers: {
|
||||
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
return new Response(CATALOG_PAYLOAD, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"cache-control": "public, max-age=3600",
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -51,13 +51,6 @@ const GEMINI_CLI_PROFILE: ClientIdentityProfile = Object.freeze({
|
||||
"User-Agent": "GeminiCLI/0.1.0 (linux; x64)",
|
||||
}),
|
||||
});
|
||||
const MUSE_CLI_PROFILE: ClientIdentityProfile = Object.freeze({
|
||||
id: "muse-cli",
|
||||
label: "Muse Code CLI",
|
||||
headers: Object.freeze({
|
||||
"User-Agent": "MuseCodeCLI/0.1.0 (linux; x64)",
|
||||
}),
|
||||
});
|
||||
|
||||
/** Ordered so `CLIENT_IDENTITY_PROFILE_OPTIONS` renders "Default" first. */
|
||||
export const CLIENT_IDENTITY_PROFILES: Readonly<Record<string, ClientIdentityProfile>> =
|
||||
@@ -66,7 +59,6 @@ export const CLIENT_IDENTITY_PROFILES: Readonly<Record<string, ClientIdentityPro
|
||||
"claude-cli": CLAUDE_CLI_PROFILE,
|
||||
"codex-cli": CODEX_CLI_PROFILE,
|
||||
"gemini-cli": GEMINI_CLI_PROFILE,
|
||||
"muse-cli": MUSE_CLI_PROFILE,
|
||||
});
|
||||
|
||||
export const CLIENT_IDENTITY_PROFILE_IDS: readonly string[] = Object.keys(CLIENT_IDENTITY_PROFILES);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,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([
|
||||
|
||||
@@ -275,19 +275,4 @@ export const APIKEY_PROVIDERS_FRONTIER = {
|
||||
"Writer Palmyra is OpenAI-compatible at https://api.writer.com/v1. palmyra-x5 offers a 1M-token context window.",
|
||||
hasFree: false,
|
||||
},
|
||||
"muse-code": {
|
||||
id: "muse-code",
|
||||
alias: "mc",
|
||||
name: "Muse Code (Meta)",
|
||||
icon: "auto_awesome",
|
||||
color: "#0866FF",
|
||||
textIcon: "MC",
|
||||
website: "https://github.com/meta-llama/llama-stack",
|
||||
authHint:
|
||||
"Use your META_API_KEY env var as a Bearer token. Muse Code CLI uses the OpenAI Responses API wire format (POST /responses).",
|
||||
apiHint:
|
||||
"Muse Code is OpenAI-compatible. OmniRoute routes chat traffic through the Responses API and exposes the proprietary model catalog at /v1/muse-code/models.",
|
||||
passthroughModels: true,
|
||||
hasFree: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -3405,26 +3405,6 @@
|
||||
"stream": "https://api.morphllm.com/v1/chat/completions"
|
||||
}
|
||||
},
|
||||
"muse-code": {
|
||||
"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": {}
|
||||
},
|
||||
"muse-spark-web": {
|
||||
"format": "openai",
|
||||
"headers": {
|
||||
|
||||
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, []);
|
||||
});
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* Tests for Muse Code CLI model catalog endpoint.
|
||||
*
|
||||
* Verifies GET /v1/muse-code/models returns the proprietary Muse format.
|
||||
*/
|
||||
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { muse_codeProvider } from "../../open-sse/config/providers/registry/muse-code/index.ts";
|
||||
|
||||
// ── Model catalog shape ─────────────────────────────────────────────────────
|
||||
|
||||
test("muse-code provider has at least one model", () => {
|
||||
assert.ok(muse_codeProvider.models.length >= 1);
|
||||
});
|
||||
|
||||
test("muse-code models have unique ids", () => {
|
||||
const ids = muse_codeProvider.models.map((m) => m.id);
|
||||
const unique = new Set(ids);
|
||||
assert.equal(unique.size, ids.length, "model IDs must be unique");
|
||||
});
|
||||
|
||||
test("muse-code models include llama-4-maverick", () => {
|
||||
const ids = muse_codeProvider.models.map((m) => m.id);
|
||||
assert.ok(ids.includes("llama-4-maverick"), "must include llama-4-maverick");
|
||||
});
|
||||
|
||||
test("muse-code models include llama-4-scout", () => {
|
||||
const ids = muse_codeProvider.models.map((m) => m.id);
|
||||
assert.ok(ids.includes("llama-4-scout"), "must include llama-4-scout");
|
||||
});
|
||||
|
||||
test("muse-code models include llama-3.3-70b", () => {
|
||||
const ids = muse_codeProvider.models.map((m) => m.id);
|
||||
assert.ok(ids.includes("llama-3.3-70b"), "must include llama-3.3-70b");
|
||||
});
|
||||
|
||||
test("llama-4 models have supportsXHighEffort", () => {
|
||||
const maverick = muse_codeProvider.models.find((m) => m.id === "llama-4-maverick");
|
||||
assert.ok(maverick, "llama-4-maverick must exist");
|
||||
assert.equal(maverick.supportsXHighEffort, true);
|
||||
|
||||
const scout = muse_codeProvider.models.find((m) => m.id === "llama-4-scout");
|
||||
assert.ok(scout, "llama-4-scout must exist");
|
||||
assert.equal(scout.supportsXHighEffort, true);
|
||||
});
|
||||
|
||||
test("llama-3.3-70b does not support reasoning", () => {
|
||||
const model = muse_codeProvider.models.find((m) => m.id === "llama-3.3-70b");
|
||||
assert.ok(model, "llama-3.3-70b must exist");
|
||||
assert.equal(model.supportsReasoning, false);
|
||||
});
|
||||
|
||||
test("non-reasoning models do not declare supportsXHighEffort", () => {
|
||||
for (const model of muse_codeProvider.models) {
|
||||
if (!model.supportsReasoning) {
|
||||
assert.equal(
|
||||
model.supportsXHighEffort,
|
||||
undefined,
|
||||
`${model.id} is not a reasoning model but has supportsXHighEffort`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ── Vision models ───────────────────────────────────────────────────────────
|
||||
|
||||
test("vision models have supportsVision: true", () => {
|
||||
const expectedVision = [
|
||||
"llama-4-maverick",
|
||||
"llama-4-scout",
|
||||
"llama-3.2-90b-vision",
|
||||
"llama-3.2-11b-vision",
|
||||
];
|
||||
for (const model of muse_codeProvider.models) {
|
||||
if (expectedVision.includes(model.id)) {
|
||||
assert.equal(model.supportsVision, true, `${model.id} should have supportsVision`);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,91 +0,0 @@
|
||||
/**
|
||||
* Tests for Muse Code CLI provider registry entry.
|
||||
*
|
||||
* Verifies the provider entry loads correctly with expected config.
|
||||
*/
|
||||
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { muse_codeProvider } from "../../open-sse/config/providers/registry/muse-code/index.ts";
|
||||
import { getRegistryEntry } from "../../open-sse/config/providerRegistry.ts";
|
||||
|
||||
// ── Registry entry structure ────────────────────────────────────────────────
|
||||
|
||||
test("muse-code provider entry has id", () => {
|
||||
assert.equal(muse_codeProvider.id, "muse-code");
|
||||
});
|
||||
|
||||
test("muse-code provider entry has alias", () => {
|
||||
assert.equal(muse_codeProvider.alias, "mc");
|
||||
});
|
||||
|
||||
test("muse-code provider uses openai format", () => {
|
||||
assert.equal(muse_codeProvider.format, "openai");
|
||||
});
|
||||
|
||||
test("muse-code provider uses apikey auth", () => {
|
||||
assert.equal(muse_codeProvider.authType, "apikey");
|
||||
assert.equal(muse_codeProvider.authHeader, "bearer");
|
||||
});
|
||||
|
||||
test("muse-code provider has passthroughModels enabled", () => {
|
||||
assert.equal(muse_codeProvider.passthroughModels, true);
|
||||
});
|
||||
|
||||
// ── Model entries ───────────────────────────────────────────────────────────
|
||||
|
||||
test("muse-code provider has curated models", () => {
|
||||
assert.ok(muse_codeProvider.models.length > 0);
|
||||
});
|
||||
|
||||
test("all muse-code models have contextLength", () => {
|
||||
for (const model of muse_codeProvider.models) {
|
||||
assert.ok(
|
||||
typeof model.contextLength === "number" && model.contextLength > 0,
|
||||
`${model.id} must have positive contextLength`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("all muse-code models have toolCalling: true", () => {
|
||||
for (const model of muse_codeProvider.models) {
|
||||
assert.equal(model.toolCalling, true, `${model.id} must have toolCalling enabled`);
|
||||
}
|
||||
});
|
||||
|
||||
test("all muse-code models have targetFormat: openai-responses", () => {
|
||||
for (const model of muse_codeProvider.models) {
|
||||
assert.equal(
|
||||
model.targetFormat,
|
||||
"openai-responses",
|
||||
`${model.id} must use openai-responses target format`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("reasoning models have supportsXHighEffort", () => {
|
||||
for (const model of muse_codeProvider.models) {
|
||||
if (model.supportsReasoning) {
|
||||
assert.equal(
|
||||
model.supportsXHighEffort,
|
||||
true,
|
||||
`${model.id} is a reasoning model but missing supportsXHighEffort`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ── Registry discovery ──────────────────────────────────────────────────────
|
||||
|
||||
test("muse-code is discoverable via getRegistryEntry", () => {
|
||||
const entry = getRegistryEntry("muse-code");
|
||||
assert.ok(entry, "getRegistryEntry must return muse-code entry");
|
||||
assert.equal(entry.id, "muse-code");
|
||||
});
|
||||
|
||||
test("muse-code is discoverable via alias", () => {
|
||||
const entry = getRegistryEntry("mc");
|
||||
assert.ok(entry, "getRegistryEntry must find muse-code by alias mc");
|
||||
assert.equal(entry.id, "muse-code");
|
||||
});
|
||||
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