SPB Git
4.9 KB · 71 lines typescript
1import "server-only";2import { sql } from "../db";3import type { AIModel, Provider } from "./types";45type Row = {6  id: string; provider: Provider; display_name: string; kind: string; api: AIModel["api"]; status: AIModel["status"]; available: boolean; tested: boolean;7  unavailable_reason: string | null; capabilities: AIModel["capabilities"]; context_window: number | null; max_output_tokens: number | null;8  pricing: AIModel["pricing"]; benchmark: AIModel["benchmark"]; hidden: boolean; favorite: boolean; last_verified_at: Date | null;9};1011function rowToModel(r: Row): AIModel {12  return {13    id: r.id, provider: r.provider, displayName: r.display_name, kind: r.kind, api: r.api, status: r.status, available: r.available, tested: r.tested,14    unavailableReason: r.unavailable_reason, capabilities: r.capabilities ?? {}, contextWindow: r.context_window, maxOutputTokens: r.max_output_tokens,15    pricing: r.pricing, benchmark: r.benchmark, hidden: r.hidden, favorite: r.favorite, lastVerifiedAt: r.last_verified_at?.toISOString() ?? null,16  };17}1819export async function listModels(opts: { includeHidden?: boolean; onlyChat?: boolean } = {}): Promise<AIModel[]> {20  const rows = await sql<Row[]>`21    SELECT * FROM models22    WHERE (${opts.includeHidden ?? false} OR hidden = false)23      AND (${!(opts.onlyChat ?? false)} OR kind IN ('chat','chat-pro','search'))24    ORDER BY provider, favorite DESC, (benchmark->>'qualityScore')::int DESC NULLS LAST, sort_order, id`;25  return rows.map(rowToModel);26}2728export async function getModel(id: string): Promise<AIModel | null> {29  const rows = await sql<Row[]>`SELECT * FROM models WHERE id = ${id}`;30  return rows[0] ? rowToModel(rows[0]) : null;31}3233export async function requireChatModel(id: string): Promise<AIModel> {34  const m = await getModel(id);35  if (!m) throw Object.assign(new Error(`Unknown model ${id}`), { status: 400 });36  if (!m.available || !["chat", "chat-pro", "search"].includes(m.kind)) throw Object.assign(new Error(`Model ${id} is not available for chat (${m.unavailableReason ?? m.status})`), { status: 400 });37  return m;38}3940export async function updateModelFlags(id: string, flags: { hidden?: boolean; favorite?: boolean }) {41  await sql`UPDATE models SET hidden = COALESCE(${flags.hidden ?? null}, hidden), favorite = COALESCE(${flags.favorite ?? null}, favorite), updated_at = now() WHERE id = ${id}`;42}4344export interface RegistryEntryInput extends Omit<AIModel, "hidden" | "favorite"> { hidden?: boolean }4546/** Upsert registry entries (from the benchmark capability matrix or a live refresh). Preserves owner flags. */47export async function upsertModels(entries: RegistryEntryInput[]) {48  for (const e of entries) {49    await sql`INSERT INTO models (id, provider, display_name, kind, api, status, available, tested, unavailable_reason, capabilities, context_window, max_output_tokens, pricing, benchmark, hidden, last_verified_at, updated_at)50      VALUES (${e.id}, ${e.provider}, ${e.displayName}, ${e.kind}, ${e.api ?? null}, ${e.status}, ${e.available}, ${e.tested}, ${e.unavailableReason ?? null}, ${sql.json(e.capabilities as never)}, ${e.contextWindow ?? null}, ${e.maxOutputTokens ?? null}, ${e.pricing ? sql.json(e.pricing as never) : null}, ${e.benchmark ? sql.json(e.benchmark as never) : null}, ${e.hidden ?? !e.available}, ${e.lastVerifiedAt ? new Date(e.lastVerifiedAt) : null}, now())51      ON CONFLICT (id) DO UPDATE SET provider = EXCLUDED.provider, display_name = EXCLUDED.display_name, kind = EXCLUDED.kind, api = COALESCE(EXCLUDED.api, models.api),52        status = EXCLUDED.status, available = EXCLUDED.available, tested = EXCLUDED.tested OR models.tested, unavailable_reason = EXCLUDED.unavailable_reason,53        capabilities = EXCLUDED.capabilities, context_window = COALESCE(EXCLUDED.context_window, models.context_window), max_output_tokens = COALESCE(EXCLUDED.max_output_tokens, models.max_output_tokens),54        pricing = COALESCE(EXCLUDED.pricing, models.pricing), benchmark = COALESCE(EXCLUDED.benchmark, models.benchmark),55        hidden = CASE WHEN EXCLUDED.available = false THEN true ELSE models.hidden END,56        last_verified_at = COALESCE(EXCLUDED.last_verified_at, models.last_verified_at), updated_at = now()`;57  }58}5960/** Mark models that disappeared from the provider listing as unavailable. */61export async function markMissing(provider: Provider, presentIds: string[]) {62  await sql`UPDATE models SET available = false, status = 'unavailable', unavailable_reason = 'No longer returned by the provider model listing', hidden = true, updated_at = now()63            WHERE provider = ${provider} AND id <> ALL(${presentIds}) AND available = true`;64}6566export function displayNameFor(id: string, provider: Provider, given?: string): string {67  if (given) return given;68  if (provider === "openai") return id.replace(/^gpt-/, "GPT-").replace(/^o(\d)/, "o$1").replace(/-(\d{4}-\d{2}-\d{2})$/, " ($1)").replace(/-/g, " ").replace(/\bmini\b/, "mini").replace(/\bpro\b/, "Pro");69  return id;70}71
Files · main
Menu

Theme