flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/packages/contracts/src/billing.ts

352 lines12,971 bytesCodeBlame
1import 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;
17 /**
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;
22};
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;
35 /** What a run on the workspace's own model provider is charged instead. */
36 orchestrationFeeMicros: number;
37};
38
39/** One line of a workspace's statement. */
40export type LedgerEntry = {
41 id: string;
42 /** Credit bought with a card, or an agent's run. */
43 kind: "top_up" | "usage";
44 /** Positive for credit added, negative for usage. */
45 amountMicros: number;
46 description: string;
47 /** For usage: the repository and pull request the agent worked on. */
48 repo: string | null;
49 number: number | null;
50 /** For usage: `implement`, `review` or `update`. */
51 task: string | null;
52 /** For usage: the model, by its public name. */
53 model: string | null;
54 /** For usage: who paid the model provider. */
55 billedTo: "g1t" | "workspace";
56 /** For a top-up: the username of whoever paid. */
57 createdBy: string | null;
58 /** RFC 3339. */
59 createdAt: string;
60};
61
62/** What lets a sandbox, and nothing else, report what its run cost. */
63export type RunTicket = { runId: string; token: string };
64
65/**
66 * What agents cost, charged to the workspace they worked for. A workspace
67 * buys credit; each run deducts its cost plus g1t's margin; with no credit,
68 * no agent starts.
69 */
70/**
71 * The free allowance on g1t's hosted models for a workspace not otherwise
72 * open to them: a few dollars of model cost each, out of one pool, until a
73 * date. Mirrors `Trial` in `crates/contracts/src/billing.rs`.
74 */
75/** How an account is charged. Standard unless g1t set otherwise in sudo. */
76export type Terms = {
77 kind: "standard" | "comped" | "custom";
78 discountPercent: number;
79 ceilingMicros: number | null;
80 note: string;
81 until: string | null;
82 setBy: string | null;
83 setAt: string | null;
84};
85
86/**
87 * Who pays: a workspace's own account, or an enterprise's, which pays for
88 * several workspaces with one bill and one limit.
89 */
90export type PayingAccount = {
91 id: string;
92 kind: "workspace" | "enterprise";
93 name: string;
94 terms: Terms;
95 workspaces: string[];
96 createdAt: string;
97};
98
99export type AccountSummary = {
100 account: PayingAccount;
101 limit: Limit;
102 chargedMicros: number;
103 costMicros: number;
104 paidMicros: number;
105};
106
107export type AdminAction = { id: string; account: string; action: string; detail: string; by: string; createdAt: string };
108
109export type AccountDetail = {
110 summary: AccountSummary;
111 workspaces: Limit[];
112 ledger: LedgerEntry[];
113 audit: AdminAction[];
114};
115
116/** Staff-only billing, for sudo.g1t.sh. Every change names who made it. */
117export interface BillingAdminApi {
118 accounts(query?: string): Promise<AccountSummary[]>;
119 account(id: string): Promise<Result<AccountDetail>>;
120 setTerms(id: string, terms: Terms, by: string): Promise<Result<PayingAccount>>;
121 createEnterprise(name: string, workspaces: string[], by: string): Promise<Result<PayingAccount>>;
122 attach(workspace: string, account: string | null, by: string): Promise<Result<PayingAccount>>;
123 credit(workspace: string, amountMicros: number, note: string, by: string): Promise<Result<LedgerEntry>>;
124}
125
126/** How much a workspace has earned g1t's trust with money. */
127export type Trust = "new" | "paid" | "reviewed" | "internal";
128
129/**
130 * How far a workspace's unpaid usage has gone this month, and where its
131 * work stops: past `ceilingMicros`, no new sandboxes, builds or app
132 * requests. Usage counts at its cost to g1t or its charge, whichever is
133 * more, so it counts while g1t is free too.
134 */
135export type Limit = {
136 workspace: string;
137 /** The account that pays: the workspace's own (`ws_<slug>`), or its enterprise's. */
138 account: string;
139 accountName: string;
140 trust: Trust;
141 exposureMicros: number;
142 /** The lower of g1t's ceiling and the owner's spend limit; null for g1t's own. */
143 ceilingMicros: number | null;
144 trustCeilingMicros: number | null;
145 spendLimitMicros: number | null;
146 state: "ok" | "warning" | "stopped";
147 message: string | null;
148};
149
150/** One metered unit: what it costs g1t and what it is sold at; the price follows the cost. */
151export type Price = {
152 meter: "sandbox_second" | "build_second" | "app_requests" | "app_cpu" | "app_month" | string;
153 title: string;
154 unit: string;
155 costMicros: number;
156 markupPercent: number;
157 priceMicros: number;
158 /** `list`: Cloudflare's published price. `cloudflare`: measured from Cloudflare's bill. */
159 source: "list" | "cloudflare" | string;
160 checkedAt: string | null;
161 updatedAt: string;
162};
163
164export type PriceChange = {
165 meter: string;
166 oldCostMicros: number;
167 newCostMicros: number;
168 markupPercent: number;
169 reason: string;
170 createdAt: string;
171};
172
173export type PriceBook = { prices: Price[]; changes: PriceChange[]; modelMarginPercent: number };
174
175export type Trial = {
176 open: boolean;
177 usedMicros: number;
178 limitMicros: number;
179 endsAt: string | null;
180 /** Why it is closed: `off`, `ended`, `used` (this workspace's) or `pool` (everyone's). */
181 reason: "off" | "ended" | "used" | "pool" | null;
182};
183
184/**
185 * A paid feature a workspace turns on with a monthly plan, as Cloudflare's
186 * Workers for Platforms or Vercel's Pro are bought. Never free: neither
187 * `free` nor the model allowance covers it. Mirrors `Feature` in
188 * `crates/contracts/src/billing.rs`.
189 */
190export type Feature = "deployments";
191
192/** What the Deployments plan includes each month. Mirrors `deployments_allowance`. */
193export const DEPLOYMENTS_ALLOWANCE = {
194 apps: 10,
195 requests: 1_000_000,
196 cpuMs: 3_000_000,
197 /** What Cloudflare charges g1t past that, in millionths of a dollar. */
198 microsPerAppMonth: 20_000,
199 microsPerMillionRequests: 300_000,
200 microsPerMillionCpuMs: 20_000,
201 /** One second of a build's sandbox; builds are charged, not included. */
202 microsPerBuildSecond: 21,
203} as const;
204
205export type FeaturePlan = {
206 feature: Feature;
207 title: string;
208 /** Charged every month while the plan is on, in cents. */
209 monthlyCents: number;
210 /** What the price includes, one line each. */
211 includes: string[];
212 /** How usage past the allowance is charged. */
213 overage: string;
214};
215
216export type SubscriptionStatus = "active" | "canceling" | "past_due" | "canceled";
217
218export type Subscription = {
219 feature: Feature;
220 status: SubscriptionStatus;
221 /** RFC 3339: when the period paid for ends. */
222 periodEnd: string | null;
223 startedBy: string;
224 startedAt: string;
225};
226
227/** A feature as a workspace sees it. */
228export type FeatureState = {
229 plan: FeaturePlan;
230 subscription: Subscription | null;
231 /** Whether the feature works for the workspace now. */
232 on: boolean;
233};
234
235export interface BillingApi {
236 status(): Promise<BillingStatus>;
237 /** Members of the workspace only. */
238 account(workspace: string, viewer: Viewer): Promise<Result<BillingAccount>>;
239 /** Newest first. Members of the workspace only. */
240 ledger(workspace: string, viewer: Viewer): Promise<Result<LedgerEntry[]>>;
241 /** What the workspace's agents cost since `since`, broken down. Members only. */
242 usage(workspace: string, viewer: Viewer, since: string): Promise<Result<Usage>>;
243 /**
244 * Starts a card payment for credit and returns the page to send the
245 * person to. Owners only. The payment's id comes back to `returnUrl` as
246 * `session`.
247 */
248 checkout(actor: User, workspace: string, amountCents: number, returnUrl: string): Promise<Result<{ url: string }>>;
249 /** Credits a payment once the processor says it was made. Safe to repeat. */
250 confirm(workspace: string, viewer: Viewer, session: string): Promise<Result<BillingAccount>>;
251 /**
252 * Whether a workspace may start an agent now, asked before anything is
253 * opened for it. A failure, with the reason to show, when it has no credit.
254 */
255 canStart(workspace: string): Promise<Result<boolean>>;
256 /** A workspace's free allowance on g1t's hosted models; `exempt` are open to them anyway. */
257 trial(workspace: string, exempt: string[]): Promise<Trial>;
258 /**
259 * Asks whether a workspace may start an agent and opens the run it will be
260 * charged for. Null when billing is off; a failure when there is no credit.
261 */
262 /** Every paid feature and the workspace's plan for each. Members only. */
263 features(workspace: string, viewer: Viewer): Promise<Result<FeatureState[]>>;
264 /**
265 * Starts the card page for a feature's monthly plan. Owners only. The
266 * page's id comes back to `returnUrl` as `session`.
267 */
268 subscribe(actor: User, workspace: string, feature: Feature, returnUrl: string): Promise<Result<{ url: string }>>;
269 /** Turns the feature on once the plan is paid for. Safe to repeat. */
270 confirmSubscription(workspace: string, viewer: Viewer, session: string): Promise<Result<FeatureState>>;
271 /** Ends a plan at the end of its period, or (`resume`) takes that back. Owners only. */
272 cancelSubscription(actor: User, workspace: string, feature: Feature, resume?: boolean): Promise<Result<FeatureState>>;
273 /** Whether a feature works for a workspace now; a failure with the reason when not. */
274 hasFeature(workspace: string, feature: Feature): Promise<Result<boolean>>;
275 /**
276 * Usage past a plan's allowance, charged from credit at cost plus the
277 * margin, once per `reference`. False if it was charged before.
278 */
279 chargeFeature(charge: {
280 workspace: string;
281 feature: Feature;
282 costMicros: number;
283 description: string;
284 repo?: string | null;
285 reference: string;
286 }): Promise<Result<boolean>>;
287 /**
288 * Usage this month to be charged later (app traffic past a plan), so the
289 * workspace's limit counts it now. Replaces the last report.
290 */
291 notePending(workspace: string, source: "deployments", costMicros: number): Promise<boolean>;
292 /** Every metered price and the recent changes. Public. */
293 prices(): Promise<PriceBook>;
294 /** A workspace's limit, for its members. */
295 limit(workspace: string, viewer: Viewer): Promise<Result<Limit>>;
296 /** The same, for the services that enforce it. */
297 checkLimit(workspace: string): Promise<Result<Limit>>;
298 /** The owner's own monthly ceiling, under g1t's; null removes it. Owners only. */
299 setSpendLimit(actor: User, workspace: string, spendLimitMicros: number | null): Promise<Result<Limit>>;
300 /**
301 * How long a sandbox ran for a workspace, reported when it stops. Its
302 * cost is always recorded; seconds past the month's free minutes are
303 * charged. False if `reference` was recorded before.
304 */
305 recordSandbox(usage: {
306 workspace: string;
307 seconds: number;
308 description: string;
309 repo?: string | null;
310 reference: string;
311 }): Promise<Result<boolean>>;
312 startRun(run: {
313 workspace: string;
314 repo: RepoPath;
315 number: number;
316 task: string;
317 model: string;
318 /** `workspace` when the run uses the workspace's own model provider. */
319 billedTo?: "g1t" | "workspace";
320 /** The model session's id, so the run can be settled at AI Gateway's price. */
321 session?: string | null;
322 }): Promise<Result<RunTicket | null>>;
323}
324
325
326/** One slice of usage: what it was for, what it cost, how many runs. */
327export type UsageSlice = { key: string; micros: number; runs: number };
328
329/** What a workspace's agents cost over a period. */
330export type Usage = {
331 since: string;
332 /** Charged, including g1t's margin. */
333 spentMicros: number;
334 /** What g1t's model provider charged, before the margin. */
335 costMicros: number;
336 /** What runs on the workspace's own provider cost there, estimated. Not charged by g1t. */
337 providerMicros: number;
338 /** What the runs used, at cost: g1t's models and the workspace's own provider together. */
339 usedMicros: number;
340 /** g1t charges nothing for now; the slices then measure usage at cost. */
341 free: boolean;
342 runs: number;
343 /** Spend per day and task, keyed `YYYY-MM-DD/task`. */
344 byDay: UsageSlice[];
345 byTask: UsageSlice[];
346 byRepo: UsageSlice[];
347 /** Keyed `namespace/name#number`. */
348 byPull: UsageSlice[];
349 byModel: UsageSlice[];
350 /** Credit bought in the period. */
351 addedMicros: number;
352};