spb/chat-spboucher Public
Private universal chat interface over the OpenRouter ecosystem — 400+ models, branching, streaming, usage tracking. Next.js 16 + SQLite, PWA, deployed on m4m64a at chat.spboucher.ai
TypeScript 78.8%
CSS 15.1%
JavaScript 4.9%
Shell 1.2%
1// Author: Simon-Pierre Boucher2// Contact: contact@spboucher.ai3// Project: chat.spboucher.ai45import { orGet } from "./client";6import { parseModelsResponse, type ORModelRaw } from "./schemas";7import { deriveCapabilities } from "./capabilities";8import { derivePricing } from "./pricing";9import type { ModelDefinition } from "./types";1011/** Provider is the human-readable prefix of the display name ("Anthropic: Claude ..."), never parsed from the ID for behavior. */12function deriveProvider(raw: ORModelRaw): string | undefined {13 const name = raw.name ?? "";14 const colon = name.indexOf(":");15 if (colon > 0) return name.slice(0, colon).trim();16 const slash = raw.id.indexOf("/");17 if (slash > 0) return raw.id.slice(0, slash);18 return undefined;19}2021function displayName(raw: ORModelRaw): string {22 const name = raw.name ?? raw.id;23 const colon = name.indexOf(":");24 return colon > 0 ? name.slice(colon + 1).trim() : name;25}2627export function normalizeModel(raw: ORModelRaw): ModelDefinition {28 return {29 id: raw.id,30 name: displayName(raw),31 provider: deriveProvider(raw),32 description: raw.description,33 contextLength: raw.context_length ?? raw.top_provider?.context_length,34 pricing: derivePricing(raw),35 capabilities: deriveCapabilities(raw),36 metadata: {37 architecture: raw.architecture?.modality,38 tokenizer: raw.architecture?.tokenizer,39 createdAt: raw.created,40 raw,41 },42 };43}4445/** Fetch and normalize the full OpenRouter model catalog. */46export async function fetchModels(signal?: AbortSignal): Promise<ModelDefinition[]> {47 const json = await orGet("/models", signal);48 return parseModelsResponse(json).map(normalizeModel);49}50