Skip to content
105 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { AgentRun, Price, 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: "implement",
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 { id: "rtk_1", run_id: "run_mine", kind: "implement", title: "On my runner", repo: "acme/web", status: "in_progress", runner_name: "mac", created_at: "2026-10-10T09:00:00Z", started_at: null },
38 { id: "rtk_2", run_id: null, kind: "checks", title: "Waiting", repo: "acme/web", status: "queued", runner_name: null, created_at: "2026-10-10T09:00:00Z", started_at: null },
39 ],
40 self_hosted_jobs_queued: 3,
41};
42
43test("g1t cloud is the running agent runs not handed to the workspace's runners, and the jobs in g1t's sandboxes", () => {
44 const now = cloudNow([run("run_a", "running"), run("run_mine", "running"), run("run_q", "queued")], activity, 200, 100)!;
45 assert.deepEqual(
46 now.running.map((work) => [work.kind, work.id, work.href]),
47 [
48 ["workflow", "job_1", "/acme/web/actions/runs/wr_1"],
49 ["agent", "run_a", "/acme/web/agents/runs/run_a"],
50 ],
51 );
52 // One queued agent run and two queued jobs.
53 assert.equal(now.queued, 3);
54 assert.equal(now.more, false);
55});
56
57test("a service that does not answer leaves only its part out", () => {
58 assert.equal(cloudNow(null, null, 200, 100), null);
59 assert.equal(cloudNow([run("run_a", "running")], null, 200, 100)!.running.length, 1);
60 assert.equal(ownWaiting(null), null);
61 assert.deepEqual(ownWaiting(activity), { tasks: 1, jobs: 3 });
62 assert.equal(runnerCost(null), null);
63});
64
65test("a full list says there may be more", () => {
66 assert.equal(cloudNow([run("run_a", "running")], activity, 1, 100)!.more, true);
67});
68
69test("this month's time adds agent sandbox and sandbox time for g1t cloud, and self-hosted time for the workspace's own", () => {
70 const meter = (key: string, quantity: number, micros: number) => ({ key, label: key, product: "x", unit: "seconds", quantity, micros, daily: [], byProject: [] });
71 const report = {
72 from: "2026-10-01",
73 until: "2026-10-10",
74 free: false,
75 products: [
76 { key: "agent", label: "Agent", micros: 0, meters: [meter("agent_sandbox", 600, 1_000_000), meter("agent_models", 9, 5_000_000)] },
77 { key: "sandboxes", label: "Sandboxes", micros: 0, meters: [meter("sandbox", 120, 200_000), meter("self_hosted", 3600, 0)] },
78 ],
79 } as unknown as UsageReport;
80 assert.deepEqual(runnerCost(report), { from: "2026-10-01", until: "2026-10-10", cloudSeconds: 720, cloudMicros: 1_200_000, ownSeconds: 3600, free: false });
81});
82
83test("prices are the sandbox's parts when both are priced, else the one price, per minute", () => {
84 const price = (meter: string, unit: string, priceMicros: number) => ({ meter, title: meter, unit, priceMicros }) as Price;
85 assert.deepEqual(
86 cloudPrices([price("sandbox_second", "second", 25), price("sandbox_base_second", "second", 14.5), price("sandbox_cpu_second", "vCPU-second", 24)])!.map((p) => [p.meter, p.unit, p.perMinuteMicros]),
87 [
88 ["sandbox_base_second", "minute", 870],
89 ["sandbox_cpu_second", "vCPU-minute", 1440],
90 ],
91 );
92 assert.deepEqual(cloudPrices([price("sandbox_second", "second", 25)])!.map((p) => p.perMinuteMicros), [1500]);
93 assert.equal(cloudPrices([]), null);
94 assert.equal(cloudPrices(null), null);
95});
96
97test("machine time and prices read as people say them", () => {
98 assert.equal(machineTime(0), "0 min");
99 assert.equal(machineTime(45), "45s");
100 assert.equal(machineTime(720), "12 min");
101 assert.equal(machineTime(12_240), "3.4 h");
102 assert.equal(perMinute(1500), "$0.0015");
103 assert.equal(perMinute(870), "$0.00087");
104 assert.equal(perMinute(0), "$0");
105});