@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/compatibility.json +8 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.js +764 -285
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts +13 -0
- package/dist/state.d.ts +4 -0
- package/dist/thread-message-queue.d.ts +47 -0
- package/dist/use-agent-chat-feedback.d.ts +13 -0
- package/dist/use-agent-chat-queue-actions.d.ts +20 -0
- package/dist/use-agent-chat-thread-list.d.ts +20 -0
- package/dist/use-agent-chat.d.ts +11 -1
- package/dist/use-agent-resources.d.ts +14 -1
- package/package.json +2 -2
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, ...
|
|
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
|
|
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
|
|
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" &&
|
|
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] =
|
|
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/
|
|
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] =
|
|
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 [
|
|
807
|
-
const [
|
|
808
|
-
const [
|
|
809
|
-
const
|
|
810
|
-
const
|
|
811
|
-
const
|
|
812
|
-
const
|
|
813
|
-
const
|
|
814
|
-
const
|
|
815
|
-
const
|
|
816
|
-
const
|
|
817
|
-
const
|
|
818
|
-
const stateRef =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
1222
|
+
const clearOperationError = React6.useCallback((operation) => {
|
|
840
1223
|
update((current) => clearAgentChatOperationError(current, operation));
|
|
841
1224
|
}, [update]);
|
|
842
|
-
const clearBackgroundError =
|
|
1225
|
+
const clearBackgroundError = React6.useCallback(() => {
|
|
843
1226
|
update(clearAgentChatBackgroundError);
|
|
844
1227
|
}, [update]);
|
|
845
|
-
const failOperation =
|
|
1228
|
+
const failOperation = React6.useCallback((operation, cause) => {
|
|
846
1229
|
update((current) => withAgentChatOperationError(current, operation, cause));
|
|
847
1230
|
}, [update]);
|
|
848
|
-
const
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1426
|
+
const submit = React6.useCallback(async (input) => {
|
|
1057
1427
|
if (!input.text.trim() && !input.attachmentIds?.length && !input.uploads?.length) return;
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
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
|
-
|
|
1139
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1229
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1787
|
+
import * as React7 from "react";
|
|
1364
1788
|
function useAgentThreads(options = {}) {
|
|
1365
1789
|
const { client, createAbortController } = useAgents24Runtime();
|
|
1366
|
-
const [threads, setThreads] =
|
|
1367
|
-
const [total, setTotal] =
|
|
1368
|
-
const [isLoading, setIsLoading] =
|
|
1369
|
-
const [error, setError] =
|
|
1370
|
-
const controllerRef =
|
|
1371
|
-
const load =
|
|
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
|
-
|
|
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] =
|
|
1399
|
-
const [isLoading, setIsLoading] =
|
|
1400
|
-
const [error, setError] =
|
|
1401
|
-
const controllerRef =
|
|
1402
|
-
const load =
|
|
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
|
-
|
|
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 =
|
|
1438
|
-
const [isAttached, setIsAttached] =
|
|
1439
|
-
const [error, setError] =
|
|
1440
|
-
|
|
1441
|
-
const detach =
|
|
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 =
|
|
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 =
|
|
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] =
|
|
1472
|
-
const [isUploading, setIsUploading] =
|
|
1473
|
-
const uploadBatch =
|
|
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 =
|
|
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] =
|
|
1495
|
-
const [lastResult, setLastResult] =
|
|
1496
|
-
const resume =
|
|
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] =
|
|
1511
|
-
const startAuthorization =
|
|
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 =
|
|
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
|
};
|