@personaai/react 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -41,15 +41,45 @@ function usePersonaContext() {
41
41
  }
42
42
 
43
43
  // src/hooks/useChat.ts
44
- import { useCallback, useRef, useState } from "react";
44
+ import { useCallback, useEffect, useRef, useState } from "react";
45
+ function isErrorToolContent(content) {
46
+ if (typeof content !== "string" || !content.trim().startsWith("{")) return false;
47
+ try {
48
+ return JSON.parse(content)?.status === "error";
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+ function normalizePendingInterrupt(pending) {
54
+ if (!pending || typeof pending !== "object") return null;
55
+ const p = pending;
56
+ if (p.kind === "hitl") {
57
+ return {
58
+ kind: "hitl",
59
+ actionRequests: p.value?.actionRequests ?? [],
60
+ reviewConfigs: p.value?.reviewConfigs ?? []
61
+ };
62
+ }
63
+ if (p.kind === "clarification") {
64
+ return {
65
+ kind: "clarification",
66
+ questions: p.value?.questions ?? []
67
+ };
68
+ }
69
+ return null;
70
+ }
45
71
  function useChat(options = {}) {
46
72
  const { defaultAgentId, fetchWithAuth } = usePersonaContext();
47
73
  const agentId = options.agentId || defaultAgentId;
74
+ const threadId = options.threadId;
48
75
  const [messages, setMessages] = useState(options.initialMessages || []);
49
76
  const [input, setInput] = useState("");
50
77
  const [isStreaming, setIsStreaming] = useState(false);
78
+ const [isLoadingHistory, setIsLoadingHistory] = useState(false);
51
79
  const [error, setError] = useState(null);
80
+ const [interrupt, setInterrupt] = useState(null);
52
81
  const abortControllerRef = useRef(null);
82
+ const loadedThreadIdRef = useRef(void 0);
53
83
  const stop = useCallback(() => {
54
84
  if (abortControllerRef.current) {
55
85
  abortControllerRef.current.abort();
@@ -62,6 +92,43 @@ function useChat(options = {}) {
62
92
  setMessages([]);
63
93
  setError(null);
64
94
  }, [stop]);
95
+ const loadThreadMessages = useCallback(
96
+ async (id) => {
97
+ setIsLoadingHistory(true);
98
+ setError(null);
99
+ try {
100
+ const res = await fetchWithAuth(`/threads/${id}/messages`);
101
+ if (!res.ok) throw new Error(`Failed to load thread history: ${res.statusText}`);
102
+ const body = await res.json();
103
+ const data = body?.data ?? body;
104
+ const raw = data?.messages ?? [];
105
+ const loaded = raw.map((m, i) => ({
106
+ id: m.id || `history-${id}-${i}`,
107
+ role: m.role,
108
+ content: m.content,
109
+ createdAt: /* @__PURE__ */ new Date(),
110
+ toolCalls: m.toolCalls
111
+ }));
112
+ setMessages(loaded);
113
+ setInterrupt(normalizePendingInterrupt(data?.pendingInterrupt));
114
+ return loaded;
115
+ } catch (err) {
116
+ const errorObj = err instanceof Error ? err : new Error(String(err));
117
+ setError(errorObj);
118
+ return [];
119
+ } finally {
120
+ setIsLoadingHistory(false);
121
+ }
122
+ },
123
+ [fetchWithAuth]
124
+ );
125
+ useEffect(() => {
126
+ if (!threadId || isStreaming) return;
127
+ if (loadedThreadIdRef.current === threadId) return;
128
+ if (messages.length > 0) return;
129
+ loadedThreadIdRef.current = threadId;
130
+ void loadThreadMessages(threadId);
131
+ }, [threadId, isStreaming, messages.length, loadThreadMessages]);
65
132
  const sendMessage = useCallback(
66
133
  async (contentToSend, overrideOptions) => {
67
134
  const prompt = (contentToSend ?? input).trim();
@@ -93,6 +160,7 @@ function useChat(options = {}) {
93
160
  setInput("");
94
161
  setIsStreaming(true);
95
162
  setError(null);
163
+ setInterrupt(null);
96
164
  const controller = new AbortController();
97
165
  abortControllerRef.current = controller;
98
166
  try {
@@ -108,7 +176,8 @@ function useChat(options = {}) {
108
176
  body: JSON.stringify({
109
177
  agentId: targetAgentId,
110
178
  messages: payloadMessages,
111
- threadId: overrideOptions?.threadId || options.threadId
179
+ threadId: overrideOptions?.threadId || options.threadId,
180
+ resume: overrideOptions?.resume
112
181
  }),
113
182
  signal: controller.signal
114
183
  });
@@ -123,7 +192,15 @@ function useChat(options = {}) {
123
192
  const decoder = new TextDecoder("utf-8");
124
193
  let buffer = "";
125
194
  let accumulatedText = "";
195
+ let accumulatedReasoning = "";
126
196
  const toolCallsMap = /* @__PURE__ */ new Map();
197
+ const patchAssistant = (patch) => {
198
+ setMessages(
199
+ (prev) => prev.map(
200
+ (msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()), ...patch } : msg
201
+ )
202
+ );
203
+ };
127
204
  while (true) {
128
205
  const { done, value } = await reader.read();
129
206
  if (done) break;
@@ -140,47 +217,49 @@ function useChat(options = {}) {
140
217
  options.onEvent?.(event);
141
218
  if (event.type === "TEXT_MESSAGE_CHUNK" && event.delta) {
142
219
  accumulatedText += event.delta;
143
- setMessages(
144
- (prev) => prev.map(
145
- (msg) => msg.id === assistantMessageId ? {
146
- ...msg,
147
- content: accumulatedText,
148
- isStreaming: true,
149
- toolCalls: Array.from(toolCallsMap.values())
150
- } : msg
151
- )
152
- );
153
- } else if (event.type === "TOOL_CALL_START") {
154
- toolCallsMap.set(event.toolCallId, {
155
- toolCallId: event.toolCallId,
156
- toolName: event.toolName,
157
- args: ""
158
- });
159
- setMessages(
160
- (prev) => prev.map(
161
- (msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()) } : msg
162
- )
163
- );
164
- } else if (event.type === "TOOL_CALL_ARGS") {
220
+ patchAssistant({ content: accumulatedText, isStreaming: true });
221
+ } else if (event.type === "TOOL_CALL_CHUNK" && event.toolCallId) {
165
222
  const existing = toolCallsMap.get(event.toolCallId);
166
223
  if (existing) {
167
- existing.args = (existing.args || "") + event.delta;
168
- setMessages(
169
- (prev) => prev.map(
170
- (msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()) } : msg
171
- )
172
- );
224
+ existing.args = (existing.args || "") + (event.delta || "");
225
+ } else {
226
+ toolCallsMap.set(event.toolCallId, {
227
+ toolCallId: event.toolCallId,
228
+ toolName: event.toolCallName || "",
229
+ args: event.delta || ""
230
+ });
173
231
  }
232
+ patchAssistant({});
174
233
  } else if (event.type === "TOOL_CALL_RESULT") {
175
234
  const existing = toolCallsMap.get(event.toolCallId);
176
235
  if (existing) {
177
- existing.result = event.result;
178
- existing.isError = event.isError;
179
- setMessages(
180
- (prev) => prev.map(
181
- (msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()) } : msg
182
- )
183
- );
236
+ existing.result = event.content;
237
+ existing.isError = isErrorToolContent(event.content);
238
+ patchAssistant({});
239
+ }
240
+ } else if (event.type === "REASONING_MESSAGE_CONTENT") {
241
+ accumulatedReasoning += event.delta;
242
+ patchAssistant({ reasoning: accumulatedReasoning, isReasoning: true });
243
+ } else if (event.type === "REASONING_END") {
244
+ patchAssistant({ isReasoning: false });
245
+ } else if (event.type === "CUSTOM") {
246
+ if (event.name === "hitl_request") {
247
+ const value2 = event.value;
248
+ setInterrupt({
249
+ kind: "hitl",
250
+ actionRequests: value2.actionRequests,
251
+ reviewConfigs: value2.reviewConfigs
252
+ });
253
+ } else if (event.name === "clarification_request") {
254
+ const value2 = event.value;
255
+ setInterrupt({ kind: "clarification", questions: value2.questions });
256
+ } else if (event.name === "subagent_activity") {
257
+ const { toolCallId, ...entry } = event.value;
258
+ const existing = toolCallsMap.get(toolCallId);
259
+ if (existing) {
260
+ existing.subagentActivity = [...existing.subagentActivity || [], entry];
261
+ patchAssistant({});
262
+ }
184
263
  }
185
264
  } else if (event.type === "RUN_ERROR") {
186
265
  throw new Error(event.message || "Stream error from agent");
@@ -198,7 +277,8 @@ function useChat(options = {}) {
198
277
  content: accumulatedText,
199
278
  createdAt: /* @__PURE__ */ new Date(),
200
279
  isStreaming: false,
201
- toolCalls: Array.from(toolCallsMap.values())
280
+ toolCalls: Array.from(toolCallsMap.values()),
281
+ ...accumulatedReasoning ? { reasoning: accumulatedReasoning, isReasoning: false } : {}
202
282
  };
203
283
  setMessages(
204
284
  (prev) => prev.map((msg) => msg.id === assistantMessageId ? finalMessage : msg)
@@ -256,6 +336,10 @@ function useChat(options = {}) {
256
336
  setMessages(messages.slice(0, lastUserIndex));
257
337
  void sendMessage(lastUserMessage.content);
258
338
  }, [isStreaming, messages, sendMessage]);
339
+ const resumeInterrupt = useCallback(
340
+ (resume, displayContent) => sendMessage(displayContent, { resume }),
341
+ [sendMessage]
342
+ );
259
343
  return {
260
344
  messages,
261
345
  input,
@@ -265,19 +349,23 @@ function useChat(options = {}) {
265
349
  sendMessage,
266
350
  isStreaming,
267
351
  isLoading: isStreaming,
352
+ isLoadingHistory,
268
353
  error,
354
+ interrupt,
355
+ resumeInterrupt,
269
356
  stop,
270
357
  reload,
271
358
  clear,
272
- setMessages
359
+ setMessages,
360
+ loadThreadMessages
273
361
  };
274
362
  }
275
363
 
276
364
  // src/hooks/useMemory.ts
277
- import { useCallback as useCallback2, useEffect, useState as useState2 } from "react";
365
+ import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
278
366
  function useMemory(autoFetch = true) {
279
367
  const { fetchWithAuth } = usePersonaContext();
280
- const [memory, setMemory] = useState2({ user: [], agents: {} });
368
+ const [memory, setMemory] = useState2({ userFiles: [], agentMemories: [] });
281
369
  const [isLoading, setIsLoading] = useState2(false);
282
370
  const [error, setError] = useState2(null);
283
371
  const fetchMemory = useCallback2(async () => {
@@ -294,7 +382,7 @@ function useMemory(autoFetch = true) {
294
382
  } catch (err) {
295
383
  const errorObj = err instanceof Error ? err : new Error(String(err));
296
384
  setError(errorObj);
297
- return { user: [], agents: {} };
385
+ return { userFiles: [], agentMemories: [] };
298
386
  } finally {
299
387
  setIsLoading(false);
300
388
  }
@@ -337,7 +425,7 @@ function useMemory(autoFetch = true) {
337
425
  },
338
426
  [fetchWithAuth, fetchMemory]
339
427
  );
340
- useEffect(() => {
428
+ useEffect2(() => {
341
429
  if (autoFetch) {
342
430
  void fetchMemory();
343
431
  }
@@ -354,7 +442,7 @@ function useMemory(autoFetch = true) {
354
442
  }
355
443
 
356
444
  // src/hooks/useThreads.ts
357
- import { useCallback as useCallback3, useEffect as useEffect2, useState as useState3 } from "react";
445
+ import { useCallback as useCallback3, useEffect as useEffect3, useState as useState3 } from "react";
358
446
  function useThreads(autoFetch = true) {
359
447
  const { fetchWithAuth, defaultAgentId } = usePersonaContext();
360
448
  const [threads, setThreads] = useState3([]);
@@ -367,7 +455,7 @@ function useThreads(autoFetch = true) {
367
455
  const res = await fetchWithAuth("/threads");
368
456
  if (!res.ok) throw new Error(`Failed to list threads: ${res.statusText}`);
369
457
  const data = await res.json();
370
- const items = Array.isArray(data) ? data : data?.threads || data?.items || [];
458
+ const items = Array.isArray(data) ? data : data?.items || data?.threads || (Array.isArray(data?.data) ? data.data : void 0) || [];
371
459
  setThreads(items);
372
460
  return items;
373
461
  } catch (err) {
@@ -387,7 +475,8 @@ function useThreads(autoFetch = true) {
387
475
  body: JSON.stringify({ agentId: targetAgentId })
388
476
  });
389
477
  if (!res.ok) throw new Error(`Failed to create thread: ${res.statusText}`);
390
- const created = await res.json();
478
+ const data = await res.json();
479
+ const created = data?.data ?? data;
391
480
  void fetchThreads();
392
481
  return created;
393
482
  },
@@ -401,7 +490,57 @@ function useThreads(autoFetch = true) {
401
490
  },
402
491
  [fetchWithAuth]
403
492
  );
404
- useEffect2(() => {
493
+ const bulkDeleteThreads = useCallback3(
494
+ async (threadIds) => {
495
+ const res = await fetchWithAuth("/threads/bulk-delete", {
496
+ method: "POST",
497
+ headers: { "Content-Type": "application/json" },
498
+ body: JSON.stringify({ ids: threadIds })
499
+ });
500
+ if (!res.ok) throw new Error(`Failed to bulk-delete threads: ${res.statusText}`);
501
+ const data = await res.json();
502
+ const result = data?.data ?? data;
503
+ const deletedSet = new Set(result.deleted);
504
+ setThreads((prev) => prev.filter((t) => !deletedSet.has(t._id)));
505
+ return result;
506
+ },
507
+ [fetchWithAuth]
508
+ );
509
+ const deleteAllThreads = useCallback3(async () => {
510
+ const ids = threads.map((t) => t._id);
511
+ for (let i = 0; i < ids.length; i += 100) {
512
+ await bulkDeleteThreads(ids.slice(i, i + 100));
513
+ }
514
+ }, [threads, bulkDeleteThreads]);
515
+ const updateThread = useCallback3(
516
+ async (threadId, input) => {
517
+ const res = await fetchWithAuth(`/threads/${threadId}`, {
518
+ method: "PATCH",
519
+ headers: { "Content-Type": "application/json" },
520
+ body: JSON.stringify(input)
521
+ });
522
+ if (!res.ok) throw new Error(`Failed to update thread: ${res.statusText}`);
523
+ const data = await res.json();
524
+ const updated = data?.data ?? data;
525
+ setThreads((prev) => prev.map((t) => t._id === threadId ? { ...t, ...updated } : t));
526
+ return updated;
527
+ },
528
+ [fetchWithAuth]
529
+ );
530
+ const renameThread = useCallback3(
531
+ (threadId, title) => updateThread(threadId, { title }),
532
+ [updateThread]
533
+ );
534
+ const getThread = useCallback3(
535
+ async (threadId) => {
536
+ const res = await fetchWithAuth(`/threads/${threadId}`);
537
+ if (!res.ok) throw new Error(`Failed to fetch thread: ${res.statusText}`);
538
+ const data = await res.json();
539
+ return data?.data ?? data;
540
+ },
541
+ [fetchWithAuth]
542
+ );
543
+ useEffect3(() => {
405
544
  if (autoFetch) {
406
545
  void fetchThreads();
407
546
  }
@@ -412,12 +551,17 @@ function useThreads(autoFetch = true) {
412
551
  error,
413
552
  refetch: fetchThreads,
414
553
  createThread,
415
- deleteThread
554
+ deleteThread,
555
+ bulkDeleteThreads,
556
+ deleteAllThreads,
557
+ updateThread,
558
+ renameThread,
559
+ getThread
416
560
  };
417
561
  }
418
562
 
419
563
  // src/hooks/useFiles.ts
420
- import { useCallback as useCallback4, useEffect as useEffect3, useState as useState4 } from "react";
564
+ import { useCallback as useCallback4, useEffect as useEffect4, useState as useState4 } from "react";
421
565
  function useFiles(autoFetch = true) {
422
566
  const { fetchWithAuth } = usePersonaContext();
423
567
  const [files, setFiles] = useState4([]);
@@ -431,7 +575,7 @@ function useFiles(autoFetch = true) {
431
575
  const res = await fetchWithAuth("/files");
432
576
  if (!res.ok) throw new Error(`Failed to list files: ${res.statusText}`);
433
577
  const data = await res.json();
434
- const items = Array.isArray(data) ? data : data?.files || data?.items || [];
578
+ const items = Array.isArray(data) ? data : data?.items || data?.files || [];
435
579
  setFiles(items);
436
580
  return items;
437
581
  } catch (err) {
@@ -479,7 +623,21 @@ function useFiles(autoFetch = true) {
479
623
  async (fileId) => {
480
624
  const res = await fetchWithAuth(`/files/${fileId}`, { method: "DELETE" });
481
625
  if (!res.ok) throw new Error(`Failed to delete file: ${res.statusText}`);
482
- setFiles((prev) => prev.filter((f) => f._id !== fileId));
626
+ setFiles((prev) => prev.filter((f) => f.id !== fileId));
627
+ },
628
+ [fetchWithAuth]
629
+ );
630
+ const bulkDeleteFiles = useCallback4(
631
+ async (fileIds) => {
632
+ const res = await fetchWithAuth("/files/bulk-delete", {
633
+ method: "POST",
634
+ headers: { "Content-Type": "application/json" },
635
+ body: JSON.stringify({ ids: fileIds })
636
+ });
637
+ if (!res.ok) throw new Error(`Failed to bulk-delete files: ${res.statusText}`);
638
+ const idSet = new Set(fileIds);
639
+ setFiles((prev) => prev.filter((f) => !idSet.has(f.id)));
640
+ return await res.json();
483
641
  },
484
642
  [fetchWithAuth]
485
643
  );
@@ -489,7 +647,7 @@ function useFiles(autoFetch = true) {
489
647
  },
490
648
  []
491
649
  );
492
- useEffect3(() => {
650
+ useEffect4(() => {
493
651
  if (autoFetch) {
494
652
  void fetchFiles();
495
653
  }
@@ -502,12 +660,13 @@ function useFiles(autoFetch = true) {
502
660
  refetch: fetchFiles,
503
661
  uploadFile,
504
662
  deleteFile,
663
+ bulkDeleteFiles,
505
664
  getDownloadUrl
506
665
  };
507
666
  }
508
667
 
509
668
  // src/hooks/useAgents.ts
510
- import { useCallback as useCallback5, useEffect as useEffect4, useState as useState5 } from "react";
669
+ import { useCallback as useCallback5, useEffect as useEffect5, useState as useState5 } from "react";
511
670
  function useAgents(autoFetch = true) {
512
671
  const { fetchWithAuth } = usePersonaContext();
513
672
  const [agents, setAgents] = useState5([]);
@@ -520,7 +679,7 @@ function useAgents(autoFetch = true) {
520
679
  const res = await fetchWithAuth("/agents");
521
680
  if (!res.ok) throw new Error(`Failed to list agents: ${res.statusText}`);
522
681
  const data = await res.json();
523
- const items = Array.isArray(data) ? data : data?.agents || data?.items || [];
682
+ const items = Array.isArray(data) ? data : data?.items || data?.agents || [];
524
683
  setAgents(items);
525
684
  return items;
526
685
  } catch (err) {
@@ -531,7 +690,7 @@ function useAgents(autoFetch = true) {
531
690
  setIsLoading(false);
532
691
  }
533
692
  }, [fetchWithAuth]);
534
- useEffect4(() => {
693
+ useEffect5(() => {
535
694
  if (autoFetch) {
536
695
  void fetchAgents();
537
696
  }
@@ -545,7 +704,7 @@ function useAgents(autoFetch = true) {
545
704
  }
546
705
 
547
706
  // src/hooks/useConnection.ts
548
- import { useCallback as useCallback6, useEffect as useEffect5, useState as useState6 } from "react";
707
+ import { useCallback as useCallback6, useEffect as useEffect6, useState as useState6 } from "react";
549
708
  function useConnection(autoCheck = true) {
550
709
  const { fetchWithAuth } = usePersonaContext();
551
710
  const [health, setHealth] = useState6(null);
@@ -570,7 +729,7 @@ function useConnection(autoCheck = true) {
570
729
  setIsLoading(false);
571
730
  }
572
731
  }, [fetchWithAuth]);
573
- useEffect5(() => {
732
+ useEffect6(() => {
574
733
  if (autoCheck) {
575
734
  void checkHealth();
576
735
  }
@@ -584,7 +743,7 @@ function useConnection(autoCheck = true) {
584
743
  }
585
744
 
586
745
  // src/index.ts
587
- var VERSION = "0.1.2";
746
+ var VERSION = "0.2.0";
588
747
  export {
589
748
  PersonaProvider,
590
749
  VERSION,