@jovan158/vantage
Version:
See and control what your coding agent does
140 lines (139 loc) • 6.65 kB
JavaScript
// `vantage pricing [show|update|check] [provider]`: the price lists in use,
// and refreshing them from Anthropic's, OpenAI's and Google's official pages.
import { activePrices, resetActivePrices, writePricingCache, pricingCachePath, PRICE_PROVIDERS, } from "../pricing.js";
import { fetchProviderTable, pricingSourceUrl, diffPrices, hasDrift, formatChange } from "../pricing-source.js";
import { log } from "./output.js";
const PROVIDER_NAMES = { anthropic: "Anthropic", openai: "OpenAI", google: "Google" };
// Human page for a source URL (the Markdown form is what gets fetched).
function pageUrl(source) {
return source.replace(/\.md(?:\.txt)?$/, "");
}
function day(iso) {
return iso.slice(0, 10);
}
function priceRow(id, m, width, note = "") {
const n = (v) => `$${v}`.padStart(8);
return ` ${id.padEnd(width)}${n(m.input)}${n(m.cache_write_5m)}${n(m.cache_write_1h)}${n(m.cache_read)}${n(m.output)}${note}\n`;
}
function showProvider(provider, models) {
const p = activePrices().providers[provider];
const updated = p.cached && p.cached.fetchedAt === p.asOf;
log(`${PROVIDER_NAMES[provider]} · prices as of ${day(p.asOf)} · ` +
(updated ? `from \`vantage pricing update\` (${pricingCachePath(provider)})` : "bundled with vantage"));
log(`source: ${pageUrl(updated ? p.cached.source : p.bundled.source)}`);
const ids = Object.keys(models);
const width = Math.max(20, ...ids.map((id) => id.length + 2));
process.stdout.write(` ${"model".padEnd(width)}${"input".padStart(8)}${"5m wr".padStart(8)}${"1h wr".padStart(8)}${"read".padStart(8)}${"output".padStart(8)} USD / MTok\n`);
for (const id of ids) {
const m = models[id];
process.stdout.write(priceRow(id, m, width));
if (m.long)
process.stdout.write(priceRow(` above ${m.long.above / 1000}K`, m.long, width, " prompt above the limit"));
if (m.next)
process.stdout.write(priceRow(` from ${m.next.from}`, m.next, width, " announced"));
}
}
function isProvider(x) {
return PRICE_PROVIDERS.includes(x);
}
export async function cmdPricing(argv) {
// `vantage pricing openai` is short for `vantage pricing show openai`.
const args = argv[0] === undefined || isProvider(argv[0]) ? ["show", ...argv] : argv;
const [sub, which] = args;
if (!["show", "update", "check"].includes(sub)) {
log(`unknown pricing command "${sub}" (show | update | check)`);
return 1;
}
if (which !== undefined && !isProvider(which)) {
log(`unknown provider "${which}" (${PRICE_PROVIDERS.join(" | ")})`);
return 1;
}
const providers = which ? [which] : PRICE_PROVIDERS;
switch (sub) {
case "show": {
const p = activePrices();
if (p.cacheError)
log(`ignored an invalid price file — ${p.cacheError}`);
for (const provider of providers) {
const models = {};
const own = p.providers[provider];
const ids = new Set([...Object.keys(own.bundled.models), ...Object.keys(own.cached?.models ?? {})]);
for (const id of ids)
if (p.models[id])
models[id] = p.models[id];
showProvider(provider, models);
}
log("vantage never fetches prices on its own — `vantage pricing update` gets the current lists");
return 0;
}
case "update": {
let failed = 0;
for (const provider of providers) {
const before = activePrices().providers[provider];
const name = PROVIDER_NAMES[provider];
log(`fetching ${pricingSourceUrl(provider)}`);
let fetched;
try {
fetched = await fetchProviderTable(provider);
}
catch (err) {
log(`${name}: update failed, prices unchanged — ${err.message}`);
failed++;
continue;
}
for (const s of fetched.skipped)
log(`${name}: skipped ${s}`);
const { skipped: _skipped, ...table } = fetched;
writePricingCache(table, pricingCachePath(provider));
const beforeModels = { ...before.bundled.models, ...(before.cached?.models ?? {}) };
const d = diffPrices(beforeModels, table.models);
for (const id of d.added)
log(`${name}: new: ${id}`);
for (const c of d.changed)
log(`${name}: changed: ${formatChange(c)}`);
if (!hasDrift(d))
log(`${name}: no price changes`);
log(`${name}: ${Object.keys(table.models).length} models saved to ${pricingCachePath(provider)}`);
}
resetActivePrices();
if (failed < providers.length)
log("new prices apply from the next session");
return failed ? 1 : 0;
}
case "check": {
// For CI: do the official lists still match the bundled snapshot?
let stale = false;
let failed = false;
for (const provider of providers) {
const name = PROVIDER_NAMES[provider];
const bundled = activePrices().providers[provider].bundled;
let fetched;
try {
fetched = await fetchProviderTable(provider);
}
catch (err) {
log(`${name}: check failed — ${err.message}`);
failed = true;
continue;
}
for (const s of fetched.skipped)
log(`${name}: skipped ${s}`);
const d = diffPrices(bundled.models, fetched.models);
for (const id of d.added)
log(`${name}: new: ${id}`);
for (const c of d.changed)
log(`${name}: changed: ${formatChange(c)}`);
for (const id of d.unlisted)
log(`${name}: no longer listed: ${id}`);
if (hasDrift(d))
stale = true;
else
log(`${name}: bundled price list matches the official one (${Object.keys(fetched.models).length} models)`);
}
if (stale)
log("the bundled price list is out of date — run `npm run pricing:snapshot` and commit");
return stale || failed ? 1 : 0;
}
}
return 1;
}