/** * Universal model naming template for the OmniRoute plugin. * * Naming pipeline: * [tag] * * [Free] - · ← free model * Auto: (p) ← auto combo * Combo: ← DB combo * - ← regular model */ // ── Constants ──────────────────────────────────────────────────────────── /** Separator between provider label and model display name. */ export const PROVIDER_TAG_SEPARATOR = " - "; /** Threshold beyond which providerDisplayName is abbreviated. */ const PROVIDER_LABEL_MAX_CHARS = 12; /** Aliases longer than this get title-case instead of UPPER. */ const ALIAS_UPPER_MAX_CHARS = 5; // ── Auto Combo Types ───────────────────────────────────────────────────── export type AutoVariant = | "coding" | "fast" | "cheap" | "offline" | "smart" | "lkgp"; export const AUTO_VARIANTS: AutoVariant[] = [ "coding", "fast", "cheap", "offline", "smart", "lkgp", ]; export const AUTO_VARIANT_DESCRIPTIONS: Record< AutoVariant | "default", string > = { default: "Best provider via scoring", coding: "Quality-first for code tasks", fast: "Latency-optimized routing", cheap: "Cost-optimized routing", offline: "Offline-friendly providers", smart: "Quality-first with exploration", lkgp: "Last-Known-Good-Provider routing", }; // ── Free Model Types ───────────────────────────────────────────────────── export type FreeModelFreeType = | "recurring-daily" | "recurring-monthly" | "recurring-credit" | "one-time-initial" | "keyless" | "discontinued"; // ── Provider Label ──────────────────────────────────────────────────────── /** * Title-case a long, lowercase-looking alias. * `antigravity` → `Antigravity` */ function titleCaseAlias(alias: string): string { if (alias.length === 0) return alias; return alias.charAt(0).toUpperCase() + alias.slice(1).toLowerCase(); } /** * Pick the short label for an upstream provider. * * Rules: * 1. Trim `providerDisplayName`. If ≤12 chars → use verbatim. * 2. Alias ≤5 chars → UPPER(alias). Alias >5 → titleCase. * 3. Neither → undefined. */ export function shortProviderLabel( enrichment: | { providerDisplayName?: string; providerAlias?: string } | undefined, ): string | undefined { if (!enrichment) return undefined; const raw = typeof enrichment.providerDisplayName === "string" ? enrichment.providerDisplayName.trim() : ""; if (raw.length > 0 && raw.length <= PROVIDER_LABEL_MAX_CHARS) return raw; const alias = typeof enrichment.providerAlias === "string" ? enrichment.providerAlias.trim() : ""; if (alias.length > 0) { return alias.length <= ALIAS_UPPER_MAX_CHARS ? alias.toUpperCase() : titleCaseAlias(alias); } return undefined; } // ── Free Label ──────────────────────────────────────────────────────────── /** * Normalise display name so free-tier models get a consistent `[Free] ` prefix. * * "GPT-4.1 (Free)" → "[Free] GPT-4.1" * "DeepSeek V4 Flash Free" → "[Free] DeepSeek V4 Flash" * "Claude Opus 4.7" → "Claude Opus 4.7" (unchanged) */ export function normaliseFreeLabel(name: string): string { const cleaned = name .replace(/\s*\(free\)\s*$/i, "") .replace(/[\s-]+free\s*$/i, "") .trim(); const wasFree = cleaned.length < name.trim().length; if (!wasFree) return name; return `[Free] ${cleaned}`; } // ── Free Budget Formatting ──────────────────────────────────────────────── function fmtTokens(n: number): string { if (n >= 1e9) return (n / 1e9).toFixed(1).replace(/\.0$/, "") + "B"; if (n >= 1e6) return (n / 1e6).toFixed(1).replace(/\.0$/, "") + "M"; if (n >= 1e3) return (n / 1e3).toFixed(1).replace(/\.0$/, "") + "K"; return String(n); } /** * Format a free model budget into a short human-readable suffix. * * recurring-daily → "25M tokens/day" * recurring-monthly → "25M tokens/month" * recurring-credit → "10M credits" * one-time-initial → "1M credits (one-time)" * keyless → "(keyless)" * discontinued → "(discontinued)" */ export function formatFreeBudget(params: { freeType: FreeModelFreeType; monthlyTokens?: number; creditTokens?: number; }): string { const { freeType, monthlyTokens = 0, creditTokens = 0 } = params; switch (freeType) { case "recurring-daily": return `${fmtTokens(monthlyTokens)} tokens/day`; case "recurring-monthly": return `${fmtTokens(monthlyTokens)} tokens/month`; case "recurring-credit": return `${fmtTokens(creditTokens)} credits`; case "one-time-initial": return `${fmtTokens(creditTokens)} credits (one-time)`; case "keyless": return "(keyless)"; case "discontinued": return "(discontinued)"; default: return ""; } } // ── Auto Combo Naming ───────────────────────────────────────────────────── /** * Format auto combo display name. * * "Auto: Coding (4p)" * "Auto: Default (6p)" * "Auto" (no candidate count when unknown) */ export function formatAutoComboName( variant: AutoVariant | undefined, candidateCount?: number, ): string { const label = variant ? variant.charAt(0).toUpperCase() + variant.slice(1) : "Default"; const count = typeof candidateCount === "number" && candidateCount > 0 ? ` (${candidateCount}p)` : ""; return `Auto: ${label}${count}`; } /** * Build the model ID for an auto combo entry. * "auto/coding", "auto/fast", "auto" (default). */ export function autoComboModelId(variant: AutoVariant | undefined): string { return variant ? `auto/${variant}` : "auto"; } // ── Universal Display Name Builder ──────────────────────────────────────── export interface ModelDisplayNameParams { /** Raw model ID (e.g. "cc/claude-sonnet-4-6"). */ rawId: string; /** Enrichment display name (e.g. "Claude Sonnet 4.6"). */ enrichmentName?: string; /** Provider tag enrichment. */ providerAlias?: string; /** Human-readable upstream provider label. */ providerDisplayName?: string; /** Whether model is free tier. */ isFree?: boolean; /** Free model budget info. */ freeType?: FreeModelFreeType; /** Monthly token budget (for recurring free models). */ monthlyTokens?: number; /** Credit token budget (for credit-based free models). */ creditTokens?: number; /** Whether this is a combo entry (skip provider tag). */ isCombo?: boolean; /** Whether this is an auto combo entry. */ isAutoCombo?: boolean; /** Auto combo variant. */ autoVariant?: AutoVariant; /** Auto combo candidate count. */ autoCandidateCount?: number; } /** * Build the final display name following the universal template. * * Priority: * 1. Auto combo → "Auto: (p)" * 2. DB combo → "Combo: " * 3. Free + enrichment + provider tag → "[Free]