@aleph-alpha/chat-kit 1.3.1 → 1.5.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 (73) 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/fileUpload/index.d.ts +7 -0
  15. package/dist/composables/fileUpload/index.d.ts.map +1 -0
  16. package/dist/composables/fileUpload/types.d.ts +295 -0
  17. package/dist/composables/fileUpload/types.d.ts.map +1 -0
  18. package/dist/composables/fileUpload/useFileUpload.d.ts +4 -0
  19. package/dist/composables/fileUpload/useFileUpload.d.ts.map +1 -0
  20. package/dist/composables/fileUpload/useFileUploadWrapper.d.ts +3 -0
  21. package/dist/composables/fileUpload/useFileUploadWrapper.d.ts.map +1 -0
  22. package/dist/composables/index.d.ts +2 -1
  23. package/dist/composables/index.d.ts.map +1 -1
  24. package/dist/composables/index.js +5 -2
  25. package/dist/composables/useConversations.d.ts +12 -0
  26. package/dist/composables/useConversations.d.ts.map +1 -1
  27. package/dist/generateId-BHf0W4Rq.js +6 -0
  28. package/dist/handlers/index.js +1 -1
  29. package/dist/handlers/useMessageHandler.d.ts +2 -2
  30. package/dist/handlers/useMessageHandler.d.ts.map +1 -1
  31. package/dist/index-BbTgIoYa.js +1557 -0
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +6 -8
  35. package/dist/services/index.d.ts +0 -2
  36. package/dist/services/index.d.ts.map +1 -1
  37. package/dist/services/index.js +1 -37
  38. package/dist/services/types.d.ts +2 -2
  39. package/dist/services/types.d.ts.map +1 -1
  40. package/dist/types.d.ts +47 -2
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/{useMessageHandler-BmBdu2EW.js → useMessageHandler-ZRC-gIOJ.js} +5 -5
  43. package/package.json +10 -2
  44. package/src/adapters/index.ts +17 -0
  45. package/src/adapters/responses-api.spec.ts +365 -0
  46. package/src/adapters/responses-api.ts +444 -0
  47. package/src/components/composed/CkConversation/CkConversation.spec.ts +171 -25
  48. package/src/components/composed/CkConversation/CkConversation.stories.ts +266 -46
  49. package/src/components/composed/CkConversation/CkConversation.vue +19 -7
  50. package/src/composables/fileUpload/index.spec.ts +213 -0
  51. package/src/composables/fileUpload/index.ts +83 -0
  52. package/src/composables/fileUpload/types.ts +409 -0
  53. package/src/composables/fileUpload/useFileUpload.ts +1242 -0
  54. package/src/composables/fileUpload/useFileUploadWrapper.ts +382 -0
  55. package/src/composables/index.ts +2 -1
  56. package/src/composables/useConversations.spec.ts +118 -0
  57. package/src/composables/useConversations.ts +32 -0
  58. package/src/handlers/useChatKitLabels.spec.ts +16 -2
  59. package/src/handlers/useMessageHandler.spec.ts +112 -44
  60. package/src/handlers/useMessageHandler.ts +16 -2
  61. package/src/helpers/conversationTree.spec.ts +46 -19
  62. package/src/index.ts +11 -2
  63. package/src/services/index.ts +0 -2
  64. package/src/services/types.ts +2 -2
  65. package/src/types.ts +55 -2
  66. package/dist/ChatService-LogwJNxh.js +0 -9
  67. package/dist/services/ChatService.d.ts +0 -42
  68. package/dist/services/ChatService.d.ts.map +0 -1
  69. package/dist/services/MockChatService.d.ts +0 -10
  70. package/dist/services/MockChatService.d.ts.map +0 -1
  71. package/dist/useConversationSessions-ulFhtpQr.js +0 -243
  72. package/src/services/ChatService.ts +0 -60
  73. package/src/services/MockChatService.ts +0 -47
