


















@@ -16,11 +16,13 @@ import {
1616type ModelCatalogInput,
1717type ModelCatalogMediaInputConfig,
1818type ModelCatalogModel,
19+type ModelCatalogOpenRouterRouting,
1920type ModelCatalogProvider,
2021type ModelCatalogSource,
2122type ModelCatalogStatus,
2223type ModelCatalogSuppression,
2324type ModelCatalogTieredCost,
25+type ModelCatalogVercelGatewayRouting,
2426type NormalizedModelCatalogRow,
2527} from "./model-catalog-types.js";
2628@@ -119,6 +121,14 @@ function normalizeNonNegativeNumber(value: unknown): number | undefined {
119121return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
120122}
121123124+function normalizeFiniteNumber(value: unknown): number | undefined {
125+return typeof value === "number" && Number.isFinite(value) ? value : undefined;
126+}
127+128+function normalizeStringOrNumber(value: unknown): string | number | undefined {
129+return normalizeOptionalString(value) ?? normalizeFiniteNumber(value);
130+}
131+122132function normalizePositiveNumber(value: unknown): number | undefined {
123133return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
124134}
@@ -187,6 +197,144 @@ function normalizeModelCatalogCost(value: unknown): ModelCatalogCost | undefined
187197return Object.keys(cost).length > 0 ? cost : undefined;
188198}
189199200+function normalizeOpenRouterPrice(value: unknown): ModelCatalogOpenRouterRouting["max_price"] {
201+if (!isRecord(value)) {
202+return undefined;
203+}
204+const maxPrice = {
205+ ...(normalizeStringOrNumber(value.prompt) !== undefined
206+ ? { prompt: normalizeStringOrNumber(value.prompt) }
207+ : {}),
208+ ...(normalizeStringOrNumber(value.completion) !== undefined
209+ ? { completion: normalizeStringOrNumber(value.completion) }
210+ : {}),
211+ ...(normalizeStringOrNumber(value.image) !== undefined
212+ ? { image: normalizeStringOrNumber(value.image) }
213+ : {}),
214+ ...(normalizeStringOrNumber(value.audio) !== undefined
215+ ? { audio: normalizeStringOrNumber(value.audio) }
216+ : {}),
217+ ...(normalizeStringOrNumber(value.request) !== undefined
218+ ? { request: normalizeStringOrNumber(value.request) }
219+ : {}),
220+} satisfies NonNullable<ModelCatalogOpenRouterRouting["max_price"]>;
221+return Object.keys(maxPrice).length > 0 ? maxPrice : undefined;
222+}
223+224+function normalizeOpenRouterPercentileCutoffs(
225+value: unknown,
226+):
227+| NonNullable<Exclude<ModelCatalogOpenRouterRouting["preferred_min_throughput"], number>>
228+| undefined {
229+if (!isRecord(value)) {
230+return undefined;
231+}
232+const normalized = {
233+ ...(normalizeFiniteNumber(value.p50) !== undefined
234+ ? { p50: normalizeFiniteNumber(value.p50) }
235+ : {}),
236+ ...(normalizeFiniteNumber(value.p75) !== undefined
237+ ? { p75: normalizeFiniteNumber(value.p75) }
238+ : {}),
239+ ...(normalizeFiniteNumber(value.p90) !== undefined
240+ ? { p90: normalizeFiniteNumber(value.p90) }
241+ : {}),
242+ ...(normalizeFiniteNumber(value.p99) !== undefined
243+ ? { p99: normalizeFiniteNumber(value.p99) }
244+ : {}),
245+};
246+return Object.keys(normalized).length > 0 ? normalized : undefined;
247+}
248+249+function normalizeOpenRouterMetricPreference(
250+value: unknown,
251+): ModelCatalogOpenRouterRouting["preferred_min_throughput"] {
252+return normalizeFiniteNumber(value) ?? normalizeOpenRouterPercentileCutoffs(value);
253+}
254+255+function normalizeOpenRouterSort(value: unknown): ModelCatalogOpenRouterRouting["sort"] {
256+const sort = normalizeOptionalString(value);
257+if (sort) {
258+return sort;
259+}
260+if (!isRecord(value)) {
261+return undefined;
262+}
263+const by = normalizeOptionalString(value.by);
264+const partition =
265+value.partition === null ? null : (normalizeOptionalString(value.partition) ?? undefined);
266+const normalized = {
267+ ...(by ? { by } : {}),
268+ ...(partition !== undefined ? { partition } : {}),
269+} satisfies NonNullable<Exclude<ModelCatalogOpenRouterRouting["sort"], string>>;
270+return Object.keys(normalized).length > 0 ? normalized : undefined;
271+}
272+273+function normalizeOpenRouterRouting(value: unknown): ModelCatalogOpenRouterRouting | undefined {
274+if (!isRecord(value)) {
275+return undefined;
276+}
277+const routing = {
278+ ...(typeof value.allow_fallbacks === "boolean"
279+ ? { allow_fallbacks: value.allow_fallbacks }
280+ : {}),
281+ ...(typeof value.require_parameters === "boolean"
282+ ? { require_parameters: value.require_parameters }
283+ : {}),
284+ ...(value.data_collection === "deny" || value.data_collection === "allow"
285+ ? { data_collection: value.data_collection }
286+ : {}),
287+ ...(typeof value.zdr === "boolean" ? { zdr: value.zdr } : {}),
288+ ...(typeof value.enforce_distillable_text === "boolean"
289+ ? { enforce_distillable_text: value.enforce_distillable_text }
290+ : {}),
291+ ...(normalizeOptionalTrimmedStringList(value.order)
292+ ? { order: normalizeOptionalTrimmedStringList(value.order) }
293+ : {}),
294+ ...(normalizeOptionalTrimmedStringList(value.only)
295+ ? { only: normalizeOptionalTrimmedStringList(value.only) }
296+ : {}),
297+ ...(normalizeOptionalTrimmedStringList(value.ignore)
298+ ? { ignore: normalizeOptionalTrimmedStringList(value.ignore) }
299+ : {}),
300+ ...(normalizeOptionalTrimmedStringList(value.quantizations)
301+ ? { quantizations: normalizeOptionalTrimmedStringList(value.quantizations) }
302+ : {}),
303+ ...(normalizeOpenRouterSort(value.sort) ? { sort: normalizeOpenRouterSort(value.sort) } : {}),
304+ ...(normalizeOpenRouterPrice(value.max_price)
305+ ? { max_price: normalizeOpenRouterPrice(value.max_price) }
306+ : {}),
307+ ...(normalizeOpenRouterMetricPreference(value.preferred_min_throughput) !== undefined
308+ ? {
309+preferred_min_throughput: normalizeOpenRouterMetricPreference(
310+value.preferred_min_throughput,
311+),
312+}
313+ : {}),
314+ ...(normalizeOpenRouterMetricPreference(value.preferred_max_latency) !== undefined
315+ ? { preferred_max_latency: normalizeOpenRouterMetricPreference(value.preferred_max_latency) }
316+ : {}),
317+} satisfies ModelCatalogOpenRouterRouting;
318+return Object.keys(routing).length > 0 ? routing : undefined;
319+}
320+321+function normalizeVercelGatewayRouting(
322+value: unknown,
323+): ModelCatalogVercelGatewayRouting | undefined {
324+if (!isRecord(value)) {
325+return undefined;
326+}
327+const routing = {
328+ ...(normalizeOptionalTrimmedStringList(value.only)
329+ ? { only: normalizeOptionalTrimmedStringList(value.only) }
330+ : {}),
331+ ...(normalizeOptionalTrimmedStringList(value.order)
332+ ? { order: normalizeOptionalTrimmedStringList(value.order) }
333+ : {}),
334+} satisfies ModelCatalogVercelGatewayRouting;
335+return Object.keys(routing).length > 0 ? routing : undefined;
336+}
337+190338function normalizeModelCatalogCompat(value: unknown): ModelCatalogCompatConfig | undefined {
191339if (!isRecord(value)) {
192340return undefined;
@@ -205,6 +353,11 @@ function normalizeModelCatalogCompat(value: unknown): ModelCatalogCompatConfig |
205353"requiresToolResultName",
206354"requiresAssistantAfterToolResult",
207355"requiresThinkingAsText",
356+"zaiToolStream",
357+"sendSessionAffinityHeaders",
358+"sendSessionIdHeader",
359+"supportsEagerToolInputStreaming",
360+"supportsLongCacheRetention",
208361"nativeWebSearchTool",
209362"requiresMistralToolIds",
210363"requiresOpenAiAnthropicToolPayload",
@@ -256,6 +409,20 @@ function normalizeModelCatalogCompat(value: unknown): ModelCatalogCompatConfig |
256409compat.thinkingFormat = thinkingFormat;
257410}
258411412+if (value.cacheControlFormat === "anthropic") {
413+compat.cacheControlFormat = "anthropic";
414+}
415+416+const openRouterRouting = normalizeOpenRouterRouting(value.openRouterRouting);
417+if (openRouterRouting) {
418+compat.openRouterRouting = openRouterRouting;
419+}
420+421+const vercelGatewayRouting = normalizeVercelGatewayRouting(value.vercelGatewayRouting);
422+if (vercelGatewayRouting) {
423+compat.vercelGatewayRouting = vercelGatewayRouting;
424+}
425+259426return Object.keys(compat).length > 0 ? (compat as ModelCatalogCompatConfig) : undefined;
260427}
261428此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。