ai-chatbot/scripts/zcode-agent/sync-multimodal-fallbacks.ts

39 lines
2.4 KiB
TypeScript

import { and, eq } from "drizzle-orm";
import { db } from "@/lib/db";
import { aiModel, aiProvider } from "@/lib/db/schema";
import { normalizeVip98kBaseUrl } from "@/lib/ai/vip98k-models";
import { selectVip98kChatInvocation, vip98kChatSupportsVision } from "@/lib/ai/vip98k-chat-contract";
// Explicit administration command: discover the provider's published vision
// chat routes. Never re-enable a disabled model or change the user's defaults.
async function main() {
const providers = await db.select().from(aiProvider).where(and(eq(aiProvider.type, "vip98k"), eq(aiProvider.isEnabled, true)));
const added: string[] = [];
for (const provider of providers) {
const response = await fetch(`${normalizeVip98kBaseUrl(provider.baseUrl)}/api/v2/catalog/models`, {
headers: { Accept: "application/json", "User-Agent": "Mozilla/5.0" }, redirect: "error", signal: AbortSignal.timeout(15_000),
});
if (!response.ok) throw new Error(`Provider catalog unavailable (${response.status})`);
const payload = await response.json();
const entries = (payload?.data?.sections || []).flatMap((section: { models?: Record<string, any>[] }) => section.models || []);
const existing = await db.select({ modelId: aiModel.modelId }).from(aiModel).where(eq(aiModel.providerId, provider.id));
const known = new Set(existing.map(model => model.modelId));
let index = 0;
for (const entry of entries) {
const contract = selectVip98kChatInvocation(entry);
if (!contract || !vip98kChatSupportsVision(entry) || !entry.id || known.has(entry.id)) continue;
const id = String(entry.id);
// The catalog's chat contract and vision modality are both required.
// Pure generation, media-task and Anthropic-only routes are excluded.
await db.insert(aiModel).values({ providerId: provider.id, modelId: id, displayName: id,
supportsVision: true, supportsImageGen: false, isEnabled: true, isHidden: true, isDefault: false,
sortOrder: 10_000 + index++, defaultParams: { agentAutoModel: false },
capabilities: { kind: "chat", multimodalModel: true, agentFallbackOnly: true, vip98k: entry },
});
known.add(id); added.push(id);
}
}
console.log(JSON.stringify({ added, count: added.length, imageModelsUnchanged: true, defaultsUnchanged: true }));
}
main().then(() => process.exit(0)).catch(() => { console.error("Multimodal fallback catalog sync failed; no credentials were logged"); process.exit(1); });