项目文件夹

文件
2026-07-13 12:52:40 +08:00

158 行
4.6 KiB
TypeScript

#!/usr/bin/env bun
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import type { ModelInfo } from "@cline/shared";
import {
fetchClineRecommendedModelsPayload,
normalizeClineRecommendedProviderModels,
} from "../src/catalog/catalog-cline-recommended";
import { loadModelsDevProviderModels } from "./models/generate-models-dev";
const OUTPUT_FILE = "src/catalog/catalog.generated.ts";
function sortObjectByKey<T>(
input: Record<string, T>,
order: "asc" | "desc" = "asc",
): Record<string, T> {
return Object.fromEntries(
Object.entries(input).sort(([a], [b]) =>
order === "asc" ? a.localeCompare(b) : b.localeCompare(a),
),
);
}
function normalizeValue(value: unknown): unknown {
if (Array.isArray(value)) {
return value.map(normalizeValue);
}
if (value && typeof value === "object") {
const objectValue = value as Record<string, unknown>;
return Object.fromEntries(
Object.entries(objectValue)
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, nestedValue]) => [key, normalizeValue(nestedValue)]),
);
}
return value;
}
async function generate(): Promise<void> {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const root = join(__dirname, "..");
const outputPath = join(root, OUTPUT_FILE);
const providerModels: Record<string, Record<string, ModelInfo>> = {};
let loadError: Error | undefined;
const [modelsDevResult, clineRecommendedPayloadResult] = await Promise.all([
loadModelsDevProviderModels().catch((error) => {
loadError =
error instanceof Error ? error : new Error(String(error ?? "unknown"));
return {};
}),
fetchClineRecommendedModelsPayload(fetch).catch((error) => {
loadError =
error instanceof Error ? error : new Error(String(error ?? "unknown"));
return undefined;
}),
]);
Object.assign(providerModels, modelsDevResult);
if (clineRecommendedPayloadResult) {
Object.assign(
providerModels,
normalizeClineRecommendedProviderModels(
clineRecommendedPayloadResult,
providerModels.openrouter || {},
),
);
}
if (Object.keys(providerModels).length === 0) {
const details = loadError?.message ?? "unknown error";
if (existsSync(outputPath)) {
console.warn(
`Skipping model generation: no provider models were loaded (${details}). Existing generated catalog was left unchanged.`,
);
return;
}
throw new Error(
`Aborting model generation: no provider models were loaded (${details}). No existing generated catalog was found.`,
);
}
const sortedProviders = sortObjectByKey(providerModels);
const generatedModelsVersion = Date.now();
const output = `/**
* Auto-generated model catalog.
*
* Generated by: @cline/llms/scripts/generate-models.ts
*
* WARNING: DO NOT EDIT THIS FILE MANUALLY.
*/
import type { ModelInfo } from "@cline/shared";
/**
* Model list auto-generated for built-in providers
* listed in @cline/llms/src/providers/provider-keys.ts
*/
export const GENERATED_PROVIDER_MODELS: {
version: number
providers: Record<string, Record<string, ModelInfo>>
} = {
version: ${JSON.stringify(generatedModelsVersion)},
providers: ${JSON.stringify(sortedProviders, null, 2)}
}
`;
mkdirSync(dirname(outputPath), { recursive: true });
if (existsSync(outputPath)) {
const nextProviders = normalizeValue(sortedProviders);
let existingProviders: unknown | undefined;
try {
const existingModule = (await import(
"../src/catalog/catalog.generated.js"
)) as {
GENERATED_PROVIDER_MODELS?: {
providers?: Record<string, Record<string, ModelInfo>>;
};
};
existingProviders = existingModule.GENERATED_PROVIDER_MODELS?.providers;
} catch {
existingProviders = undefined;
}
if (
existingProviders &&
JSON.stringify(normalizeValue(existingProviders)) ===
JSON.stringify(nextProviders)
) {
const totalModels = Object.values(sortedProviders).reduce(
(count, models) => count + Object.keys(models).length,
0,
);
console.log(`No changes detected for ${OUTPUT_FILE}`);
console.log(`Providers: ${Object.keys(sortedProviders).length}`);
console.log(`Models: ${totalModels}`);
return;
}
}
writeFileSync(outputPath, output, "utf8");
const totalModels = Object.values(sortedProviders).reduce(
(count, models) => count + Object.keys(models).length,
0,
);
console.log(`Generated ${OUTPUT_FILE}`);
console.log(`Providers: ${Object.keys(sortedProviders).length}`);
console.log(`Models: ${totalModels}`);
}
generate().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
});