import { meterDef } from "./meters"; import type { MeterId } from "./meters"; import { CONTAINER_SPECS, MARKUP, METER_PRICES, RESOURCE_RATES, containerPricePerHour, creditPoolFor } from "./pricing"; const GB = 1024 ** 3; const HOUR = 3600; const MILLION = 1e6; const THOUSAND = 1000; const ONE_CENT = 0.01; const DOLLARS_PER_MICRO_CENT = 1e-8; export function usd(dollars: number): string { if (dollars === 0) return "$0"; if (dollars < ONE_CENT) return `$${Number(dollars.toPrecision(2))}`; if (dollars < 1) return `$${dollars.toFixed(3).replace(/0$/, "")}`; return `$${dollars.toFixed(2)}`; } /** A meter's overage price in the unit people think in: per hour, per GB-month, per million. Null when free. */ export function rateLabel(meter: string): string | null { const def = meterDef(meter); const price = (METER_PRICES as Record)[meter] ?? 0; if (!def || price <= 0) return null; const perUnit = price * DOLLARS_PER_MICRO_CENT; if (def.unit === "seconds") return `${usd(perUnit * HOUR)} / hour`; if (def.unit === "bytes") return `${usd(perUnit * GB)} / GB${def.kind === "gauge" ? "-month" : ""}`; if (def.unit === "tokens") return `${usd(perUnit * MILLION)} / 1M tokens`; if (def.unit === "ms") return `${usd(perUnit * MILLION)} / 1M ms`; if (perUnit * THOUSAND >= ONE_CENT) return `${usd(perUnit * THOUSAND)} / 1,000`; return `${usd(perUnit * MILLION)} / 1M`; } export type RunnerTierRate = { meter: MeterId; instance: string; vcpu: number; memoryGib: number; diskGb: number; perHour: number; /** Standard-1 credit hours one hour on this tier uses. */ creditMultiplier: number; }; const TIERS: [MeterId, keyof typeof CONTAINER_SPECS][] = [ ["compute.runner", "standard-1"], ["compute.runner_large", "standard-2"], ["compute.runner_xlarge", "standard-4"], ["compute.editor", "standard-1"], ]; /** Container-backed tiers with their resources and price, for the compute (i) explanation. */ export function runnerTierRates(): RunnerTierRate[] { return TIERS.map(([meter, instance]) => { const spec = CONTAINER_SPECS[instance]; return { meter, instance, vcpu: spec.vcpu, memoryGib: spec.memoryGib, diskGb: spec.diskGb, perHour: containerPricePerHour(spec), creditMultiplier: creditPoolFor(meter)?.factor ?? 1, }; }); } export const PRICING_BASIS = { markup: MARKUP, vcpuHour: RESOURCE_RATES.vcpuHour, gibHour: RESOURCE_RATES.gibHour, diskGbHour: RESOURCE_RATES.diskGbHour, };