UNPKG

@adonis-agora/agent

Version:

Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.

145 lines • 7.15 kB
import { bedrockPriceCandidates, isBedrockModel, parseBedrockModelId } from './bedrock.js'; /** O endereço do catálogo. Aberto, sem chave, JSON único. */ export const MODELS_DEV_URL = 'https://models.dev/api.json'; function parseRef(ref) { const slash = ref.indexOf('/'); if (slash <= 0 || slash === ref.length - 1) { throw new Error(`models.dev: "${ref}" não tem a forma "<provider>/<model>" (ex.: "openai/gpt-4o-mini"). ` + 'O provider é obrigatório porque o mesmo nome de modelo existe em provedores diferentes, ' + 'com preços diferentes — adivinhar aqui seria adivinhar uma conta.'); } return { provider: ref.slice(0, slash), model: ref.slice(slash + 1) }; } /** * Busca no [models.dev](https://models.dev) os preços dos modelos pedidos. * * Existe porque a alternativa é alguém copiar números de uma tabela de preços para dentro de um * seeder à mão. Um preço errado ali não quebra nada e não avisa: ele produz uma conta errada no * dashboard, que é pior do que conta nenhuma — quem lê acredita. * * O que ele devolve são `ModelPriceInput` prontos para o `upsertModelPrice`, chaveados pelo NOME * DO MODELO (`gpt-4o-mini`), não pelo `provider/model` pedido. É de propósito: é assim que os * provedores publicam o preço, e a resolução do fold (ver `resolveModelPrice`) já sabe chegar do * snapshot datado que o provider reporta até esse alias. * * FALHA ALTO. Um modelo pedido que não existe no catálogo, ou existe sem preço, vira erro — e não * uma linha faltando em silêncio, que reapareceria depois como `$0.00` num painel. */ async function loadCatalog(options) { const doFetch = options.fetch ?? globalThis.fetch; if (typeof doFetch !== 'function') { throw new Error('models.dev: nenhum `fetch` disponível — passe `options.fetch`.'); } const url = options.url ?? MODELS_DEV_URL; const response = await doFetch(url, options.signal ? { signal: options.signal } : {}); if (!response.ok) { throw new Error(`models.dev: ${url} respondeu ${response.status}`); } return (await response.json()); } /** The price row a catalog entry yields, keyed by the model name — or `undefined` when it has none. */ function priceOf(catalog, ref) { const { provider, model } = parseRef(ref); const cost = catalog[provider]?.models?.[model]?.cost; if (cost === undefined || typeof cost.input !== 'number' || typeof cost.output !== 'number') { return undefined; } return { modelId: model, inputPricePer1m: cost.input, outputPricePer1m: cost.output, ...(typeof cost.cache_write === 'number' ? { cacheWritePricePer1m: cost.cache_write } : {}), ...(typeof cost.cache_read === 'number' ? { cacheReadPricePer1m: cost.cache_read } : {}), }; } /** * The lenient twin of {@link fetchModelsDevPrices}, for boot-time seeding: a model the catalog does * not price is reported in `missing` instead of failing the whole batch, so one unknown model does * not leave every other one unpriced. Throws only when the catalog itself cannot be read. * * Each ref is a LIST of candidates, tried in order — the first one the catalog prices wins. */ export async function lookupModelsDevPrices(refs, options = {}) { const found = await lookupModelsDevPricesEach(refs, options); const prices = []; const missing = []; found.forEach((price, i) => { if (price !== undefined) prices.push(price); else if (refs[i]?.[0] !== undefined) missing.push(refs[i][0]); }); return { prices, missing }; } /** * {@link lookupModelsDevPrices}, one result per element of `refs` (in order): the price the first * pricing candidate yields, or `undefined`. For callers that key the row themselves. */ export async function lookupModelsDevPricesEach(refs, options = {}) { if (refs.every((candidates) => candidates.length === 0)) return refs.map(() => undefined); const catalog = await loadCatalog(options); return refs.map((candidates) => candidates.map((ref) => priceOf(catalog, ref)).find((p) => p !== undefined)); } /** models.dev's name for AWS Bedrock. */ const MODELS_DEV_BEDROCK = 'amazon-bedrock'; /** * The models.dev refs to try for a model, most specific first: `<provider>/<modelId>`, then — for an * OpenRouter-style id (`deepseek/deepseek-v4.1-flash`) reached through another SDK — the OpenRouter * list price. `provider` is the AI SDK provider family (`openrouter`, `openai`, `vercel`, …). * * A Bedrock id is normalized first (see `parseBedrockModelId`): an ARN or a geo-prefixed inference * profile is tried as the full id, the profile id, then the base foundation-model id — all under * `amazon-bedrock`. An ARN is never tried as an OpenRouter id (its `/` is not a vendor separator), nor * looked up verbatim (models.dev lists no ARNs). */ export function modelsDevRefsFor(modelId, provider) { if (isBedrockModel(modelId, provider)) { const ids = bedrockPriceCandidates(modelId).filter((id) => parseBedrockModelId(id)?.isArn !== true); return ids.map((id) => `${MODELS_DEV_BEDROCK}/${id}`); } const refs = []; if (provider !== undefined && provider.length > 0) refs.push(`${provider}/${modelId}`); // A gateway id names its own provider (`openai/gpt-4o-mini` through the Vercel AI Gateway). if (provider === 'vercel' && modelId.includes('/')) refs.push(modelId); if (modelId.includes('/') && provider !== 'openrouter') refs.push(`openrouter/${modelId}`); return refs; } export async function fetchModelsDevPrices(models, options = {}) { if (models.length === 0) return []; const catalog = await loadCatalog(options); const prices = []; for (const ref of models) { const { provider, model } = parseRef(ref); if (catalog[provider]?.models?.[model] === undefined) { throw new Error(`models.dev: não achei "${model}" em "${provider}".`); } const price = priceOf(catalog, ref); if (price === undefined) { throw new Error(`models.dev: "${ref}" existe no catálogo mas não publica preço de input/output.`); } prices.push(price); } return prices; } /** * Busca no models.dev e grava no store. O atalho de uma linha para um comando de seed. * * Deliberadamente NÃO tem retry, cache nem modo silencioso: isto é para rodar num comando de * operador (ou num passo de deploy), onde uma falha deve aparecer e parar, e não para rodar no * caminho de uma request. Preço é dado que muda de mês em mês, não de segundo em segundo — buscar * na hora de cobrar seria trocar uma tabela que você controla por uma dependência de rede de * terceiro no meio da sua contabilidade. */ export async function seedPricesFromModelsDev(store, models, options = {}) { const prices = await fetchModelsDevPrices(models, options); for (const price of prices) { await store.upsertModelPrice(price); } return prices; } //# sourceMappingURL=models-dev.js.map