export interface ModelManifest { source: string; revision: string; architecture: string; labels: { toxic: number; nonToxic: number; names: Record; }; maxLength: number; quantization: string; runtime: string; } let manifestPromise: Promise | undefined; export function modelBaseUrl(): string { return chrome.runtime.getURL("models/toxicity/"); } export function loadModelManifest(): Promise { manifestPromise ??= fetch(`${modelBaseUrl()}model-manifest.json`) .then((response) => { if (!response.ok) throw new Error(`VibeGuard model metadata unavailable (${response.status})`); return response.json() as Promise; }) .then(validateManifest); return manifestPromise; } export function validateManifest(value: ModelManifest): ModelManifest { if (!value || typeof value.source !== "string" || typeof value.revision !== "string") throw new Error("Invalid VibeGuard model manifest"); if (!Number.isInteger(value.labels?.toxic) || !Number.isInteger(value.labels?.nonToxic) || value.labels.toxic === value.labels.nonToxic || !value.labels?.names) throw new Error("Model manifest has no binary label mapping"); if (!Number.isInteger(value.maxLength) || value.maxLength < 8) throw new Error("Model manifest has an invalid maximum length"); return value; }