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