@absolutejs/ai 0.0.1

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 (42) hide show
  1. package/README.md +6 -0
  2. package/dist/ai/client/index.js +682 -0
  3. package/dist/ai/client/index.js.map +15 -0
  4. package/dist/ai/index.js +3317 -0
  5. package/dist/ai/index.js.map +28 -0
  6. package/dist/ai/providers/anthropic.js +371 -0
  7. package/dist/ai/providers/anthropic.js.map +10 -0
  8. package/dist/ai/providers/gemini.js +260 -0
  9. package/dist/ai/providers/gemini.js.map +10 -0
  10. package/dist/ai/providers/ollama.js +221 -0
  11. package/dist/ai/providers/ollama.js.map +10 -0
  12. package/dist/ai/providers/openai.js +322 -0
  13. package/dist/ai/providers/openai.js.map +10 -0
  14. package/dist/ai/providers/openaiCompatible.js +360 -0
  15. package/dist/ai/providers/openaiCompatible.js.map +11 -0
  16. package/dist/ai/providers/openaiResponses.js +381 -0
  17. package/dist/ai/providers/openaiResponses.js.map +10 -0
  18. package/dist/src/ai/client/actions.d.ts +186 -0
  19. package/dist/src/ai/client/connection.d.ts +9 -0
  20. package/dist/src/ai/client/createAIStream.d.ts +12 -0
  21. package/dist/src/ai/client/index.d.ts +4 -0
  22. package/dist/src/ai/client/messageStore.d.ts +12 -0
  23. package/dist/src/ai/conversationManager.d.ts +19 -0
  24. package/dist/src/ai/htmxRenderers.d.ts +3 -0
  25. package/dist/src/ai/index.d.ts +19 -0
  26. package/dist/src/ai/memoryStore.d.ts +2 -0
  27. package/dist/src/ai/protocol.d.ts +4 -0
  28. package/dist/src/ai/providers/anthropic.d.ts +3 -0
  29. package/dist/src/ai/providers/gemini.d.ts +8 -0
  30. package/dist/src/ai/providers/ollama.d.ts +6 -0
  31. package/dist/src/ai/providers/openai.d.ts +7 -0
  32. package/dist/src/ai/providers/openaiCompatible.d.ts +30 -0
  33. package/dist/src/ai/providers/openaiResponses.d.ts +33 -0
  34. package/dist/src/ai/streamAI.d.ts +2 -0
  35. package/dist/src/ai/streamAIToSSE.d.ts +6 -0
  36. package/dist/src/constants.d.ts +60 -0
  37. package/dist/src/plugins/aiChat.d.ts +239 -0
  38. package/dist/types/ai.d.ts +4773 -0
  39. package/dist/types/anthropic.d.ts +20 -0
  40. package/dist/types/session.d.ts +16 -0
  41. package/dist/types/typeGuards.d.ts +3 -0
  42. package/package.json +59 -0
