fix: show custom vision models in LLM selector and model list

Expose user-added imageToText custom models as vision-capable chat
models in the default LLM selector and /v1/models, map custom service
kinds to runtime capabilities, and keep typed filtering for
/v1/models/{kind}.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Sutarto Jordan Chrisfivo
2026-06-18 09:13:27 +07:00
committed by decolua
co-authored by Cursor
parent 047fdc8960
commit 5e5e78d3e8
4 changed files with 57 additions and 8 deletions
+23 -7
View File
@@ -9,6 +9,7 @@ import { getProviderConnections, getCombos, getCustomModels, getModelAliases } f
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";
// Per-provider live model resolvers. Each receives a connection record and
// returns { models: [{ id, name? }, ...] } | null on failure.
@@ -314,13 +315,22 @@ export async function buildModelsList(kindFilter) {
})
.filter((modelId) => typeof modelId === "string" && modelId.trim() !== "");
const customModelKindById = new Map();
const customModelIds = customModels
.filter((m) => {
if (!m?.id || (getModelKind(m) && getModelKind(m) !== "llm")) return false;
if (!m?.id) return false;
const kind = getModelKind(m) || LLM_KIND;
// imageToText custom models are vision-capable chat models: expose them
// both in the default LLM list and in /v1/models/image-to-text.
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) => String(m.id).trim())
.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 || {})
@@ -349,16 +359,22 @@ export async function buildModelsList(kindFilter) {
const mergedModelIds = Array.from(new Set([...modelIds, ...customModelIds, ...aliasModelIds]));
for (const modelId of mergedModelIds) {
// Resolve kind: prefer static metadata, otherwise infer from ID heuristics
const kind = staticModelKindById.get(modelId) || inferKindFromUnknownModelId(modelId);
if (!kindFilter.includes(kind)) continue;
// Resolve kind: prefer static/custom metadata, otherwise infer from ID heuristics
const customKind = customModelKindById.get(modelId);
const kind = staticModelKindById.get(modelId) || customKind || inferKindFromUnknownModelId(modelId);
// imageToText custom models stay in the LLM list (vision-capable chat models)
const allowAsLlm = kind === "imageToText" && kindFilter.includes(LLM_KIND);
if (!kindFilter.includes(kind) && !allowAsLlm) continue;
if (isDisabled(outputAlias, modelId) || isDisabled(staticAlias, modelId)) continue;
models.push({
const model = {
id: `${outputAlias}/${modelId}`,
object: "model",
owned_by: outputAlias,
});
};
const caps = capabilitiesFromServiceKind(customKind);
if (caps) model.capabilities = caps;
models.push(model);
}
// Web search/fetch — provider IS the model, expose as {alias}/search and/or {alias}/fetch with explicit kind
+4 -1
View File
@@ -128,7 +128,10 @@ export default function ModelSelectModal({
// Filter a models[] array by kindFilter (keep only matching kind)
const filterByKind = (models) => {
if (!kindFilter) return models.filter((m) => m.isPlaceholder || !getModelKind(m) || getModelKind(m) === "llm");
// No kindFilter means the LLM selector. Keep custom models visible because
// user-added models may have typed capabilities (for example imageToText)
// while still being valid chat/combo targets.
if (!kindFilter) return models.filter((m) => m.isPlaceholder || m.isCustom || !getModelKind(m) || getModelKind(m) === "llm");
if (!TYPED_KINDS.has(kindFilter)) return models;
return models.filter((m) => m.isPlaceholder || getModelKind(m) === kindFilter);
};