@personaai/react 0.7.10 → 0.8.1
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/README.md +34 -10
- package/dist/index.cjs +206 -216
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -4
- package/dist/index.d.ts +14 -4
- package/dist/index.js +206 -216
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -396,6 +396,11 @@ function useChat(options = {}) {
|
|
|
396
396
|
const chatLogger = useMemo2(() => logger.child("chat"), [logger]);
|
|
397
397
|
const agentId = options.agentId || defaultAgentId;
|
|
398
398
|
const threadId = options.threadId;
|
|
399
|
+
const [internalThreadId, setInternalThreadId] = useState(void 0);
|
|
400
|
+
const effectiveThreadId = threadId ?? internalThreadId;
|
|
401
|
+
useEffect(() => {
|
|
402
|
+
if (threadId !== void 0) setInternalThreadId(threadId);
|
|
403
|
+
}, [threadId]);
|
|
399
404
|
const didLogInitRef = useRef(false);
|
|
400
405
|
if (!didLogInitRef.current) {
|
|
401
406
|
didLogInitRef.current = true;
|
|
@@ -432,9 +437,10 @@ function useChat(options = {}) {
|
|
|
432
437
|
}, [messages]);
|
|
433
438
|
const abortControllerRef = useRef(null);
|
|
434
439
|
const loadedThreadIdRef = useRef(void 0);
|
|
435
|
-
const voiceThreadRef = useRef(
|
|
440
|
+
const voiceThreadRef = useRef(effectiveThreadId);
|
|
436
441
|
const voicePrevLenRef = useRef(0);
|
|
437
442
|
const voiceStreamingIdRef = useRef(null);
|
|
443
|
+
const voiceUserPartialIdRef = useRef(null);
|
|
438
444
|
const stop = useCallback(() => {
|
|
439
445
|
if (abortControllerRef.current) {
|
|
440
446
|
chatLogger.info("stop streaming", {});
|
|
@@ -459,7 +465,25 @@ function useChat(options = {}) {
|
|
|
459
465
|
setFiles({});
|
|
460
466
|
setTodos([]);
|
|
461
467
|
setPresentedFile(null);
|
|
468
|
+
loadedThreadIdRef.current = void 0;
|
|
462
469
|
}, [stop, chatLogger, messages.length]);
|
|
470
|
+
const startNewChat = useCallback(() => {
|
|
471
|
+
chatLogger.info("startNewChat \u2014 entering ephemeral mode");
|
|
472
|
+
stop();
|
|
473
|
+
setMessages([]);
|
|
474
|
+
setError(null);
|
|
475
|
+
setInterrupt(null);
|
|
476
|
+
setFiles({});
|
|
477
|
+
setTodos([]);
|
|
478
|
+
setPresentedFile(null);
|
|
479
|
+
setInput("");
|
|
480
|
+
loadedThreadIdRef.current = void 0;
|
|
481
|
+
voiceThreadRef.current = void 0;
|
|
482
|
+
voicePrevLenRef.current = 0;
|
|
483
|
+
voiceStreamingIdRef.current = null;
|
|
484
|
+
voiceUserPartialIdRef.current = null;
|
|
485
|
+
setInternalThreadId(void 0);
|
|
486
|
+
}, [stop, chatLogger]);
|
|
463
487
|
const loadThreadMessages = useCallback(
|
|
464
488
|
async (id) => {
|
|
465
489
|
chatLogger.debug("loadThreadMessages start", { threadId: id });
|
|
@@ -491,6 +515,7 @@ function useChat(options = {}) {
|
|
|
491
515
|
setInterrupt(normalizePendingInterrupt(data?.pendingInterrupt));
|
|
492
516
|
setFiles(normalizeWorkspaceFiles(data?.state?.files ?? {}));
|
|
493
517
|
setTodos(data?.state?.todos ?? []);
|
|
518
|
+
loadedThreadIdRef.current = id;
|
|
494
519
|
chatLogger.info("loadThreadMessages succeeded", {
|
|
495
520
|
threadId: id,
|
|
496
521
|
messageCount: loaded.length
|
|
@@ -511,6 +536,7 @@ function useChat(options = {}) {
|
|
|
511
536
|
error: errorObj.message
|
|
512
537
|
});
|
|
513
538
|
setError(errorObj);
|
|
539
|
+
if (loadedThreadIdRef.current === id) loadedThreadIdRef.current = void 0;
|
|
514
540
|
return [];
|
|
515
541
|
} finally {
|
|
516
542
|
setIsLoadingHistory(false);
|
|
@@ -520,33 +546,33 @@ function useChat(options = {}) {
|
|
|
520
546
|
[fetchWithAuth, chatLogger]
|
|
521
547
|
);
|
|
522
548
|
useEffect(() => {
|
|
523
|
-
if (!
|
|
524
|
-
chatLogger.trace("auto-load skipped", { threadId, isStreaming });
|
|
549
|
+
if (!effectiveThreadId || isStreaming) {
|
|
550
|
+
chatLogger.trace("auto-load skipped", { threadId: effectiveThreadId, isStreaming });
|
|
525
551
|
return;
|
|
526
552
|
}
|
|
527
|
-
if (loadedThreadIdRef.current ===
|
|
528
|
-
chatLogger.trace("auto-load already loaded", { threadId });
|
|
553
|
+
if (loadedThreadIdRef.current === effectiveThreadId) {
|
|
554
|
+
chatLogger.trace("auto-load already loaded", { threadId: effectiveThreadId });
|
|
529
555
|
return;
|
|
530
556
|
}
|
|
531
557
|
if (messages.length > 0) {
|
|
532
558
|
chatLogger.trace("auto-load has messages", {
|
|
533
|
-
threadId,
|
|
559
|
+
threadId: effectiveThreadId,
|
|
534
560
|
count: messages.length
|
|
535
561
|
});
|
|
536
562
|
return;
|
|
537
563
|
}
|
|
538
|
-
|
|
539
|
-
chatLogger.
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}, [threadId, isStreaming, messages.length, loadThreadMessages, chatLogger]);
|
|
564
|
+
chatLogger.info("auto-load thread history", { threadId: effectiveThreadId });
|
|
565
|
+
chatLogger.debug("loadThreadMessages trigger", { threadId: effectiveThreadId });
|
|
566
|
+
void loadThreadMessages(effectiveThreadId);
|
|
567
|
+
}, [effectiveThreadId, isStreaming, messages.length, loadThreadMessages, chatLogger]);
|
|
543
568
|
const voice = options.voice;
|
|
544
569
|
useEffect(() => {
|
|
545
570
|
if (!voice) return;
|
|
546
|
-
if (voiceThreadRef.current !==
|
|
547
|
-
voiceThreadRef.current =
|
|
571
|
+
if (voiceThreadRef.current !== effectiveThreadId) {
|
|
572
|
+
voiceThreadRef.current = effectiveThreadId;
|
|
548
573
|
voicePrevLenRef.current = voice.transcript.length;
|
|
549
574
|
voiceStreamingIdRef.current = null;
|
|
575
|
+
voiceUserPartialIdRef.current = null;
|
|
550
576
|
return;
|
|
551
577
|
}
|
|
552
578
|
const isVoiceActive = voice.state !== "idle" && voice.state !== "ended" && voice.state !== "error";
|
|
@@ -554,15 +580,40 @@ function useChat(options = {}) {
|
|
|
554
580
|
if (curLen < voicePrevLenRef.current) {
|
|
555
581
|
voicePrevLenRef.current = 0;
|
|
556
582
|
voiceStreamingIdRef.current = null;
|
|
583
|
+
voiceUserPartialIdRef.current = null;
|
|
557
584
|
}
|
|
558
585
|
if (!isVoiceActive) {
|
|
559
|
-
voicePrevLenRef.current
|
|
586
|
+
if (curLen > voicePrevLenRef.current) {
|
|
587
|
+
const pendingLines = voice.transcript.slice(voicePrevLenRef.current);
|
|
588
|
+
voicePrevLenRef.current = curLen;
|
|
589
|
+
for (const line of pendingLines) {
|
|
590
|
+
const text = (line.text || "").trim();
|
|
591
|
+
if (!text) continue;
|
|
592
|
+
const role = line.speaker === "user" ? "user" : "assistant";
|
|
593
|
+
const voiceId = `voice-${line.id}`;
|
|
594
|
+
setMessages((prev) => {
|
|
595
|
+
if (prev.some((m) => m.id === voiceId)) return prev;
|
|
596
|
+
if (prev.some((m) => !m.id.startsWith("voice-") && m.role === role && m.content.trim() === text)) return prev;
|
|
597
|
+
const last = prev[prev.length - 1];
|
|
598
|
+
if (last?.id.startsWith("voice-") && last.role === "assistant" && role === "assistant") {
|
|
599
|
+
const merged = text.startsWith(last.content) ? text : `${last.content} ${text}`.trim();
|
|
600
|
+
return [...prev.slice(0, -1), { ...last, content: merged, isStreaming: false }];
|
|
601
|
+
}
|
|
602
|
+
return [...prev, { id: voiceId, role, content: text, createdAt: /* @__PURE__ */ new Date() }];
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
} else {
|
|
606
|
+
voicePrevLenRef.current = voice.transcript.length;
|
|
607
|
+
}
|
|
560
608
|
if (voiceStreamingIdRef.current) {
|
|
561
609
|
const doneId = voiceStreamingIdRef.current;
|
|
562
610
|
voiceStreamingIdRef.current = null;
|
|
563
|
-
setMessages(
|
|
564
|
-
|
|
565
|
-
|
|
611
|
+
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
612
|
+
}
|
|
613
|
+
if (voiceUserPartialIdRef.current) {
|
|
614
|
+
const doneId = voiceUserPartialIdRef.current;
|
|
615
|
+
voiceUserPartialIdRef.current = null;
|
|
616
|
+
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
566
617
|
}
|
|
567
618
|
return;
|
|
568
619
|
}
|
|
@@ -589,54 +640,71 @@ function useChat(options = {}) {
|
|
|
589
640
|
});
|
|
590
641
|
}
|
|
591
642
|
}
|
|
592
|
-
const
|
|
593
|
-
|
|
643
|
+
const agentPartialText = voice.partial?.speaker === "agent" ? voice.partial.text.trim() : "";
|
|
644
|
+
const userPartialText = voice.partial?.speaker === "user" ? voice.partial.text.trim() : "";
|
|
645
|
+
if (agentPartialText) {
|
|
646
|
+
if (voiceUserPartialIdRef.current) {
|
|
647
|
+
const doneId = voiceUserPartialIdRef.current;
|
|
648
|
+
voiceUserPartialIdRef.current = null;
|
|
649
|
+
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
650
|
+
}
|
|
594
651
|
setMessages((prev) => {
|
|
595
652
|
const last = prev[prev.length - 1];
|
|
596
653
|
if (last?.id.startsWith("voice-") && last.role === "assistant") {
|
|
597
654
|
voiceStreamingIdRef.current = last.id;
|
|
598
|
-
return [...prev.slice(0, -1), { ...last, content:
|
|
655
|
+
return [...prev.slice(0, -1), { ...last, content: agentPartialText, isStreaming: true }];
|
|
599
656
|
}
|
|
600
657
|
const id = voiceStreamingIdRef.current || `voice-partial-${Date.now()}`;
|
|
601
658
|
voiceStreamingIdRef.current = id;
|
|
602
|
-
return [...prev, { id, role: "assistant", content:
|
|
659
|
+
return [...prev, { id, role: "assistant", content: agentPartialText, isStreaming: true, createdAt: /* @__PURE__ */ new Date() }];
|
|
603
660
|
});
|
|
604
|
-
} else if (
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
(prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m)
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
661
|
+
} else if (userPartialText) {
|
|
662
|
+
if (voiceStreamingIdRef.current) {
|
|
663
|
+
const doneId = voiceStreamingIdRef.current;
|
|
664
|
+
voiceStreamingIdRef.current = null;
|
|
665
|
+
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
666
|
+
}
|
|
667
|
+
setMessages((prev) => {
|
|
668
|
+
const last = prev[prev.length - 1];
|
|
669
|
+
if (last?.id.startsWith("voice-") && last.role === "user" && last.isStreaming) {
|
|
670
|
+
voiceUserPartialIdRef.current = last.id;
|
|
671
|
+
return [...prev.slice(0, -1), { ...last, content: userPartialText, isStreaming: true }];
|
|
672
|
+
}
|
|
673
|
+
const id = voiceUserPartialIdRef.current || `voice-partial-user-${Date.now()}`;
|
|
674
|
+
voiceUserPartialIdRef.current = id;
|
|
675
|
+
return [...prev, { id, role: "user", content: userPartialText, isStreaming: true, createdAt: /* @__PURE__ */ new Date() }];
|
|
676
|
+
});
|
|
677
|
+
} else {
|
|
678
|
+
if (voiceStreamingIdRef.current && voice.state !== "speaking") {
|
|
679
|
+
const doneId = voiceStreamingIdRef.current;
|
|
680
|
+
voiceStreamingIdRef.current = null;
|
|
681
|
+
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
621
682
|
}
|
|
683
|
+
if (voiceUserPartialIdRef.current) {
|
|
684
|
+
const doneId = voiceUserPartialIdRef.current;
|
|
685
|
+
voiceUserPartialIdRef.current = null;
|
|
686
|
+
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}, [voice?.transcript, voice?.partial, voice?.state, effectiveThreadId]);
|
|
690
|
+
const doSend = useCallback(
|
|
691
|
+
async (prompt, baseMessages, overrideOptions) => {
|
|
622
692
|
const targetAgentId = overrideOptions?.agentId || agentId;
|
|
623
693
|
if (!targetAgentId) {
|
|
624
694
|
const err = new Error("No Agent ID specified for useChat.");
|
|
625
695
|
chatLogger.warn("sendMessage no agentId", {});
|
|
626
|
-
chatLogger.error("sendMessage failed \u2014 no agent", {
|
|
627
|
-
error: err.message
|
|
628
|
-
});
|
|
696
|
+
chatLogger.error("sendMessage failed \u2014 no agent", { error: err.message });
|
|
629
697
|
setError(err);
|
|
630
698
|
options.onError?.(err);
|
|
631
|
-
return;
|
|
699
|
+
return false;
|
|
632
700
|
}
|
|
633
701
|
chatLogger.debug("sendMessage start", {
|
|
634
702
|
promptPreview: prompt.slice(0, 100),
|
|
635
703
|
agentId: targetAgentId,
|
|
636
|
-
threadId:
|
|
704
|
+
threadId: effectiveThreadId ?? overrideOptions?.threadId,
|
|
637
705
|
hasResume: !!overrideOptions?.resume,
|
|
638
706
|
hasContextOverride: !!overrideOptions?.contextOverride,
|
|
639
|
-
messageCount:
|
|
707
|
+
messageCount: baseMessages.length
|
|
640
708
|
});
|
|
641
709
|
chatLogger.trace("sendMessage details", {
|
|
642
710
|
agentId: targetAgentId,
|
|
@@ -659,7 +727,7 @@ function useChat(options = {}) {
|
|
|
659
727
|
isStreaming: true,
|
|
660
728
|
toolCalls: []
|
|
661
729
|
};
|
|
662
|
-
const nextMessages = [...
|
|
730
|
+
const nextMessages = [...baseMessages, userMessage];
|
|
663
731
|
setMessages([...nextMessages, placeholderAssistant]);
|
|
664
732
|
setInput("");
|
|
665
733
|
setIsStreaming(true);
|
|
@@ -669,9 +737,7 @@ function useChat(options = {}) {
|
|
|
669
737
|
abortControllerRef.current = controller;
|
|
670
738
|
const finalizeReasoning = () => {
|
|
671
739
|
setMessages(
|
|
672
|
-
(prev) => prev.map(
|
|
673
|
-
(m) => m.role === "reasoning" && m.isStreaming ? { ...m, isStreaming: false } : m
|
|
674
|
-
)
|
|
740
|
+
(prev) => prev.map((m) => m.role === "reasoning" && m.isStreaming ? { ...m, isStreaming: false } : m)
|
|
675
741
|
);
|
|
676
742
|
};
|
|
677
743
|
try {
|
|
@@ -679,7 +745,24 @@ function useChat(options = {}) {
|
|
|
679
745
|
role: m.role === "assistant" ? "assistant" : "user",
|
|
680
746
|
content: m.content
|
|
681
747
|
}));
|
|
682
|
-
|
|
748
|
+
let resolvedThreadId = await overrideOptions?.threadId ?? effectiveThreadId;
|
|
749
|
+
if (!resolvedThreadId) {
|
|
750
|
+
chatLogger.info("minting real thread for ephemeral chat", { agentId: targetAgentId });
|
|
751
|
+
const createRes = await fetchWithAuth("/threads", {
|
|
752
|
+
method: "POST",
|
|
753
|
+
headers: { "Content-Type": "application/json" },
|
|
754
|
+
body: JSON.stringify({ agentId: targetAgentId })
|
|
755
|
+
});
|
|
756
|
+
if (!createRes.ok) throw new Error(`Failed to create thread: ${createRes.statusText}`);
|
|
757
|
+
const createBody = await createRes.json();
|
|
758
|
+
const created = createBody?.data ?? createBody;
|
|
759
|
+
resolvedThreadId = created._id ?? created.id;
|
|
760
|
+
if (!resolvedThreadId) throw new Error("Thread creation returned no id");
|
|
761
|
+
setInternalThreadId(resolvedThreadId);
|
|
762
|
+
loadedThreadIdRef.current = resolvedThreadId;
|
|
763
|
+
options.onThreadCreated?.(resolvedThreadId);
|
|
764
|
+
chatLogger.info("ephemeral chat minted", { threadId: resolvedThreadId });
|
|
765
|
+
}
|
|
683
766
|
chatLogger.trace("resolved threadId", { threadId: resolvedThreadId });
|
|
684
767
|
const token = getAuthToken ? await getAuthToken() : null;
|
|
685
768
|
const resolvedHookContext = typeof options.context === "function" ? options.context() : options.context;
|
|
@@ -708,27 +791,12 @@ function useChat(options = {}) {
|
|
|
708
791
|
signal: controller.signal
|
|
709
792
|
});
|
|
710
793
|
if (!stream.ok) {
|
|
711
|
-
chatLogger.warn("SSE stream not ok", {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
});
|
|
715
|
-
chatLogger.error("chat stream failed", {
|
|
716
|
-
agentId: targetAgentId,
|
|
717
|
-
status: stream.status,
|
|
718
|
-
error: stream.errorText
|
|
719
|
-
});
|
|
720
|
-
throw new Error(
|
|
721
|
-
`Chat error (${stream.status}): ${stream.errorText ?? "Stream failed"}`
|
|
722
|
-
);
|
|
794
|
+
chatLogger.warn("SSE stream not ok", { status: stream.status, errorText: stream.errorText });
|
|
795
|
+
chatLogger.error("chat stream failed", { agentId: targetAgentId, status: stream.status, error: stream.errorText });
|
|
796
|
+
throw new Error(`Chat error (${stream.status}): ${stream.errorText ?? "Stream failed"}`);
|
|
723
797
|
}
|
|
724
|
-
chatLogger.debug("SSE stream opened", {
|
|
725
|
-
|
|
726
|
-
status: stream.status
|
|
727
|
-
});
|
|
728
|
-
chatLogger.info("chat stream started", {
|
|
729
|
-
agentId: targetAgentId,
|
|
730
|
-
threadId: resolvedThreadId
|
|
731
|
-
});
|
|
798
|
+
chatLogger.debug("SSE stream opened", { agentId: targetAgentId, status: stream.status });
|
|
799
|
+
chatLogger.info("chat stream started", { agentId: targetAgentId, threadId: resolvedThreadId });
|
|
732
800
|
const reader = stream.reader;
|
|
733
801
|
let buffer = "";
|
|
734
802
|
let accumulatedText = "";
|
|
@@ -739,23 +807,12 @@ function useChat(options = {}) {
|
|
|
739
807
|
const patchAssistant = (patch) => {
|
|
740
808
|
setMessages(
|
|
741
809
|
(prev) => prev.map(
|
|
742
|
-
(msg) => msg.id === assistantMessageId ? {
|
|
743
|
-
...msg,
|
|
744
|
-
toolCalls: Array.from(toolCallsMap.values()),
|
|
745
|
-
...patch
|
|
746
|
-
} : msg
|
|
810
|
+
(msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()), ...patch } : msg
|
|
747
811
|
)
|
|
748
812
|
);
|
|
749
813
|
};
|
|
750
814
|
const insertReasoningMessage = (id, seq) => {
|
|
751
|
-
const msg = {
|
|
752
|
-
id,
|
|
753
|
-
role: "reasoning",
|
|
754
|
-
content: "",
|
|
755
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
756
|
-
isStreaming: true,
|
|
757
|
-
seq
|
|
758
|
-
};
|
|
815
|
+
const msg = { id, role: "reasoning", content: "", createdAt: /* @__PURE__ */ new Date(), isStreaming: true, seq };
|
|
759
816
|
setMessages((prev) => {
|
|
760
817
|
const idx = prev.findIndex((m) => m.id === assistantMessageId);
|
|
761
818
|
if (idx === -1) return [...prev, msg];
|
|
@@ -780,16 +837,11 @@ function useChat(options = {}) {
|
|
|
780
837
|
chatLogger.trace("stream event", { type: event.type, event });
|
|
781
838
|
options.onEvent?.(event);
|
|
782
839
|
if (event.type === "TEXT_MESSAGE_CHUNK" && event.delta) {
|
|
783
|
-
chatLogger.debug("text chunk", {
|
|
784
|
-
deltaLength: event.delta.length
|
|
785
|
-
});
|
|
840
|
+
chatLogger.debug("text chunk", { deltaLength: event.delta.length });
|
|
786
841
|
accumulatedText += event.delta;
|
|
787
842
|
patchAssistant({ content: accumulatedText, isStreaming: true });
|
|
788
843
|
} else if (event.type === "TOOL_CALL_CHUNK" && event.toolCallId) {
|
|
789
|
-
chatLogger.debug("tool call chunk", {
|
|
790
|
-
toolCallId: event.toolCallId,
|
|
791
|
-
toolCallName: event.toolCallName
|
|
792
|
-
});
|
|
844
|
+
chatLogger.debug("tool call chunk", { toolCallId: event.toolCallId, toolCallName: event.toolCallName });
|
|
793
845
|
const existing = toolCallsMap.get(event.toolCallId);
|
|
794
846
|
if (existing) {
|
|
795
847
|
existing.args = (existing.args || "") + (event.delta || "");
|
|
@@ -803,17 +855,12 @@ function useChat(options = {}) {
|
|
|
803
855
|
}
|
|
804
856
|
patchAssistant({});
|
|
805
857
|
} else if (event.type === "TOOL_CALL_RESULT") {
|
|
806
|
-
chatLogger.debug("tool call result", {
|
|
807
|
-
toolCallId: event.toolCallId
|
|
808
|
-
});
|
|
858
|
+
chatLogger.debug("tool call result", { toolCallId: event.toolCallId });
|
|
809
859
|
const existing = toolCallsMap.get(event.toolCallId);
|
|
810
860
|
if (existing) {
|
|
811
861
|
existing.result = event.content;
|
|
812
862
|
existing.isError = isErrorToolContent(event.content);
|
|
813
|
-
if (existing.isError)
|
|
814
|
-
chatLogger.warn("tool call error", {
|
|
815
|
-
toolCallId: event.toolCallId
|
|
816
|
-
});
|
|
863
|
+
if (existing.isError) chatLogger.warn("tool call error", { toolCallId: event.toolCallId });
|
|
817
864
|
if (existing.toolName === "present_file" && !existing.isError) {
|
|
818
865
|
const presented = parsePresentedFile(event.content);
|
|
819
866
|
if (presented) {
|
|
@@ -828,12 +875,10 @@ function useChat(options = {}) {
|
|
|
828
875
|
fileCount: Object.keys(event.snapshot.files ?? {}).length,
|
|
829
876
|
todoCount: event.snapshot.todos?.length ?? 0
|
|
830
877
|
});
|
|
831
|
-
setFiles(normalizeWorkspaceFiles(event.snapshot.files));
|
|
832
|
-
setTodos(event.snapshot.todos);
|
|
878
|
+
setFiles(normalizeWorkspaceFiles(event.snapshot.files ?? {}));
|
|
879
|
+
setTodos(event.snapshot.todos ?? []);
|
|
833
880
|
} else if (event.type === "REASONING_MESSAGE_START" && event.messageId) {
|
|
834
|
-
chatLogger.debug("reasoning start", {
|
|
835
|
-
messageId: event.messageId
|
|
836
|
-
});
|
|
881
|
+
chatLogger.debug("reasoning start", { messageId: event.messageId });
|
|
837
882
|
activeReasoningId = event.messageId;
|
|
838
883
|
reasoningById.set(event.messageId, { content: "" });
|
|
839
884
|
insertReasoningMessage(event.messageId, streamSeq++);
|
|
@@ -848,61 +893,31 @@ function useChat(options = {}) {
|
|
|
848
893
|
}
|
|
849
894
|
const entry = reasoningById.get(rid);
|
|
850
895
|
entry.content += event.delta;
|
|
851
|
-
chatLogger.trace("reasoning content", {
|
|
852
|
-
|
|
853
|
-
deltaLength: event.delta?.length ?? 0
|
|
854
|
-
});
|
|
855
|
-
setMessages(
|
|
856
|
-
(prev) => prev.map(
|
|
857
|
-
(m) => m.id === rid ? { ...m, content: entry.content, isStreaming: true } : m
|
|
858
|
-
)
|
|
859
|
-
);
|
|
896
|
+
chatLogger.trace("reasoning content", { messageId: rid, deltaLength: event.delta?.length ?? 0 });
|
|
897
|
+
setMessages((prev) => prev.map((m) => m.id === rid ? { ...m, content: entry.content, isStreaming: true } : m));
|
|
860
898
|
} else if (event.type === "REASONING_END") {
|
|
861
|
-
chatLogger.debug("reasoning end", {
|
|
862
|
-
messageId: activeReasoningId
|
|
863
|
-
});
|
|
899
|
+
chatLogger.debug("reasoning end", { messageId: activeReasoningId });
|
|
864
900
|
const rid = activeReasoningId;
|
|
865
901
|
activeReasoningId = null;
|
|
866
902
|
if (rid) {
|
|
867
|
-
setMessages(
|
|
868
|
-
(prev) => prev.map(
|
|
869
|
-
(m) => m.id === rid ? { ...m, isStreaming: false } : m
|
|
870
|
-
)
|
|
871
|
-
);
|
|
903
|
+
setMessages((prev) => prev.map((m) => m.id === rid ? { ...m, isStreaming: false } : m));
|
|
872
904
|
}
|
|
873
905
|
} else if (event.type === "CUSTOM") {
|
|
874
906
|
chatLogger.debug("custom event", { name: event.name });
|
|
875
907
|
if (event.name === "hitl_request") {
|
|
876
908
|
const value2 = event.value;
|
|
877
|
-
chatLogger.info("hitl interrupt", {
|
|
878
|
-
|
|
879
|
-
});
|
|
880
|
-
setInterrupt({
|
|
881
|
-
kind: "hitl",
|
|
882
|
-
actionRequests: value2.actionRequests,
|
|
883
|
-
reviewConfigs: value2.reviewConfigs
|
|
884
|
-
});
|
|
909
|
+
chatLogger.info("hitl interrupt", { actionCount: value2.actionRequests?.length ?? 0 });
|
|
910
|
+
setInterrupt({ kind: "hitl", actionRequests: value2.actionRequests, reviewConfigs: value2.reviewConfigs });
|
|
885
911
|
} else if (event.name === "clarification_request") {
|
|
886
912
|
const value2 = event.value;
|
|
887
|
-
chatLogger.info("clarification interrupt", {
|
|
888
|
-
|
|
889
|
-
});
|
|
890
|
-
setInterrupt({
|
|
891
|
-
kind: "clarification",
|
|
892
|
-
questions: value2.questions
|
|
893
|
-
});
|
|
913
|
+
chatLogger.info("clarification interrupt", { questionCount: value2.questions?.length ?? 0 });
|
|
914
|
+
setInterrupt({ kind: "clarification", questions: value2.questions });
|
|
894
915
|
} else if (event.name === "subagent_activity") {
|
|
895
916
|
const { toolCallId, ...entry } = event.value;
|
|
896
|
-
chatLogger.trace("subagent activity", {
|
|
897
|
-
toolCallId,
|
|
898
|
-
kind: entry.kind
|
|
899
|
-
});
|
|
917
|
+
chatLogger.trace("subagent activity", { toolCallId, kind: entry.kind });
|
|
900
918
|
const existing = toolCallsMap.get(toolCallId);
|
|
901
919
|
if (existing) {
|
|
902
|
-
existing.subagentActivity = [
|
|
903
|
-
...existing.subagentActivity || [],
|
|
904
|
-
entry
|
|
905
|
-
];
|
|
920
|
+
existing.subagentActivity = [...existing.subagentActivity || [], entry];
|
|
906
921
|
patchAssistant({});
|
|
907
922
|
}
|
|
908
923
|
} else if (event.name === "mcp_app") {
|
|
@@ -910,35 +925,27 @@ function useChat(options = {}) {
|
|
|
910
925
|
if (val?.toolCallId && val?.resourceUri && val?.mcpId) {
|
|
911
926
|
const existing = toolCallsMap.get(val.toolCallId);
|
|
912
927
|
if (existing) {
|
|
913
|
-
existing.mcpApp = {
|
|
914
|
-
resourceUri: val.resourceUri,
|
|
915
|
-
mcpId: val.mcpId
|
|
916
|
-
};
|
|
928
|
+
existing.mcpApp = { resourceUri: val.resourceUri, mcpId: val.mcpId };
|
|
917
929
|
patchAssistant({});
|
|
918
930
|
}
|
|
919
931
|
}
|
|
920
932
|
}
|
|
933
|
+
} else if (event.type === "title") {
|
|
934
|
+
const title = event.title;
|
|
935
|
+
if (typeof title === "string" && title.trim()) {
|
|
936
|
+
chatLogger.info("auto title", { title });
|
|
937
|
+
options.onTitle?.(title);
|
|
938
|
+
}
|
|
921
939
|
} else if (event.type === "RUN_ERROR") {
|
|
922
|
-
chatLogger.warn("run error", {
|
|
923
|
-
|
|
924
|
-
code: event.code
|
|
925
|
-
});
|
|
926
|
-
chatLogger.error("stream run error", {
|
|
927
|
-
code: event.code,
|
|
928
|
-
message: event.message
|
|
929
|
-
});
|
|
940
|
+
chatLogger.warn("run error", { message: event.message, code: event.code });
|
|
941
|
+
chatLogger.error("stream run error", { code: event.code, message: event.message });
|
|
930
942
|
throw new Error(event.message || "Stream error from agent");
|
|
931
943
|
} else {
|
|
932
944
|
chatLogger.trace("unhandled event", { type: event.type });
|
|
933
945
|
}
|
|
934
946
|
} catch (e) {
|
|
935
|
-
if (e instanceof Error && e.message.startsWith("Stream error"))
|
|
936
|
-
|
|
937
|
-
}
|
|
938
|
-
chatLogger.warn("event parse error", {
|
|
939
|
-
raw: raw.slice(0, 200),
|
|
940
|
-
error: e instanceof Error ? e.message : String(e)
|
|
941
|
-
});
|
|
947
|
+
if (e instanceof Error && e.message.startsWith("Stream error")) throw e;
|
|
948
|
+
chatLogger.warn("event parse error", { raw: raw.slice(0, 200), error: e instanceof Error ? e.message : String(e) });
|
|
942
949
|
}
|
|
943
950
|
}
|
|
944
951
|
}
|
|
@@ -950,75 +957,55 @@ function useChat(options = {}) {
|
|
|
950
957
|
isStreaming: false,
|
|
951
958
|
toolCalls: Array.from(toolCallsMap.values())
|
|
952
959
|
};
|
|
953
|
-
setMessages(
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
)
|
|
957
|
-
);
|
|
958
|
-
chatLogger.info("sendMessage succeeded", {
|
|
959
|
-
agentId: targetAgentId,
|
|
960
|
-
textLength: accumulatedText.length,
|
|
961
|
-
toolCallCount: toolCallsMap.size
|
|
962
|
-
});
|
|
963
|
-
chatLogger.debug("sendMessage completed", {
|
|
964
|
-
agentId: targetAgentId,
|
|
965
|
-
textLength: accumulatedText.length,
|
|
966
|
-
eventCount: toolCallsMap.size + 1
|
|
967
|
-
});
|
|
960
|
+
setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? finalMessage : msg));
|
|
961
|
+
chatLogger.info("sendMessage succeeded", { agentId: targetAgentId, textLength: accumulatedText.length, toolCallCount: toolCallsMap.size });
|
|
962
|
+
chatLogger.debug("sendMessage completed", { agentId: targetAgentId, textLength: accumulatedText.length, eventCount: toolCallsMap.size + 1 });
|
|
968
963
|
options.onFinish?.(finalMessage);
|
|
964
|
+
return true;
|
|
969
965
|
} catch (err) {
|
|
970
966
|
if (controller.signal.aborted) {
|
|
971
967
|
chatLogger.info("sendMessage aborted", { agentId: targetAgentId });
|
|
972
968
|
chatLogger.debug("stream aborted", { agentId: targetAgentId });
|
|
973
|
-
setMessages(
|
|
974
|
-
|
|
975
|
-
(msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg
|
|
976
|
-
)
|
|
977
|
-
);
|
|
978
|
-
return;
|
|
969
|
+
setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg));
|
|
970
|
+
return false;
|
|
979
971
|
}
|
|
980
972
|
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
981
|
-
chatLogger.warn("sendMessage failed", {
|
|
982
|
-
|
|
983
|
-
error: errorObj.message
|
|
984
|
-
});
|
|
985
|
-
chatLogger.error("sendMessage error", {
|
|
986
|
-
agentId: targetAgentId,
|
|
987
|
-
error: errorObj.message
|
|
988
|
-
});
|
|
973
|
+
chatLogger.warn("sendMessage failed", { agentId: targetAgentId, error: errorObj.message });
|
|
974
|
+
chatLogger.error("sendMessage error", { agentId: targetAgentId, error: errorObj.message });
|
|
989
975
|
setError(errorObj);
|
|
990
976
|
options.onError?.(errorObj);
|
|
991
977
|
setMessages(
|
|
992
978
|
(prev) => prev.map(
|
|
993
|
-
(msg) => msg.id === assistantMessageId ? {
|
|
994
|
-
...msg,
|
|
995
|
-
content: msg.content || `\u26A0\uFE0F Error: ${errorObj.message || "Failed to get response."}`,
|
|
996
|
-
isStreaming: false
|
|
997
|
-
} : msg
|
|
979
|
+
(msg) => msg.id === assistantMessageId ? { ...msg, content: msg.content || `\u26A0\uFE0F Error: ${errorObj.message || "Failed to get response."}`, isStreaming: false } : msg
|
|
998
980
|
)
|
|
999
981
|
);
|
|
982
|
+
return false;
|
|
1000
983
|
} finally {
|
|
1001
984
|
setIsStreaming(false);
|
|
1002
985
|
abortControllerRef.current = null;
|
|
1003
986
|
finalizeReasoning();
|
|
1004
|
-
chatLogger.debug("sendMessage finally", {
|
|
1005
|
-
agentId: targetAgentId,
|
|
1006
|
-
isStreaming: false
|
|
1007
|
-
});
|
|
987
|
+
chatLogger.debug("sendMessage finally", { agentId: targetAgentId, isStreaming: false });
|
|
1008
988
|
chatLogger.trace("sendMessage end", { agentId: targetAgentId });
|
|
1009
989
|
}
|
|
1010
990
|
},
|
|
1011
|
-
[
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
input
|
|
1016
|
-
isStreaming
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
991
|
+
[agentId, baseUrl, getAuthToken, fetchWithAuth, options, chatLogger, effectiveThreadId]
|
|
992
|
+
);
|
|
993
|
+
const sendMessage = useCallback(
|
|
994
|
+
async (contentToSend, overrideOptions) => {
|
|
995
|
+
const prompt = (contentToSend ?? input).trim();
|
|
996
|
+
if (!prompt || isStreaming) {
|
|
997
|
+
chatLogger.trace("sendMessage skipped", {
|
|
998
|
+
hasPrompt: !!prompt,
|
|
999
|
+
isStreaming
|
|
1000
|
+
});
|
|
1001
|
+
if (isStreaming) {
|
|
1002
|
+
chatLogger.warn("sendMessage dropped while streaming \u2014 caller should queue or disable send", {});
|
|
1003
|
+
}
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
return doSend(prompt, messages, overrideOptions);
|
|
1007
|
+
},
|
|
1008
|
+
[input, isStreaming, messages, doSend, chatLogger]
|
|
1022
1009
|
);
|
|
1023
1010
|
const handleInputChange = useCallback(
|
|
1024
1011
|
(e) => {
|
|
@@ -1035,13 +1022,13 @@ function useChat(options = {}) {
|
|
|
1035
1022
|
},
|
|
1036
1023
|
[sendMessage, chatLogger]
|
|
1037
1024
|
);
|
|
1038
|
-
const reload = useCallback(() => {
|
|
1025
|
+
const reload = useCallback(async () => {
|
|
1039
1026
|
if (messages.length === 0 || isStreaming) {
|
|
1040
1027
|
chatLogger.trace("reload skipped", {
|
|
1041
1028
|
messageCount: messages.length,
|
|
1042
1029
|
isStreaming
|
|
1043
1030
|
});
|
|
1044
|
-
return;
|
|
1031
|
+
return false;
|
|
1045
1032
|
}
|
|
1046
1033
|
let lastUserIndex = -1;
|
|
1047
1034
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -1052,16 +1039,16 @@ function useChat(options = {}) {
|
|
|
1052
1039
|
}
|
|
1053
1040
|
if (lastUserIndex === -1) {
|
|
1054
1041
|
chatLogger.warn("reload no user message", {});
|
|
1055
|
-
return;
|
|
1042
|
+
return false;
|
|
1056
1043
|
}
|
|
1057
1044
|
const lastUserMessage = messages[lastUserIndex];
|
|
1045
|
+
const truncated = messages.slice(0, lastUserIndex);
|
|
1058
1046
|
chatLogger.info("reload", { messageId: lastUserMessage.id });
|
|
1059
1047
|
chatLogger.debug("reload last user", {
|
|
1060
1048
|
preview: lastUserMessage.content.slice(0, 100)
|
|
1061
1049
|
});
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
}, [isStreaming, messages, sendMessage, chatLogger]);
|
|
1050
|
+
return doSend(lastUserMessage.content, truncated);
|
|
1051
|
+
}, [isStreaming, messages, doSend, chatLogger]);
|
|
1065
1052
|
const resumeInterrupt = useCallback(
|
|
1066
1053
|
(resume, displayContent) => {
|
|
1067
1054
|
chatLogger.info("resumeInterrupt", {
|
|
@@ -1109,6 +1096,9 @@ function useChat(options = {}) {
|
|
|
1109
1096
|
stop,
|
|
1110
1097
|
reload,
|
|
1111
1098
|
clear,
|
|
1099
|
+
startNewChat,
|
|
1100
|
+
currentThreadId: effectiveThreadId,
|
|
1101
|
+
isEphemeral: !effectiveThreadId,
|
|
1112
1102
|
setMessages,
|
|
1113
1103
|
loadThreadMessages
|
|
1114
1104
|
};
|
|
@@ -2077,7 +2067,7 @@ import {
|
|
|
2077
2067
|
getLogLevel,
|
|
2078
2068
|
isLevelEnabled
|
|
2079
2069
|
} from "@personaai/logger";
|
|
2080
|
-
var VERSION = "0.
|
|
2070
|
+
var VERSION = "0.8.1";
|
|
2081
2071
|
export {
|
|
2082
2072
|
PersonaProvider,
|
|
2083
2073
|
VERSION,
|