Skip to content
Source
Sign inSign up

test-fixtures.ts

272 lines8.5 KB

1import { presentThreadShell } from "@t3tools/client-runtime/state/shell";
2import {
3 EnvironmentId,
4 MessageId,
5 PlanId,
6 RunId,
7 TurnItemId,
8 type OrchestrationV2ProjectedTurnItem,
9 type OrchestrationV2TurnItem,
10 ProjectId,
11 ProviderInstanceId,
12 ThreadId,
13 type OrchestrationV2ThreadProjection,
14} from "@t3tools/contracts";
15import * as DateTime from "effect/DateTime";
16​
17import type { ChatMessage, Thread } from "./types";
18​
19const DEFAULT_TIMESTAMP = "2026-01-01T00:00:00.000Z";
20​
21export type ThreadFixtureOverrides = Partial<Thread> & {
22 readonly messages?: ReadonlyArray<ChatMessage>;
23 readonly proposedPlans?: ReadonlyArray<unknown>;
24};
25​
26export function makeThreadProjectionFixture(): OrchestrationV2ThreadProjection {
27 const now = DateTime.makeUnsafe(DEFAULT_TIMESTAMP);
28 const id = ThreadId.make("thread-test");
29 const providerInstanceId = ProviderInstanceId.make("codex");
30 return {
31 thread: {
32 id,
33 projectId: ProjectId.make("project-test"),
34 title: "Thread",
35 providerInstanceId,
36 modelSelection: { instanceId: providerInstanceId, model: "gpt-5.4" },
37 runtimeMode: "full-access",
38 interactionMode: "default",
39 branch: null,
40 worktreePath: null,
41 activeProviderThreadId: null,
42 lineage: { rootThreadId: id, parentThreadId: null, relationshipToParent: null },
43 forkedFrom: null,
44 createdBy: "user",
45 creationSource: "web",
46 createdAt: now,
47 updatedAt: now,
48 archivedAt: null,
49 settledOverride: null,
50 settledAt: null,
51 lastVisitedAt: null,
52 deletedAt: null,
53 },
54 runs: [],
55 attempts: [],
56 nodes: [],
57 subagents: [],
58 providerSessions: [],
59 providerThreads: [],
60 providerTurns: [],
61 runtimeRequests: [],
62 messages: [],
63 plans: [],
64 turnItems: [],
65 checkpointScopes: [],
66 checkpoints: [],
67 contextHandoffs: [],
68 contextTransfers: [],
69 visibleTurnItems: [],
70 updatedAt: now,
71 };
73​
74export function makeThreadFixture(overrides: ThreadFixtureOverrides = {}): Thread {
75 const environmentId = overrides.environmentId ?? EnvironmentId.make("environment-test");
76 const id = overrides.id ?? ThreadId.make("thread-test");
77 const projectId = overrides.projectId ?? ProjectId.make("project-test");
78 const providerInstanceId =
79 overrides.providerInstanceId ??
80 overrides.modelSelection?.instanceId ??
81 ProviderInstanceId.make("codex");
82 const modelSelection = overrides.modelSelection ?? {
83 instanceId: providerInstanceId,
84 model: "gpt-5.4",
85 };
86 const createdAt = DateTime.makeUnsafe(DEFAULT_TIMESTAMP);
87 const updatedAt = DateTime.makeUnsafe(DEFAULT_TIMESTAMP);
88 const archivedAt =
89 overrides.archivedAt === null || overrides.archivedAt === undefined
90 ? null
91 : DateTime.makeUnsafe(overrides.archivedAt);
92 const deletedAt =
93 overrides.deletedAt === null || overrides.deletedAt === undefined
94 ? null
95 : DateTime.makeUnsafe(overrides.deletedAt);
96 const shell = presentThreadShell(environmentId, {
97 id,
98 projectId,
99 title: overrides.title ?? "Thread",
100 providerInstanceId,
101 modelSelection,
102 runtimeMode: overrides.runtimeMode ?? "full-access",
103 interactionMode: overrides.interactionMode ?? "default",
104 branch: overrides.branch ?? null,
105 worktreePath: overrides.worktreePath ?? null,
106 activeProviderThreadId: overrides.activeProviderThreadId ?? null,
107 lineage: overrides.lineage ?? {
108 rootThreadId: id,
109 parentThreadId: null,
110 relationshipToParent: null,
111 },
112 forkedFrom: overrides.forkedFrom ?? null,
113 createdBy: "user",
114 creationSource: "web",
115 latestRunId: overrides.latestRun?.runId ?? null,
116 activeRunId: overrides.runtime?.activeRunId ?? null,
117 status: overrides.runtime?.status ?? "idle",
118 pendingRuntimeRequest: null,
119 latestVisibleMessage: null,
120 latestUserMessageAt:
121 overrides.latestUserMessageAt === null || overrides.latestUserMessageAt === undefined
122 ? null
123 : DateTime.makeUnsafe(overrides.latestUserMessageAt),
124 hasActionableProposedPlan: overrides.hasActionableProposedPlan ?? false,
125 itemCount: overrides.itemCount ?? 0,
126 visibleItemCount: overrides.visibleItemCount ?? 0,
127 createdAt,
128 updatedAt,
129 archivedAt,
130 settledOverride: overrides.settledOverride ?? null,
131 settledAt:
132 overrides.settledAt === null || overrides.settledAt === undefined
133 ? null
134 : DateTime.makeUnsafe(overrides.settledAt),
135 deletedAt,
136 });
137​
138 return { ...shell, ...overrides };
140​
141export function makeStreamingTimelineFixture(text = "") {
142 const threadId = ThreadId.make("streaming-thread");
143 const runId = RunId.make("live-run");
144 const historyRunId = RunId.make("history-run");
145 const time = (second: number) => new Date(Date.UTC(2026, 8, 4, 0, 0, second)).toISOString();
146 const base = (id: string, run: typeof runId, second: number) => ({
147 id: TurnItemId.make(id),
148 threadId,
149 runId: run,
150 nodeId: null,
151 providerThreadId: null,
152 providerTurnId: null,
153 nativeItemRef: null,
154 parentItemId: null,
155 ordinal: second,
156 status: "completed" as const,
157 title: null,
158 startedAt: DateTime.makeUnsafe(time(second)),
159 completedAt: DateTime.makeUnsafe(time(second)),
160 updatedAt: DateTime.makeUnsafe(time(second)),
161 });
162 const items: OrchestrationV2TurnItem[] = [
163 {
164 ...base("history-user-item", historyRunId, 0),
165 type: "user_message",
166 messageId: MessageId.make("history-user"),
167 text: "Inspect",
168 attachments: [],
169 inputIntent: "turn_start",
170 createdBy: "user",
171 creationSource: "web",
172 },
173 {
174 ...base("history-work", historyRunId, 1),
175 type: "command_execution",
176 status: "failed",
177 input: "vp test",
178 output: "Test failed",
179 exitCode: 1,
180 },
181 {
182 ...base("history-assistant-item", historyRunId, 3),
183 type: "assistant_message",
184 messageId: MessageId.make("history-assistant"),
185 text: "Done",
186 streaming: false,
187 updatedAt: DateTime.makeUnsafe(time(4)),
188 },
189 {
190 ...base("live-user-item", runId, 5),
191 type: "user_message",
192 messageId: MessageId.make("live-user"),
193 text: "Continue",
194 attachments: [],
195 inputIntent: "turn_start",
196 createdBy: "user",
197 creationSource: "web",
198 },
199 {
200 ...base("live-work", runId, 6),
201 type: "dynamic_tool",
202 toolName: "read_file",
203 input: { path: "/repo/src/index.ts" },
204 output: "Contents",
205 },
206 {
207 ...base("live-assistant-item", runId, 7),
208 type: "assistant_message",
209 messageId: MessageId.make("live-assistant"),
210 text,
211 streaming: true,
212 status: "running",
213 completedAt: null,
214 },
215 ];
216 const visibleTurnItems: OrchestrationV2ProjectedTurnItem[] = items.map((item, position) => ({
217 position,
218 visibility: "local",
219 sourceThreadId: threadId,
220 sourceItemId: item.id,
221 item,
222 }));
223 return { threadId, runId, historyRunId, visibleTurnItems, time, base };
225​
226/** The streaming fixture, ending in streaming thinking or a streaming plan instead of assistant text. */
227export function makeStreamingWorkTimelineFixture(kind: "reasoning" | "proposed_plan") {
228 const fixture = makeStreamingTimelineFixture();
229 const live = {
230 ...fixture.base("live-streaming", fixture.runId, 7),
231 status: "running" as const,
232 completedAt: null,
233 };
234 const streamingItem: OrchestrationV2TurnItem =
235 kind === "proposed_plan"
236 ? {
237 ...live,
238 type: "proposed_plan",
239 planId: PlanId.make("live-plan"),
240 markdown: "",
241 streaming: true,
242 }
243 : { ...live, type: "reasoning", text: "", streaming: true };
244 const visibleTurnItems = fixture.visibleTurnItems.map((row) =>
245 row.item.id === "live-assistant-item"
246 ? { ...row, sourceItemId: streamingItem.id, item: streamingItem }
247 : row,
248 );
249 return { ...fixture, visibleTurnItems, streamingItemId: streamingItem.id };
251​
252/** One coalesced provider flush: only the streamed text and `updatedAt` change. */
253export function withStreamedText(
254 rows: ReadonlyArray<OrchestrationV2ProjectedTurnItem>,
255 itemId: string,
256 text: string,
257 updatedAt: string,
258): OrchestrationV2ProjectedTurnItem[] {
259 return rows.map((row) => {
260 if (row.item.id !== itemId) return row;
261 const updated = DateTime.makeUnsafe(updatedAt);
262 return {
263 ...row,
264 item:
265 row.item.type === "proposed_plan"
266 ? { ...row.item, markdown: text, updatedAt: updated }
267 : row.item.type === "reasoning"
268 ? { ...row.item, text, updatedAt: updated }
269 : row.item,
270 };
271 });