@aleph-alpha/chat-kit 1.3.1 → 1.4.0

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.
Files changed (56) hide show
  1. package/dist/{CkInput.vue_vue_type_script_setup_true_lang-CyGLItQZ.js → CkInput.vue_vue_type_script_setup_true_lang-C5C09u0z.js} +9 -8
  2. package/dist/adapters/index.d.ts +18 -0
  3. package/dist/adapters/index.d.ts.map +1 -0
  4. package/dist/adapters/index.js +4 -0
  5. package/dist/adapters/responses-api.d.ts +180 -0
  6. package/dist/adapters/responses-api.d.ts.map +1 -0
  7. package/dist/adapters/responses-api.js +153 -0
  8. package/dist/components/base/CkPromptActionButton/CkPromptActionButton.vue.d.ts +1 -1
  9. package/dist/components/composed/CkConversation/CkConversation.stories.d.ts +11 -3
  10. package/dist/components/composed/CkConversation/CkConversation.stories.d.ts.map +1 -1
  11. package/dist/components/composed/CkConversation/CkConversation.vue.d.ts +5 -1
  12. package/dist/components/composed/CkConversation/CkConversation.vue.d.ts.map +1 -1
  13. package/dist/components/index.js +1 -1
  14. package/dist/composables/index.js +1 -1
  15. package/dist/composables/useConversations.d.ts +12 -0
  16. package/dist/composables/useConversations.d.ts.map +1 -1
  17. package/dist/generateId-BHf0W4Rq.js +6 -0
  18. package/dist/handlers/index.js +1 -1
  19. package/dist/handlers/useMessageHandler.d.ts +2 -2
  20. package/dist/handlers/useMessageHandler.d.ts.map +1 -1
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +6 -8
  24. package/dist/services/index.d.ts +0 -2
  25. package/dist/services/index.d.ts.map +1 -1
  26. package/dist/services/index.js +1 -37
  27. package/dist/services/types.d.ts +2 -2
  28. package/dist/services/types.d.ts.map +1 -1
  29. package/dist/types.d.ts +47 -2
  30. package/dist/types.d.ts.map +1 -1
  31. package/dist/{useConversationSessions-ulFhtpQr.js → useConversationSessions-GLZV_Ctr.js} +18 -2
  32. package/dist/{useMessageHandler-BmBdu2EW.js → useMessageHandler-ZRC-gIOJ.js} +5 -5
  33. package/package.json +10 -2
  34. package/src/adapters/index.ts +17 -0
  35. package/src/adapters/responses-api.spec.ts +365 -0
  36. package/src/adapters/responses-api.ts +444 -0
  37. package/src/components/composed/CkConversation/CkConversation.spec.ts +171 -25
  38. package/src/components/composed/CkConversation/CkConversation.stories.ts +266 -46
  39. package/src/components/composed/CkConversation/CkConversation.vue +19 -7
  40. package/src/composables/useConversations.spec.ts +118 -0
  41. package/src/composables/useConversations.ts +32 -0
  42. package/src/handlers/useChatKitLabels.spec.ts +16 -2
  43. package/src/handlers/useMessageHandler.spec.ts +112 -44
  44. package/src/handlers/useMessageHandler.ts +16 -2
  45. package/src/helpers/conversationTree.spec.ts +46 -19
  46. package/src/index.ts +11 -2
  47. package/src/services/index.ts +0 -2
  48. package/src/services/types.ts +2 -2
  49. package/src/types.ts +55 -2
  50. package/dist/ChatService-LogwJNxh.js +0 -9
  51. package/dist/services/ChatService.d.ts +0 -42
  52. package/dist/services/ChatService.d.ts.map +0 -1
  53. package/dist/services/MockChatService.d.ts +0 -10
  54. package/dist/services/MockChatService.d.ts.map +0 -1
  55. package/src/services/ChatService.ts +0 -60
  56. package/src/services/MockChatService.ts +0 -47
@@ -1,4 +1,4 @@
1
- import type { BaseMessage } from '../../../types';
1
+ import type { BaseMessage, MessageContent } from '../../../types';
2
2
  import CkConversation from './CkConversation.vue';
3
3
  import { defaultMarkdownClasses } from '../../base/CkMarkdownRenderer/defaultMarkdownClasses';
4
4
  import { render, screen } from '@testing-library/vue';
@@ -6,45 +6,62 @@ import { h } from 'vue';
6
6
 
7
7
  const now = new Date();
8
8
 
