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.
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 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. | 4 | import { money } from "./money.ts"; |
| 5 | import { monthSpan, spanFor } from "./spend.ts"; | |
| 6 | import { byProject, columns, defaultGrain, isStaff, pendingSentence, quantity, receipt, resolveRange, ticks, usageCsv } from "./usage.ts"; | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 7 | |
| 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. | 8 | const axisMoney = (micros: number) => money(micros, { compact: true }); |
| 9 | ||
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 10 | const NOW = new Date("2026-10-08T15:00:00Z"); |
| 11 | ||
| 12 | test("periods resolve to whole UTC days, both ends included", () => { | |
| 13 | assert.deepEqual(resolveRange(null, {}, NOW), { period: "cycle", from: "2026-10-01", until: "2026-10-08" }); | |
| 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. | 14 | // The current cycle is the one "this month" Spend and the top bar ask billing for. |
| 15 | assert.deepEqual(resolveRange("cycle", {}, NOW), { period: "cycle", ...monthSpan(NOW) }); | |
| 16 | assert.deepEqual(spanFor("month", NOW), monthSpan(NOW)); | |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 17 | assert.deepEqual(resolveRange("last_cycle", {}, NOW), { period: "last_cycle", from: "2026-09-01", until: "2026-09-30" }); |
| 18 | assert.deepEqual(resolveRange("7d", {}, NOW), { period: "7d", from: "2026-10-02", until: "2026-10-08" }); | |
| 19 | assert.deepEqual(resolveRange("custom", { from: "2026-10-05", until: "2026-09-20" }, NOW), { period: "custom", from: "2026-09-20", until: "2026-10-05" }); | |
| 20 | // A custom range that is not days falls back to the cycle. | |
| 21 | assert.equal(resolveRange("custom", { from: "yesterday", until: "2026-10-01" }, NOW).period, "cycle"); | |
| 22 | assert.equal(resolveRange("nonsense", {}, NOW).period, "cycle"); | |
| 23 | }); | |
| 24 | ||
| 25 | test("columns cover every day, stack products, and add up when cumulative", () => { | |
| 26 | const days = [ | |
| 27 | { day: "2026-10-01", product: "agent", micros: 2_000_000 }, | |
| 28 | { day: "2026-10-01", product: "sandboxes", micros: 500_000 }, | |
| 29 | { day: "2026-10-03", product: "agent", micros: 1_000_000 }, | |
| 30 | ]; | |
| 31 | const daily = columns(days, "2026-10-01", "2026-10-03"); | |
| 32 | assert.deepEqual(daily.map((c) => c.total), [2_500_000, 0, 1_000_000]); | |
| 33 | assert.equal(daily[0]!.parts.sandboxes, 500_000); | |
| 34 | const running = columns(days, "2026-10-01", "2026-10-03", "day", true); | |
| 35 | assert.deepEqual(running.map((c) => c.total), [2_500_000, 2_500_000, 3_500_000]); | |
| 36 | assert.equal(running[2]!.parts.agent, 3_000_000); | |
| 37 | // Weeks start on Monday: Oct 1 2026 is a Thursday. | |
| 38 | const weekly = columns(days, "2026-10-01", "2026-10-08", "week"); | |
| 39 | assert.deepEqual(weekly.map((c) => c.key), ["2026-09-28", "2026-10-05"]); | |
| 40 | assert.equal(weekly[0]!.total, 3_500_000); | |
| 41 | assert.equal(columns(days, "2026-09-15", "2026-10-03", "month").length, 2); | |
| 42 | assert.equal(defaultGrain("2026-10-01", "2026-10-31"), "day"); | |
| 43 | assert.equal(defaultGrain("2026-07-01", "2026-10-01"), "week"); | |
| 44 | }); | |
| 45 | ||
| 46 | test("the money axis has clean ticks, never the same label twice", () => { | |
| 47 | assert.deepEqual(ticks(0), [0]); | |
| 48 | const t = ticks(3_320_000); | |
| 49 | assert.equal(t[0], 0); | |
| 50 | assert.ok(t[t.length - 1]! >= 3_320_000); | |
| 51 | const labels = t.map(axisMoney); | |
| 52 | assert.equal(new Set(labels).size, labels.length, labels.join(" ")); | |
| 53 | const small = ticks(4_000).map(axisMoney); | |
| 54 | assert.equal(new Set(small).size, small.length, small.join(" ")); | |
| 55 | assert.equal(axisMoney(2_000_000), "$2"); | |
| 56 | assert.equal(axisMoney(1_500_000_000), "$1.5K"); | |
| 57 | }); | |
| 58 | ||
| 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. | 59 | test("quantities read in their units", () => { |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 60 | assert.equal(quantity(1_234_567, "tokens"), "1.2M tokens"); |
| 61 | assert.equal(quantity(3_725, "seconds"), "1h 2m"); | |
| 62 | assert.equal(quantity(504_000_000, "bytes"), "504 MB"); | |
| 63 | assert.equal(quantity(1, "entries"), "1 entry"); | |
| 64 | }); | |
| 65 | ||
| 66 | test("the receipt shows usage at price, then what paid, then what is charged", () => { | |
| 67 | const lines = receipt( | |
| 68 | { priceMicros: 3_320_000, discountMicros: 3_320_000, includedMicros: 0, creditsMicros: 0, chargedMicros: 0, pendingMicros: 0, costMicros: 0 }, | |
| 69 | 100, | |
| 70 | ); | |
| 71 | assert.deepEqual(lines.map((l) => l.label), ["Usage at price", "Discount (100%)", "Charged"]); | |
| 72 | }); | |
| 73 | ||
| 74 | test("grouping by project adds a project's meters together", () => { | |
| 75 | const meter = (label: string, parts: [string, number][]) => ({ | |
| 76 | key: label, label, product: "agent", unit: "entries", quantity: 0, micros: 0, daily: [], | |
| 77 | byProject: parts.map(([project, micros]) => ({ project, micros, quantity: 0 })), | |
| 78 | }); | |
| 79 | const rows = byProject({ | |
| 80 | products: [{ key: "agent", label: "Agent", micros: 0, meters: [meter("Model tokens", [["acme/web", 300], ["acme/api", 500]]), meter("Agent rate", [["acme/web", 400]])] }], | |
| 81 | }); | |
| 82 | assert.deepEqual(rows.map((r) => [r.project, r.micros]), [["acme/web", 700], ["acme/api", 500]]); | |
| 83 | }); | |
| 84 | ||
| 85 | test("the CSV has a row per day and meter with anything on it", () => { | |
| 86 | const csv = usageCsv({ | |
| 87 | from: "2026-10-01", | |
| 88 | until: "2026-10-02", | |
| 89 | products: [{ key: "agent", label: "Agent", micros: 0, meters: [{ key: "agent_models", label: "Model tokens", product: "agent", unit: "tokens", quantity: 0, micros: 0, daily: [0, 1_500_000], byProject: [] }] }], | |
| 90 | }); | |
| 91 | assert.equal(csv, "day,product,meter,usd\n2026-10-02,Agent,Model tokens,1.500000\n"); | |
| 92 | }); | |
| 93 | ||
| 94 | test("test-mode hints are for g1t's own people", () => { | |
| 95 | assert.ok(isStaff({ workspaces: [{ slug: "flagon-io" }] })); | |
| 96 | assert.ok(!isStaff({ workspaces: [{ slug: "acme" }] })); | |
| 97 | assert.ok(!isStaff(null)); | |
| 98 | }); | |
| Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero | 99 | |
| 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. | 100 | test("the pending note says what the close will charge, with the fraction of a cent that explains a gap", () => { |
| 101 | // A comped workspace: $0.009 pending, nothing of it charged. | |
| 102 | assert.equal( | |
| 103 | pendingSentence({ pendingMicros: 9_000, pendingChargedMicros: 0 }), | |
| 104 | "$0.009 of it is metered this month and not yet closed; nothing will be charged when the month closes, after your discount and included usage.", | |
| 105 | ); | |
| 106 | assert.equal( | |
| 107 | pendingSentence({ pendingMicros: 9_000, pendingChargedMicros: 6_300 }), | |
| 108 | "$0.009 of it is metered this month and not yet closed; $0.0063 of it will be charged when the month closes, after your discount and included usage.", | |
| 109 | ); | |
| 110 | assert.equal( | |
| 111 | pendingSentence({ pendingMicros: 1_340_000, pendingChargedMicros: 1_340_000 }), | |
| 112 | "$1.34 of it is metered this month and not yet closed; all of it will be charged when the month closes, after your discount and included usage.", | |
| 113 | ); | |
| 114 | // An older billing, with no split yet. | |
| 115 | assert.equal(pendingSentence({ pendingMicros: 9_000 }), "$0.009 of it is metered this month and not yet closed; it is charged when the month closes."); | |
| Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero | 116 | }); |