@agents24/chat-react 0.1.10 → 0.3.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/README.md +9 -4
- package/dist/{chunk-E3TJ3I6G.js → chunk-FFO6KEB4.js} +2 -1
- package/dist/chunk-FFO6KEB4.js.map +1 -0
- package/dist/controller-hitl.d.ts +22 -0
- package/dist/controller-options.d.ts +15 -0
- package/dist/controller-thread-events.d.ts +7 -0
- package/dist/controller.d.ts +4 -16
- package/dist/index.cjs +253 -152
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +241 -140
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts +0 -1
- package/dist/templates/index.cjs +1 -0
- package/dist/templates/index.cjs.map +1 -1
- package/dist/templates/index.js +1 -1
- package/dist/types.d.ts +39 -21
- package/dist/ui/agent-response-timeline.d.ts +3 -3
- package/dist/ui/index.cjs +169 -156
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.js +169 -156
- package/dist/ui/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-E3TJ3I6G.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
LatestThreadScrollerProvider,
|
|
8
8
|
LatestThreadScrollerRoot,
|
|
9
9
|
LatestThreadScrollerViewport
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-FFO6KEB4.js";
|
|
11
11
|
import {
|
|
12
12
|
getActiveStreamingTextPartId,
|
|
13
13
|
isActiveStreamingTextPart,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "./chunk-EWZO4QJI.js";
|
|
16
16
|
|
|
17
17
|
// src/controller.ts
|
|
18
|
-
import { useCallback as
|
|
18
|
+
import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
|
|
19
19
|
|
|
20
20
|
// src/controller-actions.ts
|
|
21
21
|
import { useCallback } from "react";
|
|
@@ -89,6 +89,47 @@ function useControllerMessageActions(input) {
|
|
|
89
89
|
return { handleCopy, handleDislike, handleLike, handleRetry, startNewThread, upsertLiveVoiceMessage };
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// src/controller-hitl.ts
|
|
93
|
+
import { useCallback as useCallback2, useMemo, useState } from "react";
|
|
94
|
+
function useControllerHitl({
|
|
95
|
+
messages,
|
|
96
|
+
transport,
|
|
97
|
+
activeRunId,
|
|
98
|
+
activeRunIdRef,
|
|
99
|
+
activeThreadIdRef,
|
|
100
|
+
runStream
|
|
101
|
+
}) {
|
|
102
|
+
const [isResolvingHitl, setIsResolvingHitl] = useState(false);
|
|
103
|
+
const pendingHitl = useMemo(() => {
|
|
104
|
+
for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
|
|
105
|
+
const parts = messages[messageIndex]?.parts || [];
|
|
106
|
+
for (let partIndex = parts.length - 1; partIndex >= 0; partIndex -= 1) {
|
|
107
|
+
const part = parts[partIndex];
|
|
108
|
+
if (part?.kind === "hitl" && part.status === "pending") return part;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}, [messages]);
|
|
113
|
+
const resumeHitl = useCallback2(async (input) => {
|
|
114
|
+
if (!transport.resumeHitl) throw new Error("HITL resume is not configured.");
|
|
115
|
+
const runId = input.runId || activeRunIdRef.current || activeRunId;
|
|
116
|
+
if (!runId) throw new Error("No paused run is available to resume.");
|
|
117
|
+
setIsResolvingHitl(true);
|
|
118
|
+
try {
|
|
119
|
+
const result = await transport.resumeHitl({ ...input, runId });
|
|
120
|
+
const threadId = result.thread_id || activeThreadIdRef.current;
|
|
121
|
+
if (threadId) await runStream({ mode: "attach", runId: result.run_id || runId, threadId });
|
|
122
|
+
return result;
|
|
123
|
+
} finally {
|
|
124
|
+
setIsResolvingHitl(false);
|
|
125
|
+
}
|
|
126
|
+
}, [activeRunId, activeRunIdRef, activeThreadIdRef, runStream, transport]);
|
|
127
|
+
return { pendingHitl, isResolvingHitl, resumeHitl };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/controller-thread-events.ts
|
|
131
|
+
import { useEffect, useRef } from "react";
|
|
132
|
+
|
|
92
133
|
// src/context-window.ts
|
|
93
134
|
var SOURCE_PRIORITY = {
|
|
94
135
|
unknown: 0,
|
|
@@ -223,12 +264,6 @@ var titleFromMessage = (text, files = []) => {
|
|
|
223
264
|
var threadActivityDate = (thread) => String(thread.updated_at || thread.last_activity_at || thread.created_at || (/* @__PURE__ */ new Date()).toISOString());
|
|
224
265
|
var assistantTextFromResponseBlocks = (blocks) => (blocks || []).filter((block) => block.kind === "assistant_text" && typeof block.text === "string").map((block) => String(block.text)).join("\n\n").trim();
|
|
225
266
|
var assistantTextFromParts = (parts) => (parts || []).filter((part) => part.kind === "text").map((part) => part.text).join("\n\n").trim();
|
|
226
|
-
var textFromFinalOutput = (value) => {
|
|
227
|
-
if (typeof value === "string") return value;
|
|
228
|
-
if (!value || typeof value !== "object") return "";
|
|
229
|
-
const record = value;
|
|
230
|
-
return String(record.message || record.text || record.answer || "");
|
|
231
|
-
};
|
|
232
267
|
var displayTextWithoutInlineAttachments = (text, hasAttachments) => {
|
|
233
268
|
if (!hasAttachments) return text;
|
|
234
269
|
const marker = "Attached text file (";
|
|
@@ -275,7 +310,7 @@ var responseBlocksFromTurn = (turn) => {
|
|
|
275
310
|
var assistantTextFromEvents = (events) => {
|
|
276
311
|
const assistantText = latestEventPayloadValue(events, "assistant_output_text");
|
|
277
312
|
if (typeof assistantText === "string" && assistantText.trim()) return assistantText;
|
|
278
|
-
return
|
|
313
|
+
return "";
|
|
279
314
|
};
|
|
280
315
|
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
281
316
|
var optionalString = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
@@ -379,13 +414,34 @@ var partsFromResponseBlocks = (blocks, fallbackText) => {
|
|
|
379
414
|
}
|
|
380
415
|
if (block.kind === "hitl_request") {
|
|
381
416
|
const hitl = asRecord(block.hitl) || block;
|
|
417
|
+
if (hitl.schema_version !== "agents24.hitl.interrupt.v2") {
|
|
418
|
+
throw new Error("Invalid V2 HITL response block.");
|
|
419
|
+
}
|
|
420
|
+
const interruptId = optionalString(block.interruptId) || optionalString(hitl.interrupt_id);
|
|
421
|
+
const hitlKind = optionalString(block.hitlKind) || optionalString(hitl.kind);
|
|
422
|
+
const status = optionalString(block.status) || optionalString(hitl.status) || "pending";
|
|
423
|
+
const allowedActions = Array.isArray(hitl.allowed_actions) ? hitl.allowed_actions.filter((value) => ["approve", "reject", "connect", "skip"].includes(String(value))) : [];
|
|
424
|
+
if (!interruptId || allowedActions.length === 0 || !["tool_review", "mcp_auth", "user_approval", "app_data_permission"].includes(String(hitlKind))) {
|
|
425
|
+
throw new Error("Invalid V2 HITL response block.");
|
|
426
|
+
}
|
|
427
|
+
const resolution = asRecord(block.resolution);
|
|
382
428
|
parts.push({
|
|
383
429
|
id,
|
|
384
430
|
type: "hitl",
|
|
385
431
|
kind: "hitl",
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
432
|
+
interruptId,
|
|
433
|
+
hitlKind,
|
|
434
|
+
message: optionalString(hitl.message) || optionalString(block.text) || "Input is required to continue.",
|
|
435
|
+
allowedActions,
|
|
436
|
+
status,
|
|
437
|
+
presentation: asRecord(hitl.presentation) || {},
|
|
438
|
+
resolution: resolution ? {
|
|
439
|
+
action: ["approve", "reject", "connect", "skip"].includes(String(resolution.action)) ? String(resolution.action) : null,
|
|
440
|
+
outcome: optionalString(resolution.outcome),
|
|
441
|
+
reason: optionalString(resolution.reason),
|
|
442
|
+
resolvedAt: optionalString(resolution.resolved_at),
|
|
443
|
+
resolver: asRecord(resolution.resolver)
|
|
444
|
+
} : null,
|
|
389
445
|
raw: block
|
|
390
446
|
});
|
|
391
447
|
return;
|
|
@@ -459,7 +515,7 @@ var turnToMessages = (turn, activeRunId) => {
|
|
|
459
515
|
attachments
|
|
460
516
|
});
|
|
461
517
|
}
|
|
462
|
-
const assistantText = turn.assistant_output_text || assistantTextFromResponseBlocks(responseBlocks) || assistantTextFromEvents(turn.run_events)
|
|
518
|
+
const assistantText = turn.assistant_output_text || assistantTextFromResponseBlocks(responseBlocks) || assistantTextFromEvents(turn.run_events);
|
|
463
519
|
if (assistantText || responseBlocks.length > 0 || isRunning) {
|
|
464
520
|
const parts = partsFromResponseBlocks(responseBlocks, assistantText);
|
|
465
521
|
messages.push({
|
|
@@ -625,6 +681,47 @@ var applyThreadSummaryEvent = (currentThreads, event) => {
|
|
|
625
681
|
return sortByActivity(next);
|
|
626
682
|
};
|
|
627
683
|
|
|
684
|
+
// src/controller-thread-events.ts
|
|
685
|
+
function useControllerThreadEvents({
|
|
686
|
+
transport,
|
|
687
|
+
storage,
|
|
688
|
+
refresh,
|
|
689
|
+
setThreads
|
|
690
|
+
}) {
|
|
691
|
+
const cursorRef = useRef(null);
|
|
692
|
+
useEffect(() => {
|
|
693
|
+
if (!transport.subscribeThreadEvents) return;
|
|
694
|
+
let cancelled = false;
|
|
695
|
+
let retryTimeout = null;
|
|
696
|
+
let controller = null;
|
|
697
|
+
const connect = () => {
|
|
698
|
+
if (cancelled) return;
|
|
699
|
+
controller = new AbortController();
|
|
700
|
+
transport.subscribeThreadEvents?.(
|
|
701
|
+
{ cursor: cursorRef.current, signal: controller.signal },
|
|
702
|
+
async (event) => {
|
|
703
|
+
if (typeof event.cursor === "number") cursorRef.current = event.cursor;
|
|
704
|
+
if (event.event === "snapshot_required") {
|
|
705
|
+
await refresh().catch(() => void 0);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
storage.setThreads(applyThreadSummaryEvent(storage.listThreads(), event));
|
|
709
|
+
setThreads(storage.listThreads());
|
|
710
|
+
}
|
|
711
|
+
).catch((error) => {
|
|
712
|
+
if (cancelled || isAbortError(error)) return;
|
|
713
|
+
retryTimeout = setTimeout(connect, 1500);
|
|
714
|
+
});
|
|
715
|
+
};
|
|
716
|
+
connect();
|
|
717
|
+
return () => {
|
|
718
|
+
cancelled = true;
|
|
719
|
+
if (retryTimeout) clearTimeout(retryTimeout);
|
|
720
|
+
controller?.abort();
|
|
721
|
+
};
|
|
722
|
+
}, [refresh, setThreads, storage, transport]);
|
|
723
|
+
}
|
|
724
|
+
|
|
628
725
|
// src/message-lifecycle.ts
|
|
629
726
|
function findStableAssistantMessageIndex(messages, input) {
|
|
630
727
|
return messages.findIndex(
|
|
@@ -663,82 +760,83 @@ function upsertStableAssistantMessage(messages, input) {
|
|
|
663
760
|
}
|
|
664
761
|
|
|
665
762
|
// src/controller.ts
|
|
666
|
-
function useAgents24ChatController({
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
763
|
+
function useAgents24ChatController(options) {
|
|
764
|
+
const {
|
|
765
|
+
transport,
|
|
766
|
+
storage,
|
|
767
|
+
activeThreadId: controlledActiveThreadId,
|
|
768
|
+
pageSize = DEFAULT_THREAD_PAGE_SIZE,
|
|
769
|
+
storageKey,
|
|
770
|
+
createId = createChatId,
|
|
771
|
+
onActiveThreadIdChange,
|
|
772
|
+
onSourceClick,
|
|
773
|
+
onStreamErrorMessage,
|
|
774
|
+
onRuntimeEvent,
|
|
775
|
+
onThreadDetailLoaded
|
|
776
|
+
} = options;
|
|
679
777
|
const activeThreadId = controlledActiveThreadId ?? storage.getActiveThreadId?.() ?? null;
|
|
680
778
|
const initialCached = activeThreadId ? storage.getThread(activeThreadId) : void 0;
|
|
681
|
-
const [messages, setMessages] =
|
|
682
|
-
const [isLoading, setIsLoading] =
|
|
683
|
-
const [isLoadingHistory, setIsLoadingHistory] =
|
|
684
|
-
const [isLoadingOlder, setIsLoadingOlder] =
|
|
685
|
-
const [hasOlderTurns, setHasOlderTurns] =
|
|
686
|
-
const [streamingContent, setStreamingContent] =
|
|
687
|
-
const [streamingMessageId, setStreamingMessageId] =
|
|
688
|
-
const [contextStatus, setContextStatus] =
|
|
689
|
-
const [currentReasoning, setCurrentReasoning] =
|
|
690
|
-
const [liked, setLiked] =
|
|
691
|
-
const [disliked, setDisliked] =
|
|
692
|
-
const [copiedMessageId, setCopiedMessageId] =
|
|
693
|
-
const [lastThinkingDurationMs, setLastThinkingDurationMs] =
|
|
694
|
-
const [threads, setThreads] =
|
|
695
|
-
const [isRefreshingThreads, setIsRefreshingThreads] =
|
|
696
|
-
const [isSelectingThread, setIsSelectingThread] =
|
|
697
|
-
const [activeRunId, setActiveRunId] =
|
|
698
|
-
const textareaRef =
|
|
699
|
-
const activeThreadIdRef =
|
|
700
|
-
const messagesRef =
|
|
701
|
-
const nextBeforeTurnIndexRef =
|
|
702
|
-
const hasOlderTurnsRef =
|
|
703
|
-
const isLoadingOlderRef =
|
|
704
|
-
const loadedThreadIdRef =
|
|
705
|
-
const requestSeqRef =
|
|
706
|
-
const isLoadingHistoryRef =
|
|
707
|
-
const activeRunIdRef =
|
|
708
|
-
const reattachedRunIdRef =
|
|
709
|
-
const abortControllerRef =
|
|
710
|
-
const
|
|
711
|
-
const
|
|
712
|
-
const
|
|
713
|
-
const
|
|
714
|
-
const
|
|
715
|
-
const
|
|
716
|
-
const setActiveRunIdValue =
|
|
779
|
+
const [messages, setMessages] = useState2(() => initialCached?.messages || []);
|
|
780
|
+
const [isLoading, setIsLoading] = useState2(false);
|
|
781
|
+
const [isLoadingHistory, setIsLoadingHistory] = useState2(() => Boolean(activeThreadId) && !initialCached?.messages?.length);
|
|
782
|
+
const [isLoadingOlder, setIsLoadingOlder] = useState2(false);
|
|
783
|
+
const [hasOlderTurns, setHasOlderTurns] = useState2(Boolean(initialCached?.hasOlderTurns));
|
|
784
|
+
const [streamingContent, setStreamingContent] = useState2("");
|
|
785
|
+
const [streamingMessageId, setStreamingMessageId] = useState2(null);
|
|
786
|
+
const [contextStatus, setContextStatus] = useState2(null);
|
|
787
|
+
const [currentReasoning, setCurrentReasoning] = useState2([]);
|
|
788
|
+
const [liked, setLiked] = useState2({});
|
|
789
|
+
const [disliked, setDisliked] = useState2({});
|
|
790
|
+
const [copiedMessageId, setCopiedMessageId] = useState2(null);
|
|
791
|
+
const [lastThinkingDurationMs, setLastThinkingDurationMs] = useState2(null);
|
|
792
|
+
const [threads, setThreads] = useState2(() => storage.listThreads());
|
|
793
|
+
const [isRefreshingThreads, setIsRefreshingThreads] = useState2(false);
|
|
794
|
+
const [isSelectingThread, setIsSelectingThread] = useState2(false);
|
|
795
|
+
const [activeRunId, setActiveRunId] = useState2(null);
|
|
796
|
+
const textareaRef = useRef2(null);
|
|
797
|
+
const activeThreadIdRef = useRef2(activeThreadId);
|
|
798
|
+
const messagesRef = useRef2(messages);
|
|
799
|
+
const nextBeforeTurnIndexRef = useRef2(initialCached?.nextBeforeTurnIndex ?? null);
|
|
800
|
+
const hasOlderTurnsRef = useRef2(Boolean(initialCached?.hasOlderTurns));
|
|
801
|
+
const isLoadingOlderRef = useRef2(false);
|
|
802
|
+
const loadedThreadIdRef = useRef2(initialCached?.messages?.length ? activeThreadId : null);
|
|
803
|
+
const requestSeqRef = useRef2(0);
|
|
804
|
+
const isLoadingHistoryRef = useRef2(isLoadingHistory);
|
|
805
|
+
const activeRunIdRef = useRef2(null);
|
|
806
|
+
const reattachedRunIdRef = useRef2(null);
|
|
807
|
+
const abortControllerRef = useRef2(null);
|
|
808
|
+
const [lifecycleAbortController] = useState2(() => new AbortController());
|
|
809
|
+
const streamingContentRef = useRef2("");
|
|
810
|
+
const streamingMessageIdRef = useRef2(null);
|
|
811
|
+
const reasoningRef = useRef2([]);
|
|
812
|
+
const liveVoiceIdsRef = useRef2({});
|
|
813
|
+
const refreshSeqRef = useRef2(0);
|
|
814
|
+
const setActiveRunIdValue = useCallback3((runId) => {
|
|
717
815
|
activeRunIdRef.current = runId;
|
|
718
816
|
setActiveRunId(runId);
|
|
719
817
|
}, []);
|
|
720
|
-
const syncThreadsFromStorage =
|
|
818
|
+
const syncThreadsFromStorage = useCallback3(() => {
|
|
721
819
|
setThreads(storage.listThreads());
|
|
722
820
|
}, [storage]);
|
|
723
|
-
const upsertStoredThread =
|
|
821
|
+
const upsertStoredThread = useCallback3(
|
|
724
822
|
(thread) => {
|
|
725
823
|
storage.upsertThread(thread);
|
|
726
824
|
syncThreadsFromStorage();
|
|
727
825
|
},
|
|
728
826
|
[storage, syncThreadsFromStorage]
|
|
729
827
|
);
|
|
730
|
-
|
|
828
|
+
useEffect2(() => {
|
|
731
829
|
messagesRef.current = messages;
|
|
732
830
|
}, [messages]);
|
|
733
|
-
const persistThread =
|
|
734
|
-
(threadId, nextMessages, paging,
|
|
831
|
+
const persistThread = useCallback3(
|
|
832
|
+
(threadId, nextMessages, paging, options2) => {
|
|
735
833
|
const existing = storage.getThread(threadId);
|
|
736
834
|
const firstUser = nextMessages.find((message) => message.role === "user");
|
|
737
835
|
upsertStoredThread({
|
|
738
836
|
...existing || {},
|
|
739
837
|
id: threadId,
|
|
740
838
|
title: existing?.title || (firstUser ? titleFromMessage(firstUser.content, firstUser.attachments || []) : "New chat"),
|
|
741
|
-
updated_at:
|
|
839
|
+
updated_at: options2?.updatedAt || (options2?.touch ? (/* @__PURE__ */ new Date()).toISOString() : existing?.updated_at) || (/* @__PURE__ */ new Date()).toISOString(),
|
|
742
840
|
messages: nextMessages,
|
|
743
841
|
isHydrated: true,
|
|
744
842
|
hasOlderTurns: paging?.hasOlderTurns ?? hasOlderTurnsRef.current,
|
|
@@ -747,7 +845,7 @@ function useAgents24ChatController({
|
|
|
747
845
|
},
|
|
748
846
|
[storage, upsertStoredThread]
|
|
749
847
|
);
|
|
750
|
-
const markThreadRunStatus =
|
|
848
|
+
const markThreadRunStatus = useCallback3(
|
|
751
849
|
(threadId, runId, status, lastEventSeq) => {
|
|
752
850
|
const existing = storage.getThread(threadId);
|
|
753
851
|
if (!existing || !runId) return;
|
|
@@ -777,20 +875,21 @@ function useAgents24ChatController({
|
|
|
777
875
|
},
|
|
778
876
|
[storage, upsertStoredThread]
|
|
779
877
|
);
|
|
780
|
-
const refresh =
|
|
878
|
+
const refresh = useCallback3(async () => {
|
|
781
879
|
const seq = ++refreshSeqRef.current;
|
|
782
880
|
setIsRefreshingThreads(true);
|
|
783
881
|
try {
|
|
784
|
-
const data = await transport.listThreads();
|
|
882
|
+
const data = await transport.listThreads({ signal: lifecycleAbortController.signal });
|
|
883
|
+
if (lifecycleAbortController.signal.aborted) return;
|
|
785
884
|
if (seq !== refreshSeqRef.current) return;
|
|
786
885
|
const nextThreads = (data.items || []).map((item) => threadSummaryToStored(item));
|
|
787
886
|
storage.setThreads(mergeStoredThreadsForRefresh(storage.listThreads(), nextThreads));
|
|
788
887
|
setThreads(storage.listThreads());
|
|
789
888
|
} finally {
|
|
790
|
-
if (seq === refreshSeqRef.current) setIsRefreshingThreads(false);
|
|
889
|
+
if (!lifecycleAbortController.signal.aborted && seq === refreshSeqRef.current) setIsRefreshingThreads(false);
|
|
791
890
|
}
|
|
792
|
-
}, [storage, transport]);
|
|
793
|
-
const applyThreadId =
|
|
891
|
+
}, [lifecycleAbortController, storage, transport]);
|
|
892
|
+
const applyThreadId = useCallback3(
|
|
794
893
|
(threadId, baseMessages) => {
|
|
795
894
|
if (!threadId || activeThreadIdRef.current === threadId) return;
|
|
796
895
|
activeThreadIdRef.current = threadId;
|
|
@@ -804,15 +903,15 @@ function useAgents24ChatController({
|
|
|
804
903
|
streamingContentRef.current = value;
|
|
805
904
|
setStreamingContent(value);
|
|
806
905
|
};
|
|
807
|
-
const setLoadingHistory =
|
|
906
|
+
const setLoadingHistory = useCallback3((value) => {
|
|
808
907
|
isLoadingHistoryRef.current = value;
|
|
809
908
|
setIsLoadingHistory(value);
|
|
810
909
|
}, []);
|
|
811
|
-
const setReasoningSteps =
|
|
910
|
+
const setReasoningSteps = useCallback3((value) => {
|
|
812
911
|
reasoningRef.current = value || [];
|
|
813
912
|
setCurrentReasoning(value || []);
|
|
814
913
|
}, []);
|
|
815
|
-
const detachActiveStream =
|
|
914
|
+
const detachActiveStream = useCallback3(() => {
|
|
816
915
|
const controller = abortControllerRef.current;
|
|
817
916
|
abortControllerRef.current = null;
|
|
818
917
|
abortDetachedStream(controller);
|
|
@@ -826,7 +925,7 @@ function useAgents24ChatController({
|
|
|
826
925
|
setStreamingContent("");
|
|
827
926
|
setCurrentReasoning([]);
|
|
828
927
|
}, [setActiveRunIdValue]);
|
|
829
|
-
const clearMissingThread =
|
|
928
|
+
const clearMissingThread = useCallback3(
|
|
830
929
|
(threadId) => {
|
|
831
930
|
storage.deleteThread?.(threadId);
|
|
832
931
|
syncThreadsFromStorage();
|
|
@@ -848,7 +947,7 @@ function useAgents24ChatController({
|
|
|
848
947
|
},
|
|
849
948
|
[detachActiveStream, onActiveThreadIdChange, setLoadingHistory, storage, syncThreadsFromStorage]
|
|
850
949
|
);
|
|
851
|
-
const setLiveAssistantMessage =
|
|
950
|
+
const setLiveAssistantMessage = useCallback3(
|
|
852
951
|
(input) => {
|
|
853
952
|
setMessages((prev) => {
|
|
854
953
|
const next = upsertStableAssistantMessage(prev, {
|
|
@@ -880,7 +979,7 @@ function useAgents24ChatController({
|
|
|
880
979
|
},
|
|
881
980
|
[]
|
|
882
981
|
);
|
|
883
|
-
const finalizeAssistantMessage =
|
|
982
|
+
const finalizeAssistantMessage = useCallback3(
|
|
884
983
|
(input) => {
|
|
885
984
|
const content = input.error || input.assistantText.trim();
|
|
886
985
|
if (!content) return input.baseMessages;
|
|
@@ -934,7 +1033,7 @@ function useAgents24ChatController({
|
|
|
934
1033
|
},
|
|
935
1034
|
[createId, persistThread, storage, upsertStoredThread]
|
|
936
1035
|
);
|
|
937
|
-
const loadThread =
|
|
1036
|
+
const loadThread = useCallback3(
|
|
938
1037
|
async (threadId) => {
|
|
939
1038
|
const seq = ++requestSeqRef.current;
|
|
940
1039
|
setLoadingHistory(true);
|
|
@@ -944,7 +1043,8 @@ function useAgents24ChatController({
|
|
|
944
1043
|
const detail = await transport.getThread({
|
|
945
1044
|
threadId,
|
|
946
1045
|
limit: pageSize,
|
|
947
|
-
includeRunEvents: false
|
|
1046
|
+
includeRunEvents: false,
|
|
1047
|
+
signal: lifecycleAbortController.signal
|
|
948
1048
|
});
|
|
949
1049
|
if (activeThreadIdRef.current !== threadId || seq !== requestSeqRef.current) return;
|
|
950
1050
|
void onThreadDetailLoaded?.(detail);
|
|
@@ -973,12 +1073,12 @@ function useAgents24ChatController({
|
|
|
973
1073
|
}
|
|
974
1074
|
throw error;
|
|
975
1075
|
} finally {
|
|
976
|
-
if (activeThreadIdRef.current === threadId && seq === requestSeqRef.current) setLoadingHistory(false);
|
|
1076
|
+
if (!lifecycleAbortController.signal.aborted && activeThreadIdRef.current === threadId && seq === requestSeqRef.current) setLoadingHistory(false);
|
|
977
1077
|
}
|
|
978
1078
|
},
|
|
979
|
-
[clearMissingThread, onThreadDetailLoaded, pageSize, setLoadingHistory, transport, upsertStoredThread]
|
|
1079
|
+
[clearMissingThread, lifecycleAbortController, onThreadDetailLoaded, pageSize, setLoadingHistory, transport, upsertStoredThread]
|
|
980
1080
|
);
|
|
981
|
-
const loadOlderTurns =
|
|
1081
|
+
const loadOlderTurns = useCallback3(async () => {
|
|
982
1082
|
const threadId = activeThreadIdRef.current;
|
|
983
1083
|
const beforeTurnIndex = nextBeforeTurnIndexRef.current;
|
|
984
1084
|
if (!threadId || beforeTurnIndex === null || isLoadingOlderRef.current) return;
|
|
@@ -989,7 +1089,8 @@ function useAgents24ChatController({
|
|
|
989
1089
|
threadId,
|
|
990
1090
|
limit: pageSize,
|
|
991
1091
|
beforeTurnIndex,
|
|
992
|
-
includeRunEvents: false
|
|
1092
|
+
includeRunEvents: false,
|
|
1093
|
+
signal: lifecycleAbortController.signal
|
|
993
1094
|
});
|
|
994
1095
|
if (activeThreadIdRef.current !== threadId) return;
|
|
995
1096
|
const older = threadDetailToMessages(detail);
|
|
@@ -1009,11 +1110,11 @@ function useAgents24ChatController({
|
|
|
1009
1110
|
}
|
|
1010
1111
|
throw error;
|
|
1011
1112
|
} finally {
|
|
1012
|
-
if (activeThreadIdRef.current === threadId) setIsLoadingOlder(false);
|
|
1113
|
+
if (!lifecycleAbortController.signal.aborted && activeThreadIdRef.current === threadId) setIsLoadingOlder(false);
|
|
1013
1114
|
isLoadingOlderRef.current = false;
|
|
1014
1115
|
}
|
|
1015
|
-
}, [clearMissingThread, pageSize, persistThread, transport]);
|
|
1016
|
-
const handleStreamEvent =
|
|
1116
|
+
}, [clearMissingThread, lifecycleAbortController, pageSize, persistThread, transport]);
|
|
1117
|
+
const handleStreamEvent = useCallback3(
|
|
1017
1118
|
(input) => {
|
|
1018
1119
|
const { mode, event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages } = input;
|
|
1019
1120
|
const payload = event.payload || {};
|
|
@@ -1059,7 +1160,7 @@ function useAgents24ChatController({
|
|
|
1059
1160
|
streamThreadIdRef.current = terminalThreadId;
|
|
1060
1161
|
applyThreadId(terminalThreadId, baseMessages);
|
|
1061
1162
|
}
|
|
1062
|
-
const finalText = String(payload.assistant_output_text ||
|
|
1163
|
+
const finalText = String(payload.assistant_output_text || assistantTextFromResponseBlocks(responseBlocks || []) || streamingContentRef.current || "");
|
|
1063
1164
|
finalizeAssistantMessage({
|
|
1064
1165
|
threadId: streamThreadIdRef.current || activeThreadIdRef.current,
|
|
1065
1166
|
baseMessages: messagesRef.current,
|
|
@@ -1074,7 +1175,7 @@ function useAgents24ChatController({
|
|
|
1074
1175
|
},
|
|
1075
1176
|
[applyThreadId, finalizeAssistantMessage, markThreadRunStatus, onRuntimeEvent, onStreamErrorMessage, setActiveRunIdValue, setLiveAssistantMessage, setReasoningSteps]
|
|
1076
1177
|
);
|
|
1077
|
-
const runStream =
|
|
1178
|
+
const runStream = useCallback3(
|
|
1078
1179
|
async (input) => {
|
|
1079
1180
|
const startedAt = Date.now();
|
|
1080
1181
|
const controller = new AbortController();
|
|
@@ -1197,18 +1298,26 @@ function useAgents24ChatController({
|
|
|
1197
1298
|
},
|
|
1198
1299
|
[createId, finalizeAssistantMessage, handleStreamEvent, onStreamErrorMessage, persistThread, refresh, setActiveRunIdValue, setReasoningSteps, transport]
|
|
1199
1300
|
);
|
|
1200
|
-
const handleSubmit =
|
|
1301
|
+
const handleSubmit = useCallback3(
|
|
1201
1302
|
async (message) => {
|
|
1202
1303
|
if (!message.text.trim() && !(message.files || []).length) return;
|
|
1203
1304
|
await runStream({ mode: "submit", message });
|
|
1204
1305
|
},
|
|
1205
1306
|
[runStream]
|
|
1206
1307
|
);
|
|
1207
|
-
const attachRun =
|
|
1308
|
+
const attachRun = useCallback3(async (runId, threadId) => {
|
|
1208
1309
|
const resolvedThreadId = threadId ?? activeThreadIdRef.current;
|
|
1209
1310
|
if (runId && resolvedThreadId) await runStream({ mode: "attach", runId, threadId: resolvedThreadId });
|
|
1210
1311
|
}, [runStream]);
|
|
1211
|
-
const
|
|
1312
|
+
const { pendingHitl, isResolvingHitl, resumeHitl } = useControllerHitl({
|
|
1313
|
+
messages,
|
|
1314
|
+
transport,
|
|
1315
|
+
activeRunId,
|
|
1316
|
+
activeRunIdRef,
|
|
1317
|
+
activeThreadIdRef,
|
|
1318
|
+
runStream
|
|
1319
|
+
});
|
|
1320
|
+
const handleStop = useCallback3(() => {
|
|
1212
1321
|
const runId = activeRunIdRef.current;
|
|
1213
1322
|
const partial = streamingContentRef.current;
|
|
1214
1323
|
const liveMessageId = streamingMessageIdRef.current;
|
|
@@ -1232,45 +1341,25 @@ function useAgents24ChatController({
|
|
|
1232
1341
|
});
|
|
1233
1342
|
}
|
|
1234
1343
|
}, [finalizeAssistantMessage, lastThinkingDurationMs, setActiveRunIdValue, setReasoningSteps, transport]);
|
|
1235
|
-
|
|
1236
|
-
refresh().catch(() =>
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
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();
|
|
1344
|
+
useEffect2(() => {
|
|
1345
|
+
refresh().catch((error) => {
|
|
1346
|
+
if (!isAbortError(error) && !lifecycleAbortController.signal.aborted) {
|
|
1347
|
+
setThreads(storage.listThreads());
|
|
1348
|
+
}
|
|
1349
|
+
});
|
|
1350
|
+
}, [lifecycleAbortController, refresh, storage]);
|
|
1351
|
+
useEffect2(() => {
|
|
1264
1352
|
return () => {
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1353
|
+
lifecycleAbortController.abort();
|
|
1354
|
+
abortDetachedStream(abortControllerRef.current);
|
|
1355
|
+
abortControllerRef.current = null;
|
|
1268
1356
|
};
|
|
1269
|
-
}, [
|
|
1270
|
-
|
|
1357
|
+
}, [lifecycleAbortController]);
|
|
1358
|
+
useControllerThreadEvents({ transport, storage, refresh, setThreads });
|
|
1359
|
+
useEffect2(() => {
|
|
1271
1360
|
syncThreadsFromStorage();
|
|
1272
1361
|
}, [storageKey, syncThreadsFromStorage]);
|
|
1273
|
-
|
|
1362
|
+
useEffect2(() => {
|
|
1274
1363
|
const previous = activeThreadIdRef.current;
|
|
1275
1364
|
activeThreadIdRef.current = activeThreadId;
|
|
1276
1365
|
if (activeThreadId && previous === activeThreadId && (activeRunIdRef.current || streamingMessageIdRef.current)) {
|
|
@@ -1314,14 +1403,14 @@ function useAgents24ChatController({
|
|
|
1314
1403
|
}
|
|
1315
1404
|
void loadThread(activeThreadId).catch(() => setLoadingHistory(false));
|
|
1316
1405
|
}, [activeThreadId, detachActiveStream, loadThread, setLoadingHistory, storage]);
|
|
1317
|
-
|
|
1406
|
+
useEffect2(() => {
|
|
1318
1407
|
const threadId = activeThreadId;
|
|
1319
1408
|
if (!threadId || isLoadingHistory || loadedThreadIdRef.current !== threadId || activeRunIdRef.current) return;
|
|
1320
1409
|
const runId = autoAttachRunIdFromThread(storage.getThread(threadId));
|
|
1321
1410
|
if (!runId || reattachedRunIdRef.current === runId) return;
|
|
1322
1411
|
void runStream({ mode: "attach", threadId, runId });
|
|
1323
1412
|
}, [activeThreadId, isLoadingHistory, runStream, storage, storageKey]);
|
|
1324
|
-
|
|
1413
|
+
useEffect2(() => {
|
|
1325
1414
|
const threadId = activeThreadId;
|
|
1326
1415
|
if (!threadId || isLoadingHistoryRef.current || activeRunIdRef.current || streamingMessageIdRef.current) {
|
|
1327
1416
|
return;
|
|
@@ -1362,7 +1451,7 @@ function useAgents24ChatController({
|
|
|
1362
1451
|
setMessages,
|
|
1363
1452
|
storage
|
|
1364
1453
|
});
|
|
1365
|
-
const loadThreadById =
|
|
1454
|
+
const loadThreadById = useCallback3(
|
|
1366
1455
|
async (threadId) => {
|
|
1367
1456
|
if (!threadId) return;
|
|
1368
1457
|
setIsSelectingThread(true);
|
|
@@ -1381,7 +1470,7 @@ function useAgents24ChatController({
|
|
|
1381
1470
|
[detachActiveStream, loadThread, onActiveThreadIdChange, storage]
|
|
1382
1471
|
);
|
|
1383
1472
|
const activeThread = activeThreadId ? storage.getThread(activeThreadId) || threads.find((thread) => thread.id === activeThreadId) || null : null;
|
|
1384
|
-
return
|
|
1473
|
+
return useMemo2(() => ({
|
|
1385
1474
|
threads,
|
|
1386
1475
|
activeThreadId,
|
|
1387
1476
|
activeThread,
|
|
@@ -1401,8 +1490,11 @@ function useAgents24ChatController({
|
|
|
1401
1490
|
copiedMessageId,
|
|
1402
1491
|
lastThinkingDurationMs,
|
|
1403
1492
|
activeRunId,
|
|
1493
|
+
pendingHitl,
|
|
1494
|
+
isResolvingHitl,
|
|
1404
1495
|
handleSubmit,
|
|
1405
1496
|
attachRun,
|
|
1497
|
+
resumeHitl,
|
|
1406
1498
|
handleStop,
|
|
1407
1499
|
handleCopy,
|
|
1408
1500
|
handleLike,
|
|
@@ -1441,8 +1533,11 @@ function useAgents24ChatController({
|
|
|
1441
1533
|
loadThreadById,
|
|
1442
1534
|
loadOlderTurns,
|
|
1443
1535
|
messages,
|
|
1536
|
+
isResolvingHitl,
|
|
1444
1537
|
onSourceClick,
|
|
1538
|
+
pendingHitl,
|
|
1445
1539
|
refresh,
|
|
1540
|
+
resumeHitl,
|
|
1446
1541
|
streamingContent,
|
|
1447
1542
|
streamingMessageId,
|
|
1448
1543
|
startNewThread,
|
|
@@ -1531,7 +1626,9 @@ var consumeSseResponse = async (response, onEvent, options = {}) => {
|
|
|
1531
1626
|
let closed = false;
|
|
1532
1627
|
const closeReader = () => {
|
|
1533
1628
|
closed = true;
|
|
1534
|
-
|
|
1629
|
+
queueMicrotask(() => {
|
|
1630
|
+
void reader.cancel().catch(() => {
|
|
1631
|
+
});
|
|
1535
1632
|
});
|
|
1536
1633
|
};
|
|
1537
1634
|
if (options.signal?.aborted) {
|
|
@@ -1621,7 +1718,7 @@ var createFetchChatTransport = ({
|
|
|
1621
1718
|
}
|
|
1622
1719
|
)
|
|
1623
1720
|
});
|
|
1624
|
-
return consumeSseResponse(response, onEvent);
|
|
1721
|
+
return consumeSseResponse(response, onEvent, { signal: input.signal });
|
|
1625
1722
|
},
|
|
1626
1723
|
async attachRun(input, onEvent) {
|
|
1627
1724
|
const response = await fetchImpl(routes.attachRun(input), {
|
|
@@ -1630,7 +1727,7 @@ var createFetchChatTransport = ({
|
|
|
1630
1727
|
headers: streamHeaders(await loadHeaders()),
|
|
1631
1728
|
body: JSON.stringify({})
|
|
1632
1729
|
});
|
|
1633
|
-
return consumeSseResponse(response, onEvent);
|
|
1730
|
+
return consumeSseResponse(response, onEvent, { signal: input.signal });
|
|
1634
1731
|
},
|
|
1635
1732
|
async cancelRun(input) {
|
|
1636
1733
|
const response = await fetchImpl(routes.cancelRun(input), {
|
|
@@ -1657,7 +1754,11 @@ var createFetchChatTransport = ({
|
|
|
1657
1754
|
signal: input.signal,
|
|
1658
1755
|
headers: streamHeaders(await loadHeaders())
|
|
1659
1756
|
});
|
|
1660
|
-
await consumeSseResponse(
|
|
1757
|
+
await consumeSseResponse(
|
|
1758
|
+
response,
|
|
1759
|
+
(event) => onEvent(event),
|
|
1760
|
+
{ signal: input.signal }
|
|
1761
|
+
);
|
|
1661
1762
|
},
|
|
1662
1763
|
async resumeHitl(input) {
|
|
1663
1764
|
if (!routes.resumeRun) throw new ChatTransportError("HITL resume is not configured.", 501);
|
|
@@ -1665,9 +1766,10 @@ var createFetchChatTransport = ({
|
|
|
1665
1766
|
method: "POST",
|
|
1666
1767
|
headers: jsonHeaders(await loadHeaders()),
|
|
1667
1768
|
body: JSON.stringify({
|
|
1668
|
-
schema_version: "agents24.hitl.resume.
|
|
1769
|
+
schema_version: "agents24.hitl.resume.v2",
|
|
1669
1770
|
interrupt_id: input.interruptId,
|
|
1670
|
-
|
|
1771
|
+
action: input.action,
|
|
1772
|
+
comment: input.comment,
|
|
1671
1773
|
client: input.client
|
|
1672
1774
|
})
|
|
1673
1775
|
});
|
|
@@ -1741,7 +1843,6 @@ export {
|
|
|
1741
1843
|
partsFromResponseBlocks,
|
|
1742
1844
|
reasoningStepsFromParts,
|
|
1743
1845
|
renderChatPart,
|
|
1744
|
-
textFromFinalOutput,
|
|
1745
1846
|
threadActivityDate,
|
|
1746
1847
|
threadDetailToMessages,
|
|
1747
1848
|
threadPaging,
|