@hastekit/hastekit-converse 0.0.4

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,1024 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var axios = require('axios');
6
+
7
+ // Message Roles
8
+ exports.Role = void 0;
9
+ (function (Role) {
10
+ Role["User"] = "user";
11
+ Role["Developer"] = "developer";
12
+ Role["System"] = "system";
13
+ Role["Assistant"] = "assistant";
14
+ })(exports.Role || (exports.Role = {}));
15
+ // Message Types
16
+ exports.MessageType = void 0;
17
+ (function (MessageType) {
18
+ MessageType["Message"] = "message";
19
+ MessageType["FunctionCall"] = "function_call";
20
+ MessageType["FunctionCallOutput"] = "function_call_output";
21
+ MessageType["Reasoning"] = "reasoning";
22
+ MessageType["ImageGenerationCall"] = "image_generation_call";
23
+ MessageType["FunctionCallApprovalResponse"] = "function_call_approval_response";
24
+ })(exports.MessageType || (exports.MessageType = {}));
25
+ // Content Types
26
+ exports.ContentType = void 0;
27
+ (function (ContentType) {
28
+ ContentType["InputText"] = "input_text";
29
+ ContentType["OutputText"] = "output_text";
30
+ ContentType["SummaryText"] = "summary_text";
31
+ ContentType["InputImage"] = "input_image";
32
+ })(exports.ContentType || (exports.ContentType = {}));
33
+ // Chunk Types
34
+ exports.ChunkType = void 0;
35
+ (function (ChunkType) {
36
+ ChunkType["ChunkTypeRunCreated"] = "run.created";
37
+ ChunkType["ChunkTypeRunInProgress"] = "run.in_progress";
38
+ ChunkType["ChunkTypeRunPaused"] = "run.paused";
39
+ ChunkType["ChunkTypeRunCompleted"] = "run.completed";
40
+ ChunkType["ChunkTypeResponseCreated"] = "response.created";
41
+ ChunkType["ChunkTypeResponseInProgress"] = "response.in_progress";
42
+ ChunkType["ChunkTypeResponseCompleted"] = "response.completed";
43
+ ChunkType["ChunkTypeOutputItemAdded"] = "response.output_item.added";
44
+ ChunkType["ChunkTypeOutputItemDone"] = "response.output_item.done";
45
+ ChunkType["ChunkTypeContentPartAdded"] = "response.content_part.added";
46
+ ChunkType["ChunkTypeContentPartDone"] = "response.content_part.done";
47
+ ChunkType["ChunkTypeOutputTextDelta"] = "response.output_text.delta";
48
+ ChunkType["ChunkTypeOutputTextDone"] = "response.output_text.done";
49
+ ChunkType["ChunkTypeFunctionCallArgumentsDelta"] = "response.function_call_arguments.delta";
50
+ ChunkType["ChunkTypeFunctionCallArgumentsDone"] = "response.function_call_arguments.done";
51
+ ChunkType["ChunkTypeReasoningSummaryPartAdded"] = "response.reasoning_summary_part.added";
52
+ ChunkType["ChunkTypeReasoningSummaryPartDone"] = "response.reasoning_summary_part.done";
53
+ ChunkType["ChunkTypeReasoningSummaryTextDelta"] = "response.reasoning_summary_text.delta";
54
+ ChunkType["ChunkTypeReasoningSummaryTextDone"] = "response.reasoning_summary_text.done";
55
+ // Image generation
56
+ ChunkType["ChunkTypeImageGenerationCallInProgress"] = "response.image_generation_call.in_progress";
57
+ ChunkType["ChunkTypeImageGenerationCallGenerating"] = "response.image_generation_call.generating";
58
+ ChunkType["ChunkTypeImageGenerationCallPartialImage"] = "response.image_generation_call.partial_image";
59
+ // Extra
60
+ ChunkType["ChunkTypeFunctionCallOutput"] = "function_call_output";
61
+ })(exports.ChunkType || (exports.ChunkType = {}));
62
+ // Type guards
63
+ function isEasyMessage(msg) {
64
+ return msg.type === exports.MessageType.Message && 'content' in msg && (typeof msg.content === 'string' || Array.isArray(msg.content));
65
+ }
66
+ function isInputMessage(msg) {
67
+ return msg.type === exports.MessageType.Message && 'content' in msg && Array.isArray(msg.content);
68
+ }
69
+ function isFunctionCallMessage(msg) {
70
+ return msg.type === exports.MessageType.FunctionCall;
71
+ }
72
+ function isFunctionCallOutputMessage(msg) {
73
+ return msg.type === exports.MessageType.FunctionCallOutput;
74
+ }
75
+ function isReasoningMessage(msg) {
76
+ return msg.type === exports.MessageType.Reasoning;
77
+ }
78
+ function isImageGenerationCallMessage(msg) {
79
+ return msg.type === exports.MessageType.ImageGenerationCall;
80
+ }
81
+
82
+ /**
83
+ * Processes streaming chunks from the LLM response.
84
+ * Builds up messages incrementally as chunks arrive.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * const processor = new ChunkProcessor(
89
+ * 'conv-123',
90
+ * 'thread-456',
91
+ * (conversation) => {
92
+ * // Update UI with new conversation state
93
+ * setConversation(conversation);
94
+ * }
95
+ * );
96
+ *
97
+ * // Process incoming chunks
98
+ * processor.processChunk(jsonData);
99
+ *
100
+ * // Get final conversation when done
101
+ * const finalConversation = processor.getConversation();
102
+ * ```
103
+ */
104
+ class ChunkProcessor {
105
+ constructor(conversationId, threadId, onChange) {
106
+ this.messages = [];
107
+ this.currentOutputItem = null;
108
+ this.conversation = {
109
+ conversation_id: conversationId,
110
+ thread_id: threadId,
111
+ message_id: '',
112
+ messages: [],
113
+ meta: {},
114
+ };
115
+ this._onChange = onChange;
116
+ }
117
+ /**
118
+ * Get all processed messages
119
+ */
120
+ getMessages() {
121
+ return this.messages;
122
+ }
123
+ /**
124
+ * Get the current conversation state
125
+ */
126
+ getConversation() {
127
+ return this.conversation;
128
+ }
129
+ emitChange() {
130
+ this.conversation.messages = [...this.messages];
131
+ this._onChange({ ...this.conversation });
132
+ }
133
+ /**
134
+ * Process a raw JSON chunk from the SSE stream
135
+ */
136
+ processChunk(data) {
137
+ try {
138
+ const chunk = JSON.parse(data);
139
+ this.handleChunk(chunk);
140
+ }
141
+ catch (e) {
142
+ console.error('Failed to parse chunk:', e, data);
143
+ }
144
+ }
145
+ handleChunk(chunk) {
146
+ switch (chunk.type) {
147
+ // Run lifecycle
148
+ case exports.ChunkType.ChunkTypeRunCreated:
149
+ case exports.ChunkType.ChunkTypeRunInProgress:
150
+ case exports.ChunkType.ChunkTypeRunCompleted:
151
+ case exports.ChunkType.ChunkTypeRunPaused:
152
+ this.conversation.meta.run_state = chunk.run_state;
153
+ this.conversation.message_id = chunk.run_state.id;
154
+ if (chunk.type !== exports.ChunkType.ChunkTypeRunCreated && chunk.type !== exports.ChunkType.ChunkTypeRunInProgress) {
155
+ this.emitChange();
156
+ }
157
+ break;
158
+ // Response lifecycle
159
+ case exports.ChunkType.ChunkTypeResponseCreated:
160
+ case exports.ChunkType.ChunkTypeResponseInProgress:
161
+ break;
162
+ case exports.ChunkType.ChunkTypeResponseCompleted:
163
+ if (chunk.response?.usage) {
164
+ this.conversation.meta.usage = chunk.response.usage;
165
+ this.emitChange();
166
+ }
167
+ break;
168
+ // Output item lifecycle
169
+ case exports.ChunkType.ChunkTypeOutputItemAdded:
170
+ this.handleOutputItemAdded(chunk);
171
+ break;
172
+ case exports.ChunkType.ChunkTypeOutputItemDone:
173
+ break;
174
+ // Content parts
175
+ case exports.ChunkType.ChunkTypeContentPartAdded:
176
+ this.handleContentPartAdded(chunk);
177
+ break;
178
+ case exports.ChunkType.ChunkTypeContentPartDone:
179
+ break;
180
+ // Text deltas
181
+ case exports.ChunkType.ChunkTypeOutputTextDelta:
182
+ this.handleOutputTextDelta(chunk);
183
+ break;
184
+ case exports.ChunkType.ChunkTypeOutputTextDone:
185
+ break;
186
+ // Reasoning summary
187
+ case exports.ChunkType.ChunkTypeReasoningSummaryPartAdded:
188
+ this.handleReasoningSummaryPartAdded(chunk);
189
+ break;
190
+ case exports.ChunkType.ChunkTypeReasoningSummaryPartDone:
191
+ break;
192
+ case exports.ChunkType.ChunkTypeReasoningSummaryTextDelta:
193
+ this.handleReasoningSummaryTextDelta(chunk);
194
+ break;
195
+ case exports.ChunkType.ChunkTypeReasoningSummaryTextDone:
196
+ break;
197
+ // Function calls
198
+ case exports.ChunkType.ChunkTypeFunctionCallArgumentsDelta:
199
+ this.handleFunctionCallArgumentsDelta(chunk);
200
+ break;
201
+ case exports.ChunkType.ChunkTypeFunctionCallArgumentsDone:
202
+ break;
203
+ case exports.ChunkType.ChunkTypeFunctionCallOutput:
204
+ this.handleFunctionCallOutput(chunk);
205
+ break;
206
+ // Image Generation Calls
207
+ case exports.ChunkType.ChunkTypeImageGenerationCallInProgress:
208
+ break;
209
+ case exports.ChunkType.ChunkTypeImageGenerationCallGenerating:
210
+ break;
211
+ case exports.ChunkType.ChunkTypeImageGenerationCallPartialImage:
212
+ this.handleImageGenerationCallPartialImage(chunk);
213
+ break;
214
+ }
215
+ }
216
+ handleOutputItemAdded(chunk) {
217
+ if (!chunk.item)
218
+ return;
219
+ switch (chunk.item.type) {
220
+ case "message":
221
+ this.currentOutputItem = {
222
+ id: chunk.item.id,
223
+ type: "message",
224
+ role: chunk.item.role || "assistant",
225
+ content: [],
226
+ };
227
+ break;
228
+ case "function_call":
229
+ this.currentOutputItem = {
230
+ id: chunk.item.id,
231
+ type: "function_call",
232
+ name: chunk.item.name || "",
233
+ call_id: chunk.item.call_id || "",
234
+ arguments: "",
235
+ };
236
+ break;
237
+ case "reasoning":
238
+ this.currentOutputItem = {
239
+ id: chunk.item.id,
240
+ type: "reasoning",
241
+ summary: [],
242
+ };
243
+ break;
244
+ case "image_generation_call":
245
+ this.currentOutputItem = {
246
+ id: chunk.item.id,
247
+ type: "image_generation_call",
248
+ status: chunk.item.status,
249
+ };
250
+ break;
251
+ }
252
+ if (this.currentOutputItem) {
253
+ this.messages.push(this.currentOutputItem);
254
+ this.emitChange();
255
+ }
256
+ }
257
+ handleContentPartAdded(chunk) {
258
+ if (!this.currentOutputItem || this.currentOutputItem.type !== "message")
259
+ return;
260
+ const message = this.currentOutputItem;
261
+ if (chunk.part?.type === exports.ContentType.OutputText) {
262
+ message.content = message.content || [];
263
+ message.content.push({
264
+ type: exports.ContentType.OutputText,
265
+ text: "",
266
+ });
267
+ this.emitChange();
268
+ }
269
+ }
270
+ handleOutputTextDelta(chunk) {
271
+ if (!this.currentOutputItem || this.currentOutputItem.type !== "message")
272
+ return;
273
+ const message = this.currentOutputItem;
274
+ const contents = message.content;
275
+ if (!contents?.length || !chunk.delta)
276
+ return;
277
+ const lastContent = contents[contents.length - 1];
278
+ if (lastContent && 'text' in lastContent) {
279
+ lastContent.text += chunk.delta;
280
+ this.emitChange();
281
+ }
282
+ }
283
+ handleReasoningSummaryPartAdded(chunk) {
284
+ if (!this.currentOutputItem || this.currentOutputItem.type !== "reasoning")
285
+ return;
286
+ const reasoning = this.currentOutputItem;
287
+ if (chunk.part?.type === exports.ContentType.SummaryText) {
288
+ reasoning.summary = reasoning.summary || [];
289
+ reasoning.summary.push({
290
+ type: exports.ContentType.SummaryText,
291
+ text: "",
292
+ });
293
+ this.emitChange();
294
+ }
295
+ }
296
+ handleReasoningSummaryTextDelta(chunk) {
297
+ if (!this.currentOutputItem || this.currentOutputItem.type !== "reasoning")
298
+ return;
299
+ const reasoning = this.currentOutputItem;
300
+ const summaries = reasoning.summary;
301
+ if (!summaries?.length || !chunk.delta)
302
+ return;
303
+ const lastSummary = summaries[summaries.length - 1];
304
+ if (lastSummary) {
305
+ lastSummary.text += chunk.delta;
306
+ this.emitChange();
307
+ }
308
+ }
309
+ handleFunctionCallArgumentsDelta(chunk) {
310
+ if (!this.currentOutputItem || this.currentOutputItem.type !== "function_call")
311
+ return;
312
+ const functionCall = this.currentOutputItem;
313
+ functionCall.arguments += chunk.delta || "";
314
+ this.emitChange();
315
+ }
316
+ handleFunctionCallOutput(chunk) {
317
+ this.currentOutputItem = chunk;
318
+ this.messages.push(this.currentOutputItem);
319
+ this.emitChange();
320
+ }
321
+ handleImageGenerationCallPartialImage(chunk) {
322
+ if (!this.currentOutputItem || this.currentOutputItem.type !== "image_generation_call")
323
+ return;
324
+ const image = this.currentOutputItem;
325
+ image.result = chunk.partial_image_b64;
326
+ image.quality = chunk.quality;
327
+ image.size = chunk.size;
328
+ image.output_format = chunk.output_format;
329
+ image.background = chunk.background;
330
+ this.emitChange();
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Streams Server-Sent Events (SSE) from a URL.
336
+ * Parses SSE frames and calls onChunk for each data payload.
337
+ *
338
+ * @param url - The URL to stream from
339
+ * @param requestOptions - Fetch request options
340
+ * @param callbacks - SSE event callbacks
341
+ * @param abortSignal - Optional signal to abort the stream
342
+ *
343
+ * @example
344
+ * ```ts
345
+ * await streamSSE(
346
+ * 'https://api.example.com/stream',
347
+ * { method: 'POST', body: JSON.stringify({ message: 'Hello' }) },
348
+ * {
349
+ * onChunk: (data) => console.log('Received:', data),
350
+ * onComplete: () => console.log('Done'),
351
+ * onError: (err) => console.error('Error:', err),
352
+ * }
353
+ * );
354
+ * ```
355
+ */
356
+ async function streamSSE(url, requestOptions, callbacks, abortSignal) {
357
+ try {
358
+ const response = await fetch(url, {
359
+ ...requestOptions,
360
+ signal: abortSignal,
361
+ });
362
+ if (!response.ok) {
363
+ throw new Error(`HTTP error! status: ${response.status}`);
364
+ }
365
+ if (!response.body) {
366
+ throw new Error('Response body is null');
367
+ }
368
+ const reader = response.body.getReader();
369
+ const decoder = new TextDecoder();
370
+ let buffer = '';
371
+ while (true) {
372
+ const { value, done } = await reader.read();
373
+ if (done)
374
+ break;
375
+ buffer += decoder.decode(value, { stream: true });
376
+ // Parse SSE frames: split on double newline
377
+ let idx;
378
+ while ((idx = buffer.indexOf('\n\n')) !== -1) {
379
+ const frame = buffer.slice(0, idx);
380
+ buffer = buffer.slice(idx + 2);
381
+ // Join all data: lines in the frame
382
+ const data = frame
383
+ .split('\n')
384
+ .filter(line => line.startsWith('data:'))
385
+ .map(line => line.slice(5).trim())
386
+ .join('\n');
387
+ if (data) {
388
+ callbacks.onChunk(data);
389
+ }
390
+ }
391
+ }
392
+ // Final flush of decoder state
393
+ decoder.decode();
394
+ callbacks.onComplete?.();
395
+ }
396
+ catch (error) {
397
+ if (error.name === 'AbortError') {
398
+ return;
399
+ }
400
+ callbacks.onError?.(error);
401
+ }
402
+ }
403
+
404
+ function useProject(options) {
405
+ const { projectName, baseUrl, getHeaders, autoLoad = true } = options;
406
+ // Project state
407
+ const [projectId, setProjectId] = react.useState('');
408
+ const [projectLoading, setProjectLoading] = react.useState(false);
409
+ // Use ref to store current projectId for interceptor access
410
+ const projectIdRef = react.useRef('');
411
+ // Update ref whenever projectId changes
412
+ react.useEffect(() => {
413
+ projectIdRef.current = projectId;
414
+ }, [projectId]);
415
+ // Create axios instance with request interceptor for custom headers and project_id
416
+ const axiosInstance = react.useMemo(() => {
417
+ const instance = axios.create({
418
+ baseURL: baseUrl,
419
+ headers: {
420
+ 'Content-Type': 'application/json',
421
+ },
422
+ });
423
+ // Add request interceptor to inject custom headers and project_id
424
+ instance.interceptors.request.use(async (config) => {
425
+ // Add custom headers if getHeaders function is provided
426
+ if (getHeaders) {
427
+ const customHeaders = await getHeaders();
428
+ Object.assign(config.headers, customHeaders);
429
+ }
430
+ // Automatically add project_id to query params if available
431
+ const currentProjectId = projectIdRef.current;
432
+ if (currentProjectId) {
433
+ if (config.params) {
434
+ config.params = {
435
+ ...config.params,
436
+ project_id: currentProjectId,
437
+ };
438
+ }
439
+ else {
440
+ config.params = { project_id: currentProjectId };
441
+ }
442
+ }
443
+ return config;
444
+ });
445
+ return instance;
446
+ }, [baseUrl, getHeaders]);
447
+ // ============================================
448
+ // API Helper Functions
449
+ // ============================================
450
+ /**
451
+ * Build query params (project_id is automatically added by axios interceptor)
452
+ */
453
+ const buildParams = react.useCallback((params) => {
454
+ return params || {};
455
+ }, []);
456
+ /**
457
+ * Get headers for streaming requests (combines default + custom headers)
458
+ */
459
+ const getRequestHeaders = react.useCallback(async () => {
460
+ const headers = {
461
+ 'Content-Type': 'application/json',
462
+ };
463
+ // Add custom headers if getHeaders function is provided
464
+ if (getHeaders) {
465
+ const customHeaders = await getHeaders();
466
+ Object.assign(headers, customHeaders);
467
+ }
468
+ return headers;
469
+ }, [getHeaders]);
470
+ // ============================================
471
+ // Project Management
472
+ // ============================================
473
+ /**
474
+ * Fetch the project ID using the project name
475
+ */
476
+ const fetchProjectId = react.useCallback(async () => {
477
+ if (!projectName) {
478
+ return;
479
+ }
480
+ setProjectLoading(true);
481
+ try {
482
+ const response = await axiosInstance.get('/project/id', {
483
+ params: { name: projectName },
484
+ });
485
+ const id = typeof response.data === 'string' ? response.data : response.data.data;
486
+ setProjectId(id || '');
487
+ }
488
+ catch (error) {
489
+ console.error('Failed to fetch project ID:', error);
490
+ throw error;
491
+ }
492
+ finally {
493
+ setProjectLoading(false);
494
+ }
495
+ }, [axiosInstance, projectName]);
496
+ // ============================================
497
+ // Effects for auto-loading
498
+ // ============================================
499
+ // Fetch project ID on mount
500
+ react.useEffect(() => {
501
+ if (autoLoad && projectName) {
502
+ fetchProjectId();
503
+ }
504
+ }, [autoLoad, projectName, fetchProjectId]);
505
+ return {
506
+ // Project state
507
+ projectId,
508
+ projectLoading,
509
+ // API client
510
+ axiosInstance,
511
+ buildParams,
512
+ getRequestHeaders,
513
+ baseUrl,
514
+ };
515
+ }
516
+
517
+ // Create the context with undefined as default to ensure it's used within provider
518
+ const ProjectContext = react.createContext(undefined);
519
+ /**
520
+ * ProjectProvider component that manages project state using React Context.
521
+ *
522
+ * This provider wraps the useProject hook and makes the project state available
523
+ * to all child components through context.
524
+ *
525
+ * @example
526
+ * ```tsx
527
+ * import { ProjectProvider, useProjectContext } from '@praveen001/uno-converse';
528
+ *
529
+ * function App() {
530
+ * return (
531
+ * <ProjectProvider
532
+ * baseUrl="https://api.example.com/api/agent-server"
533
+ * projectName="my-project"
534
+ * getHeaders={() => ({
535
+ * 'Authorization': `Bearer ${getToken()}`,
536
+ * })}
537
+ * >
538
+ * <YourApp />
539
+ * </ProjectProvider>
540
+ * );
541
+ * }
542
+ *
543
+ * function YourApp() {
544
+ * const { projectId, projectLoading } = useProjectContext();
545
+ *
546
+ * if (projectLoading) {
547
+ * return <div>Loading project...</div>;
548
+ * }
549
+ *
550
+ * return <div>Project ID: {projectId}</div>;
551
+ * }
552
+ * ```
553
+ */
554
+ const ProjectProvider = ({ baseUrl, projectName, getHeaders, autoLoad = true, children, }) => {
555
+ // Use the useProject hook to manage project state
556
+ const projectState = useProject({
557
+ baseUrl,
558
+ projectName,
559
+ getHeaders,
560
+ autoLoad,
561
+ });
562
+ // Memoize the context value to prevent unnecessary re-renders
563
+ const contextValue = react.useMemo(() => projectState, [
564
+ projectState.projectId,
565
+ projectState.projectLoading,
566
+ projectState.axiosInstance,
567
+ projectState.buildParams,
568
+ projectState.getRequestHeaders,
569
+ projectState.baseUrl,
570
+ ]);
571
+ return (jsxRuntime.jsx(ProjectContext.Provider, { value: contextValue, children: children }));
572
+ };
573
+ /**
574
+ * Hook to access the project context.
575
+ *
576
+ * Must be used within a ProjectProvider component.
577
+ *
578
+ * @throws {Error} If used outside of ProjectProvider
579
+ *
580
+ * @example
581
+ * ```tsx
582
+ * function MyComponent() {
583
+ * const { projectId, projectLoading } = useProjectContext();
584
+ *
585
+ * return (
586
+ * <div>
587
+ * {projectLoading ? 'Loading...' : `Project ID: ${projectId}`}
588
+ * </div>
589
+ * );
590
+ * }
591
+ * ```
592
+ */
593
+ const useProjectContext = () => {
594
+ const context = react.useContext(ProjectContext);
595
+ if (context === undefined) {
596
+ throw new Error('useProjectContext must be used within a ProjectProvider');
597
+ }
598
+ return context;
599
+ };
600
+
601
+ /**
602
+ * Simple ID generator for message IDs
603
+ */
604
+ function generateId() {
605
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
606
+ }
607
+ /**
608
+ * A comprehensive hook for managing conversations, threads, messages, and streaming
609
+ * with Uno Agent Server.
610
+ *
611
+ * @example
612
+ * ```tsx
613
+ * import { useConversation } from '@praveen001/uno-converse';
614
+ *
615
+ * function ChatComponent() {
616
+ * const {
617
+ * allMessages,
618
+ * isStreaming,
619
+ * sendMessage,
620
+ * startNewChat,
621
+ * } = useConversation({
622
+ * namespace: 'my-app',
623
+ * });
624
+ *
625
+ * const handleSend = async (text: string) => {
626
+ * await sendMessage(
627
+ * [{ type: 'message', id: '1', content: text }],
628
+ * {
629
+ * namespace: 'my-app',
630
+ * agentName: 'my-agent',
631
+ * }
632
+ * );
633
+ * };
634
+ *
635
+ * return (
636
+ * <div>
637
+ * {allMessages.map(msg => (
638
+ * <MessageComponent key={msg.message_id} message={msg} />
639
+ * ))}
640
+ * {isStreaming && <LoadingIndicator />}
641
+ * </div>
642
+ * );
643
+ * }
644
+ * ```
645
+ */
646
+ function useConversation(options) {
647
+ const { namespace, autoLoad = true } = options;
648
+ // Get project context (axios instance, projectId, etc.)
649
+ const { axiosInstance, projectId, projectLoading, buildParams, getRequestHeaders, baseUrl, } = useProjectContext();
650
+ // Conversation list state
651
+ const [conversations, setConversations] = react.useState([]);
652
+ const [conversationsLoading, setConversationsLoading] = react.useState(false);
653
+ // Thread state
654
+ const [threads, setThreads] = react.useState([]);
655
+ const [threadsLoading, setThreadsLoading] = react.useState(false);
656
+ // Message state
657
+ const [messages, setMessages] = react.useState([]);
658
+ const [streamingMessage, setStreamingMessage] = react.useState(null);
659
+ const [messagesLoading, setMessagesLoading] = react.useState(false);
660
+ const [isStreaming, setIsStreaming] = react.useState(false);
661
+ const [isThinking, setIsThinking] = react.useState(false);
662
+ // Current selection
663
+ const [currentConversationId, setCurrentConversationId] = react.useState(null);
664
+ const [currentThreadId, setCurrentThreadId] = react.useState(null);
665
+ const [previousMessageId, setPreviousMessageId] = react.useState('');
666
+ // Refs
667
+ const processorRef = react.useRef(null);
668
+ const abortControllerRef = react.useRef(null);
669
+ // ============================================
670
+ // API Helper Functions
671
+ // ============================================
672
+ // Note: buildParams and getRequestHeaders are now provided by ProjectProvider
673
+ // ============================================
674
+ // Conversation Management
675
+ // ============================================
676
+ /**
677
+ * Load all conversations for the namespace
678
+ */
679
+ const loadConversations = react.useCallback(async () => {
680
+ setConversationsLoading(true);
681
+ try {
682
+ const response = await axiosInstance.get('/conversations', {
683
+ params: buildParams({ namespace }),
684
+ });
685
+ const data = 'data' in response.data ? response.data.data : response.data;
686
+ setConversations(data || []);
687
+ }
688
+ catch (error) {
689
+ console.error('Failed to load conversations:', error);
690
+ throw error;
691
+ }
692
+ finally {
693
+ setConversationsLoading(false);
694
+ }
695
+ }, [axiosInstance, buildParams, namespace]);
696
+ /**
697
+ * Select a conversation and load its threads
698
+ */
699
+ const selectConversation = react.useCallback((conversationId) => {
700
+ setCurrentConversationId(conversationId);
701
+ // Threads will be loaded via useEffect
702
+ }, []);
703
+ // ============================================
704
+ // Thread Management
705
+ // ============================================
706
+ /**
707
+ * Load threads for a conversation
708
+ */
709
+ const loadThreads = react.useCallback(async (conversationId) => {
710
+ setThreadsLoading(true);
711
+ try {
712
+ const response = await axiosInstance.get('/threads', {
713
+ params: buildParams({ conversation_id: conversationId, namespace }),
714
+ });
715
+ const loadedThreads = 'data' in response.data ? response.data.data : response.data;
716
+ // Sort by created_at descending
717
+ loadedThreads.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
718
+ setThreads(loadedThreads);
719
+ // Auto-select the latest thread
720
+ if (loadedThreads.length > 0) {
721
+ setCurrentThreadId(loadedThreads[0].thread_id);
722
+ }
723
+ }
724
+ catch (error) {
725
+ console.error('Failed to load threads:', error);
726
+ throw error;
727
+ }
728
+ finally {
729
+ setThreadsLoading(false);
730
+ }
731
+ }, [axiosInstance, buildParams, namespace]);
732
+ /**
733
+ * Select a thread and load its messages
734
+ */
735
+ const selectThread = react.useCallback((threadId) => {
736
+ setCurrentThreadId(threadId);
737
+ // Messages will be loaded via useEffect
738
+ }, []);
739
+ // ============================================
740
+ // Message Management
741
+ // ============================================
742
+ /**
743
+ * Load messages for a thread
744
+ */
745
+ const loadMessages = react.useCallback(async (threadId) => {
746
+ setMessagesLoading(true);
747
+ try {
748
+ const response = await axiosInstance.get('/messages', {
749
+ params: buildParams({ thread_id: threadId, namespace }),
750
+ });
751
+ const loadedMessages = 'data' in response.data ? response.data.data : response.data;
752
+ // Extract last message ID for continuation
753
+ if (loadedMessages.length > 0) {
754
+ const lastMsgId = loadedMessages[loadedMessages.length - 1].message_id;
755
+ setPreviousMessageId(lastMsgId);
756
+ }
757
+ else {
758
+ setPreviousMessageId('');
759
+ }
760
+ setMessages(loadedMessages);
761
+ }
762
+ catch (error) {
763
+ console.error('Failed to load messages:', error);
764
+ throw error;
765
+ }
766
+ finally {
767
+ setMessagesLoading(false);
768
+ }
769
+ }, [axiosInstance, buildParams, namespace]);
770
+ /**
771
+ * Send a user message and stream the response
772
+ */
773
+ const sendMessage = react.useCallback(async (userMessages, config) => {
774
+ const messageId = generateId();
775
+ // Check if this is a tool approval response (resuming a run)
776
+ const isToolApproval = userMessages.length === 1 &&
777
+ userMessages[0].type === exports.MessageType.FunctionCallApprovalResponse;
778
+ // Only add user message for regular messages, not for tool approvals
779
+ if (!isToolApproval) {
780
+ const userConversation = {
781
+ conversation_id: currentConversationId || '',
782
+ thread_id: currentThreadId || '',
783
+ message_id: messageId + '-user',
784
+ messages: userMessages,
785
+ meta: {},
786
+ };
787
+ setMessages(prev => [...prev, userConversation]);
788
+ }
789
+ // Initialize the chunk processor for the assistant response
790
+ processorRef.current = new ChunkProcessor(currentConversationId || '', currentThreadId || '', (conversation) => {
791
+ setIsThinking(isThinking);
792
+ setStreamingMessage({ ...conversation, isStreaming: true });
793
+ });
794
+ setIsStreaming(true);
795
+ setIsThinking(true);
796
+ // Build URL with query parameters
797
+ const params = new URLSearchParams();
798
+ if (projectId) {
799
+ params.append('project_id', projectId);
800
+ }
801
+ params.append('agent_id', config.agentId);
802
+ let url = `${baseUrl}/converse?${params.toString()}`;
803
+ if (!!config.baseUrl) {
804
+ url = config.baseUrl;
805
+ }
806
+ // Prepare request body
807
+ const body = JSON.stringify({
808
+ namespace: config.namespace,
809
+ previous_message_id: previousMessageId,
810
+ message: userMessages[0],
811
+ context: config.context || {},
812
+ });
813
+ // Abort any existing stream
814
+ if (abortControllerRef.current) {
815
+ abortControllerRef.current.abort();
816
+ }
817
+ abortControllerRef.current = new AbortController();
818
+ try {
819
+ // Get headers (supports async getHeaders function)
820
+ const requestHeaders = await getRequestHeaders();
821
+ await streamSSE(url, {
822
+ method: 'POST',
823
+ body,
824
+ headers: {
825
+ ...requestHeaders,
826
+ ...(config.headers || {}),
827
+ },
828
+ }, {
829
+ onChunk: (data) => {
830
+ processorRef.current?.processChunk(data);
831
+ },
832
+ onComplete: () => {
833
+ // Move streaming message to messages list
834
+ if (processorRef.current) {
835
+ const finalConversation = processorRef.current.getConversation();
836
+ if (isToolApproval) {
837
+ // For tool approvals, update the last message instead of appending
838
+ setMessages(prev => {
839
+ const newMessages = [...prev];
840
+ if (newMessages.length > 0) {
841
+ const lastMsg = newMessages[newMessages.length - 1];
842
+ newMessages[newMessages.length - 1] = {
843
+ ...lastMsg,
844
+ messages: [...lastMsg.messages, ...finalConversation.messages],
845
+ meta: finalConversation.meta,
846
+ isStreaming: false,
847
+ };
848
+ }
849
+ return newMessages;
850
+ });
851
+ }
852
+ else {
853
+ setMessages(prev => [...prev, { ...finalConversation, isStreaming: false }]);
854
+ }
855
+ setStreamingMessage(null);
856
+ setPreviousMessageId(finalConversation.message_id);
857
+ }
858
+ },
859
+ onError: (error) => {
860
+ console.error('Streaming error:', error);
861
+ setStreamingMessage(null);
862
+ },
863
+ }, abortControllerRef.current.signal);
864
+ // If this was a new conversation, fetch the conversation info
865
+ if (previousMessageId === '' && processorRef.current) {
866
+ try {
867
+ const response = await axiosInstance.get(`/messages/${processorRef.current.getConversation().message_id}`, { params: buildParams({ namespace }) });
868
+ const messageData = 'data' in response.data ? response.data.data : response.data;
869
+ const newConversationId = messageData?.conversation_id;
870
+ if (newConversationId) {
871
+ // Add new conversation to list
872
+ setConversations(prev => [{
873
+ conversation_id: newConversationId,
874
+ name: "New Conversation",
875
+ namespace_id: namespace,
876
+ created_at: new Date().toISOString(),
877
+ last_updated: new Date().toISOString(),
878
+ }, ...prev.filter(c => c.conversation_id !== newConversationId)]);
879
+ setCurrentConversationId(newConversationId);
880
+ }
881
+ }
882
+ catch (e) {
883
+ console.error('Failed to get conversation ID:', e);
884
+ }
885
+ }
886
+ }
887
+ finally {
888
+ setIsStreaming(false);
889
+ abortControllerRef.current = null;
890
+ }
891
+ }, [currentConversationId, currentThreadId, previousMessageId, namespace, baseUrl, projectId, axiosInstance, buildParams, getRequestHeaders]);
892
+ // ============================================
893
+ // Utility Actions
894
+ // ============================================
895
+ /**
896
+ * Start a new chat (reset all state)
897
+ */
898
+ const startNewChat = react.useCallback(() => {
899
+ // Abort any ongoing stream
900
+ if (abortControllerRef.current) {
901
+ abortControllerRef.current.abort();
902
+ abortControllerRef.current = null;
903
+ }
904
+ setCurrentConversationId(null);
905
+ setCurrentThreadId(null);
906
+ setThreads([]);
907
+ setMessages([]);
908
+ setStreamingMessage(null);
909
+ setPreviousMessageId('');
910
+ setIsStreaming(false);
911
+ setIsThinking(false);
912
+ processorRef.current = null;
913
+ }, []);
914
+ // ============================================
915
+ // Effects for auto-loading
916
+ // ============================================
917
+ // Load conversations after project ID is fetched
918
+ react.useEffect(() => {
919
+ if (autoLoad && projectId) {
920
+ loadConversations();
921
+ }
922
+ }, [autoLoad, projectId, loadConversations]);
923
+ // Load threads when conversation changes
924
+ react.useEffect(() => {
925
+ if (currentConversationId) {
926
+ loadThreads(currentConversationId);
927
+ }
928
+ }, [currentConversationId, loadThreads]);
929
+ // Load messages when thread changes
930
+ react.useEffect(() => {
931
+ if (currentThreadId) {
932
+ loadMessages(currentThreadId);
933
+ }
934
+ }, [currentThreadId, loadMessages]);
935
+ // ============================================
936
+ // Computed values
937
+ // ============================================
938
+ const currentThread = threads.find(t => t.thread_id === currentThreadId) || null;
939
+ const allMessages = streamingMessage
940
+ ? [...messages, streamingMessage]
941
+ : messages;
942
+ return {
943
+ // Conversation list state
944
+ conversations,
945
+ conversationsLoading,
946
+ // Thread state
947
+ threads,
948
+ threadsLoading,
949
+ currentThread,
950
+ // Message state
951
+ messages,
952
+ streamingMessage,
953
+ messagesLoading,
954
+ isStreaming,
955
+ isThinking,
956
+ // Current selection
957
+ currentConversationId,
958
+ currentThreadId,
959
+ // Actions - Conversations
960
+ loadConversations,
961
+ selectConversation,
962
+ // Actions - Threads
963
+ loadThreads,
964
+ selectThread,
965
+ // Actions - Messages
966
+ sendMessage,
967
+ // Actions - Utility
968
+ startNewChat,
969
+ // Combined messages
970
+ allMessages,
971
+ };
972
+ }
973
+
974
+ function useAgent(options) {
975
+ const { name } = options;
976
+ // Get project context (axios instance, projectId, etc.)
977
+ const { axiosInstance, projectId, buildParams, } = useProjectContext();
978
+ // Agents state
979
+ const [agent, setAgent] = react.useState(null);
980
+ const [agentLoading, setAgentLoading] = react.useState(false);
981
+ // ============================================
982
+ // Agent Management
983
+ // ============================================
984
+ /**
985
+ * Fetch the agent
986
+ */
987
+ const loadAgent = react.useCallback(async () => {
988
+ setAgentLoading(true);
989
+ try {
990
+ const response = await axiosInstance.get('/agent-configs/by-name', {
991
+ params: buildParams({ name }),
992
+ });
993
+ setAgent(response.data.data);
994
+ }
995
+ catch (error) {
996
+ console.error('Failed to load agent:', error);
997
+ throw error;
998
+ }
999
+ finally {
1000
+ setAgentLoading(false);
1001
+ }
1002
+ }, [axiosInstance, name]);
1003
+ // Fetch agent after project is fetched
1004
+ react.useEffect(() => {
1005
+ if (projectId) {
1006
+ loadAgent();
1007
+ }
1008
+ }, [projectId, loadAgent]);
1009
+ return { agent, agentLoading };
1010
+ }
1011
+
1012
+ exports.ChunkProcessor = ChunkProcessor;
1013
+ exports.ProjectProvider = ProjectProvider;
1014
+ exports.isEasyMessage = isEasyMessage;
1015
+ exports.isFunctionCallMessage = isFunctionCallMessage;
1016
+ exports.isFunctionCallOutputMessage = isFunctionCallOutputMessage;
1017
+ exports.isImageGenerationCallMessage = isImageGenerationCallMessage;
1018
+ exports.isInputMessage = isInputMessage;
1019
+ exports.isReasoningMessage = isReasoningMessage;
1020
+ exports.streamSSE = streamSSE;
1021
+ exports.useAgent = useAgent;
1022
+ exports.useConversation = useConversation;
1023
+ exports.useProjectContext = useProjectContext;
1024
+ //# sourceMappingURL=index.cjs.map