@agents24/react 0.2.7 → 0.2.8

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
@@ -37,6 +37,24 @@ function getThreadActivity(thread) {
37
37
  };
38
38
  }
39
39
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
40
+ function computerResourceTargets(value) {
41
+ const output = record(value);
42
+ if (!output) return [];
43
+ const candidates = [
44
+ output.resource_target,
45
+ ...Array.isArray(output.resource_targets) ? output.resource_targets : [],
46
+ ...Array.isArray(output.files) ? output.files.map((file) => record(file)?.resource_target) : []
47
+ ];
48
+ const seen = /* @__PURE__ */ new Set();
49
+ return candidates.flatMap((candidate) => {
50
+ const target = record(candidate);
51
+ if (target?.type !== "computer" || typeof target.thread_id !== "string" || typeof target.computer_id !== "string" || typeof target.path !== "string" || !target.path.startsWith("/workspace") || target.kind !== "file" && target.kind !== "directory") return [];
52
+ const key = `${target.thread_id}:${target.computer_id}:${target.path}:${target.kind}`;
53
+ if (seen.has(key)) return [];
54
+ seen.add(key);
55
+ return [target];
56
+ });
57
+ }
40
58
  var text = (value) => typeof value === "string" && value.trim() ? value : null;
41
59
  var finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
42
60
  var statusToToolState = (value) => {
@@ -200,6 +218,7 @@ function partsFromResponseBlocks(blocks, fallbackText = "", fallbackPartId = "as
200
218
  state: statusToToolState(block.status),
201
219
  input: tool.input,
202
220
  output: tool.output,
221
+ resourceTargets: computerResourceTargets(tool.output),
203
222
  errorText: text(tool.error || block.error),
204
223
  failure: failureView(tool.failure),
205
224
  presentation: toolPresentation(tool)
@@ -369,6 +388,21 @@ function threadDetailToMessages(detail) {
369
388
  return output;
370
389
  });
371
390
  }
391
+ function threadTimelineToMessages(items, detail) {
392
+ const feedbackByRun = new Map((detail?.turns || []).flatMap((turn) => turn.run_id && turn.feedback ? [[turn.run_id, turn.feedback]] : []));
393
+ return items.map((item) => ({
394
+ id: item.id,
395
+ role: item.role,
396
+ runId: item.run_id,
397
+ segmentIndex: item.segment_index,
398
+ content: item.text,
399
+ createdAt: item.created_at,
400
+ isFinal: item.status === "completed" || item.status === "delivered",
401
+ parts: item.role === "assistant" ? partsFromResponseBlocks(item.response_blocks, item.text, `${item.id}-text`) : item.text ? [{ id: `${item.id}-text`, type: "text", kind: "text", text: item.text }] : [],
402
+ attachments: item.role === "user" ? item.attachment_ids.map((id) => ({ id, filename: "Attachment" })) : void 0,
403
+ feedback: item.role === "assistant" && item.run_id ? feedbackByRun.get(item.run_id) : void 0
404
+ }));
405
+ }
372
406
  function latestThreadContext(detail) {
373
407
  let contextWindow = null;
374
408
  for (const turn of detail.turns || []) {
@@ -478,10 +512,18 @@ function clearAgentChatBackgroundError(state) {
478
512
  return state.backgroundError ? { ...state, backgroundError: null } : state;
479
513
  }
480
514
  function reconcileRefreshedThreads(current, incoming, activeThreadId, concurrentEvents = []) {
515
+ const currentById = new Map(current.map((thread) => [thread.id, thread]));
516
+ const newest = incoming.map((thread) => preferNewestThreadSummary(currentById.get(thread.id), thread));
481
517
  const active = activeThreadId && !incoming.some((thread) => thread.id === activeThreadId) ? current.find((thread) => thread.id === activeThreadId) : null;
482
- const base = active?.isHydrated ? [active, ...incoming] : incoming;
518
+ const base = active?.isHydrated ? [active, ...newest] : newest;
483
519
  return concurrentEvents.reduce(applyThreadSummaryEvent, base);
484
520
  }
521
+ function preferNewestThreadSummary(current, incoming) {
522
+ if (!current?.updated_at || !incoming.updated_at) return incoming;
523
+ const currentTime = Date.parse(current.updated_at);
524
+ const incomingTime = Date.parse(incoming.updated_at);
525
+ return Number.isFinite(currentTime) && Number.isFinite(incomingTime) && currentTime > incomingTime ? current : incoming;
526
+ }
485
527
  function adoptStreamingThread(state, threadId) {
486
528
  const normalizedThreadId = threadId.trim();
487
529
  if (!normalizedThreadId || state.activeThreadId) return state;
@@ -507,13 +549,20 @@ function appendStableStreamingTurn(messages, userMessage, assistantMessage) {
507
549
  ))) next.push(assistantMessage);
508
550
  return next;
509
551
  }
