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.
| 1767 | 1767 | "Choose what happens when a team is asked to review a pull request. Off, everyone in it is asked. On (`enabled`), g1t picks `count` people from it (1 to 10, never the pull request's author) and asks them, and the team stays shown as asked beside them: `round_robin` picks whoever this team asked least recently, `load_balance` whoever has the fewest pull requests waiting on their review. `skip_busy` leaves out anyone with `busy_at` or more waiting; `include_child_teams` also picks from its child teams' people; `excluded` lists usernames never picked; `notify_team` also tells the rest of the team. Fields left out keep their current value. Owners of the workspace and the team's maintainers. People only. Returns the team." | |
| 1768 | 1768 | } | |
| 1769 | 1769 | Op::GetUsage => { | |
| 1770 | − | "A workspace's usage over a range of days, at price, and what paid for it. `from` and `until` are UTC days, `YYYY-MM-DD`, with `until` included and at most 400 days in all; left out, the current month so far. `products` narrows it to product families (agent, sandboxes, gateway, deployments, git_storage, packages, security, search) and `projects` to repositories (\"owner/name\"). Returns `totals`: `price_micros` less `discount_micros`, `included_micros` and `credits_micros` is `charged_micros`, what is left for the workspace to pay; `pending_micros` is metered this month and charged when it closes; `cost_micros` is what it cost g1t. Then `days` (each day and product with usage), `products` (every family, with its meters: quantity, unit, amount, a `daily` amount for each day of the range, any `allowance`, the split `by_project`, and a `note` where the quantity needs one: the agent rate's meters, `agent_rate` and `agent_rate_own` (on the workspace's own model key), count weighted tokens and name the weights), `projects` (every repository with usage in the range), `models` (the agent's input, output, cache-read and cache-write tokens by model, most first), `by_agent` and `by_person` (the agent product at price by the agent that did the work, by handle, and by who asked, by username; `g1t` is g1t's own runs on repositories, an empty key is work attributed to no one; each sums to the agent product), and the AI credit and other credit left now. With `group_by` (`product`, `project` or `day`), `groups` adds up the range that way. Amounts are whole millionths of a dollar. Members of the workspace only." | |
| 1770 | + | "A workspace's usage over a range of days, at price, and what paid for it. `from` and `until` are UTC days, `YYYY-MM-DD`, with `until` included and at most 400 days in all; left out, the current month so far. `products` narrows it to product families (agent, sandboxes, gateway, deployments, git_storage, packages, security, search) and `projects` to repositories (\"owner/name\"). Returns `totals`: `price_micros` less `discount_micros`, `included_micros` and `credits_micros` is `charged_micros`, what is left for the workspace to pay; `pending_micros` is metered this month and charged when it closes, counted in any range that reaches into the current month, and `pending_charged_micros` is what of it will be charged then, after the discount and what g1t covers; `cost_micros` is what it cost g1t. Then `days` (each day and product with usage), `products` (every family, with its meters: quantity, unit, amount, a `daily` amount for each day of the range, any `allowance`, the split `by_project`, and a `note` where the quantity needs one: the agent rate's meters, `agent_rate` and `agent_rate_own` (on the workspace's own model key), count weighted tokens and name the weights), `projects` (every repository with usage in the range), `models` (the agent's input, output, cache-read and cache-write tokens by model, most first), `by_agent` and `by_person` (the agent product at price by the agent that did the work, by handle, and by who asked, by username; `g1t` is g1t's own runs on repositories, an empty key is work attributed to no one; each sums to the agent product), and the AI credit and other credit left now. With `group_by` (`product`, `project` or `day`), `groups` adds up the range that way. Amounts are whole millionths of a dollar. Members of the workspace only." | |
| 1771 | 1771 | } | |
| 1772 | 1772 | Op::GetBudget => { | |
| 1773 | 1773 | "A workspace's budget: its monthly spend limit (`amount_micros`; `automatic` is true while the owners have not set one, and it is then $200 or twice last month's spend), what was charged this month (`spent_micros`), the most the owners may set it to themselves (`max_amount_micros`), its `alerts` (percent of the limit, each emailed to the owners once a month), whether usage pauses at the limit (`pause_at_limit`), the `webhook` told of each alert, and `state`: `ok`, `warning` or `stopped`, with a `message` when work is stopped or close to it. Members of the workspace only." |
| 6241 | 6241 | "credits_micros": 18000000, | |
| 6242 | 6242 | "charged_micros": 10210000, | |
| 6243 | 6243 | "pending_micros": 1340000, | |
| 6244 | + | "pending_charged_micros": 1340000, | |
| 6244 | 6245 | "cost_micros": 40100000 | |
| 6245 | 6246 | }, | |
| 6246 | 6247 | "days": [ |
| 538 | 538 | | **Agents** | The agent product within Spent: model tokens, the [agent rate](#the-agent-rate) and agents' sandbox time, for every agent's chat replies, sessions and runs on repositories. Each entry names the agent that did the work (g1t's own runs on your repositories are **@g1t**) and the person who asked, so **by agent** and **by person** are the same money sliced two ways, and the agents add up to the product. | The top bar's spend pill, Spend's **Agents** tile and its **Agents** and **People** slices, Usage's **Agent** product | | |
| 539 | 539 | | **Charged** | What is left for the workspace to pay this month: usage at price, less your discount, less what included usage, the trial, a pool or g1t paid, less what [credit](#credits-from-g1t) paid. Never below zero: a workspace with a 100% discount is charged exactly nothing. Usage metered through the month and charged when it closes (storage, git operations, scans, embeddings, domains, app traffic) counts now on the same terms. | Billing's plan card, Spend's **Charged**, the top bar's **Workspace** view, and the figure your [spend limit](#your-spend-limit) is measured against | | |
| 540 | 540 | ||
| 541 | + | **This month** is the calendar month in UTC, from its first day to today, | |
| 542 | + | on every page: the top bar's pill, Spend's **This month**, Home's "this | |
| 543 | + | month so far" and Usage's current billing cycle ask billing for the same | |
| 544 | + | days and show the same figure. Usage metered through the month and | |
| 545 | + | charged when it closes (storage, git operations, scans, embeddings, | |
| 546 | + | domains, app traffic) has no day of its own, so it counts in any range | |
| 547 | + | that reaches into the current month, and Usage's receipt says what of it | |
| 548 | + | will be charged at the close on your terms: your discount and included | |
| 549 | + | usage come off it then, the same as the rest. | |
| 550 | + | ||
| 551 | + | Every amount is written the same way: to the cent, rounded half up on | |
| 552 | + | whole millionths of a dollar, so the same sum never reads differently on | |
| 553 | + | two pages. Under a cent reads **<$0.01** rather than $0.00 on a total, | |
| 554 | + | and exactly nothing is $0.00. Where the fraction of a cent is the point, | |
| 555 | + | the [statement](#the-statement), a session's receipt, the | |
| 556 | + | [price book](https://g1t.sh/pricing) and an agent's effort costs, amounts carry up to | |
| 557 | + | four places (`$0.0063`). | |
| 558 | + | ||
| 541 | 559 | The [agent budgets](/guides/agent-budgets/) count the same entries at | |
| 542 | 560 | the same price, before any discount or included usage: an agent's chat | |
| 543 | 561 | replies and sessions against its own budget, its team's, the person's and | |
| ⋯ | |||
| 545 | 563 | predictable; the spend limit is on what is charged. | |
| 546 | 564 | ||
| 547 | 565 | Through the API, `GET /workspaces/:workspace/usage` answers with the | |
| 548 | − | products, `by_agent` and `by_person`, and `totals.charged_micros`; | |
| 566 | + | products, `by_agent` and `by_person`, and `totals.charged_micros`, with `totals.pending_charged_micros` for what of the pending usage the close will charge; | |
| 549 | 567 | `GET /workspaces/:workspace/budget` answers with the spend limit and | |
| 550 | 568 | `spent_micros`, the same charged figure. | |
| 551 | 569 | ||
| ⋯ | |||
| 1120 | 1138 | before included usage or a pool paid for it) and a `quantity` such as | |
| 1121 | 1139 | *42 build minutes*, each at price (what was charged plus what paid for it), as Usage measures it. | |
| 1122 | 1140 | ||
| 1123 | − | **`usage_report`** (`workspace`, `viewer`, `from`, `until`, optional `products` and `projects`; members only) is what the Usage page reads: totals (`priceMicros`, `discountMicros`, `includedMicros`, `creditsMicros`, `chargedMicros`, `pendingMicros`), each day's usage by product, and every product family with its meters, their daily figures and their projects. | |
| 1141 | + | **`usage_report`** (`workspace`, `viewer`, `from`, `until`, optional `products` and `projects`; members only) is what the Usage page reads: totals (`priceMicros`, `discountMicros`, `includedMicros`, `creditsMicros`, `chargedMicros`, `pendingMicros`, `pendingChargedMicros`), each day's usage by product, and every product family with its meters, their daily figures and their projects. | |
| 1124 | 1142 | ||
| 1125 | 1143 | **`reserve`** holds the work's estimated cost before it starts, so starts | |
| 1126 | 1144 | at the same moment cannot overshoot together. `kind` is `agent`, `check`, | |
Binary or large file; its contents are not shown.
| 43 | 43 | import { shownStep } from "../lib/agent-step"; | |
| 44 | 44 | import { inlinePlain } from "../lib/inline-markdown"; | |
| 45 | 45 | import { InlineMarkdown } from "./inline-markdown"; | |
| 46 | + | import { microsOf, money } from "../lib/money"; | |
| 46 | 47 | ||
| 47 | 48 | /** How often a page with something running asks again. */ | |
| 48 | 49 | export const LIVE_MS = 4000; | |
| ⋯ | |||
| 53 | 54 | } | |
| 54 | 55 | ||
| 55 | 56 | export function formatCost(usd: number | null | undefined): string | null { | |
| 56 | − | if (usd == null) return null; | |
| 57 | − | if (usd > 0 && usd < 0.01) return "<$0.01"; | |
| 58 | − | return `$${usd.toFixed(2)}`; | |
| 57 | + | return usd == null ? null : money(microsOf(usd)); | |
| 59 | 58 | } | |
| 60 | 59 | ||
| 61 | 60 | function span(ms: number): string { | |
| 23 | 23 | import { type BuilderDefinition, type ChangeRow, changeRows, integrationHint } from "../../lib/agent-builder"; | |
| 24 | 24 | import { PRESETS, TIER_LABELS, cleanHandle, dollarsField, microsFromDollars } from "../../lib/agent-form"; | |
| 25 | 25 | import { cn } from "../../lib/cn"; | |
| 26 | − | import { money } from "../../lib/usage"; | |
| 26 | + | import { money } from "../../lib/money"; | |
| 27 | 27 | ||
| 28 | 28 | const FIELD = | |
| 29 | 29 | "w-full rounded-md border border-line bg-bg px-3 py-2 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-accent-dim"; |
| 19 | 19 | import { Field, FieldDescription, FieldError, FieldLabel } from "../ui/field"; | |
| 20 | 20 | import { Input, InputAddon, InputGroup } from "../ui/input"; | |
| 21 | 21 | import { dollarsField } from "../../lib/agent-form"; | |
| 22 | − | import { money } from "../../lib/usage"; | |
| 22 | + | import { money } from "../../lib/money"; | |
| 23 | 23 | import { PrivateTitle, SpendOfCap } from "./parts"; | |
| 24 | 24 | ||
| 25 | 25 | /** What Agents mode's actions answer. */ |
| 10 | 10 | import { TimeAgo } from "../ui"; | |
| 11 | 11 | import { Card } from "../ui/card"; | |
| 12 | 12 | import { cn } from "../../lib/cn"; | |
| 13 | − | import { money } from "../../lib/usage"; | |
| 13 | + | import { money } from "../../lib/money"; | |
| 14 | 14 | import { kindLabel, meterTone, monthDays, sessionStatus, shareOf, shortDay, topSlices, whereLabel } from "./format"; | |
| 15 | 15 | ||
| 16 | 16 | /** Where a session's page is. */ |
| 12 | 12 | ||
| 13 | 13 | import type { AiCredit, BillingDetails, FeatureState, Limit, UsageReport } from "@g1t/contracts"; | |
| 14 | 14 | ||
| 15 | − | import { PLUS_TAX, type PlanStatus, cardFeeCents, dollars, feeAndTax, wholeDollars } from "../lib/billing"; | |
| 16 | − | import { money } from "../lib/usage"; | |
| 15 | + | import { PLUS_TAX, type PlanStatus, cardFeeCents, feeAndTax } from "../lib/billing"; | |
| 16 | + | import { money, wholeDollars } from "../lib/money"; | |
| 17 | 17 | import { Card } from "./billing"; | |
| 18 | 18 | import { ErrorText, SubmitButton } from "./ui"; | |
| 19 | 19 | import { Checkbox } from "./ui/checkbox"; | |
| ⋯ | |||
| 106 | 106 | <span className="block text-xs text-faint">with {wholeDollars(report?.included?.of ?? 10_000_000)} of usage included</span> | |
| 107 | 107 | {status.kind !== "comped" && status.kind !== "enterprise" && ( | |
| 108 | 108 | <span className="block text-xs text-faint"> | |
| 109 | − | {plan?.cardFeeCents ? `+ ${dollars(plan.cardFeeCents * 10_000)} card processing fee, ` : ""} | |
| 109 | + | {plan?.cardFeeCents ? `+ ${money(plan.cardFeeCents * 10_000)} card processing fee, ` : ""} | |
| 110 | 110 | {plan?.cardFeeCents ? PLUS_TAX : "Plus tax where it applies"} | |
| 111 | 111 | </span> | |
| 112 | 112 | )} | |
| ⋯ | |||
| 214 | 214 | // What is chosen, so the fee shows before Stripe's page does. | |
| 215 | 215 | const [chosenCents, setChosenCents] = useState(2_500); | |
| 216 | 216 | const chosenFee = cardFeeCents(chosenCents, fee); | |
| 217 | − | const stripeFee = `Stripe's ${(fee.percentMicros / 10_000).toFixed(1)}% + ${dollars(fee.fixedCents * 10_000)}`; | |
| 217 | + | const stripeFee = `Stripe's ${(fee.percentMicros / 10_000).toFixed(1)}% + ${money(fee.fixedCents * 10_000)}`; | |
| 218 | 218 | const feeText = !fee.on | |
| 219 | 219 | ? `${feeAndTax(0)}.` | |
| 220 | 220 | : chosenCents > 0 | |
| ⋯ | |||
| 237 | 237 | ? "Agent and AI Gateway usage is free for this workspace under its discount: shown at its price, then the discount." | |
| 238 | 238 | : credit.postpaid | |
| 239 | 239 | ? "This workspace's enterprise is invoiced for Agent and AI Gateway usage after use: no credit needed." | |
| 240 | − | : `Prepaid credit for Agent and AI Gateway usage, spent before anything else. Models at the provider's price${credit.modelMarkupPercent ? ` plus ${credit.modelMarkupPercent}%` : ""}, plus the agent rate${rate > 0 ? ` (${dollars(rate)} per million tokens)` : ""}. Credit expires ${credit.expiresDays === 365 ? "1 year" : `${credit.expiresDays} days`} after purchase.` | |
| 240 | + | : `Prepaid credit for Agent and AI Gateway usage, spent before anything else. Models at the provider's price${credit.modelMarkupPercent ? ` plus ${credit.modelMarkupPercent}%` : ""}, plus the agent rate${rate > 0 ? ` (${money(rate)} per million tokens)` : ""}. Credit expires ${credit.expiresDays === 365 ? "1 year" : `${credit.expiresDays} days`} after purchase.` | |
| 241 | 241 | } | |
| 242 | 242 | aside={ | |
| 243 | 243 | <p className="shrink-0 sm:text-right"> | |
| 20 | 20 | alertTone, | |
| 21 | 21 | cardFeeCents, | |
| 22 | 22 | creditLine, | |
| 23 | − | dollars, | |
| 24 | 23 | gigabytes, | |
| 25 | 24 | requestStatus, | |
| 26 | 25 | share, | |
| 27 | 26 | shortDay, | |
| 28 | 27 | shownMeters, | |
| 29 | 28 | spendRange, | |
| 30 | − | wholeDollars, | |
| 31 | 29 | } from "../lib/billing"; | |
| 30 | + | import { money, wholeDollars } from "../lib/money"; | |
| 32 | 31 | import { ErrorText, SubmitButton } from "./ui"; | |
| 33 | 32 | import { Badge } from "./ui/badge"; | |
| 34 | 33 | import { RadioGroup, RadioGroupItem } from "./ui/radio-group"; | |
| ⋯ | |||
| 126 | 125 | <div className="flex flex-wrap justify-between gap-x-4 text-sm"> | |
| 127 | 126 | <span className="text-muted">{label}</span> | |
| 128 | 127 | <span className="tabular-nums"> | |
| 129 | − | {dollars(used)} | |
| 130 | − | {of != null && <span className="text-faint"> of {dollars(of)}</span>} | |
| 128 | + | {money(used)} | |
| 129 | + | {of != null && <span className="text-faint"> of {money(of)}</span>} | |
| 131 | 130 | </span> | |
| 132 | 131 | </div> | |
| 133 | 132 | <div className="mt-1.5 h-1.5 overflow-hidden rounded-full bg-line" role="presentation"> | |
| ⋯ | |||
| 236 | 235 | <span className="text-sm text-muted"> / month</span> | |
| 237 | 236 | <span className="block text-xs text-faint">per workspace, never per seat</span> | |
| 238 | 237 | <span className="block text-xs text-faint"> | |
| 239 | − | {plan?.cardFeeCents ? `+ ${dollars(plan.cardFeeCents * 10_000)} card processing fee, ${PLUS_TAX}` : "Plus tax where it applies"} | |
| 238 | + | {plan?.cardFeeCents ? `+ ${money(plan.cardFeeCents * 10_000)} card processing fee, ${PLUS_TAX}` : "Plus tax where it applies"} | |
| 240 | 239 | </span> | |
| 241 | 240 | </p> | |
| 242 | 241 | } | |
| ⋯ | |||
| 348 | 347 | <span className="block truncate text-xs text-faint">{row.quantity ?? "None yet"}</span> | |
| 349 | 348 | )} | |
| 350 | 349 | </span> | |
| 351 | − | <span className={`shrink-0 tabular-nums ${row.micros > 0 ? "" : "text-faint"}`}>{dollars(row.micros)}</span> | |
| 350 | + | <span className={`shrink-0 tabular-nums ${row.micros > 0 ? "" : "text-faint"}`}>{money(row.micros)}</span> | |
| 352 | 351 | </li> | |
| 353 | 352 | ))} | |
| 354 | 353 | </ul> | |
| ⋯ | |||
| 356 | 355 | {/* At price, before the plan's included usage, the trial and the pools paid | |
| 357 | 356 | their part: Usage shows what was charged. */} | |
| 358 | 357 | <span className="font-medium">Total at price</span> | |
| 359 | − | <span className="font-medium tabular-nums">{dollars(total)}</span> | |
| 358 | + | <span className="font-medium tabular-nums">{money(total)}</span> | |
| 360 | 359 | </div> | |
| 361 | 360 | <p className="mt-2 text-xs text-faint"> | |
| 362 | 361 | {comped | |
| 363 | 362 | ? "What this workspace's usage comes to at price. Its 100% discount takes all of it off." | |
| 364 | 363 | : on | |
| 365 | − | ? `Drawn from the included usage first, then charged up to your spend limit. Projects, previews and repositories are never charged, and the first ${freeStorage} of private storage and ${freeGit} git operations a month are free. App traffic, custom domains, storage and git operations are counted through the month and charged when it closes.` | |
| 364 | + | ? `Drawn from the included usage first, then charged up to your spend limit. Projects, previews and repositories are never charged, and the first ${freeStorage} of private storage and ${freeGit} git operations a month are free. App traffic, custom domains, storage and git operations are counted through the month and charged when it closes, after the included usage and any discount, like the rest.` | |
| 366 | 365 | : `The forge is free: ${freeStorage} of private storage and ${freeGit} git operations a month. Agents run from the trial, and checks, workflows and the merge queue on public repositories from the open-source pool; together they pay part of this total: Usage shows what was charged.`} | |
| 367 | 366 | </p> | |
| 368 | 367 | </div> | |
| ⋯ | |||
| 628 | 627 | aside={ | |
| 629 | 628 | <p className="shrink-0 sm:text-right"> | |
| 630 | 629 | <span className="block text-xs text-muted">Credit left</span> | |
| 631 | − | <span className="text-xl font-semibold tabular-nums">{dollars(credits.leftMicros)}</span> | |
| 630 | + | <span className="text-xl font-semibold tabular-nums">{money(credits.leftMicros)}</span> | |
| 632 | 631 | </p> | |
| 633 | 632 | } | |
| 634 | 633 | > | |
| ⋯ | |||
| 664 | 663 | cardFee?: { on: boolean; percentMicros: number; fixedCents: number } | null; | |
| 665 | 664 | error?: string; | |
| 666 | 665 | }) { | |
| 667 | − | const fees = PREPAY.presets.map((amount) => `${dollars(cardFeeCents(amount * 100, cardFee) * 10_000)} on ${wholeDollars(amount * 1_000_000)}`); | |
| 666 | + | const fees = PREPAY.presets.map((amount) => `${money(cardFeeCents(amount * 100, cardFee) * 10_000)} on ${wholeDollars(amount * 1_000_000)}`); | |
| 668 | 667 | return ( | |
| 669 | 668 | <Card | |
| 670 | 669 | id="prepay" | |
| ⋯ | |||
| 674 | 673 | aside={ | |
| 675 | 674 | <p className="shrink-0 sm:text-right"> | |
| 676 | 675 | <span className="block text-xs text-muted">Prepaid balance</span> | |
| 677 | − | <span className="text-xl font-semibold tabular-nums">{dollars(prepaidMicros)}</span> | |
| 676 | + | <span className="text-xl font-semibold tabular-nums">{money(prepaidMicros)}</span> | |
| 678 | 677 | </p> | |
| 679 | 678 | } | |
| 680 | 679 | > | |
| 16 | 16 | import { Button } from "../ui/button"; | |
| 17 | 17 | import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; | |
| 18 | 18 | import { Skeleton } from "../ui/skeleton"; | |
| 19 | − | import { formatDollars } from "../../lib/agent-form"; | |
| 19 | + | import { wholeDollars } from "../../lib/money"; | |
| 20 | 20 | import type { UserCard } from "../../lib/hovercard"; | |
| 21 | 21 | import { localTime } from "../../lib/time-zone"; | |
| 22 | 22 | ||
| ⋯ | |||
| 182 | 182 | <p className="flex justify-between text-xs"> | |
| 183 | 183 | <span>This month</span> | |
| 184 | 184 | <span className="tabular-nums"> | |
| 185 | − | {formatDollars(agent.spent_month_micros)} of {formatDollars(cap)} | |
| 185 | + | {wholeDollars(agent.spent_month_micros)} of {wholeDollars(cap)} | |
| 186 | 186 | </span> | |
| 187 | 187 | </p> | |
| 188 | 188 | <div className="mt-1 h-1.5 overflow-hidden rounded-full bg-line"> | |
| 17 | 17 | import { Card } from "./ui/card"; | |
| 18 | 18 | import { cn } from "../lib/cn"; | |
| 19 | 19 | import { EFFORT_OPTIONS, acceptedLabel, costAt, costLine, effortLabel, savingLabel, totalSaving } from "../lib/effort"; | |
| 20 | − | import { money } from "../lib/usage"; | |
| 20 | + | import { money } from "../lib/money"; | |
| 21 | 21 | ||
| 22 | 22 | /** | |
| 23 | 23 | * The five settings as one segmented control, radio inputs underneath so | |
| ⋯ | |||
| 105 | 105 | {effortLabel(level.effort)} | |
| 106 | 106 | {costs.effort === level.effort && <span className="ml-1.5 text-faint">now</span>} | |
| 107 | 107 | </td> | |
| 108 | − | <td className="py-1.5 pr-3">{at ? money(at.typical_micros!) : <span className="text-faint">Not measured</span>}</td> | |
| 108 | + | <td className="py-1.5 pr-3">{at ? money(at.typical_micros!, { precise: true }) : <span className="text-faint">Not measured</span>}</td> | |
| 109 | 109 | <td className="py-1.5 pr-3 text-muted">{level.sessions}</td> | |
| 110 | 110 | <td className="py-1.5 text-muted">{acceptedLabel(at) || "—"}</td> | |
| 111 | 111 | </tr> | |
| 8 | 8 | import type { GatewayRequest } from "@g1t/contracts"; | |
| 9 | 9 | ||
| 10 | 10 | import { cacheKinds, duration, formatLabel, servedBy, shortCount, statusTone, tokenKinds } from "../lib/gateway"; | |
| 11 | − | import { money } from "../lib/usage"; | |
| 11 | + | import { money } from "../lib/money"; | |
| 12 | 12 | import { TimeAgo } from "./ui"; | |
| 13 | 13 | import { Badge } from "./ui/badge"; | |
| 14 | 14 | import { Card } from "./ui/card"; | |
| ⋯ | |||
| 72 | 72 | </span> | |
| 73 | 73 | </Hint> | |
| 74 | 74 | ) : ( | |
| 75 | − | money(request.chargedMicros) | |
| 75 | + | money(request.chargedMicros, { precise: true }) | |
| 76 | 76 | )} | |
| 77 | 77 | </td> | |
| 78 | 78 | <td className="px-3 py-2 whitespace-nowrap"> | |
| 42 | 42 | OUTCOME_LABEL, | |
| 43 | 43 | SOURCE_LABEL, | |
| 44 | 44 | WINDOWS, | |
| 45 | − | dollars, | |
| 46 | 45 | trendLabel, | |
| 47 | 46 | waited, | |
| 48 | 47 | whenShort, | |
| 49 | 48 | } from "../lib/home"; | |
| 50 | 49 | import { cn } from "../lib/cn"; | |
| 50 | + | import { money } from "../lib/money"; | |
| 51 | 51 | import { ButtonLink } from "./ui"; | |
| 52 | 52 | import { Skeleton } from "./ui/skeleton"; | |
| 53 | 53 | import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; | |
| ⋯ | |||
| 651 | 651 | <Quiet>Billing didn't answer, so spend can't be shown right now.</Quiet> | |
| 652 | 652 | ) : ( | |
| 653 | 653 | <div className="px-5 pb-5"> | |
| 654 | − | <div className="text-3xl font-semibold tracking-tight text-fg tabular-nums">{dollars(spend.totalMicros)}</div> | |
| 654 | + | <div className="text-3xl font-semibold tracking-tight text-fg tabular-nums">{money(spend.totalMicros)}</div> | |
| 655 | 655 | <p className="mt-1 text-xs text-muted"> | |
| 656 | − | {spend.from === spend.to ? utcDay(spend.from) : `${utcDay(spend.from)} – ${utcDay(spend.to)}`} · {dollars(spend.monthMicros)} this month so far | |
| 656 | + | {spend.from === spend.to ? utcDay(spend.from) : `${utcDay(spend.from)} – ${utcDay(spend.to)}`} | |
| 657 | + | {spend.monthMicros != null && ` · ${money(spend.monthMicros)} this month so far`} | |
| 657 | 658 | </p> | |
| 658 | 659 | {spend.lines.length === 0 ? ( | |
| 659 | 660 | <p className="mt-4 text-sm text-muted">Nothing on the statement {inSpan(span)}.</p> | |
| ⋯ | |||
| 669 | 670 | <li key={line.kind} className="flex items-center gap-2 text-sm"> | |
| 670 | 671 | <span className={`size-2 shrink-0 rounded-[2px] ${kindColour(line.kind)}`} aria-hidden /> | |
| 671 | 672 | <span className="min-w-0 grow truncate text-fg-soft">{line.kind}</span> | |
| 672 | − | <span className="text-fg tabular-nums">{dollars(line.micros)}</span> | |
| 673 | + | <span className="text-fg tabular-nums">{money(line.micros)}</span> | |
| 673 | 674 | </li> | |
| 674 | 675 | ))} | |
| 675 | 676 | </ul> | |
| 23 | 23 | parseTab, | |
| 24 | 24 | signedPercent, | |
| 25 | 25 | sortRows, | |
| 26 | − | usd, | |
| 27 | 26 | whyFor, | |
| 28 | 27 | } from "../lib/mission-control"; | |
| 29 | 28 | import { cn } from "../lib/cn"; | |
| 29 | + | import { microsOf, money } from "../lib/money"; | |
| 30 | 30 | import { AgentComposer, type ComposerResult } from "./agent-composer"; | |
| 31 | 31 | import { AgentSetup } from "./agent-setup"; | |
| 32 | 32 | import { InboxNeedsCard } from "./inbox"; | |
| ⋯ | |||
| 54 | 54 | /** Rows a tab shows before "See all". */ | |
| 55 | 55 | const ROWS = 6; | |
| 56 | 56 | ||
| 57 | − | const dollars = (micros: number) => `$${(Math.max(0, micros) / 1_000_000).toFixed(2)}`; | |
| 57 | + | const dollars = (micros: number) => money(Math.max(0, micros)); | |
| 58 | 58 | const plural = (n: number, one: string, many = `${one}s`) => `${n.toLocaleString("en-US")} ${n === 1 ? one : many}`; | |
| 59 | 59 | ||
| 60 | 60 | // --- Small pieces ----------------------------------------------------------------- | |
| ⋯ | |||
| 1151 | 1151 | to={`/${workspace}/-/usage`} | |
| 1152 | 1152 | className="mt-4 flex items-center justify-between border-t border-line pt-3 text-xs text-muted hover:text-fg" | |
| 1153 | 1153 | > | |
| 1154 | − | <span>Usage at price this week: {usd(stats.weekCost)}</span> | |
| 1154 | + | <span>Usage at price this week: {money(microsOf(stats.weekCost))}</span> | |
| 1155 | 1155 | <ChevronRight size={12} /> | |
| 1156 | 1156 | </Link> | |
| 1157 | 1157 | )} | |
| 15 | 15 | ||
| 16 | 16 | import type { IssueProgress, Plan } from "@g1t/contracts"; | |
| 17 | 17 | import { Avatar } from "./ui/avatar"; | |
| 18 | + | import { money } from "../lib/money"; | |
| 18 | 19 | ||
| 19 | 20 | type Look = { label: string; icon: ReactNode; ring: string; text: string; bar: string; live?: boolean }; | |
| 20 | 21 | ||
| ⋯ | |||
| 233 | 234 | ["Needs you", String(needsYou), needsYou > 0 ? "text-warn" : "text-muted"], | |
| 234 | 235 | [ | |
| 235 | 236 | "Agents have cost", | |
| 236 | − | costMicros == null ? "—" : `$${(costMicros / 1_000_000).toFixed(2)}`, | |
| 237 | + | costMicros == null ? "—" : money(costMicros), | |
| 237 | 238 | "text-fg", | |
| 238 | 239 | ], | |
| 239 | 240 | ].map(([label, value, tone]) => ( | |
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
This change is too large to show in full.