SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
3.3 KB · 62 lines typescript
Raw Blame History
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