g1t/packages/contracts/src/billing.ts

378 lines14,289 bytesCodeBlame

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.

Agents as a team: lifecycle, merge queue, billing and a new shell1import type { User, Viewer } from "./identity";
2import type { RepoPath } from "./repos";
3import type { Result } from "./result";
4
5/** Millionths of a US dollar in one dollar: the unit money is held in. */
6export const MICROS_PER_DOLLAR = 1_000_000;
7
8/** Whether workspaces are charged for agents at all, and with real money. */
9export type BillingStatus = {
10 /**
11 * False when no card processor is configured: nothing is charged, and who
12 * may run agents is decided some other way.
13 */
14 enabled: boolean;
15 /** False while the card processor is in its test mode, where cards are not real. */
16 live: boolean;
Free while g1t is being built out; agents can check out their own forks17 /**
18 * True while g1t is being built out: runs are recorded with what they
19 * cost, but nothing is charged and no credit is needed. Not forever.
20 */
21 free?: boolean;
Agents as a team: lifecycle, merge queue, billing and a new shell22};
23
24/** A workspace's standing. */
25export type BillingAccount = {
26 workspace: string;
27 /**
28 * Credit left, in millionths of a dollar. Can dip below zero by the cost
29 * of the runs that were under way when it ran out.
30 */
31 balanceMicros: number;
32 status: BillingStatus;
33 /** What is added to a run's cost, in percent. */
34 marginPercent: number;
Integrations: your own model provider, alerts that open issues, tickets agents read35 /** What a run on the workspace's own model provider is charged instead. */
36 orchestrationFeeMicros: number;
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace37 /** The card charged near the limit and when a month closes, if one is saved. */
38 card?: { brand: string; last4: string; expMonth: number; expYear: number } | null;
Agents as a team: lifecycle, merge queue, billing and a new shell39};
40
41/** One line of a workspace's statement. */
42export type LedgerEntry = {
43 id: string;
44 /** Credit bought with a card, or an agent's run. */
45 kind: "top_up" | "usage";
46 /** Positive for credit added, negative for usage. */
47 amountMicros: number;
48 description: string;
49 /** For usage: the repository and pull request the agent worked on. */
50 repo: string | null;
51 number: number | null;
52 /** For usage: `implement`, `review` or `update`. */
53 task: string | null;
54 /** For usage: the model, by its public name. */
55 model: string | null;
Integrations: your own model provider, alerts that open issues, tickets agents read56 /** For usage: who paid the model provider. */
57 billedTo: "g1t" | "workspace";
Agents as a team: lifecycle, merge queue, billing and a new shell58 /** For a top-up: the username of whoever paid. */
59 createdBy: string | null;
60 /** RFC 3339. */
61 createdAt: string;
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace62 /** The workspace the line belongs to, which tells an enterprise's lines apart. */
63 workspace?: string | null;
Agents as a team: lifecycle, merge queue, billing and a new shell64};
65
66/** What lets a sandbox, and nothing else, report what its run cost. */
67export type RunTicket = { runId: string; token: string };
68
69/**
70 * What agents cost, charged to the workspace they worked for. A workspace
71 * buys credit; each run deducts its cost plus g1t's margin; with no credit,
72 * no agent starts.
73 */
A free allowance on g1t's models, so anyone can try its agents74/**
75 * The free allowance on g1t's hosted models for a workspace not otherwise
76 * open to them: a few dollars of model cost each, out of one pool, until a
77 * date. Mirrors `Trial` in `crates/contracts/src/billing.rs`.
78 */
Billing accounts, terms and enterprises; g1t is no longer free79/** How an account is charged. Standard unless g1t set otherwise in sudo. */
80export type Terms = {
81 kind: "standard" | "comped" | "custom";
82 discountPercent: number;
83 ceilingMicros: number | null;
84 note: string;
85 until: string | null;
86 setBy: string | null;
87 setAt: string | null;
88};
89
90/**
91 * Who pays: a workspace's own account, or an enterprise's, which pays for
92 * several workspaces with one bill and one limit.
93 */
94export type PayingAccount = {
95 id: string;
96 kind: "workspace" | "enterprise";
97 name: string;
98 terms: Terms;
99 workspaces: string[];
100 createdAt: string;
101};
102
103export type AccountSummary = {
104 account: PayingAccount;
105 limit: Limit;
106 chargedMicros: number;
107 costMicros: number;
108 paidMicros: number;
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace109 /** The same figures for each of the account's workspaces that has any. */
110 byWorkspace: WorkspaceFigures[];
111};
112
113/** One workspace's share of an `AccountSummary`. */
114export type WorkspaceFigures = { workspace: string; chargedMicros: number; costMicros: number; paidMicros: number };
115
116/** A customer's Stripe billing page, for staff to send them. */
117export type BillingLink = {
118 /** One-time and short-lived, signed in already. */
119 portalUrl: string;
120 /** The page's sign-in, which does not expire: the customer signs in by email. */
121 loginUrl: string | null;
122 customerEmail: string | null;
123 expiresNote: string;
Billing accounts, terms and enterprises; g1t is no longer free124};
125
126export type AdminAction = { id: string; account: string; action: string; detail: string; by: string; createdAt: string };
127
128export type AccountDetail = {
129 summary: AccountSummary;
130 workspaces: Limit[];
131 ledger: LedgerEntry[];
132 audit: AdminAction[];
133};
134
135/** Staff-only billing, for sudo.g1t.sh. Every change names who made it. */
136export interface BillingAdminApi {
137 accounts(query?: string): Promise<AccountSummary[]>;
138 account(id: string): Promise<Result<AccountDetail>>;
139 setTerms(id: string, terms: Terms, by: string): Promise<Result<PayingAccount>>;
140 createEnterprise(name: string, workspaces: string[], by: string): Promise<Result<PayingAccount>>;
141 attach(workspace: string, account: string | null, by: string): Promise<Result<PayingAccount>>;
142 credit(workspace: string, amountMicros: number, note: string, by: string): Promise<Result<LedgerEntry>>;
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace143 /** The workspace's Stripe billing page, to send to the customer. Logged. */
144 billingLink(workspace: string, by: string): Promise<Result<BillingLink>>;
Billing accounts, terms and enterprises; g1t is no longer free145}
146
Usage limits: unpaid usage can only go so far147/** How much a workspace has earned g1t's trust with money. */
148export type Trust = "new" | "paid" | "reviewed" | "internal";
149
150/**
151 * How far a workspace's unpaid usage has gone this month, and where its
152 * work stops: past `ceilingMicros`, no new sandboxes, builds or app
153 * requests. Usage counts at its cost to g1t or its charge, whichever is
154 * more, so it counts while g1t is free too.
155 */
156export type Limit = {
157 workspace: string;
Billing accounts, terms and enterprises; g1t is no longer free158 /** The account that pays: the workspace's own (`ws_<slug>`), or its enterprise's. */
159 account: string;
160 accountName: string;
Usage limits: unpaid usage can only go so far161 trust: Trust;
162 exposureMicros: number;
163 /** The lower of g1t's ceiling and the owner's spend limit; null for g1t's own. */
164 ceilingMicros: number | null;
165 trustCeilingMicros: number | null;
166 spendLimitMicros: number | null;
167 state: "ok" | "warning" | "stopped";
168 message: string | null;
169};
170
Prices keep themselves current with what g1t pays171/** One metered unit: what it costs g1t and what it is sold at; the price follows the cost. */
172export type Price = {
173 meter: "sandbox_second" | "build_second" | "app_requests" | "app_cpu" | "app_month" | string;
174 title: string;
175 unit: string;
176 costMicros: number;
177 markupPercent: number;
178 priceMicros: number;
179 /** `list`: Cloudflare's published price. `cloudflare`: measured from Cloudflare's bill. */
180 source: "list" | "cloudflare" | string;
181 checkedAt: string | null;
182 updatedAt: string;
183};
184
185export type PriceChange = {
186 meter: string;
187 oldCostMicros: number;
188 newCostMicros: number;
189 markupPercent: number;
190 reason: string;
191 createdAt: string;
192};
193
194export type PriceBook = { prices: Price[]; changes: PriceChange[]; modelMarginPercent: number };
195
A free allowance on g1t's models, so anyone can try its agents196export type Trial = {
197 open: boolean;
198 usedMicros: number;
199 limitMicros: number;
200 endsAt: string | null;
201 /** Why it is closed: `off`, `ended`, `used` (this workspace's) or `pool` (everyone's). */
202 reason: "off" | "ended" | "used" | "pool" | null;
203};
204
Paid features: a workspace turns on Deployments with a monthly plan205/**
206 * A paid feature a workspace turns on with a monthly plan, as Cloudflare's
207 * Workers for Platforms or Vercel's Pro are bought. Never free: neither
208 * `free` nor the model allowance covers it. Mirrors `Feature` in
209 * `crates/contracts/src/billing.rs`.
210 */
211export type Feature = "deployments";
212
213/** What the Deployments plan includes each month. Mirrors `deployments_allowance`. */
214export const DEPLOYMENTS_ALLOWANCE = {
215 apps: 10,
216 requests: 1_000_000,
217 cpuMs: 3_000_000,
218 /** What Cloudflare charges g1t past that, in millionths of a dollar. */
219 microsPerAppMonth: 20_000,
220 microsPerMillionRequests: 300_000,
221 microsPerMillionCpuMs: 20_000,
Deployments: a preview for every pull request, production on g1t.page222 /** One second of a build's sandbox; builds are charged, not included. */
223 microsPerBuildSecond: 21,
Paid features: a workspace turns on Deployments with a monthly plan224} as const;
225
226export type FeaturePlan = {
227 feature: Feature;
228 title: string;
229 /** Charged every month while the plan is on, in cents. */
230 monthlyCents: number;
231 /** What the price includes, one line each. */
232 includes: string[];
233 /** How usage past the allowance is charged. */
234 overage: string;
235};
236
237export type SubscriptionStatus = "active" | "canceling" | "past_due" | "canceled";
238
239export type Subscription = {
240 feature: Feature;
241 status: SubscriptionStatus;
242 /** RFC 3339: when the period paid for ends. */
243 periodEnd: string | null;
244 startedBy: string;
245 startedAt: string;
246};
247
248/** A feature as a workspace sees it. */
249export type FeatureState = {
250 plan: FeaturePlan;
251 subscription: Subscription | null;
252 /** Whether the feature works for the workspace now. */
253 on: boolean;
254};
255
Agents as a team: lifecycle, merge queue, billing and a new shell256export interface BillingApi {
257 status(): Promise<BillingStatus>;
258 /** Members of the workspace only. */
259 account(workspace: string, viewer: Viewer): Promise<Result<BillingAccount>>;
260 /** Newest first. Members of the workspace only. */
261 ledger(workspace: string, viewer: Viewer): Promise<Result<LedgerEntry[]>>;
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request262 /** What the workspace's agents cost since `since`, broken down. Members only. */
263 usage(workspace: string, viewer: Viewer, since: string): Promise<Result<Usage>>;
Agents as a team: lifecycle, merge queue, billing and a new shell264 /**
265 * Starts a card payment for credit and returns the page to send the
266 * person to. Owners only. The payment's id comes back to `returnUrl` as
267 * `session`.
268 */
269 checkout(actor: User, workspace: string, amountCents: number, returnUrl: string): Promise<Result<{ url: string }>>;
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace270 /**
271 * Stripe's hosted billing page for the workspace: card, invoices, billing
272 * email and address. g1t never handles card numbers. Owners only.
273 */
274 billingPortal(actor: User, workspace: string, returnUrl: string): Promise<Result<{ url: string }>>;
Agents as a team: lifecycle, merge queue, billing and a new shell275 /** Credits a payment once the processor says it was made. Safe to repeat. */
276 confirm(workspace: string, viewer: Viewer, session: string): Promise<Result<BillingAccount>>;
277 /**
278 * Whether a workspace may start an agent now, asked before anything is
279 * opened for it. A failure, with the reason to show, when it has no credit.
280 */
281 canStart(workspace: string): Promise<Result<boolean>>;
A free allowance on g1t's models, so anyone can try its agents282 /** A workspace's free allowance on g1t's hosted models; `exempt` are open to them anyway. */
283 trial(workspace: string, exempt: string[]): Promise<Trial>;
Agents as a team: lifecycle, merge queue, billing and a new shell284 /**
285 * Asks whether a workspace may start an agent and opens the run it will be
286 * charged for. Null when billing is off; a failure when there is no credit.
287 */
Paid features: a workspace turns on Deployments with a monthly plan288 /** Every paid feature and the workspace's plan for each. Members only. */
289 features(workspace: string, viewer: Viewer): Promise<Result<FeatureState[]>>;
290 /**
291 * Starts the card page for a feature's monthly plan. Owners only. The
292 * page's id comes back to `returnUrl` as `session`.
293 */
294 subscribe(actor: User, workspace: string, feature: Feature, returnUrl: string): Promise<Result<{ url: string }>>;
295 /** Turns the feature on once the plan is paid for. Safe to repeat. */
296 confirmSubscription(workspace: string, viewer: Viewer, session: string): Promise<Result<FeatureState>>;
297 /** Ends a plan at the end of its period, or (`resume`) takes that back. Owners only. */
298 cancelSubscription(actor: User, workspace: string, feature: Feature, resume?: boolean): Promise<Result<FeatureState>>;
299 /** Whether a feature works for a workspace now; a failure with the reason when not. */
300 hasFeature(workspace: string, feature: Feature): Promise<Result<boolean>>;
301 /**
302 * Usage past a plan's allowance, charged from credit at cost plus the
303 * margin, once per `reference`. False if it was charged before.
304 */
305 chargeFeature(charge: {
306 workspace: string;
307 feature: Feature;
308 costMicros: number;
309 description: string;
310 repo?: string | null;
311 reference: string;
312 }): Promise<Result<boolean>>;
Prices keep themselves current with what g1t pays313 /**
314 * Usage this month to be charged later (app traffic past a plan), so the
315 * workspace's limit counts it now. Replaces the last report.
316 */
317 notePending(workspace: string, source: "deployments", costMicros: number): Promise<boolean>;
318 /** Every metered price and the recent changes. Public. */
319 prices(): Promise<PriceBook>;
Usage limits: unpaid usage can only go so far320 /** A workspace's limit, for its members. */
321 limit(workspace: string, viewer: Viewer): Promise<Result<Limit>>;
322 /** The same, for the services that enforce it. */
323 checkLimit(workspace: string): Promise<Result<Limit>>;
324 /** The owner's own monthly ceiling, under g1t's; null removes it. Owners only. */
325 setSpendLimit(actor: User, workspace: string, spendLimitMicros: number | null): Promise<Result<Limit>>;
Every sandbox is metered by the second326 /**
327 * How long a sandbox ran for a workspace, reported when it stops. Its
328 * cost is always recorded; seconds past the month's free minutes are
329 * charged. False if `reference` was recorded before.
330 */
331 recordSandbox(usage: {
332 workspace: string;
333 seconds: number;
334 description: string;
335 repo?: string | null;
336 reference: string;
337 }): Promise<Result<boolean>>;
Agents as a team: lifecycle, merge queue, billing and a new shell338 startRun(run: {
339 workspace: string;
340 repo: RepoPath;
341 number: number;
342 task: string;
343 model: string;
Integrations: your own model provider, alerts that open issues, tickets agents read344 /** `workspace` when the run uses the workspace's own model provider. */
345 billedTo?: "g1t" | "workspace";
Prices keep themselves current with what g1t pays346 /** The model session's id, so the run can be settled at AI Gateway's price. */
347 session?: string | null;
Agents as a team: lifecycle, merge queue, billing and a new shell348 }): Promise<Result<RunTicket | null>>;
349}
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request350
351
352/** One slice of usage: what it was for, what it cost, how many runs. */
353export type UsageSlice = { key: string; micros: number; runs: number };
354
355/** What a workspace's agents cost over a period. */
356export type Usage = {
357 since: string;
358 /** Charged, including g1t's margin. */
359 spentMicros: number;
Integrations: your own model provider, alerts that open issues, tickets agents read360 /** What g1t's model provider charged, before the margin. */
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request361 costMicros: number;
Integrations: your own model provider, alerts that open issues, tickets agents read362 /** What runs on the workspace's own provider cost there, estimated. Not charged by g1t. */
363 providerMicros: number;
Usage while free is shown at cost; agents get rustfmt and clippy364 /** What the runs used, at cost: g1t's models and the workspace's own provider together. */
365 usedMicros: number;
366 /** g1t charges nothing for now; the slices then measure usage at cost. */
367 free: boolean;
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request368 runs: number;
369 /** Spend per day and task, keyed `YYYY-MM-DD/task`. */
370 byDay: UsageSlice[];
371 byTask: UsageSlice[];
372 byRepo: UsageSlice[];
373 /** Keyed `namespace/name#number`. */
374 byPull: UsageSlice[];
375 byModel: UsageSlice[];
376 /** Credit bought in the period. */
377 addedMicros: number;
378};