@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
241 lines • 12.7 kB
JavaScript
import { resolveModelPrice, } from '../spi/pricing-store.js';
import { awsPartitionOfRegion, bedrockPriceCandidates, builtinBedrockPrice, isBedrockModel, parseBedrockModelId, } from './bedrock.js';
import { lookupModelsDevPricesEach, modelsDevRefsFor, } from './models-dev.js';
/** How long the boot check waits for the models.dev catalog when no `signal` is given. */
export const CATALOG_TIMEOUT_MS = 15_000;
const PREFIX = '[@adonis-agora/agent]';
/**
* Make sure every configured model records a cost, and say so when one will not.
*
* A model needs nothing when its provider reports the real cost (a gateway: OpenRouter, the Vercel AI
* Gateway) — that figure always wins. Every other model is priced off the pricing store, so a model
* with no row there would record `cost_usd = NULL` on every turn, silently. For each such model a row
* is written — only when it has NONE, so a price an operator set is never overwritten — from, in order:
*
* 1. the app's `priceCatalog.prices`;
* 2. for a Bedrock model in GovCloud / China, the built-in table (`BEDROCK_BUILTIN_PRICES`) — never
* models.dev, whose Bedrock prices are the commercial ones;
* 3. models.dev's current list price.
*
* The row is keyed by the exact id the model is configured with (an ARN stays an ARN), which is the id
* a turn reports and is priced under. Gateway models are seeded too, as the estimate a call that comes
* back without a cost falls back to.
*
* A model still unpriced after that gets one warning line naming it, why, and the `priceCatalog`
* entry that fixes it. Never throws: this runs at boot, and pricing is bookkeeping, not a reason for
* the app to stay down.
*/
export async function ensureModelPricing(args) {
const models = dedupe(args.models);
const catalog = args.catalog === false ? undefined : args.catalog;
const seeded = [];
const reasons = new Map();
let current = new Map();
const store = args.pricingStore;
if (store !== undefined) {
let needing;
try {
const rows = await store.listCurrentPrices();
current = new Map(rows.map((row) => [row.modelId, row]));
needing = models.filter((model) => resolveModelPrice(current, model.modelId) === undefined);
}
catch (error) {
args.log.warn(`${PREFIX} Could not read the pricing table at boot: ${messageOf(error)}`);
needing = [];
}
const effectiveFrom = new Date().toISOString();
const write = async (price) => {
if (current.has(price.modelId))
return false;
try {
await store.upsertModelPrice(price);
}
catch (error) {
args.log.warn(`${PREFIX} Could not write the price of ${price.modelId}: ${messageOf(error)}`);
return false;
}
current.set(price.modelId, { ...price, effectiveFrom });
return true;
};
const stillNeeding = () => needing.filter((model) => resolveModelPrice(current, model.modelId) === undefined);
// 1. The app's own prices: every entry gets its row, and a configured model matching one by id.
const appPrices = normalizePriceEntries(catalog?.prices ?? [], args.log);
if (appPrices.size > 0) {
const before = stillNeeding();
for (const price of appPrices.values())
await write(price);
// Keyed by the configured id too, so the id a turn reports finds it by exact match.
for (const model of before) {
const price = matchPrice(appPrices, model.modelId);
if (price !== undefined)
await write({ ...price, modelId: model.modelId });
}
const fromApp = before
.filter((model) => resolveModelPrice(current, model.modelId) !== undefined)
.map((model) => model.modelId);
if (fromApp.length > 0) {
seeded.push(...fromApp);
args.log.info(`${PREFIX} Seeded prices from \`priceCatalog.prices\` for ${fromApp.join(', ')}.`);
}
}
// 2. Bedrock outside the commercial partition: the built-in table, never models.dev.
const region = catalog?.region ?? defaultAwsRegion();
const fromBuiltin = [];
const commercial = [];
for (const model of stillNeeding()) {
const partition = bedrockPartitionOf(model, region);
if (partition === undefined || partition === 'aws') {
commercial.push(model);
continue;
}
const price = builtinBedrockPrice(model.modelId, partition);
if (price !== undefined && (await write(price)))
fromBuiltin.push(model.modelId);
else {
reasons.set(model.modelId, `no price for AWS partition ${partition}${regionNote(model, region)} — models.dev lists commercial Bedrock prices only, and the built-in table has none for it`);
}
}
if (fromBuiltin.length > 0) {
seeded.push(...fromBuiltin);
args.log.info(`${PREFIX} Seeded built-in AWS GovCloud Bedrock prices for ${fromBuiltin.join(', ')} (from the AWS Price List; may go stale). Set \`priceCatalog.prices\` to override.`);
}
// 3. models.dev, for the rest.
if (commercial.length > 0) {
if (catalog === undefined || catalog.modelsDev === false) {
for (const model of commercial) {
reasons.set(model.modelId, 'no price row, and models.dev lookups are off');
}
}
else {
try {
const refs = commercial.map((model) => modelsDevRefsFor(model.modelId, model.provider));
const { prices: _p, region: _r, modelsDev: _m, ...modelsDev } = catalog;
const found = await lookupModelsDevPricesEach(refs,
// Bounded: a hung catalog must not hold a shutdown (which awaits this) open forever.
modelsDev.signal !== undefined
? modelsDev
: { ...modelsDev, signal: AbortSignal.timeout(CATALOG_TIMEOUT_MS) });
const fromModelsDev = [];
for (const [i, model] of commercial.entries()) {
const price = found[i];
if (price === undefined) {
reasons.set(model.modelId, 'unknown model — models.dev does not list a price for it');
}
else if (await write({ ...price, modelId: model.modelId })) {
fromModelsDev.push(model.modelId);
}
}
if (fromModelsDev.length > 0) {
seeded.push(...fromModelsDev);
const bedrock = commercial.some((model) => isBedrockModel(model.modelId, model.provider));
args.log.info(`${PREFIX} Seeded list prices from models.dev for ${fromModelsDev.join(', ')} — an estimate, used when the provider reports no cost.${bedrock
? ' Bedrock rows are COMMERCIAL-region prices: for GovCloud or China set `priceCatalog.region` (or `AWS_REGION`).'
: ''} Set your own with \`priceCatalog.prices\`, \`upsertModelPrice\` to override.`);
}
}
catch (error) {
args.log.warn(`${PREFIX} Could not seed prices from models.dev: ${messageOf(error)}`);
for (const model of commercial)
reasons.set(model.modelId, 'models.dev was unreachable');
}
}
}
}
const unpriced = models
.filter((model) => !model.reportsCost)
.filter((model) => resolveModelPrice(current, model.modelId) === undefined)
.map((model) => model.modelId);
if (unpriced.length > 0) {
// Grouped by reason: `a, b (unknown model …); c (no price for AWS partition …)`.
const groups = new Map();
for (const id of unpriced) {
const reason = reasons.get(id) ?? '';
groups.set(reason, [...(groups.get(reason) ?? []), id]);
}
const named = [...groups]
.map(([reason, ids]) => `${ids.join(', ')}${reason !== '' ? ` (${reason})` : ''}`)
.join('; ');
const example = exampleModelFor(unpriced[0]);
args.log.warn(`${PREFIX} No cost will be recorded for ${named}: the provider reports no cost and ${store === undefined
? 'no `pricingStore` is configured. Configure one, then '
: 'the pricing table has no row for it. '}Add the price to \`priceCatalog.prices\`, e.g. \`{ model: '${example}', input: <USD per 1M>, output: <USD per 1M>, cacheRead?, cacheWrite? }\`, or seed a row with \`seedModelPrices(store, [...])\`, or route through a gateway that reports cost (OpenRouter, Vercel AI Gateway).`);
}
return { seeded, unpriced };
}
/** The partition a Bedrock model runs in: its id says, else the configured region; `undefined` → not Bedrock / unknown. */
function bedrockPartitionOf(model, region) {
if (!isBedrockModel(model.modelId, model.provider))
return undefined;
return parseBedrockModelId(model.modelId)?.partition ?? awsPartitionOfRegion(region);
}
function regionNote(model, region) {
const fromId = parseBedrockModelId(model.modelId);
const r = fromId?.region ?? (fromId?.partition === undefined ? region : undefined);
return r !== undefined ? ` (region ${r})` : '';
}
/** The id to put in the warning's example entry: the shortest id that still prices the model. */
function exampleModelFor(modelId) {
const parsed = parseBedrockModelId(modelId);
return parsed?.isArn ? parsed.profileId : modelId;
}
function defaultAwsRegion() {
const env = globalThis.process
?.env;
const region = env?.AWS_REGION ?? env?.AWS_DEFAULT_REGION;
return region !== undefined && region.length > 0 ? region : undefined;
}
/** A model's app price: by its exact id, then its normalized Bedrock candidates. */
function matchPrice(prices, modelId) {
for (const candidate of bedrockPriceCandidates(modelId)) {
const price = prices.get(candidate);
if (price !== undefined)
return price;
}
return undefined;
}
/** `priceCatalog.prices` as per-1M USD rows, keyed by model. Invalid entries are warned about and skipped. */
export function normalizePriceEntries(entries, log) {
const out = new Map();
const rejected = [];
for (const entry of entries) {
const label = typeof entry?.model === 'string' && entry.model.length > 0 ? entry.model : '?';
const currency = entry?.currency ?? 'USD';
const unit = entry?.unit ?? '1M tokens';
const scale = unit === '1M tokens' ? 1 : unit === '1K tokens' ? 1000 : undefined;
const rates = [entry?.input, entry?.output, entry?.cacheRead, entry?.cacheWrite];
const bad = rates.some((rate, i) => (i < 2 || rate !== undefined) &&
(typeof rate !== 'number' || !Number.isFinite(rate) || rate < 0));
if (label === '?' || currency !== 'USD' || scale === undefined || bad) {
rejected.push(`${label} (${currency !== 'USD'
? `currency ${String(currency)} — only USD is recorded`
: scale === undefined
? `unit ${String(unit)} — use '1M tokens' or '1K tokens'`
: 'needs a model and non-negative numeric input/output'})`);
continue;
}
if (out.has(entry.model))
continue; // first entry for a model wins
out.set(entry.model, {
modelId: entry.model,
inputPricePer1m: entry.input * scale,
outputPricePer1m: entry.output * scale,
...(entry.cacheRead !== undefined ? { cacheReadPricePer1m: entry.cacheRead * scale } : {}),
...(entry.cacheWrite !== undefined ? { cacheWritePricePer1m: entry.cacheWrite * scale } : {}),
});
}
if (rejected.length > 0) {
log?.warn(`${PREFIX} Ignored \`priceCatalog.prices\` entries: ${rejected.join('; ')}.`);
}
return out;
}
function dedupe(models) {
const seen = new Map();
for (const model of models)
if (!seen.has(model.modelId))
seen.set(model.modelId, model);
return [...seen.values()];
}
function messageOf(error) {
return error instanceof Error ? error.message : String(error);
}
//# sourceMappingURL=boot-pricing.js.map