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 reads: each part from the service that owns it, each on its own | |
| 3 | * so one that fails leaves only its part saying so. Billing has the | |
| 4 | * workspace's money (usage at price, the spend limit, caps, the price | |
| 5 | * book); the agents service has where agent work went (by agent, person, | |
| 6 | * channel, model, kind) and the budgets below the workspace's (its agents | |
| 7 | * together, each person, each agent, each session). The shaping is in | |
| 8 | * ./spend.ts. | |
| 9 | */ | |
| 10 | import type { AgentPolicy, AgentSpendBreakdown, Limit, PersonBudgets, SpendPeriod, UsageReport, User, WorkspaceAgent } from "@g1t/contracts"; | |
| 11 | ||
| 12 | import { type Pricing, type SpendScope, pricingOf, spanFor } from "./spend"; | |
| 13 | import { billing, workspaceAgents } from "./services.server"; | |
| 14 | ||
| 15 | const warn = (what: string) => (error: unknown) => { | |
| 16 | console.warn(`spend: ${what} failed`, error); | |
| 17 | return null; | |
| 18 | }; | |
| 19 | ||
| 20 | const value = <T>(result: { ok: true; value: T } | { ok: false } | null): T | null => (result && result.ok ? result.value : null); | |
| 21 | ||
| 22 | /** Where agent work went for the scope and period: everyone's, or only what the viewer asked for. */ | |
| 23 | export function loadBreakdown(viewer: User, slug: string, scope: SpendScope, period: SpendPeriod): Promise<AgentSpendBreakdown | null> { | |
| 24 | return workspaceAgents | |
| 25 | .spend(slug, viewer, null, { period, person: scope === "me" ? viewer.username.toLowerCase() : null }) | |
| 26 | .then(value) | |
| 27 | .catch(warn("agents spend")); | |
| 28 | } | |
| 29 | ||
| 30 | /** Everything the workspace used over the period, at price, by product and day. */ | |
| 31 | export function loadUsage(viewer: User, slug: string, period: SpendPeriod, now: Date): Promise<UsageReport | null> { | |
| 32 | const { from, until } = spanFor(period, now); | |
| 33 | return billing.usageReport(slug, viewer, { from, until }).then(value).catch(warn("usage report")); | |
| 34 | } | |
| 35 | ||
| 36 | /** One agent's budget and month, as the budgets list shows it. */ | |
| 37 | export type AgentBudgetRow = Pick<WorkspaceAgent, "id" | "handle" | "display_name" | "avatar_seed" | "budget" | "spent_month_micros" | "builtin">; | |
| 38 | ||
| 39 | /** Every level of budget, widest first, with what is spent against each this month. */ | |
| 40 | export type Budgets = { | |
| 41 | /** The workspace's spend limit (billing); null when billing did not answer or is off. */ | |
| 42 | limit: Limit | null; | |
| 43 | /** The plan's caps on one run and one issue (billing). */ | |
| 44 | caps: { runMicros: number; issueMicros: number } | null; | |
| 45 | /** The workspace's agent policy: every agent together, the per-person default, a new agent's, a session's. */ | |
| 46 | policy: AgentPolicy | null; | |
| 47 | /** Every agent's spend this month, against the policy's budget. */ | |
| 48 | agentsMonthMicros: number | null; | |
| 49 | people: PersonBudgets | null; | |
| 50 | agents: AgentBudgetRow[] | null; | |
| 51 | }; | |
| 52 | ||
| 53 | export async function loadBudgets(viewer: User, slug: string, monthBreakdown: Promise<AgentSpendBreakdown | null>): Promise<Budgets> { | |
| 54 | const [limit, entitlements, policy, people, agents, month] = await Promise.all([ | |
| 55 | billing.limit(slug, viewer).then(value).catch(warn("limit")), | |
| 56 | billing.entitlements(slug).catch(warn("entitlements")), | |
| 57 | workspaceAgents.policy(slug, viewer).then(value).catch(warn("agent policy")), | |
| 58 | workspaceAgents.personBudgets(slug, viewer).then(value).catch(warn("person budgets")), | |
| 59 | workspaceAgents.list(slug, viewer).then(value).catch(warn("agents")), | |
| 60 | monthBreakdown, | |
| 61 | ]); | |
| 62 | return { | |
| 63 | limit, | |
| 64 | caps: entitlements ? { runMicros: entitlements.runCapMicros, issueMicros: entitlements.issueCapMicros } : null, | |
| 65 | policy, | |
| 66 | agentsMonthMicros: month?.total_micros ?? null, | |
| 67 | people, | |
| 68 | agents: agents | |
| 69 | ? agents | |
| 70 | .filter((a) => !a.archived_at) | |
| 71 | .map((a) => ({ id: a.id, handle: a.handle, display_name: a.display_name, avatar_seed: a.avatar_seed, budget: a.budget, spent_month_micros: a.spent_month_micros, builtin: a.builtin })) | |
| 72 | .sort((a, b) => Number(b.builtin) - Number(a.builtin) || b.spent_month_micros - a.spent_month_micros) | |
| 73 | : null, | |
| 74 | }; | |
| 75 | } | |
| 76 | ||
| 77 | /** What the price book says about pricing. */ | |
| 78 | export function loadPricing(): Promise<Pricing | null> { | |
| 79 | return billing | |
| 80 | .prices() | |
| 81 | .then(pricingOf) | |
| 82 | .catch(warn("prices")); | |
| 83 | } | |
| 84 | ||
| 85 | /** The top bar's pill: the viewer's month, and the workspace's for someone who may see it. */ | |
| 86 | export type PillData = { | |
| 87 | month: string; | |
| 88 | me: { spentMicros: number; budgetMicros: number | null; byKind: AgentSpendBreakdown["by_kind"]; byAgent: AgentSpendBreakdown["by_agent"] } | null; | |
| The top bar's spend pill counts both tabs the same way, at price: You is what agents did for you, Workspace is everything the workspace used this month, the figure Home and Spend show, with what it was charged after its plan and credit underneath, against its spend limit; so a comped workspace reads as what it used and nothing charged, not the other way round. The spend guide says so. | 89 | /** |
| 90 | * The workspace's month: everything it used at price (the figure Home and | |
| 91 | * Spend show), what it was charged after its plan and credit (the figure | |
| 92 | * its spend limit governs), its limit, and its agents' share. | |
| 93 | */ | |
| 94 | workspace: { spentMicros: number | null; chargedMicros: number | null; limitMicros: number | null; agentsMicros: number | null; byAgent: AgentSpendBreakdown["by_agent"] } | 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. | 95 | }; |
| 96 | ||
| 97 | export async function loadPill(viewer: User, slug: string, mayWorkspace: boolean): Promise<PillData> { | |
| 98 | const me = viewer.username.toLowerCase(); | |
| The top bar's spend pill counts both tabs the same way, at price: You is what agents did for you, Workspace is everything the workspace used this month, the figure Home and Spend show, with what it was charged after its plan and credit underneath, against its spend limit; so a comped workspace reads as what it used and nothing charged, not the other way round. The spend guide says so. | 99 | const [mine, people, everyone, limit, used] = await Promise.all([ |
| 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. | 100 | loadBreakdown(viewer, slug, "me", "month"), |
| 101 | workspaceAgents.personBudgets(slug, viewer).then(value).catch(warn("person budgets")), | |
| 102 | mayWorkspace ? loadBreakdown(viewer, slug, "workspace", "month") : Promise.resolve(null), | |
| 103 | mayWorkspace ? billing.limit(slug, viewer).then(value).catch(warn("limit")) : Promise.resolve(null), | |
| The top bar's spend pill counts both tabs the same way, at price: You is what agents did for you, Workspace is everything the workspace used this month, the figure Home and Spend show, with what it was charged after its plan and credit underneath, against its spend limit; so a comped workspace reads as what it used and nothing charged, not the other way round. The spend guide says so. | 104 | mayWorkspace ? loadUsage(viewer, slug, "month", new Date()) : Promise.resolve(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. | 105 | ]); |
| 106 | const own = people?.people.find((p) => p.username === me); | |
| 107 | const budget = own ? own.monthly_micros : (people?.default_micros ?? null); | |
| 108 | return { | |
| 109 | month: (mine ?? everyone)?.period ?? new Date().toISOString().slice(0, 7), | |
| 110 | me: mine ? { spentMicros: mine.total_micros, budgetMicros: budget, byKind: mine.by_kind, byAgent: mine.by_agent } : null, | |
| 111 | workspace: mayWorkspace | |
| 112 | ? { | |
| The top bar's spend pill counts both tabs the same way, at price: You is what agents did for you, Workspace is everything the workspace used this month, the figure Home and Spend show, with what it was charged after its plan and credit underneath, against its spend limit; so a comped workspace reads as what it used and nothing charged, not the other way round. The spend guide says so. | 113 | spentMicros: used ? (used.free ? used.totals.costMicros : used.totals.priceMicros) : null, |
| 114 | chargedMicros: limit?.spentMicros ?? 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. | 115 | limitMicros: limit ? (limit.spendLimitMicros ?? limit.ceilingMicros) : null, |
| 116 | agentsMicros: everyone?.total_micros ?? null, | |
| 117 | byAgent: everyone?.by_agent ?? [], | |
| 118 | } | |
| 119 | : null, | |
| 120 | }; | |
| 121 | } |