1import { describe, expect, it } from "@effect/vitest";
2
3import { createTranscriptJsonReader, TranscriptJsonLimitError } from "./AgentSessionJson.ts";
4
5function read(
6 json: string,
7 size: number,
8 select: (path: ReadonlyArray<string | number | null>) => boolean = () => true,
9) {
10 const reader = createTranscriptJsonReader(() => {}, select);
11 for (let offset = 0; offset < json.length; offset += size)
12 reader.write(json.slice(offset, offset + size));
13 return reader.finish();
14}
15
16describe("transcript JSON projection", () => {
17 it.each([1, 2, 7, 64, 1024])("matches JSON.parse across %i-character boundaries", (size) => {
18 for (const text of [
19 '{"a":1,"a":2,"b":"before","b":"after"}',
20 '{"a":{"x":1},"a":{"y":2},"b":[],"c":{}}',
21 '{"a":[null,true,false,1,-2.3e4,"😀\\u0061\\\\\\\"",{},[],[1,2]]}',
22 '{"a":"s","a":null,"b":null,"b":"s","c":0,"c":false}',
23 '{"__proto__":{"polluted":true},"constructor":1,"__proto__":2}',
24 ])
25 expect(read(text, size)).toEqual(JSON.parse(text));
26 });
27
28 it("projects siblings and array elements without merging repeated parent objects", () => {
29 const text =
30 '{"message":{"usage":{"input":100},"content":"large"},"message":{"usage":{"output":5},"content":[1,2]},"rows":[{"keep":1,"drop":2},{"keep":3}],"drop":{"keep":4}}';
31 const projected = read(text, 1, (path) => {
32 if (path[0] === "drop") return false;
33 return !path.includes("content") && !path.includes("drop");
34 });
35 expect(projected).toEqual({
36 message: { usage: { output: 5 } },
37 rows: [{ keep: 1 }, { keep: 3 }],
38 });
39 });
40
41 it.each(['{"a":', '{"a":1} trailing', '{"a":1}{"a":2}', '{"a":"bad\\x"}', '{"a":[1,]}'])(
42 "rejects malformed input %s",
43 (text) => {
44 expect(read(text, 1)).toBeUndefined();
45 },
46 );
47
48 it("retains the import allocation and depth limits", () => {
49 const limited = createTranscriptJsonReader(
50 () => {
51 throw new TranscriptJsonLimitError("budget");
52 },
53 () => true,
54 );
55 expect(() => limited.write('{"a":1}')).toThrow(TranscriptJsonLimitError);
56 expect(() => read("[".repeat(129) + "0" + "]".repeat(129), 10)).toThrow(
57 TranscriptJsonLimitError,
58 );
59 });
60});