Skip to content
121 linesCodeBlameRaw

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 */
10import type { AgentPolicy, AgentSpendBreakdown, Limit, PersonBudgets, SpendPeriod, UsageReport, User, WorkspaceAgent } from "@g1t/contracts";
11
12import { type Pricing, type SpendScope, pricingOf, spanFor } from "./spend";
13import { billing, workspaceAgents } from "./services.server";
14
15const warn = (what: string) => (error: unknown) => {
16 console.warn(`spend: ${what} failed`, error);
17 return null;
18};
19
20const 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. */
23export 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. */
31export 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. */
37export 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. */
40export 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
53export 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. */
78export 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. */
86export 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
97export 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}