@personaai/react 0.7.10 → 0.8.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 +200 -216
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -4
- package/dist/index.d.ts +9 -4
- package/dist/index.js +200 -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
|
-
}, [voice?.transcript, voice?.partial, voice?.state, threadId]);
|
|
612
|
-
const sendMessage = useCallback(
|
|
613
|
-
async (contentToSend, overrideOptions) => {
|
|
614
|
-
const prompt = (contentToSend ?? input).trim();
|
|
615
|
-
if (!prompt || isStreaming) {
|
|
616
|
-
chatLogger.trace("sendMessage skipped", {
|
|
617
|
-
hasPrompt: !!prompt,
|
|
618
|
-
isStreaming
|
|
619
|
-
});
|
|
620
|
-
return;
|
|
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));
|
|
621
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));
|
|
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,21 @@ 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
|
}
|
|
921
933
|
} 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
|
-
});
|
|
934
|
+
chatLogger.warn("run error", { message: event.message, code: event.code });
|
|
935
|
+
chatLogger.error("stream run error", { code: event.code, message: event.message });
|
|
930
936
|
throw new Error(event.message || "Stream error from agent");
|
|
931
937
|
} else {
|
|
932
938
|
chatLogger.trace("unhandled event", { type: event.type });
|
|
933
939
|
}
|
|
934
940
|
} 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
|
-
});
|
|
941
|
+
if (e instanceof Error && e.message.startsWith("Stream error")) throw e;
|
|
942
|
+
chatLogger.warn("event parse error", { raw: raw.slice(0, 200), error: e instanceof Error ? e.message : String(e) });
|
|
942
943
|
}
|
|
943
944
|
}
|
|
944
945
|
}
|
|
@@ -950,75 +951,55 @@ function useChat(options = {}) {
|
|
|
950
951
|
isStreaming: false,
|
|
951
952
|
toolCalls: Array.from(toolCallsMap.values())
|
|
952
953
|
};
|
|
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
|
-
});
|
|
954
|
+
setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? finalMessage : msg));
|
|
955
|
+
chatLogger.info("sendMessage succeeded", { agentId: targetAgentId, textLength: accumulatedText.length, toolCallCount: toolCallsMap.size });
|
|
956
|
+
chatLogger.debug("sendMessage completed", { agentId: targetAgentId, textLength: accumulatedText.length, eventCount: toolCallsMap.size + 1 });
|
|
968
957
|
options.onFinish?.(finalMessage);
|
|
958
|
+
return true;
|
|
969
959
|
} catch (err) {
|
|
970
960
|
if (controller.signal.aborted) {
|
|
971
961
|
chatLogger.info("sendMessage aborted", { agentId: targetAgentId });
|
|
972
962
|
chatLogger.debug("stream aborted", { agentId: targetAgentId });
|
|
973
|
-
setMessages(
|
|
974
|
-
|
|
975
|
-
(msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg
|
|
976
|
-
)
|
|
977
|
-
);
|
|
978
|
-
return;
|
|
963
|
+
setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg));
|
|
964
|
+
return false;
|
|
979
965
|
}
|
|
980
966
|
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
|
-
});
|
|
967
|
+
chatLogger.warn("sendMessage failed", { agentId: targetAgentId, error: errorObj.message });
|
|
968
|
+
chatLogger.error("sendMessage error", { agentId: targetAgentId, error: errorObj.message });
|
|
989
969
|
setError(errorObj);
|
|
990
970
|
options.onError?.(errorObj);
|
|
991
971
|
setMessages(
|
|
992
972
|
(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
|
|
973
|
+
(msg) => msg.id === assistantMessageId ? { ...msg, content: msg.content || `\u26A0\uFE0F Error: ${errorObj.message || "Failed to get response."}`, isStreaming: false } : msg
|
|
998
974
|
)
|
|
999
975
|
);
|
|
976
|
+
return false;
|
|
1000
977
|
} finally {
|
|
1001
978
|
setIsStreaming(false);
|
|
1002
979
|
abortControllerRef.current = null;
|
|
1003
980
|
finalizeReasoning();
|
|
1004
|
-
chatLogger.debug("sendMessage finally", {
|
|
1005
|
-
agentId: targetAgentId,
|
|
1006
|
-
isStreaming: false
|
|
1007
|
-
});
|
|
981
|
+
chatLogger.debug("sendMessage finally", { agentId: targetAgentId, isStreaming: false });
|
|
1008
982
|
chatLogger.trace("sendMessage end", { agentId: targetAgentId });
|
|
1009
983
|
}
|
|
1010
984
|
},
|
|
1011
|
-
[
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
input
|
|
1016
|
-
isStreaming
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
985
|
+
[agentId, baseUrl, getAuthToken, fetchWithAuth, options, chatLogger, effectiveThreadId]
|
|
986
|
+
);
|
|
987
|
+
const sendMessage = useCallback(
|
|
988
|
+
async (contentToSend, overrideOptions) => {
|
|
989
|
+
const prompt = (contentToSend ?? input).trim();
|
|
990
|
+
if (!prompt || isStreaming) {
|
|
991
|
+
chatLogger.trace("sendMessage skipped", {
|
|
992
|
+
hasPrompt: !!prompt,
|
|
993
|
+
isStreaming
|
|
994
|
+
});
|
|
995
|
+
if (isStreaming) {
|
|
996
|
+
chatLogger.warn("sendMessage dropped while streaming \u2014 caller should queue or disable send", {});
|
|
997
|
+
}
|
|
998
|
+
return false;
|
|
999
|
+
}
|
|
1000
|
+
return doSend(prompt, messages, overrideOptions);
|
|
1001
|
+
},
|
|
1002
|
+
[input, isStreaming, messages, doSend, chatLogger]
|
|
1022
1003
|
);
|
|
1023
1004
|
const handleInputChange = useCallback(
|
|
1024
1005
|
(e) => {
|
|
@@ -1035,13 +1016,13 @@ function useChat(options = {}) {
|
|
|
1035
1016
|
},
|
|
1036
1017
|
[sendMessage, chatLogger]
|
|
1037
1018
|
);
|
|
1038
|
-
const reload = useCallback(() => {
|
|
1019
|
+
const reload = useCallback(async () => {
|
|
1039
1020
|
if (messages.length === 0 || isStreaming) {
|
|
1040
1021
|
chatLogger.trace("reload skipped", {
|
|
1041
1022
|
messageCount: messages.length,
|
|
1042
1023
|
isStreaming
|
|
1043
1024
|
});
|
|
1044
|
-
return;
|
|
1025
|
+
return false;
|
|
1045
1026
|
}
|
|
1046
1027
|
let lastUserIndex = -1;
|
|
1047
1028
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -1052,16 +1033,16 @@ function useChat(options = {}) {
|
|
|
1052
1033
|
}
|
|
1053
1034
|
if (lastUserIndex === -1) {
|
|
1054
1035
|
chatLogger.warn("reload no user message", {});
|
|
1055
|
-
return;
|
|
1036
|
+
return false;
|
|
1056
1037
|
}
|
|
1057
1038
|
const lastUserMessage = messages[lastUserIndex];
|
|
1039
|
+
const truncated = messages.slice(0, lastUserIndex);
|
|
1058
1040
|
chatLogger.info("reload", { messageId: lastUserMessage.id });
|
|
1059
1041
|
chatLogger.debug("reload last user", {
|
|
1060
1042
|
preview: lastUserMessage.content.slice(0, 100)
|
|
1061
1043
|
});
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
}, [isStreaming, messages, sendMessage, chatLogger]);
|
|
1044
|
+
return doSend(lastUserMessage.content, truncated);
|
|
1045
|
+
}, [isStreaming, messages, doSend, chatLogger]);
|
|
1065
1046
|
const resumeInterrupt = useCallback(
|
|
1066
1047
|
(resume, displayContent) => {
|
|
1067
1048
|
chatLogger.info("resumeInterrupt", {
|
|
@@ -1109,6 +1090,9 @@ function useChat(options = {}) {
|
|
|
1109
1090
|
stop,
|
|
1110
1091
|
reload,
|
|
1111
1092
|
clear,
|
|
1093
|
+
startNewChat,
|
|
1094
|
+
currentThreadId: effectiveThreadId,
|
|
1095
|
+
isEphemeral: !effectiveThreadId,
|
|
1112
1096
|
setMessages,
|
|
1113
1097
|
loadThreadMessages
|
|
1114
1098
|
};
|
|
@@ -2077,7 +2061,7 @@ import {
|
|
|
2077
2061
|
getLogLevel,
|
|
2078
2062
|
isLevelEnabled
|
|
2079
2063
|
} from "@personaai/logger";
|
|
2080
|
-
var VERSION = "0.
|
|
2064
|
+
var VERSION = "0.8.0";
|
|
2081
2065
|
export {
|
|
2082
2066
|
PersonaProvider,
|
|
2083
2067
|
VERSION,
|