TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1import type { PolyModel, ProviderId } from "@/lib/client/types";23/** A model retiring within 90 days (metadata.shutdownDate) is treated like a deprecation warning. */4export const RETIRE_SOON_MS = 90 * 86_400_000;56export interface DeprecationNotice {7 kind: "deprecated" | "retiring";8 shutdownDate: string | null;9}1011export function deprecationNotice(model: PolyModel | null | undefined, now: number): DeprecationNotice | null {12 if (!model) return null;13 const shutdown = typeof model.metadata?.shutdownDate === "string" ? (model.metadata.shutdownDate as string) : null;14 if (model.status === "deprecated") return { kind: "deprecated", shutdownDate: shutdown };15 if (shutdown) {16 const t = new Date(shutdown).getTime();17 if (!Number.isNaN(t) && t - now < RETIRE_SOON_MS) return { kind: "retiring", shutdownDate: shutdown };18 }19 return null;20}2122function weight(m: PolyModel): number {23 const w = Number((m.metadata as { sortWeight?: unknown } | undefined)?.sortWeight ?? 0);24 return Number.isFinite(w) ? w : 0;25}2627function releaseTime(m: PolyModel): number {28 const r = (m.metadata as { releaseDate?: unknown } | undefined)?.releaseDate;29 const t = typeof r === "string" ? new Date(r).getTime() : NaN;30 return Number.isNaN(t) ? 0 : t;31}3233/**34 * Newest active model from the same provider that the user can actually use (connected), preferring35 * the same family and models that cover the deprecated model's capabilities. Never returns the model itself.36 */37export function suggestReplacement(model: PolyModel, models: PolyModel[], connected?: Set<ProviderId>): PolyModel | null {38 const candidates = models.filter((m) => m.key !== model.key && m.provider === model.provider && m.status === "active" && (!connected || connected.has(m.provider)) && !(typeof m.metadata?.shutdownDate === "string"));39 if (!candidates.length) return null;40 const score = (m: PolyModel) => {41 let s = 0;42 if (model.family && m.family === model.family) s += 100;43 if (model.capabilities.vision && m.capabilities.vision) s += 5;44 if (model.capabilities.reasoning && m.capabilities.reasoning) s += 5;45 if (model.capabilities.tools && m.capabilities.tools) s += 3;46 if ((m.limits?.contextTokens ?? 0) >= (model.limits?.contextTokens ?? 0)) s += 2;47 return s;48 };49 return [...candidates].sort((a, b) => score(b) - score(a) || releaseTime(b) - releaseTime(a) || weight(b) - weight(a) || a.displayName.localeCompare(b.displayName))[0] ?? null;50}5152/** Connected model with the largest context window that is strictly larger than the current one. */53export function largerContextModel(current: PolyModel | null | undefined, models: PolyModel[], connected: Set<ProviderId>): PolyModel | null {54 const cur = current?.limits?.contextTokens ?? 0;55 const usable = models.filter((m) => connected.has(m.provider) && m.status !== "deprecated" && (m.limits?.contextTokens ?? 0) > cur && m.key !== current?.key);56 if (!usable.length) return null;57 // Prefer the same provider when it has a bigger model; otherwise the largest overall.58 const same = usable.filter((m) => m.provider === current?.provider).sort((a, b) => (b.limits?.contextTokens ?? 0) - (a.limits?.contextTokens ?? 0));59 if (same.length) return same[0];60 return usable.sort((a, b) => (b.limits?.contextTokens ?? 0) - (a.limits?.contextTokens ?? 0))[0];61}62