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