@ai-sdk/angular 2.0.46 → 2.0.48

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.
@@ -1,1361 +0,0 @@
1
- import { mockId } from '@ai-sdk/provider-utils/test';
2
- import {
3
- createTestServer,
4
- TestResponseController,
5
- } from '@ai-sdk/test-server/with-vitest';
6
- import {
7
- DefaultChatTransport,
8
- isStaticToolUIPart,
9
- TextStreamChatTransport,
10
- } from 'ai';
11
- import { beforeEach, describe, expect, it, vi } from 'vitest';
12
- import { Chat } from './chat.ng';
13
-
14
- function formatStreamPart(part: object) {
15
- return `data: ${JSON.stringify(part)}\n\n`;
16
- }
17
-
18
- function createFileList(...files: File[]): FileList {
19
- // file lists are really hard to create :(
20
- const input = document.createElement('input');
21
- input.setAttribute('type', 'file');
22
- input.setAttribute('name', 'file-upload');
23
- input.multiple = true;
24
- const fileList: FileList = Object.create(input.files);
25
- for (let i = 0; i < files.length; i++) {
26
- fileList[i] = files[i];
27
- }
28
- Object.defineProperty(fileList, 'length', { value: files.length });
29
- return fileList;
30
- }
31
-
32
- const server = createTestServer({
33
- '/api/chat': {},
34
- });
35
-
36
- describe('data protocol stream', () => {
37
- let chat: Chat;
38
-
39
- beforeEach(() => {
40
- chat = new Chat({
41
- generateId: mockId(),
42
- });
43
- });
44
-
45
- it('should correctly manage streamed response in messages', async () => {
46
- server.urls['/api/chat'].response = {
47
- type: 'stream-chunks',
48
- chunks: [
49
- formatStreamPart({ type: 'text-start', id: '0' }),
50
- formatStreamPart({ type: 'text-delta', id: '0', delta: 'Hello' }),
51
- formatStreamPart({ type: 'text-delta', id: '0', delta: ',' }),
52
- formatStreamPart({ type: 'text-delta', id: '0', delta: ' world' }),
53
- formatStreamPart({ type: 'text-delta', id: '0', delta: '.' }),
54
- formatStreamPart({ type: 'text-end', id: '0' }),
55
- ],
56
- };
57
-
58
- await chat.sendMessage({
59
- parts: [{ text: 'hi', type: 'text' }],
60
- });
61
- expect(chat.messages.at(0)).toStrictEqual(
62
- expect.objectContaining({
63
- role: 'user',
64
- parts: [{ text: 'hi', type: 'text' }],
65
- }),
66
- );
67
-
68
- expect(chat.messages.at(1)).toStrictEqual(
69
- expect.objectContaining({
70
- role: 'assistant',
71
- parts: [{ type: 'text', text: 'Hello, world.', state: 'done' }],
72
- }),
73
- );
74
- });
75
-
76
- it('should show error response when there is a server error', async () => {
77
- server.urls['/api/chat'].response = {
78
- type: 'error',
79
- status: 404,
80
- body: 'Not found',
81
- };
82
-
83
- await chat.sendMessage({
84
- text: 'hi',
85
- });
86
- expect(chat.error).toBeInstanceOf(Error);
87
- expect(chat.error?.message).toBe('Not found');
88
- });
89
-
90
- it('should show error response when there is a streaming error', async () => {
91
- server.urls['/api/chat'].response = {
92
- type: 'stream-chunks',
93
- chunks: [
94
- formatStreamPart({
95
- type: 'error',
96
- errorText: 'custom error message',
97
- }),
98
- ],
99
- };
100
-
101
- await chat.sendMessage({
102
- role: 'user',
103
- parts: [{ text: 'hi', type: 'text' }],
104
- });
105
- expect(chat.error).toBeInstanceOf(Error);
106
- expect(chat.error?.message).toBe('custom error message');
107
- });
108
-
109
- describe('status', () => {
110
- it('should show status', async () => {
111
- const controller = new TestResponseController();
112
- server.urls['/api/chat'].response = {
113
- type: 'controlled-stream',
114
- controller,
115
- };
116
-
117
- const appendOperation = chat.sendMessage({
118
- role: 'user',
119
- parts: [{ text: 'hi', type: 'text' }],
120
- });
121
- await vi.waitFor(() => expect(chat.status).toBe('submitted'));
122
- controller.write(formatStreamPart({ type: 'text-start', id: '0' }));
123
- controller.write(
124
- formatStreamPart({ type: 'text-delta', id: '0', delta: 'Hello' }),
125
- );
126
- controller.write(formatStreamPart({ type: 'text-end', id: '0' }));
127
- await vi.waitFor(() => expect(chat.status).toBe('streaming'));
128
- controller.close();
129
- await appendOperation;
130
- expect(chat.status).toBe('ready');
131
- });
132
-
133
- it('should set status to error when there is a server error', async () => {
134
- server.urls['/api/chat'].response = {
135
- type: 'error',
136
- status: 404,
137
- body: 'Not found',
138
- };
139
-
140
- chat.sendMessage({
141
- role: 'user',
142
- parts: [{ text: 'hi', type: 'text' }],
143
- });
144
- await vi.waitFor(() => expect(chat.status).toBe('error'));
145
- });
146
- });
147
-
148
- it('should invoke onFinish when the stream finishes', async () => {
149
- server.urls['/api/chat'].response = {
150
- type: 'stream-chunks',
151
- chunks: [
152
- formatStreamPart({ type: 'text-start', id: '0' }),
153
- formatStreamPart({ type: 'text-delta', id: '0', delta: 'Hello' }),
154
- formatStreamPart({ type: 'text-delta', id: '0', delta: ',' }),
155
- formatStreamPart({ type: 'text-delta', id: '0', delta: ' world' }),
156
- formatStreamPart({ type: 'text-delta', id: '0', delta: '.' }),
157
- formatStreamPart({ type: 'text-end', id: '0' }),
158
- formatStreamPart({
159
- type: 'finish',
160
- messageMetadata: {
161
- example: 'metadata',
162
- },
163
- }),
164
- ],
165
- };
166
-
167
- const onFinish = vi.fn();
168
- const chatWithOnFinish = new Chat({
169
- onFinish,
170
- generateId: mockId(),
171
- });
172
- await chatWithOnFinish.sendMessage({
173
- role: 'user',
174
- parts: [{ text: 'hi', type: 'text' }],
175
- });
176
-
177
- expect(onFinish).toHaveBeenCalledExactlyOnceWith({
178
- isAbort: false,
179
- isDisconnect: false,
180
- isError: false,
181
- message: {
182
- id: 'id-2',
183
- metadata: {
184
- example: 'metadata',
185
- },
186
- parts: [
187
- {
188
- text: 'Hello, world.',
189
- type: 'text',
190
- state: 'done',
191
- },
192
- ],
193
- role: 'assistant',
194
- },
195
- messages: [
196
- {
197
- id: 'id-1',
198
- role: 'user',
199
- metadata: undefined,
200
- parts: [{ text: 'hi', type: 'text' }],
201
- },
202
- {
203
- id: 'id-2',
204
- role: 'assistant',
205
- metadata: {
206
- example: 'metadata',
207
- },
208
- parts: [{ text: 'Hello, world.', type: 'text', state: 'done' }],
209
- },
210
- ],
211
- });
212
- });
213
-
214
- describe('id', () => {
215
- it('should send the id to the server', async () => {
216
- server.urls['/api/chat'].response = {
217
- type: 'stream-chunks',
218
- chunks: ['0:"Hello"\n', '0:","\n', '0:" world"\n', '0:"."\n'],
219
- };
220
-
221
- await chat.sendMessage({
222
- role: 'user',
223
- parts: [{ text: 'hi', type: 'text' }],
224
- });
225
-
226
- expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
227
- {
228
- "id": "id-0",
229
- "messages": [
230
- {
231
- "id": "id-1",
232
- "parts": [
233
- {
234
- "text": "hi",
235
- "type": "text",
236
- },
237
- ],
238
- "role": "user",
239
- },
240
- ],
241
- "trigger": "submit-message",
242
- }
243
- `);
244
- });
245
- });
246
- });
247
-
248
- describe('text stream', () => {
249
- let chat: Chat;
250
-
251
- beforeEach(() => {
252
- const generateId = mockId();
253
-
254
- chat = new Chat({
255
- generateId,
256
- transport: new TextStreamChatTransport({
257
- api: '/api/chat',
258
- }),
259
- });
260
- });
261
-
262
- it('should show streamed response', async () => {
263
- server.urls['/api/chat'].response = {
264
- type: 'stream-chunks',
265
- chunks: ['Hello', ',', ' world', '.'],
266
- };
267
-
268
- await chat.sendMessage({
269
- role: 'user',
270
- parts: [{ text: 'hi', type: 'text' }],
271
- });
272
-
273
- expect(chat.messages).toMatchInlineSnapshot(`
274
- [
275
- {
276
- "id": "id-1",
277
- "metadata": undefined,
278
- "parts": [
279
- {
280
- "text": "hi",
281
- "type": "text",
282
- },
283
- ],
284
- "role": "user",
285
- },
286
- {
287
- "id": "id-2",
288
- "metadata": undefined,
289
- "parts": [
290
- {
291
- "type": "step-start",
292
- },
293
- {
294
- "providerMetadata": undefined,
295
- "state": "done",
296
- "text": "Hello, world.",
297
- "type": "text",
298
- },
299
- ],
300
- "role": "assistant",
301
- },
302
- ]
303
- `);
304
- });
305
-
306
- it('should have stable message ids', async () => {
307
- const controller = new TestResponseController();
308
- server.urls['/api/chat'].response = {
309
- type: 'controlled-stream',
310
- controller,
311
- };
312
-
313
- const appendOperation = chat.sendMessage({
314
- role: 'user',
315
- parts: [{ text: 'hi', type: 'text' }],
316
- });
317
- controller.write('He');
318
-
319
- await vi.waitFor(() =>
320
- expect(chat.messages.at(1)).toStrictEqual(
321
- expect.objectContaining({
322
- id: expect.any(String),
323
- role: 'assistant',
324
- metadata: undefined,
325
- parts: [
326
- { type: 'step-start' },
327
- { text: 'He', type: 'text', state: 'streaming' },
328
- ],
329
- }),
330
- ),
331
- );
332
- const id = chat.messages.at(1)?.id;
333
-
334
- controller.write('llo');
335
- controller.close();
336
- await appendOperation;
337
-
338
- expect(id).toBeDefined();
339
- expect(chat.messages.at(1)).toStrictEqual(
340
- expect.objectContaining({
341
- id,
342
- role: 'assistant',
343
- }),
344
- );
345
- });
346
-
347
- it('should invoke onFinish when the stream finishes', async () => {
348
- server.urls['/api/chat'].response = {
349
- type: 'stream-chunks',
350
- chunks: ['Hello', ',', ' world', '.'],
351
- };
352
-
353
- const onFinish = vi.fn();
354
- const chatWithOnFinish = new Chat({
355
- onFinish,
356
- transport: new TextStreamChatTransport({
357
- api: '/api/chat',
358
- }),
359
- });
360
- await chatWithOnFinish.sendMessage({
361
- role: 'user',
362
- parts: [{ text: 'hi', type: 'text' }],
363
- });
364
-
365
- expect(onFinish).toHaveBeenCalledExactlyOnceWith({
366
- isAbort: false,
367
- isDisconnect: false,
368
- isError: false,
369
- message: {
370
- id: expect.any(String),
371
- role: 'assistant',
372
- metadata: undefined,
373
- parts: [
374
- { type: 'step-start' },
375
- { text: 'Hello, world.', type: 'text', state: 'done' },
376
- ],
377
- },
378
- messages: [
379
- {
380
- id: expect.any(String),
381
- role: 'user',
382
- metadata: undefined,
383
- parts: [{ text: 'hi', type: 'text' }],
384
- },
385
- {
386
- id: expect.any(String),
387
- role: 'assistant',
388
- metadata: undefined,
389
- parts: [
390
- { type: 'step-start' },
391
- { text: 'Hello, world.', type: 'text', state: 'done' },
392
- ],
393
- },
394
- ],
395
- });
396
- });
397
- });
398
-
399
- describe('onToolCall', () => {
400
- let resolve: () => void;
401
- let toolCallPromise: Promise<void>;
402
- let chat: Chat;
403
-
404
- function promiseWithResolvers<T>() {
405
- let resolve!: (v: T | PromiseLike<T>) => void;
406
- let reject!: (reason?: unknown) => void;
407
-
408
- const promise = new Promise<T>((res, rej) => {
409
- resolve = res;
410
- reject = rej;
411
- });
412
-
413
- return { promise, resolve, reject };
414
- }
415
-
416
- beforeEach(() => {
417
- ({ resolve, promise: toolCallPromise } = promiseWithResolvers<void>());
418
-
419
- chat = new Chat({
420
- async onToolCall({ toolCall }) {
421
- await toolCallPromise;
422
- chat.addToolOutput({
423
- tool: 'test-tool',
424
- toolCallId: toolCall.toolCallId,
425
- output: `test-tool-response: ${toolCall.toolName} ${
426
- toolCall.toolCallId
427
- } ${JSON.stringify(toolCall.input)}`,
428
- });
429
- },
430
- });
431
- });
432
-
433
- it("should invoke onToolCall when a tool call is received from the server's response", async () => {
434
- server.urls['/api/chat'].response = {
435
- type: 'stream-chunks',
436
- chunks: [
437
- formatStreamPart({
438
- type: 'tool-input-available',
439
- toolCallId: 'tool-call-0',
440
- toolName: 'test-tool',
441
- input: { testArg: 'test-value' },
442
- }),
443
- ],
444
- };
445
-
446
- const appendOperation = chat.sendMessage({ text: 'hi' });
447
-
448
- await vi.waitFor(() => {
449
- expect(
450
- chat.messages.at(1)?.parts.filter(isStaticToolUIPart),
451
- ).toStrictEqual([
452
- {
453
- state: 'input-available',
454
- errorText: undefined,
455
- rawInput: undefined,
456
- toolCallId: 'tool-call-0',
457
- type: 'tool-test-tool',
458
- input: { testArg: 'test-value' },
459
- output: undefined,
460
- providerExecuted: undefined,
461
- preliminary: undefined,
462
- title: undefined,
463
- },
464
- ]);
465
- });
466
-
467
- resolve();
468
- await appendOperation;
469
-
470
- expect(chat.messages.at(1)?.parts.filter(isStaticToolUIPart)).toStrictEqual(
471
- [
472
- {
473
- state: 'output-available',
474
- errorText: undefined,
475
- rawInput: undefined,
476
- toolCallId: 'tool-call-0',
477
- type: 'tool-test-tool',
478
- input: { testArg: 'test-value' },
479
- output:
480
- 'test-tool-response: test-tool tool-call-0 {"testArg":"test-value"}',
481
- providerExecuted: undefined,
482
- preliminary: undefined,
483
- title: undefined,
484
- },
485
- ],
486
- );
487
- });
488
- });
489
-
490
- describe('tool invocations', () => {
491
- let chat: Chat;
492
-
493
- beforeEach(() => {
494
- const generateId = mockId();
495
- chat = new Chat({
496
- generateId,
497
- transport: new DefaultChatTransport({
498
- api: '/api/chat',
499
- }),
500
- });
501
- });
502
-
503
- it('should display partial tool call, tool call, and tool result', async () => {
504
- const controller = new TestResponseController();
505
- server.urls['/api/chat'].response = {
506
- type: 'controlled-stream',
507
- controller,
508
- };
509
-
510
- const appendOperation = chat.sendMessage({
511
- role: 'user',
512
- parts: [{ text: 'hi', type: 'text' }],
513
- });
514
-
515
- controller.write(
516
- formatStreamPart({
517
- type: 'tool-input-start',
518
- toolCallId: 'tool-call-0',
519
- toolName: 'test-tool',
520
- }),
521
- );
522
-
523
- await vi.waitFor(() => {
524
- expect(
525
- chat.messages.at(1)?.parts.filter(isStaticToolUIPart),
526
- ).toStrictEqual([
527
- {
528
- state: 'input-streaming',
529
- errorText: undefined,
530
- rawInput: undefined,
531
- toolCallId: 'tool-call-0',
532
- type: 'tool-test-tool',
533
- input: undefined,
534
- output: undefined,
535
- providerExecuted: undefined,
536
- preliminary: undefined,
537
- title: undefined,
538
- },
539
- ]);
540
- });
541
-
542
- controller.write(
543
- formatStreamPart({
544
- type: 'tool-input-delta',
545
- toolCallId: 'tool-call-0',
546
- inputTextDelta: '{"testArg":"t',
547
- }),
548
- );
549
-
550
- await vi.waitFor(() => {
551
- expect(
552
- chat.messages.at(1)?.parts.filter(isStaticToolUIPart),
553
- ).toStrictEqual([
554
- {
555
- state: 'input-streaming',
556
- errorText: undefined,
557
- rawInput: undefined,
558
- toolCallId: 'tool-call-0',
559
- type: 'tool-test-tool',
560
- input: { testArg: 't' },
561
- output: undefined,
562
- providerExecuted: undefined,
563
- preliminary: undefined,
564
- title: undefined,
565
- },
566
- ]);
567
- });
568
-
569
- controller.write(
570
- formatStreamPart({
571
- type: 'tool-input-delta',
572
- toolCallId: 'tool-call-0',
573
- inputTextDelta: 'est-value"}}',
574
- }),
575
- );
576
-
577
- await vi.waitFor(() => {
578
- expect(
579
- chat.messages.at(1)?.parts.filter(isStaticToolUIPart),
580
- ).toStrictEqual([
581
- {
582
- state: 'input-streaming',
583
- errorText: undefined,
584
- rawInput: undefined,
585
- toolCallId: 'tool-call-0',
586
- type: 'tool-test-tool',
587
- input: { testArg: 'test-value' },
588
- output: undefined,
589
- providerExecuted: undefined,
590
- preliminary: undefined,
591
- title: undefined,
592
- },
593
- ]);
594
- });
595
-
596
- controller.write(
597
- formatStreamPart({
598
- type: 'tool-input-available',
599
- toolCallId: 'tool-call-0',
600
- toolName: 'test-tool',
601
- input: { testArg: 'test-value' },
602
- }),
603
- );
604
-
605
- await vi.waitFor(() => {
606
- expect(
607
- chat.messages.at(1)?.parts.filter(isStaticToolUIPart),
608
- ).toStrictEqual([
609
- {
610
- state: 'input-available',
611
- errorText: undefined,
612
- rawInput: undefined,
613
- toolCallId: 'tool-call-0',
614
- type: 'tool-test-tool',
615
- input: { testArg: 'test-value' },
616
- output: undefined,
617
- providerExecuted: undefined,
618
- preliminary: undefined,
619
- title: undefined,
620
- },
621
- ]);
622
- });
623
-
624
- controller.write(
625
- formatStreamPart({
626
- type: 'tool-output-available',
627
- toolCallId: 'tool-call-0',
628
- output: 'test-result',
629
- }),
630
- );
631
- controller.close();
632
- await appendOperation;
633
-
634
- expect(chat.messages.at(1)?.parts.filter(isStaticToolUIPart)).toStrictEqual(
635
- [
636
- {
637
- state: 'output-available',
638
- errorText: undefined,
639
- rawInput: undefined,
640
- toolCallId: 'tool-call-0',
641
- type: 'tool-test-tool',
642
- input: { testArg: 'test-value' },
643
- output: 'test-result',
644
- providerExecuted: undefined,
645
- preliminary: undefined,
646
- title: undefined,
647
- },
648
- ],
649
- );
650
- });
651
-
652
- it('should display partial tool call and tool result (when there is no tool call streaming)', async () => {
653
- const controller = new TestResponseController();
654
- server.urls['/api/chat'].response = {
655
- type: 'controlled-stream',
656
- controller,
657
- };
658
-
659
- const appendOperation = chat.sendMessage({ text: 'hi' });
660
-
661
- controller.write(
662
- formatStreamPart({
663
- type: 'tool-input-available',
664
- toolCallId: 'tool-call-0',
665
- toolName: 'test-tool',
666
- input: { testArg: 'test-value' },
667
- }),
668
- );
669
-
670
- await vi.waitFor(() => {
671
- expect(
672
- chat.messages.at(1)?.parts.filter(isStaticToolUIPart),
673
- ).toStrictEqual([
674
- {
675
- state: 'input-available',
676
- errorText: undefined,
677
- rawInput: undefined,
678
- toolCallId: 'tool-call-0',
679
- type: 'tool-test-tool',
680
- input: { testArg: 'test-value' },
681
- output: undefined,
682
- providerExecuted: undefined,
683
- preliminary: undefined,
684
- title: undefined,
685
- },
686
- ]);
687
- });
688
-
689
- controller.write(
690
- formatStreamPart({
691
- type: 'tool-output-available',
692
- toolCallId: 'tool-call-0',
693
- output: 'test-result',
694
- }),
695
- );
696
- controller.close();
697
-
698
- await appendOperation;
699
-
700
- expect(chat.messages.at(1)?.parts.filter(isStaticToolUIPart)).toStrictEqual(
701
- [
702
- {
703
- state: 'output-available',
704
- errorText: undefined,
705
- rawInput: undefined,
706
- toolCallId: 'tool-call-0',
707
- type: 'tool-test-tool',
708
- input: { testArg: 'test-value' },
709
- output: 'test-result',
710
- providerExecuted: undefined,
711
- preliminary: undefined,
712
- title: undefined,
713
- },
714
- ],
715
- );
716
- });
717
-
718
- it('should update tool call to result when addToolOutput is called', async () => {
719
- server.urls['/api/chat'].response = {
720
- type: 'stream-chunks',
721
- chunks: [
722
- formatStreamPart({
723
- type: 'tool-input-available',
724
- toolCallId: 'tool-call-0',
725
- toolName: 'test-tool',
726
- input: { testArg: 'test-value' },
727
- }),
728
- ],
729
- };
730
-
731
- await chat.sendMessage({
732
- text: 'hi',
733
- });
734
-
735
- await vi.waitFor(() => {
736
- expect(
737
- chat.messages.at(1)?.parts.filter(isStaticToolUIPart),
738
- ).toStrictEqual([
739
- {
740
- state: 'input-available',
741
- rawInput: undefined,
742
- errorText: undefined,
743
- toolCallId: 'tool-call-0',
744
- type: 'tool-test-tool',
745
- input: { testArg: 'test-value' },
746
- output: undefined,
747
- preliminary: undefined,
748
- providerExecuted: undefined,
749
- title: undefined,
750
- },
751
- ]);
752
- });
753
-
754
- chat.addToolOutput({
755
- tool: 'test-tool',
756
- toolCallId: 'tool-call-0',
757
- output: 'test-result',
758
- });
759
-
760
- await vi.waitFor(() => {
761
- expect(
762
- chat.messages.at(1)?.parts.filter(isStaticToolUIPart),
763
- ).toStrictEqual([
764
- {
765
- state: 'output-available',
766
- errorText: undefined,
767
- rawInput: undefined,
768
- toolCallId: 'tool-call-0',
769
- type: 'tool-test-tool',
770
- input: { testArg: 'test-value' },
771
- output: 'test-result',
772
- preliminary: undefined,
773
- providerExecuted: undefined,
774
- title: undefined,
775
- },
776
- ]);
777
- });
778
- });
779
- });
780
-
781
- describe('file attachments with data url', () => {
782
- let chat: Chat;
783
-
784
- beforeEach(() => {
785
- chat = new Chat({
786
- generateId: mockId(),
787
- });
788
- });
789
-
790
- it('should handle text file attachment and submission', async () => {
791
- server.urls['/api/chat'].response = {
792
- type: 'stream-chunks',
793
- chunks: [
794
- formatStreamPart({
795
- type: 'text-start',
796
- id: '0',
797
- }),
798
- formatStreamPart({
799
- type: 'text-delta',
800
- id: '0',
801
- delta: 'Response to message with text attachment',
802
- }),
803
- formatStreamPart({
804
- type: 'text-end',
805
- id: '0',
806
- }),
807
- ],
808
- };
809
-
810
- await chat.sendMessage({
811
- text: 'Message with text attachment',
812
- files: createFileList(
813
- new File(['test file content'], 'test.txt', {
814
- type: 'text/plain',
815
- }),
816
- ),
817
- });
818
-
819
- expect(chat.messages).toMatchInlineSnapshot(`
820
- [
821
- {
822
- "id": "id-1",
823
- "metadata": undefined,
824
- "parts": [
825
- {
826
- "filename": "test.txt",
827
- "mediaType": "text/plain",
828
- "type": "file",
829
- "url": "data:text/plain;base64,dGVzdCBmaWxlIGNvbnRlbnQ=",
830
- },
831
- {
832
- "text": "Message with text attachment",
833
- "type": "text",
834
- },
835
- ],
836
- "role": "user",
837
- },
838
- {
839
- "id": "id-2",
840
- "metadata": undefined,
841
- "parts": [
842
- {
843
- "providerMetadata": undefined,
844
- "state": "done",
845
- "text": "Response to message with text attachment",
846
- "type": "text",
847
- },
848
- ],
849
- "role": "assistant",
850
- },
851
- ]
852
- `);
853
-
854
- expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
855
- {
856
- "id": "id-0",
857
- "messages": [
858
- {
859
- "id": "id-1",
860
- "parts": [
861
- {
862
- "filename": "test.txt",
863
- "mediaType": "text/plain",
864
- "type": "file",
865
- "url": "data:text/plain;base64,dGVzdCBmaWxlIGNvbnRlbnQ=",
866
- },
867
- {
868
- "text": "Message with text attachment",
869
- "type": "text",
870
- },
871
- ],
872
- "role": "user",
873
- },
874
- ],
875
- "trigger": "submit-message",
876
- }
877
- `);
878
- });
879
-
880
- it('should handle image file attachment and submission', async () => {
881
- server.urls['/api/chat'].response = {
882
- type: 'stream-chunks',
883
- chunks: [
884
- formatStreamPart({
885
- type: 'text-start',
886
- id: '0',
887
- }),
888
- formatStreamPart({
889
- type: 'text-delta',
890
- id: '0',
891
- delta: 'Response to message with image attachment',
892
- }),
893
- formatStreamPart({
894
- type: 'text-end',
895
- id: '0',
896
- }),
897
- ],
898
- };
899
-
900
- await chat.sendMessage({
901
- text: 'Message with image attachment',
902
- files: createFileList(
903
- new File(['test image content'], 'test.png', {
904
- type: 'image/png',
905
- }),
906
- ),
907
- });
908
-
909
- expect(chat.messages).toMatchInlineSnapshot(`
910
- [
911
- {
912
- "id": "id-1",
913
- "metadata": undefined,
914
- "parts": [
915
- {
916
- "filename": "test.png",
917
- "mediaType": "image/png",
918
- "type": "file",
919
- "url": "data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50",
920
- },
921
- {
922
- "text": "Message with image attachment",
923
- "type": "text",
924
- },
925
- ],
926
- "role": "user",
927
- },
928
- {
929
- "id": "id-2",
930
- "metadata": undefined,
931
- "parts": [
932
- {
933
- "providerMetadata": undefined,
934
- "state": "done",
935
- "text": "Response to message with image attachment",
936
- "type": "text",
937
- },
938
- ],
939
- "role": "assistant",
940
- },
941
- ]
942
- `);
943
-
944
- expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
945
- {
946
- "id": "id-0",
947
- "messages": [
948
- {
949
- "id": "id-1",
950
- "parts": [
951
- {
952
- "filename": "test.png",
953
- "mediaType": "image/png",
954
- "type": "file",
955
- "url": "data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50",
956
- },
957
- {
958
- "text": "Message with image attachment",
959
- "type": "text",
960
- },
961
- ],
962
- "role": "user",
963
- },
964
- ],
965
- "trigger": "submit-message",
966
- }
967
- `);
968
- });
969
- });
970
-
971
- describe('file attachments with url', () => {
972
- let chat: Chat;
973
-
974
- beforeEach(() => {
975
- chat = new Chat({
976
- generateId: mockId(),
977
- });
978
- });
979
-
980
- it('should handle image file attachment and submission', async () => {
981
- server.urls['/api/chat'].response = {
982
- type: 'stream-chunks',
983
- chunks: [
984
- formatStreamPart({
985
- type: 'text-start',
986
- id: '0',
987
- }),
988
- formatStreamPart({
989
- type: 'text-delta',
990
- id: '0',
991
- delta: 'Response to message with image attachment',
992
- }),
993
- formatStreamPart({
994
- type: 'text-end',
995
- id: '0',
996
- }),
997
- ],
998
- };
999
-
1000
- await chat.sendMessage({
1001
- text: 'Message with image attachment',
1002
- files: createFileList(
1003
- new File(['test image content'], 'test.png', {
1004
- type: 'image/png',
1005
- }),
1006
- ),
1007
- });
1008
-
1009
- expect(chat.messages).toMatchInlineSnapshot(`
1010
- [
1011
- {
1012
- "id": "id-1",
1013
- "metadata": undefined,
1014
- "parts": [
1015
- {
1016
- "filename": "test.png",
1017
- "mediaType": "image/png",
1018
- "type": "file",
1019
- "url": "data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50",
1020
- },
1021
- {
1022
- "text": "Message with image attachment",
1023
- "type": "text",
1024
- },
1025
- ],
1026
- "role": "user",
1027
- },
1028
- {
1029
- "id": "id-2",
1030
- "metadata": undefined,
1031
- "parts": [
1032
- {
1033
- "providerMetadata": undefined,
1034
- "state": "done",
1035
- "text": "Response to message with image attachment",
1036
- "type": "text",
1037
- },
1038
- ],
1039
- "role": "assistant",
1040
- },
1041
- ]
1042
- `);
1043
-
1044
- expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
1045
- {
1046
- "id": "id-0",
1047
- "messages": [
1048
- {
1049
- "id": "id-1",
1050
- "parts": [
1051
- {
1052
- "filename": "test.png",
1053
- "mediaType": "image/png",
1054
- "type": "file",
1055
- "url": "data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50",
1056
- },
1057
- {
1058
- "text": "Message with image attachment",
1059
- "type": "text",
1060
- },
1061
- ],
1062
- "role": "user",
1063
- },
1064
- ],
1065
- "trigger": "submit-message",
1066
- }
1067
- `);
1068
- });
1069
- });
1070
-
1071
- describe('file attachments with empty text content', () => {
1072
- let chat: Chat;
1073
-
1074
- beforeEach(() => {
1075
- chat = new Chat({
1076
- generateId: mockId(),
1077
- });
1078
- });
1079
-
1080
- it('should handle image file attachment and submission', async () => {
1081
- server.urls['/api/chat'].response = {
1082
- type: 'stream-chunks',
1083
- chunks: [
1084
- formatStreamPart({
1085
- type: 'text-start',
1086
- id: '0',
1087
- }),
1088
- formatStreamPart({
1089
- type: 'text-delta',
1090
- id: '0',
1091
- delta: 'Response to message with image attachment',
1092
- }),
1093
- formatStreamPart({
1094
- type: 'text-end',
1095
- id: '0',
1096
- }),
1097
- ],
1098
- };
1099
-
1100
- await chat.sendMessage({
1101
- files: createFileList(
1102
- new File(['test image content'], 'test.png', {
1103
- type: 'image/png',
1104
- }),
1105
- ),
1106
- });
1107
-
1108
- expect(chat.messages).toMatchInlineSnapshot(`
1109
- [
1110
- {
1111
- "id": "id-1",
1112
- "metadata": undefined,
1113
- "parts": [
1114
- {
1115
- "filename": "test.png",
1116
- "mediaType": "image/png",
1117
- "type": "file",
1118
- "url": "data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50",
1119
- },
1120
- ],
1121
- "role": "user",
1122
- },
1123
- {
1124
- "id": "id-2",
1125
- "metadata": undefined,
1126
- "parts": [
1127
- {
1128
- "providerMetadata": undefined,
1129
- "state": "done",
1130
- "text": "Response to message with image attachment",
1131
- "type": "text",
1132
- },
1133
- ],
1134
- "role": "assistant",
1135
- },
1136
- ]
1137
- `);
1138
-
1139
- expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
1140
- {
1141
- "id": "id-0",
1142
- "messages": [
1143
- {
1144
- "id": "id-1",
1145
- "parts": [
1146
- {
1147
- "filename": "test.png",
1148
- "mediaType": "image/png",
1149
- "type": "file",
1150
- "url": "data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50",
1151
- },
1152
- ],
1153
- "role": "user",
1154
- },
1155
- ],
1156
- "trigger": "submit-message",
1157
- }
1158
- `);
1159
- });
1160
- });
1161
-
1162
- describe('reload', () => {
1163
- let chat: Chat;
1164
-
1165
- beforeEach(() => {
1166
- chat = new Chat({
1167
- generateId: mockId(),
1168
- });
1169
- });
1170
-
1171
- it('should show streamed response', async () => {
1172
- server.urls['/api/chat'].response = [
1173
- {
1174
- type: 'stream-chunks',
1175
- chunks: [
1176
- formatStreamPart({ type: 'text-start', id: '0' }),
1177
- formatStreamPart({
1178
- type: 'text-delta',
1179
- id: '0',
1180
- delta: 'first response',
1181
- }),
1182
- formatStreamPart({ type: 'text-end', id: '0' }),
1183
- ],
1184
- },
1185
- {
1186
- type: 'stream-chunks',
1187
- chunks: [
1188
- formatStreamPart({ type: 'text-start', id: '0' }),
1189
- formatStreamPart({
1190
- type: 'text-delta',
1191
- id: '0',
1192
- delta: 'second response',
1193
- }),
1194
- formatStreamPart({ type: 'text-end', id: '0' }),
1195
- ],
1196
- },
1197
- ];
1198
-
1199
- await chat.sendMessage({
1200
- role: 'user',
1201
- parts: [{ text: 'hi', type: 'text' }],
1202
- });
1203
-
1204
- expect(chat.messages.at(0)).toStrictEqual(
1205
- expect.objectContaining({
1206
- role: 'user',
1207
- }),
1208
- );
1209
-
1210
- expect(chat.messages.at(1)).toStrictEqual(
1211
- expect.objectContaining({
1212
- role: 'assistant',
1213
- parts: [{ text: 'first response', type: 'text', state: 'done' }],
1214
- }),
1215
- );
1216
-
1217
- // Setup done, call regenerate:
1218
- await chat.regenerate({
1219
- body: { 'request-body-key': 'request-body-value' },
1220
- headers: { 'header-key': 'header-value' },
1221
- });
1222
-
1223
- expect(await server.calls[1].requestBodyJson).toMatchInlineSnapshot(`
1224
- {
1225
- "id": "id-0",
1226
- "messages": [
1227
- {
1228
- "id": "id-1",
1229
- "parts": [
1230
- {
1231
- "text": "hi",
1232
- "type": "text",
1233
- },
1234
- ],
1235
- "role": "user",
1236
- },
1237
- ],
1238
- "request-body-key": "request-body-value",
1239
- "trigger": "regenerate-message",
1240
- }
1241
- `);
1242
-
1243
- expect(server.calls[1].requestHeaders).toStrictEqual({
1244
- 'content-type': 'application/json',
1245
- 'header-key': 'header-value',
1246
- });
1247
-
1248
- expect(chat.messages.at(1)).toStrictEqual(
1249
- expect.objectContaining({
1250
- role: 'assistant',
1251
- parts: [{ text: 'second response', type: 'text', state: 'done' }],
1252
- }),
1253
- );
1254
- });
1255
- });
1256
-
1257
- describe('test sending additional fields during message submission', () => {
1258
- let chat: Chat;
1259
-
1260
- beforeEach(() => {
1261
- chat = new Chat({
1262
- generateId: mockId(),
1263
- });
1264
- });
1265
-
1266
- it('should send metadata with the message', async () => {
1267
- server.urls['/api/chat'].response = {
1268
- type: 'stream-chunks',
1269
- chunks: ['0:"first response"\n'],
1270
- };
1271
-
1272
- await chat.sendMessage({
1273
- role: 'user',
1274
- metadata: { test: 'example' },
1275
- parts: [{ text: 'hi', type: 'text' }],
1276
- });
1277
-
1278
- expect(chat.messages.at(0)).toStrictEqual(
1279
- expect.objectContaining({
1280
- role: 'user',
1281
- }),
1282
- );
1283
-
1284
- expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
1285
- {
1286
- "id": "id-0",
1287
- "messages": [
1288
- {
1289
- "id": "id-1",
1290
- "metadata": {
1291
- "test": "example",
1292
- },
1293
- "parts": [
1294
- {
1295
- "text": "hi",
1296
- "type": "text",
1297
- },
1298
- ],
1299
- "role": "user",
1300
- },
1301
- ],
1302
- "trigger": "submit-message",
1303
- }
1304
- `);
1305
- });
1306
- });
1307
-
1308
- describe('generateId function', () => {
1309
- it('should use the provided generateId function for both user and assistant messages', async () => {
1310
- server.urls['/api/chat'].response = {
1311
- type: 'stream-chunks',
1312
- chunks: [
1313
- formatStreamPart({ type: 'start', messageId: '123' }),
1314
- formatStreamPart({ type: 'text-start', id: '0' }),
1315
- formatStreamPart({ type: 'text-delta', id: '0', delta: 'Hello' }),
1316
- formatStreamPart({ type: 'text-delta', id: '0', delta: ',' }),
1317
- formatStreamPart({ type: 'text-delta', id: '0', delta: ' world' }),
1318
- formatStreamPart({ type: 'text-delta', id: '0', delta: '.' }),
1319
- formatStreamPart({ type: 'text-end', id: '0' }),
1320
- ],
1321
- };
1322
-
1323
- const chatWithCustomId = new Chat({
1324
- generateId: mockId({ prefix: 'testid' }),
1325
- });
1326
-
1327
- await chatWithCustomId.sendMessage({
1328
- role: 'user',
1329
- parts: [{ text: 'hi', type: 'text' }],
1330
- });
1331
-
1332
- expect(chatWithCustomId.messages).toMatchInlineSnapshot(`
1333
- [
1334
- {
1335
- "id": "testid-1",
1336
- "metadata": undefined,
1337
- "parts": [
1338
- {
1339
- "text": "hi",
1340
- "type": "text",
1341
- },
1342
- ],
1343
- "role": "user",
1344
- },
1345
- {
1346
- "id": "123",
1347
- "metadata": undefined,
1348
- "parts": [
1349
- {
1350
- "providerMetadata": undefined,
1351
- "state": "done",
1352
- "text": "Hello, world.",
1353
- "type": "text",
1354
- },
1355
- ],
1356
- "role": "assistant",
1357
- },
1358
- ]
1359
- `);
1360
- });
1361
- });