import { METER_PRICES } from "./pricing"; /** The meter catalog: every billable thing the platform records. Prices come from src/lib/pricing.ts. */ export type MeterKind = "counter" | "gauge"; export type MeterCategory = "storage" | "compute" | "network" | "ai" | "ops"; export type MeterDef = { id: string; label: string; category: MeterCategory; unit: "bytes" | "seconds" | "ms" | "count" | "tokens"; kind: MeterKind; /** Price in micro-cents (1e-6 cent) per unit above the plan's included amount; 0 means free. */ microCentsPerUnit: number; }; export const METERS = [ { id: "storage.artifacts", label: "Repository storage", category: "storage", unit: "bytes", kind: "gauge", microCentsPerUnit: METER_PRICES["storage.artifacts"], }, { id: "storage.lfs", label: "LFS storage", category: "storage", unit: "bytes", kind: "gauge", microCentsPerUnit: METER_PRICES["storage.lfs"], }, { id: "storage.ci_cache", label: "Actions cache", category: "storage", unit: "bytes", kind: "gauge", microCentsPerUnit: METER_PRICES["storage.ci_cache"], }, { id: "storage.sites", label: "Site assets", category: "storage", unit: "bytes", kind: "gauge", microCentsPerUnit: METER_PRICES["storage.sites"], }, { id: "storage.r2", label: "Other object storage", category: "storage", unit: "bytes", kind: "gauge", microCentsPerUnit: METER_PRICES["storage.r2"], }, { id: "compute.runner", label: "Cloud runner (standard-1)", category: "compute", unit: "seconds", kind: "counter", microCentsPerUnit: METER_PRICES["compute.runner"], }, { id: "compute.runner_large", label: "Cloud runner (standard-2)", category: "compute", unit: "seconds", kind: "counter", microCentsPerUnit: METER_PRICES["compute.runner_large"], }, { id: "compute.runner_xlarge", label: "Cloud runner (standard-4)", category: "compute", unit: "seconds", kind: "counter", microCentsPerUnit: METER_PRICES["compute.runner_xlarge"], }, { id: "compute.runner_self_hosted", label: "Self-hosted runner", category: "compute", unit: "seconds", kind: "counter", microCentsPerUnit: METER_PRICES["compute.runner_self_hosted"], }, { id: "compute.editor", label: "Editor containers", category: "compute", unit: "seconds", kind: "counter", microCentsPerUnit: METER_PRICES["compute.editor"], }, { id: "compute.site_requests", label: "Site requests", category: "compute", unit: "count", kind: "counter", microCentsPerUnit: METER_PRICES["compute.site_requests"], }, { id: "compute.site_cpu", label: "Site CPU time", category: "compute", unit: "ms", kind: "counter", microCentsPerUnit: METER_PRICES["compute.site_cpu"], }, { id: "compute.workflow_steps", label: "Workflow steps", category: "compute", unit: "count", kind: "counter", microCentsPerUnit: METER_PRICES["compute.workflow_steps"], }, { id: "network.lfs", label: "LFS bandwidth", category: "network", unit: "bytes", kind: "counter", microCentsPerUnit: METER_PRICES["network.lfs"], }, { id: "network.sites", label: "Site egress", category: "network", unit: "bytes", kind: "counter", microCentsPerUnit: METER_PRICES["network.sites"], }, { id: "network.downloads", label: "Raw and archive downloads", category: "network", unit: "bytes", kind: "counter", microCentsPerUnit: METER_PRICES["network.downloads"], }, { id: "ai.calls", label: "AI calls", category: "ai", unit: "count", kind: "counter", microCentsPerUnit: METER_PRICES["ai.calls"], }, { id: "ai.tokens", label: "AI tokens", category: "ai", unit: "tokens", kind: "counter", microCentsPerUnit: METER_PRICES["ai.tokens"], }, { id: "ai.byok_calls", label: "AI calls (your own key)", category: "ai", unit: "count", kind: "counter", microCentsPerUnit: METER_PRICES["ai.byok_calls"], }, { id: "ai.byok_tokens", label: "AI tokens (your own key)", category: "ai", unit: "tokens", kind: "counter", microCentsPerUnit: METER_PRICES["ai.byok_tokens"], }, { id: "ops.integration_deliveries", label: "Integration deliveries", category: "ops", unit: "count", kind: "counter", microCentsPerUnit: METER_PRICES["ops.integration_deliveries"], }, { id: "ops.mcp_calls", label: "MCP tool calls", category: "ops", unit: "count", kind: "counter", microCentsPerUnit: METER_PRICES["ops.mcp_calls"], }, { id: "ops.git_ops", label: "Git operations", category: "ops", unit: "count", kind: "counter", microCentsPerUnit: METER_PRICES["ops.git_ops"], }, { id: "ops.git_bytes", label: "Git transfer", category: "ops", unit: "bytes", kind: "counter", microCentsPerUnit: METER_PRICES["ops.git_bytes"], }, { id: "ops.action_fetches", label: "Action downloads from GitHub", category: "ops", unit: "count", kind: "counter", microCentsPerUnit: METER_PRICES["ops.action_fetches"], }, { id: "domains.custom", label: "Custom domains", category: "ops", unit: "count", kind: "gauge", microCentsPerUnit: METER_PRICES["domains.custom"], }, ] as const satisfies readonly MeterDef[]; export type MeterId = (typeof METERS)[number]["id"]; const BY_ID = new Map(METERS.map((meter) => [meter.id, meter])); export function meterDef(id: string): MeterDef | null { return BY_ID.get(id) ?? null; } const KIB = 1024; const SIXTY = 60; export function formatQuantity(unit: MeterDef["unit"], value: number): string { if (unit === "bytes") { const units = ["B", "KiB", "MiB", "GiB", "TiB"]; let scaled = value; let index = 0; while (scaled >= KIB && index < units.length - 1) { scaled /= KIB; index += 1; } return `${scaled.toFixed(index === 0 ? 0 : 1)} ${units[index]}`; } if (unit === "seconds") { if (value < SIXTY) return `${Math.round(value)}s`; const minutes = value / SIXTY; return minutes < SIXTY ? `${minutes.toFixed(1)} min` : `${(minutes / SIXTY).toFixed(1)} h`; } if (unit === "ms") return `${Math.round(value).toLocaleString()} ms`; return Math.round(value).toLocaleString(); }