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/** Normalized model definition, decoupled from the OpenRouter wire format. */6export interface ModelDefinition {7 id: string; // exact OpenRouter model ID — opaque, sent back verbatim8 name: string;9 provider?: string;10 description?: string;11 contextLength?: number;12 pricing?: {13 prompt?: number; // USD per token14 completion?: number;15 image?: number;16 request?: number;17 };18 capabilities: {19 text: boolean;20 vision: boolean;21 reasoning: boolean;22 tools: boolean;23 structuredOutput: boolean;24 };25 metadata: {26 architecture?: string;27 tokenizer?: string;28 createdAt?: number;29 raw?: unknown;30 };31}3233/** Normalized application stream events — the browser never sees raw OpenRouter frames. */34export type ChatStreamEvent =35 | { type: "generation.start"; generationId: string; model: string }36 | { type: "content.delta"; text: string }37 | { type: "reasoning.delta"; text: string }38 | { type: "tool.start"; toolCallId: string; name: string }39 | { type: "tool.delta"; toolCallId: string; argumentsDelta: string }40 | {41 type: "usage";42 promptTokens?: number;43 completionTokens?: number;44 reasoningTokens?: number;45 cachedTokens?: number;46 totalTokens?: number;47 cost?: number;48 }49 | { type: "generation.end" }50 | { type: "generation.error"; message: string; retryable: boolean };5152export interface ChatMessageInput {53 role: "system" | "user" | "assistant";54 content: string;55}5657export interface GenerationRequest {58 model: string;59 messages: ChatMessageInput[];60 signal?: AbortSignal;61 temperature?: number;62 maxTokens?: number;63 /** Reserved for future per-generation routing config (provider prefs, fallbacks). */64 routing?: Record<string, unknown>;65}6667/** Narrow gateway abstraction so the frontend never becomes inseparable from OpenRouter. */68export interface ModelGateway {69 stream(request: GenerationRequest): AsyncIterable<ChatStreamEvent>;70 listModels(): Promise<ModelDefinition[]>;71}72