freellmapi / server /src /scripts /export-catalog.ts
Nryn215's picture
Deploy: Enable and enforce pure ELO-based Intelligence routing strategy
ed57015
Raw
History Blame Contribute Delete
5.17 kB
/**
* export-catalog — build the signed catalog product from the live freeapi DB.
*
* This is the single source of truth pipeline: every model and its resolved
* quirks come straight out of the same SQLite DB the router uses, so the
* published catalog can never drift from what the gateway actually serves.
* The output JSON is what the standalone catalog server (../../catalog) hands
* to clients — fresh ("live") to Premium, a monthly snapshot to free users.
*
* Usage:
* tsx src/scripts/export-catalog.ts [--db <path>] [--out <path>]
* [--version <YYYY.MM.DD>] [--tier live|monthly]
* [--stdout]
*
* Defaults: reads the server's data/freeapi.db, writes the suite's
* catalog/data/catalog.live.json. The catalog server signs it separately
* (catalog/src/scripts/sign-catalog.ts) so the signature covers the exact
* bytes served.
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { initDb, getDb } from '../db/index.js';
import { getAllProviders } from '../providers/index.js';
import { resolveQuirksByModel, quirkKey, listQuirkDefinitions } from '../services/quirks.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SUITE_ROOT = path.resolve(__dirname, '../../../..'); // freeapi/server/src/scripts -> suite root
function arg(name: string): string | undefined {
const i = process.argv.indexOf(`--${name}`);
return i !== -1 ? process.argv[i + 1] : undefined;
}
function flag(name: string): boolean {
return process.argv.includes(`--${name}`);
}
function dateVersion(d = new Date()): string {
const yyyy = d.getUTCFullYear();
const mm = String(d.getUTCMonth() + 1).padStart(2, '0');
const dd = String(d.getUTCDate()).padStart(2, '0');
return `${yyyy}.${mm}.${dd}`;
}
interface ModelRow {
platform: string;
model_id: string;
display_name: string;
intelligence_rank: number;
speed_rank: number;
size_label: string;
rpm_limit: number | null;
rpd_limit: number | null;
tpm_limit: number | null;
tpd_limit: number | null;
monthly_token_budget: string;
context_window: number | null;
enabled: number;
supports_vision: number;
supports_tools: number;
}
function main() {
const dbPath = arg('db');
const tier = (arg('tier') as 'live' | 'monthly' | undefined) ?? 'live';
const version = arg('version') ?? dateVersion();
// In --stdout mode stdout must carry ONLY the JSON document; incidental
// logs (e.g. initDb's "Database initialized at ...") go to stderr instead.
if (flag('stdout')) console.log = console.error;
initDb(dbPath);
const db = getDb();
const models = db
.prepare(
`SELECT platform, model_id, display_name, intelligence_rank, speed_rank, size_label,
rpm_limit, rpd_limit, tpm_limit, tpd_limit, monthly_token_budget, context_window,
enabled, supports_vision, supports_tools
FROM models
ORDER BY intelligence_rank ASC, platform ASC, model_id ASC`,
)
.all() as ModelRow[];
const quirksByModel = resolveQuirksByModel(db);
const quirkDefs = listQuirkDefinitions(db);
// Only platforms that actually have a registered provider AND a model in the
// catalog — skip 'custom' (user-supplied endpoint, nothing to publish).
const livePlatforms = new Set(models.map((m) => m.platform));
const platforms = getAllProviders()
.filter((p) => p.platform !== 'custom' && livePlatforms.has(p.platform))
.map((p) => ({ id: p.platform, name: p.name }))
.sort((a, b) => a.id.localeCompare(b.id));
const catalogModels = models.map((m) => ({
platform: m.platform,
modelId: m.model_id,
displayName: m.display_name,
intelligenceRank: m.intelligence_rank,
speedRank: m.speed_rank,
sizeLabel: m.size_label,
limits: {
rpm: m.rpm_limit,
rpd: m.rpd_limit,
tpm: m.tpm_limit,
tpd: m.tpd_limit,
},
monthlyTokenBudget: m.monthly_token_budget,
contextWindow: m.context_window,
enabled: m.enabled === 1,
supportsVision: m.supports_vision === 1,
supportsTools: m.supports_tools === 1,
quirks: quirksByModel.get(quirkKey(m.platform, m.model_id)) ?? [],
}));
const catalog = {
version,
generatedAt: new Date().toISOString(),
tier,
counts: {
platforms: platforms.length,
models: catalogModels.length,
enabledModels: catalogModels.filter((m) => m.enabled).length,
quirks: quirkDefs.length,
},
platforms,
models: catalogModels,
quirks: quirkDefs,
};
const json = JSON.stringify(catalog, null, 2) + '\n';
if (flag('stdout')) {
process.stdout.write(json);
return;
}
const outPath = arg('out') ?? path.join(SUITE_ROOT, 'catalog', 'data', `catalog.${tier}.json`);
fs.mkdirSync(path.dirname(outPath), { recursive: true });
fs.writeFileSync(outPath, json);
console.log(
`Wrote ${tier} catalog ${version}: ${catalog.counts.models} models ` +
`(${catalog.counts.enabledModels} enabled), ${catalog.counts.platforms} platforms, ` +
`${catalog.counts.quirks} quirks -> ${outPath}`,
);
}
main();