fix(ai): validate generated model data before builds
This commit is contained in:
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user