/** * db/models.js — Model aliases, MITM aliases, and custom models. */ import { getDbInstance } from "./core"; import { backupDbFile } from "./backup"; type JsonRecord = Record; function asRecord(value: unknown): JsonRecord { return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {}; } function getKeyValue(row: unknown): { key: string | null; value: string | null } { const record = asRecord(row); return { key: typeof record.key === "string" ? record.key : null, value: typeof record.value === "string" ? record.value : null, }; } // ──────────────── Model Aliases ──────────────── export async function getModelAliases() { const db = getDbInstance(); const rows = db .prepare("SELECT key, value FROM key_value WHERE namespace = 'modelAliases'") .all(); const result: Record = {}; for (const row of rows) { const { key, value } = getKeyValue(row); if (!key || value === null) continue; result[key] = JSON.parse(value); } return result; } export async function setModelAlias(alias, model) { const db = getDbInstance(); db.prepare( "INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES ('modelAliases', ?, ?)" ).run(alias, JSON.stringify(model)); backupDbFile("pre-write"); } export async function deleteModelAlias(alias) { const db = getDbInstance(); db.prepare("DELETE FROM key_value WHERE namespace = 'modelAliases' AND key = ?").run(alias); backupDbFile("pre-write"); } // ──────────────── MITM Alias ──────────────── export async function getMitmAlias(toolName) { const db = getDbInstance(); if (toolName) { const row = db .prepare("SELECT value FROM key_value WHERE namespace = 'mitmAlias' AND key = ?") .get(toolName); const value = getKeyValue(row).value; return value ? JSON.parse(value) : {}; } const rows = db.prepare("SELECT key, value FROM key_value WHERE namespace = 'mitmAlias'").all(); const result: Record = {}; for (const row of rows) { const { key, value } = getKeyValue(row); if (!key || value === null) continue; result[key] = JSON.parse(value); } return result; } export async function setMitmAliasAll(toolName, mappings) { const db = getDbInstance(); db.prepare( "INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES ('mitmAlias', ?, ?)" ).run(toolName, JSON.stringify(mappings || {})); backupDbFile("pre-write"); } // ──────────────── Custom Models ──────────────── export async function getCustomModels(providerId) { const db = getDbInstance(); if (providerId) { const row = db .prepare("SELECT value FROM key_value WHERE namespace = 'customModels' AND key = ?") .get(providerId); const value = getKeyValue(row).value; return value ? JSON.parse(value) : []; } const rows = db .prepare("SELECT key, value FROM key_value WHERE namespace = 'customModels'") .all(); const result: Record = {}; for (const row of rows) { const { key, value } = getKeyValue(row); if (!key || value === null) continue; result[key] = JSON.parse(value); } return result; } export async function getAllCustomModels() { const db = getDbInstance(); const rows = db .prepare("SELECT key, value FROM key_value WHERE namespace = 'customModels'") .all(); const result: Record = {}; for (const row of rows) { const { key, value } = getKeyValue(row); if (!key || value === null) continue; result[key] = JSON.parse(value); } return result; } export async function addCustomModel( providerId: string, modelId: string, modelName?: string, source = "manual", apiFormat: "chat-completions" | "responses" = "chat-completions", supportedEndpoints: string[] = ["chat"] ) { const db = getDbInstance(); const row = db .prepare("SELECT value FROM key_value WHERE namespace = 'customModels' AND key = ?") .get(providerId); const value = getKeyValue(row).value; const models = value ? JSON.parse(value) : []; const exists = models.find((m) => m.id === modelId); if (exists) return exists; const model = { id: modelId, name: modelName || modelId, source, apiFormat, supportedEndpoints, }; models.push(model); db.prepare( "INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES ('customModels', ?, ?)" ).run(providerId, JSON.stringify(models)); backupDbFile("pre-write"); return model; } export async function removeCustomModel(providerId, modelId) { const db = getDbInstance(); const row = db .prepare("SELECT value FROM key_value WHERE namespace = 'customModels' AND key = ?") .get(providerId); if (!row) return false; const value = getKeyValue(row).value; if (!value) return false; const models = JSON.parse(value); const before = models.length; const filtered = models.filter((m) => m.id !== modelId); if (filtered.length === before) return false; if (filtered.length === 0) { db.prepare("DELETE FROM key_value WHERE namespace = 'customModels' AND key = ?").run( providerId ); } else { db.prepare("UPDATE key_value SET value = ? WHERE namespace = 'customModels' AND key = ?").run( JSON.stringify(filtered), providerId ); } backupDbFile("pre-write"); return true; } export async function updateCustomModel(providerId, modelId, updates = {}) { const db = getDbInstance(); const row = db .prepare("SELECT value FROM key_value WHERE namespace = 'customModels' AND key = ?") .get(providerId); if (!row) return null; const value = getKeyValue(row).value; if (!value) return null; const models = JSON.parse(value); const index = models.findIndex((m) => m.id === modelId); if (index === -1) return null; const current = models[index]; const next = { ...current, ...(updates.modelName !== undefined ? { name: updates.modelName || current.name } : {}), ...(updates.apiFormat !== undefined ? { apiFormat: updates.apiFormat } : {}), ...(updates.supportedEndpoints !== undefined ? { supportedEndpoints: updates.supportedEndpoints } : {}), ...(updates.normalizeToolCallId !== undefined ? { normalizeToolCallId: Boolean(updates.normalizeToolCallId) } : {}), }; models[index] = next; db.prepare("UPDATE key_value SET value = ? WHERE namespace = 'customModels' AND key = ?").run( JSON.stringify(models), providerId ); backupDbFile("pre-write"); return next; } /** * Whether the given provider/model has "normalize tool call id" (9-char Mistral-style) enabled. * Only custom models can have this set; returns false for built-in models. */ export function getModelNormalizeToolCallId(providerId: string, modelId: string): boolean { const db = getDbInstance(); const row = db .prepare("SELECT value FROM key_value WHERE namespace = 'customModels' AND key = ?") .get(providerId); const value = getKeyValue(row).value; if (!value) return false; let models: { id: string; normalizeToolCallId?: boolean }[]; try { models = JSON.parse(value); } catch { return false; } if (!Array.isArray(models)) return false; const m = models.find((x: { id: string }) => x.id === modelId); return Boolean(m?.normalizeToolCallId); }