feat(providers): add 5 free-tier providers (ainative, aion, sealion, routeway, nara) (#7887)

Five OpenAI-compatible free-tier aggregators OmniRoute did not cover yet, added
as a registry entry plus a canonical provider each.

  ainative  api.ainative.studio/api/v1  — 84-model public catalog, passthrough
  aion      api.aionlabs.ai/v1          — 5 models, public catalog w/ pricing
  sealion   api.sea-lion.ai/v1          — AI Singapore, pinned models (10 RPM)
  routeway  api.routeway.ai/v1          — 236-model catalog; browser UA pinned
                                          because Cloudflare 1010s non-browser UAs
  nara      router.bynara.id/v1         — shared 5M/day pool, pinned free models

All five /models endpoints were probed live 2026-07-20 (200 for the public ones;
sealion/nara 401 without a key, as expected) and every pinned model id was
confirmed to exist upstream.

Free-catalog honesty: ainative ("~10M tok/mo claimed"), aion (20k tok/day),
sealion (10 RPM) and routeway (200 RPD) have no verifiable monthly TOKEN quota,
so they are recurring-uncapped — real access, never summed into the headline.
Only nara publishes a token figure (5M tokens/day shared = 150M/month), recorded
as one deduped pool. Net: 484 -> 514 models, 1.376B -> 1.526B tokens, the +150M
coming solely from nara.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-21 12:01:52 -03:00
committed by GitHub
parent f909b1d45e
commit 11b5ce1f0e
10 changed files with 294 additions and 0 deletions

View File

@@ -499,4 +499,34 @@ export const FREE_MODEL_BUDGETS: FreeModelBudget[] = [
{ provider: "aihorde", modelId: "aphrodite/TheDrummer/Cydonia-24B-v4.3", displayName: "Cydonia 24B (AI Horde)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "aihorde-anon", tos: "ok" },
{ provider: "aihorde", modelId: "aphrodite/TheDrummer/Skyfall-31B-v4.2", displayName: "Skyfall 31B (AI Horde)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "aihorde-anon", tos: "ok" },
{ provider: "aihorde", modelId: "google/gemma-4-31b", displayName: "Gemma 4 31B (AI Horde)", monthlyTokens: 0, creditTokens: 0, freeType: "keyless", poolKey: "aihorde-anon", tos: "ok" },
{ provider: "ainative", modelId: "qwen3-235b-cerebras", displayName: "Qwen3 235B (Cerebras)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "ainative-free", tos: "caution" },
{ provider: "ainative", modelId: "qwen3-32b", displayName: "Qwen3 32B", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "ainative-free", tos: "caution" },
{ provider: "ainative", modelId: "qwen3-14b", displayName: "Qwen3 14B", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "ainative-free", tos: "caution" },
{ provider: "ainative", modelId: "qwen3-8b", displayName: "Qwen3 8B", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "ainative-free", tos: "caution" },
{ provider: "ainative", modelId: "llama-4-maverick", displayName: "Llama 4 Maverick", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "ainative-free", tos: "caution" },
{ provider: "ainative", modelId: "llama3.1-8b-cerebras", displayName: "Llama 3.1 8B (Cerebras)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "ainative-free", tos: "caution" },
{ provider: "ainative", modelId: "deepseek-r1", displayName: "DeepSeek R1", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "ainative-free", tos: "caution" },
{ provider: "ainative", modelId: "nous-coder", displayName: "Nous Coder", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "ainative-free", tos: "caution" },
{ provider: "ainative", modelId: "gemini-flash", displayName: "Gemini Flash", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "ainative-free", tos: "caution" },
{ provider: "aion", modelId: "aion-labs/aion-3.0", displayName: "Aion 3.0", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "aion-free", tos: "ok" },
{ provider: "aion", modelId: "aion-labs/aion-3.0-mini", displayName: "Aion 3.0 Mini", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "aion-free", tos: "ok" },
{ provider: "aion", modelId: "aion-labs/aion-2.5", displayName: "Aion 2.5", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "aion-free", tos: "ok" },
{ provider: "aion", modelId: "aion-labs/aion-2.0", displayName: "Aion 2.0", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "aion-free", tos: "ok" },
{ provider: "aion", modelId: "aion-labs/aion-rp-llama-3.1-8b", displayName: "Aion RP Llama 3.1 8B", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "aion-free", tos: "ok" },
{ provider: "sealion", modelId: "aisingapore/Llama-SEA-LION-v3.5-70B-R", displayName: "Llama SEA-LION v3.5 70B R", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "sealion-free", tos: "ok" },
{ provider: "sealion", modelId: "aisingapore/Llama-SEA-LION-v3-70B-IT", displayName: "Llama SEA-LION v3 70B IT", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "sealion-free", tos: "ok" },
{ provider: "sealion", modelId: "aisingapore/Gemma-SEA-LION-v4-27B-IT", displayName: "Gemma SEA-LION v4 27B IT", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "sealion-free", tos: "ok" },
{ provider: "sealion", modelId: "aisingapore/Qwen-SEA-LION-v4.5-27B-IT", displayName: "Qwen SEA-LION v4.5 27B IT", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "sealion-free", tos: "ok" },
{ provider: "sealion", modelId: "aisingapore/Qwen-SEA-LION-v4-32B-IT", displayName: "Qwen SEA-LION v4 32B IT", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "sealion-free", tos: "ok" },
{ provider: "routeway", modelId: "llama-3.3-70b-instruct:free", displayName: "Llama 3.3 70B Instruct (free)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "routeway-free", tos: "caution" },
{ provider: "routeway", modelId: "nemotron-3-nano-30b-a3b:free", displayName: "Nemotron 3 Nano 30B (free)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "routeway-free", tos: "caution" },
{ provider: "routeway", modelId: "nemotron-nano-9b-v2:free", displayName: "Nemotron Nano 9B v2 (free)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "routeway-free", tos: "caution" },
{ provider: "routeway", modelId: "step-3.7-flash:free", displayName: "Step 3.7 Flash (free)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "routeway-free", tos: "caution" },
{ provider: "routeway", modelId: "step-3.5-flash:free", displayName: "Step 3.5 Flash (free)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "routeway-free", tos: "caution" },
{ provider: "routeway", modelId: "laguna-m.1:free", displayName: "Laguna M.1 (free)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "routeway-free", tos: "caution" },
{ provider: "routeway", modelId: "laguna-xs.2:free", displayName: "Laguna XS.2 (free)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "routeway-free", tos: "caution" },
{ provider: "routeway", modelId: "llama-3.2-3b-instruct:free", displayName: "Llama 3.2 3B Instruct (free)", monthlyTokens: 0, creditTokens: 0, freeType: "recurring-uncapped", poolKey: "routeway-free", tos: "caution" },
{ provider: "nara", modelId: "tencent-hy3", displayName: "Tencent Hy3", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "nara-free", tos: "caution" },
{ provider: "nara", modelId: "mistral-large", displayName: "Mistral Large", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "nara-free", tos: "caution" },
{ provider: "nara", modelId: "mistral-medium-3-5", displayName: "Mistral Medium 3.5", monthlyTokens: 150000000, creditTokens: 0, freeType: "recurring-daily", poolKey: "nara-free", tos: "caution" },
];

View File

@@ -127,6 +127,8 @@ import { deepinfraProvider } from "./registry/deepinfra/index.ts";
import { agyProvider } from "./registry/agy/index.ts";
import { agnesProvider } from "./registry/agnes/index.ts";
import { aihordeProvider } from "./registry/aihorde/index.ts";
import { ainativeProvider } from "./registry/ainative/index.ts";
import { aionProvider } from "./registry/aion/index.ts";
import { udioProvider } from "./registry/udio/index.ts";
import { longcatProvider } from "./registry/longcat/index.ts";
import { vertex_partnerProvider } from "./registry/vertex/partner/index.ts";
@@ -199,7 +201,10 @@ import { sumopodProvider } from "./registry/sumopod/index.ts";
import { x5labProvider } from "./registry/x5lab/index.ts";
import { kenariProvider } from "./registry/kenari/index.ts";
import { navyProvider } from "./registry/navy/index.ts";
import { naraProvider } from "./registry/nara/index.ts";
import { requestyProvider } from "./registry/requesty/index.ts";
import { sealionProvider } from "./registry/sealion/index.ts";
import { routewayProvider } from "./registry/routeway/index.ts";
import { digitaloceanProvider } from "./registry/digitalocean/index.ts";
import { hcnsecProvider } from "./registry/hcnsec/index.ts";
@@ -329,6 +334,8 @@ export const REGISTRY: Record<string, RegistryEntry> = {
agy: agyProvider,
agnes: agnesProvider,
aihorde: aihordeProvider,
ainative: ainativeProvider,
aion: aionProvider,
udio: udioProvider,
longcat: longcatProvider,
"vertex-partner": vertex_partnerProvider,
@@ -403,7 +410,10 @@ export const REGISTRY: Record<string, RegistryEntry> = {
x5lab: x5labProvider,
kenari: kenariProvider,
navy: navyProvider,
nara: naraProvider,
requesty: requestyProvider,
sealion: sealionProvider,
routeway: routewayProvider,
digitalocean: digitaloceanProvider,
hcnsec: hcnsecProvider,
};

View File

@@ -0,0 +1,27 @@
import type { RegistryEntry } from "../../shared.ts";
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
/**
* AINative Studio — OpenAI-compatible aggregator (api.ainative.studio).
*
* The /models catalog is public (84 models,
* verified 2026-07-20) so it is discovered via passthrough; the entries below
* are the free-tier ones and act as a fallback when discovery fails.
*/
export const ainativeProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
id: "ainative",
baseUrl: "https://api.ainative.studio/api/v1/chat/completions",
modelsUrl: "https://api.ainative.studio/api/v1/models",
passthroughModels: true,
models: [
{ id: "qwen3-235b-cerebras", name: "Qwen3 235B (Cerebras)", contextLength: 131072, toolCalling: true },
{ id: "qwen3-32b", name: "Qwen3 32B", contextLength: 131072, toolCalling: true },
{ id: "qwen3-14b", name: "Qwen3 14B", contextLength: 131072, toolCalling: true },
{ id: "qwen3-8b", name: "Qwen3 8B", contextLength: 131072, toolCalling: true },
{ id: "llama-4-maverick", name: "Llama 4 Maverick", contextLength: 131072, toolCalling: true },
{ id: "llama3.1-8b-cerebras", name: "Llama 3.1 8B (Cerebras)", contextLength: 131072, toolCalling: true },
{ id: "deepseek-r1", name: "DeepSeek R1", contextLength: 65536, supportsReasoning: true },
{ id: "nous-coder", name: "Nous Coder", contextLength: 131072, toolCalling: true },
{ id: "gemini-flash", name: "Gemini Flash", contextLength: 131072, toolCalling: true },
],
});

View File

@@ -0,0 +1,23 @@
import type { RegistryEntry } from "../../shared.ts";
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
/**
* Aion Labs — OpenAI-compatible aggregator (api.aionlabs.ai).
*
* Free key from aionlabs.ai (no card). The
* public /models catalog (5 models, verified 2026-07-20) carries context and
* pricing; free tier is 20k tokens/day.
*/
export const aionProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
id: "aion",
baseUrl: "https://api.aionlabs.ai/v1/chat/completions",
modelsUrl: "https://api.aionlabs.ai/v1/models",
passthroughModels: true,
models: [
{ id: "aion-labs/aion-3.0", name: "Aion 3.0", contextLength: 131072 },
{ id: "aion-labs/aion-3.0-mini", name: "Aion 3.0 Mini", contextLength: 131072 },
{ id: "aion-labs/aion-2.5", name: "Aion 2.5", contextLength: 131072 },
{ id: "aion-labs/aion-2.0", name: "Aion 2.0", contextLength: 131072 },
{ id: "aion-labs/aion-rp-llama-3.1-8b", name: "Aion RP Llama 3.1 8B", contextLength: 32768 },
],
});

View File

@@ -0,0 +1,19 @@
import type { RegistryEntry } from "../../shared.ts";
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
/**
* NaraRouter — OpenAI-compatible aggregator (router.bynara.id).
*
* Free key issued via their Telegram channel. The free tier is a shared
* 5M-tokens/day pool; many models are gated behind
* credit/plan, so only the free-tier models are pinned.
*/
export const naraProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
id: "nara",
baseUrl: "https://router.bynara.id/v1/chat/completions",
models: [
{ id: "tencent-hy3", name: "Tencent Hy3", contextLength: 1000000 },
{ id: "mistral-large", name: "Mistral Large", contextLength: 252000, toolCalling: true },
{ id: "mistral-medium-3-5", name: "Mistral Medium 3.5", contextLength: 256000, toolCalling: true, supportsVision: true },
],
});

View File

@@ -0,0 +1,28 @@
import type { RegistryEntry } from "../../shared.ts";
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
/**
* Routeway — OpenAI-compatible aggregator (api.routeway.ai).
*
* Cloudflare fronts the API and rejects
* non-browser User-Agents with error 1010, so a browser-style UA is pinned
* (verified needed 2026-07-20). The public /models catalog has 236 models with
* an `available`/pricing shape; free models carry the `:free` suffix.
*/
export const routewayProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
id: "routeway",
baseUrl: "https://api.routeway.ai/v1/chat/completions",
modelsUrl: "https://api.routeway.ai/v1/models",
passthroughModels: true,
extraHeaders: { "User-Agent": "Mozilla/5.0 OmniRoute/1.0" },
models: [
{ id: "llama-3.3-70b-instruct:free", name: "Llama 3.3 70B Instruct (free)", contextLength: 131072, toolCalling: true },
{ id: "nemotron-3-nano-30b-a3b:free", name: "Nemotron 3 Nano 30B (free)", contextLength: 256000, toolCalling: true },
{ id: "nemotron-nano-9b-v2:free", name: "Nemotron Nano 9B v2 (free)", contextLength: 128000, toolCalling: true },
{ id: "step-3.7-flash:free", name: "Step 3.7 Flash (free)", contextLength: 256000, toolCalling: true, supportsVision: true },
{ id: "step-3.5-flash:free", name: "Step 3.5 Flash (free)", contextLength: 65536, toolCalling: true },
{ id: "laguna-m.1:free", name: "Laguna M.1 (free)", contextLength: 131072, toolCalling: true },
{ id: "laguna-xs.2:free", name: "Laguna XS.2 (free)", contextLength: 131072, toolCalling: true },
{ id: "llama-3.2-3b-instruct:free", name: "Llama 3.2 3B Instruct (free)", contextLength: 16000, toolCalling: true },
],
});

View File

@@ -0,0 +1,21 @@
import type { RegistryEntry } from "../../shared.ts";
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
/**
* SEA-LION (AI Singapore) — OpenAI-compatible first-party API (api.sea-lion.ai).
*
* Free key from sea-lion.ai (Google sign-in, no
* card); recurring free tier at 10 RPM. /models requires the key, so the free
* models are pinned rather than discovered.
*/
export const sealionProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
id: "sealion",
baseUrl: "https://api.sea-lion.ai/v1/chat/completions",
models: [
{ id: "aisingapore/Llama-SEA-LION-v3.5-70B-R", name: "Llama SEA-LION v3.5 70B R", contextLength: 131072 },
{ id: "aisingapore/Llama-SEA-LION-v3-70B-IT", name: "Llama SEA-LION v3 70B IT", contextLength: 131072 },
{ id: "aisingapore/Gemma-SEA-LION-v4-27B-IT", name: "Gemma SEA-LION v4 27B IT", contextLength: 131072 },
{ id: "aisingapore/Qwen-SEA-LION-v4.5-27B-IT", name: "Qwen SEA-LION v4.5 27B IT", contextLength: 32768 },
{ id: "aisingapore/Qwen-SEA-LION-v4-32B-IT", name: "Qwen SEA-LION v4 32B IT", contextLength: 32768 },
],
});

View File

@@ -732,4 +732,64 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
"OpenAI-compatible endpoint at https://api.navy/v1 with a live /v1/models catalog that exposes " +
"per-model token_multiplier and premium flags. Upstream requires an explicit User-Agent header.",
},
ainative: {
id: "ainative",
alias: "ainative",
name: "AINative Studio",
icon: "hub",
color: "#7C3AED",
textIcon: "AN",
passthroughModels: true,
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.",
apiHint:
"OpenAI-compatible endpoint at https://api.ainative.studio/api/v1 with a public /models catalog (84 models). OmniRoute lists models via passthrough.",
},
aion: {
id: "aion",
alias: "aion",
name: "Aion Labs",
icon: "hub",
color: "#0EA5E9",
textIcon: "AI",
passthroughModels: true,
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.",
apiHint:
"OpenAI-compatible endpoint at https://api.aionlabs.ai/v1 with a public /models catalog carrying context and pricing.",
},
routeway: {
id: "routeway",
alias: "routeway",
name: "Routeway",
icon: "hub",
color: "#F59E0B",
textIcon: "RW",
passthroughModels: true,
website: "https://routeway.ai",
hasFree: true,
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.",
},
nara: {
id: "nara",
alias: "nara",
name: "NaraRouter",
icon: "hub",
color: "#EC4899",
textIcon: "NA",
passthroughModels: true,
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.",
apiHint:
"OpenAI-compatible endpoint at https://router.bynara.id/v1. Free-tier models are pinned; others need credit.",
},
};

View File

@@ -388,4 +388,16 @@ export const APIKEY_PROVIDERS_REGIONAL = {
freeNote: "Permanently free API - no credit card required.",
authHint: "Get API key at agnes-ai.com",
},
sealion: {
id: "sealion",
alias: "sealion",
name: "SEA-LION",
icon: "public",
color: "#0D9488",
textIcon: "SL",
website: "https://sea-lion.ai",
hasFree: true,
freeNote: "Permanently free at 10 RPM — AI Singapore's Southeast-Asian models (Llama/Qwen/Gemma SEA-LION).",
authHint: "Sign in at sea-lion.ai with Google (no card, no region wall), create an API key, then paste it here.",
},
};

