@personaai/react 0.5.1 → 0.5.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 CHANGED
@@ -1,47 +1,111 @@
1
1
  // src/context/PersonaContext.tsx
2
2
  import { createContext, useContext, useMemo } from "react";
3
+ import { createLogger } from "@personaai/logger";
3
4
  import { jsx } from "react/jsx-runtime";
4
5
  var PersonaContext = createContext(null);
5
6
  function PersonaProvider({
6
7
  baseUrl,
7
8
  getAuthToken,
8
9
  defaultAgentId,
10
+ logLevel,
11
+ logger: loggerProp,
9
12
  children
10
13
  }) {
11
- const normalizedBaseUrl = useMemo(() => baseUrl.replace(/\/+$/, ""), [baseUrl]);
14
+ const normalizedBaseUrl = useMemo(
15
+ () => baseUrl.replace(/\/+$/, ""),
16
+ [baseUrl]
17
+ );
18
+ const logger = useMemo(() => {
19
+ const l = loggerProp ?? createLogger(
20
+ "react",
21
+ logLevel !== void 0 ? { level: logLevel } : void 0
22
+ );
23
+ l.debug("PersonaProvider created", {
24
+ baseUrl: normalizedBaseUrl,
25
+ hasDefaultAgentId: !!defaultAgentId
26
+ });
27
+ l.info("PersonaProvider mounted", { baseUrl: normalizedBaseUrl });
28
+ l.trace("PersonaProvider config", {
29
+ baseUrl: normalizedBaseUrl,
30
+ hasGetAuthToken: !!getAuthToken
31
+ });
32
+ return l;
33
+ }, [loggerProp, logLevel, normalizedBaseUrl, defaultAgentId, getAuthToken]);
12
34
  const value = useMemo(() => {
13
35
  async function fetchWithAuth(path, init = {}) {
14
36
  const token = getAuthToken ? await getAuthToken() : null;
15
37
  const headers = new Headers(init.headers || {});
16
38
  if (token) {
17
39
  headers.set("Authorization", `Bearer ${token}`);
40
+ logger.trace("fetchWithAuth with token", { path });
41
+ } else {
42
+ logger.trace("fetchWithAuth without token", { path });
43
+ logger.debug("fetchWithAuth no token", { path });
18
44
  }
19
45
  const cleanPath = path.startsWith("/") ? path : `/${path}`;
20
46
  const url = `${normalizedBaseUrl}${cleanPath}`;
21
- return fetch(url, {
22
- ...init,
23
- headers
47
+ logger.debug("fetchWithAuth request", {
48
+ path: cleanPath,
49
+ url,
50
+ hasToken: !!token
51
+ });
52
+ logger.trace("fetchWithAuth details", {
53
+ url,
54
+ path: cleanPath,
55
+ hasToken: !!token
24
56
  });
57
+ try {
58
+ const res = await fetch(url, {
59
+ ...init,
60
+ headers
61
+ });
62
+ logger.debug("fetchWithAuth response", {
63
+ path: cleanPath,
64
+ status: res.status,
65
+ ok: res.ok
66
+ });
67
+ if (!res.ok) {
68
+ logger.warn("fetchWithAuth non-ok", {
69
+ path: cleanPath,
70
+ status: res.status
71
+ });
72
+ } else {
73
+ logger.info("fetchWithAuth succeeded", {
74
+ path: cleanPath,
75
+ status: res.status
76
+ });
77
+ }
78
+ return res;
79
+ } catch (err) {
80
+ logger.error("fetchWithAuth failed", {
81
+ path: cleanPath,
82
+ error: err instanceof Error ? err.message : String(err)
83
+ });
84
+ throw err;
85
+ }
25
86
  }
26
87
  return {
27
88
  baseUrl: normalizedBaseUrl,
28
89
  getAuthToken,
29
90
  defaultAgentId,
30
- fetchWithAuth
91
+ fetchWithAuth,
92
+ logger
31
93
  };
32
- }, [normalizedBaseUrl, getAuthToken, defaultAgentId]);
94
+ }, [normalizedBaseUrl, getAuthToken, defaultAgentId, logger]);
33
95
  return /* @__PURE__ */ jsx(PersonaContext.Provider, { value, children });
34
96
  }
35
97
  function usePersonaContext() {
36
98
  const context = useContext(PersonaContext);
37
99
  if (!context) {
38
- throw new Error("usePersonaContext must be used within a <PersonaProvider>");
100
+ throw new Error(
101
+ "usePersonaContext must be used within a <PersonaProvider>"
102
+ );
39
103
  }
40
104
  return context;
41
105
  }
42
106
 
43
107
  // src/hooks/useChat.ts
44
- import { useCallback, useEffect, useRef, useState } from "react";
108
+ import { useCallback, useEffect, useMemo as useMemo2, useRef, useState } from "react";
45
109
 
46
110
  // src/streaming.ts
