g1t/crates/contracts/src/billing.rs

156 lines5,105 bytesCodeBlame
1//! 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,
30}
31
32/// A workspace's standing.
33#[derive(Clone, Debug, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct Account {
36 pub workspace: String,
37 /// Credit left, in millionths of a dollar. Can dip below zero by the
38 /// cost of the runs that were under way when it ran out.
39 pub balance_micros: i64,
40 pub status: Status,
41 /// What is added to a run's cost, in percent.
42 pub margin_percent: u32,
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "snake_case")]
47pub enum EntryKind {
48 /// Credit bought with a card.
49 TopUp,
50 /// An agent's run.
51 Usage,
52}
53
54/// One line of a workspace's statement.
55#[derive(Clone, Debug, Serialize, Deserialize)]
56#[serde(rename_all = "camelCase")]
57pub struct LedgerEntry {
58 pub id: String,
59 pub kind: EntryKind,
60 /// Positive for credit added, negative for usage.
61 pub amount_micros: i64,
62 pub description: String,
63 /// For usage: the repository and pull request the agent worked on.
64 pub repo: Option<String>,
65 pub number: Option<u32>,
66 /// For usage: `implement`, `review` or `update`.
67 pub task: Option<String>,
68 /// For usage: the model, by its public name.
69 pub model: Option<String>,
70 /// For a top-up: the username of whoever paid.
71 pub created_by: Option<String>,
72 /// RFC 3339.
73 pub created_at: String,
74}
75
76/// `account` (`Outcome<Account>`) and `ledger` (`Outcome<Vec<LedgerEntry>>`,
77/// newest first). Members of the workspace only.
78#[derive(Debug, Serialize, Deserialize)]
79pub struct AccountArgs {
80 pub workspace: String,
81 pub viewer: Viewer,
82}
83
84/// `checkout`: starts a card payment for credit. Owners of the workspace
85/// only. Returns `Outcome<Checkout>`.
86#[derive(Debug, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub struct CheckoutArgs {
89 pub actor: User,
90 pub workspace: String,
91 /// How much credit to buy, in cents.
92 pub amount_cents: u32,
93 /// Where the payment page sends the person afterwards. The payment's
94 /// id is appended as `session`.
95 pub return_url: String,
96}
97
98#[derive(Debug, Serialize, Deserialize)]
99pub struct Checkout {
100 /// The payment page to send the person to.
101 pub url: String,
102}
103
104/// `confirm`: credits a payment once the provider says it was made. Safe
105/// to call any number of times. Returns `Outcome<Account>`.
106#[derive(Debug, Serialize, Deserialize)]
107pub struct ConfirmArgs {
108 pub workspace: String,
109 pub viewer: Viewer,
110 /// The payment's id, as returned to `return_url`.
111 pub session: String,
112}
113
114/// `can_start`: whether a workspace may start an agent now, asked before
115/// anything is opened for it. Returns `Outcome<bool>`: a failure, with the
116/// reason to show, when it has no credit.
117#[derive(Debug, Serialize, Deserialize)]
118pub struct CanStartArgs {
119 pub workspace: String,
120}
121
122/// `start_run`: asks whether a workspace may start an agent, and opens the
123/// run it will be charged for. Called by the runner service. Returns
124/// `Outcome<Option<RunTicket>>`: no ticket when billing is off, a failure
125/// when the workspace has no credit.
126#[derive(Debug, Serialize, Deserialize)]
127pub struct StartRunArgs {
128 pub workspace: String,
129 pub repo: RepoPath,
130 pub number: u32,
131 /// `implement`, `review` or `update`.
132 pub task: String,
133 /// The model, by its public name.
134 pub model: String,
135}
136
137#[derive(Clone, Debug, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct RunTicket {
140 pub run_id: String,
141 /// Lets the sandbox, and nothing else, report what this run cost.
142 pub token: String,
143}
144
145/// `finish_run`: what a run cost, as its sandbox reports it. Charged once.
146/// Returns `Outcome<bool>`.
147#[derive(Debug, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct FinishRunArgs {
150 pub run_id: String,
151 pub token: String,
152 /// What the model provider charged, in US dollars.
153 pub cost_usd: f64,
154 #[serde(default)]
155 pub turns: u32,
156}