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.ai45// Minimal runtime validation of OpenRouter wire shapes (no external dep).67export interface ORModelRaw {8 id: string;9 name?: string;10 description?: string;11 context_length?: number;12 created?: number;13 pricing?: Record<string, string | number | null>;14 architecture?: {15 modality?: string;16 input_modalities?: string[];17 output_modalities?: string[];18 tokenizer?: string;19 instruct_type?: string | null;20 };21 supported_parameters?: string[];22 top_provider?: { context_length?: number; max_completion_tokens?: number };23}2425export function parseModelsResponse(json: unknown): ORModelRaw[] {26 if (typeof json !== "object" || json === null) return [];27 const data = (json as { data?: unknown }).data;28 if (!Array.isArray(data)) return [];29 return data.filter(30 (m): m is ORModelRaw => typeof m === "object" && m !== null && typeof (m as ORModelRaw).id === "string"31 );32}3334/** One parsed SSE chunk from the OpenRouter chat stream. */35export interface ORStreamChunk {36 id?: string;37 choices?: Array<{38 delta?: {39 content?: string | null;40 reasoning?: string | null;41 tool_calls?: Array<{42 index?: number;43 id?: string;44 function?: { name?: string; arguments?: string };45 }>;46 };47 finish_reason?: string | null;48 }>;49 usage?: {50 prompt_tokens?: number;51 completion_tokens?: number;52 total_tokens?: number;53 cost?: number;54 completion_tokens_details?: { reasoning_tokens?: number };55 prompt_tokens_details?: { cached_tokens?: number };56 };57 error?: { message?: string; code?: number | string };58}5960export function parseStreamChunk(line: string): ORStreamChunk | null {61 try {62 const parsed = JSON.parse(line);63 if (typeof parsed !== "object" || parsed === null) return null;64 return parsed as ORStreamChunk;65 } catch {66 return null;67 }68}69