9router/src/app/api/providers/[id]/models/route.js

472 lines
16 KiB
JavaScript
Raw Normal View History

2026-01-04 21:58:59 -05:00
import { NextResponse } from "next/server";
import { getProviderConnectionById } from "@/models";
import { isOpenAICompatibleProvider, isAnthropicCompatibleProvider } from "@/shared/constants/providers";
import { KiroService } from "@/lib/oauth/services/kiro";
import { GEMINI_CONFIG } from "@/lib/oauth/constants/oauth";
feat: Add Google Cloud Vertex AI provider support (vertex, vertex-partner) Co-authored-by: Quan <quanle96@outlook.com> PR: https://github.com/decolua/9router/pull/298 Thanks to @kwanLeeFrmVi for the original implementation. Here is a summary of changes made during review integration: - Replaced google-auth-library with jose (already a project dependency) for SA JSON -> OAuth2 Bearer token minting (RS256 JWT assertion flow) - Moved auth logic (parseSaJson, refreshVertexToken, token cache) from executor into open-sse/services/tokenRefresh.js to match project pattern - Fixed executor to use proxyAwareFetch instead of raw fetch (proxy support) - Simplified buildUrl: use global aiplatform.googleapis.com endpoint for both vertex (Gemini) and vertex-partner; removed region/modelFamily fields - Added auto-detection of GCP project_id from raw API key via probe request (vertex-partner only, cached per key) - Added vertex/vertex-partner cases to /api/providers/validate/route.js - Updated model lists based on live testing: - vertex: gemini-3.1-pro-preview, gemini-3.1-flash-lite-preview, gemini-3-flash-preview, gemini-2.5-flash (removed gemini-2.5-pro: 404) - vertex-partner: deepseek-v3.2, qwen3-next-80b (instruct+thinking), glm-5 (removed Mistral/Llama: not enabled in test project) - gemini provider: added gemini-3.1-pro-preview, gemini-3.1-flash-lite-preview - Removed bun.lock (project uses npm/package-lock.json) - Removed region and modelFamily UI fields (global endpoint, auto-detect) - Kiro token auto-refresh on AccessDeniedException (from commit 2) Made-with: Cursor
2026-03-14 00:37:23 -04:00
import { refreshGoogleToken, updateProviderCredentials, refreshKiroToken } from "@/sse/services/tokenRefresh";
import { resolveOllamaLocalHost } from "open-sse/config/providers.js";
const GEMINI_CLI_MODELS_URL = "https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels";
const parseOpenAIStyleModels = (data) => {
if (Array.isArray(data)) return data;
return data?.data || data?.models || data?.results || [];
};
const parseGeminiCliModels = (data) => {
if (Array.isArray(data?.models)) {
return data.models
.map((item) => {
const id = item?.id || item?.model || item?.name;
if (!id) return null;
return { id, name: item?.displayName || item?.name || id };
})
.filter(Boolean);
}
if (data?.models && typeof data.models === "object") {
return Object.entries(data.models)
.filter(([, info]) => !info?.isInternal)
.map(([id, info]) => ({
id,
name: info?.displayName || info?.name || id,
}));
}
return [];
};
const createOpenAIModelsConfig = (url) => ({
url,
method: "GET",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
authPrefix: "Bearer ",
parseResponse: parseOpenAIStyleModels
});
2026-01-04 21:58:59 -05:00
const resolveQwenModelsUrl = (connection) => {
const fallback = "https://portal.qwen.ai/v1/models";
const raw = connection?.providerSpecificData?.resourceUrl;
if (!raw || typeof raw !== "string") return fallback;
const value = raw.trim();
if (!value) return fallback;
if (value.startsWith("http://") || value.startsWith("https://")) {
return `${value.replace(/\/$/, "")}/models`;
}
return `https://${value.replace(/\/$/, "")}/v1/models`;
};
2026-01-04 21:58:59 -05:00
// Provider models endpoints configuration
const PROVIDER_MODELS_CONFIG = {
claude: {
url: "https://api.anthropic.com/v1/models",
method: "GET",
headers: {
"Anthropic-Version": "2023-06-01",
"Content-Type": "application/json"
},
authHeader: "x-api-key",
parseResponse: (data) => data.data || []
},
gemini: {
url: "https://generativelanguage.googleapis.com/v1beta/models",
method: "GET",
headers: { "Content-Type": "application/json" },
authQuery: "key", // Use query param for API key
parseResponse: (data) => data.models || []
},
qwen: {
url: "https://portal.qwen.ai/v1/models",
method: "GET",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
authPrefix: "Bearer ",
parseResponse: (data) => data.data || []
},
antigravity: {
url: "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:models",
method: "POST",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
authPrefix: "Bearer ",
body: {},
parseResponse: (data) => data.models || []
},
github: {
url: "https://api.githubcopilot.com/models",
2026-01-04 21:58:59 -05:00
method: "GET",
headers: {
"Content-Type": "application/json",
"Copilot-Integration-Id": "vscode-chat",
"editor-version": "vscode/1.107.1",
"editor-plugin-version": "copilot-chat/0.26.7",
"user-agent": "GitHubCopilotChat/0.26.7"
},
2026-01-04 21:58:59 -05:00
authHeader: "Authorization",
authPrefix: "Bearer ",
parseResponse: (data) => {
if (!data?.data) return [];
// Filter out embeddings, non-chat models, and disabled models
return data.data
.filter(m => m.capabilities?.type === "chat")
.filter(m => m.policy?.state !== "disabled") // Only return explicitly enabled models
.map(m => ({
id: m.id,
name: m.name || m.id,
version: m.version,
capabilities: m.capabilities,
isDefault: m.model_picker_enabled === true
}));
}
2026-01-04 21:58:59 -05:00
},
openai: createOpenAIModelsConfig("https://api.openai.com/v1/models"),
openrouter: createOpenAIModelsConfig("https://openrouter.ai/api/v1/models"),
2026-01-04 21:58:59 -05:00
anthropic: {
url: "https://api.anthropic.com/v1/models",
method: "GET",
headers: {
"Anthropic-Version": "2023-06-01",
"Content-Type": "application/json"
},
authHeader: "x-api-key",
parseResponse: (data) => data.data || []
},
alicode: {
url: "https://coding.dashscope.aliyuncs.com/v1/models",
method: "GET",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
authPrefix: "Bearer ",
parseResponse: (data) => data.data || []
2026-02-26 21:33:14 -05:00
},
"alicode-intl": {
url: "https://coding-intl.dashscope.aliyuncs.com/v1/models",
method: "GET",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
authPrefix: "Bearer ",
parseResponse: (data) => data.data || []
},
"volcengine-ark": createOpenAIModelsConfig("https://ark.cn-beijing.volces.com/api/coding/v3/models"),
2026-04-25 06:00:39 -04:00
byteplus: createOpenAIModelsConfig("https://ark.ap-southeast.bytepluses.com/api/coding/v3/models"),
2026-02-26 21:33:14 -05:00
// OpenAI-compatible API key providers
deepseek: createOpenAIModelsConfig("https://api.deepseek.com/models"),
groq: createOpenAIModelsConfig("https://api.groq.com/openai/v1/models"),
xai: createOpenAIModelsConfig("https://api.x.ai/v1/models"),
mistral: createOpenAIModelsConfig("https://api.mistral.ai/v1/models"),
perplexity: createOpenAIModelsConfig("https://api.perplexity.ai/models"),
together: createOpenAIModelsConfig("https://api.together.xyz/v1/models"),
fireworks: createOpenAIModelsConfig("https://api.fireworks.ai/inference/v1/models"),
cerebras: createOpenAIModelsConfig("https://api.cerebras.ai/v1/models"),
cohere: createOpenAIModelsConfig("https://api.cohere.ai/v1/models"),
nebius: createOpenAIModelsConfig("https://api.studio.nebius.ai/v1/models"),
siliconflow: createOpenAIModelsConfig("https://api.siliconflow.cn/v1/models"),
hyperbolic: createOpenAIModelsConfig("https://api.hyperbolic.xyz/v1/models"),
ollama: createOpenAIModelsConfig("https://ollama.com/api/tags"),
// ollama-local: url resolved dynamically below via providerSpecificData.baseUrl
nanobanana: createOpenAIModelsConfig("https://api.nanobananaapi.ai/v1/models"),
chutes: createOpenAIModelsConfig("https://llm.chutes.ai/v1/models"),
nvidia: createOpenAIModelsConfig("https://integrate.api.nvidia.com/v1/models"),
assemblyai: createOpenAIModelsConfig("https://api.assemblyai.com/v1/models")
2026-01-04 21:58:59 -05:00
};
/**
* GET /api/providers/[id]/models - Get models list from provider
*/
export async function GET(request, { params }) {
try {
const { id } = await params;
const connection = await getProviderConnectionById(id);
if (!connection) {
return NextResponse.json({ error: "Connection not found" }, { status: 404 });
}
if (isOpenAICompatibleProvider(connection.provider)) {
const baseUrl = connection.providerSpecificData?.baseUrl;
if (!baseUrl) {
return NextResponse.json({ error: "No base URL configured for OpenAI compatible provider" }, { status: 400 });
}
const url = `${baseUrl.replace(/\/$/, "")}/models`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${connection.apiKey}`,
},
});
if (!response.ok) {
const errorText = await response.text();
console.log(`Error fetching models from ${connection.provider}:`, errorText);
return NextResponse.json(
{ error: `Failed to fetch models: ${response.status}` },
{ status: response.status }
);
}
const data = await response.json();
const models = data.data || data.models || [];
return NextResponse.json({
provider: connection.provider,
connectionId: connection.id,
models
});
}
if (isAnthropicCompatibleProvider(connection.provider)) {
let baseUrl = connection.providerSpecificData?.baseUrl;
if (!baseUrl) {
return NextResponse.json({ error: "No base URL configured for Anthropic compatible provider" }, { status: 400 });
}
baseUrl = baseUrl.replace(/\/$/, "");
if (baseUrl.endsWith("/messages")) {
baseUrl = baseUrl.slice(0, -9);
}
const url = `${baseUrl}/models`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-api-key": connection.apiKey,
"anthropic-version": "2023-06-01",
"Authorization": `Bearer ${connection.apiKey}`
},
});
if (!response.ok) {
const errorText = await response.text();
console.log(`Error fetching models from ${connection.provider}:`, errorText);
return NextResponse.json(
{ error: `Failed to fetch models: ${response.status}` },
{ status: response.status }
);
}
const data = await response.json();
const models = data.data || data.models || [];
return NextResponse.json({
provider: connection.provider,
connectionId: connection.id,
models
});
}
// Kiro: Try dynamic model fetching first
if (connection.provider === "kiro") {
feat: Add Google Cloud Vertex AI provider support (vertex, vertex-partner) Co-authored-by: Quan <quanle96@outlook.com> PR: https://github.com/decolua/9router/pull/298 Thanks to @kwanLeeFrmVi for the original implementation. Here is a summary of changes made during review integration: - Replaced google-auth-library with jose (already a project dependency) for SA JSON -> OAuth2 Bearer token minting (RS256 JWT assertion flow) - Moved auth logic (parseSaJson, refreshVertexToken, token cache) from executor into open-sse/services/tokenRefresh.js to match project pattern - Fixed executor to use proxyAwareFetch instead of raw fetch (proxy support) - Simplified buildUrl: use global aiplatform.googleapis.com endpoint for both vertex (Gemini) and vertex-partner; removed region/modelFamily fields - Added auto-detection of GCP project_id from raw API key via probe request (vertex-partner only, cached per key) - Added vertex/vertex-partner cases to /api/providers/validate/route.js - Updated model lists based on live testing: - vertex: gemini-3.1-pro-preview, gemini-3.1-flash-lite-preview, gemini-3-flash-preview, gemini-2.5-flash (removed gemini-2.5-pro: 404) - vertex-partner: deepseek-v3.2, qwen3-next-80b (instruct+thinking), glm-5 (removed Mistral/Llama: not enabled in test project) - gemini provider: added gemini-3.1-pro-preview, gemini-3.1-flash-lite-preview - Removed bun.lock (project uses npm/package-lock.json) - Removed region and modelFamily UI fields (global endpoint, auto-detect) - Kiro token auto-refresh on AccessDeniedException (from commit 2) Made-with: Cursor
2026-03-14 00:37:23 -04:00
let warning;
try {
const kiroService = new KiroService();
const profileArn = connection.providerSpecificData?.profileArn;
const accessToken = connection.accessToken;
feat: Add Google Cloud Vertex AI provider support (vertex, vertex-partner) Co-authored-by: Quan <quanle96@outlook.com> PR: https://github.com/decolua/9router/pull/298 Thanks to @kwanLeeFrmVi for the original implementation. Here is a summary of changes made during review integration: - Replaced google-auth-library with jose (already a project dependency) for SA JSON -> OAuth2 Bearer token minting (RS256 JWT assertion flow) - Moved auth logic (parseSaJson, refreshVertexToken, token cache) from executor into open-sse/services/tokenRefresh.js to match project pattern - Fixed executor to use proxyAwareFetch instead of raw fetch (proxy support) - Simplified buildUrl: use global aiplatform.googleapis.com endpoint for both vertex (Gemini) and vertex-partner; removed region/modelFamily fields - Added auto-detection of GCP project_id from raw API key via probe request (vertex-partner only, cached per key) - Added vertex/vertex-partner cases to /api/providers/validate/route.js - Updated model lists based on live testing: - vertex: gemini-3.1-pro-preview, gemini-3.1-flash-lite-preview, gemini-3-flash-preview, gemini-2.5-flash (removed gemini-2.5-pro: 404) - vertex-partner: deepseek-v3.2, qwen3-next-80b (instruct+thinking), glm-5 (removed Mistral/Llama: not enabled in test project) - gemini provider: added gemini-3.1-pro-preview, gemini-3.1-flash-lite-preview - Removed bun.lock (project uses npm/package-lock.json) - Removed region and modelFamily UI fields (global endpoint, auto-detect) - Kiro token auto-refresh on AccessDeniedException (from commit 2) Made-with: Cursor
2026-03-14 00:37:23 -04:00
const refreshToken = connection.refreshToken;
if (accessToken && profileArn) {
feat: Add Google Cloud Vertex AI provider support (vertex, vertex-partner) Co-authored-by: Quan <quanle96@outlook.com> PR: https://github.com/decolua/9router/pull/298 Thanks to @kwanLeeFrmVi for the original implementation. Here is a summary of changes made during review integration: - Replaced google-auth-library with jose (already a project dependency) for SA JSON -> OAuth2 Bearer token minting (RS256 JWT assertion flow) - Moved auth logic (parseSaJson, refreshVertexToken, token cache) from executor into open-sse/services/tokenRefresh.js to match project pattern - Fixed executor to use proxyAwareFetch instead of raw fetch (proxy support) - Simplified buildUrl: use global aiplatform.googleapis.com endpoint for both vertex (Gemini) and vertex-partner; removed region/modelFamily fields - Added auto-detection of GCP project_id from raw API key via probe request (vertex-partner only, cached per key) - Added vertex/vertex-partner cases to /api/providers/validate/route.js - Updated model lists based on live testing: - vertex: gemini-3.1-pro-preview, gemini-3.1-flash-lite-preview, gemini-3-flash-preview, gemini-2.5-flash (removed gemini-2.5-pro: 404) - vertex-partner: deepseek-v3.2, qwen3-next-80b (instruct+thinking), glm-5 (removed Mistral/Llama: not enabled in test project) - gemini provider: added gemini-3.1-pro-preview, gemini-3.1-flash-lite-preview - Removed bun.lock (project uses npm/package-lock.json) - Removed region and modelFamily UI fields (global endpoint, auto-detect) - Kiro token auto-refresh on AccessDeniedException (from commit 2) Made-with: Cursor
2026-03-14 00:37:23 -04:00
try {
const models = await kiroService.listAvailableModels(accessToken, profileArn);
return NextResponse.json({
provider: connection.provider,
connectionId: connection.id,
models
});
} catch (error) {
if (error.message.includes("AccessDeniedException") && refreshToken) {
console.log("Kiro token invalid/expired. Attempting refresh...");
const refreshed = await refreshKiroToken(refreshToken, connection.providerSpecificData);
if (refreshed?.accessToken) {
await updateProviderCredentials(connection.id, {
accessToken: refreshed.accessToken,
refreshToken: refreshed.refreshToken || refreshToken,
expiresIn: refreshed.expiresIn,
});
const models = await kiroService.listAvailableModels(refreshed.accessToken, profileArn);
return NextResponse.json({
provider: connection.provider,
connectionId: connection.id,
models
});
}
}
throw error; // Let outer catch handle it
}
}
} catch (error) {
feat: Add Google Cloud Vertex AI provider support (vertex, vertex-partner) Co-authored-by: Quan <quanle96@outlook.com> PR: https://github.com/decolua/9router/pull/298 Thanks to @kwanLeeFrmVi for the original implementation. Here is a summary of changes made during review integration: - Replaced google-auth-library with jose (already a project dependency) for SA JSON -> OAuth2 Bearer token minting (RS256 JWT assertion flow) - Moved auth logic (parseSaJson, refreshVertexToken, token cache) from executor into open-sse/services/tokenRefresh.js to match project pattern - Fixed executor to use proxyAwareFetch instead of raw fetch (proxy support) - Simplified buildUrl: use global aiplatform.googleapis.com endpoint for both vertex (Gemini) and vertex-partner; removed region/modelFamily fields - Added auto-detection of GCP project_id from raw API key via probe request (vertex-partner only, cached per key) - Added vertex/vertex-partner cases to /api/providers/validate/route.js - Updated model lists based on live testing: - vertex: gemini-3.1-pro-preview, gemini-3.1-flash-lite-preview, gemini-3-flash-preview, gemini-2.5-flash (removed gemini-2.5-pro: 404) - vertex-partner: deepseek-v3.2, qwen3-next-80b (instruct+thinking), glm-5 (removed Mistral/Llama: not enabled in test project) - gemini provider: added gemini-3.1-pro-preview, gemini-3.1-flash-lite-preview - Removed bun.lock (project uses npm/package-lock.json) - Removed region and modelFamily UI fields (global endpoint, auto-detect) - Kiro token auto-refresh on AccessDeniedException (from commit 2) Made-with: Cursor
2026-03-14 00:37:23 -04:00
warning = `Failed to fetch Kiro models: ${error.message}`;
console.log("Failed to fetch Kiro models dynamically, falling back to static:", error.message);
}
feat: Add Google Cloud Vertex AI provider support (vertex, vertex-partner) Co-authored-by: Quan <quanle96@outlook.com> PR: https://github.com/decolua/9router/pull/298 Thanks to @kwanLeeFrmVi for the original implementation. Here is a summary of changes made during review integration: - Replaced google-auth-library with jose (already a project dependency) for SA JSON -> OAuth2 Bearer token minting (RS256 JWT assertion flow) - Moved auth logic (parseSaJson, refreshVertexToken, token cache) from executor into open-sse/services/tokenRefresh.js to match project pattern - Fixed executor to use proxyAwareFetch instead of raw fetch (proxy support) - Simplified buildUrl: use global aiplatform.googleapis.com endpoint for both vertex (Gemini) and vertex-partner; removed region/modelFamily fields - Added auto-detection of GCP project_id from raw API key via probe request (vertex-partner only, cached per key) - Added vertex/vertex-partner cases to /api/providers/validate/route.js - Updated model lists based on live testing: - vertex: gemini-3.1-pro-preview, gemini-3.1-flash-lite-preview, gemini-3-flash-preview, gemini-2.5-flash (removed gemini-2.5-pro: 404) - vertex-partner: deepseek-v3.2, qwen3-next-80b (instruct+thinking), glm-5 (removed Mistral/Llama: not enabled in test project) - gemini provider: added gemini-3.1-pro-preview, gemini-3.1-flash-lite-preview - Removed bun.lock (project uses npm/package-lock.json) - Removed region and modelFamily UI fields (global endpoint, auto-detect) - Kiro token auto-refresh on AccessDeniedException (from commit 2) Made-with: Cursor
2026-03-14 00:37:23 -04:00
// Return empty dynamic list so UI falls back to static provider models.
return NextResponse.json({
provider: connection.provider,
connectionId: connection.id,
models: [],
warning,
});
}
if (connection.provider === "gemini-cli") {
const { accessToken, refreshToken } = connection;
if (!accessToken) {
return NextResponse.json({ error: "No valid token found" }, { status: 401 });
}
const projectId = connection.projectId || connection.providerSpecificData?.projectId;
const body = projectId ? { project: projectId } : {};
const fetchModels = async (token) => {
const response = await fetch(GEMINI_CLI_MODELS_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
"User-Agent": "google-api-nodejs-client/9.15.1",
"X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1"
},
body: JSON.stringify(body)
});
return response;
};
let warning;
try {
let response = await fetchModels(accessToken);
// Attempt refresh on 401/403 when refresh token exists
if (!response.ok && (response.status === 401 || response.status === 403) && refreshToken) {
const refreshed = await refreshGoogleToken(refreshToken, GEMINI_CONFIG.clientId, GEMINI_CONFIG.clientSecret);
if (refreshed?.accessToken) {
await updateProviderCredentials(connection.id, {
accessToken: refreshed.accessToken,
refreshToken: refreshed.refreshToken,
expiresIn: refreshed.expiresIn,
});
response = await fetchModels(refreshed.accessToken);
}
}
if (response.ok) {
const data = await response.json();
const models = parseGeminiCliModels(data);
if (models.length > 0) {
return NextResponse.json({
provider: connection.provider,
connectionId: connection.id,
models
});
}
} else {
const errorText = await response.text();
warning = `Failed to fetch Gemini CLI models: ${response.status} ${errorText}`;
console.log("Failed to fetch Gemini CLI models dynamically, falling back to static:", errorText);
}
} catch (error) {
warning = `Failed to fetch Gemini CLI models: ${error.message}`;
console.log("Failed to fetch Gemini CLI models dynamically, falling back to static:", error.message);
}
// Return empty dynamic list so UI falls back to static provider models.
return NextResponse.json({
provider: connection.provider,
connectionId: connection.id,
models: [],
warning,
});
}
if (connection.provider === "ollama-local") {
const url = `${resolveOllamaLocalHost(connection)}/api/tags`;
const response = await fetch(url, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
if (!response.ok) {
const errorText = await response.text();
console.log(`Error fetching models from ollama-local:`, errorText);
return NextResponse.json(
{ error: `Failed to fetch models: ${response.status}` },
{ status: response.status }
);
}
const data = await response.json();
const models = parseOpenAIStyleModels(data);
return NextResponse.json({
provider: connection.provider,
connectionId: connection.id,
models,
});
}
2026-01-04 21:58:59 -05:00
const config = PROVIDER_MODELS_CONFIG[connection.provider];
if (!config) {
return NextResponse.json(
{ error: `Provider ${connection.provider} does not support models listing` },
{ status: 400 }
);
}
// Get auth token
const token = connection.providerSpecificData?.copilotToken || connection.accessToken || connection.apiKey;
2026-01-04 21:58:59 -05:00
if (!token) {
return NextResponse.json({ error: "No valid token found" }, { status: 401 });
}
// Build request URL
let url = config.url;
if (connection.provider === "qwen") {
url = resolveQwenModelsUrl(connection);
}
2026-01-04 21:58:59 -05:00
if (config.authQuery) {
url += `?${config.authQuery}=${token}`;
}
// Build headers
const headers = { ...config.headers };
if (config.authHeader && !config.authQuery) {
headers[config.authHeader] = (config.authPrefix || "") + token;
}
// Make request
const fetchOptions = {
method: config.method,
headers
};
if (config.body && config.method === "POST") {
fetchOptions.body = JSON.stringify(config.body);
}
const response = await fetch(url, fetchOptions);
if (!response.ok) {
const errorText = await response.text();
console.log(`Error fetching models from ${connection.provider}:`, errorText);
return NextResponse.json(
{ error: `Failed to fetch models: ${response.status}` },
{ status: response.status }
);
}
const data = await response.json();
const models = config.parseResponse(data);
return NextResponse.json({
provider: connection.provider,
connectionId: connection.id,
models
});
} catch (error) {
console.log("Error fetching provider models:", error);
return NextResponse.json({ error: "Failed to fetch models" }, { status: 500 });
}
}