125 lines
4.1 KiB
TypeScript
125 lines
4.1 KiB
TypeScript
/**
|
|
* Tier configuration schema with Zod validation and sensible defaults.
|
|
*
|
|
* `freeProviders` is the union of two sources:
|
|
* 1. Legacy explicit list (`LEGACY_FREE_PROVIDERS`) — keeps historical behavior
|
|
* for providers that aren't noAuth (e.g., groq, cerebras) so the old test
|
|
* suite and existing DB overrides keep working.
|
|
* 2. Auto-derived from `NOAUTH_PROVIDERS` where `noAuth === true` AND
|
|
* `serviceKinds` is empty or includes "llm" (excludes free audio/video
|
|
* gateways like veo-free that would never feed the chat tier resolver).
|
|
* See `deriveNoAuthFreeProviders()`.
|
|
*
|
|
* The two are merged into `DEFAULT_TIER_CONFIG.freeProviders` at module load.
|
|
*/
|
|
|
|
import { z } from "zod";
|
|
import type { TierConfig, ProviderTierOverride, ModelTierOverride } from "./tierTypes";
|
|
import { PROVIDER_TIER } from "./tierTypes";
|
|
import { NOAUTH_PROVIDERS } from "@/shared/constants/providers";
|
|
|
|
export const providerTierOverrideSchema = z.object({
|
|
provider: z.string().min(1),
|
|
tier: z.enum(["free", "cheap", "premium"]),
|
|
});
|
|
|
|
export const modelTierOverrideSchema = z.object({
|
|
provider: z.string().min(1),
|
|
modelPattern: z.string().min(1),
|
|
tier: z.enum(["free", "cheap", "premium"]),
|
|
});
|
|
|
|
export const tierConfigSchema = z.object({
|
|
version: z.string().default("1.0.0"),
|
|
defaults: z.object({
|
|
freeThreshold: z.number().min(0).default(0),
|
|
cheapThreshold: z.number().min(0).default(1.0),
|
|
}),
|
|
providerOverrides: z.array(providerTierOverrideSchema).default([]),
|
|
modelOverrides: z.array(modelTierOverrideSchema).default([]),
|
|
freeProviders: z.array(z.string()).default([]),
|
|
});
|
|
|
|
/**
|
|
* Legacy explicit free providers — kept for back-compat with existing DB rows
|
|
* and test fixtures. New free providers should be added by registering them
|
|
* in `NOAUTH_PROVIDERS` with `noAuth: true` instead of editing this list.
|
|
*/
|
|
export const LEGACY_FREE_PROVIDERS: readonly string[] = [
|
|
"kiro",
|
|
"qoder",
|
|
"pollinations",
|
|
"longcat",
|
|
"cloudflare-ai",
|
|
"qwen",
|
|
"nvidia-nim",
|
|
"cerebras",
|
|
"groq",
|
|
];
|
|
|
|
/**
|
|
* Derive free provider IDs from `NOAUTH_PROVIDERS`. Only chat-tier noAuth
|
|
* providers count (serviceKinds === undefined || includes "llm"), so the
|
|
* free video / audio / image noAuth entries (e.g. veo-free, muse-spark) don't
|
|
* accidentally mark themselves as chat-free.
|
|
*
|
|
* Wrapped in try/catch to tolerate the import failing in non-Node runtimes
|
|
* (vitest worker setup, MCP server entry) — the legacy list still applies.
|
|
*/
|
|
export function deriveNoAuthFreeProviders(): string[] {
|
|
try {
|
|
const ids: string[] = [];
|
|
for (const def of Object.values(NOAUTH_PROVIDERS)) {
|
|
if (!def || typeof def !== "object") continue;
|
|
if (def.noAuth !== true) continue;
|
|
const kinds = (def as { serviceKinds?: unknown }).serviceKinds;
|
|
const isLlm =
|
|
!Array.isArray(kinds) || kinds.length === 0 || (kinds as unknown[]).includes("llm");
|
|
if (!isLlm) continue;
|
|
if (typeof def.id === "string" && def.id.length > 0) {
|
|
ids.push(def.id);
|
|
}
|
|
}
|
|
return ids;
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
const NOAUTH_FREE_PROVIDERS = deriveNoAuthFreeProviders();
|
|
|
|
export const DEFAULT_TIER_CONFIG: TierConfig = {
|
|
version: "1.0.0",
|
|
defaults: {
|
|
freeThreshold: 0,
|
|
cheapThreshold: 1.0,
|
|
},
|
|
providerOverrides: [],
|
|
modelOverrides: [],
|
|
freeProviders: [...new Set([...LEGACY_FREE_PROVIDERS, ...NOAUTH_FREE_PROVIDERS])],
|
|
};
|
|
|
|
export function validateTierConfig(raw: unknown): TierConfig {
|
|
return tierConfigSchema.parse(raw);
|
|
}
|
|
|
|
export function mergeTierConfig(userConfig?: Partial<TierConfig>): TierConfig {
|
|
if (!userConfig) return DEFAULT_TIER_CONFIG;
|
|
return {
|
|
...DEFAULT_TIER_CONFIG,
|
|
...userConfig,
|
|
defaults: {
|
|
...DEFAULT_TIER_CONFIG.defaults,
|
|
...userConfig.defaults,
|
|
},
|
|
providerOverrides: [
|
|
...DEFAULT_TIER_CONFIG.providerOverrides,
|
|
...(userConfig.providerOverrides || []),
|
|
],
|
|
modelOverrides: [...DEFAULT_TIER_CONFIG.modelOverrides, ...(userConfig.modelOverrides || [])],
|
|
freeProviders: [
|
|
...new Set([...DEFAULT_TIER_CONFIG.freeProviders, ...(userConfig.freeProviders || [])]),
|
|
],
|
|
};
|
|
}
|