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.
| Deploy scripts live in the repository | 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 | ||
| 12 | use serde::{Deserialize, Serialize}; | |
| 13 | ||
| 14 | use crate::repos::RepoPath; | |
| 15 | use crate::{User, Viewer}; | |
| 16 | ||
| 17 | /// Millionths of a US dollar in one dollar. | |
| 18 | pub 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)] | |
| 23 | pub 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 | /// 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, | |
| 35 | } | |
| 36 | ||
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 37 | /// `trial`: a workspace's trial credit, so people can try g1t (its agents on |
| 38 | /// g1t's hosted models among it) without a key or a card of their own. Each | |
| 39 | /// new workspace gets one grant of usage credit (`TRIAL_WORKSPACE_MICROS`), | |
| 40 | /// made when it first uses something, out of a pool for everyone that | |
| 41 | /// resets each calendar month (`TRIAL_MONTHLY_POOL_MICROS`). When this | |
| 42 | /// month's pool is given out, new grants wait for the next month. Returns | |
| 43 | /// `Trial`. | |
| Deploy scripts live in the repository | 44 | #[derive(Debug, Serialize, Deserialize)] |
| 45 | #[serde(rename_all = "camelCase")] | |
| 46 | pub struct TrialArgs { | |
| 47 | pub workspace: String, | |
| 48 | /// Workspaces open to hosted models anyway, whose use is not counted | |
| 49 | /// against the pool. | |
| 50 | #[serde(default)] | |
| 51 | pub exempt: Vec<String>, | |
| 52 | } | |
| 53 | ||
| 54 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 55 | #[serde(rename_all = "camelCase")] | |
| 56 | pub struct Trial { | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 57 | /// Whether its agents may use g1t's hosted models on the trial now: it |
| 58 | /// has credit left, or this month's pool can still grant it some. | |
| Deploy scripts live in the repository | 59 | pub open: bool, |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 60 | /// What the trial has paid for so far, in millionths of a dollar. |
| Deploy scripts live in the repository | 61 | pub used_micros: i64, |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 62 | /// Its grant, or what it would be granted. |
| Deploy scripts live in the repository | 63 | pub limit_micros: i64, |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 64 | /// No longer used: the trial does not end on a date. Kept for older |
| 65 | /// readers; always null. | |
| Deploy scripts live in the repository | 66 | pub ends_at: Option<String>, |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 67 | /// Why it is closed: `off` (no trials), `used` (this workspace's grant |
| 68 | /// is spent) or `pool` (this month's grants are all given out; see | |
| 69 | /// `waits_until`). `ended` is no longer sent. | |
| Deploy scripts live in the repository | 70 | pub reason: Option<String>, |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 71 | /// Whether the workspace has its grant already. |
| 72 | #[serde(default)] | |
| 73 | pub granted: bool, | |
| 74 | /// RFC 3339: when a workspace waiting for a grant can get one, the | |
| 75 | /// first of next month. Only with reason `pool`. | |
| 76 | #[serde(default)] | |
| 77 | pub waits_until: Option<String>, | |
| Deploy scripts live in the repository | 78 | } |
| 79 | ||
| 80 | /// A workspace's standing. | |
| 81 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 82 | #[serde(rename_all = "camelCase")] | |
| 83 | pub struct Account { | |
| 84 | pub workspace: String, | |
| 85 | /// Credit left, in millionths of a dollar. Can dip below zero by the | |
| 86 | /// cost of the runs that were under way when it ran out. | |
| 87 | pub balance_micros: i64, | |
| 88 | pub status: Status, | |
| 89 | /// What is added to a run's cost, in percent. | |
| 90 | pub margin_percent: u32, | |
| Billing on Stripe's pages, month-end charges, warnings; sudo by workspace | 91 | /// The card g1t charges as the workspace nears its limit and when a |
| 92 | /// month closes, if one is on file. | |
| 93 | #[serde(default)] | |
| 94 | pub card: Option<Card>, | |
| Deploy scripts live in the repository | 95 | } |
| 96 | ||
| Billing on Stripe's pages, month-end charges, warnings; sudo by workspace | 97 | /// A saved card, as far as it is safe to show. |
| 98 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 99 | #[serde(rename_all = "camelCase")] | |
| 100 | pub struct Card { | |
| 101 | /// `visa`, `mastercard`, ... | |
| 102 | pub brand: String, | |
| 103 | pub last4: String, | |
| 104 | pub exp_month: u32, | |
| 105 | pub exp_year: u32, | |
| 106 | } | |
| 107 | ||
| 108 | /// `billing_portal`: Stripe's hosted billing page for the workspace, where | |
| 109 | /// an owner adds or replaces the card, sees invoices and receipts, and sets | |
| 110 | /// the billing email and address. g1t never handles card numbers. Owners | |
| 111 | /// only. Returns `Outcome<Checkout>` (its `url`); Stripe sends them back | |
| 112 | /// to `return_url`. | |
| 113 | #[derive(Debug, Serialize, Deserialize)] | |
| 114 | pub struct BillingPortalArgs { | |
| 115 | pub actor: User, | |
| 116 | pub workspace: String, | |
| 117 | pub return_url: String, | |
| 118 | } | |
| 119 | ||
| 120 | /// `admin_billing_link`: for staff to send a customer: their Stripe billing | |
| 121 | /// page. Returns `Outcome<BillingLink>`. | |
| 122 | #[derive(Debug, Serialize, Deserialize)] | |
| 123 | pub struct AdminBillingLinkArgs { | |
| 124 | pub workspace: String, | |
| 125 | pub by: String, | |
| 126 | } | |
| 127 | ||
| 128 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 129 | #[serde(rename_all = "camelCase")] | |
| 130 | pub struct BillingLink { | |
| 131 | /// A one-time session on Stripe's billing page, signed in already. | |
| 132 | pub portal_url: String, | |
| 133 | /// The billing page's sign-in page, which does not expire: the | |
| 134 | /// customer signs in with the email Stripe has for them. | |
| 135 | pub login_url: Option<String>, | |
| 136 | pub customer_email: Option<String>, | |
| 137 | pub expires_note: String, | |
| 138 | } | |
| 139 | ||
| Deploy scripts live in the repository | 140 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] |
| 141 | #[serde(rename_all = "snake_case")] | |
| 142 | pub enum EntryKind { | |
| 143 | /// Credit bought with a card. | |
| 144 | TopUp, | |
| 145 | /// An agent's run, or a paid feature's usage past its allowance. | |
| 146 | Usage, | |
| 147 | } | |
| 148 | ||
| 149 | /// One line of a workspace's statement. | |
| 150 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 151 | #[serde(rename_all = "camelCase")] | |
| 152 | pub struct LedgerEntry { | |
| 153 | pub id: String, | |
| 154 | pub kind: EntryKind, | |
| 155 | /// Positive for credit added, negative for usage. | |
| 156 | pub amount_micros: i64, | |
| 157 | pub description: String, | |
| 158 | /// For usage: the repository and pull request the agent worked on. | |
| 159 | pub repo: Option<String>, | |
| 160 | pub number: Option<u32>, | |
| 161 | /// For usage: `implement`, `review` or `update`. | |
| 162 | pub task: Option<String>, | |
| 163 | /// For usage: the model, by its public name. | |
| 164 | pub model: Option<String>, | |
| 165 | /// For usage: `g1t` when g1t paid the model provider, `workspace` when | |
| Prices are what g1t pays plus 20%, from the first second | 166 | /// the workspace's own account did. Runs on the workspace's own |
| 167 | /// provider pay only their sandbox time now, so only older entries | |
| 168 | /// are `workspace`. | |
| Deploy scripts live in the repository | 169 | #[serde(default = "g1t")] |
| 170 | pub billed_to: String, | |
| 171 | /// For a top-up: the username of whoever paid. | |
| 172 | pub created_by: Option<String>, | |
| 173 | /// RFC 3339. | |
| 174 | pub created_at: String, | |
| Billing on Stripe's pages, month-end charges, warnings; sudo by workspace | 175 | /// The workspace the line belongs to, which tells an enterprise's |
| 176 | /// lines apart. | |
| 177 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 178 | pub workspace: Option<String>, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 179 | /// For usage: what the g1t plan's monthly included usage paid of it. |
| 180 | /// The entry's `amount_micros` is what is left to pay. | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 181 | #[serde(default)] |
| 182 | pub credit_micros: i64, | |
| 183 | /// For usage: what the workspace's trial credit paid of it. | |
| 184 | #[serde(default)] | |
| 185 | pub trial_micros: i64, | |
| 186 | /// For usage: what g1t's open-source pool paid of it. | |
| 187 | #[serde(default)] | |
| 188 | pub oss_micros: i64, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 189 | /// For usage: what g1t covered itself, such as the part of a free |
| 190 | /// workspace's last trial run that went past its trial credit. | |
| 191 | #[serde(default)] | |
| 192 | pub given_micros: i64, | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 193 | /// For usage: what the account's discount took off its price. The |
| 194 | /// price is `-amount_micros` plus this and what paid for it. | |
| 195 | #[serde(default)] | |
| 196 | pub discount_micros: i64, | |
| 197 | /// For a credit from g1t, and for what of one expired or was revoked: | |
| 198 | /// its kind. | |
| 199 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 200 | pub credit_kind: Option<CreditKind>, | |
| Deploy scripts live in the repository | 201 | } |
| 202 | ||
| 203 | fn g1t() -> String { | |
| 204 | "g1t".to_owned() | |
| 205 | } | |
| 206 | ||
| 207 | /// `account` (`Outcome<Account>`) and `ledger` (`Outcome<Vec<LedgerEntry>>`, | |
| 208 | /// newest first). Members of the workspace only. | |
| 209 | #[derive(Debug, Serialize, Deserialize)] | |
| 210 | pub struct AccountArgs { | |
| 211 | pub workspace: String, | |
| 212 | pub viewer: Viewer, | |
| 213 | } | |
| 214 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 215 | /// `checkout`: prepays usage: money paid in advance, drawn down by usage |
| 216 | /// after the plan's included usage, which raises what can be used before | |
| 217 | /// work stops by the same amount at once. $25 at the least. By card, with | |
| 218 | /// 3-D Secure; from $1,000 also by bank transfer. Owners of the workspace | |
| Deploy scripts live in the repository | 219 | /// only. Returns `Outcome<Checkout>`. |
| 220 | #[derive(Debug, Serialize, Deserialize)] | |
| 221 | #[serde(rename_all = "camelCase")] | |
| 222 | pub struct CheckoutArgs { | |
| 223 | pub actor: User, | |
| 224 | pub workspace: String, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 225 | /// How much to prepay, in cents. |
| Deploy scripts live in the repository | 226 | pub amount_cents: u32, |
| 227 | /// Where the payment page sends the person afterwards. The payment's | |
| 228 | /// id is appended as `session`. | |
| 229 | pub return_url: String, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 230 | /// `card` (the default) or `bank_transfer` (from $1,000): Stripe gives |
| 231 | /// the account details, and the money counts once it arrives. | |
| 232 | #[serde(default)] | |
| 233 | pub method: Option<String>, | |
| Deploy scripts live in the repository | 234 | } |
| 235 | ||
| 236 | #[derive(Debug, Serialize, Deserialize)] | |
| 237 | pub struct Checkout { | |
| 238 | /// The payment page to send the person to. | |
| 239 | pub url: String, | |
| 240 | } | |
| 241 | ||
| 242 | /// `confirm`: credits a payment once the provider says it was made. Safe | |
| 243 | /// to call any number of times. Returns `Outcome<Account>`. | |
| 244 | #[derive(Debug, Serialize, Deserialize)] | |
| 245 | pub struct ConfirmArgs { | |
| 246 | pub workspace: String, | |
| 247 | pub viewer: Viewer, | |
| 248 | /// The payment's id, as returned to `return_url`. | |
| 249 | pub session: String, | |
| 250 | } | |
| 251 | ||
| 252 | /// `can_start`: whether a workspace may start an agent now, asked before | |
| 253 | /// anything is opened for it. Returns `Outcome<bool>`: a failure, with the | |
| 254 | /// reason to show, when it has no credit. | |
| 255 | #[derive(Debug, Serialize, Deserialize)] | |
| 256 | pub struct CanStartArgs { | |
| 257 | pub workspace: String, | |
| 258 | } | |
| 259 | ||
| 260 | /// `start_run`: asks whether a workspace may start an agent, and opens the | |
| 261 | /// run it will be charged for. Called by the runner service. Returns | |
| 262 | /// `Outcome<Option<RunTicket>>`: no ticket when billing is off, a failure | |
| 263 | /// when the workspace has no credit. | |
| 264 | #[derive(Debug, Serialize, Deserialize)] | |
| 265 | pub struct StartRunArgs { | |
| 266 | pub workspace: String, | |
| 267 | pub repo: RepoPath, | |
| 268 | pub number: u32, | |
| 269 | /// `implement`, `review` or `update`. | |
| 270 | pub task: String, | |
| 271 | /// The model, by its public name. | |
| 272 | pub model: String, | |
| 273 | /// `workspace` when the run uses the workspace's own model provider. | |
| 274 | /// The runner, which is TypeScript, sends it as `billedTo`. | |
| 275 | #[serde(default = "g1t", alias = "billedTo")] | |
| 276 | pub billed_to: String, | |
| Merge branch 'model-routing' | 277 | /// The model session's id. Through g1t's AI Gateway, settling charges |
| 278 | /// the run what the gateway priced its requests at; on the workspace's | |
| 279 | /// own provider, it is what the proxy counts the run's tokens under, | |
| 280 | /// for the agent rate. | |
| Prices keep themselves current with what g1t pays | 281 | #[serde(default)] |
| 282 | pub session: Option<String>, | |
| Merge branch 'model-routing' | 283 | /// `small`, `large` or `frontier`: the tier g1t routed the run to. |
| 284 | /// None when the workspace's own provider names its model. | |
| Auto model routing: the cheapest tier that can do each piece of work, a retry goes up a tier, and each run records its tier | 285 | #[serde(default)] |
| 286 | pub tier: Option<String>, | |
| Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted. | 287 | /// The agent doing the work, by handle: a workspace agent's (the one |
| 288 | /// whose budget pays), or `g1t` for g1t's own work on a repository. | |
| 289 | /// Every line the run puts on the ledger carries it, so Spend's "by | |
| 290 | /// agent" reads from the ledger that is charged. | |
| 291 | #[serde(default)] | |
| 292 | pub agent: Option<String>, | |
| 293 | /// Who asked for the work, by username; none for a routine's or | |
| 294 | /// another agent's. The runner, which is TypeScript, sends `askedBy`. | |
| 295 | #[serde(default, alias = "askedBy")] | |
| 296 | pub asked_by: Option<String>, | |
| Deploy scripts live in the repository | 297 | } |
| 298 | ||
| 299 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 300 | #[serde(rename_all = "camelCase")] | |
| 301 | pub struct RunTicket { | |
| 302 | pub run_id: String, | |
| 303 | /// Lets the sandbox, and nothing else, report what this run cost. | |
| 304 | pub token: String, | |
| 305 | } | |
| 306 | ||
| 307 | /// `finish_run`: what a run cost, as its sandbox reports it. Charged once. | |
| 308 | /// Returns `Outcome<bool>`. | |
| 309 | #[derive(Debug, Serialize, Deserialize)] | |
| 310 | #[serde(rename_all = "camelCase")] | |
| 311 | pub struct FinishRunArgs { | |
| 312 | pub run_id: String, | |
| 313 | pub token: String, | |
| 314 | /// What the model provider charged, in US dollars. | |
| 315 | pub cost_usd: f64, | |
| 316 | #[serde(default)] | |
| 317 | pub turns: u32, | |
| Merge branch 'model-routing' | 318 | /// The tokens the run used, as the harness counted them from the |
| 319 | /// provider's answers. On the workspace's own provider, the agent rate | |
| 320 | /// is charged on no fewer than these. Absent from older sandboxes. | |
| 321 | #[serde(default)] | |
| 322 | pub tokens: Option<RunTokens>, | |
| 323 | } | |
| 324 | ||
| 325 | /// The tokens one run used, by kind. | |
| 326 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| 327 | #[serde(rename_all = "camelCase")] | |
| 328 | pub struct RunTokens { | |
| 329 | #[serde(default)] | |
| 330 | pub input: u64, | |
| 331 | #[serde(default)] | |
| 332 | pub output: u64, | |
| 333 | #[serde(default)] | |
| 334 | pub cache_read: u64, | |
| 335 | #[serde(default)] | |
| 336 | pub cache_write: u64, | |
| 337 | } | |
| 338 | ||
| 339 | impl RunTokens { | |
| 340 | /// Every token, of every kind: what the agent rate is charged on. | |
| 341 | pub fn total(&self) -> u64 { | |
| 342 | self.input.saturating_add(self.output).saturating_add(self.cache_read).saturating_add(self.cache_write) | |
| 343 | } | |
| Deploy scripts live in the repository | 344 | } |
| 345 | ||
| 346 | ||
| 347 | /// `usage`: what a workspace's agents cost over a period, broken down. | |
| 348 | /// Members only. Returns `Outcome<Usage>`. | |
| 349 | #[derive(Debug, Serialize, Deserialize)] | |
| 350 | pub struct UsageArgs { | |
| 351 | pub workspace: String, | |
| 352 | pub viewer: Viewer, | |
| 353 | /// RFC 3339: the start of the period. The period runs to now. | |
| 354 | pub since: String, | |
| 355 | } | |
| 356 | ||
| 357 | /// One slice of usage: what it was for, what it cost, how many runs. | |
| 358 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 359 | #[serde(rename_all = "camelCase")] | |
| 360 | pub struct UsageSlice { | |
| 361 | pub key: String, | |
| 362 | pub micros: i64, | |
| 363 | pub runs: u32, | |
| 364 | } | |
| 365 | ||
| 366 | /// What a workspace's agents cost over a period. | |
| 367 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 368 | #[serde(rename_all = "camelCase")] | |
| 369 | pub struct Usage { | |
| 370 | pub since: String, | |
| 371 | /// Charged, including g1t's margin. | |
| 372 | pub spent_micros: i64, | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 373 | /// What g1t's usage came to at price, less what was charged: the plan's |
| 374 | /// included usage, the trial, a pool or a free period paid it. Usage at | |
| 375 | /// price is `spent_micros` plus this. | |
| Billing's usage total is labeled at price, and Usage says what part of it was paid for | 376 | #[serde(default)] |
| 377 | pub covered_micros: i64, | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 378 | /// What the account's discount took off the price. Usage at price is |
| 379 | /// `spent_micros` plus `covered_micros` plus this. | |
| 380 | #[serde(default)] | |
| 381 | pub discount_micros: i64, | |
| 382 | /// The account's discount now, in percent; absent without one. With | |
| 383 | /// one, the slices measure usage at price. | |
| 384 | #[serde(default)] | |
| 385 | pub discount_percent: Option<u32>, | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 386 | /// Usage at price: `spent_micros` plus `covered_micros` plus |
| 387 | /// `discount_micros`, from the same ledger lines. The one figure every | |
| 388 | /// page shows as usage (mission control, the agent fleet, Usage and | |
| 389 | /// Billing), labelled "usage at price". | |
| 390 | #[serde(default)] | |
| 391 | pub price_micros: i64, | |
| Deploy scripts live in the repository | 392 | /// What g1t's model provider charged, before the margin. |
| 393 | pub cost_micros: i64, | |
| 394 | /// What runs on the workspace's own provider cost there, as the harness | |
| 395 | /// estimated it. Not charged by g1t. | |
| 396 | pub provider_micros: i64, | |
| 397 | /// What the runs used, at cost: g1t's models and the workspace's own | |
| 398 | /// provider together, whatever was charged for them. | |
| 399 | pub used_micros: i64, | |
| 400 | /// g1t charges nothing for now. The slices then measure usage at cost, | |
| 401 | /// since every charge is zero. | |
| 402 | pub free: bool, | |
| 403 | pub runs: u32, | |
| 404 | /// Spend per day (`YYYY-MM-DD`) and task, as `day/task` keys. | |
| 405 | pub by_day: Vec<UsageSlice>, | |
| 406 | /// Per task: implement, review, revise, update, plan. | |
| 407 | pub by_task: Vec<UsageSlice>, | |
| 408 | /// Per repository, `namespace/name`. | |
| 409 | pub by_repo: Vec<UsageSlice>, | |
| 410 | /// The pull requests that cost most, as `namespace/name#number`. | |
| 411 | pub by_pull: Vec<UsageSlice>, | |
| 412 | /// Per model, by its public name. | |
| 413 | pub by_model: Vec<UsageSlice>, | |
| 414 | /// Credit bought in the period. | |
| 415 | pub added_micros: i64, | |
| 416 | } | |
| 417 | ||
| Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix | 418 | /// `record_tokens`: what one model answer used, added to the day's count |
| 419 | /// for its run. The model proxy sends it after each answer. For usage | |
| 420 | /// views only: runs are still priced from AI Gateway. Returns | |
| 421 | /// `Outcome<bool>`: false when there was nothing to count. | |
| 422 | #[derive(Debug, Serialize, Deserialize)] | |
| 423 | #[serde(rename_all = "camelCase")] | |
| 424 | pub struct RecordTokensArgs { | |
| 425 | pub workspace: String, | |
| 426 | /// The model session's id (`ModelSession::id`), one per run. | |
| 427 | pub session: String, | |
| 428 | /// The person the run is for, by username. Absent when nobody asked. | |
| 429 | #[serde(default)] | |
| 430 | pub person: Option<String>, | |
| 431 | pub model: String, | |
| Merge branch 'model-routing' | 432 | /// The tier g1t routed the run to: `small`, `large` or `frontier`. |
| Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix | 433 | #[serde(default)] |
| 434 | pub tier: Option<String>, | |
| 435 | #[serde(default)] | |
| 436 | pub input: u64, | |
| 437 | #[serde(default)] | |
| 438 | pub output: u64, | |
| 439 | #[serde(default)] | |
| 440 | pub cache_read: u64, | |
| 441 | #[serde(default)] | |
| 442 | pub cache_write: u64, | |
| 443 | } | |
| 444 | ||
| 445 | /// `token_usage`: the model tokens a workspace's runs used, day by day, | |
| 446 | /// for the whole workspace or for one person. Members only; a member may | |
| 447 | /// ask only for themselves, an owner for anyone. Returns | |
| 448 | /// `Outcome<TokenUsage>`. | |
| 449 | #[derive(Debug, Serialize, Deserialize)] | |
| 450 | pub struct TokenUsageArgs { | |
| 451 | pub workspace: String, | |
| 452 | pub viewer: Viewer, | |
| 453 | /// A username: only the runs for them. | |
| 454 | #[serde(default)] | |
| 455 | pub person: Option<String>, | |
| 456 | /// How many days, to today: 42 when absent, 366 at most. | |
| 457 | #[serde(default)] | |
| 458 | pub days: Option<u32>, | |
| 459 | } | |
| 460 | ||
| 461 | /// One day's tokens. | |
| 462 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 463 | pub struct DayTokens { | |
| 464 | /// `YYYY-MM-DD`, UTC. | |
| 465 | pub day: String, | |
| 466 | pub tokens: u64, | |
| 467 | } | |
| 468 | ||
| 469 | /// The model tokens runs used over a window of days. | |
| 470 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 471 | #[serde(rename_all = "camelCase")] | |
| 472 | pub struct TokenUsage { | |
| 473 | /// `YYYY-MM-DD`: the first day counted. | |
| 474 | pub since: String, | |
| 475 | pub days: u32, | |
| 476 | /// Null for the whole workspace. | |
| 477 | pub person: Option<String>, | |
| 478 | pub total_tokens: u64, | |
| 479 | pub input_tokens: u64, | |
| 480 | pub output_tokens: u64, | |
| 481 | pub cache_read_tokens: u64, | |
| 482 | pub cache_write_tokens: u64, | |
| 483 | /// What those runs were charged, as `usage` measures it. | |
| 484 | pub cost_micros: i64, | |
| 485 | /// Days in the window with any tokens. | |
| 486 | pub active_days: u32, | |
| 487 | /// Every day in the window, oldest first, zeros included. | |
| 488 | pub by_day: Vec<DayTokens>, | |
| 489 | } | |
| 490 | ||
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 491 | // --- AI Gateway ------------------------------------------------------------- |
| 492 | // | |
| 493 | // A workspace's own model requests, sent with one of its access tokens to | |
| AI Gateway: OpenAI's format, open models, and your own providers | 494 | // the model proxy (`models.g1t.sh/anthropic` in Anthropic's Messages format, |
| 495 | // `models.g1t.sh/openai/v1` in OpenAI's Chat Completions format). On g1t's | |
| 496 | // models each request | |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 497 | // is charged to the workspace at the model's price, with the price book's |
| 498 | // `gateway_models` markup, drawn from AI credit; on the workspace's own | |
| 499 | // provider key it is only counted. | |
| 500 | ||
| 501 | /// A model the AI Gateway offers on g1t's own key, with its price per | |
| 502 | /// million tokens of each kind. `gateway_models` takes nothing and returns | |
| 503 | /// these, in the order they are shown. | |
| 504 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 505 | #[serde(rename_all = "camelCase")] | |
| 506 | pub struct GatewayModel { | |
| AI Gateway: OpenAI's format, open models, and your own providers | 507 | /// The provider's own id, such as `claude-sonnet-5-5` or |
| 508 | /// `@cf/openai/gpt-oss-120b`. A request names it as it is or with its | |
| 509 | /// provider in front (`anthropic/claude-sonnet-5-5`, | |
| 510 | /// `workers-ai/@cf/openai/gpt-oss-120b`). | |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 511 | pub model: String, |
| 512 | /// For people: `Claude Sonnet 5.5`. | |
| 513 | pub name: String, | |
| AI Gateway: OpenAI's format, open models, and your own providers | 514 | /// `anthropic` or `workers-ai`. |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 515 | pub provider: String, |
| AI Gateway: OpenAI's format, open models, and your own providers | 516 | /// `chat`, or `embeddings` for a model that only embeds text. |
| 517 | #[serde(default = "chat")] | |
| 518 | pub kind: String, | |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 519 | pub input_micros: i64, |
| 520 | pub output_micros: i64, | |
| 521 | pub cache_read_micros: i64, | |
| AI Gateway: OpenAI's format, open models, and your own providers | 522 | /// Cache writes that live five minutes. |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 523 | pub cache_write_micros: i64, |
| AI Gateway: OpenAI's format, open models, and your own providers | 524 | /// Cache writes that live an hour. |
| 525 | #[serde(default)] | |
| 526 | pub cache_write_1h_micros: i64, | |
| 527 | /// A model priced by the prompt's length: a request whose prompt (its | |
| 528 | /// input, cache read and cache write tokens) is longer than this many | |
| 529 | /// tokens is charged entirely at the `over_` prices. 0 for one price. | |
| 530 | #[serde(default)] | |
| 531 | pub threshold: u64, | |
| 532 | #[serde(default)] | |
| 533 | pub over_input_micros: i64, | |
| 534 | #[serde(default)] | |
| 535 | pub over_output_micros: i64, | |
| 536 | #[serde(default)] | |
| 537 | pub over_cache_read_micros: i64, | |
| 538 | #[serde(default)] | |
| 539 | pub over_cache_write_micros: i64, | |
| 540 | #[serde(default)] | |
| 541 | pub over_cache_write_1h_micros: i64, | |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 542 | } |
| 543 | ||
| AI Gateway: OpenAI's format, open models, and your own providers | 544 | fn chat() -> String { |
| 545 | "chat".to_owned() | |
| 546 | } | |
| 547 | ||
| 548 | fn anthropic_format() -> String { | |
| 549 | "anthropic".to_owned() | |
| 550 | } | |
| 551 | ||
| Merge branch 'main' into actions-toolkit-oidc-artifacts | 552 | // --- The model catalogue ---------------------------------------------------- |
| 553 | // | |
| 554 | // Every model g1t can use, in one table (billing's `gateway_models`): the | |
| 555 | // models agents run on, the AI Gateway's, and the embeddings model. New | |
| 556 | // models are found by the models service listing each provider daily | |
| 557 | // (`record_discovery`) and wait as `new` until staff approve them in sudo. | |
| 558 | // Which model each purpose uses by default is staff's choice | |
| 559 | // (`model_defaults`), read by the runner and the AI Gateway. | |
| 560 | ||
| 561 | /// Where a model stands. Only `available` models are routed to; the AI | |
| 562 | /// Gateway offers `available` and `deprecated` ones that have a price. | |
| 563 | pub mod model_status { | |
| 564 | /// Approved and priced: routed to and offered. | |
| 565 | pub const AVAILABLE: &str = "available"; | |
| 566 | /// Found by discovery and not approved yet: never routed to, offered or charged. | |
| 567 | pub const NEW: &str = "new"; | |
| 568 | /// The provider stopped listing it: still offered to anyone who names | |
| 569 | /// it, but no default routes to it. | |
| 570 | pub const DEPRECATED: &str = "deprecated"; | |
| 571 | /// Staff retired it: neither routed to nor offered. | |
| 572 | pub const RETIRED: &str = "retired"; | |
| 573 | } | |
| 574 | ||
| 575 | /// One model in the catalogue: its prices (as the AI Gateway reads them) | |
| 576 | /// and what g1t knows about it. `admin_models` returns these. | |
| 577 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 578 | #[serde(rename_all = "camelCase")] | |
| 579 | pub struct CatalogueModel { | |
| 580 | #[serde(flatten)] | |
| 581 | pub prices: GatewayModel, | |
| 582 | /// Other ids the provider lists it by, such as a dated one. | |
| 583 | #[serde(default)] | |
| 584 | pub aliases: Vec<String>, | |
| 585 | /// `haiku`, `sonnet`, `opus`, `fable`, or a Workers AI author. | |
| 586 | #[serde(default)] | |
| 587 | pub family: String, | |
| 588 | /// The agent tier it suits: `small`, `large`, `frontier`, or empty. | |
| 589 | #[serde(default)] | |
| 590 | pub tier_hint: String, | |
| 591 | /// Tokens it reads at most; 0 when not known. | |
| 592 | #[serde(default)] | |
| 593 | pub context_window: u64, | |
| 594 | /// Tokens it writes at most; 0 when not known. | |
| 595 | #[serde(default)] | |
| 596 | pub max_output: u64, | |
| 597 | /// Any of `effort`, `thinking`, `tools`, `vision`, `embeddings`. | |
| 598 | #[serde(default)] | |
| 599 | pub capabilities: Vec<String>, | |
| 600 | /// An embeddings model's vector length; 0 otherwise or when not known. | |
| 601 | #[serde(default)] | |
| 602 | pub dimensions: u32, | |
| 603 | /// `available`, `new`, `deprecated` or `retired` (`model_status`). | |
| 604 | pub status: String, | |
| 605 | /// Whether its prices are known. An unpriced model is never routed to, | |
| 606 | /// offered or charged for. | |
| 607 | pub priced: bool, | |
| 608 | /// `discovered` (found by listing its provider) or `staff`. | |
| 609 | pub source: String, | |
| 610 | #[serde(default)] | |
| 611 | pub first_seen_at: Option<String>, | |
| 612 | /// When its provider last listed it. | |
| 613 | #[serde(default)] | |
| 614 | pub last_seen_at: Option<String>, | |
| 615 | /// Since when its provider has not listed it. | |
| 616 | #[serde(default)] | |
| 617 | pub missing_since: Option<String>, | |
| 618 | #[serde(default)] | |
| 619 | pub approved_by: Option<String>, | |
| 620 | #[serde(default)] | |
| 621 | pub approved_at: Option<String>, | |
| 622 | #[serde(default)] | |
| 623 | pub note: String, | |
| 624 | /// What a typical agent run would cost on it, in millionths of a | |
| 625 | /// dollar, from its prices (`typical_run` in billing's catalogue.rs); | |
| 626 | /// 0 for an embeddings or unpriced model. | |
| 627 | #[serde(default)] | |
| 628 | pub typical_run_micros: i64, | |
| 629 | } | |
| 630 | ||
| 631 | /// A provider's list price per million tokens, as its listing gives it, in | |
| 632 | /// millionths of a dollar. | |
| 633 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 634 | #[serde(rename_all = "camelCase")] | |
| 635 | pub struct ListedPrice { | |
| 636 | pub input_micros: i64, | |
| 637 | #[serde(default)] | |
| 638 | pub output_micros: i64, | |
| 639 | } | |
| 640 | ||
| 641 | /// One model as its provider lists it, from the models service's | |
| 642 | /// discovery. | |
| 643 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 644 | #[serde(rename_all = "camelCase")] | |
| 645 | pub struct ProviderModel { | |
| 646 | /// The provider's id: `claude-haiku-5-5`, `@cf/openai/gpt-oss-120b`. | |
| 647 | pub id: String, | |
| 648 | /// For people, as the provider names it. | |
| 649 | #[serde(default)] | |
| 650 | pub name: String, | |
| 651 | /// `chat`, `embeddings`, or anything else (not added to the catalogue, | |
| 652 | /// but still counted as listed). | |
| 653 | #[serde(default)] | |
| 654 | pub kind: String, | |
| 655 | #[serde(default)] | |
| 656 | pub context_window: u64, | |
| 657 | #[serde(default)] | |
| 658 | pub max_output: u64, | |
| 659 | #[serde(default)] | |
| 660 | pub capabilities: Vec<String>, | |
| 661 | /// Workers AI lists a price with each model; Anthropic does not. | |
| 662 | #[serde(default)] | |
| 663 | pub price: Option<ListedPrice>, | |
| 664 | } | |
| 665 | ||
| 666 | /// `record_discovery`: what one provider lists now, from the models | |
| 667 | /// service (daily, or when staff press "Check for new models"). Billing | |
| 668 | /// adds new ids as `new`, marks ones no longer listed `deprecated`, records | |
| 669 | /// the check and emails staff about anything new. With `error` (the listing | |
| 670 | /// failed) only the check is recorded. Returns `DiscoveryResult`. | |
| 671 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 672 | #[serde(rename_all = "camelCase")] | |
| 673 | pub struct RecordDiscoveryArgs { | |
| 674 | /// `anthropic` or `workers-ai`. | |
| 675 | pub provider: String, | |
| 676 | #[serde(default)] | |
| 677 | pub models: Vec<ProviderModel>, | |
| 678 | /// The staff member who asked, or `schedule`. | |
| 679 | pub by: String, | |
| 680 | #[serde(default)] | |
| 681 | pub error: Option<String>, | |
| 682 | } | |
| 683 | ||
| 684 | /// What one check found. | |
| 685 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 686 | #[serde(rename_all = "camelCase")] | |
| 687 | pub struct DiscoveryResult { | |
| 688 | pub provider: String, | |
| 689 | pub checked_at: String, | |
| 690 | pub by: String, | |
| 691 | /// Ids the provider listed. | |
| 692 | pub listed: u32, | |
| 693 | /// Ids added to the catalogue as `new`. | |
| 694 | pub added: Vec<String>, | |
| 695 | /// Catalogue models the provider no longer lists, now `deprecated`. | |
| 696 | pub deprecated: Vec<String>, | |
| 697 | /// Deprecated models listed again. | |
| 698 | pub restored: Vec<String>, | |
| 699 | /// The listing failed: nothing changed. | |
| 700 | #[serde(default)] | |
| 701 | pub error: Option<String>, | |
| 702 | } | |
| 703 | ||
| 704 | /// Which model, tier or effort one purpose uses by default, as staff last | |
| 705 | /// set it. | |
| 706 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 707 | #[serde(rename_all = "camelCase")] | |
| 708 | pub struct ModelDefault { | |
| 709 | /// `tier_small`, `tier_large`, `tier_frontier`, `background`, | |
| 710 | /// `gateway_first`, or `job_<kind>` for `implement`, `revise`, | |
| 711 | /// `answer`, `review`, `update` and `plan`. | |
| 712 | pub purpose: String, | |
| 713 | /// The model, for a model purpose. | |
| 714 | #[serde(default)] | |
| 715 | pub model: Option<String>, | |
| 716 | /// For a job: `small`, `large`, `frontier`, or `change` (sized by the change). | |
| 717 | #[serde(default)] | |
| 718 | pub tier: Option<String>, | |
| 719 | /// For a job: `low`, `medium`, `high`, `xhigh` or `max`; none for the harness's own. | |
| 720 | #[serde(default)] | |
| 721 | pub effort: Option<String>, | |
| 722 | pub updated_at: String, | |
| 723 | pub updated_by: String, | |
| 724 | #[serde(default)] | |
| 725 | pub reason: String, | |
| 726 | } | |
| 727 | ||
| 728 | /// A model purpose's default as it applies now: the chosen model, or the | |
| 729 | /// one routing falls back to when the chosen one cannot be used. | |
| 730 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 731 | #[serde(rename_all = "camelCase")] | |
| 732 | pub struct ResolvedModel { | |
| 733 | pub purpose: String, | |
| 734 | /// The model staff chose. | |
| 735 | pub chosen: String, | |
| 736 | /// The model to use, with its prices; none when neither the chosen | |
| 737 | /// model nor any other suits (callers keep their own fallback). | |
| 738 | #[serde(default)] | |
| 739 | pub model: Option<GatewayModel>, | |
| 740 | #[serde(default)] | |
| 741 | pub capabilities: Vec<String>, | |
| 742 | /// Why it is not the chosen one, in a sentence: `Claude Haiku 5.5 is | |
| 743 | /// retired; using Claude Haiku 4.5.` | |
| 744 | #[serde(default)] | |
| 745 | pub note: Option<String>, | |
| 746 | } | |
| 747 | ||
| 748 | /// One kind of agent job's starting tier and effort. | |
| 749 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 750 | #[serde(rename_all = "camelCase")] | |
| 751 | pub struct JobDefault { | |
| 752 | /// `implement`, `revise`, `answer`, `review`, `update` or `plan`. | |
| 753 | pub kind: String, | |
| 754 | /// `small`, `large`, `frontier` or `change`. | |
| 755 | pub tier: String, | |
| 756 | #[serde(default)] | |
| 757 | pub effort: Option<String>, | |
| 758 | } | |
| 759 | ||
| 760 | /// `model_defaults` takes nothing and returns this: every purpose's model | |
| 761 | /// as it applies now, and each job's tier and effort. Read by the runner | |
| 762 | /// (cached a minute) and the AI Gateway. | |
| 763 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 764 | #[serde(rename_all = "camelCase")] | |
| 765 | pub struct ModelDefaults { | |
| 766 | pub models: Vec<ResolvedModel>, | |
| 767 | pub jobs: Vec<JobDefault>, | |
| 768 | } | |
| 769 | ||
| 770 | /// A check of one provider, as `model_checks` keeps it. | |
| 771 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 772 | #[serde(rename_all = "camelCase")] | |
| 773 | pub struct ModelCheck { | |
| 774 | pub id: String, | |
| 775 | pub provider: String, | |
| 776 | pub checked_at: String, | |
| 777 | pub by: String, | |
| 778 | pub listed: u32, | |
| 779 | pub added: Vec<String>, | |
| 780 | pub deprecated: Vec<String>, | |
| 781 | #[serde(default)] | |
| 782 | pub error: Option<String>, | |
| 783 | } | |
| 784 | ||
| 785 | /// `admin_models` takes nothing and returns this: sudo's Agents & models. | |
| 786 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 787 | #[serde(rename_all = "camelCase")] | |
| 788 | pub struct AdminModels { | |
| 789 | /// Every model, `new` ones first, then by provider and position. | |
| 790 | pub catalogue: Vec<CatalogueModel>, | |
| 791 | pub defaults: Vec<ModelDefault>, | |
| 792 | pub resolved: ModelDefaults, | |
| 793 | /// The latest checks, newest first. | |
| 794 | pub checks: Vec<ModelCheck>, | |
| 795 | /// The token mix `typical_run_micros` prices, for the page to state. | |
| 796 | pub typical: TypicalRun, | |
| 797 | } | |
| 798 | ||
| 799 | /// The tokens of the typical agent run estimates are priced from. | |
| 800 | #[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 801 | #[serde(rename_all = "camelCase")] | |
| 802 | pub struct TypicalRun { | |
| 803 | pub requests: u64, | |
| 804 | /// Per request. | |
| 805 | pub input: u64, | |
| 806 | pub output: u64, | |
| 807 | pub cache_read: u64, | |
| 808 | pub cache_write: u64, | |
| 809 | } | |
| 810 | ||
| 811 | /// A model's prices as staff confirm them, per million tokens in | |
| 812 | /// millionths of a dollar. | |
| 813 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 814 | pub struct ModelPrices { | |
| 815 | pub input_micros: i64, | |
| 816 | pub output_micros: i64, | |
| 817 | pub cache_read_micros: i64, | |
| 818 | pub cache_write_micros: i64, | |
| 819 | #[serde(default)] | |
| 820 | pub cache_write_1h_micros: i64, | |
| 821 | #[serde(default)] | |
| 822 | pub threshold: u64, | |
| 823 | #[serde(default)] | |
| 824 | pub over_input_micros: i64, | |
| 825 | #[serde(default)] | |
| 826 | pub over_output_micros: i64, | |
| 827 | #[serde(default)] | |
| 828 | pub over_cache_read_micros: i64, | |
| 829 | #[serde(default)] | |
| 830 | pub over_cache_write_micros: i64, | |
| 831 | #[serde(default)] | |
| 832 | pub over_cache_write_1h_micros: i64, | |
| 833 | } | |
| 834 | ||
| 835 | /// `admin_decide_model`: `approve` a model (its prices confirmed, made | |
| 836 | /// available), `retire` one, or `restore` a retired or deprecated one. | |
| 837 | /// Audited. Returns `Outcome<CatalogueModel>`. | |
| 838 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 839 | pub struct AdminDecideModelArgs { | |
| 840 | pub model: String, | |
| 841 | pub decision: String, | |
| 842 | /// On approval: the name people see, and the prices. | |
| 843 | #[serde(default)] | |
| 844 | pub name: Option<String>, | |
| 845 | #[serde(default)] | |
| 846 | pub tier_hint: Option<String>, | |
| 847 | #[serde(default)] | |
| 848 | pub prices: Option<ModelPrices>, | |
| 849 | pub reason: String, | |
| 850 | pub by: String, | |
| 851 | } | |
| 852 | ||
| 853 | /// `admin_set_model_default`: one purpose's default. A model purpose takes | |
| 854 | /// `model` (available, priced, and suited to the purpose); a job takes | |
| 855 | /// `tier` and `effort`. Audited with the old and new values and why. | |
| 856 | /// Returns `Outcome<ModelDefault>`. | |
| 857 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 858 | pub struct AdminSetModelDefaultArgs { | |
| 859 | pub purpose: String, | |
| 860 | #[serde(default)] | |
| 861 | pub model: Option<String>, | |
| 862 | #[serde(default)] | |
| 863 | pub tier: Option<String>, | |
| 864 | #[serde(default)] | |
| 865 | pub effort: Option<String>, | |
| 866 | pub reason: String, | |
| 867 | pub by: String, | |
| 868 | } | |
| 869 | ||
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 870 | /// `gateway_admit`: whether a workspace's next AI Gateway request may go to |
| 871 | /// g1t's models. Fails with `payment_required` and what to do when it may | |
| 872 | /// not: over its spend limit, out of AI credit, or not on the plan. Returns | |
| 873 | /// `Outcome<bool>`. | |
| 874 | #[derive(Debug, Serialize, Deserialize)] | |
| 875 | pub struct GatewayAdmitArgs { | |
| 876 | pub workspace: String, | |
| 877 | } | |
| 878 | ||
| 879 | /// `record_gateway`: one AI Gateway request, logged, and charged when it | |
| 880 | /// went to g1t's models and used tokens. The model proxy sends it after | |
| 881 | /// the answer. `id` makes it idempotent: a request recorded twice is | |
| 882 | /// logged and charged once. Returns `Outcome<bool>`: false when it was | |
| 883 | /// already recorded. | |
| 884 | #[derive(Debug, Serialize, Deserialize)] | |
| 885 | #[serde(rename_all = "camelCase")] | |
| 886 | pub struct RecordGatewayArgs { | |
| 887 | /// `gw_…`, chosen by the proxy. | |
| 888 | pub id: String, | |
| 889 | pub workspace: String, | |
| 890 | /// The access token's id and name. | |
| 891 | pub token_id: String, | |
| 892 | #[serde(default)] | |
| 893 | pub token_name: Option<String>, | |
| 894 | pub model: String, | |
| 895 | #[serde(default)] | |
| 896 | pub input: u64, | |
| 897 | #[serde(default)] | |
| 898 | pub output: u64, | |
| 899 | #[serde(default)] | |
| 900 | pub cache_read: u64, | |
| AI Gateway: OpenAI's format, open models, and your own providers | 901 | /// Every cache write, of either lifetime. |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 902 | #[serde(default)] |
| 903 | pub cache_write: u64, | |
| AI Gateway: OpenAI's format, open models, and your own providers | 904 | /// Of `cache_write`, those that live an hour. |
| 905 | #[serde(default)] | |
| 906 | pub cache_write_hour: u64, | |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 907 | /// The HTTP status the caller was answered with. |
| 908 | pub status: u16, | |
| 909 | /// On the workspace's own provider key: counted, never charged. | |
| 910 | #[serde(default)] | |
| 911 | pub own_key: bool, | |
| AI Gateway: OpenAI's format, open models, and your own providers | 912 | /// The format the request was sent in: `anthropic` or `openai`. |
| 913 | #[serde(default = "anthropic_format")] | |
| 914 | pub format: String, | |
| 915 | /// Who served it: on g1t's key the catalogue's provider (`anthropic`, | |
| 916 | /// `workers-ai`); on the workspace's own, its connection's provider | |
| 917 | /// (`openai`, `openai_endpoint`…). Empty when it never got that far. | |
| 918 | #[serde(default)] | |
| 919 | pub provider: String, | |
| 920 | /// On the workspace's own provider: the connection's name. | |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 921 | #[serde(default)] |
| AI Gateway: OpenAI's format, open models, and your own providers | 922 | pub connection: Option<String>, |
| 923 | #[serde(default)] | |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 924 | pub streamed: bool, |
| 925 | #[serde(default)] | |
| 926 | pub duration_ms: u64, | |
| 927 | /// What went wrong, for a request that was refused or failed. | |
| 928 | #[serde(default)] | |
| 929 | pub error: Option<String>, | |
| 930 | } | |
| 931 | ||
| 932 | /// `gateway_requests`: a workspace's recent AI Gateway requests, newest | |
| 933 | /// first. Members only. Returns `Outcome<GatewayRequests>`. | |
| 934 | #[derive(Debug, Serialize, Deserialize)] | |
| 935 | pub struct GatewayRequestsArgs { | |
| 936 | pub workspace: String, | |
| 937 | pub viewer: Viewer, | |
| 938 | /// How many, 50 when absent, 200 at most. | |
| 939 | #[serde(default)] | |
| 940 | pub limit: Option<u32>, | |
| 941 | /// Only requests older than this one (a request's `id`), for the next page. | |
| 942 | #[serde(default)] | |
| 943 | pub before: Option<String>, | |
| 944 | } | |
| 945 | ||
| 946 | /// One AI Gateway request, as its log keeps it. | |
| 947 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 948 | #[serde(rename_all = "camelCase")] | |
| 949 | pub struct GatewayRequest { | |
| 950 | pub id: String, | |
| 951 | /// RFC 3339. | |
| 952 | pub created_at: String, | |
| 953 | pub model: String, | |
| 954 | pub token_id: String, | |
| 955 | pub token_name: Option<String>, | |
| 956 | pub input: u64, | |
| 957 | pub output: u64, | |
| 958 | pub cache_read: u64, | |
| 959 | pub cache_write: u64, | |
| AI Gateway: OpenAI's format, open models, and your own providers | 960 | /// Of `cache_write`, those that live an hour. |
| 961 | #[serde(default)] | |
| 962 | pub cache_write_hour: u64, | |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 963 | /// What the tokens cost at the model's price. |
| 964 | pub cost_micros: i64, | |
| 965 | /// What the workspace was charged for it, before included usage and | |
| 966 | /// credit paid for it: 0 on its own key. | |
| 967 | pub charged_micros: i64, | |
| 968 | pub status: u16, | |
| 969 | pub own_key: bool, | |
| AI Gateway: OpenAI's format, open models, and your own providers | 970 | /// `anthropic` or `openai`: the format it was sent in. |
| 971 | #[serde(default = "anthropic_format")] | |
| 972 | pub format: String, | |
| 973 | /// Who served it: `anthropic` or `workers-ai` on g1t's key, the | |
| 974 | /// connection's provider on the workspace's own. | |
| 975 | #[serde(default)] | |
| 976 | pub provider: String, | |
| 977 | /// On the workspace's own provider: the connection's name. | |
| 978 | #[serde(default)] | |
| 979 | pub connection: Option<String>, | |
| Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens | 980 | pub streamed: bool, |
| 981 | pub duration_ms: u64, | |
| 982 | pub error: Option<String>, | |
| 983 | } | |
| 984 | ||
| 985 | /// A page of AI Gateway requests. | |
| 986 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 987 | #[serde(rename_all = "camelCase")] | |
| 988 | pub struct GatewayRequests { | |
| 989 | pub requests: Vec<GatewayRequest>, | |
| 990 | /// The `before` for the next page, when there is one. | |
| 991 | pub next: Option<String>, | |
| 992 | /// How many days requests are kept. | |
| 993 | pub retention_days: u32, | |
| 994 | } | |
| 995 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 996 | /// What a workspace pays a monthly price for. There is one plan, `plan` |
| 997 | /// ("g1t"): a flat price per workspace, never per person, with included | |
| 998 | /// usage each month, more private storage, and deployments. Never free: | |
| 999 | /// `FREE_WHILE_BUILDING` does not cover it. | |
| 1000 | /// | |
| 1001 | /// `deployments` is not sold on its own any more: it comes with the plan. | |
| 1002 | /// A service that asks `has_feature` for it is told whether the workspace | |
| 1003 | /// has the plan, and a Deployments subscription bought before the change | |
| 1004 | /// keeps working until its period ends. | |
| Deploy scripts live in the repository | 1005 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] |
| 1006 | #[serde(rename_all = "snake_case")] | |
| 1007 | pub enum Feature { | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1008 | /// The g1t plan. Older readers called it `team`. |
| 1009 | #[serde(alias = "team")] | |
| 1010 | Plan, | |
| 1011 | /// Previews per pull request and production on g1t.page: part of the | |
| 1012 | /// plan. | |
| Deploy scripts live in the repository | 1013 | Deployments, |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 1014 | /// Security and quality: the security suite's paid features on private |
| 1015 | /// repositories. It comes with the plan; the price book's | |
| 1016 | /// `security_activation` is $0, and its scans are metered like | |
| 1017 | /// everything else. | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1018 | Security, |
| Deploy scripts live in the repository | 1019 | } |
| 1020 | ||
| 1021 | impl Feature { | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1022 | /// What is sold: the plan, and the Security and quality activation. |
| 1023 | pub const ALL: [Feature; 2] = [Feature::Plan, Feature::Security]; | |
| Deploy scripts live in the repository | 1024 | |
| 1025 | pub fn as_str(self) -> &'static str { | |
| 1026 | match self { | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1027 | Feature::Plan => "plan", |
| Deploy scripts live in the repository | 1028 | Feature::Deployments => "deployments", |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1029 | Feature::Security => "security", |
| Deploy scripts live in the repository | 1030 | } |
| 1031 | } | |
| 1032 | ||
| 1033 | pub fn parse(name: &str) -> Option<Feature> { | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1034 | match name { |
| 1035 | "plan" | "team" => Some(Feature::Plan), | |
| 1036 | "deployments" => Some(Feature::Deployments), | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1037 | "security" => Some(Feature::Security), |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1038 | _ => None, |
| 1039 | } | |
| Deploy scripts live in the repository | 1040 | } |
| 1041 | ||
| 1042 | pub fn title(self) -> &'static str { | |
| 1043 | match self { | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1044 | Feature::Plan => "g1t", |
| Deploy scripts live in the repository | 1045 | Feature::Deployments => "Deployments", |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1046 | Feature::Security => "Security and quality", |
| Deploy scripts live in the repository | 1047 | } |
| 1048 | } | |
| 1049 | } | |
| 1050 | ||
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 1051 | /// What deployments cost g1t, in millionths of a dollar: fallbacks for |
| 1052 | /// when billing's price book cannot be read. Nothing here is an allowance: | |
| 1053 | /// on the plan every unit is metered from the first, at cost plus the | |
| 1054 | /// margin, and drawn from the plan's included usage before anything is | |
| 1055 | /// charged. Projects, previews and the apps behind them are not metered at | |
| 1056 | /// all: Cloudflare's Workers for Platforms includes far more scripts than | |
| 1057 | /// g1t runs, so an app costs g1t only the requests and CPU it answers with. | |
| 1058 | pub mod deployment_costs { | |
| 1059 | /// Workers for Platforms: $0.30 per million requests. | |
| Deploy scripts live in the repository | 1060 | pub const MICROS_PER_MILLION_REQUESTS: i64 = 300_000; |
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 1061 | /// $0.02 per million CPU milliseconds. |
| Deploy scripts live in the repository | 1062 | pub const MICROS_PER_MILLION_CPU_MS: i64 = 20_000; |
| 1063 | /// What one second of a build's sandbox costs g1t (Cloudflare | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1064 | /// Containers, standard-1: half a vCPU, 4 GiB, 8 GB disk), rounded up, |
| 1065 | /// as the price keeper measured it on 2026-10-05 (14.5). Only a | |
| 1066 | /// fallback: billing charges builds at the price book's `build_second`, | |
| 1067 | /// which the keeper keeps current. | |
| 1068 | pub const MICROS_PER_BUILD_SECOND: i64 = 15; | |
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 1069 | /// What one custom hostname costs g1t a month (Cloudflare for SaaS): |
| 1070 | /// $0.10. | |
| Agents and memory, checks and conflicts, profiles, slug renames, custom domains | 1071 | pub const MICROS_PER_DOMAIN_MONTH: i64 = 100_000; |
| Deploy scripts live in the repository | 1072 | } |
| 1073 | ||
| Every sandbox is metered by the second | 1074 | /// `record_sandbox`: how long one sandbox ran for a workspace, reported by |
| Prices are what g1t pays plus 20%, from the first second | 1075 | /// the runner when it stops. Every sandbox g1t starts for a workspace |
| 1076 | /// (agents, reviews, checks, the merge queue, workflow jobs) is metered by | |
| 1077 | /// the second, from the first: recorded once per `reference`, with what it | |
| 1078 | /// cost g1t, and charged at the price book's `sandbox_second` price unless | |
| 1079 | /// `FREE_WHILE_BUILDING`. Deploy builds are charged by the Deployments plan | |
| 1080 | /// instead. | |
| Every sandbox is metered by the second | 1081 | /// Returns `Outcome<bool>`: false if that reference was recorded before. |
| 1082 | #[derive(Debug, Serialize, Deserialize)] | |
| 1083 | #[serde(rename_all = "camelCase")] | |
| 1084 | pub struct RecordSandboxArgs { | |
| 1085 | pub workspace: String, | |
| 1086 | pub seconds: u32, | |
| 1087 | /// What ran, e.g. `Checks on acme/api#12`. | |
| 1088 | pub description: String, | |
| 1089 | /// `namespace/name`. | |
| 1090 | pub repo: Option<String>, | |
| 1091 | /// Unique to the run. | |
| 1092 | pub reference: String, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1093 | /// What ran: `agent`, `check`, `workflow` or `queue`. Decides whether |
| 1094 | /// g1t's open-source pool may pay for it (checks, workflows and the | |
| 1095 | /// merge queue on public repositories). Absent: not the pool. | |
| 1096 | #[serde(default)] | |
| 1097 | pub kind: Option<ComputeKind>, | |
| 1098 | /// The vCPU-seconds the sandbox used, when it can tell. With it, the | |
| 1099 | /// run is priced on its own CPU (`sandbox_base_second` per second plus | |
| 1100 | /// `sandbox_cpu_second` per vCPU-second); without it, at the average | |
| 1101 | /// (`sandbox_second`). | |
| 1102 | #[serde(default, alias = "cpu_seconds")] | |
| 1103 | pub cpu_seconds: Option<f64>, | |
| 1104 | /// The reservation the work started under, settled with this cost. | |
| 1105 | #[serde(default, alias = "reservation_id")] | |
| 1106 | pub reservation_id: Option<String>, | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 1107 | /// It ran on one of the workspace's self-hosted runners: recorded as |
| 1108 | /// self-hosted time, for the minutes, at $0. | |
| 1109 | #[serde(default, alias = "self_hosted")] | |
| 1110 | pub self_hosted: bool, | |
| 1111 | /// The machine it ran on, by label (`g1t-4core`); absent, the standard | |
| 1112 | /// one. A larger machine's memory and disk cost more each second. | |
| 1113 | #[serde(default)] | |
| 1114 | pub instance: Option<String>, | |
| Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted. | 1115 | /// The agent whose work this was, by handle (`g1t` for g1t's own runs |
| 1116 | /// on a repository), so an agent's sandbox time is attributed with | |
| 1117 | /// the rest of its work. Absent for checks, workflows and builds. | |
| 1118 | #[serde(default)] | |
| 1119 | pub agent: Option<String>, | |
| 1120 | /// Who asked for the work, by username. | |
| 1121 | #[serde(default, alias = "asked_by")] | |
| 1122 | pub asked_by: Option<String>, | |
| Every sandbox is metered by the second | 1123 | } |
| 1124 | ||
| Usage limits: unpaid usage can only go so far | 1125 | /// How much a workspace has earned g1t's trust with money, which sets how |
| 1126 | /// far its unpaid usage can go before its work stops. | |
| 1127 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 1128 | #[serde(rename_all = "snake_case")] | |
| 1129 | pub enum Trust { | |
| 1130 | /// No live payment yet: only a little past the free allowances. | |
| 1131 | New, | |
| 1132 | /// Has paid g1t real money: the ceiling grows with what it has paid. | |
| 1133 | Paid, | |
| Two limits, real invoices, trust that grows by itself, sales signals | 1134 | /// Has paid steadily for months, with nothing disputed or declined: |
| 1135 | /// the ceiling follows its monthly spend, up to $10,000, by itself. | |
| 1136 | Established, | |
| Usage limits: unpaid usage can only go so far | 1137 | /// A ceiling g1t set by hand, after talking to the workspace. |
| 1138 | Reviewed, | |
| 1139 | /// g1t's own workspaces: no ceiling. | |
| 1140 | Internal, | |
| 1141 | } | |
| 1142 | ||
| 1143 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 1144 | #[serde(rename_all = "snake_case")] | |
| 1145 | pub enum LimitState { | |
| 1146 | Ok, | |
| 1147 | /// Past 80% of the ceiling. | |
| 1148 | Warning, | |
| 1149 | /// At or past it: no new sandboxes, builds or app requests. | |
| 1150 | Stopped, | |
| 1151 | } | |
| 1152 | ||
| 1153 | /// How far a workspace's unpaid usage has gone this month, and where its | |
| 1154 | /// work stops: like Fly's or Cloudflare's limits for new accounts, so no | |
| 1155 | /// one runs up costs g1t cannot collect. Usage counts at what it cost g1t | |
| 1156 | /// or what it is charged, whichever is more, so it counts while g1t is | |
| 1157 | /// free too. | |
| 1158 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 1159 | #[serde(rename_all = "camelCase")] | |
| 1160 | pub struct Limit { | |
| 1161 | pub workspace: String, | |
| Billing accounts, terms and enterprises; g1t is no longer free | 1162 | /// The account that pays, whose usage and payments the limit counts: |
| 1163 | /// the workspace's own, or its enterprise's. | |
| 1164 | #[serde(default)] | |
| 1165 | pub account: String, | |
| 1166 | #[serde(default)] | |
| 1167 | pub account_name: String, | |
| Usage limits: unpaid usage can only go so far | 1168 | pub trust: Trust, |
| 1169 | /// Usage this month (UTC) less what was paid this month. | |
| 1170 | pub exposure_micros: i64, | |
| 1171 | /// Where work stops: the lower of g1t's ceiling and the owner's own | |
| 1172 | /// spend limit. None for g1t's own workspaces. | |
| 1173 | pub ceiling_micros: Option<i64>, | |
| 1174 | /// The ceiling g1t sets from `trust`. | |
| 1175 | pub trust_ceiling_micros: Option<i64>, | |
| 1176 | /// The owner's own monthly limit, if they set one. | |
| 1177 | pub spend_limit_micros: Option<i64>, | |
| 1178 | pub state: LimitState, | |
| 1179 | /// What to tell people when work is stopped or close to it. | |
| 1180 | pub message: Option<String>, | |
| Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted. | 1181 | /// Charged this month, which the spend limit is measured against: the |
| 1182 | /// month's usage at price, less the account's discount, what the | |
| 1183 | /// plan's included usage, the trial or a pool paid, and what credit | |
| 1184 | /// paid; usage metered through the month and charged when it closes | |
| 1185 | /// counted on the same terms. The one figure the Billing page's plan | |
| 1186 | /// card, Spend's "Charged" and the top bar show (`UsageTotals::charged_micros` | |
| 1187 | /// over the month is the same number). | |
| Two limits, real invoices, trust that grows by itself, sales signals | 1188 | #[serde(default)] |
| 1189 | pub spent_micros: i64, | |
| 1190 | /// True while the owners have not chosen a spend limit of their own, so | |
| 1191 | /// the automatic one applies: $200, or twice last month's spend. | |
| 1192 | #[serde(default)] | |
| 1193 | pub default_spend_limit: bool, | |
| 1194 | /// The most the owners may set their own limit to: g1t's ceiling. To | |
| 1195 | /// go past it, they contact g1t. | |
| 1196 | #[serde(default)] | |
| 1197 | pub available_micros: Option<i64>, | |
| 1198 | /// How the ceiling grows from here, in a sentence. | |
| 1199 | #[serde(default)] | |
| 1200 | pub growth: Option<String>, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1201 | /// Money paid in advance and not used yet. It raises what can be used |
| 1202 | /// before work stops by the same amount, at once. | |
| 1203 | #[serde(default)] | |
| 1204 | pub prepaid_micros: i64, | |
| 1205 | /// The highest ceiling the workspace has ever had. Owners may set their | |
| 1206 | /// spend limit anywhere up to it (plus what is prepaid) without asking. | |
| 1207 | #[serde(default)] | |
| 1208 | pub max_ceiling_micros: Option<i64>, | |
| 1209 | /// The most the owners may raise the limit to themselves, once, with | |
| 1210 | /// `raise_once`: twice the highest ceiling. None once it is used. | |
| 1211 | #[serde(default)] | |
| 1212 | pub raise_once_micros: Option<i64>, | |
| 1213 | /// When the one-time raise was used, RFC 3339. | |
| 1214 | #[serde(default)] | |
| 1215 | pub raised_at: Option<String>, | |
| 1216 | /// True in a paid workspace's first billing cycle, when the ceiling is | |
| 1217 | /// the starting one (`LIMIT_PAID_START_MICROS`). | |
| 1218 | #[serde(default)] | |
| 1219 | pub first_month: bool, | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 1220 | /// The budget's alerts, in percent of the spend limit: some of 50, 75, |
| 1221 | /// 90 and 100. Each is emailed to the owners once a month. | |
| 1222 | #[serde(default)] | |
| 1223 | pub alert_levels: Vec<u32>, | |
| 1224 | /// Whether usage pauses at the spend limit (the default). Off, the | |
| 1225 | /// limit only alerts; g1t's own ceiling still applies. | |
| 1226 | #[serde(default = "yes")] | |
| 1227 | pub pause_at_limit: bool, | |
| 1228 | /// An HTTPS address told of each budget alert with a JSON POST. | |
| 1229 | #[serde(default)] | |
| 1230 | pub budget_webhook: Option<String>, | |
| Usage limits: unpaid usage can only go so far | 1231 | } |
| 1232 | ||
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 1233 | fn yes() -> bool { |
| 1234 | true | |
| 1235 | } | |
| 1236 | ||
| Usage limits: unpaid usage can only go so far | 1237 | /// `limit`: a workspace's limit, for its members. Returns `Outcome<Limit>`. |
| 1238 | #[derive(Debug, Serialize, Deserialize)] | |
| 1239 | pub struct LimitArgs { | |
| 1240 | pub workspace: String, | |
| 1241 | pub viewer: Viewer, | |
| 1242 | } | |
| 1243 | ||
| 1244 | /// `check_limit`: the same, for the services that enforce it. Returns | |
| 1245 | /// `Outcome<Limit>`. | |
| 1246 | #[derive(Debug, Serialize, Deserialize)] | |
| 1247 | pub struct CheckLimitArgs { | |
| 1248 | pub workspace: String, | |
| 1249 | } | |
| 1250 | ||
| Prices keep themselves current with what g1t pays | 1251 | /// `note_pending`: usage this month that will be charged later, such as |
| 1252 | /// app traffic past a plan, so the workspace's limit counts it now. Each | |
| 1253 | /// report replaces the last for that workspace, source and month. Called | |
| 1254 | /// by the service that meters it. Returns `bool`. | |
| 1255 | #[derive(Debug, Serialize, Deserialize)] | |
| 1256 | #[serde(rename_all = "camelCase")] | |
| 1257 | pub struct NotePendingArgs { | |
| 1258 | pub workspace: String, | |
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 1259 | /// `deployments`, `security` (scans), `context` (search embeddings), |
| 1260 | /// `storage` or `cache` (actions/cache, plan only). Billing charges | |
| 1261 | /// `security`, `context`, `storage` and `cache` itself once the month | |
| 1262 | /// is over; `deployments` charges its own. | |
| Prices keep themselves current with what g1t pays | 1263 | pub source: String, |
| 1264 | /// What it cost g1t so far this month, before the margin. | |
| 1265 | pub cost_micros: i64, | |
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 1266 | /// How much of it, for the Billing page: `1.2 million requests and |
| 1267 | /// 3.4 million CPU ms`, `2 custom domains`. | |
| 1268 | #[serde(default)] | |
| 1269 | pub detail: Option<String>, | |
| Prices keep themselves current with what g1t pays | 1270 | } |
| 1271 | ||
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 1272 | /// `usage_meters`: this month's usage for a workspace, one line per kind |
| 1273 | /// of meter, at what it is charged (cost plus the margin, on the account's | |
| 1274 | /// terms) before the plan's included usage, the trial or g1t's pools paid | |
| 1275 | /// for any of it. Members only. Returns `Outcome<Vec<MeterUsage>>`. | |
| 1276 | #[derive(Debug, Serialize, Deserialize)] | |
| 1277 | pub struct UsageMetersArgs { | |
| 1278 | pub workspace: String, | |
| 1279 | pub viewer: Viewer, | |
| 1280 | } | |
| 1281 | ||
| 1282 | /// One kind of meter's usage this month. | |
| 1283 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 1284 | #[serde(rename_all = "camelCase")] | |
| 1285 | pub struct MeterUsage { | |
| 1286 | /// `agents` (agent runs, models and sandboxes for checks, workflows | |
| 1287 | /// and the merge queue), `builds`, `requests` (app requests and CPU), | |
| 1288 | /// `domains`, `git_storage` (git operations and private storage) or | |
| 1289 | /// `search_scans` (search embeddings and security scans). | |
| 1290 | pub key: String, | |
| 1291 | pub label: String, | |
| 1292 | /// At price, before what paid for it. | |
| 1293 | pub micros: i64, | |
| 1294 | /// How much, when it is known: `12 runs`, `41 build minutes`. | |
| 1295 | #[serde(default)] | |
| 1296 | pub quantity: Option<String>, | |
| 1297 | } | |
| 1298 | ||
| Usage limits: unpaid usage can only go so far | 1299 | /// `set_spend_limit`: the owner's own monthly ceiling, under g1t's; None |
| 1300 | /// removes it. Owners only. Returns `Outcome<Limit>`. | |
| 1301 | #[derive(Debug, Serialize, Deserialize)] | |
| 1302 | #[serde(rename_all = "camelCase")] | |
| 1303 | pub struct SetSpendLimitArgs { | |
| 1304 | pub actor: User, | |
| 1305 | pub workspace: String, | |
| Two limits, real invoices, trust that grows by itself, sales signals | 1306 | /// A monthly limit, at most what is available; None goes back to the |
| 1307 | /// default. | |
| Usage limits: unpaid usage can only go so far | 1308 | pub spend_limit_micros: Option<i64>, |
| Two limits, real invoices, trust that grows by itself, sales signals | 1309 | /// Use everything available, with no limit of their own. |
| 1310 | #[serde(default)] | |
| 1311 | pub use_full_limit: bool, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1312 | /// Use the one-time raise: up to twice the highest ceiling the |
| 1313 | /// workspace has had, without asking. Once per workspace. | |
| 1314 | #[serde(default, alias = "raiseOnce")] | |
| 1315 | pub raise_once: bool, | |
| Usage limits: unpaid usage can only go so far | 1316 | } |
| 1317 | ||
| Prices keep themselves current with what g1t pays | 1318 | /// One metered unit: what it costs g1t, and what it is sold at. The price |
| 1319 | /// is always `cost × (100 + markup) / 100`, so it follows the cost. | |
| 1320 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 1321 | #[serde(rename_all = "camelCase")] | |
| 1322 | pub struct Price { | |
| 1323 | /// `sandbox_second`, `build_second`, `app_requests`, `app_cpu`, `app_month`. | |
| 1324 | pub meter: String, | |
| 1325 | pub title: String, | |
| 1326 | pub unit: String, | |
| 1327 | /// Millionths of a dollar per unit; may have a fraction. | |
| 1328 | pub cost_micros: f64, | |
| 1329 | pub markup_percent: u32, | |
| 1330 | pub price_micros: f64, | |
| 1331 | /// `list`: Cloudflare's published price. `cloudflare`: what Cloudflare | |
| 1332 | /// actually billed g1t, measured. | |
| 1333 | pub source: String, | |
| 1334 | /// When it was last checked against Cloudflare's bill. | |
| 1335 | pub checked_at: Option<String>, | |
| 1336 | pub updated_at: String, | |
| 1337 | } | |
| 1338 | ||
| 1339 | impl Price { | |
| 1340 | pub fn price_for(cost_micros: f64, markup_percent: u32) -> f64 { | |
| 1341 | cost_micros * f64::from(100 + markup_percent) / 100.0 | |
| 1342 | } | |
| 1343 | } | |
| 1344 | ||
| 1345 | /// A cost that moved. | |
| 1346 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 1347 | #[serde(rename_all = "camelCase")] | |
| 1348 | pub struct PriceChange { | |
| 1349 | pub meter: String, | |
| 1350 | pub old_cost_micros: f64, | |
| 1351 | pub new_cost_micros: f64, | |
| 1352 | pub markup_percent: u32, | |
| Prices are what g1t pays plus 20%, from the first second | 1353 | /// The markup before, when the change was to the markup rather than |
| 1354 | /// to the cost. Absent when the markup stayed `markup_percent`. | |
| 1355 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 1356 | pub old_markup_percent: Option<u32>, | |
| Prices keep themselves current with what g1t pays | 1357 | pub reason: String, |
| 1358 | pub created_at: String, | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 1359 | /// When a change still to come takes effect: a rise is announced |
| 1360 | /// before it is charged. Absent for changes already made. | |
| 1361 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 1362 | pub effective_at: Option<String>, | |
| Prices keep themselves current with what g1t pays | 1363 | } |
| 1364 | ||
| 1365 | /// `prices`: every metered price and the recent changes. Public. Returns | |
| 1366 | /// `PriceBook`. | |
| 1367 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 1368 | #[serde(rename_all = "camelCase")] | |
| 1369 | pub struct PriceBook { | |
| 1370 | pub prices: Vec<Price>, | |
| 1371 | pub changes: Vec<PriceChange>, | |
| 1372 | /// The margin on model usage, which is charged at what AI Gateway | |
| 1373 | /// priced each request at. | |
| 1374 | pub model_margin_percent: u32, | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1375 | /// Every plan, as it is sold now. |
| 1376 | #[serde(default)] | |
| 1377 | pub plans: Vec<Plan>, | |
| 1378 | /// What is free, and what pays for it. | |
| 1379 | #[serde(default)] | |
| 1380 | pub free: Option<FreeTier>, | |
| Prices keep themselves current with what g1t pays | 1381 | } |
| 1382 | ||
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1383 | /// What g1t gives without a plan, each with what pays for it: a capped |
| 1384 | /// budget, never an open-ended allowance. | |
| 1385 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 1386 | #[serde(rename_all = "camelCase")] | |
| 1387 | pub struct FreeTier { | |
| 1388 | /// Each new workspace's trial credit, once. | |
| 1389 | pub trial_workspace_micros: i64, | |
| 1390 | /// Trial grants each month, in all; new trials wait when it is spent. | |
| 1391 | pub trial_monthly_pool_micros: i64, | |
| 1392 | /// g1t's open-source pool each month, and any one repository's share. | |
| 1393 | pub oss_pool_micros: i64, | |
| 1394 | pub oss_repo_micros: i64, | |
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 1395 | /// Private repository storage that is free for every workspace. Past |
| 1396 | /// it, the plan pays at cost plus the margin; a free workspace's pushes | |
| 1397 | /// to private repositories stop instead. | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1398 | pub free_private_storage_bytes: i64, |
| Audit logs are kept by plan: a week on free, 90 days on the plan, and what staff set for an account in sudo | 1399 | /// Days of audit log a free workspace keeps. |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1400 | pub audit_retention_days: u32, |
| Audit logs are kept by plan: a week on free, 90 days on the plan, and what staff set for an account in sudo | 1401 | /// Days of audit log the g1t plan keeps, and g1t's own and enterprise |
| 1402 | /// workspaces. Longer is by arrangement, set per account in sudo. | |
| 1403 | #[serde(default)] | |
| 1404 | pub plan_audit_retention_days: u32, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1405 | /// The smallest amount a card is charged when a month closes; less |
| 1406 | /// carries over. Charges at a limit always go through. | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1407 | pub min_charge_micros: i64, |
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 1408 | /// Git operations (clones, fetches and pushes through g1t) that are |
| 1409 | /// free for every workspace each month. Past it, the plan pays at cost | |
| 1410 | /// plus the margin and is never slowed; a free workspace is slowed | |
| 1411 | /// down, never charged. | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1412 | #[serde(default)] |
| 1413 | pub git_operations_included: u64, | |
| 1414 | /// A new paid workspace's ceiling in its first month. | |
| 1415 | #[serde(default)] | |
| 1416 | pub paid_start_ceiling_micros: i64, | |
| 1417 | /// The most a one-click goodwill credit can cost g1t. | |
| 1418 | #[serde(default)] | |
| 1419 | pub overage_forgive_cost_micros: i64, | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1420 | } |
| 1421 | ||
| Billing accounts, terms and enterprises; g1t is no longer free | 1422 | /// Who pays: a billing account. Every workspace has one; by default its |
| 1423 | /// own. An enterprise account pays for several workspaces at once, as | |
| 1424 | /// GitHub Enterprise does: one bill, one limit, one set of terms. | |
| 1425 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 1426 | #[serde(rename_all = "camelCase")] | |
| 1427 | pub struct BillingAccount { | |
| 1428 | /// `ws_<slug>` for a workspace's own account; `ent_…` for an enterprise. | |
| 1429 | pub id: String, | |
| 1430 | pub kind: AccountKind, | |
| 1431 | pub name: String, | |
| 1432 | pub terms: Terms, | |
| 1433 | /// The workspaces it pays for. | |
| 1434 | pub workspaces: Vec<String>, | |
| Stripe webhooks, enterprise invoices, and sudo for both | 1435 | /// Where an enterprise's invoices go. |
| 1436 | #[serde(default)] | |
| 1437 | pub billing_email: Option<String>, | |
| 1438 | /// An enterprise's invoices, newest first. Empty for a workspace's own. | |
| 1439 | #[serde(default)] | |
| 1440 | pub invoices: Vec<EnterpriseInvoice>, | |
| Billing accounts, terms and enterprises; g1t is no longer free | 1441 | pub created_at: String, |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1442 | /// What g1t staff set for the account beyond its terms. |
| 1443 | #[serde(default)] | |
| 1444 | pub allowances: Allowances, | |
| 1445 | } | |
| 1446 | ||
| 1447 | /// Set per account by g1t staff in sudo, on top of its terms. | |
| 1448 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 1449 | #[serde(rename_all = "camelCase")] | |
| 1450 | pub struct Allowances { | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1451 | /// The g1t plan without paying for its monthly price, such as for a |
| 1452 | /// partner. Usage is charged as usual. Comped accounts have it anyway. | |
| 1453 | #[serde(default, alias = "team")] | |
| 1454 | pub plan: bool, | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1455 | /// Each of the account's public repositories' monthly cap on g1t's |
| 1456 | /// open-source pool, in place of `OSS_REPO_MICROS`. None: the default. | |
| 1457 | #[serde(default)] | |
| 1458 | pub oss_repo_micros: Option<i64>, | |
| 1459 | /// The trial credit each of its workspaces gets, in place of | |
| 1460 | /// `TRIAL_WORKSPACE_MICROS`, outside the monthly pool. None: the default. | |
| 1461 | #[serde(default)] | |
| 1462 | pub trial_micros: Option<i64>, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1463 | /// Agents at once, in place of the plan's (2 in the first month or on |
| 1464 | /// the trial, then 10). None: the default. | |
| 1465 | #[serde(default)] | |
| 1466 | pub max_concurrent_agents: Option<u32>, | |
| 1467 | /// One run's spend cap, in place of `RUN_CAP_MICROS` and the owners' | |
| 1468 | /// own. None: theirs, or the default. | |
| 1469 | #[serde(default)] | |
| 1470 | pub run_cap_micros: Option<i64>, | |
| 1471 | /// What the agents on one issue may spend in all, in place of | |
| 1472 | /// `ISSUE_CAP_MICROS` and the owners' own. None: theirs, or the default. | |
| 1473 | #[serde(default)] | |
| 1474 | pub issue_cap_micros: Option<i64>, | |
| Audit logs are kept by plan: a week on free, 90 days on the plan, and what staff set for an account in sudo | 1475 | /// Days of audit log its workspaces keep, in place of the plan's (7 |
| 1476 | /// free, 90 on the plan), longer or shorter. None: the plan's. | |
| 1477 | #[serde(default)] | |
| 1478 | pub audit_retention_days: Option<u32>, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1479 | /// A hold g1t staff put on new compute, with why. None: no hold. |
| 1480 | #[serde(default)] | |
| 1481 | pub hold: Option<String>, | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1482 | } |
| 1483 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1484 | /// `admin_set_allowances`: the plan on or off without charge, overrides of |
| 1485 | /// the plan's caps, a hold, and the account's share of g1t's pools. | |
| 1486 | /// Recorded with who and why. Returns `Outcome<BillingAccount>`. | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1487 | #[derive(Debug, Serialize, Deserialize)] |
| 1488 | pub struct AdminSetAllowancesArgs { | |
| 1489 | pub id: String, | |
| 1490 | pub allowances: Allowances, | |
| 1491 | pub note: String, | |
| 1492 | pub by: String, | |
| 1493 | } | |
| 1494 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1495 | // --- Entitlements, and compute started under a reservation ----------------- |
| 1496 | // | |
| 1497 | // Every service that starts compute (sandboxes for agents, checks, | |
| 1498 | // workflows and the merge queue; builds; models; semantic search) asks | |
| 1499 | // billing first: | |
| 1500 | // | |
| 1501 | // 1. `entitlements { workspace }` says what the workspace may do at all: | |
| 1502 | // its plan, whether it may start compute, its caps, and whether compute | |
| 1503 | // is paused. | |
| 1504 | // 2. `reserve { workspace, repo, public, kind, estimate_micros }` holds the | |
| 1505 | // work's estimated cost against what may pay for it, so that starts at | |
| 1506 | // the same moment cannot overshoot the ceiling together. It answers who | |
| 1507 | // pays first, or refuses with a stable code and a message for the owner. | |
| 1508 | // 3. `settle { reservation_id, actual_micros }` releases the hold once the | |
| 1509 | // work is done. The charge itself goes on the ledger the usual way | |
| 1510 | // (`finish_run`, `record_sandbox`, `charge_feature`, `note_pending`). | |
| 1511 | // | |
| 1512 | // A reservation never settled expires after `RESERVATION_HOURS`. | |
| 1513 | ||
| 1514 | /// A reservation that is never settled stops holding after this long. | |
| 1515 | pub const RESERVATION_HOURS: u64 = 3; | |
| 1516 | /// What a ceiling reads as when there is none (g1t's own workspaces): a | |
| 1517 | /// billion dollars, which JavaScript holds exactly. | |
| 1518 | pub const UNLIMITED_MICROS: i64 = 1_000_000_000_000_000; | |
| 1519 | ||
| 1520 | /// What a workspace pays g1t on, as far as compute is concerned. | |
| 1521 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 1522 | #[serde(rename_all = "snake_case")] | |
| 1523 | pub enum PlanKind { | |
| 1524 | /// No plan: the forge is free; compute only from a trial or g1t's | |
| 1525 | /// open-source pool, after a card check. | |
| 1526 | Free, | |
| 1527 | /// The g1t plan, paid for (or given by g1t staff without its price). | |
| 1528 | Paid, | |
| 1529 | /// g1t's own workspaces and Flagon's (comped terms): the plan without | |
| 1530 | /// being charged. Usage is still recorded at what it cost. | |
| 1531 | Internal, | |
| 1532 | /// Paid for by an enterprise account, invoiced. | |
| 1533 | Enterprise, | |
| 1534 | } | |
| 1535 | ||
| 1536 | impl PlanKind { | |
| 1537 | pub fn as_str(self) -> &'static str { | |
| 1538 | match self { | |
| 1539 | PlanKind::Free => "free", | |
| 1540 | PlanKind::Paid => "paid", | |
| 1541 | PlanKind::Internal => "internal", | |
| 1542 | PlanKind::Enterprise => "enterprise", | |
| 1543 | } | |
| 1544 | } | |
| 1545 | ||
| 1546 | /// Whether usage past what is included may be charged (on demand). | |
| 1547 | pub fn on_demand(self) -> bool { | |
| 1548 | !matches!(self, PlanKind::Free) | |
| 1549 | } | |
| 1550 | } | |
| 1551 | ||
| 1552 | /// What compute is for. | |
| 1553 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 1554 | #[serde(rename_all = "snake_case")] | |
| 1555 | pub enum ComputeKind { | |
| 1556 | /// An agent's run: its sandbox and its model. | |
| 1557 | Agent, | |
| 1558 | /// Checks on a pull request. | |
| 1559 | Check, | |
| 1560 | /// A workflow job. | |
| 1561 | Workflow, | |
| 1562 | /// The merge queue's checks. | |
| 1563 | Queue, | |
| 1564 | /// A deployment's build. | |
| 1565 | Deploy, | |
| 1566 | /// Semantic search: embeddings in the context hub. | |
| 1567 | Embedding, | |
| 1568 | } | |
| 1569 | ||
| 1570 | impl ComputeKind { | |
| 1571 | pub fn as_str(self) -> &'static str { | |
| 1572 | match self { | |
| 1573 | ComputeKind::Agent => "agent", | |
| 1574 | ComputeKind::Check => "check", | |
| 1575 | ComputeKind::Workflow => "workflow", | |
| 1576 | ComputeKind::Queue => "queue", | |
| 1577 | ComputeKind::Deploy => "deploy", | |
| 1578 | ComputeKind::Embedding => "embedding", | |
| 1579 | } | |
| 1580 | } | |
| 1581 | ||
| 1582 | /// Whether g1t's open-source pool may pay for it on a public | |
| 1583 | /// repository: checks, workflows and the merge queue only. | |
| 1584 | pub fn open_source_pool(self) -> bool { | |
| 1585 | matches!(self, ComputeKind::Check | ComputeKind::Workflow | ComputeKind::Queue) | |
| 1586 | } | |
| 1587 | } | |
| 1588 | ||
| 1589 | /// Who pays first for reserved work. What the first source cannot cover | |
| 1590 | /// falls to the next, in this order. | |
| 1591 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 1592 | #[serde(rename_all = "snake_case")] | |
| 1593 | pub enum PaidBy { | |
| 1594 | /// The plan's included usage this month. | |
| 1595 | Credit, | |
| 1596 | /// The workspace's one-time trial credit. | |
| 1597 | Trial, | |
| 1598 | /// g1t's open-source pool. | |
| 1599 | Oss, | |
| 1600 | /// Charged to the workspace, at cost plus the margin. | |
| 1601 | OnDemand, | |
| 1602 | } | |
| 1603 | ||
| 1604 | /// `entitlements`: what a workspace may do now, for the services that | |
| 1605 | /// start compute and the pages that show it. Takes `EntitlementsArgs`; | |
| 1606 | /// returns `Entitlements`. No viewer: callers decide who sees it. | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1607 | #[derive(Debug, Serialize, Deserialize)] |
| 1608 | pub struct EntitlementsArgs { | |
| 1609 | pub workspace: String, | |
| 1610 | } | |
| 1611 | ||
| Audit logs are kept by plan: a week on free, 90 days on the plan, and what staff set for an account in sudo | 1612 | /// `audit_retention`: how many days of audit log each workspace keeps, for |
| 1613 | /// the events service's daily purge. Takes `AuditRetentionArgs`; returns | |
| 1614 | /// `Vec<AuditRetention>`, one for each workspace asked about. | |
| 1615 | #[derive(Debug, Serialize, Deserialize)] | |
| 1616 | pub struct AuditRetentionArgs { | |
| 1617 | pub workspaces: Vec<String>, | |
| 1618 | } | |
| 1619 | ||
| 1620 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 1621 | pub struct AuditRetention { | |
| 1622 | pub workspace: String, | |
| 1623 | pub days: u32, | |
| 1624 | } | |
| 1625 | ||
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1626 | #[derive(Clone, Debug, Serialize, Deserialize)] |
| 1627 | #[serde(rename_all = "camelCase")] | |
| 1628 | pub struct Entitlements { | |
| 1629 | pub workspace: String, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1630 | pub plan: PlanKind, |
| 1631 | /// May start sandboxes, models, deployments and semantic search at all: | |
| 1632 | /// paid, internal and enterprise workspaces, or a free one with trial | |
| 1633 | /// credit left. A free workspace may still use the open-source pool | |
| 1634 | /// for checks, workflows and the merge queue on public repositories | |
| 1635 | /// after a card check; `reserve` decides that per start. | |
| 1636 | pub compute: bool, | |
| 1637 | /// The one-time trial credit left; 0 if none was granted or it is used. | |
| 1638 | pub trial_micros_left: i64, | |
| 1639 | /// A card check has been done. The trial and the open-source pool need | |
| 1640 | /// it. | |
| 1641 | pub trial_verified: bool, | |
| 1642 | /// A paid workspace still in its first billing cycle. | |
| 1643 | pub first_month: bool, | |
| 1644 | /// Agents at once: 2 in the first month or on the trial, 10 after; | |
| 1645 | /// staff can override it. | |
| 1646 | pub max_concurrent_agents: u32, | |
| 1647 | /// The longest one run may take: 60 minutes in the first month or on | |
| 1648 | /// the trial; otherwise the guardrails' own caps (`MAX_MINUTES`). | |
| 1649 | pub max_run_minutes: u32, | |
| 1650 | /// One run's spend cap (`RUN_CAP_MICROS`, $2 by default); staff can | |
| 1651 | /// override it. | |
| 1652 | pub run_cap_micros: i64, | |
| 1653 | /// What agents may spend on one issue in all (`ISSUE_CAP_MICROS`, $10 | |
| 1654 | /// by default); the owners can set it (`set_caps`), and staff override. | |
| 1655 | pub issue_cap_micros: i64, | |
| 1656 | /// Where on-demand work stops: g1t's ceiling on usage not yet paid | |
| 1657 | /// for. `UNLIMITED_MICROS` for g1t's own workspaces; 0 for a free one, | |
| 1658 | /// which has no on-demand usage. | |
| 1659 | pub ceiling_micros: i64, | |
| 1660 | /// Usage not yet paid for this month, with prepayment taken off. | |
| 1661 | pub exposure_micros: i64, | |
| 1662 | /// Why new compute is paused, for the owner: the limit is reached, a | |
| 1663 | /// spend spike is waiting for an owner to confirm it, or g1t staff put | |
| 1664 | /// a hold on it. None when it is not. | |
| 1665 | pub paused: Option<String>, | |
| 1666 | // What the workspace's plan gives it, for its pages. | |
| 1667 | /// What open reservations hold now. | |
| 1668 | #[serde(default)] | |
| 1669 | pub held_micros: i64, | |
| 1670 | /// Paid in advance and not used yet. | |
| 1671 | #[serde(default)] | |
| 1672 | pub prepaid_micros: i64, | |
| 1673 | /// The plan's included usage each month, and what of it is used. | |
| 1674 | #[serde(default)] | |
| 1675 | pub included_micros: i64, | |
| 1676 | #[serde(default)] | |
| 1677 | pub included_used_micros: i64, | |
| Audit logs are kept by plan: a week on free, 90 days on the plan, and what staff set for an account in sudo | 1678 | /// How far back the audit log can be read and exported, and what is |
| 1679 | /// kept: the plan's days, or what g1t staff set for the account. | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1680 | pub audit_retention_days: u32, |
| Audit logs are kept by plan: a week on free, 90 days on the plan, and what staff set for an account in sudo | 1681 | /// Whether `audit_retention_days` is what staff set for the account |
| 1682 | /// rather than the plan's. | |
| 1683 | #[serde(default)] | |
| 1684 | pub audit_retention_custom: bool, | |
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 1685 | /// Private repository storage that is free for every workspace: past |
| 1686 | /// it, the plan pays for it and a free workspace's pushes stop. | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1687 | pub free_private_storage_bytes: i64, |
| 1688 | /// The last daily measure of the workspace's private repositories. | |
| 1689 | pub private_storage_bytes: i64, | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 1690 | /// On a paid plan (not Free): storage past the free amounts below is |
| 1691 | /// charged, so nothing is refused for it. | |
| 1692 | #[serde(default)] | |
| 1693 | pub has_plan: bool, | |
| 1694 | /// Package storage free for every workspace, public and private: past | |
| 1695 | /// it, the plan pays for it and a free workspace's pushes are refused. | |
| 1696 | #[serde(default)] | |
| 1697 | pub package_public_free_bytes: i64, | |
| 1698 | #[serde(default)] | |
| 1699 | pub package_private_free_bytes: i64, | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1700 | /// What g1t's open-source pool paid for the workspace this month. |
| 1701 | pub oss_paid_micros: i64, | |
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 1702 | /// Deploy build time this month, every second of it metered. |
| 1703 | #[serde(default)] | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1704 | pub build_seconds_used: u32, |
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 1705 | /// Git operations this month, and how many are free for every |
| 1706 | /// workspace (past it: metered on the plan, slowed when free). | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1707 | #[serde(default)] |
| 1708 | pub git_operations: u64, | |
| 1709 | #[serde(default)] | |
| 1710 | pub git_operations_included: u64, | |
| 1711 | /// The smallest amount a card is charged when a month closes. | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 1712 | pub min_charge_micros: i64, |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1713 | /// A spend spike waiting for an owner, or decided. |
| 1714 | #[serde(default)] | |
| 1715 | pub spike: Option<Spike>, | |
| 1716 | /// Where usage stands against what is included and the limits, from 50%. | |
| 1717 | #[serde(default)] | |
| 1718 | pub alerts: Vec<UsageAlert>, | |
| 1719 | } | |
| 1720 | ||
| 1721 | /// One level reached: 50, 75, 90 or 100 percent of something. | |
| 1722 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 1723 | #[serde(rename_all = "camelCase")] | |
| 1724 | pub struct UsageAlert { | |
| 1725 | /// `included` (the plan's included usage), `spend_limit` (the owners' | |
| 1726 | /// own limit) or `ceiling` (g1t's, on usage not yet paid for). | |
| 1727 | pub meter: String, | |
| 1728 | pub level: u32, | |
| 1729 | pub used_micros: i64, | |
| 1730 | pub limit_micros: i64, | |
| 1731 | pub message: String, | |
| Billing accounts, terms and enterprises; g1t is no longer free | 1732 | } |
| 1733 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1734 | /// An hour's spend well above the workspace's usual pace: new compute |
| 1735 | /// waits until an owner says to keep going. | |
| 1736 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 1737 | #[serde(rename_all = "camelCase")] | |
| 1738 | pub struct Spike { | |
| 1739 | pub id: String, | |
| 1740 | /// `open` (waiting for an owner), `continued` (an owner said keep | |
| 1741 | /// going) or `stopped` (an owner said stop). | |
| 1742 | pub status: String, | |
| 1743 | /// The hour's spend when it was found, and the usual hour's. | |
| 1744 | pub hour_micros: i64, | |
| 1745 | pub average_micros: i64, | |
| 1746 | pub detected_at: String, | |
| 1747 | #[serde(default)] | |
| 1748 | pub decided_by: Option<String>, | |
| 1749 | #[serde(default)] | |
| 1750 | pub decided_at: Option<String>, | |
| 1751 | /// While continued: until when, unless spend doubles again first. | |
| 1752 | #[serde(default)] | |
| 1753 | pub until: Option<String>, | |
| 1754 | } | |
| 1755 | ||
| 1756 | /// `reserve`: holds an estimate of a start's cost before the work starts. | |
| 1757 | /// Returns `Outcome<Reservation>`, or a failure whose code says why not: | |
| 1758 | /// | |
| 1759 | /// - `paused`: a spend spike waiting for an owner, or a hold. | |
| 1760 | /// - `limit`: the spend limit or g1t's ceiling would be passed. | |
| 1761 | /// - `not_paid`: no plan, and nothing else pays for this kind of work (or | |
| 1762 | /// no card check yet). | |
| 1763 | /// - `trial_used`: the one-time trial is spent. | |
| 1764 | /// - `oss_pool_empty`: the open-source pool, or the repository's share of | |
| 1765 | /// it, is spent this month. | |
| 1766 | /// | |
| 1767 | /// The message says exactly what to do, with the page to do it on (such as | |
| 1768 | /// `/acme/-/billing`). | |
| 1769 | #[derive(Debug, Serialize, Deserialize)] | |
| 1770 | #[serde(rename_all = "camelCase")] | |
| 1771 | pub struct ReserveArgs { | |
| 1772 | pub workspace: String, | |
| 1773 | pub repo: RepoPath, | |
| 1774 | /// Whether the repository is public: the open-source pool pays only for | |
| 1775 | /// public repositories' checks, workflows and merge queue. | |
| 1776 | pub public: bool, | |
| 1777 | pub kind: ComputeKind, | |
| 1778 | /// The most the work is expected to cost g1t, before the margin, in | |
| 1779 | /// millionths of a dollar (billing adds the margin, as it does to every | |
| 1780 | /// charge). For an agent, its model's average plus its sandbox for its | |
| 1781 | /// whole time cap. | |
| 1782 | #[serde(alias = "estimate_micros")] | |
| 1783 | pub estimate_micros: i64, | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 1784 | /// An agent run on g1t's hosted models (not the workspace's own |
| 1785 | /// provider). Unsaid, an agent run is taken to be one. g1t's daily | |
| 1786 | /// spend breaker pauses these when g1t is paying for them. | |
| 1787 | #[serde(default, alias = "hosted_model")] | |
| 1788 | pub hosted_model: Option<bool>, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1789 | } |
| 1790 | ||
| 1791 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 1792 | #[serde(rename_all = "camelCase")] | |
| 1793 | pub struct Reservation { | |
| 1794 | pub id: String, | |
| 1795 | pub paid_by: PaidBy, | |
| 1796 | /// What is held, at cost; less than the estimate when a free | |
| 1797 | /// workspace's last bit of trial credit is all there is. | |
| 1798 | #[serde(default)] | |
| 1799 | pub held_micros: i64, | |
| 1800 | /// RFC 3339: when the hold lapses if never settled. | |
| 1801 | #[serde(default)] | |
| 1802 | pub expires_at: String, | |
| 1803 | } | |
| 1804 | ||
| 1805 | /// `settle`: releases a reservation's hold with what the work cost. The | |
| 1806 | /// charge goes on the ledger the usual way. Safe to repeat. Returns | |
| 1807 | /// `Outcome<bool>`: false if it was settled or had lapsed before. | |
| 1808 | #[derive(Debug, Serialize, Deserialize)] | |
| 1809 | #[serde(rename_all = "camelCase")] | |
| 1810 | pub struct SettleArgs { | |
| 1811 | #[serde(alias = "reservation_id")] | |
| 1812 | pub reservation_id: String, | |
| 1813 | /// What the work cost g1t, before the margin. | |
| 1814 | #[serde(alias = "actual_micros")] | |
| 1815 | pub actual_micros: i64, | |
| 1816 | } | |
| 1817 | ||
| 1818 | // --- Card checks, the plan, prepayment ------------------------------------- | |
| 1819 | ||
| 1820 | /// `card_check`: starts Stripe's page to save and verify a card: a setup | |
| 1821 | /// with 3-D Secure where the card supports it, which the card's bank sees | |
| 1822 | /// as a $0 or $1 authorization that is never charged. The trial and the | |
| 1823 | /// open-source pool need it, and it is the card the plan uses. Owners only. | |
| 1824 | /// Returns `Outcome<Checkout>`; the page's id comes back to `return_url` as | |
| 1825 | /// `session`, for `confirm_card_check`. | |
| 1826 | #[derive(Debug, Serialize, Deserialize)] | |
| 1827 | #[serde(rename_all = "camelCase")] | |
| 1828 | pub struct CardCheckArgs { | |
| 1829 | pub actor: User, | |
| 1830 | pub workspace: String, | |
| 1831 | #[serde(alias = "return_url")] | |
| 1832 | pub return_url: String, | |
| 1833 | } | |
| 1834 | ||
| 1835 | /// `confirm_card_check`: records the check once Stripe says the card was | |
| 1836 | /// verified, and grants the trial if the month's pool has room and the card | |
| 1837 | /// has not had one before. Safe to repeat. Returns `Outcome<Entitlements>`. | |
| 1838 | #[derive(Debug, Serialize, Deserialize)] | |
| 1839 | pub struct ConfirmCardCheckArgs { | |
| 1840 | pub workspace: String, | |
| 1841 | pub viewer: Viewer, | |
| 1842 | pub session: String, | |
| 1843 | } | |
| 1844 | ||
| 1845 | // --- Limits: raising them, and spikes --------------------------------------- | |
| 1846 | ||
| 1847 | /// A request to g1t: a higher limit, or help with usage that went past | |
| 1848 | /// what was meant. | |
| 1849 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 1850 | #[serde(rename_all = "camelCase")] | |
| 1851 | pub struct LimitRequest { | |
| 1852 | pub id: String, | |
| 1853 | pub workspace: String, | |
| 1854 | /// `limit` (raise my limit) or `overage` (spent more than meant to). | |
| 1855 | pub kind: String, | |
| 1856 | /// The limit asked for; for an overage, what they think went wrong. | |
| 1857 | pub amount_micros: i64, | |
| 1858 | pub reason: String, | |
| 1859 | pub expected_monthly_micros: i64, | |
| 1860 | /// `open`, `approved` or `declined`. | |
| 1861 | pub status: String, | |
| 1862 | /// What was approved, which may differ from what was asked. | |
| 1863 | #[serde(default)] | |
| 1864 | pub decided_micros: Option<i64>, | |
| 1865 | #[serde(default)] | |
| 1866 | pub decided_by: Option<String>, | |
| 1867 | /// The answer, as the owner sees it. | |
| 1868 | #[serde(default)] | |
| 1869 | pub answer: Option<String>, | |
| 1870 | pub created_by: String, | |
| 1871 | pub created_at: String, | |
| 1872 | #[serde(default)] | |
| 1873 | pub decided_at: Option<String>, | |
| 1874 | } | |
| 1875 | ||
| 1876 | /// `request_limit`: an owner asks g1t for more, or for help with usage past | |
| 1877 | /// what they meant. Answered within one business day, in the app and by | |
| 1878 | /// email. Owners only. Returns `Outcome<LimitRequest>`. | |
| 1879 | #[derive(Debug, Serialize, Deserialize)] | |
| 1880 | #[serde(rename_all = "camelCase")] | |
| 1881 | pub struct RequestLimitArgs { | |
| 1882 | pub actor: User, | |
| 1883 | pub workspace: String, | |
| 1884 | /// `limit` or `overage`. | |
| 1885 | pub kind: String, | |
| 1886 | #[serde(alias = "amount_micros")] | |
| 1887 | pub amount_micros: i64, | |
| 1888 | pub reason: String, | |
| 1889 | #[serde(default, alias = "expected_monthly_micros")] | |
| 1890 | pub expected_monthly_micros: i64, | |
| 1891 | } | |
| 1892 | ||
| 1893 | /// `limit_requests`: a workspace's requests, newest first. Members only. | |
| 1894 | /// Returns `Outcome<Vec<LimitRequest>>`. | |
| 1895 | #[derive(Debug, Serialize, Deserialize)] | |
| 1896 | pub struct LimitRequestsArgs { | |
| 1897 | pub workspace: String, | |
| 1898 | pub viewer: Viewer, | |
| 1899 | } | |
| 1900 | ||
| 1901 | /// `confirm_spike`: an owner's answer to a spend spike. Keep going lifts the | |
| 1902 | /// pause for 24 hours, or until the hour's spend doubles again; stop keeps | |
| 1903 | /// new compute paused until an owner says to keep going. Owners only. | |
| 1904 | /// Returns `Outcome<Entitlements>`. | |
| 1905 | #[derive(Debug, Serialize, Deserialize)] | |
| 1906 | #[serde(rename_all = "camelCase")] | |
| 1907 | pub struct ConfirmSpikeArgs { | |
| 1908 | pub actor: User, | |
| 1909 | pub workspace: String, | |
| 1910 | #[serde(alias = "keep_going")] | |
| 1911 | pub keep_going: bool, | |
| 1912 | } | |
| 1913 | ||
| 1914 | /// `set_caps`: the owners' own caps on agents: one run's spend ($0.10 to | |
| 1915 | /// $100) and what the agents on one issue may spend in all ($1 to $1,000). | |
| 1916 | /// None goes back to the default ($2 and $10). A cap g1t staff set for the | |
| 1917 | /// account wins over both. Owners only. Returns `Outcome<Entitlements>`. | |
| 1918 | #[derive(Debug, Serialize, Deserialize)] | |
| 1919 | #[serde(rename_all = "camelCase")] | |
| 1920 | pub struct SetCapsArgs { | |
| 1921 | pub actor: User, | |
| 1922 | pub workspace: String, | |
| 1923 | #[serde(default, alias = "run_cap_micros")] | |
| 1924 | pub run_cap_micros: Option<i64>, | |
| 1925 | #[serde(default, alias = "issue_cap_micros")] | |
| 1926 | pub issue_cap_micros: Option<i64>, | |
| 1927 | } | |
| 1928 | ||
| 1929 | /// What staff see beside a request: the workspace's history with g1t. | |
| 1930 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 1931 | #[serde(rename_all = "camelCase")] | |
| 1932 | pub struct WorkspaceHistory { | |
| 1933 | pub plan: Option<PlanKind>, | |
| 1934 | /// The last six months, oldest first. | |
| 1935 | pub months: Vec<MonthFigures>, | |
| 1936 | /// Live payments that have cleared, and how many. | |
| 1937 | pub paid_cleared_micros: i64, | |
| 1938 | pub payments: u32, | |
| 1939 | pub disputes: u32, | |
| 1940 | pub declines: u32, | |
| 1941 | /// The first time the workspace appears in billing, RFC 3339. | |
| 1942 | pub first_seen: Option<String>, | |
| 1943 | pub ceiling_micros: Option<i64>, | |
| 1944 | pub max_ceiling_micros: Option<i64>, | |
| 1945 | pub spend_limit_micros: Option<i64>, | |
| 1946 | /// Recent velocity: the last hour, the usual hour over the last week, | |
| 1947 | /// and the last 24 hours, at price. | |
| 1948 | pub last_hour_micros: i64, | |
| 1949 | pub average_hour_micros: i64, | |
| 1950 | pub last_day_micros: i64, | |
| 1951 | } | |
| 1952 | ||
| 1953 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 1954 | #[serde(rename_all = "camelCase")] | |
| 1955 | pub struct LimitRequestReview { | |
| 1956 | pub request: LimitRequest, | |
| 1957 | pub history: WorkspaceHistory, | |
| 1958 | } | |
| 1959 | ||
| 1960 | /// `admin_limit_requests`: requests for staff, oldest open first. Returns | |
| 1961 | /// `Vec<LimitRequestReview>`. | |
| 1962 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 1963 | pub struct AdminLimitRequestsArgs { | |
| 1964 | /// `open` (the default), `approved`, `declined` or `all`. | |
| 1965 | #[serde(default)] | |
| 1966 | pub status: Option<String>, | |
| 1967 | } | |
| 1968 | ||
| 1969 | /// `admin_decide_limit_request`: approve (at the amount asked, or | |
| 1970 | /// `amount_micros`) or decline. The owner is told in the app and by email. | |
| 1971 | /// Recorded with who and why. Returns `Outcome<LimitRequest>`. | |
| 1972 | #[derive(Debug, Serialize, Deserialize)] | |
| 1973 | pub struct AdminDecideLimitRequestArgs { | |
| 1974 | pub id: String, | |
| 1975 | /// `approve` or `decline`. | |
| 1976 | pub decision: String, | |
| 1977 | #[serde(default)] | |
| 1978 | pub amount_micros: Option<i64>, | |
| 1979 | /// What the owner is told, beside the decision. | |
| 1980 | #[serde(default)] | |
| 1981 | pub note: String, | |
| 1982 | pub by: String, | |
| 1983 | } | |
| 1984 | ||
| 1985 | /// `admin_record_payment`: money that reached g1t outside the card pages, | |
| 1986 | /// such as a bank transfer, entered as a payment (it raises the limit like | |
| 1987 | /// one). Recorded with who and the transfer's reference. Returns | |
| 1988 | /// `Outcome<LedgerEntry>`. | |
| 1989 | #[derive(Debug, Serialize, Deserialize)] | |
| 1990 | pub struct AdminRecordPaymentArgs { | |
| 1991 | pub workspace: String, | |
| 1992 | pub amount_micros: i64, | |
| 1993 | /// The bank's reference for the transfer, or Stripe's payment id. | |
| 1994 | pub reference: String, | |
| 1995 | pub note: String, | |
| 1996 | pub by: String, | |
| 1997 | } | |
| 1998 | ||
| 1999 | // --- Overages and goodwill (sudo) -------------------------------------------- | |
| 2000 | ||
| 2001 | /// What a one-time goodwill credit would come to: g1t's margin on the | |
| 2002 | /// overage, always, plus as much of its underlying cost as the cap allows. | |
| 2003 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| 2004 | #[serde(rename_all = "camelCase")] | |
| 2005 | pub struct Goodwill { | |
| 2006 | /// This month's charges above the workspace's typical month. | |
| 2007 | pub overage_micros: i64, | |
| 2008 | /// The part of the overage that is g1t's margin. | |
| 2009 | pub margin_micros: i64, | |
| 2010 | /// The part that is what g1t paid its providers. | |
| 2011 | pub cost_micros: i64, | |
| 2012 | /// The one-click credit: the margin plus the cost up to the cap. | |
| 2013 | pub credit_micros: i64, | |
| 2014 | /// Of the credit, the real cost g1t absorbs. | |
| 2015 | pub absorbed_micros: i64, | |
| 2016 | } | |
| 2017 | ||
| 2018 | /// A workspace whose month went well past its usual, or hit a spike. | |
| 2019 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2020 | #[serde(rename_all = "camelCase")] | |
| 2021 | pub struct Overage { | |
| 2022 | pub workspace: String, | |
| 2023 | pub plan: PlanKind, | |
| 2024 | /// The median of its last three months' charges. | |
| 2025 | pub typical_month_micros: i64, | |
| 2026 | pub this_month_micros: i64, | |
| 2027 | /// What this month cost g1t, and what g1t keeps of it. | |
| 2028 | pub cost_micros: i64, | |
| 2029 | pub margin_micros: i64, | |
| 2030 | /// A spike this month, if there was one. | |
| 2031 | pub spike: Option<Spike>, | |
| 2032 | /// The runs that cost the most this month. | |
| 2033 | pub top_entries: Vec<LedgerEntry>, | |
| 2034 | pub goodwill: Goodwill, | |
| 2035 | /// False when a goodwill credit was given in the last 12 months. | |
| 2036 | pub goodwill_available: bool, | |
| 2037 | pub last_goodwill_at: Option<String>, | |
| 2038 | /// An open overage request from the owner, if there is one. | |
| 2039 | pub request: Option<LimitRequest>, | |
| 2040 | } | |
| 2041 | ||
| 2042 | /// `admin_overages`: the Overages queue. Returns `Vec<Overage>`. | |
| 2043 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 2044 | pub struct AdminOveragesArgs {} | |
| 2045 | ||
| 2046 | /// `admin_goodwill`: credits a workspace for accidental usage. With no | |
| 2047 | /// amount, the one-click credit (`Goodwill::credit_micros`), once per | |
| 2048 | /// workspace in 12 months. A larger amount, or a second within 12 months, | |
| 2049 | /// needs a typed reason. It shows on the statement as "Credit from g1t: | |
| 2050 | /// accidental usage on <date>". Returns `Outcome<LedgerEntry>`. | |
| 2051 | #[derive(Debug, Serialize, Deserialize)] | |
| 2052 | pub struct AdminGoodwillArgs { | |
| 2053 | pub workspace: String, | |
| 2054 | #[serde(default)] | |
| 2055 | pub amount_micros: Option<i64>, | |
| 2056 | /// Why, typed by staff; needed past the one-click credit. | |
| 2057 | #[serde(default)] | |
| 2058 | pub reason: String, | |
| 2059 | /// The day the accidental usage happened, `YYYY-MM-DD`; today if absent. | |
| 2060 | #[serde(default)] | |
| 2061 | pub day: Option<String>, | |
| 2062 | pub by: String, | |
| 2063 | } | |
| 2064 | ||
| 2065 | /// One workspace's recent pace, for sudo's velocity view. | |
| 2066 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2067 | #[serde(rename_all = "camelCase")] | |
| 2068 | pub struct Velocity { | |
| 2069 | pub workspace: String, | |
| 2070 | pub plan: PlanKind, | |
| 2071 | pub last_hour_micros: i64, | |
| 2072 | pub average_hour_micros: i64, | |
| 2073 | pub last_day_micros: i64, | |
| 2074 | pub this_month_micros: i64, | |
| 2075 | /// The last hour over the usual hour; 0 with no history. | |
| 2076 | pub ratio: f64, | |
| 2077 | pub spike: Option<Spike>, | |
| 2078 | pub first_seen: Option<String>, | |
| 2079 | } | |
| 2080 | ||
| 2081 | /// `admin_velocity`: workspaces spending in the last day, fastest first. | |
| 2082 | /// Returns `Vec<Velocity>`. | |
| 2083 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 2084 | pub struct AdminVelocityArgs {} | |
| 2085 | ||
| Billing accounts, terms and enterprises; g1t is no longer free | 2086 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] |
| 2087 | #[serde(rename_all = "snake_case")] | |
| 2088 | pub enum AccountKind { | |
| 2089 | Workspace, | |
| 2090 | Enterprise, | |
| 2091 | } | |
| 2092 | ||
| 2093 | /// How an account is charged. Standard unless g1t set otherwise in sudo. | |
| 2094 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 2095 | #[serde(rename_all = "camelCase")] | |
| 2096 | pub struct Terms { | |
| 2097 | pub kind: TermsKind, | |
| 2098 | /// Off every usage charge, in percent. Custom terms only. | |
| 2099 | #[serde(default)] | |
| 2100 | pub discount_percent: u32, | |
| 2101 | /// A ceiling on unpaid usage that replaces the one trust would give. | |
| 2102 | #[serde(default)] | |
| 2103 | pub ceiling_micros: Option<i64>, | |
| 2104 | /// Why, for whoever looks next. | |
| 2105 | #[serde(default)] | |
| 2106 | pub note: String, | |
| 2107 | /// When the terms end and the account goes back to standard. | |
| 2108 | #[serde(default)] | |
| 2109 | pub until: Option<String>, | |
| 2110 | #[serde(default)] | |
| 2111 | pub set_by: Option<String>, | |
| 2112 | #[serde(default)] | |
| 2113 | pub set_at: Option<String>, | |
| 2114 | } | |
| 2115 | ||
| 2116 | impl Terms { | |
| 2117 | pub fn standard() -> Self { | |
| 2118 | Terms { | |
| 2119 | kind: TermsKind::Standard, | |
| 2120 | discount_percent: 0, | |
| 2121 | ceiling_micros: None, | |
| 2122 | note: String::new(), | |
| 2123 | until: None, | |
| 2124 | set_by: None, | |
| 2125 | set_at: None, | |
| 2126 | } | |
| 2127 | } | |
| 2128 | ||
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 2129 | /// The discount in percent, 0 to 100. Terms from before discounts |
| 2130 | /// replaced "comped" read as 100%. | |
| 2131 | pub fn percent_off(&self) -> u32 { | |
| 2132 | match self.kind { | |
| 2133 | TermsKind::Comped => 100, | |
| 2134 | TermsKind::Custom => self.discount_percent.min(100), | |
| 2135 | TermsKind::Standard => 0, | |
| 2136 | } | |
| 2137 | } | |
| 2138 | ||
| 2139 | /// A 100% discount: nothing is charged, usage is recorded at its price | |
| 2140 | /// and discounted in full. g1t's own workspaces and partners. Paid | |
| 2141 | /// features are on without a plan, and g1t's own spend on it is held to | |
| 2142 | /// a monthly budget (the terms' ceiling, at cost). | |
| 2143 | pub fn full_discount(&self) -> bool { | |
| 2144 | self.percent_off() >= 100 | |
| 2145 | } | |
| 2146 | ||
| Billing accounts, terms and enterprises; g1t is no longer free | 2147 | /// What a charge becomes under these terms. |
| 2148 | pub fn apply(&self, charge_micros: i64) -> i64 { | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 2149 | charge_micros * i64::from(100 - self.percent_off()) / 100 |
| Billing accounts, terms and enterprises; g1t is no longer free | 2150 | } |
| Merge branch 'worktree-agent-a633ac0f7f66d419d' | 2151 | |
| 2152 | /// What a charge at cost plus the margin becomes under these terms, and | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 2153 | /// what the discount took off it (`ledger.discount_micros`), so the |
| 2154 | /// statement shows the usage at its price and the discount beside it, | |
| 2155 | /// and a discount below cost plus the margin is counted as given, never | |
| 2156 | /// lost. A 100% discount takes it all. | |
| Merge branch 'worktree-agent-a633ac0f7f66d419d' | 2157 | pub fn discounted(&self, charge_micros: i64) -> (i64, i64) { |
| 2158 | let charged = self.apply(charge_micros); | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 2159 | (charged, (charge_micros - charged).max(0)) |
| 2160 | } | |
| 2161 | ||
| 2162 | /// How the statement and sudo name the terms: `100% discount`, `30% off`. | |
| 2163 | pub fn discount_label(&self) -> Option<String> { | |
| 2164 | match self.percent_off() { | |
| 2165 | 0 => None, | |
| 2166 | 100 => Some("100% discount".to_owned()), | |
| 2167 | percent => Some(format!("{percent}% off")), | |
| Merge branch 'worktree-agent-a633ac0f7f66d419d' | 2168 | } |
| 2169 | } | |
| Billing accounts, terms and enterprises; g1t is no longer free | 2170 | } |
| 2171 | ||
| 2172 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 2173 | #[serde(rename_all = "snake_case")] | |
| 2174 | pub enum TermsKind { | |
| 2175 | /// Prices as published, limits by trust. | |
| 2176 | Standard, | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 2177 | /// Before discounts: what a 100% discount is now. Read as one |
| 2178 | /// (`Terms::percent_off`); billing never writes it (migration 0039). | |
| Billing accounts, terms and enterprises; g1t is no longer free | 2179 | Comped, |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 2180 | /// A discount (up to 100%), a ceiling, or both. |
| Billing accounts, terms and enterprises; g1t is no longer free | 2181 | Custom, |
| 2182 | } | |
| 2183 | ||
| Stripe webhooks, enterprise invoices, and sudo for both | 2184 | /// `stripe_webhook`: an event from Stripe, as the API received it: the raw |
| 2185 | /// body and its `Stripe-Signature` header. Billing checks the signature | |
| 2186 | /// against the secret of the endpoint it registered, and handles each | |
| 2187 | /// event once. Returns `Outcome<bool>`: false for one already handled. | |
| 2188 | #[derive(Debug, Serialize, Deserialize)] | |
| 2189 | pub struct StripeWebhookArgs { | |
| 2190 | pub payload: String, | |
| 2191 | pub signature: String, | |
| 2192 | } | |
| 2193 | ||
| 2194 | /// `admin_stripe`: where billing stands with Stripe. Staff only. Returns | |
| Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard | 2195 | /// `StripeStatus`. With `fix: true`, first enables the destination at |
| 2196 | /// billing's address and gives it the events billing needs. | |
| Stripe webhooks, enterprise invoices, and sudo for both | 2197 | #[derive(Debug, Default, Serialize, Deserialize)] |
| 2198 | pub struct AdminStripeArgs { | |
| 2199 | #[serde(default)] | |
| Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard | 2200 | pub fix: bool, |
| Stripe webhooks, enterprise invoices, and sudo for both | 2201 | #[serde(default)] |
| 2202 | pub by: Option<String>, | |
| 2203 | } | |
| 2204 | ||
| 2205 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2206 | #[serde(rename_all = "camelCase")] | |
| 2207 | pub struct StripeStatus { | |
| 2208 | /// `test` or `live`, from the key; `off` without one. | |
| 2209 | pub mode: String, | |
| Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard | 2210 | /// Whether `STRIPE_WEBHOOK_SECRET` is set, so events can be checked. |
| 2211 | pub secret_set: bool, | |
| 2212 | /// The destination at billing's address in Stripe, as Stripe has it. | |
| Stripe webhooks, enterprise invoices, and sudo for both | 2213 | pub webhook: Option<StripeWebhook>, |
| Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard | 2214 | /// Events billing handles that the destination does not send. |
| 2215 | pub missing_events: Vec<String>, | |
| Stripe webhooks, enterprise invoices, and sudo for both | 2216 | /// The latest events handled, newest first. |
| 2217 | pub recent_events: Vec<StripeEventSummary>, | |
| Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard | 2218 | /// What went wrong reading or fixing the destination, if it did. |
| Stripe webhooks, enterprise invoices, and sudo for both | 2219 | pub error: Option<String>, |
| 2220 | } | |
| 2221 | ||
| 2222 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2223 | #[serde(rename_all = "camelCase")] | |
| 2224 | pub struct StripeWebhook { | |
| 2225 | pub url: String, | |
| 2226 | pub endpoint_id: String, | |
| Stripe's webhook secret is a Worker secret, STRIPE_WEBHOOK_SECRET, from a destination made in Stripe's dashboard | 2227 | /// `enabled` or `disabled`. |
| 2228 | pub status: String, | |
| Stripe webhooks, enterprise invoices, and sudo for both | 2229 | pub events: Vec<String>, |
| 2230 | pub created_at: String, | |
| 2231 | } | |
| 2232 | ||
| 2233 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2234 | #[serde(rename_all = "camelCase")] | |
| 2235 | pub struct StripeEventSummary { | |
| 2236 | pub id: String, | |
| 2237 | pub kind: String, | |
| 2238 | pub outcome: String, | |
| 2239 | pub received_at: String, | |
| 2240 | } | |
| 2241 | ||
| 2242 | /// `admin_enterprise_billing`: where an enterprise's invoices go. Creates | |
| 2243 | /// or updates its Stripe customer. Returns `Outcome<BillingAccount>`. | |
| 2244 | #[derive(Debug, Serialize, Deserialize)] | |
| 2245 | pub struct AdminEnterpriseBillingArgs { | |
| 2246 | pub id: String, | |
| 2247 | pub email: String, | |
| 2248 | pub by: String, | |
| 2249 | } | |
| 2250 | ||
| Merge Stripe Tax, the card fee on card payments, and one free workspace per person | 2251 | /// `admin_enterprise_address`: the enterprise's billing address and tax ID, |
| 2252 | /// saved on its Stripe customer (made by `admin_enterprise_billing`). | |
| 2253 | /// Stripe Tax works its invoices' tax out from the address; an invoice is | |
| 2254 | /// not sent without one. Returns `Outcome<bool>`. | |
| 2255 | #[derive(Debug, Serialize, Deserialize)] | |
| 2256 | #[serde(rename_all = "camelCase")] | |
| 2257 | pub struct AdminEnterpriseAddressArgs { | |
| 2258 | pub id: String, | |
| 2259 | pub address: PostalAddress, | |
| 2260 | #[serde(default, alias = "tax_id_type")] | |
| 2261 | pub tax_id_type: Option<String>, | |
| 2262 | #[serde(default, alias = "tax_id")] | |
| 2263 | pub tax_id: Option<String>, | |
| 2264 | pub by: String, | |
| 2265 | } | |
| 2266 | ||
| Stripe webhooks, enterprise invoices, and sudo for both | 2267 | /// `admin_invoice_enterprise`: sends an enterprise its invoice now, for |
| 2268 | /// what its workspaces owe, rather than waiting for the month to close. | |
| 2269 | /// Returns `Outcome<EnterpriseInvoice>`. | |
| 2270 | #[derive(Debug, Serialize, Deserialize)] | |
| 2271 | pub struct AdminInvoiceEnterpriseArgs { | |
| 2272 | pub id: String, | |
| 2273 | pub by: String, | |
| 2274 | } | |
| 2275 | ||
| 2276 | /// An enterprise's invoice: one line per workspace, paid on Stripe. | |
| 2277 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2278 | #[serde(rename_all = "camelCase")] | |
| 2279 | pub struct EnterpriseInvoice { | |
| 2280 | pub invoice_id: String, | |
| 2281 | /// Stripe's page for it, where it is paid. | |
| 2282 | pub hosted_url: Option<String>, | |
| 2283 | pub amount_micros: i64, | |
| 2284 | /// `open`, `paid`, `overdue` or `void`. | |
| 2285 | pub status: String, | |
| 2286 | pub period: String, | |
| 2287 | pub lines: Vec<InvoiceLine>, | |
| 2288 | pub created_at: String, | |
| 2289 | } | |
| 2290 | ||
| 2291 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2292 | #[serde(rename_all = "camelCase")] | |
| 2293 | pub struct InvoiceLine { | |
| 2294 | pub workspace: String, | |
| 2295 | pub amount_micros: i64, | |
| 2296 | } | |
| 2297 | ||
| Two limits, real invoices, trust that grows by itself, sales signals | 2298 | /// A workspace's invoice from g1t: one per month, and one each time it is |
| 2299 | /// charged near its limit. Itemised, charged to the card on file, and kept | |
| 2300 | /// in Stripe's billing page with its PDF. | |
| 2301 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2302 | #[serde(rename_all = "camelCase")] | |
| 2303 | pub struct WorkspaceInvoice { | |
| 2304 | pub invoice_id: String, | |
| 2305 | pub workspace: String, | |
| 2306 | /// `month` (2026-10) or `threshold`. | |
| 2307 | pub reason: String, | |
| 2308 | pub period: String, | |
| 2309 | pub amount_micros: i64, | |
| 2310 | /// `paid`, `open`, `failed` or `void`. | |
| 2311 | pub status: String, | |
| 2312 | pub hosted_url: Option<String>, | |
| 2313 | pub pdf_url: Option<String>, | |
| 2314 | pub lines: Vec<InvoiceItem>, | |
| 2315 | pub created_at: String, | |
| Merge Stripe Tax, the card fee on card payments, and one free workspace per person | 2316 | /// The card processing fee on top of `amount_micros`, when the invoice |
| 2317 | /// is charged to a card; never part of the usage it pays for. | |
| 2318 | #[serde(default)] | |
| 2319 | pub fee_micros: i64, | |
| 2320 | /// The tax Stripe added on top, once it is known (after paying). | |
| 2321 | #[serde(default)] | |
| 2322 | pub tax_micros: i64, | |
| Two limits, real invoices, trust that grows by itself, sales signals | 2323 | } |
| 2324 | ||
| 2325 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2326 | #[serde(rename_all = "camelCase")] | |
| 2327 | pub struct InvoiceItem { | |
| 2328 | pub description: String, | |
| 2329 | pub amount_micros: i64, | |
| 2330 | } | |
| 2331 | ||
| 2332 | /// `invoices`: a workspace's invoices from g1t, newest first. Members | |
| 2333 | /// only. Returns `Outcome<Vec<WorkspaceInvoice>>`. | |
| 2334 | #[derive(Debug, Serialize, Deserialize)] | |
| 2335 | pub struct InvoicesArgs { | |
| 2336 | pub workspace: String, | |
| 2337 | pub viewer: Viewer, | |
| 2338 | } | |
| 2339 | ||
| 2340 | /// `admin_workspace_invoices`: the same, for staff. Returns | |
| 2341 | /// `Vec<WorkspaceInvoice>`. | |
| 2342 | #[derive(Debug, Serialize, Deserialize)] | |
| 2343 | pub struct AdminWorkspaceInvoicesArgs { | |
| 2344 | pub workspace: String, | |
| 2345 | } | |
| 2346 | ||
| The statement is a month at a time, a line per kind of charge | 2347 | /// `statement`: a month of a workspace's ledger, grouped by day (or by |
| 2348 | /// project) with a line per kind of charge. Members only. Returns | |
| 2349 | /// `Outcome<Statement>`. | |
| 2350 | #[derive(Debug, Serialize, Deserialize)] | |
| 2351 | pub struct StatementArgs { | |
| 2352 | pub workspace: String, | |
| 2353 | pub viewer: Viewer, | |
| 2354 | /// YYYY-MM; this month when absent. | |
| 2355 | #[serde(default)] | |
| 2356 | pub month: Option<String>, | |
| 2357 | /// `day` (the default) or `project`. | |
| 2358 | #[serde(default)] | |
| 2359 | pub group: Option<String>, | |
| 2360 | } | |
| 2361 | ||
| 2362 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2363 | #[serde(rename_all = "camelCase")] | |
| 2364 | pub struct Statement { | |
| 2365 | pub month: String, | |
| 2366 | /// Months with any entries, newest first. | |
| 2367 | pub months: Vec<String>, | |
| 2368 | pub groups: Vec<StatementGroup>, | |
| 2369 | pub totals: StatementTotals, | |
| 2370 | } | |
| 2371 | ||
| 2372 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2373 | #[serde(rename_all = "camelCase")] | |
| 2374 | pub struct StatementGroup { | |
| 2375 | /// The day (YYYY-MM-DD) or the project (`owner/name`, or empty). | |
| 2376 | pub key: String, | |
| 2377 | pub label: String, | |
| 2378 | pub lines: Vec<StatementLine>, | |
| 2379 | /// What the group's charges come to. | |
| 2380 | pub charged_micros: i64, | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 2381 | /// Its usage at price, and what the discount took off it. |
| 2382 | #[serde(default)] | |
| 2383 | pub price_micros: i64, | |
| 2384 | #[serde(default)] | |
| 2385 | pub discount_micros: i64, | |
| The statement is a month at a time, a line per kind of charge | 2386 | } |
| 2387 | ||
| 2388 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2389 | #[serde(rename_all = "camelCase")] | |
| 2390 | pub struct StatementLine { | |
| 2391 | /// Agent runs, Sandbox time, Deployments, Payments, Credits from g1t, | |
| Prices are what g1t pays plus 20%, from the first second | 2392 | /// Refunds, and, for older entries, Runs on your own model provider. |
| The statement is a month at a time, a line per kind of charge | 2393 | pub kind: String, |
| 2394 | pub count: u32, | |
| 2395 | /// Charges positive; money in (payments, credits) negative. | |
| 2396 | pub charged_micros: i64, | |
| 2397 | pub cost_micros: i64, | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 2398 | /// Of the usage on the line, what was paid for before it was charged: |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 2399 | /// by the plan's included usage, the trial credit, g1t's open-source |
| 2400 | /// pool, or g1t itself. Not in `charged_micros`. | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 2401 | #[serde(default)] |
| 2402 | pub covered_micros: i64, | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 2403 | /// Usage at its price: charged, plus what paid for it and what the |
| 2404 | /// discount took off. Zero for money in. | |
| 2405 | #[serde(default)] | |
| 2406 | pub price_micros: i64, | |
| 2407 | /// What the account's discount took off the line's price. | |
| 2408 | #[serde(default)] | |
| 2409 | pub discount_micros: i64, | |
| Merge Stripe Tax, the card fee on card payments, and one free workspace per person | 2410 | /// On the `Tax` and `Card processing fees` lines: what was paid with |
| 2411 | /// payments on top of what reached the balance (negative for what a | |
| 2412 | /// refund gave back). Never in `charged_micros` or the balance. | |
| 2413 | #[serde(default)] | |
| 2414 | pub passed_micros: i64, | |
| The statement is a month at a time, a line per kind of charge | 2415 | } |
| 2416 | ||
| 2417 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2418 | #[serde(rename_all = "camelCase")] | |
| 2419 | pub struct StatementTotals { | |
| 2420 | pub charged_micros: i64, | |
| 2421 | pub paid_micros: i64, | |
| 2422 | pub cost_micros: i64, | |
| 2423 | pub entries: u32, | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 2424 | /// Usage at price, and what the discount took off it: charged is the |
| 2425 | /// price less the discount and what paid for it. | |
| 2426 | #[serde(default)] | |
| 2427 | pub price_micros: i64, | |
| 2428 | #[serde(default)] | |
| 2429 | pub discount_micros: i64, | |
| 2430 | /// The account's discount now, in percent; absent without one. | |
| 2431 | #[serde(default)] | |
| 2432 | pub discount_percent: Option<u32>, | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 2433 | /// What paid for usage before it was charged, one line per source, |
| 2434 | /// such as "Paid by g1t's open-source pool". | |
| 2435 | #[serde(default)] | |
| 2436 | pub covered: Vec<Covered>, | |
| 2437 | /// Owed when the month closed but under the minimum charge, so it | |
| 2438 | /// carries over to the next invoice. Zero when nothing carried. | |
| 2439 | #[serde(default)] | |
| 2440 | pub carried_micros: i64, | |
| Merge Stripe Tax, the card fee on card payments, and one free workspace per person | 2441 | /// Tax and card processing fees paid with the month's payments, on top |
| 2442 | /// of `paid_micros`. | |
| 2443 | #[serde(default)] | |
| 2444 | pub tax_micros: i64, | |
| 2445 | #[serde(default)] | |
| 2446 | pub card_fee_micros: i64, | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 2447 | } |
| 2448 | ||
| 2449 | /// One source that paid for usage before it was charged. | |
| 2450 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 2451 | #[serde(rename_all = "camelCase")] | |
| 2452 | pub struct Covered { | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 2453 | /// `included`, `trial`, `oss_pool` or `given`. |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 2454 | pub source: String, |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 2455 | /// "Paid by your plan's included usage", "Paid by your trial credit", |
| 2456 | /// "Paid by g1t's open-source pool", "Covered by g1t". | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 2457 | pub label: String, |
| 2458 | pub micros: i64, | |
| The statement is a month at a time, a line per kind of charge | 2459 | } |
| 2460 | ||
| 2461 | /// `statement_entries`: one statement line's entries, newest first, 50 at | |
| 2462 | /// a time (`before` = the last id seen). Returns `Outcome<Vec<LedgerEntry>>`. | |
| 2463 | #[derive(Debug, Serialize, Deserialize)] | |
| 2464 | pub struct StatementEntriesArgs { | |
| 2465 | pub workspace: String, | |
| 2466 | pub viewer: Viewer, | |
| 2467 | pub month: String, | |
| 2468 | pub kind: String, | |
| 2469 | #[serde(default)] | |
| 2470 | pub day: Option<String>, | |
| 2471 | #[serde(default)] | |
| 2472 | pub project: Option<String>, | |
| 2473 | #[serde(default)] | |
| 2474 | pub before: Option<String>, | |
| 2475 | } | |
| 2476 | ||
| Two limits, real invoices, trust that grows by itself, sales signals | 2477 | // --- Sales (sudo.g1t.sh) ------------------------------------------------------ |
| 2478 | // | |
| 2479 | // What staff need to know to reach out: who is growing, who is close to | |
| 2480 | // their limit, who was declined, who has become a steady customer. And what | |
| 2481 | // was done about it: a stage, an owner on g1t's side, a next step, notes. | |
| 2482 | ||
| 2483 | /// Why a workspace is worth a look. | |
| 2484 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 2485 | #[serde(rename_all = "snake_case")] | |
| 2486 | pub enum SignalKind { | |
| 2487 | /// At its limit, or its own spend limit: work is stopped. | |
| 2488 | AtLimit, | |
| 2489 | /// Past 80% of what is available to it: about to need more. | |
| 2490 | NearCeiling, | |
| 2491 | /// Its card was declined or a payment disputed. | |
| 2492 | Declined, | |
| 2493 | /// This month is well ahead of last month. | |
| 2494 | Growing, | |
| 2495 | /// Became Established: the ceiling now follows its spend. | |
| 2496 | Established, | |
| 2497 | /// Paid g1t for the first time. | |
| 2498 | FirstPayment, | |
| 2499 | /// Spending enough that custom terms or an enterprise may suit it. | |
| 2500 | HighSpend, | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 2501 | /// Costs g1t more on Cloudflare than it pays, over 30 days: a pricing |
| 2502 | /// gap or abuse to look at (billing's `margin`). | |
| 2503 | CostOverRevenue, | |
| Two limits, real invoices, trust that grows by itself, sales signals | 2504 | } |
| 2505 | ||
| 2506 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2507 | #[serde(rename_all = "camelCase")] | |
| 2508 | pub struct Signal { | |
| 2509 | pub workspace: String, | |
| 2510 | pub kind: SignalKind, | |
| 2511 | /// One sentence, with the figures. | |
| 2512 | pub detail: String, | |
| 2513 | /// The figure that matters, such as this month's spend. | |
| 2514 | pub value_micros: i64, | |
| 2515 | /// Its sales stage, if staff gave it one. | |
| 2516 | pub stage: Option<String>, | |
| 2517 | pub owner: Option<String>, | |
| Billing lists every invoice and every staff change; signals carry follow-ups | 2518 | #[serde(default)] |
| 2519 | pub next_step: Option<String>, | |
| 2520 | /// When the next step is due, `YYYY-MM-DD`. | |
| 2521 | #[serde(default)] | |
| 2522 | pub next_at: Option<String>, | |
| 2523 | } | |
| 2524 | ||
| 2525 | /// `admin_invoices`: every invoice g1t has sent, workspaces' and | |
| 2526 | /// enterprises', newest first. Returns `Vec<InvoiceSummary>`. | |
| 2527 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 2528 | pub struct AdminInvoicesArgs { | |
| 2529 | /// `paid`, `open`, `failed`, `overdue` or `void`. | |
| 2530 | #[serde(default)] | |
| 2531 | pub status: Option<String>, | |
| 2532 | /// YYYY-MM, by when it was sent. | |
| 2533 | #[serde(default)] | |
| 2534 | pub month: Option<String>, | |
| 2535 | } | |
| 2536 | ||
| 2537 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2538 | #[serde(rename_all = "camelCase")] | |
| 2539 | pub struct InvoiceSummary { | |
| 2540 | pub invoice_id: String, | |
| 2541 | /// `workspace` or `enterprise`. | |
| 2542 | pub kind: String, | |
| 2543 | /// The workspace's slug, or the enterprise's account id. | |
| 2544 | pub account: String, | |
| 2545 | /// What to call it: the workspace, or the enterprise's name. | |
| 2546 | pub name: String, | |
| 2547 | pub reason: String, | |
| 2548 | pub period: String, | |
| 2549 | pub amount_micros: i64, | |
| 2550 | pub status: String, | |
| 2551 | pub hosted_url: Option<String>, | |
| 2552 | pub created_at: String, | |
| 2553 | pub paid_at: Option<String>, | |
| 2554 | } | |
| 2555 | ||
| 2556 | /// `admin_audit`: every change made in sudo, and by Stripe, newest first. | |
| 2557 | /// Returns `Vec<AdminAction>`. | |
| 2558 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 2559 | pub struct AdminAuditArgs { | |
| 2560 | #[serde(default)] | |
| 2561 | pub by: Option<String>, | |
| 2562 | #[serde(default)] | |
| 2563 | pub action: Option<String>, | |
| 2564 | /// Only those before this time, for paging. | |
| 2565 | #[serde(default)] | |
| 2566 | pub before: Option<String>, | |
| Two limits, real invoices, trust that grows by itself, sales signals | 2567 | } |
| 2568 | ||
| 2569 | /// `admin_signals`: every workspace worth reaching out to, most urgent | |
| 2570 | /// first. Returns `Vec<Signal>`. | |
| 2571 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 2572 | pub struct AdminSignalsArgs {} | |
| 2573 | ||
| 2574 | /// What staff are doing about a workspace. | |
| 2575 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2576 | #[serde(rename_all = "camelCase")] | |
| 2577 | pub struct SalesRecord { | |
| 2578 | pub workspace: String, | |
| 2579 | /// `none`, `lead`, `contacted`, `negotiating`, `won`, `lost` or `churn_risk`. | |
| 2580 | pub stage: String, | |
| 2581 | /// The staff member looking after it. | |
| 2582 | pub owner: Option<String>, | |
| 2583 | pub next_step: Option<String>, | |
| 2584 | /// RFC 3339 date. | |
| 2585 | pub next_at: Option<String>, | |
| 2586 | pub notes: Vec<SalesNote>, | |
| 2587 | pub updated_at: Option<String>, | |
| 2588 | } | |
| 2589 | ||
| 2590 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2591 | #[serde(rename_all = "camelCase")] | |
| 2592 | pub struct SalesNote { | |
| 2593 | pub id: String, | |
| 2594 | pub text: String, | |
| 2595 | pub by: String, | |
| 2596 | pub created_at: String, | |
| 2597 | } | |
| 2598 | ||
| 2599 | /// `admin_sales`: a workspace's sales record. Returns `SalesRecord`. | |
| 2600 | #[derive(Debug, Serialize, Deserialize)] | |
| 2601 | pub struct AdminSalesArgs { | |
| 2602 | pub workspace: String, | |
| 2603 | } | |
| 2604 | ||
| 2605 | /// `admin_set_sales`: its stage, owner and next step. Returns `Outcome<SalesRecord>`. | |
| 2606 | #[derive(Debug, Serialize, Deserialize)] | |
| 2607 | pub struct AdminSetSalesArgs { | |
| 2608 | pub workspace: String, | |
| 2609 | pub stage: String, | |
| 2610 | #[serde(default)] | |
| 2611 | pub owner: Option<String>, | |
| 2612 | #[serde(default)] | |
| 2613 | pub next_step: Option<String>, | |
| 2614 | #[serde(default)] | |
| 2615 | pub next_at: Option<String>, | |
| 2616 | pub by: String, | |
| 2617 | } | |
| 2618 | ||
| 2619 | /// `admin_add_note`. Returns `Outcome<SalesRecord>`. | |
| 2620 | #[derive(Debug, Serialize, Deserialize)] | |
| 2621 | pub struct AdminAddNoteArgs { | |
| 2622 | pub workspace: String, | |
| 2623 | pub text: String, | |
| 2624 | pub by: String, | |
| 2625 | } | |
| 2626 | ||
| 2627 | /// `admin_overview`: the business at a glance. Returns `Overview`. | |
| 2628 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 2629 | pub struct AdminOverviewArgs {} | |
| 2630 | ||
| 2631 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2632 | #[serde(rename_all = "camelCase")] | |
| 2633 | pub struct Overview { | |
| 2634 | /// YYYY-MM. | |
| 2635 | pub month: String, | |
| 2636 | /// The last six months, oldest first, all workspaces together. | |
| 2637 | pub months: Vec<MonthFigures>, | |
| 2638 | /// This month by kind of usage: models, sandbox, deployments, plans. | |
| 2639 | pub by_kind: Vec<KindFigures>, | |
| 2640 | pub paying_workspaces: u32, | |
| 2641 | pub stopped: u32, | |
| 2642 | pub near_ceiling: u32, | |
| 2643 | pub declined: u32, | |
| 2644 | /// Sent and not yet paid, workspaces and enterprises. | |
| 2645 | pub open_invoices_micros: i64, | |
| 2646 | /// Follow-ups due today or earlier. | |
| 2647 | pub follow_ups_due: u32, | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 2648 | /// The capped budgets g1t pays from, this month. |
| 2649 | #[serde(default)] | |
| 2650 | pub pools: Option<Pools>, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 2651 | /// This month's revenue: usage charged plus the plan's price paid. |
| 2652 | #[serde(default)] | |
| 2653 | pub revenue_micros: i64, | |
| 2654 | /// Workspaces on the paid plan now, and what their price comes to a | |
| 2655 | /// month. | |
| 2656 | #[serde(default)] | |
| 2657 | pub active_plans: u32, | |
| 2658 | #[serde(default)] | |
| 2659 | pub plan_mrr_micros: i64, | |
| 2660 | /// What g1t gave this month, by source, apart from its margin. | |
| 2661 | #[serde(default)] | |
| 2662 | pub given: Vec<GivenFigures>, | |
| 2663 | /// g1t's own and Flagon's workspaces this month: what their use cost, | |
| 2664 | /// and why they are not charged. | |
| 2665 | #[serde(default)] | |
| 2666 | pub internal: Vec<InternalUse>, | |
| 2667 | /// Open limit requests, and workspaces in the Overages queue. | |
| 2668 | #[serde(default)] | |
| 2669 | pub open_requests: u32, | |
| 2670 | #[serde(default)] | |
| 2671 | pub overages: u32, | |
| 2672 | /// Spend spikes waiting for an owner. | |
| 2673 | #[serde(default)] | |
| 2674 | pub open_spikes: u32, | |
| Two limits, real invoices, trust that grows by itself, sales signals | 2675 | } |
| 2676 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 2677 | /// What g1t gave this month from one source. |
| 2678 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 2679 | #[serde(rename_all = "camelCase")] | |
| 2680 | pub struct GivenFigures { | |
| 2681 | /// `internal`, `trial`, `oss_pool`, `goodwill` or `covered`. | |
| 2682 | pub source: String, | |
| 2683 | pub label: String, | |
| 2684 | /// At price, and what it cost g1t. | |
| 2685 | pub micros: i64, | |
| 2686 | pub cost_micros: i64, | |
| 2687 | } | |
| 2688 | ||
| 2689 | /// One internal workspace's use this month. | |
| 2690 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 2691 | #[serde(rename_all = "camelCase")] | |
| 2692 | pub struct InternalUse { | |
| 2693 | pub workspace: String, | |
| 2694 | /// Why it is not charged: its terms' note. | |
| 2695 | pub reason: String, | |
| 2696 | pub cost_micros: i64, | |
| 2697 | pub entries: u32, | |
| 2698 | } | |
| 2699 | ||
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 2700 | /// g1t's capped budgets for free usage, this calendar month (UTC). |
| 2701 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 2702 | #[serde(rename_all = "camelCase")] | |
| 2703 | pub struct Pools { | |
| 2704 | /// YYYY-MM. | |
| 2705 | pub month: String, | |
| 2706 | /// Trial grants made this month, against the month's pool. | |
| 2707 | pub trial_granted_micros: i64, | |
| 2708 | pub trial_pool_micros: i64, | |
| 2709 | pub trial_grants: u32, | |
| 2710 | /// What the open-source pool paid this month, against its cap. | |
| 2711 | pub oss_used_micros: i64, | |
| 2712 | pub oss_pool_micros: i64, | |
| 2713 | /// Each public repository's monthly cap on the pool. | |
| 2714 | pub oss_repo_micros: i64, | |
| 2715 | } | |
| 2716 | ||
| Two limits, real invoices, trust that grows by itself, sales signals | 2717 | #[derive(Clone, Debug, Serialize, Deserialize)] |
| 2718 | #[serde(rename_all = "camelCase")] | |
| 2719 | pub struct KindFigures { | |
| 2720 | pub kind: String, | |
| 2721 | pub charged_micros: i64, | |
| 2722 | pub cost_micros: i64, | |
| 2723 | } | |
| 2724 | ||
| Billing accounts, terms and enterprises; g1t is no longer free | 2725 | // --- Staff (sudo.g1t.sh) ------------------------------------------------------ |
| 2726 | // | |
| 2727 | // Called only by the sudo app, which only g1t staff can reach (behind | |
| 2728 | // Cloudflare Access). Each change names who made it, and is kept in the | |
| 2729 | // audit log. | |
| 2730 | ||
| 2731 | /// `admin_accounts`: every billing account, with where each stands this | |
| 2732 | /// month. Returns `Vec<AccountSummary>`. | |
| 2733 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 2734 | pub struct AdminAccountsArgs { | |
| 2735 | #[serde(default)] | |
| 2736 | pub query: Option<String>, | |
| Stripe webhooks, enterprise invoices, and sudo for both | 2737 | /// Exactly these workspaces' accounts, such as one page of sudo's |
| 2738 | /// list; every account with activity when absent. | |
| 2739 | #[serde(default)] | |
| 2740 | pub workspaces: Option<Vec<String>>, | |
| Billing accounts, terms and enterprises; g1t is no longer free | 2741 | } |
| 2742 | ||
| 2743 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2744 | #[serde(rename_all = "camelCase")] | |
| 2745 | pub struct AccountSummary { | |
| 2746 | pub account: BillingAccount, | |
| 2747 | pub limit: Limit, | |
| 2748 | /// Charged this month, after terms. | |
| 2749 | pub charged_micros: i64, | |
| 2750 | /// What this month's usage cost g1t. | |
| 2751 | pub cost_micros: i64, | |
| 2752 | /// Paid, ever. | |
| 2753 | pub paid_micros: i64, | |
| Billing on Stripe's pages, month-end charges, warnings; sudo by workspace | 2754 | /// The same figures for each of the account's workspaces that has |
| 2755 | /// any, so staff can see what one member of an enterprise used. | |
| 2756 | #[serde(default)] | |
| 2757 | pub by_workspace: Vec<WorkspaceFigures>, | |
| Two limits, real invoices, trust that grows by itself, sales signals | 2758 | /// The last six months, oldest first, for trends. |
| 2759 | #[serde(default)] | |
| 2760 | pub months: Vec<MonthFigures>, | |
| 2761 | } | |
| 2762 | ||
| 2763 | /// One month of an account's billing. | |
| 2764 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 2765 | #[serde(rename_all = "camelCase")] | |
| 2766 | pub struct MonthFigures { | |
| 2767 | /// YYYY-MM. | |
| 2768 | pub month: String, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 2769 | /// Usage charged, after what paid for it first. |
| Two limits, real invoices, trust that grows by itself, sales signals | 2770 | pub charged_micros: i64, |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 2771 | /// What usage cost g1t: only what g1t paid for, never a workspace's own |
| 2772 | /// model provider. | |
| Two limits, real invoices, trust that grows by itself, sales signals | 2773 | pub cost_micros: i64, |
| 2774 | pub paid_micros: i64, | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 2775 | /// The plan's monthly price, paid. |
| 2776 | #[serde(default)] | |
| 2777 | pub plans_micros: i64, | |
| 2778 | /// What g1t gave, at price: internal (comped) use, trials, the | |
| 2779 | /// open-source pool, goodwill credits and what g1t covered. Not margin. | |
| 2780 | #[serde(default)] | |
| 2781 | pub given_micros: i64, | |
| Billing on Stripe's pages, month-end charges, warnings; sudo by workspace | 2782 | } |
| 2783 | ||
| 2784 | /// One workspace's share of an [`AccountSummary`]. | |
| 2785 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 2786 | #[serde(rename_all = "camelCase")] | |
| 2787 | pub struct WorkspaceFigures { | |
| 2788 | pub workspace: String, | |
| 2789 | pub charged_micros: i64, | |
| 2790 | pub cost_micros: i64, | |
| 2791 | pub paid_micros: i64, | |
| Billing accounts, terms and enterprises; g1t is no longer free | 2792 | } |
| 2793 | ||
| 2794 | /// `admin_account`: one account in full. Returns `Outcome<AccountDetail>`. | |
| 2795 | #[derive(Debug, Serialize, Deserialize)] | |
| 2796 | pub struct AdminAccountArgs { | |
| 2797 | /// An account id, or a workspace slug. | |
| 2798 | pub id: String, | |
| 2799 | } | |
| 2800 | ||
| 2801 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2802 | #[serde(rename_all = "camelCase")] | |
| 2803 | pub struct AccountDetail { | |
| 2804 | pub summary: AccountSummary, | |
| 2805 | /// Each workspace's limit, for an enterprise. | |
| 2806 | pub workspaces: Vec<Limit>, | |
| 2807 | pub ledger: Vec<LedgerEntry>, | |
| 2808 | pub audit: Vec<AdminAction>, | |
| 2809 | } | |
| 2810 | ||
| 2811 | /// `admin_set_terms`. Returns `Outcome<BillingAccount>`. | |
| 2812 | #[derive(Debug, Serialize, Deserialize)] | |
| 2813 | pub struct AdminSetTermsArgs { | |
| 2814 | pub id: String, | |
| 2815 | pub terms: Terms, | |
| 2816 | pub by: String, | |
| 2817 | } | |
| 2818 | ||
| 2819 | /// `admin_create_enterprise`. Returns `Outcome<BillingAccount>`. | |
| 2820 | #[derive(Debug, Serialize, Deserialize)] | |
| 2821 | pub struct AdminCreateEnterpriseArgs { | |
| 2822 | pub name: String, | |
| 2823 | pub workspaces: Vec<String>, | |
| 2824 | pub by: String, | |
| 2825 | } | |
| 2826 | ||
| 2827 | /// `admin_attach`: moves a workspace onto an enterprise account, or back | |
| 2828 | /// onto its own with `account: None`. Returns `Outcome<BillingAccount>`. | |
| 2829 | #[derive(Debug, Serialize, Deserialize)] | |
| 2830 | pub struct AdminAttachArgs { | |
| 2831 | pub workspace: String, | |
| 2832 | pub account: Option<String>, | |
| 2833 | pub by: String, | |
| 2834 | } | |
| 2835 | ||
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 2836 | /// Why g1t gave a workspace credit. |
| 2837 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] | |
| 2838 | #[serde(rename_all = "snake_case")] | |
| 2839 | pub enum CreditKind { | |
| 2840 | /// Marketing: a welcome, a referral, an event. Given away when spent. | |
| 2841 | Promotional, | |
| 2842 | /// An apology, or accidental usage forgiven. Given away when spent. | |
| 2843 | #[default] | |
| 2844 | Goodwill, | |
| 2845 | /// Money back for something that went wrong. Not given away: it gives | |
| 2846 | /// back money already paid, so it comes off what was paid on the day | |
| 2847 | /// it refunds, and what it pays for later is paid for. | |
| 2848 | Refund, | |
| 2849 | /// Bought by the workspace (prepaid AI): money paid in up front, owed | |
| 2850 | /// as usage until spent. What it pays for is paid for, never given. | |
| 2851 | /// Staff never give it; its ledger line is a payment, not `crd…`. | |
| 2852 | Purchased, | |
| 2853 | } | |
| 2854 | ||
| 2855 | impl CreditKind { | |
| 2856 | pub fn as_str(self) -> &'static str { | |
| 2857 | match self { | |
| 2858 | CreditKind::Promotional => "promotional", | |
| 2859 | CreditKind::Goodwill => "goodwill", | |
| 2860 | CreditKind::Refund => "refund", | |
| 2861 | CreditKind::Purchased => "purchased", | |
| 2862 | } | |
| 2863 | } | |
| 2864 | ||
| 2865 | pub fn parse(text: &str) -> Option<CreditKind> { | |
| 2866 | match text { | |
| 2867 | "promotional" => Some(CreditKind::Promotional), | |
| 2868 | "goodwill" => Some(CreditKind::Goodwill), | |
| 2869 | "refund" => Some(CreditKind::Refund), | |
| 2870 | "purchased" => Some(CreditKind::Purchased), | |
| 2871 | _ => None, | |
| 2872 | } | |
| 2873 | } | |
| 2874 | ||
| 2875 | /// As people read it: `Promotional`. | |
| 2876 | pub fn label(self) -> &'static str { | |
| 2877 | match self { | |
| 2878 | CreditKind::Promotional => "Promotional", | |
| 2879 | CreditKind::Goodwill => "Goodwill", | |
| 2880 | CreditKind::Refund => "Refund", | |
| 2881 | CreditKind::Purchased => "Purchased", | |
| 2882 | } | |
| 2883 | } | |
| 2884 | } | |
| 2885 | ||
| 2886 | /// `admin_credit`: credit g1t gives a workspace: promotional, goodwill or a | |
| 2887 | /// refund, with a note, and optionally an expiry. It is spent before | |
| 2888 | /// anything paid in advance, the soonest-expiring first. The workspace's | |
| 2889 | /// owners are emailed. Returns `Outcome<LedgerEntry>`. | |
| Billing accounts, terms and enterprises; g1t is no longer free | 2890 | #[derive(Debug, Serialize, Deserialize)] |
| 2891 | pub struct AdminCreditArgs { | |
| 2892 | pub workspace: String, | |
| 2893 | pub amount_micros: i64, | |
| 2894 | pub note: String, | |
| 2895 | pub by: String, | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 2896 | #[serde(default)] |
| 2897 | pub kind: CreditKind, | |
| 2898 | /// RFC 3339; unused credit stops counting then. Never for a refund. | |
| 2899 | #[serde(default)] | |
| 2900 | pub expires_at: Option<String>, | |
| 2901 | /// A refund: what it refunds, in a line, and the day of it | |
| 2902 | /// (`YYYY-MM-DD`; today if absent). | |
| 2903 | #[serde(default)] | |
| 2904 | pub refund_for: Option<String>, | |
| 2905 | #[serde(default)] | |
| 2906 | pub refund_day: Option<String>, | |
| 2907 | } | |
| 2908 | ||
| 2909 | /// One credit g1t gave, with what of it was used: spent on usage, the | |
| 2910 | /// soonest-expiring grant first, before anything paid in advance. | |
| 2911 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 2912 | #[serde(rename_all = "camelCase")] | |
| 2913 | pub struct CreditGrant { | |
| 2914 | /// `crd_…`, the grant's ledger reference. | |
| 2915 | pub id: String, | |
| 2916 | pub workspace: String, | |
| 2917 | pub kind: CreditKind, | |
| 2918 | pub amount_micros: i64, | |
| 2919 | pub used_micros: i64, | |
| 2920 | /// What can still be spent: nothing once it expired or was revoked. | |
| 2921 | pub left_micros: i64, | |
| 2922 | pub note: String, | |
| 2923 | #[serde(default)] | |
| 2924 | pub refund_for: Option<String>, | |
| 2925 | #[serde(default)] | |
| 2926 | pub refund_day: Option<String>, | |
| 2927 | pub expires_at: Option<String>, | |
| 2928 | pub created_by: String, | |
| 2929 | pub created_at: String, | |
| 2930 | /// `open`, `used`, `expired` or `revoked`. | |
| 2931 | pub state: String, | |
| 2932 | #[serde(default)] | |
| 2933 | pub closed_at: Option<String>, | |
| 2934 | #[serde(default)] | |
| 2935 | pub closed_note: Option<String>, | |
| 2936 | #[serde(default)] | |
| 2937 | pub closed_by: Option<String>, | |
| 2938 | /// What expiring or revoking took off the balance. | |
| 2939 | #[serde(default)] | |
| 2940 | pub closed_micros: i64, | |
| 2941 | /// What it pays for: `all` usage, or `models` only (agent runs' model | |
| 2942 | /// cost), which is spent first. | |
| 2943 | #[serde(default)] | |
| 2944 | pub scope: String, | |
| 2945 | /// Where it came from: `staff`, `purchase` or `promo_code`. | |
| 2946 | #[serde(default)] | |
| 2947 | pub source: String, | |
| 2948 | } | |
| 2949 | ||
| 2950 | /// `credits` (`Outcome<Credits>`, `AccountArgs`): a workspace's credits from | |
| 2951 | /// g1t, newest first, for its members. | |
| 2952 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 2953 | #[serde(rename_all = "camelCase")] | |
| 2954 | pub struct Credits { | |
| 2955 | pub grants: Vec<CreditGrant>, | |
| 2956 | /// What is left to spend, in all. | |
| 2957 | pub left_micros: i64, | |
| 2958 | } | |
| 2959 | ||
| 2960 | /// `admin_credits`: every credit g1t gave, newest first, filtered. Returns | |
| 2961 | /// `AdminCredits`. | |
| 2962 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 2963 | pub struct AdminCreditsArgs { | |
| 2964 | #[serde(default)] | |
| 2965 | pub workspace: Option<String>, | |
| 2966 | #[serde(default)] | |
| 2967 | pub kind: Option<CreditKind>, | |
| 2968 | /// `YYYY-MM`: given that month. | |
| 2969 | #[serde(default)] | |
| 2970 | pub month: Option<String>, | |
| 2971 | /// Given by this member of staff. | |
| 2972 | #[serde(default)] | |
| 2973 | pub by: Option<String>, | |
| 2974 | } | |
| 2975 | ||
| 2976 | /// One month's credits of one kind: given, used on usage that month, and | |
| 2977 | /// taken back unused (expired or revoked). | |
| 2978 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 2979 | #[serde(rename_all = "camelCase")] | |
| 2980 | pub struct CreditMonth { | |
| 2981 | pub month: String, | |
| 2982 | pub kind: CreditKind, | |
| 2983 | pub given_micros: i64, | |
| 2984 | pub grants: u32, | |
| 2985 | pub used_micros: i64, | |
| 2986 | pub expired_micros: i64, | |
| 2987 | pub revoked_micros: i64, | |
| 2988 | } | |
| 2989 | ||
| 2990 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 2991 | #[serde(rename_all = "camelCase")] | |
| 2992 | pub struct AdminCredits { | |
| 2993 | /// At most 200. | |
| 2994 | pub grants: Vec<CreditGrant>, | |
| 2995 | /// The last 12 months, newest first, whatever the month filter. | |
| 2996 | pub months: Vec<CreditMonth>, | |
| 2997 | /// Who has given credit, for the filter. | |
| 2998 | pub staff: Vec<String>, | |
| 2999 | } | |
| 3000 | ||
| 3001 | /// `admin_revoke_credit`: what is left of a grant, taken off the balance, | |
| 3002 | /// with why. Returns `Outcome<CreditGrant>`. | |
| 3003 | #[derive(Debug, Serialize, Deserialize)] | |
| 3004 | pub struct AdminRevokeCreditArgs { | |
| 3005 | pub id: String, | |
| 3006 | pub note: String, | |
| 3007 | pub by: String, | |
| Billing accounts, terms and enterprises; g1t is no longer free | 3008 | } |
| 3009 | ||
| sudo: reset a test workspace's billing so it starts again as a new customer; refused on a live Stripe key, for comped workspaces and for an enterprise's | 3010 | /// `admin_reset_billing`: a test workspace's billing wiped, so it starts |
| 3011 | /// again as a new customer. Only while billing runs on Stripe's test key; | |
| 3012 | /// never a comped workspace or one an enterprise pays for. `confirm` is the | |
| 3013 | /// workspace's slug typed out. Returns `Outcome<BillingReset>`. | |
| 3014 | #[derive(Debug, Serialize, Deserialize)] | |
| 3015 | pub struct AdminResetBillingArgs { | |
| 3016 | pub workspace: String, | |
| 3017 | pub confirm: String, | |
| 3018 | pub note: String, | |
| 3019 | pub by: String, | |
| 3020 | } | |
| 3021 | ||
| 3022 | /// What a reset removed. | |
| 3023 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 3024 | #[serde(rename_all = "camelCase")] | |
| 3025 | pub struct BillingReset { | |
| 3026 | pub workspace: String, | |
| 3027 | pub rows: u32, | |
| sudo: a billing reset runs the costs analysis again so every figure is fresh; every submit button shows it is working (CSS only); no margin percentage on less than a cent sold | 3028 | /// Whether the costs analysis ran again after it, so the margin |
| 3029 | /// figures no longer hold the workspace's past usage. | |
| 3030 | #[serde(default)] | |
| 3031 | pub refreshed: bool, | |
| sudo: reset a test workspace's billing so it starts again as a new customer; refused on a live Stripe key, for comped workspaces and for an enterprise's | 3032 | } |
| 3033 | ||
| Billing accounts, terms and enterprises; g1t is no longer free | 3034 | /// One change made in sudo. |
| 3035 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 3036 | #[serde(rename_all = "camelCase")] | |
| 3037 | pub struct AdminAction { | |
| 3038 | pub id: String, | |
| 3039 | pub account: String, | |
| 3040 | pub action: String, | |
| 3041 | pub detail: String, | |
| 3042 | pub by: String, | |
| 3043 | pub created_at: String, | |
| 3044 | } | |
| 3045 | ||
| Deploy scripts live in the repository | 3046 | /// What a feature's plan costs and includes. |
| 3047 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 3048 | #[serde(rename_all = "camelCase")] | |
| 3049 | pub struct Plan { | |
| 3050 | pub feature: Feature, | |
| 3051 | pub title: String, | |
| Merge Stripe Tax, the card fee on card payments, and one free workspace per person | 3052 | /// Charged every month while the plan is on, in cents, excluding tax. |
| Deploy scripts live in the repository | 3053 | pub monthly_cents: u32, |
| Merge Stripe Tax, the card fee on card payments, and one free workspace per person | 3054 | /// The card processing fee on top each month, in cents (0 when the |
| 3055 | /// fee is off). Excluding tax, like the price. | |
| 3056 | #[serde(default)] | |
| 3057 | pub card_fee_cents: u32, | |
| Deploy scripts live in the repository | 3058 | /// What the monthly price includes, one line each, for people to read. |
| 3059 | pub includes: Vec<String>, | |
| 3060 | /// How usage past the allowance is charged, for people to read. | |
| 3061 | pub overage: String, | |
| 3062 | } | |
| 3063 | ||
| 3064 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 3065 | #[serde(rename_all = "snake_case")] | |
| 3066 | pub enum SubscriptionStatus { | |
| 3067 | /// Paid up; the feature works. | |
| 3068 | Active, | |
| 3069 | /// Paid up to the end of the period, and ends then. | |
| 3070 | Canceling, | |
| 3071 | /// The last payment failed; the feature is off until it is paid. | |
| 3072 | PastDue, | |
| 3073 | /// Ended. | |
| 3074 | Canceled, | |
| 3075 | } | |
| 3076 | ||
| 3077 | impl SubscriptionStatus { | |
| 3078 | /// Whether the feature works in this state. | |
| 3079 | pub fn on(self) -> bool { | |
| 3080 | matches!(self, SubscriptionStatus::Active | SubscriptionStatus::Canceling) | |
| 3081 | } | |
| 3082 | } | |
| 3083 | ||
| 3084 | /// A workspace's plan for one feature. | |
| 3085 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 3086 | #[serde(rename_all = "camelCase")] | |
| 3087 | pub struct Subscription { | |
| 3088 | pub feature: Feature, | |
| 3089 | pub status: SubscriptionStatus, | |
| 3090 | /// RFC 3339: when the period paid for ends, and the plan renews or | |
| 3091 | /// ends. | |
| 3092 | pub period_end: Option<String>, | |
| 3093 | /// Username of whoever turned it on. | |
| 3094 | pub started_by: String, | |
| 3095 | /// RFC 3339. | |
| 3096 | pub started_at: String, | |
| 3097 | } | |
| 3098 | ||
| 3099 | /// A feature as a workspace sees it: what it costs, and its plan if it has | |
| 3100 | /// one. | |
| 3101 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 3102 | #[serde(rename_all = "camelCase")] | |
| 3103 | pub struct FeatureState { | |
| 3104 | pub plan: Plan, | |
| 3105 | pub subscription: Option<Subscription>, | |
| 3106 | /// Whether the feature works for the workspace now. | |
| 3107 | pub on: bool, | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 3108 | /// On without a plan: comped terms, or given by g1t. Nothing to pay |
| 3109 | /// and nothing to turn off. | |
| 3110 | #[serde(default)] | |
| 3111 | pub included: bool, | |
| Deploy scripts live in the repository | 3112 | } |
| 3113 | ||
| 3114 | /// `features`: every paid feature and the workspace's plan for each. | |
| 3115 | /// Members only. Returns `Outcome<Vec<FeatureState>>`. | |
| 3116 | #[derive(Debug, Serialize, Deserialize)] | |
| 3117 | pub struct FeaturesArgs { | |
| 3118 | pub workspace: String, | |
| 3119 | pub viewer: Viewer, | |
| 3120 | } | |
| 3121 | ||
| 3122 | /// `subscribe`: starts the card page for a feature's monthly plan. Owners | |
| 3123 | /// only. Returns `Outcome<Checkout>`; the page's id comes back to | |
| 3124 | /// `return_url` as `session`, for `confirm_subscription`. | |
| 3125 | #[derive(Debug, Serialize, Deserialize)] | |
| 3126 | #[serde(rename_all = "camelCase")] | |
| 3127 | pub struct SubscribeArgs { | |
| 3128 | pub actor: User, | |
| 3129 | pub workspace: String, | |
| 3130 | pub feature: Feature, | |
| 3131 | pub return_url: String, | |
| 3132 | } | |
| 3133 | ||
| 3134 | /// `confirm_subscription`: turns the feature on once the processor says | |
| 3135 | /// the plan was paid for. Safe to call any number of times. Returns | |
| 3136 | /// `Outcome<FeatureState>`. | |
| 3137 | #[derive(Debug, Serialize, Deserialize)] | |
| 3138 | pub struct ConfirmSubscriptionArgs { | |
| 3139 | pub workspace: String, | |
| 3140 | pub viewer: Viewer, | |
| 3141 | pub session: String, | |
| 3142 | } | |
| 3143 | ||
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 3144 | /// `admin_log`: a staff change another service made to a workspace, kept |
| 3145 | /// in sudo's audit log with billing's own (`admin_audit`). For identity's | |
| 3146 | /// restores and purges of deleted workspaces. Returns `bool`. | |
| 3147 | #[derive(Debug, Serialize, Deserialize)] | |
| 3148 | pub struct AdminLogArgs { | |
| 3149 | pub workspace: String, | |
| 3150 | pub action: String, | |
| 3151 | pub detail: String, | |
| 3152 | /// The staff member's email. | |
| 3153 | pub by: String, | |
| 3154 | } | |
| 3155 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 3156 | /// `close_workspace`: settles a workspace that is about to be deleted. |
| 3157 | /// Owners only. Refused while it has an invoice that failed, while it | |
| 3158 | /// holds prepaid credit, or while it owes money it cannot be charged for | |
| 3159 | /// now; otherwise what it owes is invoiced to its card at once (no | |
| 3160 | /// minimum), its plan is cancelled at Stripe straight away, and its | |
| 3161 | /// account is marked closed, so the month-end close, autopay and limit | |
| 3162 | /// warnings pass it by. Its ledger, invoices and statements stay. With | |
| 3163 | /// `dry_run`, only says whether it could, changing nothing. Returns | |
| 3164 | /// `Outcome<bool>`. | |
| 3165 | #[derive(Debug, Serialize, Deserialize)] | |
| 3166 | #[serde(rename_all = "camelCase")] | |
| 3167 | pub struct CloseWorkspaceArgs { | |
| 3168 | pub actor: User, | |
| 3169 | pub workspace: String, | |
| 3170 | #[serde(default)] | |
| 3171 | pub dry_run: bool, | |
| 3172 | } | |
| 3173 | ||
| Deploy scripts live in the repository | 3174 | /// `cancel_subscription` (`resume` false) ends a plan at the end of the |
| 3175 | /// period paid for; with `resume` true, takes that back. Owners only. | |
| 3176 | /// Returns `Outcome<FeatureState>`. | |
| 3177 | #[derive(Debug, Serialize, Deserialize)] | |
| 3178 | pub struct CancelSubscriptionArgs { | |
| 3179 | pub actor: User, | |
| 3180 | pub workspace: String, | |
| 3181 | pub feature: Feature, | |
| 3182 | #[serde(default)] | |
| 3183 | pub resume: bool, | |
| 3184 | } | |
| 3185 | ||
| 3186 | /// `has_feature`: whether a feature works for a workspace now, asked by the | |
| 3187 | /// service that provides it before doing paid work. Returns | |
| 3188 | /// `Outcome<bool>`: a failure, with the reason to show, when it does not. | |
| 3189 | /// True everywhere when no card processor is configured. | |
| 3190 | #[derive(Debug, Serialize, Deserialize)] | |
| 3191 | pub struct HasFeatureArgs { | |
| 3192 | pub workspace: String, | |
| 3193 | pub feature: Feature, | |
| 3194 | } | |
| 3195 | ||
| Merge Stripe Tax, the card fee on card payments, and one free workspace per person | 3196 | /// `free_workspaces`: which of `workspaces` are free, that is on no paid |
| 3197 | /// plan. Paid is the g1t plan, an enterprise's terms, or a discount of | |
| 3198 | /// 100% (g1t's own workspaces). Identity asks before a workspace is made | |
| 3199 | /// (a person owns at most one free workspace) and before anyone is added | |
| 3200 | /// to one (a free workspace cannot invite). Returns `Vec<String>`, the | |
| 3201 | /// free ones, lower-cased; none where payments are not set up. | |
| 3202 | #[derive(Debug, Serialize, Deserialize)] | |
| 3203 | pub struct FreeWorkspacesArgs { | |
| 3204 | pub workspaces: Vec<String>, | |
| 3205 | } | |
| 3206 | ||
| Deploy scripts live in the repository | 3207 | /// `charge_feature`: usage of a feature past its plan's allowance, charged |
| 3208 | /// from the workspace's credit at cost plus the margin, whatever | |
| 3209 | /// `FREE_WHILE_BUILDING` says. Called by the service that provides it. | |
| 3210 | /// Charged once per `reference`. Returns `Outcome<bool>`: false if that | |
| 3211 | /// reference was charged before. | |
| 3212 | #[derive(Debug, Serialize, Deserialize)] | |
| 3213 | #[serde(rename_all = "camelCase")] | |
| 3214 | pub struct ChargeFeatureArgs { | |
| 3215 | pub workspace: String, | |
| 3216 | pub feature: Feature, | |
| 3217 | /// What it cost g1t, in millionths of a dollar, before the margin. | |
| 3218 | pub cost_micros: i64, | |
| 3219 | pub description: String, | |
| 3220 | /// `namespace/name`, when the usage was one repository's. | |
| 3221 | pub repo: Option<String>, | |
| 3222 | /// Unique to this charge, e.g. `deployments/acme/2026-10`. | |
| 3223 | pub reference: String, | |
| Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put | 3224 | /// For a build: how long it ran. The plan's included build time this |
| 3225 | /// month pays for what it can, and only the rest of `cost_micros` is | |
| 3226 | /// charged. | |
| 3227 | #[serde(default)] | |
| 3228 | pub build_seconds: Option<u32>, | |
| Deploy scripts live in the repository | 3229 | } |
| 3230 | ||
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 3231 | // --------------------------------------------------------------------- |
| 3232 | // Costs and margin: what Cloudflare charges g1t against what g1t | |
| 3233 | // charges (billing's costs.rs, margin.rs and pricing.rs). Staff only. | |
| 3234 | // --------------------------------------------------------------------- | |
| 3235 | ||
| 3236 | /// `admin_costs`: the Costs & margin page. Returns `CostsReport`. | |
| 3237 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 3238 | pub struct AdminCostsArgs { | |
| 3239 | /// How many days back, 7 to 90; 30 when absent. | |
| 3240 | #[serde(default)] | |
| 3241 | pub days: Option<u32>, | |
| 3242 | } | |
| 3243 | ||
| 3244 | /// One of g1t's products on one day. | |
| 3245 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 3246 | #[serde(rename_all = "camelCase")] | |
| 3247 | pub struct CostDay { | |
| 3248 | pub day: String, | |
| 3249 | pub bucket: String, | |
| 3250 | /// What Cloudflare charged g1t. | |
| 3251 | pub cf_cost_micros: i64, | |
| 3252 | /// What g1t's meters recorded it cost, at the price book's cost. | |
| 3253 | pub own_cost_micros: i64, | |
| 3254 | /// What customers were charged for it at price, before included | |
| 3255 | /// usage, trials and pools paid for some. | |
| 3256 | pub value_micros: i64, | |
| 3257 | /// Of that, what workspaces paid. | |
| 3258 | pub cash_micros: i64, | |
| 3259 | } | |
| 3260 | ||
| 3261 | /// One product over the range. | |
| 3262 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 3263 | #[serde(rename_all = "camelCase")] | |
| 3264 | pub struct ProductMargin { | |
| 3265 | pub bucket: String, | |
| 3266 | pub title: String, | |
| 3267 | /// The cost the margin is taken from: Cloudflare's bill, or g1t's own | |
| 3268 | /// figure for what Cloudflare does not bill (models). | |
| 3269 | pub cost_micros: i64, | |
| 3270 | pub cf_cost_micros: i64, | |
| 3271 | pub own_cost_micros: i64, | |
| 3272 | pub value_micros: i64, | |
| 3273 | pub margin_micros: i64, | |
| 3274 | pub margin_percent: Option<f64>, | |
| 3275 | /// `cloudflare` or `ledger`. | |
| 3276 | pub cost_source: String, | |
| 3277 | /// Running g1t itself, paid for by the plan. | |
| 3278 | pub overhead: bool, | |
| 3279 | } | |
| 3280 | ||
| Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it | 3281 | /// All of g1t over the range: money in against every cost, and against |
| 3282 | /// the cost of what was sold (every cost less what g1t gave away). | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 3283 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] |
| 3284 | #[serde(rename_all = "camelCase")] | |
| 3285 | pub struct OverallMargin { | |
| 3286 | /// What workspaces paid for usage, and for the plan. | |
| 3287 | pub usage_micros: i64, | |
| 3288 | pub plans_micros: i64, | |
| 3289 | pub cost_micros: i64, | |
| 3290 | pub margin_micros: i64, | |
| 3291 | pub margin_percent: Option<f64>, | |
| Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it | 3292 | /// Of `cost_micros`, what went on usage g1t gave away on purpose: |
| 3293 | /// comped workspaces, free periods, the trial and the open-source pool. | |
| 3294 | #[serde(default)] | |
| 3295 | pub given_micros: i64, | |
| 3296 | /// Money in against `cost_micros - given_micros`. | |
| 3297 | #[serde(default)] | |
| 3298 | pub sold_margin_micros: i64, | |
| 3299 | #[serde(default)] | |
| 3300 | pub sold_margin_percent: Option<f64>, | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 3301 | /// What was sold, apart: usage (`usage_micros` against what that usage |
| 3302 | /// cost, less what was given), running g1t (`plans_micros` against the | |
| 3303 | /// platform's cost, less its given share) and what no mapping names. | |
| 3304 | #[serde(default)] | |
| 3305 | pub usage_cost_micros: i64, | |
| 3306 | #[serde(default)] | |
| 3307 | pub usage_margin_micros: i64, | |
| 3308 | #[serde(default)] | |
| 3309 | pub usage_margin_percent: Option<f64>, | |
| 3310 | #[serde(default)] | |
| 3311 | pub running_cost_micros: i64, | |
| 3312 | #[serde(default)] | |
| 3313 | pub unmapped_cost_micros: i64, | |
| 3314 | /// `given_micros` by why: comped workspaces, free use (free periods, | |
| 3315 | /// free allowances, overruns g1t covered), the trial, the open-source pool. | |
| 3316 | #[serde(default)] | |
| 3317 | pub given_comped_micros: i64, | |
| 3318 | #[serde(default)] | |
| 3319 | pub given_free_micros: i64, | |
| 3320 | #[serde(default)] | |
| 3321 | pub given_trial_micros: i64, | |
| 3322 | #[serde(default)] | |
| 3323 | pub given_pool_micros: i64, | |
| Merge branch 'worktree-agent-a633ac0f7f66d419d' | 3324 | /// What discounts on an account's terms took below cost plus the |
| 3325 | /// margin: given, so a discounted sale is not margin lost. | |
| 3326 | #[serde(default)] | |
| 3327 | pub given_discount_micros: i64, | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 3328 | /// Credits from g1t spent on usage, by kind: given, so usage paid for |
| 3329 | /// with them is never money in. Refunds are not here: they come off | |
| 3330 | /// money in on the day they refund. | |
| 3331 | #[serde(default)] | |
| 3332 | pub given_credit_promotional_micros: i64, | |
| 3333 | #[serde(default)] | |
| 3334 | pub given_credit_goodwill_micros: i64, | |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 3335 | /// What testing resets wiped that g1t paid for (`reset_costs`): the |
| 3336 | /// model calls and Cloudflare usage still happened, so their cost is | |
| 3337 | /// given, never a leak. | |
| 3338 | #[serde(default)] | |
| 3339 | pub given_reset_micros: i64, | |
| Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052) | 3340 | /// What workspaces were charged while payments were not live (Stripe's |
| 3341 | /// test mode): no real money came in, so it is given, never money in. | |
| 3342 | #[serde(default)] | |
| 3343 | pub given_unpaid_micros: i64, | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 3344 | /// Credits over the range: given (every kind), spent on usage, and |
| 3345 | /// refunds' money given back. | |
| 3346 | #[serde(default)] | |
| 3347 | pub credits_given_micros: i64, | |
| 3348 | #[serde(default)] | |
| 3349 | pub credits_used_micros: i64, | |
| 3350 | #[serde(default)] | |
| 3351 | pub credits_refunded_micros: i64, | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 3352 | /// `cost_micros` by who g1t pays: Cloudflare's bill (billed amounts, |
| 3353 | /// after the included allowances), and model providers (the ledger's | |
| 3354 | /// cost of the tokens, which Cloudflare's bill does not show). | |
| Costs: the plan's included usage counts as paid for the usage it covered, out of what plans leave for running g1t; the run button shows it is running with CSS alone (sudo ships no JavaScript) | 3355 | /// What the plan's included usage paid for, at price (the ledger's |
| 3356 | /// `credit_micros`, comped workspaces left out): money in for usage, | |
| 3357 | /// paid out of `plans_micros`. | |
| 3358 | #[serde(default)] | |
| 3359 | pub included_micros: i64, | |
| Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running | 3360 | #[serde(default)] |
| 3361 | pub cloudflare_cost_micros: i64, | |
| 3362 | #[serde(default)] | |
| 3363 | pub models_cost_micros: i64, | |
| Merge Stripe Tax, the card fee on card payments, and one free workspace per person | 3364 | /// Tax collected with payments over the range, net of refunds: owed to |
| 3365 | /// the tax authorities, never in cash or revenue. | |
| 3366 | #[serde(default)] | |
| 3367 | pub tax_collected_micros: i64, | |
| 3368 | /// Card processing fees passed on with card payments, net of refunds: | |
| 3369 | /// they pay Stripe's fee, so they are not revenue either. | |
| 3370 | #[serde(default)] | |
| 3371 | pub card_fees_micros: i64, | |
| Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052) | 3372 | /// Cloudflare's subscriptions over the range: each day's share of the |
| 3373 | /// billing cycle it is in (a month's price over the cycle's days), the | |
| 3374 | /// same accrual g1t's own spend uses for the calendar month. | |
| 3375 | #[serde(default)] | |
| 3376 | pub subscriptions_micros: i64, | |
| 3377 | /// What AI Gateway priced g1t's own provider traffic at over the range | |
| 3378 | /// (Cloudflare-billed requests left out): what the providers bill, to | |
| 3379 | /// set beside `models_cost_micros`, the ledger's figure. | |
| 3380 | #[serde(default)] | |
| 3381 | pub gateway_cost_micros: i64, | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 3382 | } |
| 3383 | ||
| 3384 | /// A count, cost or leak that does not add up. | |
| 3385 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 3386 | #[serde(rename_all = "camelCase")] | |
| 3387 | pub struct CostDrift { | |
| 3388 | pub bucket: String, | |
| 3389 | pub title: String, | |
| 3390 | /// `count` (units g1t counted against Cloudflare's), `cost` (the bill | |
| Merge branch 'worktree-agent-a633ac0f7f66d419d' | 3391 | /// against the price book's cost of the same usage; for models, what AI |
| 3392 | /// Gateway priced g1t's provider traffic at against the ledger's model | |
| 3393 | /// cost), `unpriced` (model usage AI Gateway put no price on, so its | |
| 3394 | /// cost is not the providers'), or `leak`. | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 3395 | pub kind: String, |
| 3396 | pub ours: f64, | |
| 3397 | pub cloudflare: f64, | |
| 3398 | pub delta_percent: Option<f64>, | |
| 3399 | pub detail: String, | |
| 3400 | pub found_at: String, | |
| 3401 | } | |
| 3402 | ||
| 3403 | /// A margin alert, open while its condition lasts. | |
| 3404 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 3405 | #[serde(rename_all = "camelCase")] | |
| 3406 | pub struct MarginAlert { | |
| 3407 | pub id: String, | |
| 3408 | /// `margin`, `overall`, `leak`, `drift` or `workspace`. | |
| 3409 | pub kind: String, | |
| 3410 | /// The product, or the workspace. | |
| 3411 | pub subject: String, | |
| 3412 | pub detail: String, | |
| 3413 | pub since: String, | |
| 3414 | pub opened_at: String, | |
| 3415 | pub emailed_at: Option<String>, | |
| 3416 | } | |
| 3417 | ||
| 3418 | /// A change to a price the reconciler measured. | |
| 3419 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 3420 | #[serde(rename_all = "camelCase")] | |
| 3421 | pub struct PriceProposal { | |
| 3422 | pub id: String, | |
| 3423 | pub meter: String, | |
| 3424 | pub title: String, | |
| 3425 | pub unit: String, | |
| 3426 | pub current_cost_micros: f64, | |
| 3427 | pub proposed_cost_micros: f64, | |
| 3428 | pub change_percent: f64, | |
| 3429 | pub markup_percent: u32, | |
| 3430 | pub reason: String, | |
| 3431 | /// `keeper` or `reconciler`. | |
| 3432 | pub source: String, | |
| 3433 | /// Far off the current cost: look before approving. | |
| 3434 | pub suspect: bool, | |
| 3435 | /// `open`, `applied`, `approved`, `rejected` or `superseded`. | |
| 3436 | pub status: String, | |
| 3437 | pub created_at: String, | |
| 3438 | pub decided_at: Option<String>, | |
| 3439 | pub decided_by: Option<String>, | |
| 3440 | pub note: Option<String>, | |
| 3441 | /// When it takes or took effect, once approved or applied. | |
| 3442 | pub effective_at: Option<String>, | |
| 3443 | } | |
| 3444 | ||
| 3445 | /// One version of one meter's price. Never changed once written. | |
| 3446 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 3447 | #[serde(rename_all = "camelCase")] | |
| 3448 | pub struct PriceVersion { | |
| 3449 | pub id: String, | |
| 3450 | pub meter: String, | |
| 3451 | pub version: u32, | |
| 3452 | pub cost_micros: f64, | |
| 3453 | pub markup_percent: u32, | |
| 3454 | pub price_micros: f64, | |
| 3455 | pub effective_at: String, | |
| 3456 | pub reason: String, | |
| 3457 | pub created_by: String, | |
| 3458 | /// When the price book took it on; absent while it waits for its date. | |
| 3459 | pub applied_at: Option<String>, | |
| Merge costs and margin review: gateway query, own spend, discount meters, superseded rises | 3460 | /// What `cost_micros` is: `cost`, what g1t pays for a unit (a |
| 3461 | /// provider's dollar passed on at cost is one); `rate`, a price g1t | |
| 3462 | /// sets with no cost behind it (the agent rate, security activation), | |
| 3463 | /// so its cost column is its price; `weight`, a multiplier in | |
| 3464 | /// millionths, not money (the agent rate's token weights). | |
| 3465 | #[serde(default)] | |
| 3466 | pub basis: String, | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 3467 | } |
| 3468 | ||
| 3469 | /// What a workspace cost g1t over the range, Cloudflare's costs shared | |
| 3470 | /// out by g1t's own meters, against what it paid. | |
| 3471 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 3472 | #[serde(rename_all = "camelCase")] | |
| 3473 | pub struct WorkspaceCost { | |
| 3474 | pub workspace: String, | |
| 3475 | pub cost_micros: i64, | |
| 3476 | pub revenue_micros: i64, | |
| Costs: margin is measured on what was sold; comped workspaces, free periods, the trial and the pools are given away, a budget shown beside it | 3477 | /// Of `cost_micros`, what g1t gave away. |
| 3478 | #[serde(default)] | |
| 3479 | pub given_micros: i64, | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 3480 | /// One of g1t's own (comped) workspaces. |
| 3481 | pub internal: bool, | |
| 3482 | } | |
| 3483 | ||
| 3484 | /// One Cloudflare meter over the range, and the product it is a cost of. | |
| 3485 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 3486 | #[serde(rename_all = "camelCase")] | |
| 3487 | pub struct CostLineSummary { | |
| 3488 | pub product: String, | |
| 3489 | pub meter: String, | |
| 3490 | pub raw_name: String, | |
| 3491 | pub unit: String, | |
| 3492 | pub source: String, | |
| 3493 | pub quantity: f64, | |
| 3494 | pub cost_micros: i64, | |
| 3495 | /// Absent when no mapping claims it. | |
| 3496 | pub bucket: Option<String>, | |
| 3497 | } | |
| 3498 | ||
| 3499 | /// A row of the mapping from Cloudflare's meters to g1t's products. | |
| 3500 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 3501 | #[serde(rename_all = "camelCase")] | |
| 3502 | pub struct CostMapping { | |
| 3503 | pub product: String, | |
| 3504 | pub meter: String, | |
| 3505 | pub bucket: String, | |
| 3506 | pub price_meter: Option<String>, | |
| 3507 | pub own_meter: Option<String>, | |
| 3508 | pub scale_to_own: bool, | |
| 3509 | pub drift_percent: f64, | |
| 3510 | pub note: String, | |
| 3511 | pub updated_at: String, | |
| 3512 | pub updated_by: String, | |
| 3513 | } | |
| 3514 | ||
| 3515 | /// The guardrails on prices and the alerts. | |
| 3516 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 3517 | #[serde(rename_all = "camelCase")] | |
| 3518 | pub struct CostSettings { | |
| 3519 | /// Apply small moves without staff. | |
| 3520 | pub auto_apply: bool, | |
| 3521 | /// The largest move applied without staff, either way, in percent. | |
| 3522 | pub auto_apply_percent: f64, | |
| 3523 | /// Days between telling customers of a rise and charging it. | |
| 3524 | pub notice_days: u32, | |
| 3525 | /// Below this margin, in percent, for `alert_days` days in a row, alert. | |
| 3526 | pub margin_floor_percent: f64, | |
| 3527 | pub alert_days: u32, | |
| 3528 | /// Days with less cost than this say nothing about a margin. | |
| 3529 | pub min_daily_cost_micros: i64, | |
| 3530 | /// A workspace costing more than its revenue times this, over 30 days, | |
| 3531 | /// and at least `anomaly_floor_micros`, is flagged. | |
| 3532 | pub anomaly_factor: f64, | |
| 3533 | pub anomaly_floor_micros: i64, | |
| Merge Stripe Tax, the card fee on card payments, and one free workspace per person | 3534 | /// Pass Stripe's card fee on as its own line on every card payment (the |
| 3535 | /// plan, Security and quality, prepaying, AI credit, auto-reload and | |
| 3536 | /// invoices charged to a card), never on a bank transfer or an invoice | |
| 3537 | /// sent to be paid (`card_fee_percent` and `card_fee_fixed` in the | |
| 3538 | /// price book). On by default. | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 3539 | #[serde(default = "yes")] |
| 3540 | pub card_fee: bool, | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 3541 | } |
| 3542 | ||
| 3543 | impl Default for CostSettings { | |
| 3544 | fn default() -> Self { | |
| 3545 | CostSettings { | |
| 3546 | auto_apply: true, | |
| 3547 | auto_apply_percent: 25.0, | |
| 3548 | notice_days: 14, | |
| 3549 | margin_floor_percent: 10.0, | |
| 3550 | alert_days: 3, | |
| 3551 | min_daily_cost_micros: 100_000, | |
| 3552 | anomaly_factor: 1.0, | |
| 3553 | anomaly_floor_micros: 1_000_000, | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 3554 | card_fee: true, |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 3555 | } |
| 3556 | } | |
| 3557 | } | |
| 3558 | ||
| 3559 | /// The Costs & margin page. | |
| 3560 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 3561 | #[serde(rename_all = "camelCase")] | |
| 3562 | pub struct CostsReport { | |
| 3563 | /// A token to read Cloudflare's bill is set. | |
| 3564 | pub configured: bool, | |
| 3565 | /// When Cloudflare's bill was last read. | |
| 3566 | pub fetched_at: Option<String>, | |
| 3567 | /// The days shown, YYYY-MM-DD. | |
| 3568 | pub since: String, | |
| 3569 | pub until: String, | |
| 3570 | pub days: Vec<CostDay>, | |
| 3571 | pub products: Vec<ProductMargin>, | |
| 3572 | pub overall: OverallMargin, | |
| 3573 | pub drift: Vec<CostDrift>, | |
| 3574 | pub alerts: Vec<MarginAlert>, | |
| 3575 | pub proposals: Vec<PriceProposal>, | |
| 3576 | pub versions: Vec<PriceVersion>, | |
| 3577 | pub top_workspaces: Vec<WorkspaceCost>, | |
| 3578 | pub lines: Vec<CostLineSummary>, | |
| 3579 | pub mappings: Vec<CostMapping>, | |
| 3580 | pub settings: CostSettings, | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 3581 | /// g1t's own spend against its two caps. |
| 3582 | #[serde(default)] | |
| 3583 | pub caps: SpendCaps, | |
| Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052) | 3584 | /// Cloudflare's current billing cycle: its usage cost so far, by meter, |
| 3585 | /// and where it is heading. Absent until the bill has been read. | |
| 3586 | #[serde(default)] | |
| 3587 | pub cycle: Option<CloudflareCycle>, | |
| 3588 | /// The last read of Cloudflare's billable usage: what came back. | |
| 3589 | #[serde(default)] | |
| 3590 | pub bill_read: Option<BillRead>, | |
| 3591 | /// Of the range's cost, what no workspace's usage could carry (a day | |
| 3592 | /// with no usage at all): running g1t, attributed to no one. The | |
| 3593 | /// workspaces' costs and this add up to the cost. | |
| 3594 | #[serde(default)] | |
| 3595 | pub unattributed_micros: i64, | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 3596 | } |
| 3597 | ||
| Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052) | 3598 | /// Cloudflare's billing cycle (monthly, from the day the account's |
| 3599 | /// subscription renews), as Cloudflare's Billable usage page shows it. | |
| 3600 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 3601 | #[serde(rename_all = "camelCase")] | |
| 3602 | pub struct CloudflareCycle { | |
| 3603 | /// The cycle's first and last days, YYYY-MM-DD, UTC. | |
| 3604 | pub start: String, | |
| 3605 | pub end: String, | |
| 3606 | pub days: u32, | |
| 3607 | /// Days from its start to today, today included. | |
| 3608 | pub days_elapsed: u32, | |
| 3609 | /// Usage cost so far, after the included allowances. | |
| 3610 | pub usage_micros: i64, | |
| 3611 | /// `usage_micros` over the days elapsed, times the cycle's days. | |
| 3612 | pub projected_micros: i64, | |
| 3613 | pub average_daily_micros: i64, | |
| 3614 | /// Cloudflare's subscriptions for the cycle (not on the usage bill). | |
| 3615 | pub subscriptions_micros: i64, | |
| 3616 | pub meters: Vec<CycleMeter>, | |
| 3617 | } | |
| 3618 | ||
| 3619 | /// One of Cloudflare's meters over the cycle so far. | |
| 3620 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 3621 | #[serde(rename_all = "camelCase")] | |
| 3622 | pub struct CycleMeter { | |
| 3623 | pub product: String, | |
| 3624 | pub meter: String, | |
| 3625 | pub raw_name: String, | |
| 3626 | pub unit: String, | |
| 3627 | /// What was used. | |
| 3628 | pub quantity: f64, | |
| 3629 | /// What the cycle includes, in the same unit; None without a list price. | |
| 3630 | pub included: Option<f64>, | |
| 3631 | /// Past the included amount, as Cloudflare bills it. | |
| 3632 | pub billable_quantity: f64, | |
| 3633 | pub cost_micros: i64, | |
| 3634 | /// `cloudflare` (the cost Cloudflare put on its lines), `list` (the | |
| 3635 | /// list price past the included amount, while Cloudflare's lines carry | |
| 3636 | /// no cost), or `none` (no list price known: costed at $0). | |
| 3637 | pub basis: String, | |
| 3638 | } | |
| 3639 | ||
| 3640 | /// What the last read of Cloudflare's billable usage got back. | |
| 3641 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 3642 | #[serde(rename_all = "camelCase")] | |
| 3643 | pub struct BillRead { | |
| 3644 | pub read_at: String, | |
| 3645 | pub since: String, | |
| 3646 | pub until: String, | |
| 3647 | pub rows: u32, | |
| 3648 | pub pages: u32, | |
| 3649 | /// Rows with a consumed quantity (`ConsumedQuantity`), and rows with | |
| 3650 | /// only a pricing quantity. | |
| 3651 | pub consumed_rows: u32, | |
| 3652 | pub pricing_only_rows: u32, | |
| 3653 | /// Rows Cloudflare put a cost on. | |
| 3654 | pub costed_rows: u32, | |
| 3655 | } | |
| 3656 | ||
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 3657 | /// What g1t itself pays for, against its caps (billing's `budget`): the |
| 3658 | /// daily breaker on all of it, and each comped account's monthly budget. | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 3659 | /// One of Cloudflare's subscriptions, at what it comes to a month. |
| 3660 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 3661 | #[serde(rename_all = "camelCase")] | |
| 3662 | pub struct FixedCost { | |
| 3663 | pub name: String, | |
| 3664 | pub monthly_micros: i64, | |
| 3665 | } | |
| 3666 | ||
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 3667 | /// At cost, never at price. What sudo's Costs page and its red bar show. |
| 3668 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 3669 | #[serde(rename_all = "camelCase")] | |
| 3670 | pub struct SpendCaps { | |
| 3671 | /// Today (UTC), YYYY-MM-DD, and this month, YYYY-MM. | |
| 3672 | pub day: String, | |
| 3673 | pub month: String, | |
| 3674 | /// What g1t paid for itself today across every workspace: comped work, | |
| 3675 | /// the trial and open-source pools, free workspaces' overruns, and | |
| 3676 | /// anything charged without real money behind it. | |
| 3677 | pub today_micros: i64, | |
| 3678 | /// `PLATFORM_DAILY_SPEND_CAP_MICROS`. Zero: no breaker. | |
| 3679 | pub daily_cap_micros: i64, | |
| 3680 | /// The breaker is open: new hosted-model agent runs that g1t would pay | |
| 3681 | /// for wait until tomorrow (UTC) or until staff lift it. | |
| 3682 | pub tripped: bool, | |
| 3683 | pub tripped_at: Option<String>, | |
| 3684 | /// Staff lifted it for the rest of the day. | |
| 3685 | pub lifted_by: Option<String>, | |
| 3686 | pub lifted_at: Option<String>, | |
| 3687 | pub lift_note: Option<String>, | |
| 3688 | /// This month so far, by what paid: `comped`, `trial`, `oss`, `given`, | |
| 3689 | /// `unpaid`. | |
| 3690 | pub month_buckets: Vec<SpendBucket>, | |
| 3691 | /// Each comped account's monthly budget. | |
| 3692 | pub comped: Vec<CompedBudget>, | |
| 3693 | /// Free workspaces' share of this month's reconciled costs (git, | |
| 3694 | /// storage, platform), through yesterday. | |
| 3695 | pub free_tier_micros: i64, | |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 3696 | /// Cloudflare's subscriptions a month: as read from Cloudflare each |
| 3697 | /// day, else `CLOUDFLARE_FIXED_MONTHLY_MICROS`, an estimate. | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 3698 | pub fixed_monthly_micros: i64, |
| Costs: Cloudflare's subscriptions read from Cloudflare each day, the estimate only until then; sudo's costs split into Costs & margin and Bill & pricing | 3699 | /// `cloudflare` or `estimate`. |
| 3700 | #[serde(default)] | |
| 3701 | pub fixed_source: String, | |
| 3702 | #[serde(default)] | |
| 3703 | pub fixed_read_at: Option<String>, | |
| 3704 | /// Each subscription, when read from Cloudflare. | |
| 3705 | #[serde(default)] | |
| 3706 | pub fixed_items: Vec<FixedCost>, | |
| Merge Cloudflare's usage over its billing cycle: every page read, included amounts once a cycle, a projection, test-mode charges never money in (billing 0052) | 3707 | /// Of `fixed_monthly_micros`, this calendar month's days so far: each |
| 3708 | /// day's share of the billing cycle it is in, today included. | |
| 3709 | #[serde(default)] | |
| 3710 | pub fixed_month_micros: i64, | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 3711 | /// Money in this month, through the last reconciled day. |
| 3712 | pub revenue_micros: i64, | |
| Merge costs and margin review: gateway query, own spend, discount meters, superseded rises | 3713 | /// Of this month's buckets, what was spent on workspaces whose billing |
| 3714 | /// a testing reset later wiped: still g1t's spend, but no longer on | |
| 3715 | /// their ledger, so the reconciled figures have it only where the | |
| 3716 | /// reset kept it (as given away, testing resets). | |
| 3717 | #[serde(default)] | |
| 3718 | pub reset_micros: i64, | |
| 3719 | /// Those workspaces. | |
| 3720 | #[serde(default)] | |
| 3721 | pub reset_workspaces: Vec<String>, | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 3722 | } |
| 3723 | ||
| 3724 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 3725 | #[serde(rename_all = "camelCase")] | |
| 3726 | pub struct SpendBucket { | |
| 3727 | pub bucket: String, | |
| 3728 | pub title: String, | |
| 3729 | pub micros: i64, | |
| 3730 | } | |
| 3731 | ||
| 3732 | /// A comped account's monthly budget: what its work cost g1t this month. | |
| 3733 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 3734 | #[serde(rename_all = "camelCase")] | |
| 3735 | pub struct CompedBudget { | |
| 3736 | pub account: String, | |
| 3737 | pub name: String, | |
| 3738 | pub used_micros: i64, | |
| 3739 | /// Zero: no budget. | |
| 3740 | pub ceiling_micros: i64, | |
| 3741 | /// The ceiling is `COMPED_MONTHLY_CEILING_MICROS`, not the account's own. | |
| 3742 | pub default_ceiling: bool, | |
| 3743 | /// 50, 75, 90, 100, or 0. | |
| 3744 | pub level: u32, | |
| 3745 | } | |
| 3746 | ||
| 3747 | /// `admin_spend_caps`: g1t's own spend against its caps. Returns `SpendCaps`. | |
| 3748 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 3749 | pub struct AdminSpendCapsArgs {} | |
| 3750 | ||
| 3751 | /// `admin_lift_breaker`: lets hosted-model runs start again for the rest | |
| 3752 | /// of today (UTC), with why. Recorded in the audit log. Returns | |
| 3753 | /// `Outcome<SpendCaps>`. | |
| 3754 | #[derive(Debug, Serialize, Deserialize)] | |
| 3755 | pub struct AdminLiftBreakerArgs { | |
| 3756 | pub note: String, | |
| 3757 | pub by: String, | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 3758 | } |
| 3759 | ||
| The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were. | 3760 | // ---- Platform pauses and the usage watcher ---- |
| 3761 | // docs.g1t.sh/guides/deploy-to-cloudflare/#spend-guardrails | |
| Merge platform pause and the hourly usage watcher: staff can pause compute, schedules, indexing or renders for everyone, the watcher emails on a breach and is never blind quietly, and the models proxy holds each run to its cap (billing 0051, integrations 0006) | 3762 | |
| 3763 | /// A g1t-wide pause, set by staff in sudo or by billing's hourly usage | |
| 3764 | /// watcher on a severe breach. Each level is independent. | |
| 3765 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 3766 | #[serde(rename_all = "snake_case")] | |
| 3767 | pub enum PauseLevel { | |
| 3768 | /// Agents, sandboxes, Actions hosted jobs, deploy builds: every | |
| 3769 | /// reservation through billing's `reserve` but embeddings. | |
| 3770 | Compute, | |
| 3771 | /// Actions' cron-triggered runs, and the runner's sweep that starts | |
| 3772 | /// queued agents. | |
| 3773 | Schedules, | |
| 3774 | /// Context embeddings and backfills, and search's backfills. | |
| 3775 | Indexing, | |
| 3776 | /// Social card rendering, which falls back to a static image. | |
| 3777 | Renders, | |
| 3778 | } | |
| 3779 | ||
| 3780 | impl PauseLevel { | |
| 3781 | pub const ALL: [PauseLevel; 4] = [PauseLevel::Compute, PauseLevel::Schedules, PauseLevel::Indexing, PauseLevel::Renders]; | |
| 3782 | ||
| 3783 | pub fn as_str(self) -> &'static str { | |
| 3784 | match self { | |
| 3785 | PauseLevel::Compute => "compute", | |
| 3786 | PauseLevel::Schedules => "schedules", | |
| 3787 | PauseLevel::Indexing => "indexing", | |
| 3788 | PauseLevel::Renders => "renders", | |
| 3789 | } | |
| 3790 | } | |
| 3791 | ||
| 3792 | pub fn parse(text: &str) -> Option<PauseLevel> { | |
| 3793 | PauseLevel::ALL.into_iter().find(|level| level.as_str() == text.trim()) | |
| 3794 | } | |
| 3795 | } | |
| 3796 | ||
| 3797 | /// `platform_pause`: which levels are paused now. Takes nothing. Callers | |
| 3798 | /// keep the answer about 30 seconds; one that cannot read it runs (fails | |
| 3799 | /// open). | |
| 3800 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| 3801 | pub struct PlatformPause { | |
| 3802 | #[serde(default)] | |
| 3803 | pub compute: bool, | |
| 3804 | #[serde(default)] | |
| 3805 | pub schedules: bool, | |
| 3806 | #[serde(default)] | |
| 3807 | pub indexing: bool, | |
| 3808 | #[serde(default)] | |
| 3809 | pub renders: bool, | |
| 3810 | } | |
| 3811 | ||
| 3812 | impl PlatformPause { | |
| 3813 | pub fn is(&self, level: PauseLevel) -> bool { | |
| 3814 | match level { | |
| 3815 | PauseLevel::Compute => self.compute, | |
| 3816 | PauseLevel::Schedules => self.schedules, | |
| 3817 | PauseLevel::Indexing => self.indexing, | |
| 3818 | PauseLevel::Renders => self.renders, | |
| 3819 | } | |
| 3820 | } | |
| 3821 | ||
| 3822 | pub fn set(&mut self, level: PauseLevel, paused: bool) { | |
| 3823 | match level { | |
| 3824 | PauseLevel::Compute => self.compute = paused, | |
| 3825 | PauseLevel::Schedules => self.schedules = paused, | |
| 3826 | PauseLevel::Indexing => self.indexing = paused, | |
| 3827 | PauseLevel::Renders => self.renders = paused, | |
| 3828 | } | |
| 3829 | } | |
| 3830 | ||
| 3831 | pub fn any(&self) -> bool { | |
| 3832 | PauseLevel::ALL.into_iter().any(|level| self.is(level)) | |
| 3833 | } | |
| 3834 | } | |
| 3835 | ||
| 3836 | /// One level as sudo shows it: who paused it, when and why. | |
| 3837 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 3838 | pub struct PauseState { | |
| 3839 | pub level: String, | |
| 3840 | pub paused: bool, | |
| 3841 | pub note: Option<String>, | |
| 3842 | pub set_by: Option<String>, | |
| 3843 | pub set_at: Option<String>, | |
| 3844 | /// Set by the usage watcher, not a person. | |
| 3845 | pub auto: bool, | |
| 3846 | } | |
| 3847 | ||
| 3848 | /// One metric's usage over an hour or the month so far. | |
| 3849 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 3850 | pub struct PlatformMetric { | |
| 3851 | pub metric: String, | |
| 3852 | pub title: String, | |
| 3853 | pub value: f64, | |
| 3854 | /// Its hourly threshold (`PLATFORM_HOURLY_*`); zero: none. | |
| 3855 | pub threshold: f64, | |
| 3856 | /// The script, queue, database or namespace that counted most. | |
| 3857 | pub top_name: Option<String>, | |
| 3858 | pub top_value: Option<f64>, | |
| 3859 | } | |
| 3860 | ||
| 3861 | /// A breach the watcher found. | |
| 3862 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 3863 | pub struct PlatformBreach { | |
| 3864 | pub id: String, | |
| 3865 | pub metric: String, | |
| 3866 | pub hour: String, | |
| 3867 | /// `threshold` or `spike`. | |
| 3868 | pub rule: String, | |
| 3869 | pub value: f64, | |
| 3870 | pub threshold: f64, | |
| 3871 | pub severe: bool, | |
| 3872 | pub top_name: Option<String>, | |
| 3873 | pub detail: String, | |
| 3874 | /// Levels it paused. | |
| 3875 | pub paused: Vec<String>, | |
| 3876 | pub opened_at: String, | |
| 3877 | pub emailed_at: Option<String>, | |
| 3878 | } | |
| 3879 | ||
| 3880 | /// `admin_platform_guard`: the pauses, the last hour read, the month so | |
| 3881 | /// far and the last day's breaches, for sudo's Costs page and its banner. | |
| 3882 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 3883 | pub struct PlatformGuard { | |
| 3884 | pub levels: Vec<PauseState>, | |
| 3885 | /// The last hour the watcher read (`YYYY-MM-DDTHH:00:00Z`), if any. | |
| 3886 | pub hour: Option<String>, | |
| 3887 | pub last_hour: Vec<PlatformMetric>, | |
| 3888 | pub month: String, | |
| 3889 | pub month_to_date: Vec<PlatformMetric>, | |
| 3890 | /// The last 24 hours' breaches, newest first. | |
| 3891 | pub breaches: Vec<PlatformBreach>, | |
| 3892 | /// Whether the watcher can read Cloudflare's analytics at all. | |
| 3893 | pub can_read: bool, | |
| 3894 | /// `AUTO_PAUSE`: the levels a severe breach may pause. | |
| 3895 | pub auto_pause: Vec<String>, | |
| 3896 | /// What the latest run could not see: each query that failed. | |
| 3897 | #[serde(default)] | |
| 3898 | pub blind: Vec<BlindQuery>, | |
| 3899 | /// The latest run found every dataset empty (the wrong account, or a | |
| 3900 | /// token that cannot see its analytics). | |
| 3901 | #[serde(default)] | |
| 3902 | pub empty: bool, | |
| 3903 | /// The hour the latest run read. | |
| 3904 | #[serde(default)] | |
| 3905 | pub last_run: Option<String>, | |
| 3906 | } | |
| 3907 | ||
| 3908 | /// A query the watcher's latest run could not read. | |
| 3909 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 3910 | pub struct BlindQuery { | |
| 3911 | pub key: String, | |
| 3912 | pub dataset: String, | |
| 3913 | pub error: String, | |
| 3914 | } | |
| 3915 | ||
| 3916 | /// `admin_platform_guard`. Returns `PlatformGuard`. | |
| 3917 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 3918 | pub struct AdminPlatformGuardArgs {} | |
| 3919 | ||
| 3920 | /// `admin_set_pause`: pauses or resumes one level, with why. Recorded in | |
| 3921 | /// the audit log. Returns `Outcome<PlatformGuard>`. | |
| 3922 | #[derive(Debug, Serialize, Deserialize)] | |
| 3923 | pub struct AdminSetPauseArgs { | |
| 3924 | pub level: String, | |
| 3925 | pub paused: bool, | |
| 3926 | pub note: String, | |
| 3927 | pub by: String, | |
| 3928 | } | |
| 3929 | ||
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 3930 | /// `admin_cost_alerts`: the open margin alerts, for sudo's banner. |
| 3931 | /// Returns `Vec<MarginAlert>`. | |
| 3932 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 3933 | pub struct AdminCostAlertsArgs {} | |
| 3934 | ||
| 3935 | /// `admin_decide_proposal`: approve or reject a price proposal. An | |
| 3936 | /// approved rise takes effect after the notice period. Returns | |
| 3937 | /// `Outcome<PriceProposal>`. | |
| 3938 | #[derive(Debug, Serialize, Deserialize)] | |
| 3939 | pub struct AdminDecideProposalArgs { | |
| 3940 | pub id: String, | |
| 3941 | /// `approve` or `reject`. | |
| 3942 | pub decision: String, | |
| 3943 | #[serde(default)] | |
| 3944 | pub note: String, | |
| 3945 | pub by: String, | |
| 3946 | } | |
| 3947 | ||
| 3948 | /// `admin_set_cost_settings`. Returns `Outcome<CostSettings>`. | |
| 3949 | #[derive(Debug, Serialize, Deserialize)] | |
| 3950 | pub struct AdminSetCostSettingsArgs { | |
| 3951 | pub settings: CostSettings, | |
| 3952 | pub by: String, | |
| 3953 | } | |
| 3954 | ||
| 3955 | /// `admin_set_cost_mapping`: adds, changes or (with `remove`) removes a | |
| 3956 | /// mapping row. Returns `Outcome<CostMapping>`. | |
| 3957 | #[derive(Debug, Serialize, Deserialize)] | |
| 3958 | pub struct AdminSetCostMappingArgs { | |
| 3959 | pub product: String, | |
| 3960 | pub meter: String, | |
| 3961 | #[serde(default)] | |
| 3962 | pub bucket: String, | |
| 3963 | #[serde(default)] | |
| 3964 | pub price_meter: Option<String>, | |
| 3965 | #[serde(default)] | |
| 3966 | pub own_meter: Option<String>, | |
| 3967 | #[serde(default)] | |
| 3968 | pub scale_to_own: bool, | |
| 3969 | #[serde(default)] | |
| 3970 | pub drift_percent: Option<f64>, | |
| 3971 | #[serde(default)] | |
| 3972 | pub note: String, | |
| 3973 | #[serde(default)] | |
| 3974 | pub remove: bool, | |
| 3975 | pub by: String, | |
| 3976 | } | |
| 3977 | ||
| 3978 | /// `admin_run_costs`: reads Cloudflare's bill and reconciles now, as the | |
| 3979 | /// daily run does. Returns `Outcome<CostsRun>`. | |
| 3980 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 3981 | pub struct AdminRunCostsArgs { | |
| 3982 | #[serde(default)] | |
| 3983 | pub by: String, | |
| 3984 | } | |
| 3985 | ||
| 3986 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 3987 | #[serde(rename_all = "camelCase")] | |
| 3988 | pub struct CostsRun { | |
| 3989 | pub lines: u32, | |
| 3990 | pub days: u32, | |
| 3991 | pub proposals: u32, | |
| 3992 | pub alerts: u32, | |
| 3993 | /// What could not be read, in words. | |
| 3994 | pub problems: Vec<String>, | |
| 3995 | } | |
| 3996 | ||
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 3997 | // --- The Usage page ---------------------------------------------------------- |
| 3998 | ||
| 3999 | /// The product families the Usage page groups meters into, in order, with | |
| 4000 | /// their names. | |
| 4001 | pub const PRODUCTS: [(&str, &str); 8] = [ | |
| 4002 | ("agent", "Agent"), | |
| 4003 | ("sandboxes", "Sandboxes"), | |
| 4004 | ("gateway", "AI Gateway"), | |
| 4005 | ("deployments", "Deployments"), | |
| 4006 | ("git_storage", "Git & storage"), | |
| 4007 | ("packages", "Packages"), | |
| 4008 | ("security", "Security & quality"), | |
| 4009 | ("search", "Search"), | |
| 4010 | ]; | |
| 4011 | ||
| 4012 | /// `usage_report`: a workspace's usage over a range of days, at price, by | |
| 4013 | /// product, meter, project and day. The figures are the ledger's: the same | |
| 4014 | /// lines the statement and invoices read, so every page agrees. Members | |
| 4015 | /// only. Returns `Outcome<UsageReport>`. | |
| 4016 | #[derive(Debug, Serialize, Deserialize)] | |
| 4017 | #[serde(rename_all = "camelCase")] | |
| 4018 | pub struct UsageReportArgs { | |
| 4019 | pub workspace: String, | |
| 4020 | pub viewer: Viewer, | |
| 4021 | /// The first day, `YYYY-MM-DD` (UTC). | |
| 4022 | pub from: String, | |
| 4023 | /// The last day, `YYYY-MM-DD`, included. | |
| 4024 | pub until: String, | |
| 4025 | /// Only these product families (`agent`, `sandboxes`…); all when empty. | |
| 4026 | #[serde(default)] | |
| 4027 | pub products: Vec<String>, | |
| 4028 | /// Only these projects (repositories, `owner/name`); all when empty. | |
| 4029 | #[serde(default)] | |
| 4030 | pub projects: Vec<String>, | |
| 4031 | } | |
| 4032 | ||
| 4033 | /// What usage came to over a range, and what paid for it. `price_micros` | |
| 4034 | /// less `discount_micros`, `included_micros` and `credits_micros` is | |
| 4035 | /// `charged_micros`. | |
| 4036 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 4037 | #[serde(rename_all = "camelCase")] | |
| 4038 | pub struct UsageTotals { | |
| 4039 | /// Usage at price, metered usage not yet charged (`pending_micros`) | |
| 4040 | /// included. | |
| 4041 | pub price_micros: i64, | |
| 4042 | /// What the account's discount took off. | |
| 4043 | pub discount_micros: i64, | |
| 4044 | /// What the plan's included usage, the trial and g1t's pools paid. | |
| 4045 | pub included_micros: i64, | |
| 4046 | /// What credit paid: AI credit, credit from g1t. | |
| 4047 | pub credits_micros: i64, | |
| 4048 | /// What is left for the workspace to pay. | |
| 4049 | pub charged_micros: i64, | |
| 4050 | /// Metered this month and charged when it closes (storage, git | |
| 4051 | /// operations, scans, embeddings, domains), at price. | |
| 4052 | pub pending_micros: i64, | |
| Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means. | 4053 | /// What of `pending_micros` the workspace will be charged when the |
| 4054 | /// month closes: its price less what g1t covers and what the discount | |
| 4055 | /// takes off, as the close will enter it. Credit comes off at the close. | |
| 4056 | #[serde(default)] | |
| 4057 | pub pending_charged_micros: i64, | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 4058 | /// What it cost g1t, before any markup. |
| 4059 | pub cost_micros: i64, | |
| 4060 | } | |
| 4061 | ||
| Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted. | 4062 | /// One agent's or one person's share of the agent product over the range, |
| 4063 | /// at price, from the ledger lines attributed to them. | |
| 4064 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 4065 | #[serde(rename_all = "camelCase")] | |
| 4066 | pub struct UsageShare { | |
| 4067 | /// The agent's handle or the person's username; empty for lines | |
| 4068 | /// attributed to no one (work from before attribution, or that no | |
| 4069 | /// person asked for). | |
| 4070 | pub key: String, | |
| 4071 | pub label: String, | |
| 4072 | pub micros: i64, | |
| 4073 | /// Ledger lines. | |
| 4074 | pub count: u32, | |
| 4075 | } | |
| 4076 | ||
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 4077 | /// One day's usage of one product, at price. |
| 4078 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 4079 | #[serde(rename_all = "camelCase")] | |
| 4080 | pub struct UsageDay { | |
| 4081 | /// `YYYY-MM-DD`. | |
| 4082 | pub day: String, | |
| 4083 | pub product: String, | |
| 4084 | pub micros: i64, | |
| 4085 | } | |
| 4086 | ||
| 4087 | /// How much of an allowance is used, in the meter's unit. | |
| 4088 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 4089 | #[serde(rename_all = "camelCase")] | |
| 4090 | pub struct Allowance { | |
| 4091 | pub used: f64, | |
| 4092 | pub of: f64, | |
| 4093 | /// `bytes`, `operations`, `dollars`… | |
| 4094 | pub unit: String, | |
| 4095 | } | |
| 4096 | ||
| 4097 | /// One project's part of a meter. | |
| 4098 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 4099 | #[serde(rename_all = "camelCase")] | |
| 4100 | pub struct ProjectUsage { | |
| 4101 | /// `owner/name`, or empty for usage that is not one project's. | |
| 4102 | pub project: String, | |
| 4103 | pub micros: i64, | |
| 4104 | pub quantity: f64, | |
| 4105 | } | |
| 4106 | ||
| 4107 | /// One meter over the range. | |
| 4108 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 4109 | #[serde(rename_all = "camelCase")] | |
| 4110 | pub struct MeterLine { | |
| 4111 | /// `agent_models`, `agent_rate`, `sandbox`, `builds`… | |
| 4112 | pub key: String, | |
| 4113 | pub label: String, | |
| 4114 | pub product: String, | |
| 4115 | /// What `quantity` counts: `tokens`, `seconds`, `bytes`, `operations`, | |
| 4116 | /// `entries`. | |
| 4117 | pub unit: String, | |
| 4118 | pub quantity: f64, | |
| 4119 | /// At price. | |
| 4120 | pub micros: i64, | |
| 4121 | /// Of `micros`, metered this month and charged when it closes. | |
| 4122 | #[serde(default)] | |
| 4123 | pub pending_micros: i64, | |
| 4124 | /// Every day of the range, oldest first, at price: the sparkline. | |
| 4125 | pub daily: Vec<i64>, | |
| 4126 | #[serde(default)] | |
| 4127 | pub allowance: Option<Allowance>, | |
| 4128 | pub by_project: Vec<ProjectUsage>, | |
| Merge branch 'model-routing' | 4129 | /// How the quantity is counted, when that needs saying: for the agent |
| 4130 | /// rate, its tokens are weighted by kind, and this names the weights. | |
| 4131 | #[serde(default)] | |
| 4132 | pub note: Option<String>, | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 4133 | } |
| 4134 | ||
| 4135 | /// A part of a product, such as the agent's runs, reviews and plans. | |
| 4136 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 4137 | #[serde(rename_all = "camelCase")] | |
| 4138 | pub struct FeatureUsage { | |
| 4139 | pub key: String, | |
| 4140 | pub label: String, | |
| 4141 | pub micros: i64, | |
| 4142 | pub count: u32, | |
| 4143 | } | |
| 4144 | ||
| Merge branch 'model-routing' | 4145 | /// The tokens one model used over the range, as the model proxy counted |
| 4146 | /// them: on g1t's models and the workspace's own provider alike. | |
| 4147 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 4148 | #[serde(rename_all = "camelCase")] | |
| 4149 | pub struct ModelTokens { | |
| 4150 | /// The model's id, as it ran. | |
| 4151 | pub model: String, | |
| 4152 | pub input: u64, | |
| 4153 | pub output: u64, | |
| 4154 | pub cache_read: u64, | |
| 4155 | pub cache_write: u64, | |
| 4156 | } | |
| 4157 | ||
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 4158 | /// One product family over the range. |
| 4159 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 4160 | #[serde(rename_all = "camelCase")] | |
| 4161 | pub struct ProductUsage { | |
| 4162 | pub key: String, | |
| 4163 | pub label: String, | |
| 4164 | pub micros: i64, | |
| 4165 | pub meters: Vec<MeterLine>, | |
| 4166 | /// For the agent: by what it was doing (runs, reviews, plans, checks). | |
| 4167 | #[serde(default)] | |
| 4168 | pub features: Vec<FeatureUsage>, | |
| 4169 | } | |
| 4170 | ||
| 4171 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 4172 | #[serde(rename_all = "camelCase")] | |
| 4173 | pub struct UsageReport { | |
| 4174 | pub from: String, | |
| 4175 | pub until: String, | |
| 4176 | pub totals: UsageTotals, | |
| 4177 | /// Each day and product with usage, oldest first. | |
| 4178 | pub days: Vec<UsageDay>, | |
| 4179 | /// Every product family, in order, even with nothing used. | |
| 4180 | pub products: Vec<ProductUsage>, | |
| 4181 | /// Every project with usage in the range, for the filter. | |
| 4182 | pub projects: Vec<String>, | |
| Merge branch 'model-routing' | 4183 | /// Agent tokens by model over the range, most first. |
| 4184 | #[serde(default)] | |
| 4185 | pub models: Vec<ModelTokens>, | |
| Spend adds up on one ledger. Charged this month has one definition, price less discount, included usage and credit, shared by the plan card, the spend limit, Spend and the top bar; the limit had counted usage not yet closed at full price before the discount, so a comped workspace read as charged a cent. Every agent line on the ledger names the agent and who asked, repository runs by g1t included, so Spent is the sum of its products, Agents is the agent product, and by agent adds up to it; the billing API returns by_agent and by_person. The usage and billing guide says how spend is counted. | 4186 | /// The agent product (model tokens, the agent rate and agents' |
| 4187 | /// sandbox time) by the agent that did the work, most first. Their sum | |
| 4188 | /// is the agent product's total: what agents cost is what the ledger | |
| 4189 | /// charges for them, not a second count. | |
| 4190 | #[serde(default)] | |
| 4191 | pub by_agent: Vec<UsageShare>, | |
| 4192 | /// The same by who asked; work no person asked for (routines, agents | |
| 4193 | /// helping agents, lines from before attribution) under an empty key. | |
| 4194 | #[serde(default)] | |
| 4195 | pub by_person: Vec<UsageShare>, | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 4196 | /// The plan's included usage this month, when the workspace has it. |
| 4197 | #[serde(default)] | |
| 4198 | pub included: Option<Allowance>, | |
| 4199 | /// The account's discount, in percent, when it has one. | |
| 4200 | #[serde(default)] | |
| 4201 | pub discount_percent: Option<u32>, | |
| 4202 | /// AI credit left now, and credit from g1t for everything. | |
| 4203 | pub ai_credit_micros: i64, | |
| 4204 | pub credit_micros: i64, | |
| 4205 | /// The trial credit left, for a workspace on its trial. | |
| 4206 | #[serde(default)] | |
| 4207 | pub trial_micros: Option<i64>, | |
| 4208 | pub plan: PlanKind, | |
| 4209 | /// Nothing is charged while g1t is being built out. | |
| 4210 | pub free: bool, | |
| 4211 | } | |
| 4212 | ||
| 4213 | // --- AI credit ----------------------------------------------------------------- | |
| 4214 | ||
| 4215 | /// Auto-reload: when AI credit falls below `threshold_micros`, the saved | |
| 4216 | /// card is charged to bring it back to `target_micros`, at most | |
| 4217 | /// `monthly_max_micros` in a calendar month. Off by default. A failed | |
| 4218 | /// charge turns it off and tells the owners. | |
| 4219 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 4220 | #[serde(rename_all = "camelCase")] | |
| 4221 | pub struct AiReload { | |
| 4222 | pub enabled: bool, | |
| 4223 | pub threshold_micros: i64, | |
| 4224 | pub target_micros: i64, | |
| 4225 | pub monthly_max_micros: i64, | |
| 4226 | /// Reloaded this month so far. | |
| 4227 | #[serde(default)] | |
| 4228 | pub reloaded_micros: i64, | |
| 4229 | /// When it last failed and was turned off, and why. | |
| 4230 | #[serde(default)] | |
| 4231 | pub failed_at: Option<String>, | |
| 4232 | #[serde(default)] | |
| 4233 | pub error: Option<String>, | |
| 4234 | } | |
| 4235 | ||
| 4236 | /// The card fee passed on when AI credit is bought by card. | |
| 4237 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 4238 | #[serde(rename_all = "camelCase")] | |
| 4239 | pub struct CardFee { | |
| 4240 | pub on: bool, | |
| 4241 | /// Per dollar charged, in millionths: 29,000 is 2.9%. | |
| 4242 | pub percent_micros: f64, | |
| 4243 | pub fixed_cents: u32, | |
| 4244 | } | |
| 4245 | ||
| 4246 | /// `ai_credit` (`AccountArgs`): a workspace's prepaid AI credit, what it | |
| 4247 | /// pays for and how it is bought. Members only. Returns `Outcome<AiCredit>`. | |
| 4248 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 4249 | #[serde(rename_all = "camelCase")] | |
| 4250 | pub struct AiCredit { | |
| 4251 | /// What is left to spend on Agent and AI Gateway usage. | |
| 4252 | pub balance_micros: i64, | |
| 4253 | /// Of it, bought (paid) and given (promotional). | |
| 4254 | pub purchased_micros: i64, | |
| 4255 | pub given_micros: i64, | |
| 4256 | /// Its grants, newest first. | |
| 4257 | pub grants: Vec<CreditGrant>, | |
| 4258 | /// A 100% discount: AI usage is free, shown at its price then the | |
| 4259 | /// discount. Nothing to buy. | |
| 4260 | pub free_via_discount: bool, | |
| 4261 | /// Invoiced terms (an enterprise): models are billed after use, so no | |
| 4262 | /// credit is needed. | |
| 4263 | pub postpaid: bool, | |
| 4264 | /// Whether new runs on g1t's models are refused now for want of credit. | |
| 4265 | pub blocked: bool, | |
| 4266 | /// Whether the workspace may buy it: on the plan, not free. | |
| 4267 | pub can_buy: bool, | |
| 4268 | pub presets_cents: Vec<u32>, | |
| 4269 | pub min_cents: u32, | |
| 4270 | pub max_cents: u32, | |
| 4271 | pub card_fee: CardFee, | |
| 4272 | pub reload: AiReload, | |
| 4273 | /// The agent rate per million tokens, now, at price. | |
| 4274 | pub agent_rate_micros: f64, | |
| 4275 | /// The markup on models' provider price, in percent. | |
| 4276 | pub model_markup_percent: u32, | |
| 4277 | /// The markup on AI Gateway's provider price, in percent. | |
| 4278 | pub gateway_markup_percent: u32, | |
| 4279 | /// The AI credit given once on starting the plan. | |
| 4280 | pub upgrade_credit_micros: i64, | |
| 4281 | /// How long bought credit lasts, in days. | |
| 4282 | pub expires_days: u32, | |
| 4283 | } | |
| 4284 | ||
| 4285 | /// `buy_ai_credit`: Stripe's page to buy AI credit, one payment by card, | |
| 4286 | /// with the card fee as its own line. Owners only. Returns | |
| 4287 | /// `Outcome<Checkout>`; the page's id comes back to `return_url` as | |
| 4288 | /// `ai_credit`, for `confirm_ai_credit`. | |
| 4289 | #[derive(Debug, Serialize, Deserialize)] | |
| 4290 | #[serde(rename_all = "camelCase")] | |
| 4291 | pub struct BuyAiCreditArgs { | |
| 4292 | pub actor: User, | |
| 4293 | pub workspace: String, | |
| 4294 | /// The credit, in cents; the card fee is added on top. | |
| 4295 | #[serde(alias = "amount_cents")] | |
| 4296 | pub amount_cents: u32, | |
| 4297 | #[serde(alias = "return_url")] | |
| 4298 | pub return_url: String, | |
| 4299 | } | |
| 4300 | ||
| 4301 | /// `confirm_ai_credit`: credits a purchase once Stripe says it was paid, | |
| 4302 | /// once. Safe to repeat; the webhook does the same. Returns | |
| 4303 | /// `Outcome<AiCredit>`. | |
| 4304 | #[derive(Debug, Serialize, Deserialize)] | |
| 4305 | pub struct ConfirmAiCreditArgs { | |
| 4306 | pub workspace: String, | |
| 4307 | pub viewer: Viewer, | |
| 4308 | pub session: String, | |
| 4309 | } | |
| 4310 | ||
| 4311 | /// `set_ai_reload`: auto-reload's settings. Owners only. Returns | |
| 4312 | /// `Outcome<AiCredit>`. | |
| 4313 | #[derive(Debug, Serialize, Deserialize)] | |
| 4314 | #[serde(rename_all = "camelCase")] | |
| 4315 | pub struct SetAiReloadArgs { | |
| 4316 | pub actor: User, | |
| 4317 | pub workspace: String, | |
| 4318 | pub enabled: bool, | |
| 4319 | #[serde(alias = "threshold_micros")] | |
| 4320 | pub threshold_micros: i64, | |
| 4321 | #[serde(alias = "target_micros")] | |
| 4322 | pub target_micros: i64, | |
| 4323 | #[serde(alias = "monthly_max_micros")] | |
| 4324 | pub monthly_max_micros: i64, | |
| 4325 | } | |
| 4326 | ||
| 4327 | // --- Budgets ------------------------------------------------------------------ | |
| 4328 | ||
| 4329 | /// `set_budget`: the monthly budget on usage after included usage: the | |
| 4330 | /// owners' spend limit, its alerts, whether usage pauses at 100%, and an | |
| 4331 | /// optional webhook. Owners only. Returns `Outcome<Limit>`. | |
| 4332 | #[derive(Debug, Serialize, Deserialize)] | |
| 4333 | #[serde(rename_all = "camelCase")] | |
| 4334 | pub struct SetBudgetArgs { | |
| 4335 | pub actor: User, | |
| 4336 | pub workspace: String, | |
| 4337 | /// The amount; None keeps the automatic one. | |
| 4338 | #[serde(default, alias = "amount_micros")] | |
| 4339 | pub amount_micros: Option<i64>, | |
| 4340 | /// Some of 50, 75, 90 and 100. | |
| 4341 | #[serde(default)] | |
| 4342 | pub alerts: Vec<u32>, | |
| 4343 | #[serde(default = "yes", alias = "pause_at_limit")] | |
| 4344 | pub pause_at_limit: bool, | |
| 4345 | /// An HTTPS address, or None for no webhook. | |
| 4346 | #[serde(default)] | |
| 4347 | pub webhook: Option<String>, | |
| 4348 | /// Leave the spend limit as it is and change only the alerts, the | |
| 4349 | /// pause and the webhook. | |
| 4350 | #[serde(default, alias = "keep_limit")] | |
| 4351 | pub keep_limit: bool, | |
| 4352 | } | |
| 4353 | ||
| 4354 | // --- Billing details ----------------------------------------------------------- | |
| 4355 | ||
| 4356 | /// A postal address, as Stripe keeps it. | |
| 4357 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 4358 | #[serde(rename_all = "camelCase")] | |
| 4359 | pub struct PostalAddress { | |
| 4360 | #[serde(default)] | |
| 4361 | pub line1: String, | |
| 4362 | #[serde(default)] | |
| 4363 | pub line2: String, | |
| 4364 | #[serde(default)] | |
| 4365 | pub city: String, | |
| 4366 | #[serde(default)] | |
| 4367 | pub state: String, | |
| 4368 | #[serde(default)] | |
| 4369 | pub postal_code: String, | |
| 4370 | /// Two letters, `US`. | |
| 4371 | #[serde(default)] | |
| 4372 | pub country: String, | |
| 4373 | } | |
| 4374 | ||
| 4375 | /// The default way the workspace pays, as far as it is safe to show. | |
| 4376 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 4377 | #[serde(rename_all = "camelCase")] | |
| 4378 | pub struct PaymentMethod { | |
| 4379 | /// `card`, or another kind Stripe has. | |
| 4380 | pub kind: String, | |
| 4381 | #[serde(default)] | |
| 4382 | pub brand: Option<String>, | |
| 4383 | #[serde(default)] | |
| 4384 | pub last4: Option<String>, | |
| 4385 | #[serde(default)] | |
| 4386 | pub exp_month: Option<u32>, | |
| 4387 | #[serde(default)] | |
| 4388 | pub exp_year: Option<u32>, | |
| 4389 | } | |
| 4390 | ||
| 4391 | /// One of the customer's invoices at Stripe: the plan, activations, AI | |
| 4392 | /// credit and month-end usage. | |
| 4393 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 4394 | #[serde(rename_all = "camelCase")] | |
| 4395 | pub struct StripeInvoice { | |
| 4396 | pub id: String, | |
| 4397 | #[serde(default)] | |
| 4398 | pub number: Option<String>, | |
| 4399 | /// `paid`, `open`, `void`, `uncollectible` or `draft`. | |
| 4400 | pub status: String, | |
| 4401 | pub total_cents: i64, | |
| 4402 | pub currency: String, | |
| 4403 | /// RFC 3339. | |
| 4404 | pub created_at: String, | |
| 4405 | #[serde(default)] | |
| 4406 | pub description: Option<String>, | |
| 4407 | #[serde(default)] | |
| 4408 | pub hosted_url: Option<String>, | |
| 4409 | #[serde(default)] | |
| 4410 | pub pdf_url: Option<String>, | |
| 4411 | } | |
| 4412 | ||
| 4413 | /// What the next invoice will be, from g1t's own ledger. | |
| 4414 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 4415 | #[serde(rename_all = "camelCase")] | |
| 4416 | pub struct UpcomingInvoice { | |
| 4417 | /// When the month closes, RFC 3339. | |
| 4418 | pub closes_at: String, | |
| 4419 | /// The plan and activations, at their monthly price. | |
| 4420 | pub subscriptions_micros: i64, | |
| 4421 | /// Usage still owed, after included usage, credit and any discount. | |
| 4422 | pub usage_micros: i64, | |
| 4423 | pub total_micros: i64, | |
| 4424 | } | |
| 4425 | ||
| 4426 | /// `billing_details` (`AccountArgs`): who the invoices are for, the default | |
| 4427 | /// payment method, and the invoices, from the Stripe customer. Members see | |
| 4428 | /// it; owners change it. Returns `Outcome<BillingDetails>`. | |
| 4429 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 4430 | #[serde(rename_all = "camelCase")] | |
| 4431 | pub struct BillingDetails { | |
| 4432 | /// Whether the workspace has a Stripe customer yet. | |
| 4433 | pub customer: bool, | |
| 4434 | pub email: Option<String>, | |
| 4435 | pub name: Option<String>, | |
| 4436 | pub address: Option<PostalAddress>, | |
| 4437 | /// `eu_vat`, `us_ein`…, and its value. | |
| 4438 | pub tax_id_type: Option<String>, | |
| 4439 | pub tax_id: Option<String>, | |
| 4440 | /// Printed on invoices. | |
| 4441 | pub po_number: Option<String>, | |
| 4442 | /// The invoices' language, such as `en` or `fr`. | |
| 4443 | pub language: Option<String>, | |
| 4444 | pub payment_method: Option<PaymentMethod>, | |
| 4445 | pub invoices: Vec<StripeInvoice>, | |
| 4446 | pub upcoming: UpcomingInvoice, | |
| 4447 | /// Stripe could not be read: what is shown is what g1t keeps. | |
| 4448 | #[serde(default)] | |
| 4449 | pub unavailable: Option<String>, | |
| Merge Stripe Tax, the card fee on card payments, and one free workspace per person | 4450 | /// Whether Stripe Tax can place the customer from the address: tax |
| 4451 | /// is worked out from it, and without it nothing is charged. | |
| 4452 | #[serde(default)] | |
| 4453 | pub tax_location: bool, | |
| 4454 | /// Set when g1t did not charge for want of an address (RFC 3339). | |
| 4455 | #[serde(default)] | |
| 4456 | pub tax_address_needed_at: Option<String>, | |
| 4457 | /// Stripe's check of the tax ID: `pending`, `verified`, `unverified` or | |
| 4458 | /// `unavailable`. | |
| 4459 | #[serde(default)] | |
| 4460 | pub tax_id_status: Option<String>, | |
| 4461 | /// `none`, `exempt` or `reverse`, as staff set it at Stripe; g1t never | |
| 4462 | /// changes it. | |
| 4463 | #[serde(default)] | |
| 4464 | pub tax_exempt: Option<String>, | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 4465 | } |
| 4466 | ||
| 4467 | /// `set_billing_details`: saves the invoice details on the Stripe customer. | |
| 4468 | /// Owners only. Absent fields are left as they are; an empty string clears | |
| 4469 | /// one. Returns `Outcome<BillingDetails>`. | |
| 4470 | #[derive(Debug, Serialize, Deserialize)] | |
| 4471 | #[serde(rename_all = "camelCase")] | |
| 4472 | pub struct SetBillingDetailsArgs { | |
| 4473 | pub actor: User, | |
| 4474 | pub workspace: String, | |
| 4475 | #[serde(default)] | |
| 4476 | pub email: Option<String>, | |
| 4477 | #[serde(default)] | |
| 4478 | pub name: Option<String>, | |
| 4479 | #[serde(default)] | |
| 4480 | pub address: Option<PostalAddress>, | |
| 4481 | #[serde(default, alias = "tax_id_type")] | |
| 4482 | pub tax_id_type: Option<String>, | |
| 4483 | #[serde(default, alias = "tax_id")] | |
| 4484 | pub tax_id: Option<String>, | |
| 4485 | #[serde(default, alias = "po_number")] | |
| 4486 | pub po_number: Option<String>, | |
| 4487 | #[serde(default)] | |
| 4488 | pub language: Option<String>, | |
| 4489 | } | |
| 4490 | ||
| Deploy scripts live in the repository | 4491 | #[cfg(test)] |
| 4492 | mod tests { | |
| 4493 | use super::*; | |
| 4494 | ||
| 4495 | #[test] | |
| Prices are what g1t pays plus 20%, from the first second | 4496 | fn an_account_carries_no_run_fee() { |
| 4497 | let account = Account { | |
| 4498 | workspace: "acme".into(), | |
| 4499 | balance_micros: 0, | |
| 4500 | status: Status { enabled: true, live: false, free: false }, | |
| 4501 | margin_percent: 20, | |
| 4502 | card: None, | |
| 4503 | }; | |
| 4504 | let json = serde_json::to_value(account).unwrap(); | |
| 4505 | let mut keys: Vec<&str> = json.as_object().unwrap().keys().map(String::as_str).collect(); | |
| 4506 | keys.sort_unstable(); | |
| 4507 | assert_eq!(keys, ["balanceMicros", "card", "marginPercent", "status", "workspace"]); | |
| 4508 | } | |
| 4509 | ||
| 4510 | #[test] | |
| 4511 | fn a_price_change_says_when_the_markup_moved() { | |
| 4512 | let change = PriceChange { | |
| 4513 | meter: "sandbox_second".into(), | |
| 4514 | old_cost_micros: 21.0, | |
| 4515 | new_cost_micros: 21.0, | |
| 4516 | markup_percent: 20, | |
| 4517 | old_markup_percent: Some(138), | |
| 4518 | reason: "Sandbox time is now charged at cost plus 20% from the first second".into(), | |
| 4519 | created_at: "2026-10-05T00:00:00Z".into(), | |
| Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily | 4520 | effective_at: None, |
| Prices are what g1t pays plus 20%, from the first second | 4521 | }; |
| 4522 | assert_eq!(serde_json::to_value(&change).unwrap()["oldMarkupPercent"], 138); | |
| 4523 | let cost_only = PriceChange { old_markup_percent: None, ..change }; | |
| 4524 | assert!(serde_json::to_value(&cost_only).unwrap().get("oldMarkupPercent").is_none()); | |
| 4525 | } | |
| 4526 | ||
| 4527 | #[test] | |
| Deploy scripts live in the repository | 4528 | fn features_are_named_as_the_site_sends_them() { |
| 4529 | assert_eq!( | |
| 4530 | serde_json::to_value(Feature::Deployments).unwrap(), | |
| 4531 | serde_json::json!("deployments") | |
| 4532 | ); | |
| 4533 | assert_eq!(Feature::parse("deployments"), Some(Feature::Deployments)); | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 4534 | assert_eq!(serde_json::to_value(Feature::Plan).unwrap(), serde_json::json!("plan")); |
| 4535 | assert_eq!(Feature::parse("plan"), Some(Feature::Plan)); | |
| 4536 | // Older readers named the plan Team. | |
| 4537 | assert_eq!(Feature::parse("team"), Some(Feature::Plan)); | |
| 4538 | assert_eq!(serde_json::from_value::<Feature>(serde_json::json!("team")).unwrap(), Feature::Plan); | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 4539 | assert_eq!(Feature::ALL, [Feature::Plan, Feature::Security]); |
| 4540 | assert_eq!(Feature::parse("security"), Some(Feature::Security)); | |
| 4541 | assert_eq!(serde_json::to_value(Feature::Security).unwrap(), serde_json::json!("security")); | |
| Deploy scripts live in the repository | 4542 | assert!(SubscriptionStatus::Canceling.on()); |
| 4543 | assert!(!SubscriptionStatus::PastDue.on()); | |
| 4544 | } | |
| 4545 | ||
| 4546 | #[test] | |
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 4547 | fn a_reservation_is_asked_for_and_answered_in_camel_case() { |
| 4548 | let asked: ReserveArgs = serde_json::from_value(serde_json::json!({ | |
| 4549 | "workspace": "acme", | |
| 4550 | "repo": { "namespace": "acme", "name": "web" }, | |
| 4551 | "public": true, | |
| 4552 | "kind": "check", | |
| 4553 | "estimateMicros": 2_000_000, | |
| 4554 | })) | |
| 4555 | .unwrap(); | |
| 4556 | assert_eq!(asked.kind, ComputeKind::Check); | |
| 4557 | assert!(asked.kind.open_source_pool()); | |
| 4558 | assert!(!ComputeKind::Agent.open_source_pool()); | |
| 4559 | // Rust callers that write snake_case are read too. | |
| 4560 | let snake: ReserveArgs = serde_json::from_value(serde_json::json!({ | |
| 4561 | "workspace": "acme", | |
| 4562 | "repo": { "namespace": "acme", "name": "web" }, | |
| 4563 | "public": false, | |
| 4564 | "kind": "agent", | |
| 4565 | "estimate_micros": 1, | |
| 4566 | })) | |
| 4567 | .unwrap(); | |
| 4568 | assert_eq!(snake.estimate_micros, 1); | |
| 4569 | let answer = Reservation { id: "rsv_1".into(), paid_by: PaidBy::OnDemand, held_micros: 5, expires_at: String::new() }; | |
| 4570 | assert_eq!(serde_json::to_value(&answer).unwrap()["paidBy"], "on_demand"); | |
| 4571 | assert_eq!(serde_json::to_value(PlanKind::Internal).unwrap(), "internal"); | |
| 4572 | assert!(!PlanKind::Free.on_demand() && PlanKind::Enterprise.on_demand()); | |
| 4573 | } | |
| 4574 | ||
| 4575 | #[test] | |
| 4576 | fn a_refusal_carries_its_own_code() { | |
| 4577 | let refused: crate::Outcome<Reservation> = | |
| 4578 | crate::Outcome::fail(crate::FailureCode::OssPoolEmpty, "The open-source pool is spent."); | |
| 4579 | let json = serde_json::to_value(&refused).unwrap(); | |
| 4580 | assert_eq!(json["error"]["code"], "oss_pool_empty"); | |
| 4581 | assert_eq!(crate::FailureCode::NotPaid.http_status(), 402); | |
| 4582 | assert_eq!(crate::FailureCode::Paused.http_status(), 409); | |
| 4583 | } | |
| 4584 | ||
| 4585 | #[test] | |
| Deploy scripts live in the repository | 4586 | fn who_pays_is_read_as_the_runner_sends_it() { |
| 4587 | let run: StartRunArgs = serde_json::from_value(serde_json::json!({ | |
| 4588 | "workspace": "acme", | |
| 4589 | "repo": { "namespace": "acme", "name": "web" }, | |
| 4590 | "number": 7, | |
| 4591 | "task": "implement", | |
| 4592 | "model": "Claude Sonnet 5.5", | |
| 4593 | "billedTo": "workspace", | |
| 4594 | })) | |
| 4595 | .unwrap(); | |
| 4596 | assert_eq!(run.billed_to, "workspace"); | |
| 4597 | } | |
| 4598 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.