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 | ||
| 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. | 12 | import { type Pricing, type SpendScope, agentMicros, attributionSlices, pricingOf, spanFor, spentMicros } from "./spend"; |
| 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. | 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. */ | |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 37 | export type AgentBudgetRow = Pick<WorkspaceAgent, "id" | "handle" | "display_name" | "avatar_seed" | "look" | "budget" | "spent_month_micros" | "builtin">; |
| 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. | 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) | |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 71 | .map((a) => ({ id: a.id, handle: a.handle, display_name: a.display_name, avatar_seed: a.avatar_seed, look: a.look ?? null, budget: a.budget, spent_month_micros: a.spent_month_micros, builtin: a.builtin })) |
| 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. | 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(); | |
| 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. | 99 | const [mine, people, everyone, limit, usage] = 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), | |
| 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. | 104 | // The workspace's agents figure is the agent product on billing's |
| 105 | // ledger, the same ledger "charged" is read from; the agents service's | |
| 106 | // own count stands in only while billing has not answered. | |
| 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. | 107 | 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. | 108 | ]); |
| 109 | const own = people?.people.find((p) => p.username === me); | |
| 110 | const budget = own ? own.monthly_micros : (people?.default_micros ?? null); | |
| 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. | 111 | const attributed = attributionSlices(usage); |
| 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. | 112 | return { |
| 113 | month: (mine ?? everyone)?.period ?? new Date().toISOString().slice(0, 7), | |
| 114 | me: mine ? { spentMicros: mine.total_micros, budgetMicros: budget, byKind: mine.by_kind, byAgent: mine.by_agent } : null, | |
| 115 | workspace: mayWorkspace | |
| 116 | ? { | |
| 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. | 117 | spentMicros: usage ? spentMicros(usage) : 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. | 118 | 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. | 119 | limitMicros: limit ? (limit.spendLimitMicros ?? limit.ceilingMicros) : null, |
| 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. | 120 | agentsMicros: usage ? agentMicros(usage) : (everyone?.total_micros ?? null), |
| 121 | byAgent: attributed?.agent ?? everyone?.by_agent ?? [], | |
| 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. | 122 | } |
| 123 | : null, | |
| 124 | }; | |
| 125 | } |