@assistant-ui/eve 0.0.24 → 0.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/convertEveMessages.d.ts +40 -51
- package/dist/convertEveMessages.d.ts.map +1 -1
- package/dist/convertEveMessages.js +146 -117
- package/dist/convertEveMessages.js.map +1 -1
- package/dist/deriveCreatedAt.d.ts +9 -9
- package/dist/deriveCreatedAt.d.ts.map +1 -1
- package/dist/deriveCreatedAt.js +25 -34
- package/dist/deriveCreatedAt.js.map +1 -1
- package/dist/eveCloudSessions.js +1 -2
- package/dist/eveExtras.d.ts +4 -2
- package/dist/eveExtras.d.ts.map +1 -1
- package/dist/eveExtras.js +1 -2
- package/dist/eveExtras.js.map +1 -1
- package/dist/hooks.d.ts +4 -3
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +5 -5
- package/dist/hooks.js.map +1 -1
- package/dist/sdkIdentity.js +2 -3
- package/dist/useEveAgentRuntime.d.ts +3 -5
- package/dist/useEveAgentRuntime.d.ts.map +1 -1
- package/dist/useEveAgentRuntime.js +34 -23
- package/dist/useEveAgentRuntime.js.map +1 -1
- package/package.json +10 -10
- package/src/convertEveMessages.test.ts +925 -729
- package/src/convertEveMessages.ts +282 -227
- package/src/deriveCreatedAt.test.ts +90 -88
- package/src/deriveCreatedAt.ts +32 -36
- package/src/eveExtras.ts +4 -4
- package/src/hooks.ts +4 -3
- package/src/testUtils.ts +189 -8
- package/src/useEveAgentRuntime.cloud.test.tsx +105 -136
- package/src/useEveAgentRuntime.eveResume.test.tsx +22 -104
- package/src/useEveAgentRuntime.fast-refresh.test.tsx +195 -37
- package/src/useEveAgentRuntime.interruptedTurn.test.tsx +86 -40
- package/src/useEveAgentRuntime.refetch.test.tsx +19 -58
- package/src/useEveAgentRuntime.test.tsx +372 -291
- package/src/useEveAgentRuntime.ts +100 -113
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
import { act, render, renderHook, waitFor } from "@testing-library/react";
|
|
4
4
|
import { AssistantRuntimeProvider } from "@assistant-ui/core/react";
|
|
5
5
|
import type { AssistantRuntime } from "@assistant-ui/core";
|
|
6
|
-
import
|
|
6
|
+
import { AssistantCloud } from "assistant-cloud";
|
|
7
7
|
import type { ClientSessionState } from "eve/client";
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
8
|
+
import { emptySessionView } from "eve/events";
|
|
9
|
+
import type { EveMessage, UseEveAgentOptions } from "eve/react";
|
|
10
|
+
import type { Delivery } from "eve/sessions";
|
|
11
|
+
import { StrictMode, useState } from "react";
|
|
10
12
|
import { afterEach, describe, expect, it, vi, type Mock } from "vitest";
|
|
11
13
|
|
|
12
14
|
const { mockUseEveAgent } = vi.hoisted(() => ({
|
|
@@ -19,26 +21,29 @@ vi.mock("eve/react", async (importOriginal) => ({
|
|
|
19
21
|
}));
|
|
20
22
|
|
|
21
23
|
import { useEveReset } from "./hooks";
|
|
24
|
+
import { createEveDelivery } from "./testUtils";
|
|
22
25
|
import {
|
|
23
26
|
useEveAgentRuntime,
|
|
24
27
|
type UseEveAgentRuntimeOptions,
|
|
25
28
|
} from "./useEveAgentRuntime";
|
|
26
29
|
|
|
27
|
-
type AgentOptions = UseEveAgentOptions
|
|
30
|
+
type AgentOptions = UseEveAgentOptions;
|
|
28
31
|
|
|
29
|
-
type FakeSnapshot = {
|
|
32
|
+
type FakeSnapshot = {
|
|
33
|
+
messages: readonly EveMessage[];
|
|
34
|
+
status: "idle" | "busy" | "resuming";
|
|
35
|
+
};
|
|
30
36
|
|
|
31
37
|
type FakeAgent = {
|
|
32
38
|
readonly initialOptions: AgentOptions;
|
|
33
39
|
options: AgentOptions;
|
|
34
40
|
update: (next: Partial<FakeSnapshot>) => void;
|
|
35
|
-
readonly send: Mock<() => Promise<
|
|
36
|
-
readonly
|
|
41
|
+
readonly send: Mock<() => Promise<Delivery>>;
|
|
42
|
+
readonly newSession: Mock<() => void>;
|
|
37
43
|
readonly resume: Mock<() => Promise<void>>;
|
|
38
44
|
};
|
|
39
45
|
|
|
40
46
|
const agents: FakeAgent[] = [];
|
|
41
|
-
let withResume = true;
|
|
42
47
|
|
|
43
48
|
const createdSession = (sessionId: string): ClientSessionState => ({
|
|
44
49
|
sessionId,
|
|
@@ -53,30 +58,31 @@ mockUseEveAgent.mockImplementation(function useFakeEveAgent(
|
|
|
53
58
|
initialOptions: options,
|
|
54
59
|
options,
|
|
55
60
|
update: () => {},
|
|
56
|
-
send: vi.fn(async () =>
|
|
57
|
-
|
|
61
|
+
send: vi.fn(async () => createEveDelivery()),
|
|
62
|
+
newSession: vi.fn(),
|
|
58
63
|
resume: vi.fn(async () => {}),
|
|
59
64
|
};
|
|
60
65
|
agents.push(created);
|
|
61
66
|
return created;
|
|
62
67
|
});
|
|
63
68
|
const [snapshot, setSnapshot] = useState<FakeSnapshot>({
|
|
64
|
-
|
|
65
|
-
status: "
|
|
69
|
+
messages: [],
|
|
70
|
+
status: "idle",
|
|
66
71
|
});
|
|
67
72
|
agent.options = options;
|
|
68
73
|
agent.update = (next) => setSnapshot((current) => ({ ...current, ...next }));
|
|
69
74
|
return {
|
|
70
|
-
|
|
75
|
+
messages: snapshot.messages,
|
|
76
|
+
view: emptySessionView(),
|
|
71
77
|
error: undefined,
|
|
72
78
|
events: [],
|
|
73
79
|
session: agent.initialOptions.initialSession,
|
|
74
80
|
status: snapshot.status,
|
|
75
81
|
send: agent.send,
|
|
76
|
-
respond: vi.fn(async () =>
|
|
77
|
-
cancel: vi.fn(async () => (
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
respond: vi.fn(async () => createEveDelivery()),
|
|
83
|
+
cancel: vi.fn(async () => createEveDelivery()),
|
|
84
|
+
newSession: agent.newSession,
|
|
85
|
+
resume: agent.resume,
|
|
80
86
|
};
|
|
81
87
|
});
|
|
82
88
|
|
|
@@ -87,47 +93,50 @@ const makeCloud = (
|
|
|
87
93
|
telemetry: { enabled: boolean } = { enabled: false },
|
|
88
94
|
) => {
|
|
89
95
|
let created = 0;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
: rows.map((row) => ({
|
|
96
|
-
...row,
|
|
97
|
-
title: row.id,
|
|
98
|
-
is_archived: false,
|
|
99
|
-
last_message_at: new Date(0),
|
|
100
|
-
metadata: null,
|
|
101
|
-
})),
|
|
102
|
-
})),
|
|
103
|
-
create: vi.fn(async () => ({ thread_id: `cloud-${++created}` })),
|
|
104
|
-
update: vi.fn(async () => {}),
|
|
105
|
-
delete: vi.fn(async () => {}),
|
|
106
|
-
get: vi.fn(),
|
|
107
|
-
messages: {
|
|
108
|
-
list: vi.fn(async () => ({ messages: [] })),
|
|
109
|
-
create: vi.fn(async () => ({ message_id: "cloud-message" })),
|
|
110
|
-
update: vi.fn(async () => {}),
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
events: { track: vi.fn() },
|
|
96
|
+
const cloud = new AssistantCloud({
|
|
97
|
+
baseUrl: "http://localhost",
|
|
98
|
+
apiKey: "test-key",
|
|
99
|
+
userId: "test-user",
|
|
100
|
+
workspaceId: "test-workspace",
|
|
114
101
|
telemetry,
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
102
|
+
});
|
|
103
|
+
const list = vi
|
|
104
|
+
.spyOn(cloud.threads, "list")
|
|
105
|
+
.mockImplementation(async ({ is_archived } = {}) => ({
|
|
106
|
+
threads: is_archived
|
|
107
|
+
? []
|
|
108
|
+
: rows.map((row) => ({
|
|
109
|
+
...row,
|
|
110
|
+
title: row.id,
|
|
111
|
+
is_archived: false,
|
|
112
|
+
last_message_at: new Date(0),
|
|
113
|
+
created_at: new Date(0),
|
|
114
|
+
updated_at: new Date(0),
|
|
115
|
+
project_id: "test-project",
|
|
116
|
+
workspace_id: "test-workspace",
|
|
117
|
+
metadata: null,
|
|
118
|
+
})),
|
|
119
|
+
}));
|
|
120
|
+
const create = vi
|
|
121
|
+
.spyOn(cloud.threads, "create")
|
|
122
|
+
.mockImplementation(async () => ({
|
|
123
|
+
thread_id: `cloud-${++created}`,
|
|
124
|
+
}));
|
|
125
|
+
vi.spyOn(cloud.threads, "update").mockResolvedValue(undefined);
|
|
126
|
+
vi.spyOn(cloud.threads, "delete").mockResolvedValue(undefined);
|
|
127
|
+
vi.spyOn(cloud.threads.messages, "list").mockResolvedValue({ messages: [] });
|
|
128
|
+
const messagesCreate = vi
|
|
129
|
+
.spyOn(cloud.threads.messages, "create")
|
|
130
|
+
.mockResolvedValue({ message_id: "cloud-message" });
|
|
131
|
+
vi.spyOn(cloud.threads.messages, "update").mockResolvedValue(undefined);
|
|
132
|
+
vi.spyOn(cloud.events, "track").mockImplementation(() => {});
|
|
133
|
+
vi.spyOn(cloud.runs, "report").mockResolvedValue({ run_id: "test-run" });
|
|
134
|
+
const registerSdk = vi
|
|
135
|
+
.spyOn(cloud, "registerSdk")
|
|
136
|
+
.mockImplementation(() => {});
|
|
137
|
+
return Object.assign(cloud, {
|
|
138
|
+
mocks: { list, create, messagesCreate, registerSdk },
|
|
139
|
+
});
|
|
131
140
|
};
|
|
132
141
|
|
|
133
142
|
const settle = () =>
|
|
@@ -187,7 +196,6 @@ const lastAgent = () => {
|
|
|
187
196
|
|
|
188
197
|
afterEach(() => {
|
|
189
198
|
agents.length = 0;
|
|
190
|
-
withResume = true;
|
|
191
199
|
});
|
|
192
200
|
|
|
193
201
|
describe("useEveAgentRuntime with cloud", () => {
|
|
@@ -203,7 +211,7 @@ describe("useEveAgentRuntime with cloud", () => {
|
|
|
203
211
|
const agent = lastAgent();
|
|
204
212
|
expect(agent.initialOptions.initialSession).toBeUndefined();
|
|
205
213
|
expect(agent.initialOptions.resume).toBeUndefined();
|
|
206
|
-
expect(cloud.registerSdk).toHaveBeenCalledWith({
|
|
214
|
+
expect(cloud.mocks.registerSdk).toHaveBeenCalledWith({
|
|
207
215
|
name: "@assistant-ui/eve",
|
|
208
216
|
version: expect.any(String),
|
|
209
217
|
});
|
|
@@ -211,10 +219,10 @@ describe("useEveAgentRuntime with cloud", () => {
|
|
|
211
219
|
let acceptTurn!: () => void;
|
|
212
220
|
agent.send.mockImplementation(
|
|
213
221
|
() =>
|
|
214
|
-
new Promise<
|
|
222
|
+
new Promise<Delivery>((resolve) => {
|
|
215
223
|
acceptTurn = () => {
|
|
216
224
|
agent.options.onSessionChange?.(createdSession("session-1"));
|
|
217
|
-
resolve();
|
|
225
|
+
resolve(createEveDelivery());
|
|
218
226
|
};
|
|
219
227
|
}),
|
|
220
228
|
);
|
|
@@ -222,11 +230,11 @@ describe("useEveAgentRuntime with cloud", () => {
|
|
|
222
230
|
await settle();
|
|
223
231
|
|
|
224
232
|
expect(agent.send).toHaveBeenCalledOnce();
|
|
225
|
-
expect(cloud.
|
|
233
|
+
expect(cloud.mocks.create).not.toHaveBeenCalled();
|
|
226
234
|
|
|
227
235
|
await act(async () => acceptTurn());
|
|
228
236
|
await waitFor(() =>
|
|
229
|
-
expect(cloud.
|
|
237
|
+
expect(cloud.mocks.create).toHaveBeenCalledExactlyOnceWith({
|
|
230
238
|
last_message_at: expect.any(Date),
|
|
231
239
|
external_id: "session-1",
|
|
232
240
|
upsert: true,
|
|
@@ -249,20 +257,22 @@ describe("useEveAgentRuntime with cloud", () => {
|
|
|
249
257
|
|
|
250
258
|
agent.send.mockImplementationOnce(async () => {
|
|
251
259
|
agent.options.onSessionChange?.(undefined);
|
|
260
|
+
return createEveDelivery();
|
|
252
261
|
});
|
|
253
262
|
act(() => app.runtime().thread.append("hi"));
|
|
254
263
|
await settle();
|
|
255
264
|
|
|
256
|
-
expect(cloud.
|
|
265
|
+
expect(cloud.mocks.create).not.toHaveBeenCalled();
|
|
257
266
|
expect(app.runtime().threads.mainItem.getState().status).toBe("new");
|
|
258
267
|
|
|
259
268
|
agent.send.mockImplementationOnce(async () => {
|
|
260
269
|
agent.options.onSessionChange?.(createdSession("session-2"));
|
|
270
|
+
return createEveDelivery();
|
|
261
271
|
});
|
|
262
272
|
act(() => app.runtime().thread.append("again"));
|
|
263
273
|
|
|
264
274
|
await waitFor(() =>
|
|
265
|
-
expect(cloud.
|
|
275
|
+
expect(cloud.mocks.create).toHaveBeenCalledExactlyOnceWith({
|
|
266
276
|
last_message_at: expect.any(Date),
|
|
267
277
|
external_id: "session-2",
|
|
268
278
|
upsert: true,
|
|
@@ -286,7 +296,7 @@ describe("useEveAgentRuntime with cloud", () => {
|
|
|
286
296
|
await settle();
|
|
287
297
|
|
|
288
298
|
expect(agent.send).not.toHaveBeenCalled();
|
|
289
|
-
expect(cloud.
|
|
299
|
+
expect(cloud.mocks.create).not.toHaveBeenCalled();
|
|
290
300
|
expect(app.runtime().threads.mainItem.getState().status).toBe("new");
|
|
291
301
|
const switched = await act(() =>
|
|
292
302
|
Promise.race([
|
|
@@ -303,11 +313,12 @@ describe("useEveAgentRuntime with cloud", () => {
|
|
|
303
313
|
if (!staged) throw new Error("the staged message is missing");
|
|
304
314
|
agent.send.mockImplementationOnce(async () => {
|
|
305
315
|
agent.options.onSessionChange?.(createdSession("session-3"));
|
|
316
|
+
return createEveDelivery();
|
|
306
317
|
});
|
|
307
318
|
await act(() => app.runtime().thread.startRun({ parentId: staged.id }));
|
|
308
319
|
|
|
309
320
|
await waitFor(() =>
|
|
310
|
-
expect(cloud.
|
|
321
|
+
expect(cloud.mocks.create).toHaveBeenCalledExactlyOnceWith({
|
|
311
322
|
last_message_at: expect.any(Date),
|
|
312
323
|
external_id: "session-3",
|
|
313
324
|
upsert: true,
|
|
@@ -317,22 +328,23 @@ describe("useEveAgentRuntime with cloud", () => {
|
|
|
317
328
|
|
|
318
329
|
it("retries a lost save with the same session and upsert, so the cloud answers the thread it already stored", async () => {
|
|
319
330
|
const cloud = makeCloud();
|
|
320
|
-
cloud.
|
|
331
|
+
cloud.mocks.create.mockRejectedValueOnce(new Error("response lost"));
|
|
321
332
|
const app = setup({ cloud });
|
|
322
333
|
await settle();
|
|
323
334
|
const agent = lastAgent();
|
|
324
335
|
agent.send.mockImplementation(async () => {
|
|
325
336
|
agent.options.onSessionChange?.(createdSession("session-1"));
|
|
337
|
+
return createEveDelivery();
|
|
326
338
|
});
|
|
327
339
|
|
|
328
340
|
act(() => app.runtime().thread.append("hi"));
|
|
329
|
-
await waitFor(() => expect(cloud.
|
|
341
|
+
await waitFor(() => expect(cloud.mocks.create).toHaveBeenCalledOnce());
|
|
330
342
|
await settle();
|
|
331
343
|
expect(app.runtime().threads.mainItem.getState().status).toBe("new");
|
|
332
344
|
|
|
333
345
|
act(() => app.runtime().thread.append("again"));
|
|
334
|
-
await waitFor(() => expect(cloud.
|
|
335
|
-
for (const [body] of cloud.
|
|
346
|
+
await waitFor(() => expect(cloud.mocks.create).toHaveBeenCalledTimes(2));
|
|
347
|
+
for (const [body] of cloud.mocks.create.mock.calls) {
|
|
336
348
|
expect(body).toEqual({
|
|
337
349
|
last_message_at: expect.any(Date),
|
|
338
350
|
external_id: "session-1",
|
|
@@ -402,6 +414,7 @@ describe("useEveAgentRuntime with cloud", () => {
|
|
|
402
414
|
const agent = lastAgent();
|
|
403
415
|
agent.send.mockImplementationOnce(async () => {
|
|
404
416
|
agent.options.onSessionChange?.(createdSession("session-1"));
|
|
417
|
+
return createEveDelivery();
|
|
405
418
|
});
|
|
406
419
|
act(() => app.runtime().thread.append("hi"));
|
|
407
420
|
await waitFor(() =>
|
|
@@ -414,7 +427,7 @@ describe("useEveAgentRuntime with cloud", () => {
|
|
|
414
427
|
act(() => app.reset());
|
|
415
428
|
await settle();
|
|
416
429
|
|
|
417
|
-
expect(agent.
|
|
430
|
+
expect(agent.newSession).not.toHaveBeenCalled();
|
|
418
431
|
expect(app.runtime().threads.mainItem.getState()).toMatchObject({
|
|
419
432
|
status: "new",
|
|
420
433
|
});
|
|
@@ -431,38 +444,37 @@ describe("useEveAgentRuntime with cloud", () => {
|
|
|
431
444
|
const agent = lastAgent();
|
|
432
445
|
agent.send.mockImplementationOnce(async () => {
|
|
433
446
|
agent.options.onSessionChange?.(createdSession("session-1"));
|
|
447
|
+
return createEveDelivery();
|
|
434
448
|
});
|
|
435
449
|
act(() => app.runtime().thread.append("hi"));
|
|
436
|
-
await waitFor(() => expect(cloud.
|
|
450
|
+
await waitFor(() => expect(cloud.mocks.create).toHaveBeenCalledOnce());
|
|
437
451
|
|
|
438
|
-
act(() => agent.update({ status: "
|
|
452
|
+
act(() => agent.update({ status: "busy" }));
|
|
439
453
|
act(() =>
|
|
440
454
|
agent.update({
|
|
441
|
-
status: "
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
],
|
|
457
|
-
} as never,
|
|
455
|
+
status: "idle",
|
|
456
|
+
messages: [
|
|
457
|
+
{
|
|
458
|
+
id: "turn_1:user",
|
|
459
|
+
role: "user",
|
|
460
|
+
parts: [{ type: "text", text: "hi" }],
|
|
461
|
+
metadata: { turnId: "turn_1" },
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
id: "turn_1:assistant",
|
|
465
|
+
role: "assistant",
|
|
466
|
+
parts: [{ type: "text", text: "hello" }],
|
|
467
|
+
metadata: { turnId: "turn_1" },
|
|
468
|
+
},
|
|
469
|
+
],
|
|
458
470
|
}),
|
|
459
471
|
);
|
|
460
472
|
await settle();
|
|
461
473
|
|
|
462
474
|
await waitFor(() =>
|
|
463
|
-
expect(cloud.
|
|
475
|
+
expect(cloud.mocks.messagesCreate).toHaveBeenCalledTimes(2),
|
|
464
476
|
);
|
|
465
|
-
expect(cloud.
|
|
477
|
+
expect(cloud.mocks.messagesCreate.mock.calls).toMatchObject([
|
|
466
478
|
["cloud-1", { format: "aui/v0", external_id: "turn_1:user" }],
|
|
467
479
|
[
|
|
468
480
|
"cloud-1",
|
|
@@ -474,49 +486,6 @@ describe("useEveAgentRuntime with cloud", () => {
|
|
|
474
486
|
],
|
|
475
487
|
]);
|
|
476
488
|
});
|
|
477
|
-
|
|
478
|
-
it("requires an eve that can resume a session", async () => {
|
|
479
|
-
withResume = false;
|
|
480
|
-
const errors: unknown[] = [];
|
|
481
|
-
class Boundary extends Component<{ children: ReactNode }> {
|
|
482
|
-
override state = { failed: false };
|
|
483
|
-
static getDerivedStateFromError() {
|
|
484
|
-
return { failed: true };
|
|
485
|
-
}
|
|
486
|
-
override componentDidCatch(error: unknown) {
|
|
487
|
-
errors.push(error);
|
|
488
|
-
}
|
|
489
|
-
override render() {
|
|
490
|
-
return this.state.failed ? null : this.props.children;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
const cloud = makeCloud();
|
|
494
|
-
const App = () => {
|
|
495
|
-
const runtime = useEveAgentRuntime({ cloud });
|
|
496
|
-
return (
|
|
497
|
-
<AssistantRuntimeProvider runtime={runtime}>
|
|
498
|
-
{null}
|
|
499
|
-
</AssistantRuntimeProvider>
|
|
500
|
-
);
|
|
501
|
-
};
|
|
502
|
-
const consoleError = vi
|
|
503
|
-
.spyOn(console, "error")
|
|
504
|
-
.mockImplementation(() => {});
|
|
505
|
-
|
|
506
|
-
render(
|
|
507
|
-
<Boundary>
|
|
508
|
-
<App />
|
|
509
|
-
</Boundary>,
|
|
510
|
-
);
|
|
511
|
-
await settle();
|
|
512
|
-
|
|
513
|
-
expect(errors).toContainEqual(
|
|
514
|
-
expect.objectContaining({
|
|
515
|
-
message: expect.stringContaining("eve 0.44.1 or later"),
|
|
516
|
-
}),
|
|
517
|
-
);
|
|
518
|
-
consoleError.mockRestore();
|
|
519
|
-
});
|
|
520
489
|
});
|
|
521
490
|
|
|
522
491
|
describe("useEveAgentRuntime switching cloud after mount", () => {
|
|
@@ -2,111 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
4
4
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import {
|
|
6
|
+
createCompletedTurnEvents,
|
|
7
|
+
createEveSessionFixture,
|
|
8
|
+
} from "./testUtils";
|
|
8
9
|
import { useEveAgentRuntime } from "./useEveAgentRuntime";
|
|
9
10
|
|
|
10
|
-
const TURN = "turn_resumed";
|
|
11
|
-
|
|
12
11
|
const USER_AT = "2026-01-02T10:00:01.000Z";
|
|
13
12
|
const ASSISTANT_AT = "2026-01-02T10:02:00.000Z";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
meta: { at: ASSISTANT_AT, id: "evt_102" },
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
type: "message.completed",
|
|
33
|
-
data: {
|
|
34
|
-
finishReason: "stop",
|
|
35
|
-
message: "hello",
|
|
36
|
-
sequence: 5,
|
|
37
|
-
stepIndex: 0,
|
|
38
|
-
turnId: TURN,
|
|
39
|
-
},
|
|
40
|
-
meta: { at: "2026-01-02T10:02:06.000Z", id: "evt_003" },
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
type: "turn.completed",
|
|
44
|
-
data: { sequence: 6, turnId: TURN },
|
|
45
|
-
meta: { at: "2026-01-02T10:02:07.000Z", id: "evt_004" },
|
|
46
|
-
},
|
|
47
|
-
] as const satisfies readonly MessageStreamEvent[];
|
|
48
|
-
|
|
49
|
-
const NEXT_TURN = "turn_next";
|
|
50
|
-
|
|
51
|
-
const nextTurnEvents = [
|
|
52
|
-
{
|
|
53
|
-
type: "turn.started",
|
|
54
|
-
data: { sequence: 7, turnId: NEXT_TURN },
|
|
55
|
-
meta: { at: "2026-01-02T10:03:00.000Z", id: "evt_201" },
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
type: "message.received",
|
|
59
|
-
data: { message: "follow up", sequence: 8, turnId: NEXT_TURN },
|
|
60
|
-
meta: {
|
|
61
|
-
at: "2026-01-02T10:03:01.000Z",
|
|
62
|
-
id: "evt_202",
|
|
63
|
-
deliveryIds: ["delivery_1"],
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
type: "step.started",
|
|
68
|
-
data: {
|
|
69
|
-
modelId: "test-model",
|
|
70
|
-
sequence: 9,
|
|
71
|
-
stepIndex: 0,
|
|
72
|
-
turnId: NEXT_TURN,
|
|
73
|
-
},
|
|
74
|
-
meta: { at: "2026-01-02T10:03:02.000Z", id: "evt_203" },
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
type: "message.completed",
|
|
78
|
-
data: {
|
|
79
|
-
finishReason: "stop",
|
|
80
|
-
message: "noted",
|
|
81
|
-
sequence: 10,
|
|
82
|
-
stepIndex: 0,
|
|
83
|
-
turnId: NEXT_TURN,
|
|
84
|
-
},
|
|
85
|
-
meta: { at: "2026-01-02T10:03:03.000Z", id: "evt_204" },
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
type: "turn.completed",
|
|
89
|
-
data: { sequence: 11, turnId: NEXT_TURN },
|
|
90
|
-
meta: { at: "2026-01-02T10:03:04.000Z", id: "evt_205" },
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
type: "session.waiting",
|
|
94
|
-
data: { continuationToken: "session_resumed", wait: "next-user-message" },
|
|
95
|
-
meta: { at: "2026-01-02T10:03:05.000Z", id: "evt_206" },
|
|
96
|
-
},
|
|
97
|
-
] as const satisfies readonly MessageStreamEvent[];
|
|
98
|
-
|
|
99
|
-
const parkedEvents = [
|
|
100
|
-
...resumedEvents,
|
|
101
|
-
{
|
|
102
|
-
type: "session.waiting",
|
|
103
|
-
data: { continuationToken: "session_resumed", wait: "next-user-message" },
|
|
104
|
-
meta: { at: "2026-01-02T10:02:08.000Z", id: "evt_005" },
|
|
105
|
-
},
|
|
106
|
-
] as const satisfies readonly MessageStreamEvent[];
|
|
107
|
-
|
|
108
|
-
const copyEvents = (events: readonly MessageStreamEvent[]) =>
|
|
109
|
-
JSON.parse(JSON.stringify(events)) as readonly MessageStreamEvent[];
|
|
13
|
+
const resumedEvents = createCompletedTurnEvents({
|
|
14
|
+
turnId: "turn_resumed",
|
|
15
|
+
text: "hi",
|
|
16
|
+
reply: "hello",
|
|
17
|
+
userAt: USER_AT,
|
|
18
|
+
assistantAt: ASSISTANT_AT,
|
|
19
|
+
});
|
|
20
|
+
const nextTurnEvents = createCompletedTurnEvents({
|
|
21
|
+
turnId: "turn_next",
|
|
22
|
+
text: "follow up",
|
|
23
|
+
reply: "next answer",
|
|
24
|
+
startLine: resumedEvents.length,
|
|
25
|
+
follows: "turn_resumed",
|
|
26
|
+
deliveryId: "delivery_1",
|
|
27
|
+
});
|
|
110
28
|
|
|
111
29
|
afterEach(() => vi.unstubAllGlobals());
|
|
112
30
|
|
|
@@ -128,8 +46,8 @@ describe("useEveAgentRuntime against a resumed eve session", () => {
|
|
|
128
46
|
|
|
129
47
|
it("reports the replay as loading and holds a send typed during it until the replay ends", async () => {
|
|
130
48
|
const { fetch, push, session } = createEveSessionFixture({
|
|
131
|
-
tailIndex:
|
|
132
|
-
onSend: () => setTimeout(() => push(
|
|
49
|
+
tailIndex: resumedEvents.length - 1,
|
|
50
|
+
onSend: () => setTimeout(() => push(nextTurnEvents), 0),
|
|
133
51
|
});
|
|
134
52
|
|
|
135
53
|
const { result } = renderHook(() =>
|
|
@@ -146,7 +64,7 @@ describe("useEveAgentRuntime against a resumed eve session", () => {
|
|
|
146
64
|
expect(fetch).toHaveBeenCalledOnce();
|
|
147
65
|
|
|
148
66
|
await act(async () => {
|
|
149
|
-
push(
|
|
67
|
+
push(resumedEvents);
|
|
150
68
|
});
|
|
151
69
|
|
|
152
70
|
await waitFor(() =>
|