mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 09:02:11 +03:00
chore(free-models): derive the free-tier regime sets from the regime table (#11537)
Validated in a combined 3-PR batch worktree off release/v3.8.51 tip. - Focused tests: free-regime-traits-derived-sets.test.ts, free-model-catalog.test.ts — pass; vitest autoCombo suite 108/111 (3 pre-existing timing-flaky failures unrelated to this diff, reproduced identically on the pure release/v3.8.51 tip in an isolated probe — auto/glm and Cerebras-rotation timeouts, none of the files this PR touches) - typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK Thanks for collapsing the four duplicated regime answers into one table-driven source — the `satisfies Record<...>` trick turning a missing answer into a compile error is a nice touch.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- **chore(free-models):** the free-tier regime table now declares which totals figure each regime feeds and whether it may take the no-auth shortcut, so the sets that used to repeat those answers by hand are derived from it — a new `freeType` no longer compiles until it has answered every question, instead of silently contributing to no total ([#11537](https://github.com/diegosouzapw/OmniRoute/pull/11537))
|
||||
@@ -67,33 +67,106 @@ export interface FreeModelTotals {
|
||||
headline: string;
|
||||
}
|
||||
|
||||
const RECURRING = new Set<FreeModelFreeType>(["recurring-daily", "recurring-monthly", "keyless"]);
|
||||
/**
|
||||
* Which figure a regime's allowance belongs to. Every regime lands in exactly
|
||||
* one bucket, so a regime added tomorrow cannot quietly contribute to nothing:
|
||||
* the compiler asks which figure it feeds.
|
||||
*/
|
||||
export type FreeRegimeTokenBucket =
|
||||
| "steady-monthly" // summed into the steady recurring headline
|
||||
| "recurring-credit" // credit that refills, reported next to the steady figure
|
||||
| "one-time-credit" // signup credit, first month only
|
||||
| "uncapped" // real access, no published cap — listed, never summed
|
||||
| "none"; // grants nothing, so it feeds no figure
|
||||
|
||||
interface FreeRegimeTraits {
|
||||
/** Can a request route here without paying? */
|
||||
grantsFreeAccess: boolean;
|
||||
/** Which totals figure this regime's allowance belongs to. */
|
||||
tokenBucket: FreeRegimeTokenBucket;
|
||||
/**
|
||||
* May a candidate of this regime skip the live allowance check when it is
|
||||
* reached through the synthetic no-auth path? True only where the catalogue
|
||||
* says no credential exists at all, so no request against it can be billed.
|
||||
*
|
||||
* This is NOT "this provider needs no API key". `providerCredentialRequirement.ts`
|
||||
* answers that other question and documents (`:1-16`) the cost of confusing the
|
||||
* two: blackbox, friendliai, iflytek and sparkdesk are catalogued `keyless` yet
|
||||
* answer 401 without a credential. Keep the two questions apart.
|
||||
*/
|
||||
allowsNoAuthShortcut: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* What each free-tier regime engages for "can I route here without paying?".
|
||||
* Exhaustive by construction: adding a member to `FreeModelFreeType` will not
|
||||
* compile until it is classified here. `discontinued` is the one regime a
|
||||
* provider uses to retire a free tier behind a paid key — it does NOT grant
|
||||
* free access, and the shared predicate (`isFreeModel`) must read this instead
|
||||
* of treating every catalogued id as free. `RECURRING` (above) answers a
|
||||
* different question (which regimes feed the headline token totals) and is left
|
||||
* independent on purpose — deriving it from this table would silently change
|
||||
* the homepage totals.
|
||||
* What each free-tier regime engages, for every question the codebase asks of a
|
||||
* regime. Exhaustive by construction: adding a member to `FreeModelFreeType`
|
||||
* will not compile until it is classified here, on every axis.
|
||||
*
|
||||
* `discontinued` is the regime a provider uses to retire a free tier behind a
|
||||
* paid key — it grants no access, so the shared predicate (`isFreeModel`) reads
|
||||
* this table instead of treating every catalogued id as free.
|
||||
*/
|
||||
const FREE_REGIME_TRAITS = {
|
||||
"recurring-daily": { grantsFreeAccess: true },
|
||||
"recurring-monthly": { grantsFreeAccess: true },
|
||||
"recurring-credit": { grantsFreeAccess: true },
|
||||
"recurring-uncapped": { grantsFreeAccess: true },
|
||||
"one-time-initial": { grantsFreeAccess: true },
|
||||
keyless: { grantsFreeAccess: true },
|
||||
discontinued: { grantsFreeAccess: false },
|
||||
} satisfies Record<FreeModelFreeType, { grantsFreeAccess: boolean }>;
|
||||
export const FREE_REGIME_TRAITS = {
|
||||
"recurring-daily": {
|
||||
grantsFreeAccess: true,
|
||||
tokenBucket: "steady-monthly",
|
||||
allowsNoAuthShortcut: false,
|
||||
},
|
||||
"recurring-monthly": {
|
||||
grantsFreeAccess: true,
|
||||
tokenBucket: "steady-monthly",
|
||||
allowsNoAuthShortcut: false,
|
||||
},
|
||||
"recurring-credit": {
|
||||
grantsFreeAccess: true,
|
||||
tokenBucket: "recurring-credit",
|
||||
allowsNoAuthShortcut: false,
|
||||
},
|
||||
"recurring-uncapped": {
|
||||
grantsFreeAccess: true,
|
||||
tokenBucket: "uncapped",
|
||||
allowsNoAuthShortcut: false,
|
||||
},
|
||||
"one-time-initial": {
|
||||
grantsFreeAccess: true,
|
||||
tokenBucket: "one-time-credit",
|
||||
allowsNoAuthShortcut: false,
|
||||
},
|
||||
keyless: {
|
||||
grantsFreeAccess: true,
|
||||
tokenBucket: "steady-monthly",
|
||||
allowsNoAuthShortcut: true,
|
||||
},
|
||||
discontinued: {
|
||||
grantsFreeAccess: false,
|
||||
tokenBucket: "none",
|
||||
allowsNoAuthShortcut: false,
|
||||
},
|
||||
} satisfies Record<FreeModelFreeType, FreeRegimeTraits>;
|
||||
|
||||
export function grantsFreeAccess(freeType: FreeModelFreeType): boolean {
|
||||
return FREE_REGIME_TRAITS[freeType].grantsFreeAccess;
|
||||
}
|
||||
|
||||
/** The regimes whose allowance belongs to `bucket`, derived from the table. */
|
||||
export function freeTypesInBucket(bucket: FreeRegimeTokenBucket): Set<FreeModelFreeType> {
|
||||
return new Set(
|
||||
(Object.keys(FREE_REGIME_TRAITS) as FreeModelFreeType[]).filter(
|
||||
(freeType) => FREE_REGIME_TRAITS[freeType].tokenBucket === bucket
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/** See `FreeRegimeTraits.allowsNoAuthShortcut` — routing question, not a credential one. */
|
||||
export function allowsNoAuthShortcut(freeType: FreeModelFreeType): boolean {
|
||||
return FREE_REGIME_TRAITS[freeType].allowsNoAuthShortcut;
|
||||
}
|
||||
|
||||
const STEADY_MONTHLY = freeTypesInBucket("steady-monthly");
|
||||
const RECURRING_CREDIT = freeTypesInBucket("recurring-credit");
|
||||
const ONE_TIME_CREDIT = freeTypesInBucket("one-time-credit");
|
||||
const UNCAPPED = freeTypesInBucket("uncapped");
|
||||
|
||||
/**
|
||||
* Deposit-unlock boosts: a one-time small top-up that permanently raises a
|
||||
* provider's recurring free quota. Kept OUT of the steady headline and surfaced
|
||||
@@ -139,30 +212,30 @@ export function computeFreeModelTotals(opts: { excludeTosAvoid?: boolean } = {})
|
||||
const steadyRecurringTokens = dedupedSum(
|
||||
models,
|
||||
(m) => m.monthlyTokens,
|
||||
(m) => RECURRING.has(m.freeType)
|
||||
(m) => STEADY_MONTHLY.has(m.freeType)
|
||||
);
|
||||
const recurringCredits = dedupedSum(
|
||||
models,
|
||||
(m) => m.creditTokens,
|
||||
(m) => m.freeType === "recurring-credit"
|
||||
(m) => RECURRING_CREDIT.has(m.freeType)
|
||||
);
|
||||
const oneTimeCredits = dedupedSum(
|
||||
models,
|
||||
(m) => m.creditTokens,
|
||||
(m) => m.freeType === "one-time-initial"
|
||||
(m) => ONE_TIME_CREDIT.has(m.freeType)
|
||||
);
|
||||
|
||||
const steadyWithRecurringCreditsTokens = steadyRecurringTokens + recurringCredits;
|
||||
const firstMonthRealisticTokens = steadyWithRecurringCreditsTokens + oneTimeCredits;
|
||||
|
||||
const poolCount = new Set(
|
||||
models.filter((m) => RECURRING.has(m.freeType) && m.poolKey).map((m) => m.poolKey)
|
||||
models.filter((m) => STEADY_MONTHLY.has(m.freeType) && m.poolKey).map((m) => m.poolKey)
|
||||
).size;
|
||||
|
||||
// Deposit-unlock boost: sum the FREE_TIER_BOOSTS whose pool still has a live
|
||||
// recurring model in the (optionally ToS-filtered) set.
|
||||
const livePools = new Set(
|
||||
models.filter((m) => RECURRING.has(m.freeType) && m.poolKey).map((m) => m.poolKey)
|
||||
models.filter((m) => STEADY_MONTHLY.has(m.freeType) && m.poolKey).map((m) => m.poolKey)
|
||||
);
|
||||
const boostMonthlyTokens = Object.entries(FREE_TIER_BOOSTS)
|
||||
.filter(([pool]) => livePools.has(pool))
|
||||
@@ -170,7 +243,7 @@ export function computeFreeModelTotals(opts: { excludeTosAvoid?: boolean } = {})
|
||||
|
||||
// Permanently-free-but-uncapped providers (real access, no published cap).
|
||||
const uncappedProviders = [
|
||||
...new Set(models.filter((m) => m.freeType === "recurring-uncapped").map((m) => m.provider)),
|
||||
...new Set(models.filter((m) => UNCAPPED.has(m.freeType)).map((m) => m.provider)),
|
||||
].sort();
|
||||
|
||||
return {
|
||||
|
||||
@@ -49,16 +49,13 @@
|
||||
* same set, by construction — no new enforcement point needed.
|
||||
*/
|
||||
import {
|
||||
allowsNoAuthShortcut,
|
||||
FREE_MODEL_BUDGETS,
|
||||
grantsFreeAccess,
|
||||
type FreeModelBudget,
|
||||
} from "@omniroute/open-sse/config/freeModelCatalog.ts";
|
||||
import { SYNTHETIC_NOAUTH_CONNECTION_ID } from "./resilienceCandidateFilter";
|
||||
|
||||
/** Types whose allowance needs no runtime verification: no credential exists
|
||||
* for the candidate at all, so no request against it can ever be billed. */
|
||||
const KEYLESS_FREE_TYPES = new Set<FreeModelBudget["freeType"]>(["keyless"]);
|
||||
|
||||
export type FreeAccessStatus = "SAFE" | "EXHAUSTED" | "UNKNOWN";
|
||||
|
||||
/** Live-checked allowance state for one (provider, connection) pair. Resolved
|
||||
@@ -174,7 +171,7 @@ export function evaluateCandidateConnections(
|
||||
if (!budgetEntry) return []; // not in the catalog at all → paid, or genuinely unknown
|
||||
|
||||
const isGenuineNoAuthCandidate = candidate.connectionId === SYNTHETIC_NOAUTH_CONNECTION_ID;
|
||||
if (KEYLESS_FREE_TYPES.has(budgetEntry.freeType)) {
|
||||
if (allowsNoAuthShortcut(budgetEntry.freeType)) {
|
||||
// The keyless shortcut is trustworthy ONLY when this specific candidate
|
||||
// instance actually has no credential behind it. A `keyless`-catalogued
|
||||
// model reached through a real DB connection (connectionId is a real id,
|
||||
|
||||
103
tests/unit/free-regime-traits-derived-sets.test.ts
Normal file
103
tests/unit/free-regime-traits-derived-sets.test.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import {
|
||||
FREE_REGIME_TRAITS,
|
||||
computeFreeModelTotals,
|
||||
freeTypesInBucket,
|
||||
grantsFreeAccess,
|
||||
type FreeModelFreeType,
|
||||
} from "../../open-sse/config/freeModelCatalog.ts";
|
||||
|
||||
const ALL_FREE_TYPES: FreeModelFreeType[] = [
|
||||
"recurring-daily",
|
||||
"recurring-monthly",
|
||||
"recurring-credit",
|
||||
"recurring-uncapped",
|
||||
"one-time-initial",
|
||||
"keyless",
|
||||
"discontinued",
|
||||
];
|
||||
|
||||
test("every free-tier regime is classified for every question the table answers", () => {
|
||||
assert.deepEqual(Object.keys(FREE_REGIME_TRAITS).sort(), [...ALL_FREE_TYPES].sort());
|
||||
for (const freeType of ALL_FREE_TYPES) {
|
||||
const traits = FREE_REGIME_TRAITS[freeType];
|
||||
assert.equal(typeof traits.grantsFreeAccess, "boolean", `${freeType}: grantsFreeAccess`);
|
||||
assert.equal(typeof traits.tokenBucket, "string", `${freeType}: tokenBucket`);
|
||||
assert.equal(
|
||||
typeof traits.allowsNoAuthShortcut,
|
||||
"boolean",
|
||||
`${freeType}: allowsNoAuthShortcut`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("each regime declares the one totals bucket it feeds", () => {
|
||||
const buckets = Object.fromEntries(
|
||||
ALL_FREE_TYPES.map((t) => [t, FREE_REGIME_TRAITS[t].tokenBucket])
|
||||
);
|
||||
assert.deepEqual(buckets, {
|
||||
"recurring-daily": "steady-monthly",
|
||||
"recurring-monthly": "steady-monthly",
|
||||
keyless: "steady-monthly",
|
||||
"recurring-credit": "recurring-credit",
|
||||
"one-time-initial": "one-time-credit",
|
||||
"recurring-uncapped": "uncapped",
|
||||
discontinued: "none",
|
||||
});
|
||||
});
|
||||
|
||||
test("the steady-headline set derived from the table is the one the totals were built on", () => {
|
||||
// Non-regression lock. Before this table existed, `RECURRING` was a hand-kept
|
||||
// literal; these three regimes are what the published homepage totals have
|
||||
// always summed. A fourth regime silently joining this bucket would inflate
|
||||
// the headline with no other test noticing.
|
||||
assert.deepEqual([...freeTypesInBucket("steady-monthly")].sort(), [
|
||||
"keyless",
|
||||
"recurring-daily",
|
||||
"recurring-monthly",
|
||||
]);
|
||||
});
|
||||
|
||||
test("a regime that grants no free access feeds no total", () => {
|
||||
for (const freeType of ALL_FREE_TYPES) {
|
||||
if (grantsFreeAccess(freeType)) continue;
|
||||
assert.equal(
|
||||
FREE_REGIME_TRAITS[freeType].tokenBucket,
|
||||
"none",
|
||||
`${freeType} does not grant free access, so it cannot feed a free-tier total`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("only the keyless regime takes the no-auth shortcut", () => {
|
||||
const shortcut = ALL_FREE_TYPES.filter((t) => FREE_REGIME_TRAITS[t].allowsNoAuthShortcut);
|
||||
assert.deepEqual(shortcut, ["keyless"]);
|
||||
});
|
||||
|
||||
test("the dashboard's own copy of the steady regimes still matches the derived set", () => {
|
||||
// FreeBudgetCard states the invariant it depends on: "Segments therefore sum
|
||||
// to `steadyRecurringTokens`". It cannot import the catalog — that would pull
|
||||
// every budget row into the client bundle — so the copy is guarded here
|
||||
// instead of being hoped for.
|
||||
const card = readFileSync(
|
||||
new URL(
|
||||
"../../src/app/(dashboard)/dashboard/usage/components/FreeBudgetCard.tsx",
|
||||
import.meta.url
|
||||
),
|
||||
"utf8"
|
||||
);
|
||||
const literal = card.match(/const RECURRING_TYPES = new Set\(\[([^\]]*)\]\)/);
|
||||
assert.ok(literal, "RECURRING_TYPES literal not found in FreeBudgetCard.tsx");
|
||||
const clientSide = [...literal[1].matchAll(/"([^"]+)"/g)].map((m) => m[1]).sort();
|
||||
assert.deepEqual(clientSide, [...freeTypesInBucket("steady-monthly")].sort());
|
||||
});
|
||||
|
||||
test("totals stay split across the buckets the table declares", () => {
|
||||
const totals = computeFreeModelTotals();
|
||||
assert.ok(totals.steadyRecurringTokens > 0);
|
||||
assert.ok(totals.steadyWithRecurringCreditsTokens >= totals.steadyRecurringTokens);
|
||||
assert.ok(totals.firstMonthRealisticTokens >= totals.steadyWithRecurringCreditsTokens);
|
||||
assert.ok(totals.uncappedProviders.length >= 3);
|
||||
});
|
||||
Reference in New Issue
Block a user