552
+ function preserveMountedMessageIds(canonical, current) {
553
+ return canonical.map((message) => {
554
+ const mounted = current.find((item) => item.role === message.role && (item.durableId === message.id || item.id === message.id || message.role === "assistant" && message.segmentIndex === 1 && item.runId && item.runId === message.runId && !item.durableId));
555
+ return mounted && mounted.id !== message.id ? { ...message, id: mounted.id, durableId: message.id } : message;
556
+ });
557
+ }
510
558
  function upsertStableAssistantMessage(messages, input) {
511
559
  const next = [...messages];
512
560
  for (const baseMessage of input.baseMessages || []) {
513
- const isTarget = baseMessage.role === "assistant" && (Boolean(input.messageId && baseMessage.id === input.messageId) || Boolean(input.runId && baseMessage.runId === input.runId));
561
+ const isTarget = baseMessage.role === "assistant" && (Boolean(input.messageId && baseMessage.id === input.messageId) || Boolean(!input.strictMessageId && input.runId && baseMessage.runId === input.runId));
514
562
  if (!isTarget && !next.some((message) => message.id === baseMessage.id)) next.push(baseMessage);
515
563
  }
516
- const index = next.findIndex((message) => message.role === "assistant" && (Boolean(input.messageId && message.id === input.messageId) || Boolean(input.runId && message.runId === input.runId)));
564
+ const exactIndex = next.findIndex((message) => message.role === "assistant" && Boolean(input.messageId && message.id === input.messageId));
565
+ const index = exactIndex >= 0 ? exactIndex : input.strictMessageId ? -1 : next.findIndex((message) => message.role === "assistant" && Boolean(input.runId && message.runId === input.runId));
517
566
  if (index === -1) next.push(input.create());
518
567
  else next[index] = input.update(next[index]);
519
568
  return next;
@@ -559,12 +608,14 @@ function applyRuntimeEvent(state, event) {
559
608
  const projection = assistantProjectionFromEvent(event);
560
609
  const eventName = String(event.event || "");
561
610
  const runId = event.run_id || state.activeRunId;
562
- const messageId = state.streamingMessageId || `${runId || "run"}-assistant`;
611
+ const segmentId = typeof event.payload?.segment_id === "string" ? event.payload.segment_id : null;
612
+ const distinctSegment = Boolean(segmentId && segmentId !== `${runId}:0`);
613
+ const messageId = distinctSegment ? `${runId || "run"}-assistant-${segmentId}` : state.streamingMessageId || `${runId || "run"}-assistant`;
563
614
  const terminal = eventName === "run.completed" || eventName === "run.failed" || eventName === "run.cancelled";
564
615
  const paused = eventName === "run.paused" || projection.parts.some((part) => part.kind === "hitl" && part.status === "pending");
565
616
  const suspension = eventName === "run.waiting" ? "waiting" : eventName === "run.interrupted" ? "interrupted" : null;
566
617
  const cancelling = eventName === "run.cancelling" || String(event.payload?.status || "") === "cancelling";
567
- const existing = state.messages.find((message) => message.role === "assistant" && (message.id === messageId || Boolean(runId && message.runId === runId)));
618
+ const existing = state.messages.find((message) => message.role === "assistant" && message.id === messageId) || (!distinctSegment ? state.messages.find((message) => message.role === "assistant" && Boolean(runId && message.runId === runId)) : void 0);
568
619
  const payload = event.payload || {};
569
620
  const eventThreadId = typeof payload.thread_id === "string" && payload.thread_id.trim() ? payload.thread_id.trim() : null;
570
621
  const contextApplies = (!state.activeThreadId || !eventThreadId || eventThreadId === state.activeThreadId) && (!state.activeRunId || !runId || runId === state.activeRunId);
@@ -591,6 +642,7 @@ function applyRuntimeEvent(state, event) {
591
642
  const messages = upsertStableAssistantMessage(state.messages, {
592
643
  messageId,
593
644
  runId,
645
+ strictMessageId: distinctSegment,
594
646
  create: () => ({
595
647
  id: messageId,
596
648
  role: "assistant",
@@ -663,12 +715,153 @@ function applyThreadSummaryEvent(threads, event) {
663
715
  const index = threads.findIndex((thread) => thread.id === incoming.id);
664
716
  if (index === -1) return [{ ...incoming, messages: incoming.messages || [], isHydrated: false }, ...threads];
665
717
  const next = [...threads];
666
- next[index] = { ...next[index], ...incoming, messages: next[index].messages, isHydrated: next[index].isHydrated };
718
+ next[index] = preferNewestThreadSummary(next[index], {
719
+ ...next[index],
720
+ ...incoming,
721
+ messages: next[index].messages,
722
+ isHydrated: next[index].isHydrated
723
+ });
667
724
  return next;
668
725
  }
669
726
 
670
- // src/use-agent-chat.ts
727
+ // src/thread-message-queue.ts
671
728
  import * as React2 from "react";
729
+ function useThreadMessageQueue({ threadId, transport, onPromoted }) {
730
+ const [pendingMessages, setPendingMessages] = React2.useState([]);
731
+ const [messageQueue, setMessageQueue] = React2.useState(null);
732
+ const pendingRef = React2.useRef(/* @__PURE__ */ new Map());
733
+ const versionRef = React2.useRef(0);
734
+ const cursorRef = React2.useRef(0);
735
+ const promotedRef = React2.useRef(/* @__PURE__ */ new Set());
736
+ const threadRef = React2.useRef(threadId);
737
+ const transportRef = React2.useRef(transport);
738
+ const onPromotedRef = React2.useRef(onPromoted);
739
+ threadRef.current = threadId;
740
+ transportRef.current = transport;
741
+ onPromotedRef.current = onPromoted;
742
+ const hydrate = React2.useCallback((page, selectedThreadId) => {
743
+ if (threadRef.current !== selectedThreadId) return;
744
+ const pending = page.pending.filter((message) => !promotedRef.current.has(message.id));
745
+ for (const message of pending) pendingRef.current.set(message.id, message);
746
+ setPendingMessages(pending);
747
+ setMessageQueue(page.queue);
748
+ }, []);
749
+ const refresh = React2.useCallback(async (selectedThreadId = threadRef.current) => {
750
+ if (!selectedThreadId) return null;
751
+ const version = ++versionRef.current;
752
+ const page = await transportRef.current.list(selectedThreadId);
753
+ if (version !== versionRef.current || threadRef.current !== selectedThreadId) return null;
754
+ for (const item of page.items) {
755
+ if (item.role === "user" && item.status === "delivered" && item.run_id) {
756
+ const pending = pendingRef.current.get(item.id);
757
+ if (pending && !promotedRef.current.has(item.id)) {
758
+ promotedRef.current.add(item.id);
759
+ pendingRef.current.delete(item.id);
760
+ onPromotedRef.current(pending, item.run_id);
761
+ }
762
+ }
763
+ }
764
+ hydrate(page, selectedThreadId);
765
+ return page;
766
+ }, [hydrate]);
767
+ const acceptNotice = React2.useCallback((notice) => {
768
+ if (notice.cursor && notice.cursor <= cursorRef.current) return false;
769
+ if (notice.cursor) cursorRef.current = notice.cursor;
770
+ const pending = pendingRef.current.get(notice.messageId);
771
+ if (notice.status === "delivered" && notice.runId && pending && !promotedRef.current.has(pending.id)) {
772
+ promotedRef.current.add(pending.id);
773
+ pendingRef.current.delete(pending.id);
774
+ versionRef.current += 1;
775
+ setPendingMessages((current) => current.filter((message) => message.id !== pending.id));
776
+ onPromotedRef.current(pending, notice.runId);
777
+ }
778
+ if (notice.status === "removed") pendingRef.current.delete(notice.messageId);
779
+ void refresh().catch(() => void 0);
780
+ return true;
781
+ }, [refresh]);
782
+ const submit = React2.useCallback(async (input) => {
783
+ const selectedThreadId = threadRef.current;
784
+ if (!selectedThreadId) throw new Error("No active Agent thread is available.");
785
+ const result = await transportRef.current.submit({ ...input, threadId: selectedThreadId });
786
+ if (threadRef.current !== selectedThreadId) return result;
787
+ versionRef.current += 1;
788
+ if (!result.run_id && ["queued", "steer_pending", "blocked"].includes(result.status)) {
789
+ const now = (/* @__PURE__ */ new Date()).toISOString();
790
+ const pending = {
791
+ id: result.message_id,
792
+ thread_id: selectedThreadId,
793
+ sequence: Number.MAX_SAFE_INTEGER,
794
+ run_id: null,
795
+ turn_id: null,
796
+ segment_index: null,
797
+ role: "user",
798
+ status: result.status,
799
+ requested_mode: input.mode,
800
+ text: input.input,
801
+ attachment_ids: input.attachmentIds || [],
802
+ response_blocks: [],
803
+ revision: 1,
804
+ created_at: now,
805
+ updated_at: now
806
+ };
807
+ pendingRef.current.set(pending.id, pending);
808
+ setPendingMessages((current) => current.some((message) => message.id === pending.id) ? current : [...current, pending]);
809
+ setMessageQueue((current) => ({
810
+ pending_count: Math.max(current?.pending_count || 0, result.queue_position || 1),
811
+ paused: current?.paused || false,
812
+ reason: current?.reason || null
813
+ }));
814
+ }
815
+ void refresh().catch(() => void 0);
816
+ return result;
817
+ }, [refresh]);
818
+ const edit = React2.useCallback(async (message, input) => {
819
+ const updated = await transportRef.current.edit(message, input);
820
+ versionRef.current += 1;
821
+ pendingRef.current.set(updated.id, updated);
822
+ await refresh(message.thread_id);
823
+ return updated;
824
+ }, [refresh]);
825
+ const remove = React2.useCallback(async (message) => {
826
+ await transportRef.current.remove(message);
827
+ versionRef.current += 1;
828
+ pendingRef.current.delete(message.id);
829
+ setPendingMessages((current) => current.filter((item) => item.id !== message.id));
830
+ await refresh(message.thread_id);
831
+ }, [refresh]);
832
+ const retractForEdit = React2.useCallback(async (message) => {
833
+ await remove(message);
834
+ return { text: message.text, attachment_ids: message.attachment_ids, attachments: message.attachments };
835
+ }, [remove]);
836
+ const resume = React2.useCallback(async () => {
837
+ const selectedThreadId = threadRef.current;
838
+ if (!selectedThreadId) return null;
839
+ const result = await transportRef.current.resume(selectedThreadId);
840
+ await refresh(selectedThreadId);
841
+ return result;
842
+ }, [refresh]);
843
+ React2.useEffect(() => {
844
+ versionRef.current += 1;
845
+ cursorRef.current = 0;
846
+ promotedRef.current.clear();
847
+ pendingRef.current.clear();
848
+ setPendingMessages([]);
849
+ setMessageQueue(null);
850
+ if (threadId) void refresh(threadId).catch(() => void 0);
851
+ }, [threadId, refresh]);
852
+ React2.useEffect(() => {
853
+ if (!threadId || !pendingMessages.length || messageQueue?.paused) return;
854
+ const clock = globalThis;
855
+ const timer = clock.setInterval(() => {
856
+ void refresh(threadId).catch(() => void 0);
857
+ }, 3e3);
858
+ return () => clock.clearInterval(timer);
859
+ }, [threadId, pendingMessages.length, messageQueue?.paused, refresh]);
860
+ return { pendingMessages, messageQueue, refresh, hydrate, acceptNotice, submit, edit, remove, retractForEdit, resume };
861
+ }
862
+
863
+ // src/use-agent-chat.ts
864
+ import * as React6 from "react";
672
865
 
673
866
  // src/runtime-event-buffer.ts
674
867
  function runtimeEventFrameScheduler() {
@@ -770,6 +963,167 @@ async function subscribeToThreadSummaries(input) {
770
963
  }
771
964
  }
772
965
 
966
+ // src/use-agent-chat-thread-list.ts
967
+ import * as React3 from "react";
968
+ function summaryToStored(thread) {
969
+ return { ...thread, id: String(thread.id), messages: [], isHydrated: false };
970
+ }
971
+ function useAgentChatThreadList({ client, threadPageSize, stateRef, update, clearOperationError, failOperation }) {
972
+ const [isLoadingThreads, setIsLoadingThreads] = React3.useState(false);
973
+ const [isLoadingMoreThreads, setIsLoadingMoreThreads] = React3.useState(false);
974
+ const [threadTotal, setThreadTotal] = React3.useState();
975
+ const refreshPromiseRef = React3.useRef(null);
976
+ const eventsDuringRefreshRef = React3.useRef(null);
977
+ const refreshThreads = React3.useCallback(() => {
978
+ if (refreshPromiseRef.current) return refreshPromiseRef.current;
979
+ clearOperationError();
980
+ setIsLoadingThreads(true);
981
+ eventsDuringRefreshRef.current = [];
982
+ const request = (async () => {
983
+ try {
984
+ const result = await client.threads.list({ limit: threadPageSize });
985
+ const concurrentEvents = eventsDuringRefreshRef.current || [];
986
+ eventsDuringRefreshRef.current = null;
987
+ setThreadTotal(result.total);
988
+ update((current) => {
989
+ const byId = new Map(current.threads.map((thread) => [thread.id, thread]));
990
+ const incoming = result.items.map((item) => {
991
+ const existing = byId.get(item.id);
992
+ return { ...existing, ...summaryToStored(item), messages: existing?.messages || [], isHydrated: existing?.isHydrated };
993
+ });
994
+ return { ...current, threads: reconcileRefreshedThreads(current.threads, incoming, current.activeThreadId, concurrentEvents) };
995
+ });
996
+ } catch (error) {
997
+ if (!isAbortLike(error)) {
998
+ failOperation("refresh_threads", error);
999
+ throw error;
1000
+ }
1001
+ } finally {
1002
+ eventsDuringRefreshRef.current = null;
1003
+ setIsLoadingThreads(false);
1004
+ }
1005
+ })();
1006
+ refreshPromiseRef.current = request;
1007
+ void request.finally(() => {
1008
+ if (refreshPromiseRef.current === request) refreshPromiseRef.current = null;
1009
+ }).catch(() => void 0);
1010
+ return request;
1011
+ }, [clearOperationError, client, failOperation, threadPageSize, update]);
1012
+ const loadMoreThreads = React3.useCallback(async () => {
1013
+ if (isLoadingMoreThreads) return;
1014
+ const skip = stateRef.current.threads.length;
1015
+ if (threadTotal !== void 0 && skip >= threadTotal) return;
1016
+ clearOperationError();
1017
+ setIsLoadingMoreThreads(true);
1018
+ try {
1019
+ const result = await client.threads.list({ skip, limit: threadPageSize });
1020
+ setThreadTotal(result.total);
1021
+ update((current) => {
1022
+ const byId = new Map(current.threads.map((thread) => [thread.id, thread]));
1023
+ for (const item of result.items) {
1024
+ const existing = byId.get(item.id);
1025
+ byId.set(item.id, preferNewestThreadSummary(existing, {
1026
+ ...existing,
1027
+ ...summaryToStored(item),
1028
+ messages: existing?.messages || [],
1029
+ isHydrated: existing?.isHydrated
1030
+ }));
1031
+ }
1032
+ return { ...current, threads: [...byId.values()] };
1033
+ });
1034
+ } catch (error) {
1035
+ if (!isAbortLike(error)) {
1036
+ failOperation("load_more_threads", error);
1037
+ throw error;
1038
+ }
1039
+ } finally {
1040
+ setIsLoadingMoreThreads(false);
1041
+ }
1042
+ }, [clearOperationError, client, failOperation, isLoadingMoreThreads, stateRef, threadPageSize, threadTotal, update]);
1043
+ return { isLoadingThreads, isLoadingMoreThreads, threadTotal, eventsDuringRefreshRef, refreshThreads, loadMoreThreads };
1044
+ }
1045
+
1046
+ // src/use-agent-chat-feedback.ts
1047
+ import * as React4 from "react";
1048
+ function useAgentChatFeedback({ client, update, clearOperationError, failOperation }) {
1049
+ return React4.useCallback(async (message, input) => {
1050
+ const runId = message.runId;
1051
+ if (!runId || message.role !== "assistant" || message.isFinal === false) {
1052
+ const error = new Error("Only persisted final responses can receive feedback.");
1053
+ failOperation("set_feedback", error);
1054
+ throw error;
1055
+ }
1056
+ const previous = message.feedback ?? null;
1057
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1058
+ const optimistic = input.rating ? {
1059
+ rating: input.rating,
1060
+ reason: input.rating === "dislike" ? input.reason ?? null : null,
1061
+ comment: input.rating === "dislike" ? input.comment?.trim() || null : null,
1062
+ created_at: previous?.created_at || timestamp,
1063
+ updated_at: timestamp
1064
+ } : null;
1065
+ const replaceFeedback = (current, feedback) => ({
1066
+ ...current,
1067
+ messages: current.messages.map((item) => item.id === message.id ? { ...item, feedback } : item),
1068
+ threads: current.threads.map((thread) => ({
1069
+ ...thread,
1070
+ messages: thread.messages.map((item) => item.id === message.id ? { ...item, feedback } : item)
1071
+ }))
1072
+ });
1073
+ clearOperationError();
1074
+ update((current) => replaceFeedback(current, optimistic));
1075
+ try {
1076
+ const result = await client.runs.setFeedback({ runId, ...input });
1077
+ update((current) => replaceFeedback(current, result.feedback));
1078
+ } catch (error) {
1079
+ update((current) => withAgentChatOperationError(replaceFeedback(current, previous), "set_feedback", error));
1080
+ throw error;
1081
+ }
1082
+ }, [clearOperationError, client, failOperation, update]);
1083
+ }
1084
+
1085
+ // src/use-agent-chat-queue-actions.ts
1086
+ import * as React5 from "react";
1087
+ function useAgentChatQueueActions({
1088
+ queue,
1089
+ stateRef,
1090
+ createId,
1091
+ clearOperationError,
1092
+ failOperation,
1093
+ refreshMessages,
1094
+ attachRun
1095
+ }) {
1096
+ const steer = React5.useCallback(async (text2) => {
1097
+ const threadId = stateRef.current.activeThreadId;
1098
+ if (!threadId || !text2.trim()) throw new Error("An active thread and text are required to steer.");
1099
+ clearOperationError();
1100
+ try {
1101
+ await queue.submit({ mode: "steer", input: text2.trim(), idempotencyKey: createId() });
1102
+ await refreshMessages(threadId);
1103
+ } catch (error) {
1104
+ failOperation("submit", error);
1105
+ throw error;
1106
+ }
1107
+ }, [clearOperationError, createId, failOperation, queue.submit, refreshMessages, stateRef]);
1108
+ const editPendingMessage = React5.useCallback(async (message, input) => {
1109
+ await queue.edit(message, {
1110
+ text: input.text,
1111
+ attachmentIds: input.attachmentIds ?? message.attachment_ids,
1112
+ requestedAgentId: input.requestedAgentId,
1113
+ requestedModelId: input.requestedModelId
1114
+ });
1115
+ }, [queue.edit]);
1116
+ const steerPendingMessage = React5.useCallback(async (message) => {
1117
+ await queue.edit(message, { text: message.text, attachmentIds: message.attachment_ids, mode: "steer" });
1118
+ }, [queue.edit]);
1119
+ const resumeQueue = React5.useCallback(async () => {
1120
+ if (!stateRef.current.activeThreadId) return;
1121
+ const result = await queue.resume();
1122
+ if (result?.run_id) void attachRun(result.run_id).catch(() => void 0);
1123
+ }, [attachRun, queue.resume, stateRef]);
1124
+ return { steer, editPendingMessage, steerPendingMessage, removePendingMessage: queue.remove, resumeQueue };
1125
+ }
1126
+
773
1127
  // src/initial-thread.ts
774
1128
  function resolveInitialActiveThreadId(options, storedThreadId) {
775
1129
  return Object.prototype.hasOwnProperty.call(options, "activeThreadId") ? options.activeThreadId ?? null : storedThreadId;
@@ -782,9 +1136,6 @@ function isCurrentThreadOpenRequest(input) {
782
1136
  }
783
1137
 
784
1138
  // src/use-agent-chat.ts
785
- function summaryToStored(thread) {
786
- return { ...thread, id: String(thread.id), messages: [], isHydrated: false };
787
- }
788
1139
  function activeRunIdFromThread(thread) {
789
1140
  if (!thread) return null;
790
1141
  const activity = getThreadActivity(thread);
@@ -795,7 +1146,7 @@ function useAgentChat(options = {}) {
795
1146
  const client = options.client || runtime.client;
796
1147
  const pageSize = options.pageSize ?? 30;
797
1148
  const threadPageSize = options.threadPageSize ?? 50;
798
- const [state, setState] = React2.useState(() => ({
1149
+ const [state, setState] = React6.useState(() => ({
799
1150
  ...initialAgentChatState,
800
1151
  activeThreadId: resolveInitialActiveThreadId(
801
1152
  options,
@@ -803,25 +1154,25 @@ function useAgentChat(options = {}) {
803
1154
  ),
804
1155
  threads: runtime.storage?.loadThreads() ?? []
805
1156
  }));
806
- const [isLoadingThreads, setIsLoadingThreads] = React2.useState(false);
807
- const [isLoadingMoreThreads, setIsLoadingMoreThreads] = React2.useState(false);
808
- const [threadTotal, setThreadTotal] = React2.useState();
809
- const [isLoadingHistory, setIsLoadingHistory] = React2.useState(false);
810
- const [isLoadingOlder, setIsLoadingOlder] = React2.useState(false);
811
- const [isSubmitting, setIsSubmitting] = React2.useState(false);
812
- const streamControllerRef = React2.useRef(null);
813
- const threadOpenControllerRef = React2.useRef(null);
814
- const threadOpenGenerationRef = React2.useRef(0);
815
- const refreshThreadsPromiseRef = React2.useRef(null);
816
- const threadEventsDuringRefreshRef = React2.useRef(null);
817
- const attachRunRef = React2.useRef(async () => void 0);
818
- const stateRef = React2.useRef(state);
1157
+ const [isLoadingHistory, setIsLoadingHistory] = React6.useState(false);
1158
+ const [isLoadingOlder, setIsLoadingOlder] = React6.useState(false);
1159
+ const [isSubmitting, setIsSubmitting] = React6.useState(false);
1160
+ const streamControllerRef = React6.useRef(null);
1161
+ const threadOpenControllerRef = React6.useRef(null);
1162
+ const threadOpenGenerationRef = React6.useRef(0);
1163
+ const attachRunRef = React6.useRef(async () => void 0);
1164
+ const messageRefreshVersionRef = React6.useRef(0);
1165
+ const promotedRunHintRef = React6.useRef(null);
1166
+ const promotedAttachRef = React6.useRef(null);
1167
+ const firstSubmissionRef = React6.useRef(null);
1168
+ const firstSubmissionAdmittedRef = React6.useRef(null);
1169
+ const stateRef = React6.useRef(state);
819
1170
  stateRef.current = state;
820
- const persist = React2.useCallback((next) => {
1171
+ const persist = React6.useCallback((next) => {
821
1172
  runtime.storage?.saveThreads(next.threads);
822
1173
  runtime.storage?.saveActiveThreadId?.(next.activeThreadId);
823
1174
  }, [runtime.storage]);
824
- const update = React2.useCallback((updater) => {
1175
+ const update = React6.useCallback((updater) => {
825
1176
  setState((current) => {
826
1177
  const next = updater(current);
827
1178
  stateRef.current = next;
@@ -829,119 +1180,119 @@ function useAgentChat(options = {}) {
829
1180
  return next;
830
1181
  });
831
1182
  }, [persist]);
832
- const eventBuffer = React2.useMemo(
1183
+ const queueTransport = React6.useMemo(() => ({
1184
+ list: (threadId) => client.threads.messages.list({ threadId, limit: pageSize }),
1185
+ submit: (input) => client.threads.messages.submit(input),
1186
+ edit: (message, input) => client.threads.messages.edit({
1187
+ threadId: message.thread_id,
1188
+ messageId: message.id,
1189
+ revision: message.revision,
1190
+ input: input.text,
1191
+ attachmentIds: input.attachmentIds ?? message.attachment_ids,
1192
+ mode: input.mode,
1193
+ requestedAgentId: input.requestedAgentId,
1194
+ requestedModelId: input.requestedModelId
1195
+ }),
1196
+ remove: async (message) => {
1197
+ await client.threads.messages.remove({ threadId: message.thread_id, messageId: message.id, revision: message.revision });
1198
+ },
1199
+ resume: (threadId) => client.threads.resumeQueue({ threadId })
1200
+ }), [client, pageSize]);
1201
+ const onQueuePromotion = React6.useCallback((message, runId) => {
1202
+ if (stateRef.current.activeThreadId !== message.thread_id) return;
1203
+ promotedRunHintRef.current = runId;
1204
+ const userMessage = threadTimelineToMessages([{ ...message, run_id: runId, status: "delivered" }])[0];
1205
+ update((current) => current.activeThreadId === message.thread_id && !current.messages.some((item) => item.id === userMessage.id) ? { ...current, messages: [...current.messages, userMessage] } : current);
1206
+ if (stateRef.current.activeRunId !== runId && promotedAttachRef.current !== runId) {
1207
+ promotedAttachRef.current = runId;
1208
+ void attachRunRef.current(runId).catch(() => {
1209
+ promotedAttachRef.current = null;
1210
+ });
1211
+ }
1212
+ }, [update]);
1213
+ const queue = useThreadMessageQueue({ threadId: state.activeThreadId, transport: queueTransport, onPromoted: onQueuePromotion });
1214
+ const { pendingMessages, messageQueue } = queue;
1215
+ const eventBuffer = React6.useMemo(
833
1216
  () => createRuntimeEventFrameBuffer(
834
1217
  (events) => update((current) => applyRuntimeEvents(current, events)),
835
1218
  runtimeEventFrameScheduler()
836
1219
  ),
837
1220
  [update]
838
1221
  );
839
- const clearOperationError = React2.useCallback((operation) => {
1222
+ const clearOperationError = React6.useCallback((operation) => {
840
1223
  update((current) => clearAgentChatOperationError(current, operation));
841
1224
  }, [update]);
842
- const clearBackgroundError = React2.useCallback(() => {
1225
+ const clearBackgroundError = React6.useCallback(() => {
843
1226
  update(clearAgentChatBackgroundError);
844
1227
  }, [update]);
845
- const failOperation = React2.useCallback((operation, cause) => {
1228
+ const failOperation = React6.useCallback((operation, cause) => {
846
1229
  update((current) => withAgentChatOperationError(current, operation, cause));
847
1230
  }, [update]);
848
- const invalidateThreadOpen = React2.useCallback(() => {
1231
+ const {
1232
+ isLoadingThreads,
1233
+ isLoadingMoreThreads,
1234
+ threadTotal,
1235
+ eventsDuringRefreshRef: threadEventsDuringRefreshRef,
1236
+ refreshThreads,
1237
+ loadMoreThreads
1238
+ } = useAgentChatThreadList({ client, threadPageSize, stateRef, update, clearOperationError, failOperation });
1239
+ const invalidateThreadOpen = React6.useCallback(() => {
849
1240
  threadOpenGenerationRef.current += 1;
850
1241
  threadOpenControllerRef.current?.abort("Thread selection changed.");
851
1242
  threadOpenControllerRef.current = null;
852
1243
  setIsLoadingHistory(false);
853
1244
  }, []);
854
- const detach = React2.useCallback(() => {
1245
+ const detach = React6.useCallback(() => {
855
1246
  eventBuffer.flush();
856
1247
  streamControllerRef.current?.abort();
857
1248
  streamControllerRef.current = null;
858
1249
  update((current) => ({ ...current, streamingMessageId: null, runState: current.activeRunId ? "reconnecting" : "idle" }));
859
1250
  }, [eventBuffer, update]);
860
- React2.useEffect(() => () => {
1251
+ const refreshMessages = React6.useCallback(async (threadId, promotedRunId) => {
1252
+ const target = threadId || stateRef.current.activeThreadId;
1253
+ if (!target) return;
1254
+ const version = ++messageRefreshVersionRef.current;
1255
+ const page = await client.threads.messages.list({ threadId: target, limit: pageSize });
1256
+ if (stateRef.current.activeThreadId !== target || version !== messageRefreshVersionRef.current) return;
1257
+ queue.hydrate(page, target);
1258
+ const promoted = promotedRunId || promotedRunHintRef.current;
1259
+ if (promoted && page.items.some((message) => message.role === "user" && message.run_id === promoted)) {
1260
+ promotedRunHintRef.current = null;
1261
+ update((current) => {
1262
+ if (current.activeThreadId !== target) return current;
1263
+ const canonical = preserveMountedMessageIds(threadTimelineToMessages(page.items), current.messages);
1264
+ const canonicalIds = new Set(canonical.map((message) => message.id));
1265
+ const older = current.messages.filter((message) => !canonicalIds.has(message.id) && message.id !== current.streamingMessageId);
1266
+ const live = current.activeRunId === promoted ? current.messages.filter((message) => message.id === current.streamingMessageId) : [];
1267
+ return {
1268
+ ...current,
1269
+ messages: [...older, ...canonical, ...live.filter((message) => !canonicalIds.has(message.id))],
1270
+ streamingMessageId: live.length ? current.streamingMessageId : null
1271
+ };
1272
+ });
1273
+ return;
1274
+ }
1275
+ if (stateRef.current.activeRunId) {
1276
+ const delivered = threadTimelineToMessages(page.items.filter((item) => item.role === "user" && item.run_id === stateRef.current.activeRunId && (item.segment_index ?? 0) > 0));
1277
+ if (delivered.length) update((current) => {
1278
+ if (current.activeThreadId !== target) return current;
1279
+ const additions = delivered.filter((item) => !current.messages.some((existing) => existing.id === item.id));
1280
+ return additions.length ? { ...current, messages: [...current.messages, ...additions] } : current;
1281
+ });
1282
+ }
1283
+ if (!stateRef.current.activeRunId && page.items.length) {
1284
+ update((current) => current.activeThreadId === target && !current.activeRunId ? (() => {
1285
+ const messages = preserveMountedMessageIds(threadTimelineToMessages(page.items), current.messages);
1286
+ return { ...current, messages, threads: current.threads.map((thread) => thread.id === target ? { ...thread, messages } : thread) };
1287
+ })() : current);
1288
+ }
1289
+ }, [client, pageSize, queue.hydrate, update]);
1290
+ React6.useEffect(() => () => {
861
1291
  eventBuffer.dispose();
862
1292
  streamControllerRef.current?.abort();
863
1293
  threadOpenControllerRef.current?.abort();
864
1294
  }, [eventBuffer]);
865
- const refreshThreads = React2.useCallback(() => {
866
- if (refreshThreadsPromiseRef.current) return refreshThreadsPromiseRef.current;
867
- clearOperationError();
868
- setIsLoadingThreads(true);
869
- threadEventsDuringRefreshRef.current = [];
870
- const request = (async () => {
871
- try {
872
- const result = await client.threads.list({ limit: threadPageSize });
873
- const concurrentEvents = threadEventsDuringRefreshRef.current || [];
874
- threadEventsDuringRefreshRef.current = null;
875
- setThreadTotal(result.total);
876
- update((current) => {
877
- const byId = new Map(current.threads.map((thread) => [thread.id, thread]));
878
- const nextThreads = result.items.map((item) => {
879
- const existing = byId.get(item.id);
880
- return { ...existing, ...summaryToStored(item), messages: existing?.messages || [], isHydrated: existing?.isHydrated };
881
- });
882
- return {
883
- ...current,
884
- threads: reconcileRefreshedThreads(
885
- current.threads,
886
- nextThreads,
887
- current.activeThreadId,
888
- concurrentEvents
889
- )
890
- };
891
- });
892
- } catch (error) {
893
- if (!isAbortLike(error)) {
894
- failOperation("refresh_threads", error);
895
- throw error;
896
- }
897
- } finally {
898
- threadEventsDuringRefreshRef.current = null;
899
- setIsLoadingThreads(false);
900
- }
901
- })();
902
- refreshThreadsPromiseRef.current = request;
903
- void request.then(
904
- () => {
905
- if (refreshThreadsPromiseRef.current === request) refreshThreadsPromiseRef.current = null;
906
- },
907
- () => {
908
- if (refreshThreadsPromiseRef.current === request) refreshThreadsPromiseRef.current = null;
909
- }
910
- );
911
- return request;
912
- }, [clearOperationError, client, failOperation, threadPageSize, update]);
913
- const loadMoreThreads = React2.useCallback(async () => {
914
- if (isLoadingMoreThreads) return;
915
- const skip = stateRef.current.threads.length;
916
- if (threadTotal !== void 0 && skip >= threadTotal) return;
917
- clearOperationError();
918
- setIsLoadingMoreThreads(true);
919
- try {
920
- const result = await client.threads.list({ skip, limit: threadPageSize });
921
- setThreadTotal(result.total);
922
- update((current) => {
923
- const byId = new Map(current.threads.map((thread) => [thread.id, thread]));
924
- for (const item of result.items) {
925
- const existing = byId.get(item.id);
926
- byId.set(item.id, {
927
- ...existing,
928
- ...summaryToStored(item),
929
- messages: existing?.messages || [],
930
- isHydrated: existing?.isHydrated
931
- });
932
- }
933
- return { ...current, threads: [...byId.values()] };
934
- });
935
- } catch (error) {
936
- if (!isAbortLike(error)) {
937
- failOperation("load_more_threads", error);
938
- throw error;
939
- }
940
- } finally {
941
- setIsLoadingMoreThreads(false);
942
- }
943
- }, [clearOperationError, client, failOperation, isLoadingMoreThreads, threadPageSize, threadTotal, update]);
944
- const openThread = React2.useCallback(async (threadId) => {
1295
+ const openThread = React6.useCallback(async (threadId) => {
945
1296
  const previousSelection = captureAgentChatThreadSelection(stateRef.current);
946
1297
  threadOpenControllerRef.current?.abort();
947
1298
  const requestController = runtime.createAbortController();
@@ -977,7 +1328,16 @@ function useAgentChat(options = {}) {
977
1328
  requestedThreadId: threadId,
978
1329
  signalAborted: requestController.signal.aborted
979
1330
  })) return;
980
- const messages = threadDetailToMessages(detail);
1331
+ const timeline = await client.threads.messages.list({ threadId, limit: pageSize, signal: requestController.signal });
1332
+ if (!isCurrentThreadOpenRequest({
1333
+ activeThreadId: stateRef.current.activeThreadId,
1334
+ currentGeneration: threadOpenGenerationRef.current,
1335
+ requestGeneration,
1336
+ requestedThreadId: threadId,
1337
+ signalAborted: requestController.signal.aborted
1338
+ })) return;
1339
+ queue.hydrate(timeline, threadId);
1340
+ const messages = timeline.items.length ? threadTimelineToMessages(timeline.items, detail) : threadDetailToMessages(detail);
981
1341
  const context = latestThreadContext(detail);
982
1342
  const stored = {
983
1343
  ...detail,
@@ -1025,15 +1385,23 @@ function useAgentChat(options = {}) {
1025
1385
  setIsLoadingHistory(false);
1026
1386
  }
1027
1387
  }
1028
- }, [clearOperationError, client, detach, failOperation, pageSize, runtime, update]);
1029
- const consumeEvent = React2.useCallback((event) => {
1388
+ }, [clearOperationError, client, detach, failOperation, pageSize, queue.hydrate, runtime, update]);
1389
+ const consumeEvent = React6.useCallback((event) => {
1390
+ if (event.event === "run.accepted" && !stateRef.current.activeThreadId && typeof event.payload?.message_id === "string") {
1391
+ const durableId = event.payload.message_id;
1392
+ update((current) => ({
1393
+ ...current,
1394
+ messages: current.messages.map((message) => message.role === "user" && !message.runId && !message.durableId ? { ...message, durableId, runId: event.run_id } : message.role === "assistant" && !message.runId ? { ...message, runId: event.run_id } : message)
1395
+ }));
1396
+ }
1030
1397
  const eventThreadId = typeof event.payload?.thread_id === "string" ? event.payload.thread_id.trim() : "";
1031
1398
  if (eventThreadId && !stateRef.current.activeThreadId) {
1032
1399
  update((current) => adoptStreamingThread(current, eventThreadId));
1400
+ firstSubmissionAdmittedRef.current?.();
1033
1401
  }
1034
1402
  eventBuffer.push(event);
1035
1403
  }, [eventBuffer, update]);
1036
- const attachRun = React2.useCallback(async (runId, cursor) => {
1404
+ const attachRun = React6.useCallback(async (runId, cursor) => {
1037
1405
  streamControllerRef.current?.abort();
1038
1406
  const controller = runtime.createAbortController();
1039
1407
  streamControllerRef.current = controller;
@@ -1041,7 +1409,9 @@ function useAgentChat(options = {}) {
1041
1409
  update((current) => ({ ...current, activeRunId: runId, runState: "reconnecting", error: null }));
1042
1410
  try {
1043
1411
  const result = await client.runs.attach({ runId, cursor, signal: controller.signal }, consumeEvent);
1412
+ eventBuffer.flush();
1044
1413
  update((current) => ({ ...current, activeRunId: result.detached ? runId : current.activeRunId, cursor: result.cursor ?? current.cursor, runState: result.detached ? "reconnecting" : current.runState }));
1414
+ if (!result.detached && result.threadId) await refreshMessages(result.threadId);
1045
1415
  } catch (error) {
1046
1416
  if (!isAbortLike(error)) {
1047
1417
  update((current) => ({ ...current, runState: "failed", streamingMessageId: null }));
@@ -1051,100 +1421,171 @@ function useAgentChat(options = {}) {
1051
1421
  } finally {
1052
1422
  if (streamControllerRef.current === controller) streamControllerRef.current = null;
1053
1423
  }
1054
- }, [clearOperationError, client, consumeEvent, failOperation, runtime, update]);
1424
+ }, [clearOperationError, client, consumeEvent, eventBuffer, failOperation, refreshMessages, runtime, update]);
1055
1425
  attachRunRef.current = attachRun;
1056
- const submit = React2.useCallback(async (input) => {
1426
+ const submit = React6.useCallback(async (input) => {
1057
1427
  if (!input.text.trim() && !input.attachmentIds?.length && !input.uploads?.length) return;
1058
- invalidateThreadOpen();
1059
- detach();
1060
- const controller = runtime.createAbortController();
1061
- streamControllerRef.current = controller;
1062
- const userId = runtime.createId();
1063
- const assistantId = runtime.createId();
1064
- const idempotencyKey = runtime.createId();
1065
- const optimisticAttachments = input.attachments?.length ? input.attachments : input.uploads?.map((upload, index) => ({
1066
- id: `${userId}-attachment-${index}`,
1067
- filename: upload.name,
1068
- mediaType: upload.mediaType,
1069
- status: "uploading"
1070
- }));
1071
- const userMessage = { id: userId, role: "user", content: input.text.trim(), createdAt: (/* @__PURE__ */ new Date()).toISOString(), parts: input.text.trim() ? [{ id: `${userId}-text`, type: "text", kind: "text", text: input.text.trim() }] : [], attachments: optimisticAttachments };
1072
- const assistantMessage = { id: assistantId, role: "assistant", content: "", createdAt: (/* @__PURE__ */ new Date()).toISOString(), isFinal: false, parts: [] };
1073
- clearOperationError();
1074
- const hasPendingUploads = Boolean(input.uploads?.length);
1075
- update((current) => ({
1076
- ...current,
1077
- messages: appendStableStreamingTurn(current.messages, userMessage, assistantMessage),
1078
- streamingMessageId: assistantId,
1079
- runState: hasPendingUploads ? "uploading" : "streaming",
1080
- error: null
1081
- }));
1082
- setIsSubmitting(true);
1083
- let streamStarted = false;
1084
- try {
1085
- const uploaded = hasPendingUploads ? await client.attachments.uploadBatch({
1086
- uploads: input.uploads || [],
1087
- threadId: stateRef.current.activeThreadId || void 0,
1088
- requestedAgentId: input.requestedAgentId,
1089
- idempotencyKey: runtime.createId(),
1090
- signal: controller.signal
1091
- }) : [];
1092
- if (hasPendingUploads && uploaded.length !== input.uploads?.length) {
1093
- throw new Error("Attachment upload returned an incomplete batch");
1094
- }
1095
- const resolvedAttachments = uploaded.length ? uploaded.map(attachmentResultToChatAttachment) : input.attachments;
1096
- const attachmentIds = uploaded.length ? uploaded.map((attachment) => attachment.id) : input.attachmentIds;
1097
- if (uploaded.length) {
1098
- update((current) => ({
1099
- ...current,
1100
- messages: current.messages.map((message) => message.id === userId ? { ...message, attachments: resolvedAttachments } : message),
1101
- runState: "streaming"
1102
- }));
1103
- }
1104
- streamStarted = true;
1105
- const result = await client.chat.stream({ input: input.text.trim(), threadId: stateRef.current.activeThreadId || void 0, attachmentIds, requestedModelId: input.requestedModelId, requestedAgentId: input.requestedAgentId, idempotencyKey, signal: controller.signal }, consumeEvent);
1106
- eventBuffer.flush();
1107
- update((current) => {
1108
- const threadId = result.threadId || current.activeThreadId;
1109
- return {
1110
- ...current,
1111
- activeThreadId: threadId,
1112
- activeRunId: result.detached ? result.runId : current.activeRunId,
1113
- cursor: result.cursor,
1114
- runState: result.detached ? "reconnecting" : current.runState,
1115
- threads: threadId ? current.threads.map((thread) => thread.id === threadId ? { ...thread, messages: current.messages, isHydrated: true } : thread) : current.threads
1116
- };
1117
- });
1118
- if (result.threadId) await refreshThreads().catch(() => void 0);
1119
- } catch (error) {
1120
- if (isAbortLike(error) && !streamStarted) {
1121
- update((current) => current.streamingMessageId === assistantId ? {
1122
- ...current,
1123
- messages: current.messages.filter((message) => message.id !== userId && message.id !== assistantId),
1124
- runState: "idle",
1125
- streamingMessageId: null
1126
- } : current);
1127
- } else if (!isAbortLike(error)) {
1128
- update((current) => ({
1129
- ...current,
1130
- messages: streamStarted ? current.messages : current.messages.filter((message) => message.id !== userId && message.id !== assistantId),
1131
- runState: "failed",
1132
- streamingMessageId: null
1133
- }));
1428
+ if (firstSubmissionRef.current && !stateRef.current.activeThreadId) {
1429
+ await firstSubmissionRef.current;
1430
+ }
1431
+ const existingThreadId = stateRef.current.activeThreadId;
1432
+ if (existingThreadId) {
1433
+ clearOperationError();
1434
+ setIsSubmitting(true);
1435
+ try {
1436
+ const uploaded = input.uploads?.length ? await client.attachments.uploadBatch({
1437
+ uploads: input.uploads,
1438
+ threadId: existingThreadId,
1439
+ requestedAgentId: input.requestedAgentId,
1440
+ idempotencyKey: runtime.createId()
1441
+ }) : [];
1442
+ const result = await queue.submit({
1443
+ mode: "queue",
1444
+ input: input.text.trim(),
1445
+ attachmentIds: uploaded.length ? uploaded.map((item) => item.id) : input.attachmentIds,
1446
+ requestedAgentId: input.requestedAgentId,
1447
+ requestedModelId: input.requestedModelId,
1448
+ idempotencyKey: runtime.createId()
1449
+ });
1450
+ if (stateRef.current.activeThreadId !== existingThreadId) return;
1451
+ if (result.run_id) {
1452
+ const userMessage = {
1453
+ id: result.message_id,
1454
+ role: "user",
1455
+ runId: result.run_id,
1456
+ content: input.text.trim(),
1457
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1458
+ isFinal: true,
1459
+ parts: input.text.trim() ? [{ id: `${result.message_id}-text`, type: "text", kind: "text", text: input.text.trim() }] : [],
1460
+ attachments: input.attachments || []
1461
+ };
1462
+ update((current) => current.activeThreadId !== existingThreadId || current.messages.some((message) => message.id === userMessage.id) ? current : { ...current, messages: [...current.messages, userMessage] });
1463
+ }
1464
+ void refreshMessages(existingThreadId).catch(() => void 0);
1465
+ if (result.run_id && promotedAttachRef.current !== result.run_id && stateRef.current.activeRunId !== result.run_id) {
1466
+ void attachRunRef.current(result.run_id).catch(() => void 0);
1467
+ }
1468
+ } catch (error) {
1134
1469
  failOperation("submit", error);
1135
1470
  throw error;
1471
+ } finally {
1472
+ setIsSubmitting(false);
1136
1473
  }
1474
+ return;
1475
+ }
1476
+ const admission = new Promise((resolve) => {
1477
+ firstSubmissionAdmittedRef.current = resolve;
1478
+ });
1479
+ const stream = (async () => {
1480
+ invalidateThreadOpen();
1481
+ detach();
1482
+ const controller = runtime.createAbortController();
1483
+ streamControllerRef.current = controller;
1484
+ const userId = runtime.createId();
1485
+ const assistantId = runtime.createId();
1486
+ const idempotencyKey = runtime.createId();
1487
+ const optimisticAttachments = input.attachments?.length ? input.attachments : input.uploads?.map((upload, index) => ({
1488
+ id: `${userId}-attachment-${index}`,
1489
+ filename: upload.name,
1490
+ mediaType: upload.mediaType,
1491
+ status: "uploading"
1492
+ }));
1493
+ const userMessage = { id: userId, role: "user", content: input.text.trim(), createdAt: (/* @__PURE__ */ new Date()).toISOString(), parts: input.text.trim() ? [{ id: `${userId}-text`, type: "text", kind: "text", text: input.text.trim() }] : [], attachments: optimisticAttachments };
1494
+ const assistantMessage = { id: assistantId, role: "assistant", content: "", createdAt: (/* @__PURE__ */ new Date()).toISOString(), isFinal: false, parts: [] };
1495
+ clearOperationError();
1496
+ const hasPendingUploads = Boolean(input.uploads?.length);
1497
+ update((current) => ({
1498
+ ...current,
1499
+ messages: appendStableStreamingTurn(current.messages, userMessage, assistantMessage),
1500
+ streamingMessageId: assistantId,
1501
+ runState: hasPendingUploads ? "uploading" : "streaming",
1502
+ error: null
1503
+ }));
1504
+ setIsSubmitting(true);
1505
+ let streamStarted = false;
1506
+ try {
1507
+ const uploaded = hasPendingUploads ? await client.attachments.uploadBatch({
1508
+ uploads: input.uploads || [],
1509
+ threadId: stateRef.current.activeThreadId || void 0,
1510
+ requestedAgentId: input.requestedAgentId,
1511
+ idempotencyKey: runtime.createId(),
1512
+ signal: controller.signal
1513
+ }) : [];
1514
+ if (hasPendingUploads && uploaded.length !== input.uploads?.length) {
1515
+ throw new Error("Attachment upload returned an incomplete batch");
1516
+ }
1517
+ const resolvedAttachments = uploaded.length ? uploaded.map(attachmentResultToChatAttachment) : input.attachments;
1518
+ const attachmentIds = uploaded.length ? uploaded.map((attachment) => attachment.id) : input.attachmentIds;
1519
+ if (uploaded.length) {
1520
+ update((current) => ({
1521
+ ...current,
1522
+ messages: current.messages.map((message) => message.id === userId ? { ...message, attachments: resolvedAttachments } : message),
1523
+ runState: "streaming"
1524
+ }));
1525
+ }
1526
+ streamStarted = true;
1527
+ const result = await client.chat.stream({ input: input.text.trim(), threadId: stateRef.current.activeThreadId || void 0, attachmentIds, requestedModelId: input.requestedModelId, requestedAgentId: input.requestedAgentId, idempotencyKey, signal: controller.signal }, consumeEvent);
1528
+ eventBuffer.flush();
1529
+ update((current) => {
1530
+ const threadId = result.threadId || current.activeThreadId;
1531
+ return {
1532
+ ...current,
1533
+ activeThreadId: threadId,
1534
+ activeRunId: result.detached ? result.runId : current.activeRunId,
1535
+ cursor: result.cursor,
1536
+ runState: result.detached ? "reconnecting" : current.runState,
1537
+ threads: threadId ? current.threads.map((thread) => thread.id === threadId ? { ...thread, messages: current.messages, isHydrated: true } : thread) : current.threads
1538
+ };
1539
+ });
1540
+ if (result.threadId) await refreshThreads().catch(() => void 0);
1541
+ if (result.threadId && !result.detached) await refreshMessages(result.threadId);
1542
+ } catch (error) {
1543
+ if (isAbortLike(error) && !streamStarted) {
1544
+ update((current) => current.streamingMessageId === assistantId ? {
1545
+ ...current,
1546
+ messages: current.messages.filter((message) => message.id !== userId && message.id !== assistantId),
1547
+ runState: "idle",
1548
+ streamingMessageId: null
1549
+ } : current);
1550
+ } else if (!isAbortLike(error)) {
1551
+ update((current) => ({
1552
+ ...current,
1553
+ messages: streamStarted ? current.messages : current.messages.filter((message) => message.id !== userId && message.id !== assistantId),
1554
+ runState: "failed",
1555
+ streamingMessageId: null
1556
+ }));
1557
+ failOperation("submit", error);
1558
+ throw error;
1559
+ }
1560
+ } finally {
1561
+ setIsSubmitting(false);
1562
+ if (streamControllerRef.current === controller) streamControllerRef.current = null;
1563
+ }
1564
+ })();
1565
+ const accepted = Promise.race([stream, admission]);
1566
+ firstSubmissionRef.current = accepted;
1567
+ try {
1568
+ await accepted;
1137
1569
  } finally {
1138
- setIsSubmitting(false);
1139
- if (streamControllerRef.current === controller) streamControllerRef.current = null;
1570
+ if (firstSubmissionRef.current === accepted) firstSubmissionRef.current = null;
1571
+ firstSubmissionAdmittedRef.current = null;
1140
1572
  }
1141
- }, [clearOperationError, client, consumeEvent, detach, eventBuffer, failOperation, invalidateThreadOpen, refreshThreads, runtime, update]);
1142
- const startNewThread = React2.useCallback(() => {
1573
+ }, [clearOperationError, client, consumeEvent, detach, eventBuffer, failOperation, invalidateThreadOpen, queue.submit, refreshMessages, refreshThreads, runtime, update]);
1574
+ const { steer, editPendingMessage, steerPendingMessage, removePendingMessage, resumeQueue } = useAgentChatQueueActions({
1575
+ queue,
1576
+ stateRef,
1577
+ createId: runtime.createId,
1578
+ clearOperationError,
1579
+ failOperation,
1580
+ refreshMessages,
1581
+ attachRun
1582
+ });
1583
+ const startNewThread = React6.useCallback(() => {
1143
1584
  invalidateThreadOpen();
1144
1585
  detach();
1145
1586
  update((current) => ({ ...initialAgentChatState, threads: current.threads }));
1146
1587
  }, [detach, invalidateThreadOpen, update]);
1147
- const deleteThread = React2.useCallback(async (threadId) => {
1588
+ const deleteThread = React6.useCallback(async (threadId) => {
1148
1589
  if (stateRef.current.activeThreadId === threadId) invalidateThreadOpen();
1149
1590
  clearOperationError();
1150
1591
  try {
@@ -1165,7 +1606,7 @@ function useAgentChat(options = {}) {
1165
1606
  }
1166
1607
  }
1167
1608
  }, [clearOperationError, client, failOperation, invalidateThreadOpen, runtime, update]);
1168
- const cancelRun = React2.useCallback(async () => {
1609
+ const cancelRun = React6.useCallback(async () => {
1169
1610
  const runId = stateRef.current.activeRunId;
1170
1611
  if (!runId || stateRef.current.runState === "cancelling") return;
1171
1612
  const previousRunState = stateRef.current.runState;
@@ -1204,7 +1645,7 @@ function useAgentChat(options = {}) {
1204
1645
  }
1205
1646
  }
1206
1647
  }, [clearOperationError, client, detach, failOperation, runtime, update]);
1207
- const resumeHitl = React2.useCallback(async (part, response) => {
1648
+ const resumeHitl = React6.useCallback(async (part, response) => {
1208
1649
  const runId = stateRef.current.activeRunId || stateRef.current.messages.find((message) => message.parts.includes(part))?.runId;
1209
1650
  clearOperationError();
1210
1651
  if (!runId) {
@@ -1225,54 +1666,8 @@ function useAgentChat(options = {}) {
1225
1666
  throw error;
1226
1667
  }
1227
1668
  }, [attachRun, clearOperationError, client, failOperation, runtime]);
1228
- const setFeedback = React2.useCallback(async (message, input) => {
1229
- const runId = message.runId;
1230
- if (!runId || message.role !== "assistant" || message.isFinal === false) {
1231
- const error = new Error("Only persisted final responses can receive feedback.");
1232
- failOperation("set_feedback", error);
1233
- throw error;
1234
- }
1235
- const previous = message.feedback ?? null;
1236
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1237
- const optimistic = input.rating ? {
1238
- rating: input.rating,
1239
- reason: input.rating === "dislike" ? input.reason ?? null : null,
1240
- comment: input.rating === "dislike" ? input.comment?.trim() || null : null,
1241
- created_at: previous?.created_at || timestamp,
1242
- updated_at: timestamp
1243
- } : null;
1244
- const replaceFeedback = (current, feedback) => {
1245
- const messages = current.messages.map(
1246
- (item) => item.id === message.id ? { ...item, feedback } : item
1247
- );
1248
- return {
1249
- ...current,
1250
- messages,
1251
- threads: current.threads.map((thread) => ({
1252
- ...thread,
1253
- messages: thread.messages.map(
1254
- (item) => item.id === message.id ? { ...item, feedback } : item
1255
- )
1256
- }))
1257
- };
1258
- };
1259
- clearOperationError();
1260
- update((current) => replaceFeedback(current, optimistic));
1261
- try {
1262
- const result = await client.runs.setFeedback({ runId, ...input });
1263
- update((current) => replaceFeedback(current, result.feedback));
1264
- } catch (error) {
1265
- update(
1266
- (current) => withAgentChatOperationError(
1267
- replaceFeedback(current, previous),
1268
- "set_feedback",
1269
- error
1270
- )
1271
- );
1272
- throw error;
1273
- }
1274
- }, [clearOperationError, client, failOperation, update]);
1275
- const loadOlder = React2.useCallback(async () => {
1669
+ const setFeedback = useAgentChatFeedback({ client, update, clearOperationError, failOperation });
1670
+ const loadOlder = React6.useCallback(async () => {
1276
1671
  const thread = stateRef.current.threads.find((item) => item.id === stateRef.current.activeThreadId);
1277
1672
  if (!thread?.nextBeforeTurnIndex) return;
1278
1673
  clearOperationError();
@@ -1290,7 +1685,7 @@ function useAgentChat(options = {}) {
1290
1685
  setIsLoadingOlder(false);
1291
1686
  }
1292
1687
  }, [clearOperationError, client, failOperation, pageSize, update]);
1293
- const regenerate = React2.useCallback(async (message) => {
1688
+ const regenerate = React6.useCallback(async (message) => {
1294
1689
  const index = stateRef.current.messages.findIndex((item) => item.id === message.id);
1295
1690
  const user = stateRef.current.messages.slice(0, index).reverse().find((item) => item.role === "user");
1296
1691
  if (user) await submit({
@@ -1299,7 +1694,7 @@ function useAgentChat(options = {}) {
1299
1694
  attachments: user.attachments
1300
1695
  });
1301
1696
  }, [submit]);
1302
- React2.useEffect(() => {
1697
+ React6.useEffect(() => {
1303
1698
  const controller = runtime.createAbortController();
1304
1699
  clearBackgroundError();
1305
1700
  void subscribeToThreadSummaries({
@@ -1312,6 +1707,26 @@ function useAgentChat(options = {}) {
1312
1707
  ...clearAgentChatBackgroundError(current),
1313
1708
  threads: applyThreadSummaryEvent(current.threads, event)
1314
1709
  }));
1710
+ if (event.event === "thread.message.updated" && event.thread_id === stateRef.current.activeThreadId) {
1711
+ const wasQueued = queue.acceptNotice({
1712
+ messageId: event.message_id,
1713
+ status: event.status,
1714
+ runId: event.run_id,
1715
+ cursor: event.cursor
1716
+ });
1717
+ if (wasQueued) void refreshMessages(event.thread_id, event.run_id || void 0).catch(() => void 0);
1718
+ }
1719
+ if (event.event === "thread.summary.updated" && event.thread.id === stateRef.current.activeThreadId) {
1720
+ if (streamControllerRef.current && stateRef.current.messages.some((message) => message.role === "user" && !message.runId)) return;
1721
+ const promotedRunId = activeRunIdFromThread(summaryToStored(event.thread));
1722
+ void (async () => {
1723
+ await refreshMessages(String(event.thread.id), promotedRunId || void 0);
1724
+ if (promotedRunId && stateRef.current.activeRunId !== promotedRunId && stateRef.current.activeThreadId === String(event.thread.id) && promotedAttachRef.current !== promotedRunId) {
1725
+ promotedAttachRef.current = promotedRunId;
1726
+ await attachRunRef.current(promotedRunId);
1727
+ }
1728
+ })().catch(() => void 0);
1729
+ }
1315
1730
  },
1316
1731
  onError(error) {
1317
1732
  update((current) => withAgentChatBackgroundError(current, error));
@@ -1321,8 +1736,8 @@ function useAgentChat(options = {}) {
1321
1736
  }
1322
1737
  });
1323
1738
  return () => controller.abort();
1324
- }, [clearBackgroundError, client, refreshThreads, runtime.createAbortController, update]);
1325
- React2.useEffect(() => {
1739
+ }, [clearBackgroundError, client, queue.acceptNotice, refreshMessages, refreshThreads, runtime.createAbortController, update]);
1740
+ React6.useEffect(() => {
1326
1741
  const threadId = options.activeThreadId;
1327
1742
  const activeThread2 = stateRef.current.threads.find((thread) => thread.id === stateRef.current.activeThreadId);
1328
1743
  if (shouldHydrateSelectedThread(threadId, stateRef.current.activeThreadId, Boolean(activeThread2?.isHydrated))) {
@@ -1334,6 +1749,8 @@ function useAgentChat(options = {}) {
1334
1749
  return {
1335
1750
  ...state,
1336
1751
  activeThread,
1752
+ pendingMessages,
1753
+ messageQueue,
1337
1754
  hasOlderTurns: Boolean(activeThread?.hasOlderTurns),
1338
1755
  hasMoreThreads,
1339
1756
  isLoadingHistory,
@@ -1352,6 +1769,13 @@ function useAgentChat(options = {}) {
1352
1769
  openThread,
1353
1770
  refreshThreads,
1354
1771
  regenerate,
1772
+ refreshMessages,
1773
+ editPendingMessage,
1774
+ steerPendingMessage,
1775
+ removePendingMessage,
1776
+ retractPendingMessage: queue.retractForEdit,
1777
+ resumeQueue,
1778
+ steer,
1355
1779
  resumeHitl,
1356
1780
  setFeedback,
1357
1781
  startNewThread,
@@ -1360,15 +1784,15 @@ function useAgentChat(options = {}) {
1360
1784
  }
1361
1785
 
1362
1786
  // src/use-agent-resources.ts
1363
- import * as React3 from "react";
1787
+ import * as React7 from "react";
1364
1788
  function useAgentThreads(options = {}) {
1365
1789
  const { client, createAbortController } = useAgents24Runtime();
1366
- const [threads, setThreads] = React3.useState([]);
1367
- const [total, setTotal] = React3.useState();
1368
- const [isLoading, setIsLoading] = React3.useState(true);
1369
- const [error, setError] = React3.useState(null);
1370
- const controllerRef = React3.useRef(null);
1371
- const load = React3.useCallback(async () => {
1790
+ const [threads, setThreads] = React7.useState([]);
1791
+ const [total, setTotal] = React7.useState();
1792
+ const [isLoading, setIsLoading] = React7.useState(true);
1793
+ const [error, setError] = React7.useState(null);
1794
+ const controllerRef = React7.useRef(null);
1795
+ const load = React7.useCallback(async () => {
1372
1796
  controllerRef.current?.abort();
1373
1797
  const controller = createAbortController();
1374
1798
  controllerRef.current = controller;
@@ -1387,7 +1811,7 @@ function useAgentThreads(options = {}) {
1387
1811
  }
1388
1812
  }
1389
1813
  }, [client, createAbortController, options.limit, options.skip]);
1390
- React3.useEffect(() => {
1814
+ React7.useEffect(() => {
1391
1815
  void load();
1392
1816
  return () => controllerRef.current?.abort();
1393
1817
  }, [load]);
@@ -1395,11 +1819,11 @@ function useAgentThreads(options = {}) {
1395
1819
  }
1396
1820
  function useAgentThread(threadId, options = {}) {
1397
1821
  const { client, createAbortController } = useAgents24Runtime();
1398
- const [thread, setThread] = React3.useState(null);
1399
- const [isLoading, setIsLoading] = React3.useState(Boolean(threadId));
1400
- const [error, setError] = React3.useState(null);
1401
- const controllerRef = React3.useRef(null);
1402
- const load = React3.useCallback(async () => {
1822
+ const [thread, setThread] = React7.useState(null);
1823
+ const [isLoading, setIsLoading] = React7.useState(Boolean(threadId));
1824
+ const [error, setError] = React7.useState(null);
1825
+ const controllerRef = React7.useRef(null);
1826
+ const load = React7.useCallback(async () => {
1403
1827
  if (!threadId) {
1404
1828
  controllerRef.current?.abort();
1405
1829
  controllerRef.current = null;
@@ -1426,24 +1850,73 @@ function useAgentThread(threadId, options = {}) {
1426
1850
  }
1427
1851
  }
1428
1852
  }, [client, createAbortController, options.beforeTurnIndex, options.limit, threadId]);
1429
- React3.useEffect(() => {
1853
+ React7.useEffect(() => {
1430
1854
  void load();
1431
1855
  return () => controllerRef.current?.abort();
1432
1856
  }, [load]);
1433
1857
  return { error, isLoading, refresh: load, thread };
1434
1858
  }
1859
+ function useThreadComputer(threadId) {
1860
+ const { client, createAbortController } = useAgents24Runtime();
1861
+ const [computer, setComputer] = React7.useState(null);
1862
+ const [isLoading, setIsLoading] = React7.useState(Boolean(threadId));
1863
+ const [error, setError] = React7.useState(null);
1864
+ const controllerRef = React7.useRef(null);
1865
+ const refresh = React7.useCallback(async () => {
1866
+ controllerRef.current?.abort();
1867
+ if (!threadId) {
1868
+ setComputer(null);
1869
+ setIsLoading(false);
1870
+ return null;
1871
+ }
1872
+ const controller = createAbortController();
1873
+ controllerRef.current = controller;
1874
+ setIsLoading(true);
1875
+ setError(null);
1876
+ try {
1877
+ const result = await client.threads.computer.get({ threadId, signal: controller.signal });
1878
+ setComputer(result);
1879
+ return result;
1880
+ } catch (nextError) {
1881
+ if (!isAbortLike(nextError)) setError(nextError);
1882
+ return null;
1883
+ } finally {
1884
+ if (controllerRef.current === controller) {
1885
+ controllerRef.current = null;
1886
+ setIsLoading(false);
1887
+ }
1888
+ }
1889
+ }, [client, createAbortController, threadId]);
1890
+ React7.useEffect(() => {
1891
+ void refresh();
1892
+ return () => controllerRef.current?.abort();
1893
+ }, [refresh]);
1894
+ const list = React7.useCallback((input) => {
1895
+ if (!threadId) throw new Error("A thread is required to list computer files");
1896
+ return client.threads.computer.list({ threadId, ...input });
1897
+ }, [client, threadId]);
1898
+ const read = React7.useCallback((path) => {
1899
+ if (!threadId) throw new Error("A thread is required to read a computer file");
1900
+ return client.threads.computer.read({ threadId, path });
1901
+ }, [client, threadId]);
1902
+ const createContentAccess = React7.useCallback((path, download = false) => {
1903
+ if (!threadId) throw new Error("A thread is required to access a computer file");
1904
+ return client.threads.computer.createContentAccess({ threadId, path, download });
1905
+ }, [client, threadId]);
1906
+ return { computer, error, isLoading, list, read, createContentAccess, refresh };
1907
+ }
1435
1908
  function useAgentRun() {
1436
1909
  const { client, createAbortController, createId } = useAgents24Runtime();
1437
- const controllerRef = React3.useRef(null);
1438
- const [isAttached, setIsAttached] = React3.useState(false);
1439
- const [error, setError] = React3.useState(null);
1440
- React3.useEffect(() => () => controllerRef.current?.abort(), []);
1441
- const detach = React3.useCallback(() => {
1910
+ const controllerRef = React7.useRef(null);
1911
+ const [isAttached, setIsAttached] = React7.useState(false);
1912
+ const [error, setError] = React7.useState(null);
1913
+ React7.useEffect(() => () => controllerRef.current?.abort(), []);
1914
+ const detach = React7.useCallback(() => {
1442
1915
  controllerRef.current?.abort();
1443
1916
  controllerRef.current = null;
1444
1917
  setIsAttached(false);
1445
1918
  }, []);
1446
- const attach = React3.useCallback(async (runId, onEvent, cursor) => {
1919
+ const attach = React7.useCallback(async (runId, onEvent, cursor) => {
1447
1920
  detach();
1448
1921
  const controller = createAbortController();
1449
1922
  controllerRef.current = controller;
@@ -1459,7 +1932,7 @@ function useAgentRun() {
1459
1932
  setIsAttached(false);
1460
1933
  }
1461
1934
  }, [client, createAbortController, detach]);
1462
- const cancel = React3.useCallback(async (runId) => {
1935
+ const cancel = React7.useCallback(async (runId) => {
1463
1936
  const result = await client.runs.cancel({ runId, idempotencyKey: createId() });
1464
1937
  detach();
1465
1938
  return result;
@@ -1468,9 +1941,9 @@ function useAgentRun() {
1468
1941
  }
1469
1942
  function useAgentAttachments() {
1470
1943
  const { client, createId } = useAgents24Runtime();
1471
- const [uploads, setUploads] = React3.useState({});
1472
- const [isUploading, setIsUploading] = React3.useState(false);
1473
- const uploadBatch = React3.useCallback(async (input) => {
1944
+ const [uploads, setUploads] = React7.useState({});
1945
+ const [isUploading, setIsUploading] = React7.useState(false);
1946
+ const uploadBatch = React7.useCallback(async (input) => {
1474
1947
  setIsUploading(true);
1475
1948
  try {
1476
1949
  const results = await client.attachments.uploadBatch({ uploads: input.uploads, threadId: input.threadId, requestedAgentId: input.requestedAgentId, idempotencyKey: createId(), signal: input.signal });
@@ -1483,7 +1956,7 @@ function useAgentAttachments() {
1483
1956
  setIsUploading(false);
1484
1957
  }
1485
1958
  }, [client, createId]);
1486
- const createContentAccess = React3.useCallback(
1959
+ const createContentAccess = React7.useCallback(
1487
1960
  (input) => client.attachments.createContentAccess(input),
1488
1961
  [client]
1489
1962
  );
@@ -1491,9 +1964,9 @@ function useAgentAttachments() {
1491
1964
  }
1492
1965
  function useAgentHitl() {
1493
1966
  const { client, createId } = useAgents24Runtime();
1494
- const [isResolving, setIsResolving] = React3.useState(false);
1495
- const [lastResult, setLastResult] = React3.useState(null);
1496
- const resume = React3.useCallback(async (input) => {
1967
+ const [isResolving, setIsResolving] = React7.useState(false);
1968
+ const [lastResult, setLastResult] = React7.useState(null);
1969
+ const resume = React7.useCallback(async (input) => {
1497
1970
  setIsResolving(true);
1498
1971
  try {
1499
1972
  const result = await client.hitl.resume({ ...input, idempotencyKey: createId() });
@@ -1507,8 +1980,8 @@ function useAgentHitl() {
1507
1980
  }
1508
1981
  function useAgentMcp() {
1509
1982
  const { client, createId } = useAgents24Runtime();
1510
- const [isAuthorizing, setIsAuthorizing] = React3.useState(false);
1511
- const startAuthorization = React3.useCallback(async (input) => {
1983
+ const [isAuthorizing, setIsAuthorizing] = React7.useState(false);
1984
+ const startAuthorization = React7.useCallback(async (input) => {
1512
1985
  setIsAuthorizing(true);
1513
1986
  try {
1514
1987
  return await client.mcp.startAuthorization({ ...input, idempotencyKey: createId() });
@@ -1516,7 +1989,7 @@ function useAgentMcp() {
1516
1989
  setIsAuthorizing(false);
1517
1990
  }
1518
1991
  }, [client, createId]);
1519
- const redeemCallback = React3.useCallback(async (input) => client.mcp.redeemCallback({ ...input, idempotencyKey: createId() }), [client, createId]);
1992
+ const redeemCallback = React7.useCallback(async (input) => client.mcp.redeemCallback({ ...input, idempotencyKey: createId() }), [client, createId]);
1520
1993
  return { isAuthorizing, redeemCallback, startAuthorization };
1521
1994
  }
1522
1995
  export {
@@ -1532,6 +2005,7 @@ export {
1532
2005
  chatAttachmentIds,
1533
2006
  clearAgentChatBackgroundError,
1534
2007
  clearAgentChatOperationError,
2008
+ computerResourceTargets,
1535
2009
  contextCompressionPartFromEvent,
1536
2010
  createPortableId,
1537
2011
  getThreadActivity,
@@ -1540,9 +2014,12 @@ export {
1540
2014
  latestThreadContext,
1541
2015
  modelTurnPartFromEvent,
1542
2016
  partsFromResponseBlocks,
2017
+ preferNewestThreadSummary,
2018
+ preserveMountedMessageIds,
1543
2019
  reconcileRefreshedThreads,
1544
2020
  restoreAgentChatThreadSelection,
1545
2021
  threadDetailToMessages,
2022
+ threadTimelineToMessages,
1546
2023
  upsertStableAssistantMessage,
1547
2024
  useAgentAttachments,
1548
2025
  useAgentChat,
@@ -1553,6 +2030,8 @@ export {
1553
2030
  useAgentThread,
1554
2031
  useAgentThreads,
1555
2032
  useAgents24Runtime,
2033
+ useThreadComputer,
2034
+ useThreadMessageQueue,
1556
2035
  withAgentChatBackgroundError,
1557
2036
  withAgentChatOperationError
1558
2037
  };