import { IconLoader2, IconRefresh } from "@tabler/icons-react"; import { useEffect, useState } from "react"; import { agentNativePath } from "../api-path.js"; interface UsageBucket { key: string; cents: number; cost: UsageCostAggregate; calls: number; inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; } interface DailyBucket { date: string; cents: number; cost: UsageCostAggregate; calls: number; } interface UsageRecentEntry { id: number; createdAt: number; label: string; app: string; model: string; inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; cents: number; costSource: "reported" | "estimated" | "unavailable"; } type UsageCostAggregate = | { status: "known"; knownCents: number; unavailableCalls: 0 } | { status: "partial"; knownCents: number; unavailableCalls: number } | { status: "unavailable"; knownCents: 0; unavailableCalls: number }; interface UsageBillingMode { unit: "usd" | "builder-credits"; label: string; shortLabel: string; source: "estimated-provider-cost" | "builder-agent-credits"; hardCostMarginMultiplier?: number; creditsPerUsd?: number; } interface UsageSummary { billing?: UsageBillingMode; totalCents: number; totalCost: UsageCostAggregate; totalCalls: number; totalInputTokens: number; totalOutputTokens: number; totalCacheReadTokens: number; totalCacheWriteTokens: number; sinceMs: number; byLabel: UsageBucket[]; byModel: UsageBucket[]; byApp: UsageBucket[]; byDay: DailyBucket[]; recent: UsageRecentEntry[]; } const RANGES = [ { value: 1, label: "24h" }, { value: 7, label: "7d" }, { value: 30, label: "30d" }, { value: 90, label: "90d" }, ]; const USD_BILLING: UsageBillingMode = { unit: "usd", label: "Estimated spend", shortLabel: "Cost", source: "estimated-provider-cost", }; function displayAmountFromCostCents( cents: number, billing: UsageBillingMode, ): number { if (billing.unit !== "builder-credits") return cents; const margin = billing.hardCostMarginMultiplier ?? 1.25; const creditsPerUsd = billing.creditsPerUsd ?? 20; const credits = (cents / 100) * margin * creditsPerUsd; return credits <= 0 ? 0 : Math.ceil(credits * 1000) / 1000; } function formatCredits(credits: number): string { if (!Number.isFinite(credits) || credits === 0) return "0 credits"; const maximumFractionDigits = credits < 1 ? 3 : credits < 10 ? 2 : 1; const value = credits.toLocaleString(undefined, { maximumFractionDigits, }); return `${value} ${credits === 1 ? "credit" : "credits"}`; } function formatSpend(cents: number, billing: UsageBillingMode): string { if (billing.unit === "builder-credits") { return formatCredits(displayAmountFromCostCents(cents, billing)); } // Sub-cent values (e.g. a single LLM call at $0.0045 = 0.45¢) — keep // three decimals so tiny calls don't round to 0.00¢. The prior impl // multiplied by 100 in this branch, overstating small costs 100×. if (cents < 1) return `${cents.toFixed(3)}¢`; if (cents < 100) return `${cents.toFixed(2)}¢`; return `$${(cents / 100).toFixed(2)}`; } function formatAggregateCost( cost: UsageCostAggregate, billing: UsageBillingMode, ): string { if (cost.status === "known") return formatSpend(cost.knownCents, billing); if (cost.status === "unavailable") return "Unknown"; return `${formatSpend(cost.knownCents, billing)} + ${cost.unavailableCalls} unpriced`; } function formatTokens(n: number): string { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`; return String(n); } function BucketBars({ buckets, emptyMessage, billing, }: { buckets: UsageBucket[]; emptyMessage: string; billing: UsageBillingMode; }) { if (buckets.length === 0) { return (

{emptyMessage}

); } const max = Math.max( ...buckets.map((b) => displayAmountFromCostCents(b.cents, billing)), 0.0001, ); return (
{buckets.map((b) => (
{b.key || "(none)"} {formatAggregateCost(b.cost, billing)} · {formatTokens(b.inputTokens + b.outputTokens)} tok
))}
); } function DailySparkline({ days, billing, }: { days: DailyBucket[]; billing: UsageBillingMode; }) { if (days.length === 0) return null; const max = Math.max( ...days.map((d) => displayAmountFromCostCents(d.cents, billing)), 0.0001, ); return (
{days.map((d) => (
))}
); } export function UsageSection() { const [days, setDays] = useState(30); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const billing = data?.billing ?? USD_BILLING; const load = async (rangeDays: number) => { setLoading(true); setError(null); try { const res = await fetch( agentNativePath(`/_agent-native/usage?sinceDays=${rangeDays}`), ); if (!res.ok) { throw new Error(`Failed (${res.status})`); } const json = (await res.json()) as UsageSummary; setData(json); } catch (err: any) { setError(err?.message || "Failed to load usage"); } finally { setLoading(false); } }; useEffect(() => { load(days); }, [days]); return (
{/* Range selector + refresh */}
{RANGES.map((r) => ( ))}
{error &&

{error}

} {data && ( <> {/* Totals */}
{billing.unit === "builder-credits" ? "Builder.io credit spend" : "Total spend"}
{formatAggregateCost(data.totalCost, billing)}
{data.totalCalls} calls
{formatTokens(data.totalInputTokens)} in ·{" "} {formatTokens(data.totalOutputTokens)} out
{data.totalCacheReadTokens > 0 && (
{formatTokens(data.totalCacheReadTokens)} cached
)}
{/* By label */}
By label
{/* By model */}
By model
{/* By app — only show when multiple apps contribute */} {data.byApp.filter((b) => b.key).length > 1 && (
By app
)} {/* Recent calls */} {data.recent.length > 0 && (
Recent calls ({data.recent.length})
{data.recent.map((r) => (
{r.label} {r.app ? ( {" "} · {r.app} ) : null}
{new Date(r.createdAt).toLocaleString()} · {r.model}
{r.costSource === "unavailable" ? "Unknown" : formatSpend(r.cents, billing)}
))}
)} {billing.unit === "builder-credits" ? (

Builder.io credits are estimated from hard token cost, a{" "} {billing.hardCostMarginMultiplier ?? 1.25}x margin, and{" "} {billing.creditsPerUsd ?? 20} credits per dollar.

) : (

Spend is estimated from published Anthropic pricing and your own recorded token counts. Cached input is priced at ~10% of regular input. Calls without provider pricing are marked unknown and excluded from known-cost subtotals.

)} )}
); }