@assistant-ui/ai-sdk 0.0.2 → 0.0.4
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/README.md +1 -1
- package/dist/converters/convertMessage.d.ts.map +1 -1
- package/dist/converters/convertMessage.js +23 -2
- package/dist/converters/convertMessage.js.map +1 -1
- package/dist/converters/toCreateMessage.d.ts.map +1 -1
- package/dist/converters/toCreateMessage.js +6 -2
- package/dist/converters/toCreateMessage.js.map +1 -1
- package/dist/model-context/injectInteractableContext.d.ts +1 -1
- package/dist/model-context/injectInteractableContext.js +1 -1
- package/dist/model-context/injectInteractableContext.js.map +1 -1
- package/dist/model-context/injectQuoteContext.d.ts +1 -1
- package/dist/model-context/injectQuoteContext.js +1 -1
- package/dist/model-context/injectQuoteContext.js.map +1 -1
- package/dist/runtime/AISDKChat.js +1 -1
- package/dist/runtime/AISDKThreads.d.ts +10 -8
- package/dist/runtime/AISDKThreads.d.ts.map +1 -1
- package/dist/runtime/AISDKThreads.js +34 -27
- package/dist/runtime/AISDKThreads.js.map +1 -1
- package/dist/runtime/useAISDKRuntime.d.ts +18 -3
- package/dist/runtime/useAISDKRuntime.d.ts.map +1 -1
- package/dist/runtime/useAISDKRuntime.js +199 -73
- package/dist/runtime/useAISDKRuntime.js.map +1 -1
- package/dist/runtime/useChatRuntime.js +1 -1
- package/dist/runtime/useChatThread.d.ts +14 -1
- package/dist/runtime/useChatThread.d.ts.map +1 -1
- package/dist/runtime/useChatThread.js +11 -4
- package/dist/runtime/useChatThread.js.map +1 -1
- package/dist/runtime/useExternalHistory.d.ts.map +1 -1
- package/dist/runtime/useExternalHistory.js +52 -47
- package/dist/runtime/useExternalHistory.js.map +1 -1
- package/dist/runtime/useResourceCleanup.js +1 -1
- package/dist/usage.d.ts +1 -2
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +5 -7
- package/dist/usage.js.map +1 -1
- package/package.json +12 -12
- package/src/converters/convertMessage.test.ts +22 -0
- package/src/converters/convertMessage.ts +26 -2
- package/src/converters/toCreateMessage.ts +6 -5
- package/src/model-context/injectInteractableContext.ts +1 -1
- package/src/model-context/injectQuoteContext.ts +1 -1
- package/src/runtime/AISDKThreads.cloud.test.ts +11 -1
- package/src/runtime/AISDKThreads.test.ts +102 -0
- package/src/runtime/AISDKThreads.ts +16 -9
- package/src/runtime/useAISDKRuntime.denied-tool.test.tsx +122 -0
- package/src/runtime/useAISDKRuntime.test.ts +479 -10
- package/src/runtime/useAISDKRuntime.ts +199 -55
- package/src/runtime/useChatRuntime.integration.test.tsx +46 -0
- package/src/runtime/useChatRuntime.test.ts +16 -0
- package/src/runtime/useChatThread.ts +26 -0
- package/src/runtime/useExternalHistory.test.ts +143 -1
- package/src/runtime/useExternalHistory.ts +53 -56
- package/src/usage.test.ts +26 -8
- package/src/usage.ts +4 -9
|
@@ -7,13 +7,17 @@ import { validateUIMessages } from "ai";
|
|
|
7
7
|
// Mock only the sibling module that requires AUI store context (not available
|
|
8
8
|
// in isolation). Every other dependency — useExternalStoreRuntime,
|
|
9
9
|
// useToolInvocations, the message converter — runs for real.
|
|
10
|
-
vi.mock("./useExternalHistory", () =>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
vi.mock("./useExternalHistory", async (importOriginal) => {
|
|
11
|
+
const original =
|
|
12
|
+
await importOriginal<typeof import("./useExternalHistory")>();
|
|
13
|
+
return {
|
|
14
|
+
...original,
|
|
15
|
+
useExternalHistory: vi.fn(() => ({
|
|
16
|
+
isLoading: false,
|
|
17
|
+
deleteMessage: vi.fn().mockResolvedValue(undefined),
|
|
18
|
+
})),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
17
21
|
|
|
18
22
|
import { useExternalHistory } from "./useExternalHistory";
|
|
19
23
|
import { useAISDKRuntime } from "./useAISDKRuntime";
|
|
@@ -23,6 +27,7 @@ const createChatHelpers = (messages: any[] = []) => {
|
|
|
23
27
|
let currentMessages = [...messages];
|
|
24
28
|
|
|
25
29
|
const chatHelpers: any = {
|
|
30
|
+
id: "chat-1",
|
|
26
31
|
status: "ready",
|
|
27
32
|
error: null,
|
|
28
33
|
messages: currentMessages,
|
|
@@ -131,10 +136,11 @@ describe("useAISDKRuntime", () => {
|
|
|
131
136
|
abortError.name = "AbortError";
|
|
132
137
|
const chat = createChatHelpers();
|
|
133
138
|
let stopCalls = 0;
|
|
139
|
+
let rejectStop!: (error: unknown) => void;
|
|
134
140
|
chat.stop = () => {
|
|
135
141
|
stopCalls += 1;
|
|
136
142
|
return new Promise((_, reject) => {
|
|
137
|
-
|
|
143
|
+
rejectStop = reject;
|
|
138
144
|
});
|
|
139
145
|
};
|
|
140
146
|
const consoleError = vi
|
|
@@ -143,8 +149,11 @@ describe("useAISDKRuntime", () => {
|
|
|
143
149
|
|
|
144
150
|
try {
|
|
145
151
|
const { result } = renderHook(() => useAISDKRuntime(chat));
|
|
146
|
-
const unhandledRejections = await captureUnhandledRejections(() => {
|
|
147
|
-
|
|
152
|
+
const unhandledRejections = await captureUnhandledRejections(async () => {
|
|
153
|
+
await act(async () => {
|
|
154
|
+
result.current.thread.cancelRun();
|
|
155
|
+
rejectStop(abortError);
|
|
156
|
+
});
|
|
148
157
|
});
|
|
149
158
|
|
|
150
159
|
expect(stopCalls).toBe(1);
|
|
@@ -155,6 +164,286 @@ describe("useAISDKRuntime", () => {
|
|
|
155
164
|
}
|
|
156
165
|
});
|
|
157
166
|
|
|
167
|
+
it("marks only the stopped output cancelled", async () => {
|
|
168
|
+
let resolveStop!: () => void;
|
|
169
|
+
const chat = createChatHelpers([
|
|
170
|
+
{
|
|
171
|
+
id: "assistant-1",
|
|
172
|
+
role: "assistant",
|
|
173
|
+
parts: [{ type: "text", text: "partial", state: "streaming" }],
|
|
174
|
+
},
|
|
175
|
+
]);
|
|
176
|
+
chat.status = "streaming";
|
|
177
|
+
chat.stop = vi.fn(
|
|
178
|
+
() =>
|
|
179
|
+
new Promise<void>((resolve) => {
|
|
180
|
+
resolveStop = resolve;
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const { result, rerender } = renderHook(() => useAISDKRuntime(chat));
|
|
185
|
+
|
|
186
|
+
act(() => {
|
|
187
|
+
result.current.thread.cancelRun();
|
|
188
|
+
});
|
|
189
|
+
rerender();
|
|
190
|
+
|
|
191
|
+
expect(
|
|
192
|
+
result.current.thread.getState().messages.at(-1)?.status,
|
|
193
|
+
).toMatchObject({ type: "running" });
|
|
194
|
+
|
|
195
|
+
act(() => {
|
|
196
|
+
chat.status = "ready";
|
|
197
|
+
rerender();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
await waitFor(() => {
|
|
201
|
+
expect(
|
|
202
|
+
result.current.thread.getState().messages.at(-1)?.status,
|
|
203
|
+
).toMatchObject({
|
|
204
|
+
type: "incomplete",
|
|
205
|
+
reason: "cancelled",
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
await act(async () => {
|
|
210
|
+
resolveStop();
|
|
211
|
+
await Promise.resolve();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
act(() => {
|
|
215
|
+
chat.setMessages([
|
|
216
|
+
{
|
|
217
|
+
id: "assistant-2",
|
|
218
|
+
role: "assistant",
|
|
219
|
+
parts: [{ type: "text", text: "replacement" }],
|
|
220
|
+
},
|
|
221
|
+
]);
|
|
222
|
+
rerender();
|
|
223
|
+
});
|
|
224
|
+
await waitFor(() => {
|
|
225
|
+
expect(
|
|
226
|
+
result.current.thread.getState().messages.at(-1)?.status,
|
|
227
|
+
).toMatchObject({ type: "complete", reason: "unknown" });
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
act(() => {
|
|
231
|
+
chat.setMessages([
|
|
232
|
+
{
|
|
233
|
+
id: "assistant-1",
|
|
234
|
+
role: "assistant",
|
|
235
|
+
parts: [{ type: "text", text: "partial" }],
|
|
236
|
+
},
|
|
237
|
+
]);
|
|
238
|
+
chat.status = "streaming";
|
|
239
|
+
rerender();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
expect(
|
|
243
|
+
result.current.thread.getState().messages.at(-1)?.status,
|
|
244
|
+
).toMatchObject({ type: "running" });
|
|
245
|
+
|
|
246
|
+
act(() => {
|
|
247
|
+
chat.status = "ready";
|
|
248
|
+
rerender();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
await waitFor(() => {
|
|
252
|
+
expect(
|
|
253
|
+
result.current.thread.getState().messages.at(-1)?.status,
|
|
254
|
+
).toMatchObject({
|
|
255
|
+
type: "complete",
|
|
256
|
+
reason: "unknown",
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("keeps the stopped output cancelled through the next turn", async () => {
|
|
262
|
+
const chat = createChatHelpers([
|
|
263
|
+
{ id: "u1", role: "user", parts: [{ type: "text", text: "hi" }] },
|
|
264
|
+
{
|
|
265
|
+
id: "assistant-1",
|
|
266
|
+
role: "assistant",
|
|
267
|
+
parts: [{ type: "text", text: "partial", state: "streaming" }],
|
|
268
|
+
},
|
|
269
|
+
]);
|
|
270
|
+
chat.status = "streaming";
|
|
271
|
+
|
|
272
|
+
const { result, rerender } = renderHook(() => useAISDKRuntime(chat));
|
|
273
|
+
|
|
274
|
+
await act(async () => {
|
|
275
|
+
result.current.thread.cancelRun();
|
|
276
|
+
});
|
|
277
|
+
act(() => {
|
|
278
|
+
chat.status = "ready";
|
|
279
|
+
rerender();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
await waitFor(() => {
|
|
283
|
+
expect(
|
|
284
|
+
result.current.thread.getState().messages.at(-1)?.status,
|
|
285
|
+
).toMatchObject({ type: "incomplete", reason: "cancelled" });
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
act(() => {
|
|
289
|
+
chat.setMessages([
|
|
290
|
+
...chat.messages,
|
|
291
|
+
{ id: "u2", role: "user", parts: [{ type: "text", text: "next" }] },
|
|
292
|
+
{
|
|
293
|
+
id: "assistant-2",
|
|
294
|
+
role: "assistant",
|
|
295
|
+
parts: [{ type: "text", text: "answer" }],
|
|
296
|
+
},
|
|
297
|
+
]);
|
|
298
|
+
rerender();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
await waitFor(() => {
|
|
302
|
+
const messages = result.current.thread.getState().messages;
|
|
303
|
+
expect(
|
|
304
|
+
messages.find((message) => message.id === "assistant-1")?.status,
|
|
305
|
+
).toMatchObject({ type: "incomplete", reason: "cancelled" });
|
|
306
|
+
expect(messages.at(-1)?.status).toMatchObject({
|
|
307
|
+
type: "complete",
|
|
308
|
+
reason: "unknown",
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("retracts the cancellation when the provider picks the message back up", async () => {
|
|
314
|
+
const chat = createChatHelpers([
|
|
315
|
+
{
|
|
316
|
+
id: "assistant-1",
|
|
317
|
+
role: "assistant",
|
|
318
|
+
parts: [{ type: "text", text: "partial", state: "streaming" }],
|
|
319
|
+
},
|
|
320
|
+
]);
|
|
321
|
+
chat.status = "streaming";
|
|
322
|
+
|
|
323
|
+
const { result, rerender } = renderHook(() => useAISDKRuntime(chat));
|
|
324
|
+
|
|
325
|
+
await act(async () => {
|
|
326
|
+
result.current.thread.cancelRun();
|
|
327
|
+
});
|
|
328
|
+
act(() => {
|
|
329
|
+
chat.status = "ready";
|
|
330
|
+
rerender();
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
await waitFor(() => {
|
|
334
|
+
expect(
|
|
335
|
+
result.current.thread.getState().messages.at(-1)?.status,
|
|
336
|
+
).toMatchObject({ type: "incomplete", reason: "cancelled" });
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
act(() => {
|
|
340
|
+
chat.status = "streaming";
|
|
341
|
+
rerender();
|
|
342
|
+
});
|
|
343
|
+
act(() => {
|
|
344
|
+
chat.status = "ready";
|
|
345
|
+
rerender();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
await waitFor(() => {
|
|
349
|
+
expect(
|
|
350
|
+
result.current.thread.getState().messages.at(-1)?.status,
|
|
351
|
+
).toMatchObject({ type: "complete", reason: "unknown" });
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("does not mark completed output cancelled when already idle", async () => {
|
|
356
|
+
const chat = createChatHelpers([
|
|
357
|
+
{
|
|
358
|
+
id: "assistant-1",
|
|
359
|
+
role: "assistant",
|
|
360
|
+
parts: [{ type: "text", text: "finished" }],
|
|
361
|
+
},
|
|
362
|
+
]);
|
|
363
|
+
|
|
364
|
+
const { result, rerender } = renderHook(() => useAISDKRuntime(chat));
|
|
365
|
+
|
|
366
|
+
act(() => {
|
|
367
|
+
result.current.thread.cancelRun();
|
|
368
|
+
rerender();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
await waitFor(() => {
|
|
372
|
+
expect(
|
|
373
|
+
result.current.thread.getState().messages.at(-1)?.status,
|
|
374
|
+
).toMatchObject({ type: "complete", reason: "unknown" });
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("marks output cancelled while a client tool is still executing", async () => {
|
|
379
|
+
let resolveTool!: (value: string) => void;
|
|
380
|
+
const execute = vi.fn(
|
|
381
|
+
() =>
|
|
382
|
+
new Promise<string>((resolve) => {
|
|
383
|
+
resolveTool = resolve;
|
|
384
|
+
}),
|
|
385
|
+
);
|
|
386
|
+
const chat = createChatHelpers();
|
|
387
|
+
|
|
388
|
+
const { result, rerender } = renderHook(() => useAISDKRuntime(chat));
|
|
389
|
+
const unregister = result.current.registerModelContextProvider({
|
|
390
|
+
getModelContext: () => ({
|
|
391
|
+
tools: {
|
|
392
|
+
weather: {
|
|
393
|
+
parameters: { type: "object", properties: {} },
|
|
394
|
+
execute,
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
}),
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
act(() => {
|
|
402
|
+
chat.setMessages([
|
|
403
|
+
{
|
|
404
|
+
id: "assistant-1",
|
|
405
|
+
role: "assistant",
|
|
406
|
+
parts: [
|
|
407
|
+
{
|
|
408
|
+
type: "tool-weather",
|
|
409
|
+
toolCallId: "tool-1",
|
|
410
|
+
state: "input-available",
|
|
411
|
+
input: { city: "London" },
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
},
|
|
415
|
+
]);
|
|
416
|
+
rerender();
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
await waitFor(() => {
|
|
420
|
+
expect(execute).toHaveBeenCalledOnce();
|
|
421
|
+
expect(result.current.thread.getState().isRunning).toBe(true);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
act(() => {
|
|
425
|
+
result.current.thread.cancelRun();
|
|
426
|
+
chat.setMessages([
|
|
427
|
+
{
|
|
428
|
+
id: "assistant-1",
|
|
429
|
+
role: "assistant",
|
|
430
|
+
parts: [{ type: "text", text: "stopped" }],
|
|
431
|
+
},
|
|
432
|
+
]);
|
|
433
|
+
resolveTool("sunny");
|
|
434
|
+
rerender();
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
await waitFor(() => {
|
|
438
|
+
expect(
|
|
439
|
+
result.current.thread.getState().messages.at(-1)?.status,
|
|
440
|
+
).toMatchObject({ type: "incomplete", reason: "cancelled" });
|
|
441
|
+
});
|
|
442
|
+
} finally {
|
|
443
|
+
unregister();
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
158
447
|
it("reports non-AbortError cancellation failures", async () => {
|
|
159
448
|
const stopError = new Error("stop failed");
|
|
160
449
|
const chat = createChatHelpers();
|
|
@@ -690,6 +979,186 @@ describe("useAISDKRuntime", () => {
|
|
|
690
979
|
expect(result.current.thread.getState().suggestions).toEqual(suggestions);
|
|
691
980
|
});
|
|
692
981
|
|
|
982
|
+
it("imports a message tree without replacing the chat feed", async () => {
|
|
983
|
+
const chat = createChatHelpers();
|
|
984
|
+
const onBranchChange = vi.fn();
|
|
985
|
+
const messageRepository = {
|
|
986
|
+
headId: "a2",
|
|
987
|
+
messages: [
|
|
988
|
+
{
|
|
989
|
+
parentId: null,
|
|
990
|
+
message: {
|
|
991
|
+
id: "u1",
|
|
992
|
+
role: "user" as const,
|
|
993
|
+
parts: [{ type: "text" as const, text: "question" }],
|
|
994
|
+
},
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
parentId: "u1",
|
|
998
|
+
message: {
|
|
999
|
+
id: "a1",
|
|
1000
|
+
role: "assistant" as const,
|
|
1001
|
+
parts: [{ type: "text" as const, text: "first" }],
|
|
1002
|
+
},
|
|
1003
|
+
},
|
|
1004
|
+
{
|
|
1005
|
+
parentId: "u1",
|
|
1006
|
+
message: {
|
|
1007
|
+
id: "a2",
|
|
1008
|
+
role: "assistant" as const,
|
|
1009
|
+
parts: [{ type: "text" as const, text: "second" }],
|
|
1010
|
+
},
|
|
1011
|
+
},
|
|
1012
|
+
],
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
const { result, rerender } = renderHook(() =>
|
|
1016
|
+
useAISDKRuntime(chat, {
|
|
1017
|
+
messageRepository,
|
|
1018
|
+
unstable_onBranchChange: onBranchChange,
|
|
1019
|
+
}),
|
|
1020
|
+
);
|
|
1021
|
+
|
|
1022
|
+
await waitFor(() => {
|
|
1023
|
+
expect(
|
|
1024
|
+
result.current.thread.getState().messages.map((message) => message.id),
|
|
1025
|
+
).toEqual(["u1", "a2"]);
|
|
1026
|
+
});
|
|
1027
|
+
expect(result.current.thread.getMessageById("a2").getState()).toMatchObject(
|
|
1028
|
+
{
|
|
1029
|
+
branchNumber: 2,
|
|
1030
|
+
branchCount: 2,
|
|
1031
|
+
},
|
|
1032
|
+
);
|
|
1033
|
+
expect(chat.messages.map((message: { id: string }) => message.id)).toEqual([
|
|
1034
|
+
"u1",
|
|
1035
|
+
"a2",
|
|
1036
|
+
]);
|
|
1037
|
+
|
|
1038
|
+
act(() => {
|
|
1039
|
+
result.current.thread
|
|
1040
|
+
.getMessageById("a2")
|
|
1041
|
+
.switchToBranch({ branchId: "a1" });
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
expect(onBranchChange).toHaveBeenCalledWith({
|
|
1045
|
+
headId: "a1",
|
|
1046
|
+
visibleMessageIds: ["u1", "a1"],
|
|
1047
|
+
});
|
|
1048
|
+
expect(chat.messages.map((message: { id: string }) => message.id)).toEqual([
|
|
1049
|
+
"u1",
|
|
1050
|
+
"a1",
|
|
1051
|
+
]);
|
|
1052
|
+
expect(chat.messages).not.toEqual([]);
|
|
1053
|
+
|
|
1054
|
+
chat.messages = [
|
|
1055
|
+
{ id: "u1", role: "user", parts: [{ type: "text", text: "question" }] },
|
|
1056
|
+
{
|
|
1057
|
+
id: "a1",
|
|
1058
|
+
role: "assistant",
|
|
1059
|
+
parts: [{ type: "text", text: "first token" }],
|
|
1060
|
+
},
|
|
1061
|
+
];
|
|
1062
|
+
rerender();
|
|
1063
|
+
|
|
1064
|
+
await waitFor(() => {
|
|
1065
|
+
expect(textOf(result.current.thread.getState().messages.at(-1))).toBe(
|
|
1066
|
+
"first token",
|
|
1067
|
+
);
|
|
1068
|
+
});
|
|
1069
|
+
expect(result.current.thread.getMessageById("a1").getState()).toMatchObject(
|
|
1070
|
+
{
|
|
1071
|
+
branchNumber: 1,
|
|
1072
|
+
branchCount: 2,
|
|
1073
|
+
},
|
|
1074
|
+
);
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
it("does not overwrite a nonempty chat with the repository", async () => {
|
|
1078
|
+
const chat = createChatHelpers([
|
|
1079
|
+
{ id: "live", role: "user", parts: [{ type: "text", text: "keep me" }] },
|
|
1080
|
+
]);
|
|
1081
|
+
const messageRepository = {
|
|
1082
|
+
headId: "a1",
|
|
1083
|
+
messages: [
|
|
1084
|
+
{
|
|
1085
|
+
parentId: null,
|
|
1086
|
+
message: {
|
|
1087
|
+
id: "u1",
|
|
1088
|
+
role: "user" as const,
|
|
1089
|
+
parts: [{ type: "text" as const, text: "question" }],
|
|
1090
|
+
},
|
|
1091
|
+
},
|
|
1092
|
+
{
|
|
1093
|
+
parentId: "u1",
|
|
1094
|
+
message: {
|
|
1095
|
+
id: "a1",
|
|
1096
|
+
role: "assistant" as const,
|
|
1097
|
+
parts: [{ type: "text" as const, text: "first" }],
|
|
1098
|
+
},
|
|
1099
|
+
},
|
|
1100
|
+
],
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
const { result } = renderHook(() =>
|
|
1104
|
+
useAISDKRuntime(chat, { messageRepository }),
|
|
1105
|
+
);
|
|
1106
|
+
|
|
1107
|
+
await waitFor(() => {
|
|
1108
|
+
expect(
|
|
1109
|
+
result.current.thread.getState().messages.map((message) => message.id),
|
|
1110
|
+
).toEqual(["live"]);
|
|
1111
|
+
});
|
|
1112
|
+
expect(chat.messages.map((message: { id: string }) => message.id)).toEqual([
|
|
1113
|
+
"live",
|
|
1114
|
+
]);
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
it("does not reseed when the repository object identity changes", async () => {
|
|
1118
|
+
const chat = createChatHelpers();
|
|
1119
|
+
const makeRepository = (text: string) => ({
|
|
1120
|
+
headId: "a1",
|
|
1121
|
+
messages: [
|
|
1122
|
+
{
|
|
1123
|
+
parentId: null,
|
|
1124
|
+
message: {
|
|
1125
|
+
id: "u1",
|
|
1126
|
+
role: "user" as const,
|
|
1127
|
+
parts: [{ type: "text" as const, text: "question" }],
|
|
1128
|
+
},
|
|
1129
|
+
},
|
|
1130
|
+
{
|
|
1131
|
+
parentId: "u1",
|
|
1132
|
+
message: {
|
|
1133
|
+
id: "a1",
|
|
1134
|
+
role: "assistant" as const,
|
|
1135
|
+
parts: [{ type: "text" as const, text }],
|
|
1136
|
+
},
|
|
1137
|
+
},
|
|
1138
|
+
],
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
const { result, rerender } = renderHook(
|
|
1142
|
+
({ repository }) =>
|
|
1143
|
+
useAISDKRuntime(chat, { messageRepository: repository }),
|
|
1144
|
+
{ initialProps: { repository: makeRepository("first") } },
|
|
1145
|
+
);
|
|
1146
|
+
|
|
1147
|
+
await waitFor(() => {
|
|
1148
|
+
expect(textOf(result.current.thread.getState().messages.at(-1))).toBe(
|
|
1149
|
+
"first",
|
|
1150
|
+
);
|
|
1151
|
+
});
|
|
1152
|
+
const setMessagesCalls = chat.setMessages.mock.calls.length;
|
|
1153
|
+
|
|
1154
|
+
rerender({ repository: makeRepository("second") });
|
|
1155
|
+
|
|
1156
|
+
expect(textOf(result.current.thread.getState().messages.at(-1))).toBe(
|
|
1157
|
+
"first",
|
|
1158
|
+
);
|
|
1159
|
+
expect(chat.setMessages.mock.calls.length).toBe(setMessagesCalls);
|
|
1160
|
+
});
|
|
1161
|
+
|
|
693
1162
|
it("calls adapters.suggestion after settle with messages and signal", async () => {
|
|
694
1163
|
const generate = vi.fn().mockResolvedValue([{ prompt: "next" }]);
|
|
695
1164
|
const chat = createChatHelpers([
|