Metadatos de modelo y cálculo de coste
models.ts es la entrada al catálogo de modelos de pi-ai: getModel consulta el registro y devuelve el Model, calculateCost calcula el coste por usage, getSupportedThinkingLevels lista los niveles de thinking soportados por un modelo y clampThinkingLevel ajusta el nivel pedido al valor legal más cercano. Los datos del modelo no están en este archivo, sino en models.generated.ts (autogenerado; esta página no lo desglosa); models.ts sólo consulta y calcula.
Responsabilidades
- Inicialización del catálogo: al cargar el módulo,
MODELS(demodels.generated.ts) se aplana a unMap<provider, Map<id, Model>>. Verpackages/ai/src/models.ts:4-13. - Consultar modelo:
getModel(provider, modelId)devuelve elModelpor provider + id, con genéricos preservando el tipoapi. Verpackages/ai/src/models.ts:20-26. - Listar providers / modelos:
getProviders()ygetModels(provider)para los listados de UI. Verpackages/ai/src/models.ts:28-37. - Cálculo de coste:
calculateCost(model, usage)multiplica precio por millón de tokens × uso, rellena los campos deusage.costy devuelve. Verpackages/ai/src/models.ts:39-46. - Niveles de thinking:
getSupportedThinkingLevelsfiltra los nivelesnullenmodel.thinkingLevelMap;clampThinkingLevelajusta el nivel pedido a un valor legal. Verpackages/ai/src/models.ts:48-80. - Igualdad de modelos:
modelsAreEqualcomparaid+provider, para los cambios de UI. Verpackages/ai/src/models.ts:86-92.
Motivación de diseño
¿Por qué separar models.generated.ts y models.ts? Porque el catálogo de modelos (precios, longitud de contexto, soporte de thinking) es un dato que cambia con frecuencia: el proveedor cambia precios, lanza modelos nuevos, ajusta campos de la API. Aislarlo de la lógica de consulta/cálculo de models.ts permite que el generador corra una vez y produzca datos estáticos, mientras la lógica de consulta se mantiene estable. Esta página no cubre el generador, sólo el lado de consulta.
¿Por qué ModelThinkingLevel usa seis niveles off | minimal | low | medium | high | xhigh? Porque la granularidad del control de thinking varía mucho entre providers: Anthropic da budget tokens, OpenAI Responses da effort, Google da thinking budget. Se abstrae a 6 niveles; thinkingLevelMap mapea cada nivel a parámetros específicos del provider, y null indica "este nivel no está soportado para este modelo". clampThinkingLevel resuelve casos como "se pide high pero el modelo sólo llega a medium": busca hacia abajo el valor legal más cercano, garantizando que la petición no falle.
Archivos clave
packages/ai/src/models.ts:4-13— Inicialización demodelRegistry, Map de dos niveles.packages/ai/src/models.ts:15-18— Tipo condicionalModelApi, infiere el literalapidesdeMODELS.packages/ai/src/models.ts:20-26—getModel, con genérico preservandoapi.packages/ai/src/models.ts:39-46—calculateCost, suma de cuatro tramos de precio de tokens.packages/ai/src/models.ts:48-48— ConstanteEXTENDED_THINKING_LEVELS, orden de los 6 niveles.packages/ai/src/models.ts:50-59—getSupportedThinkingLevels, filtranull.packages/ai/src/models.ts:61-80—clampThinkingLevel, ajuste hacia abajo.packages/ai/src/models.ts:86-92—modelsAreEqual, comparación id + provider.
calculateCost es pura aritmética: precio por tramo dividido por un millón × uso:
// packages/ai/src/models.ts:39-46
export function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage): Usage["cost"] {
usage.cost.input = (model.cost.input / 1000000) * usage.input;
usage.cost.output = (model.cost.output / 1000000) * usage.output;
usage.cost.cacheRead = (model.cost.cacheRead / 1000000) * usage.cacheRead;
usage.cost.cacheWrite = (model.cost.cacheWrite / 1000000) * usage.cacheWrite;
usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
return usage.cost;
}clampThinkingLevel busca primero hacia abajo y luego hacia arriba, garantizando devolver siempre un valor legal:
// packages/ai/src/models.ts:71-79
for (let i = requestedIndex; i < EXTENDED_THINKING_LEVELS.length; i++) {
const candidate = EXTENDED_THINKING_LEVELS[i];
if (availableLevels.includes(candidate)) return candidate;
}
for (let i = requestedIndex - 1; i >= 0; i--) {
const candidate = EXTENDED_THINKING_LEVELS[i];
if (availableLevels.includes(candidate)) return candidate;
}
return availableLevels[0] ?? "off";Flujo de datos
Consulta de modelo y resolución de nivel de thinking:
Límites y fallos
- Modelo inexistente:
getModeldevuelveundefined; el llamador debe gestionarlo. Verpackages/ai/src/models.ts:24-25. - Sin reasoning:
getSupportedThinkingLevelsdevuelve["off"]si!model.reasoning. Verpackages/ai/src/models.ts:51-51. - Caso especial
xhigh: sólo se soporta sithinkingLevelMap.xhigh !== undefined, a diferencia del filtronulldel resto de niveles. Verpackages/ai/src/models.ts:56-57. - Nivel pedido fuera de la lista: cuando
requestedIndex === -1,clampThinkingLeveldevuelveavailableLevels[0] ?? "off". Verpackages/ai/src/models.ts:68-69. calculateCostmuta in place:usage.costse escribe directamente y se devuelve el mismo objeto; el llamador no debe asumir inmutabilidad.
Resumen
models.ts es la capa de consulta/cálculo; los datos del catálogo están en models.generated.ts. getModel consulta, calculateCost calcula coste, y getSupportedThinkingLevels / clampThinkingLevel gestionan el mapeo de niveles de thinking. Una vez obtenido el Model, cómo se pasa al provider en fachada stream/complete; cómo usa Anthropic el thinkingLevelMap en implementación SSE de Anthropic.