import "server-only"; import type { MastraModelConfig } from "@mastra/core/llm"; import { queryAdminRows } from "@/lib/admin/database"; import { assertAllowedModelProviderUrl } from "@/lib/epay/gateway-policy"; import { decryptModelProviderApiKey, type ModelProviderType } from "@/lib/model-provider-policy"; import type { LanguageModelCatalog, ResolvedLanguageModel } from "@/mastra/model"; const cacheTtlMs = 15_000; const secretSettingNames = new Set(["apikey", "authorization", "cookie", "databaseurl", "connectionstring", "password", "privatekey", "clientsecret", "secret", "token"]); type PublishedModelRow = { model_id: string; version: number; label: string; description: string; provider_model: string; credit_cost: number; is_default: boolean; provider_code: string; provider_type: ModelProviderType; base_url: string | null; encrypted_api_key: string | null; input_cost: string | number; output_cost: string | number; }; type Cache = { expiresAt: number; catalog: LanguageModelCatalog }; const state = globalThis as typeof globalThis & { jyotishaModelCatalogCache?: Cache }; function isSecretSettingKey(key: string) { const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, ""); return secretSettingNames.has(normalized) || /(apikey|password|authorization|cookie|databaseurl|connectionstring|privatekey|clientsecret|accesstoken|refreshtoken|authtoken|bearertoken)$/.test(normalized); } export function modelSettingsContainSecrets(value: unknown): boolean { if (Array.isArray(value)) return value.some(modelSettingsContainSecrets); if (!value || typeof value !== "object") return false; return Object.entries(value).some(([key, nested]) => isSecretSettingKey(key) || modelSettingsContainSecrets(nested)); } export function sanitizeModelSettings(value: unknown): unknown { if (Array.isArray(value)) return value.map(sanitizeModelSettings); if (!value || typeof value !== "object") return value; return Object.fromEntries(Object.entries(value).filter(([key]) => !isSecretSettingKey(key)).map(([key, nested]) => [key, sanitizeModelSettings(nested)])); } function publicModel(model: ResolvedLanguageModel) { return { id: model.id, label: model.label, description: model.description, creditCost: model.creditCost, isDefault: model.isDefault }; } async function resolveRow(row: PublishedModelRow): Promise { const apiKey = decryptModelProviderApiKey({ encryptedApiKey: row.encrypted_api_key }); if (!apiKey) return null; let model: MastraModelConfig; if (row.provider_type === "openai") { model = { providerId: "openai", modelId: row.provider_model.replace(/^openai\//, ""), apiKey }; } else if (row.provider_type === "anthropic") { model = { providerId: "anthropic", modelId: row.provider_model.replace(/^anthropic\//, ""), apiKey }; } else { model = { providerId: row.provider_code, modelId: row.provider_model, url: (await assertAllowedModelProviderUrl(row.base_url ?? "")).url.toString(), apiKey }; } return { id: row.model_id, label: row.label, description: row.description, creditCost: row.credit_cost, isDefault: row.is_default, mode: row.provider_type === "openai-compatible" ? "compatible" : row.provider_type, model, configVersion: row.version, inputCostMicrousdPerMillion: Number(row.input_cost), outputCostMicrousdPerMillion: Number(row.output_cost), }; } async function queryCatalog(where: string, values: readonly unknown[] = []) { return queryAdminRows(` select c.model_id,v.version,v.label,v.description,v.provider_model,v.credit_cost,v.is_default, p.code provider_code,p.provider_type,p.base_url,p.encrypted_api_key, v.input_cost_microusd_per_million input_cost,v.output_cost_microusd_per_million output_cost from public.model_config_versions v join public.model_configs c on c.id=v.config_id join public.model_providers p on p.id=v.provider_id where ${where} `, values); } async function readPublishedCatalog(): Promise { const rows = await queryCatalog("v.status='published' and v.enabled and p.enabled order by v.is_default desc,c.model_id"); const models: ResolvedLanguageModel[] = []; for (const row of rows) { try { const model = await resolveRow(row); if (model) models.push(model); } catch { /* exclude undecryptable/unsafe providers */ } } const defaults = models.filter((model) => model.isDefault); return defaults.length === 1 ? { models, publicModels: models.map(publicModel), defaultModelId: defaults[0]!.id, issues: [] } : { models, publicModels: models.map(publicModel), defaultModelId: null, issues: [rows.length ? "default_model_unavailable" : "database_model_catalog_empty"] }; } export async function loadLanguageModelCatalog(): Promise { const cached = state.jyotishaModelCatalogCache; if (cached && cached.expiresAt > Date.now()) return cached.catalog; let catalog: LanguageModelCatalog; try { catalog = await readPublishedCatalog(); } catch { catalog = { models: [], publicModels: [], defaultModelId: null, issues: ["database_model_catalog_unavailable"] }; } state.jyotishaModelCatalogCache = { expiresAt: Date.now() + cacheTtlMs, catalog }; return catalog; } async function readVersion(modelId: string, version: number) { const rows = await queryCatalog("c.model_id=$1 and v.version=$2 and v.status in ('published','retired') and v.enabled and p.enabled", [modelId, version]); return rows[0] ? resolveRow(rows[0]) : null; } export async function resolveSessionLanguageModel(modelId: string, configVersion: number | null | undefined) { if (configVersion !== null && configVersion !== undefined) { try { return await readVersion(modelId, configVersion); } catch { return null; } } const catalog = await loadLanguageModelCatalog(); return catalog.models.find((model) => model.id === modelId) ?? null; } export function invalidateLanguageModelCatalog() { delete state.jyotishaModelCatalogCache; }