export interface PrimeInferenceCatalogEntry {
id: string;
name?: string;
input: number;
output: number;
cacheRead?: number;
cacheWrite?: number;
contextWindow?: number;
maxTokens?: number;
vision?: boolean;
reasoning?: boolean;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function nonNegativeNumber(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
}
function positiveInteger(value: unknown): number | undefined {
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
}
export function isPrivatePrimeInferenceModelId(modelId: string): boolean {
const normalizedId = modelId.toLowerCase();
return normalizedId.startsWith("internal/") || normalizedId.startsWith("dev/") || normalizedId.includes(":");
}
export function parsePrimeInferenceModelCatalog(
value: unknown,
options: { allowEmpty?: boolean } = {},
): PrimeInferenceCatalogEntry[] {
if (!isRecord(value) || !Array.isArray(value.data)) throw new Error("Invalid Prime Inference model catalog");
const models: PrimeInferenceCatalogEntry[] = [];
const seen = new Set<string>();
for (const item of value.data) {
if (!isRecord(item) || typeof item.id !== "string" || !item.id || item.id.length > 1_024) continue;
if (/[\u0000-\u001f\u007f-\u009f]/.test(item.id)) continue;
if (seen.has(item.id)) throw new Error(`Duplicate Prime Inference model ${item.id}`);
const pricing = isRecord(item.pricing) ? item.pricing : {};
const input = nonNegativeNumber(pricing.input_usd_per_mtok);
const output = nonNegativeNumber(pricing.output_usd_per_mtok);
if (input === undefined || output === undefined) continue;
const name =
typeof item.display_name === "string"
? item.display_name.replace(/[\u0000-\u001f\u007f-\u009f]/g, "").trim()
: "";
const specs = isRecord(item.specs) ? item.specs : {};
const modalities = isRecord(specs.modalities) ? specs.modalities : {};
const inputModalities =
Array.isArray(modalities.input) && modalities.input.every((modality) => typeof modality === "string")
? modalities.input
: undefined;
const outputModalities =
Array.isArray(modalities.output) && modalities.output.every((modality) => typeof modality === "string")
? modalities.output
: undefined;
const contextWindow = positiveInteger(specs.context_window);
const maxTokens = positiveInteger(specs.max_output_tokens);
const reasoning = typeof specs.supports_reasoning === "boolean" ? specs.supports_reasoning : undefined;
const hasSpecs =
contextWindow !== undefined &&
maxTokens !== undefined &&
reasoning !== undefined &&
inputModalities !== undefined &&
outputModalities !== undefined;
const cacheRead = nonNegativeNumber(pricing.cache_read_usd_per_mtok);
const cacheWrite = nonNegativeNumber(pricing.cache_write_usd_per_mtok);
seen.add(item.id);
models.push({
id: item.id,
...(name ? { name } : {}),
input,
output,
...(cacheRead !== undefined ? { cacheRead } : {}),
...(cacheWrite !== undefined ? { cacheWrite } : {}),
...(hasSpecs
? {
contextWindow,
maxTokens: Math.min(maxTokens, contextWindow),
vision: inputModalities.includes("image"),
reasoning,
}
: {}),
});
}
if (models.length === 0 && !options.allowEmpty) throw new Error("Prime Inference model catalog is empty");
return models;
}