View File

@@ -0,0 +1,64 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
FREE_MODEL_BUDGETS,
computeFreeModelTotals,
} from "@omniroute/open-sse/config/freeModelCatalog.ts";
import { REGISTRY } from "@omniroute/open-sse/config/providerRegistry.ts";
import { AI_PROVIDERS } from "@/shared/constants/providers.ts";
/**
* Five OpenAI-compatible free-tier aggregators (ainative, aion, sealion,
* routeway, nara). The tests pin the decisions that are easy to break silently.
*/
const NEW_PROVIDERS = ["ainative", "aion", "sealion", "routeway", "nara"] as const;
const byProvider = (id: string) => FREE_MODEL_BUDGETS.filter((m) => m.provider === id);
test("each new provider is both routable and canonically registered", () => {
for (const id of NEW_PROVIDERS) {
assert.ok(REGISTRY[id], `${id} must exist in the execution REGISTRY`);
assert.ok(AI_PROVIDERS[id], `${id} must exist as a canonical provider`);
assert.equal(REGISTRY[id].format, "openai");
assert.ok(byProvider(id).length > 0, `${id} must have free-catalog entries`);
}
});
test("routeway pins a browser User-Agent (Cloudflare rejects non-browser UAs with 1010)", () => {
const ua = REGISTRY.routeway.extraHeaders?.["User-Agent"] ?? "";
assert.match(ua, /Mozilla/, "routeway must send a browser-style User-Agent");
});
test("providers with no published token quota never inflate the headline", () => {
// ainative ("claimed"), aion (per-day request cap), sealion (RPM only),
// routeway (RPD): real free access, no verifiable monthly token figure.
for (const id of ["ainative", "aion", "sealion", "routeway"]) {
const rows = byProvider(id);
assert.ok(
rows.every((m) => m.monthlyTokens === 0 && m.creditTokens === 0),
`${id} has no published token quota — it must not carry invented numbers`
);
assert.ok(rows.every((m) => m.freeType === "recurring-uncapped"));
}
const totals = computeFreeModelTotals();
for (const id of ["ainative", "aion", "sealion", "routeway"]) {
assert.ok(totals.uncappedProviders.includes(id), `${id} must surface as uncapped`);
}
});
test("nara is a single shared 5M/day pool, counted once", () => {
const rows = byProvider("nara");
assert.ok(rows.length >= 1);
// 5M tokens/day shared across all models => 150M/month, deduped by poolKey.
assert.ok(rows.every((m) => m.poolKey === "nara-free"));
assert.ok(rows.every((m) => m.monthlyTokens === 150_000_000));
assert.ok(rows.every((m) => m.freeType === "recurring-daily"));
});
test("every catalog provider id still resolves to a canonical provider", () => {
const unknown = [...new Set(FREE_MODEL_BUDGETS.map((m) => m.provider))].filter(
(id) => !AI_PROVIDERS[id]
);
assert.deepEqual(unknown, [], `dangling catalog providers: ${unknown.join(", ")}`);
});