@agents24/react 0.2.15 → 0.2.16
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 +108 -36
- package/dist/index.js.map +1 -1
- package/dist/thread-message-queue.d.ts +7 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1098,12 +1098,14 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onUnknownPromo
|
|
|
1098
1098
|
const [pendingMessages, setPendingMessages] = React3.useState([]);
|
|
1099
1099
|
const [messageQueue, setMessageQueue] = React3.useState(null);
|
|
1100
1100
|
const pendingRef = React3.useRef(/* @__PURE__ */ new Map());
|
|
1101
|
+
const admissionRef = React3.useRef(null);
|
|
1102
|
+
const initialAdmissionRef = React3.useRef(null);
|
|
1101
1103
|
const versionRef = React3.useRef(0);
|
|
1102
1104
|
const cursorRef = React3.useRef(0);
|
|
1103
1105
|
const promotedRef = React3.useRef(/* @__PURE__ */ new Set());
|
|
1104
1106
|
const threadRef = React3.useRef(threadId);
|
|
1105
1107
|
const selectionGenerationRef = React3.useRef(0);
|
|
1106
|
-
if (threadRef.current !== threadId) selectionGenerationRef.current += 1;
|
|
1108
|
+
if (threadRef.current !== threadId && initialAdmissionRef.current?.threadId !== threadId) selectionGenerationRef.current += 1;
|
|
1107
1109
|
const transportRef = React3.useRef(transport);
|
|
1108
1110
|
const onPromotedRef = React3.useRef(onPromoted);
|
|
1109
1111
|
threadRef.current = threadId;
|
|
@@ -1113,10 +1115,16 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onUnknownPromo
|
|
|
1113
1115
|
ownSelectionRef.current = ownSelection;
|
|
1114
1116
|
const callbacksRef = React3.useRef({ onOptimistic, onDiscard, onUnknownPromotion });
|
|
1115
1117
|
callbacksRef.current = { onOptimistic, onDiscard, onUnknownPromotion };
|
|
1116
|
-
const ownOperation = React3.useCallback(() => ownSelectionRef.current?.() ?? captureChatOperation(() => ({
|
|
1118
|
+
const ownOperation = React3.useCallback((options) => ownSelectionRef.current?.(options) ?? captureChatOperation(() => ({
|
|
1117
1119
|
generation: selectionGenerationRef.current,
|
|
1118
1120
|
threadId: threadRef.current
|
|
1119
|
-
})), []);
|
|
1121
|
+
}), options), []);
|
|
1122
|
+
const confirmThreadAdmission = React3.useCallback((admittedThreadId) => {
|
|
1123
|
+
const initial = initialAdmissionRef.current;
|
|
1124
|
+
if (!initial || !initial.isCurrent()) return;
|
|
1125
|
+
initial.threadId = admittedThreadId;
|
|
1126
|
+
initial.resolve(admittedThreadId);
|
|
1127
|
+
}, []);
|
|
1120
1128
|
const assertCurrent = (isCurrent) => {
|
|
1121
1129
|
if (!isCurrent()) throw Object.assign(new Error("Thread selection changed."), { name: "AbortError" });
|
|
1122
1130
|
};
|
|
@@ -1195,12 +1203,24 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onUnknownPromo
|
|
|
1195
1203
|
updated_at: now
|
|
1196
1204
|
};
|
|
1197
1205
|
pendingRef.current.set(message.id, { ...message, optimisticQueued: queued });
|
|
1206
|
+
if (!queued && !threadRef.current && !initialAdmissionRef.current) {
|
|
1207
|
+
let resolve;
|
|
1208
|
+
const done = new Promise((callback) => {
|
|
1209
|
+
resolve = callback;
|
|
1210
|
+
});
|
|
1211
|
+
initialAdmissionRef.current = { id: message.id, done, resolve, isCurrent: ownOperation({ adoptThread: true }) };
|
|
1212
|
+
}
|
|
1198
1213
|
versionRef.current += 1;
|
|
1199
1214
|
if (queued) setPendingMessages((current) => [...current, message]);
|
|
1200
1215
|
else callbacksRef.current.onOptimistic?.(message, Boolean(input.uploading));
|
|
1201
1216
|
return message;
|
|
1202
|
-
}, []);
|
|
1203
|
-
const discardSubmission = React3.useCallback((message) => {
|
|
1217
|
+
}, [ownOperation]);
|
|
1218
|
+
const discardSubmission = React3.useCallback((message, failure) => {
|
|
1219
|
+
if (initialAdmissionRef.current?.id === message.id) {
|
|
1220
|
+
initialAdmissionRef.current.failure = failure;
|
|
1221
|
+
initialAdmissionRef.current.resolve(null);
|
|
1222
|
+
initialAdmissionRef.current = null;
|
|
1223
|
+
}
|
|
1204
1224
|
if (!pendingRef.current.has(message.id)) return;
|
|
1205
1225
|
pendingRef.current.delete(message.id);
|
|
1206
1226
|
callbacksRef.current.onDiscard?.(message);
|
|
@@ -1208,19 +1228,31 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onUnknownPromo
|
|
|
1208
1228
|
setPendingMessages((current) => current.filter((item) => item.client_submission_id !== message.client_submission_id));
|
|
1209
1229
|
}, []);
|
|
1210
1230
|
const submit = React3.useCallback(async (input) => {
|
|
1211
|
-
const
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
const
|
|
1231
|
+
const initial = !threadRef.current ? initialAdmissionRef.current : null;
|
|
1232
|
+
let selectedThreadId = threadRef.current;
|
|
1233
|
+
if (!selectedThreadId && !initial) throw new Error("No active Agent thread is available.");
|
|
1234
|
+
const isCurrent = ownOperation({ adoptThread: Boolean(initial) });
|
|
1235
|
+
const { queued: requestedQueued, prepare, attachments, uploading, ...submission } = input;
|
|
1236
|
+
const priorAdmission = admissionRef.current?.threadId === (selectedThreadId || "") ? admissionRef.current.done : void 0;
|
|
1237
|
+
const queued = Boolean(requestedQueued || priorAdmission || initial);
|
|
1215
1238
|
const optimistic = beginSubmission({
|
|
1216
1239
|
...submission,
|
|
1217
1240
|
attachments,
|
|
1218
1241
|
uploading,
|
|
1219
1242
|
clientSubmissionId: submission.clientSubmissionId || submission.idempotencyKey
|
|
1220
1243
|
}, queued);
|
|
1244
|
+
let releaseAdmission;
|
|
1245
|
+
const admission = { threadId: selectedThreadId || "", done: new Promise((resolve) => {
|
|
1246
|
+
releaseAdmission = resolve;
|
|
1247
|
+
}) };
|
|
1248
|
+
admissionRef.current = admission;
|
|
1221
1249
|
try {
|
|
1222
1250
|
const prepared = prepare ? await prepare() : {};
|
|
1251
|
+
if (initial) selectedThreadId = await initial.done;
|
|
1252
|
+
if (priorAdmission) await priorAdmission;
|
|
1223
1253
|
assertCurrent(isCurrent);
|
|
1254
|
+
if (!selectedThreadId) throw Object.assign(new Error("Initial message admission did not complete."), { name: "AbortError" });
|
|
1255
|
+
admission.threadId = selectedThreadId;
|
|
1224
1256
|
pendingRef.current.set(optimistic.id, { ...optimistic, optimisticQueued: queued, attachment_ids: prepared.attachmentIds || optimistic.attachment_ids, attachments: prepared.attachments || attachments });
|
|
1225
1257
|
const request = { ...submission, ...prepared, clientSubmissionId: optimistic.client_submission_id, threadId: selectedThreadId };
|
|
1226
1258
|
const result = await retrySubmissionAdmission(() => transportRef.current.submit(request));
|
|
@@ -1253,6 +1285,11 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onUnknownPromo
|
|
|
1253
1285
|
} catch (error) {
|
|
1254
1286
|
if (isCurrent()) discardSubmission(optimistic);
|
|
1255
1287
|
throw error;
|
|
1288
|
+
} finally {
|
|
1289
|
+
void Promise.resolve(priorAdmission).then(() => {
|
|
1290
|
+
releaseAdmission();
|
|
1291
|
+
if (admissionRef.current === admission) admissionRef.current = null;
|
|
1292
|
+
});
|
|
1256
1293
|
}
|
|
1257
1294
|
}, [beginSubmission, discardSubmission, refresh, ownOperation]);
|
|
1258
1295
|
const edit = React3.useCallback(async (message, input) => {
|
|
@@ -1288,16 +1325,63 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onUnknownPromo
|
|
|
1288
1325
|
await refresh(selectedThreadId);
|
|
1289
1326
|
return result;
|
|
1290
1327
|
}, [refresh, ownOperation]);
|
|
1291
|
-
React3.
|
|
1328
|
+
const reset = React3.useCallback(() => {
|
|
1329
|
+
initialAdmissionRef.current?.resolve(null);
|
|
1330
|
+
initialAdmissionRef.current = null;
|
|
1331
|
+
admissionRef.current = null;
|
|
1332
|
+
selectionGenerationRef.current += 1;
|
|
1292
1333
|
versionRef.current += 1;
|
|
1293
1334
|
cursorRef.current = 0;
|
|
1294
1335
|
promotedRef.current.clear();
|
|
1295
1336
|
pendingRef.current.clear();
|
|
1296
1337
|
setPendingMessages([]);
|
|
1297
1338
|
setMessageQueue(null);
|
|
1339
|
+
}, []);
|
|
1340
|
+
React3.useEffect(() => {
|
|
1341
|
+
const initial = initialAdmissionRef.current;
|
|
1342
|
+
const adopting = Boolean(initial?.threadId && initial.threadId === threadId && initial.isCurrent());
|
|
1343
|
+
if (adopting) {
|
|
1344
|
+
for (const [id, message] of pendingRef.current) pendingRef.current.set(id, { ...message, thread_id: threadId });
|
|
1345
|
+
initialAdmissionRef.current = null;
|
|
1346
|
+
} else {
|
|
1347
|
+
initial?.resolve(null);
|
|
1348
|
+
initialAdmissionRef.current = null;
|
|
1349
|
+
pendingRef.current.clear();
|
|
1350
|
+
setPendingMessages([]);
|
|
1351
|
+
}
|
|
1352
|
+
versionRef.current += 1;
|
|
1353
|
+
cursorRef.current = 0;
|
|
1354
|
+
promotedRef.current.clear();
|
|
1355
|
+
setMessageQueue(null);
|
|
1298
1356
|
if (threadId) void refresh(threadId).catch(() => void 0);
|
|
1357
|
+
return () => {
|
|
1358
|
+
initialAdmissionRef.current?.resolve(null);
|
|
1359
|
+
};
|
|
1299
1360
|
}, [threadId, refresh]);
|
|
1300
|
-
return {
|
|
1361
|
+
return {
|
|
1362
|
+
pendingMessages,
|
|
1363
|
+
messageQueue,
|
|
1364
|
+
refresh,
|
|
1365
|
+
hydrate,
|
|
1366
|
+
acceptNotice,
|
|
1367
|
+
beginSubmission,
|
|
1368
|
+
discardSubmission,
|
|
1369
|
+
reset,
|
|
1370
|
+
confirmThreadAdmission,
|
|
1371
|
+
hasPendingAdmission: () => Boolean(initialAdmissionRef.current?.isCurrent()),
|
|
1372
|
+
waitForInitialAdmission: () => {
|
|
1373
|
+
const initial = initialAdmissionRef.current;
|
|
1374
|
+
return initial ? initial.done.then((threadId2) => {
|
|
1375
|
+
if (!threadId2) throw initial.failure || Object.assign(new Error("Initial message admission detached."), { name: "AbortError" });
|
|
1376
|
+
return threadId2;
|
|
1377
|
+
}) : Promise.resolve(threadRef.current);
|
|
1378
|
+
},
|
|
1379
|
+
submit,
|
|
1380
|
+
edit,
|
|
1381
|
+
remove,
|
|
1382
|
+
retractForEdit,
|
|
1383
|
+
resume
|
|
1384
|
+
};
|
|
1301
1385
|
}
|
|
1302
1386
|
function optimisticSubmissionTurn(message) {
|
|
1303
1387
|
const user = {
|
|
@@ -1573,8 +1657,6 @@ function useAgentChat(options = {}) {
|
|
|
1573
1657
|
const promotedAttachRef = React7.useRef(null);
|
|
1574
1658
|
const olderRequestRef = React7.useRef(null);
|
|
1575
1659
|
const submissionGenerationRef = React7.useRef(0);
|
|
1576
|
-
const firstSubmissionRef = React7.useRef(null);
|
|
1577
|
-
const firstSubmissionAdmittedRef = React7.useRef(null);
|
|
1578
1660
|
const stateRef = React7.useRef(state);
|
|
1579
1661
|
stateRef.current = state;
|
|
1580
1662
|
const ownOperation = React7.useCallback((options2) => captureChatOperation(() => ({
|
|
@@ -1669,12 +1751,11 @@ function useAgentChat(options = {}) {
|
|
|
1669
1751
|
promotedAttachRef.current = null;
|
|
1670
1752
|
promotedRunHintRef.current = null;
|
|
1671
1753
|
olderRequestRef.current = null;
|
|
1672
|
-
|
|
1673
|
-
firstSubmissionAdmittedRef.current = null;
|
|
1754
|
+
queue.reset();
|
|
1674
1755
|
setIsSubmitting(false);
|
|
1675
1756
|
setIsLoadingOlder(false);
|
|
1676
1757
|
setIsLoadingHistory(false);
|
|
1677
|
-
}, []);
|
|
1758
|
+
}, [queue.reset]);
|
|
1678
1759
|
const detach = React7.useCallback(() => {
|
|
1679
1760
|
eventBuffer.flush();
|
|
1680
1761
|
streamControllerRef.current?.abort();
|
|
@@ -1794,12 +1875,12 @@ function useAgentChat(options = {}) {
|
|
|
1794
1875
|
const consumeEvent = React7.useCallback((event) => {
|
|
1795
1876
|
const eventThreadId = typeof event.payload?.thread_id === "string" ? event.payload.thread_id.trim() : "";
|
|
1796
1877
|
if (eventThreadId && !stateRef.current.activeThreadId) {
|
|
1878
|
+
queue.confirmThreadAdmission(eventThreadId);
|
|
1797
1879
|
update((current) => adoptStreamingThread(current, eventThreadId));
|
|
1798
1880
|
setIsSubmitting(false);
|
|
1799
|
-
firstSubmissionAdmittedRef.current?.();
|
|
1800
1881
|
}
|
|
1801
1882
|
eventBuffer.push(event);
|
|
1802
|
-
}, [eventBuffer, update]);
|
|
1883
|
+
}, [eventBuffer, update, queue.confirmThreadAdmission]);
|
|
1803
1884
|
const attachRun = React7.useCallback(async (runId, cursor) => {
|
|
1804
1885
|
eventBuffer.flush();
|
|
1805
1886
|
streamControllerRef.current?.abort();
|
|
@@ -1829,12 +1910,9 @@ function useAgentChat(options = {}) {
|
|
|
1829
1910
|
attachRunRef.current = attachRun;
|
|
1830
1911
|
const submit = React7.useCallback(async (input) => {
|
|
1831
1912
|
if (!input.text.trim() && !input.attachmentIds?.length && !input.uploads?.length) return;
|
|
1832
|
-
if (firstSubmissionRef.current && !stateRef.current.activeThreadId) {
|
|
1833
|
-
await firstSubmissionRef.current;
|
|
1834
|
-
}
|
|
1835
1913
|
const existingThreadId = stateRef.current.activeThreadId;
|
|
1836
|
-
if (existingThreadId) {
|
|
1837
|
-
const isCurrent = ownOperation();
|
|
1914
|
+
if (existingThreadId || queue.hasPendingAdmission()) {
|
|
1915
|
+
const isCurrent = ownOperation({ adoptThread: !existingThreadId });
|
|
1838
1916
|
const submissionGeneration = ++submissionGenerationRef.current;
|
|
1839
1917
|
clearOperationError();
|
|
1840
1918
|
setIsSubmitting(true);
|
|
@@ -1863,7 +1941,7 @@ function useAgentChat(options = {}) {
|
|
|
1863
1941
|
prepare: async () => {
|
|
1864
1942
|
const uploaded = input.uploads?.length ? await client.attachments.uploadBatch({
|
|
1865
1943
|
uploads: input.uploads,
|
|
1866
|
-
threadId: existingThreadId,
|
|
1944
|
+
threadId: existingThreadId || void 0,
|
|
1867
1945
|
requestedAgentId: input.requestedAgentId,
|
|
1868
1946
|
idempotencyKey: runtime.createId()
|
|
1869
1947
|
}) : [];
|
|
@@ -1888,9 +1966,7 @@ function useAgentChat(options = {}) {
|
|
|
1888
1966
|
return;
|
|
1889
1967
|
}
|
|
1890
1968
|
invalidateThreadOpen();
|
|
1891
|
-
const
|
|
1892
|
-
firstSubmissionAdmittedRef.current = resolve;
|
|
1893
|
-
});
|
|
1969
|
+
const submissionOwner = ownOperation({ adoptThread: true });
|
|
1894
1970
|
const stream = (async () => {
|
|
1895
1971
|
detach();
|
|
1896
1972
|
const controller = runtime.createAbortController();
|
|
@@ -1971,7 +2047,7 @@ function useAgentChat(options = {}) {
|
|
|
1971
2047
|
} catch (error) {
|
|
1972
2048
|
if (!isCurrent()) return;
|
|
1973
2049
|
const admitted = Boolean(stateRef.current.messages.find((message) => message.id === userId)?.runId);
|
|
1974
|
-
if (!admitted) queue.discardSubmission(ticket);
|
|
2050
|
+
if (!admitted) queue.discardSubmission(ticket, error);
|
|
1975
2051
|
if (!isAbortLike(error)) {
|
|
1976
2052
|
update((current) => ({
|
|
1977
2053
|
...current,
|
|
@@ -1982,19 +2058,15 @@ function useAgentChat(options = {}) {
|
|
|
1982
2058
|
throw error;
|
|
1983
2059
|
}
|
|
1984
2060
|
} finally {
|
|
2061
|
+
if (!stateRef.current.messages.some((message) => (message.id === ticket.id || message.clientSubmissionId === ticket.client_submission_id) && message.runId)) queue.discardSubmission(ticket);
|
|
1985
2062
|
if (isCurrent() && submissionGeneration === submissionGenerationRef.current) setIsSubmitting(false);
|
|
1986
2063
|
if (streamControllerRef.current === controller) streamControllerRef.current = null;
|
|
1987
2064
|
}
|
|
1988
2065
|
})();
|
|
1989
|
-
const accepted = Promise.race([stream, admission]);
|
|
1990
|
-
firstSubmissionRef.current = accepted;
|
|
1991
2066
|
try {
|
|
1992
|
-
await
|
|
1993
|
-
}
|
|
1994
|
-
if (
|
|
1995
|
-
firstSubmissionRef.current = null;
|
|
1996
|
-
firstSubmissionAdmittedRef.current = null;
|
|
1997
|
-
}
|
|
2067
|
+
await Promise.race([stream, queue.waitForInitialAdmission()]);
|
|
2068
|
+
} catch (error) {
|
|
2069
|
+
if (submissionOwner() || !isAbortLike(error)) throw error;
|
|
1998
2070
|
}
|
|
1999
2071
|
}, [clearOperationError, client, consumeEvent, detach, eventBuffer, failOperation, invalidateThreadOpen, queue.submit, refreshMessages, refreshThreads, runtime, update, ownOperation]);
|
|
2000
2072
|
const { steer, editPendingMessage, steerPendingMessage, removePendingMessage, resumeQueue } = useAgentChatQueueActions({
|