| import { PROVIDER_MODELS, PROVIDER_ID_TO_ALIAS, getModelKind } from "@/shared/constants/models"; |
| import { |
| AI_PROVIDERS, |
| getProviderAlias, |
| isAnthropicCompatibleProvider, |
| isOpenAICompatibleProvider, |
| } from "@/shared/constants/providers"; |
| import { getProviderConnections, getCombos, getCustomModels, getModelAliases } from "@/lib/localDb"; |
| import { getDisabledModels } from "@/lib/disabledModelsDb"; |
| import { resolveKiroModels } from "open-sse/services/kiroModels.js"; |
| import { resolveQoderModels } from "open-sse/services/qoderModels.js"; |
| import { capabilitiesFromServiceKind } from "open-sse/providers/capabilities.js"; |
|
|
| |
| |
| |
| const LIVE_MODEL_RESOLVERS = { |
| kiro: async (conn) => { |
| const result = await resolveKiroModels({ |
| accessToken: conn.accessToken, |
| refreshToken: conn.refreshToken, |
| providerSpecificData: conn.providerSpecificData || {} |
| }, { log: console }); |
| return result?.models?.length ? { models: result.models } : null; |
| }, |
| qoder: async (conn) => { |
| const result = await resolveQoderModels({ |
| accessToken: conn.accessToken, |
| refreshToken: conn.refreshToken, |
| email: conn.email, |
| displayName: conn.displayName, |
| providerSpecificData: conn.providerSpecificData || {} |
| }); |
| if (!result?.models?.length) return null; |
| return { |
| models: result.models.map((m) => ({ id: m.id, name: m.name })), |
| }; |
| } |
| }; |
|
|
| const parseOpenAIStyleModels = (data) => { |
| if (Array.isArray(data)) return data; |
| return data?.data || data?.models || data?.results || []; |
| }; |
|
|
| |
| const UPSTREAM_CONNECTION_RE = /[-_][0-9a-f]{8,}$/i; |
|
|
| |
| const LLM_KIND = "llm"; |
|
|
| |
| |
| const MODEL_TYPE_TO_KIND = { |
| image: "image", |
| tts: "tts", |
| embedding: "embedding", |
| stt: "stt", |
| imageToText: "imageToText", |
| }; |
|
|
| function modelKind(model) { |
| const k = model?.kind || model?.type; |
| if (!k) return LLM_KIND; |
| return MODEL_TYPE_TO_KIND[k] || LLM_KIND; |
| } |
|
|
| |
| |
| function inferKindFromUnknownModelId(modelId) { |
| const lower = String(modelId).toLowerCase(); |
| if (/embed/.test(lower)) return "embedding"; |
| if (/tts|speech|audio|voice/.test(lower)) return "tts"; |
| if (/image|imagen|dall-?e|flux|sdxl|sd-|stable-diffusion/.test(lower)) return "image"; |
| return LLM_KIND; |
| } |
|
|
| async function fetchCompatibleModelIds(connection) { |
| if (!connection?.apiKey) return []; |
|
|
| const baseUrl = typeof connection?.providerSpecificData?.baseUrl === "string" |
| ? connection.providerSpecificData.baseUrl.trim().replace(/\/$/, "") |
| : ""; |
|
|
| if (!baseUrl) return []; |
|
|
| let url = `${baseUrl}/models`; |
| const headers = { |
| "Content-Type": "application/json", |
| }; |
|
|
| if (isOpenAICompatibleProvider(connection.provider)) { |
| headers.Authorization = `Bearer ${connection.apiKey}`; |
| } else if (isAnthropicCompatibleProvider(connection.provider)) { |
| if (url.endsWith("/messages/models")) { |
| url = url.slice(0, -9); |
| } else if (url.endsWith("/messages")) { |
| url = `${url.slice(0, -9)}/models`; |
| } |
| headers["x-api-key"] = connection.apiKey; |
| headers["anthropic-version"] = "2023-06-01"; |
| headers.Authorization = `Bearer ${connection.apiKey}`; |
| } else { |
| return []; |
| } |
|
|
| try { |
| const controller = new AbortController(); |
| const timeoutId = setTimeout(() => controller.abort(), 5000); |
| const response = await fetch(url, { |
| method: "GET", |
| headers, |
| cache: "no-store", |
| signal: controller.signal, |
| }); |
| clearTimeout(timeoutId); |
|
|
| if (!response.ok) return []; |
|
|
| const data = await response.json(); |
| const rawModels = parseOpenAIStyleModels(data); |
|
|
| return Array.from( |
| new Set( |
| rawModels |
| .map((model) => model?.id || model?.name || model?.model) |
| .filter((modelId) => typeof modelId === "string" && modelId.trim() !== "") |
| ) |
| ); |
| } catch { |
| return []; |
| } |
| } |
|
|
| |
| |
| function providerMatchesKinds(providerId, kindFilter) { |
| const provider = AI_PROVIDERS[providerId]; |
| const kinds = Array.isArray(provider?.serviceKinds) && provider.serviceKinds.length > 0 |
| ? provider.serviceKinds |
| : [LLM_KIND]; |
| return kindFilter.some((k) => kinds.includes(k)); |
| } |
|
|
| |
| |
| function comboMatchesKinds(combo, kindFilter) { |
| const kind = combo?.kind || LLM_KIND; |
| return kindFilter.includes(kind); |
| } |
|
|
| |
| |
| |
| |
| export async function buildModelsList(kindFilter) { |
| let connections = []; |
| try { |
| connections = await getProviderConnections(); |
| connections = connections.filter(c => c.isActive !== false); |
| } catch (e) { |
| console.log("Could not fetch providers, returning all models"); |
| } |
|
|
| let combos = []; |
| try { |
| combos = await getCombos(); |
| } catch (e) { |
| console.log("Could not fetch combos"); |
| } |
|
|
| let customModels = []; |
| try { |
| customModels = await getCustomModels(); |
| } catch (e) { |
| console.log("Could not fetch custom models"); |
| } |
|
|
| let modelAliases = {}; |
| try { |
| modelAliases = await getModelAliases(); |
| } catch (e) { |
| console.log("Could not fetch model aliases"); |
| } |
|
|
| let disabledByAlias = {}; |
| try { |
| disabledByAlias = await getDisabledModels(); |
| } catch (e) { |
| console.log("Could not fetch disabled models"); |
| } |
| const isDisabled = (alias, modelId) => Array.isArray(disabledByAlias[alias]) && disabledByAlias[alias].includes(modelId); |
|
|
| const activeConnectionByProvider = new Map(); |
| for (const conn of connections) { |
| if (!activeConnectionByProvider.has(conn.provider)) { |
| activeConnectionByProvider.set(conn.provider, conn); |
| } |
| } |
|
|
| const models = []; |
|
|
| |
| for (const combo of combos) { |
| if (!comboMatchesKinds(combo, kindFilter)) continue; |
| const entry = { |
| id: combo.name, |
| object: "model", |
| owned_by: "combo", |
| }; |
| if (combo.kind === "webSearch" || combo.kind === "webFetch") { |
| entry.kind = combo.kind; |
| } |
| models.push(entry); |
| } |
|
|
| if (connections.length === 0) { |
| |
| const aliasToProviderId = Object.fromEntries( |
| Object.entries(PROVIDER_ID_TO_ALIAS).map(([id, alias]) => [alias, id]) |
| ); |
| for (const [alias, providerModels] of Object.entries(PROVIDER_MODELS)) { |
| const providerId = aliasToProviderId[alias] || alias; |
| if (!providerMatchesKinds(providerId, kindFilter)) continue; |
| for (const model of providerModels) { |
| if (!kindFilter.includes(modelKind(model))) continue; |
| if (isDisabled(alias, model.id)) continue; |
| models.push({ |
| id: `${alias}/${model.id}`, |
| object: "model", |
| owned_by: alias, |
| }); |
| } |
| } |
|
|
| for (const customModel of customModels) { |
| if (!customModel?.id || (customModel.type && customModel.type !== "llm")) continue; |
| |
| if (!kindFilter.includes(LLM_KIND)) continue; |
| const providerAlias = customModel.providerAlias; |
| if (!providerAlias) continue; |
|
|
| const modelId = String(customModel.id).trim(); |
| if (!modelId) continue; |
|
|
| models.push({ |
| id: `${providerAlias}/${modelId}`, |
| object: "model", |
| owned_by: providerAlias, |
| }); |
| } |
| } else { |
| for (const [providerId, conn] of activeConnectionByProvider.entries()) { |
| if (!providerMatchesKinds(providerId, kindFilter)) continue; |
|
|
| const staticAlias = PROVIDER_ID_TO_ALIAS[providerId] || providerId; |
| const outputAlias = ( |
| conn?.providerSpecificData?.prefix |
| || getProviderAlias(providerId) |
| || staticAlias |
| ).trim(); |
| const providerModels = PROVIDER_MODELS[staticAlias] || []; |
| const enabledModels = conn?.providerSpecificData?.enabledModels; |
| const hasExplicitEnabledModels = |
| Array.isArray(enabledModels) && enabledModels.length > 0; |
| const isCompatibleProvider = |
| isOpenAICompatibleProvider(providerId) || isAnthropicCompatibleProvider(providerId); |
|
|
| |
| const staticModelKindById = new Map( |
| providerModels.map((m) => [m.id, modelKind(m)]) |
| ); |
|
|
| let rawModelIds = hasExplicitEnabledModels |
| ? Array.from( |
| new Set( |
| enabledModels.filter( |
| (modelId) => typeof modelId === "string" && modelId.trim() !== "", |
| ), |
| ), |
| ) |
| : providerModels.map((model) => model.id); |
|
|
| if (isCompatibleProvider && rawModelIds.length === 0 && !UPSTREAM_CONNECTION_RE.test(providerId)) { |
| rawModelIds = await fetchCompatibleModelIds(conn); |
| } |
|
|
| |
| |
| |
| const liveResolver = LIVE_MODEL_RESOLVERS[providerId]; |
| if (liveResolver && !hasExplicitEnabledModels) { |
| try { |
| const live = await liveResolver(conn); |
| if (live?.models?.length) { |
| rawModelIds = live.models.map((m) => m.id); |
| } |
| } catch (err) { |
| console.log(`Live model fetch failed for ${providerId}: ${err?.message || err}`); |
| } |
| } |
|
|
| const modelIds = rawModelIds |
| .map((modelId) => { |
| if (modelId.startsWith(`${outputAlias}/`)) { |
| return modelId.slice(outputAlias.length + 1); |
| } |
| if (modelId.startsWith(`${staticAlias}/`)) { |
| return modelId.slice(staticAlias.length + 1); |
| } |
| if (modelId.startsWith(`${providerId}/`)) { |
| return modelId.slice(providerId.length + 1); |
| } |
| return modelId; |
| }) |
| .filter((modelId) => typeof modelId === "string" && modelId.trim() !== ""); |
|
|
| const customModelKindById = new Map(); |
| const customModelIds = customModels |
| .filter((m) => { |
| if (!m?.id) return false; |
| const kind = getModelKind(m) || LLM_KIND; |
| |
| |
| if (!kindFilter.includes(kind) && !(kind === "imageToText" && kindFilter.includes(LLM_KIND))) return false; |
| const alias = m.providerAlias; |
| return alias === staticAlias || alias === outputAlias || alias === providerId; |
| }) |
| .map((m) => { |
| const modelId = String(m.id).trim(); |
| if (modelId) customModelKindById.set(modelId, getModelKind(m) || LLM_KIND); |
| return modelId; |
| }) |
| .filter((modelId) => modelId !== ""); |
|
|
| const aliasModelIds = Object.values(modelAliases || {}) |
| .filter((fullModel) => { |
| if (typeof fullModel !== "string" || !fullModel.includes("/")) return false; |
| return ( |
| fullModel.startsWith(`${outputAlias}/`) || |
| fullModel.startsWith(`${staticAlias}/`) || |
| fullModel.startsWith(`${providerId}/`) |
| ); |
| }) |
| .map((fullModel) => { |
| if (fullModel.startsWith(`${outputAlias}/`)) { |
| return fullModel.slice(outputAlias.length + 1); |
| } |
| if (fullModel.startsWith(`${staticAlias}/`)) { |
| return fullModel.slice(staticAlias.length + 1); |
| } |
| if (fullModel.startsWith(`${providerId}/`)) { |
| return fullModel.slice(providerId.length + 1); |
| } |
| return fullModel; |
| }) |
| .filter((modelId) => typeof modelId === "string" && modelId.trim() !== ""); |
|
|
| const mergedModelIds = Array.from(new Set([...modelIds, ...customModelIds, ...aliasModelIds])); |
|
|
| for (const modelId of mergedModelIds) { |
| |
| const customKind = customModelKindById.get(modelId); |
| const kind = staticModelKindById.get(modelId) || customKind || inferKindFromUnknownModelId(modelId); |
| |
| const allowAsLlm = kind === "imageToText" && kindFilter.includes(LLM_KIND); |
| if (!kindFilter.includes(kind) && !allowAsLlm) continue; |
| if (isDisabled(outputAlias, modelId) || isDisabled(staticAlias, modelId)) continue; |
|
|
| const model = { |
| id: `${outputAlias}/${modelId}`, |
| object: "model", |
| owned_by: outputAlias, |
| }; |
| const caps = capabilitiesFromServiceKind(customKind); |
| if (caps) model.capabilities = caps; |
| models.push(model); |
| } |
|
|
| |
| const providerInfo = AI_PROVIDERS[providerId]; |
| if (kindFilter.includes("webSearch") && providerInfo?.searchConfig) { |
| models.push({ |
| id: `${outputAlias}/search`, |
| object: "model", |
| kind: "webSearch", |
| owned_by: outputAlias, |
| }); |
| } |
| if (kindFilter.includes("webFetch") && providerInfo?.fetchConfig) { |
| models.push({ |
| id: `${outputAlias}/fetch`, |
| object: "model", |
| kind: "webFetch", |
| owned_by: outputAlias, |
| }); |
| } |
| } |
| } |
|
|
| const dedupedModels = []; |
| const seenModelIds = new Set(); |
| for (const model of models) { |
| if (!model?.id || seenModelIds.has(model.id)) continue; |
| seenModelIds.add(model.id); |
| dedupedModels.push(model); |
| } |
|
|
| return dedupedModels; |
| } |
|
|
| |
| |
| |
| export async function OPTIONS() { |
| return new Response(null, { |
| headers: { |
| "Access-Control-Allow-Origin": "*", |
| "Access-Control-Allow-Methods": "GET, OPTIONS", |
| "Access-Control-Allow-Headers": "*", |
| }, |
| }); |
| } |
|
|
| |
| |
| |
| |
| export async function GET() { |
| try { |
| const data = await buildModelsList([LLM_KIND]); |
| return Response.json({ object: "list", data }, { |
| headers: { "Access-Control-Allow-Origin": "*" }, |
| }); |
| } catch (error) { |
| console.log("Error fetching models:", error); |
| return Response.json( |
| { error: { message: error.message, type: "server_error" } }, |
| { status: 500 } |
| ); |
| } |
| } |
|
|