9
+ let textIdCounter = 0;
10
+ function text(value: string): MessageContent {
11
+ textIdCounter += 1;
12
+ return {
13
+ type: 'text',
14
+ id: `txt-${textIdCounter}`,
15
+ text: value,
16
+ status: 'completed',
17
+ };
18
+ }
19
+
9
20
  const messages: BaseMessage[] = [
10
21
  {
11
22
  id: '1',
12
23
  role: 'user',
13
- content: 'Hello there',
24
+ content: [text('Hello there')],
14
25
  createdAt: now,
15
26
  status: 'completed',
16
27
  },
17
28
  {
18
29
  id: '2',
19
30
  role: 'assistant',
20
- content: 'Hi! How can I help?',
31
+ content: [text('Hi! How can I help?')],
21
32
  createdAt: now,
22
33
  status: 'completed',
23
34
  },
24
35
  {
25
36
  id: '3',
26
37
  role: 'user',
27
- content: 'Tell me a joke',
38
+ content: [text('Tell me a joke')],
28
39
  createdAt: now,
29
40
  status: 'completed',
30
41
  },
31
42
  {
32
43
  id: '4',
33
44
  role: 'assistant',
34
- content: 'Why did the chicken cross the road?',
45
+ content: [text('Why did the chicken cross the road?')],
35
46
  createdAt: now,
36
47
  status: 'completed',
37
48
  },
38
49
  ];
39
50
 
40
- function agentMessage(overrides: Partial<BaseMessage> = {}): BaseMessage {
51
+ function agentMessage(
52
+ overrides: Partial<Omit<BaseMessage, 'content'>> & {
53
+ content?: MessageContent[];
54
+ text?: string;
55
+ } = {},
56
+ ): BaseMessage {
57
+ const { content, text: textOverride, ...rest } = overrides;
41
58
  return {
42
59
  id: '1',
43
60
  role: 'assistant',
44
- content: 'Hello',
45
61
  createdAt: now,
46
62
  status: 'completed',
47
- ...overrides,
63
+ ...rest,
64
+ content: content ?? [text(textOverride ?? 'Hello')],
48
65
  };
49
66
  }
50
67
 
@@ -65,6 +82,44 @@ describe('CkConversation', () => {
65
82
  screen.getByText('Why did the chicken cross the road?'),
66
83
  ).toBeInTheDocument();
67
84
  });
85
+
86
+ it('joins multiple text content parts in agent messages', () => {
87
+ const message: BaseMessage = {
88
+ id: 'a-multi',
89
+ role: 'assistant',
90
+ content: [text('Part one. '), text('Part two.')],
91
+ createdAt: now,
92
+ status: 'completed',
93
+ };
94
+
95
+ const { container } = render(CkConversation, {
96
+ props: { messages: [message] },
97
+ });
98
+
99
+ const renderer = container.querySelector('.markdown-renderer');
100
+ expect(renderer).not.toBeNull();
101
+ expect(renderer?.textContent).toBe('Part one. Part two.');
102
+ });
103
+
104
+ it('ignores non-text content parts in default rendering', () => {
105
+ const message: BaseMessage = {
106
+ id: 'a-skip',
107
+ role: 'assistant',
108
+ content: [
109
+ { type: 'reasoning', id: 'r-1', status: 'completed' },
110
+ { type: 'tool', id: 't-1', name: 'search' },
111
+ text('Final answer.'),
112
+ ],
113
+ createdAt: now,
114
+ status: 'completed',
115
+ };
116
+
117
+ render(CkConversation, { props: { messages: [message] } });
118
+
119
+ expect(screen.getByText('Final answer.')).toBeInTheDocument();
120
+ // Tool name and reasoning lifecycle are not rendered by default
121
+ expect(screen.queryByText('search')).not.toBeInTheDocument();
122
+ });
68
123
  });
69
124
 
