fix(ai): validate generated model data before builds

This commit is contained in:
Armin Ronacher
2026-07-20 22:28:48 +02:00
parent ff992261e2
commit c8c3cd499f
14 changed files with 700 additions and 103 deletions
+76 -45
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import { writeFileSync } from "fs";
import { dirname, join } from "path";
import { dirname, join, resolve } from "path";
import { fileURLToPath } from "url";
import type { ImagesModel } from "../src/types.ts";
@@ -10,6 +10,13 @@ const __dirname = dirname(__filename);
const packageRoot = join(__dirname, "..");
const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
function readStrictOption(args: string[]): boolean {
for (const arg of args) {
if (arg !== "--strict") throw new Error(`Unknown argument: ${arg}`);
}
return args.includes("--strict");
}
interface OpenRouterModelRecord {
id: string;
name: string;
@@ -26,52 +33,73 @@ interface OpenRouterModelRecord {
};
}
async function fetchOpenRouterImageModels(): Promise<ImagesModel<"openrouter-images">[]> {
export function parseOpenRouterImageModels(
payload: unknown,
strict: boolean,
): ImagesModel<"openrouter-images">[] {
const data =
typeof payload === "object" && payload !== null
? (payload as { data?: OpenRouterModelRecord[] }).data
: undefined;
if (!Array.isArray(data) || data.length === 0) {
if (strict) throw new Error("OpenRouter API returned a missing or empty image model list");
return [];
}
const models: ImagesModel<"openrouter-images">[] = [];
for (const model of data) {
const input = Array.from(
new Set(
(model.architecture?.input_modalities ?? []).filter(
(modality): modality is "text" | "image" => modality === "text" || modality === "image",
),
),
);
const output = Array.from(
new Set(
(model.architecture?.output_modalities ?? []).filter(
(modality): modality is "text" | "image" => modality === "text" || modality === "image",
),
),
);
if (!output.includes("image")) continue;
if (input.length === 0) input.push("text");
models.push({
id: model.id,
name: model.name,
api: "openrouter-images",
provider: "openrouter",
baseUrl: OPENROUTER_BASE_URL,
input,
output,
cost: {
input: parseFloat(model.pricing?.prompt || "0") * 1_000_000,
output: parseFloat(model.pricing?.completion || "0") * 1_000_000,
cacheRead: parseFloat(model.pricing?.input_cache_read || "0") * 1_000_000,
cacheWrite: parseFloat(model.pricing?.input_cache_write || "0") * 1_000_000,
},
});
}
if (strict && models.length === 0) {
throw new Error("OpenRouter API returned no usable image models");
}
return models;
}
async function fetchOpenRouterImageModels(strict: boolean): Promise<ImagesModel<"openrouter-images">[]> {
try {
console.log("Fetching image models from OpenRouter API...");
const response = await fetch(`${OPENROUTER_BASE_URL}/models?output_modalities=image`);
const data = (await response.json()) as { data?: OpenRouterModelRecord[] };
const models: ImagesModel<"openrouter-images">[] = [];
for (const model of data.data ?? []) {
const input = Array.from(
new Set(
(model.architecture?.input_modalities ?? [])
.filter((modality): modality is "text" | "image" => modality === "text" || modality === "image"),
),
);
const output = Array.from(
new Set(
(model.architecture?.output_modalities ?? []).filter(
(modality): modality is "text" | "image" => modality === "text" || modality === "image",
),
),
);
if (!output.includes("image")) continue;
if (input.length === 0) input.push("text");
models.push({
id: model.id,
name: model.name,
api: "openrouter-images",
provider: "openrouter",
baseUrl: OPENROUTER_BASE_URL,
input,
output,
cost: {
input: parseFloat(model.pricing?.prompt || "0") * 1_000_000,
output: parseFloat(model.pricing?.completion || "0") * 1_000_000,
cacheRead: parseFloat(model.pricing?.input_cache_read || "0") * 1_000_000,
cacheWrite: parseFloat(model.pricing?.input_cache_write || "0") * 1_000_000,
},
});
}
if (!response.ok) throw new Error(`OpenRouter API returned ${response.status}`);
const models = parseOpenRouterImageModels(await response.json(), strict);
console.log(`Fetched ${models.length} image models from OpenRouter`);
return models;
} catch (error) {
console.error("Failed to fetch OpenRouter image models:", error);
if (strict) throw error;
return [];
}
}
@@ -118,14 +146,17 @@ ${providerEntries}
}
async function main(): Promise<void> {
const models = await fetchOpenRouterImageModels();
const strict = readStrictOption(process.argv.slice(2));
const models = await fetchOpenRouterImageModels(strict);
const output = generateImageModelsFile(models);
const outputPath = join(packageRoot, "src", "image-models.generated.ts");
writeFileSync(outputPath, output, "utf-8");
console.log(`Generated ${outputPath}`);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
if (process.argv[1] && resolve(process.argv[1]) === __filename) {
main().catch((error) => {
console.error(error);
process.exit(1);
});
}