mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 06:12:10 +03:00
Bug Fixes: - #212: Auto-generate API_KEY_SECRET at startup (like JWT_SECRET) - #213: Circuit breaker now scoped per-model instead of per-provider - #200: Connectivity fallback for custom providers (Ollama, LM Studio) Features: - #204: API Format selector (Chat Completions / Responses API) for custom models - #205: Combo endpoint field (chat / embeddings / images) in schema - #206: Supported Endpoints checkboxes (chat, embeddings, images, audio) - Custom models with endpoint tags appear in /v1/embeddings and /v1/images/generations - Model catalog includes api_format, type, and supported_endpoints metadata - Provider detail page shows badges for non-default endpoint configurations Files changed: instrumentation.ts, combo.ts, validation.ts, models.ts, schemas.ts, provider-models/route.ts, providers/[id]/page.tsx, catalog.ts, embeddings/route.ts, images/generations/route.ts
180 lines
5.4 KiB
TypeScript
180 lines
5.4 KiB
TypeScript
/**
|
|
* db/models.js — Model aliases, MITM aliases, and custom models.
|
|
*/
|
|
|
|
import { getDbInstance } from "./core";
|
|
import { backupDbFile } from "./backup";
|
|
|
|
type JsonRecord = Record<string, unknown>;
|
|
|
|
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<string, unknown> = {};
|
|
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<string, unknown> = {};
|
|
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<string, unknown> = {};
|
|
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<string, unknown> = {};
|
|
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;
|
|
}
|