SPB Git

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%
2.0 KB · 65 lines typescript
Raw Blame History
1// Author: Simon-Pierre Boucher2// Contact: contact@spboucher.ai3// Project: chat.spboucher.ai45// Context compiler: DB history and model context are separate concepts.6// Compiles the active thread into a request that fits the model's window.78import type { ChatMessageInput } from "@/lib/openrouter";9import type { MessageRow } from "@/lib/conversations";1011/** Cheap token estimate: ~4 characters per token. Deliberately conservative. */12export function estimateTokens(text: string): number {13  return Math.ceil(text.length / 3.6);14}1516export interface CompiledContext {17  messages: ChatMessageInput[];18  estimatedPromptTokens: number;19  trimmedCount: number;20}2122const RESERVED_OUTPUT_TOKENS = 4096;2324/**25 * Compile the thread for a request: keep system instructions, keep the most26 * recent messages, trim oldest low-value context when over budget.27 */28export function compileContext(29  thread: MessageRow[],30  contextLength: number | undefined,31  systemPrompt?: string32): CompiledContext {33  const budget = Math.max((contextLength ?? 128_000) - RESERVED_OUTPUT_TOKENS, 8_000);3435  const system: ChatMessageInput[] = systemPrompt36    ? [{ role: "system", content: systemPrompt }]37    : [];38  const systemTokens = systemPrompt ? estimateTokens(systemPrompt) : 0;3940  // Only completed / meaningful messages reach the model.41  const usable = thread.filter(42    (m) => (m.role === "user" || m.role === "assistant") && m.content.length > 043  );4445  // Walk from newest to oldest, accumulating until the budget is spent.46  const kept: MessageRow[] = [];47  let used = systemTokens;48  for (let i = usable.length - 1; i >= 0; i--) {49    const t = estimateTokens(usable[i].content) + 6; // per-message overhead50    if (used + t > budget && kept.length > 0) break;51    kept.push(usable[i]);52    used += t;53  }54  kept.reverse();5556  return {57    messages: [58      ...system,59      ...kept.map((m): ChatMessageInput => ({ role: m.role as "user" | "assistant", content: m.content })),60    ],61    estimatedPromptTokens: used,62    trimmedCount: usable.length - kept.length,63  };64}65