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

1,346 lines45,310 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,
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace77 /// The card g1t charges as the workspace nears its limit and when a
78 /// month closes, if one is on file.
79 #[serde(default)]
80 pub card: Option<Card>,
Agents as a team: lifecycle, merge queue, billing and a new shell81}
82
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace83/// A saved card, as far as it is safe to show.
84#[derive(Clone, Debug, Serialize, Deserialize)]
85#[serde(rename_all = "camelCase")]
86pub struct Card {
87 /// `visa`, `mastercard`, ...
88 pub brand: String,
89 pub last4: String,
90 pub exp_month: u32,
91 pub exp_year: u32,
92}
93
94/// `billing_portal`: Stripe's hosted billing page for the workspace, where
95/// an owner adds or replaces the card, sees invoices and receipts, and sets
96/// the billing email and address. g1t never handles card numbers. Owners
97/// only. Returns `Outcome<Checkout>` (its `url`); Stripe sends them back
98/// to `return_url`.
99#[derive(Debug, Serialize, Deserialize)]
100pub struct BillingPortalArgs {
101 pub actor: User,
102 pub workspace: String,
103 pub return_url: String,
104}
105
106/// `admin_billing_link`: for staff to send a customer: their Stripe billing
107/// page. Returns `Outcome<BillingLink>`.
108#[derive(Debug, Serialize, Deserialize)]
109pub struct AdminBillingLinkArgs {
110 pub workspace: String,
111 pub by: String,
112}
113
114#[derive(Clone, Debug, Serialize, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub struct BillingLink {
117 /// A one-time session on Stripe's billing page, signed in already.
118 pub portal_url: String,
119 /// The billing page's sign-in page, which does not expire: the
120 /// customer signs in with the email Stripe has for them.
121 pub login_url: Option<String>,
122 pub customer_email: Option<String>,
123 pub expires_note: String,
124}
125
Agents as a team: lifecycle, merge queue, billing and a new shell126#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
127#[serde(rename_all = "snake_case")]
128pub enum EntryKind {
129 /// Credit bought with a card.
130 TopUp,
Paid features: a workspace turns on Deployments with a monthly plan131 /// An agent's run, or a paid feature's usage past its allowance.
Agents as a team: lifecycle, merge queue, billing and a new shell132 Usage,
133}
134
135/// One line of a workspace's statement.
136#[derive(Clone, Debug, Serialize, Deserialize)]
137#[serde(rename_all = "camelCase")]
138pub struct LedgerEntry {
139 pub id: String,
140 pub kind: EntryKind,
141 /// Positive for credit added, negative for usage.
142 pub amount_micros: i64,
143 pub description: String,
144 /// For usage: the repository and pull request the agent worked on.
145 pub repo: Option<String>,
146 pub number: Option<u32>,
147 /// For usage: `implement`, `review` or `update`.
148 pub task: Option<String>,
149 /// For usage: the model, by its public name.
150 pub model: Option<String>,
Integrations: your own model provider, alerts that open issues, tickets agents read151 /// For usage: `g1t` when g1t paid the model provider, `workspace` when
Prices are what g1t pays plus 20%, from the first second152 /// the workspace's own account did. Runs on the workspace's own
153 /// provider pay only their sandbox time now, so only older entries
154 /// are `workspace`.
Integrations: your own model provider, alerts that open issues, tickets agents read155 #[serde(default = "g1t")]
156 pub billed_to: String,
Agents as a team: lifecycle, merge queue, billing and a new shell157 /// For a top-up: the username of whoever paid.
158 pub created_by: Option<String>,
159 /// RFC 3339.
160 pub created_at: String,
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace161 /// The workspace the line belongs to, which tells an enterprise's
162 /// lines apart.
163 #[serde(default, skip_serializing_if = "Option::is_none")]
164 pub workspace: Option<String>,
Agents as a team: lifecycle, merge queue, billing and a new shell165}
166
Integrations: your own model provider, alerts that open issues, tickets agents read167fn g1t() -> String {
168 "g1t".to_owned()
169}
170
Agents as a team: lifecycle, merge queue, billing and a new shell171/// `account` (`Outcome<Account>`) and `ledger` (`Outcome<Vec<LedgerEntry>>`,
172/// newest first). Members of the workspace only.
173#[derive(Debug, Serialize, Deserialize)]
174pub struct AccountArgs {
175 pub workspace: String,
176 pub viewer: Viewer,
177}
178
179/// `checkout`: starts a card payment for credit. Owners of the workspace
180/// only. Returns `Outcome<Checkout>`.
181#[derive(Debug, Serialize, Deserialize)]
182#[serde(rename_all = "camelCase")]
183pub struct CheckoutArgs {
184 pub actor: User,
185 pub workspace: String,
186 /// How much credit to buy, in cents.
187 pub amount_cents: u32,
188 /// Where the payment page sends the person afterwards. The payment's
189 /// id is appended as `session`.
190 pub return_url: String,
191}
192
193#[derive(Debug, Serialize, Deserialize)]
194pub struct Checkout {
195 /// The payment page to send the person to.
196 pub url: String,
197}
198
199/// `confirm`: credits a payment once the provider says it was made. Safe
200/// to call any number of times. Returns `Outcome<Account>`.
201#[derive(Debug, Serialize, Deserialize)]
202pub struct ConfirmArgs {
203 pub workspace: String,
204 pub viewer: Viewer,
205 /// The payment's id, as returned to `return_url`.
206 pub session: String,
207}
208
209/// `can_start`: whether a workspace may start an agent now, asked before
210/// anything is opened for it. Returns `Outcome<bool>`: a failure, with the
211/// reason to show, when it has no credit.
212#[derive(Debug, Serialize, Deserialize)]
213pub struct CanStartArgs {
214 pub workspace: String,
215}
216
217/// `start_run`: asks whether a workspace may start an agent, and opens the
218/// run it will be charged for. Called by the runner service. Returns
219/// `Outcome<Option<RunTicket>>`: no ticket when billing is off, a failure
220/// when the workspace has no credit.
221#[derive(Debug, Serialize, Deserialize)]
222pub struct StartRunArgs {
223 pub workspace: String,
224 pub repo: RepoPath,
225 pub number: u32,
226 /// `implement`, `review` or `update`.
227 pub task: String,
228 /// The model, by its public name.
229 pub model: String,
Integrations: your own model provider, alerts that open issues, tickets agents read230 /// `workspace` when the run uses the workspace's own model provider.
Models per workspace: several providers, routed by kind of work231 /// The runner, which is TypeScript, sends it as `billedTo`.
232 #[serde(default = "g1t", alias = "billedTo")]
Integrations: your own model provider, alerts that open issues, tickets agents read233 pub billed_to: String,
Prices keep themselves current with what g1t pays234 /// The model session's id, when its requests go through g1t's AI
235 /// Gateway: settling charges the run what the gateway priced them at.
236 #[serde(default)]
237 pub session: Option<String>,
Agents as a team: lifecycle, merge queue, billing and a new shell238}
239
240#[derive(Clone, Debug, Serialize, Deserialize)]
241#[serde(rename_all = "camelCase")]
242pub struct RunTicket {
243 pub run_id: String,
244 /// Lets the sandbox, and nothing else, report what this run cost.
245 pub token: String,
246}
247
248/// `finish_run`: what a run cost, as its sandbox reports it. Charged once.
249/// Returns `Outcome<bool>`.
250#[derive(Debug, Serialize, Deserialize)]
251#[serde(rename_all = "camelCase")]
252pub struct FinishRunArgs {
253 pub run_id: String,
254 pub token: String,
255 /// What the model provider charged, in US dollars.
256 pub cost_usd: f64,
257 #[serde(default)]
258 pub turns: u32,
259}
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request260
261
262/// `usage`: what a workspace's agents cost over a period, broken down.
263/// Members only. Returns `Outcome<Usage>`.
264#[derive(Debug, Serialize, Deserialize)]
265pub struct UsageArgs {
266 pub workspace: String,
267 pub viewer: Viewer,
268 /// RFC 3339: the start of the period. The period runs to now.
269 pub since: String,
270}
271
272/// One slice of usage: what it was for, what it cost, how many runs.
273#[derive(Clone, Debug, Serialize, Deserialize)]
274#[serde(rename_all = "camelCase")]
275pub struct UsageSlice {
276 pub key: String,
277 pub micros: i64,
278 pub runs: u32,
279}
280
281/// What a workspace's agents cost over a period.
282#[derive(Clone, Debug, Serialize, Deserialize)]
283#[serde(rename_all = "camelCase")]
284pub struct Usage {
285 pub since: String,
286 /// Charged, including g1t's margin.
287 pub spent_micros: i64,
Integrations: your own model provider, alerts that open issues, tickets agents read288 /// What g1t's model provider charged, before the margin.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request289 pub cost_micros: i64,
Integrations: your own model provider, alerts that open issues, tickets agents read290 /// What runs on the workspace's own provider cost there, as the harness
291 /// estimated it. Not charged by g1t.
292 pub provider_micros: i64,
Usage while free is shown at cost; agents get rustfmt and clippy293 /// What the runs used, at cost: g1t's models and the workspace's own
294 /// provider together, whatever was charged for them.
295 pub used_micros: i64,
296 /// g1t charges nothing for now. The slices then measure usage at cost,
297 /// since every charge is zero.
298 pub free: bool,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request299 pub runs: u32,
300 /// Spend per day (`YYYY-MM-DD`) and task, as `day/task` keys.
301 pub by_day: Vec<UsageSlice>,
302 /// Per task: implement, review, revise, update, plan.
303 pub by_task: Vec<UsageSlice>,
304 /// Per repository, `namespace/name`.
305 pub by_repo: Vec<UsageSlice>,
306 /// The pull requests that cost most, as `namespace/name#number`.
307 pub by_pull: Vec<UsageSlice>,
308 /// Per model, by its public name.
309 pub by_model: Vec<UsageSlice>,
310 /// Credit bought in the period.
311 pub added_micros: i64,
312}
Models per workspace: several providers, routed by kind of work313
Paid features: a workspace turns on Deployments with a monthly plan314/// A paid feature a workspace turns on with a monthly plan, the way
315/// Cloudflare's Workers for Platforms or Vercel's Pro are bought. Never
316/// free: `FREE_WHILE_BUILDING` and the free model allowance do not cover
317/// it.
318#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
319#[serde(rename_all = "snake_case")]
320pub enum Feature {
321 /// Previews per pull request and production on g1t.page.
322 Deployments,
323}
324
325impl Feature {
326 pub const ALL: [Feature; 1] = [Feature::Deployments];
327
328 pub fn as_str(self) -> &'static str {
329 match self {
330 Feature::Deployments => "deployments",
331 }
332 }
333
334 pub fn parse(name: &str) -> Option<Feature> {
335 Feature::ALL.into_iter().find(|feature| feature.as_str() == name)
336 }
337
338 pub fn title(self) -> &'static str {
339 match self {
340 Feature::Deployments => "Deployments",
341 }
342 }
343}
344
345/// What the Deployments plan includes each month; usage past it is charged
346/// at cost plus the margin. The billing service describes the plan with
347/// these and the deployments service meters against them.
348pub mod deployments_allowance {
349 /// Apps deployed at once: production and previews together.
350 pub const APPS: u32 = 10;
351 pub const REQUESTS: u64 = 1_000_000;
352 pub const CPU_MS: u64 = 3_000_000;
353 /// What Cloudflare charges g1t past that, in millionths of a dollar.
354 pub const MICROS_PER_APP_MONTH: i64 = 20_000;
355 pub const MICROS_PER_MILLION_REQUESTS: i64 = 300_000;
356 pub const MICROS_PER_MILLION_CPU_MS: i64 = 20_000;
Deployments: a preview for every pull request, production on g1t.page357 /// What one second of a build's sandbox costs g1t (Cloudflare
358 /// Containers, standard-1: half a vCPU, 4 GiB, 8 GB disk), rounded up.
359 /// Builds are not in the allowance: each is charged at this plus the
360 /// margin.
361 pub const MICROS_PER_BUILD_SECOND: i64 = 21;
Agents and memory, checks and conflicts, profiles, slug renames, custom domains362 /// Custom domains across the workspace (Cloudflare for SaaS custom
363 /// hostnames); each one past these is charged by the month.
364 pub const CUSTOM_DOMAINS: u32 = 3;
365 /// What one custom hostname costs g1t a month: $0.10.
366 pub const MICROS_PER_DOMAIN_MONTH: i64 = 100_000;
Paid features: a workspace turns on Deployments with a monthly plan367}
368
Every sandbox is metered by the second369/// `record_sandbox`: how long one sandbox ran for a workspace, reported by
Prices are what g1t pays plus 20%, from the first second370/// the runner when it stops. Every sandbox g1t starts for a workspace
371/// (agents, reviews, checks, the merge queue, workflow jobs) is metered by
372/// the second, from the first: recorded once per `reference`, with what it
373/// cost g1t, and charged at the price book's `sandbox_second` price unless
374/// `FREE_WHILE_BUILDING`. Deploy builds are charged by the Deployments plan
375/// instead.
Every sandbox is metered by the second376/// Returns `Outcome<bool>`: false if that reference was recorded before.
377#[derive(Debug, Serialize, Deserialize)]
378#[serde(rename_all = "camelCase")]
379pub struct RecordSandboxArgs {
380 pub workspace: String,
381 pub seconds: u32,
382 /// What ran, e.g. `Checks on acme/api#12`.
383 pub description: String,
384 /// `namespace/name`.
385 pub repo: Option<String>,
386 /// Unique to the run.
387 pub reference: String,
388}
389
Usage limits: unpaid usage can only go so far390/// How much a workspace has earned g1t's trust with money, which sets how
391/// far its unpaid usage can go before its work stops.
392#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
393#[serde(rename_all = "snake_case")]
394pub enum Trust {
395 /// No live payment yet: only a little past the free allowances.
396 New,
397 /// Has paid g1t real money: the ceiling grows with what it has paid.
398 Paid,
Two limits, real invoices, trust that grows by itself, sales signals399 /// Has paid steadily for months, with nothing disputed or declined:
400 /// the ceiling follows its monthly spend, up to $10,000, by itself.
401 Established,
Usage limits: unpaid usage can only go so far402 /// A ceiling g1t set by hand, after talking to the workspace.
403 Reviewed,
404 /// g1t's own workspaces: no ceiling.
405 Internal,
406}
407
408#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
409#[serde(rename_all = "snake_case")]
410pub enum LimitState {
411 Ok,
412 /// Past 80% of the ceiling.
413 Warning,
414 /// At or past it: no new sandboxes, builds or app requests.
415 Stopped,
416}
417
418/// How far a workspace's unpaid usage has gone this month, and where its
419/// work stops: like Fly's or Cloudflare's limits for new accounts, so no
420/// one runs up costs g1t cannot collect. Usage counts at what it cost g1t
421/// or what it is charged, whichever is more, so it counts while g1t is
422/// free too.
423#[derive(Clone, Debug, Serialize, Deserialize)]
424#[serde(rename_all = "camelCase")]
425pub struct Limit {
426 pub workspace: String,
Billing accounts, terms and enterprises; g1t is no longer free427 /// The account that pays, whose usage and payments the limit counts:
428 /// the workspace's own, or its enterprise's.
429 #[serde(default)]
430 pub account: String,
431 #[serde(default)]
432 pub account_name: String,
Usage limits: unpaid usage can only go so far433 pub trust: Trust,
434 /// Usage this month (UTC) less what was paid this month.
435 pub exposure_micros: i64,
436 /// Where work stops: the lower of g1t's ceiling and the owner's own
437 /// spend limit. None for g1t's own workspaces.
438 pub ceiling_micros: Option<i64>,
439 /// The ceiling g1t sets from `trust`.
440 pub trust_ceiling_micros: Option<i64>,
441 /// The owner's own monthly limit, if they set one.
442 pub spend_limit_micros: Option<i64>,
443 pub state: LimitState,
444 /// What to tell people when work is stopped or close to it.
445 pub message: Option<String>,
Two limits, real invoices, trust that grows by itself, sales signals446 /// Charged this month, which the spend limit is measured against.
447 #[serde(default)]
448 pub spent_micros: i64,
449 /// True while the owners have not chosen a spend limit of their own, so
450 /// the automatic one applies: $200, or twice last month's spend.
451 #[serde(default)]
452 pub default_spend_limit: bool,
453 /// The most the owners may set their own limit to: g1t's ceiling. To
454 /// go past it, they contact g1t.
455 #[serde(default)]
456 pub available_micros: Option<i64>,
457 /// How the ceiling grows from here, in a sentence.
458 #[serde(default)]
459 pub growth: Option<String>,
Usage limits: unpaid usage can only go so far460}
461
462/// `limit`: a workspace's limit, for its members. Returns `Outcome<Limit>`.
463#[derive(Debug, Serialize, Deserialize)]
464pub struct LimitArgs {
465 pub workspace: String,
466 pub viewer: Viewer,
467}
468
469/// `check_limit`: the same, for the services that enforce it. Returns
470/// `Outcome<Limit>`.
471#[derive(Debug, Serialize, Deserialize)]
472pub struct CheckLimitArgs {
473 pub workspace: String,
474}
475
Prices keep themselves current with what g1t pays476/// `note_pending`: usage this month that will be charged later, such as
477/// app traffic past a plan, so the workspace's limit counts it now. Each
478/// report replaces the last for that workspace, source and month. Called
479/// by the service that meters it. Returns `bool`.
480#[derive(Debug, Serialize, Deserialize)]
481#[serde(rename_all = "camelCase")]
482pub struct NotePendingArgs {
483 pub workspace: String,
484 /// `deployments`.
485 pub source: String,
486 /// What it cost g1t so far this month, before the margin.
487 pub cost_micros: i64,
488}
489
Usage limits: unpaid usage can only go so far490/// `set_spend_limit`: the owner's own monthly ceiling, under g1t's; None
491/// removes it. Owners only. Returns `Outcome<Limit>`.
492#[derive(Debug, Serialize, Deserialize)]
493#[serde(rename_all = "camelCase")]
494pub struct SetSpendLimitArgs {
495 pub actor: User,
496 pub workspace: String,
Two limits, real invoices, trust that grows by itself, sales signals497 /// A monthly limit, at most what is available; None goes back to the
498 /// default.
Usage limits: unpaid usage can only go so far499 pub spend_limit_micros: Option<i64>,
Two limits, real invoices, trust that grows by itself, sales signals500 /// Use everything available, with no limit of their own.
501 #[serde(default)]
502 pub use_full_limit: bool,
Usage limits: unpaid usage can only go so far503}
504
Prices keep themselves current with what g1t pays505/// One metered unit: what it costs g1t, and what it is sold at. The price
506/// is always `cost × (100 + markup) / 100`, so it follows the cost.
507#[derive(Clone, Debug, Serialize, Deserialize)]
508#[serde(rename_all = "camelCase")]
509pub struct Price {
510 /// `sandbox_second`, `build_second`, `app_requests`, `app_cpu`, `app_month`.
511 pub meter: String,
512 pub title: String,
513 pub unit: String,
514 /// Millionths of a dollar per unit; may have a fraction.
515 pub cost_micros: f64,
516 pub markup_percent: u32,
517 pub price_micros: f64,
518 /// `list`: Cloudflare's published price. `cloudflare`: what Cloudflare
519 /// actually billed g1t, measured.
520 pub source: String,
521 /// When it was last checked against Cloudflare's bill.
522 pub checked_at: Option<String>,
523 pub updated_at: String,
524}
525
526impl Price {
527 pub fn price_for(cost_micros: f64, markup_percent: u32) -> f64 {
528 cost_micros * f64::from(100 + markup_percent) / 100.0
529 }
530}
531
532/// A cost that moved.
533#[derive(Clone, Debug, Serialize, Deserialize)]
534#[serde(rename_all = "camelCase")]
535pub struct PriceChange {
536 pub meter: String,
537 pub old_cost_micros: f64,
538 pub new_cost_micros: f64,
539 pub markup_percent: u32,
Prices are what g1t pays plus 20%, from the first second540 /// The markup before, when the change was to the markup rather than
541 /// to the cost. Absent when the markup stayed `markup_percent`.
542 #[serde(default, skip_serializing_if = "Option::is_none")]
543 pub old_markup_percent: Option<u32>,
Prices keep themselves current with what g1t pays544 pub reason: String,
545 pub created_at: String,
546}
547
548/// `prices`: every metered price and the recent changes. Public. Returns
549/// `PriceBook`.
550#[derive(Clone, Debug, Serialize, Deserialize)]
551#[serde(rename_all = "camelCase")]
552pub struct PriceBook {
553 pub prices: Vec<Price>,
554 pub changes: Vec<PriceChange>,
555 /// The margin on model usage, which is charged at what AI Gateway
556 /// priced each request at.
557 pub model_margin_percent: u32,
558}
559
Billing accounts, terms and enterprises; g1t is no longer free560/// Who pays: a billing account. Every workspace has one; by default its
561/// own. An enterprise account pays for several workspaces at once, as
562/// GitHub Enterprise does: one bill, one limit, one set of terms.
563#[derive(Clone, Debug, Serialize, Deserialize)]
564#[serde(rename_all = "camelCase")]
565pub struct BillingAccount {
566 /// `ws_<slug>` for a workspace's own account; `ent_…` for an enterprise.
567 pub id: String,
568 pub kind: AccountKind,
569 pub name: String,
570 pub terms: Terms,
571 /// The workspaces it pays for.
572 pub workspaces: Vec<String>,
Stripe webhooks, enterprise invoices, and sudo for both573 /// Where an enterprise's invoices go.
574 #[serde(default)]
575 pub billing_email: Option<String>,
576 /// An enterprise's invoices, newest first. Empty for a workspace's own.
577 #[serde(default)]
578 pub invoices: Vec<EnterpriseInvoice>,
Billing accounts, terms and enterprises; g1t is no longer free579 pub created_at: String,
580}
581
582#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
583#[serde(rename_all = "snake_case")]
584pub enum AccountKind {
585 Workspace,
586 Enterprise,
587}
588
589/// How an account is charged. Standard unless g1t set otherwise in sudo.
590#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
591#[serde(rename_all = "camelCase")]
592pub struct Terms {
593 pub kind: TermsKind,
594 /// Off every usage charge, in percent. Custom terms only.
595 #[serde(default)]
596 pub discount_percent: u32,
597 /// A ceiling on unpaid usage that replaces the one trust would give.
598 #[serde(default)]
599 pub ceiling_micros: Option<i64>,
600 /// Why, for whoever looks next.
601 #[serde(default)]
602 pub note: String,
603 /// When the terms end and the account goes back to standard.
604 #[serde(default)]
605 pub until: Option<String>,
606 #[serde(default)]
607 pub set_by: Option<String>,
608 #[serde(default)]
609 pub set_at: Option<String>,
610}
611
612impl Terms {
613 pub fn standard() -> Self {
614 Terms {
615 kind: TermsKind::Standard,
616 discount_percent: 0,
617 ceiling_micros: None,
618 note: String::new(),
619 until: None,
620 set_by: None,
621 set_at: None,
622 }
623 }
624
625 /// What a charge becomes under these terms.
626 pub fn apply(&self, charge_micros: i64) -> i64 {
627 match self.kind {
628 TermsKind::Comped => 0,
629 TermsKind::Custom => charge_micros * i64::from(100 - self.discount_percent.min(100)) / 100,
630 TermsKind::Standard => charge_micros,
631 }
632 }
633}
634
635#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
636#[serde(rename_all = "snake_case")]
637pub enum TermsKind {
638 /// Prices as published, limits by trust.
639 Standard,
640 /// Nothing charged; usage still recorded with its cost. Paid features
641 /// are on without a plan. For g1t's own workspaces, partners, and the
642 /// like.
643 Comped,
644 /// A discount, a ceiling, or both.
645 Custom,
646}
647
Stripe webhooks, enterprise invoices, and sudo for both648/// `stripe_webhook`: an event from Stripe, as the API received it: the raw
649/// body and its `Stripe-Signature` header. Billing checks the signature
650/// against the secret of the endpoint it registered, and handles each
651/// event once. Returns `Outcome<bool>`: false for one already handled.
652#[derive(Debug, Serialize, Deserialize)]
653pub struct StripeWebhookArgs {
654 pub payload: String,
655 pub signature: String,
656}
657
658/// `admin_stripe`: where billing stands with Stripe. Staff only. Returns
659/// `StripeStatus`. With `setup: true`, registers (or replaces) the webhook
660/// endpoint for the current mode first.
661#[derive(Debug, Default, Serialize, Deserialize)]
662pub struct AdminStripeArgs {
663 #[serde(default)]
664 pub setup: bool,
665 #[serde(default)]
666 pub by: Option<String>,
667}
668
669#[derive(Clone, Debug, Serialize, Deserialize)]
670#[serde(rename_all = "camelCase")]
671pub struct StripeStatus {
672 /// `test` or `live`, from the key; `off` without one.
673 pub mode: String,
674 pub webhook: Option<StripeWebhook>,
675 /// The latest events handled, newest first.
676 pub recent_events: Vec<StripeEventSummary>,
677 /// What went wrong setting up, if it did.
678 pub error: Option<String>,
679}
680
681#[derive(Clone, Debug, Serialize, Deserialize)]
682#[serde(rename_all = "camelCase")]
683pub struct StripeWebhook {
684 pub url: String,
685 pub endpoint_id: String,
686 pub events: Vec<String>,
687 pub created_by: String,
688 pub created_at: String,
689}
690
691#[derive(Clone, Debug, Serialize, Deserialize)]
692#[serde(rename_all = "camelCase")]
693pub struct StripeEventSummary {
694 pub id: String,
695 pub kind: String,
696 pub outcome: String,
697 pub received_at: String,
698}
699
700/// `admin_enterprise_billing`: where an enterprise's invoices go. Creates
701/// or updates its Stripe customer. Returns `Outcome<BillingAccount>`.
702#[derive(Debug, Serialize, Deserialize)]
703pub struct AdminEnterpriseBillingArgs {
704 pub id: String,
705 pub email: String,
706 pub by: String,
707}
708
709/// `admin_invoice_enterprise`: sends an enterprise its invoice now, for
710/// what its workspaces owe, rather than waiting for the month to close.
711/// Returns `Outcome<EnterpriseInvoice>`.
712#[derive(Debug, Serialize, Deserialize)]
713pub struct AdminInvoiceEnterpriseArgs {
714 pub id: String,
715 pub by: String,
716}
717
718/// An enterprise's invoice: one line per workspace, paid on Stripe.
719#[derive(Clone, Debug, Serialize, Deserialize)]
720#[serde(rename_all = "camelCase")]
721pub struct EnterpriseInvoice {
722 pub invoice_id: String,
723 /// Stripe's page for it, where it is paid.
724 pub hosted_url: Option<String>,
725 pub amount_micros: i64,
726 /// `open`, `paid`, `overdue` or `void`.
727 pub status: String,
728 pub period: String,
729 pub lines: Vec<InvoiceLine>,
730 pub created_at: String,
731}
732
733#[derive(Clone, Debug, Serialize, Deserialize)]
734#[serde(rename_all = "camelCase")]
735pub struct InvoiceLine {
736 pub workspace: String,
737 pub amount_micros: i64,
738}
739
Two limits, real invoices, trust that grows by itself, sales signals740/// A workspace's invoice from g1t: one per month, and one each time it is
741/// charged near its limit. Itemised, charged to the card on file, and kept
742/// in Stripe's billing page with its PDF.
743#[derive(Clone, Debug, Serialize, Deserialize)]
744#[serde(rename_all = "camelCase")]
745pub struct WorkspaceInvoice {
746 pub invoice_id: String,
747 pub workspace: String,
748 /// `month` (2026-10) or `threshold`.
749 pub reason: String,
750 pub period: String,
751 pub amount_micros: i64,
752 /// `paid`, `open`, `failed` or `void`.
753 pub status: String,
754 pub hosted_url: Option<String>,
755 pub pdf_url: Option<String>,
756 pub lines: Vec<InvoiceItem>,
757 pub created_at: String,
758}
759
760#[derive(Clone, Debug, Serialize, Deserialize)]
761#[serde(rename_all = "camelCase")]
762pub struct InvoiceItem {
763 pub description: String,
764 pub amount_micros: i64,
765}
766
767/// `invoices`: a workspace's invoices from g1t, newest first. Members
768/// only. Returns `Outcome<Vec<WorkspaceInvoice>>`.
769#[derive(Debug, Serialize, Deserialize)]
770pub struct InvoicesArgs {
771 pub workspace: String,
772 pub viewer: Viewer,
773}
774
775/// `admin_workspace_invoices`: the same, for staff. Returns
776/// `Vec<WorkspaceInvoice>`.
777#[derive(Debug, Serialize, Deserialize)]
778pub struct AdminWorkspaceInvoicesArgs {
779 pub workspace: String,
780}
781
The statement is a month at a time, a line per kind of charge782/// `statement`: a month of a workspace's ledger, grouped by day (or by
783/// project) with a line per kind of charge. Members only. Returns
784/// `Outcome<Statement>`.
785#[derive(Debug, Serialize, Deserialize)]
786pub struct StatementArgs {
787 pub workspace: String,
788 pub viewer: Viewer,
789 /// YYYY-MM; this month when absent.
790 #[serde(default)]
791 pub month: Option<String>,
792 /// `day` (the default) or `project`.
793 #[serde(default)]
794 pub group: Option<String>,
795}
796
797#[derive(Clone, Debug, Serialize, Deserialize)]
798#[serde(rename_all = "camelCase")]
799pub struct Statement {
800 pub month: String,
801 /// Months with any entries, newest first.
802 pub months: Vec<String>,
803 pub groups: Vec<StatementGroup>,
804 pub totals: StatementTotals,
805}
806
807#[derive(Clone, Debug, Serialize, Deserialize)]
808#[serde(rename_all = "camelCase")]
809pub struct StatementGroup {
810 /// The day (YYYY-MM-DD) or the project (`owner/name`, or empty).
811 pub key: String,
812 pub label: String,
813 pub lines: Vec<StatementLine>,
814 /// What the group's charges come to.
815 pub charged_micros: i64,
816}
817
818#[derive(Clone, Debug, Serialize, Deserialize)]
819#[serde(rename_all = "camelCase")]
820pub struct StatementLine {
821 /// Agent runs, Sandbox time, Deployments, Payments, Credits from g1t,
Prices are what g1t pays plus 20%, from the first second822 /// Refunds, and, for older entries, Runs on your own model provider.
The statement is a month at a time, a line per kind of charge823 pub kind: String,
824 pub count: u32,
825 /// Charges positive; money in (payments, credits) negative.
826 pub charged_micros: i64,
827 pub cost_micros: i64,
828}
829
830#[derive(Clone, Debug, Serialize, Deserialize)]
831#[serde(rename_all = "camelCase")]
832pub struct StatementTotals {
833 pub charged_micros: i64,
834 pub paid_micros: i64,
835 pub cost_micros: i64,
836 pub entries: u32,
837}
838
839/// `statement_entries`: one statement line's entries, newest first, 50 at
840/// a time (`before` = the last id seen). Returns `Outcome<Vec<LedgerEntry>>`.
841#[derive(Debug, Serialize, Deserialize)]
842pub struct StatementEntriesArgs {
843 pub workspace: String,
844 pub viewer: Viewer,
845 pub month: String,
846 pub kind: String,
847 #[serde(default)]
848 pub day: Option<String>,
849 #[serde(default)]
850 pub project: Option<String>,
851 #[serde(default)]
852 pub before: Option<String>,
853}
854
Two limits, real invoices, trust that grows by itself, sales signals855// --- Sales (sudo.g1t.sh) ------------------------------------------------------
856//
857// What staff need to know to reach out: who is growing, who is close to
858// their limit, who was declined, who has become a steady customer. And what
859// was done about it: a stage, an owner on g1t's side, a next step, notes.
860
861/// Why a workspace is worth a look.
862#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
863#[serde(rename_all = "snake_case")]
864pub enum SignalKind {
865 /// At its limit, or its own spend limit: work is stopped.
866 AtLimit,
867 /// Past 80% of what is available to it: about to need more.
868 NearCeiling,
869 /// Its card was declined or a payment disputed.
870 Declined,
871 /// This month is well ahead of last month.
872 Growing,
873 /// Became Established: the ceiling now follows its spend.
874 Established,
875 /// Paid g1t for the first time.
876 FirstPayment,
877 /// Spending enough that custom terms or an enterprise may suit it.
878 HighSpend,
879}
880
881#[derive(Clone, Debug, Serialize, Deserialize)]
882#[serde(rename_all = "camelCase")]
883pub struct Signal {
884 pub workspace: String,
885 pub kind: SignalKind,
886 /// One sentence, with the figures.
887 pub detail: String,
888 /// The figure that matters, such as this month's spend.
889 pub value_micros: i64,
890 /// Its sales stage, if staff gave it one.
891 pub stage: Option<String>,
892 pub owner: Option<String>,
Billing lists every invoice and every staff change; signals carry follow-ups893 #[serde(default)]
894 pub next_step: Option<String>,
895 /// When the next step is due, `YYYY-MM-DD`.
896 #[serde(default)]
897 pub next_at: Option<String>,
898}
899
900/// `admin_invoices`: every invoice g1t has sent, workspaces' and
901/// enterprises', newest first. Returns `Vec<InvoiceSummary>`.
902#[derive(Debug, Default, Serialize, Deserialize)]
903pub struct AdminInvoicesArgs {
904 /// `paid`, `open`, `failed`, `overdue` or `void`.
905 #[serde(default)]
906 pub status: Option<String>,
907 /// YYYY-MM, by when it was sent.
908 #[serde(default)]
909 pub month: Option<String>,
910}
911
912#[derive(Clone, Debug, Serialize, Deserialize)]
913#[serde(rename_all = "camelCase")]
914pub struct InvoiceSummary {
915 pub invoice_id: String,
916 /// `workspace` or `enterprise`.
917 pub kind: String,
918 /// The workspace's slug, or the enterprise's account id.
919 pub account: String,
920 /// What to call it: the workspace, or the enterprise's name.
921 pub name: String,
922 pub reason: String,
923 pub period: String,
924 pub amount_micros: i64,
925 pub status: String,
926 pub hosted_url: Option<String>,
927 pub created_at: String,
928 pub paid_at: Option<String>,
929}
930
931/// `admin_audit`: every change made in sudo, and by Stripe, newest first.
932/// Returns `Vec<AdminAction>`.
933#[derive(Debug, Default, Serialize, Deserialize)]
934pub struct AdminAuditArgs {
935 #[serde(default)]
936 pub by: Option<String>,
937 #[serde(default)]
938 pub action: Option<String>,
939 /// Only those before this time, for paging.
940 #[serde(default)]
941 pub before: Option<String>,
Two limits, real invoices, trust that grows by itself, sales signals942}
943
944/// `admin_signals`: every workspace worth reaching out to, most urgent
945/// first. Returns `Vec<Signal>`.
946#[derive(Debug, Default, Serialize, Deserialize)]
947pub struct AdminSignalsArgs {}
948
949/// What staff are doing about a workspace.
950#[derive(Clone, Debug, Serialize, Deserialize)]
951#[serde(rename_all = "camelCase")]
952pub struct SalesRecord {
953 pub workspace: String,
954 /// `none`, `lead`, `contacted`, `negotiating`, `won`, `lost` or `churn_risk`.
955 pub stage: String,
956 /// The staff member looking after it.
957 pub owner: Option<String>,
958 pub next_step: Option<String>,
959 /// RFC 3339 date.
960 pub next_at: Option<String>,
961 pub notes: Vec<SalesNote>,
962 pub updated_at: Option<String>,
963}
964
965#[derive(Clone, Debug, Serialize, Deserialize)]
966#[serde(rename_all = "camelCase")]
967pub struct SalesNote {
968 pub id: String,
969 pub text: String,
970 pub by: String,
971 pub created_at: String,
972}
973
974/// `admin_sales`: a workspace's sales record. Returns `SalesRecord`.
975#[derive(Debug, Serialize, Deserialize)]
976pub struct AdminSalesArgs {
977 pub workspace: String,
978}
979
980/// `admin_set_sales`: its stage, owner and next step. Returns `Outcome<SalesRecord>`.
981#[derive(Debug, Serialize, Deserialize)]
982pub struct AdminSetSalesArgs {
983 pub workspace: String,
984 pub stage: String,
985 #[serde(default)]
986 pub owner: Option<String>,
987 #[serde(default)]
988 pub next_step: Option<String>,
989 #[serde(default)]
990 pub next_at: Option<String>,
991 pub by: String,
992}
993
994/// `admin_add_note`. Returns `Outcome<SalesRecord>`.
995#[derive(Debug, Serialize, Deserialize)]
996pub struct AdminAddNoteArgs {
997 pub workspace: String,
998 pub text: String,
999 pub by: String,
1000}
1001
1002/// `admin_overview`: the business at a glance. Returns `Overview`.
1003#[derive(Debug, Default, Serialize, Deserialize)]
1004pub struct AdminOverviewArgs {}
1005
1006#[derive(Clone, Debug, Serialize, Deserialize)]
1007#[serde(rename_all = "camelCase")]
1008pub struct Overview {
1009 /// YYYY-MM.
1010 pub month: String,
1011 /// The last six months, oldest first, all workspaces together.
1012 pub months: Vec<MonthFigures>,
1013 /// This month by kind of usage: models, sandbox, deployments, plans.
1014 pub by_kind: Vec<KindFigures>,
1015 pub paying_workspaces: u32,
1016 pub stopped: u32,
1017 pub near_ceiling: u32,
1018 pub declined: u32,
1019 /// Sent and not yet paid, workspaces and enterprises.
1020 pub open_invoices_micros: i64,
1021 /// Follow-ups due today or earlier.
1022 pub follow_ups_due: u32,
1023}
1024
1025#[derive(Clone, Debug, Serialize, Deserialize)]
1026#[serde(rename_all = "camelCase")]
1027pub struct KindFigures {
1028 pub kind: String,
1029 pub charged_micros: i64,
1030 pub cost_micros: i64,
1031}
1032
Billing accounts, terms and enterprises; g1t is no longer free1033// --- Staff (sudo.g1t.sh) ------------------------------------------------------
1034//
1035// Called only by the sudo app, which only g1t staff can reach (behind
1036// Cloudflare Access). Each change names who made it, and is kept in the
1037// audit log.
1038
1039/// `admin_accounts`: every billing account, with where each stands this
1040/// month. Returns `Vec<AccountSummary>`.
1041#[derive(Debug, Default, Serialize, Deserialize)]
1042pub struct AdminAccountsArgs {
1043 #[serde(default)]
1044 pub query: Option<String>,
Stripe webhooks, enterprise invoices, and sudo for both1045 /// Exactly these workspaces' accounts, such as one page of sudo's
1046 /// list; every account with activity when absent.
1047 #[serde(default)]
1048 pub workspaces: Option<Vec<String>>,
Billing accounts, terms and enterprises; g1t is no longer free1049}
1050
1051#[derive(Clone, Debug, Serialize, Deserialize)]
1052#[serde(rename_all = "camelCase")]
1053pub struct AccountSummary {
1054 pub account: BillingAccount,
1055 pub limit: Limit,
1056 /// Charged this month, after terms.
1057 pub charged_micros: i64,
1058 /// What this month's usage cost g1t.
1059 pub cost_micros: i64,
1060 /// Paid, ever.
1061 pub paid_micros: i64,
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace1062 /// The same figures for each of the account's workspaces that has
1063 /// any, so staff can see what one member of an enterprise used.
1064 #[serde(default)]
1065 pub by_workspace: Vec<WorkspaceFigures>,
Two limits, real invoices, trust that grows by itself, sales signals1066 /// The last six months, oldest first, for trends.
1067 #[serde(default)]
1068 pub months: Vec<MonthFigures>,
1069}
1070
1071/// One month of an account's billing.
1072#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1073#[serde(rename_all = "camelCase")]
1074pub struct MonthFigures {
1075 /// YYYY-MM.
1076 pub month: String,
1077 pub charged_micros: i64,
1078 pub cost_micros: i64,
1079 pub paid_micros: i64,
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace1080}
1081
1082/// One workspace's share of an [`AccountSummary`].
1083#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1084#[serde(rename_all = "camelCase")]
1085pub struct WorkspaceFigures {
1086 pub workspace: String,
1087 pub charged_micros: i64,
1088 pub cost_micros: i64,
1089 pub paid_micros: i64,
Billing accounts, terms and enterprises; g1t is no longer free1090}
1091
1092/// `admin_account`: one account in full. Returns `Outcome<AccountDetail>`.
1093#[derive(Debug, Serialize, Deserialize)]
1094pub struct AdminAccountArgs {
1095 /// An account id, or a workspace slug.
1096 pub id: String,
1097}
1098
1099#[derive(Clone, Debug, Serialize, Deserialize)]
1100#[serde(rename_all = "camelCase")]
1101pub struct AccountDetail {
1102 pub summary: AccountSummary,
1103 /// Each workspace's limit, for an enterprise.
1104 pub workspaces: Vec<Limit>,
1105 pub ledger: Vec<LedgerEntry>,
1106 pub audit: Vec<AdminAction>,
1107}
1108
1109/// `admin_set_terms`. Returns `Outcome<BillingAccount>`.
1110#[derive(Debug, Serialize, Deserialize)]
1111pub struct AdminSetTermsArgs {
1112 pub id: String,
1113 pub terms: Terms,
1114 pub by: String,
1115}
1116
1117/// `admin_create_enterprise`. Returns `Outcome<BillingAccount>`.
1118#[derive(Debug, Serialize, Deserialize)]
1119pub struct AdminCreateEnterpriseArgs {
1120 pub name: String,
1121 pub workspaces: Vec<String>,
1122 pub by: String,
1123}
1124
1125/// `admin_attach`: moves a workspace onto an enterprise account, or back
1126/// onto its own with `account: None`. Returns `Outcome<BillingAccount>`.
1127#[derive(Debug, Serialize, Deserialize)]
1128pub struct AdminAttachArgs {
1129 pub workspace: String,
1130 pub account: Option<String>,
1131 pub by: String,
1132}
1133
1134/// `admin_credit`: money g1t gives a workspace, such as a refund or a
1135/// goodwill credit. Returns `Outcome<LedgerEntry>`.
1136#[derive(Debug, Serialize, Deserialize)]
1137pub struct AdminCreditArgs {
1138 pub workspace: String,
1139 pub amount_micros: i64,
1140 pub note: String,
1141 pub by: String,
1142}
1143
1144/// One change made in sudo.
1145#[derive(Clone, Debug, Serialize, Deserialize)]
1146#[serde(rename_all = "camelCase")]
1147pub struct AdminAction {
1148 pub id: String,
1149 pub account: String,
1150 pub action: String,
1151 pub detail: String,
1152 pub by: String,
1153 pub created_at: String,
1154}
1155
Paid features: a workspace turns on Deployments with a monthly plan1156/// What a feature's plan costs and includes.
1157#[derive(Clone, Debug, Serialize, Deserialize)]
1158#[serde(rename_all = "camelCase")]
1159pub struct Plan {
1160 pub feature: Feature,
1161 pub title: String,
1162 /// Charged every month while the plan is on, in cents.
1163 pub monthly_cents: u32,
1164 /// What the monthly price includes, one line each, for people to read.
1165 pub includes: Vec<String>,
1166 /// How usage past the allowance is charged, for people to read.
1167 pub overage: String,
1168}
1169
1170#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1171#[serde(rename_all = "snake_case")]
1172pub enum SubscriptionStatus {
1173 /// Paid up; the feature works.
1174 Active,
1175 /// Paid up to the end of the period, and ends then.
1176 Canceling,
1177 /// The last payment failed; the feature is off until it is paid.
1178 PastDue,
1179 /// Ended.
1180 Canceled,
1181}
1182
1183impl SubscriptionStatus {
1184 /// Whether the feature works in this state.
1185 pub fn on(self) -> bool {
1186 matches!(self, SubscriptionStatus::Active | SubscriptionStatus::Canceling)
1187 }
1188}
1189
1190/// A workspace's plan for one feature.
1191#[derive(Clone, Debug, Serialize, Deserialize)]
1192#[serde(rename_all = "camelCase")]
1193pub struct Subscription {
1194 pub feature: Feature,
1195 pub status: SubscriptionStatus,
1196 /// RFC 3339: when the period paid for ends, and the plan renews or
1197 /// ends.
1198 pub period_end: Option<String>,
1199 /// Username of whoever turned it on.
1200 pub started_by: String,
1201 /// RFC 3339.
1202 pub started_at: String,
1203}
1204
1205/// A feature as a workspace sees it: what it costs, and its plan if it has
1206/// one.
1207#[derive(Clone, Debug, Serialize, Deserialize)]
1208#[serde(rename_all = "camelCase")]
1209pub struct FeatureState {
1210 pub plan: Plan,
1211 pub subscription: Option<Subscription>,
1212 /// Whether the feature works for the workspace now.
1213 pub on: bool,
1214}
1215
1216/// `features`: every paid feature and the workspace's plan for each.
1217/// Members only. Returns `Outcome<Vec<FeatureState>>`.
1218#[derive(Debug, Serialize, Deserialize)]
1219pub struct FeaturesArgs {
1220 pub workspace: String,
1221 pub viewer: Viewer,
1222}
1223
1224/// `subscribe`: starts the card page for a feature's monthly plan. Owners
1225/// only. Returns `Outcome<Checkout>`; the page's id comes back to
1226/// `return_url` as `session`, for `confirm_subscription`.
1227#[derive(Debug, Serialize, Deserialize)]
1228#[serde(rename_all = "camelCase")]
1229pub struct SubscribeArgs {
1230 pub actor: User,
1231 pub workspace: String,
1232 pub feature: Feature,
1233 pub return_url: String,
1234}
1235
1236/// `confirm_subscription`: turns the feature on once the processor says
1237/// the plan was paid for. Safe to call any number of times. Returns
1238/// `Outcome<FeatureState>`.
1239#[derive(Debug, Serialize, Deserialize)]
1240pub struct ConfirmSubscriptionArgs {
1241 pub workspace: String,
1242 pub viewer: Viewer,
1243 pub session: String,
1244}
1245
1246/// `cancel_subscription` (`resume` false) ends a plan at the end of the
1247/// period paid for; with `resume` true, takes that back. Owners only.
1248/// Returns `Outcome<FeatureState>`.
1249#[derive(Debug, Serialize, Deserialize)]
1250pub struct CancelSubscriptionArgs {
1251 pub actor: User,
1252 pub workspace: String,
1253 pub feature: Feature,
1254 #[serde(default)]
1255 pub resume: bool,
1256}
1257
1258/// `has_feature`: whether a feature works for a workspace now, asked by the
1259/// service that provides it before doing paid work. Returns
1260/// `Outcome<bool>`: a failure, with the reason to show, when it does not.
1261/// True everywhere when no card processor is configured.
1262#[derive(Debug, Serialize, Deserialize)]
1263pub struct HasFeatureArgs {
1264 pub workspace: String,
1265 pub feature: Feature,
1266}
1267
1268/// `charge_feature`: usage of a feature past its plan's allowance, charged
1269/// from the workspace's credit at cost plus the margin, whatever
1270/// `FREE_WHILE_BUILDING` says. Called by the service that provides it.
1271/// Charged once per `reference`. Returns `Outcome<bool>`: false if that
1272/// reference was charged before.
1273#[derive(Debug, Serialize, Deserialize)]
1274#[serde(rename_all = "camelCase")]
1275pub struct ChargeFeatureArgs {
1276 pub workspace: String,
1277 pub feature: Feature,
1278 /// What it cost g1t, in millionths of a dollar, before the margin.
1279 pub cost_micros: i64,
1280 pub description: String,
1281 /// `namespace/name`, when the usage was one repository's.
1282 pub repo: Option<String>,
1283 /// Unique to this charge, e.g. `deployments/acme/2026-10`.
1284 pub reference: String,
1285}
1286
Models per workspace: several providers, routed by kind of work1287#[cfg(test)]
1288mod tests {
1289 use super::*;
1290
1291 #[test]
Prices are what g1t pays plus 20%, from the first second1292 fn an_account_carries_no_run_fee() {
1293 let account = Account {
1294 workspace: "acme".into(),
1295 balance_micros: 0,
1296 status: Status { enabled: true, live: false, free: false },
1297 margin_percent: 20,
1298 card: None,
1299 };
1300 let json = serde_json::to_value(account).unwrap();
1301 let mut keys: Vec<&str> = json.as_object().unwrap().keys().map(String::as_str).collect();
1302 keys.sort_unstable();
1303 assert_eq!(keys, ["balanceMicros", "card", "marginPercent", "status", "workspace"]);
1304 }
1305
1306 #[test]
1307 fn a_price_change_says_when_the_markup_moved() {
1308 let change = PriceChange {
1309 meter: "sandbox_second".into(),
1310 old_cost_micros: 21.0,
1311 new_cost_micros: 21.0,
1312 markup_percent: 20,
1313 old_markup_percent: Some(138),
1314 reason: "Sandbox time is now charged at cost plus 20% from the first second".into(),
1315 created_at: "2026-10-05T00:00:00Z".into(),
1316 };
1317 assert_eq!(serde_json::to_value(&change).unwrap()["oldMarkupPercent"], 138);
1318 let cost_only = PriceChange { old_markup_percent: None, ..change };
1319 assert!(serde_json::to_value(&cost_only).unwrap().get("oldMarkupPercent").is_none());
1320 }
1321
1322 #[test]
Paid features: a workspace turns on Deployments with a monthly plan1323 fn features_are_named_as_the_site_sends_them() {
1324 assert_eq!(
1325 serde_json::to_value(Feature::Deployments).unwrap(),
1326 serde_json::json!("deployments")
1327 );
1328 assert_eq!(Feature::parse("deployments"), Some(Feature::Deployments));
1329 assert!(SubscriptionStatus::Canceling.on());
1330 assert!(!SubscriptionStatus::PastDue.on());
1331 }
1332
1333 #[test]
Models per workspace: several providers, routed by kind of work1334 fn who_pays_is_read_as_the_runner_sends_it() {
1335 let run: StartRunArgs = serde_json::from_value(serde_json::json!({
1336 "workspace": "acme",
1337 "repo": { "namespace": "acme", "name": "web" },
1338 "number": 7,
1339 "task": "implement",
1340 "model": "Claude Sonnet 5.5",
1341 "billedTo": "workspace",
1342 }))
1343 .unwrap();
1344 assert_eq!(run.billed_to, "workspace");
1345 }
1346}