mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
Replace the single 3300-line generate-models.ts with declarative provider definitions and ordered rule packs under scripts/model-generation/. Each provider is a defineProvider() call: an upstream source (usually the generic modelsDev() mapper), static models upstream lacks, and rules scoped to that provider. Cross-provider facts live in rule packs applied in a fixed order. Every rule() carries a why, matches models with plain data, and patches them with typed set/replace/apply semantics. Generated model data is unchanged: 1443 models across 41 providers compare equal against a snapshot of the same upstream responses, apart from a now stable key order. Committed models.generated.ts and provider shards are byte-identical. Also: - Bring packages/ai/scripts under tsgo and biome; the old generator had 20+ type errors that nothing checked. - Add --upstream-snapshot <dir> to record and replay upstream catalogs and --explain <provider>:<model> to list the rules applied to a model. - Report rules that matched no model so stale corrections get removed. - Export parseRadiusGatewayConfig so the generator can replay Radius too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ExCU9zaZnN8vB5fKpTAjP
115 lines
4.5 KiB
TypeScript
115 lines
4.5 KiB
TypeScript
import type { Api, ModelCost } from "../../src/types.ts";
|
|
import type { CompatPatch, GeneratedModel, ModelSource } from "./dsl.ts";
|
|
import type { ModelsDevModel } from "./sources.ts";
|
|
|
|
/** Per-model transport overrides resolved from the upstream entry. */
|
|
export interface ModelsDevRoute {
|
|
api?: Api;
|
|
baseUrl?: string;
|
|
compat?: CompatPatch;
|
|
headers?: Record<string, string>;
|
|
}
|
|
|
|
export interface ModelsDevSourceOptions {
|
|
/** Provider id in the generated catalog. */
|
|
provider: string;
|
|
/** models.dev provider key(s); the first key present in the catalog is used. */
|
|
key: string | readonly string[];
|
|
api: Api;
|
|
baseUrl: string | ((id: string, entry: ModelsDevModel) => string);
|
|
compat?: CompatPatch;
|
|
headers?: Record<string, string>;
|
|
/** Skip entries with `status: "deprecated"`. */
|
|
skipDeprecated?: boolean;
|
|
/** Extra filter beyond the always-applied `tool_call` check. Receives the models.dev id. */
|
|
include?: (id: string, entry: ModelsDevModel, models: Readonly<Record<string, ModelsDevModel>>) => boolean;
|
|
/** Rename an entry. Return `undefined` to skip it. Receives the models.dev id. */
|
|
mapId?: (id: string, entry: ModelsDevModel) => string | undefined;
|
|
/** Per-model api, baseUrl, compat, or headers. Receives the models.dev id. */
|
|
route?: (id: string, entry: ModelsDevModel) => ModelsDevRoute | undefined;
|
|
/**
|
|
* Entries whose capabilities, limits, and prices come from another entry of the same
|
|
* provider (`{ alias: target }`). The alias keeps its own id and name.
|
|
*/
|
|
aliases?: Readonly<Record<string, string>>;
|
|
/** Fallbacks when models.dev omits limits. Default: 4096 for both. */
|
|
defaults?: { contextWindow?: number; maxTokens?: number };
|
|
/** Import models.dev context-size price tiers as `cost.tiers`. */
|
|
costTiers?: boolean;
|
|
}
|
|
|
|
export function getModelsDevCost(cost: ModelsDevModel["cost"], includeTiers = false): ModelCost {
|
|
const tiers = includeTiers
|
|
? cost?.tiers?.flatMap((tier) => {
|
|
const context = tier.tier;
|
|
if (context?.type !== "context" || context.size === undefined) return [];
|
|
return [
|
|
{
|
|
inputTokensAbove: context.size,
|
|
input: tier.input || 0,
|
|
output: tier.output || 0,
|
|
cacheRead: tier.cache_read || 0,
|
|
cacheWrite: tier.cache_write || 0,
|
|
},
|
|
];
|
|
})
|
|
: undefined;
|
|
|
|
return {
|
|
input: cost?.input || 0,
|
|
output: cost?.output || 0,
|
|
cacheRead: cost?.cache_read || 0,
|
|
cacheWrite: cost?.cache_write || 0,
|
|
...(tiers && tiers.length > 0 ? { tiers } : {}),
|
|
};
|
|
}
|
|
|
|
export function getModelsDevInput(entry: ModelsDevModel): GeneratedModel["input"] {
|
|
return entry.modalities?.input?.includes("image") ? ["text", "image"] : ["text"];
|
|
}
|
|
|
|
/** Maps tool-capable models.dev entries of one provider onto pi's `Model` shape. */
|
|
export function modelsDev(options: ModelsDevSourceOptions): ModelSource {
|
|
return async (context) => {
|
|
const catalog = await context.upstream.modelsDev();
|
|
const keys = Array.isArray(options.key) ? (options.key as readonly string[]) : [options.key as string];
|
|
const key = keys.find((candidate) => catalog[candidate]?.models);
|
|
const models = key ? (catalog[key]?.models ?? {}) : {};
|
|
const generated: GeneratedModel[] = [];
|
|
|
|
for (const [sourceId, entry] of Object.entries(models)) {
|
|
if (entry.tool_call !== true) continue;
|
|
if (options.skipDeprecated && entry.status === "deprecated") continue;
|
|
if (options.include && !options.include(sourceId, entry, models)) continue;
|
|
const id = options.mapId ? options.mapId(sourceId, entry) : sourceId;
|
|
if (id === undefined) continue;
|
|
|
|
const aliasTarget = options.aliases?.[sourceId];
|
|
const data = aliasTarget ? (models[aliasTarget] ?? entry) : entry;
|
|
const route = options.route?.(sourceId, entry);
|
|
const compat = route?.compat ?? options.compat;
|
|
const headers = route?.headers ?? options.headers;
|
|
const baseUrl =
|
|
route?.baseUrl ?? (typeof options.baseUrl === "string" ? options.baseUrl : options.baseUrl(id, entry));
|
|
|
|
const model: GeneratedModel = {
|
|
id,
|
|
name: entry.name || id,
|
|
api: route?.api ?? options.api,
|
|
provider: options.provider,
|
|
baseUrl,
|
|
reasoning: data.reasoning === true,
|
|
input: getModelsDevInput(data),
|
|
cost: getModelsDevCost(data.cost, options.costTiers === true),
|
|
contextWindow: data.limit?.context || options.defaults?.contextWindow || 4096,
|
|
maxTokens: data.limit?.output || options.defaults?.maxTokens || 4096,
|
|
...(headers ? { headers: { ...headers } } : {}),
|
|
...(compat ? { compat: { ...compat } as GeneratedModel["compat"] } : {}),
|
|
};
|
|
context.attachSource(model, sourceId, data);
|
|
generated.push(model);
|
|
}
|
|
|
|
return generated;
|
|
};
|
|
}
|