flagon-io/g1t

public

Git for AI scale: a forge for thousands of agents working on the same code at once.

g1t/crates/contracts/src/billing.rs

821 lines27,956 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 shell1//! The billing service: what agents cost, charged to the workspace they
2//! worked for.
3//!
4//! A workspace buys credit and each agent run deducts what it cost, plus
5//! g1t's margin. With no credit, no agent starts. Money is held in
6//! millionths of a US dollar, so that a run costing a fraction of a cent is
7//! recorded exactly.
8//!
9//! Each `*Args` struct is the argument of the method of the same name,
10//! served at `POST /rpc/<method>`.
11
12use serde::{Deserialize, Serialize};
13
14use crate::repos::RepoPath;
15use crate::{User, Viewer};
16
17/// Millionths of a US dollar in one dollar.
18pub const MICROS_PER_DOLLAR: i64 = 1_000_000;
19
20/// Whether workspaces are charged for agents at all, and with real money.
21/// `status` takes nothing and returns this.
22#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
23pub struct Status {
24 /// False when no payment provider is configured: nothing is charged,
25 /// and who may run agents is decided some other way.
26 pub enabled: bool,
27 /// False while the payment provider is in its test mode, where cards
28 /// are not real.
29 pub live: bool,
Free while g1t is being built out; agents can check out their own forks30 /// True while g1t is being built out: runs are recorded, with what
31 /// they cost, but nothing is charged and no credit is needed. Not a
32 /// promise that it stays free.
33 #[serde(default)]
34 pub free: bool,
Agents as a team: lifecycle, merge queue, billing and a new shell35}
36
A free allowance on g1t's models, so anyone can try its agents37/// `trial`: the free allowance on g1t's hosted models for a workspace that
38/// is not otherwise open to them, so people can try g1t's agents without a
39/// key of their own. Each workspace gets a few dollars of model cost, out
40/// of one pool, until an end date. Returns `Trial`.
41#[derive(Debug, Serialize, Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct TrialArgs {
44 pub workspace: String,
45 /// Workspaces open to hosted models anyway, whose use is not counted
46 /// against the pool.
47 #[serde(default)]
48 pub exempt: Vec<String>,
49}
50
51#[derive(Clone, Debug, Serialize, Deserialize)]
52#[serde(rename_all = "camelCase")]
53pub struct Trial {
54 /// Whether its agents may use g1t's hosted models on the allowance now.
55 pub open: bool,
56 /// What its runs on g1t's models have cost, in millionths of a dollar.
57 pub used_micros: i64,
58 pub limit_micros: i64,
59 /// RFC 3339; when the allowance ends for everyone.
60 pub ends_at: Option<String>,
61 /// Why it is closed: `off` (no allowance), `ended`, `used` (this
62 /// workspace's is spent) or `pool` (everyone's is).
63 pub reason: Option<String>,
64}
65
Agents as a team: lifecycle, merge queue, billing and a new shell66/// A workspace's standing.
67#[derive(Clone, Debug, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct Account {
70 pub workspace: String,
71 /// Credit left, in millionths of a dollar. Can dip below zero by the
72 /// cost of the runs that were under way when it ran out.
73 pub balance_micros: i64,
74 pub status: Status,
75 /// What is added to a run's cost, in percent.
76 pub margin_percent: u32,
Integrations: your own model provider, alerts that open issues, tickets agents read77 /// What a run on the workspace's own model provider is charged: g1t's
78 /// sandbox and orchestration, with the model paid for elsewhere.
79 pub orchestration_fee_micros: i64,
Agents as a team: lifecycle, merge queue, billing and a new shell80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
83#[serde(rename_all = "snake_case")]
84pub enum EntryKind {
85 /// Credit bought with a card.
86 TopUp,
Paid features: a workspace turns on Deployments with a monthly plan87 /// An agent's run, or a paid feature's usage past its allowance.
Agents as a team: lifecycle, merge queue, billing and a new shell88 Usage,
89}
90
91/// One line of a workspace's statement.
92#[derive(Clone, Debug, Serialize, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct LedgerEntry {
95 pub id: String,
96 pub kind: EntryKind,
97 /// Positive for credit added, negative for usage.
98 pub amount_micros: i64,
99 pub description: String,
100 /// For usage: the repository and pull request the agent worked on.
101 pub repo: Option<String>,
102 pub number: Option<u32>,
103 /// For usage: `implement`, `review` or `update`.
104 pub task: Option<String>,
105 /// For usage: the model, by its public name.
106 pub model: Option<String>,
Integrations: your own model provider, alerts that open issues, tickets agents read107 /// For usage: `g1t` when g1t paid the model provider, `workspace` when
108 /// the workspace's own account did and only orchestration is charged.
109 #[serde(default = "g1t")]
110 pub billed_to: String,
Agents as a team: lifecycle, merge queue, billing and a new shell111 /// For a top-up: the username of whoever paid.
112 pub created_by: Option<String>,
113 /// RFC 3339.
114 pub created_at: String,
115}
116
Integrations: your own model provider, alerts that open issues, tickets agents read117fn g1t() -> String {
118 "g1t".to_owned()
119}
120
Agents as a team: lifecycle, merge queue, billing and a new shell121/// `account` (`Outcome<Account>`) and `ledger` (`Outcome<Vec<LedgerEntry>>`,
122/// newest first). Members of the workspace only.
123#[derive(Debug, Serialize, Deserialize)]
124pub struct AccountArgs {
125 pub workspace: String,
126 pub viewer: Viewer,
127}
128
129/// `checkout`: starts a card payment for credit. Owners of the workspace
130/// only. Returns `Outcome<Checkout>`.
131#[derive(Debug, Serialize, Deserialize)]
132#[serde(rename_all = "camelCase")]
133pub struct CheckoutArgs {
134 pub actor: User,
135 pub workspace: String,
136 /// How much credit to buy, in cents.
137 pub amount_cents: u32,
138 /// Where the payment page sends the person afterwards. The payment's
139 /// id is appended as `session`.
140 pub return_url: String,
141}
142
143#[derive(Debug, Serialize, Deserialize)]
144pub struct Checkout {
145 /// The payment page to send the person to.
146 pub url: String,
147}
148
149/// `confirm`: credits a payment once the provider says it was made. Safe
150/// to call any number of times. Returns `Outcome<Account>`.
151#[derive(Debug, Serialize, Deserialize)]
152pub struct ConfirmArgs {
153 pub workspace: String,
154 pub viewer: Viewer,
155 /// The payment's id, as returned to `return_url`.
156 pub session: String,
157}
158
159/// `can_start`: whether a workspace may start an agent now, asked before
160/// anything is opened for it. Returns `Outcome<bool>`: a failure, with the
161/// reason to show, when it has no credit.
162#[derive(Debug, Serialize, Deserialize)]
163pub struct CanStartArgs {
164 pub workspace: String,
165}
166
167/// `start_run`: asks whether a workspace may start an agent, and opens the
168/// run it will be charged for. Called by the runner service. Returns
169/// `Outcome<Option<RunTicket>>`: no ticket when billing is off, a failure
170/// when the workspace has no credit.
171#[derive(Debug, Serialize, Deserialize)]
172pub struct StartRunArgs {
173 pub workspace: String,
174 pub repo: RepoPath,
175 pub number: u32,
176 /// `implement`, `review` or `update`.
177 pub task: String,
178 /// The model, by its public name.
179 pub model: String,
Integrations: your own model provider, alerts that open issues, tickets agents read180 /// `workspace` when the run uses the workspace's own model provider.
Models per workspace: several providers, routed by kind of work181 /// The runner, which is TypeScript, sends it as `billedTo`.
182 #[serde(default = "g1t", alias = "billedTo")]
Integrations: your own model provider, alerts that open issues, tickets agents read183 pub billed_to: String,
Prices keep themselves current with what g1t pays184 /// The model session's id, when its requests go through g1t's AI
185 /// Gateway: settling charges the run what the gateway priced them at.
186 #[serde(default)]
187 pub session: Option<String>,
Agents as a team: lifecycle, merge queue, billing and a new shell188}
189
190#[derive(Clone, Debug, Serialize, Deserialize)]
191#[serde(rename_all = "camelCase")]
192pub struct RunTicket {
193 pub run_id: String,
194 /// Lets the sandbox, and nothing else, report what this run cost.
195 pub token: String,
196}
197
198/// `finish_run`: what a run cost, as its sandbox reports it. Charged once.
199/// Returns `Outcome<bool>`.
200#[derive(Debug, Serialize, Deserialize)]
201#[serde(rename_all = "camelCase")]
202pub struct FinishRunArgs {
203 pub run_id: String,
204 pub token: String,
205 /// What the model provider charged, in US dollars.
206 pub cost_usd: f64,
207 #[serde(default)]
208 pub turns: u32,
209}
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request210
211
212/// `usage`: what a workspace's agents cost over a period, broken down.
213/// Members only. Returns `Outcome<Usage>`.
214#[derive(Debug, Serialize, Deserialize)]
215pub struct UsageArgs {
216 pub workspace: String,
217 pub viewer: Viewer,
218 /// RFC 3339: the start of the period. The period runs to now.
219 pub since: String,
220}
221
222/// One slice of usage: what it was for, what it cost, how many runs.
223#[derive(Clone, Debug, Serialize, Deserialize)]
224#[serde(rename_all = "camelCase")]
225pub struct UsageSlice {
226 pub key: String,
227 pub micros: i64,
228 pub runs: u32,
229}
230
231/// What a workspace's agents cost over a period.
232#[derive(Clone, Debug, Serialize, Deserialize)]
233#[serde(rename_all = "camelCase")]
234pub struct Usage {
235 pub since: String,
236 /// Charged, including g1t's margin.
237 pub spent_micros: i64,
Integrations: your own model provider, alerts that open issues, tickets agents read238 /// What g1t's model provider charged, before the margin.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request239 pub cost_micros: i64,
Integrations: your own model provider, alerts that open issues, tickets agents read240 /// What runs on the workspace's own provider cost there, as the harness
241 /// estimated it. Not charged by g1t.
242 pub provider_micros: i64,
Usage while free is shown at cost; agents get rustfmt and clippy243 /// What the runs used, at cost: g1t's models and the workspace's own
244 /// provider together, whatever was charged for them.
245 pub used_micros: i64,
246 /// g1t charges nothing for now. The slices then measure usage at cost,
247 /// since every charge is zero.
248 pub free: bool,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request249 pub runs: u32,
250 /// Spend per day (`YYYY-MM-DD`) and task, as `day/task` keys.
251 pub by_day: Vec<UsageSlice>,
252 /// Per task: implement, review, revise, update, plan.
253 pub by_task: Vec<UsageSlice>,
254 /// Per repository, `namespace/name`.
255 pub by_repo: Vec<UsageSlice>,
256 /// The pull requests that cost most, as `namespace/name#number`.
257 pub by_pull: Vec<UsageSlice>,
258 /// Per model, by its public name.
259 pub by_model: Vec<UsageSlice>,
260 /// Credit bought in the period.
261 pub added_micros: i64,
262}
Models per workspace: several providers, routed by kind of work263
Paid features: a workspace turns on Deployments with a monthly plan264/// A paid feature a workspace turns on with a monthly plan, the way
265/// Cloudflare's Workers for Platforms or Vercel's Pro are bought. Never
266/// free: `FREE_WHILE_BUILDING` and the free model allowance do not cover
267/// it.
268#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
269#[serde(rename_all = "snake_case")]
270pub enum Feature {
271 /// Previews per pull request and production on g1t.page.
272 Deployments,
273}
274
275impl Feature {
276 pub const ALL: [Feature; 1] = [Feature::Deployments];
277
278 pub fn as_str(self) -> &'static str {
279 match self {
280 Feature::Deployments => "deployments",
281 }
282 }
283
284 pub fn parse(name: &str) -> Option<Feature> {
285 Feature::ALL.into_iter().find(|feature| feature.as_str() == name)
286 }
287
288 pub fn title(self) -> &'static str {
289 match self {
290 Feature::Deployments => "Deployments",
291 }
292 }
293}
294
295/// What the Deployments plan includes each month; usage past it is charged
296/// at cost plus the margin. The billing service describes the plan with
297/// these and the deployments service meters against them.
298pub mod deployments_allowance {
299 /// Apps deployed at once: production and previews together.
300 pub const APPS: u32 = 10;
301 pub const REQUESTS: u64 = 1_000_000;
302 pub const CPU_MS: u64 = 3_000_000;
303 /// What Cloudflare charges g1t past that, in millionths of a dollar.
304 pub const MICROS_PER_APP_MONTH: i64 = 20_000;
305 pub const MICROS_PER_MILLION_REQUESTS: i64 = 300_000;
306 pub const MICROS_PER_MILLION_CPU_MS: i64 = 20_000;
Deployments: a preview for every pull request, production on g1t.page307 /// What one second of a build's sandbox costs g1t (Cloudflare
308 /// Containers, standard-1: half a vCPU, 4 GiB, 8 GB disk), rounded up.
309 /// Builds are not in the allowance: each is charged at this plus the
310 /// margin.
311 pub const MICROS_PER_BUILD_SECOND: i64 = 21;
Paid features: a workspace turns on Deployments with a monthly plan312}
313
Every sandbox is metered by the second314/// Sandbox time: every sandbox g1t starts for a workspace (agents,
315/// reviews, checks, the merge queue, workflow jobs) is metered by the
316/// second. Deploy builds are charged by the Deployments plan instead.
317pub mod sandbox_allowance {
318 /// Free each calendar month (UTC): 500 minutes.
319 pub const FREE_SECONDS: i64 = 30_000;
320 /// What one second costs g1t (Cloudflare Containers, standard-1),
321 /// rounded up. Recorded with every entry.
322 pub const COST_MICROS_PER_SECOND: i64 = super::deployments_allowance::MICROS_PER_BUILD_SECOND;
323 /// What one second past the free minutes is charged: $0.003 a minute.
324 pub const MICROS_PER_SECOND: i64 = 50;
325}
326
327/// `record_sandbox`: how long one sandbox ran for a workspace, reported by
328/// the runner when it stops. Recorded once per `reference`, with what it
329/// cost g1t; seconds past the month's free minutes are charged at
330/// `sandbox_allowance::MICROS_PER_SECOND`, unless `FREE_WHILE_BUILDING`.
331/// Returns `Outcome<bool>`: false if that reference was recorded before.
332#[derive(Debug, Serialize, Deserialize)]
333#[serde(rename_all = "camelCase")]
334pub struct RecordSandboxArgs {
335 pub workspace: String,
336 pub seconds: u32,
337 /// What ran, e.g. `Checks on acme/api#12`.
338 pub description: String,
339 /// `namespace/name`.
340 pub repo: Option<String>,
341 /// Unique to the run.
342 pub reference: String,
343}
344
Usage limits: unpaid usage can only go so far345/// How much a workspace has earned g1t's trust with money, which sets how
346/// far its unpaid usage can go before its work stops.
347#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
348#[serde(rename_all = "snake_case")]
349pub enum Trust {
350 /// No live payment yet: only a little past the free allowances.
351 New,
352 /// Has paid g1t real money: the ceiling grows with what it has paid.
353 Paid,
354 /// A ceiling g1t set by hand, after talking to the workspace.
355 Reviewed,
356 /// g1t's own workspaces: no ceiling.
357 Internal,
358}
359
360#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
361#[serde(rename_all = "snake_case")]
362pub enum LimitState {
363 Ok,
364 /// Past 80% of the ceiling.
365 Warning,
366 /// At or past it: no new sandboxes, builds or app requests.
367 Stopped,
368}
369
370/// How far a workspace's unpaid usage has gone this month, and where its
371/// work stops: like Fly's or Cloudflare's limits for new accounts, so no
372/// one runs up costs g1t cannot collect. Usage counts at what it cost g1t
373/// or what it is charged, whichever is more, so it counts while g1t is
374/// free too.
375#[derive(Clone, Debug, Serialize, Deserialize)]
376#[serde(rename_all = "camelCase")]
377pub struct Limit {
378 pub workspace: String,
Billing accounts, terms and enterprises; g1t is no longer free379 /// The account that pays, whose usage and payments the limit counts:
380 /// the workspace's own, or its enterprise's.
381 #[serde(default)]
382 pub account: String,
383 #[serde(default)]
384 pub account_name: String,
Usage limits: unpaid usage can only go so far385 pub trust: Trust,
386 /// Usage this month (UTC) less what was paid this month.
387 pub exposure_micros: i64,
388 /// Where work stops: the lower of g1t's ceiling and the owner's own
389 /// spend limit. None for g1t's own workspaces.
390 pub ceiling_micros: Option<i64>,
391 /// The ceiling g1t sets from `trust`.
392 pub trust_ceiling_micros: Option<i64>,
393 /// The owner's own monthly limit, if they set one.
394 pub spend_limit_micros: Option<i64>,
395 pub state: LimitState,
396 /// What to tell people when work is stopped or close to it.
397 pub message: Option<String>,
398}
399
400/// `limit`: a workspace's limit, for its members. Returns `Outcome<Limit>`.
401#[derive(Debug, Serialize, Deserialize)]
402pub struct LimitArgs {
403 pub workspace: String,
404 pub viewer: Viewer,
405}
406
407/// `check_limit`: the same, for the services that enforce it. Returns
408/// `Outcome<Limit>`.
409#[derive(Debug, Serialize, Deserialize)]
410pub struct CheckLimitArgs {
411 pub workspace: String,
412}
413
Prices keep themselves current with what g1t pays414/// `note_pending`: usage this month that will be charged later, such as
415/// app traffic past a plan, so the workspace's limit counts it now. Each
416/// report replaces the last for that workspace, source and month. Called
417/// by the service that meters it. Returns `bool`.
418#[derive(Debug, Serialize, Deserialize)]
419#[serde(rename_all = "camelCase")]
420pub struct NotePendingArgs {
421 pub workspace: String,
422 /// `deployments`.
423 pub source: String,
424 /// What it cost g1t so far this month, before the margin.
425 pub cost_micros: i64,
426}
427
Usage limits: unpaid usage can only go so far428/// `set_spend_limit`: the owner's own monthly ceiling, under g1t's; None
429/// removes it. Owners only. Returns `Outcome<Limit>`.
430#[derive(Debug, Serialize, Deserialize)]
431#[serde(rename_all = "camelCase")]
432pub struct SetSpendLimitArgs {
433 pub actor: User,
434 pub workspace: String,
435 pub spend_limit_micros: Option<i64>,
436}
437
Prices keep themselves current with what g1t pays438/// One metered unit: what it costs g1t, and what it is sold at. The price
439/// is always `cost × (100 + markup) / 100`, so it follows the cost.
440#[derive(Clone, Debug, Serialize, Deserialize)]
441#[serde(rename_all = "camelCase")]
442pub struct Price {
443 /// `sandbox_second`, `build_second`, `app_requests`, `app_cpu`, `app_month`.
444 pub meter: String,
445 pub title: String,
446 pub unit: String,
447 /// Millionths of a dollar per unit; may have a fraction.
448 pub cost_micros: f64,
449 pub markup_percent: u32,
450 pub price_micros: f64,
451 /// `list`: Cloudflare's published price. `cloudflare`: what Cloudflare
452 /// actually billed g1t, measured.
453 pub source: String,
454 /// When it was last checked against Cloudflare's bill.
455 pub checked_at: Option<String>,
456 pub updated_at: String,
457}
458
459impl Price {
460 pub fn price_for(cost_micros: f64, markup_percent: u32) -> f64 {
461 cost_micros * f64::from(100 + markup_percent) / 100.0
462 }
463}
464
465/// A cost that moved.
466#[derive(Clone, Debug, Serialize, Deserialize)]
467#[serde(rename_all = "camelCase")]
468pub struct PriceChange {
469 pub meter: String,
470 pub old_cost_micros: f64,
471 pub new_cost_micros: f64,
472 pub markup_percent: u32,
473 pub reason: String,
474 pub created_at: String,
475}
476
477/// `prices`: every metered price and the recent changes. Public. Returns
478/// `PriceBook`.
479#[derive(Clone, Debug, Serialize, Deserialize)]
480#[serde(rename_all = "camelCase")]
481pub struct PriceBook {
482 pub prices: Vec<Price>,
483 pub changes: Vec<PriceChange>,
484 /// The margin on model usage, which is charged at what AI Gateway
485 /// priced each request at.
486 pub model_margin_percent: u32,
487}
488
Billing accounts, terms and enterprises; g1t is no longer free489/// Who pays: a billing account. Every workspace has one; by default its
490/// own. An enterprise account pays for several workspaces at once, as
491/// GitHub Enterprise does: one bill, one limit, one set of terms.
492#[derive(Clone, Debug, Serialize, Deserialize)]
493#[serde(rename_all = "camelCase")]
494pub struct BillingAccount {
495 /// `ws_<slug>` for a workspace's own account; `ent_…` for an enterprise.
496 pub id: String,
497 pub kind: AccountKind,
498 pub name: String,
499 pub terms: Terms,
500 /// The workspaces it pays for.
501 pub workspaces: Vec<String>,
502 pub created_at: String,
503}
504
505#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
506#[serde(rename_all = "snake_case")]
507pub enum AccountKind {
508 Workspace,
509 Enterprise,
510}
511
512/// How an account is charged. Standard unless g1t set otherwise in sudo.
513#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
514#[serde(rename_all = "camelCase")]
515pub struct Terms {
516 pub kind: TermsKind,
517 /// Off every usage charge, in percent. Custom terms only.
518 #[serde(default)]
519 pub discount_percent: u32,
520 /// A ceiling on unpaid usage that replaces the one trust would give.
521 #[serde(default)]
522 pub ceiling_micros: Option<i64>,
523 /// Why, for whoever looks next.
524 #[serde(default)]
525 pub note: String,
526 /// When the terms end and the account goes back to standard.
527 #[serde(default)]
528 pub until: Option<String>,
529 #[serde(default)]
530 pub set_by: Option<String>,
531 #[serde(default)]
532 pub set_at: Option<String>,
533}
534
535impl Terms {
536 pub fn standard() -> Self {
537 Terms {
538 kind: TermsKind::Standard,
539 discount_percent: 0,
540 ceiling_micros: None,
541 note: String::new(),
542 until: None,
543 set_by: None,
544 set_at: None,
545 }
546 }
547
548 /// What a charge becomes under these terms.
549 pub fn apply(&self, charge_micros: i64) -> i64 {
550 match self.kind {
551 TermsKind::Comped => 0,
552 TermsKind::Custom => charge_micros * i64::from(100 - self.discount_percent.min(100)) / 100,
553 TermsKind::Standard => charge_micros,
554 }
555 }
556}
557
558#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
559#[serde(rename_all = "snake_case")]
560pub enum TermsKind {
561 /// Prices as published, limits by trust.
562 Standard,
563 /// Nothing charged; usage still recorded with its cost. Paid features
564 /// are on without a plan. For g1t's own workspaces, partners, and the
565 /// like.
566 Comped,
567 /// A discount, a ceiling, or both.
568 Custom,
569}
570
571// --- Staff (sudo.g1t.sh) ------------------------------------------------------
572//
573// Called only by the sudo app, which only g1t staff can reach (behind
574// Cloudflare Access). Each change names who made it, and is kept in the
575// audit log.
576
577/// `admin_accounts`: every billing account, with where each stands this
578/// month. Returns `Vec<AccountSummary>`.
579#[derive(Debug, Default, Serialize, Deserialize)]
580pub struct AdminAccountsArgs {
581 #[serde(default)]
582 pub query: Option<String>,
583}
584
585#[derive(Clone, Debug, Serialize, Deserialize)]
586#[serde(rename_all = "camelCase")]
587pub struct AccountSummary {
588 pub account: BillingAccount,
589 pub limit: Limit,
590 /// Charged this month, after terms.
591 pub charged_micros: i64,
592 /// What this month's usage cost g1t.
593 pub cost_micros: i64,
594 /// Paid, ever.
595 pub paid_micros: i64,
596}
597
598/// `admin_account`: one account in full. Returns `Outcome<AccountDetail>`.
599#[derive(Debug, Serialize, Deserialize)]
600pub struct AdminAccountArgs {
601 /// An account id, or a workspace slug.
602 pub id: String,
603}
604
605#[derive(Clone, Debug, Serialize, Deserialize)]
606#[serde(rename_all = "camelCase")]
607pub struct AccountDetail {
608 pub summary: AccountSummary,
609 /// Each workspace's limit, for an enterprise.
610 pub workspaces: Vec<Limit>,
611 pub ledger: Vec<LedgerEntry>,
612 pub audit: Vec<AdminAction>,
613}
614
615/// `admin_set_terms`. Returns `Outcome<BillingAccount>`.
616#[derive(Debug, Serialize, Deserialize)]
617pub struct AdminSetTermsArgs {
618 pub id: String,
619 pub terms: Terms,
620 pub by: String,
621}
622
623/// `admin_create_enterprise`. Returns `Outcome<BillingAccount>`.
624#[derive(Debug, Serialize, Deserialize)]
625pub struct AdminCreateEnterpriseArgs {
626 pub name: String,
627 pub workspaces: Vec<String>,
628 pub by: String,
629}
630
631/// `admin_attach`: moves a workspace onto an enterprise account, or back
632/// onto its own with `account: None`. Returns `Outcome<BillingAccount>`.
633#[derive(Debug, Serialize, Deserialize)]
634pub struct AdminAttachArgs {
635 pub workspace: String,
636 pub account: Option<String>,
637 pub by: String,
638}
639
640/// `admin_credit`: money g1t gives a workspace, such as a refund or a
641/// goodwill credit. Returns `Outcome<LedgerEntry>`.
642#[derive(Debug, Serialize, Deserialize)]
643pub struct AdminCreditArgs {
644 pub workspace: String,
645 pub amount_micros: i64,
646 pub note: String,
647 pub by: String,
648}
649
650/// One change made in sudo.
651#[derive(Clone, Debug, Serialize, Deserialize)]
652#[serde(rename_all = "camelCase")]
653pub struct AdminAction {
654 pub id: String,
655 pub account: String,
656 pub action: String,
657 pub detail: String,
658 pub by: String,
659 pub created_at: String,
660}
661
Paid features: a workspace turns on Deployments with a monthly plan662/// What a feature's plan costs and includes.
663#[derive(Clone, Debug, Serialize, Deserialize)]
664#[serde(rename_all = "camelCase")]
665pub struct Plan {
666 pub feature: Feature,
667 pub title: String,
668 /// Charged every month while the plan is on, in cents.
669 pub monthly_cents: u32,
670 /// What the monthly price includes, one line each, for people to read.
671 pub includes: Vec<String>,
672 /// How usage past the allowance is charged, for people to read.
673 pub overage: String,
674}
675
676#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
677#[serde(rename_all = "snake_case")]
678pub enum SubscriptionStatus {
679 /// Paid up; the feature works.
680 Active,
681 /// Paid up to the end of the period, and ends then.
682 Canceling,
683 /// The last payment failed; the feature is off until it is paid.
684 PastDue,
685 /// Ended.
686 Canceled,
687}
688
689impl SubscriptionStatus {
690 /// Whether the feature works in this state.
691 pub fn on(self) -> bool {
692 matches!(self, SubscriptionStatus::Active | SubscriptionStatus::Canceling)
693 }
694}
695
696/// A workspace's plan for one feature.
697#[derive(Clone, Debug, Serialize, Deserialize)]
698#[serde(rename_all = "camelCase")]
699pub struct Subscription {
700 pub feature: Feature,
701 pub status: SubscriptionStatus,
702 /// RFC 3339: when the period paid for ends, and the plan renews or
703 /// ends.
704 pub period_end: Option<String>,
705 /// Username of whoever turned it on.
706 pub started_by: String,
707 /// RFC 3339.
708 pub started_at: String,
709}
710
711/// A feature as a workspace sees it: what it costs, and its plan if it has
712/// one.
713#[derive(Clone, Debug, Serialize, Deserialize)]
714#[serde(rename_all = "camelCase")]
715pub struct FeatureState {
716 pub plan: Plan,
717 pub subscription: Option<Subscription>,
718 /// Whether the feature works for the workspace now.
719 pub on: bool,
720}
721
722/// `features`: every paid feature and the workspace's plan for each.
723/// Members only. Returns `Outcome<Vec<FeatureState>>`.
724#[derive(Debug, Serialize, Deserialize)]
725pub struct FeaturesArgs {
726 pub workspace: String,
727 pub viewer: Viewer,
728}
729
730/// `subscribe`: starts the card page for a feature's monthly plan. Owners
731/// only. Returns `Outcome<Checkout>`; the page's id comes back to
732/// `return_url` as `session`, for `confirm_subscription`.
733#[derive(Debug, Serialize, Deserialize)]
734#[serde(rename_all = "camelCase")]
735pub struct SubscribeArgs {
736 pub actor: User,
737 pub workspace: String,
738 pub feature: Feature,
739 pub return_url: String,
740}
741
742/// `confirm_subscription`: turns the feature on once the processor says
743/// the plan was paid for. Safe to call any number of times. Returns
744/// `Outcome<FeatureState>`.
745#[derive(Debug, Serialize, Deserialize)]
746pub struct ConfirmSubscriptionArgs {
747 pub workspace: String,
748 pub viewer: Viewer,
749 pub session: String,
750}
751
752/// `cancel_subscription` (`resume` false) ends a plan at the end of the
753/// period paid for; with `resume` true, takes that back. Owners only.
754/// Returns `Outcome<FeatureState>`.
755#[derive(Debug, Serialize, Deserialize)]
756pub struct CancelSubscriptionArgs {
757 pub actor: User,
758 pub workspace: String,
759 pub feature: Feature,
760 #[serde(default)]
761 pub resume: bool,
762}
763
764/// `has_feature`: whether a feature works for a workspace now, asked by the
765/// service that provides it before doing paid work. Returns
766/// `Outcome<bool>`: a failure, with the reason to show, when it does not.
767/// True everywhere when no card processor is configured.
768#[derive(Debug, Serialize, Deserialize)]
769pub struct HasFeatureArgs {
770 pub workspace: String,
771 pub feature: Feature,
772}
773
774/// `charge_feature`: usage of a feature past its plan's allowance, charged
775/// from the workspace's credit at cost plus the margin, whatever
776/// `FREE_WHILE_BUILDING` says. Called by the service that provides it.
777/// Charged once per `reference`. Returns `Outcome<bool>`: false if that
778/// reference was charged before.
779#[derive(Debug, Serialize, Deserialize)]
780#[serde(rename_all = "camelCase")]
781pub struct ChargeFeatureArgs {
782 pub workspace: String,
783 pub feature: Feature,
784 /// What it cost g1t, in millionths of a dollar, before the margin.
785 pub cost_micros: i64,
786 pub description: String,
787 /// `namespace/name`, when the usage was one repository's.
788 pub repo: Option<String>,
789 /// Unique to this charge, e.g. `deployments/acme/2026-10`.
790 pub reference: String,
791}
792
Models per workspace: several providers, routed by kind of work793#[cfg(test)]
794mod tests {
795 use super::*;
796
797 #[test]
Paid features: a workspace turns on Deployments with a monthly plan798 fn features_are_named_as_the_site_sends_them() {
799 assert_eq!(
800 serde_json::to_value(Feature::Deployments).unwrap(),
801 serde_json::json!("deployments")
802 );
803 assert_eq!(Feature::parse("deployments"), Some(Feature::Deployments));
804 assert!(SubscriptionStatus::Canceling.on());
805 assert!(!SubscriptionStatus::PastDue.on());
806 }
807
808 #[test]
Models per workspace: several providers, routed by kind of work809 fn who_pays_is_read_as_the_runner_sends_it() {
810 let run: StartRunArgs = serde_json::from_value(serde_json::json!({
811 "workspace": "acme",
812 "repo": { "namespace": "acme", "name": "web" },
813 "number": 7,
814 "task": "implement",
815 "model": "Claude Sonnet 5.5",
816 "billedTo": "workspace",
817 }))
818 .unwrap();
819 assert_eq!(run.billed_to, "workspace");
820 }
821}