@agents24/react 0.2.7 → 0.2.9
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 +2 -0
- package/dist/index.js +848 -333
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts +13 -0
- package/dist/state.d.ts +8 -0
- package/dist/thread-message-queue.d.ts +49 -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,24 @@ 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) => {
|
|
403
|
+
const descriptor = item.attachments?.find((attachment) => attachment.id === id);
|
|
404
|
+
return { id, filename: descriptor?.filename || "Attachment", mediaType: descriptor?.mime_type || "application/octet-stream" };
|
|
405
|
+
}) : void 0,
|
|
406
|
+
feedback: item.role === "assistant" && item.run_id ? feedbackByRun.get(item.run_id) : void 0
|
|
407
|
+
}));
|
|
408
|
+
}
|
|
372
409
|
function latestThreadContext(detail) {
|
|
373
410
|
let contextWindow = null;
|
|
374
411
|
for (const turn of detail.turns || []) {
|
|
@@ -415,6 +452,54 @@ function useAgentClient() {
|
|
|
415
452
|
return useAgents24Runtime().client;
|
|
416
453
|
}
|
|
417
454
|
|
|
455
|
+
// src/runtime-event-buffer.ts
|
|
456
|
+
function runtimeEventFrameScheduler() {
|
|
457
|
+
const runtime = globalThis;
|
|
458
|
+
if (typeof runtime.requestAnimationFrame !== "function") return void 0;
|
|
459
|
+
return (callback) => {
|
|
460
|
+
const handle = runtime.requestAnimationFrame?.(callback);
|
|
461
|
+
return () => {
|
|
462
|
+
if (handle !== void 0) runtime.cancelAnimationFrame?.(handle);
|
|
463
|
+
};
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function createRuntimeEventFrameBuffer(apply, scheduleFrame) {
|
|
467
|
+
let pending = [];
|
|
468
|
+
let cancelFrame = null;
|
|
469
|
+
const flush = () => {
|
|
470
|
+
cancelFrame?.();
|
|
471
|
+
cancelFrame = null;
|
|
472
|
+
if (!pending.length) return;
|
|
473
|
+
const events = pending;
|
|
474
|
+
pending = [];
|
|
475
|
+
apply(events);
|
|
476
|
+
};
|
|
477
|
+
return {
|
|
478
|
+
dispose() {
|
|
479
|
+
cancelFrame?.();
|
|
480
|
+
cancelFrame = null;
|
|
481
|
+
pending = [];
|
|
482
|
+
},
|
|
483
|
+
flush,
|
|
484
|
+
push(event) {
|
|
485
|
+
if (event.event !== "assistant.delta" || !scheduleFrame) {
|
|
486
|
+
pending.push(event);
|
|
487
|
+
flush();
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
pending.push(event);
|
|
491
|
+
if (cancelFrame) return;
|
|
492
|
+
cancelFrame = scheduleFrame(() => {
|
|
493
|
+
cancelFrame = null;
|
|
494
|
+
if (!pending.length) return;
|
|
495
|
+
const events = pending;
|
|
496
|
+
pending = [];
|
|
497
|
+
apply(events);
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
418
503
|
// src/state.ts
|
|
419
504
|
import {
|
|
420
505
|
compressionFromContextWindow as compressionFromContextWindow2,
|
|
@@ -478,10 +563,18 @@ function clearAgentChatBackgroundError(state) {
|
|
|
478
563
|
return state.backgroundError ? { ...state, backgroundError: null } : state;
|
|
479
564
|
}
|
|
480
565
|
function reconcileRefreshedThreads(current, incoming, activeThreadId, concurrentEvents = []) {
|
|
566
|
+
const currentById = new Map(current.map((thread) => [thread.id, thread]));
|
|
567
|
+
const newest = incoming.map((thread) => preferNewestThreadSummary(currentById.get(thread.id), thread));
|
|
481
568
|
const active = activeThreadId && !incoming.some((thread) => thread.id === activeThreadId) ? current.find((thread) => thread.id === activeThreadId) : null;
|
|
482
|
-
const base = active?.isHydrated ? [active, ...
|
|
569
|
+
const base = active?.isHydrated ? [active, ...newest] : newest;
|
|
483
570
|
return concurrentEvents.reduce(applyThreadSummaryEvent, base);
|
|
484
571
|
}
|
|
572
|
+
function preferNewestThreadSummary(current, incoming) {
|
|
573
|
+
if (!current?.updated_at || !incoming.updated_at) return incoming;
|
|
574
|
+
const currentTime = Date.parse(current.updated_at);
|
|
575
|
+
const incomingTime = Date.parse(incoming.updated_at);
|
|
576
|
+
return Number.isFinite(currentTime) && Number.isFinite(incomingTime) && currentTime > incomingTime ? current : incoming;
|
|
577
|
+
}
|
|
485
578
|
function adoptStreamingThread(state, threadId) {
|
|
486
579
|
const normalizedThreadId = threadId.trim();
|
|
487
580
|
if (!normalizedThreadId || state.activeThreadId) return state;
|
|
@@ -507,13 +600,36 @@ function appendStableStreamingTurn(messages, userMessage, assistantMessage) {
|
|
|
507
600
|
))) next.push(assistantMessage);
|
|
508
601
|
return next;
|
|
509
602
|
}
|
|
603
|
+
function preserveMountedMessageIds(canonical, current) {
|
|
604
|
+
return canonical.map((message) => {
|
|
605
|
+
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));
|
|
606
|
+
return mounted && mounted.id !== message.id ? { ...message, id: mounted.id, durableId: message.id } : message;
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
function reconcileThreadTimeline(current, canonical, activeRunId, streamingMessageId) {
|
|
610
|
+
const mounted = preserveMountedMessageIds(canonical, current);
|
|
611
|
+
const canonicalIds = new Set(mounted.map((message) => message.id));
|
|
612
|
+
const retained = current.filter((message) => !canonicalIds.has(message.id) && (message.isFinal || activeRunId && (!message.runId || message.runId === activeRunId)));
|
|
613
|
+
const reconciled = mounted.map((message) => {
|
|
614
|
+
const live = current.find((item) => item.id === message.id);
|
|
615
|
+
return activeRunId && live?.role === "assistant" && live.runId === activeRunId && (live.id === streamingMessageId || !live.isFinal) ? { ...message, ...live, durableId: message.durableId || live.durableId } : message;
|
|
616
|
+
});
|
|
617
|
+
return [...retained, ...reconciled];
|
|
618
|
+
}
|
|
619
|
+
function promoteThreadMessage(current, message, runId) {
|
|
620
|
+
const promoted = { ...message, runId, isFinal: true };
|
|
621
|
+
const index = current.findIndex((item) => item.id === message.id || item.durableId === message.id);
|
|
622
|
+
if (index < 0) return [...current, promoted];
|
|
623
|
+
return current.map((item, itemIndex) => itemIndex === index ? { ...item, ...promoted, id: item.id, durableId: item.id === message.id ? item.durableId : message.id } : item);
|
|
624
|
+
}
|
|
510
625
|
function upsertStableAssistantMessage(messages, input) {
|
|
511
626
|
const next = [...messages];
|
|
512
627
|
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));
|
|
628
|
+
const isTarget = baseMessage.role === "assistant" && (Boolean(input.messageId && baseMessage.id === input.messageId) || Boolean(!input.strictMessageId && input.runId && baseMessage.runId === input.runId));
|
|
514
629
|
if (!isTarget && !next.some((message) => message.id === baseMessage.id)) next.push(baseMessage);
|
|
515
630
|
}
|
|
516
|
-
const
|
|
631
|
+
const exactIndex = next.findIndex((message) => message.role === "assistant" && Boolean(input.messageId && message.id === input.messageId));
|
|
632
|
+
const index = exactIndex >= 0 ? exactIndex : input.strictMessageId ? -1 : next.findIndex((message) => message.role === "assistant" && Boolean(input.runId && message.runId === input.runId));
|
|
517
633
|
if (index === -1) next.push(input.create());
|
|
518
634
|
else next[index] = input.update(next[index]);
|
|
519
635
|
return next;
|
|
@@ -556,24 +672,41 @@ function reconcileModelTurnParts(parts, event) {
|
|
|
556
672
|
return active ? upsertResponseBlockParts(next, [active]) : next;
|
|
557
673
|
}
|
|
558
674
|
function applyRuntimeEvent(state, event) {
|
|
675
|
+
if (event.event === "run.accepted" && typeof event.payload?.message_id === "string") {
|
|
676
|
+
const messageId2 = event.payload.message_id;
|
|
677
|
+
let optimisticIndex = -1;
|
|
678
|
+
for (let index = state.messages.length - 1; index >= 0; index -= 1) {
|
|
679
|
+
const message = state.messages[index];
|
|
680
|
+
if (message.role === "user" && !message.runId && !message.durableId) {
|
|
681
|
+
optimisticIndex = index;
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (optimisticIndex >= 0) {
|
|
686
|
+
state = { ...state, messages: state.messages.map((message, index) => index === optimisticIndex ? { ...message, durableId: messageId2, runId: event.run_id } : message.role === "assistant" && !message.runId ? { ...message, runId: event.run_id } : message) };
|
|
687
|
+
}
|
|
688
|
+
}
|
|
559
689
|
const projection = assistantProjectionFromEvent(event);
|
|
560
690
|
const eventName = String(event.event || "");
|
|
561
|
-
const runId = event.run_id || state.activeRunId;
|
|
562
|
-
const
|
|
691
|
+
const runId = state.activeRunId && event.event !== "run.accepted" ? state.activeRunId : event.run_id || state.activeRunId;
|
|
692
|
+
const segmentId = typeof event.payload?.segment_id === "string" ? event.payload.segment_id : null;
|
|
693
|
+
const distinctSegment = Boolean(segmentId && segmentId !== `${runId}:0`);
|
|
694
|
+
const messageId = distinctSegment ? `${runId || "run"}-assistant-${segmentId}` : state.streamingMessageId || `${runId || "run"}-assistant`;
|
|
563
695
|
const terminal = eventName === "run.completed" || eventName === "run.failed" || eventName === "run.cancelled";
|
|
564
696
|
const paused = eventName === "run.paused" || projection.parts.some((part) => part.kind === "hitl" && part.status === "pending");
|
|
565
697
|
const suspension = eventName === "run.waiting" ? "waiting" : eventName === "run.interrupted" ? "interrupted" : null;
|
|
566
698
|
const cancelling = eventName === "run.cancelling" || String(event.payload?.status || "") === "cancelling";
|
|
567
|
-
const existing = state.messages.find((message) => message.role === "assistant" &&
|
|
699
|
+
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
700
|
const payload = event.payload || {};
|
|
569
701
|
const eventThreadId = typeof payload.thread_id === "string" && payload.thread_id.trim() ? payload.thread_id.trim() : null;
|
|
570
702
|
const contextApplies = (!state.activeThreadId || !eventThreadId || eventThreadId === state.activeThreadId) && (!state.activeRunId || !runId || runId === state.activeRunId);
|
|
571
|
-
const
|
|
703
|
+
const contextPayload = payload.context_window && typeof payload.context_window === "object" ? { ...payload.context_window, run_id: runId } : payload.context_window;
|
|
704
|
+
const contextWindow = contextApplies ? mergeContextWindowUpdate2(state.contextWindow, contextPayload) : state.contextWindow;
|
|
572
705
|
const explicitCompression = contextApplies ? normalizeContextCompression2(payload.context_compression) : null;
|
|
573
706
|
const contextCompression = contextApplies ? explicitCompression || compressionFromContextWindow2(contextWindow) || state.contextCompression : state.contextCompression;
|
|
574
707
|
const hasLiveCompressionUpdate = Boolean(
|
|
575
708
|
contextApplies && (explicitCompression?.active || compressionFromContextWindow2(
|
|
576
|
-
mergeContextWindowUpdate2(null,
|
|
709
|
+
mergeContextWindowUpdate2(null, contextPayload)
|
|
577
710
|
)?.active)
|
|
578
711
|
);
|
|
579
712
|
const delta = eventName === "assistant.delta" && typeof payload.content === "string" ? payload.content : "";
|
|
@@ -591,6 +724,7 @@ function applyRuntimeEvent(state, event) {
|
|
|
591
724
|
const messages = upsertStableAssistantMessage(state.messages, {
|
|
592
725
|
messageId,
|
|
593
726
|
runId,
|
|
727
|
+
strictMessageId: distinctSegment,
|
|
594
728
|
create: () => ({
|
|
595
729
|
id: messageId,
|
|
596
730
|
role: "assistant",
|
|
@@ -663,61 +797,164 @@ function applyThreadSummaryEvent(threads, event) {
|
|
|
663
797
|
const index = threads.findIndex((thread) => thread.id === incoming.id);
|
|
664
798
|
if (index === -1) return [{ ...incoming, messages: incoming.messages || [], isHydrated: false }, ...threads];
|
|
665
799
|
const next = [...threads];
|
|
666
|
-
next[index] =
|
|
800
|
+
next[index] = preferNewestThreadSummary(next[index], {
|
|
801
|
+
...next[index],
|
|
802
|
+
...incoming,
|
|
803
|
+
messages: next[index].messages,
|
|
804
|
+
isHydrated: next[index].isHydrated
|
|
805
|
+
});
|
|
667
806
|
return next;
|
|
668
807
|
}
|
|
669
808
|
|
|
670
|
-
// src/
|
|
809
|
+
// src/thread-message-queue.ts
|
|
671
810
|
import * as React2 from "react";
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
pending
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
pending.
|
|
705
|
-
|
|
706
|
-
|
|
811
|
+
function useThreadMessageQueue({ threadId, transport, onPromoted }) {
|
|
812
|
+
const [pendingMessages, setPendingMessages] = React2.useState([]);
|
|
813
|
+
const [messageQueue, setMessageQueue] = React2.useState(null);
|
|
814
|
+
const pendingRef = React2.useRef(/* @__PURE__ */ new Map());
|
|
815
|
+
const versionRef = React2.useRef(0);
|
|
816
|
+
const cursorRef = React2.useRef(0);
|
|
817
|
+
const promotedRef = React2.useRef(/* @__PURE__ */ new Set());
|
|
818
|
+
const threadRef = React2.useRef(threadId);
|
|
819
|
+
const transportRef = React2.useRef(transport);
|
|
820
|
+
const onPromotedRef = React2.useRef(onPromoted);
|
|
821
|
+
threadRef.current = threadId;
|
|
822
|
+
transportRef.current = transport;
|
|
823
|
+
onPromotedRef.current = onPromoted;
|
|
824
|
+
const hydrate = React2.useCallback((page, selectedThreadId) => {
|
|
825
|
+
if (threadRef.current !== selectedThreadId) return;
|
|
826
|
+
const pending = page.pending.filter((message) => !promotedRef.current.has(message.id)).map((message) => {
|
|
827
|
+
const known = pendingRef.current.get(message.id);
|
|
828
|
+
if (message.attachments?.length || !known?.attachments?.length) return message;
|
|
829
|
+
return { ...message, attachments: known.attachments.filter((attachment) => message.attachment_ids.includes(attachment.id)) };
|
|
830
|
+
});
|
|
831
|
+
for (const message of pending) pendingRef.current.set(message.id, message);
|
|
832
|
+
setPendingMessages(pending);
|
|
833
|
+
setMessageQueue(page.queue);
|
|
834
|
+
}, []);
|
|
835
|
+
const refresh = React2.useCallback(async (selectedThreadId = threadRef.current) => {
|
|
836
|
+
if (!selectedThreadId) return null;
|
|
837
|
+
const version = ++versionRef.current;
|
|
838
|
+
const page = await transportRef.current.list(selectedThreadId);
|
|
839
|
+
if (version !== versionRef.current || threadRef.current !== selectedThreadId) return null;
|
|
840
|
+
for (const item of page.items) {
|
|
841
|
+
if (item.role === "user" && item.status === "delivered" && item.run_id) {
|
|
842
|
+
const pending = pendingRef.current.get(item.id);
|
|
843
|
+
if (pending && !promotedRef.current.has(item.id)) {
|
|
844
|
+
promotedRef.current.add(item.id);
|
|
845
|
+
pendingRef.current.delete(item.id);
|
|
846
|
+
onPromotedRef.current({
|
|
847
|
+
...pending,
|
|
848
|
+
...item,
|
|
849
|
+
attachments: item.attachments?.length ? item.attachments : pending.attachments
|
|
850
|
+
}, item.run_id);
|
|
851
|
+
}
|
|
707
852
|
}
|
|
708
|
-
pending.push(event);
|
|
709
|
-
if (cancelFrame) return;
|
|
710
|
-
cancelFrame = scheduleFrame(() => {
|
|
711
|
-
cancelFrame = null;
|
|
712
|
-
if (!pending.length) return;
|
|
713
|
-
const events = pending;
|
|
714
|
-
pending = [];
|
|
715
|
-
apply(events);
|
|
716
|
-
});
|
|
717
853
|
}
|
|
718
|
-
|
|
854
|
+
hydrate(page, selectedThreadId);
|
|
855
|
+
return page;
|
|
856
|
+
}, [hydrate]);
|
|
857
|
+
const acceptNotice = React2.useCallback((notice) => {
|
|
858
|
+
if (notice.cursor && notice.cursor <= cursorRef.current) return false;
|
|
859
|
+
if (notice.cursor) cursorRef.current = notice.cursor;
|
|
860
|
+
const pending = pendingRef.current.get(notice.messageId);
|
|
861
|
+
if (notice.status === "delivered" && notice.runId && pending && !promotedRef.current.has(pending.id)) {
|
|
862
|
+
promotedRef.current.add(pending.id);
|
|
863
|
+
pendingRef.current.delete(pending.id);
|
|
864
|
+
versionRef.current += 1;
|
|
865
|
+
setPendingMessages((current) => current.filter((message) => message.id !== pending.id));
|
|
866
|
+
onPromotedRef.current(pending, notice.runId);
|
|
867
|
+
}
|
|
868
|
+
if (notice.status === "removed") pendingRef.current.delete(notice.messageId);
|
|
869
|
+
void refresh().catch(() => void 0);
|
|
870
|
+
return true;
|
|
871
|
+
}, [refresh]);
|
|
872
|
+
const submit = React2.useCallback(async (input) => {
|
|
873
|
+
const selectedThreadId = threadRef.current;
|
|
874
|
+
if (!selectedThreadId) throw new Error("No active Agent thread is available.");
|
|
875
|
+
const { attachments, ...submission } = input;
|
|
876
|
+
const result = await transportRef.current.submit({ ...submission, threadId: selectedThreadId });
|
|
877
|
+
if (threadRef.current !== selectedThreadId) return result;
|
|
878
|
+
versionRef.current += 1;
|
|
879
|
+
if (!result.run_id && ["queued", "steer_pending", "blocked"].includes(result.status)) {
|
|
880
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
881
|
+
const pending = {
|
|
882
|
+
id: result.message_id,
|
|
883
|
+
thread_id: selectedThreadId,
|
|
884
|
+
sequence: Number.MAX_SAFE_INTEGER,
|
|
885
|
+
run_id: null,
|
|
886
|
+
turn_id: null,
|
|
887
|
+
segment_index: null,
|
|
888
|
+
role: "user",
|
|
889
|
+
status: result.status,
|
|
890
|
+
requested_mode: input.mode,
|
|
891
|
+
text: input.input,
|
|
892
|
+
attachment_ids: input.attachmentIds || [],
|
|
893
|
+
attachments,
|
|
894
|
+
response_blocks: [],
|
|
895
|
+
revision: 1,
|
|
896
|
+
created_at: now,
|
|
897
|
+
updated_at: now
|
|
898
|
+
};
|
|
899
|
+
pendingRef.current.set(pending.id, pending);
|
|
900
|
+
setPendingMessages((current) => current.some((message) => message.id === pending.id) ? current : [...current, pending]);
|
|
901
|
+
setMessageQueue((current) => ({
|
|
902
|
+
pending_count: Math.max(current?.pending_count || 0, result.queue_position || 1),
|
|
903
|
+
paused: current?.paused || false,
|
|
904
|
+
reason: current?.reason || null
|
|
905
|
+
}));
|
|
906
|
+
}
|
|
907
|
+
void refresh().catch(() => void 0);
|
|
908
|
+
return result;
|
|
909
|
+
}, [refresh]);
|
|
910
|
+
const edit = React2.useCallback(async (message, input) => {
|
|
911
|
+
const updated = await transportRef.current.edit(message, input);
|
|
912
|
+
versionRef.current += 1;
|
|
913
|
+
pendingRef.current.set(updated.id, updated);
|
|
914
|
+
await refresh(message.thread_id);
|
|
915
|
+
return updated;
|
|
916
|
+
}, [refresh]);
|
|
917
|
+
const remove = React2.useCallback(async (message) => {
|
|
918
|
+
await transportRef.current.remove(message);
|
|
919
|
+
versionRef.current += 1;
|
|
920
|
+
pendingRef.current.delete(message.id);
|
|
921
|
+
setPendingMessages((current) => current.filter((item) => item.id !== message.id));
|
|
922
|
+
await refresh(message.thread_id);
|
|
923
|
+
}, [refresh]);
|
|
924
|
+
const retractForEdit = React2.useCallback(async (message) => {
|
|
925
|
+
await remove(message);
|
|
926
|
+
return { text: message.text, attachment_ids: message.attachment_ids, attachments: message.attachments };
|
|
927
|
+
}, [remove]);
|
|
928
|
+
const resume = React2.useCallback(async () => {
|
|
929
|
+
const selectedThreadId = threadRef.current;
|
|
930
|
+
if (!selectedThreadId) return null;
|
|
931
|
+
const result = await transportRef.current.resume(selectedThreadId);
|
|
932
|
+
await refresh(selectedThreadId);
|
|
933
|
+
return result;
|
|
934
|
+
}, [refresh]);
|
|
935
|
+
React2.useEffect(() => {
|
|
936
|
+
versionRef.current += 1;
|
|
937
|
+
cursorRef.current = 0;
|
|
938
|
+
promotedRef.current.clear();
|
|
939
|
+
pendingRef.current.clear();
|
|
940
|
+
setPendingMessages([]);
|
|
941
|
+
setMessageQueue(null);
|
|
942
|
+
if (threadId) void refresh(threadId).catch(() => void 0);
|
|
943
|
+
}, [threadId, refresh]);
|
|
944
|
+
React2.useEffect(() => {
|
|
945
|
+
if (!threadId || !pendingMessages.length || messageQueue?.paused) return;
|
|
946
|
+
const clock = globalThis;
|
|
947
|
+
const timer = clock.setInterval(() => {
|
|
948
|
+
void refresh(threadId).catch(() => void 0);
|
|
949
|
+
}, 3e3);
|
|
950
|
+
return () => clock.clearInterval(timer);
|
|
951
|
+
}, [threadId, pendingMessages.length, messageQueue?.paused, refresh]);
|
|
952
|
+
return { pendingMessages, messageQueue, refresh, hydrate, acceptNotice, submit, edit, remove, retractForEdit, resume };
|
|
719
953
|
}
|
|
720
954
|
|
|
955
|
+
// src/use-agent-chat.ts
|
|
956
|
+
import * as React6 from "react";
|
|
957
|
+
|
|
721
958
|
// src/thread-summary-subscription.ts
|
|
722
959
|
function waitForReconnect(signal, delay) {
|
|
723
960
|
return new Promise((resolve) => {
|
|
@@ -770,6 +1007,167 @@ async function subscribeToThreadSummaries(input) {
|
|
|
770
1007
|
}
|
|
771
1008
|
}
|
|
772
1009
|
|
|
1010
|
+
// src/use-agent-chat-thread-list.ts
|
|
1011
|
+
import * as React3 from "react";
|
|
1012
|
+
function summaryToStored(thread) {
|
|
1013
|
+
return { ...thread, id: String(thread.id), messages: [], isHydrated: false };
|
|
1014
|
+
}
|
|
1015
|
+
function useAgentChatThreadList({ client, threadPageSize, stateRef, update, clearOperationError, failOperation }) {
|
|
1016
|
+
const [isLoadingThreads, setIsLoadingThreads] = React3.useState(false);
|
|
1017
|
+
const [isLoadingMoreThreads, setIsLoadingMoreThreads] = React3.useState(false);
|
|
1018
|
+
const [threadTotal, setThreadTotal] = React3.useState();
|
|
1019
|
+
const refreshPromiseRef = React3.useRef(null);
|
|
1020
|
+
const eventsDuringRefreshRef = React3.useRef(null);
|
|
1021
|
+
const refreshThreads = React3.useCallback(() => {
|
|
1022
|
+
if (refreshPromiseRef.current) return refreshPromiseRef.current;
|
|
1023
|
+
clearOperationError();
|
|
1024
|
+
setIsLoadingThreads(true);
|
|
1025
|
+
eventsDuringRefreshRef.current = [];
|
|
1026
|
+
const request = (async () => {
|
|
1027
|
+
try {
|
|
1028
|
+
const result = await client.threads.list({ limit: threadPageSize });
|
|
1029
|
+
const concurrentEvents = eventsDuringRefreshRef.current || [];
|
|
1030
|
+
eventsDuringRefreshRef.current = null;
|
|
1031
|
+
setThreadTotal(result.total);
|
|
1032
|
+
update((current) => {
|
|
1033
|
+
const byId = new Map(current.threads.map((thread) => [thread.id, thread]));
|
|
1034
|
+
const incoming = result.items.map((item) => {
|
|
1035
|
+
const existing = byId.get(item.id);
|
|
1036
|
+
return { ...existing, ...summaryToStored(item), messages: existing?.messages || [], isHydrated: existing?.isHydrated };
|
|
1037
|
+
});
|
|
1038
|
+
return { ...current, threads: reconcileRefreshedThreads(current.threads, incoming, current.activeThreadId, concurrentEvents) };
|
|
1039
|
+
});
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
if (!isAbortLike(error)) {
|
|
1042
|
+
failOperation("refresh_threads", error);
|
|
1043
|
+
throw error;
|
|
1044
|
+
}
|
|
1045
|
+
} finally {
|
|
1046
|
+
eventsDuringRefreshRef.current = null;
|
|
1047
|
+
setIsLoadingThreads(false);
|
|
1048
|
+
}
|
|
1049
|
+
})();
|
|
1050
|
+
refreshPromiseRef.current = request;
|
|
1051
|
+
void request.finally(() => {
|
|
1052
|
+
if (refreshPromiseRef.current === request) refreshPromiseRef.current = null;
|
|
1053
|
+
}).catch(() => void 0);
|
|
1054
|
+
return request;
|
|
1055
|
+
}, [clearOperationError, client, failOperation, threadPageSize, update]);
|
|
1056
|
+
const loadMoreThreads = React3.useCallback(async () => {
|
|
1057
|
+
if (isLoadingMoreThreads) return;
|
|
1058
|
+
const skip = stateRef.current.threads.length;
|
|
1059
|
+
if (threadTotal !== void 0 && skip >= threadTotal) return;
|
|
1060
|
+
clearOperationError();
|
|
1061
|
+
setIsLoadingMoreThreads(true);
|
|
1062
|
+
try {
|
|
1063
|
+
const result = await client.threads.list({ skip, limit: threadPageSize });
|
|
1064
|
+
setThreadTotal(result.total);
|
|
1065
|
+
update((current) => {
|
|
1066
|
+
const byId = new Map(current.threads.map((thread) => [thread.id, thread]));
|
|
1067
|
+
for (const item of result.items) {
|
|
1068
|
+
const existing = byId.get(item.id);
|
|
1069
|
+
byId.set(item.id, preferNewestThreadSummary(existing, {
|
|
1070
|
+
...existing,
|
|
1071
|
+
...summaryToStored(item),
|
|
1072
|
+
messages: existing?.messages || [],
|
|
1073
|
+
isHydrated: existing?.isHydrated
|
|
1074
|
+
}));
|
|
1075
|
+
}
|
|
1076
|
+
return { ...current, threads: [...byId.values()] };
|
|
1077
|
+
});
|
|
1078
|
+
} catch (error) {
|
|
1079
|
+
if (!isAbortLike(error)) {
|
|
1080
|
+
failOperation("load_more_threads", error);
|
|
1081
|
+
throw error;
|
|
1082
|
+
}
|
|
1083
|
+
} finally {
|
|
1084
|
+
setIsLoadingMoreThreads(false);
|
|
1085
|
+
}
|
|
1086
|
+
}, [clearOperationError, client, failOperation, isLoadingMoreThreads, stateRef, threadPageSize, threadTotal, update]);
|
|
1087
|
+
return { isLoadingThreads, isLoadingMoreThreads, threadTotal, eventsDuringRefreshRef, refreshThreads, loadMoreThreads };
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// src/use-agent-chat-feedback.ts
|
|
1091
|
+
import * as React4 from "react";
|
|
1092
|
+
function useAgentChatFeedback({ client, update, clearOperationError, failOperation }) {
|
|
1093
|
+
return React4.useCallback(async (message, input) => {
|
|
1094
|
+
const runId = message.runId;
|
|
1095
|
+
if (!runId || message.role !== "assistant" || message.isFinal === false) {
|
|
1096
|
+
const error = new Error("Only persisted final responses can receive feedback.");
|
|
1097
|
+
failOperation("set_feedback", error);
|
|
1098
|
+
throw error;
|
|
1099
|
+
}
|
|
1100
|
+
const previous = message.feedback ?? null;
|
|
1101
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1102
|
+
const optimistic = input.rating ? {
|
|
1103
|
+
rating: input.rating,
|
|
1104
|
+
reason: input.rating === "dislike" ? input.reason ?? null : null,
|
|
1105
|
+
comment: input.rating === "dislike" ? input.comment?.trim() || null : null,
|
|
1106
|
+
created_at: previous?.created_at || timestamp,
|
|
1107
|
+
updated_at: timestamp
|
|
1108
|
+
} : null;
|
|
1109
|
+
const replaceFeedback = (current, feedback) => ({
|
|
1110
|
+
...current,
|
|
1111
|
+
messages: current.messages.map((item) => item.id === message.id ? { ...item, feedback } : item),
|
|
1112
|
+
threads: current.threads.map((thread) => ({
|
|
1113
|
+
...thread,
|
|
1114
|
+
messages: thread.messages.map((item) => item.id === message.id ? { ...item, feedback } : item)
|
|
1115
|
+
}))
|
|
1116
|
+
});
|
|
1117
|
+
clearOperationError();
|
|
1118
|
+
update((current) => replaceFeedback(current, optimistic));
|
|
1119
|
+
try {
|
|
1120
|
+
const result = await client.runs.setFeedback({ runId, ...input });
|
|
1121
|
+
update((current) => replaceFeedback(current, result.feedback));
|
|
1122
|
+
} catch (error) {
|
|
1123
|
+
update((current) => withAgentChatOperationError(replaceFeedback(current, previous), "set_feedback", error));
|
|
1124
|
+
throw error;
|
|
1125
|
+
}
|
|
1126
|
+
}, [clearOperationError, client, failOperation, update]);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// src/use-agent-chat-queue-actions.ts
|
|
1130
|
+
import * as React5 from "react";
|
|
1131
|
+
function useAgentChatQueueActions({
|
|
1132
|
+
queue,
|
|
1133
|
+
stateRef,
|
|
1134
|
+
createId,
|
|
1135
|
+
clearOperationError,
|
|
1136
|
+
failOperation,
|
|
1137
|
+
refreshMessages,
|
|
1138
|
+
attachRun
|
|
1139
|
+
}) {
|
|
1140
|
+
const steer = React5.useCallback(async (text2) => {
|
|
1141
|
+
const threadId = stateRef.current.activeThreadId;
|
|
1142
|
+
if (!threadId || !text2.trim()) throw new Error("An active thread and text are required to steer.");
|
|
1143
|
+
clearOperationError();
|
|
1144
|
+
try {
|
|
1145
|
+
await queue.submit({ mode: "steer", input: text2.trim(), idempotencyKey: createId() });
|
|
1146
|
+
await refreshMessages(threadId);
|
|
1147
|
+
} catch (error) {
|
|
1148
|
+
failOperation("submit", error);
|
|
1149
|
+
throw error;
|
|
1150
|
+
}
|
|
1151
|
+
}, [clearOperationError, createId, failOperation, queue.submit, refreshMessages, stateRef]);
|
|
1152
|
+
const editPendingMessage = React5.useCallback(async (message, input) => {
|
|
1153
|
+
await queue.edit(message, {
|
|
1154
|
+
text: input.text,
|
|
1155
|
+
attachmentIds: input.attachmentIds ?? message.attachment_ids,
|
|
1156
|
+
requestedAgentId: input.requestedAgentId,
|
|
1157
|
+
requestedModelId: input.requestedModelId
|
|
1158
|
+
});
|
|
1159
|
+
}, [queue.edit]);
|
|
1160
|
+
const steerPendingMessage = React5.useCallback(async (message) => {
|
|
1161
|
+
await queue.edit(message, { text: message.text, attachmentIds: message.attachment_ids, mode: "steer" });
|
|
1162
|
+
}, [queue.edit]);
|
|
1163
|
+
const resumeQueue = React5.useCallback(async () => {
|
|
1164
|
+
if (!stateRef.current.activeThreadId) return;
|
|
1165
|
+
const result = await queue.resume();
|
|
1166
|
+
if (result?.run_id) void attachRun(result.run_id).catch(() => void 0);
|
|
1167
|
+
}, [attachRun, queue.resume, stateRef]);
|
|
1168
|
+
return { steer, editPendingMessage, steerPendingMessage, removePendingMessage: queue.remove, resumeQueue };
|
|
1169
|
+
}
|
|
1170
|
+
|
|
773
1171
|
// src/initial-thread.ts
|
|
774
1172
|
function resolveInitialActiveThreadId(options, storedThreadId) {
|
|
775
1173
|
return Object.prototype.hasOwnProperty.call(options, "activeThreadId") ? options.activeThreadId ?? null : storedThreadId;
|
|
@@ -782,9 +1180,6 @@ function isCurrentThreadOpenRequest(input) {
|
|
|
782
1180
|
}
|
|
783
1181
|
|
|
784
1182
|
// src/use-agent-chat.ts
|
|
785
|
-
function summaryToStored(thread) {
|
|
786
|
-
return { ...thread, id: String(thread.id), messages: [], isHydrated: false };
|
|
787
|
-
}
|
|
788
1183
|
function activeRunIdFromThread(thread) {
|
|
789
1184
|
if (!thread) return null;
|
|
790
1185
|
const activity = getThreadActivity(thread);
|
|
@@ -795,7 +1190,7 @@ function useAgentChat(options = {}) {
|
|
|
795
1190
|
const client = options.client || runtime.client;
|
|
796
1191
|
const pageSize = options.pageSize ?? 30;
|
|
797
1192
|
const threadPageSize = options.threadPageSize ?? 50;
|
|
798
|
-
const [state, setState] =
|
|
1193
|
+
const [state, setState] = React6.useState(() => ({
|
|
799
1194
|
...initialAgentChatState,
|
|
800
1195
|
activeThreadId: resolveInitialActiveThreadId(
|
|
801
1196
|
options,
|
|
@@ -803,25 +1198,25 @@ function useAgentChat(options = {}) {
|
|
|
803
1198
|
),
|
|
804
1199
|
threads: runtime.storage?.loadThreads() ?? []
|
|
805
1200
|
}));
|
|
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 =
|
|
1201
|
+
const [isLoadingHistory, setIsLoadingHistory] = React6.useState(false);
|
|
1202
|
+
const [isLoadingOlder, setIsLoadingOlder] = React6.useState(false);
|
|
1203
|
+
const [isSubmitting, setIsSubmitting] = React6.useState(false);
|
|
1204
|
+
const streamControllerRef = React6.useRef(null);
|
|
1205
|
+
const threadOpenControllerRef = React6.useRef(null);
|
|
1206
|
+
const threadOpenGenerationRef = React6.useRef(0);
|
|
1207
|
+
const attachRunRef = React6.useRef(async () => void 0);
|
|
1208
|
+
const messageRefreshVersionRef = React6.useRef(0);
|
|
1209
|
+
const promotedRunHintRef = React6.useRef(null);
|
|
1210
|
+
const promotedAttachRef = React6.useRef(null);
|
|
1211
|
+
const firstSubmissionRef = React6.useRef(null);
|
|
1212
|
+
const firstSubmissionAdmittedRef = React6.useRef(null);
|
|
1213
|
+
const stateRef = React6.useRef(state);
|
|
819
1214
|
stateRef.current = state;
|
|
820
|
-
const persist =
|
|
1215
|
+
const persist = React6.useCallback((next) => {
|
|
821
1216
|
runtime.storage?.saveThreads(next.threads);
|
|
822
1217
|
runtime.storage?.saveActiveThreadId?.(next.activeThreadId);
|
|
823
1218
|
}, [runtime.storage]);
|
|
824
|
-
const update =
|
|
1219
|
+
const update = React6.useCallback((updater) => {
|
|
825
1220
|
setState((current) => {
|
|
826
1221
|
const next = updater(current);
|
|
827
1222
|
stateRef.current = next;
|
|
@@ -829,119 +1224,114 @@ function useAgentChat(options = {}) {
|
|
|
829
1224
|
return next;
|
|
830
1225
|
});
|
|
831
1226
|
}, [persist]);
|
|
832
|
-
const
|
|
1227
|
+
const queueTransport = React6.useMemo(() => ({
|
|
1228
|
+
list: (threadId) => client.threads.messages.list({ threadId, limit: pageSize }),
|
|
1229
|
+
submit: (input) => client.threads.messages.submit(input),
|
|
1230
|
+
edit: (message, input) => client.threads.messages.edit({
|
|
1231
|
+
threadId: message.thread_id,
|
|
1232
|
+
messageId: message.id,
|
|
1233
|
+
revision: message.revision,
|
|
1234
|
+
input: input.text,
|
|
1235
|
+
attachmentIds: input.attachmentIds ?? message.attachment_ids,
|
|
1236
|
+
mode: input.mode,
|
|
1237
|
+
requestedAgentId: input.requestedAgentId,
|
|
1238
|
+
requestedModelId: input.requestedModelId
|
|
1239
|
+
}),
|
|
1240
|
+
remove: async (message) => {
|
|
1241
|
+
await client.threads.messages.remove({ threadId: message.thread_id, messageId: message.id, revision: message.revision });
|
|
1242
|
+
},
|
|
1243
|
+
resume: (threadId) => client.threads.resumeQueue({ threadId })
|
|
1244
|
+
}), [client, pageSize]);
|
|
1245
|
+
const onQueuePromotion = React6.useCallback((message, runId) => {
|
|
1246
|
+
if (stateRef.current.activeThreadId !== message.thread_id) return;
|
|
1247
|
+
promotedRunHintRef.current = runId;
|
|
1248
|
+
const userMessage = threadTimelineToMessages([{ ...message, run_id: runId, status: "delivered" }])[0];
|
|
1249
|
+
update((current) => current.activeThreadId === message.thread_id ? { ...current, messages: promoteThreadMessage(current.messages, userMessage, runId) } : current);
|
|
1250
|
+
if (stateRef.current.activeRunId !== runId && promotedAttachRef.current !== runId) {
|
|
1251
|
+
promotedAttachRef.current = runId;
|
|
1252
|
+
void attachRunRef.current(runId).catch(() => {
|
|
1253
|
+
promotedAttachRef.current = null;
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
}, [update]);
|
|
1257
|
+
const queue = useThreadMessageQueue({ threadId: state.activeThreadId, transport: queueTransport, onPromoted: onQueuePromotion });
|
|
1258
|
+
const { pendingMessages, messageQueue } = queue;
|
|
1259
|
+
const eventBuffer = React6.useMemo(
|
|
833
1260
|
() => createRuntimeEventFrameBuffer(
|
|
834
1261
|
(events) => update((current) => applyRuntimeEvents(current, events)),
|
|
835
1262
|
runtimeEventFrameScheduler()
|
|
836
1263
|
),
|
|
837
1264
|
[update]
|
|
838
1265
|
);
|
|
839
|
-
const clearOperationError =
|
|
1266
|
+
const clearOperationError = React6.useCallback((operation) => {
|
|
840
1267
|
update((current) => clearAgentChatOperationError(current, operation));
|
|
841
1268
|
}, [update]);
|
|
842
|
-
const clearBackgroundError =
|
|
1269
|
+
const clearBackgroundError = React6.useCallback(() => {
|
|
843
1270
|
update(clearAgentChatBackgroundError);
|
|
844
1271
|
}, [update]);
|
|
845
|
-
const failOperation =
|
|
1272
|
+
const failOperation = React6.useCallback((operation, cause) => {
|
|
846
1273
|
update((current) => withAgentChatOperationError(current, operation, cause));
|
|
847
1274
|
}, [update]);
|
|
848
|
-
const
|
|
1275
|
+
const {
|
|
1276
|
+
isLoadingThreads,
|
|
1277
|
+
isLoadingMoreThreads,
|
|
1278
|
+
threadTotal,
|
|
1279
|
+
eventsDuringRefreshRef: threadEventsDuringRefreshRef,
|
|
1280
|
+
refreshThreads,
|
|
1281
|
+
loadMoreThreads
|
|
1282
|
+
} = useAgentChatThreadList({ client, threadPageSize, stateRef, update, clearOperationError, failOperation });
|
|
1283
|
+
const invalidateThreadOpen = React6.useCallback(() => {
|
|
849
1284
|
threadOpenGenerationRef.current += 1;
|
|
850
1285
|
threadOpenControllerRef.current?.abort("Thread selection changed.");
|
|
851
1286
|
threadOpenControllerRef.current = null;
|
|
852
1287
|
setIsLoadingHistory(false);
|
|
853
1288
|
}, []);
|
|
854
|
-
const detach =
|
|
1289
|
+
const detach = React6.useCallback(() => {
|
|
855
1290
|
eventBuffer.flush();
|
|
856
1291
|
streamControllerRef.current?.abort();
|
|
857
1292
|
streamControllerRef.current = null;
|
|
858
1293
|
update((current) => ({ ...current, streamingMessageId: null, runState: current.activeRunId ? "reconnecting" : "idle" }));
|
|
859
1294
|
}, [eventBuffer, update]);
|
|
860
|
-
|
|
1295
|
+
const refreshMessages = React6.useCallback(async (threadId, promotedRunId) => {
|
|
1296
|
+
const target = threadId || stateRef.current.activeThreadId;
|
|
1297
|
+
if (!target) return;
|
|
1298
|
+
const version = ++messageRefreshVersionRef.current;
|
|
1299
|
+
const page = await client.threads.messages.list({ threadId: target, limit: pageSize });
|
|
1300
|
+
if (stateRef.current.activeThreadId !== target || version !== messageRefreshVersionRef.current) return;
|
|
1301
|
+
queue.hydrate(page, target);
|
|
1302
|
+
const promoted = promotedRunId || promotedRunHintRef.current;
|
|
1303
|
+
if (promoted && page.items.some((message) => message.role === "user" && message.run_id === promoted)) {
|
|
1304
|
+
promotedRunHintRef.current = null;
|
|
1305
|
+
update((current) => {
|
|
1306
|
+
if (current.activeThreadId !== target) return current;
|
|
1307
|
+
return {
|
|
1308
|
+
...current,
|
|
1309
|
+
messages: reconcileThreadTimeline(current.messages, threadTimelineToMessages(page.items), current.activeRunId, current.streamingMessageId)
|
|
1310
|
+
};
|
|
1311
|
+
});
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
if (stateRef.current.activeRunId) {
|
|
1315
|
+
const delivered = threadTimelineToMessages(page.items.filter((item) => item.role === "user" && item.run_id === stateRef.current.activeRunId && (item.segment_index ?? 0) > 0));
|
|
1316
|
+
if (delivered.length) update((current) => {
|
|
1317
|
+
if (current.activeThreadId !== target) return current;
|
|
1318
|
+
const additions = delivered.filter((item) => !current.messages.some((existing) => existing.id === item.id));
|
|
1319
|
+
return additions.length ? { ...current, messages: [...current.messages, ...additions] } : current;
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
if (!stateRef.current.activeRunId && page.items.length) {
|
|
1323
|
+
update((current) => current.activeThreadId === target && !current.activeRunId ? (() => {
|
|
1324
|
+
const messages = reconcileThreadTimeline(current.messages, threadTimelineToMessages(page.items), null, null);
|
|
1325
|
+
return { ...current, messages, threads: current.threads.map((thread) => thread.id === target ? { ...thread, messages } : thread) };
|
|
1326
|
+
})() : current);
|
|
1327
|
+
}
|
|
1328
|
+
}, [client, pageSize, queue.hydrate, update]);
|
|
1329
|
+
React6.useEffect(() => () => {
|
|
861
1330
|
eventBuffer.dispose();
|
|
862
1331
|
streamControllerRef.current?.abort();
|
|
863
1332
|
threadOpenControllerRef.current?.abort();
|
|
864
1333
|
}, [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) => {
|
|
1334
|
+
const openThread = React6.useCallback(async (threadId) => {
|
|
945
1335
|
const previousSelection = captureAgentChatThreadSelection(stateRef.current);
|
|
946
1336
|
threadOpenControllerRef.current?.abort();
|
|
947
1337
|
const requestController = runtime.createAbortController();
|
|
@@ -977,7 +1367,16 @@ function useAgentChat(options = {}) {
|
|
|
977
1367
|
requestedThreadId: threadId,
|
|
978
1368
|
signalAborted: requestController.signal.aborted
|
|
979
1369
|
})) return;
|
|
980
|
-
const
|
|
1370
|
+
const timeline = await client.threads.messages.list({ threadId, limit: pageSize, signal: requestController.signal });
|
|
1371
|
+
if (!isCurrentThreadOpenRequest({
|
|
1372
|
+
activeThreadId: stateRef.current.activeThreadId,
|
|
1373
|
+
currentGeneration: threadOpenGenerationRef.current,
|
|
1374
|
+
requestGeneration,
|
|
1375
|
+
requestedThreadId: threadId,
|
|
1376
|
+
signalAborted: requestController.signal.aborted
|
|
1377
|
+
})) return;
|
|
1378
|
+
queue.hydrate(timeline, threadId);
|
|
1379
|
+
const messages = timeline.items.length ? threadTimelineToMessages(timeline.items, detail) : threadDetailToMessages(detail);
|
|
981
1380
|
const context = latestThreadContext(detail);
|
|
982
1381
|
const stored = {
|
|
983
1382
|
...detail,
|
|
@@ -1025,15 +1424,16 @@ function useAgentChat(options = {}) {
|
|
|
1025
1424
|
setIsLoadingHistory(false);
|
|
1026
1425
|
}
|
|
1027
1426
|
}
|
|
1028
|
-
}, [clearOperationError, client, detach, failOperation, pageSize, runtime, update]);
|
|
1029
|
-
const consumeEvent =
|
|
1427
|
+
}, [clearOperationError, client, detach, failOperation, pageSize, queue.hydrate, runtime, update]);
|
|
1428
|
+
const consumeEvent = React6.useCallback((event) => {
|
|
1030
1429
|
const eventThreadId = typeof event.payload?.thread_id === "string" ? event.payload.thread_id.trim() : "";
|
|
1031
1430
|
if (eventThreadId && !stateRef.current.activeThreadId) {
|
|
1032
1431
|
update((current) => adoptStreamingThread(current, eventThreadId));
|
|
1432
|
+
firstSubmissionAdmittedRef.current?.();
|
|
1033
1433
|
}
|
|
1034
1434
|
eventBuffer.push(event);
|
|
1035
1435
|
}, [eventBuffer, update]);
|
|
1036
|
-
const attachRun =
|
|
1436
|
+
const attachRun = React6.useCallback(async (runId, cursor) => {
|
|
1037
1437
|
streamControllerRef.current?.abort();
|
|
1038
1438
|
const controller = runtime.createAbortController();
|
|
1039
1439
|
streamControllerRef.current = controller;
|
|
@@ -1041,7 +1441,9 @@ function useAgentChat(options = {}) {
|
|
|
1041
1441
|
update((current) => ({ ...current, activeRunId: runId, runState: "reconnecting", error: null }));
|
|
1042
1442
|
try {
|
|
1043
1443
|
const result = await client.runs.attach({ runId, cursor, signal: controller.signal }, consumeEvent);
|
|
1444
|
+
eventBuffer.flush();
|
|
1044
1445
|
update((current) => ({ ...current, activeRunId: result.detached ? runId : current.activeRunId, cursor: result.cursor ?? current.cursor, runState: result.detached ? "reconnecting" : current.runState }));
|
|
1446
|
+
if (!result.detached && result.threadId) await refreshMessages(result.threadId);
|
|
1045
1447
|
} catch (error) {
|
|
1046
1448
|
if (!isAbortLike(error)) {
|
|
1047
1449
|
update((current) => ({ ...current, runState: "failed", streamingMessageId: null }));
|
|
@@ -1051,100 +1453,171 @@ function useAgentChat(options = {}) {
|
|
|
1051
1453
|
} finally {
|
|
1052
1454
|
if (streamControllerRef.current === controller) streamControllerRef.current = null;
|
|
1053
1455
|
}
|
|
1054
|
-
}, [clearOperationError, client, consumeEvent, failOperation, runtime, update]);
|
|
1456
|
+
}, [clearOperationError, client, consumeEvent, eventBuffer, failOperation, refreshMessages, runtime, update]);
|
|
1055
1457
|
attachRunRef.current = attachRun;
|
|
1056
|
-
const submit =
|
|
1458
|
+
const submit = React6.useCallback(async (input) => {
|
|
1057
1459
|
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
|
-
}));
|
|
1460
|
+
if (firstSubmissionRef.current && !stateRef.current.activeThreadId) {
|
|
1461
|
+
await firstSubmissionRef.current;
|
|
1462
|
+
}
|
|
1463
|
+
const existingThreadId = stateRef.current.activeThreadId;
|
|
1464
|
+
if (existingThreadId) {
|
|
1465
|
+
clearOperationError();
|
|
1466
|
+
setIsSubmitting(true);
|
|
1467
|
+
try {
|
|
1468
|
+
const uploaded = input.uploads?.length ? await client.attachments.uploadBatch({
|
|
1469
|
+
uploads: input.uploads,
|
|
1470
|
+
threadId: existingThreadId,
|
|
1471
|
+
requestedAgentId: input.requestedAgentId,
|
|
1472
|
+
idempotencyKey: runtime.createId()
|
|
1473
|
+
}) : [];
|
|
1474
|
+
const result = await queue.submit({
|
|
1475
|
+
mode: "queue",
|
|
1476
|
+
input: input.text.trim(),
|
|
1477
|
+
attachmentIds: uploaded.length ? uploaded.map((item) => item.id) : input.attachmentIds,
|
|
1478
|
+
requestedAgentId: input.requestedAgentId,
|
|
1479
|
+
requestedModelId: input.requestedModelId,
|
|
1480
|
+
idempotencyKey: runtime.createId()
|
|
1481
|
+
});
|
|
1482
|
+
if (stateRef.current.activeThreadId !== existingThreadId) return;
|
|
1483
|
+
if (result.run_id) {
|
|
1484
|
+
const userMessage = {
|
|
1485
|
+
id: result.message_id,
|
|
1486
|
+
role: "user",
|
|
1487
|
+
runId: result.run_id,
|
|
1488
|
+
content: input.text.trim(),
|
|
1489
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1490
|
+
isFinal: true,
|
|
1491
|
+
parts: input.text.trim() ? [{ id: `${result.message_id}-text`, type: "text", kind: "text", text: input.text.trim() }] : [],
|
|
1492
|
+
attachments: input.attachments || []
|
|
1493
|
+
};
|
|
1494
|
+
update((current) => current.activeThreadId !== existingThreadId || current.messages.some((message) => message.id === userMessage.id) ? current : { ...current, messages: [...current.messages, userMessage] });
|
|
1495
|
+
}
|
|
1496
|
+
void refreshMessages(existingThreadId).catch(() => void 0);
|
|
1497
|
+
if (result.run_id && promotedAttachRef.current !== result.run_id && stateRef.current.activeRunId !== result.run_id) {
|
|
1498
|
+
void attachRunRef.current(result.run_id).catch(() => void 0);
|
|
1499
|
+
}
|
|
1500
|
+
} catch (error) {
|
|
1134
1501
|
failOperation("submit", error);
|
|
1135
1502
|
throw error;
|
|
1503
|
+
} finally {
|
|
1504
|
+
setIsSubmitting(false);
|
|
1136
1505
|
}
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
const admission = new Promise((resolve) => {
|
|
1509
|
+
firstSubmissionAdmittedRef.current = resolve;
|
|
1510
|
+
});
|
|
1511
|
+
const stream = (async () => {
|
|
1512
|
+
invalidateThreadOpen();
|
|
1513
|
+
detach();
|
|
1514
|
+
const controller = runtime.createAbortController();
|
|
1515
|
+
streamControllerRef.current = controller;
|
|
1516
|
+
const userId = runtime.createId();
|
|
1517
|
+
const assistantId = runtime.createId();
|
|
1518
|
+
const idempotencyKey = runtime.createId();
|
|
1519
|
+
const optimisticAttachments = input.attachments?.length ? input.attachments : input.uploads?.map((upload, index) => ({
|
|
1520
|
+
id: `${userId}-attachment-${index}`,
|
|
1521
|
+
filename: upload.name,
|
|
1522
|
+
mediaType: upload.mediaType,
|
|
1523
|
+
status: "uploading"
|
|
1524
|
+
}));
|
|
1525
|
+
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 };
|
|
1526
|
+
const assistantMessage = { id: assistantId, role: "assistant", content: "", createdAt: (/* @__PURE__ */ new Date()).toISOString(), isFinal: false, parts: [] };
|
|
1527
|
+
clearOperationError();
|
|
1528
|
+
const hasPendingUploads = Boolean(input.uploads?.length);
|
|
1529
|
+
update((current) => ({
|
|
1530
|
+
...current,
|
|
1531
|
+
messages: appendStableStreamingTurn(current.messages, userMessage, assistantMessage),
|
|
1532
|
+
streamingMessageId: assistantId,
|
|
1533
|
+
runState: hasPendingUploads ? "uploading" : "streaming",
|
|
1534
|
+
error: null
|
|
1535
|
+
}));
|
|
1536
|
+
setIsSubmitting(true);
|
|
1537
|
+
let streamStarted = false;
|
|
1538
|
+
try {
|
|
1539
|
+
const uploaded = hasPendingUploads ? await client.attachments.uploadBatch({
|
|
1540
|
+
uploads: input.uploads || [],
|
|
1541
|
+
threadId: stateRef.current.activeThreadId || void 0,
|
|
1542
|
+
requestedAgentId: input.requestedAgentId,
|
|
1543
|
+
idempotencyKey: runtime.createId(),
|
|
1544
|
+
signal: controller.signal
|
|
1545
|
+
}) : [];
|
|
1546
|
+
if (hasPendingUploads && uploaded.length !== input.uploads?.length) {
|
|
1547
|
+
throw new Error("Attachment upload returned an incomplete batch");
|
|
1548
|
+
}
|
|
1549
|
+
const resolvedAttachments = uploaded.length ? uploaded.map(attachmentResultToChatAttachment) : input.attachments;
|
|
1550
|
+
const attachmentIds = uploaded.length ? uploaded.map((attachment) => attachment.id) : input.attachmentIds;
|
|
1551
|
+
if (uploaded.length) {
|
|
1552
|
+
update((current) => ({
|
|
1553
|
+
...current,
|
|
1554
|
+
messages: current.messages.map((message) => message.id === userId ? { ...message, attachments: resolvedAttachments } : message),
|
|
1555
|
+
runState: "streaming"
|
|
1556
|
+
}));
|
|
1557
|
+
}
|
|
1558
|
+
streamStarted = true;
|
|
1559
|
+
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);
|
|
1560
|
+
eventBuffer.flush();
|
|
1561
|
+
update((current) => {
|
|
1562
|
+
const threadId = result.threadId || current.activeThreadId;
|
|
1563
|
+
return {
|
|
1564
|
+
...current,
|
|
1565
|
+
activeThreadId: threadId,
|
|
1566
|
+
activeRunId: result.detached ? result.runId : current.activeRunId,
|
|
1567
|
+
cursor: result.cursor,
|
|
1568
|
+
runState: result.detached ? "reconnecting" : current.runState,
|
|
1569
|
+
threads: threadId ? current.threads.map((thread) => thread.id === threadId ? { ...thread, messages: current.messages, isHydrated: true } : thread) : current.threads
|
|
1570
|
+
};
|
|
1571
|
+
});
|
|
1572
|
+
if (result.threadId) await refreshThreads().catch(() => void 0);
|
|
1573
|
+
if (result.threadId && !result.detached) await refreshMessages(result.threadId);
|
|
1574
|
+
} catch (error) {
|
|
1575
|
+
if (isAbortLike(error) && !streamStarted) {
|
|
1576
|
+
update((current) => current.streamingMessageId === assistantId ? {
|
|
1577
|
+
...current,
|
|
1578
|
+
messages: current.messages.filter((message) => message.id !== userId && message.id !== assistantId),
|
|
1579
|
+
runState: "idle",
|
|
1580
|
+
streamingMessageId: null
|
|
1581
|
+
} : current);
|
|
1582
|
+
} else if (!isAbortLike(error)) {
|
|
1583
|
+
update((current) => ({
|
|
1584
|
+
...current,
|
|
1585
|
+
messages: streamStarted ? current.messages : current.messages.filter((message) => message.id !== userId && message.id !== assistantId),
|
|
1586
|
+
runState: "failed",
|
|
1587
|
+
streamingMessageId: null
|
|
1588
|
+
}));
|
|
1589
|
+
failOperation("submit", error);
|
|
1590
|
+
throw error;
|
|
1591
|
+
}
|
|
1592
|
+
} finally {
|
|
1593
|
+
setIsSubmitting(false);
|
|
1594
|
+
if (streamControllerRef.current === controller) streamControllerRef.current = null;
|
|
1595
|
+
}
|
|
1596
|
+
})();
|
|
1597
|
+
const accepted = Promise.race([stream, admission]);
|
|
1598
|
+
firstSubmissionRef.current = accepted;
|
|
1599
|
+
try {
|
|
1600
|
+
await accepted;
|
|
1137
1601
|
} finally {
|
|
1138
|
-
|
|
1139
|
-
|
|
1602
|
+
if (firstSubmissionRef.current === accepted) firstSubmissionRef.current = null;
|
|
1603
|
+
firstSubmissionAdmittedRef.current = null;
|
|
1140
1604
|
}
|
|
1141
|
-
}, [clearOperationError, client, consumeEvent, detach, eventBuffer, failOperation, invalidateThreadOpen, refreshThreads, runtime, update]);
|
|
1142
|
-
const
|
|
1605
|
+
}, [clearOperationError, client, consumeEvent, detach, eventBuffer, failOperation, invalidateThreadOpen, queue.submit, refreshMessages, refreshThreads, runtime, update]);
|
|
1606
|
+
const { steer, editPendingMessage, steerPendingMessage, removePendingMessage, resumeQueue } = useAgentChatQueueActions({
|
|
1607
|
+
queue,
|
|
1608
|
+
stateRef,
|
|
1609
|
+
createId: runtime.createId,
|
|
1610
|
+
clearOperationError,
|
|
1611
|
+
failOperation,
|
|
1612
|
+
refreshMessages,
|
|
1613
|
+
attachRun
|
|
1614
|
+
});
|
|
1615
|
+
const startNewThread = React6.useCallback(() => {
|
|
1143
1616
|
invalidateThreadOpen();
|
|
1144
1617
|
detach();
|
|
1145
1618
|
update((current) => ({ ...initialAgentChatState, threads: current.threads }));
|
|
1146
1619
|
}, [detach, invalidateThreadOpen, update]);
|
|
1147
|
-
const deleteThread =
|
|
1620
|
+
const deleteThread = React6.useCallback(async (threadId) => {
|
|
1148
1621
|
if (stateRef.current.activeThreadId === threadId) invalidateThreadOpen();
|
|
1149
1622
|
clearOperationError();
|
|
1150
1623
|
try {
|
|
@@ -1165,7 +1638,7 @@ function useAgentChat(options = {}) {
|
|
|
1165
1638
|
}
|
|
1166
1639
|
}
|
|
1167
1640
|
}, [clearOperationError, client, failOperation, invalidateThreadOpen, runtime, update]);
|
|
1168
|
-
const cancelRun =
|
|
1641
|
+
const cancelRun = React6.useCallback(async () => {
|
|
1169
1642
|
const runId = stateRef.current.activeRunId;
|
|
1170
1643
|
if (!runId || stateRef.current.runState === "cancelling") return;
|
|
1171
1644
|
const previousRunState = stateRef.current.runState;
|
|
@@ -1204,7 +1677,7 @@ function useAgentChat(options = {}) {
|
|
|
1204
1677
|
}
|
|
1205
1678
|
}
|
|
1206
1679
|
}, [clearOperationError, client, detach, failOperation, runtime, update]);
|
|
1207
|
-
const resumeHitl =
|
|
1680
|
+
const resumeHitl = React6.useCallback(async (part, response) => {
|
|
1208
1681
|
const runId = stateRef.current.activeRunId || stateRef.current.messages.find((message) => message.parts.includes(part))?.runId;
|
|
1209
1682
|
clearOperationError();
|
|
1210
1683
|
if (!runId) {
|
|
@@ -1225,54 +1698,8 @@ function useAgentChat(options = {}) {
|
|
|
1225
1698
|
throw error;
|
|
1226
1699
|
}
|
|
1227
1700
|
}, [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 () => {
|
|
1701
|
+
const setFeedback = useAgentChatFeedback({ client, update, clearOperationError, failOperation });
|
|
1702
|
+
const loadOlder = React6.useCallback(async () => {
|
|
1276
1703
|
const thread = stateRef.current.threads.find((item) => item.id === stateRef.current.activeThreadId);
|
|
1277
1704
|
if (!thread?.nextBeforeTurnIndex) return;
|
|
1278
1705
|
clearOperationError();
|
|
@@ -1290,7 +1717,7 @@ function useAgentChat(options = {}) {
|
|
|
1290
1717
|
setIsLoadingOlder(false);
|
|
1291
1718
|
}
|
|
1292
1719
|
}, [clearOperationError, client, failOperation, pageSize, update]);
|
|
1293
|
-
const regenerate =
|
|
1720
|
+
const regenerate = React6.useCallback(async (message) => {
|
|
1294
1721
|
const index = stateRef.current.messages.findIndex((item) => item.id === message.id);
|
|
1295
1722
|
const user = stateRef.current.messages.slice(0, index).reverse().find((item) => item.role === "user");
|
|
1296
1723
|
if (user) await submit({
|
|
@@ -1299,7 +1726,7 @@ function useAgentChat(options = {}) {
|
|
|
1299
1726
|
attachments: user.attachments
|
|
1300
1727
|
});
|
|
1301
1728
|
}, [submit]);
|
|
1302
|
-
|
|
1729
|
+
React6.useEffect(() => {
|
|
1303
1730
|
const controller = runtime.createAbortController();
|
|
1304
1731
|
clearBackgroundError();
|
|
1305
1732
|
void subscribeToThreadSummaries({
|
|
@@ -1312,6 +1739,26 @@ function useAgentChat(options = {}) {
|
|
|
1312
1739
|
...clearAgentChatBackgroundError(current),
|
|
1313
1740
|
threads: applyThreadSummaryEvent(current.threads, event)
|
|
1314
1741
|
}));
|
|
1742
|
+
if (event.event === "thread.message.updated" && event.thread_id === stateRef.current.activeThreadId) {
|
|
1743
|
+
const wasQueued = queue.acceptNotice({
|
|
1744
|
+
messageId: event.message_id,
|
|
1745
|
+
status: event.status,
|
|
1746
|
+
runId: event.run_id,
|
|
1747
|
+
cursor: event.cursor
|
|
1748
|
+
});
|
|
1749
|
+
if (wasQueued) void refreshMessages(event.thread_id, event.run_id || void 0).catch(() => void 0);
|
|
1750
|
+
}
|
|
1751
|
+
if (event.event === "thread.summary.updated" && event.thread.id === stateRef.current.activeThreadId) {
|
|
1752
|
+
if (streamControllerRef.current && stateRef.current.messages.some((message) => message.role === "user" && !message.runId)) return;
|
|
1753
|
+
const promotedRunId = activeRunIdFromThread(summaryToStored(event.thread));
|
|
1754
|
+
void (async () => {
|
|
1755
|
+
await refreshMessages(String(event.thread.id), promotedRunId || void 0);
|
|
1756
|
+
if (promotedRunId && stateRef.current.activeRunId !== promotedRunId && stateRef.current.activeThreadId === String(event.thread.id) && promotedAttachRef.current !== promotedRunId) {
|
|
1757
|
+
promotedAttachRef.current = promotedRunId;
|
|
1758
|
+
await attachRunRef.current(promotedRunId);
|
|
1759
|
+
}
|
|
1760
|
+
})().catch(() => void 0);
|
|
1761
|
+
}
|
|
1315
1762
|
},
|
|
1316
1763
|
onError(error) {
|
|
1317
1764
|
update((current) => withAgentChatBackgroundError(current, error));
|
|
@@ -1321,8 +1768,8 @@ function useAgentChat(options = {}) {
|
|
|
1321
1768
|
}
|
|
1322
1769
|
});
|
|
1323
1770
|
return () => controller.abort();
|
|
1324
|
-
}, [clearBackgroundError, client, refreshThreads, runtime.createAbortController, update]);
|
|
1325
|
-
|
|
1771
|
+
}, [clearBackgroundError, client, queue.acceptNotice, refreshMessages, refreshThreads, runtime.createAbortController, update]);
|
|
1772
|
+
React6.useEffect(() => {
|
|
1326
1773
|
const threadId = options.activeThreadId;
|
|
1327
1774
|
const activeThread2 = stateRef.current.threads.find((thread) => thread.id === stateRef.current.activeThreadId);
|
|
1328
1775
|
if (shouldHydrateSelectedThread(threadId, stateRef.current.activeThreadId, Boolean(activeThread2?.isHydrated))) {
|
|
@@ -1334,6 +1781,8 @@ function useAgentChat(options = {}) {
|
|
|
1334
1781
|
return {
|
|
1335
1782
|
...state,
|
|
1336
1783
|
activeThread,
|
|
1784
|
+
pendingMessages,
|
|
1785
|
+
messageQueue,
|
|
1337
1786
|
hasOlderTurns: Boolean(activeThread?.hasOlderTurns),
|
|
1338
1787
|
hasMoreThreads,
|
|
1339
1788
|
isLoadingHistory,
|
|
@@ -1352,6 +1801,13 @@ function useAgentChat(options = {}) {
|
|
|
1352
1801
|
openThread,
|
|
1353
1802
|
refreshThreads,
|
|
1354
1803
|
regenerate,
|
|
1804
|
+
refreshMessages,
|
|
1805
|
+
editPendingMessage,
|
|
1806
|
+
steerPendingMessage,
|
|
1807
|
+
removePendingMessage,
|
|
1808
|
+
retractPendingMessage: queue.retractForEdit,
|
|
1809
|
+
resumeQueue,
|
|
1810
|
+
steer,
|
|
1355
1811
|
resumeHitl,
|
|
1356
1812
|
setFeedback,
|
|
1357
1813
|
startNewThread,
|
|
@@ -1360,15 +1816,15 @@ function useAgentChat(options = {}) {
|
|
|
1360
1816
|
}
|
|
1361
1817
|
|
|
1362
1818
|
// src/use-agent-resources.ts
|
|
1363
|
-
import * as
|
|
1819
|
+
import * as React7 from "react";
|
|
1364
1820
|
function useAgentThreads(options = {}) {
|
|
1365
1821
|
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 =
|
|
1822
|
+
const [threads, setThreads] = React7.useState([]);
|
|
1823
|
+
const [total, setTotal] = React7.useState();
|
|
1824
|
+
const [isLoading, setIsLoading] = React7.useState(true);
|
|
1825
|
+
const [error, setError] = React7.useState(null);
|
|
1826
|
+
const controllerRef = React7.useRef(null);
|
|
1827
|
+
const load = React7.useCallback(async () => {
|
|
1372
1828
|
controllerRef.current?.abort();
|
|
1373
1829
|
const controller = createAbortController();
|
|
1374
1830
|
controllerRef.current = controller;
|
|
@@ -1387,7 +1843,7 @@ function useAgentThreads(options = {}) {
|
|
|
1387
1843
|
}
|
|
1388
1844
|
}
|
|
1389
1845
|
}, [client, createAbortController, options.limit, options.skip]);
|
|
1390
|
-
|
|
1846
|
+
React7.useEffect(() => {
|
|
1391
1847
|
void load();
|
|
1392
1848
|
return () => controllerRef.current?.abort();
|
|
1393
1849
|
}, [load]);
|
|
@@ -1395,11 +1851,11 @@ function useAgentThreads(options = {}) {
|
|
|
1395
1851
|
}
|
|
1396
1852
|
function useAgentThread(threadId, options = {}) {
|
|
1397
1853
|
const { client, createAbortController } = useAgents24Runtime();
|
|
1398
|
-
const [thread, setThread] =
|
|
1399
|
-
const [isLoading, setIsLoading] =
|
|
1400
|
-
const [error, setError] =
|
|
1401
|
-
const controllerRef =
|
|
1402
|
-
const load =
|
|
1854
|
+
const [thread, setThread] = React7.useState(null);
|
|
1855
|
+
const [isLoading, setIsLoading] = React7.useState(Boolean(threadId));
|
|
1856
|
+
const [error, setError] = React7.useState(null);
|
|
1857
|
+
const controllerRef = React7.useRef(null);
|
|
1858
|
+
const load = React7.useCallback(async () => {
|
|
1403
1859
|
if (!threadId) {
|
|
1404
1860
|
controllerRef.current?.abort();
|
|
1405
1861
|
controllerRef.current = null;
|
|
@@ -1426,24 +1882,73 @@ function useAgentThread(threadId, options = {}) {
|
|
|
1426
1882
|
}
|
|
1427
1883
|
}
|
|
1428
1884
|
}, [client, createAbortController, options.beforeTurnIndex, options.limit, threadId]);
|
|
1429
|
-
|
|
1885
|
+
React7.useEffect(() => {
|
|
1430
1886
|
void load();
|
|
1431
1887
|
return () => controllerRef.current?.abort();
|
|
1432
1888
|
}, [load]);
|
|
1433
1889
|
return { error, isLoading, refresh: load, thread };
|
|
1434
1890
|
}
|
|
1891
|
+
function useThreadComputer(threadId) {
|
|
1892
|
+
const { client, createAbortController } = useAgents24Runtime();
|
|
1893
|
+
const [computer, setComputer] = React7.useState(null);
|
|
1894
|
+
const [isLoading, setIsLoading] = React7.useState(Boolean(threadId));
|
|
1895
|
+
const [error, setError] = React7.useState(null);
|
|
1896
|
+
const controllerRef = React7.useRef(null);
|
|
1897
|
+
const refresh = React7.useCallback(async () => {
|
|
1898
|
+
controllerRef.current?.abort();
|
|
1899
|
+
if (!threadId) {
|
|
1900
|
+
setComputer(null);
|
|
1901
|
+
setIsLoading(false);
|
|
1902
|
+
return null;
|
|
1903
|
+
}
|
|
1904
|
+
const controller = createAbortController();
|
|
1905
|
+
controllerRef.current = controller;
|
|
1906
|
+
setIsLoading(true);
|
|
1907
|
+
setError(null);
|
|
1908
|
+
try {
|
|
1909
|
+
const result = await client.threads.computer.get({ threadId, signal: controller.signal });
|
|
1910
|
+
setComputer(result);
|
|
1911
|
+
return result;
|
|
1912
|
+
} catch (nextError) {
|
|
1913
|
+
if (!isAbortLike(nextError)) setError(nextError);
|
|
1914
|
+
return null;
|
|
1915
|
+
} finally {
|
|
1916
|
+
if (controllerRef.current === controller) {
|
|
1917
|
+
controllerRef.current = null;
|
|
1918
|
+
setIsLoading(false);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
}, [client, createAbortController, threadId]);
|
|
1922
|
+
React7.useEffect(() => {
|
|
1923
|
+
void refresh();
|
|
1924
|
+
return () => controllerRef.current?.abort();
|
|
1925
|
+
}, [refresh]);
|
|
1926
|
+
const list = React7.useCallback((input) => {
|
|
1927
|
+
if (!threadId) throw new Error("A thread is required to list computer files");
|
|
1928
|
+
return client.threads.computer.list({ threadId, ...input });
|
|
1929
|
+
}, [client, threadId]);
|
|
1930
|
+
const read = React7.useCallback((path) => {
|
|
1931
|
+
if (!threadId) throw new Error("A thread is required to read a computer file");
|
|
1932
|
+
return client.threads.computer.read({ threadId, path });
|
|
1933
|
+
}, [client, threadId]);
|
|
1934
|
+
const createContentAccess = React7.useCallback((path, download = false) => {
|
|
1935
|
+
if (!threadId) throw new Error("A thread is required to access a computer file");
|
|
1936
|
+
return client.threads.computer.createContentAccess({ threadId, path, download });
|
|
1937
|
+
}, [client, threadId]);
|
|
1938
|
+
return { computer, error, isLoading, list, read, createContentAccess, refresh };
|
|
1939
|
+
}
|
|
1435
1940
|
function useAgentRun() {
|
|
1436
1941
|
const { client, createAbortController, createId } = useAgents24Runtime();
|
|
1437
|
-
const controllerRef =
|
|
1438
|
-
const [isAttached, setIsAttached] =
|
|
1439
|
-
const [error, setError] =
|
|
1440
|
-
|
|
1441
|
-
const detach =
|
|
1942
|
+
const controllerRef = React7.useRef(null);
|
|
1943
|
+
const [isAttached, setIsAttached] = React7.useState(false);
|
|
1944
|
+
const [error, setError] = React7.useState(null);
|
|
1945
|
+
React7.useEffect(() => () => controllerRef.current?.abort(), []);
|
|
1946
|
+
const detach = React7.useCallback(() => {
|
|
1442
1947
|
controllerRef.current?.abort();
|
|
1443
1948
|
controllerRef.current = null;
|
|
1444
1949
|
setIsAttached(false);
|
|
1445
1950
|
}, []);
|
|
1446
|
-
const attach =
|
|
1951
|
+
const attach = React7.useCallback(async (runId, onEvent, cursor) => {
|
|
1447
1952
|
detach();
|
|
1448
1953
|
const controller = createAbortController();
|
|
1449
1954
|
controllerRef.current = controller;
|
|
@@ -1459,7 +1964,7 @@ function useAgentRun() {
|
|
|
1459
1964
|
setIsAttached(false);
|
|
1460
1965
|
}
|
|
1461
1966
|
}, [client, createAbortController, detach]);
|
|
1462
|
-
const cancel =
|
|
1967
|
+
const cancel = React7.useCallback(async (runId) => {
|
|
1463
1968
|
const result = await client.runs.cancel({ runId, idempotencyKey: createId() });
|
|
1464
1969
|
detach();
|
|
1465
1970
|
return result;
|
|
@@ -1468,9 +1973,9 @@ function useAgentRun() {
|
|
|
1468
1973
|
}
|
|
1469
1974
|
function useAgentAttachments() {
|
|
1470
1975
|
const { client, createId } = useAgents24Runtime();
|
|
1471
|
-
const [uploads, setUploads] =
|
|
1472
|
-
const [isUploading, setIsUploading] =
|
|
1473
|
-
const uploadBatch =
|
|
1976
|
+
const [uploads, setUploads] = React7.useState({});
|
|
1977
|
+
const [isUploading, setIsUploading] = React7.useState(false);
|
|
1978
|
+
const uploadBatch = React7.useCallback(async (input) => {
|
|
1474
1979
|
setIsUploading(true);
|
|
1475
1980
|
try {
|
|
1476
1981
|
const results = await client.attachments.uploadBatch({ uploads: input.uploads, threadId: input.threadId, requestedAgentId: input.requestedAgentId, idempotencyKey: createId(), signal: input.signal });
|
|
@@ -1483,7 +1988,7 @@ function useAgentAttachments() {
|
|
|
1483
1988
|
setIsUploading(false);
|
|
1484
1989
|
}
|
|
1485
1990
|
}, [client, createId]);
|
|
1486
|
-
const createContentAccess =
|
|
1991
|
+
const createContentAccess = React7.useCallback(
|
|
1487
1992
|
(input) => client.attachments.createContentAccess(input),
|
|
1488
1993
|
[client]
|
|
1489
1994
|
);
|
|
@@ -1491,9 +1996,9 @@ function useAgentAttachments() {
|
|
|
1491
1996
|
}
|
|
1492
1997
|
function useAgentHitl() {
|
|
1493
1998
|
const { client, createId } = useAgents24Runtime();
|
|
1494
|
-
const [isResolving, setIsResolving] =
|
|
1495
|
-
const [lastResult, setLastResult] =
|
|
1496
|
-
const resume =
|
|
1999
|
+
const [isResolving, setIsResolving] = React7.useState(false);
|
|
2000
|
+
const [lastResult, setLastResult] = React7.useState(null);
|
|
2001
|
+
const resume = React7.useCallback(async (input) => {
|
|
1497
2002
|
setIsResolving(true);
|
|
1498
2003
|
try {
|
|
1499
2004
|
const result = await client.hitl.resume({ ...input, idempotencyKey: createId() });
|
|
@@ -1507,8 +2012,8 @@ function useAgentHitl() {
|
|
|
1507
2012
|
}
|
|
1508
2013
|
function useAgentMcp() {
|
|
1509
2014
|
const { client, createId } = useAgents24Runtime();
|
|
1510
|
-
const [isAuthorizing, setIsAuthorizing] =
|
|
1511
|
-
const startAuthorization =
|
|
2015
|
+
const [isAuthorizing, setIsAuthorizing] = React7.useState(false);
|
|
2016
|
+
const startAuthorization = React7.useCallback(async (input) => {
|
|
1512
2017
|
setIsAuthorizing(true);
|
|
1513
2018
|
try {
|
|
1514
2019
|
return await client.mcp.startAuthorization({ ...input, idempotencyKey: createId() });
|
|
@@ -1516,7 +2021,7 @@ function useAgentMcp() {
|
|
|
1516
2021
|
setIsAuthorizing(false);
|
|
1517
2022
|
}
|
|
1518
2023
|
}, [client, createId]);
|
|
1519
|
-
const redeemCallback =
|
|
2024
|
+
const redeemCallback = React7.useCallback(async (input) => client.mcp.redeemCallback({ ...input, idempotencyKey: createId() }), [client, createId]);
|
|
1520
2025
|
return { isAuthorizing, redeemCallback, startAuthorization };
|
|
1521
2026
|
}
|
|
1522
2027
|
export {
|
|
@@ -1532,17 +2037,25 @@ export {
|
|
|
1532
2037
|
chatAttachmentIds,
|
|
1533
2038
|
clearAgentChatBackgroundError,
|
|
1534
2039
|
clearAgentChatOperationError,
|
|
2040
|
+
computerResourceTargets,
|
|
1535
2041
|
contextCompressionPartFromEvent,
|
|
1536
2042
|
createPortableId,
|
|
2043
|
+
createRuntimeEventFrameBuffer,
|
|
1537
2044
|
getThreadActivity,
|
|
1538
2045
|
initialAgentChatState,
|
|
1539
2046
|
isAbortLike,
|
|
1540
2047
|
latestThreadContext,
|
|
1541
2048
|
modelTurnPartFromEvent,
|
|
1542
2049
|
partsFromResponseBlocks,
|
|
2050
|
+
preferNewestThreadSummary,
|
|
2051
|
+
preserveMountedMessageIds,
|
|
2052
|
+
promoteThreadMessage,
|
|
1543
2053
|
reconcileRefreshedThreads,
|
|
2054
|
+
reconcileThreadTimeline,
|
|
1544
2055
|
restoreAgentChatThreadSelection,
|
|
2056
|
+
runtimeEventFrameScheduler,
|
|
1545
2057
|
threadDetailToMessages,
|
|
2058
|
+
threadTimelineToMessages,
|
|
1546
2059
|
upsertStableAssistantMessage,
|
|
1547
2060
|
useAgentAttachments,
|
|
1548
2061
|
useAgentChat,
|
|
@@ -1553,6 +2066,8 @@ export {
|
|
|
1553
2066
|
useAgentThread,
|
|
1554
2067
|
useAgentThreads,
|
|
1555
2068
|
useAgents24Runtime,
|
|
2069
|
+
useThreadComputer,
|
|
2070
|
+
useThreadMessageQueue,
|
|
1556
2071
|
withAgentChatBackgroundError,
|
|
1557
2072
|
withAgentChatOperationError
|
|
1558
2073
|
};
|