47
111
  function supportsStreamingFetch() {
@@ -70,7 +134,8 @@ function xhrStream(opts) {
70
134
  return new Promise((resolveStream, rejectStream) => {
71
135
  const xhr = new XMLHttpRequest();
72
136
  xhr.open("POST", opts.url);
73
- for (const [key, val] of Object.entries(opts.headers)) xhr.setRequestHeader(key, val);
137
+ for (const [key, val] of Object.entries(opts.headers))
138
+ xhr.setRequestHeader(key, val);
74
139
  let consumed = 0;
75
140
  let finished = false;
76
141
  let failure = null;
@@ -192,7 +257,10 @@ async function openSSEStream(opts) {
192
257
  ok: false,
193
258
  errorText,
194
259
  getHeader: (name) => response.headers.get(name),
195
- reader: { read: async () => ({ done: true }), cancel: () => controller.abort() }
260
+ reader: {
261
+ read: async () => ({ done: true }),
262
+ cancel: () => controller.abort()
263
+ }
196
264
  };
197
265
  }
198
266
  if (!response.body) {
@@ -218,7 +286,8 @@ async function openSSEStream(opts) {
218
286
 
219
287
  // src/hooks/useChat.ts
220
288
  function isErrorToolContent(content) {
221
- if (typeof content !== "string" || !content.trim().startsWith("{")) return false;
289
+ if (typeof content !== "string" || !content.trim().startsWith("{"))
290
+ return false;
222
291
  try {
223
292
  return JSON.parse(content)?.status === "error";
224
293
  } catch {
@@ -231,9 +300,17 @@ function persistedTraceToActivityEntries(items) {
231
300
  if (item.type === "text") {
232
301
  if (item.text) entries.push({ kind: "text", delta: item.text });
233
302
  } else {
234
- entries.push({ kind: "tool_start", toolName: item.name, args: item.argsText });
303
+ entries.push({
304
+ kind: "tool_start",
305
+ toolName: item.name,
306
+ args: item.argsText
307
+ });
235
308
  if (item.status === "completed") {
236
- entries.push({ kind: "tool_result", toolName: item.name, result: item.resultText });
309
+ entries.push({
310
+ kind: "tool_result",
311
+ toolName: item.name,
312
+ result: item.resultText
313
+ });
237
314
  }
238
315
  }
239
316
  }
@@ -242,7 +319,8 @@ function persistedTraceToActivityEntries(items) {
242
319
  function parsePresentedFile(content) {
243
320
  try {
244
321
  const parsed = JSON.parse(content);
245
- if (parsed?.status !== "success" || typeof parsed.filePath !== "string") return null;
322
+ if (parsed?.status !== "success" || typeof parsed.filePath !== "string")
323
+ return null;
246
324
  return {
247
325
  path: parsed.filePath,
248
326
  title: typeof parsed.title === "string" ? parsed.title : parsed.filePath,
@@ -283,10 +361,27 @@ function normalizePendingInterrupt(pending) {
283
361
  return null;
284
362
  }
285
363
  function useChat(options = {}) {
286
- const { defaultAgentId, fetchWithAuth, baseUrl, getAuthToken } = usePersonaContext();
364
+ const { defaultAgentId, fetchWithAuth, baseUrl, getAuthToken, logger } = usePersonaContext();
365
+ const chatLogger = useMemo2(() => logger.child("chat"), [logger]);
287
366
  const agentId = options.agentId || defaultAgentId;
288
367
  const threadId = options.threadId;
289
- const [messages, setMessages] = useState(options.initialMessages || []);
368
+ const didLogInitRef = useRef(false);
369
+ if (!didLogInitRef.current) {
370
+ didLogInitRef.current = true;
371
+ chatLogger.debug("useChat init", {
372
+ agentId,
373
+ threadId,
374
+ hasInitialMessages: !!options.initialMessages?.length
375
+ });
376
+ chatLogger.trace("useChat options", {
377
+ agentId,
378
+ threadId,
379
+ initialMessageCount: options.initialMessages?.length ?? 0
380
+ });
381
+ }
382
+ const [messages, setMessages] = useState(
383
+ options.initialMessages || []
384
+ );
290
385
  const [input, setInput] = useState("");
291
386
  const [isStreaming, setIsStreaming] = useState(false);
292
387
  const [isLoadingHistory, setIsLoadingHistory] = useState(false);
@@ -299,12 +394,21 @@ function useChat(options = {}) {
299
394
  const loadedThreadIdRef = useRef(void 0);
300
395
  const stop = useCallback(() => {
301
396
  if (abortControllerRef.current) {
397
+ chatLogger.info("stop streaming", {});
398
+ chatLogger.debug("abort controller", {
399
+ hasController: !!abortControllerRef.current
400
+ });
302
401
  abortControllerRef.current.abort();
303
402
  abortControllerRef.current = null;
304
403
  setIsStreaming(false);
404
+ chatLogger.debug("streaming stopped");
405
+ } else {
406
+ chatLogger.trace("stop called \u2014 no active stream");
305
407
  }
306
- }, []);
408
+ }, [chatLogger]);
307
409
  const clear = useCallback(() => {
410
+ chatLogger.info("clear chat", { messageCount: messages.length });
411
+ chatLogger.debug("clear", { messageCount: messages.length });
308
412
  stop();
309
413
  setMessages([]);
310
414
  setError(null);
@@ -312,14 +416,17 @@ function useChat(options = {}) {
312
416
  setFiles({});
313
417
  setTodos([]);
314
418
  setPresentedFile(null);
315
- }, [stop]);
419
+ }, [stop, chatLogger, messages.length]);
316
420
  const loadThreadMessages = useCallback(
317
421
  async (id) => {
422
+ chatLogger.debug("loadThreadMessages start", { threadId: id });
423
+ chatLogger.trace("loadThreadMessages", { threadId: id });
318
424
  setIsLoadingHistory(true);
319
425
  setError(null);
320
426
  try {
321
427
  const res = await fetchWithAuth(`/threads/${id}/messages`);
322
- if (!res.ok) throw new Error(`Failed to load thread history: ${res.statusText}`);
428
+ if (!res.ok)
429
+ throw new Error(`Failed to load thread history: ${res.statusText}`);
323
430
  const body = await res.json();
324
431
  const data = body?.data ?? body;
325
432
  const raw = data?.messages ?? [];
@@ -331,42 +438,98 @@ function useChat(options = {}) {
331
438
  createdAt: /* @__PURE__ */ new Date(),
332
439
  toolCalls: m.toolCalls?.map((tc) => {
333
440
  const trace = subagentTraces[tc.toolCallId];
334
- return Array.isArray(trace) && trace.length > 0 ? { ...tc, subagentActivity: persistedTraceToActivityEntries(trace) } : tc;
441
+ return Array.isArray(trace) && trace.length > 0 ? {
442
+ ...tc,
443
+ subagentActivity: persistedTraceToActivityEntries(trace)
444
+ } : tc;
335
445
  })
336
446
  }));
337
447
  setMessages(loaded);
338
448
  setInterrupt(normalizePendingInterrupt(data?.pendingInterrupt));
339
449
  setFiles(normalizeWorkspaceFiles(data?.state?.files ?? {}));
340
450
  setTodos(data?.state?.todos ?? []);
451
+ chatLogger.info("loadThreadMessages succeeded", {
452
+ threadId: id,
453
+ messageCount: loaded.length
454
+ });
455
+ chatLogger.debug("loadThreadMessages completed", {
456
+ threadId: id,
457
+ messageCount: loaded.length
458
+ });
341
459
  return loaded;
342
460
  } catch (err) {
343
461
  const errorObj = err instanceof Error ? err : new Error(String(err));
462
+ chatLogger.warn("loadThreadMessages failed", {
463
+ threadId: id,
464
+ error: errorObj.message
465
+ });
466
+ chatLogger.error("loadThreadMessages error", {
467
+ threadId: id,
468
+ error: errorObj.message
469
+ });
344
470
  setError(errorObj);
345
471
  return [];
346
472
  } finally {
347
473
  setIsLoadingHistory(false);
474
+ chatLogger.trace("loadThreadMessages end", { threadId: id });
348
475
  }
349
476
  },
350
- [fetchWithAuth]
477
+ [fetchWithAuth, chatLogger]
351
478
  );
352
479
  useEffect(() => {
353
- if (!threadId || isStreaming) return;
354
- if (loadedThreadIdRef.current === threadId) return;
355
- if (messages.length > 0) return;
480
+ if (!threadId || isStreaming) {
481
+ chatLogger.trace("auto-load skipped", { threadId, isStreaming });
482
+ return;
483
+ }
484
+ if (loadedThreadIdRef.current === threadId) {
485
+ chatLogger.trace("auto-load already loaded", { threadId });
486
+ return;
487
+ }
488
+ if (messages.length > 0) {
489
+ chatLogger.trace("auto-load has messages", {
490
+ threadId,
491
+ count: messages.length
492
+ });
493
+ return;
494
+ }
356
495
  loadedThreadIdRef.current = threadId;
496
+ chatLogger.info("auto-load thread history", { threadId });
497
+ chatLogger.debug("loadThreadMessages trigger", { threadId });
357
498
  void loadThreadMessages(threadId);
358
- }, [threadId, isStreaming, messages.length, loadThreadMessages]);
499
+ }, [threadId, isStreaming, messages.length, loadThreadMessages, chatLogger]);
359
500
  const sendMessage = useCallback(
360
501
  async (contentToSend, overrideOptions) => {
361
502
  const prompt = (contentToSend ?? input).trim();
362
- if (!prompt || isStreaming) return;
503
+ if (!prompt || isStreaming) {
504
+ chatLogger.trace("sendMessage skipped", {
505
+ hasPrompt: !!prompt,
506
+ isStreaming
507
+ });
508
+ return;
509
+ }
363
510
  const targetAgentId = overrideOptions?.agentId || agentId;
364
511
  if (!targetAgentId) {
365
512
  const err = new Error("No Agent ID specified for useChat.");
513
+ chatLogger.warn("sendMessage no agentId", {});
514
+ chatLogger.error("sendMessage failed \u2014 no agent", {
515
+ error: err.message
516
+ });
366
517
  setError(err);
367
518
  options.onError?.(err);
368
519
  return;
369
520
  }
521
+ chatLogger.debug("sendMessage start", {
522
+ promptPreview: prompt.slice(0, 100),
523
+ agentId: targetAgentId,
524
+ threadId: threadId ?? overrideOptions?.threadId,
525
+ hasResume: !!overrideOptions?.resume,
526
+ messageCount: messages.length
527
+ });
528
+ chatLogger.trace("sendMessage details", {
529
+ agentId: targetAgentId,
530
+ promptPreview: prompt.slice(0, 200),
531
+ hasResume: !!overrideOptions?.resume
532
+ });
370
533
  const userMessage = {
371
534
  id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
372
535
  role: "user",
@@ -403,7 +566,13 @@ function useChat(options = {}) {
403
566
  content: m.content
404
567
  }));
405
568
  const resolvedThreadId = await (overrideOptions?.threadId ?? options.threadId);
569
+ chatLogger.trace("resolved threadId", { threadId: resolvedThreadId });
406
570
  const token = getAuthToken ? await getAuthToken() : null;
571
+ chatLogger.debug("opening SSE stream", {
572
+ agentId: targetAgentId,
573
+ hasToken: !!token,
574
+ hasThreadId: !!resolvedThreadId
575
+ });
407
576
  const stream = await openSSEStream({
408
577
  url: `${baseUrl}/chat`,
409
578
  headers: {
@@ -419,28 +588,53 @@ function useChat(options = {}) {
419
588
  signal: controller.signal
420
589
  });
421
590
  if (!stream.ok) {
422
- throw new Error(`Chat error (${stream.status}): ${stream.errorText ?? "Stream failed"}`);
591
+ chatLogger.warn("SSE stream not ok", {
592
+ status: stream.status,
593
+ errorText: stream.errorText
594
+ });
595
+ chatLogger.error("chat stream failed", {
596
+ agentId: targetAgentId,
597
+ status: stream.status,
598
+ error: stream.errorText
599
+ });
600
+ throw new Error(
601
+ `Chat error (${stream.status}): ${stream.errorText ?? "Stream failed"}`
602
+ );
423
603
  }
604
+ chatLogger.debug("SSE stream opened", {
605
+ agentId: targetAgentId,
606
+ status: stream.status
607
+ });
608
+ chatLogger.info("chat stream started", {
609
+ agentId: targetAgentId,
610
+ threadId: resolvedThreadId
611
+ });
424
612
  const reader = stream.reader;
425
613
  let buffer = "";
426
614
  let accumulatedText = "";
427
615
  const toolCallsMap = /* @__PURE__ */ new Map();
616
+ let streamSeq = 0;
428
617
  let activeReasoningId = null;
429
618
  const reasoningById = /* @__PURE__ */ new Map();
430
619
  const patchAssistant = (patch) => {
431
620
  setMessages(
432
621
  (prev) => prev.map(
433
- (msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()), ...patch } : msg
622
+ (msg) => msg.id === assistantMessageId ? {
623
+ ...msg,
624
+ toolCalls: Array.from(toolCallsMap.values()),
625
+ ...patch
626
+ } : msg
434
627
  )
435
628
  );
436
629
  };
437
- const insertReasoningMessage = (id) => {
630
+ const insertReasoningMessage = (id, seq) => {
438
631
  const msg = {
439
632
  id,
440
633
  role: "reasoning",
441
634
  content: "",
442
635
  createdAt: /* @__PURE__ */ new Date(),
443
- isStreaming: true
636
+ isStreaming: true,
637
+ seq
444
638
  };
445
639
  setMessages((prev) => {
446
640
  const idx = prev.findIndex((m) => m.id === assistantMessageId);
@@ -463,11 +657,19 @@ function useChat(options = {}) {
463
657
  if (raw === "[DONE]") break;
464
658
  try {
465
659
  const event = JSON.parse(raw);
660
+ chatLogger.trace("stream event", { type: event.type, event });
466
661
  options.onEvent?.(event);
467
662
  if (event.type === "TEXT_MESSAGE_CHUNK" && event.delta) {
663
+ chatLogger.debug("text chunk", {
664
+ deltaLength: event.delta.length
665
+ });
468
666
  accumulatedText += event.delta;
469
667
  patchAssistant({ content: accumulatedText, isStreaming: true });
470
668
  } else if (event.type === "TOOL_CALL_CHUNK" && event.toolCallId) {
669
+ chatLogger.debug("tool call chunk", {
670
+ toolCallId: event.toolCallId,
671
+ toolCallName: event.toolCallName
672
+ });
471
673
  const existing = toolCallsMap.get(event.toolCallId);
472
674
  if (existing) {
473
675
  existing.args = (existing.args || "") + (event.delta || "");
@@ -475,54 +677,86 @@ function useChat(options = {}) {
475
677
  toolCallsMap.set(event.toolCallId, {
476
678
  toolCallId: event.toolCallId,
477
679
  toolName: event.toolCallName || "",
478
- args: event.delta || ""
680
+ args: event.delta || "",
681
+ seq: streamSeq++
479
682
  });
480
683
  }
481
684
  patchAssistant({});
482
685
  } else if (event.type === "TOOL_CALL_RESULT") {
686
+ chatLogger.debug("tool call result", {
687
+ toolCallId: event.toolCallId
688
+ });
483
689
  const existing = toolCallsMap.get(event.toolCallId);
484
690
  if (existing) {
485
691
  existing.result = event.content;
486
692
  existing.isError = isErrorToolContent(event.content);
693
+ if (existing.isError)
694
+ chatLogger.warn("tool call error", {
695
+ toolCallId: event.toolCallId
696
+ });
487
697
  if (existing.toolName === "present_file" && !existing.isError) {
488
698
  const presented = parsePresentedFile(event.content);
489
- if (presented) setPresentedFile(presented);
699
+ if (presented) {
700
+ chatLogger.info("present_file", { path: presented.path });
701
+ setPresentedFile(presented);
702
+ }
490
703
  }
491
704
  patchAssistant({});
492
705
  }
493
706
  } else if (event.type === "STATE_SNAPSHOT") {
707
+ chatLogger.debug("state snapshot", {
708
+ fileCount: Object.keys(event.snapshot.files ?? {}).length,
709
+ todoCount: event.snapshot.todos?.length ?? 0
710
+ });
494
711
  setFiles(normalizeWorkspaceFiles(event.snapshot.files));
495
712
  setTodos(event.snapshot.todos);
496
713
  } else if (event.type === "REASONING_MESSAGE_START" && event.messageId) {
714
+ chatLogger.debug("reasoning start", {
715
+ messageId: event.messageId
716
+ });
497
717
  activeReasoningId = event.messageId;
498
718
  reasoningById.set(event.messageId, { content: "" });
499
- insertReasoningMessage(event.messageId);
719
+ insertReasoningMessage(event.messageId, streamSeq++);
500
720
  } else if (event.type === "REASONING_MESSAGE_CONTENT") {
501
721
  let rid = event.messageId || activeReasoningId || "";
502
722
  if (!rid || !reasoningById.has(rid)) {
503
723
  rid = rid || `reasoning-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
504
724
  activeReasoningId = rid;
505
725
  reasoningById.set(rid, { content: "" });
506
- insertReasoningMessage(rid);
726
+ insertReasoningMessage(rid, streamSeq++);
727
+ chatLogger.debug("reasoning lazy start", { messageId: rid });
507
728
  }
508
729
  const entry = reasoningById.get(rid);
509
730
  entry.content += event.delta;
731
+ chatLogger.trace("reasoning content", {
732
+ messageId: rid,
733
+ deltaLength: event.delta?.length ?? 0
734
+ });
510
735
  setMessages(
511
736
  (prev) => prev.map(
512
737
  (m) => m.id === rid ? { ...m, content: entry.content, isStreaming: true } : m
513
738
  )
514
739
  );
515
740
  } else if (event.type === "REASONING_END") {
741
+ chatLogger.debug("reasoning end", {
742
+ messageId: activeReasoningId
743
+ });
516
744
  const rid = activeReasoningId;
517
745
  activeReasoningId = null;
518
746
  if (rid) {
519
747
  setMessages(
520
- (prev) => prev.map((m) => m.id === rid ? { ...m, isStreaming: false } : m)
748
+ (prev) => prev.map(
749
+ (m) => m.id === rid ? { ...m, isStreaming: false } : m
750
+ )
521
751
  );
522
752
  }
523
753
  } else if (event.type === "CUSTOM") {
754
+ chatLogger.debug("custom event", { name: event.name });
524
755
  if (event.name === "hitl_request") {
525
756
  const value2 = event.value;
757
+ chatLogger.info("hitl interrupt", {
758
+ actionCount: value2.actionRequests?.length ?? 0
759
+ });
526
760
  setInterrupt({
527
761
  kind: "hitl",
528
762
  actionRequests: value2.actionRequests,
@@ -530,22 +764,49 @@ function useChat(options = {}) {
530
764
  });
531
765
  } else if (event.name === "clarification_request") {
532
766
  const value2 = event.value;
533
- setInterrupt({ kind: "clarification", questions: value2.questions });
767
+ chatLogger.info("clarification interrupt", {
768
+ questionCount: value2.questions?.length ?? 0
769
+ });
770
+ setInterrupt({
771
+ kind: "clarification",
772
+ questions: value2.questions
773
+ });
534
774
  } else if (event.name === "subagent_activity") {
535
775
  const { toolCallId, ...entry } = event.value;
776
+ chatLogger.trace("subagent activity", {
777
+ toolCallId,
778
+ kind: entry.kind
779
+ });
536
780
  const existing = toolCallsMap.get(toolCallId);
537
781
  if (existing) {
538
- existing.subagentActivity = [...existing.subagentActivity || [], entry];
782
+ existing.subagentActivity = [
783
+ ...existing.subagentActivity || [],
784
+ entry
785
+ ];
539
786
  patchAssistant({});
540
787
  }
541
788
  }
542
789
  } else if (event.type === "RUN_ERROR") {
790
+ chatLogger.warn("run error", {
791
+ message: event.message,
792
+ code: event.code
793
+ });
794
+ chatLogger.error("stream run error", {
795
+ code: event.code,
796
+ message: event.message
797
+ });
543
798
  throw new Error(event.message || "Stream error from agent");
799
+ } else {
800
+ chatLogger.trace("unhandled event", { type: event.type });
544
801
  }
545
802
  } catch (e) {
546
803
  if (e instanceof Error && e.message.startsWith("Stream error")) {
547
804
  throw e;
548
805
  }
806
+ chatLogger.warn("event parse error", {
807
+ raw: raw.slice(0, 200),
808
+ error: e instanceof Error ? e.message : String(e)
809
+ });
549
810
  }
550
811
  }
551
812
  }
@@ -558,11 +819,25 @@ function useChat(options = {}) {
558
819
  toolCalls: Array.from(toolCallsMap.values())
559
820
  };
560
821
  setMessages(
561
- (prev) => prev.map((msg) => msg.id === assistantMessageId ? finalMessage : msg)
822
+ (prev) => prev.map(
823
+ (msg) => msg.id === assistantMessageId ? finalMessage : msg
824
+ )
562
825
  );
826
+ chatLogger.info("sendMessage succeeded", {
827
+ agentId: targetAgentId,
828
+ textLength: accumulatedText.length,
829
+ toolCallCount: toolCallsMap.size
830
+ });
831
+ chatLogger.debug("sendMessage completed", {
832
+ agentId: targetAgentId,
833
+ textLength: accumulatedText.length,
834
+ eventCount: toolCallsMap.size + 1
835
+ });
563
836
  options.onFinish?.(finalMessage);
564
837
  } catch (err) {
565
838
  if (controller.signal.aborted) {
839
+ chatLogger.info("sendMessage aborted", { agentId: targetAgentId });
840
+ chatLogger.debug("stream aborted", { agentId: targetAgentId });
566
841
  setMessages(
567
842
  (prev) => prev.map(
568
843
  (msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg
@@ -571,6 +846,14 @@ function useChat(options = {}) {
571
846
  return;
572
847
  }
573
848
  const errorObj = err instanceof Error ? err : new Error(String(err));
849
+ chatLogger.warn("sendMessage failed", {
850
+ agentId: targetAgentId,
851
+ error: errorObj.message
852
+ });
853
+ chatLogger.error("sendMessage error", {
854
+ agentId: targetAgentId,
855
+ error: errorObj.message
856
+ });
574
857
  setError(errorObj);
575
858
  options.onError?.(errorObj);
576
859
  setMessages(
@@ -586,22 +869,48 @@ function useChat(options = {}) {
586
869
  setIsStreaming(false);
587
870
  abortControllerRef.current = null;
588
871
  finalizeReasoning();
872
+ chatLogger.debug("sendMessage finally", {
873
+ agentId: targetAgentId,
874
+ isStreaming: false
875
+ });
876
+ chatLogger.trace("sendMessage end", { agentId: targetAgentId });
589
877
  }
590
878
  },
591
- [agentId, baseUrl, getAuthToken, input, isStreaming, messages, options]
879
+ [
880
+ agentId,
881
+ baseUrl,
882
+ getAuthToken,
883
+ input,
884
+ isStreaming,
885
+ messages,
886
+ options,
887
+ chatLogger,
888
+ threadId
889
+ ]
890
+ );
891
+ const handleInputChange = useCallback(
892
+ (e) => {
893
+ chatLogger.trace("handleInputChange", { length: e.target.value.length });
894
+ setInput(e.target.value);
895
+ },
896
+ [chatLogger]
592
897
  );
593
- const handleInputChange = useCallback((e) => {
594
- setInput(e.target.value);
595
- }, []);
596
898
  const handleSubmit = useCallback(
597
899
  (e) => {
598
900
  if (e) e.preventDefault();
901
+ chatLogger.debug("handleSubmit", {});
599
902
  void sendMessage();
600
903
  },
601
- [sendMessage]
904
+ [sendMessage, chatLogger]
602
905
  );
603
906
  const reload = useCallback(() => {
604
- if (messages.length === 0 || isStreaming) return;
907
+ if (messages.length === 0 || isStreaming) {
908
+ chatLogger.trace("reload skipped", {
909
+ messageCount: messages.length,
910
+ isStreaming
911
+ });
912
+ return;
913
+ }
605
914
  let lastUserIndex = -1;
606
915
  for (let i = messages.length - 1; i >= 0; i--) {
607
916
  if (messages[i].role === "user") {
@@ -609,19 +918,42 @@ function useChat(options = {}) {
609
918
  break;
610
919
  }
611
920
  }
612
- if (lastUserIndex === -1) return;
921
+ if (lastUserIndex === -1) {
922
+ chatLogger.warn("reload no user message", {});
923
+ return;
924
+ }
613
925
  const lastUserMessage = messages[lastUserIndex];
926
+ chatLogger.info("reload", { messageId: lastUserMessage.id });
927
+ chatLogger.debug("reload last user", {
928
+ preview: lastUserMessage.content.slice(0, 100)
929
+ });
614
930
  setMessages(messages.slice(0, lastUserIndex));
615
931
  void sendMessage(lastUserMessage.content);
616
- }, [isStreaming, messages, sendMessage]);
932
+ }, [isStreaming, messages, sendMessage, chatLogger]);
617
933
  const resumeInterrupt = useCallback(
618
- (resume, displayContent) => sendMessage(displayContent, { resume }),
619
- [sendMessage]
934
+ (resume, displayContent) => {
935
+ chatLogger.info("resumeInterrupt", {
936
+ kind: resume.decisions ? "hitl" : "clarification",
937
+ preview: displayContent.slice(0, 50)
938
+ });
939
+ return sendMessage(displayContent, { resume });
940
+ },
941
+ [sendMessage, chatLogger]
620
942
  );
621
- const dismissPresentedFile = useCallback(() => setPresentedFile(null), []);
943
+ const dismissPresentedFile = useCallback(() => {
944
+ chatLogger.debug("dismissPresentedFile", {});
945
+ setPresentedFile(null);
946
+ }, [chatLogger]);
622
947
  const openWorkspaceFile = useCallback(
623
- (path) => setPresentedFile({ path, title: path.split("/").pop() || path, description: "" }),
624
- []
948
+ (path) => {
949
+ chatLogger.debug("openWorkspaceFile", { path });
950
+ setPresentedFile({
951
+ path,
952
+ title: path.split("/").pop() || path,
953
+ description: ""
954
+ });
955
+ },
956
+ [chatLogger]
625
957
  );
626
958
  return {
627
959
  messages,
@@ -653,7 +985,10 @@ function useChat(options = {}) {
653
985
  import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
654
986
  function useMemory(autoFetch = true) {
655
987
  const { fetchWithAuth } = usePersonaContext();
656
- const [memory, setMemory] = useState2({ userFiles: [], agentMemories: [] });
988
+ const [memory, setMemory] = useState2({
989
+ userFiles: [],
990
+ agentMemories: []
991
+ });
657
992
  const [isLoading, setIsLoading] = useState2(false);
658
993
  const [error, setError] = useState2(null);
659
994
  const fetchMemory = useCallback2(async () => {
@@ -681,7 +1016,8 @@ function useMemory(autoFetch = true) {
681
1016
  if (params.scope) query.set("scope", params.scope);
682
1017
  if (params.agentId) query.set("agentId", params.agentId);
683
1018
  const res = await fetchWithAuth(`/memory/file?${query.toString()}`);
684
- if (!res.ok) throw new Error(`Failed to get memory file: ${res.statusText}`);
1019
+ if (!res.ok)
1020
+ throw new Error(`Failed to get memory file: ${res.statusText}`);
685
1021
  return await res.json();
686
1022
  },
687
1023
  [fetchWithAuth]
@@ -693,7 +1029,8 @@ function useMemory(autoFetch = true) {
693
1029
  headers: { "Content-Type": "application/json" },
694
1030
  body: JSON.stringify(params)
695
1031
  });
696
- if (!res.ok) throw new Error(`Failed to write memory file: ${res.statusText}`);
1032
+ if (!res.ok)
1033
+ throw new Error(`Failed to write memory file: ${res.statusText}`);
697
1034
  const updated = await res.json();
698
1035
  void fetchMemory();
699
1036
  return updated;
@@ -708,7 +1045,8 @@ function useMemory(autoFetch = true) {
708
1045
  const res = await fetchWithAuth(`/memory/file?${query.toString()}`, {
709
1046
  method: "DELETE"
710
1047
  });
711
- if (!res.ok) throw new Error(`Failed to delete memory file: ${res.statusText}`);
1048
+ if (!res.ok)
1049
+ throw new Error(`Failed to delete memory file: ${res.statusText}`);
712
1050
  void fetchMemory();
713
1051
  },
714
1052
  [fetchWithAuth, fetchMemory]
@@ -762,7 +1100,8 @@ function useThreads(autoFetch = true) {
762
1100
  headers: { "Content-Type": "application/json" },
763
1101
  body: JSON.stringify({ agentId: targetAgentId })
764
1102
  });
765
- if (!res.ok) throw new Error(`Failed to create thread: ${res.statusText}`);
1103
+ if (!res.ok)
1104
+ throw new Error(`Failed to create thread: ${res.statusText}`);
766
1105
  const data = await res.json();
767
1106
  const created = data?.data ?? data;
768
1107
  void fetchThreads();
@@ -772,8 +1111,11 @@ function useThreads(autoFetch = true) {
772
1111
  );
773
1112
  const deleteThread = useCallback3(
774
1113
  async (threadId) => {
775
- const res = await fetchWithAuth(`/threads/${threadId}`, { method: "DELETE" });
776
- if (!res.ok) throw new Error(`Failed to delete thread: ${res.statusText}`);
1114
+ const res = await fetchWithAuth(`/threads/${threadId}`, {
1115
+ method: "DELETE"
1116
+ });
1117
+ if (!res.ok)
1118
+ throw new Error(`Failed to delete thread: ${res.statusText}`);
777
1119
  setThreads((prev) => prev.filter((t) => t._id !== threadId));
778
1120
  },
779
1121
  [fetchWithAuth]
@@ -785,7 +1127,8 @@ function useThreads(autoFetch = true) {
785
1127
  headers: { "Content-Type": "application/json" },
786
1128
  body: JSON.stringify({ ids: threadIds })
787
1129
  });
788
- if (!res.ok) throw new Error(`Failed to bulk-delete threads: ${res.statusText}`);
1130
+ if (!res.ok)
1131
+ throw new Error(`Failed to bulk-delete threads: ${res.statusText}`);
789
1132
  const data = await res.json();
790
1133
  const result = data?.data ?? data;
791
1134
  const deletedSet = new Set(result.deleted);
@@ -807,10 +1150,13 @@ function useThreads(autoFetch = true) {
807
1150
  headers: { "Content-Type": "application/json" },
808
1151
  body: JSON.stringify(input)
809
1152
  });
810
- if (!res.ok) throw new Error(`Failed to update thread: ${res.statusText}`);
1153
+ if (!res.ok)
1154
+ throw new Error(`Failed to update thread: ${res.statusText}`);
811
1155
  const data = await res.json();
812
1156
  const updated = data?.data ?? data;
813
- setThreads((prev) => prev.map((t) => t._id === threadId ? { ...t, ...updated } : t));
1157
+ setThreads(
1158
+ (prev) => prev.map((t) => t._id === threadId ? { ...t, ...updated } : t)
1159
+ );
814
1160
  return updated;
815
1161
  },
816
1162
  [fetchWithAuth]
@@ -922,19 +1268,17 @@ function useFiles(autoFetch = true) {
922
1268
  headers: { "Content-Type": "application/json" },
923
1269
  body: JSON.stringify({ ids: fileIds })
924
1270
  });
925
- if (!res.ok) throw new Error(`Failed to bulk-delete files: ${res.statusText}`);
1271
+ if (!res.ok)
1272
+ throw new Error(`Failed to bulk-delete files: ${res.statusText}`);
926
1273
  const idSet = new Set(fileIds);
927
1274
  setFiles((prev) => prev.filter((f) => !idSet.has(f.id)));
928
1275
  return await res.json();
929
1276
  },
930
1277
  [fetchWithAuth]
931
1278
  );
932
- const getDownloadUrl = useCallback4(
933
- (fileId) => {
934
- return `/files/${fileId}`;
935
- },
936
- []
937
- );
1279
+ const getDownloadUrl = useCallback4((fileId) => {
1280
+ return `/files/${fileId}`;
1281
+ }, []);
938
1282
  useEffect4(() => {
939
1283
  if (autoFetch) {
940
1284
  void fetchFiles();
@@ -1046,8 +1390,11 @@ function useMcpConnections(options = {}) {
1046
1390
  try {
1047
1391
  const returnTo = options.returnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
1048
1392
  const query = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : "";
1049
- const res = await fetchWithAuth(`/agents/${agentId}/mcp-connections${query}`);
1050
- if (!res.ok) throw new Error(`Failed to load MCP connections: ${res.statusText}`);
1393
+ const res = await fetchWithAuth(
1394
+ `/agents/${agentId}/mcp-connections${query}`
1395
+ );
1396
+ if (!res.ok)
1397
+ throw new Error(`Failed to load MCP connections: ${res.statusText}`);
1051
1398
  const data = await res.json();
1052
1399
  const items = Array.isArray(data) ? data : data?.items || [];
1053
1400
  setConnections(items);
@@ -1074,11 +1421,23 @@ function useMcpConnections(options = {}) {
1074
1421
  }
1075
1422
 
1076
1423
  // src/index.ts
1077
- var VERSION = "0.4.0";
1424
+ import {
1425
+ createLogger as createLogger2,
1426
+ createNoopLogger,
1427
+ setLogLevel,
1428
+ getLogLevel,
1429
+ isLevelEnabled
1430
+ } from "@personaai/logger";
1431
+ var VERSION = "0.5.4";
1078
1432
  export {
1079
1433
  PersonaProvider,
1080
1434
  VERSION,
1435
+ createLogger2 as createLogger,
1436
+ createNoopLogger,
1437
+ getLogLevel,
1438
+ isLevelEnabled,
1081
1439
  openSSEStream,
1440
+ setLogLevel,
1082
1441
  supportsStreamingFetch,
1083
1442
  useAgents,
1084
1443
  useChat,