Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

144 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { AgentRun, RunnerActivity, UsageReport } from "@g1t/contracts";
5
6import { cloudNow, cloudPrices, machineTime, ownWaiting, perMinute, runnerCost } from "./runners.ts";
7
8function run(id: string, status: AgentRun["status"], over: Partial<AgentRun> = {}): AgentRun {
9 return {
10 id,
11 repo: { namespace: "acme", name: "web" },
12 number: 12,
13 title: "Fix the flaky checkout test",
14 kind: "checks",
15 agent: "g1t",
16 model: null,
17 status,
18 step: null,
19 steps: [],
20 stepCount: 0,
21 startedBy: null,
22 error: null,
23 costUsd: null,
24 turns: null,
25 createdAt: "2026-10-10T10:00:00Z",
26 startedAt: "2026-10-10T10:01:00Z",
27 finishedAt: null,
28 updatedAt: "2026-10-10T10:02:00Z",
29 ...over,
30 };
31}
32
33const activity: RunnerActivity = {
34 cloud_jobs: [{ id: "job_1", name: "test (ubuntu)", run_id: "wr_1", repo: "acme/web", started_at: "2026-10-10T10:05:00Z" }],
35 cloud_jobs_queued: 2,
36 handed_over: [
37 {
38 id: "rtk_1",
39 run_id: "run_mine",
40 kind: "implement",
41 title: "On my runner",
42 repo: "acme/web",
43 status: "in_progress",
44 runner_name: "mac",
45 created_at: "2026-10-10T09:00:00Z",
46 started_at: null,
47 },
48 {
49 id: "rtk_2",
50 run_id: null,
51 kind: "checks",
52 title: "Waiting",
53 repo: "acme/web",
54 status: "queued",
55 runner_name: null,
56 created_at: "2026-10-10T09:00:00Z",
57 started_at: null,
58 },
59 ],
60 self_hosted_jobs_queued: 3,
61};
62
63test("g1t cloud is the running sandbox runs not handed to the workspace's runners, and the jobs in g1t's sandboxes", () => {
64 const now = cloudNow([run("run_a", "running"), run("run_mine", "running"), run("run_q", "queued")], activity, 200, 100)!;
65 assert.deepEqual(
66 now.running.map((work) => [work.kind, work.id, work.href]),
67 [
68 ["workflow", "job_1", "/acme/web/actions/runs/wr_1"],
69 ["agent", "run_a", "/acme/web/pull/12"],
70 ],
71 );
72 // One queued sandbox run and two queued jobs.
73 assert.equal(now.queued, 3);
74 assert.equal(now.more, false);
75});
76
77test("a service that does not answer leaves only its part out", () => {
78 assert.equal(cloudNow(null, null, 200, 100), null);
79 assert.equal(cloudNow([run("run_a", "running")], null, 200, 100)!.running.length, 1);
80 assert.equal(ownWaiting(null), null);
81 assert.deepEqual(ownWaiting(activity), { tasks: 1, jobs: 3 });
82 assert.equal(runnerCost(null), null);
83});
84
85test("a full list says there may be more", () => {
86 assert.equal(cloudNow([run("run_a", "running")], activity, 1, 100)!.more, true);
87});
88
89test("this month's time adds agent sandbox and sandbox time for g1t cloud, and self-hosted time for the workspace's own", () => {
90 const meter = (key: string, quantity: number, micros: number) => ({
91 key,
92 label: key,
93 product: "x",
94 unit: "seconds",
95 quantity,
96 micros,
97 daily: [],
98 byProject: [],
99 });
100 const report = {
101 from: "2026-10-01",
102 until: "2026-10-10",
103 free: false,
104 products: [
105 { key: "agent", label: "Agent", micros: 0, meters: [meter("agent_sandbox", 600, 1_000_000), meter("agent_models", 9, 5_000_000)] },
106 {
107 key: "sandboxes",
108 label: "Sandboxes",
109 micros: 0,
110 meters: [meter("runner_2-core", 10, 40_000), meter("runner_4-core", 2, 14_000), meter("sandbox", 120, 200_000), meter("self_hosted", 3600, 0)],
111 },
112 ],
113 } as unknown as UsageReport;
114 // Runner minutes count as their minutes; older sandbox time by the second.
115 assert.deepEqual(runnerCost(report), {
116 from: "2026-10-01",
117 until: "2026-10-10",
118 cloudSeconds: 720 + 12 * 60,
119 cloudMicros: 1_254_000,
120 ownSeconds: 3600,
121 free: false,
122 });
123});
124
125test("prices are each machine's rate a minute, from the catalogue", () => {
126 assert.deepEqual(
127 cloudPrices().map((p) => [p.machine, p.perMinuteMicros, p.title]),
128 [
129 ["2-core", 4000, "2-core: 2 vCPU, 8 GiB, 16 GB"],
130 ["4-core", 7000, "4-core: 4 vCPU, 12 GiB, 20 GB"],
131 ],
132 );
133 assert.equal(perMinute(4000), "$0.004");
134});
135
136test("machine time and prices read as people say them", () => {
137 assert.equal(machineTime(0), "0 min");
138 assert.equal(machineTime(45), "45s");
139 assert.equal(machineTime(720), "12 min");
140 assert.equal(machineTime(12_240), "3.4 h");
141 assert.equal(perMinute(1500), "$0.0015");
142 assert.equal(perMinute(870), "$0.00087");
143 assert.equal(perMinute(0), "$0");
144});