diff --git a/supplier/src/surplus/client.ts b/supplier/src/surplus/client.ts index 6a8495d..8ba9b85 100644 --- a/supplier/src/surplus/client.ts +++ b/supplier/src/surplus/client.ts @@ -380,16 +380,38 @@ function parseOffer(value: unknown, index: number): SurplusOffer { function parseDiscoveredModel(value: unknown, index: number): SurplusDiscoveredModel { const item = record(value, `Surplus discovery item ${index}`); - const pricing = record(item.pricing, `Surplus discovery item ${index}.pricing`); - const metadata = record(item.metadata, `Surplus discovery item ${index}.metadata`); + const model = string(item.model, `Surplus discovery item ${index}.model`); + const supported = boolean( + item.supported, + `Surplus discovery item ${index}.supported`, + ); + const pricing = optionalRecord(item.pricing); + const metadata = optionalRecord(item.metadata) ?? {}; return { - model: string(item.model, `Surplus discovery item ${index}.model`), - supported: boolean(item.supported, `Surplus discovery item ${index}.supported`), - inputUsdPer1m: nonNegativeNumber(pricing.input_per_1m, `Surplus discovery item ${index}.pricing.input_per_1m`), - outputUsdPer1m: nonNegativeNumber(pricing.output_per_1m, `Surplus discovery item ${index}.pricing.output_per_1m`), - priceUnit: optionalString(pricing.price_unit) ?? "M", - priceVariable: pricing.price_variable === undefined ? false : boolean(pricing.price_variable, `Surplus discovery item ${index}.pricing.price_variable`), - providerModelId: string(metadata.provider_model_id, `Surplus discovery item ${index}.metadata.provider_model_id`), + model, + supported, + inputUsdPer1m: pricing + ? optionalNonNegativeNumber( + pricing.input_per_1m, + `Surplus discovery item ${index}.pricing.input_per_1m`, + ) ?? 0 + : 0, + outputUsdPer1m: pricing + ? optionalNonNegativeNumber( + pricing.output_per_1m, + `Surplus discovery item ${index}.pricing.output_per_1m`, + ) ?? 0 + : 0, + priceUnit: pricing + ? optionalString(pricing.price_unit) ?? "M" + : "unknown", + priceVariable: pricing?.price_variable === undefined + ? false + : boolean( + pricing.price_variable, + `Surplus discovery item ${index}.pricing.price_variable`, + ), + providerModelId: optionalString(metadata.provider_model_id) ?? "", modelType: optionalString(metadata.model_type) ?? "unknown", availabilityStatus: optionalString(metadata.availability_status) ?? "unknown", @@ -446,6 +468,14 @@ function record(value: unknown, field: string): Record { return value as Record; } + +function optionalRecord(value: unknown): Record | null { + if (value === null || value === undefined) return null; + if (typeof value !== "object" || Array.isArray(value)) { + throw new Error("Optional Surplus object has the wrong type"); + } + return value as Record; +} function string(value: unknown, field: string): string { if (typeof value !== "string" || value === "") throw new Error(`${field} must be a non-empty string`); return value; diff --git a/supplier/src/surplus/selection.ts b/supplier/src/surplus/selection.ts index 0de5632..10baaff 100644 --- a/supplier/src/surplus/selection.ts +++ b/supplier/src/surplus/selection.ts @@ -33,6 +33,7 @@ export function selectSurplusModelCandidates( if ( !market || !model.supported || + model.providerModelId === "" || model.availabilityStatus !== "available" || model.modelType !== "text" || model.priceUnit !== "M" || diff --git a/tests/unit/surplus-client.test.ts b/tests/unit/surplus-client.test.ts index 39c5a2e..a75e2cc 100644 --- a/tests/unit/surplus-client.test.ts +++ b/tests/unit/surplus-client.test.ts @@ -71,6 +71,35 @@ describe("SurplusClient", () => { ]); }); + it("normalizes unsupported discovery rows with null pricing as ineligible", async () => { + const client = new SurplusClient({ + apiBaseUrl: "https://api.surplusintelligence.ai", + sellerApiKey: SELLER_KEY, + timeoutMs: 1_000, + fetchFn: async () => ndjsonResponse([JSON.stringify({ + model: "unsupported", + supported: false, + pricing: null, + metadata: { + provider_model_id: "vendor/unsupported", + model_type: null, + availability_status: null, + }, + })]), + }); + + await expect( + client.discoverModels(PROVIDER_KEY, "https://openrouter.ai/api/v1"), + ).resolves.toEqual([expect.objectContaining({ + model: "unsupported", + inputUsdPer1m: 0, + outputUsdPer1m: 0, + priceUnit: "unknown", + modelType: "unknown", + availabilityStatus: "unknown", + })]); + }); + it("rejects one malformed discovery line without returning a partial catalog", async () => { const client = new SurplusClient({ apiBaseUrl: "https://api.surplusintelligence.ai",