Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how. | 1 | /** |
| 2 | * Spend's arithmetic and wording (routes/workspace/spend.tsx and the top | |
| 3 | * bar's spend pill): which days a period covers, what the price book says | |
| 4 | * about pricing, a day-by-day series, a task's receipt from its session | |
| 5 | * tree, and what each budget does when it is reached. Pure, with type-only | |
| 6 | * imports, so it is tested on its own. Every number comes from a service; | |
| 7 | * nothing here invents one. | |
| 8 | */ | |
| Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted. | 9 | import type { AgentSession, PriceBook, SpendPeriod, SpendSlice, UsageDay, UsageReport, UsageShare } from "@g1t/contracts"; |
| The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how. | 10 | |
| 11 | /** Whose spend: your own (what agents did for you), or the whole workspace's. */ | |
| 12 | export type SpendScope = "me" | "workspace"; | |
| 13 | ||
| Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted. | 14 | /** |
| 15 | * What agents cost over the report's range, at price: the agent product | |
| 16 | * (model tokens, the agent rate and agents' sandbox time), every agent's | |
| 17 | * replies, sessions and runs on repositories together. The top bar's and | |
| 18 | * Spend's "Agents" figure, from the same ledger as "Spent", so it is part | |
| 19 | * of it: Spent = Agents + the other products. | |
| 20 | */ | |
| 21 | export function agentMicros(report: Pick<UsageReport, "products">): number { | |
| 22 | return report.products.find((p) => p.key === "agent")?.micros ?? 0; | |
| 23 | } | |
| 24 | ||
| 25 | /** | |
| 26 | * The agent product by the agent that did the work and by who asked, as | |
| 27 | * Spend's slices, from the ledger's own attribution. Null when billing did | |
| 28 | * not attribute the range (a report from before attribution), so the | |
| 29 | * caller can fall back to the agents service's own count. | |
| 30 | */ | |
| 31 | export function attributionSlices(report: Pick<UsageReport, "byAgent" | "byPerson"> | null): { agent: SpendSlice[]; person: SpendSlice[] } | null { | |
| 32 | if (!report?.byAgent || !report.byPerson) return null; | |
| 33 | const slice = (s: UsageShare): SpendSlice => ({ key: s.key, label: s.label, micros: s.micros, count: s.count }); | |
| 34 | return { agent: report.byAgent.map(slice), person: report.byPerson.map(slice) }; | |
| 35 | } | |
| 36 | ||
| 37 | /** Where an agent's slice opens: its own Spend tab, for a slice that names one. */ | |
| 38 | export function agentSpendHref(slug: string, slice: Pick<SpendSlice, "key">): string | null { | |
| 39 | return slice.key ? `/${slug}/-/agents/${slice.key}/spend` : null; | |
| 40 | } | |
| 41 | ||
| The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how. | 42 | export const SPEND_PERIODS: { key: SpendPeriod; label: string; short: string }[] = [ |
| 43 | { key: "month", label: "This month", short: "This month" }, | |
| 44 | { key: "last_month", label: "Last month", short: "Last month" }, | |
| 45 | { key: "30d", label: "Last 30 days", short: "30 days" }, | |
| 46 | { key: "7d", label: "Last 7 days", short: "7 days" }, | |
| 47 | ]; | |
| 48 | ||
| 49 | export function readPeriod(raw: string | null | undefined): SpendPeriod { | |
| 50 | return SPEND_PERIODS.some((p) => p.key === raw) ? (raw as SpendPeriod) : "month"; | |
| 51 | } | |
| 52 | ||
| 53 | export function periodLabel(period: SpendPeriod): string { | |
| 54 | return SPEND_PERIODS.find((p) => p.key === period)?.label ?? "This month"; | |
| 55 | } | |
| 56 | ||
| 57 | /** Whose view a request asks for: the workspace's only for someone who may see it. */ | |
| 58 | export function readScope(raw: string | null | undefined, mayWorkspace: boolean): SpendScope { | |
| 59 | if (!mayWorkspace) return "me"; | |
| 60 | return raw === "me" ? "me" : "workspace"; | |
| 61 | } | |
| 62 | ||
| 63 | const iso = (at: Date) => at.toISOString().slice(0, 10); | |
| 64 | ||
| 65 | /** | |
| Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means. | 66 | * This month, in UTC: its first day to today, both included. The one |
| 67 | * range "this month" means on every page: the top bar's pill, Spend's | |
| 68 | * "This month", Home's "this month so far" and Usage's current cycle all | |
| 69 | * read it, so the same month shows the same number everywhere. Billing | |
| 70 | * adds the month's usage not yet closed (`pending_micros`) to any range | |
| 71 | * that reaches into the current month, so `until` need not be month end. | |
| 72 | */ | |
| 73 | export function monthSpan(now: Date): { from: string; until: string } { | |
| 74 | return { from: iso(new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1))), until: iso(now) }; | |
| 75 | } | |
| 76 | ||
| 77 | /** | |
| 78 | * What the workspace used over the report's range, at price: the figure | |
| 79 | * the pill, Spend's "Spent", Home's "this month so far" and Usage's | |
| 80 | * "Usage at price" all show. Billing already measures at cost while g1t | |
| 81 | * is free, so the report's price is the one figure in every mode. | |
| 82 | */ | |
| 83 | export function spentMicros(report: Pick<UsageReport, "totals">): number { | |
| 84 | return report.totals.priceMicros; | |
| 85 | } | |
| 86 | ||
| 87 | /** | |
| The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how. | 88 | * The days a period covers in UTC, both ends included, as the agents |
| 89 | * service counts them (services/agents/src/budget.ts `spendSpan`). | |
| 90 | */ | |
| 91 | export function spanFor(period: SpendPeriod, now: Date): { from: string; until: string } { | |
| 92 | const y = now.getUTCFullYear(); | |
| 93 | const m = now.getUTCMonth(); | |
| 94 | const d = now.getUTCDate(); | |
| 95 | switch (period) { | |
| 96 | case "last_month": | |
| 97 | return { from: iso(new Date(Date.UTC(y, m - 1, 1))), until: iso(new Date(Date.UTC(y, m, 0))) }; | |
| 98 | case "7d": | |
| 99 | return { from: iso(new Date(Date.UTC(y, m, d - 6))), until: iso(now) }; | |
| 100 | case "30d": | |
| 101 | return { from: iso(new Date(Date.UTC(y, m, d - 29))), until: iso(now) }; | |
| 102 | default: | |
| Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means. | 103 | return monthSpan(now); |
| The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how. | 104 | } |
| 105 | } | |
| 106 | ||
| 107 | /** Every day from `from` to `until`, with what was spent on it; days with none are 0. */ | |
| 108 | export function daySeries(from: string, until: string, spent: { day: string; micros: number }[]): { day: string; micros: number }[] { | |
| 109 | const by = new Map<string, number>(); | |
| 110 | for (const s of spent) by.set(s.day, (by.get(s.day) ?? 0) + s.micros); | |
| 111 | const out: { day: string; micros: number }[] = []; | |
| 112 | const at = new Date(`${from}T00:00:00Z`); | |
| 113 | const end = new Date(`${until}T00:00:00Z`); | |
| 114 | if (Number.isNaN(at.getTime()) || Number.isNaN(end.getTime())) return out; | |
| 115 | for (let n = 0; at <= end && n < 400; n++) { | |
| 116 | const key = iso(at); | |
| 117 | out.push({ day: key, micros: by.get(key) ?? 0 }); | |
| 118 | at.setUTCDate(at.getUTCDate() + 1); | |
| 119 | } | |
| 120 | return out; | |
| 121 | } | |
| 122 | ||
| 123 | /** Usage's days (one row per product a day) as one total a day. */ | |
| 124 | export function usageByDay(days: UsageDay[]): { day: string; micros: number }[] { | |
| 125 | return days.map((d) => ({ day: d.day, micros: d.micros })); | |
| 126 | } | |
| 127 | ||
| 128 | /** What the price book says about pricing, for the page's pricing card. */ | |
| 129 | export type Pricing = { | |
| 130 | /** What models are marked up, in percent: 0 is the provider's price. */ | |
| 131 | modelMarkupPercent: number; | |
| 132 | /** What g1t's own metered work is marked up: one figure, or the range across meters. */ | |
| 133 | markup: { min: number; max: number } | null; | |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 134 | /** g1t's agent rate per million tokens, on g1t's models and on your own key, in force now. Null when the book has none. */ |
| The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how. | 135 | agentRateMicros: number | null; |
| 136 | agentRateOwnMicros: number | null; | |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 137 | /** The agent rate still to come, when it is $0 now and a dated version waits: its price and the day it starts. */ |
| 138 | agentRateComing: { micros: number; from: string } | null; | |
| The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how. | 139 | }; |
| 140 | ||
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 141 | /** |
| 142 | * Meters that are not something g1t runs at a cost: models (at the | |
| 143 | * provider's price), AI Gateway, card fees (Stripe's), flat activations, | |
| 144 | * and per-token rates with their weights. | |
| 145 | */ | |
| 146 | const NOT_RUN = (meter: string) => | |
| 147 | meter === "agent_models" || meter === "gateway_models" || meter.startsWith("agent_token") || meter.startsWith("card_fee") || meter === "security_activation"; | |
| The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how. | 148 | |
| 149 | export function pricingOf(book: PriceBook): Pricing { | |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 150 | const run = book.prices.filter((p) => !NOT_RUN(p.meter) && p.costMicros > 0); |
| The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how. | 151 | const markups = run.map((p) => p.markupPercent); |
| 152 | const rate = (meter: string) => { | |
| 153 | const price = book.prices.find((p) => p.meter === meter); | |
| 154 | return price ? price.priceMicros : null; | |
| 155 | }; | |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 156 | const coming = book.changes.find((c) => c.meter === "agent_tokens" && c.effectiveAt); |
| The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how. | 157 | return { |
| 158 | modelMarkupPercent: book.modelMarginPercent, | |
| 159 | markup: markups.length ? { min: Math.min(...markups), max: Math.max(...markups) } : null, | |
| 160 | agentRateMicros: rate("agent_tokens"), | |
| 161 | agentRateOwnMicros: rate("agent_tokens_own"), | |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 162 | agentRateComing: coming ? { micros: coming.newCostMicros, from: coming.effectiveAt!.slice(0, 10) } : null, |
| The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how. | 163 | }; |
| 164 | } | |
| 165 | ||
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 166 | /** "$0.25 per million tokens", or "$0.25 per million tokens from 2026-10-22" while it waits for its date. */ |
| 167 | export function agentRateLabel(pricing: Pick<Pricing, "agentRateMicros" | "agentRateComing">, money: (micros: number) => string): string { | |
| 168 | if (pricing.agentRateMicros && pricing.agentRateMicros > 0) return `${money(pricing.agentRateMicros)} per million tokens`; | |
| 169 | if (pricing.agentRateComing) return `${money(pricing.agentRateComing.micros)} per million tokens from ${pricing.agentRateComing.from}`; | |
| 170 | return "Per million tokens"; | |
| 171 | } | |
| 172 | ||
| The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how. | 173 | /** "20%", or "15–20%" when meters differ. */ |
| 174 | export function markupLabel(markup: { min: number; max: number }): string { | |
| 175 | return markup.min === markup.max ? `${markup.min}%` : `${markup.min}–${markup.max}%`; | |
| 176 | } | |
| 177 | ||
| 178 | /** One line of a task's receipt: one session of its tree. */ | |
| 179 | export type ReceiptLine = { | |
| 180 | session: AgentSession; | |
| 181 | depth: number; | |
| 182 | /** What this session's own steps were charged (a root's charge includes its tree's). */ | |
| 183 | ownMicros: number; | |
| 184 | }; | |
| 185 | ||
| 186 | export type Receipt = { | |
| 187 | root: AgentSession; | |
| 188 | lines: ReceiptLine[]; | |
| 189 | /** The whole tree, as the root's charge counts it. */ | |
| 190 | chargedMicros: number; | |
| 191 | /** The model answers at the provider's price, every session's together. */ | |
| 192 | providerMicros: number; | |
| 193 | inputTokens: number; | |
| 194 | outputTokens: number; | |
| 195 | steps: number; | |
| 196 | toolCalls: number; | |
| 197 | }; | |
| 198 | ||
| 199 | /** | |
| 200 | * A task's receipt from its session tree (root first, as the agents | |
| 201 | * service gives it): each session's own charge, children under their | |
| 202 | * parents, and the totals. A child's charge is added to the root as it is | |
| 203 | * spent, so the root's own share is its charge less its children's. | |
| 204 | */ | |
| 205 | export function receiptOf(tree: AgentSession[], rootId: string): Receipt | null { | |
| 206 | const root = tree.find((s) => s.id === rootId) ?? tree.find((s) => s.parent_id == null); | |
| 207 | if (!root) return null; | |
| 208 | const children = new Map<string, AgentSession[]>(); | |
| 209 | for (const s of tree) { | |
| 210 | if (s.id === root.id || !s.parent_id) continue; | |
| 211 | children.set(s.parent_id, [...(children.get(s.parent_id) ?? []), s]); | |
| 212 | } | |
| 213 | const lines: ReceiptLine[] = []; | |
| 214 | const seen = new Set<string>(); | |
| 215 | const walk = (s: AgentSession, depth: number) => { | |
| 216 | if (seen.has(s.id)) return; | |
| 217 | seen.add(s.id); | |
| 218 | lines.push({ session: s, depth, ownMicros: s.charged_micros }); | |
| 219 | for (const kid of [...(children.get(s.id) ?? [])].sort((a, b) => a.created_at.localeCompare(b.created_at))) walk(kid, depth + 1); | |
| 220 | }; | |
| 221 | walk(root, 0); | |
| 222 | const others = lines.slice(1).reduce((n, l) => n + l.ownMicros, 0); | |
| 223 | lines[0]!.ownMicros = Math.max(0, root.charged_micros - others); | |
| 224 | const sum = (pick: (s: AgentSession) => number) => lines.reduce((n, l) => n + (pick(l.session) || 0), 0); | |
| 225 | return { | |
| 226 | root, | |
| 227 | lines, | |
| 228 | chargedMicros: root.charged_micros, | |
| 229 | providerMicros: sum((s) => s.cost_micros ?? 0), | |
| 230 | inputTokens: sum((s) => s.input_tokens), | |
| 231 | outputTokens: sum((s) => s.output_tokens), | |
| 232 | steps: sum((s) => s.steps), | |
| 233 | toolCalls: sum((s) => s.tool_calls), | |
| 234 | }; | |
| 235 | } | |
| 236 | ||
| 237 | /** A count of tokens for a receipt: "412K", "1.2M", "830". */ | |
| 238 | export function tokenCount(n: number): string { | |
| 239 | if (n >= 1_000_000) return `${(n / 1_000_000).toLocaleString("en-US", { maximumFractionDigits: 1 })}M`; | |
| 240 | if (n >= 10_000) return `${Math.round(n / 1000).toLocaleString("en-US")}K`; | |
| 241 | if (n >= 1000) return `${(n / 1000).toLocaleString("en-US", { maximumFractionDigits: 1 })}K`; | |
| 242 | return n.toLocaleString("en-US"); | |
| 243 | } | |
| 244 | ||
| 245 | /** How far through a budget: 0 to 1 (more when over), null with none. */ | |
| 246 | export function shareOfBudget(spent: number, budget: number | null | undefined): number | null { | |
| 247 | return budget != null && budget > 0 ? Math.max(0, spent) / budget : null; | |
| 248 | } | |
| 249 | ||
| 250 | /** "62%" of a budget, for a pill or a row; empty with none. */ | |
| 251 | export function percentLabel(spent: number, budget: number | null | undefined): string { | |
| 252 | const share = shareOfBudget(spent, budget); | |
| 253 | return share == null ? "" : `${Math.round(share * 100)}%`; | |
| 254 | } | |
| 255 | ||
| 256 | /** The slices a person's own view leaves out: the "who asked" one is only ever them. */ | |
| 257 | export function withoutSelf(slices: SpendSlice[], username: string): SpendSlice[] { | |
| 258 | return slices.filter((s) => s.key !== username); | |
| 259 | } | |
| 260 | ||
| 261 | /** What a level of budget does when it is reached, as the page says it. */ | |
| 262 | export const AT_LIMIT = { | |
| 263 | workspace: (pauses: boolean) => | |
| 264 | pauses | |
| 265 | ? "New work stops until the month turns or an owner raises it: agents, workflows, builds and deploys. Work already running finishes." | |
| 266 | : "Owners are alerted, and work goes on; g1t's own ceiling still applies.", | |
| 267 | agents: "No agent takes new work until the 1st, or until an owner raises it.", | |
| 268 | person: "Agents take no new work for that person until the 1st; they say so where they were asked.", | |
| 269 | agent: "That agent takes no new work until the 1st, or the next day for a daily cap.", | |
| 270 | session: "The session stops at Needs approval, and an owner decides whether it goes on.", | |
| 271 | } as const; |