History

feat(orchestration): bound thread history and resume payloads

- Add paginated thread history with bounded snapshots and replay limits - Trim oversized wire payloads and support progressive mobile history loading

Julius Marminge committed 3 months ago

Commit 7d42d311 parent e3883a943 files · +4239 −211Browse files

Showing the changes to packages/client-runtime/src/state/boundedThreadSnapshotHttp.test.ts (1 of 43 files). Show all

Unified · Split

packages/client-runtime/src/state/boundedThreadSnapshotHttp.test.ts+214−0
@@ -0,0 +1,214 @@
1import { ThreadId } from "@t3tools/contracts";
2import { describe, expect, it } from "@effect/vitest";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5
6import { PrimaryConnectionTarget, type PreparedConnection } from "../connection/model.ts";
7import { remoteHttpClientLayer } from "../rpc/http.ts";
8import { boundedThreadSnapshotLoaderLayer } from "./boundedThreadSnapshotHttp.ts";
9import { ThreadSnapshotLoader } from "./threadSnapshotHttp.ts";
10
11const TARGET = new PrimaryConnectionTarget({
12 environmentId: "environment-bounded" as never,
13 label: "Bounded",
14 httpBaseUrl: "https://environment.example.test",
15 wsBaseUrl: "wss://environment.example.test",
16});
17
18const PREPARED: PreparedConnection = {
19 environmentId: TARGET.environmentId,
20 label: TARGET.label,
21 httpBaseUrl: TARGET.httpBaseUrl,
22 socketUrl: TARGET.wsBaseUrl,
23 httpAuthorization: null,
24 target: TARGET,
25};
26
27const THREAD_ID = ThreadId.make("thread-bounded");
28const NOW = "2026-06-20T00:00:00.000Z";
29
30const FULL_SNAPSHOT_BODY = {
31 snapshotSequence: 9,
32 projection: {
33 thread: {
34 createdBy: "user",
35 creationSource: "web",
36 id: String(THREAD_ID),
37 projectId: "project-1",
38 title: "Full fallback thread",
39 providerInstanceId: "codex",
40 modelSelection: { instanceId: "codex", model: "gpt-5" },
41 runtimeMode: "full-access",
42 interactionMode: "default",
43 branch: null,
44 worktreePath: null,
45 activeProviderThreadId: null,
46 lineage: {
47 parentThreadId: null,
48 relationshipToParent: null,
49 rootThreadId: String(THREAD_ID),
50 },
51 forkedFrom: null,
52 createdAt: NOW,
53 updatedAt: NOW,
54 archivedAt: null,
55 deletedAt: null,
56 settledOverride: null,
57 settledAt: null,
58 },
59 runs: [],
60 attempts: [],
61 nodes: [],
62 subagents: [],
63 providerSessions: [],
64 providerThreads: [],
65 providerTurns: [],
66 runtimeRequests: [],
67 messages: [],
68 plans: [],
69 turnItems: [],
70 checkpointScopes: [],
71 checkpoints: [],
72 contextHandoffs: [],
73 contextTransfers: [],
74 visibleTurnItems: [],
75 updatedAt: NOW,
76 },
77};
78
79describe("boundedThreadSnapshotLoader", () => {
80 it.effect("falls back to full HTTP snapshot when bounded returns a plain route 404", () => {
81 const fetchFn = ((input: RequestInfo | URL) => {
82 const url = String(input);
83 if (url.includes(`/api/orchestration/threads/${THREAD_ID}/bounded`)) {
84 // Generic HTML/route 404: not a structured thread_not_found payload.
85 return Promise.resolve(
86 new Response("Not Found", {
87 status: 404,
88 headers: { "content-type": "text/plain" },
89 }),
90 );
91 }
92 expect(url).toContain(`/api/orchestration/threads/${THREAD_ID}`);
93 expect(url.includes("/bounded")).toBe(false);
94 return Promise.resolve(
95 new Response(JSON.stringify(FULL_SNAPSHOT_BODY), {
96 status: 200,
97 headers: { "content-type": "application/json" },
98 }),
99 );
100 }) satisfies typeof fetch;
101
102 return Effect.gen(function* () {
103 const loader = yield* ThreadSnapshotLoader;
104 const result = yield* loader.load(PREPARED, THREAD_ID);
105 expect(result._tag).toBe("present");
106 if (result._tag === "present") {
107 expect(result.snapshot.snapshotSequence).toBe(9);
108 expect(result.snapshot.projection.thread.title).toBe("Full fallback thread");
109 expect(result.history).toBeUndefined();
110 }
111 }).pipe(
112 Effect.provide(
113 Layer.provide(boundedThreadSnapshotLoaderLayer, remoteHttpClientLayer(fetchFn)),
114 ),
115 );
116 });
117
118 it.effect("treats structured EnvironmentResourceNotFoundError from bounded as missing", () => {
119 const fetchFn = ((input: RequestInfo | URL) => {
120 const url = String(input);
121 expect(url).toContain(`/api/orchestration/threads/${THREAD_ID}/bounded`);
122 return Promise.resolve(
123 Response.json(
124 {
125 _tag: "EnvironmentResourceNotFoundError",
126 code: "not_found",
127 reason: "thread_not_found",
128 traceId: "trace-bounded-missing",
129 },
130 { status: 404 },
131 ),
132 );
133 }) satisfies typeof fetch;
134
135 return Effect.gen(function* () {
136 const loader = yield* ThreadSnapshotLoader;
137 const result = yield* loader.load(PREPARED, THREAD_ID);
138 expect(result).toEqual({ _tag: "missing" });
139 }).pipe(
140 Effect.provide(
141 Layer.provide(boundedThreadSnapshotLoaderLayer, remoteHttpClientLayer(fetchFn)),
142 ),
143 );
144 });
145
146 it.effect("treats structured missing from full fallback as missing", () => {
147 let fullCalls = 0;
148 const fetchFn = ((input: RequestInfo | URL) => {
149 const url = String(input);
150 if (url.includes("/bounded")) {
151 return Promise.resolve(
152 new Response("Not Found", {
153 status: 404,
154 headers: { "content-type": "text/plain" },
155 }),
156 );
157 }
158 fullCalls += 1;
159 return Promise.resolve(
160 Response.json(
161 {
162 _tag: "EnvironmentResourceNotFoundError",
163 code: "not_found",
164 reason: "thread_not_found",
165 traceId: "trace-full-missing",
166 },
167 { status: 404 },
168 ),
169 );
170 }) satisfies typeof fetch;
171
172 return Effect.gen(function* () {
173 const loader = yield* ThreadSnapshotLoader;
174 const result = yield* loader.load(PREPARED, THREAD_ID);
175 expect(result).toEqual({ _tag: "missing" });
176 expect(fullCalls).toBe(1);
177 }).pipe(
178 Effect.provide(
179 Layer.provide(boundedThreadSnapshotLoaderLayer, remoteHttpClientLayer(fetchFn)),
180 ),
181 );
182 });
183
184 it.effect(
185 "uses socket fallback directly when the bounded endpoint has a transient failure",
186 () => {
187 let fullCalls = 0;
188 const fetchFn = ((input: RequestInfo | URL) => {
189 const url = String(input);
190 if (url.includes("/bounded")) {
191 return Promise.resolve(new Response("Service Unavailable", { status: 503 }));
192 }
193 fullCalls += 1;
194 return Promise.resolve(
195 new Response(JSON.stringify(FULL_SNAPSHOT_BODY), {
196 status: 200,
197 headers: { "content-type": "application/json" },
198 }),
199 );
200 }) satisfies typeof fetch;
201
202 return Effect.gen(function* () {
203 const loader = yield* ThreadSnapshotLoader;
204 const result = yield* loader.load(PREPARED, THREAD_ID);
205 expect(result).toEqual({ _tag: "unavailable" });
206 expect(fullCalls).toBe(0);
207 }).pipe(
208 Effect.provide(
209 Layer.provide(boundedThreadSnapshotLoaderLayer, remoteHttpClientLayer(fetchFn)),
210 ),
211 );
212 },
213 );
214});