@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
162 lines • 7.4 kB
JavaScript
import { estimateCost, resolveModelPrice, } from '../spi/pricing-store.js';
import { awsPartitionOfRegion, bedrockPriceCandidates, builtinBedrockPrice, isBedrockModel, parseBedrockModelId, } from './bedrock.js';
import { CATALOG_TIMEOUT_MS, ensureModelPricing, normalizePriceEntries, } from './boot-pricing.js';
import { lookupModelsDevPricesEach, modelsDevRefsFor } from './models-dev.js';
/**
* The cost a usage row persists: the provider's figure (`'provider'`), else the estimate off the
* model's price row (`'estimate'`), else nothing (`cost_usd` NULL). The one rule every engine
* records usage by, so the ledger, a USD quota and the message carry the same figure.
*/
export function resolveUsageCost(usage, reportedCostUsd, price) {
if (reportedCostUsd !== undefined)
return { costUsd: reportedCostUsd, costSource: 'provider' };
if (price === undefined)
return {};
return { costUsd: estimateCost(usage, { ...price, effectiveFrom: '' }), costSource: 'estimate' };
}
const DEFAULT_CACHE_MS = 60_000;
/**
* The price of a model an engine only learns about at run time (OpenCode reports the model each step
* ran on), resolved the way boot pricing does it for configured models:
*
* 1. the pricing store's current row (by the id, then its normalized Bedrock candidates);
* 2. once per model and process, the boot seed (`ensureModelPricing`): the app's
* `priceCatalog.prices`, then the built-in GovCloud Bedrock table, then models.dev — written to
* the store as a row, so the dashboard and a later run read the same price;
* 3. without a pricing store, the same three sources, kept in memory.
*
* Never throws: pricing is bookkeeping, and an unpriced step is recorded without a cost.
*/
export class ModelPriceResolver {
options;
rows;
rowsAt = 0;
seeded = new Map();
constructor(options = {}) {
this.options = options;
}
async priceFor(model) {
const ids = [...new Set([model.modelId, ...(model.aliases ?? [])])].filter(Boolean);
try {
const found = matchIn(await this.currentRows(), ids);
if (found !== undefined)
return found;
const key = `${model.provider ?? ''}|${ids.join('|')}`;
let seeding = this.seeded.get(key);
if (seeding === undefined) {
seeding = this.seed(ids, model.provider);
this.seeded.set(key, seeding);
}
return await seeding;
}
catch {
return undefined;
}
}
catalog() {
const catalog = this.options.catalog;
if (catalog !== undefined)
return catalog;
// As boot pricing: no network catalog in tests unless the app asked for one.
return env('NODE_ENV') === 'test' ? { modelsDev: false } : {};
}
async currentRows(force = false) {
const store = this.options.pricingStore;
if (store === undefined)
return new Map();
const ttl = this.options.cacheMs ?? DEFAULT_CACHE_MS;
if (force || this.rows === undefined || Date.now() - this.rowsAt > ttl) {
const rows = await store.listCurrentPrices();
this.rows = new Map(rows.map((row) => [row.modelId, row]));
this.rowsAt = Date.now();
}
return this.rows;
}
async seed(ids, provider) {
const store = this.options.pricingStore;
const catalog = this.catalog();
if (store === undefined)
return this.inMemory(ids, provider, catalog);
// A label that wraps a Bedrock id (`amazon-bedrock/us-gov.…`) does not say its partition, so it
// would be looked up as a commercial model: only the Bedrock-shaped ids are seeded then.
const bedrock = ids.filter((id) => parseBedrockModelId(id) !== undefined);
const seedIds = bedrock.length > 0 ? bedrock : ids;
// The boot seed, for this model only. Its "no cost will be recorded" warning names every id it
// was given, so it is held back until it is known that none of them got a price.
const warnings = [];
await ensureModelPricing({
models: seedIds.map((modelId) => ({
modelId,
...(provider !== undefined ? { provider } : {}),
reportsCost: false,
})),
pricingStore: store,
catalog,
log: {
info: (message) => this.options.log?.info(message),
warn: (message) => warnings.push(message),
},
});
const found = matchIn(await this.currentRows(true), ids);
if (found === undefined)
for (const message of warnings)
this.options.log?.warn(message);
return found;
}
/** No pricing store: the app's prices, the built-in Bedrock table, then models.dev. */
async inMemory(ids, provider, catalog) {
const options = catalog === false ? undefined : catalog;
const app = matchIn(normalizePriceEntries(options?.prices ?? [], this.options.log), ids);
if (app !== undefined)
return app;
const region = options?.region ?? env('AWS_REGION') ?? env('AWS_DEFAULT_REGION');
const bedrock = ids.filter((id) => isBedrockModel(id, provider));
const partition = bedrock.map((id) => parseBedrockModelId(id)?.partition).find((p) => p !== undefined) ??
(bedrock.length > 0 ? awsPartitionOfRegion(region) : undefined);
if (partition !== undefined && partition !== 'aws') {
// Never models.dev here: its Bedrock prices are the commercial ones.
for (const id of bedrock) {
const price = builtinBedrockPrice(id, partition);
if (price !== undefined)
return { ...price, modelId: ids[0] };
}
return undefined;
}
if (options === undefined || options.modelsDev === false)
return undefined;
const { prices: _p, region: _r, modelsDev: _m, ...modelsDev } = options;
const refs = [...new Set(ids.flatMap((id) => modelsDevRefsFor(id, provider)))];
const [price] = await lookupModelsDevPricesEach([refs], modelsDev.signal !== undefined
? modelsDev
: { ...modelsDev, signal: AbortSignal.timeout(CATALOG_TIMEOUT_MS) });
return price !== undefined ? { ...price, modelId: ids[0] } : undefined;
}
}
/** The first price for any of `ids`: exact, then its normalized Bedrock candidates. */
function matchIn(prices, ids) {
for (const id of ids) {
const exact = prices.get(id);
if (exact !== undefined)
return exact;
}
const rows = new Map([...prices].map(([id, p]) => [id, { ...p, effectiveFrom: '' }]));
for (const id of ids) {
const price = resolveModelPrice(rows, id);
if (price !== undefined)
return price;
}
for (const id of ids) {
for (const candidate of bedrockPriceCandidates(id)) {
const price = prices.get(candidate);
if (price !== undefined)
return price;
}
}
return undefined;
}
function env(name) {
const value = globalThis.process
?.env?.[name];
return value !== undefined && value.length > 0 ? value : undefined;
}
//# sourceMappingURL=resolve.js.map