@@ -0,0 +1,444 @@
1
+ /**
2
+ * Adapter: OpenAI-style **Responses API** → chat-kit `ConversationTree`.
3
+ *
4
+ * chat-kit's domain model (`TreeMessage`, `MessageContent`) is intentionally
5
+ * format-agnostic. This module is the bridge that turns a stored Responses-
6
+ * API timeline (`GET /conversations/{id}/responses`) into a `ConversationTree`
7
+ * the message handler can render. Apps that use a different transcript shape
8
+ * — or none at all — can build their own adapter or feed the handler via
9
+ * `setTree(tree)` directly.
10
+ *
11
+ * The wire-level types that describe the Responses-API payload (`StoredResponse`,
12
+ * `ResponseOutput*`, `ResponseAttachment`, `ResponseList`, …) live alongside the
13
+ * conversion function so the entire Responses-API surface is encapsulated here.
14
+ *
15
+ * Public surface:
16
+ * - `responsesToTree(responses)` — pure function, the entry point.
17
+ * - All Responses-API wire types (re-exported via the adapter subpath).
18
+ *
19
+ * Available via `@aleph-alpha/chat-kit/adapters/responses-api` and
20
+ * `@aleph-alpha/chat-kit/adapters`.
21
+ */
22
+ import { generateId } from '../helpers';
23
+ import type {
24
+ ConversationTree,
25
+ MessageContent,
26
+ MessageRole,
27
+ TreeMessage,
28
+ } from '../types';
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Wire-level Responses-API types
32
+ // ---------------------------------------------------------------------------
33
+
34
+ /**
35
+ * Annotation attached to a Responses-API content part (citations, file
36
+ * references, etc.). chat-kit does not render annotations itself; the shape
37
+ * is kept open so consumers can downcast to a more specific schema.
38
+ */
39
+ export interface ResponseAnnotation {
40
+ type: string;
41
+ [key: string]: unknown;
42
+ }
43
+
44
+ /**
45
+ * Plain text fragment inside a `ResponseOutputMessage`. Streamed by the
46
+ * Responses API as `output_text` (or the historical `text` alias).
47
+ */
48
+ export interface ResponseOutputTextPart {
49
+ type: 'output_text' | 'text';
50
+ text: string;
51
+ annotations?: ResponseAnnotation[];
52
+ }
53
+
54
+ /**
55
+ * One reasoning fragment inside a `ResponseOutputReasoning`. The Responses
56
+ * API streams chain-of-thought under `reasoning_text`; the body is exposed
57
+ * for consumers that want to render it (chat-kit itself only surfaces a
58
+ * "thinking" indicator).
59
+ */
60
+ export interface ResponseReasoningTextPart {
61
+ type: 'reasoning_text';
62
+ text: string;
63
+ annotations?: ResponseAnnotation[];
64
+ }
65
+
66
+ /**
67
+ * Forward-compatible catch-all for content parts whose `type` chat-kit does
68
+ * not yet model (e.g. refusals, future part kinds). Consumers narrow on
69
+ * `type` themselves when they need richer handling.
70
+ */
71
+ export interface ResponseUnknownContentPart {
72
+ type: string;
73
+ text?: string;
74
+ annotations?: ResponseAnnotation[];
75
+ [key: string]: unknown;
76
+ }
77
+
78
+ /**
79
+ * Discriminated union of content parts that can appear inside a Responses-
80
+ * API output item. `ResponseOutputTextPart` and `ResponseReasoningTextPart`
81
+ * cover the rendered shapes; the catch-all keeps the union open.
82
+ */
83
+ export type OutputContentPart =
84
+ | ResponseOutputTextPart
85
+ | ResponseReasoningTextPart
86
+ | ResponseUnknownContentPart;
87
+
88
+ /**
89
+ * Visible message produced by a Responses-API turn (assistant reply or
90
+ * echoed user input). Carries the renderable text content; `responsesToTree`
91
+ * extracts text from `content` to build the chat tree.
92
+ */
93
+ export interface ResponseOutputMessage {
94
+ type: 'message';
95
+ id: string;
96
+ status: string;
97
+ role: 'assistant' | 'user';
98
+ content: string | OutputContentPart[];
99
+ }
100
+
101
+ /**
102
+ * Chain-of-thought trail produced by the assistant during a turn. Surfaced
103
+ * in chat-kit only as a "thinking" indicator; consumers may render the
104
+ * underlying `reasoning_text` parts directly when desired.
105
+ */
106
+ export interface ResponseOutputReasoning {
107
+ type: 'reasoning';
108
+ id: string;
109
+ status: string;
110
+ role?: 'assistant' | null;
111
+ content: OutputContentPart[];
112
+ }
113
+
114
+ /**
115
+ * Single tool entry returned inside `mcp_list_tools` (and similar tool-
116
+ * discovery outputs). Schema fields are passed through verbatim because
117
+ * chat-kit does not model JSON Schema itself.
118
+ */
119
+ export interface ResponseToolDescriptor {
120
+ name: string;
121
+ description?: string;
122
+ annotations?: Record<string, unknown>;
123
+ input_schema?: unknown;
124
+ }
125
+
126
+ /**
127
+ * Wire-level Responses-API output kinds that represent tool activity rather
128
+ * than visible content. The list enumerates the values chat-kit recognises
129
+ * today; extend it as new tool kinds are introduced upstream.
130
+ */
131
+ export type ResponseOutputToolType =
132
+ | 'mcp_list_tools'
133
+ | 'mcp_call'
134
+ | 'tool_call'
135
+ | 'function_call'
136
+ | 'web_search_call'
137
+ | 'file_search_call'
138
+ | 'code_interpreter_call'
139
+ | 'image_generation_call'
140
+ | 'local_shell_call';
141
+
142
+ /**
143
+ * Tool-related output produced during a Responses-API turn. Covers tool
144
+ * discovery (`mcp_list_tools`), invocation (`mcp_call`, `function_call`,
145
+ * etc.) and the various built-in tool kinds. Common fields are typed; the
146
+ * rest is passed through so consumers can read kind-specific data without
147
+ * redefining the shape.
148
+ */
149
+ export interface ResponseOutputTool {
150
+ type: ResponseOutputToolType;
151
+ id: string;
152
+ status?: string;
153
+ /** `mcp_list_tools` / `mcp_call`: server identifier. */
154
+ server_label?: string;
155
+ /** `mcp_list_tools`: catalog of tools exposed by the server. */
156
+ tools?: ResponseToolDescriptor[];
157
+ /** `function_call` / `mcp_call`: invoked tool name. */
158
+ name?: string;
159
+ /** `function_call` / `mcp_call`: serialized JSON arguments. */
160
+ arguments?: string;
161
+ /** `function_call` / `tool_call`: correlation id for a paired result. */
162
+ call_id?: string;
163
+ /** Tool result payload (shape depends on the tool kind). */
164
+ output?: unknown;
165
+ [key: string]: unknown;
166
+ }
167
+
168
+ /**
169
+ * One output item produced by a Responses-API response, discriminated by
170
+ * `type`. Modelled in the same spirit as `MessageContent`: visible
171
+ * `'message'`, optional `'reasoning'` trail, and `tool` activity.
172
+ */
173
+ export type ResponseOutput =
174
+ | ResponseOutputMessage
175
+ | ResponseOutputReasoning
176
+ | ResponseOutputTool;
177
+
178
+ /**
179
+ * Attachment metadata stored under `metadata.attachments` on a Responses-API
180
+ * response. chat-kit does not render attachments itself; this type exists so
181
+ * consumers can read them off `StoredResponse.metadata` without redeclaring
182
+ * the shape.
183
+ */
184
+ export interface ResponseAttachment {
185
+ fileId: string;
186
+ fileName: string;
187
+ searchStoreId: string;
188
+ }
189
+
190
+ /**
191
+ * One stored response from the OpenAI-style Responses API, as returned by
192
+ * `GET /conversations/:id/responses`. `previous_response_id` chains
193
+ * responses into a tree; `responsesToTree` walks that chain to produce a
194
+ * `ConversationTree`.
195
+ */
196
+ export interface StoredResponse {
197
+ id: string;
198
+ object: 'response';
199
+ created_at: number;
200
+ completed_at: number;
201
+ status: string;
202
+ model: string;
203
+ previous_response_id?: string | null;
204
+ output: ResponseOutput[];
205
+ request_input: Record<string, unknown>[];
206
+ metadata?: Record<string, unknown> & {
207
+ attachments?: ResponseAttachment[];
208
+ };
209
+ conversation?: { id: string };
210
+ ancestor_ids?: string[];
211
+ depth?: number;
212
+ }
213
+
214
+ /**
215
+ * Raw envelope returned by the Responses list endpoint. The endpoint is not
216
+ * paginated, so chat-kit unwraps `data` and exposes `StoredResponse[]` to
217
+ * callers; this type is kept exported for users implementing or mocking the
218
+ * `HttpClient`.
219
+ */
220
+ export interface ResponseList {
221
+ object: 'list';
222
+ data: StoredResponse[];
223
+ }
224
+
225
+ // ---------------------------------------------------------------------------
226
+ // Conversion: StoredResponse[] → ConversationTree
227
+ // ---------------------------------------------------------------------------
228
+
229
+ interface ResponseMessageNode {
230
+ id: string;
231
+ role: MessageRole;
232
+ content: MessageContent[];
233
+ createdAt: Date;
234
+ }
235
+
236
+ function extractMessageText(content: ResponseOutputMessage['content']): string {
237
+ if (typeof content === 'string') return content.trim();
238
+ return extractTextFromParts(content);
239
+ }
240
+
241
+ function extractTextFromParts(parts: OutputContentPart[]): string {
242
+ let text = '';
243
+ for (const part of parts) {
244
+ if (part.type === 'output_text' || part.type === 'text') {
245
+ text += part.text ?? '';
246
+ }
247
+ }
248
+ return text.trim();
249
+ }
250
+
251
+ function extractReasoningText(parts: OutputContentPart[]): string {
252
+ let text = '';
253
+ for (const part of parts) {
254
+ if (part.type === 'reasoning_text') {
255
+ text += part.text ?? '';
256
+ }
257
+ }
258
+ return text.trim();
259
+ }
260
+
261
+ function textPart(text: string, id?: string): MessageContent {
262
+ return { type: 'text', id: id ?? generateId(), text, status: 'completed' };
263
+ }
264
+
265
+ // Build the `content` array for one assistant turn from a Responses-API
266
+ // record. Reasoning trails, tool invocations, and visible text all belong to
267
+ // the same turn and are kept in their original wire order so the UI can
268
+ // render them inline with the streaming flow.
269
+ function toAssistantParts(resp: StoredResponse): MessageContent[] {
270
+ const parts: MessageContent[] = [];
271
+ for (const out of resp.output ?? []) {
272
+ switch (out.type) {
273
+ case 'reasoning': {
274
+ const text = extractReasoningText(out.content);
275
+ parts.push({
276
+ type: 'reasoning',
277
+ id: out.id,
278
+ ...(text ? { text } : {}),
279
+ status: 'completed',
280
+ });
281
+ break;
282
+ }
283
+ case 'message': {
284
+ if (out.role !== 'assistant') break;
285
+ const text = extractMessageText(out.content);
286
+ if (!text) break;
287
+ parts.push(textPart(text, out.id || undefined));
288
+ break;
289
+ }
290
+ // Actual tool invocations surface as tool parts. `mcp_list_tools` is
291
+ // informational discovery (the server advertising tools) rather than
292
+ // a call the assistant made, so it is intentionally skipped.
293
+ case 'mcp_call':
294
+ case 'function_call':
295
+ case 'tool_call': {
296
+ parts.push({
297
+ type: 'tool',
298
+ id: out.id,
299
+ name: out.name ?? out.type,
300
+ });
301
+ break;
302
+ }
303
+ default:
304
+ break;
305
+ }
306
+ }
307
+ return parts;
308
+ }
309
+
310
+ // The id used for the assistant `TreeMessage` of a response. Prefer the id
311
+ // of the first visible message output (matches the wire id seen during
312
+ // streaming); fall back to a deterministic `assistant-${resp.id}` so
313
+ // reasoning- or tool-only turns still get a stable node id.
314
+ function getAssistantNodeId(resp: StoredResponse): string {
315
+ for (const out of resp.output ?? []) {
316
+ if (out.type === 'message' && out.role === 'assistant' && out.id) {
317
+ return out.id;
318
+ }
319
+ }
320
+ return `assistant-${resp.id}`;
321
+ }
322
+
323
+ // Within a single response, the visible messages chain linearly: every user
324
+ // input is followed by exactly one assistant turn (whose reasoning, tool,
325
+ // and text outputs are merged into one node). Across responses, branching
326
+ // is encoded by `previous_response_id` so several responses can share the
327
+ // same parent and form sibling subtrees.
328
+ function extractInputText(rawContent: unknown): string {
329
+ if (typeof rawContent === 'string') return rawContent.trim();
330
+ if (Array.isArray(rawContent)) {
331
+ return extractTextFromParts(rawContent as OutputContentPart[]);
332
+ }
333
+ return '';
334
+ }
335
+
336
+ function responseToMessageNodes(resp: StoredResponse): ResponseMessageNode[] {
337
+ const nodes: ResponseMessageNode[] = [];
338
+ if (resp.request_input) {
339
+ for (const [idx, input] of resp.request_input.entries()) {
340
+ const role = (input as { role?: string }).role;
341
+ const rawContent = (input as { content?: unknown }).content;
342
+ const text = extractInputText(rawContent);
343
+ if (role === 'user' && text) {
344
+ nodes.push({
345
+ id: `user-${resp.id}-${idx}`,
346
+ role: 'user',
347
+ content: [textPart(text)],
348
+ createdAt: new Date(resp.created_at * 1000),
349
+ });
350
+ }
351
+ }
352
+ }
353
+ const assistantParts = toAssistantParts(resp);
354
+ if (assistantParts.length > 0) {
355
+ nodes.push({
356
+ id: getAssistantNodeId(resp),
357
+ role: 'assistant',
358
+ content: assistantParts,
359
+ createdAt: new Date((resp.completed_at ?? resp.created_at) * 1000),
360
+ });
361
+ }
362
+ return nodes;
363
+ }
364
+
365
+ /**
366
+ * Convert an OpenAI Responses API list (already ordered by `created_at`
367
+ * ascending) into a chat-kit `ConversationTree`.
368
+ *
369
+ * The caller guarantees ordering, which lets us build the tree in a single
370
+ * pass: each response's `previous_response_id` always points to a response
371
+ * we have already processed, so we can wire its messages onto the parent's
372
+ * last message immediately. Sibling responses (same `previous_response_id`)
373
+ * surface as branches; the latest one wins as the active branch.
374
+ *
375
+ * Pair with `useMessageHandler().setTree(responsesToTree(responses))` when
376
+ * you want to hydrate a handler.
377
+ */
378
+ export function responsesToTree(responses: StoredResponse[]): ConversationTree {
379
+ if (responses.length === 0) {
380
+ return { nodes: {}, rootId: null };
381
+ }
382
+
383
+ const treeNodes: Record<string, TreeMessage> = {};
384
+ // For each response, the id of its last visible message — that's the
385
+ // attachment point for any future child response that chains onto it.
386
+ // Empty responses fall back to their parent's attachment point so the
387
+ // chain stays connected.
388
+ const tailMessageByResponse = new Map<string, string | null>();
389
+ let rootMessageId: string | null = null;
390
+
391
+ function appendNode(node: ResponseMessageNode, parentId: string | null) {
392
+ treeNodes[node.id] = {
393
+ id: node.id,
394
+ parentId,
395
+ childrenIds: [],
396
+ activeChildIndex: 0,
397
+ role: node.role,
398
+ content: node.content,
399
+ status: 'completed',
400
+ createdAt: node.createdAt,
401
+ };
402
+ if (parentId !== null) {
403
+ const parent = treeNodes[parentId];
404
+ if (parent) {
405
+ parent.childrenIds.push(node.id);
406
+ // Latest insertion wins as the active branch — within a single
407
+ // response there's only one push, so this is a no-op there; for
408
+ // sibling branches it surfaces the most recent one by default.
409
+ parent.activeChildIndex = parent.childrenIds.length - 1;
410
+ }
411
+ }
412
+ }
413
+
414
+ for (const resp of responses) {
415
+ const parentResponseId =
416
+ resp.previous_response_id &&
417
+ tailMessageByResponse.has(resp.previous_response_id)
418
+ ? resp.previous_response_id
419
+ : null;
420
+ let chainParent =
421
+ parentResponseId !== null
422
+ ? (tailMessageByResponse.get(parentResponseId) ?? null)
423
+ : null;
424
+
425
+ const messageNodes = responseToMessageNodes(resp);
426
+ let firstId: string | null = null;
427
+ for (const node of messageNodes) {
428
+ appendNode(node, chainParent);
429
+ if (firstId === null) firstId = node.id;
430
+ chainParent = node.id;
431
+ }
432
+
433
+ if (rootMessageId === null && firstId !== null) {
434
+ rootMessageId = firstId;
435
+ }
436
+
437
+ // Track the attachment point for descendants. If the response had no
438
+ // visible messages, pass through the parent's tail so children of this
439
+ // response still land in the right place.
440
+ tailMessageByResponse.set(resp.id, chainParent);
441
+ }
442
+
443
+ return { nodes: treeNodes, rootId: rootMessageId };
444
+ }