package/README.md ADDED
@@ -0,0 +1,6 @@
1
+ # @absolutejs/ai
2
+
3
+ Standalone AI runtime and provider package extracted from AbsoluteJS.
4
+
5
+ This package currently focuses on generic AI/chat/provider functionality.
6
+ RAG remains a separate package.
@@ -0,0 +1,682 @@
1
+ // @bun
2
+ // src/ai/client/actions.ts
3
+ var serverMessageToAction = (message) => {
4
+ switch (message.type) {
5
+ case "chunk":
6
+ return {
7
+ content: message.content,
8
+ conversationId: message.conversationId,
9
+ messageId: message.messageId,
10
+ type: "chunk"
11
+ };
12
+ case "thinking":
13
+ return {
14
+ content: message.content,
15
+ conversationId: message.conversationId,
16
+ messageId: message.messageId,
17
+ type: "thinking"
18
+ };
19
+ case "tool_status":
20
+ return {
21
+ conversationId: message.conversationId,
22
+ input: message.input,
23
+ messageId: message.messageId,
24
+ name: message.name,
25
+ result: message.result,
26
+ status: message.status,
27
+ type: "tool_status"
28
+ };
29
+ case "image":
30
+ return {
31
+ conversationId: message.conversationId,
32
+ data: message.data,
33
+ format: message.format,
34
+ imageId: message.imageId,
35
+ isPartial: message.isPartial,
36
+ messageId: message.messageId,
37
+ revisedPrompt: message.revisedPrompt,
38
+ type: "image"
39
+ };
40
+ case "complete":
41
+ return {
42
+ conversationId: message.conversationId,
43
+ durationMs: message.durationMs,
44
+ messageId: message.messageId,
45
+ model: message.model,
46
+ sources: message.sources,
47
+ type: "complete",
48
+ usage: message.usage
49
+ };
50
+ case "rag_retrieving":
51
+ return {
52
+ conversationId: message.conversationId,
53
+ messageId: message.messageId,
54
+ retrievalStartedAt: message.retrievalStartedAt,
55
+ type: "rag_retrieving"
56
+ };
57
+ case "rag_retrieved":
58
+ return {
59
+ conversationId: message.conversationId,
60
+ messageId: message.messageId,
61
+ retrievalDurationMs: message.retrievalDurationMs,
62
+ retrievalStartedAt: message.retrievalStartedAt,
63
+ retrievedAt: message.retrievedAt,
64
+ sources: message.sources,
65
+ trace: message.trace,
66
+ type: "rag_retrieved"
67
+ };
68
+ case "error":
69
+ return { message: message.message, type: "error" };
70
+ default:
71
+ return null;
72
+ }
73
+ };
74
+ // types/typeGuards.ts
75
+ var isValidAIClientMessage = (data) => {
76
+ if (!data || typeof data !== "object") {
77
+ return false;
78
+ }
79
+ if (!("type" in data) || typeof data.type !== "string") {
80
+ return false;
81
+ }
82
+ switch (data.type) {
83
+ case "message":
84
+ return "content" in data && typeof data.content === "string";
85
+ case "cancel":
86
+ return "conversationId" in data && typeof data.conversationId === "string";
87
+ case "branch":
88
+ return "messageId" in data && typeof data.messageId === "string" && "content" in data && typeof data.content === "string" && "conversationId" in data && typeof data.conversationId === "string";
89
+ default:
90
+ return false;
91
+ }
92
+ };
93
+ var isValidAIServerMessage = (data) => {
94
+ if (!data || typeof data !== "object") {
95
+ return false;
96
+ }
97
+ if (!("type" in data) || typeof data.type !== "string") {
98
+ return false;
99
+ }
100
+ switch (data.type) {
101
+ case "chunk":
102
+ case "thinking":
103
+ return "content" in data && typeof data.content === "string" && "messageId" in data && "conversationId" in data;
104
+ case "tool_status":
105
+ return "name" in data && "status" in data && "messageId" in data && "conversationId" in data;
106
+ case "image":
107
+ return "data" in data && typeof data.data === "string" && "format" in data && typeof data.format === "string" && "isPartial" in data && typeof data.isPartial === "boolean" && "messageId" in data && "conversationId" in data;
108
+ case "complete":
109
+ return "messageId" in data && "conversationId" in data;
110
+ case "rag_retrieved":
111
+ return "conversationId" in data && "messageId" in data && "sources" in data && Array.isArray(data.sources);
112
+ case "error":
113
+ return "message" in data && typeof data.message === "string";
114
+ default:
115
+ return false;
116
+ }
117
+ };
118
+
119
+ // src/ai/client/connection.ts
120
+ var WS_OPEN = 1;
121
+ var WS_NORMAL_CLOSURE = 1000;
122
+ var WS_CLOSED = 3;
123
+ var DEFAULT_PING_INTERVAL = 30000;
124
+ var RECONNECT_INITIAL_DELAY = 500;
125
+ var RECONNECT_POLL_INTERVAL = 300;
126
+ var DEFAULT_MAX_RECONNECT_ATTEMPTS = 60;
127
+ var noop = () => {};
128
+ var noopUnsubscribe = () => noop;
129
+ var NOOP_CONNECTION = {
130
+ close: noop,
131
+ send: noop,
132
+ subscribe: noopUnsubscribe,
133
+ getReadyState: () => WS_CLOSED
134
+ };
135
+ var buildWsUrl = (path) => {
136
+ const { hostname, port, protocol } = window.location;
137
+ const wsProtocol = protocol === "https:" ? "wss:" : "ws:";
138
+ const portSuffix = port ? `:${port}` : "";
139
+ return `${wsProtocol}//${hostname}${portSuffix}${path}`;
140
+ };
141
+ var parseServerMessage = (event) => {
142
+ let data;
143
+ try {
144
+ data = JSON.parse(String(event.data));
145
+ } catch {
146
+ return null;
147
+ }
148
+ if (data && typeof data === "object" && "type" in data && data.type === "pong") {
149
+ return null;
150
+ }
151
+ if (!isValidAIServerMessage(data)) {
152
+ return null;
153
+ }
154
+ return data;
155
+ };
156
+ var createAIConnection = (path, options = {}) => {
157
+ if (typeof window === "undefined") {
158
+ return NOOP_CONNECTION;
159
+ }
160
+ const shouldReconnect = options.reconnect !== false;
161
+ const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
162
+ const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
163
+ const listeners = new Set;
164
+ const connState = {
165
+ isConnected: false,
166
+ pendingMessages: [],
167
+ pingInterval: null,
168
+ reconnectAttempts: 0,
169
+ reconnectTimeout: null,
170
+ ws: null
171
+ };
172
+ const flushPendingMessages = () => {
173
+ if (connState.ws?.readyState !== WS_OPEN) {
174
+ return;
175
+ }
176
+ while (connState.pendingMessages.length > 0) {
177
+ const next = connState.pendingMessages.shift();
178
+ if (typeof next === "string") {
179
+ connState.ws.send(next);
180
+ }
181
+ }
182
+ };
183
+ const clearTimers = () => {
184
+ if (connState.pingInterval) {
185
+ clearInterval(connState.pingInterval);
186
+ connState.pingInterval = null;
187
+ }
188
+ if (connState.reconnectTimeout) {
189
+ clearTimeout(connState.reconnectTimeout);
190
+ connState.reconnectTimeout = null;
191
+ }
192
+ };
193
+ const scheduleReconnect = () => {
194
+ connState.reconnectAttempts++;
195
+ const delay = connState.reconnectAttempts === 1 ? RECONNECT_INITIAL_DELAY : RECONNECT_POLL_INTERVAL;
196
+ connState.reconnectTimeout = setTimeout(() => {
197
+ if (connState.reconnectAttempts > maxReconnectAttempts) {
198
+ return;
199
+ }
200
+ connect();
201
+ }, delay);
202
+ };
203
+ const connect = () => {
204
+ const url = buildWsUrl(path);
205
+ const wsInstance = new WebSocket(url, options.protocols);
206
+ wsInstance.onopen = () => {
207
+ connState.isConnected = true;
208
+ connState.reconnectAttempts = 0;
209
+ flushPendingMessages();
210
+ connState.pingInterval = setInterval(() => {
211
+ if (wsInstance.readyState === WS_OPEN && connState.isConnected) {
212
+ wsInstance.send(JSON.stringify({ type: "ping" }));
213
+ }
214
+ }, pingInterval);
215
+ };
216
+ wsInstance.onmessage = (event) => {
217
+ const message = parseServerMessage(event);
218
+ if (!message) {
219
+ return;
220
+ }
221
+ listeners.forEach((listener) => listener(message));
222
+ };
223
+ wsInstance.onclose = (event) => {
224
+ connState.isConnected = false;
225
+ clearTimers();
226
+ const shouldAttemptReconnect = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && connState.reconnectAttempts < maxReconnectAttempts;
227
+ if (shouldAttemptReconnect) {
228
+ scheduleReconnect();
229
+ }
230
+ };
231
+ wsInstance.onerror = () => {};
232
+ connState.ws = wsInstance;
233
+ };
234
+ const send = (msg) => {
235
+ const serialized = JSON.stringify(msg);
236
+ if (connState.ws?.readyState === WS_OPEN) {
237
+ connState.ws.send(serialized);
238
+ return;
239
+ }
240
+ connState.pendingMessages.push(serialized);
241
+ };
242
+ const subscribe = (callback) => {
243
+ listeners.add(callback);
244
+ return () => {
245
+ listeners.delete(callback);
246
+ };
247
+ };
248
+ const close = () => {
249
+ clearTimers();
250
+ if (connState.ws) {
251
+ connState.ws.close(WS_NORMAL_CLOSURE);
252
+ connState.ws = null;
253
+ }
254
+ connState.isConnected = false;
255
+ listeners.clear();
256
+ };
257
+ const getReadyState = () => connState.ws?.readyState ?? WS_CLOSED;
258
+ connect();
259
+ return { close, getReadyState, send, subscribe };
260
+ };
261
+ // src/ai/client/messageStore.ts
262
+ var EMPTY_STATE = {
263
+ activeConversationId: null,
264
+ conversations: new Map,
265
+ error: null,
266
+ isStreaming: false
267
+ };
268
+ var initialActiveConversationId = null;
269
+ var initialError = null;
270
+ var freshState = () => ({
271
+ activeConversationId: initialActiveConversationId,
272
+ conversations: new Map,
273
+ error: initialError,
274
+ isStreaming: false
275
+ });
276
+ var findAssistantMessage = (conversation, messageId) => conversation.messages.find((msg) => msg.id === messageId && msg.role === "assistant");
277
+ var getOrCreate = (state, conversationId) => {
278
+ let conversation = state.conversations.get(conversationId);
279
+ if (!conversation) {
280
+ conversation = {
281
+ createdAt: Date.now(),
282
+ id: conversationId,
283
+ messages: []
284
+ };
285
+ state.conversations.set(conversationId, conversation);
286
+ }
287
+ return conversation;
288
+ };
289
+ var handleSend = (state, action) => {
290
+ const conversation = getOrCreate(state, action.conversationId);
291
+ const message = {
292
+ attachments: action.attachments,
293
+ content: action.content,
294
+ conversationId: action.conversationId,
295
+ id: action.messageId,
296
+ role: "user",
297
+ timestamp: Date.now()
298
+ };
299
+ conversation.messages = [...conversation.messages, message];
300
+ state.activeConversationId = action.conversationId;
301
+ state.error = null;
302
+ state.isStreaming = true;
303
+ };
304
+ var handleChunk = (state, action) => {
305
+ const conversation = getOrCreate(state, action.conversationId);
306
+ const existingIdx = conversation.messages.findIndex((msg) => msg.id === action.messageId && msg.role === "assistant");
307
+ if (existingIdx >= 0) {
308
+ const prevContent = conversation.messages[existingIdx]?.content ?? "";
309
+ conversation.messages = conversation.messages.map((msg, idx) => idx === existingIdx ? { ...msg, content: prevContent + action.content } : msg);
310
+ return;
311
+ }
312
+ const message = {
313
+ content: action.content,
314
+ conversationId: action.conversationId,
315
+ id: action.messageId,
316
+ isStreaming: true,
317
+ role: "assistant",
318
+ timestamp: Date.now()
319
+ };
320
+ conversation.messages = [...conversation.messages, message];
321
+ };
322
+ var handleThinking = (state, action) => {
323
+ const conversation = getOrCreate(state, action.conversationId);
324
+ const existingIdx = conversation.messages.findIndex((msg) => msg.id === action.messageId && msg.role === "assistant");
325
+ if (existingIdx >= 0) {
326
+ const prevThinking = conversation.messages[existingIdx]?.thinking ?? "";
327
+ conversation.messages = conversation.messages.map((msg, idx) => idx === existingIdx ? { ...msg, thinking: prevThinking + action.content } : msg);
328
+ return;
329
+ }
330
+ const message = {
331
+ content: "",
332
+ conversationId: action.conversationId,
333
+ id: action.messageId,
334
+ isStreaming: true,
335
+ role: "assistant",
336
+ thinking: action.content,
337
+ timestamp: Date.now()
338
+ };
339
+ conversation.messages = [...conversation.messages, message];
340
+ };
341
+ var upsertToolCall = (message, toolCall) => {
342
+ if (!message.toolCalls) {
343
+ message.toolCalls = [toolCall];
344
+ return;
345
+ }
346
+ const existingIdx = message.toolCalls.findIndex((existing) => existing.name === toolCall.name);
347
+ if (existingIdx >= 0) {
348
+ message.toolCalls[existingIdx] = toolCall;
349
+ } else {
350
+ message.toolCalls = [...message.toolCalls, toolCall];
351
+ }
352
+ };
353
+ var getOrCreateAssistantMessage = (conversation, messageId, conversationId) => {
354
+ const existing = findAssistantMessage(conversation, messageId);
355
+ if (existing) {
356
+ return existing;
357
+ }
358
+ const message = {
359
+ content: "",
360
+ conversationId,
361
+ id: messageId,
362
+ isStreaming: true,
363
+ role: "assistant",
364
+ timestamp: Date.now()
365
+ };
366
+ conversation.messages = [...conversation.messages, message];
367
+ return message;
368
+ };
369
+ var handleRAGRetrieved = (state, action) => {
370
+ const conversation = getOrCreate(state, action.conversationId);
371
+ const message = getOrCreateAssistantMessage(conversation, action.messageId, action.conversationId);
372
+ message.sources = action.sources;
373
+ message.retrievalStartedAt = action.retrievalStartedAt ?? message.retrievalStartedAt;
374
+ message.retrievedAt = action.retrievedAt;
375
+ message.retrievalDurationMs = action.retrievalDurationMs;
376
+ message.retrievalTrace = action.trace;
377
+ conversation.messages = [...conversation.messages];
378
+ };
379
+ var handleRAGRetrieving = (state, action) => {
380
+ const conversation = getOrCreate(state, action.conversationId);
381
+ const message = getOrCreateAssistantMessage(conversation, action.messageId, action.conversationId);
382
+ message.retrievalStartedAt = action.retrievalStartedAt;
383
+ conversation.messages = [...conversation.messages];
384
+ };
385
+ var handleToolStatus = (state, action) => {
386
+ const conversation = getOrCreate(state, action.conversationId);
387
+ const message = getOrCreateAssistantMessage(conversation, action.messageId, action.conversationId);
388
+ const toolCall = {
389
+ id: action.messageId,
390
+ input: action.input,
391
+ name: action.name,
392
+ result: action.status === "complete" ? action.result ?? "" : undefined
393
+ };
394
+ upsertToolCall(message, toolCall);
395
+ conversation.messages = [...conversation.messages];
396
+ };
397
+ var markMessageComplete = (conversation, messageId, usage, durationMs, model, sources) => {
398
+ conversation.messages = conversation.messages.map((msg) => msg.id === messageId && msg.role === "assistant" ? {
399
+ ...msg,
400
+ durationMs,
401
+ isStreaming: false,
402
+ model,
403
+ sources: sources ?? msg.sources,
404
+ usage
405
+ } : msg);
406
+ };
407
+ var NOT_FOUND = -1;
408
+ var findExistingImageById = (images, imageId) => {
409
+ if (!imageId)
410
+ return NOT_FOUND;
411
+ return images.findIndex((img) => img.imageId === imageId);
412
+ };
413
+ var findReplaceablePartialIndex = (images) => {
414
+ const lastIdx = images.length - 1;
415
+ if (lastIdx >= 0 && images[lastIdx]?.isPartial) {
416
+ return lastIdx;
417
+ }
418
+ return NOT_FOUND;
419
+ };
420
+ var upsertImage = (message, imageData) => {
421
+ if (!message.images) {
422
+ message.images = [imageData];
423
+ return;
424
+ }
425
+ const existingIdx = findExistingImageById(message.images, imageData.imageId);
426
+ if (existingIdx >= 0) {
427
+ message.images[existingIdx] = imageData;
428
+ return;
429
+ }
430
+ const replaceableIdx = findReplaceablePartialIndex(message.images);
431
+ if (replaceableIdx >= 0) {
432
+ message.images[replaceableIdx] = imageData;
433
+ return;
434
+ }
435
+ message.images = [...message.images, imageData];
436
+ };
437
+ var handleImage = (state, action) => {
438
+ const conversation = getOrCreate(state, action.conversationId);
439
+ const message = getOrCreateAssistantMessage(conversation, action.messageId, action.conversationId);
440
+ upsertImage(message, {
441
+ data: action.data,
442
+ format: action.format,
443
+ imageId: action.imageId,
444
+ isPartial: action.isPartial,
445
+ revisedPrompt: action.revisedPrompt
446
+ });
447
+ conversation.messages = [...conversation.messages];
448
+ };
449
+ var handleComplete = (state, action) => {
450
+ const conversation = state.conversations.get(action.conversationId);
451
+ if (conversation) {
452
+ markMessageComplete(conversation, action.messageId, action.usage, action.durationMs, action.model, action.sources);
453
+ }
454
+ state.isStreaming = false;
455
+ };
456
+ var markConversationStreamsComplete = (conversation) => {
457
+ const streamingMessages = conversation.messages.filter((msg) => msg.isStreaming);
458
+ if (streamingMessages.length === 0) {
459
+ return;
460
+ }
461
+ for (const msg of streamingMessages) {
462
+ msg.isStreaming = false;
463
+ }
464
+ conversation.messages = [...conversation.messages];
465
+ };
466
+ var markAllStreamsComplete = (state) => {
467
+ for (const [, conversation] of state.conversations) {
468
+ markConversationStreamsComplete(conversation);
469
+ }
470
+ };
471
+ var handleBranch = (state, action) => {
472
+ const source = state.conversations.get(action.oldConversationId);
473
+ if (!source) {
474
+ return;
475
+ }
476
+ const cutoffIndex = source.messages.findIndex((msg) => msg.id === action.fromMessageId);
477
+ if (cutoffIndex < 0) {
478
+ return;
479
+ }
480
+ const branchedMessages = source.messages.slice(0, cutoffIndex + 1).map((msg) => ({ ...msg, conversationId: action.newConversationId }));
481
+ const newConversation = {
482
+ createdAt: Date.now(),
483
+ id: action.newConversationId,
484
+ messages: branchedMessages
485
+ };
486
+ state.conversations.set(action.newConversationId, newConversation);
487
+ state.activeConversationId = action.newConversationId;
488
+ };
489
+ var applyAction = (state, action) => {
490
+ switch (action.type) {
491
+ case "send":
492
+ handleSend(state, action);
493
+ break;
494
+ case "chunk":
495
+ handleChunk(state, action);
496
+ break;
497
+ case "thinking":
498
+ handleThinking(state, action);
499
+ break;
500
+ case "tool_status":
501
+ handleToolStatus(state, action);
502
+ break;
503
+ case "image":
504
+ handleImage(state, action);
505
+ break;
506
+ case "complete":
507
+ handleComplete(state, action);
508
+ break;
509
+ case "error":
510
+ state.error = action.message;
511
+ state.isStreaming = false;
512
+ break;
513
+ case "rag_retrieving":
514
+ handleRAGRetrieving(state, action);
515
+ break;
516
+ case "rag_retrieved":
517
+ handleRAGRetrieved(state, action);
518
+ break;
519
+ case "cancel":
520
+ state.isStreaming = false;
521
+ markAllStreamsComplete(state);
522
+ break;
523
+ case "branch":
524
+ handleBranch(state, action);
525
+ break;
526
+ case "set_conversation":
527
+ state.activeConversationId = action.conversationId;
528
+ break;
529
+ }
530
+ };
531
+ var createAIMessageStore = () => {
532
+ let state = freshState();
533
+ const subscribers = new Set;
534
+ return {
535
+ dispatch: (action) => {
536
+ applyAction(state, action);
537
+ state = { ...state, conversations: new Map(state.conversations) };
538
+ subscribers.forEach((callback) => callback());
539
+ },
540
+ getServerSnapshot: () => EMPTY_STATE,
541
+ getSnapshot: () => state,
542
+ subscribe: (callback) => {
543
+ subscribers.add(callback);
544
+ return () => {
545
+ subscribers.delete(callback);
546
+ };
547
+ }
548
+ };
549
+ };
550
+
551
+ // src/ai/protocol.ts
552
+ var generateId = () => crypto.randomUUID();
553
+ var parseAIMessage = (raw) => {
554
+ if (raw === null || raw === undefined) {
555
+ return null;
556
+ }
557
+ let text;
558
+ if (typeof raw === "string") {
559
+ text = raw;
560
+ } else if (raw instanceof ArrayBuffer) {
561
+ text = new TextDecoder().decode(raw);
562
+ } else if (ArrayBuffer.isView(raw)) {
563
+ text = new TextDecoder().decode(raw);
564
+ } else if (typeof raw === "object") {
565
+ if (isValidAIClientMessage(raw)) {
566
+ return raw;
567
+ }
568
+ return null;
569
+ } else {
570
+ return null;
571
+ }
572
+ try {
573
+ const parsed = JSON.parse(text);
574
+ if (isValidAIClientMessage(parsed)) {
575
+ return parsed;
576
+ }
577
+ return null;
578
+ } catch {
579
+ return null;
580
+ }
581
+ };
582
+ var serializeAIMessage = (message) => JSON.stringify(message);
583
+
584
+ // src/ai/client/createAIStream.ts
585
+ var createAIStream = (path, conversationId) => {
586
+ const connection = createAIConnection(path);
587
+ const store = createAIMessageStore();
588
+ const listeners = new Set;
589
+ let currentError = null;
590
+ let currentIsStreaming = false;
591
+ let currentMessages = [];
592
+ let activeConversationId = conversationId ?? null;
593
+ const syncState = () => {
594
+ const snapshot = store.getSnapshot();
595
+ const convId = activeConversationId ?? snapshot.activeConversationId;
596
+ const conversation = convId ? snapshot.conversations.get(convId) : undefined;
597
+ activeConversationId = convId ?? snapshot.activeConversationId;
598
+ currentError = snapshot.error;
599
+ currentIsStreaming = snapshot.isStreaming;
600
+ currentMessages = conversation?.messages ?? [];
601
+ listeners.forEach((listener) => listener());
602
+ };
603
+ const unsubscribeStore = store.subscribe(syncState);
604
+ const unsubscribeConnection = connection.subscribe((message) => {
605
+ const action = serverMessageToAction(message);
606
+ if (action) {
607
+ store.dispatch(action);
608
+ }
609
+ });
610
+ const branch = (messageId, content) => {
611
+ if (activeConversationId) {
612
+ connection.send({
613
+ content,
614
+ conversationId: activeConversationId,
615
+ messageId,
616
+ type: "branch"
617
+ });
618
+ }
619
+ };
620
+ const cancel = () => {
621
+ if (activeConversationId) {
622
+ store.dispatch({ type: "cancel" });
623
+ connection.send({
624
+ conversationId: activeConversationId,
625
+ type: "cancel"
626
+ });
627
+ }
628
+ };
629
+ const destroy = () => {
630
+ unsubscribeStore();
631
+ unsubscribeConnection();
632
+ connection.close();
633
+ listeners.clear();
634
+ };
635
+ const send = (content, attachments) => {
636
+ const convId = activeConversationId ?? generateId();
637
+ const msgId = generateId();
638
+ store.dispatch({
639
+ attachments,
640
+ content,
641
+ conversationId: convId,
642
+ messageId: msgId,
643
+ type: "send"
644
+ });
645
+ connection.send({
646
+ attachments,
647
+ content,
648
+ conversationId: convId,
649
+ type: "message"
650
+ });
651
+ };
652
+ const subscribe = (callback) => {
653
+ listeners.add(callback);
654
+ return () => {
655
+ listeners.delete(callback);
656
+ };
657
+ };
658
+ return {
659
+ branch,
660
+ cancel,
661
+ destroy,
662
+ send,
663
+ subscribe,
664
+ get error() {
665
+ return currentError;
666
+ },
667
+ get isStreaming() {
668
+ return currentIsStreaming;
669
+ },
670
+ get messages() {
671
+ return currentMessages;
672
+ }
673
+ };
674
+ };
675
+ export {
676
+ serverMessageToAction,
677
+ createAIStream,
678
+ createAIConnection
679
+ };
680
+
681
+ //# debugId=D06BB75544A02DB464756E2164756E21
682
+ //# sourceMappingURL=index.js.map