@agents24/chat-react 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -4
- package/dist/controller-actions.d.ts +45 -0
- package/dist/controller-helpers.d.ts +27 -0
- package/dist/controller.d.ts +2 -7
- package/dist/index.cjs +568 -207
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +511 -157
- package/dist/index.js.map +1 -1
- package/dist/message-lifecycle.d.ts +15 -0
- package/dist/model.d.ts +2 -0
- package/dist/renderers.d.ts +1 -1
- package/dist/sse.d.ts +3 -1
- package/dist/templates/agent-chat-shell.d.ts +13 -1
- package/dist/templates/index.cjs +90 -24
- package/dist/templates/index.cjs.map +1 -1
- package/dist/templates/index.js +88 -23
- package/dist/templates/index.js.map +1 -1
- package/dist/transport.d.ts +14 -0
- package/dist/types.d.ts +61 -4
- package/dist/ui/adapters.d.ts +1 -2
- package/dist/ui/agent-chat-actions.d.ts +16 -0
- package/dist/ui/agent-chat-composer.d.ts +28 -0
- package/dist/ui/agent-chat-message.d.ts +40 -0
- package/dist/ui/agent-response-timeline.d.ts +24 -0
- package/dist/ui/index.cjs +944 -206
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.ts +5 -0
- package/dist/ui/index.js +932 -202
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/mcp-connect-required-card.d.ts +13 -0
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -15,7 +15,79 @@ import {
|
|
|
15
15
|
} from "./chunk-EWZO4QJI.js";
|
|
16
16
|
|
|
17
17
|
// src/controller.ts
|
|
18
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
18
|
+
import { useCallback as useCallback2, useEffect, useMemo, useRef, useState } from "react";
|
|
19
|
+
|
|
20
|
+
// src/controller-actions.ts
|
|
21
|
+
import { useCallback } from "react";
|
|
22
|
+
function useControllerMessageActions(input) {
|
|
23
|
+
const handleCopy = useCallback((content, messageId) => {
|
|
24
|
+
navigator.clipboard?.writeText(content);
|
|
25
|
+
input.setCopiedMessageId(messageId);
|
|
26
|
+
setTimeout(() => input.setCopiedMessageId(null), 200);
|
|
27
|
+
}, [input]);
|
|
28
|
+
const handleLike = useCallback(async (msg) => {
|
|
29
|
+
const nextLiked = !input.liked[msg.id];
|
|
30
|
+
input.setLiked((prev) => ({ ...prev, [msg.id]: nextLiked }));
|
|
31
|
+
if (nextLiked) input.setDisliked((prev) => ({ ...prev, [msg.id]: false }));
|
|
32
|
+
}, [input]);
|
|
33
|
+
const handleDislike = useCallback(async (msg) => {
|
|
34
|
+
const nextDisliked = !input.disliked[msg.id];
|
|
35
|
+
input.setDisliked((prev) => ({ ...prev, [msg.id]: nextDisliked }));
|
|
36
|
+
if (nextDisliked) input.setLiked((prev) => ({ ...prev, [msg.id]: false }));
|
|
37
|
+
}, [input]);
|
|
38
|
+
const handleRetry = useCallback(async (msg) => {
|
|
39
|
+
const index = input.messagesRef.current.findIndex((message) => message.id === msg.id);
|
|
40
|
+
if (index <= 0) return;
|
|
41
|
+
const userMessage = input.messagesRef.current[index - 1];
|
|
42
|
+
if (userMessage.role !== "user") return;
|
|
43
|
+
const trimmed = input.messagesRef.current.slice(0, index);
|
|
44
|
+
input.setMessages(trimmed);
|
|
45
|
+
input.messagesRef.current = trimmed;
|
|
46
|
+
if (input.activeThreadIdRef.current) input.persistThread(input.activeThreadIdRef.current, trimmed);
|
|
47
|
+
await input.handleSubmit({ text: userMessage.content, files: userMessage.attachments || [] });
|
|
48
|
+
}, [input]);
|
|
49
|
+
const upsertLiveVoiceMessage = useCallback((payload) => {
|
|
50
|
+
const content = payload.content?.trim() ?? "";
|
|
51
|
+
if (!content && !payload.citations?.length && !payload.reasoningSteps?.length) return;
|
|
52
|
+
input.setMessages((prev) => {
|
|
53
|
+
const currentId = input.liveVoiceIdsRef.current[payload.role];
|
|
54
|
+
const index = currentId ? prev.findIndex((message) => message.id === currentId) : -1;
|
|
55
|
+
const nextMessage = {
|
|
56
|
+
id: currentId || input.createId(),
|
|
57
|
+
role: payload.role,
|
|
58
|
+
content,
|
|
59
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
60
|
+
isFinal: Boolean(payload.isFinal),
|
|
61
|
+
isVoice: payload.role === "user",
|
|
62
|
+
parts: content ? [{ id: input.createId(), type: "text", kind: "text", text: content }] : [],
|
|
63
|
+
citations: payload.citations,
|
|
64
|
+
reasoningSteps: payload.reasoningSteps
|
|
65
|
+
};
|
|
66
|
+
const next = index === -1 ? [...prev, nextMessage] : prev.map((message, itemIndex) => itemIndex === index ? { ...message, ...nextMessage } : message);
|
|
67
|
+
input.liveVoiceIdsRef.current[payload.role] = payload.isFinal ? void 0 : nextMessage.id;
|
|
68
|
+
input.messagesRef.current = next;
|
|
69
|
+
if (input.activeThreadIdRef.current) input.persistThread(input.activeThreadIdRef.current, next);
|
|
70
|
+
return next;
|
|
71
|
+
});
|
|
72
|
+
}, [input]);
|
|
73
|
+
const startNewThread = useCallback(() => {
|
|
74
|
+
input.detachActiveStream();
|
|
75
|
+
input.requestSeqRef.current += 1;
|
|
76
|
+
input.activeThreadIdRef.current = null;
|
|
77
|
+
input.loadedThreadIdRef.current = null;
|
|
78
|
+
input.nextBeforeTurnIndexRef.current = null;
|
|
79
|
+
input.hasOlderTurnsRef.current = false;
|
|
80
|
+
input.storage.setActiveThreadId?.(null);
|
|
81
|
+
input.onActiveThreadIdChange?.(null);
|
|
82
|
+
input.setMessages([]);
|
|
83
|
+
input.messagesRef.current = [];
|
|
84
|
+
input.setHasOlderTurns(false);
|
|
85
|
+
input.setIsLoadingOlder(false);
|
|
86
|
+
input.setContextStatus(null);
|
|
87
|
+
input.setLoadingHistory(false);
|
|
88
|
+
}, [input]);
|
|
89
|
+
return { handleCopy, handleDislike, handleLike, handleRetry, startNewThread, upsertLiveVoiceMessage };
|
|
90
|
+
}
|
|
19
91
|
|
|
20
92
|
// src/context-window.ts
|
|
21
93
|
var SOURCE_PRIORITY = {
|
|
@@ -138,7 +210,8 @@ function compressionFromContextWindow(contextWindow) {
|
|
|
138
210
|
|
|
139
211
|
// src/model.ts
|
|
140
212
|
var DEFAULT_THREAD_PAGE_SIZE = 5;
|
|
141
|
-
var isRunningThreadStatus = (status) => (/* @__PURE__ */ new Set(["queued", "running", "cancelling"])).has(String(status || "").toLowerCase());
|
|
213
|
+
var isRunningThreadStatus = (status) => (/* @__PURE__ */ new Set(["queued", "running", "cancelling", "paused"])).has(String(status || "").toLowerCase());
|
|
214
|
+
var isAutoAttachThreadStatus = (status) => (/* @__PURE__ */ new Set(["queued", "running", "cancelling"])).has(String(status || "").toLowerCase());
|
|
142
215
|
var createChatId = () => {
|
|
143
216
|
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
144
217
|
return `chat-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -304,8 +377,17 @@ var partsFromResponseBlocks = (blocks, fallbackText) => {
|
|
|
304
377
|
});
|
|
305
378
|
return;
|
|
306
379
|
}
|
|
307
|
-
if (block.kind === "
|
|
308
|
-
|
|
380
|
+
if (block.kind === "hitl_request") {
|
|
381
|
+
const hitl = asRecord(block.hitl) || block;
|
|
382
|
+
parts.push({
|
|
383
|
+
id,
|
|
384
|
+
type: "hitl",
|
|
385
|
+
kind: "hitl",
|
|
386
|
+
hitl,
|
|
387
|
+
interruptId: optionalString(block.interruptId) || optionalString(hitl.interrupt_id) || null,
|
|
388
|
+
hitlKind: optionalString(block.hitlKind) || optionalString(hitl.kind) || null,
|
|
389
|
+
raw: block
|
|
390
|
+
});
|
|
309
391
|
return;
|
|
310
392
|
}
|
|
311
393
|
if (block.kind === "error") {
|
|
@@ -431,6 +513,23 @@ var activeRunIdFromThread = (thread) => {
|
|
|
431
513
|
const lastRunId = hasCamelLastRunId ? thread?.lastRunId : thread?.last_run_id || thread?.lastRunId;
|
|
432
514
|
return lastRunId && isRunningThreadStatus(lastRunStatus) ? String(lastRunId) : null;
|
|
433
515
|
};
|
|
516
|
+
var autoAttachRunIdFromThread = (thread) => {
|
|
517
|
+
const hasCamelActiveRun = Boolean(
|
|
518
|
+
thread && Object.prototype.hasOwnProperty.call(thread, "activeRun")
|
|
519
|
+
);
|
|
520
|
+
const hasCamelLastRunStatus = Boolean(
|
|
521
|
+
thread && Object.prototype.hasOwnProperty.call(thread, "lastRunStatus")
|
|
522
|
+
);
|
|
523
|
+
const hasCamelLastRunId = Boolean(
|
|
524
|
+
thread && Object.prototype.hasOwnProperty.call(thread, "lastRunId")
|
|
525
|
+
);
|
|
526
|
+
const activeRun = hasCamelActiveRun ? thread?.activeRun || null : thread?.active_run || null;
|
|
527
|
+
const lastRunStatus = hasCamelLastRunStatus ? thread?.lastRunStatus || null : thread?.last_run_status || thread?.lastRunStatus || null;
|
|
528
|
+
const activeRunId = activeRun?.run_id ? String(activeRun.run_id) : "";
|
|
529
|
+
if (activeRunId && isAutoAttachThreadStatus(activeRun?.status || lastRunStatus)) return activeRunId;
|
|
530
|
+
const lastRunId = hasCamelLastRunId ? thread?.lastRunId : thread?.last_run_id || thread?.lastRunId;
|
|
531
|
+
return lastRunId && isAutoAttachThreadStatus(lastRunStatus) ? String(lastRunId) : null;
|
|
532
|
+
};
|
|
434
533
|
var hasUnfinishedAssistantMessage = (messages) => Boolean(messages?.some((message) => message.role === "assistant" && message.isFinal === false));
|
|
435
534
|
var hasStaleUnfinishedAssistantCache = (thread) => hasUnfinishedAssistantMessage(thread?.messages) && !activeRunIdFromThread(thread);
|
|
436
535
|
var activeRunIdFromThreadDetail = (thread) => {
|
|
@@ -440,7 +539,7 @@ var activeRunIdFromThreadDetail = (thread) => {
|
|
|
440
539
|
return runningTurn?.run_id ? String(runningTurn.run_id) : null;
|
|
441
540
|
};
|
|
442
541
|
|
|
443
|
-
// src/controller.ts
|
|
542
|
+
// src/controller-helpers.ts
|
|
444
543
|
var threadSummaryToStored = (thread) => ({
|
|
445
544
|
...thread,
|
|
446
545
|
id: String(thread.id),
|
|
@@ -449,6 +548,121 @@ var threadSummaryToStored = (thread) => ({
|
|
|
449
548
|
messages: [],
|
|
450
549
|
isHydrated: false
|
|
451
550
|
});
|
|
551
|
+
var isThreadNotFoundError = (error) => {
|
|
552
|
+
if (!error || typeof error !== "object" || !("status" in error)) return false;
|
|
553
|
+
return Number(error.status) === 404;
|
|
554
|
+
};
|
|
555
|
+
var isAbortError = (error) => {
|
|
556
|
+
if (!error || typeof error !== "object") return false;
|
|
557
|
+
const maybe = error;
|
|
558
|
+
return maybe.name === "AbortError" || String(maybe.message || "").toLowerCase().includes("aborted");
|
|
559
|
+
};
|
|
560
|
+
var abortDetachedStream = (controller) => {
|
|
561
|
+
if (!controller || controller.signal.aborted) return;
|
|
562
|
+
try {
|
|
563
|
+
const reason = typeof DOMException !== "undefined" ? new DOMException("Chat stream detached.", "AbortError") : new Error("Chat stream detached.");
|
|
564
|
+
controller.abort(reason);
|
|
565
|
+
} catch {
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
var hasRunState = (thread) => Boolean(
|
|
569
|
+
thread.active_run || thread.activeRun || thread.last_run_id || thread.lastRunId || thread.last_run_status || thread.lastRunStatus || thread.isRunning
|
|
570
|
+
);
|
|
571
|
+
var mergeStoredThreadsForRefresh = (currentThreads, serverThreads) => {
|
|
572
|
+
const currentById = new Map(currentThreads.map((thread) => [thread.id, thread]));
|
|
573
|
+
const serverIds = new Set(serverThreads.map((thread) => thread.id));
|
|
574
|
+
const mergedServerThreads = serverThreads.map((serverThread) => {
|
|
575
|
+
const current = currentById.get(serverThread.id);
|
|
576
|
+
if (!current) return serverThread;
|
|
577
|
+
const incomingHasRunState = hasRunState(serverThread);
|
|
578
|
+
return {
|
|
579
|
+
...current,
|
|
580
|
+
...serverThread,
|
|
581
|
+
messages: current.messages || [],
|
|
582
|
+
isHydrated: current.isHydrated,
|
|
583
|
+
hasOlderTurns: current.hasOlderTurns,
|
|
584
|
+
nextBeforeTurnIndex: current.nextBeforeTurnIndex,
|
|
585
|
+
active_run: incomingHasRunState ? serverThread.active_run : current.active_run,
|
|
586
|
+
activeRun: incomingHasRunState ? serverThread.activeRun : current.activeRun,
|
|
587
|
+
last_run_id: incomingHasRunState ? serverThread.last_run_id : current.last_run_id,
|
|
588
|
+
lastRunId: incomingHasRunState ? serverThread.lastRunId : current.lastRunId,
|
|
589
|
+
last_run_status: incomingHasRunState ? serverThread.last_run_status : current.last_run_status,
|
|
590
|
+
lastRunStatus: incomingHasRunState ? serverThread.lastRunStatus : current.lastRunStatus,
|
|
591
|
+
lastEventSeq: incomingHasRunState ? serverThread.lastEventSeq : current.lastEventSeq,
|
|
592
|
+
isRunning: incomingHasRunState ? serverThread.isRunning : current.isRunning
|
|
593
|
+
};
|
|
594
|
+
});
|
|
595
|
+
const localOnlyThreads = currentThreads.filter((thread) => {
|
|
596
|
+
if (serverIds.has(thread.id)) return false;
|
|
597
|
+
return Boolean(thread.isHydrated || thread.messages?.length || thread.isRunning || thread.activeRun || thread.active_run);
|
|
598
|
+
});
|
|
599
|
+
return [...mergedServerThreads, ...localOnlyThreads];
|
|
600
|
+
};
|
|
601
|
+
var stableStringify = (value) => JSON.stringify(value ?? null);
|
|
602
|
+
var sameStoredThread = (left, right) => {
|
|
603
|
+
if (left === right) return true;
|
|
604
|
+
if (!left || !right) return false;
|
|
605
|
+
return stableStringify(left) === stableStringify(right);
|
|
606
|
+
};
|
|
607
|
+
var applyThreadSummaryEvent = (currentThreads, event) => {
|
|
608
|
+
if (event.event === "snapshot_required") return currentThreads;
|
|
609
|
+
if (event.event === "thread.deleted") {
|
|
610
|
+
const next2 = currentThreads.filter((thread) => thread.id !== event.thread_id);
|
|
611
|
+
return next2.length === currentThreads.length ? currentThreads : next2;
|
|
612
|
+
}
|
|
613
|
+
const incoming = threadSummaryToStored(event.thread);
|
|
614
|
+
const index = currentThreads.findIndex((thread) => thread.id === incoming.id);
|
|
615
|
+
const sortByActivity = (threads) => [...threads].sort((a, b) => {
|
|
616
|
+
const left = Date.parse(String(a.updated_at || a.last_activity_at || a.created_at || ""));
|
|
617
|
+
const right = Date.parse(String(b.updated_at || b.last_activity_at || b.created_at || ""));
|
|
618
|
+
return (Number.isFinite(right) ? right : 0) - (Number.isFinite(left) ? left : 0);
|
|
619
|
+
});
|
|
620
|
+
if (index === -1) return sortByActivity([...currentThreads, incoming]);
|
|
621
|
+
const [merged] = mergeStoredThreadsForRefresh([currentThreads[index]], [incoming]);
|
|
622
|
+
if (sameStoredThread(currentThreads[index], merged)) return currentThreads;
|
|
623
|
+
const next = [...currentThreads];
|
|
624
|
+
next[index] = merged;
|
|
625
|
+
return sortByActivity(next);
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
// src/message-lifecycle.ts
|
|
629
|
+
function findStableAssistantMessageIndex(messages, input) {
|
|
630
|
+
return messages.findIndex(
|
|
631
|
+
(message) => message.role === "assistant" && (input.messageId && message.id === input.messageId || Boolean(input.runId && message.runId === input.runId))
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
function appendStableStreamingTurn(messages, userMessage, assistantMessage) {
|
|
635
|
+
const next = [...messages];
|
|
636
|
+
if (!next.some((message) => message.id === userMessage.id)) {
|
|
637
|
+
next.push(userMessage);
|
|
638
|
+
}
|
|
639
|
+
if (!next.some(
|
|
640
|
+
(message) => message.id === assistantMessage.id || Boolean(
|
|
641
|
+
assistantMessage.runId && message.role === "assistant" && message.runId === assistantMessage.runId
|
|
642
|
+
)
|
|
643
|
+
)) {
|
|
644
|
+
next.push(assistantMessage);
|
|
645
|
+
}
|
|
646
|
+
return next;
|
|
647
|
+
}
|
|
648
|
+
function upsertStableAssistantMessage(messages, input) {
|
|
649
|
+
const next = [...messages];
|
|
650
|
+
for (const baseMessage of input.baseMessages || []) {
|
|
651
|
+
const matchesTarget = baseMessage.role === "assistant" && (input.messageId && baseMessage.id === input.messageId || Boolean(input.runId && baseMessage.runId === input.runId));
|
|
652
|
+
if (!matchesTarget && !next.some((message) => message.id === baseMessage.id)) {
|
|
653
|
+
next.push(baseMessage);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
const index = findStableAssistantMessageIndex(next, input);
|
|
657
|
+
if (index === -1) {
|
|
658
|
+
next.push(input.create());
|
|
659
|
+
return next;
|
|
660
|
+
}
|
|
661
|
+
next[index] = input.update(next[index]);
|
|
662
|
+
return next;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// src/controller.ts
|
|
452
666
|
function useAgents24ChatController({
|
|
453
667
|
transport,
|
|
454
668
|
storage,
|
|
@@ -479,6 +693,7 @@ function useAgents24ChatController({
|
|
|
479
693
|
const [lastThinkingDurationMs, setLastThinkingDurationMs] = useState(null);
|
|
480
694
|
const [threads, setThreads] = useState(() => storage.listThreads());
|
|
481
695
|
const [isRefreshingThreads, setIsRefreshingThreads] = useState(false);
|
|
696
|
+
const [isSelectingThread, setIsSelectingThread] = useState(false);
|
|
482
697
|
const [activeRunId, setActiveRunId] = useState(null);
|
|
483
698
|
const textareaRef = useRef(null);
|
|
484
699
|
const activeThreadIdRef = useRef(activeThreadId);
|
|
@@ -496,14 +711,16 @@ function useAgents24ChatController({
|
|
|
496
711
|
const streamingMessageIdRef = useRef(null);
|
|
497
712
|
const reasoningRef = useRef([]);
|
|
498
713
|
const liveVoiceIdsRef = useRef({});
|
|
499
|
-
const
|
|
714
|
+
const refreshSeqRef = useRef(0);
|
|
715
|
+
const threadEventsCursorRef = useRef(null);
|
|
716
|
+
const setActiveRunIdValue = useCallback2((runId) => {
|
|
500
717
|
activeRunIdRef.current = runId;
|
|
501
718
|
setActiveRunId(runId);
|
|
502
719
|
}, []);
|
|
503
|
-
const syncThreadsFromStorage =
|
|
720
|
+
const syncThreadsFromStorage = useCallback2(() => {
|
|
504
721
|
setThreads(storage.listThreads());
|
|
505
722
|
}, [storage]);
|
|
506
|
-
const upsertStoredThread =
|
|
723
|
+
const upsertStoredThread = useCallback2(
|
|
507
724
|
(thread) => {
|
|
508
725
|
storage.upsertThread(thread);
|
|
509
726
|
syncThreadsFromStorage();
|
|
@@ -513,7 +730,7 @@ function useAgents24ChatController({
|
|
|
513
730
|
useEffect(() => {
|
|
514
731
|
messagesRef.current = messages;
|
|
515
732
|
}, [messages]);
|
|
516
|
-
const persistThread =
|
|
733
|
+
const persistThread = useCallback2(
|
|
517
734
|
(threadId, nextMessages, paging, options) => {
|
|
518
735
|
const existing = storage.getThread(threadId);
|
|
519
736
|
const firstUser = nextMessages.find((message) => message.role === "user");
|
|
@@ -530,7 +747,7 @@ function useAgents24ChatController({
|
|
|
530
747
|
},
|
|
531
748
|
[storage, upsertStoredThread]
|
|
532
749
|
);
|
|
533
|
-
const markThreadRunStatus =
|
|
750
|
+
const markThreadRunStatus = useCallback2(
|
|
534
751
|
(threadId, runId, status, lastEventSeq) => {
|
|
535
752
|
const existing = storage.getThread(threadId);
|
|
536
753
|
if (!existing || !runId) return;
|
|
@@ -560,18 +777,20 @@ function useAgents24ChatController({
|
|
|
560
777
|
},
|
|
561
778
|
[storage, upsertStoredThread]
|
|
562
779
|
);
|
|
563
|
-
const refresh =
|
|
780
|
+
const refresh = useCallback2(async () => {
|
|
781
|
+
const seq = ++refreshSeqRef.current;
|
|
564
782
|
setIsRefreshingThreads(true);
|
|
565
783
|
try {
|
|
566
784
|
const data = await transport.listThreads();
|
|
785
|
+
if (seq !== refreshSeqRef.current) return;
|
|
567
786
|
const nextThreads = (data.items || []).map((item) => threadSummaryToStored(item));
|
|
568
|
-
storage.setThreads(nextThreads);
|
|
787
|
+
storage.setThreads(mergeStoredThreadsForRefresh(storage.listThreads(), nextThreads));
|
|
569
788
|
setThreads(storage.listThreads());
|
|
570
789
|
} finally {
|
|
571
|
-
setIsRefreshingThreads(false);
|
|
790
|
+
if (seq === refreshSeqRef.current) setIsRefreshingThreads(false);
|
|
572
791
|
}
|
|
573
792
|
}, [storage, transport]);
|
|
574
|
-
const applyThreadId =
|
|
793
|
+
const applyThreadId = useCallback2(
|
|
575
794
|
(threadId, baseMessages) => {
|
|
576
795
|
if (!threadId || activeThreadIdRef.current === threadId) return;
|
|
577
796
|
activeThreadIdRef.current = threadId;
|
|
@@ -585,18 +804,18 @@ function useAgents24ChatController({
|
|
|
585
804
|
streamingContentRef.current = value;
|
|
586
805
|
setStreamingContent(value);
|
|
587
806
|
};
|
|
588
|
-
const setLoadingHistory =
|
|
807
|
+
const setLoadingHistory = useCallback2((value) => {
|
|
589
808
|
isLoadingHistoryRef.current = value;
|
|
590
809
|
setIsLoadingHistory(value);
|
|
591
810
|
}, []);
|
|
592
|
-
const setReasoningSteps =
|
|
811
|
+
const setReasoningSteps = useCallback2((value) => {
|
|
593
812
|
reasoningRef.current = value || [];
|
|
594
813
|
setCurrentReasoning(value || []);
|
|
595
814
|
}, []);
|
|
596
|
-
const detachActiveStream =
|
|
815
|
+
const detachActiveStream = useCallback2(() => {
|
|
597
816
|
const controller = abortControllerRef.current;
|
|
598
817
|
abortControllerRef.current = null;
|
|
599
|
-
controller
|
|
818
|
+
abortDetachedStream(controller);
|
|
600
819
|
setActiveRunIdValue(null);
|
|
601
820
|
reattachedRunIdRef.current = null;
|
|
602
821
|
streamingMessageIdRef.current = null;
|
|
@@ -607,21 +826,36 @@ function useAgents24ChatController({
|
|
|
607
826
|
setStreamingContent("");
|
|
608
827
|
setCurrentReasoning([]);
|
|
609
828
|
}, [setActiveRunIdValue]);
|
|
610
|
-
const
|
|
829
|
+
const clearMissingThread = useCallback2(
|
|
830
|
+
(threadId) => {
|
|
831
|
+
storage.deleteThread?.(threadId);
|
|
832
|
+
syncThreadsFromStorage();
|
|
833
|
+
if (activeThreadIdRef.current !== threadId) return;
|
|
834
|
+
requestSeqRef.current += 1;
|
|
835
|
+
detachActiveStream();
|
|
836
|
+
activeThreadIdRef.current = null;
|
|
837
|
+
loadedThreadIdRef.current = null;
|
|
838
|
+
nextBeforeTurnIndexRef.current = null;
|
|
839
|
+
hasOlderTurnsRef.current = false;
|
|
840
|
+
storage.setActiveThreadId?.(null);
|
|
841
|
+
onActiveThreadIdChange?.(null);
|
|
842
|
+
setMessages([]);
|
|
843
|
+
messagesRef.current = [];
|
|
844
|
+
setHasOlderTurns(false);
|
|
845
|
+
setIsLoadingOlder(false);
|
|
846
|
+
setContextStatus(null);
|
|
847
|
+
setLoadingHistory(false);
|
|
848
|
+
},
|
|
849
|
+
[detachActiveStream, onActiveThreadIdChange, setLoadingHistory, storage, syncThreadsFromStorage]
|
|
850
|
+
);
|
|
851
|
+
const setLiveAssistantMessage = useCallback2(
|
|
611
852
|
(input) => {
|
|
612
853
|
setMessages((prev) => {
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
(input.baseMessages || []).forEach((message) => {
|
|
619
|
-
const isSameAssistant = message.id === input.messageId || Boolean(input.runId && message.role === "assistant" && message.runId === input.runId);
|
|
620
|
-
if (!isSameAssistant && !next2.some((item) => item.id === message.id)) {
|
|
621
|
-
next2.push(message);
|
|
622
|
-
}
|
|
623
|
-
});
|
|
624
|
-
next2.push({
|
|
854
|
+
const next = upsertStableAssistantMessage(prev, {
|
|
855
|
+
baseMessages: input.baseMessages,
|
|
856
|
+
messageId: input.messageId,
|
|
857
|
+
runId: input.runId,
|
|
858
|
+
create: () => ({
|
|
625
859
|
id: input.messageId,
|
|
626
860
|
role: "assistant",
|
|
627
861
|
runId: input.runId ?? null,
|
|
@@ -630,26 +864,23 @@ function useAgents24ChatController({
|
|
|
630
864
|
reasoningSteps: input.reasoning,
|
|
631
865
|
isFinal: false,
|
|
632
866
|
parts: input.parts || []
|
|
633
|
-
})
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
isFinal: false,
|
|
644
|
-
parts: input.parts ?? next[index].parts
|
|
645
|
-
};
|
|
867
|
+
}),
|
|
868
|
+
update: (message) => ({
|
|
869
|
+
...message,
|
|
870
|
+
runId: input.runId ?? message.runId ?? null,
|
|
871
|
+
content: input.content,
|
|
872
|
+
reasoningSteps: input.reasoning,
|
|
873
|
+
isFinal: false,
|
|
874
|
+
parts: input.parts ?? message.parts
|
|
875
|
+
})
|
|
876
|
+
});
|
|
646
877
|
messagesRef.current = next;
|
|
647
878
|
return next;
|
|
648
879
|
});
|
|
649
880
|
},
|
|
650
881
|
[]
|
|
651
882
|
);
|
|
652
|
-
const finalizeAssistantMessage =
|
|
883
|
+
const finalizeAssistantMessage = useCallback2(
|
|
653
884
|
(input) => {
|
|
654
885
|
const content = input.error || input.assistantText.trim();
|
|
655
886
|
if (!content) return input.baseMessages;
|
|
@@ -669,7 +900,17 @@ function useAgents24ChatController({
|
|
|
669
900
|
reasoningSteps: mergeReasoningSteps(input.reasoning, { finalize: true }),
|
|
670
901
|
thinkingDurationMs: input.thinkingDurationMs
|
|
671
902
|
};
|
|
672
|
-
const completed =
|
|
903
|
+
const completed = upsertStableAssistantMessage(input.baseMessages, {
|
|
904
|
+
messageId: input.messageId,
|
|
905
|
+
runId: input.runId,
|
|
906
|
+
create: () => assistant,
|
|
907
|
+
update: (message) => ({
|
|
908
|
+
...message,
|
|
909
|
+
...assistant,
|
|
910
|
+
id: message.id,
|
|
911
|
+
createdAt: message.createdAt
|
|
912
|
+
})
|
|
913
|
+
});
|
|
673
914
|
setMessages(completed);
|
|
674
915
|
messagesRef.current = completed;
|
|
675
916
|
if (input.threadId) {
|
|
@@ -693,7 +934,7 @@ function useAgents24ChatController({
|
|
|
693
934
|
},
|
|
694
935
|
[createId, persistThread, storage, upsertStoredThread]
|
|
695
936
|
);
|
|
696
|
-
const loadThread =
|
|
937
|
+
const loadThread = useCallback2(
|
|
697
938
|
async (threadId) => {
|
|
698
939
|
const seq = ++requestSeqRef.current;
|
|
699
940
|
setLoadingHistory(true);
|
|
@@ -724,13 +965,20 @@ function useAgents24ChatController({
|
|
|
724
965
|
nextBeforeTurnIndex: paging.nextBeforeTurnIndex,
|
|
725
966
|
updated_at: threadActivityDate(detail)
|
|
726
967
|
});
|
|
968
|
+
} catch (error) {
|
|
969
|
+
if (isAbortError(error)) return;
|
|
970
|
+
if (isThreadNotFoundError(error)) {
|
|
971
|
+
clearMissingThread(threadId);
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
throw error;
|
|
727
975
|
} finally {
|
|
728
|
-
if (activeThreadIdRef.current === threadId) setLoadingHistory(false);
|
|
976
|
+
if (activeThreadIdRef.current === threadId && seq === requestSeqRef.current) setLoadingHistory(false);
|
|
729
977
|
}
|
|
730
978
|
},
|
|
731
|
-
[onThreadDetailLoaded, pageSize, setLoadingHistory, transport, upsertStoredThread]
|
|
979
|
+
[clearMissingThread, onThreadDetailLoaded, pageSize, setLoadingHistory, transport, upsertStoredThread]
|
|
732
980
|
);
|
|
733
|
-
const loadOlderTurns =
|
|
981
|
+
const loadOlderTurns = useCallback2(async () => {
|
|
734
982
|
const threadId = activeThreadIdRef.current;
|
|
735
983
|
const beforeTurnIndex = nextBeforeTurnIndexRef.current;
|
|
736
984
|
if (!threadId || beforeTurnIndex === null || isLoadingOlderRef.current) return;
|
|
@@ -753,12 +1001,19 @@ function useAgents24ChatController({
|
|
|
753
1001
|
hasOlderTurnsRef.current = paging.hasOlderTurns;
|
|
754
1002
|
nextBeforeTurnIndexRef.current = paging.nextBeforeTurnIndex;
|
|
755
1003
|
persistThread(threadId, next, paging);
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
if (isAbortError(error)) return;
|
|
1006
|
+
if (isThreadNotFoundError(error)) {
|
|
1007
|
+
clearMissingThread(threadId);
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
throw error;
|
|
756
1011
|
} finally {
|
|
757
1012
|
if (activeThreadIdRef.current === threadId) setIsLoadingOlder(false);
|
|
758
1013
|
isLoadingOlderRef.current = false;
|
|
759
1014
|
}
|
|
760
|
-
}, [pageSize, persistThread, transport]);
|
|
761
|
-
const handleStreamEvent =
|
|
1015
|
+
}, [clearMissingThread, pageSize, persistThread, transport]);
|
|
1016
|
+
const handleStreamEvent = useCallback2(
|
|
762
1017
|
(input) => {
|
|
763
1018
|
const { mode, event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages } = input;
|
|
764
1019
|
const payload = event.payload || {};
|
|
@@ -819,7 +1074,7 @@ function useAgents24ChatController({
|
|
|
819
1074
|
},
|
|
820
1075
|
[applyThreadId, finalizeAssistantMessage, markThreadRunStatus, onRuntimeEvent, onStreamErrorMessage, setActiveRunIdValue, setLiveAssistantMessage, setReasoningSteps]
|
|
821
1076
|
);
|
|
822
|
-
const runStream =
|
|
1077
|
+
const runStream = useCallback2(
|
|
823
1078
|
async (input) => {
|
|
824
1079
|
const startedAt = Date.now();
|
|
825
1080
|
const controller = new AbortController();
|
|
@@ -878,19 +1133,28 @@ function useAgents24ChatController({
|
|
|
878
1133
|
);
|
|
879
1134
|
}
|
|
880
1135
|
try {
|
|
1136
|
+
let streamResult = null;
|
|
881
1137
|
if (input.mode === "attach") {
|
|
882
1138
|
setActiveRunIdValue(input.runId);
|
|
883
1139
|
reattachedRunIdRef.current = input.runId;
|
|
884
|
-
await transport.attachRun(
|
|
1140
|
+
streamResult = await transport.attachRun(
|
|
885
1141
|
{ runId: input.runId, signal: controller.signal },
|
|
886
1142
|
(event) => handleStreamEvent({ mode: "attach", event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages })
|
|
887
1143
|
);
|
|
888
1144
|
} else {
|
|
889
|
-
await transport.streamMessage(
|
|
1145
|
+
streamResult = await transport.streamMessage(
|
|
890
1146
|
{ ...input.message, files: input.message.files || [], threadId: activeThreadIdRef.current, signal: controller.signal },
|
|
891
1147
|
(event) => handleStreamEvent({ mode: "submit", event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages })
|
|
892
1148
|
);
|
|
893
1149
|
}
|
|
1150
|
+
if (streamResult?.runId) setActiveRunIdValue(streamResult.runId);
|
|
1151
|
+
if (streamResult?.threadId) {
|
|
1152
|
+
streamThreadIdRef.current = streamResult.threadId;
|
|
1153
|
+
applyThreadId(streamResult.threadId, baseMessages);
|
|
1154
|
+
if (streamResult.runId) {
|
|
1155
|
+
markThreadRunStatus(streamResult.threadId, streamResult.runId, "running");
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
894
1158
|
if (streamingContentRef.current.trim() && streamingMessageIdRef.current) {
|
|
895
1159
|
finalizeAssistantMessage({
|
|
896
1160
|
threadId: streamThreadIdRef.current,
|
|
@@ -904,7 +1168,7 @@ function useAgents24ChatController({
|
|
|
904
1168
|
}
|
|
905
1169
|
await refresh().catch(() => void 0);
|
|
906
1170
|
} catch (error) {
|
|
907
|
-
if (error
|
|
1171
|
+
if (!isAbortError(error)) {
|
|
908
1172
|
finalizeAssistantMessage({
|
|
909
1173
|
threadId: streamThreadIdRef.current,
|
|
910
1174
|
baseMessages: messagesRef.current,
|
|
@@ -933,18 +1197,22 @@ function useAgents24ChatController({
|
|
|
933
1197
|
},
|
|
934
1198
|
[createId, finalizeAssistantMessage, handleStreamEvent, onStreamErrorMessage, persistThread, refresh, setActiveRunIdValue, setReasoningSteps, transport]
|
|
935
1199
|
);
|
|
936
|
-
const handleSubmit =
|
|
1200
|
+
const handleSubmit = useCallback2(
|
|
937
1201
|
async (message) => {
|
|
938
1202
|
if (!message.text.trim() && !(message.files || []).length) return;
|
|
939
1203
|
await runStream({ mode: "submit", message });
|
|
940
1204
|
},
|
|
941
1205
|
[runStream]
|
|
942
1206
|
);
|
|
943
|
-
const
|
|
1207
|
+
const attachRun = useCallback2(async (runId, threadId) => {
|
|
1208
|
+
const resolvedThreadId = threadId ?? activeThreadIdRef.current;
|
|
1209
|
+
if (runId && resolvedThreadId) await runStream({ mode: "attach", runId, threadId: resolvedThreadId });
|
|
1210
|
+
}, [runStream]);
|
|
1211
|
+
const handleStop = useCallback2(() => {
|
|
944
1212
|
const runId = activeRunIdRef.current;
|
|
945
1213
|
const partial = streamingContentRef.current;
|
|
946
1214
|
const liveMessageId = streamingMessageIdRef.current;
|
|
947
|
-
abortControllerRef.current
|
|
1215
|
+
abortDetachedStream(abortControllerRef.current);
|
|
948
1216
|
abortControllerRef.current = null;
|
|
949
1217
|
setActiveRunIdValue(null);
|
|
950
1218
|
streamingMessageIdRef.current = null;
|
|
@@ -965,11 +1233,40 @@ function useAgents24ChatController({
|
|
|
965
1233
|
}
|
|
966
1234
|
}, [finalizeAssistantMessage, lastThinkingDurationMs, setActiveRunIdValue, setReasoningSteps, transport]);
|
|
967
1235
|
useEffect(() => {
|
|
968
|
-
refresh().catch(() =>
|
|
969
|
-
storage.setThreads([]);
|
|
970
|
-
setThreads([]);
|
|
971
|
-
});
|
|
1236
|
+
refresh().catch(() => setThreads(storage.listThreads()));
|
|
972
1237
|
}, [refresh, storage]);
|
|
1238
|
+
useEffect(() => {
|
|
1239
|
+
if (!transport.subscribeThreadEvents) return;
|
|
1240
|
+
let cancelled = false;
|
|
1241
|
+
let retryTimeout = null;
|
|
1242
|
+
let controller = null;
|
|
1243
|
+
const connect = () => {
|
|
1244
|
+
if (cancelled) return;
|
|
1245
|
+
controller = new AbortController();
|
|
1246
|
+
transport.subscribeThreadEvents?.(
|
|
1247
|
+
{ cursor: threadEventsCursorRef.current, signal: controller.signal },
|
|
1248
|
+
async (event) => {
|
|
1249
|
+
if (typeof event.cursor === "number") threadEventsCursorRef.current = event.cursor;
|
|
1250
|
+
if (event.event === "snapshot_required") {
|
|
1251
|
+
await refresh().catch(() => void 0);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
const next = applyThreadSummaryEvent(storage.listThreads(), event);
|
|
1255
|
+
storage.setThreads(next);
|
|
1256
|
+
setThreads(storage.listThreads());
|
|
1257
|
+
}
|
|
1258
|
+
).catch((error) => {
|
|
1259
|
+
if (cancelled || isAbortError(error)) return;
|
|
1260
|
+
retryTimeout = setTimeout(connect, 1500);
|
|
1261
|
+
});
|
|
1262
|
+
};
|
|
1263
|
+
connect();
|
|
1264
|
+
return () => {
|
|
1265
|
+
cancelled = true;
|
|
1266
|
+
if (retryTimeout) clearTimeout(retryTimeout);
|
|
1267
|
+
controller?.abort();
|
|
1268
|
+
};
|
|
1269
|
+
}, [refresh, storage, transport]);
|
|
973
1270
|
useEffect(() => {
|
|
974
1271
|
syncThreadsFromStorage();
|
|
975
1272
|
}, [storageKey, syncThreadsFromStorage]);
|
|
@@ -983,6 +1280,9 @@ function useAgents24ChatController({
|
|
|
983
1280
|
detachActiveStream();
|
|
984
1281
|
}
|
|
985
1282
|
if (!activeThreadId) {
|
|
1283
|
+
if (previous === null && loadedThreadIdRef.current === null && messagesRef.current.length === 0 && !hasOlderTurnsRef.current && !isLoadingHistoryRef.current && !isLoadingOlderRef.current && nextBeforeTurnIndexRef.current === null) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
986
1286
|
requestSeqRef.current += 1;
|
|
987
1287
|
loadedThreadIdRef.current = null;
|
|
988
1288
|
setMessages([]);
|
|
@@ -1017,7 +1317,7 @@ function useAgents24ChatController({
|
|
|
1017
1317
|
useEffect(() => {
|
|
1018
1318
|
const threadId = activeThreadId;
|
|
1019
1319
|
if (!threadId || isLoadingHistory || loadedThreadIdRef.current !== threadId || activeRunIdRef.current) return;
|
|
1020
|
-
const runId =
|
|
1320
|
+
const runId = autoAttachRunIdFromThread(storage.getThread(threadId));
|
|
1021
1321
|
if (!runId || reattachedRunIdRef.current === runId) return;
|
|
1022
1322
|
void runStream({ mode: "attach", threadId, runId });
|
|
1023
1323
|
}, [activeThreadId, isLoadingHistory, runStream, storage, storageKey]);
|
|
@@ -1030,82 +1330,53 @@ function useAgents24ChatController({
|
|
|
1030
1330
|
if (!hasStaleUnfinishedAssistantCache(cached)) return;
|
|
1031
1331
|
void loadThread(threadId).catch(() => setLoadingHistory(false));
|
|
1032
1332
|
}, [activeThreadId, loadThread, setLoadingHistory, storage, storageKey]);
|
|
1033
|
-
const
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
setMessages
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
id: currentId || createId(),
|
|
1067
|
-
role: input.role,
|
|
1068
|
-
content,
|
|
1069
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
1070
|
-
isFinal: Boolean(input.isFinal),
|
|
1071
|
-
isVoice: input.role === "user",
|
|
1072
|
-
parts: content ? [{ id: createId(), type: "text", kind: "text", text: content }] : [],
|
|
1073
|
-
citations: input.citations,
|
|
1074
|
-
reasoningSteps: input.reasoningSteps
|
|
1075
|
-
};
|
|
1076
|
-
const next = index === -1 ? [...prev, nextMessage] : prev.map((message, itemIndex) => itemIndex === index ? { ...message, ...nextMessage } : message);
|
|
1077
|
-
liveVoiceIdsRef.current[input.role] = input.isFinal ? void 0 : nextMessage.id;
|
|
1078
|
-
messagesRef.current = next;
|
|
1079
|
-
if (activeThreadIdRef.current) persistThread(activeThreadIdRef.current, next);
|
|
1080
|
-
return next;
|
|
1081
|
-
});
|
|
1082
|
-
}, [createId, persistThread]);
|
|
1083
|
-
const startNewThread = useCallback(() => {
|
|
1084
|
-
detachActiveStream();
|
|
1085
|
-
requestSeqRef.current += 1;
|
|
1086
|
-
activeThreadIdRef.current = null;
|
|
1087
|
-
loadedThreadIdRef.current = null;
|
|
1088
|
-
nextBeforeTurnIndexRef.current = null;
|
|
1089
|
-
hasOlderTurnsRef.current = false;
|
|
1090
|
-
storage.setActiveThreadId?.(null);
|
|
1091
|
-
onActiveThreadIdChange?.(null);
|
|
1092
|
-
setMessages([]);
|
|
1093
|
-
messagesRef.current = [];
|
|
1094
|
-
setHasOlderTurns(false);
|
|
1095
|
-
setIsLoadingOlder(false);
|
|
1096
|
-
setContextStatus(null);
|
|
1097
|
-
setLoadingHistory(false);
|
|
1098
|
-
}, [detachActiveStream, onActiveThreadIdChange, setLoadingHistory, storage]);
|
|
1099
|
-
const loadThreadById = useCallback(
|
|
1333
|
+
const {
|
|
1334
|
+
handleCopy,
|
|
1335
|
+
handleDislike,
|
|
1336
|
+
handleLike,
|
|
1337
|
+
handleRetry,
|
|
1338
|
+
startNewThread,
|
|
1339
|
+
upsertLiveVoiceMessage
|
|
1340
|
+
} = useControllerMessageActions({
|
|
1341
|
+
activeThreadIdRef,
|
|
1342
|
+
createId,
|
|
1343
|
+
detachActiveStream,
|
|
1344
|
+
disliked,
|
|
1345
|
+
handleSubmit,
|
|
1346
|
+
hasOlderTurnsRef,
|
|
1347
|
+
liked,
|
|
1348
|
+
liveVoiceIdsRef,
|
|
1349
|
+
loadedThreadIdRef,
|
|
1350
|
+
messagesRef,
|
|
1351
|
+
nextBeforeTurnIndexRef,
|
|
1352
|
+
onActiveThreadIdChange,
|
|
1353
|
+
persistThread,
|
|
1354
|
+
requestSeqRef,
|
|
1355
|
+
setContextStatus,
|
|
1356
|
+
setCopiedMessageId,
|
|
1357
|
+
setDisliked,
|
|
1358
|
+
setHasOlderTurns,
|
|
1359
|
+
setIsLoadingOlder,
|
|
1360
|
+
setLiked,
|
|
1361
|
+
setLoadingHistory,
|
|
1362
|
+
setMessages,
|
|
1363
|
+
storage
|
|
1364
|
+
});
|
|
1365
|
+
const loadThreadById = useCallback2(
|
|
1100
1366
|
async (threadId) => {
|
|
1101
1367
|
if (!threadId) return;
|
|
1368
|
+
setIsSelectingThread(true);
|
|
1102
1369
|
if (activeThreadIdRef.current !== threadId) {
|
|
1103
1370
|
if (abortControllerRef.current) detachActiveStream();
|
|
1104
1371
|
activeThreadIdRef.current = threadId;
|
|
1105
1372
|
storage.setActiveThreadId?.(threadId);
|
|
1106
1373
|
onActiveThreadIdChange?.(threadId);
|
|
1107
1374
|
}
|
|
1108
|
-
|
|
1375
|
+
try {
|
|
1376
|
+
await loadThread(threadId);
|
|
1377
|
+
} finally {
|
|
1378
|
+
if (activeThreadIdRef.current === threadId) setIsSelectingThread(false);
|
|
1379
|
+
}
|
|
1109
1380
|
},
|
|
1110
1381
|
[detachActiveStream, loadThread, onActiveThreadIdChange, storage]
|
|
1111
1382
|
);
|
|
@@ -1123,6 +1394,7 @@ function useAgents24ChatController({
|
|
|
1123
1394
|
isLoadingHistory,
|
|
1124
1395
|
isLoadingOlder,
|
|
1125
1396
|
isRefreshingThreads,
|
|
1397
|
+
isSelectingThread,
|
|
1126
1398
|
hasOlderTurns,
|
|
1127
1399
|
liked,
|
|
1128
1400
|
disliked,
|
|
@@ -1130,6 +1402,7 @@ function useAgents24ChatController({
|
|
|
1130
1402
|
lastThinkingDurationMs,
|
|
1131
1403
|
activeRunId,
|
|
1132
1404
|
handleSubmit,
|
|
1405
|
+
attachRun,
|
|
1133
1406
|
handleStop,
|
|
1134
1407
|
handleCopy,
|
|
1135
1408
|
handleLike,
|
|
@@ -1147,6 +1420,7 @@ function useAgents24ChatController({
|
|
|
1147
1420
|
activeRunId,
|
|
1148
1421
|
activeThread,
|
|
1149
1422
|
activeThreadId,
|
|
1423
|
+
attachRun,
|
|
1150
1424
|
contextStatus,
|
|
1151
1425
|
currentReasoning,
|
|
1152
1426
|
disliked,
|
|
@@ -1161,6 +1435,7 @@ function useAgents24ChatController({
|
|
|
1161
1435
|
isLoadingHistory,
|
|
1162
1436
|
isLoadingOlder,
|
|
1163
1437
|
isRefreshingThreads,
|
|
1438
|
+
isSelectingThread,
|
|
1164
1439
|
lastThinkingDurationMs,
|
|
1165
1440
|
liked,
|
|
1166
1441
|
loadThreadById,
|
|
@@ -1205,13 +1480,16 @@ var DefaultChatPart = ({
|
|
|
1205
1480
|
if (part.kind === "ui-blocks") {
|
|
1206
1481
|
return /* @__PURE__ */ jsx("div", { "data-agents24-ui-blocks-part": true, "data-state": part.state });
|
|
1207
1482
|
}
|
|
1208
|
-
if (part.kind === "
|
|
1209
|
-
return /* @__PURE__ */ jsx("div", { "data-agents24-
|
|
1483
|
+
if (part.kind === "hitl") {
|
|
1484
|
+
return /* @__PURE__ */ jsx("div", { "data-agents24-hitl-part": part.interruptId || "" });
|
|
1210
1485
|
}
|
|
1211
1486
|
if (part.kind === "error") {
|
|
1212
1487
|
return /* @__PURE__ */ jsx("div", { "data-agents24-error-part": true, children: part.errorText });
|
|
1213
1488
|
}
|
|
1214
|
-
|
|
1489
|
+
if (part.kind === "data") {
|
|
1490
|
+
return /* @__PURE__ */ jsx("div", { "data-agents24-data-part": part.name });
|
|
1491
|
+
}
|
|
1492
|
+
return null;
|
|
1215
1493
|
};
|
|
1216
1494
|
var renderChatPart = (part, message, renderOptions) => /* @__PURE__ */ jsx(
|
|
1217
1495
|
DefaultChatPart,
|
|
@@ -1224,12 +1502,17 @@ var renderChatPart = (part, message, renderOptions) => /* @__PURE__ */ jsx(
|
|
|
1224
1502
|
);
|
|
1225
1503
|
|
|
1226
1504
|
// src/sse.ts
|
|
1505
|
+
var isAbortError2 = (error) => {
|
|
1506
|
+
if (!error || typeof error !== "object") return false;
|
|
1507
|
+
const maybe = error;
|
|
1508
|
+
return maybe.name === "AbortError" || String(maybe.message || "").toLowerCase().includes("aborted");
|
|
1509
|
+
};
|
|
1227
1510
|
var parseSseBlock = (block) => {
|
|
1228
1511
|
const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.replace(/^data:\s?/, "")).join("\n").trim();
|
|
1229
1512
|
if (!data) return null;
|
|
1230
1513
|
return JSON.parse(data);
|
|
1231
1514
|
};
|
|
1232
|
-
var consumeSseResponse = async (response, onEvent) => {
|
|
1515
|
+
var consumeSseResponse = async (response, onEvent, options = {}) => {
|
|
1233
1516
|
if (!response.ok) {
|
|
1234
1517
|
let message = response.statusText || "Failed to open chat stream.";
|
|
1235
1518
|
try {
|
|
@@ -1243,24 +1526,41 @@ var consumeSseResponse = async (response, onEvent) => {
|
|
|
1243
1526
|
if (!reader) throw new Error("The chat stream did not return a readable body.");
|
|
1244
1527
|
const decoder = new TextDecoder();
|
|
1245
1528
|
let buffer = "";
|
|
1246
|
-
let threadId = null;
|
|
1247
|
-
let runId = null;
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1529
|
+
let threadId = response.headers.get("X-Thread-ID") || null;
|
|
1530
|
+
let runId = response.headers.get("X-Run-ID") || null;
|
|
1531
|
+
let closed = false;
|
|
1532
|
+
const closeReader = () => {
|
|
1533
|
+
closed = true;
|
|
1534
|
+
void reader.cancel().catch(() => {
|
|
1535
|
+
});
|
|
1536
|
+
};
|
|
1537
|
+
if (options.signal?.aborted) {
|
|
1538
|
+
closeReader();
|
|
1539
|
+
return { threadId, runId };
|
|
1540
|
+
}
|
|
1541
|
+
options.signal?.addEventListener("abort", closeReader, { once: true });
|
|
1542
|
+
try {
|
|
1543
|
+
while (!closed) {
|
|
1544
|
+
const { value, done } = await reader.read();
|
|
1545
|
+
if (done) break;
|
|
1546
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1547
|
+
let boundary = buffer.indexOf("\n\n");
|
|
1548
|
+
while (boundary !== -1) {
|
|
1549
|
+
const block = buffer.slice(0, boundary);
|
|
1550
|
+
buffer = buffer.slice(boundary + 2);
|
|
1551
|
+
boundary = buffer.indexOf("\n\n");
|
|
1552
|
+
const event = parseSseBlock(block);
|
|
1553
|
+
if (!event) continue;
|
|
1554
|
+
if (event.run_id) runId = event.run_id;
|
|
1555
|
+
const payloadThreadId = event.payload?.thread_id;
|
|
1556
|
+
if (payloadThreadId) threadId = String(payloadThreadId);
|
|
1557
|
+
await onEvent(event);
|
|
1558
|
+
}
|
|
1263
1559
|
}
|
|
1560
|
+
} catch (error) {
|
|
1561
|
+
if (!isAbortError2(error)) throw error;
|
|
1562
|
+
} finally {
|
|
1563
|
+
options.signal?.removeEventListener("abort", closeReader);
|
|
1264
1564
|
}
|
|
1265
1565
|
return { threadId, runId };
|
|
1266
1566
|
};
|
|
@@ -1276,6 +1576,14 @@ var streamHeaders = (headers) => ({
|
|
|
1276
1576
|
Accept: "text/event-stream",
|
|
1277
1577
|
"Content-Type": "application/json"
|
|
1278
1578
|
});
|
|
1579
|
+
var ChatTransportError = class extends Error {
|
|
1580
|
+
constructor(message, status) {
|
|
1581
|
+
super(message);
|
|
1582
|
+
this.name = "ChatTransportError";
|
|
1583
|
+
this.status = status;
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
var transportError = (response, fallback) => new ChatTransportError(response.statusText || fallback, response.status);
|
|
1279
1587
|
var createFetchChatTransport = ({
|
|
1280
1588
|
routes,
|
|
1281
1589
|
fetchImpl = fetch,
|
|
@@ -1289,7 +1597,7 @@ var createFetchChatTransport = ({
|
|
|
1289
1597
|
signal: input?.signal,
|
|
1290
1598
|
headers: jsonHeaders(await loadHeaders())
|
|
1291
1599
|
});
|
|
1292
|
-
if (!response.ok) throw
|
|
1600
|
+
if (!response.ok) throw transportError(response, "Failed to list chat threads.");
|
|
1293
1601
|
return response.json();
|
|
1294
1602
|
},
|
|
1295
1603
|
async getThread(input) {
|
|
@@ -1297,7 +1605,7 @@ var createFetchChatTransport = ({
|
|
|
1297
1605
|
signal: input.signal,
|
|
1298
1606
|
headers: jsonHeaders(await loadHeaders())
|
|
1299
1607
|
});
|
|
1300
|
-
if (!response.ok) throw
|
|
1608
|
+
if (!response.ok) throw transportError(response, "Failed to load chat thread.");
|
|
1301
1609
|
return response.json();
|
|
1302
1610
|
},
|
|
1303
1611
|
async streamMessage(input, onEvent) {
|
|
@@ -1330,7 +1638,7 @@ var createFetchChatTransport = ({
|
|
|
1330
1638
|
headers: jsonHeaders(await loadHeaders()),
|
|
1331
1639
|
body: JSON.stringify({ assistant_output_text: input.assistantOutputText || void 0 })
|
|
1332
1640
|
});
|
|
1333
|
-
if (!response.ok) throw
|
|
1641
|
+
if (!response.ok) throw transportError(response, "Failed to cancel chat run.");
|
|
1334
1642
|
return response.json();
|
|
1335
1643
|
},
|
|
1336
1644
|
async deleteThread(input) {
|
|
@@ -1339,7 +1647,46 @@ var createFetchChatTransport = ({
|
|
|
1339
1647
|
method: "DELETE",
|
|
1340
1648
|
headers: jsonHeaders(await loadHeaders())
|
|
1341
1649
|
});
|
|
1342
|
-
if (!response.ok) throw
|
|
1650
|
+
if (!response.ok) throw transportError(response, "Failed to delete chat thread.");
|
|
1651
|
+
return response.json();
|
|
1652
|
+
},
|
|
1653
|
+
async subscribeThreadEvents(input, onEvent) {
|
|
1654
|
+
if (!routes.threadEvents) return;
|
|
1655
|
+
const response = await fetchImpl(routes.threadEvents({ cursor: input.cursor }), {
|
|
1656
|
+
method: "GET",
|
|
1657
|
+
signal: input.signal,
|
|
1658
|
+
headers: streamHeaders(await loadHeaders())
|
|
1659
|
+
});
|
|
1660
|
+
await consumeSseResponse(response, (event) => onEvent(event));
|
|
1661
|
+
},
|
|
1662
|
+
async resumeHitl(input) {
|
|
1663
|
+
if (!routes.resumeRun) throw new ChatTransportError("HITL resume is not configured.", 501);
|
|
1664
|
+
const response = await fetchImpl(routes.resumeRun({ runId: input.runId }), {
|
|
1665
|
+
method: "POST",
|
|
1666
|
+
headers: jsonHeaders(await loadHeaders()),
|
|
1667
|
+
body: JSON.stringify({
|
|
1668
|
+
schema_version: "agents24.hitl.resume.v1",
|
|
1669
|
+
interrupt_id: input.interruptId,
|
|
1670
|
+
decisions: input.decisions,
|
|
1671
|
+
client: input.client
|
|
1672
|
+
})
|
|
1673
|
+
});
|
|
1674
|
+
if (!response.ok) throw transportError(response, "Failed to resume chat run.");
|
|
1675
|
+
return response.json();
|
|
1676
|
+
},
|
|
1677
|
+
async startMcpAuth(input) {
|
|
1678
|
+
if (!routes.startMcpAuth) throw new ChatTransportError("MCP auth is not configured.", 501);
|
|
1679
|
+
const response = await fetchImpl(routes.startMcpAuth({ runId: input.runId, serverId: input.serverId }), {
|
|
1680
|
+
method: "POST",
|
|
1681
|
+
headers: jsonHeaders(await loadHeaders()),
|
|
1682
|
+
body: JSON.stringify({
|
|
1683
|
+
interrupt_id: input.interruptId || void 0,
|
|
1684
|
+
principal_type: input.principalType || void 0,
|
|
1685
|
+
principal_id: input.principalId || void 0,
|
|
1686
|
+
client: input.client
|
|
1687
|
+
})
|
|
1688
|
+
});
|
|
1689
|
+
if (!response.ok) throw transportError(response, "Failed to start MCP authorization.");
|
|
1343
1690
|
return response.json();
|
|
1344
1691
|
}
|
|
1345
1692
|
};
|
|
@@ -1353,6 +1700,7 @@ import {
|
|
|
1353
1700
|
useMessageScrollerVisibility
|
|
1354
1701
|
} from "@shadcn/react/message-scroller";
|
|
1355
1702
|
export {
|
|
1703
|
+
ChatTransportError,
|
|
1356
1704
|
DEFAULT_THREAD_PAGE_SIZE,
|
|
1357
1705
|
DefaultChatPart,
|
|
1358
1706
|
DefaultToolPart,
|
|
@@ -1367,21 +1715,26 @@ export {
|
|
|
1367
1715
|
MessageScroller,
|
|
1368
1716
|
activeRunIdFromThread,
|
|
1369
1717
|
activeRunIdFromThreadDetail,
|
|
1718
|
+
appendStableStreamingTurn,
|
|
1370
1719
|
assistantTextFromParts,
|
|
1371
1720
|
assistantTextFromResponseBlocks,
|
|
1721
|
+
autoAttachRunIdFromThread,
|
|
1372
1722
|
compressionFromContextWindow,
|
|
1373
1723
|
consumeSseResponse,
|
|
1374
1724
|
createChatId,
|
|
1375
1725
|
createFetchChatTransport,
|
|
1726
|
+
findStableAssistantMessageIndex,
|
|
1376
1727
|
getActiveStreamingTextPartId,
|
|
1377
1728
|
hasStaleUnfinishedAssistantCache,
|
|
1378
1729
|
hasUnfinishedAssistantMessage,
|
|
1379
1730
|
isActiveStreamingTextPart,
|
|
1731
|
+
isAutoAttachThreadStatus,
|
|
1380
1732
|
isRunningThreadStatus,
|
|
1381
1733
|
latestContextWindowFromThread,
|
|
1382
1734
|
mergeContextWindow,
|
|
1383
1735
|
mergeContextWindowUpdate,
|
|
1384
1736
|
mergeReasoningSteps,
|
|
1737
|
+
mergeStoredThreadsForRefresh,
|
|
1385
1738
|
normalizeContextCompression,
|
|
1386
1739
|
normalizeContextWindow,
|
|
1387
1740
|
parseSseBlock,
|
|
@@ -1394,6 +1747,7 @@ export {
|
|
|
1394
1747
|
threadPaging,
|
|
1395
1748
|
titleFromMessage,
|
|
1396
1749
|
toolStateFromStatus,
|
|
1750
|
+
upsertStableAssistantMessage,
|
|
1397
1751
|
useAgents24ChatController,
|
|
1398
1752
|
useMessageScroller,
|
|
1399
1753
|
useMessageScrollerScrollable,
|