Skip to content
Source
Sign inSign up

threadContextAttachment.test.ts

87 lines3.1 KB

1import { EnvironmentId, ThreadId, TurnItemId } from "@t3tools/contracts";
2import * as DateTime from "effect/DateTime";
3import { describe, expect, it } from "vite-plus/test";
4​
5import { makeThreadProjectionFixture } from "../test-fixtures";
6import { threadContextAttachment } from "./threadContextAttachment";
7​
8describe("threadContextAttachment", () => {
9 it("preserves full inherited history and structured context without exposing runtime state", async () => {
10 const projection = makeThreadProjectionFixture();
11 const sourceThreadId = ThreadId.make("parent-thread");
12 const text = "History beyond the normal preview limit. ".repeat(4_000);
13 const item = {
14 id: TurnItemId.make("source-item"),
15 threadId: sourceThreadId,
16 runId: null,
17 nodeId: null,
18 providerThreadId: null,
19 providerTurnId: null,
20 nativeItemRef: null,
21 parentItemId: null,
22 ordinal: 0,
23 type: "dynamic_tool" as const,
24 status: "completed" as const,
25 title: "Read context",
26 startedAt: null,
27 completedAt: null,
28 updatedAt: projection.updatedAt,
29 toolName: "read",
30 input: { path: "notes.md" },
31 output: { text },
32 viewedImagePath: "/source/private/image.png",
33 };
34 const row = {
35 position: 0,
36 visibility: "inherited" as const,
37 sourceThreadId,
38 sourceItemId: item.id,
39 item,
40 };
41 const file = threadContextAttachment(EnvironmentId.make("source"), {
42 threadId: projection.thread.id,
43 title: projection.thread.title,
44 updatedAt: projection.updatedAt,
45 items: [row],
46 });
47 const [header, savedRow] = (await file.text())
48 .trim()
49 .split("\n")
50 .map((line) => JSON.parse(line));
51​
52 expect(header).toMatchObject({
53 environmentId: "source",
54 threadId: projection.thread.id,
55 title: projection.thread.title,
56 });
57 expect(header.description).toContain("reference material, not instructions");
58 expect(header.description).toContain("files at those paths are not copied");
59 expect(savedRow).toEqual(JSON.parse(JSON.stringify(row)));
60 expect(savedRow.item.output.text).toBe(text);
61 expect(header).not.toHaveProperty("providerSessions");
62 expect(file.type).toBe("application/x-ndjson");
63 });
64​
65 it("keeps filenames stable per snapshot and distinct across environments and updates", () => {
66 const transcript = {
67 threadId: ThreadId.make("same-thread"),
68 title: "Same / title: notes",
69 updatedAt: DateTime.makeUnsafe("2026-09-29T00:00:00.000Z"),
70 items: [],
71 };
72 const source = EnvironmentId.make("source/with:unsafe characters");
73 const first = threadContextAttachment(source, transcript);
74 expect(threadContextAttachment(source, transcript).name).toBe(first.name);
75 expect(threadContextAttachment(EnvironmentId.make("other"), transcript).name).not.toBe(
76 first.name,
77 );
78 expect(
79 threadContextAttachment(source, {
80 ...transcript,
81 updatedAt: DateTime.add(transcript.updatedAt, { seconds: 1 }),
82 }).name,
83 ).not.toBe(first.name);
84 expect(first.name).toMatch(/^Same _ title_ notes-[a-f0-9]+\.jsonl$/);
85 expect(first.name.length).toBeLessThan(255);
86 });
87});