From a36aeac072a7f166652d29d522ce3f9b5076d976 Mon Sep 17 00:00:00 2001 From: Shakker Date: Tue, 28 Apr 2026 03:39:52 +0100 Subject: [PATCH] fix: reject incomplete manifest provider catalogs --- .../provider-catalog-shared.test.ts | 35 +++++++++++++++++++ src/plugin-sdk/provider-catalog-shared.ts | 17 +++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/plugin-sdk/provider-catalog-shared.test.ts b/src/plugin-sdk/provider-catalog-shared.test.ts index 79e9d13f180..bcb29e8d9d0 100644 --- a/src/plugin-sdk/provider-catalog-shared.test.ts +++ b/src/plugin-sdk/provider-catalog-shared.test.ts @@ -172,6 +172,7 @@ describe("provider-catalog-shared manifest provider configs", () => { buildManifestModelProviderConfig({ providerId: "example", catalog: { + baseUrl: "https://api.example.test/v1", models: [ { id: "missing-context", @@ -182,4 +183,38 @@ describe("provider-catalog-shared manifest provider configs", () => { }), ).toThrow("missing contextWindow"); }); + + it("rejects catalog data that cannot become runtime provider config", () => { + expect(() => + buildManifestModelProviderConfig({ + providerId: "example", + catalog: { + models: [ + { + id: "missing-base-url", + contextWindow: 1024, + maxTokens: 1024, + }, + ], + }, + }), + ).toThrow("providers.example.baseUrl"); + + expect(() => + buildManifestModelProviderConfig({ + providerId: "example", + catalog: { + baseUrl: "https://api.example.test/v1", + models: [ + { + id: "document-model", + input: ["document"], + contextWindow: 1024, + maxTokens: 1024, + }, + ], + }, + }), + ).toThrow("unsupported runtime input document"); + }); }); diff --git a/src/plugin-sdk/provider-catalog-shared.ts b/src/plugin-sdk/provider-catalog-shared.ts index 47f21ba082b..51676f3b953 100644 --- a/src/plugin-sdk/provider-catalog-shared.ts +++ b/src/plugin-sdk/provider-catalog-shared.ts @@ -56,6 +56,15 @@ function cloneManifestCatalogCost(cost: ModelCatalogCost): ModelDefinitionConfig }; } +function buildManifestCatalogModelInput(model: ModelCatalogModel): ModelDefinitionConfig["input"] { + if (model.input?.includes("document")) { + throw new Error( + `Manifest modelCatalog row ${model.id} uses unsupported runtime input document`, + ); + } + return model.input ?? ["text"]; +} + function buildManifestCatalogModel(model: ModelCatalogModel): ModelDefinitionConfig { if (model.contextWindow === undefined) { throw new Error(`Manifest modelCatalog row ${model.id} is missing contextWindow`); @@ -63,14 +72,13 @@ function buildManifestCatalogModel(model: ModelCatalogModel): ModelDefinitionCon if (model.maxTokens === undefined) { throw new Error(`Manifest modelCatalog row ${model.id} is missing maxTokens`); } - const input = model.input?.filter((item) => item !== "document") ?? ["text"]; return { id: model.id, name: model.name ?? model.id, ...(model.api ? { api: model.api } : {}), ...(model.baseUrl ? { baseUrl: model.baseUrl } : {}), reasoning: model.reasoning ?? false, - input: input.length > 0 ? input : ["text"], + input: buildManifestCatalogModelInput(model), cost: cloneManifestCatalogCost(model.cost ?? {}), contextWindow: model.contextWindow, ...(model.contextTokens !== undefined ? { contextTokens: model.contextTokens } : {}), @@ -87,8 +95,11 @@ export function buildManifestModelProviderConfig(params: { if (!params.catalog) { throw new Error(`Missing modelCatalog.providers.${params.providerId}`); } + if (!params.catalog.baseUrl) { + throw new Error(`Missing modelCatalog.providers.${params.providerId}.baseUrl`); + } return { - baseUrl: params.catalog.baseUrl ?? "", + baseUrl: params.catalog.baseUrl, ...(params.catalog.api ? { api: params.catalog.api } : {}), ...(params.catalog.headers ? { headers: { ...params.catalog.headers } } : {}), models: params.catalog.models.map(buildManifestCatalogModel),