70
125
  describe('agent icon', () => {
@@ -81,12 +136,10 @@ describe('CkConversation', () => {
81
136
  props: { messages: [agentMessage()] },
82
137
  });
83
138
 
84
- // The icon container inside CkAgentMessage should not be rendered
85
139
  const agentMessageEl = container.querySelector(
86
140
  '.flex.w-full.items-start.gap-2',
87
141
  );
88
142
  expect(agentMessageEl).toBeInTheDocument();
89
- // The flex-shrink-0 icon wrapper should not exist
90
143
  expect(
91
144
  agentMessageEl?.querySelector(
92
145
  '.flex.flex-shrink-0.items-center.justify-center',
@@ -216,6 +269,51 @@ describe('CkConversation', () => {
216
269
  });
217
270
  });
218
271
 
272
+ describe('copy action', () => {
273
+ it('exposes the joined text via the copy action button', () => {
274
+ const message: BaseMessage = {
275
+ id: 'mix2',
276
+ role: 'assistant',
277
+ content: [
278
+ { type: 'reasoning', id: 'r', status: 'completed' },
279
+ text('Hello '),
280
+ { type: 'tool', id: 'tl', name: 'search' },
281
+ text('world.'),
282
+ ],
283
+ createdAt: now,
284
+ status: 'completed',
285
+ };
286
+ const { container } = render(CkConversation, {
287
+ props: { messages: [message] },
288
+ });
289
+
290
+ const copyButton = container.querySelector(
291
+ 'button[aria-label="Copy response"]',
292
+ );
293
+ expect(copyButton).not.toBeNull();
294
+ });
295
+
296
+ it('hides the copy button when there is no text content', () => {
297
+ const message: BaseMessage = {
298
+ id: 'mix3',
299
+ role: 'assistant',
300
+ content: [
301
+ { type: 'reasoning', id: 'r', status: 'completed' },
302
+ { type: 'tool', id: 'tl', name: 'search' },
303
+ ],
304
+ createdAt: now,
305
+ status: 'completed',
306
+ };
307
+ const { container } = render(CkConversation, {
308
+ props: { messages: [message] },
309
+ });
310
+
311
+ expect(
312
+ container.querySelector('button[aria-label="Copy response"]'),
313
+ ).toBeNull();
314
+ });
315
+ });
316
+
219
317
  describe('slot overrides', () => {
220
318
  it('allows replacing user message content via user-message-content slot', () => {
221
319
  render(CkConversation, {
@@ -224,7 +322,7 @@ describe('CkConversation', () => {
224
322
  {
225
323
  id: '1',
226
324
  role: 'user',
227
- content: 'Original',
325
+ content: [text('Original')],
228
326
  createdAt: now,
229
327
  status: 'completed',
230
328
  } as BaseMessage,
@@ -244,7 +342,7 @@ describe('CkConversation', () => {
244
342
  {
245
343
  id: '1',
246
344
  role: 'user',
247
- content: 'Original',
345
+ content: [text('Original')],
248
346
  createdAt: now,
249
347
  status: 'completed',
250
348
  } as BaseMessage,
@@ -260,7 +358,7 @@ describe('CkConversation', () => {
260
358
 
261
359
  it('allows replacing agent content via agent-message-content slot', () => {
262
360
  render(CkConversation, {
263
- props: { messages: [agentMessage({ content: 'Original' })] },
361
+ props: { messages: [agentMessage({ text: 'Original' })] },
264
362
  slots: { 'agent-message-content': 'Rich agent content' },
265
363
  });
266
364
 
@@ -270,7 +368,7 @@ describe('CkConversation', () => {
270
368
 
271
369
  it('allows replacing the entire agent message via agent-message slot', () => {
272
370
  render(CkConversation, {
273
- props: { messages: [agentMessage({ content: 'Original' })] },
371
+ props: { messages: [agentMessage({ text: 'Original' })] },
274
372
  slots: {
275
373
  'agent-message':
276
374
  '<div data-testid="custom-agent">Fully replaced</div>',
@@ -306,22 +404,71 @@ describe('CkConversation', () => {
306
404
 
307
405
  expect(screen.getByText('Copy')).toBeInTheDocument();
308
406
  });
407
+
408
+ it('exposes the full content array to agent-message-content for custom rendering', () => {
409
+ const message: BaseMessage = {
410
+ id: 'cot',
411
+ role: 'assistant',
412
+ content: [
413
+ { type: 'reasoning', id: 'r', status: 'completed' },
414
+ { type: 'tool', id: 'tl', name: 'web_search' },
415
+ text('Done.'),
416
+ ],
417
+ createdAt: now,
418
+ status: 'completed',
419
+ };
420
+ function describePart(part: MessageContent): string {
421
+ if (part.type === 'tool') return part.name;
422
+ if (part.type === 'text') return part.text;
423
+ return part.status;
424
+ }
425
+
426
+ const { container } = render(CkConversation, {
427
+ props: { messages: [message] },
428
+ slots: {
429
+ 'agent-message-content': ({ message: m }: { message: BaseMessage }) =>
430
+ h(
431
+ 'ul',
432
+ { 'data-testid': 'parts' },
433
+ m.content.map((part) =>
434
+ h(
435
+ 'li',
436
+ { 'data-type': part.type, key: part.id },
437
+ describePart(part),
438
+ ),
439
+ ),
440
+ ),
441
+ },
442
+ });
443
+
444
+ const list = container.querySelector('[data-testid="parts"]');
445
+ expect(list).not.toBeNull();
446
+ expect(list?.querySelectorAll('li')).toHaveLength(3);
447
+ expect(list?.querySelector('[data-type="reasoning"]')?.textContent).toBe(
448
+ 'completed',
449
+ );
450
+ expect(list?.querySelector('[data-type="tool"]')?.textContent).toBe(
451
+ 'web_search',
452
+ );
453
+ expect(list?.querySelector('[data-type="text"]')?.textContent).toBe(
454
+ 'Done.',
455
+ );
456
+ });
309
457
  });
310
458
 
311
459
  describe('empty conversation', () => {
312
460
  it('renders nothing when messages array is empty', () => {
313
461
  const { container } = render(CkConversation, { props: { messages: [] } });
314
462
 
315
- // Only the scroll container should exist, with no message content
316
463
  expect(container.querySelectorAll('[data-message-row]')).toHaveLength(0);
317
464
  });
318
465
  });
319
466
 
320
467
  describe('markdown rendering', () => {
321
- it('renders agent message content as markdown by default', () => {
468
+ it('renders agent text content as markdown by default', () => {
322
469
  const { container } = render(CkConversation, {
323
470
  props: {
324
- messages: [agentMessage({ content: 'This has **bold** text' })],
471
+ messages: [agentMessage({ text: 'This has **bold** text' })],
325
472
  },
326
473
  });
327
474
 
@@ -332,7 +479,7 @@ describe('CkConversation', () => {
332
479
  const { container } = render(CkConversation, {
333
480
  props: {
334
481
  messages: [
335
- agentMessage({ content: '## Title\n\n- item one\n- item two' }),
482
+ agentMessage({ text: '## Title\n\n- item one\n- item two' }),
336
483
  ],
337
484
  },
338
485
  });
@@ -344,7 +491,7 @@ describe('CkConversation', () => {
344
491
  it('applies default markdown classes to elements', () => {
345
492
  const { container } = render(CkConversation, {
346
493
  props: {
347
- messages: [agentMessage({ content: 'A paragraph' })],
494
+ messages: [agentMessage({ text: 'A paragraph' })],
348
495
  },
349
496
  });
350
497
 
@@ -356,7 +503,7 @@ describe('CkConversation', () => {
356
503
  it('merges agentMarkdownClasses prop with defaults', () => {
357
504
  const { container } = render(CkConversation, {
358
505
  props: {
359
- messages: [agentMessage({ content: 'A paragraph' })],
506
+ messages: [agentMessage({ text: 'A paragraph' })],
360
507
  agentMarkdownClasses: { p: 'custom-paragraph' },
361
508
  },
362
509
  });
@@ -371,8 +518,7 @@ describe('CkConversation', () => {
371
518
  props: {
372
519
  messages: [
373
520
  agentMessage({
374
- content:
375
- '## Heading\n\nParagraph with [link](https://example.com)',
521
+ text: '## Heading\n\nParagraph with [link](https://example.com)',
376
522
  }),
377
523
  ],
378
524
  agentMarkdownClasses: { p: 'custom-paragraph' },
@@ -388,7 +534,7 @@ describe('CkConversation', () => {
388
534
 
389
535
  it('does not render CkMarkdownRenderer when agent-message-content slot is provided', () => {
390
536
  const { container } = render(CkConversation, {
391
- props: { messages: [agentMessage({ content: '**bold**' })] },
537
+ props: { messages: [agentMessage({ text: '**bold**' })] },
392
538
  slots: {
393
539
  'agent-message-content':
394
540
  '<div data-testid="custom-content">Custom</div>',
@@ -403,7 +549,7 @@ describe('CkConversation', () => {
403
549
  const markdown = '```python\nprint("hello")\n```';
404
550
 
405
551
  const { container } = render(CkConversation, {
406
- props: { messages: [agentMessage({ content: markdown })] },
552
+ props: { messages: [agentMessage({ text: markdown })] },
407
553
  slots: {
408
554
  'agent-message-code-block': (props: {
409
555
  code: string;
@@ -428,7 +574,7 @@ describe('CkConversation', () => {
428
574
  const markdown = '```javascript\nconsole.log("hi");\n```';
429
575
 
430
576
  const { container } = render(CkConversation, {
431
- props: { messages: [agentMessage({ content: markdown })] },
577
+ props: { messages: [agentMessage({ text: markdown })] },
432
578
  });
433
579
 
434
580
  const codeEl = container.querySelector('pre > code');