@usecrow/client 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,836 @@
1
+ // src/identity.ts
2
+ var IdentityManager = class {
3
+ constructor() {
4
+ this.state = {
5
+ token: null,
6
+ metadata: {},
7
+ isVerified: false
8
+ };
9
+ this.listeners = /* @__PURE__ */ new Set();
10
+ }
11
+ /**
12
+ * Identify the current user with a JWT token
13
+ */
14
+ identify(options) {
15
+ const { token, ...metadata } = options;
16
+ this.state = {
17
+ token,
18
+ metadata,
19
+ isVerified: false
20
+ // Will be set when server confirms
21
+ };
22
+ this.notify();
23
+ console.log("[Crow] User identified");
24
+ }
25
+ /**
26
+ * Update verification status (called when server confirms)
27
+ */
28
+ setVerified(isVerified) {
29
+ this.state = { ...this.state, isVerified };
30
+ this.notify();
31
+ }
32
+ /**
33
+ * Reset user identity (call on logout)
34
+ */
35
+ reset() {
36
+ this.state = {
37
+ token: null,
38
+ metadata: {},
39
+ isVerified: false
40
+ };
41
+ this.notify();
42
+ console.log("[Crow] User reset");
43
+ }
44
+ /**
45
+ * Get current identity token
46
+ */
47
+ getToken() {
48
+ return this.state.token;
49
+ }
50
+ /**
51
+ * Get current identity state
52
+ */
53
+ getState() {
54
+ return { ...this.state };
55
+ }
56
+ /**
57
+ * Check if user is identified
58
+ */
59
+ isIdentified() {
60
+ return this.state.token !== null;
61
+ }
62
+ /**
63
+ * Check if user is verified
64
+ */
65
+ isVerified() {
66
+ return this.state.isVerified;
67
+ }
68
+ /**
69
+ * Subscribe to identity changes
70
+ */
71
+ subscribe(callback) {
72
+ this.listeners.add(callback);
73
+ return () => this.listeners.delete(callback);
74
+ }
75
+ notify() {
76
+ const state = this.getState();
77
+ for (const listener of this.listeners) {
78
+ listener(state);
79
+ }
80
+ }
81
+ };
82
+
83
+ // src/tools.ts
84
+ var ToolManager = class {
85
+ constructor() {
86
+ this.handlers = {};
87
+ }
88
+ /**
89
+ * Register client-side tool handlers
90
+ */
91
+ register(tools) {
92
+ for (const [name, handler] of Object.entries(tools)) {
93
+ if (typeof handler === "function") {
94
+ this.handlers[name] = handler;
95
+ console.log(`[Crow] Registered client tool: ${name}`);
96
+ } else {
97
+ console.warn(`[Crow] Skipping ${name}: handler is not a function`);
98
+ }
99
+ }
100
+ }
101
+ /**
102
+ * Unregister a tool handler
103
+ */
104
+ unregister(name) {
105
+ delete this.handlers[name];
106
+ console.log(`[Crow] Unregistered client tool: ${name}`);
107
+ }
108
+ /**
109
+ * Check if a tool is registered
110
+ */
111
+ has(name) {
112
+ return name in this.handlers;
113
+ }
114
+ /**
115
+ * Get all registered tool names
116
+ */
117
+ getRegisteredTools() {
118
+ return Object.keys(this.handlers);
119
+ }
120
+ /**
121
+ * Execute a client-side tool
122
+ */
123
+ async execute(name, args) {
124
+ const handler = this.handlers[name];
125
+ if (!handler) {
126
+ console.warn(`[Crow] No handler registered for tool: ${name}`);
127
+ return {
128
+ status: "error",
129
+ error: `No handler registered for tool: ${name}`
130
+ };
131
+ }
132
+ try {
133
+ console.log(`[Crow] Executing client tool: ${name}`, args);
134
+ const result = await handler(args);
135
+ console.log(`[Crow] Tool ${name} completed:`, result);
136
+ return result;
137
+ } catch (error) {
138
+ const errorMessage = error instanceof Error ? error.message : String(error);
139
+ console.error(`[Crow] Tool ${name} failed:`, error);
140
+ return {
141
+ status: "error",
142
+ error: errorMessage
143
+ };
144
+ }
145
+ }
146
+ };
147
+
148
+ // src/conversations.ts
149
+ var STORAGE_KEY_PREFIX = "crow_conv_";
150
+ var ConversationManager = class {
151
+ constructor(productId, apiUrl) {
152
+ this.currentId = null;
153
+ this.productId = productId;
154
+ this.apiUrl = apiUrl;
155
+ this.currentId = this.loadFromStorage();
156
+ }
157
+ /**
158
+ * Get localStorage key for this product
159
+ */
160
+ getStorageKey() {
161
+ return `${STORAGE_KEY_PREFIX}${this.productId}`;
162
+ }
163
+ /**
164
+ * Load conversation ID from localStorage
165
+ */
166
+ loadFromStorage() {
167
+ try {
168
+ return localStorage.getItem(this.getStorageKey());
169
+ } catch {
170
+ return null;
171
+ }
172
+ }
173
+ /**
174
+ * Save conversation ID to localStorage
175
+ */
176
+ saveToStorage(id) {
177
+ try {
178
+ localStorage.setItem(this.getStorageKey(), id);
179
+ } catch {
180
+ }
181
+ }
182
+ /**
183
+ * Clear conversation ID from localStorage
184
+ */
185
+ clearStorage() {
186
+ try {
187
+ localStorage.removeItem(this.getStorageKey());
188
+ } catch {
189
+ }
190
+ }
191
+ /**
192
+ * Get current conversation ID
193
+ */
194
+ getCurrentId() {
195
+ return this.currentId;
196
+ }
197
+ /**
198
+ * Set current conversation ID
199
+ */
200
+ setCurrentId(id) {
201
+ this.currentId = id;
202
+ if (id) {
203
+ this.saveToStorage(id);
204
+ } else {
205
+ this.clearStorage();
206
+ }
207
+ }
208
+ /**
209
+ * Check if there's a restored conversation
210
+ */
211
+ hasRestoredConversation() {
212
+ return this.currentId !== null;
213
+ }
214
+ /**
215
+ * Clear current conversation (start new)
216
+ */
217
+ clear() {
218
+ this.currentId = null;
219
+ this.clearStorage();
220
+ }
221
+ /**
222
+ * Fetch list of conversations for verified user
223
+ */
224
+ async getConversations(identityToken) {
225
+ try {
226
+ const response = await fetch(
227
+ `${this.apiUrl}/api/chat/conversations?product_id=${this.productId}&identity_token=${encodeURIComponent(identityToken)}`
228
+ );
229
+ if (!response.ok) {
230
+ throw new Error(`HTTP error: ${response.status}`);
231
+ }
232
+ const data = await response.json();
233
+ return data.conversations || [];
234
+ } catch (error) {
235
+ console.error("[Crow] Failed to load conversations:", error);
236
+ return [];
237
+ }
238
+ }
239
+ /**
240
+ * Load conversation history for verified user
241
+ */
242
+ async loadHistory(conversationId, identityToken) {
243
+ try {
244
+ const response = await fetch(
245
+ `${this.apiUrl}/api/chat/conversations/${conversationId}/history?product_id=${this.productId}&identity_token=${encodeURIComponent(identityToken)}`
246
+ );
247
+ if (!response.ok) {
248
+ throw new Error(`HTTP error: ${response.status}`);
249
+ }
250
+ const data = await response.json();
251
+ return this.parseHistoryMessages(data.messages || []);
252
+ } catch (error) {
253
+ console.error("[Crow] Failed to load conversation history:", error);
254
+ return [];
255
+ }
256
+ }
257
+ /**
258
+ * Load conversation history for anonymous user
259
+ */
260
+ async loadAnonymousHistory(conversationId) {
261
+ try {
262
+ const response = await fetch(
263
+ `${this.apiUrl}/api/chat/conversations/${conversationId}/history/anonymous?product_id=${this.productId}`
264
+ );
265
+ if (!response.ok) {
266
+ throw new Error(`HTTP error: ${response.status}`);
267
+ }
268
+ const data = await response.json();
269
+ return this.parseHistoryMessages(data.messages || []);
270
+ } catch (error) {
271
+ console.error("[Crow] Failed to load anonymous conversation history:", error);
272
+ return [];
273
+ }
274
+ }
275
+ /**
276
+ * Parse history messages from API format
277
+ */
278
+ parseHistoryMessages(messages) {
279
+ return messages.map((msg, idx) => ({
280
+ id: `history-${idx}`,
281
+ content: this.parseContent(msg.content),
282
+ role: msg.role === "assistant" ? "assistant" : "user",
283
+ timestamp: /* @__PURE__ */ new Date()
284
+ }));
285
+ }
286
+ /**
287
+ * Parse structured content (with thinking/text blocks) and extract just text
288
+ */
289
+ parseContent(content) {
290
+ try {
291
+ const parsed = JSON.parse(content);
292
+ if (Array.isArray(parsed)) {
293
+ const textBlock = parsed.find(
294
+ (b) => b.type === "text"
295
+ );
296
+ return textBlock?.text || content;
297
+ }
298
+ } catch {
299
+ }
300
+ if (content.includes("'type': 'text'")) {
301
+ const textBlockMatch = content.match(
302
+ /\{'text':\s*'((?:[^'\\]|\\.)*)'\s*,\s*'type':\s*'text'/
303
+ );
304
+ if (textBlockMatch) {
305
+ return textBlockMatch[1].replace(/\\n/g, "\n").replace(/\\'/g, "'");
306
+ }
307
+ }
308
+ return content;
309
+ }
310
+ };
311
+
312
+ // src/streaming.ts
313
+ function parseSSEData(data) {
314
+ if (data === "[DONE]") {
315
+ return { type: "done" };
316
+ }
317
+ try {
318
+ const parsed = JSON.parse(data);
319
+ switch (parsed.type) {
320
+ case "verification_status":
321
+ return {
322
+ type: "verification_status",
323
+ isVerified: parsed.is_verified === true
324
+ };
325
+ case "conversation_id":
326
+ return {
327
+ type: "conversation_id",
328
+ conversationId: parsed.conversation_id
329
+ };
330
+ case "thinking":
331
+ if (parsed.status === "complete") {
332
+ return { type: "thinking_complete" };
333
+ }
334
+ return null;
335
+ case "thinking_token":
336
+ return {
337
+ type: "thinking",
338
+ content: parsed.content || ""
339
+ };
340
+ case "content":
341
+ return {
342
+ type: "content",
343
+ text: parsed.content || "",
344
+ accumulated: ""
345
+ // Will be set by caller
346
+ };
347
+ case "citations":
348
+ return {
349
+ type: "citations",
350
+ citations: parsed.citations
351
+ };
352
+ case "error":
353
+ return {
354
+ type: "error",
355
+ message: parsed.message || "Unknown error"
356
+ };
357
+ case "tool_call_start":
358
+ return {
359
+ type: "tool_call_start",
360
+ toolName: parsed.tool_name,
361
+ arguments: parsed.arguments || {}
362
+ };
363
+ case "tool_call_complete":
364
+ return {
365
+ type: "tool_call_complete",
366
+ toolName: parsed.tool_name,
367
+ success: parsed.success
368
+ };
369
+ case "client_tool_call":
370
+ return {
371
+ type: "client_tool_call",
372
+ toolName: parsed.tool_name,
373
+ arguments: parsed.arguments || {}
374
+ };
375
+ case "workflow_started":
376
+ return {
377
+ type: "workflow_started",
378
+ name: parsed.name,
379
+ todos: parsed.todos
380
+ };
381
+ case "todo_updated":
382
+ return {
383
+ type: "workflow_todo_updated",
384
+ todoId: parsed.id,
385
+ status: parsed.status
386
+ };
387
+ case "workflow_ended":
388
+ return { type: "workflow_ended" };
389
+ case "workflow_complete_prompt":
390
+ return { type: "workflow_complete_prompt" };
391
+ default:
392
+ return null;
393
+ }
394
+ } catch {
395
+ console.error("[Crow] Failed to parse SSE data:", data);
396
+ return null;
397
+ }
398
+ }
399
+ function* parseSSEChunk(chunk) {
400
+ const lines = chunk.split("\n");
401
+ for (const line of lines) {
402
+ if (line.startsWith("data: ")) {
403
+ yield line.slice(6).trim();
404
+ }
405
+ }
406
+ }
407
+ async function* streamResponse(response, signal) {
408
+ const reader = response.body?.getReader();
409
+ if (!reader) {
410
+ throw new Error("Response body is not readable");
411
+ }
412
+ const decoder = new TextDecoder();
413
+ let accumulatedContent = "";
414
+ try {
415
+ while (true) {
416
+ if (signal?.aborted) {
417
+ reader.cancel();
418
+ return;
419
+ }
420
+ const { done, value } = await reader.read();
421
+ if (done) break;
422
+ const chunk = decoder.decode(value);
423
+ for (const data of parseSSEChunk(chunk)) {
424
+ const event = parseSSEData(data);
425
+ if (event) {
426
+ if (event.type === "content") {
427
+ accumulatedContent += event.text;
428
+ yield { ...event, accumulated: accumulatedContent };
429
+ } else {
430
+ yield event;
431
+ }
432
+ if (event.type === "done") {
433
+ return;
434
+ }
435
+ }
436
+ }
437
+ }
438
+ } finally {
439
+ reader.releaseLock();
440
+ }
441
+ }
442
+
443
+ // src/CrowClient.ts
444
+ var DEFAULT_API_URL = "https://api.usecrow.org";
445
+ var DEFAULT_MODEL = "claude-sonnet-4-20250514";
446
+ var CrowClient = class {
447
+ constructor(config) {
448
+ this.context = {};
449
+ this.abortController = null;
450
+ this.callbacks = {};
451
+ // Message state
452
+ this._messages = [];
453
+ this.messageListeners = /* @__PURE__ */ new Set();
454
+ // Loading state
455
+ this._isLoading = false;
456
+ this.loadingListeners = /* @__PURE__ */ new Set();
457
+ this.config = {
458
+ productId: config.productId,
459
+ apiUrl: config.apiUrl || DEFAULT_API_URL,
460
+ model: config.model || DEFAULT_MODEL
461
+ };
462
+ this.identity = new IdentityManager();
463
+ this.tools = new ToolManager();
464
+ this.conversations = new ConversationManager(
465
+ this.config.productId,
466
+ this.config.apiUrl
467
+ );
468
+ this.identity.subscribe((state) => {
469
+ this.callbacks.onVerificationStatus?.(state.isVerified);
470
+ });
471
+ }
472
+ // ============================================================================
473
+ // Configuration
474
+ // ============================================================================
475
+ /**
476
+ * Get current product ID
477
+ */
478
+ get productId() {
479
+ return this.config.productId;
480
+ }
481
+ /**
482
+ * Get current API URL
483
+ */
484
+ get apiUrl() {
485
+ return this.config.apiUrl;
486
+ }
487
+ /**
488
+ * Get/set current model
489
+ */
490
+ get model() {
491
+ return this.config.model;
492
+ }
493
+ set model(value) {
494
+ this.config.model = value;
495
+ }
496
+ // ============================================================================
497
+ // Event Callbacks
498
+ // ============================================================================
499
+ /**
500
+ * Register event callbacks
501
+ */
502
+ on(callbacks) {
503
+ this.callbacks = { ...this.callbacks, ...callbacks };
504
+ }
505
+ // ============================================================================
506
+ // Identity
507
+ // ============================================================================
508
+ /**
509
+ * Identify the current user with a JWT token
510
+ */
511
+ identify(options) {
512
+ this.identity.identify(options);
513
+ }
514
+ /**
515
+ * Reset user identity (call on logout)
516
+ */
517
+ resetUser() {
518
+ this.identity.reset();
519
+ this.clearMessages();
520
+ }
521
+ /**
522
+ * Check if user is identified
523
+ */
524
+ isIdentified() {
525
+ return this.identity.isIdentified();
526
+ }
527
+ /**
528
+ * Check if user is verified by server
529
+ */
530
+ isVerified() {
531
+ return this.identity.isVerified();
532
+ }
533
+ // ============================================================================
534
+ // Tools
535
+ // ============================================================================
536
+ /**
537
+ * Register client-side tool handlers
538
+ */
539
+ registerTools(tools) {
540
+ this.tools.register(tools);
541
+ }
542
+ /**
543
+ * Unregister a tool handler
544
+ */
545
+ unregisterTool(name) {
546
+ this.tools.unregister(name);
547
+ }
548
+ /**
549
+ * Get list of registered tool names
550
+ */
551
+ getRegisteredTools() {
552
+ return this.tools.getRegisteredTools();
553
+ }
554
+ // ============================================================================
555
+ // Context
556
+ // ============================================================================
557
+ /**
558
+ * Set context data to be sent with messages
559
+ */
560
+ setContext(data) {
561
+ this.context = { ...this.context, ...data };
562
+ }
563
+ /**
564
+ * Clear context data
565
+ */
566
+ clearContext() {
567
+ this.context = {};
568
+ }
569
+ // ============================================================================
570
+ // Messages
571
+ // ============================================================================
572
+ /**
573
+ * Get current messages
574
+ */
575
+ get messages() {
576
+ return [...this._messages];
577
+ }
578
+ /**
579
+ * Check if currently loading/streaming
580
+ */
581
+ get isLoading() {
582
+ return this._isLoading;
583
+ }
584
+ /**
585
+ * Subscribe to message changes
586
+ */
587
+ onMessages(callback) {
588
+ this.messageListeners.add(callback);
589
+ return () => this.messageListeners.delete(callback);
590
+ }
591
+ /**
592
+ * Subscribe to loading state changes
593
+ */
594
+ onLoading(callback) {
595
+ this.loadingListeners.add(callback);
596
+ return () => this.loadingListeners.delete(callback);
597
+ }
598
+ /**
599
+ * Clear all messages and start new conversation
600
+ */
601
+ clearMessages() {
602
+ this._messages = [];
603
+ this.conversations.clear();
604
+ this.notifyMessages();
605
+ }
606
+ /**
607
+ * Load messages from history
608
+ */
609
+ loadMessages(messages) {
610
+ this._messages = messages;
611
+ this.notifyMessages();
612
+ }
613
+ notifyMessages() {
614
+ const messages = this.messages;
615
+ for (const listener of this.messageListeners) {
616
+ listener(messages);
617
+ }
618
+ }
619
+ setLoading(isLoading) {
620
+ this._isLoading = isLoading;
621
+ for (const listener of this.loadingListeners) {
622
+ listener(isLoading);
623
+ }
624
+ }
625
+ addMessage(message) {
626
+ this._messages = [...this._messages, message];
627
+ this.notifyMessages();
628
+ this.callbacks.onMessage?.(message);
629
+ }
630
+ updateMessage(id, updates) {
631
+ this._messages = this._messages.map(
632
+ (msg) => msg.id === id ? { ...msg, ...updates } : msg
633
+ );
634
+ this.notifyMessages();
635
+ this.callbacks.onMessageUpdate?.(id, updates);
636
+ }
637
+ generateMessageId(prefix) {
638
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
639
+ }
640
+ // ============================================================================
641
+ // Conversations
642
+ // ============================================================================
643
+ /**
644
+ * Get current conversation ID
645
+ */
646
+ get conversationId() {
647
+ return this.conversations.getCurrentId();
648
+ }
649
+ /**
650
+ * Set current conversation ID
651
+ */
652
+ set conversationId(id) {
653
+ this.conversations.setCurrentId(id);
654
+ }
655
+ /**
656
+ * Get list of conversations for verified user
657
+ */
658
+ async getConversations() {
659
+ const token = this.identity.getToken();
660
+ if (!token) {
661
+ console.warn("[Crow] Cannot get conversations: user not identified");
662
+ return [];
663
+ }
664
+ return this.conversations.getConversations(token);
665
+ }
666
+ /**
667
+ * Load conversation history
668
+ */
669
+ async loadHistory(conversationId) {
670
+ const token = this.identity.getToken();
671
+ if (token) {
672
+ return this.conversations.loadHistory(conversationId, token);
673
+ }
674
+ return this.conversations.loadAnonymousHistory(conversationId);
675
+ }
676
+ /**
677
+ * Switch to a different conversation
678
+ */
679
+ async switchConversation(conversationId) {
680
+ const messages = await this.loadHistory(conversationId);
681
+ this.conversations.setCurrentId(conversationId);
682
+ this.loadMessages(messages);
683
+ }
684
+ // ============================================================================
685
+ // Messaging
686
+ // ============================================================================
687
+ /**
688
+ * Send a message and receive streaming response
689
+ * Returns an async generator of stream events
690
+ */
691
+ async *sendMessage(content) {
692
+ if (!content.trim()) {
693
+ return;
694
+ }
695
+ const userMsgId = this.generateMessageId("user");
696
+ this.addMessage({
697
+ id: userMsgId,
698
+ content,
699
+ role: "user",
700
+ timestamp: /* @__PURE__ */ new Date()
701
+ });
702
+ const botMsgId = this.generateMessageId("assistant");
703
+ this.addMessage({
704
+ id: botMsgId,
705
+ content: "",
706
+ role: "assistant",
707
+ timestamp: /* @__PURE__ */ new Date()
708
+ });
709
+ this.setLoading(true);
710
+ this.abortController = new AbortController();
711
+ let accumulatedContent = "";
712
+ let accumulatedThinking = "";
713
+ try {
714
+ const response = await fetch(`${this.config.apiUrl}/api/chat/message`, {
715
+ method: "POST",
716
+ headers: { "Content-Type": "application/json" },
717
+ body: JSON.stringify({
718
+ product_id: this.config.productId,
719
+ message: content,
720
+ conversation_id: this.conversations.getCurrentId(),
721
+ identity_token: this.identity.getToken(),
722
+ model: this.config.model,
723
+ context: Object.keys(this.context).length > 0 ? this.context : void 0
724
+ }),
725
+ signal: this.abortController.signal
726
+ });
727
+ if (!response.ok) {
728
+ throw new Error(`HTTP error: ${response.status}`);
729
+ }
730
+ for await (const event of streamResponse(response, this.abortController.signal)) {
731
+ switch (event.type) {
732
+ case "content":
733
+ accumulatedContent = event.accumulated;
734
+ this.updateMessage(botMsgId, { content: accumulatedContent });
735
+ break;
736
+ case "thinking":
737
+ accumulatedThinking += event.content;
738
+ this.updateMessage(botMsgId, { thinking: accumulatedThinking });
739
+ break;
740
+ case "thinking_complete":
741
+ this.updateMessage(botMsgId, { thinkingComplete: true });
742
+ break;
743
+ case "citations":
744
+ this.updateMessage(botMsgId, { citations: event.citations });
745
+ break;
746
+ case "verification_status":
747
+ this.identity.setVerified(event.isVerified);
748
+ break;
749
+ case "conversation_id":
750
+ this.conversations.setCurrentId(event.conversationId);
751
+ break;
752
+ case "client_tool_call":
753
+ await this.tools.execute(event.toolName, event.arguments);
754
+ this.callbacks.onToolCall?.(event);
755
+ break;
756
+ case "tool_call_start":
757
+ case "tool_call_complete":
758
+ this.callbacks.onToolCall?.(event);
759
+ break;
760
+ case "workflow_started":
761
+ case "workflow_todo_updated":
762
+ case "workflow_ended":
763
+ case "workflow_complete_prompt":
764
+ this.callbacks.onWorkflow?.(event);
765
+ break;
766
+ case "error":
767
+ this.updateMessage(botMsgId, { content: event.message });
768
+ this.callbacks.onError?.(new Error(event.message));
769
+ break;
770
+ }
771
+ yield event;
772
+ }
773
+ } catch (error) {
774
+ if (error instanceof Error && error.name === "AbortError") {
775
+ if (accumulatedContent) {
776
+ this.updateMessage(botMsgId, { content: accumulatedContent });
777
+ } else {
778
+ this._messages = this._messages.filter((msg) => msg.id !== botMsgId);
779
+ this.notifyMessages();
780
+ }
781
+ return;
782
+ }
783
+ console.error("[Crow] Error:", error);
784
+ this.updateMessage(botMsgId, {
785
+ content: "Sorry, I encountered an error. Please try again."
786
+ });
787
+ this.callbacks.onError?.(error instanceof Error ? error : new Error(String(error)));
788
+ } finally {
789
+ this.setLoading(false);
790
+ this.abortController = null;
791
+ }
792
+ }
793
+ /**
794
+ * Send a message and wait for complete response (non-streaming)
795
+ */
796
+ async send(content) {
797
+ let lastMessage = null;
798
+ for await (const event of this.sendMessage(content)) {
799
+ if (event.type === "done") {
800
+ break;
801
+ }
802
+ }
803
+ const messages = this.messages;
804
+ if (messages.length > 0) {
805
+ lastMessage = messages[messages.length - 1];
806
+ if (lastMessage.role === "assistant") {
807
+ return lastMessage;
808
+ }
809
+ }
810
+ return null;
811
+ }
812
+ /**
813
+ * Stop current generation
814
+ */
815
+ stop() {
816
+ if (this.abortController) {
817
+ this.abortController.abort();
818
+ this.setLoading(false);
819
+ }
820
+ }
821
+ // ============================================================================
822
+ // Cleanup
823
+ // ============================================================================
824
+ /**
825
+ * Destroy the client and clean up resources
826
+ */
827
+ destroy() {
828
+ this.stop();
829
+ this.messageListeners.clear();
830
+ this.loadingListeners.clear();
831
+ }
832
+ };
833
+
834
+ export { ConversationManager, CrowClient, IdentityManager, ToolManager, parseSSEChunk, parseSSEData, streamResponse };
835
+ //# sourceMappingURL=index.js.map
836
+ //# sourceMappingURL=index.js.map