@personaai/react 0.1.2 → 0.3.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 CHANGED
@@ -76,14 +76,60 @@ function usePersonaContext() {
76
76
 
77
77
  // src/hooks/useChat.ts
78
78
  var import_react2 = require("react");
79
+ function isErrorToolContent(content) {
80
+ if (typeof content !== "string" || !content.trim().startsWith("{")) return false;
81
+ try {
82
+ return JSON.parse(content)?.status === "error";
83
+ } catch {
84
+ return false;
85
+ }
86
+ }
87
+ function parsePresentedFile(content) {
88
+ try {
89
+ const parsed = JSON.parse(content);
90
+ if (parsed?.status !== "success" || typeof parsed.filePath !== "string") return null;
91
+ return {
92
+ path: parsed.filePath,
93
+ title: typeof parsed.title === "string" ? parsed.title : parsed.filePath,
94
+ description: typeof parsed.description === "string" ? parsed.description : ""
95
+ };
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+ function normalizePendingInterrupt(pending) {
101
+ if (!pending || typeof pending !== "object") return null;
102
+ const p = pending;
103
+ if (p.kind === "hitl") {
104
+ return {
105
+ kind: "hitl",
106
+ actionRequests: p.value?.actionRequests ?? [],
107
+ reviewConfigs: p.value?.reviewConfigs ?? []
108
+ };
109
+ }
110
+ if (p.kind === "clarification") {
111
+ return {
112
+ kind: "clarification",
113
+ questions: p.value?.questions ?? []
114
+ };
115
+ }
116
+ return null;
117
+ }
79
118
  function useChat(options = {}) {
80
119
  const { defaultAgentId, fetchWithAuth } = usePersonaContext();
81
120
  const agentId = options.agentId || defaultAgentId;
121
+ const threadId = options.threadId;
82
122
  const [messages, setMessages] = (0, import_react2.useState)(options.initialMessages || []);
83
123
  const [input, setInput] = (0, import_react2.useState)("");
84
124
  const [isStreaming, setIsStreaming] = (0, import_react2.useState)(false);
125
+ const [isLoadingHistory, setIsLoadingHistory] = (0, import_react2.useState)(false);
85
126
  const [error, setError] = (0, import_react2.useState)(null);
127
+ const [interrupt, setInterrupt] = (0, import_react2.useState)(null);
128
+ const [files, setFiles] = (0, import_react2.useState)({});
129
+ const [todos, setTodos] = (0, import_react2.useState)([]);
130
+ const [presentedFile, setPresentedFile] = (0, import_react2.useState)(null);
86
131
  const abortControllerRef = (0, import_react2.useRef)(null);
132
+ const loadedThreadIdRef = (0, import_react2.useRef)(void 0);
87
133
  const stop = (0, import_react2.useCallback)(() => {
88
134
  if (abortControllerRef.current) {
89
135
  abortControllerRef.current.abort();
@@ -95,7 +141,50 @@ function useChat(options = {}) {
95
141
  stop();
96
142
  setMessages([]);
97
143
  setError(null);
144
+ setInterrupt(null);
145
+ setFiles({});
146
+ setTodos([]);
147
+ setPresentedFile(null);
98
148
  }, [stop]);
149
+ const loadThreadMessages = (0, import_react2.useCallback)(
150
+ async (id) => {
151
+ setIsLoadingHistory(true);
152
+ setError(null);
153
+ try {
154
+ const res = await fetchWithAuth(`/threads/${id}/messages`);
155
+ if (!res.ok) throw new Error(`Failed to load thread history: ${res.statusText}`);
156
+ const body = await res.json();
157
+ const data = body?.data ?? body;
158
+ const raw = data?.messages ?? [];
159
+ const loaded = raw.map((m, i) => ({
160
+ id: m.id || `history-${id}-${i}`,
161
+ role: m.role,
162
+ content: m.content,
163
+ createdAt: /* @__PURE__ */ new Date(),
164
+ toolCalls: m.toolCalls
165
+ }));
166
+ setMessages(loaded);
167
+ setInterrupt(normalizePendingInterrupt(data?.pendingInterrupt));
168
+ setFiles(data?.state?.files ?? {});
169
+ setTodos(data?.state?.todos ?? []);
170
+ return loaded;
171
+ } catch (err) {
172
+ const errorObj = err instanceof Error ? err : new Error(String(err));
173
+ setError(errorObj);
174
+ return [];
175
+ } finally {
176
+ setIsLoadingHistory(false);
177
+ }
178
+ },
179
+ [fetchWithAuth]
180
+ );
181
+ (0, import_react2.useEffect)(() => {
182
+ if (!threadId || isStreaming) return;
183
+ if (loadedThreadIdRef.current === threadId) return;
184
+ if (messages.length > 0) return;
185
+ loadedThreadIdRef.current = threadId;
186
+ void loadThreadMessages(threadId);
187
+ }, [threadId, isStreaming, messages.length, loadThreadMessages]);
99
188
  const sendMessage = (0, import_react2.useCallback)(
100
189
  async (contentToSend, overrideOptions) => {
101
190
  const prompt = (contentToSend ?? input).trim();
@@ -127,6 +216,7 @@ function useChat(options = {}) {
127
216
  setInput("");
128
217
  setIsStreaming(true);
129
218
  setError(null);
219
+ setInterrupt(null);
130
220
  const controller = new AbortController();
131
221
  abortControllerRef.current = controller;
132
222
  try {
@@ -142,7 +232,8 @@ function useChat(options = {}) {
142
232
  body: JSON.stringify({
143
233
  agentId: targetAgentId,
144
234
  messages: payloadMessages,
145
- threadId: overrideOptions?.threadId || options.threadId
235
+ threadId: overrideOptions?.threadId || options.threadId,
236
+ resume: overrideOptions?.resume
146
237
  }),
147
238
  signal: controller.signal
148
239
  });
@@ -157,7 +248,15 @@ function useChat(options = {}) {
157
248
  const decoder = new TextDecoder("utf-8");
158
249
  let buffer = "";
159
250
  let accumulatedText = "";
251
+ let accumulatedReasoning = "";
160
252
  const toolCallsMap = /* @__PURE__ */ new Map();
253
+ const patchAssistant = (patch) => {
254
+ setMessages(
255
+ (prev) => prev.map(
256
+ (msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()), ...patch } : msg
257
+ )
258
+ );
259
+ };
161
260
  while (true) {
162
261
  const { done, value } = await reader.read();
163
262
  if (done) break;
@@ -174,47 +273,56 @@ function useChat(options = {}) {
174
273
  options.onEvent?.(event);
175
274
  if (event.type === "TEXT_MESSAGE_CHUNK" && event.delta) {
176
275
  accumulatedText += event.delta;
177
- setMessages(
178
- (prev) => prev.map(
179
- (msg) => msg.id === assistantMessageId ? {
180
- ...msg,
181
- content: accumulatedText,
182
- isStreaming: true,
183
- toolCalls: Array.from(toolCallsMap.values())
184
- } : msg
185
- )
186
- );
187
- } else if (event.type === "TOOL_CALL_START") {
188
- toolCallsMap.set(event.toolCallId, {
189
- toolCallId: event.toolCallId,
190
- toolName: event.toolName,
191
- args: ""
192
- });
193
- setMessages(
194
- (prev) => prev.map(
195
- (msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()) } : msg
196
- )
197
- );
198
- } else if (event.type === "TOOL_CALL_ARGS") {
276
+ patchAssistant({ content: accumulatedText, isStreaming: true });
277
+ } else if (event.type === "TOOL_CALL_CHUNK" && event.toolCallId) {
199
278
  const existing = toolCallsMap.get(event.toolCallId);
200
279
  if (existing) {
201
- existing.args = (existing.args || "") + event.delta;
202
- setMessages(
203
- (prev) => prev.map(
204
- (msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()) } : msg
205
- )
206
- );
280
+ existing.args = (existing.args || "") + (event.delta || "");
281
+ } else {
282
+ toolCallsMap.set(event.toolCallId, {
283
+ toolCallId: event.toolCallId,
284
+ toolName: event.toolCallName || "",
285
+ args: event.delta || ""
286
+ });
207
287
  }
288
+ patchAssistant({});
208
289
  } else if (event.type === "TOOL_CALL_RESULT") {
209
290
  const existing = toolCallsMap.get(event.toolCallId);
210
291
  if (existing) {
211
- existing.result = event.result;
212
- existing.isError = event.isError;
213
- setMessages(
214
- (prev) => prev.map(
215
- (msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()) } : msg
216
- )
217
- );
292
+ existing.result = event.content;
293
+ existing.isError = isErrorToolContent(event.content);
294
+ if (existing.toolName === "present_file" && !existing.isError) {
295
+ const presented = parsePresentedFile(event.content);
296
+ if (presented) setPresentedFile(presented);
297
+ }
298
+ patchAssistant({});
299
+ }
300
+ } else if (event.type === "STATE_SNAPSHOT") {
301
+ setFiles(event.snapshot.files);
302
+ setTodos(event.snapshot.todos);
303
+ } else if (event.type === "REASONING_MESSAGE_CONTENT") {
304
+ accumulatedReasoning += event.delta;
305
+ patchAssistant({ reasoning: accumulatedReasoning, isReasoning: true });
306
+ } else if (event.type === "REASONING_END") {
307
+ patchAssistant({ isReasoning: false });
308
+ } else if (event.type === "CUSTOM") {
309
+ if (event.name === "hitl_request") {
310
+ const value2 = event.value;
311
+ setInterrupt({
312
+ kind: "hitl",
313
+ actionRequests: value2.actionRequests,
314
+ reviewConfigs: value2.reviewConfigs
315
+ });
316
+ } else if (event.name === "clarification_request") {
317
+ const value2 = event.value;
318
+ setInterrupt({ kind: "clarification", questions: value2.questions });
319
+ } else if (event.name === "subagent_activity") {
320
+ const { toolCallId, ...entry } = event.value;
321
+ const existing = toolCallsMap.get(toolCallId);
322
+ if (existing) {
323
+ existing.subagentActivity = [...existing.subagentActivity || [], entry];
324
+ patchAssistant({});
325
+ }
218
326
  }
219
327
  } else if (event.type === "RUN_ERROR") {
220
328
  throw new Error(event.message || "Stream error from agent");
@@ -232,7 +340,8 @@ function useChat(options = {}) {
232
340
  content: accumulatedText,
233
341
  createdAt: /* @__PURE__ */ new Date(),
234
342
  isStreaming: false,
235
- toolCalls: Array.from(toolCallsMap.values())
343
+ toolCalls: Array.from(toolCallsMap.values()),
344
+ ...accumulatedReasoning ? { reasoning: accumulatedReasoning, isReasoning: false } : {}
236
345
  };
237
346
  setMessages(
238
347
  (prev) => prev.map((msg) => msg.id === assistantMessageId ? finalMessage : msg)
@@ -290,6 +399,11 @@ function useChat(options = {}) {
290
399
  setMessages(messages.slice(0, lastUserIndex));
291
400
  void sendMessage(lastUserMessage.content);
292
401
  }, [isStreaming, messages, sendMessage]);
402
+ const resumeInterrupt = (0, import_react2.useCallback)(
403
+ (resume, displayContent) => sendMessage(displayContent, { resume }),
404
+ [sendMessage]
405
+ );
406
+ const dismissPresentedFile = (0, import_react2.useCallback)(() => setPresentedFile(null), []);
293
407
  return {
294
408
  messages,
295
409
  input,
@@ -299,11 +413,19 @@ function useChat(options = {}) {
299
413
  sendMessage,
300
414
  isStreaming,
301
415
  isLoading: isStreaming,
416
+ isLoadingHistory,
302
417
  error,
418
+ interrupt,
419
+ resumeInterrupt,
420
+ files,
421
+ todos,
422
+ presentedFile,
423
+ dismissPresentedFile,
303
424
  stop,
304
425
  reload,
305
426
  clear,
306
- setMessages
427
+ setMessages,
428
+ loadThreadMessages
307
429
  };
308
430
  }
309
431
 
@@ -311,7 +433,7 @@ function useChat(options = {}) {
311
433
  var import_react3 = require("react");
312
434
  function useMemory(autoFetch = true) {
313
435
  const { fetchWithAuth } = usePersonaContext();
314
- const [memory, setMemory] = (0, import_react3.useState)({ user: [], agents: {} });
436
+ const [memory, setMemory] = (0, import_react3.useState)({ userFiles: [], agentMemories: [] });
315
437
  const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
316
438
  const [error, setError] = (0, import_react3.useState)(null);
317
439
  const fetchMemory = (0, import_react3.useCallback)(async () => {
@@ -328,7 +450,7 @@ function useMemory(autoFetch = true) {
328
450
  } catch (err) {
329
451
  const errorObj = err instanceof Error ? err : new Error(String(err));
330
452
  setError(errorObj);
331
- return { user: [], agents: {} };
453
+ return { userFiles: [], agentMemories: [] };
332
454
  } finally {
333
455
  setIsLoading(false);
334
456
  }
@@ -401,7 +523,7 @@ function useThreads(autoFetch = true) {
401
523
  const res = await fetchWithAuth("/threads");
402
524
  if (!res.ok) throw new Error(`Failed to list threads: ${res.statusText}`);
403
525
  const data = await res.json();
404
- const items = Array.isArray(data) ? data : data?.threads || data?.items || [];
526
+ const items = Array.isArray(data) ? data : data?.items || data?.threads || (Array.isArray(data?.data) ? data.data : void 0) || [];
405
527
  setThreads(items);
406
528
  return items;
407
529
  } catch (err) {
@@ -421,7 +543,8 @@ function useThreads(autoFetch = true) {
421
543
  body: JSON.stringify({ agentId: targetAgentId })
422
544
  });
423
545
  if (!res.ok) throw new Error(`Failed to create thread: ${res.statusText}`);
424
- const created = await res.json();
546
+ const data = await res.json();
547
+ const created = data?.data ?? data;
425
548
  void fetchThreads();
426
549
  return created;
427
550
  },
@@ -435,6 +558,56 @@ function useThreads(autoFetch = true) {
435
558
  },
436
559
  [fetchWithAuth]
437
560
  );
561
+ const bulkDeleteThreads = (0, import_react4.useCallback)(
562
+ async (threadIds) => {
563
+ const res = await fetchWithAuth("/threads/bulk-delete", {
564
+ method: "POST",
565
+ headers: { "Content-Type": "application/json" },
566
+ body: JSON.stringify({ ids: threadIds })
567
+ });
568
+ if (!res.ok) throw new Error(`Failed to bulk-delete threads: ${res.statusText}`);
569
+ const data = await res.json();
570
+ const result = data?.data ?? data;
571
+ const deletedSet = new Set(result.deleted);
572
+ setThreads((prev) => prev.filter((t) => !deletedSet.has(t._id)));
573
+ return result;
574
+ },
575
+ [fetchWithAuth]
576
+ );
577
+ const deleteAllThreads = (0, import_react4.useCallback)(async () => {
578
+ const ids = threads.map((t) => t._id);
579
+ for (let i = 0; i < ids.length; i += 100) {
580
+ await bulkDeleteThreads(ids.slice(i, i + 100));
581
+ }
582
+ }, [threads, bulkDeleteThreads]);
583
+ const updateThread = (0, import_react4.useCallback)(
584
+ async (threadId, input) => {
585
+ const res = await fetchWithAuth(`/threads/${threadId}`, {
586
+ method: "PATCH",
587
+ headers: { "Content-Type": "application/json" },
588
+ body: JSON.stringify(input)
589
+ });
590
+ if (!res.ok) throw new Error(`Failed to update thread: ${res.statusText}`);
591
+ const data = await res.json();
592
+ const updated = data?.data ?? data;
593
+ setThreads((prev) => prev.map((t) => t._id === threadId ? { ...t, ...updated } : t));
594
+ return updated;
595
+ },
596
+ [fetchWithAuth]
597
+ );
598
+ const renameThread = (0, import_react4.useCallback)(
599
+ (threadId, title) => updateThread(threadId, { title }),
600
+ [updateThread]
601
+ );
602
+ const getThread = (0, import_react4.useCallback)(
603
+ async (threadId) => {
604
+ const res = await fetchWithAuth(`/threads/${threadId}`);
605
+ if (!res.ok) throw new Error(`Failed to fetch thread: ${res.statusText}`);
606
+ const data = await res.json();
607
+ return data?.data ?? data;
608
+ },
609
+ [fetchWithAuth]
610
+ );
438
611
  (0, import_react4.useEffect)(() => {
439
612
  if (autoFetch) {
440
613
  void fetchThreads();
@@ -446,7 +619,12 @@ function useThreads(autoFetch = true) {
446
619
  error,
447
620
  refetch: fetchThreads,
448
621
  createThread,
449
- deleteThread
622
+ deleteThread,
623
+ bulkDeleteThreads,
624
+ deleteAllThreads,
625
+ updateThread,
626
+ renameThread,
627
+ getThread
450
628
  };
451
629
  }
452
630
 
@@ -465,7 +643,7 @@ function useFiles(autoFetch = true) {
465
643
  const res = await fetchWithAuth("/files");
466
644
  if (!res.ok) throw new Error(`Failed to list files: ${res.statusText}`);
467
645
  const data = await res.json();
468
- const items = Array.isArray(data) ? data : data?.files || data?.items || [];
646
+ const items = Array.isArray(data) ? data : data?.items || data?.files || [];
469
647
  setFiles(items);
470
648
  return items;
471
649
  } catch (err) {
@@ -513,7 +691,21 @@ function useFiles(autoFetch = true) {
513
691
  async (fileId) => {
514
692
  const res = await fetchWithAuth(`/files/${fileId}`, { method: "DELETE" });
515
693
  if (!res.ok) throw new Error(`Failed to delete file: ${res.statusText}`);
516
- setFiles((prev) => prev.filter((f) => f._id !== fileId));
694
+ setFiles((prev) => prev.filter((f) => f.id !== fileId));
695
+ },
696
+ [fetchWithAuth]
697
+ );
698
+ const bulkDeleteFiles = (0, import_react5.useCallback)(
699
+ async (fileIds) => {
700
+ const res = await fetchWithAuth("/files/bulk-delete", {
701
+ method: "POST",
702
+ headers: { "Content-Type": "application/json" },
703
+ body: JSON.stringify({ ids: fileIds })
704
+ });
705
+ if (!res.ok) throw new Error(`Failed to bulk-delete files: ${res.statusText}`);
706
+ const idSet = new Set(fileIds);
707
+ setFiles((prev) => prev.filter((f) => !idSet.has(f.id)));
708
+ return await res.json();
517
709
  },
518
710
  [fetchWithAuth]
519
711
  );
@@ -536,6 +728,7 @@ function useFiles(autoFetch = true) {
536
728
  refetch: fetchFiles,
537
729
  uploadFile,
538
730
  deleteFile,
731
+ bulkDeleteFiles,
539
732
  getDownloadUrl
540
733
  };
541
734
  }
@@ -554,7 +747,7 @@ function useAgents(autoFetch = true) {
554
747
  const res = await fetchWithAuth("/agents");
555
748
  if (!res.ok) throw new Error(`Failed to list agents: ${res.statusText}`);
556
749
  const data = await res.json();
557
- const items = Array.isArray(data) ? data : data?.agents || data?.items || [];
750
+ const items = Array.isArray(data) ? data : data?.items || data?.agents || [];
558
751
  setAgents(items);
559
752
  return items;
560
753
  } catch (err) {
@@ -618,7 +811,7 @@ function useConnection(autoCheck = true) {
618
811
  }
619
812
 
620
813
  // src/index.ts
621
- var VERSION = "0.1.2";
814
+ var VERSION = "0.2.0";
622
815
  // Annotate the CommonJS export names for ESM import in node:
623
816
  0 && (module.exports = {
624
817
  PersonaProvider,