@assistant-ui/eve 0.0.24 → 0.0.25
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
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
|
|
3
|
-
import { act, type ReactNode } from "react";
|
|
4
|
-
import
|
|
3
|
+
import { Activity, act, type ReactNode, version } from "react";
|
|
4
|
+
import { AssistantCloud } from "assistant-cloud";
|
|
5
|
+
import { emptySessionView } from "eve/events";
|
|
5
6
|
import { afterAll, afterEach, expect, it, vi } from "vitest";
|
|
6
7
|
import type { EveCloudSessions } from "./eveCloudSessions";
|
|
8
|
+
import { createEveDelivery, createEveDeliveryAfter } from "./testUtils";
|
|
7
9
|
|
|
8
10
|
const { mockUseEveAgent, directRuntimeHook, sessionInstances } = vi.hoisted(
|
|
9
11
|
() => ({
|
|
@@ -104,6 +106,9 @@ afterEach(() => {
|
|
|
104
106
|
});
|
|
105
107
|
afterAll(() => vi.unstubAllGlobals());
|
|
106
108
|
|
|
109
|
+
// Activity is React 19 only
|
|
110
|
+
const onReact18 = version.startsWith("18.");
|
|
111
|
+
|
|
107
112
|
const refresh = async (Before: unknown, After: unknown) => {
|
|
108
113
|
const family: Family = { current: After };
|
|
109
114
|
renderer!.setRefreshHandler((type) =>
|
|
@@ -121,24 +126,23 @@ const refresh = async (Before: unknown, After: unknown) => {
|
|
|
121
126
|
};
|
|
122
127
|
|
|
123
128
|
const makeAgent = (send: (content: string) => Promise<void>) => ({
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
},
|
|
129
|
+
messages: [
|
|
130
|
+
{ id: "u1", role: "user", parts: [{ type: "text", text: "earlier" }] },
|
|
131
|
+
{
|
|
132
|
+
id: "a1",
|
|
133
|
+
role: "assistant",
|
|
134
|
+
parts: [{ type: "text", text: "earlier answer" }],
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
view: emptySessionView(),
|
|
134
138
|
error: undefined,
|
|
135
139
|
events: [],
|
|
136
140
|
session: undefined,
|
|
137
|
-
status: "
|
|
138
|
-
send: vi.fn(send),
|
|
139
|
-
respond: vi.fn(async () =>
|
|
140
|
-
cancel: vi.fn(async () => (
|
|
141
|
-
|
|
141
|
+
status: "idle",
|
|
142
|
+
send: vi.fn(async (content: string) => createEveDeliveryAfter(send(content))),
|
|
143
|
+
respond: vi.fn(async () => createEveDelivery()),
|
|
144
|
+
cancel: vi.fn(async () => createEveDelivery()),
|
|
145
|
+
newSession: vi.fn(),
|
|
142
146
|
resume: vi.fn(async () => {}),
|
|
143
147
|
});
|
|
144
148
|
|
|
@@ -214,25 +218,29 @@ it("abandons an Eve send still queued when the runtime unmounts", async () => {
|
|
|
214
218
|
});
|
|
215
219
|
|
|
216
220
|
const makeCloud = () => {
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
221
|
+
const cloud = new AssistantCloud({
|
|
222
|
+
baseUrl: "http://localhost",
|
|
223
|
+
apiKey: "test-key",
|
|
224
|
+
userId: "test-user",
|
|
225
|
+
workspaceId: "test-workspace",
|
|
226
|
+
telemetry: false,
|
|
227
|
+
});
|
|
228
|
+
let created = 0;
|
|
229
|
+
vi.spyOn(cloud.threads, "list").mockResolvedValue({ threads: [] });
|
|
230
|
+
const create = vi
|
|
231
|
+
.spyOn(cloud.threads, "create")
|
|
232
|
+
.mockImplementation(async () => ({
|
|
233
|
+
thread_id: `cloud-${++created}`,
|
|
234
|
+
}));
|
|
235
|
+
vi.spyOn(cloud.threads, "update").mockResolvedValue(undefined);
|
|
236
|
+
vi.spyOn(cloud.threads, "delete").mockResolvedValue(undefined);
|
|
237
|
+
vi.spyOn(cloud.threads.messages, "list").mockResolvedValue({ messages: [] });
|
|
238
|
+
vi.spyOn(cloud.threads.messages, "create").mockResolvedValue({
|
|
239
|
+
message_id: "cloud-message",
|
|
240
|
+
});
|
|
241
|
+
vi.spyOn(cloud.events, "track").mockImplementation(() => {});
|
|
242
|
+
vi.spyOn(cloud.runs, "report").mockResolvedValue({ run_id: "test-run" });
|
|
243
|
+
vi.spyOn(cloud, "registerSdk").mockImplementation(() => {});
|
|
236
244
|
return { cloud, create };
|
|
237
245
|
};
|
|
238
246
|
|
|
@@ -242,7 +250,7 @@ it("resolves a pending first Eve session after Fast Refresh", async () => {
|
|
|
242
250
|
| ((session: { sessionId: string; streamIndex: number }) => void)
|
|
243
251
|
| undefined;
|
|
244
252
|
const agent = makeAgent(async () => {});
|
|
245
|
-
agent.
|
|
253
|
+
agent.messages = [];
|
|
246
254
|
mockUseEveAgent.mockImplementation((options) => {
|
|
247
255
|
onSessionChange = options.onSessionChange;
|
|
248
256
|
return agent;
|
|
@@ -340,3 +348,153 @@ it("keeps a first Eve session wait alive when a nested runtimeHook refreshes", a
|
|
|
340
348
|
await expect(waiting).resolves.toBe("session-1");
|
|
341
349
|
view.unmount();
|
|
342
350
|
});
|
|
351
|
+
|
|
352
|
+
it.skipIf(onReact18)(
|
|
353
|
+
"reads Eve delivery results again after an Activity hides and shows the runtime",
|
|
354
|
+
async () => {
|
|
355
|
+
const agent = makeAgent(async () => {});
|
|
356
|
+
agent.send.mockImplementation(async () => {
|
|
357
|
+
const receipt = createEveDelivery();
|
|
358
|
+
return {
|
|
359
|
+
...receipt,
|
|
360
|
+
result: (options?: { readonly signal?: AbortSignal }) =>
|
|
361
|
+
options?.signal?.aborted
|
|
362
|
+
? Promise.reject(options.signal.reason)
|
|
363
|
+
: receipt.result(),
|
|
364
|
+
};
|
|
365
|
+
});
|
|
366
|
+
mockUseEveAgent.mockReturnValue(agent);
|
|
367
|
+
let runtime!: ReturnType<typeof useEveAgentRuntime>;
|
|
368
|
+
const Host = () => {
|
|
369
|
+
runtime = useEveAgentRuntime();
|
|
370
|
+
return null;
|
|
371
|
+
};
|
|
372
|
+
const view = render(
|
|
373
|
+
<Activity mode="visible">
|
|
374
|
+
<Host />
|
|
375
|
+
</Activity>,
|
|
376
|
+
);
|
|
377
|
+
view.rerender(
|
|
378
|
+
<Activity mode="hidden">
|
|
379
|
+
<Host />
|
|
380
|
+
</Activity>,
|
|
381
|
+
);
|
|
382
|
+
await act(async () => {});
|
|
383
|
+
view.rerender(
|
|
384
|
+
<Activity mode="visible">
|
|
385
|
+
<Host />
|
|
386
|
+
</Activity>,
|
|
387
|
+
);
|
|
388
|
+
await act(async () => {});
|
|
389
|
+
|
|
390
|
+
act(() => {
|
|
391
|
+
runtime.thread.composer.setText("after show");
|
|
392
|
+
runtime.thread.composer.send();
|
|
393
|
+
});
|
|
394
|
+
await waitFor(() =>
|
|
395
|
+
expect(agent.send).toHaveBeenCalledWith("after show", undefined),
|
|
396
|
+
);
|
|
397
|
+
await act(async () => {});
|
|
398
|
+
expect(runtime.thread.composer.getState().text).toBe("");
|
|
399
|
+
view.unmount();
|
|
400
|
+
},
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
it.skipIf(onReact18)(
|
|
404
|
+
"keeps a send queued after an Activity re-show behind the turn still in flight",
|
|
405
|
+
async () => {
|
|
406
|
+
let releaseFirst!: () => void;
|
|
407
|
+
const agent = makeAgent(
|
|
408
|
+
vi
|
|
409
|
+
.fn()
|
|
410
|
+
.mockImplementationOnce(
|
|
411
|
+
() =>
|
|
412
|
+
new Promise<void>((resolve) => {
|
|
413
|
+
releaseFirst = resolve;
|
|
414
|
+
}),
|
|
415
|
+
)
|
|
416
|
+
.mockResolvedValue(undefined),
|
|
417
|
+
);
|
|
418
|
+
mockUseEveAgent.mockReturnValue(agent);
|
|
419
|
+
let runtime!: ReturnType<typeof useEveAgentRuntime>;
|
|
420
|
+
const Host = () => {
|
|
421
|
+
runtime = useEveAgentRuntime();
|
|
422
|
+
return null;
|
|
423
|
+
};
|
|
424
|
+
const view = render(
|
|
425
|
+
<Activity mode="visible">
|
|
426
|
+
<Host />
|
|
427
|
+
</Activity>,
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
await act(async () => runtime.thread.append("first"));
|
|
431
|
+
await waitFor(() => expect(agent.send).toHaveBeenCalledTimes(1));
|
|
432
|
+
view.rerender(
|
|
433
|
+
<Activity mode="hidden">
|
|
434
|
+
<Host />
|
|
435
|
+
</Activity>,
|
|
436
|
+
);
|
|
437
|
+
await act(async () => {});
|
|
438
|
+
view.rerender(
|
|
439
|
+
<Activity mode="visible">
|
|
440
|
+
<Host />
|
|
441
|
+
</Activity>,
|
|
442
|
+
);
|
|
443
|
+
await act(async () => {});
|
|
444
|
+
|
|
445
|
+
await act(async () => runtime.thread.append("second"));
|
|
446
|
+
await act(async () => {});
|
|
447
|
+
expect(agent.send).toHaveBeenCalledTimes(1);
|
|
448
|
+
|
|
449
|
+
await act(async () => releaseFirst());
|
|
450
|
+
await waitFor(() => expect(agent.send).toHaveBeenCalledTimes(2));
|
|
451
|
+
expect(agent.send).toHaveBeenNthCalledWith(2, "second", undefined);
|
|
452
|
+
view.unmount();
|
|
453
|
+
},
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
it("keeps the composer empty when a reset ends a send Eve already accepted", async () => {
|
|
457
|
+
const agent = makeAgent(async () => {});
|
|
458
|
+
agent.send.mockImplementation(async () => {
|
|
459
|
+
const receipt = createEveDelivery();
|
|
460
|
+
return {
|
|
461
|
+
...receipt,
|
|
462
|
+
result: (options?: { readonly signal?: AbortSignal }) =>
|
|
463
|
+
new Promise<never>((_, reject) => {
|
|
464
|
+
options?.signal?.addEventListener("abort", () =>
|
|
465
|
+
reject(options.signal?.reason),
|
|
466
|
+
);
|
|
467
|
+
}),
|
|
468
|
+
};
|
|
469
|
+
});
|
|
470
|
+
mockUseEveAgent.mockReturnValue(agent);
|
|
471
|
+
let runtime!: ReturnType<typeof useEveAgentRuntime>;
|
|
472
|
+
let reset!: () => void;
|
|
473
|
+
const Probe = () => {
|
|
474
|
+
reset = useEveReset();
|
|
475
|
+
return null;
|
|
476
|
+
};
|
|
477
|
+
const Host = () => {
|
|
478
|
+
runtime = useEveAgentRuntime();
|
|
479
|
+
return (
|
|
480
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
481
|
+
<Probe />
|
|
482
|
+
</AssistantRuntimeProvider>
|
|
483
|
+
);
|
|
484
|
+
};
|
|
485
|
+
const view = render(<Host />);
|
|
486
|
+
|
|
487
|
+
act(() => {
|
|
488
|
+
runtime.thread.composer.setText("sent");
|
|
489
|
+
runtime.thread.composer.send();
|
|
490
|
+
});
|
|
491
|
+
await waitFor(() =>
|
|
492
|
+
expect(agent.send).toHaveBeenCalledWith("sent", undefined),
|
|
493
|
+
);
|
|
494
|
+
act(() => reset());
|
|
495
|
+
await act(async () => {});
|
|
496
|
+
|
|
497
|
+
expect(agent.newSession).toHaveBeenCalledTimes(1);
|
|
498
|
+
expect(runtime.thread.composer.getState().text).toBe("");
|
|
499
|
+
view.unmount();
|
|
500
|
+
});
|
|
@@ -1,48 +1,93 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
|
|
3
3
|
import { renderHook, waitFor } from "@testing-library/react";
|
|
4
|
+
import type { SessionEvent } from "eve/client";
|
|
4
5
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
-
|
|
6
|
-
import { createEveSessionFixture } from "./testUtils";
|
|
6
|
+
import { createEveEvents, createEveSessionFixture } from "./testUtils";
|
|
7
7
|
import { useEveAgentRuntime } from "./useEveAgentRuntime";
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
data: { message: "hi", sequence: 2, turnId: "t1" },
|
|
19
|
-
meta: meta(1),
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
type: "step.started",
|
|
23
|
-
data: { modelId: "m", sequence: 3, stepIndex: 0, turnId: "t1" },
|
|
24
|
-
meta: meta(2),
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
type: "message.appended",
|
|
28
|
-
data: {
|
|
29
|
-
messageDelta: "Let me th",
|
|
30
|
-
sequence: 4,
|
|
31
|
-
stepIndex: 0,
|
|
32
|
-
turnId: "t1",
|
|
9
|
+
const renderInterruptedTurn = (
|
|
10
|
+
ending: Extract<SessionEvent, { type: "turn.settled" }>["data"],
|
|
11
|
+
) => {
|
|
12
|
+
const at = "2026-01-02T10:00:00.000Z";
|
|
13
|
+
const events = createEveEvents([
|
|
14
|
+
{ at, event: { type: "session.started", data: {} } },
|
|
15
|
+
{
|
|
16
|
+
at,
|
|
17
|
+
event: { type: "delivery.admitted", data: { deliveryId: "d1" } },
|
|
33
18
|
},
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
19
|
+
{
|
|
20
|
+
at,
|
|
21
|
+
event: {
|
|
22
|
+
type: "turn.started",
|
|
23
|
+
data: { turnId: "t1", cause: { deliveryId: "d1" }, follows: null },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
at,
|
|
28
|
+
event: {
|
|
29
|
+
type: "delivery.consumed",
|
|
30
|
+
data: {
|
|
31
|
+
deliveryId: "d1",
|
|
32
|
+
turnId: "t1",
|
|
33
|
+
parts: [{ kind: "text", text: "hi" }],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
at,
|
|
39
|
+
event: {
|
|
40
|
+
type: "model.requested",
|
|
41
|
+
data: { runId: "r1", owner: { turnId: "t1" } },
|
|
42
|
+
scope: { turnId: "t1" },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
at,
|
|
47
|
+
event: {
|
|
48
|
+
type: "model.started",
|
|
49
|
+
data: { runId: "r1", modelId: "m", attempt: 0 },
|
|
50
|
+
scope: { turnId: "t1", runId: "r1" },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
at,
|
|
55
|
+
event: {
|
|
56
|
+
type: "content.completed",
|
|
57
|
+
data: {
|
|
58
|
+
partId: "p1",
|
|
59
|
+
runId: "r1",
|
|
60
|
+
kind: "text",
|
|
61
|
+
value: "Let me th",
|
|
62
|
+
phase: "reply",
|
|
63
|
+
interrupted: true,
|
|
64
|
+
},
|
|
65
|
+
scope: { turnId: "t1", runId: "r1" },
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
at,
|
|
70
|
+
event: {
|
|
71
|
+
type: "model.settled",
|
|
72
|
+
data: { runId: "r1", outcome: "interrupted" },
|
|
73
|
+
scope: { turnId: "t1", runId: "r1" },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
at,
|
|
78
|
+
event: { type: "turn.settled", data: ending, scope: { turnId: "t1" } },
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
at,
|
|
82
|
+
event: {
|
|
83
|
+
type: "delivery.settled",
|
|
84
|
+
data: { deliveryId: "d1", turnId: "t1", outcome: "handled" },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
] satisfies readonly { at: string; event: SessionEvent }[]);
|
|
40
88
|
const { session } = createEveSessionFixture({});
|
|
41
89
|
return renderHook(() =>
|
|
42
|
-
useEveAgentRuntime({
|
|
43
|
-
initialEvents: interruptedTurn(ending) as never,
|
|
44
|
-
session,
|
|
45
|
-
}),
|
|
90
|
+
useEveAgentRuntime({ initialEvents: events, session }),
|
|
46
91
|
);
|
|
47
92
|
};
|
|
48
93
|
|
|
@@ -51,8 +96,9 @@ afterEach(() => vi.unstubAllGlobals());
|
|
|
51
96
|
describe("useEveAgentRuntime with an interrupted turn", () => {
|
|
52
97
|
it("shows a failed turn's assistant message as an error", async () => {
|
|
53
98
|
const { result } = renderInterruptedTurn({
|
|
54
|
-
|
|
55
|
-
|
|
99
|
+
turnId: "t1",
|
|
100
|
+
outcome: "failed",
|
|
101
|
+
error: { code: "internal", message: "boom" },
|
|
56
102
|
});
|
|
57
103
|
|
|
58
104
|
await waitFor(() =>
|
|
@@ -66,8 +112,8 @@ describe("useEveAgentRuntime with an interrupted turn", () => {
|
|
|
66
112
|
|
|
67
113
|
it("shows a cancelled turn's assistant message as cancelled", async () => {
|
|
68
114
|
const { result } = renderInterruptedTurn({
|
|
69
|
-
|
|
70
|
-
|
|
115
|
+
turnId: "t1",
|
|
116
|
+
outcome: "cancelled",
|
|
71
117
|
});
|
|
72
118
|
|
|
73
119
|
await waitFor(() =>
|
|
@@ -2,75 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
import { act, renderHook } 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
|
|
11
|
-
turnId:
|
|
12
|
-
text:
|
|
13
|
-
reply:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
{
|
|
23
|
-
type: "message.received",
|
|
24
|
-
data: { message: text, sequence: sequence + 1, turnId },
|
|
25
|
-
meta: { at: "2026-01-02T10:00:01.000Z", id: `${turnId}-2` },
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
type: "step.started",
|
|
29
|
-
data: { modelId: "m", sequence: sequence + 2, stepIndex: 0, turnId },
|
|
30
|
-
meta: { at: "2026-01-02T10:00:02.000Z", id: `${turnId}-3` },
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
type: "message.completed",
|
|
34
|
-
data: {
|
|
35
|
-
finishReason: "stop",
|
|
36
|
-
message: reply,
|
|
37
|
-
sequence: sequence + 3,
|
|
38
|
-
stepIndex: 0,
|
|
39
|
-
turnId,
|
|
40
|
-
},
|
|
41
|
-
meta: { at: "2026-01-02T10:00:03.000Z", id: `${turnId}-4` },
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
type: "turn.completed",
|
|
45
|
-
data: { sequence: sequence + 4, turnId },
|
|
46
|
-
meta: { at: "2026-01-02T10:00:04.000Z", id: `${turnId}-5` },
|
|
47
|
-
},
|
|
48
|
-
] as const satisfies readonly HandleMessageStreamEvent[];
|
|
49
|
-
|
|
50
|
-
const knownTurn = completedTurn("t1", "hi", "hello", 1);
|
|
51
|
-
const outOfBandTurn = completedTurn("t2", "answered elsewhere", "done", 6);
|
|
11
|
+
const knownTurn = createCompletedTurnEvents({
|
|
12
|
+
turnId: "t1",
|
|
13
|
+
text: "hi",
|
|
14
|
+
reply: "hello",
|
|
15
|
+
});
|
|
16
|
+
const outOfBandTurn = createCompletedTurnEvents({
|
|
17
|
+
turnId: "t2",
|
|
18
|
+
text: "answered elsewhere",
|
|
19
|
+
reply: "done",
|
|
20
|
+
startLine: knownTurn.length,
|
|
21
|
+
follows: "t1",
|
|
22
|
+
});
|
|
52
23
|
|
|
53
24
|
afterEach(() => vi.unstubAllGlobals());
|
|
54
25
|
|
|
55
26
|
describe("useEveAgentRuntime against a durable eve session", () => {
|
|
56
27
|
it("surfaces a turn the server appended out of band after reloadMainThread", async () => {
|
|
57
28
|
const { fetch, session } = createEveSessionFixture({
|
|
58
|
-
events:
|
|
59
|
-
|
|
60
|
-
...outOfBandTurn,
|
|
61
|
-
{
|
|
62
|
-
type: "session.waiting",
|
|
63
|
-
data: {
|
|
64
|
-
continuationToken: "session_resumed",
|
|
65
|
-
wait: "next-user-message",
|
|
66
|
-
},
|
|
67
|
-
meta: { at: "2026-01-02T10:00:05.000Z", id: "waiting" },
|
|
68
|
-
},
|
|
69
|
-
],
|
|
29
|
+
events: outOfBandTurn,
|
|
30
|
+
tailIndex: knownTurn.length + outOfBandTurn.length - 1,
|
|
70
31
|
});
|
|
71
32
|
|
|
72
33
|
const { result } = renderHook(() =>
|
|
73
|
-
useEveAgentRuntime({ initialEvents: knownTurn
|
|
34
|
+
useEveAgentRuntime({ initialEvents: knownTurn, session }),
|
|
74
35
|
);
|
|
75
36
|
expect(
|
|
76
37
|
result.current.thread.getState().messages.map((m) => m.role),
|