g1t/crates/contracts/src/billing.rs

200 lines6,533 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}
157
158
159/// `usage`: what a workspace's agents cost over a period, broken down.
160/// Members only. Returns `Outcome<Usage>`.
161#[derive(Debug, Serialize, Deserialize)]
162pub struct UsageArgs {
163 pub workspace: String,
164 pub viewer: Viewer,
165 /// RFC 3339: the start of the period. The period runs to now.
166 pub since: String,
167}
168
169/// One slice of usage: what it was for, what it cost, how many runs.
170#[derive(Clone, Debug, Serialize, Deserialize)]
171#[serde(rename_all = "camelCase")]
172pub struct UsageSlice {
173 pub key: String,
174 pub micros: i64,
175 pub runs: u32,
176}
177
178/// What a workspace's agents cost over a period.
179#[derive(Clone, Debug, Serialize, Deserialize)]
180#[serde(rename_all = "camelCase")]
181pub struct Usage {
182 pub since: String,
183 /// Charged, including g1t's margin.
184 pub spent_micros: i64,
185 /// What the model provider charged, before the margin.
186 pub cost_micros: i64,
187 pub runs: u32,
188 /// Spend per day (`YYYY-MM-DD`) and task, as `day/task` keys.
189 pub by_day: Vec<UsageSlice>,
190 /// Per task: implement, review, revise, update, plan.
191 pub by_task: Vec<UsageSlice>,
192 /// Per repository, `namespace/name`.
193 pub by_repo: Vec<UsageSlice>,
194 /// The pull requests that cost most, as `namespace/name#number`.
195 pub by_pull: Vec<UsageSlice>,
196 /// Per model, by its public name.
197 pub by_model: Vec<UsageSlice>,
198 /// Credit bought in the period.
199 pub added_micros: i64,
200}