@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/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 === "approval_request") {
308
- parts.push({ id, type: "approval", kind: "approval", raw: block });
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 setActiveRunIdValue = useCallback((runId) => {
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 = useCallback(() => {
720
+ const syncThreadsFromStorage = useCallback2(() => {
504
721
  setThreads(storage.listThreads());
505
722
  }, [storage]);
506
- const upsertStoredThread = useCallback(
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 = useCallback(
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 = useCallback(
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 = useCallback(async () => {
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 = useCallback(
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 = useCallback((value) => {
807
+ const setLoadingHistory = useCallback2((value) => {
589
808
  isLoadingHistoryRef.current = value;
590
809
  setIsLoadingHistory(value);
591
810
  }, []);
592
- const setReasoningSteps = useCallback((value) => {
811
+ const setReasoningSteps = useCallback2((value) => {
593
812
  reasoningRef.current = value || [];
594
813
  setCurrentReasoning(value || []);
595
814
  }, []);
596
- const detachActiveStream = useCallback(() => {
815
+ const detachActiveStream = useCallback2(() => {
597
816
  const controller = abortControllerRef.current;
598
817
  abortControllerRef.current = null;
599
- controller?.abort();
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 setLiveAssistantMessage = useCallback(
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 index = prev.findIndex(
614
- (message) => message.id === input.messageId || input.runId && message.role === "assistant" && message.runId === input.runId
615
- );
616
- if (index === -1) {
617
- const next2 = [...prev];
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
- messagesRef.current = next2;
635
- return next2;
636
- }
637
- const next = [...prev];
638
- next[index] = {
639
- ...next[index],
640
- runId: input.runId ?? next[index].runId ?? null,
641
- content: input.content,
642
- reasoningSteps: input.reasoning,
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 = useCallback(
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 = existingIndex >= 0 ? input.baseMessages.map((message, index) => index === existingIndex ? assistant : message) : [...input.baseMessages, assistant];
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 = useCallback(
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 = useCallback(async () => {
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 = useCallback(
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 = useCallback(
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.name !== "AbortError") {
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 = useCallback(
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 handleStop = useCallback(() => {
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?.abort();
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 = activeRunIdFromThread(storage.getThread(threadId));
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 handleCopy = useCallback((content, messageId) => {
1034
- navigator.clipboard?.writeText(content);
1035
- setCopiedMessageId(messageId);
1036
- setTimeout(() => setCopiedMessageId(null), 200);
1037
- }, []);
1038
- const handleLike = useCallback(async (msg) => {
1039
- const nextLiked = !liked[msg.id];
1040
- setLiked((prev) => ({ ...prev, [msg.id]: nextLiked }));
1041
- if (nextLiked) setDisliked((prev) => ({ ...prev, [msg.id]: false }));
1042
- }, [liked]);
1043
- const handleDislike = useCallback(async (msg) => {
1044
- const nextDisliked = !disliked[msg.id];
1045
- setDisliked((prev) => ({ ...prev, [msg.id]: nextDisliked }));
1046
- if (nextDisliked) setLiked((prev) => ({ ...prev, [msg.id]: false }));
1047
- }, [disliked]);
1048
- const handleRetry = useCallback(async (msg) => {
1049
- const index = messagesRef.current.findIndex((message) => message.id === msg.id);
1050
- if (index <= 0) return;
1051
- const userMessage = messagesRef.current[index - 1];
1052
- if (userMessage.role !== "user") return;
1053
- const trimmed = messagesRef.current.slice(0, index);
1054
- setMessages(trimmed);
1055
- messagesRef.current = trimmed;
1056
- if (activeThreadIdRef.current) persistThread(activeThreadIdRef.current, trimmed);
1057
- await handleSubmit({ text: userMessage.content, files: userMessage.attachments || [] });
1058
- }, [handleSubmit, persistThread]);
1059
- const upsertLiveVoiceMessage = useCallback((input) => {
1060
- const content = input.content?.trim() ?? "";
1061
- if (!content && !input.citations?.length && !input.reasoningSteps?.length) return;
1062
- setMessages((prev) => {
1063
- const currentId = liveVoiceIdsRef.current[input.role];
1064
- const index = currentId ? prev.findIndex((message) => message.id === currentId) : -1;
1065
- const nextMessage = {
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
- await loadThread(threadId);
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 === "approval") {
1209
- return /* @__PURE__ */ jsx("div", { "data-agents24-approval-part": true });
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
- return /* @__PURE__ */ jsx("div", { "data-agents24-data-part": part.name });
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
- while (true) {
1249
- const { value, done } = await reader.read();
1250
- if (done) break;
1251
- buffer += decoder.decode(value, { stream: true });
1252
- let boundary = buffer.indexOf("\n\n");
1253
- while (boundary !== -1) {
1254
- const block = buffer.slice(0, boundary);
1255
- buffer = buffer.slice(boundary + 2);
1256
- boundary = buffer.indexOf("\n\n");
1257
- const event = parseSseBlock(block);
1258
- if (!event) continue;
1259
- if (event.run_id) runId = event.run_id;
1260
- const payloadThreadId = event.payload?.thread_id;
1261
- if (payloadThreadId) threadId = String(payloadThreadId);
1262
- await onEvent(event);
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 new Error(response.statusText || "Failed to list chat threads.");
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 new Error(response.statusText || "Failed to load chat thread.");
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 new Error(response.statusText || "Failed to cancel chat run.");
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 new Error(response.statusText || "Failed to delete chat thread.");
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,