@agents24/react 0.2.10 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +220 -106
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts +1 -0
- package/dist/thread-message-queue.d.ts +26 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -392,6 +392,7 @@ function threadTimelineToMessages(items, detail) {
|
|
|
392
392
|
const feedbackByRun = new Map((detail?.turns || []).flatMap((turn) => turn.run_id && turn.feedback ? [[turn.run_id, turn.feedback]] : []));
|
|
393
393
|
return items.map((item) => ({
|
|
394
394
|
id: item.id,
|
|
395
|
+
clientSubmissionId: item.client_submission_id || void 0,
|
|
395
396
|
role: item.role,
|
|
396
397
|
runId: item.run_id,
|
|
397
398
|
segmentIndex: item.segment_index,
|
|
@@ -602,7 +603,7 @@ function appendStableStreamingTurn(messages, userMessage, assistantMessage) {
|
|
|
602
603
|
}
|
|
603
604
|
function preserveMountedMessageIds(canonical, current) {
|
|
604
605
|
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
|
+
const mounted = current.find((item) => item.role === message.role && (item.durableId === message.id || item.id === message.id || Boolean(message.clientSubmissionId && item.clientSubmissionId === message.clientSubmissionId) || message.role === "assistant" && message.segmentIndex === 1 && item.runId && item.runId === message.runId && !item.durableId));
|
|
606
607
|
return mounted && mounted.id !== message.id ? { ...message, id: mounted.id, durableId: message.id } : message;
|
|
607
608
|
});
|
|
608
609
|
}
|
|
@@ -618,9 +619,9 @@ function reconcileThreadTimeline(current, canonical, activeRunId, streamingMessa
|
|
|
618
619
|
}
|
|
619
620
|
function promoteThreadMessage(current, message, runId) {
|
|
620
621
|
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);
|
|
622
|
+
const index = current.findIndex((item) => item.id === message.id || item.durableId === message.id || Boolean(message.clientSubmissionId && item.clientSubmissionId === message.clientSubmissionId));
|
|
623
|
+
if (index < 0) return [...current, message.clientSubmissionId ? { ...promoted, id: message.clientSubmissionId, durableId: message.id } : promoted];
|
|
624
|
+
return current.map((item, itemIndex) => itemIndex === index ? { ...item, ...promoted, id: item.id, durableId: item.id === message.id ? item.durableId : message.id } : message.clientSubmissionId && item.role === "assistant" && item.id === `${current[index].id}:assistant` ? { ...item, runId } : item);
|
|
624
625
|
}
|
|
625
626
|
function upsertStableAssistantMessage(messages, input) {
|
|
626
627
|
const next = [...messages];
|
|
@@ -677,13 +678,14 @@ function applyRuntimeEvent(state, event) {
|
|
|
677
678
|
let optimisticIndex = -1;
|
|
678
679
|
for (let index = state.messages.length - 1; index >= 0; index -= 1) {
|
|
679
680
|
const message = state.messages[index];
|
|
680
|
-
if (message.role === "user" && !message.runId && !message.durableId) {
|
|
681
|
+
if (message.role === "user" && (event.payload.client_submission_id ? message.clientSubmissionId === event.payload.client_submission_id : !message.runId && !message.durableId)) {
|
|
681
682
|
optimisticIndex = index;
|
|
682
683
|
break;
|
|
683
684
|
}
|
|
684
685
|
}
|
|
685
686
|
if (optimisticIndex >= 0) {
|
|
686
|
-
|
|
687
|
+
const assistantId = event.payload.client_submission_id ? `${state.messages[optimisticIndex].id}:assistant` : state.streamingMessageId;
|
|
688
|
+
state = { ...state, streamingMessageId: assistantId, messages: state.messages.map((message, index) => index === optimisticIndex ? { ...message, durableId: messageId2, runId: event.run_id } : message.role === "assistant" && message.id === assistantId ? { ...message, runId: event.run_id } : message) };
|
|
687
689
|
}
|
|
688
690
|
}
|
|
689
691
|
const projection = assistantProjectionFromEvent(event);
|
|
@@ -808,7 +810,16 @@ function applyThreadSummaryEvent(threads, event) {
|
|
|
808
810
|
|
|
809
811
|
// src/thread-message-queue.ts
|
|
810
812
|
import * as React2 from "react";
|
|
811
|
-
function
|
|
813
|
+
async function retrySubmissionAdmission(admit) {
|
|
814
|
+
try {
|
|
815
|
+
return await admit();
|
|
816
|
+
} catch (error) {
|
|
817
|
+
const failure = error;
|
|
818
|
+
if (failure?.kind !== "network" && failure?.name !== "HttpRequestTimeoutError" && !(error instanceof TypeError)) throw error;
|
|
819
|
+
return admit();
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic, onDiscard }) {
|
|
812
823
|
const [pendingMessages, setPendingMessages] = React2.useState([]);
|
|
813
824
|
const [messageQueue, setMessageQueue] = React2.useState(null);
|
|
814
825
|
const pendingRef = React2.useRef(/* @__PURE__ */ new Map());
|
|
@@ -821,15 +832,19 @@ function useThreadMessageQueue({ threadId, transport, onPromoted }) {
|
|
|
821
832
|
threadRef.current = threadId;
|
|
822
833
|
transportRef.current = transport;
|
|
823
834
|
onPromotedRef.current = onPromoted;
|
|
835
|
+
const callbacksRef = React2.useRef({ onOptimistic, onDiscard });
|
|
836
|
+
callbacksRef.current = { onOptimistic, onDiscard };
|
|
837
|
+
const knownMessage = (id, submissionId) => pendingRef.current.get(id) || (submissionId ? [...pendingRef.current.values()].find((item) => item.client_submission_id === submissionId) : void 0);
|
|
824
838
|
const hydrate = React2.useCallback((page, selectedThreadId) => {
|
|
825
839
|
if (threadRef.current !== selectedThreadId) return;
|
|
826
840
|
const pending = page.pending.filter((message) => !promotedRef.current.has(message.id)).map((message) => {
|
|
827
|
-
const known =
|
|
841
|
+
const known = knownMessage(message.id, message.client_submission_id);
|
|
828
842
|
if (message.attachments?.length || !known?.attachments?.length) return message;
|
|
829
843
|
return { ...message, attachments: known.attachments.filter((attachment) => message.attachment_ids.includes(attachment.id)) };
|
|
830
844
|
});
|
|
831
845
|
for (const message of pending) pendingRef.current.set(message.id, message);
|
|
832
|
-
|
|
846
|
+
const optimistic = [...pendingRef.current.values()].filter((message) => message.thread_id === selectedThreadId && message.revision === 0 && message.optimisticQueued && !pending.some((item) => item.client_submission_id === message.client_submission_id));
|
|
847
|
+
setPendingMessages([...pending, ...optimistic]);
|
|
833
848
|
setMessageQueue(page.queue);
|
|
834
849
|
}, []);
|
|
835
850
|
const refresh = React2.useCallback(async (selectedThreadId = threadRef.current) => {
|
|
@@ -839,9 +854,10 @@ function useThreadMessageQueue({ threadId, transport, onPromoted }) {
|
|
|
839
854
|
if (version !== versionRef.current || threadRef.current !== selectedThreadId) return null;
|
|
840
855
|
for (const item of page.items) {
|
|
841
856
|
if (item.role === "user" && item.status === "delivered" && item.run_id) {
|
|
842
|
-
const pending =
|
|
857
|
+
const pending = knownMessage(item.id, item.client_submission_id);
|
|
843
858
|
if (pending && !promotedRef.current.has(item.id)) {
|
|
844
859
|
promotedRef.current.add(item.id);
|
|
860
|
+
pendingRef.current.delete(pending.id);
|
|
845
861
|
pendingRef.current.delete(item.id);
|
|
846
862
|
onPromotedRef.current({
|
|
847
863
|
...pending,
|
|
@@ -857,56 +873,98 @@ function useThreadMessageQueue({ threadId, transport, onPromoted }) {
|
|
|
857
873
|
const acceptNotice = React2.useCallback((notice) => {
|
|
858
874
|
if (notice.cursor && notice.cursor <= cursorRef.current) return false;
|
|
859
875
|
if (notice.cursor) cursorRef.current = notice.cursor;
|
|
860
|
-
const pending =
|
|
861
|
-
if (notice.status === "delivered" && notice.runId && pending && !promotedRef.current.has(
|
|
862
|
-
promotedRef.current.add(
|
|
876
|
+
const pending = knownMessage(notice.messageId, notice.clientSubmissionId);
|
|
877
|
+
if (notice.status === "delivered" && notice.runId && pending && !promotedRef.current.has(notice.messageId)) {
|
|
878
|
+
promotedRef.current.add(notice.messageId);
|
|
863
879
|
pendingRef.current.delete(pending.id);
|
|
864
880
|
versionRef.current += 1;
|
|
865
881
|
setPendingMessages((current) => current.filter((message) => message.id !== pending.id));
|
|
866
|
-
onPromotedRef.current(pending, notice.runId);
|
|
882
|
+
onPromotedRef.current({ ...pending, id: notice.messageId, run_id: notice.runId, status: "delivered" }, notice.runId);
|
|
867
883
|
}
|
|
868
884
|
if (notice.status === "removed") pendingRef.current.delete(notice.messageId);
|
|
869
885
|
void refresh().catch(() => void 0);
|
|
870
886
|
return true;
|
|
871
887
|
}, [refresh]);
|
|
888
|
+
const beginSubmission = React2.useCallback((input, queued = false) => {
|
|
889
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
890
|
+
const message = {
|
|
891
|
+
id: input.clientSubmissionId,
|
|
892
|
+
client_submission_id: input.clientSubmissionId,
|
|
893
|
+
thread_id: threadRef.current || "",
|
|
894
|
+
sequence: Number.MAX_SAFE_INTEGER,
|
|
895
|
+
run_id: null,
|
|
896
|
+
turn_id: null,
|
|
897
|
+
segment_index: 0,
|
|
898
|
+
role: "user",
|
|
899
|
+
status: "queued",
|
|
900
|
+
requested_mode: input.mode || "queue",
|
|
901
|
+
text: input.input,
|
|
902
|
+
attachment_ids: input.attachmentIds || input.attachments?.map((item) => item.id) || [],
|
|
903
|
+
attachments: input.attachments,
|
|
904
|
+
response_blocks: [],
|
|
905
|
+
revision: 0,
|
|
906
|
+
created_at: now,
|
|
907
|
+
updated_at: now
|
|
908
|
+
};
|
|
909
|
+
pendingRef.current.set(message.id, { ...message, optimisticQueued: queued });
|
|
910
|
+
versionRef.current += 1;
|
|
911
|
+
if (queued) setPendingMessages((current) => [...current, message]);
|
|
912
|
+
else callbacksRef.current.onOptimistic?.(message, Boolean(input.uploading));
|
|
913
|
+
return message;
|
|
914
|
+
}, []);
|
|
915
|
+
const discardSubmission = React2.useCallback((message) => {
|
|
916
|
+
if (!pendingRef.current.has(message.id)) return;
|
|
917
|
+
pendingRef.current.delete(message.id);
|
|
918
|
+
callbacksRef.current.onDiscard?.(message);
|
|
919
|
+
versionRef.current += 1;
|
|
920
|
+
setPendingMessages((current) => current.filter((item) => item.client_submission_id !== message.client_submission_id));
|
|
921
|
+
}, []);
|
|
872
922
|
const submit = React2.useCallback(async (input) => {
|
|
873
923
|
const selectedThreadId = threadRef.current;
|
|
874
924
|
if (!selectedThreadId) throw new Error("No active Agent thread is available.");
|
|
875
|
-
const { attachments, ...submission } = input;
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
925
|
+
const { queued, prepare, attachments, uploading, ...submission } = input;
|
|
926
|
+
const optimistic = beginSubmission({
|
|
927
|
+
...submission,
|
|
928
|
+
attachments,
|
|
929
|
+
uploading,
|
|
930
|
+
clientSubmissionId: submission.clientSubmissionId || submission.idempotencyKey
|
|
931
|
+
}, queued);
|
|
932
|
+
try {
|
|
933
|
+
const prepared = prepare ? await prepare() : {};
|
|
934
|
+
if (threadRef.current === selectedThreadId) pendingRef.current.set(optimistic.id, { ...optimistic, optimisticQueued: queued, attachment_ids: prepared.attachmentIds || optimistic.attachment_ids, attachments: prepared.attachments || attachments });
|
|
935
|
+
const request = { ...submission, ...prepared, clientSubmissionId: optimistic.client_submission_id, threadId: selectedThreadId };
|
|
936
|
+
const result = await retrySubmissionAdmission(() => transportRef.current.submit(request));
|
|
937
|
+
if (threadRef.current !== selectedThreadId) return result;
|
|
938
|
+
versionRef.current += 1;
|
|
939
|
+
const bound = {
|
|
940
|
+
...optimistic,
|
|
882
941
|
id: result.message_id,
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
segment_index: null,
|
|
888
|
-
role: "user",
|
|
942
|
+
client_submission_id: result.client_submission_id || optimistic.client_submission_id,
|
|
943
|
+
attachment_ids: request.attachmentIds || [],
|
|
944
|
+
attachments: prepared.attachments || attachments,
|
|
945
|
+
run_id: result.run_id,
|
|
889
946
|
status: result.status,
|
|
890
|
-
|
|
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
|
|
947
|
+
revision: 1
|
|
898
948
|
};
|
|
899
|
-
pendingRef.current.
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
949
|
+
pendingRef.current.delete(optimistic.id);
|
|
950
|
+
if (result.run_id) {
|
|
951
|
+
setPendingMessages((current) => current.filter((item) => item.client_submission_id !== bound.client_submission_id));
|
|
952
|
+
if (!promotedRef.current.has(bound.id)) {
|
|
953
|
+
promotedRef.current.add(bound.id);
|
|
954
|
+
onPromotedRef.current(bound, result.run_id);
|
|
955
|
+
}
|
|
956
|
+
} else if (!promotedRef.current.has(bound.id)) {
|
|
957
|
+
if (!queued) callbacksRef.current.onDiscard?.(optimistic);
|
|
958
|
+
pendingRef.current.set(bound.id, bound);
|
|
959
|
+
setPendingMessages((current) => [...current.filter((item) => item.client_submission_id !== bound.client_submission_id), bound]);
|
|
960
|
+
}
|
|
961
|
+
void refresh().catch(() => void 0);
|
|
962
|
+
return result;
|
|
963
|
+
} catch (error) {
|
|
964
|
+
if (threadRef.current === selectedThreadId) discardSubmission(optimistic);
|
|
965
|
+
throw error;
|
|
906
966
|
}
|
|
907
|
-
|
|
908
|
-
return result;
|
|
909
|
-
}, [refresh]);
|
|
967
|
+
}, [beginSubmission, discardSubmission, refresh]);
|
|
910
968
|
const edit = React2.useCallback(async (message, input) => {
|
|
911
969
|
const updated = await transportRef.current.edit(message, input);
|
|
912
970
|
versionRef.current += 1;
|
|
@@ -949,7 +1007,51 @@ function useThreadMessageQueue({ threadId, transport, onPromoted }) {
|
|
|
949
1007
|
}, 3e3);
|
|
950
1008
|
return () => clock.clearInterval(timer);
|
|
951
1009
|
}, [threadId, pendingMessages.length, messageQueue?.paused, refresh]);
|
|
952
|
-
return { pendingMessages, messageQueue, refresh, hydrate, acceptNotice, submit, edit, remove, retractForEdit, resume };
|
|
1010
|
+
return { pendingMessages, messageQueue, refresh, hydrate, acceptNotice, beginSubmission, discardSubmission, submit, edit, remove, retractForEdit, resume };
|
|
1011
|
+
}
|
|
1012
|
+
function optimisticSubmissionTurn(message) {
|
|
1013
|
+
const user = {
|
|
1014
|
+
id: message.client_submission_id || message.id,
|
|
1015
|
+
clientSubmissionId: message.client_submission_id || void 0,
|
|
1016
|
+
role: "user",
|
|
1017
|
+
content: message.text,
|
|
1018
|
+
createdAt: message.created_at,
|
|
1019
|
+
isFinal: true,
|
|
1020
|
+
parts: message.text ? [{ id: `${message.id}:text`, type: "text", kind: "text", text: message.text }] : [],
|
|
1021
|
+
attachments: message.attachments?.map((item) => ({
|
|
1022
|
+
id: item.id,
|
|
1023
|
+
filename: item.filename,
|
|
1024
|
+
mediaType: item.mime_type,
|
|
1025
|
+
byteSize: item.byte_size
|
|
1026
|
+
}))
|
|
1027
|
+
};
|
|
1028
|
+
return [user, {
|
|
1029
|
+
id: `${user.id}:assistant`,
|
|
1030
|
+
role: "assistant",
|
|
1031
|
+
content: "",
|
|
1032
|
+
parts: [],
|
|
1033
|
+
createdAt: message.created_at,
|
|
1034
|
+
isFinal: false
|
|
1035
|
+
}];
|
|
1036
|
+
}
|
|
1037
|
+
function optimisticSubmissionState(state, message, uploading) {
|
|
1038
|
+
const [user, assistant] = optimisticSubmissionTurn(message);
|
|
1039
|
+
return {
|
|
1040
|
+
...state,
|
|
1041
|
+
messages: appendStableStreamingTurn(state.messages, user, assistant),
|
|
1042
|
+
streamingMessageId: assistant.id,
|
|
1043
|
+
runState: uploading ? "uploading" : "streaming",
|
|
1044
|
+
error: null
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
function discardOptimisticSubmission(state, message) {
|
|
1048
|
+
const activeDraft = state.streamingMessageId === `${message.id}:assistant`;
|
|
1049
|
+
return {
|
|
1050
|
+
...state,
|
|
1051
|
+
messages: state.messages.filter((item) => item.runId || item.id !== message.id && item.id !== `${message.id}:assistant`),
|
|
1052
|
+
streamingMessageId: activeDraft ? null : state.streamingMessageId,
|
|
1053
|
+
runState: activeDraft && !state.activeRunId ? "idle" : state.runState
|
|
1054
|
+
};
|
|
953
1055
|
}
|
|
954
1056
|
|
|
955
1057
|
// src/use-agent-chat.ts
|
|
@@ -1254,7 +1356,13 @@ function useAgentChat(options = {}) {
|
|
|
1254
1356
|
});
|
|
1255
1357
|
}
|
|
1256
1358
|
}, [update]);
|
|
1257
|
-
const queue = useThreadMessageQueue({
|
|
1359
|
+
const queue = useThreadMessageQueue({
|
|
1360
|
+
threadId: state.activeThreadId,
|
|
1361
|
+
transport: queueTransport,
|
|
1362
|
+
onPromoted: onQueuePromotion,
|
|
1363
|
+
onOptimistic: (message, uploading) => update((current) => optimisticSubmissionState(current, message, uploading)),
|
|
1364
|
+
onDiscard: (message) => update((current) => discardOptimisticSubmission(current, message))
|
|
1365
|
+
});
|
|
1258
1366
|
const { pendingMessages, messageQueue } = queue;
|
|
1259
1367
|
const eventBuffer = React6.useMemo(
|
|
1260
1368
|
() => createRuntimeEventFrameBuffer(
|
|
@@ -1465,38 +1573,46 @@ function useAgentChat(options = {}) {
|
|
|
1465
1573
|
clearOperationError();
|
|
1466
1574
|
setIsSubmitting(true);
|
|
1467
1575
|
try {
|
|
1468
|
-
|
|
1469
|
-
uploads: input.uploads,
|
|
1470
|
-
threadId: existingThreadId,
|
|
1471
|
-
requestedAgentId: input.requestedAgentId,
|
|
1472
|
-
idempotencyKey: runtime.createId()
|
|
1473
|
-
}) : [];
|
|
1474
|
-
const result = await queue.submit({
|
|
1576
|
+
await queue.submit({
|
|
1475
1577
|
mode: "queue",
|
|
1476
1578
|
input: input.text.trim(),
|
|
1477
|
-
attachmentIds:
|
|
1579
|
+
attachmentIds: input.attachmentIds,
|
|
1580
|
+
attachments: input.attachments?.map((item, index) => ({
|
|
1581
|
+
id: item.id || `attachment-${index}`,
|
|
1582
|
+
filename: item.filename || "Attachment",
|
|
1583
|
+
mime_type: item.mediaType || "application/octet-stream",
|
|
1584
|
+
byte_size: typeof item.byteSize === "number" ? item.byteSize : 0
|
|
1585
|
+
})) || input.uploads?.map((item, index) => ({
|
|
1586
|
+
id: `upload-${index}`,
|
|
1587
|
+
filename: item.name,
|
|
1588
|
+
mime_type: item.mediaType || "application/octet-stream",
|
|
1589
|
+
byte_size: 0
|
|
1590
|
+
})),
|
|
1591
|
+
queued: Boolean(stateRef.current.activeRunId),
|
|
1592
|
+
uploading: Boolean(input.uploads?.length),
|
|
1478
1593
|
requestedAgentId: input.requestedAgentId,
|
|
1479
1594
|
requestedModelId: input.requestedModelId,
|
|
1480
|
-
|
|
1595
|
+
clientSubmissionId: runtime.createId(),
|
|
1596
|
+
idempotencyKey: runtime.createId(),
|
|
1597
|
+
prepare: async () => {
|
|
1598
|
+
const uploaded = input.uploads?.length ? await client.attachments.uploadBatch({
|
|
1599
|
+
uploads: input.uploads,
|
|
1600
|
+
threadId: existingThreadId,
|
|
1601
|
+
requestedAgentId: input.requestedAgentId,
|
|
1602
|
+
idempotencyKey: runtime.createId()
|
|
1603
|
+
}) : [];
|
|
1604
|
+
if (input.uploads?.length && uploaded.length !== input.uploads.length) throw new Error("Attachment upload returned an incomplete batch");
|
|
1605
|
+
return {
|
|
1606
|
+
attachmentIds: uploaded.length ? uploaded.map((item) => item.id) : input.attachmentIds,
|
|
1607
|
+
attachments: uploaded.length ? uploaded.map((item) => ({
|
|
1608
|
+
id: item.id,
|
|
1609
|
+
filename: item.filename,
|
|
1610
|
+
mime_type: item.mime_type,
|
|
1611
|
+
byte_size: item.byte_size
|
|
1612
|
+
})) : void 0
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1481
1615
|
});
|
|
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
1616
|
} catch (error) {
|
|
1501
1617
|
failOperation("submit", error);
|
|
1502
1618
|
throw error;
|
|
@@ -1513,28 +1629,28 @@ function useAgentChat(options = {}) {
|
|
|
1513
1629
|
detach();
|
|
1514
1630
|
const controller = runtime.createAbortController();
|
|
1515
1631
|
streamControllerRef.current = controller;
|
|
1516
|
-
const
|
|
1517
|
-
|
|
1632
|
+
const ticket = queue.beginSubmission({
|
|
1633
|
+
input: input.text.trim(),
|
|
1634
|
+
clientSubmissionId: runtime.createId(),
|
|
1635
|
+
uploading: Boolean(input.uploads?.length),
|
|
1636
|
+
attachmentIds: input.attachmentIds,
|
|
1637
|
+
attachments: input.attachments?.map((item, index) => ({
|
|
1638
|
+
id: item.id || `attachment-${index}`,
|
|
1639
|
+
filename: item.filename || "Attachment",
|
|
1640
|
+
mime_type: item.mediaType || "application/octet-stream",
|
|
1641
|
+
byte_size: typeof item.byteSize === "number" ? item.byteSize : 0
|
|
1642
|
+
})) || input.uploads?.map((upload, index) => ({
|
|
1643
|
+
id: `upload-${index}`,
|
|
1644
|
+
filename: upload.name,
|
|
1645
|
+
mime_type: upload.mediaType || "application/octet-stream",
|
|
1646
|
+
byte_size: 0
|
|
1647
|
+
}))
|
|
1648
|
+
});
|
|
1649
|
+
const userId = ticket.id;
|
|
1518
1650
|
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
1651
|
clearOperationError();
|
|
1528
1652
|
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
1653
|
setIsSubmitting(true);
|
|
1537
|
-
let streamStarted = false;
|
|
1538
1654
|
try {
|
|
1539
1655
|
const uploaded = hasPendingUploads ? await client.attachments.uploadBatch({
|
|
1540
1656
|
uploads: input.uploads || [],
|
|
@@ -1555,8 +1671,7 @@ function useAgentChat(options = {}) {
|
|
|
1555
1671
|
runState: "streaming"
|
|
1556
1672
|
}));
|
|
1557
1673
|
}
|
|
1558
|
-
|
|
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);
|
|
1674
|
+
const result = await client.chat.stream({ clientSubmissionId: ticket.client_submission_id, input: input.text.trim(), threadId: stateRef.current.activeThreadId || void 0, attachmentIds, requestedModelId: input.requestedModelId, requestedAgentId: input.requestedAgentId, idempotencyKey, signal: controller.signal }, consumeEvent);
|
|
1560
1675
|
eventBuffer.flush();
|
|
1561
1676
|
update((current) => {
|
|
1562
1677
|
const threadId = result.threadId || current.activeThreadId;
|
|
@@ -1572,19 +1687,13 @@ function useAgentChat(options = {}) {
|
|
|
1572
1687
|
if (result.threadId) await refreshThreads().catch(() => void 0);
|
|
1573
1688
|
if (result.threadId && !result.detached) await refreshMessages(result.threadId);
|
|
1574
1689
|
} catch (error) {
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
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)) {
|
|
1690
|
+
const admitted = Boolean(stateRef.current.messages.find((message) => message.id === userId)?.runId);
|
|
1691
|
+
if (!admitted) queue.discardSubmission(ticket);
|
|
1692
|
+
if (!isAbortLike(error)) {
|
|
1583
1693
|
update((current) => ({
|
|
1584
1694
|
...current,
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
streamingMessageId: null
|
|
1695
|
+
runState: admitted ? "reconnecting" : "failed",
|
|
1696
|
+
streamingMessageId: admitted ? current.streamingMessageId : null
|
|
1588
1697
|
}));
|
|
1589
1698
|
failOperation("submit", error);
|
|
1590
1699
|
throw error;
|
|
@@ -1744,7 +1853,8 @@ function useAgentChat(options = {}) {
|
|
|
1744
1853
|
messageId: event.message_id,
|
|
1745
1854
|
status: event.status,
|
|
1746
1855
|
runId: event.run_id,
|
|
1747
|
-
cursor: event.cursor
|
|
1856
|
+
cursor: event.cursor,
|
|
1857
|
+
clientSubmissionId: event.client_submission_id
|
|
1748
1858
|
});
|
|
1749
1859
|
if (wasQueued) void refreshMessages(event.thread_id, event.run_id || void 0).catch(() => void 0);
|
|
1750
1860
|
}
|
|
@@ -2041,11 +2151,14 @@ export {
|
|
|
2041
2151
|
contextCompressionPartFromEvent,
|
|
2042
2152
|
createPortableId,
|
|
2043
2153
|
createRuntimeEventFrameBuffer,
|
|
2154
|
+
discardOptimisticSubmission,
|
|
2044
2155
|
getThreadActivity,
|
|
2045
2156
|
initialAgentChatState,
|
|
2046
2157
|
isAbortLike,
|
|
2047
2158
|
latestThreadContext,
|
|
2048
2159
|
modelTurnPartFromEvent,
|
|
2160
|
+
optimisticSubmissionState,
|
|
2161
|
+
optimisticSubmissionTurn,
|
|
2049
2162
|
partsFromResponseBlocks,
|
|
2050
2163
|
preferNewestThreadSummary,
|
|
2051
2164
|
preserveMountedMessageIds,
|
|
@@ -2053,6 +2166,7 @@ export {
|
|
|
2053
2166
|
reconcileRefreshedThreads,
|
|
2054
2167
|
reconcileThreadTimeline,
|
|
2055
2168
|
restoreAgentChatThreadSelection,
|
|
2169
|
+
retrySubmissionAdmission,
|
|
2056
2170
|
runtimeEventFrameScheduler,
|
|
2057
2171
|
threadDetailToMessages,
|
|
2058
2172
|
threadTimelineToMessages,
|