refactor(registry): B2 migrate to LiteLLM-style schema — unified models[] with kind field
- 71 registry files: flat `media.*Config.models` → `models[]` with `kind` field - `media` wrapper removed → serviceKinds, *Config fields promoted top-level - `type` field renamed to `kind` (llm/image/tts/stt/embedding/embedding/video/music) - providers/index.js: PROVIDER_MEDIA now built from flat top-level media fields - shared/constants/providers.js: buildProviderEntry reads flat top-level media fields - route /v1/models: modelKind() uses kind||type; removed subConfig merge block - models/info route: removed sub-config fallback lookup (all models in PROVIDER_MODELS) - ttsProviders/index.js: synthesizeViaConfig reads tts models from PROVIDER_MODELS - test-models route, helpers.js, validate route: kind||type compat - Baselines: PROVIDERS 62/62 ✅, Alias 90/90 ✅ Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -44,14 +44,14 @@ export async function POST(request, { params }) {
|
||||
// Warm up with first model to trigger token refresh (if needed) before parallel calls.
|
||||
// This prevents race condition where multiple requests concurrently refresh the same token.
|
||||
const [first, ...rest] = models;
|
||||
const firstKind = first.type || "llm";
|
||||
const firstKind = first.kind || first.type || "llm";
|
||||
const firstResult = await pingModelByKind(`${alias}/${first.id}`, firstKind, baseUrl);
|
||||
const results = [{ modelId: first.id, name: first.name || first.id, ...firstResult }];
|
||||
|
||||
if (rest.length > 0) {
|
||||
const restResults = await Promise.all(
|
||||
rest.map(async (model) => {
|
||||
const result = await pingModelByKind(`${alias}/${model.id}`, model.type || "llm", baseUrl);
|
||||
const result = await pingModelByKind(`${alias}/${model.id}`, model.kind || model.type || "llm", baseUrl);
|
||||
return { modelId: model.id, name: model.name || model.id, ...result };
|
||||
})
|
||||
);
|
||||
|
||||
@@ -75,7 +75,7 @@ async function probeMediaProvider(provider, apiKey) {
|
||||
const res = await fetch(cfg.baseUrl, {
|
||||
method,
|
||||
headers,
|
||||
body: method === "GET" ? undefined : JSON.stringify({ input: "ping", text: "ping", prompt: "ping", model: cfg.models?.[0]?.id || "test" }),
|
||||
body: method === "GET" ? undefined : JSON.stringify({ input: "ping", text: "ping", prompt: "ping", model: getDefaultModel(provider) || "test" }),
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
return res.status !== 401 && res.status !== 403;
|
||||
|
||||
@@ -52,21 +52,10 @@ function lookup(fullId) {
|
||||
const list = PROVIDER_MODELS[alias] || PROVIDER_MODELS[providerId] || [];
|
||||
const m = list.find((x) => x.id === modelId);
|
||||
if (m) {
|
||||
const kind = m.type || "llm";
|
||||
const kind = m.kind || m.type || "llm";
|
||||
return buildInfo({ alias, providerId, model: m, kind, providerInfo });
|
||||
}
|
||||
|
||||
// Sub-configs (TTS/STT/embedding only-in-config)
|
||||
const subs = [
|
||||
["tts", providerInfo?.ttsConfig],
|
||||
["stt", providerInfo?.sttConfig],
|
||||
["embedding", providerInfo?.embeddingConfig],
|
||||
];
|
||||
for (const [kind, cfg] of subs) {
|
||||
const sm = cfg?.models?.find((x) => x.id === modelId);
|
||||
if (sm) return buildInfo({ alias, providerId, model: sm, kind, providerInfo });
|
||||
}
|
||||
|
||||
// Web search/fetch — virtual model id "search" / "fetch"
|
||||
if (modelId === "search" && providerInfo?.searchConfig) {
|
||||
return buildInfo({
|
||||
|
||||
@@ -59,8 +59,9 @@ const MODEL_TYPE_TO_KIND = {
|
||||
};
|
||||
|
||||
function modelKind(model) {
|
||||
if (!model?.type) return LLM_KIND;
|
||||
return MODEL_TYPE_TO_KIND[model.type] || LLM_KIND;
|
||||
const k = model?.kind || model?.type;
|
||||
if (!k) return LLM_KIND;
|
||||
return MODEL_TYPE_TO_KIND[k] || LLM_KIND;
|
||||
}
|
||||
|
||||
// For dynamic/unknown model IDs (compatible providers, alias map, custom models)
|
||||
@@ -360,29 +361,8 @@ export async function buildModelsList(kindFilter) {
|
||||
});
|
||||
}
|
||||
|
||||
// Merge sub-config models (TTS / embedding) that live on AI_PROVIDERS, not PROVIDER_MODELS
|
||||
const providerInfo = AI_PROVIDERS[providerId];
|
||||
const subConfigModels = [];
|
||||
if (kindFilter.includes("tts") && Array.isArray(providerInfo?.ttsConfig?.models)) {
|
||||
for (const m of providerInfo.ttsConfig.models) {
|
||||
if (m?.id) subConfigModels.push(m.id);
|
||||
}
|
||||
}
|
||||
if (kindFilter.includes("embedding") && Array.isArray(providerInfo?.embeddingConfig?.models)) {
|
||||
for (const m of providerInfo.embeddingConfig.models) {
|
||||
if (m?.id) subConfigModels.push(m.id);
|
||||
}
|
||||
}
|
||||
for (const subId of subConfigModels) {
|
||||
if (isDisabled(outputAlias, subId) || isDisabled(staticAlias, subId)) continue;
|
||||
models.push({
|
||||
id: `${outputAlias}/${subId}`,
|
||||
object: "model",
|
||||
owned_by: outputAlias,
|
||||
});
|
||||
}
|
||||
|
||||
// Web search/fetch — provider IS the model, expose as {alias}/search and/or {alias}/fetch with explicit kind
|
||||
const providerInfo = AI_PROVIDERS[providerId];
|
||||
if (kindFilter.includes("webSearch") && providerInfo?.searchConfig) {
|
||||
models.push({
|
||||
id: `${outputAlias}/search`,
|
||||
|
||||
Reference in New Issue
Block a user