@aikaara/chat-sdk 0.2.0 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikaara/chat-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "description": "Aikaara Chat SDK — embeddable chat widget and headless client",
6
6
  "license": "MIT",
@@ -1,611 +0,0 @@
1
- class p {
2
- identifier;
3
- callbacks = {};
4
- sendFn;
5
- constructor(t, e) {
6
- this.identifier = t, this.sendFn = e;
7
- }
8
- onReceived(t) {
9
- return this.callbacks.received = t, this;
10
- }
11
- onConnected(t) {
12
- return this.callbacks.connected = t, this;
13
- }
14
- onDisconnected(t) {
15
- return this.callbacks.disconnected = t, this;
16
- }
17
- onRejected(t) {
18
- return this.callbacks.rejected = t, this;
19
- }
20
- perform(t, e = {}) {
21
- this.sendFn({ action: t, ...e });
22
- }
23
- /** @internal */
24
- _notifyReceived(t) {
25
- this.callbacks.received?.(t);
26
- }
27
- /** @internal */
28
- _notifyConnected() {
29
- this.callbacks.connected?.();
30
- }
31
- /** @internal */
32
- _notifyDisconnected() {
33
- this.callbacks.disconnected?.();
34
- }
35
- /** @internal */
36
- _notifyRejected() {
37
- this.callbacks.rejected?.();
38
- }
39
- }
40
- class g {
41
- ws = null;
42
- url;
43
- subscriptions = /* @__PURE__ */ new Map();
44
- welcomePromise = null;
45
- pendingSubscriptions = /* @__PURE__ */ new Map();
46
- constructor(t) {
47
- this.url = t;
48
- }
49
- connect() {
50
- return new Promise((t, e) => {
51
- this.welcomePromise = { resolve: t, reject: e }, this.ws = new WebSocket(this.url), this.ws.onopen = () => {
52
- }, this.ws.onmessage = (s) => {
53
- this.handleMessage(s);
54
- }, this.ws.onerror = () => {
55
- const s = new Error("WebSocket connection error");
56
- this.welcomePromise?.reject(s), this.welcomePromise = null;
57
- }, this.ws.onclose = () => {
58
- this.subscriptions.forEach((s) => s._notifyDisconnected());
59
- };
60
- });
61
- }
62
- disconnect() {
63
- this.ws && (this.ws.onclose = null, this.ws.close(), this.ws = null), this.subscriptions.forEach((t) => t._notifyDisconnected()), this.subscriptions.clear();
64
- }
65
- subscribe(t) {
66
- const e = JSON.stringify(t), s = new p(e, (n) => {
67
- this.send({
68
- command: "message",
69
- identifier: e,
70
- data: JSON.stringify(n)
71
- });
72
- });
73
- return this.subscriptions.set(e, s), this.send({
74
- command: "subscribe",
75
- identifier: e
76
- }), s;
77
- }
78
- subscribeAsync(t) {
79
- const e = this.subscribe(t), s = e.identifier;
80
- return new Promise((n, i) => {
81
- this.pendingSubscriptions.set(s, {
82
- resolve: () => n(e),
83
- reject: i
84
- }), setTimeout(() => {
85
- this.pendingSubscriptions.has(s) && (this.pendingSubscriptions.delete(s), i(new Error(`Subscription timeout for ${s}`)));
86
- }, 1e4);
87
- });
88
- }
89
- unsubscribe(t) {
90
- this.send({
91
- command: "unsubscribe",
92
- identifier: t
93
- }), this.subscriptions.delete(t);
94
- }
95
- perform(t, e, s = {}) {
96
- this.send({
97
- command: "message",
98
- identifier: t,
99
- data: JSON.stringify({ action: e, ...s })
100
- });
101
- }
102
- get isConnected() {
103
- return this.ws?.readyState === WebSocket.OPEN;
104
- }
105
- send(t) {
106
- this.ws?.readyState === WebSocket.OPEN && this.ws.send(JSON.stringify(t));
107
- }
108
- handleMessage(t) {
109
- let e;
110
- try {
111
- e = JSON.parse(t.data);
112
- } catch {
113
- return;
114
- }
115
- switch (e.type) {
116
- case "welcome":
117
- this.welcomePromise?.resolve(), this.welcomePromise = null;
118
- break;
119
- case "ping":
120
- break;
121
- case "confirm_subscription": {
122
- const s = e.identifier;
123
- this.subscriptions.get(s)?._notifyConnected();
124
- const i = this.pendingSubscriptions.get(s);
125
- i && (i.resolve(), this.pendingSubscriptions.delete(s));
126
- break;
127
- }
128
- case "reject_subscription": {
129
- const s = e.identifier;
130
- this.subscriptions.get(s)?._notifyRejected(), this.subscriptions.delete(s);
131
- const i = this.pendingSubscriptions.get(s);
132
- i && (i.reject(new Error(`Subscription rejected: ${s}`)), this.pendingSubscriptions.delete(s));
133
- break;
134
- }
135
- case "disconnect":
136
- this.subscriptions.forEach((s) => s._notifyDisconnected());
137
- break;
138
- default: {
139
- e.identifier && e.message !== void 0 && this.subscriptions.get(e.identifier)?._notifyReceived(e.message);
140
- break;
141
- }
142
- }
143
- }
144
- }
145
- class m {
146
- handlers = /* @__PURE__ */ new Map();
147
- on(t, e) {
148
- return this.handlers.has(t) || this.handlers.set(t, /* @__PURE__ */ new Set()), this.handlers.get(t).add(e), () => this.off(t, e);
149
- }
150
- off(t, e) {
151
- this.handlers.get(t)?.delete(e);
152
- }
153
- emit(t, e) {
154
- this.handlers.get(t)?.forEach((s) => {
155
- try {
156
- s(e);
157
- } catch (n) {
158
- console.error(`Error in event handler for "${t}":`, n);
159
- }
160
- });
161
- }
162
- removeAllListeners() {
163
- this.handlers.clear();
164
- }
165
- }
166
- const f = 1e3, _ = 10, w = 400, k = 600, T = "#6366f1", I = 12, E = "system-ui, -apple-system, sans-serif", C = "Type a message...", A = "bottom-right", M = "light", O = { x: 20, y: 20 }, d = "aikaara_conversation_id";
167
- class b extends m {
168
- client;
169
- config;
170
- state = "disconnected";
171
- reconnectAttempt = 0;
172
- reconnectTimer = null;
173
- constructor(t) {
174
- super(), this.config = t;
175
- const e = this.buildWsUrl(t.baseUrl, t.userToken);
176
- this.client = new g(e);
177
- }
178
- async connect() {
179
- this.setState("connecting");
180
- try {
181
- await this.client.connect(), this.setState("connected"), this.reconnectAttempt = 0;
182
- } catch (t) {
183
- if (this.setState("disconnected"), this.config.reconnect !== !1)
184
- this.scheduleReconnect();
185
- else
186
- throw t;
187
- }
188
- }
189
- async disconnect() {
190
- this.clearReconnectTimer(), this.client.disconnect(), this.setState("disconnected");
191
- }
192
- subscribeToConversation(t) {
193
- return this.client.subscribeAsync({
194
- channel: "ConversationChannel",
195
- conversation_id: t
196
- });
197
- }
198
- sendMessage(t, e) {
199
- const s = JSON.stringify({
200
- channel: "ConversationChannel",
201
- conversation_id: t
202
- });
203
- this.client.perform(s, "send_message", { content: e });
204
- }
205
- sendUserEvent(t, e, s, n) {
206
- const i = JSON.stringify({
207
- channel: "ConversationChannel",
208
- conversation_id: t
209
- });
210
- this.client.perform(i, "send_user_event", {
211
- event_key: e,
212
- ...s && { value: s },
213
- ...n && { source: n }
214
- });
215
- }
216
- get connectionState() {
217
- return this.state;
218
- }
219
- setState(t) {
220
- this.state !== t && (this.state = t, this.emit("connection:state", t));
221
- }
222
- scheduleReconnect() {
223
- const t = this.config.maxReconnectAttempts ?? _;
224
- if (this.reconnectAttempt >= t) {
225
- this.emit("error", new Error("Max reconnection attempts reached"));
226
- return;
227
- }
228
- this.setState("reconnecting");
229
- const s = (this.config.reconnectInterval ?? f) * Math.pow(2, this.reconnectAttempt);
230
- this.reconnectAttempt++, this.reconnectTimer = setTimeout(async () => {
231
- try {
232
- const n = this.buildWsUrl(this.config.baseUrl, this.config.userToken);
233
- this.client = new g(n), await this.connect();
234
- } catch {
235
- }
236
- }, s);
237
- }
238
- clearReconnectTimer() {
239
- this.reconnectTimer && (clearTimeout(this.reconnectTimer), this.reconnectTimer = null);
240
- }
241
- buildWsUrl(t, e) {
242
- if (this.config.wsUrl) {
243
- const n = this.config.wsUrl.includes("?") ? "&" : "?";
244
- return `${this.config.wsUrl}${n}token=${e}`;
245
- }
246
- return `${t.replace(/^http/, "ws")}/cable?token=${e}`;
247
- }
248
- }
249
- class y {
250
- baseUrl;
251
- apiKey;
252
- authToken;
253
- userToken;
254
- constructor(t, e, s, n) {
255
- this.baseUrl = t, this.userToken = e, this.apiKey = s, this.authToken = n;
256
- }
257
- async createConversation(t) {
258
- const e = {
259
- conversation: {
260
- ...t.extUid && { ext_uid: t.extUid },
261
- ...t.systemPromptId && { system_prompt_id: t.systemPromptId },
262
- ...t.channel && { channel: t.channel },
263
- ...t.title && { title: t.title }
264
- }
265
- };
266
- return this.request("POST", "/api/v1/conversations", e);
267
- }
268
- async updateContext(t, e) {
269
- const s = this.authToken ? `/dashboard/sidekick_conversations/${t}` : `/api/v1/conversations/${t}`;
270
- await this.request("PATCH", s, e);
271
- }
272
- async getMessages(t) {
273
- return (await this.request(
274
- "GET",
275
- `/api/v1/conversations/${t}/messages`
276
- )).map(this.mapMessage);
277
- }
278
- mapMessage(t) {
279
- return {
280
- id: String(t.id),
281
- conversationId: String(t.conversation_id),
282
- role: t.role,
283
- content: t.content || "",
284
- toolCalls: t.tool_calls?.map((e) => ({
285
- id: e.id,
286
- type: e.type,
287
- function: e.function
288
- })),
289
- toolCallResults: t.tool_call_results,
290
- tokensInput: t.tokens_input,
291
- tokensOutput: t.tokens_output,
292
- metadata: t.metadata,
293
- createdAt: t.created_at,
294
- status: "complete"
295
- };
296
- }
297
- async request(t, e, s) {
298
- const n = {
299
- "Content-Type": "application/json",
300
- Accept: "application/json"
301
- };
302
- this.apiKey && (n["X-Api-Key"] = this.apiKey), this.authToken && (n.Authorization = `Bearer ${this.authToken}`);
303
- const i = `${this.baseUrl}${e}`, l = { method: t, headers: n };
304
- s && (l.body = JSON.stringify(s));
305
- const a = await fetch(i, l);
306
- if (!a.ok) {
307
- const c = await a.text();
308
- let h;
309
- try {
310
- const u = JSON.parse(c);
311
- h = u.error || u.message || c;
312
- } catch {
313
- h = c;
314
- }
315
- throw new Error(`API error ${a.status}: ${h}`);
316
- }
317
- const r = await a.json();
318
- if (r && typeof r == "object" && "success" in r) {
319
- if (!r.success)
320
- throw new Error(`API error: ${r.message || "Request failed"}`);
321
- return r.data;
322
- }
323
- return r;
324
- }
325
- }
326
- class v {
327
- _messages = [];
328
- optimisticCounter = 0;
329
- get messages() {
330
- return [...this._messages];
331
- }
332
- addOptimistic(t, e, s) {
333
- const n = {
334
- id: `optimistic_${++this.optimisticCounter}`,
335
- conversationId: s,
336
- role: t,
337
- content: e,
338
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
339
- status: "sending"
340
- };
341
- return this._messages.push(n), n;
342
- }
343
- confirmOptimistic(t) {
344
- const e = this._messages.find((s) => s.id === t);
345
- e && (e.status = "sent");
346
- }
347
- addStreamingMessage(t) {
348
- const e = {
349
- id: `streaming_${Date.now()}`,
350
- conversationId: t,
351
- role: "assistant",
352
- content: "",
353
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
354
- status: "streaming"
355
- };
356
- return this._messages.push(e), e;
357
- }
358
- updateStreaming(t) {
359
- const e = this._messages.findLast((s) => s.status === "streaming");
360
- e && (e.content = t);
361
- }
362
- appendToStreaming(t) {
363
- const e = this._messages.findLast((s) => s.status === "streaming");
364
- e && (e.content += t);
365
- }
366
- get streamingContent() {
367
- return this._messages.findLast((e) => e.status === "streaming")?.content || "";
368
- }
369
- finalizeStreaming(t) {
370
- const e = this._messages.findLast((s) => s.status === "streaming");
371
- return e && (e.status = "complete", t && (e.tokensInput = t.tokensInput, e.tokensOutput = t.tokensOutput)), e;
372
- }
373
- addMessage(t) {
374
- this._messages.push(t);
375
- }
376
- setMessages(t) {
377
- this._messages = [...t];
378
- }
379
- clear() {
380
- this._messages = [];
381
- }
382
- }
383
- class S {
384
- _conversationId;
385
- persist;
386
- constructor(t, e = !0) {
387
- this.persist = e, this._conversationId = t || this.loadFromStorage();
388
- }
389
- get conversationId() {
390
- return this._conversationId;
391
- }
392
- set conversationId(t) {
393
- this._conversationId = t, this.persist && t && this.saveToStorage(t);
394
- }
395
- clear() {
396
- if (this._conversationId = null, this.persist)
397
- try {
398
- localStorage.removeItem(d);
399
- } catch {
400
- }
401
- }
402
- loadFromStorage() {
403
- if (!this.persist) return null;
404
- try {
405
- return localStorage.getItem(d);
406
- } catch {
407
- return null;
408
- }
409
- }
410
- saveToStorage(t) {
411
- try {
412
- localStorage.setItem(d, t);
413
- } catch {
414
- }
415
- }
416
- }
417
- class U extends m {
418
- connection;
419
- api;
420
- messageStore;
421
- conversationManager;
422
- subscription = null;
423
- config;
424
- constructor(t) {
425
- super(), this.config = t, this.connection = new b(t), this.api = new y(t.baseUrl, t.userToken, t.apiKey, t.authToken), this.messageStore = new v(), this.conversationManager = new S(t.conversationId), this.connection.on("connection:state", (e) => {
426
- this.emit("connection:state", e), this.config.onConnectionStateChange?.(e);
427
- }), this.connection.on("error", (e) => {
428
- this.emit("error", e), this.config.onError?.(e);
429
- });
430
- }
431
- async connect() {
432
- if (await this.connection.connect(), !this.conversationManager.conversationId) {
433
- const t = await this.api.createConversation({
434
- systemPromptId: this.config.systemPromptId,
435
- channel: this.config.channel || "widget",
436
- extUid: this.config.extUid
437
- });
438
- this.conversationManager.conversationId = String(t.id);
439
- }
440
- this.subscription = await this.connection.subscribeToConversation(
441
- this.conversationManager.conversationId
442
- ), this.subscription.onReceived((t) => {
443
- this.handleBroadcast(t);
444
- }), await this.loadHistory();
445
- }
446
- async sendMessage(t) {
447
- const e = this.conversationManager.conversationId;
448
- if (!e)
449
- throw new Error("No active conversation");
450
- const s = this.messageStore.addOptimistic("user", t, e);
451
- this.emit("message:sent", s), this.config.onMessage?.(s), this.connection.sendMessage(e, t);
452
- }
453
- async sendUserEvent(t, e, s) {
454
- const n = this.conversationManager.conversationId;
455
- if (!n)
456
- throw new Error("No active conversation");
457
- this.connection.sendUserEvent(n, t, e, s);
458
- }
459
- async loadHistory() {
460
- const t = this.conversationManager.conversationId;
461
- if (!t) return [];
462
- try {
463
- const e = await this.api.getMessages(t);
464
- return this.messageStore.setMessages(e), e;
465
- } catch {
466
- return [];
467
- }
468
- }
469
- get messages() {
470
- return this.messageStore.messages;
471
- }
472
- get conversationId() {
473
- return this.conversationManager.conversationId;
474
- }
475
- get isConnected() {
476
- return this.connection.connectionState === "connected";
477
- }
478
- /**
479
- * Update the agent's context with information about the host app's current state.
480
- * Call this on route changes so the agent knows what page/entity the user is viewing.
481
- *
482
- * The context is stored in conversation metadata and interpolated into the system prompt.
483
- *
484
- * @example
485
- * ```typescript
486
- * // On route change
487
- * client.setContext({
488
- * currentPage: '/products/42',
489
- * entityType: 'product',
490
- * entityId: '42',
491
- * availableRoutes: { products: '/products', orders: '/orders' },
492
- * });
493
- * ```
494
- */
495
- async setContext(t) {
496
- const e = this.conversationManager.conversationId;
497
- e && await this.api.updateContext(e, {
498
- current_page: t.currentPage,
499
- entity_type: t.entityType,
500
- entity_id: t.entityId,
501
- project_id: t.projectId,
502
- available_routes: t.availableRoutes,
503
- custom_context: t.custom
504
- });
505
- }
506
- async disconnect() {
507
- this.subscription && (this.subscription = null), await this.connection.disconnect();
508
- }
509
- /**
510
- * Parse structured action results from tool execution output.
511
- * When the agent calls tools like `edit_current_entity`, `save_current_entity`,
512
- * `navigate_to`, or `test_tool_by_id`, the result contains an action payload
513
- * that the SDK emits as a typed event for the host app to handle.
514
- */
515
- parseActionResult(t) {
516
- try {
517
- const e = typeof t == "string" ? JSON.parse(t) : t;
518
- if (!e || typeof e != "object") return;
519
- e.navigate_to ? this.emit("action:navigate", e) : e.action === "edit_entity" ? this.emit("action:edit_entity", e) : e.action === "save_entity" ? this.emit("action:save_entity", e) : e.action === "test_tool" && this.emit("action:test_tool", e);
520
- } catch {
521
- }
522
- }
523
- handleBroadcast(t) {
524
- const e = this.conversationManager.conversationId;
525
- switch (t.type) {
526
- case "status": {
527
- const s = t.status;
528
- this.emit("status", s), this.config.onStatusChange?.(s), s === "processing" && this.emit("typing:start", void 0);
529
- break;
530
- }
531
- case "error": {
532
- const s = new Error(t.message || "Unknown error");
533
- this.emit("error", s), this.config.onError?.(s);
534
- break;
535
- }
536
- case "message_start": {
537
- if (t.role === "assistant") {
538
- const s = this.messageStore.addStreamingMessage(e);
539
- this.emit("stream:start", { messageId: s.id }), this.emit("typing:start", void 0);
540
- }
541
- break;
542
- }
543
- case "message_update": {
544
- const s = t.delta || "", n = t.content || "";
545
- n ? this.messageStore.updateStreaming(n) : s && this.messageStore.appendToStreaming(s);
546
- const i = this.messageStore.streamingContent;
547
- this.emit("stream:update", { delta: s, content: i }), this.config.onStreamUpdate?.(s, i);
548
- break;
549
- }
550
- case "message_end": {
551
- const s = t.usage, n = this.messageStore.finalizeStreaming(
552
- s ? { tokensInput: s.tokens_input || 0, tokensOutput: s.tokens_output || 0 } : void 0
553
- );
554
- this.emit("typing:stop", void 0), n && (this.emit("stream:end", {
555
- messageId: n.id,
556
- usage: s ? { tokensInput: s.tokens_input || 0, tokensOutput: s.tokens_output || 0 } : void 0
557
- }), this.emit("message:received", n), this.config.onMessage?.(n));
558
- break;
559
- }
560
- case "message_queued": {
561
- const s = this.messageStore.messages.findLast((n) => n.status === "sending");
562
- s && this.messageStore.confirmOptimistic(s.id);
563
- break;
564
- }
565
- case "tool_execution_start": {
566
- this.emit("tool:start", {
567
- toolName: t.tool_name || "",
568
- args: t.args || {}
569
- }), this.emit("status", t.type);
570
- break;
571
- }
572
- case "tool_execution_end": {
573
- this.emit("tool:end", {
574
- toolName: t.tool_name || "",
575
- result: t.result,
576
- isError: !!t.is_error
577
- }), this.emit("status", t.type), this.parseActionResult(t.result);
578
- break;
579
- }
580
- case "tool_execution_update":
581
- case "agent_start":
582
- case "agent_end":
583
- case "turn_start":
584
- case "turn_end":
585
- case "auto_retry_start":
586
- case "auto_retry_end":
587
- case "cancelled":
588
- this.emit("status", t.type);
589
- break;
590
- }
591
- }
592
- }
593
- export {
594
- g as A,
595
- p as C,
596
- O as D,
597
- m as E,
598
- v as M,
599
- U as a,
600
- y as b,
601
- b as c,
602
- S as d,
603
- C as e,
604
- I as f,
605
- E as g,
606
- k as h,
607
- w as i,
608
- A as j,
609
- T as k,
610
- M as l
611
- };
@@ -1 +0,0 @@
1
- "use strict";class p{identifier;callbacks={};sendFn;constructor(t,e){this.identifier=t,this.sendFn=e}onReceived(t){return this.callbacks.received=t,this}onConnected(t){return this.callbacks.connected=t,this}onDisconnected(t){return this.callbacks.disconnected=t,this}onRejected(t){return this.callbacks.rejected=t,this}perform(t,e={}){this.sendFn({action:t,...e})}_notifyReceived(t){this.callbacks.received?.(t)}_notifyConnected(){this.callbacks.connected?.()}_notifyDisconnected(){this.callbacks.disconnected?.()}_notifyRejected(){this.callbacks.rejected?.()}}class l{ws=null;url;subscriptions=new Map;welcomePromise=null;pendingSubscriptions=new Map;constructor(t){this.url=t}connect(){return new Promise((t,e)=>{this.welcomePromise={resolve:t,reject:e},this.ws=new WebSocket(this.url),this.ws.onopen=()=>{},this.ws.onmessage=s=>{this.handleMessage(s)},this.ws.onerror=()=>{const s=new Error("WebSocket connection error");this.welcomePromise?.reject(s),this.welcomePromise=null},this.ws.onclose=()=>{this.subscriptions.forEach(s=>s._notifyDisconnected())}})}disconnect(){this.ws&&(this.ws.onclose=null,this.ws.close(),this.ws=null),this.subscriptions.forEach(t=>t._notifyDisconnected()),this.subscriptions.clear()}subscribe(t){const e=JSON.stringify(t),s=new p(e,n=>{this.send({command:"message",identifier:e,data:JSON.stringify(n)})});return this.subscriptions.set(e,s),this.send({command:"subscribe",identifier:e}),s}subscribeAsync(t){const e=this.subscribe(t),s=e.identifier;return new Promise((n,i)=>{this.pendingSubscriptions.set(s,{resolve:()=>n(e),reject:i}),setTimeout(()=>{this.pendingSubscriptions.has(s)&&(this.pendingSubscriptions.delete(s),i(new Error(`Subscription timeout for ${s}`)))},1e4)})}unsubscribe(t){this.send({command:"unsubscribe",identifier:t}),this.subscriptions.delete(t)}perform(t,e,s={}){this.send({command:"message",identifier:t,data:JSON.stringify({action:e,...s})})}get isConnected(){return this.ws?.readyState===WebSocket.OPEN}send(t){this.ws?.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(t))}handleMessage(t){let e;try{e=JSON.parse(t.data)}catch{return}switch(e.type){case"welcome":this.welcomePromise?.resolve(),this.welcomePromise=null;break;case"ping":break;case"confirm_subscription":{const s=e.identifier;this.subscriptions.get(s)?._notifyConnected();const i=this.pendingSubscriptions.get(s);i&&(i.resolve(),this.pendingSubscriptions.delete(s));break}case"reject_subscription":{const s=e.identifier;this.subscriptions.get(s)?._notifyRejected(),this.subscriptions.delete(s);const i=this.pendingSubscriptions.get(s);i&&(i.reject(new Error(`Subscription rejected: ${s}`)),this.pendingSubscriptions.delete(s));break}case"disconnect":this.subscriptions.forEach(s=>s._notifyDisconnected());break;default:{e.identifier&&e.message!==void 0&&this.subscriptions.get(e.identifier)?._notifyReceived(e.message);break}}}}class u{handlers=new Map;on(t,e){return this.handlers.has(t)||this.handlers.set(t,new Set),this.handlers.get(t).add(e),()=>this.off(t,e)}off(t,e){this.handlers.get(t)?.delete(e)}emit(t,e){this.handlers.get(t)?.forEach(s=>{try{s(e)}catch(n){console.error(`Error in event handler for "${t}":`,n)}})}removeAllListeners(){this.handlers.clear()}}const y=1e3,S=10,w=400,T=600,E="#6366f1",I=12,k="system-ui, -apple-system, sans-serif",A="Type a message...",C="bottom-right",M="light",O={x:20,y:20},d="aikaara_conversation_id";class f extends u{client;config;state="disconnected";reconnectAttempt=0;reconnectTimer=null;constructor(t){super(),this.config=t;const e=this.buildWsUrl(t.baseUrl,t.userToken);this.client=new l(e)}async connect(){this.setState("connecting");try{await this.client.connect(),this.setState("connected"),this.reconnectAttempt=0}catch(t){if(this.setState("disconnected"),this.config.reconnect!==!1)this.scheduleReconnect();else throw t}}async disconnect(){this.clearReconnectTimer(),this.client.disconnect(),this.setState("disconnected")}subscribeToConversation(t){return this.client.subscribeAsync({channel:"ConversationChannel",conversation_id:t})}sendMessage(t,e){const s=JSON.stringify({channel:"ConversationChannel",conversation_id:t});this.client.perform(s,"send_message",{content:e})}sendUserEvent(t,e,s,n){const i=JSON.stringify({channel:"ConversationChannel",conversation_id:t});this.client.perform(i,"send_user_event",{event_key:e,...s&&{value:s},...n&&{source:n}})}get connectionState(){return this.state}setState(t){this.state!==t&&(this.state=t,this.emit("connection:state",t))}scheduleReconnect(){const t=this.config.maxReconnectAttempts??S;if(this.reconnectAttempt>=t){this.emit("error",new Error("Max reconnection attempts reached"));return}this.setState("reconnecting");const s=(this.config.reconnectInterval??y)*Math.pow(2,this.reconnectAttempt);this.reconnectAttempt++,this.reconnectTimer=setTimeout(async()=>{try{const n=this.buildWsUrl(this.config.baseUrl,this.config.userToken);this.client=new l(n),await this.connect()}catch{}},s)}clearReconnectTimer(){this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null)}buildWsUrl(t,e){if(this.config.wsUrl){const n=this.config.wsUrl.includes("?")?"&":"?";return`${this.config.wsUrl}${n}token=${e}`}return`${t.replace(/^http/,"ws")}/cable?token=${e}`}}class _{baseUrl;apiKey;authToken;userToken;constructor(t,e,s,n){this.baseUrl=t,this.userToken=e,this.apiKey=s,this.authToken=n}async createConversation(t){const e={conversation:{...t.extUid&&{ext_uid:t.extUid},...t.systemPromptId&&{system_prompt_id:t.systemPromptId},...t.channel&&{channel:t.channel},...t.title&&{title:t.title}}};return this.request("POST","/api/v1/conversations",e)}async updateContext(t,e){const s=this.authToken?`/dashboard/sidekick_conversations/${t}`:`/api/v1/conversations/${t}`;await this.request("PATCH",s,e)}async getMessages(t){return(await this.request("GET",`/api/v1/conversations/${t}/messages`)).map(this.mapMessage)}mapMessage(t){return{id:String(t.id),conversationId:String(t.conversation_id),role:t.role,content:t.content||"",toolCalls:t.tool_calls?.map(e=>({id:e.id,type:e.type,function:e.function})),toolCallResults:t.tool_call_results,tokensInput:t.tokens_input,tokensOutput:t.tokens_output,metadata:t.metadata,createdAt:t.created_at,status:"complete"}}async request(t,e,s){const n={"Content-Type":"application/json",Accept:"application/json"};this.apiKey&&(n["X-Api-Key"]=this.apiKey),this.authToken&&(n.Authorization=`Bearer ${this.authToken}`);const i=`${this.baseUrl}${e}`,g={method:t,headers:n};s&&(g.body=JSON.stringify(s));const a=await fetch(i,g);if(!a.ok){const c=await a.text();let h;try{const m=JSON.parse(c);h=m.error||m.message||c}catch{h=c}throw new Error(`API error ${a.status}: ${h}`)}const r=await a.json();if(r&&typeof r=="object"&&"success"in r){if(!r.success)throw new Error(`API error: ${r.message||"Request failed"}`);return r.data}return r}}class b{_messages=[];optimisticCounter=0;get messages(){return[...this._messages]}addOptimistic(t,e,s){const n={id:`optimistic_${++this.optimisticCounter}`,conversationId:s,role:t,content:e,createdAt:new Date().toISOString(),status:"sending"};return this._messages.push(n),n}confirmOptimistic(t){const e=this._messages.find(s=>s.id===t);e&&(e.status="sent")}addStreamingMessage(t){const e={id:`streaming_${Date.now()}`,conversationId:t,role:"assistant",content:"",createdAt:new Date().toISOString(),status:"streaming"};return this._messages.push(e),e}updateStreaming(t){const e=this._messages.findLast(s=>s.status==="streaming");e&&(e.content=t)}appendToStreaming(t){const e=this._messages.findLast(s=>s.status==="streaming");e&&(e.content+=t)}get streamingContent(){return this._messages.findLast(e=>e.status==="streaming")?.content||""}finalizeStreaming(t){const e=this._messages.findLast(s=>s.status==="streaming");return e&&(e.status="complete",t&&(e.tokensInput=t.tokensInput,e.tokensOutput=t.tokensOutput)),e}addMessage(t){this._messages.push(t)}setMessages(t){this._messages=[...t]}clear(){this._messages=[]}}class v{_conversationId;persist;constructor(t,e=!0){this.persist=e,this._conversationId=t||this.loadFromStorage()}get conversationId(){return this._conversationId}set conversationId(t){this._conversationId=t,this.persist&&t&&this.saveToStorage(t)}clear(){if(this._conversationId=null,this.persist)try{localStorage.removeItem(d)}catch{}}loadFromStorage(){if(!this.persist)return null;try{return localStorage.getItem(d)}catch{return null}}saveToStorage(t){try{localStorage.setItem(d,t)}catch{}}}class U extends u{connection;api;messageStore;conversationManager;subscription=null;config;constructor(t){super(),this.config=t,this.connection=new f(t),this.api=new _(t.baseUrl,t.userToken,t.apiKey,t.authToken),this.messageStore=new b,this.conversationManager=new v(t.conversationId),this.connection.on("connection:state",e=>{this.emit("connection:state",e),this.config.onConnectionStateChange?.(e)}),this.connection.on("error",e=>{this.emit("error",e),this.config.onError?.(e)})}async connect(){if(await this.connection.connect(),!this.conversationManager.conversationId){const t=await this.api.createConversation({systemPromptId:this.config.systemPromptId,channel:this.config.channel||"widget",extUid:this.config.extUid});this.conversationManager.conversationId=String(t.id)}this.subscription=await this.connection.subscribeToConversation(this.conversationManager.conversationId),this.subscription.onReceived(t=>{this.handleBroadcast(t)}),await this.loadHistory()}async sendMessage(t){const e=this.conversationManager.conversationId;if(!e)throw new Error("No active conversation");const s=this.messageStore.addOptimistic("user",t,e);this.emit("message:sent",s),this.config.onMessage?.(s),this.connection.sendMessage(e,t)}async sendUserEvent(t,e,s){const n=this.conversationManager.conversationId;if(!n)throw new Error("No active conversation");this.connection.sendUserEvent(n,t,e,s)}async loadHistory(){const t=this.conversationManager.conversationId;if(!t)return[];try{const e=await this.api.getMessages(t);return this.messageStore.setMessages(e),e}catch{return[]}}get messages(){return this.messageStore.messages}get conversationId(){return this.conversationManager.conversationId}get isConnected(){return this.connection.connectionState==="connected"}async setContext(t){const e=this.conversationManager.conversationId;e&&await this.api.updateContext(e,{current_page:t.currentPage,entity_type:t.entityType,entity_id:t.entityId,project_id:t.projectId,available_routes:t.availableRoutes,custom_context:t.custom})}async disconnect(){this.subscription&&(this.subscription=null),await this.connection.disconnect()}parseActionResult(t){try{const e=typeof t=="string"?JSON.parse(t):t;if(!e||typeof e!="object")return;e.navigate_to?this.emit("action:navigate",e):e.action==="edit_entity"?this.emit("action:edit_entity",e):e.action==="save_entity"?this.emit("action:save_entity",e):e.action==="test_tool"&&this.emit("action:test_tool",e)}catch{}}handleBroadcast(t){const e=this.conversationManager.conversationId;switch(t.type){case"status":{const s=t.status;this.emit("status",s),this.config.onStatusChange?.(s),s==="processing"&&this.emit("typing:start",void 0);break}case"error":{const s=new Error(t.message||"Unknown error");this.emit("error",s),this.config.onError?.(s);break}case"message_start":{if(t.role==="assistant"){const s=this.messageStore.addStreamingMessage(e);this.emit("stream:start",{messageId:s.id}),this.emit("typing:start",void 0)}break}case"message_update":{const s=t.delta||"",n=t.content||"";n?this.messageStore.updateStreaming(n):s&&this.messageStore.appendToStreaming(s);const i=this.messageStore.streamingContent;this.emit("stream:update",{delta:s,content:i}),this.config.onStreamUpdate?.(s,i);break}case"message_end":{const s=t.usage,n=this.messageStore.finalizeStreaming(s?{tokensInput:s.tokens_input||0,tokensOutput:s.tokens_output||0}:void 0);this.emit("typing:stop",void 0),n&&(this.emit("stream:end",{messageId:n.id,usage:s?{tokensInput:s.tokens_input||0,tokensOutput:s.tokens_output||0}:void 0}),this.emit("message:received",n),this.config.onMessage?.(n));break}case"message_queued":{const s=this.messageStore.messages.findLast(n=>n.status==="sending");s&&this.messageStore.confirmOptimistic(s.id);break}case"tool_execution_start":{this.emit("tool:start",{toolName:t.tool_name||"",args:t.args||{}}),this.emit("status",t.type);break}case"tool_execution_end":{this.emit("tool:end",{toolName:t.tool_name||"",result:t.result,isError:!!t.is_error}),this.emit("status",t.type),this.parseActionResult(t.result);break}case"tool_execution_update":case"agent_start":case"agent_end":case"turn_start":case"turn_end":case"auto_retry_start":case"auto_retry_end":case"cancelled":this.emit("status",t.type);break}}}exports.ActionCableClient=l;exports.AikaaraChatClient=U;exports.ApiClient=_;exports.ChannelSubscription=p;exports.ConnectionManager=f;exports.ConversationManager=v;exports.DEFAULT_BORDER_RADIUS=I;exports.DEFAULT_FONT_FAMILY=k;exports.DEFAULT_OFFSET=O;exports.DEFAULT_PLACEHOLDER=A;exports.DEFAULT_POSITION=C;exports.DEFAULT_PRIMARY_COLOR=E;exports.DEFAULT_THEME=M;exports.DEFAULT_WIDGET_HEIGHT=T;exports.DEFAULT_WIDGET_WIDTH=w;exports.EventEmitter=u;exports.MessageStore=b;