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