feat(models): refresh model capabilities from models.dev in the background
Capability tables are hand-maintained, so a model gains vision or a wider context only when someone notices and edits the file. This adds a daily sync that fills the gap for models already in the registry. How it decides: - Modalities (vision/pdf/audio/video) belong to the MODEL — every gateway serving glm-5.3-flash serves the same weights — so they are keyed by model id and shared. A majority of sources must declare one, which keeps out lone mis-declarations: minimax-m2.5 (1 of 45), glm-4.7 (1 of 44) and gpt-oss-120b (2 of 76) are text-only despite a reseller claiming vision. - Context/output limits belong to the GATEWAY — each truncates differently (glm-5 ships as 202752/16384 on one host and 204800/131072 on another) — so they are keyed by provider + model and only the matching provider's own numbers are trusted. Both layers are strictly additive and sit BELOW the hand-written tables, which short-circuit first. A capability already true stays true. Mechanics: worker thread (the 4MB parse would block the loop ~20ms), ETag so an unchanged catalog costs one empty request, 60s startup delay, 30min backoff on failure, MODEL_CATALOG_SYNC=off to disable. Only the ~57KB delta is kept; lookups cost ~0.1us via an mtime-guarded cache. capabilities.js is bundled into the browser through useModelCaps, so it cannot import node:fs — the server injects the reader via setCatalogSource() from instrumentation. visionPatterns.js is the last resort: a model nobody has catalogued yet still accepts images when its id says so (qwen3-vl-plus, glm-4.6v, llava), with image-generation and embedding ids excluded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
31
src/app/api/models/catalog-sync/route.js
Normal file
31
src/app/api/models/catalog-sync/route.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import fs from "node:fs";
|
||||
import { getSyncState, syncModelCatalog } from "@/lib/modelCatalog/sync.js";
|
||||
import { CATALOG_FILE } from "open-sse/providers/catalogOverride.js";
|
||||
|
||||
// GET /api/models/catalog-sync - Sync status and what the catalog currently holds
|
||||
export async function GET() {
|
||||
const state = getSyncState();
|
||||
let catalog = null;
|
||||
try {
|
||||
const parsed = JSON.parse(fs.readFileSync(CATALOG_FILE, "utf8"));
|
||||
catalog = {
|
||||
syncedAt: parsed.syncedAt,
|
||||
models: Object.keys(parsed.models || {}).length,
|
||||
providers: Object.keys(parsed.providers || {}).length,
|
||||
bytes: fs.statSync(CATALOG_FILE).size,
|
||||
};
|
||||
} catch {
|
||||
catalog = null;
|
||||
}
|
||||
return NextResponse.json({ ...state, catalog });
|
||||
}
|
||||
|
||||
// POST /api/models/catalog-sync - Run a sync now instead of waiting for the timer
|
||||
export async function POST() {
|
||||
const result = await syncModelCatalog();
|
||||
if (!result) {
|
||||
return NextResponse.json({ error: getSyncState().lastError || "sync in progress" }, { status: 503 });
|
||||
}
|
||||
return NextResponse.json({ success: true, result });
|
||||
}
|
||||
Reference in New Issue
Block a user