| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import { gather, homeKey, ndjson, outcomeLine, transcriptOf } from "./computer-shape.ts"; |
| 5 | |
| 6 | test("a computer's home is kept under one key per agent", () => { |
| 7 | assert.equal(homeKey("agt_1"), "homes/agt_1.tar.zst"); |
| 8 | }); |
| 9 | |
| 10 | test("lines gather in order and stop at the cap", () => { |
| 11 | const lines = [ |
| 12 | { stream: "stdout", line: "one" }, |
| 13 | { stream: "stderr", line: "two" }, |
| 14 | { stream: "stdout", line: "three" }, |
| 15 | ]; |
| 16 | assert.deepEqual(gather(lines), { output: "one\ntwo\nthree", truncated: false }); |
| 17 | assert.deepEqual(gather(lines, 7), { output: "one\ntwo", truncated: true }); |
| 18 | assert.deepEqual(gather([]), { output: "", truncated: false }); |
| 19 | }); |
| 20 | |
| 21 | test("how a command ended reads as one line", () => { |
| 22 | assert.equal(outcomeLine({ exit_code: 0, duration_ms: 1234, timed_out: false, truncated: false }), "exit 0 · 1.2 s"); |
| 23 | assert.equal(outcomeLine({ exit_code: 1, duration_ms: 42_000, timed_out: false, truncated: true }), "exit 1 · 42 s · output cut"); |
| 24 | assert.equal(outcomeLine({ exit_code: 124, duration_ms: 300_000, timed_out: true, truncated: false }), "exit 124 · 5 min · timed out"); |
| 25 | }); |
| 26 | |
| 27 | test("a transcript carries the closing note, or says the computer stopped answering", () => { |
| 28 | const lines = [{ stream: "stdout", line: "hello" }]; |
| 29 | const plain = transcriptOf(lines, { exit_code: 0, duration_ms: 10 }, null); |
| 30 | assert.equal(plain.output, "hello"); |
| 31 | assert.equal(plain.exit_code, 0); |
| 32 | assert.equal(plain.duration_ms, 10); |
| 33 | const cut = transcriptOf(lines, { exit_code: 0, duration_ms: 10, truncated: true, note: "Output past 1.0 MB was dropped: 5 more bytes." }, null); |
| 34 | assert.equal(cut.output, "hello\n[Output past 1.0 MB was dropped: 5 more bytes.]"); |
| 35 | assert.equal(cut.truncated, true); |
| 36 | const gone = transcriptOf(lines, null, "connection reset"); |
| 37 | assert.equal(gone.exit_code, -1); |
| 38 | assert.equal(gone.duration_ms, null); |
| 39 | assert.equal(gone.output, "hello\n[The computer stopped answering: connection reset.]"); |
| 40 | assert.equal(transcriptOf([], null, null).output, "[The computer stopped answering.]"); |
| 41 | }); |
| 42 | |
| 43 | test("NDJSON is read line by line across chunks, skipping what is not an object", async () => { |
| 44 | const parts = ['{"stream":"stdout","li', 'ne":"a"}\n{"stream":"stderr","line":"b"}\nnot json\n[1]\n{"exit_code":0,"duration_ms":5}']; |
| 45 | const body = new ReadableStream<Uint8Array>({ |
| 46 | start(controller) { |
| 47 | for (const part of parts) controller.enqueue(new TextEncoder().encode(part)); |
| 48 | controller.close(); |
| 49 | }, |
| 50 | }); |
| 51 | const seen: Record<string, unknown>[] = []; |
| 52 | for await (const line of ndjson(body)) seen.push(line); |
| 53 | assert.deepEqual(seen, [ |
| 54 | { stream: "stdout", line: "a" }, |
| 55 | { stream: "stderr", line: "b" }, |
| 56 | { exit_code: 0, duration_ms: 5 }, |
| 57 | ]); |
| 58 | }); |