@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.js
CHANGED
|
@@ -330,6 +330,37 @@ function parsePresentedFile(content) {
|
|
|
330
330
|
return null;
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
|
+
function parseSandboxCommand(tc) {
|
|
334
|
+
let command = tc.args ?? "";
|
|
335
|
+
try {
|
|
336
|
+
const parsedArgs = JSON.parse(tc.args || "{}");
|
|
337
|
+
if (typeof parsedArgs.command === "string") command = parsedArgs.command;
|
|
338
|
+
} catch {
|
|
339
|
+
}
|
|
340
|
+
let output;
|
|
341
|
+
let exitCode;
|
|
342
|
+
if (tc.result) {
|
|
343
|
+
try {
|
|
344
|
+
const parsedResult = JSON.parse(tc.result);
|
|
345
|
+
if (typeof parsedResult.output === "string") {
|
|
346
|
+
output = parsedResult.output;
|
|
347
|
+
exitCode = typeof parsedResult.exitCode === "number" ? parsedResult.exitCode : null;
|
|
348
|
+
} else {
|
|
349
|
+
output = tc.result;
|
|
350
|
+
}
|
|
351
|
+
} catch {
|
|
352
|
+
output = tc.result;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
toolCallId: tc.toolCallId,
|
|
357
|
+
command,
|
|
358
|
+
output,
|
|
359
|
+
exitCode,
|
|
360
|
+
status: tc.isError ? "error" : tc.result ? "done" : "running",
|
|
361
|
+
seq: tc.seq
|
|
362
|
+
};
|
|
363
|
+
}
|
|
333
364
|
function normalizeWorkspaceFiles(raw) {
|
|
334
365
|
const normalized = {};
|
|
335
366
|
for (const [path, file] of Object.entries(raw || {})) {
|
|
@@ -365,6 +396,11 @@ function useChat(options = {}) {
|
|
|
365
396
|
const chatLogger = useMemo2(() => logger.child("chat"), [logger]);
|
|
366
397
|
const agentId = options.agentId || defaultAgentId;
|
|
367
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]);
|
|
368
404
|
const didLogInitRef = useRef(false);
|
|
369
405
|
if (!didLogInitRef.current) {
|
|
370
406
|
didLogInitRef.current = true;
|
|
@@ -390,11 +426,21 @@ function useChat(options = {}) {
|
|
|
390
426
|
const [files, setFiles] = useState({});
|
|
391
427
|
const [todos, setTodos] = useState([]);
|
|
392
428
|
const [presentedFile, setPresentedFile] = useState(null);
|
|
429
|
+
const sandboxCommands = useMemo2(() => {
|
|
430
|
+
const commands = [];
|
|
431
|
+
for (const message of messages) {
|
|
432
|
+
for (const tc of message.toolCalls ?? []) {
|
|
433
|
+
if (tc.toolName === "execute") commands.push(parseSandboxCommand(tc));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return commands;
|
|
437
|
+
}, [messages]);
|
|
393
438
|
const abortControllerRef = useRef(null);
|
|
394
439
|
const loadedThreadIdRef = useRef(void 0);
|
|
395
|
-
const voiceThreadRef = useRef(
|
|
440
|
+
const voiceThreadRef = useRef(effectiveThreadId);
|
|
396
441
|
const voicePrevLenRef = useRef(0);
|
|
397
442
|
const voiceStreamingIdRef = useRef(null);
|
|
443
|
+
const voiceUserPartialIdRef = useRef(null);
|
|
398
444
|
const stop = useCallback(() => {
|
|
399
445
|
if (abortControllerRef.current) {
|
|
400
446
|
chatLogger.info("stop streaming", {});
|
|
@@ -419,7 +465,25 @@ function useChat(options = {}) {
|
|
|
419
465
|
setFiles({});
|
|
420
466
|
setTodos([]);
|
|
421
467
|
setPresentedFile(null);
|
|
468
|
+
loadedThreadIdRef.current = void 0;
|
|
422
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]);
|
|
423
487
|
const loadThreadMessages = useCallback(
|
|
424
488
|
async (id) => {
|
|
425
489
|
chatLogger.debug("loadThreadMessages start", { threadId: id });
|
|
@@ -451,6 +515,7 @@ function useChat(options = {}) {
|
|
|
451
515
|
setInterrupt(normalizePendingInterrupt(data?.pendingInterrupt));
|
|
452
516
|
setFiles(normalizeWorkspaceFiles(data?.state?.files ?? {}));
|
|
453
517
|
setTodos(data?.state?.todos ?? []);
|
|
518
|
+
loadedThreadIdRef.current = id;
|
|
454
519
|
chatLogger.info("loadThreadMessages succeeded", {
|
|
455
520
|
threadId: id,
|
|
456
521
|
messageCount: loaded.length
|
|
@@ -471,6 +536,7 @@ function useChat(options = {}) {
|
|
|
471
536
|
error: errorObj.message
|
|
472
537
|
});
|
|
473
538
|
setError(errorObj);
|
|
539
|
+
if (loadedThreadIdRef.current === id) loadedThreadIdRef.current = void 0;
|
|
474
540
|
return [];
|
|
475
541
|
} finally {
|
|
476
542
|
setIsLoadingHistory(false);
|
|
@@ -480,33 +546,33 @@ function useChat(options = {}) {
|
|
|
480
546
|
[fetchWithAuth, chatLogger]
|
|
481
547
|
);
|
|
482
548
|
useEffect(() => {
|
|
483
|
-
if (!
|
|
484
|
-
chatLogger.trace("auto-load skipped", { threadId, isStreaming });
|
|
549
|
+
if (!effectiveThreadId || isStreaming) {
|
|
550
|
+
chatLogger.trace("auto-load skipped", { threadId: effectiveThreadId, isStreaming });
|
|
485
551
|
return;
|
|
486
552
|
}
|
|
487
|
-
if (loadedThreadIdRef.current ===
|
|
488
|
-
chatLogger.trace("auto-load already loaded", { threadId });
|
|
553
|
+
if (loadedThreadIdRef.current === effectiveThreadId) {
|
|
554
|
+
chatLogger.trace("auto-load already loaded", { threadId: effectiveThreadId });
|
|
489
555
|
return;
|
|
490
556
|
}
|
|
491
557
|
if (messages.length > 0) {
|
|
492
558
|
chatLogger.trace("auto-load has messages", {
|
|
493
|
-
threadId,
|
|
559
|
+
threadId: effectiveThreadId,
|
|
494
560
|
count: messages.length
|
|
495
561
|
});
|
|
496
562
|
return;
|
|
497
563
|
}
|
|
498
|
-
|
|
499
|
-
chatLogger.
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}, [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]);
|
|
503
568
|
const voice = options.voice;
|
|
504
569
|
useEffect(() => {
|
|
505
570
|
if (!voice) return;
|
|
506
|
-
if (voiceThreadRef.current !==
|
|
507
|
-
voiceThreadRef.current =
|
|
571
|
+
if (voiceThreadRef.current !== effectiveThreadId) {
|
|
572
|
+
voiceThreadRef.current = effectiveThreadId;
|
|
508
573
|
voicePrevLenRef.current = voice.transcript.length;
|
|
509
574
|
voiceStreamingIdRef.current = null;
|
|
575
|
+
voiceUserPartialIdRef.current = null;
|
|
510
576
|
return;
|
|
511
577
|
}
|
|
512
578
|
const isVoiceActive = voice.state !== "idle" && voice.state !== "ended" && voice.state !== "error";
|
|
@@ -514,15 +580,40 @@ function useChat(options = {}) {
|
|
|
514
580
|
if (curLen < voicePrevLenRef.current) {
|
|
515
581
|
voicePrevLenRef.current = 0;
|
|
516
582
|
voiceStreamingIdRef.current = null;
|
|
583
|
+
voiceUserPartialIdRef.current = null;
|
|
517
584
|
}
|
|
518
585
|
if (!isVoiceActive) {
|
|
519
|
-
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
|
+
}
|
|
520
608
|
if (voiceStreamingIdRef.current) {
|
|
521
609
|
const doneId = voiceStreamingIdRef.current;
|
|
522
610
|
voiceStreamingIdRef.current = null;
|
|
523
|
-
setMessages(
|
|
524
|
-
|
|
525
|
-
|
|
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));
|
|
526
617
|
}
|
|
527
618
|
return;
|
|
528
619
|
}
|
|
@@ -549,54 +640,71 @@ function useChat(options = {}) {
|
|
|
549
640
|
});
|
|
550
641
|
}
|
|
551
642
|
}
|
|
552
|
-
const
|
|
553
|
-
|
|
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
|
+
}
|
|
554
651
|
setMessages((prev) => {
|
|
555
652
|
const last = prev[prev.length - 1];
|
|
556
653
|
if (last?.id.startsWith("voice-") && last.role === "assistant") {
|
|
557
654
|
voiceStreamingIdRef.current = last.id;
|
|
558
|
-
return [...prev.slice(0, -1), { ...last, content:
|
|
655
|
+
return [...prev.slice(0, -1), { ...last, content: agentPartialText, isStreaming: true }];
|
|
559
656
|
}
|
|
560
657
|
const id = voiceStreamingIdRef.current || `voice-partial-${Date.now()}`;
|
|
561
658
|
voiceStreamingIdRef.current = id;
|
|
562
|
-
return [...prev, { id, role: "assistant", content:
|
|
659
|
+
return [...prev, { id, role: "assistant", content: agentPartialText, isStreaming: true, createdAt: /* @__PURE__ */ new Date() }];
|
|
563
660
|
});
|
|
564
|
-
} else if (
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
(prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m)
|
|
569
|
-
);
|
|
570
|
-
}
|
|
571
|
-
}, [voice?.transcript, voice?.partial, voice?.state, threadId]);
|
|
572
|
-
const sendMessage = useCallback(
|
|
573
|
-
async (contentToSend, overrideOptions) => {
|
|
574
|
-
const prompt = (contentToSend ?? input).trim();
|
|
575
|
-
if (!prompt || isStreaming) {
|
|
576
|
-
chatLogger.trace("sendMessage skipped", {
|
|
577
|
-
hasPrompt: !!prompt,
|
|
578
|
-
isStreaming
|
|
579
|
-
});
|
|
580
|
-
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));
|
|
581
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) => {
|
|
582
692
|
const targetAgentId = overrideOptions?.agentId || agentId;
|
|
583
693
|
if (!targetAgentId) {
|
|
584
694
|
const err = new Error("No Agent ID specified for useChat.");
|
|
585
695
|
chatLogger.warn("sendMessage no agentId", {});
|
|
586
|
-
chatLogger.error("sendMessage failed \u2014 no agent", {
|
|
587
|
-
error: err.message
|
|
588
|
-
});
|
|
696
|
+
chatLogger.error("sendMessage failed \u2014 no agent", { error: err.message });
|
|
589
697
|
setError(err);
|
|
590
698
|
options.onError?.(err);
|
|
591
|
-
return;
|
|
699
|
+
return false;
|
|
592
700
|
}
|
|
593
701
|
chatLogger.debug("sendMessage start", {
|
|
594
702
|
promptPreview: prompt.slice(0, 100),
|
|
595
703
|
agentId: targetAgentId,
|
|
596
|
-
threadId:
|
|
704
|
+
threadId: effectiveThreadId ?? overrideOptions?.threadId,
|
|
597
705
|
hasResume: !!overrideOptions?.resume,
|
|
598
706
|
hasContextOverride: !!overrideOptions?.contextOverride,
|
|
599
|
-
messageCount:
|
|
707
|
+
messageCount: baseMessages.length
|
|
600
708
|
});
|
|
601
709
|
chatLogger.trace("sendMessage details", {
|
|
602
710
|
agentId: targetAgentId,
|
|
@@ -619,7 +727,7 @@ function useChat(options = {}) {
|
|
|
619
727
|
isStreaming: true,
|
|
620
728
|
toolCalls: []
|
|
621
729
|
};
|
|
622
|
-
const nextMessages = [...
|
|
730
|
+
const nextMessages = [...baseMessages, userMessage];
|
|
623
731
|
setMessages([...nextMessages, placeholderAssistant]);
|
|
624
732
|
setInput("");
|
|
625
733
|
setIsStreaming(true);
|
|
@@ -629,9 +737,7 @@ function useChat(options = {}) {
|
|
|
629
737
|
abortControllerRef.current = controller;
|
|
630
738
|
const finalizeReasoning = () => {
|
|
631
739
|
setMessages(
|
|
632
|
-
(prev) => prev.map(
|
|
633
|
-
(m) => m.role === "reasoning" && m.isStreaming ? { ...m, isStreaming: false } : m
|
|
634
|
-
)
|
|
740
|
+
(prev) => prev.map((m) => m.role === "reasoning" && m.isStreaming ? { ...m, isStreaming: false } : m)
|
|
635
741
|
);
|
|
636
742
|
};
|
|
637
743
|
try {
|
|
@@ -639,7 +745,24 @@ function useChat(options = {}) {
|
|
|
639
745
|
role: m.role === "assistant" ? "assistant" : "user",
|
|
640
746
|
content: m.content
|
|
641
747
|
}));
|
|
642
|
-
|
|
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
|
+
}
|
|
643
766
|
chatLogger.trace("resolved threadId", { threadId: resolvedThreadId });
|
|
644
767
|
const token = getAuthToken ? await getAuthToken() : null;
|
|
645
768
|
const resolvedHookContext = typeof options.context === "function" ? options.context() : options.context;
|
|
@@ -668,27 +791,12 @@ function useChat(options = {}) {
|
|
|
668
791
|
signal: controller.signal
|
|
669
792
|
});
|
|
670
793
|
if (!stream.ok) {
|
|
671
|
-
chatLogger.warn("SSE stream not ok", {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
});
|
|
675
|
-
chatLogger.error("chat stream failed", {
|
|
676
|
-
agentId: targetAgentId,
|
|
677
|
-
status: stream.status,
|
|
678
|
-
error: stream.errorText
|
|
679
|
-
});
|
|
680
|
-
throw new Error(
|
|
681
|
-
`Chat error (${stream.status}): ${stream.errorText ?? "Stream failed"}`
|
|
682
|
-
);
|
|
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"}`);
|
|
683
797
|
}
|
|
684
|
-
chatLogger.debug("SSE stream opened", {
|
|
685
|
-
|
|
686
|
-
status: stream.status
|
|
687
|
-
});
|
|
688
|
-
chatLogger.info("chat stream started", {
|
|
689
|
-
agentId: targetAgentId,
|
|
690
|
-
threadId: resolvedThreadId
|
|
691
|
-
});
|
|
798
|
+
chatLogger.debug("SSE stream opened", { agentId: targetAgentId, status: stream.status });
|
|
799
|
+
chatLogger.info("chat stream started", { agentId: targetAgentId, threadId: resolvedThreadId });
|
|
692
800
|
const reader = stream.reader;
|
|
693
801
|
let buffer = "";
|
|
694
802
|
let accumulatedText = "";
|
|
@@ -699,23 +807,12 @@ function useChat(options = {}) {
|
|
|
699
807
|
const patchAssistant = (patch) => {
|
|
700
808
|
setMessages(
|
|
701
809
|
(prev) => prev.map(
|
|
702
|
-
(msg) => msg.id === assistantMessageId ? {
|
|
703
|
-
...msg,
|
|
704
|
-
toolCalls: Array.from(toolCallsMap.values()),
|
|
705
|
-
...patch
|
|
706
|
-
} : msg
|
|
810
|
+
(msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()), ...patch } : msg
|
|
707
811
|
)
|
|
708
812
|
);
|
|
709
813
|
};
|
|
710
814
|
const insertReasoningMessage = (id, seq) => {
|
|
711
|
-
const msg = {
|
|
712
|
-
id,
|
|
713
|
-
role: "reasoning",
|
|
714
|
-
content: "",
|
|
715
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
716
|
-
isStreaming: true,
|
|
717
|
-
seq
|
|
718
|
-
};
|
|
815
|
+
const msg = { id, role: "reasoning", content: "", createdAt: /* @__PURE__ */ new Date(), isStreaming: true, seq };
|
|
719
816
|
setMessages((prev) => {
|
|
720
817
|
const idx = prev.findIndex((m) => m.id === assistantMessageId);
|
|
721
818
|
if (idx === -1) return [...prev, msg];
|
|
@@ -740,16 +837,11 @@ function useChat(options = {}) {
|
|
|
740
837
|
chatLogger.trace("stream event", { type: event.type, event });
|
|
741
838
|
options.onEvent?.(event);
|
|
742
839
|
if (event.type === "TEXT_MESSAGE_CHUNK" && event.delta) {
|
|
743
|
-
chatLogger.debug("text chunk", {
|
|
744
|
-
deltaLength: event.delta.length
|
|
745
|
-
});
|
|
840
|
+
chatLogger.debug("text chunk", { deltaLength: event.delta.length });
|
|
746
841
|
accumulatedText += event.delta;
|
|
747
842
|
patchAssistant({ content: accumulatedText, isStreaming: true });
|
|
748
843
|
} else if (event.type === "TOOL_CALL_CHUNK" && event.toolCallId) {
|
|
749
|
-
chatLogger.debug("tool call chunk", {
|
|
750
|
-
toolCallId: event.toolCallId,
|
|
751
|
-
toolCallName: event.toolCallName
|
|
752
|
-
});
|
|
844
|
+
chatLogger.debug("tool call chunk", { toolCallId: event.toolCallId, toolCallName: event.toolCallName });
|
|
753
845
|
const existing = toolCallsMap.get(event.toolCallId);
|
|
754
846
|
if (existing) {
|
|
755
847
|
existing.args = (existing.args || "") + (event.delta || "");
|
|
@@ -763,17 +855,12 @@ function useChat(options = {}) {
|
|
|
763
855
|
}
|
|
764
856
|
patchAssistant({});
|
|
765
857
|
} else if (event.type === "TOOL_CALL_RESULT") {
|
|
766
|
-
chatLogger.debug("tool call result", {
|
|
767
|
-
toolCallId: event.toolCallId
|
|
768
|
-
});
|
|
858
|
+
chatLogger.debug("tool call result", { toolCallId: event.toolCallId });
|
|
769
859
|
const existing = toolCallsMap.get(event.toolCallId);
|
|
770
860
|
if (existing) {
|
|
771
861
|
existing.result = event.content;
|
|
772
862
|
existing.isError = isErrorToolContent(event.content);
|
|
773
|
-
if (existing.isError)
|
|
774
|
-
chatLogger.warn("tool call error", {
|
|
775
|
-
toolCallId: event.toolCallId
|
|
776
|
-
});
|
|
863
|
+
if (existing.isError) chatLogger.warn("tool call error", { toolCallId: event.toolCallId });
|
|
777
864
|
if (existing.toolName === "present_file" && !existing.isError) {
|
|
778
865
|
const presented = parsePresentedFile(event.content);
|
|
779
866
|
if (presented) {
|
|
@@ -788,12 +875,10 @@ function useChat(options = {}) {
|
|
|
788
875
|
fileCount: Object.keys(event.snapshot.files ?? {}).length,
|
|
789
876
|
todoCount: event.snapshot.todos?.length ?? 0
|
|
790
877
|
});
|
|
791
|
-
setFiles(normalizeWorkspaceFiles(event.snapshot.files));
|
|
792
|
-
setTodos(event.snapshot.todos);
|
|
878
|
+
setFiles(normalizeWorkspaceFiles(event.snapshot.files ?? {}));
|
|
879
|
+
setTodos(event.snapshot.todos ?? []);
|
|
793
880
|
} else if (event.type === "REASONING_MESSAGE_START" && event.messageId) {
|
|
794
|
-
chatLogger.debug("reasoning start", {
|
|
795
|
-
messageId: event.messageId
|
|
796
|
-
});
|
|
881
|
+
chatLogger.debug("reasoning start", { messageId: event.messageId });
|
|
797
882
|
activeReasoningId = event.messageId;
|
|
798
883
|
reasoningById.set(event.messageId, { content: "" });
|
|
799
884
|
insertReasoningMessage(event.messageId, streamSeq++);
|
|
@@ -808,61 +893,31 @@ function useChat(options = {}) {
|
|
|
808
893
|
}
|
|
809
894
|
const entry = reasoningById.get(rid);
|
|
810
895
|
entry.content += event.delta;
|
|
811
|
-
chatLogger.trace("reasoning content", {
|
|
812
|
-
|
|
813
|
-
deltaLength: event.delta?.length ?? 0
|
|
814
|
-
});
|
|
815
|
-
setMessages(
|
|
816
|
-
(prev) => prev.map(
|
|
817
|
-
(m) => m.id === rid ? { ...m, content: entry.content, isStreaming: true } : m
|
|
818
|
-
)
|
|
819
|
-
);
|
|
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));
|
|
820
898
|
} else if (event.type === "REASONING_END") {
|
|
821
|
-
chatLogger.debug("reasoning end", {
|
|
822
|
-
messageId: activeReasoningId
|
|
823
|
-
});
|
|
899
|
+
chatLogger.debug("reasoning end", { messageId: activeReasoningId });
|
|
824
900
|
const rid = activeReasoningId;
|
|
825
901
|
activeReasoningId = null;
|
|
826
902
|
if (rid) {
|
|
827
|
-
setMessages(
|
|
828
|
-
(prev) => prev.map(
|
|
829
|
-
(m) => m.id === rid ? { ...m, isStreaming: false } : m
|
|
830
|
-
)
|
|
831
|
-
);
|
|
903
|
+
setMessages((prev) => prev.map((m) => m.id === rid ? { ...m, isStreaming: false } : m));
|
|
832
904
|
}
|
|
833
905
|
} else if (event.type === "CUSTOM") {
|
|
834
906
|
chatLogger.debug("custom event", { name: event.name });
|
|
835
907
|
if (event.name === "hitl_request") {
|
|
836
908
|
const value2 = event.value;
|
|
837
|
-
chatLogger.info("hitl interrupt", {
|
|
838
|
-
|
|
839
|
-
});
|
|
840
|
-
setInterrupt({
|
|
841
|
-
kind: "hitl",
|
|
842
|
-
actionRequests: value2.actionRequests,
|
|
843
|
-
reviewConfigs: value2.reviewConfigs
|
|
844
|
-
});
|
|
909
|
+
chatLogger.info("hitl interrupt", { actionCount: value2.actionRequests?.length ?? 0 });
|
|
910
|
+
setInterrupt({ kind: "hitl", actionRequests: value2.actionRequests, reviewConfigs: value2.reviewConfigs });
|
|
845
911
|
} else if (event.name === "clarification_request") {
|
|
846
912
|
const value2 = event.value;
|
|
847
|
-
chatLogger.info("clarification interrupt", {
|
|
848
|
-
|
|
849
|
-
});
|
|
850
|
-
setInterrupt({
|
|
851
|
-
kind: "clarification",
|
|
852
|
-
questions: value2.questions
|
|
853
|
-
});
|
|
913
|
+
chatLogger.info("clarification interrupt", { questionCount: value2.questions?.length ?? 0 });
|
|
914
|
+
setInterrupt({ kind: "clarification", questions: value2.questions });
|
|
854
915
|
} else if (event.name === "subagent_activity") {
|
|
855
916
|
const { toolCallId, ...entry } = event.value;
|
|
856
|
-
chatLogger.trace("subagent activity", {
|
|
857
|
-
toolCallId,
|
|
858
|
-
kind: entry.kind
|
|
859
|
-
});
|
|
917
|
+
chatLogger.trace("subagent activity", { toolCallId, kind: entry.kind });
|
|
860
918
|
const existing = toolCallsMap.get(toolCallId);
|
|
861
919
|
if (existing) {
|
|
862
|
-
existing.subagentActivity = [
|
|
863
|
-
...existing.subagentActivity || [],
|
|
864
|
-
entry
|
|
865
|
-
];
|
|
920
|
+
existing.subagentActivity = [...existing.subagentActivity || [], entry];
|
|
866
921
|
patchAssistant({});
|
|
867
922
|
}
|
|
868
923
|
} else if (event.name === "mcp_app") {
|
|
@@ -870,35 +925,21 @@ function useChat(options = {}) {
|
|
|
870
925
|
if (val?.toolCallId && val?.resourceUri && val?.mcpId) {
|
|
871
926
|
const existing = toolCallsMap.get(val.toolCallId);
|
|
872
927
|
if (existing) {
|
|
873
|
-
existing.mcpApp = {
|
|
874
|
-
resourceUri: val.resourceUri,
|
|
875
|
-
mcpId: val.mcpId
|
|
876
|
-
};
|
|
928
|
+
existing.mcpApp = { resourceUri: val.resourceUri, mcpId: val.mcpId };
|
|
877
929
|
patchAssistant({});
|
|
878
930
|
}
|
|
879
931
|
}
|
|
880
932
|
}
|
|
881
933
|
} else if (event.type === "RUN_ERROR") {
|
|
882
|
-
chatLogger.warn("run error", {
|
|
883
|
-
|
|
884
|
-
code: event.code
|
|
885
|
-
});
|
|
886
|
-
chatLogger.error("stream run error", {
|
|
887
|
-
code: event.code,
|
|
888
|
-
message: event.message
|
|
889
|
-
});
|
|
934
|
+
chatLogger.warn("run error", { message: event.message, code: event.code });
|
|
935
|
+
chatLogger.error("stream run error", { code: event.code, message: event.message });
|
|
890
936
|
throw new Error(event.message || "Stream error from agent");
|
|
891
937
|
} else {
|
|
892
938
|
chatLogger.trace("unhandled event", { type: event.type });
|
|
893
939
|
}
|
|
894
940
|
} catch (e) {
|
|
895
|
-
if (e instanceof Error && e.message.startsWith("Stream error"))
|
|
896
|
-
|
|
897
|
-
}
|
|
898
|
-
chatLogger.warn("event parse error", {
|
|
899
|
-
raw: raw.slice(0, 200),
|
|
900
|
-
error: e instanceof Error ? e.message : String(e)
|
|
901
|
-
});
|
|
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) });
|
|
902
943
|
}
|
|
903
944
|
}
|
|
904
945
|
}
|
|
@@ -910,75 +951,55 @@ function useChat(options = {}) {
|
|
|
910
951
|
isStreaming: false,
|
|
911
952
|
toolCalls: Array.from(toolCallsMap.values())
|
|
912
953
|
};
|
|
913
|
-
setMessages(
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
)
|
|
917
|
-
);
|
|
918
|
-
chatLogger.info("sendMessage succeeded", {
|
|
919
|
-
agentId: targetAgentId,
|
|
920
|
-
textLength: accumulatedText.length,
|
|
921
|
-
toolCallCount: toolCallsMap.size
|
|
922
|
-
});
|
|
923
|
-
chatLogger.debug("sendMessage completed", {
|
|
924
|
-
agentId: targetAgentId,
|
|
925
|
-
textLength: accumulatedText.length,
|
|
926
|
-
eventCount: toolCallsMap.size + 1
|
|
927
|
-
});
|
|
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 });
|
|
928
957
|
options.onFinish?.(finalMessage);
|
|
958
|
+
return true;
|
|
929
959
|
} catch (err) {
|
|
930
960
|
if (controller.signal.aborted) {
|
|
931
961
|
chatLogger.info("sendMessage aborted", { agentId: targetAgentId });
|
|
932
962
|
chatLogger.debug("stream aborted", { agentId: targetAgentId });
|
|
933
|
-
setMessages(
|
|
934
|
-
|
|
935
|
-
(msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg
|
|
936
|
-
)
|
|
937
|
-
);
|
|
938
|
-
return;
|
|
963
|
+
setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg));
|
|
964
|
+
return false;
|
|
939
965
|
}
|
|
940
966
|
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
941
|
-
chatLogger.warn("sendMessage failed", {
|
|
942
|
-
|
|
943
|
-
error: errorObj.message
|
|
944
|
-
});
|
|
945
|
-
chatLogger.error("sendMessage error", {
|
|
946
|
-
agentId: targetAgentId,
|
|
947
|
-
error: errorObj.message
|
|
948
|
-
});
|
|
967
|
+
chatLogger.warn("sendMessage failed", { agentId: targetAgentId, error: errorObj.message });
|
|
968
|
+
chatLogger.error("sendMessage error", { agentId: targetAgentId, error: errorObj.message });
|
|
949
969
|
setError(errorObj);
|
|
950
970
|
options.onError?.(errorObj);
|
|
951
971
|
setMessages(
|
|
952
972
|
(prev) => prev.map(
|
|
953
|
-
(msg) => msg.id === assistantMessageId ? {
|
|
954
|
-
...msg,
|
|
955
|
-
content: msg.content || `\u26A0\uFE0F Error: ${errorObj.message || "Failed to get response."}`,
|
|
956
|
-
isStreaming: false
|
|
957
|
-
} : msg
|
|
973
|
+
(msg) => msg.id === assistantMessageId ? { ...msg, content: msg.content || `\u26A0\uFE0F Error: ${errorObj.message || "Failed to get response."}`, isStreaming: false } : msg
|
|
958
974
|
)
|
|
959
975
|
);
|
|
976
|
+
return false;
|
|
960
977
|
} finally {
|
|
961
978
|
setIsStreaming(false);
|
|
962
979
|
abortControllerRef.current = null;
|
|
963
980
|
finalizeReasoning();
|
|
964
|
-
chatLogger.debug("sendMessage finally", {
|
|
965
|
-
agentId: targetAgentId,
|
|
966
|
-
isStreaming: false
|
|
967
|
-
});
|
|
981
|
+
chatLogger.debug("sendMessage finally", { agentId: targetAgentId, isStreaming: false });
|
|
968
982
|
chatLogger.trace("sendMessage end", { agentId: targetAgentId });
|
|
969
983
|
}
|
|
970
984
|
},
|
|
971
|
-
[
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
input
|
|
976
|
-
isStreaming
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
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]
|
|
982
1003
|
);
|
|
983
1004
|
const handleInputChange = useCallback(
|
|
984
1005
|
(e) => {
|
|
@@ -995,13 +1016,13 @@ function useChat(options = {}) {
|
|
|
995
1016
|
},
|
|
996
1017
|
[sendMessage, chatLogger]
|
|
997
1018
|
);
|
|
998
|
-
const reload = useCallback(() => {
|
|
1019
|
+
const reload = useCallback(async () => {
|
|
999
1020
|
if (messages.length === 0 || isStreaming) {
|
|
1000
1021
|
chatLogger.trace("reload skipped", {
|
|
1001
1022
|
messageCount: messages.length,
|
|
1002
1023
|
isStreaming
|
|
1003
1024
|
});
|
|
1004
|
-
return;
|
|
1025
|
+
return false;
|
|
1005
1026
|
}
|
|
1006
1027
|
let lastUserIndex = -1;
|
|
1007
1028
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -1012,16 +1033,16 @@ function useChat(options = {}) {
|
|
|
1012
1033
|
}
|
|
1013
1034
|
if (lastUserIndex === -1) {
|
|
1014
1035
|
chatLogger.warn("reload no user message", {});
|
|
1015
|
-
return;
|
|
1036
|
+
return false;
|
|
1016
1037
|
}
|
|
1017
1038
|
const lastUserMessage = messages[lastUserIndex];
|
|
1039
|
+
const truncated = messages.slice(0, lastUserIndex);
|
|
1018
1040
|
chatLogger.info("reload", { messageId: lastUserMessage.id });
|
|
1019
1041
|
chatLogger.debug("reload last user", {
|
|
1020
1042
|
preview: lastUserMessage.content.slice(0, 100)
|
|
1021
1043
|
});
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
}, [isStreaming, messages, sendMessage, chatLogger]);
|
|
1044
|
+
return doSend(lastUserMessage.content, truncated);
|
|
1045
|
+
}, [isStreaming, messages, doSend, chatLogger]);
|
|
1025
1046
|
const resumeInterrupt = useCallback(
|
|
1026
1047
|
(resume, displayContent) => {
|
|
1027
1048
|
chatLogger.info("resumeInterrupt", {
|
|
@@ -1065,9 +1086,13 @@ function useChat(options = {}) {
|
|
|
1065
1086
|
presentedFile,
|
|
1066
1087
|
dismissPresentedFile,
|
|
1067
1088
|
openWorkspaceFile,
|
|
1089
|
+
sandboxCommands,
|
|
1068
1090
|
stop,
|
|
1069
1091
|
reload,
|
|
1070
1092
|
clear,
|
|
1093
|
+
startNewChat,
|
|
1094
|
+
currentThreadId: effectiveThreadId,
|
|
1095
|
+
isEphemeral: !effectiveThreadId,
|
|
1071
1096
|
setMessages,
|
|
1072
1097
|
loadThreadMessages
|
|
1073
1098
|
};
|
|
@@ -2036,7 +2061,7 @@ import {
|
|
|
2036
2061
|
getLogLevel,
|
|
2037
2062
|
isLevelEnabled
|
|
2038
2063
|
} from "@personaai/logger";
|
|
2039
|
-
var VERSION = "0.
|
|
2064
|
+
var VERSION = "0.8.0";
|
|
2040
2065
|
export {
|
|
2041
2066
|
PersonaProvider,
|
|
2042
2067
|
VERSION,
|