@agents24/react 0.2.14 → 0.2.15
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 +5 -5
- package/dist/chat-observation.d.ts +27 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +677 -383
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.d.ts +4 -0
- package/dist/model.d.ts +1 -1
- package/dist/operation-ownership.d.ts +12 -0
- package/dist/provider.d.ts +4 -2
- package/dist/state.d.ts +2 -0
- package/dist/thread-message-queue.d.ts +6 -4
- package/dist/use-agent-chat-feedback.d.ts +14 -7
- package/dist/use-agent-chat-queue-actions.d.ts +2 -1
- package/dist/use-agent-chat.d.ts +1 -0
- package/dist/use-chat-observation.d.ts +4 -0
- package/package.json +2 -2
- package/dist/thread-summary-subscription.d.ts +0 -12
package/dist/index.js
CHANGED
|
@@ -10,6 +10,235 @@ function isAbortLike(error) {
|
|
|
10
10
|
return input.name === "AbortError" || input.kind === "aborted" || input.code === "ABORTED";
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
// src/chat-observation.ts
|
|
14
|
+
function createChatObservation(input) {
|
|
15
|
+
const clock = globalThis;
|
|
16
|
+
let state = { threadId: null, running: false, waiting: false, pending: false, paused: false };
|
|
17
|
+
let visible = input.visibility?.isVisible() ?? true;
|
|
18
|
+
let disposed = false;
|
|
19
|
+
let generation = 0;
|
|
20
|
+
let cursor;
|
|
21
|
+
let connection;
|
|
22
|
+
let recovery;
|
|
23
|
+
let refreshing;
|
|
24
|
+
let timer;
|
|
25
|
+
let reconnectTimer;
|
|
26
|
+
let failures = 0;
|
|
27
|
+
let handoffAt;
|
|
28
|
+
let buffered;
|
|
29
|
+
const needed = () => !disposed && visible && Boolean(state.threadId) && !state.waiting && !state.paused && (state.running || state.pending || state.mode === "realtime");
|
|
30
|
+
const clearTimer = () => {
|
|
31
|
+
clock.clearTimeout(timer);
|
|
32
|
+
timer = void 0;
|
|
33
|
+
};
|
|
34
|
+
const stop = () => {
|
|
35
|
+
clearTimer();
|
|
36
|
+
clock.clearTimeout(reconnectTimer);
|
|
37
|
+
connection?.abort();
|
|
38
|
+
connection = void 0;
|
|
39
|
+
};
|
|
40
|
+
const schedule = (callback, ms) => {
|
|
41
|
+
clearTimer();
|
|
42
|
+
timer = clock.setTimeout(callback, ms);
|
|
43
|
+
};
|
|
44
|
+
function refresh() {
|
|
45
|
+
if (disposed || !visible || !state.threadId) return Promise.resolve();
|
|
46
|
+
if (refreshing) return refreshing;
|
|
47
|
+
const owned = generation;
|
|
48
|
+
const controller = input.createAbortController();
|
|
49
|
+
recovery = controller;
|
|
50
|
+
const timeout = clock.setTimeout(() => controller.abort("Observation refresh timed out."), 5e3);
|
|
51
|
+
let onAbort = () => {
|
|
52
|
+
};
|
|
53
|
+
const aborted = new Promise((_, reject) => {
|
|
54
|
+
onAbort = () => reject(Object.assign(new Error("Observation refresh detached."), { name: "AbortError" }));
|
|
55
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
56
|
+
});
|
|
57
|
+
const promise = Promise.race([input.refresh(controller.signal), aborted]).then(() => {
|
|
58
|
+
if (owned === generation && !controller.signal.aborted) input.onRecovered();
|
|
59
|
+
}).finally(() => {
|
|
60
|
+
clock.clearTimeout(timeout);
|
|
61
|
+
controller.signal.removeEventListener("abort", onAbort);
|
|
62
|
+
if (recovery === controller) recovery = void 0;
|
|
63
|
+
if (refreshing === promise) refreshing = void 0;
|
|
64
|
+
});
|
|
65
|
+
refreshing = promise;
|
|
66
|
+
return promise;
|
|
67
|
+
}
|
|
68
|
+
function recover() {
|
|
69
|
+
const owned = generation;
|
|
70
|
+
return refresh().then(() => {
|
|
71
|
+
if (owned === generation && !disposed && visible) {
|
|
72
|
+
failures = 0;
|
|
73
|
+
input.onResume();
|
|
74
|
+
}
|
|
75
|
+
}).catch((error) => {
|
|
76
|
+
if (owned !== generation || disposed || !visible) return;
|
|
77
|
+
failures += 1;
|
|
78
|
+
input.onError(error);
|
|
79
|
+
if (needed()) schedule(poll, 1e3);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function poll() {
|
|
83
|
+
if (!needed()) return;
|
|
84
|
+
void recover().finally(() => {
|
|
85
|
+
if (needed() && (state.pending && !state.running || state.degraded || failures > 0)) schedule(poll, 1e3);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function connect() {
|
|
89
|
+
if (!needed() || connection) return;
|
|
90
|
+
const owned = generation;
|
|
91
|
+
const controller = input.createAbortController();
|
|
92
|
+
connection = controller;
|
|
93
|
+
buffered = [];
|
|
94
|
+
let ready = false;
|
|
95
|
+
let leased = false;
|
|
96
|
+
let hydration;
|
|
97
|
+
const hydrate = () => hydration ??= recover().finally(() => {
|
|
98
|
+
if (owned !== generation || controller.signal.aborted) return;
|
|
99
|
+
const notices = buffered || [];
|
|
100
|
+
buffered = void 0;
|
|
101
|
+
for (const event of notices) input.onEvent(event);
|
|
102
|
+
if (notices.length) void recover();
|
|
103
|
+
});
|
|
104
|
+
void input.events({
|
|
105
|
+
threadId: state.threadId,
|
|
106
|
+
cursor,
|
|
107
|
+
leaseSeconds: 60,
|
|
108
|
+
signal: controller.signal,
|
|
109
|
+
onControl(control) {
|
|
110
|
+
if (control.type === "lease_expired") leased = true;
|
|
111
|
+
if (control.type === "ready") {
|
|
112
|
+
ready = true;
|
|
113
|
+
cursor ??= control.cursor;
|
|
114
|
+
void hydrate().catch(input.onError);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}, (event) => {
|
|
118
|
+
if (owned !== generation || controller.signal.aborted) return;
|
|
119
|
+
if (event.event === "snapshot_required") {
|
|
120
|
+
cursor = void 0;
|
|
121
|
+
void refresh().catch(input.onError);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
cursor = Math.max(cursor ?? 0, event.cursor);
|
|
125
|
+
failures = 0;
|
|
126
|
+
if (buffered) buffered.push(event);
|
|
127
|
+
else {
|
|
128
|
+
input.onEvent(event);
|
|
129
|
+
void recover();
|
|
130
|
+
}
|
|
131
|
+
if (!ready) {
|
|
132
|
+
ready = true;
|
|
133
|
+
void hydrate().catch(input.onError);
|
|
134
|
+
}
|
|
135
|
+
}).then(() => {
|
|
136
|
+
if (owned !== generation || controller.signal.aborted) return;
|
|
137
|
+
connection = void 0;
|
|
138
|
+
if (needed()) {
|
|
139
|
+
if (leased) connect();
|
|
140
|
+
else {
|
|
141
|
+
void refresh().catch(input.onError);
|
|
142
|
+
reconnectTimer = clock.setTimeout(connect, 1e3);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}).catch((error) => {
|
|
146
|
+
if (owned !== generation || controller.signal.aborted || isAbortLike(error)) return;
|
|
147
|
+
connection = void 0;
|
|
148
|
+
failures += 1;
|
|
149
|
+
input.onError(error);
|
|
150
|
+
poll();
|
|
151
|
+
reconnectTimer = clock.setTimeout(connect, Math.min(3e4, 1e3 * 2 ** Math.min(failures - 1, 5)));
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function reconcile() {
|
|
155
|
+
if (!needed()) {
|
|
156
|
+
handoffAt = void 0;
|
|
157
|
+
stop();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
connect();
|
|
161
|
+
if (state.pending && !state.running) {
|
|
162
|
+
handoffAt ??= Date.now() + 2e3;
|
|
163
|
+
schedule(poll, Math.max(0, handoffAt - Date.now()));
|
|
164
|
+
} else {
|
|
165
|
+
handoffAt = void 0;
|
|
166
|
+
if (state.degraded) schedule(poll, 1e3);
|
|
167
|
+
else if (!failures) clearTimer();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const unsubscribe = input.visibility?.subscribe(() => {
|
|
171
|
+
const next = input.visibility.isVisible();
|
|
172
|
+
if (next === visible) return;
|
|
173
|
+
visible = next;
|
|
174
|
+
if (!visible) {
|
|
175
|
+
stop();
|
|
176
|
+
recovery?.abort();
|
|
177
|
+
refreshing = void 0;
|
|
178
|
+
input.onDetach();
|
|
179
|
+
} else {
|
|
180
|
+
reconcile();
|
|
181
|
+
if (!needed()) void refresh().then(input.onResume).catch(input.onError);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
refresh,
|
|
186
|
+
update(next) {
|
|
187
|
+
if (next.threadId !== state.threadId) {
|
|
188
|
+
generation += 1;
|
|
189
|
+
stop();
|
|
190
|
+
recovery?.abort();
|
|
191
|
+
refreshing = void 0;
|
|
192
|
+
cursor = void 0;
|
|
193
|
+
buffered = void 0;
|
|
194
|
+
failures = 0;
|
|
195
|
+
handoffAt = void 0;
|
|
196
|
+
}
|
|
197
|
+
state = next;
|
|
198
|
+
reconcile();
|
|
199
|
+
},
|
|
200
|
+
dispose() {
|
|
201
|
+
disposed = true;
|
|
202
|
+
generation += 1;
|
|
203
|
+
stop();
|
|
204
|
+
recovery?.abort();
|
|
205
|
+
unsubscribe?.();
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/use-chat-observation.ts
|
|
211
|
+
import * as React from "react";
|
|
212
|
+
function useChatObservation(input) {
|
|
213
|
+
const latest = React.useRef(input);
|
|
214
|
+
latest.current = input;
|
|
215
|
+
const owner = React.useRef(null);
|
|
216
|
+
React.useEffect(() => {
|
|
217
|
+
const observation = createChatObservation({
|
|
218
|
+
createAbortController: input.createAbortController,
|
|
219
|
+
visibility: input.visibility,
|
|
220
|
+
events: (options, handler) => latest.current.events(options, handler),
|
|
221
|
+
refresh: (signal) => latest.current.refresh(signal),
|
|
222
|
+
onEvent: (event) => latest.current.onEvent(event),
|
|
223
|
+
onError: (error) => latest.current.onError(error),
|
|
224
|
+
onRecovered: () => latest.current.onRecovered(),
|
|
225
|
+
onDetach: () => latest.current.onDetach(),
|
|
226
|
+
onResume: () => latest.current.onResume()
|
|
227
|
+
});
|
|
228
|
+
owner.current = observation;
|
|
229
|
+
observation.update(latest.current.state);
|
|
230
|
+
return () => {
|
|
231
|
+
observation.dispose();
|
|
232
|
+
if (owner.current === observation) owner.current = null;
|
|
233
|
+
};
|
|
234
|
+
}, [input.createAbortController, input.visibility]);
|
|
235
|
+
const { threadId, running, waiting, pending, paused, degraded, mode } = input.state;
|
|
236
|
+
React.useEffect(() => {
|
|
237
|
+
owner.current?.update({ threadId, running, waiting, pending, paused, degraded, mode });
|
|
238
|
+
}, [threadId, running, waiting, pending, paused, degraded, mode]);
|
|
239
|
+
return React.useCallback(() => owner.current?.refresh() ?? Promise.resolve(), []);
|
|
240
|
+
}
|
|
241
|
+
|
|
13
242
|
// src/model.ts
|
|
14
243
|
import {
|
|
15
244
|
compressionFromContextWindow,
|
|
@@ -389,8 +618,7 @@ function threadDetailToMessages(detail) {
|
|
|
389
618
|
return output;
|
|
390
619
|
});
|
|
391
620
|
}
|
|
392
|
-
function threadTimelineToMessages(items
|
|
393
|
-
const feedbackByRun = new Map((detail?.turns || []).flatMap((turn) => turn.run_id && turn.feedback ? [[turn.run_id, turn.feedback]] : []));
|
|
621
|
+
function threadTimelineToMessages(items) {
|
|
394
622
|
return items.map((item) => ({
|
|
395
623
|
id: item.id,
|
|
396
624
|
clientSubmissionId: item.client_submission_id || void 0,
|
|
@@ -406,7 +634,7 @@ function threadTimelineToMessages(items, detail) {
|
|
|
406
634
|
const descriptor = item.attachments?.find((attachment) => attachment.id === id);
|
|
407
635
|
return { id, filename: descriptor?.filename || "Attachment", mediaType: descriptor?.mime_type || "application/octet-stream" };
|
|
408
636
|
}) : void 0,
|
|
409
|
-
feedback: item.role === "assistant"
|
|
637
|
+
feedback: item.role === "assistant" ? item.feedback ?? null : void 0
|
|
410
638
|
}));
|
|
411
639
|
}
|
|
412
640
|
function latestThreadContext(detail) {
|
|
@@ -430,24 +658,25 @@ function attachmentResultToChatAttachment(result) {
|
|
|
430
658
|
}
|
|
431
659
|
|
|
432
660
|
// src/provider.tsx
|
|
433
|
-
import * as
|
|
661
|
+
import * as React2 from "react";
|
|
434
662
|
import { jsx } from "react/jsx-runtime";
|
|
435
|
-
var Agents24Context =
|
|
663
|
+
var Agents24Context = React2.createContext(null);
|
|
436
664
|
function Agents24Provider({
|
|
437
665
|
children,
|
|
438
666
|
client,
|
|
439
667
|
createAbortController,
|
|
440
668
|
createId = createPortableId,
|
|
441
|
-
storage
|
|
669
|
+
storage,
|
|
670
|
+
visibility
|
|
442
671
|
}) {
|
|
443
|
-
const value =
|
|
444
|
-
() => ({ client, createAbortController, createId, storage }),
|
|
445
|
-
[client, createAbortController, createId, storage]
|
|
672
|
+
const value = React2.useMemo(
|
|
673
|
+
() => ({ client, createAbortController, createId, storage, visibility: visibility ?? client.visibility }),
|
|
674
|
+
[client, createAbortController, createId, storage, visibility]
|
|
446
675
|
);
|
|
447
676
|
return /* @__PURE__ */ jsx(Agents24Context.Provider, { value, children });
|
|
448
677
|
}
|
|
449
678
|
function useAgents24Runtime() {
|
|
450
|
-
const value =
|
|
679
|
+
const value = React2.useContext(Agents24Context);
|
|
451
680
|
if (!value) throw new Error("Agents24 hooks must be rendered inside Agents24Provider.");
|
|
452
681
|
return value;
|
|
453
682
|
}
|
|
@@ -615,10 +844,15 @@ function reconcileThreadTimeline(current, canonical, activeRunId, streamingMessa
|
|
|
615
844
|
const retained = current.filter((message) => !canonicalIds.has(message.id) && (message.isFinal || activeRunId && (!message.runId || message.runId === activeRunId)));
|
|
616
845
|
const reconciled = mounted.map((message) => {
|
|
617
846
|
const live = current.find((item) => item.id === message.id);
|
|
618
|
-
return activeRunId && live?.role === "assistant" && live.runId === activeRunId && (live.id === streamingMessageId || !live.isFinal || live.responseCompletion) ? message.responseCompletion && !live.responseCompletion ? { ...message, id: live.id, durableId: message.durableId || live.durableId } : { ...message, ...live, durableId: message.durableId || live.durableId } : message;
|
|
847
|
+
return activeRunId && live?.role === "assistant" && live.runId === activeRunId && (live.id === streamingMessageId || !live.isFinal || live.responseCompletion) ? message.responseCompletion && !live.responseCompletion ? { ...message, id: live.id, durableId: message.durableId || live.durableId } : { ...message, ...live, feedback: message.feedback, durableId: message.durableId || live.durableId } : message;
|
|
619
848
|
});
|
|
620
849
|
return [...retained, ...reconciled];
|
|
621
850
|
}
|
|
851
|
+
function prependThreadTimeline(current, older) {
|
|
852
|
+
const mounted = preserveMountedMessageIds(older, current);
|
|
853
|
+
const ids = new Set(current.map((message) => message.id));
|
|
854
|
+
return [...mounted.filter((message) => !ids.has(message.id)), ...current];
|
|
855
|
+
}
|
|
622
856
|
function promoteThreadMessage(current, message, runId) {
|
|
623
857
|
const promoted = { ...message, runId, isFinal: true };
|
|
624
858
|
const index = current.findIndex((item) => item.id === message.id || item.durableId === message.id || Boolean(message.clientSubmissionId && item.clientSubmissionId === message.clientSubmissionId));
|
|
@@ -839,7 +1073,18 @@ function applyThreadSummaryEvent(threads, event) {
|
|
|
839
1073
|
}
|
|
840
1074
|
|
|
841
1075
|
// src/thread-message-queue.ts
|
|
842
|
-
import * as
|
|
1076
|
+
import * as React3 from "react";
|
|
1077
|
+
|
|
1078
|
+
// src/operation-ownership.ts
|
|
1079
|
+
function captureChatOperation(read, options = {}) {
|
|
1080
|
+
const owner = read();
|
|
1081
|
+
return () => {
|
|
1082
|
+
const current = read();
|
|
1083
|
+
return current.generation === owner.generation && (options.adoptThread || current.threadId === owner.threadId) && (!options.run || current.runId === owner.runId) && (!options.stream || current.stream === owner.stream);
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// src/thread-message-queue.ts
|
|
843
1088
|
async function retrySubmissionAdmission(admit) {
|
|
844
1089
|
try {
|
|
845
1090
|
return await admit();
|
|
@@ -849,23 +1094,34 @@ async function retrySubmissionAdmission(admit) {
|
|
|
849
1094
|
return admit();
|
|
850
1095
|
}
|
|
851
1096
|
}
|
|
852
|
-
function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic, onDiscard }) {
|
|
853
|
-
const [pendingMessages, setPendingMessages] =
|
|
854
|
-
const [messageQueue, setMessageQueue] =
|
|
855
|
-
const pendingRef =
|
|
856
|
-
const versionRef =
|
|
857
|
-
const cursorRef =
|
|
858
|
-
const promotedRef =
|
|
859
|
-
const threadRef =
|
|
860
|
-
const
|
|
861
|
-
|
|
1097
|
+
function useThreadMessageQueue({ threadId, transport, onPromoted, onUnknownPromotion, onOptimistic, onDiscard, ownOperation: ownSelection }) {
|
|
1098
|
+
const [pendingMessages, setPendingMessages] = React3.useState([]);
|
|
1099
|
+
const [messageQueue, setMessageQueue] = React3.useState(null);
|
|
1100
|
+
const pendingRef = React3.useRef(/* @__PURE__ */ new Map());
|
|
1101
|
+
const versionRef = React3.useRef(0);
|
|
1102
|
+
const cursorRef = React3.useRef(0);
|
|
1103
|
+
const promotedRef = React3.useRef(/* @__PURE__ */ new Set());
|
|
1104
|
+
const threadRef = React3.useRef(threadId);
|
|
1105
|
+
const selectionGenerationRef = React3.useRef(0);
|
|
1106
|
+
if (threadRef.current !== threadId) selectionGenerationRef.current += 1;
|
|
1107
|
+
const transportRef = React3.useRef(transport);
|
|
1108
|
+
const onPromotedRef = React3.useRef(onPromoted);
|
|
862
1109
|
threadRef.current = threadId;
|
|
863
1110
|
transportRef.current = transport;
|
|
864
1111
|
onPromotedRef.current = onPromoted;
|
|
865
|
-
const
|
|
866
|
-
|
|
1112
|
+
const ownSelectionRef = React3.useRef(ownSelection);
|
|
1113
|
+
ownSelectionRef.current = ownSelection;
|
|
1114
|
+
const callbacksRef = React3.useRef({ onOptimistic, onDiscard, onUnknownPromotion });
|
|
1115
|
+
callbacksRef.current = { onOptimistic, onDiscard, onUnknownPromotion };
|
|
1116
|
+
const ownOperation = React3.useCallback(() => ownSelectionRef.current?.() ?? captureChatOperation(() => ({
|
|
1117
|
+
generation: selectionGenerationRef.current,
|
|
1118
|
+
threadId: threadRef.current
|
|
1119
|
+
})), []);
|
|
1120
|
+
const assertCurrent = (isCurrent) => {
|
|
1121
|
+
if (!isCurrent()) throw Object.assign(new Error("Thread selection changed."), { name: "AbortError" });
|
|
1122
|
+
};
|
|
867
1123
|
const knownMessage = (id, submissionId) => pendingRef.current.get(id) || (submissionId ? [...pendingRef.current.values()].find((item) => item.client_submission_id === submissionId) : void 0);
|
|
868
|
-
const hydrate =
|
|
1124
|
+
const hydrate = React3.useCallback((page, selectedThreadId) => {
|
|
869
1125
|
if (threadRef.current !== selectedThreadId) return;
|
|
870
1126
|
const pending = page.pending.filter((message) => !promotedRef.current.has(message.id)).map((message) => {
|
|
871
1127
|
const known = knownMessage(message.id, message.client_submission_id);
|
|
@@ -877,11 +1133,12 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
877
1133
|
setPendingMessages([...pending, ...optimistic]);
|
|
878
1134
|
setMessageQueue(page.queue);
|
|
879
1135
|
}, []);
|
|
880
|
-
const refresh =
|
|
1136
|
+
const refresh = React3.useCallback(async (selectedThreadId = threadRef.current, signal) => {
|
|
881
1137
|
if (!selectedThreadId) return null;
|
|
1138
|
+
const isCurrent = ownOperation();
|
|
882
1139
|
const version = ++versionRef.current;
|
|
883
|
-
const page = await transportRef.current.list(selectedThreadId);
|
|
884
|
-
if (version !== versionRef.current || threadRef.current !== selectedThreadId) return null;
|
|
1140
|
+
const page = await transportRef.current.list(selectedThreadId, signal);
|
|
1141
|
+
if (signal?.aborted || !isCurrent() || version !== versionRef.current || threadRef.current !== selectedThreadId) return null;
|
|
885
1142
|
for (const item of page.items) {
|
|
886
1143
|
if (item.role === "user" && item.status === "delivered" && item.run_id) {
|
|
887
1144
|
const pending = knownMessage(item.id, item.client_submission_id);
|
|
@@ -899,23 +1156,24 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
899
1156
|
}
|
|
900
1157
|
hydrate(page, selectedThreadId);
|
|
901
1158
|
return page;
|
|
902
|
-
}, [hydrate]);
|
|
903
|
-
const acceptNotice =
|
|
1159
|
+
}, [hydrate, ownOperation]);
|
|
1160
|
+
const acceptNotice = React3.useCallback((notice) => {
|
|
904
1161
|
if (notice.cursor && notice.cursor <= cursorRef.current) return false;
|
|
905
1162
|
if (notice.cursor) cursorRef.current = notice.cursor;
|
|
906
1163
|
const pending = knownMessage(notice.messageId, notice.clientSubmissionId);
|
|
907
|
-
if (notice.status === "delivered" && notice.runId &&
|
|
1164
|
+
if (notice.status === "delivered" && notice.runId && !promotedRef.current.has(notice.messageId)) {
|
|
908
1165
|
promotedRef.current.add(notice.messageId);
|
|
909
|
-
pendingRef.current.delete(pending.id);
|
|
910
1166
|
versionRef.current += 1;
|
|
911
|
-
|
|
912
|
-
|
|
1167
|
+
if (pending) {
|
|
1168
|
+
pendingRef.current.delete(pending.id);
|
|
1169
|
+
setPendingMessages((current) => current.filter((message) => message.id !== pending.id));
|
|
1170
|
+
onPromotedRef.current({ ...pending, id: notice.messageId, run_id: notice.runId, status: "delivered" }, notice.runId);
|
|
1171
|
+
} else if (threadRef.current) callbacksRef.current.onUnknownPromotion?.(notice.runId, threadRef.current);
|
|
913
1172
|
}
|
|
914
1173
|
if (notice.status === "removed") pendingRef.current.delete(notice.messageId);
|
|
915
|
-
void refresh().catch(() => void 0);
|
|
916
1174
|
return true;
|
|
917
|
-
}, [
|
|
918
|
-
const beginSubmission =
|
|
1175
|
+
}, []);
|
|
1176
|
+
const beginSubmission = React3.useCallback((input, queued = false) => {
|
|
919
1177
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
920
1178
|
const message = {
|
|
921
1179
|
id: input.clientSubmissionId,
|
|
@@ -942,16 +1200,17 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
942
1200
|
else callbacksRef.current.onOptimistic?.(message, Boolean(input.uploading));
|
|
943
1201
|
return message;
|
|
944
1202
|
}, []);
|
|
945
|
-
const discardSubmission =
|
|
1203
|
+
const discardSubmission = React3.useCallback((message) => {
|
|
946
1204
|
if (!pendingRef.current.has(message.id)) return;
|
|
947
1205
|
pendingRef.current.delete(message.id);
|
|
948
1206
|
callbacksRef.current.onDiscard?.(message);
|
|
949
1207
|
versionRef.current += 1;
|
|
950
1208
|
setPendingMessages((current) => current.filter((item) => item.client_submission_id !== message.client_submission_id));
|
|
951
1209
|
}, []);
|
|
952
|
-
const submit =
|
|
1210
|
+
const submit = React3.useCallback(async (input) => {
|
|
953
1211
|
const selectedThreadId = threadRef.current;
|
|
954
1212
|
if (!selectedThreadId) throw new Error("No active Agent thread is available.");
|
|
1213
|
+
const isCurrent = ownOperation();
|
|
955
1214
|
const { queued, prepare, attachments, uploading, ...submission } = input;
|
|
956
1215
|
const optimistic = beginSubmission({
|
|
957
1216
|
...submission,
|
|
@@ -961,10 +1220,11 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
961
1220
|
}, queued);
|
|
962
1221
|
try {
|
|
963
1222
|
const prepared = prepare ? await prepare() : {};
|
|
964
|
-
|
|
1223
|
+
assertCurrent(isCurrent);
|
|
1224
|
+
pendingRef.current.set(optimistic.id, { ...optimistic, optimisticQueued: queued, attachment_ids: prepared.attachmentIds || optimistic.attachment_ids, attachments: prepared.attachments || attachments });
|
|
965
1225
|
const request = { ...submission, ...prepared, clientSubmissionId: optimistic.client_submission_id, threadId: selectedThreadId };
|
|
966
1226
|
const result = await retrySubmissionAdmission(() => transportRef.current.submit(request));
|
|
967
|
-
if (
|
|
1227
|
+
if (!isCurrent()) return result;
|
|
968
1228
|
versionRef.current += 1;
|
|
969
1229
|
const bound = {
|
|
970
1230
|
...optimistic,
|
|
@@ -991,36 +1251,44 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
991
1251
|
void refresh().catch(() => void 0);
|
|
992
1252
|
return result;
|
|
993
1253
|
} catch (error) {
|
|
994
|
-
if (
|
|
1254
|
+
if (isCurrent()) discardSubmission(optimistic);
|
|
995
1255
|
throw error;
|
|
996
1256
|
}
|
|
997
|
-
}, [beginSubmission, discardSubmission, refresh]);
|
|
998
|
-
const edit =
|
|
1257
|
+
}, [beginSubmission, discardSubmission, refresh, ownOperation]);
|
|
1258
|
+
const edit = React3.useCallback(async (message, input) => {
|
|
1259
|
+
const isCurrent = ownOperation();
|
|
999
1260
|
const updated = await transportRef.current.edit(message, input);
|
|
1261
|
+
assertCurrent(isCurrent);
|
|
1000
1262
|
versionRef.current += 1;
|
|
1001
1263
|
pendingRef.current.set(updated.id, updated);
|
|
1002
1264
|
await refresh(message.thread_id);
|
|
1003
1265
|
return updated;
|
|
1004
|
-
}, [refresh]);
|
|
1005
|
-
const remove =
|
|
1266
|
+
}, [refresh, ownOperation]);
|
|
1267
|
+
const remove = React3.useCallback(async (message) => {
|
|
1268
|
+
const isCurrent = ownOperation();
|
|
1006
1269
|
await transportRef.current.remove(message);
|
|
1270
|
+
assertCurrent(isCurrent);
|
|
1007
1271
|
versionRef.current += 1;
|
|
1008
1272
|
pendingRef.current.delete(message.id);
|
|
1009
1273
|
setPendingMessages((current) => current.filter((item) => item.id !== message.id));
|
|
1010
1274
|
await refresh(message.thread_id);
|
|
1011
|
-
}, [refresh]);
|
|
1012
|
-
const retractForEdit =
|
|
1275
|
+
}, [refresh, ownOperation]);
|
|
1276
|
+
const retractForEdit = React3.useCallback(async (message) => {
|
|
1277
|
+
const isCurrent = ownOperation();
|
|
1013
1278
|
await remove(message);
|
|
1279
|
+
assertCurrent(isCurrent);
|
|
1014
1280
|
return { text: message.text, attachment_ids: message.attachment_ids, attachments: message.attachments };
|
|
1015
|
-
}, [remove]);
|
|
1016
|
-
const resume =
|
|
1281
|
+
}, [remove, ownOperation]);
|
|
1282
|
+
const resume = React3.useCallback(async () => {
|
|
1017
1283
|
const selectedThreadId = threadRef.current;
|
|
1018
1284
|
if (!selectedThreadId) return null;
|
|
1285
|
+
const isCurrent = ownOperation();
|
|
1019
1286
|
const result = await transportRef.current.resume(selectedThreadId);
|
|
1287
|
+
if (!isCurrent()) return null;
|
|
1020
1288
|
await refresh(selectedThreadId);
|
|
1021
1289
|
return result;
|
|
1022
|
-
}, [refresh]);
|
|
1023
|
-
|
|
1290
|
+
}, [refresh, ownOperation]);
|
|
1291
|
+
React3.useEffect(() => {
|
|
1024
1292
|
versionRef.current += 1;
|
|
1025
1293
|
cursorRef.current = 0;
|
|
1026
1294
|
promotedRef.current.clear();
|
|
@@ -1029,14 +1297,6 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
1029
1297
|
setMessageQueue(null);
|
|
1030
1298
|
if (threadId) void refresh(threadId).catch(() => void 0);
|
|
1031
1299
|
}, [threadId, refresh]);
|
|
1032
|
-
React2.useEffect(() => {
|
|
1033
|
-
if (!threadId || !pendingMessages.length || messageQueue?.paused) return;
|
|
1034
|
-
const clock = globalThis;
|
|
1035
|
-
const timer = clock.setInterval(() => {
|
|
1036
|
-
void refresh(threadId).catch(() => void 0);
|
|
1037
|
-
}, 3e3);
|
|
1038
|
-
return () => clock.clearInterval(timer);
|
|
1039
|
-
}, [threadId, pendingMessages.length, messageQueue?.paused, refresh]);
|
|
1040
1300
|
return { pendingMessages, messageQueue, refresh, hydrate, acceptNotice, beginSubmission, discardSubmission, submit, edit, remove, retractForEdit, resume };
|
|
1041
1301
|
}
|
|
1042
1302
|
function optimisticSubmissionTurn(message) {
|
|
@@ -1085,72 +1345,20 @@ function discardOptimisticSubmission(state, message) {
|
|
|
1085
1345
|
}
|
|
1086
1346
|
|
|
1087
1347
|
// src/use-agent-chat.ts
|
|
1088
|
-
import * as
|
|
1089
|
-
|
|
1090
|
-
// src/thread-summary-subscription.ts
|
|
1091
|
-
function waitForReconnect(signal, delay) {
|
|
1092
|
-
return new Promise((resolve) => {
|
|
1093
|
-
if (signal.aborted) return resolve();
|
|
1094
|
-
const clock = globalThis;
|
|
1095
|
-
const finish = () => {
|
|
1096
|
-
clock.clearTimeout(timer);
|
|
1097
|
-
signal.removeEventListener("abort", finish);
|
|
1098
|
-
resolve();
|
|
1099
|
-
};
|
|
1100
|
-
const timer = clock.setTimeout(finish, delay);
|
|
1101
|
-
signal.addEventListener("abort", finish, { once: true });
|
|
1102
|
-
});
|
|
1103
|
-
}
|
|
1104
|
-
async function subscribeToThreadSummaries(input) {
|
|
1105
|
-
let cursor;
|
|
1106
|
-
let failures = 0;
|
|
1107
|
-
while (!input.signal.aborted) {
|
|
1108
|
-
let refreshedFromSnapshot = false;
|
|
1109
|
-
try {
|
|
1110
|
-
await input.client.threads.events({ cursor, signal: input.signal }, async (event) => {
|
|
1111
|
-
if (event.event === "snapshot_required") {
|
|
1112
|
-
await input.refresh();
|
|
1113
|
-
cursor = void 0;
|
|
1114
|
-
refreshedFromSnapshot = true;
|
|
1115
|
-
return;
|
|
1116
|
-
}
|
|
1117
|
-
cursor = event.cursor;
|
|
1118
|
-
failures = 0;
|
|
1119
|
-
await input.onEvent(event);
|
|
1120
|
-
});
|
|
1121
|
-
if (input.signal.aborted) break;
|
|
1122
|
-
if (!refreshedFromSnapshot) await input.refresh();
|
|
1123
|
-
input.onRecovered();
|
|
1124
|
-
failures = 0;
|
|
1125
|
-
} catch (error) {
|
|
1126
|
-
if (input.signal.aborted || isAbortLike(error)) break;
|
|
1127
|
-
input.onError(error);
|
|
1128
|
-
failures += 1;
|
|
1129
|
-
try {
|
|
1130
|
-
await input.refresh();
|
|
1131
|
-
input.onRecovered();
|
|
1132
|
-
} catch (refreshError) {
|
|
1133
|
-
if (!input.signal.aborted && !isAbortLike(refreshError)) input.onError(refreshError);
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
if (!input.signal.aborted) {
|
|
1137
|
-
await waitForReconnect(input.signal, Math.min(3e4, 1e3 * 2 ** Math.min(failures, 5)));
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1348
|
+
import * as React7 from "react";
|
|
1141
1349
|
|
|
1142
1350
|
// src/use-agent-chat-thread-list.ts
|
|
1143
|
-
import * as
|
|
1351
|
+
import * as React4 from "react";
|
|
1144
1352
|
function summaryToStored(thread) {
|
|
1145
1353
|
return { ...thread, id: String(thread.id), messages: [], isHydrated: false };
|
|
1146
1354
|
}
|
|
1147
1355
|
function useAgentChatThreadList({ client, threadPageSize, stateRef, update, clearOperationError, failOperation }) {
|
|
1148
|
-
const [isLoadingThreads, setIsLoadingThreads] =
|
|
1149
|
-
const [isLoadingMoreThreads, setIsLoadingMoreThreads] =
|
|
1150
|
-
const [threadTotal, setThreadTotal] =
|
|
1151
|
-
const refreshPromiseRef =
|
|
1152
|
-
const eventsDuringRefreshRef =
|
|
1153
|
-
const refreshThreads =
|
|
1356
|
+
const [isLoadingThreads, setIsLoadingThreads] = React4.useState(false);
|
|
1357
|
+
const [isLoadingMoreThreads, setIsLoadingMoreThreads] = React4.useState(false);
|
|
1358
|
+
const [threadTotal, setThreadTotal] = React4.useState();
|
|
1359
|
+
const refreshPromiseRef = React4.useRef(null);
|
|
1360
|
+
const eventsDuringRefreshRef = React4.useRef(null);
|
|
1361
|
+
const refreshThreads = React4.useCallback(() => {
|
|
1154
1362
|
if (refreshPromiseRef.current) return refreshPromiseRef.current;
|
|
1155
1363
|
clearOperationError();
|
|
1156
1364
|
setIsLoadingThreads(true);
|
|
@@ -1185,7 +1393,7 @@ function useAgentChatThreadList({ client, threadPageSize, stateRef, update, clea
|
|
|
1185
1393
|
}).catch(() => void 0);
|
|
1186
1394
|
return request;
|
|
1187
1395
|
}, [clearOperationError, client, failOperation, threadPageSize, update]);
|
|
1188
|
-
const loadMoreThreads =
|
|
1396
|
+
const loadMoreThreads = React4.useCallback(async () => {
|
|
1189
1397
|
if (isLoadingMoreThreads) return;
|
|
1190
1398
|
const skip = stateRef.current.threads.length;
|
|
1191
1399
|
if (threadTotal !== void 0 && skip >= threadTotal) return;
|
|
@@ -1220,15 +1428,30 @@ function useAgentChatThreadList({ client, threadPageSize, stateRef, update, clea
|
|
|
1220
1428
|
}
|
|
1221
1429
|
|
|
1222
1430
|
// src/use-agent-chat-feedback.ts
|
|
1223
|
-
import * as
|
|
1224
|
-
function useAgentChatFeedback({ client, update, clearOperationError, failOperation }) {
|
|
1225
|
-
|
|
1431
|
+
import * as React5 from "react";
|
|
1432
|
+
function useAgentChatFeedback({ client, update, clearOperationError, failOperation, ownOperation, refreshMessages }) {
|
|
1433
|
+
const mutations = React5.useRef(/* @__PURE__ */ new Map());
|
|
1434
|
+
const captureRead = React5.useCallback(() => {
|
|
1435
|
+
const revisions = new Map([...mutations.current].map(([run, mutation]) => [run, mutation.readRevision]));
|
|
1436
|
+
return (incoming, current) => incoming.map((message) => {
|
|
1437
|
+
const mutation = message.runId ? mutations.current.get(message.runId) : void 0;
|
|
1438
|
+
const preserve = mutation && (mutation.pending > 0 || mutation.readRevision !== revisions.get(message.runId));
|
|
1439
|
+
const live = preserve && current.find((item) => item.role === "assistant" && item.runId === message.runId);
|
|
1440
|
+
return live ? { ...message, feedback: live.feedback } : message;
|
|
1441
|
+
});
|
|
1442
|
+
}, []);
|
|
1443
|
+
const setFeedback = React5.useCallback(async (message, input) => {
|
|
1226
1444
|
const runId = message.runId;
|
|
1227
1445
|
if (!runId || message.role !== "assistant" || message.isFinal === false) {
|
|
1228
1446
|
const error = new Error("Only persisted final responses can receive feedback.");
|
|
1229
1447
|
failOperation("set_feedback", error);
|
|
1230
1448
|
throw error;
|
|
1231
1449
|
}
|
|
1450
|
+
const isCurrent = ownOperation();
|
|
1451
|
+
const revision = (mutations.current.get(runId)?.revision ?? 0) + 1;
|
|
1452
|
+
const pending = (mutations.current.get(runId)?.pending ?? 0) + 1;
|
|
1453
|
+
mutations.current.set(runId, { revision, pending, readRevision: (mutations.current.get(runId)?.readRevision ?? 0) + 1 });
|
|
1454
|
+
const isLatest = () => mutations.current.get(runId)?.revision === revision;
|
|
1232
1455
|
const previous = message.feedback ?? null;
|
|
1233
1456
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1234
1457
|
const optimistic = input.rating ? {
|
|
@@ -1240,26 +1463,34 @@ function useAgentChatFeedback({ client, update, clearOperationError, failOperati
|
|
|
1240
1463
|
} : null;
|
|
1241
1464
|
const replaceFeedback = (current, feedback) => ({
|
|
1242
1465
|
...current,
|
|
1243
|
-
messages: current.messages.map((item) => item.
|
|
1466
|
+
messages: current.messages.map((item) => item.role === "assistant" && item.runId === runId ? { ...item, feedback } : item),
|
|
1244
1467
|
threads: current.threads.map((thread) => ({
|
|
1245
1468
|
...thread,
|
|
1246
|
-
messages: thread.messages.map((item) => item.
|
|
1469
|
+
messages: thread.messages.map((item) => item.role === "assistant" && item.runId === runId ? { ...item, feedback } : item)
|
|
1247
1470
|
}))
|
|
1248
1471
|
});
|
|
1249
1472
|
clearOperationError();
|
|
1250
1473
|
update((current) => replaceFeedback(current, optimistic));
|
|
1251
1474
|
try {
|
|
1252
1475
|
const result = await client.runs.setFeedback({ runId, ...input });
|
|
1253
|
-
update((current) => replaceFeedback(current, result.feedback));
|
|
1476
|
+
if (isLatest() && isCurrent()) update((current) => replaceFeedback(current, result.feedback));
|
|
1254
1477
|
} catch (error) {
|
|
1255
|
-
|
|
1478
|
+
if (isLatest() && isCurrent()) update((current) => {
|
|
1479
|
+
const restored = replaceFeedback(current, previous);
|
|
1480
|
+
return isCurrent() ? withAgentChatOperationError(restored, "set_feedback", error) : restored;
|
|
1481
|
+
});
|
|
1256
1482
|
throw error;
|
|
1483
|
+
} finally {
|
|
1484
|
+
const mutation = mutations.current.get(runId);
|
|
1485
|
+
mutations.current.set(runId, { ...mutation, readRevision: mutation.readRevision + 1, pending: mutation.pending - 1 });
|
|
1486
|
+
if (mutation.pending === 1 && isCurrent()) await refreshMessages().catch(() => void 0);
|
|
1257
1487
|
}
|
|
1258
|
-
}, [clearOperationError, client, failOperation, update]);
|
|
1488
|
+
}, [clearOperationError, client, failOperation, update, ownOperation, refreshMessages]);
|
|
1489
|
+
return { setFeedback, captureRead };
|
|
1259
1490
|
}
|
|
1260
1491
|
|
|
1261
1492
|
// src/use-agent-chat-queue-actions.ts
|
|
1262
|
-
import * as
|
|
1493
|
+
import * as React6 from "react";
|
|
1263
1494
|
function useAgentChatQueueActions({
|
|
1264
1495
|
queue,
|
|
1265
1496
|
stateRef,
|
|
@@ -1267,21 +1498,23 @@ function useAgentChatQueueActions({
|
|
|
1267
1498
|
clearOperationError,
|
|
1268
1499
|
failOperation,
|
|
1269
1500
|
refreshMessages,
|
|
1270
|
-
attachRun
|
|
1501
|
+
attachRun,
|
|
1502
|
+
ownOperation
|
|
1271
1503
|
}) {
|
|
1272
|
-
const steer =
|
|
1504
|
+
const steer = React6.useCallback(async (text2) => {
|
|
1273
1505
|
const threadId = stateRef.current.activeThreadId;
|
|
1274
1506
|
if (!threadId || !text2.trim()) throw new Error("An active thread and text are required to steer.");
|
|
1507
|
+
const isCurrent = ownOperation();
|
|
1275
1508
|
clearOperationError();
|
|
1276
1509
|
try {
|
|
1277
1510
|
await queue.submit({ mode: "steer", input: text2.trim(), idempotencyKey: createId() });
|
|
1278
|
-
await refreshMessages(threadId);
|
|
1511
|
+
if (isCurrent()) await refreshMessages(threadId);
|
|
1279
1512
|
} catch (error) {
|
|
1280
|
-
failOperation("submit", error);
|
|
1513
|
+
if (isCurrent()) failOperation("submit", error);
|
|
1281
1514
|
throw error;
|
|
1282
1515
|
}
|
|
1283
|
-
}, [clearOperationError, createId, failOperation, queue.submit, refreshMessages, stateRef]);
|
|
1284
|
-
const editPendingMessage =
|
|
1516
|
+
}, [clearOperationError, createId, failOperation, queue.submit, refreshMessages, stateRef, ownOperation]);
|
|
1517
|
+
const editPendingMessage = React6.useCallback(async (message, input) => {
|
|
1285
1518
|
await queue.edit(message, {
|
|
1286
1519
|
text: input.text,
|
|
1287
1520
|
attachmentIds: input.attachmentIds ?? message.attachment_ids,
|
|
@@ -1289,14 +1522,15 @@ function useAgentChatQueueActions({
|
|
|
1289
1522
|
requestedModelId: input.requestedModelId
|
|
1290
1523
|
});
|
|
1291
1524
|
}, [queue.edit]);
|
|
1292
|
-
const steerPendingMessage =
|
|
1525
|
+
const steerPendingMessage = React6.useCallback(async (message) => {
|
|
1293
1526
|
await queue.edit(message, { text: message.text, attachmentIds: message.attachment_ids, mode: "steer" });
|
|
1294
1527
|
}, [queue.edit]);
|
|
1295
|
-
const resumeQueue =
|
|
1528
|
+
const resumeQueue = React6.useCallback(async () => {
|
|
1296
1529
|
if (!stateRef.current.activeThreadId) return;
|
|
1530
|
+
const isCurrent = ownOperation();
|
|
1297
1531
|
const result = await queue.resume();
|
|
1298
|
-
if (result?.run_id) void attachRun(result.run_id).catch(() => void 0);
|
|
1299
|
-
}, [attachRun, queue.resume, stateRef]);
|
|
1532
|
+
if (isCurrent() && result?.run_id && stateRef.current.activeRunId !== result.run_id) void attachRun(result.run_id).catch(() => void 0);
|
|
1533
|
+
}, [attachRun, queue.resume, stateRef, ownOperation]);
|
|
1300
1534
|
return { steer, editPendingMessage, steerPendingMessage, removePendingMessage: queue.remove, resumeQueue };
|
|
1301
1535
|
}
|
|
1302
1536
|
|
|
@@ -1307,9 +1541,6 @@ function resolveInitialActiveThreadId(options, storedThreadId) {
|
|
|
1307
1541
|
function shouldHydrateSelectedThread(selectedThreadId, activeThreadId, activeThreadIsHydrated) {
|
|
1308
1542
|
return Boolean(selectedThreadId) && (selectedThreadId !== activeThreadId || !activeThreadIsHydrated);
|
|
1309
1543
|
}
|
|
1310
|
-
function isCurrentThreadOpenRequest(input) {
|
|
1311
|
-
return !input.signalAborted && input.requestGeneration === input.currentGeneration && input.requestedThreadId === input.activeThreadId;
|
|
1312
|
-
}
|
|
1313
1544
|
|
|
1314
1545
|
// src/use-agent-chat.ts
|
|
1315
1546
|
function activeRunIdFromThread(thread) {
|
|
@@ -1322,7 +1553,7 @@ function useAgentChat(options = {}) {
|
|
|
1322
1553
|
const client = options.client || runtime.client;
|
|
1323
1554
|
const pageSize = options.pageSize ?? 30;
|
|
1324
1555
|
const threadPageSize = options.threadPageSize ?? 50;
|
|
1325
|
-
const [state, setState] =
|
|
1556
|
+
const [state, setState] = React7.useState(() => ({
|
|
1326
1557
|
...initialAgentChatState,
|
|
1327
1558
|
activeThreadId: resolveInitialActiveThreadId(
|
|
1328
1559
|
options,
|
|
@@ -1330,34 +1561,40 @@ function useAgentChat(options = {}) {
|
|
|
1330
1561
|
),
|
|
1331
1562
|
threads: runtime.storage?.loadThreads() ?? []
|
|
1332
1563
|
}));
|
|
1333
|
-
const [isLoadingHistory, setIsLoadingHistory] =
|
|
1334
|
-
const [isLoadingOlder, setIsLoadingOlder] =
|
|
1335
|
-
const [isSubmitting, setIsSubmitting] =
|
|
1336
|
-
const streamControllerRef =
|
|
1337
|
-
const threadOpenControllerRef =
|
|
1338
|
-
const threadOpenGenerationRef =
|
|
1339
|
-
const attachRunRef =
|
|
1340
|
-
const messageRefreshVersionRef =
|
|
1341
|
-
const promotedRunHintRef =
|
|
1342
|
-
const promotedAttachRef =
|
|
1343
|
-
const
|
|
1344
|
-
const
|
|
1345
|
-
const
|
|
1564
|
+
const [isLoadingHistory, setIsLoadingHistory] = React7.useState(false);
|
|
1565
|
+
const [isLoadingOlder, setIsLoadingOlder] = React7.useState(false);
|
|
1566
|
+
const [isSubmitting, setIsSubmitting] = React7.useState(false);
|
|
1567
|
+
const streamControllerRef = React7.useRef(null);
|
|
1568
|
+
const threadOpenControllerRef = React7.useRef(null);
|
|
1569
|
+
const threadOpenGenerationRef = React7.useRef(0);
|
|
1570
|
+
const attachRunRef = React7.useRef(async () => void 0);
|
|
1571
|
+
const messageRefreshVersionRef = React7.useRef(0);
|
|
1572
|
+
const promotedRunHintRef = React7.useRef(null);
|
|
1573
|
+
const promotedAttachRef = React7.useRef(null);
|
|
1574
|
+
const olderRequestRef = React7.useRef(null);
|
|
1575
|
+
const submissionGenerationRef = React7.useRef(0);
|
|
1576
|
+
const firstSubmissionRef = React7.useRef(null);
|
|
1577
|
+
const firstSubmissionAdmittedRef = React7.useRef(null);
|
|
1578
|
+
const stateRef = React7.useRef(state);
|
|
1346
1579
|
stateRef.current = state;
|
|
1347
|
-
const
|
|
1580
|
+
const ownOperation = React7.useCallback((options2) => captureChatOperation(() => ({
|
|
1581
|
+
generation: threadOpenGenerationRef.current,
|
|
1582
|
+
threadId: stateRef.current.activeThreadId,
|
|
1583
|
+
runId: stateRef.current.activeRunId,
|
|
1584
|
+
stream: streamControllerRef.current
|
|
1585
|
+
}), options2), []);
|
|
1586
|
+
const persist = React7.useCallback((next) => {
|
|
1348
1587
|
runtime.storage?.saveThreads(next.threads);
|
|
1349
1588
|
runtime.storage?.saveActiveThreadId?.(next.activeThreadId);
|
|
1350
1589
|
}, [runtime.storage]);
|
|
1351
|
-
const update =
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
return next;
|
|
1357
|
-
});
|
|
1590
|
+
const update = React7.useCallback((updater) => {
|
|
1591
|
+
const next = updater(stateRef.current);
|
|
1592
|
+
stateRef.current = next;
|
|
1593
|
+
persist(next);
|
|
1594
|
+
setState(next);
|
|
1358
1595
|
}, [persist]);
|
|
1359
|
-
const queueTransport =
|
|
1360
|
-
list: (threadId) => client.threads.messages.list({ threadId, limit: pageSize }),
|
|
1596
|
+
const queueTransport = React7.useMemo(() => ({
|
|
1597
|
+
list: (threadId, signal) => client.threads.messages.list({ threadId, limit: pageSize, signal }),
|
|
1361
1598
|
submit: (input) => client.threads.messages.submit(input),
|
|
1362
1599
|
edit: (message, input) => client.threads.messages.edit({
|
|
1363
1600
|
threadId: message.thread_id,
|
|
@@ -1374,40 +1611,46 @@ function useAgentChat(options = {}) {
|
|
|
1374
1611
|
},
|
|
1375
1612
|
resume: (threadId) => client.threads.resumeQueue({ threadId })
|
|
1376
1613
|
}), [client, pageSize]);
|
|
1377
|
-
const
|
|
1378
|
-
if (stateRef.current.activeThreadId !==
|
|
1614
|
+
const attachPromotion = React7.useCallback((runId, threadId) => {
|
|
1615
|
+
if (stateRef.current.activeThreadId !== threadId) return;
|
|
1379
1616
|
promotedRunHintRef.current = runId;
|
|
1617
|
+
if (stateRef.current.activeRunId === runId || promotedAttachRef.current === runId) return;
|
|
1618
|
+
promotedAttachRef.current = runId;
|
|
1619
|
+
const isCurrent = ownOperation();
|
|
1620
|
+
void attachRunRef.current(runId, 0).catch(() => {
|
|
1621
|
+
if (isCurrent() && promotedAttachRef.current === runId) promotedAttachRef.current = null;
|
|
1622
|
+
});
|
|
1623
|
+
}, [ownOperation]);
|
|
1624
|
+
const onQueuePromotion = React7.useCallback((message, runId) => {
|
|
1625
|
+
if (stateRef.current.activeThreadId !== message.thread_id) return;
|
|
1380
1626
|
const userMessage = threadTimelineToMessages([{ ...message, run_id: runId, status: "delivered" }])[0];
|
|
1381
1627
|
update((current) => current.activeThreadId === message.thread_id ? { ...current, messages: promoteThreadMessage(current.messages, userMessage, runId) } : current);
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
void attachRunRef.current(runId).catch(() => {
|
|
1385
|
-
promotedAttachRef.current = null;
|
|
1386
|
-
});
|
|
1387
|
-
}
|
|
1388
|
-
}, [update]);
|
|
1628
|
+
attachPromotion(runId, message.thread_id);
|
|
1629
|
+
}, [update, attachPromotion]);
|
|
1389
1630
|
const queue = useThreadMessageQueue({
|
|
1390
1631
|
threadId: state.activeThreadId,
|
|
1391
1632
|
transport: queueTransport,
|
|
1633
|
+
ownOperation,
|
|
1392
1634
|
onPromoted: onQueuePromotion,
|
|
1635
|
+
onUnknownPromotion: attachPromotion,
|
|
1393
1636
|
onOptimistic: (message, uploading) => update((current) => optimisticSubmissionState(current, message, uploading)),
|
|
1394
1637
|
onDiscard: (message) => update((current) => discardOptimisticSubmission(current, message))
|
|
1395
1638
|
});
|
|
1396
1639
|
const { pendingMessages, messageQueue } = queue;
|
|
1397
|
-
const eventBuffer =
|
|
1640
|
+
const eventBuffer = React7.useMemo(
|
|
1398
1641
|
() => createRuntimeEventFrameBuffer(
|
|
1399
1642
|
(events) => update((current) => applyRuntimeEvents(current, events)),
|
|
1400
1643
|
runtimeEventFrameScheduler()
|
|
1401
1644
|
),
|
|
1402
1645
|
[update]
|
|
1403
1646
|
);
|
|
1404
|
-
const clearOperationError =
|
|
1647
|
+
const clearOperationError = React7.useCallback((operation) => {
|
|
1405
1648
|
update((current) => clearAgentChatOperationError(current, operation));
|
|
1406
1649
|
}, [update]);
|
|
1407
|
-
const clearBackgroundError =
|
|
1650
|
+
const clearBackgroundError = React7.useCallback(() => {
|
|
1408
1651
|
update(clearAgentChatBackgroundError);
|
|
1409
1652
|
}, [update]);
|
|
1410
|
-
const failOperation =
|
|
1653
|
+
const failOperation = React7.useCallback((operation, cause) => {
|
|
1411
1654
|
update((current) => withAgentChatOperationError(current, operation, cause));
|
|
1412
1655
|
}, [update]);
|
|
1413
1656
|
const {
|
|
@@ -1418,60 +1661,67 @@ function useAgentChat(options = {}) {
|
|
|
1418
1661
|
refreshThreads,
|
|
1419
1662
|
loadMoreThreads
|
|
1420
1663
|
} = useAgentChatThreadList({ client, threadPageSize, stateRef, update, clearOperationError, failOperation });
|
|
1421
|
-
const invalidateThreadOpen =
|
|
1664
|
+
const invalidateThreadOpen = React7.useCallback(() => {
|
|
1422
1665
|
threadOpenGenerationRef.current += 1;
|
|
1423
1666
|
threadOpenControllerRef.current?.abort("Thread selection changed.");
|
|
1424
1667
|
threadOpenControllerRef.current = null;
|
|
1668
|
+
messageRefreshVersionRef.current += 1;
|
|
1669
|
+
promotedAttachRef.current = null;
|
|
1670
|
+
promotedRunHintRef.current = null;
|
|
1671
|
+
olderRequestRef.current = null;
|
|
1672
|
+
firstSubmissionRef.current = null;
|
|
1673
|
+
firstSubmissionAdmittedRef.current = null;
|
|
1674
|
+
setIsSubmitting(false);
|
|
1675
|
+
setIsLoadingOlder(false);
|
|
1425
1676
|
setIsLoadingHistory(false);
|
|
1426
1677
|
}, []);
|
|
1427
|
-
const detach =
|
|
1678
|
+
const detach = React7.useCallback(() => {
|
|
1428
1679
|
eventBuffer.flush();
|
|
1429
1680
|
streamControllerRef.current?.abort();
|
|
1430
1681
|
streamControllerRef.current = null;
|
|
1431
1682
|
update((current) => ({ ...current, streamingMessageId: null, runState: current.activeRunId ? "reconnecting" : "idle" }));
|
|
1432
1683
|
}, [eventBuffer, update]);
|
|
1433
|
-
const
|
|
1684
|
+
const refreshMessagesRef = React7.useRef(async () => void 0);
|
|
1685
|
+
const { setFeedback, captureRead: captureFeedbackRead } = useAgentChatFeedback({
|
|
1686
|
+
client,
|
|
1687
|
+
update,
|
|
1688
|
+
clearOperationError,
|
|
1689
|
+
failOperation,
|
|
1690
|
+
ownOperation,
|
|
1691
|
+
refreshMessages: React7.useCallback(() => refreshMessagesRef.current(), [])
|
|
1692
|
+
});
|
|
1693
|
+
const refreshMessages = React7.useCallback(async (threadId, promotedRunId, signal) => {
|
|
1434
1694
|
const target = threadId || stateRef.current.activeThreadId;
|
|
1435
1695
|
if (!target) return;
|
|
1696
|
+
const isCurrent = ownOperation();
|
|
1697
|
+
const reconcileFeedback = captureFeedbackRead();
|
|
1436
1698
|
const version = ++messageRefreshVersionRef.current;
|
|
1437
|
-
const page = await client.threads.messages.list({ threadId: target, limit: pageSize });
|
|
1438
|
-
if (stateRef.current.activeThreadId !== target || version !== messageRefreshVersionRef.current) return;
|
|
1699
|
+
const page = await client.threads.messages.list({ threadId: target, limit: pageSize, ...signal ? { signal } : {} });
|
|
1700
|
+
if (!isCurrent() || stateRef.current.activeThreadId !== target || version !== messageRefreshVersionRef.current) return;
|
|
1439
1701
|
queue.hydrate(page, target);
|
|
1440
1702
|
const promoted = promotedRunId || promotedRunHintRef.current;
|
|
1441
|
-
if (promoted && page.items.some((message) => message.
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
return;
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
const additions = delivered.filter((item) => !current.messages.some((existing) => existing.id === item.id));
|
|
1457
|
-
return additions.length ? { ...current, messages: [...current.messages, ...additions] } : current;
|
|
1458
|
-
});
|
|
1459
|
-
}
|
|
1460
|
-
if (!stateRef.current.activeRunId && page.items.length) {
|
|
1461
|
-
update((current) => current.activeThreadId === target && !current.activeRunId ? (() => {
|
|
1462
|
-
const messages = reconcileThreadTimeline(current.messages, threadTimelineToMessages(page.items), null, null);
|
|
1463
|
-
return { ...current, messages, threads: current.threads.map((thread) => thread.id === target ? { ...thread, messages } : thread) };
|
|
1464
|
-
})() : current);
|
|
1465
|
-
}
|
|
1466
|
-
}, [client, pageSize, queue.hydrate, update]);
|
|
1467
|
-
React6.useEffect(() => () => {
|
|
1703
|
+
if (promoted && page.items.some((message) => message.run_id === promoted)) promotedRunHintRef.current = null;
|
|
1704
|
+
update((current) => {
|
|
1705
|
+
if (!isCurrent() || current.activeThreadId !== target) return current;
|
|
1706
|
+
const messages = reconcileThreadTimeline(
|
|
1707
|
+
current.messages,
|
|
1708
|
+
reconcileFeedback(threadTimelineToMessages(page.items), current.messages),
|
|
1709
|
+
current.activeRunId,
|
|
1710
|
+
current.streamingMessageId
|
|
1711
|
+
);
|
|
1712
|
+
return { ...current, messages, threads: current.threads.map((thread) => thread.id === target ? { ...thread, messages } : thread) };
|
|
1713
|
+
});
|
|
1714
|
+
}, [client, ownOperation, pageSize, queue.hydrate, update, captureFeedbackRead]);
|
|
1715
|
+
refreshMessagesRef.current = refreshMessages;
|
|
1716
|
+
React7.useEffect(() => () => {
|
|
1717
|
+
threadOpenGenerationRef.current += 1;
|
|
1468
1718
|
eventBuffer.dispose();
|
|
1469
1719
|
streamControllerRef.current?.abort();
|
|
1470
1720
|
threadOpenControllerRef.current?.abort();
|
|
1471
1721
|
}, [eventBuffer]);
|
|
1472
|
-
const openThread =
|
|
1722
|
+
const openThread = React7.useCallback(async (threadId) => {
|
|
1473
1723
|
const previousSelection = captureAgentChatThreadSelection(stateRef.current);
|
|
1474
|
-
|
|
1724
|
+
invalidateThreadOpen();
|
|
1475
1725
|
const requestController = runtime.createAbortController();
|
|
1476
1726
|
threadOpenControllerRef.current = requestController;
|
|
1477
1727
|
const requestGeneration = threadOpenGenerationRef.current + 1;
|
|
@@ -1491,6 +1741,8 @@ function useAgentChat(options = {}) {
|
|
|
1491
1741
|
runState: "idle",
|
|
1492
1742
|
streamingMessageId: null
|
|
1493
1743
|
}));
|
|
1744
|
+
const isCurrent = ownOperation();
|
|
1745
|
+
const reconcileFeedback = captureFeedbackRead();
|
|
1494
1746
|
setIsLoadingHistory(true);
|
|
1495
1747
|
try {
|
|
1496
1748
|
const detail = await client.threads.get({
|
|
@@ -1498,39 +1750,21 @@ function useAgentChat(options = {}) {
|
|
|
1498
1750
|
limit: pageSize,
|
|
1499
1751
|
signal: requestController.signal
|
|
1500
1752
|
});
|
|
1501
|
-
if (!
|
|
1502
|
-
activeThreadId: stateRef.current.activeThreadId,
|
|
1503
|
-
currentGeneration: threadOpenGenerationRef.current,
|
|
1504
|
-
requestGeneration,
|
|
1505
|
-
requestedThreadId: threadId,
|
|
1506
|
-
signalAborted: requestController.signal.aborted
|
|
1507
|
-
})) return;
|
|
1753
|
+
if (!isCurrent()) return;
|
|
1508
1754
|
const timeline = await client.threads.messages.list({ threadId, limit: pageSize, signal: requestController.signal });
|
|
1509
|
-
if (!
|
|
1510
|
-
activeThreadId: stateRef.current.activeThreadId,
|
|
1511
|
-
currentGeneration: threadOpenGenerationRef.current,
|
|
1512
|
-
requestGeneration,
|
|
1513
|
-
requestedThreadId: threadId,
|
|
1514
|
-
signalAborted: requestController.signal.aborted
|
|
1515
|
-
})) return;
|
|
1755
|
+
if (!isCurrent()) return;
|
|
1516
1756
|
queue.hydrate(timeline, threadId);
|
|
1517
|
-
const messages =
|
|
1757
|
+
const messages = reconcileFeedback(threadTimelineToMessages(timeline.items), stateRef.current.messages);
|
|
1518
1758
|
const context = latestThreadContext(detail);
|
|
1519
1759
|
const stored = {
|
|
1520
1760
|
...detail,
|
|
1521
1761
|
messages,
|
|
1522
1762
|
isHydrated: true,
|
|
1523
|
-
hasOlderTurns: Boolean(
|
|
1524
|
-
nextBeforeTurnIndex:
|
|
1763
|
+
hasOlderTurns: Boolean(timeline.paging.has_more),
|
|
1764
|
+
nextBeforeTurnIndex: timeline.paging.next_before_turn_index,
|
|
1525
1765
|
...context
|
|
1526
1766
|
};
|
|
1527
|
-
update((current) =>
|
|
1528
|
-
activeThreadId: current.activeThreadId,
|
|
1529
|
-
currentGeneration: threadOpenGenerationRef.current,
|
|
1530
|
-
requestGeneration,
|
|
1531
|
-
requestedThreadId: threadId,
|
|
1532
|
-
signalAborted: requestController.signal.aborted
|
|
1533
|
-
}) ? {
|
|
1767
|
+
update((current) => isCurrent() ? {
|
|
1534
1768
|
...current,
|
|
1535
1769
|
activeThreadId: threadId,
|
|
1536
1770
|
messages,
|
|
@@ -1540,13 +1774,7 @@ function useAgentChat(options = {}) {
|
|
|
1540
1774
|
threads: current.threads.some((thread) => thread.id === threadId) ? current.threads.map((thread) => thread.id === threadId ? stored : thread) : [stored, ...current.threads]
|
|
1541
1775
|
} : current);
|
|
1542
1776
|
const activeRunId = activeRunIdFromThread(stored);
|
|
1543
|
-
if (activeRunId &&
|
|
1544
|
-
activeThreadId: stateRef.current.activeThreadId,
|
|
1545
|
-
currentGeneration: threadOpenGenerationRef.current,
|
|
1546
|
-
requestGeneration,
|
|
1547
|
-
requestedThreadId: threadId,
|
|
1548
|
-
signalAborted: requestController.signal.aborted
|
|
1549
|
-
})) await attachRunRef.current(activeRunId).catch(() => void 0);
|
|
1777
|
+
if (activeRunId && isCurrent()) void attachRunRef.current(activeRunId).catch(() => void 0);
|
|
1550
1778
|
} catch (error) {
|
|
1551
1779
|
if (!requestController.signal.aborted && !isAbortLike(error) && requestGeneration === threadOpenGenerationRef.current) {
|
|
1552
1780
|
failOperation("open_thread", error);
|
|
@@ -1562,28 +1790,34 @@ function useAgentChat(options = {}) {
|
|
|
1562
1790
|
setIsLoadingHistory(false);
|
|
1563
1791
|
}
|
|
1564
1792
|
}
|
|
1565
|
-
}, [clearOperationError, client, detach, failOperation, pageSize, queue.hydrate, runtime, update]);
|
|
1566
|
-
const consumeEvent =
|
|
1793
|
+
}, [clearOperationError, client, detach, failOperation, pageSize, queue.hydrate, runtime, update, invalidateThreadOpen, ownOperation, captureFeedbackRead]);
|
|
1794
|
+
const consumeEvent = React7.useCallback((event) => {
|
|
1567
1795
|
const eventThreadId = typeof event.payload?.thread_id === "string" ? event.payload.thread_id.trim() : "";
|
|
1568
1796
|
if (eventThreadId && !stateRef.current.activeThreadId) {
|
|
1569
1797
|
update((current) => adoptStreamingThread(current, eventThreadId));
|
|
1798
|
+
setIsSubmitting(false);
|
|
1570
1799
|
firstSubmissionAdmittedRef.current?.();
|
|
1571
1800
|
}
|
|
1572
1801
|
eventBuffer.push(event);
|
|
1573
1802
|
}, [eventBuffer, update]);
|
|
1574
|
-
const attachRun =
|
|
1803
|
+
const attachRun = React7.useCallback(async (runId, cursor) => {
|
|
1804
|
+
eventBuffer.flush();
|
|
1575
1805
|
streamControllerRef.current?.abort();
|
|
1576
1806
|
const controller = runtime.createAbortController();
|
|
1577
1807
|
streamControllerRef.current = controller;
|
|
1578
|
-
|
|
1808
|
+
const isCurrent = ownOperation({ stream: true });
|
|
1809
|
+
clearOperationError("attach_run");
|
|
1579
1810
|
update((current) => ({ ...current, activeRunId: runId, runState: "reconnecting", error: null }));
|
|
1580
1811
|
try {
|
|
1581
|
-
const result = await client.runs.attach({ runId, cursor, signal: controller.signal },
|
|
1812
|
+
const result = await client.runs.attach({ runId, cursor, leaseSeconds: 60, signal: controller.signal }, (event) => {
|
|
1813
|
+
if (isCurrent()) consumeEvent(event);
|
|
1814
|
+
});
|
|
1815
|
+
if (!isCurrent()) return;
|
|
1582
1816
|
eventBuffer.flush();
|
|
1583
1817
|
update((current) => ({ ...current, activeRunId: result.detached ? runId : current.activeRunId, cursor: result.cursor ?? current.cursor, runState: result.detached ? "reconnecting" : current.runState }));
|
|
1584
1818
|
if (!result.detached && result.threadId) await refreshMessages(result.threadId);
|
|
1585
1819
|
} catch (error) {
|
|
1586
|
-
if (!isAbortLike(error)) {
|
|
1820
|
+
if (isCurrent() && !isAbortLike(error)) {
|
|
1587
1821
|
update((current) => ({ ...current, runState: "failed", streamingMessageId: null }));
|
|
1588
1822
|
failOperation("attach_run", error);
|
|
1589
1823
|
throw error;
|
|
@@ -1591,15 +1825,17 @@ function useAgentChat(options = {}) {
|
|
|
1591
1825
|
} finally {
|
|
1592
1826
|
if (streamControllerRef.current === controller) streamControllerRef.current = null;
|
|
1593
1827
|
}
|
|
1594
|
-
}, [clearOperationError, client, consumeEvent, eventBuffer, failOperation, refreshMessages, runtime, update]);
|
|
1828
|
+
}, [clearOperationError, client, consumeEvent, eventBuffer, failOperation, refreshMessages, runtime, update, ownOperation]);
|
|
1595
1829
|
attachRunRef.current = attachRun;
|
|
1596
|
-
const submit =
|
|
1830
|
+
const submit = React7.useCallback(async (input) => {
|
|
1597
1831
|
if (!input.text.trim() && !input.attachmentIds?.length && !input.uploads?.length) return;
|
|
1598
1832
|
if (firstSubmissionRef.current && !stateRef.current.activeThreadId) {
|
|
1599
1833
|
await firstSubmissionRef.current;
|
|
1600
1834
|
}
|
|
1601
1835
|
const existingThreadId = stateRef.current.activeThreadId;
|
|
1602
1836
|
if (existingThreadId) {
|
|
1837
|
+
const isCurrent = ownOperation();
|
|
1838
|
+
const submissionGeneration = ++submissionGenerationRef.current;
|
|
1603
1839
|
clearOperationError();
|
|
1604
1840
|
setIsSubmitting(true);
|
|
1605
1841
|
try {
|
|
@@ -1644,21 +1880,23 @@ function useAgentChat(options = {}) {
|
|
|
1644
1880
|
}
|
|
1645
1881
|
});
|
|
1646
1882
|
} catch (error) {
|
|
1647
|
-
failOperation("submit", error);
|
|
1883
|
+
if (isCurrent()) failOperation("submit", error);
|
|
1648
1884
|
throw error;
|
|
1649
1885
|
} finally {
|
|
1650
|
-
setIsSubmitting(false);
|
|
1886
|
+
if (isCurrent() && submissionGeneration === submissionGenerationRef.current) setIsSubmitting(false);
|
|
1651
1887
|
}
|
|
1652
1888
|
return;
|
|
1653
1889
|
}
|
|
1890
|
+
invalidateThreadOpen();
|
|
1654
1891
|
const admission = new Promise((resolve) => {
|
|
1655
1892
|
firstSubmissionAdmittedRef.current = resolve;
|
|
1656
1893
|
});
|
|
1657
1894
|
const stream = (async () => {
|
|
1658
|
-
invalidateThreadOpen();
|
|
1659
1895
|
detach();
|
|
1660
1896
|
const controller = runtime.createAbortController();
|
|
1661
1897
|
streamControllerRef.current = controller;
|
|
1898
|
+
const isCurrent = ownOperation({ stream: true, adoptThread: true });
|
|
1899
|
+
const submissionGeneration = ++submissionGenerationRef.current;
|
|
1662
1900
|
const ticket = queue.beginSubmission({
|
|
1663
1901
|
input: input.text.trim(),
|
|
1664
1902
|
clientSubmissionId: runtime.createId(),
|
|
@@ -1689,6 +1927,7 @@ function useAgentChat(options = {}) {
|
|
|
1689
1927
|
idempotencyKey: runtime.createId(),
|
|
1690
1928
|
signal: controller.signal
|
|
1691
1929
|
}) : [];
|
|
1930
|
+
if (!isCurrent()) return;
|
|
1692
1931
|
if (hasPendingUploads && uploaded.length !== input.uploads?.length) {
|
|
1693
1932
|
throw new Error("Attachment upload returned an incomplete batch");
|
|
1694
1933
|
}
|
|
@@ -1701,7 +1940,20 @@ function useAgentChat(options = {}) {
|
|
|
1701
1940
|
runState: "streaming"
|
|
1702
1941
|
}));
|
|
1703
1942
|
}
|
|
1704
|
-
const result = await client.chat.stream({
|
|
1943
|
+
const result = await client.chat.stream({
|
|
1944
|
+
leaseSeconds: 60,
|
|
1945
|
+
clientSubmissionId: ticket.client_submission_id,
|
|
1946
|
+
input: input.text.trim(),
|
|
1947
|
+
threadId: stateRef.current.activeThreadId || void 0,
|
|
1948
|
+
attachmentIds,
|
|
1949
|
+
requestedModelId: input.requestedModelId,
|
|
1950
|
+
requestedAgentId: input.requestedAgentId,
|
|
1951
|
+
idempotencyKey,
|
|
1952
|
+
signal: controller.signal
|
|
1953
|
+
}, (event) => {
|
|
1954
|
+
if (isCurrent()) consumeEvent(event);
|
|
1955
|
+
});
|
|
1956
|
+
if (!isCurrent()) return;
|
|
1705
1957
|
eventBuffer.flush();
|
|
1706
1958
|
update((current) => {
|
|
1707
1959
|
const threadId = result.threadId || current.activeThreadId;
|
|
@@ -1717,6 +1969,7 @@ function useAgentChat(options = {}) {
|
|
|
1717
1969
|
if (result.threadId) await refreshThreads().catch(() => void 0);
|
|
1718
1970
|
if (result.threadId && !result.detached) await refreshMessages(result.threadId);
|
|
1719
1971
|
} catch (error) {
|
|
1972
|
+
if (!isCurrent()) return;
|
|
1720
1973
|
const admitted = Boolean(stateRef.current.messages.find((message) => message.id === userId)?.runId);
|
|
1721
1974
|
if (!admitted) queue.discardSubmission(ticket);
|
|
1722
1975
|
if (!isAbortLike(error)) {
|
|
@@ -1729,7 +1982,7 @@ function useAgentChat(options = {}) {
|
|
|
1729
1982
|
throw error;
|
|
1730
1983
|
}
|
|
1731
1984
|
} finally {
|
|
1732
|
-
setIsSubmitting(false);
|
|
1985
|
+
if (isCurrent() && submissionGeneration === submissionGenerationRef.current) setIsSubmitting(false);
|
|
1733
1986
|
if (streamControllerRef.current === controller) streamControllerRef.current = null;
|
|
1734
1987
|
}
|
|
1735
1988
|
})();
|
|
@@ -1738,10 +1991,12 @@ function useAgentChat(options = {}) {
|
|
|
1738
1991
|
try {
|
|
1739
1992
|
await accepted;
|
|
1740
1993
|
} finally {
|
|
1741
|
-
if (firstSubmissionRef.current === accepted)
|
|
1742
|
-
|
|
1994
|
+
if (firstSubmissionRef.current === accepted) {
|
|
1995
|
+
firstSubmissionRef.current = null;
|
|
1996
|
+
firstSubmissionAdmittedRef.current = null;
|
|
1997
|
+
}
|
|
1743
1998
|
}
|
|
1744
|
-
}, [clearOperationError, client, consumeEvent, detach, eventBuffer, failOperation, invalidateThreadOpen, queue.submit, refreshMessages, refreshThreads, runtime, update]);
|
|
1999
|
+
}, [clearOperationError, client, consumeEvent, detach, eventBuffer, failOperation, invalidateThreadOpen, queue.submit, refreshMessages, refreshThreads, runtime, update, ownOperation]);
|
|
1745
2000
|
const { steer, editPendingMessage, steerPendingMessage, removePendingMessage, resumeQueue } = useAgentChatQueueActions({
|
|
1746
2001
|
queue,
|
|
1747
2002
|
stateRef,
|
|
@@ -1749,42 +2004,38 @@ function useAgentChat(options = {}) {
|
|
|
1749
2004
|
clearOperationError,
|
|
1750
2005
|
failOperation,
|
|
1751
2006
|
refreshMessages,
|
|
1752
|
-
attachRun
|
|
2007
|
+
attachRun,
|
|
2008
|
+
ownOperation
|
|
1753
2009
|
});
|
|
1754
|
-
const startNewThread =
|
|
2010
|
+
const startNewThread = React7.useCallback(() => {
|
|
1755
2011
|
invalidateThreadOpen();
|
|
1756
2012
|
detach();
|
|
1757
2013
|
update((current) => ({ ...initialAgentChatState, threads: current.threads }));
|
|
1758
2014
|
}, [detach, invalidateThreadOpen, update]);
|
|
1759
|
-
const deleteThread =
|
|
1760
|
-
|
|
2015
|
+
const deleteThread = React7.useCallback(async (threadId) => {
|
|
2016
|
+
const isCurrent = ownOperation();
|
|
1761
2017
|
clearOperationError();
|
|
1762
2018
|
try {
|
|
1763
2019
|
await client.threads.delete({ threadId, idempotencyKey: runtime.createId() });
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
activeThreadId: current.activeThreadId === threadId ? null : current.activeThreadId,
|
|
1767
|
-
messages: current.activeThreadId === threadId ? [] : current.messages,
|
|
1768
|
-
contextWindow: current.activeThreadId === threadId ? null : current.contextWindow,
|
|
1769
|
-
contextCompression: current.activeThreadId === threadId ? null : current.contextCompression,
|
|
1770
|
-
contextCompressionRevision: current.activeThreadId === threadId ? 0 : current.contextCompressionRevision,
|
|
1771
|
-
threads: current.threads.filter((thread) => thread.id !== threadId)
|
|
1772
|
-
}));
|
|
2020
|
+
if (stateRef.current.activeThreadId === threadId) startNewThread();
|
|
2021
|
+
update((current) => ({ ...current, threads: current.threads.filter((thread) => thread.id !== threadId) }));
|
|
1773
2022
|
} catch (error) {
|
|
1774
2023
|
if (!isAbortLike(error)) {
|
|
1775
|
-
failOperation("delete_thread", error);
|
|
2024
|
+
if (isCurrent()) failOperation("delete_thread", error);
|
|
1776
2025
|
throw error;
|
|
1777
2026
|
}
|
|
1778
2027
|
}
|
|
1779
|
-
}, [clearOperationError, client, failOperation,
|
|
1780
|
-
const cancelRun =
|
|
2028
|
+
}, [clearOperationError, client, failOperation, ownOperation, runtime, startNewThread, update]);
|
|
2029
|
+
const cancelRun = React7.useCallback(async () => {
|
|
1781
2030
|
const runId = stateRef.current.activeRunId;
|
|
1782
2031
|
if (!runId || stateRef.current.runState === "cancelling") return;
|
|
2032
|
+
const isCurrent = ownOperation({ run: true, stream: true });
|
|
1783
2033
|
const previousRunState = stateRef.current.runState;
|
|
1784
2034
|
clearOperationError();
|
|
1785
2035
|
update((current) => current.activeRunId === runId ? { ...current, runState: "cancelling" } : current);
|
|
1786
2036
|
try {
|
|
1787
2037
|
const result = await client.runs.cancel({ runId, idempotencyKey: runtime.createId() });
|
|
2038
|
+
if (!isCurrent()) return;
|
|
1788
2039
|
if (result.status === "cancelled") {
|
|
1789
2040
|
detach();
|
|
1790
2041
|
update((current) => ({
|
|
@@ -1809,14 +2060,15 @@ function useAgentChat(options = {}) {
|
|
|
1809
2060
|
}));
|
|
1810
2061
|
}
|
|
1811
2062
|
} catch (error) {
|
|
1812
|
-
if (!isAbortLike(error)) {
|
|
2063
|
+
if (isCurrent() && !isAbortLike(error)) {
|
|
1813
2064
|
update((current) => current.activeRunId === runId ? { ...current, runState: previousRunState } : current);
|
|
1814
2065
|
failOperation("cancel_run", error);
|
|
1815
2066
|
throw error;
|
|
1816
2067
|
}
|
|
1817
2068
|
}
|
|
1818
|
-
}, [clearOperationError, client, detach, failOperation, runtime, update]);
|
|
1819
|
-
const resumeHitl =
|
|
2069
|
+
}, [clearOperationError, client, detach, failOperation, runtime, update, ownOperation]);
|
|
2070
|
+
const resumeHitl = React7.useCallback(async (part, response) => {
|
|
2071
|
+
const isCurrent = ownOperation({ run: true, stream: true });
|
|
1820
2072
|
const runId = stateRef.current.activeRunId || stateRef.current.messages.find((message) => message.parts.includes(part))?.runId;
|
|
1821
2073
|
clearOperationError();
|
|
1822
2074
|
if (!runId) {
|
|
@@ -1831,32 +2083,52 @@ function useAgentChat(options = {}) {
|
|
|
1831
2083
|
...response,
|
|
1832
2084
|
idempotencyKey: runtime.createId()
|
|
1833
2085
|
});
|
|
1834
|
-
await attachRun(result.run_id || runId);
|
|
2086
|
+
if (isCurrent()) await attachRun(result.run_id || runId);
|
|
1835
2087
|
} catch (error) {
|
|
1836
|
-
if (!isAbortLike(error)) failOperation("resume_hitl", error);
|
|
2088
|
+
if (isCurrent() && !isAbortLike(error)) failOperation("resume_hitl", error);
|
|
1837
2089
|
throw error;
|
|
1838
2090
|
}
|
|
1839
|
-
}, [attachRun, clearOperationError, client, failOperation, runtime]);
|
|
1840
|
-
const
|
|
1841
|
-
const loadOlder = React6.useCallback(async () => {
|
|
2091
|
+
}, [attachRun, clearOperationError, client, failOperation, runtime, ownOperation]);
|
|
2092
|
+
const loadOlder = React7.useCallback(() => {
|
|
1842
2093
|
const thread = stateRef.current.threads.find((item) => item.id === stateRef.current.activeThreadId);
|
|
1843
|
-
if (
|
|
2094
|
+
if (thread?.nextBeforeTurnIndex == null) return Promise.resolve();
|
|
2095
|
+
const cursor = thread.nextBeforeTurnIndex;
|
|
2096
|
+
const reconcileFeedback = captureFeedbackRead();
|
|
2097
|
+
const key = `${threadOpenGenerationRef.current}:${thread.id}:${cursor}`;
|
|
2098
|
+
if (olderRequestRef.current?.key === key) return olderRequestRef.current.promise;
|
|
2099
|
+
const isCurrent = ownOperation();
|
|
1844
2100
|
clearOperationError();
|
|
1845
2101
|
setIsLoadingOlder(true);
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
2102
|
+
const promise = (async () => {
|
|
2103
|
+
try {
|
|
2104
|
+
const page = await client.threads.messages.list({ threadId: thread.id, limit: pageSize, before: cursor });
|
|
2105
|
+
if (!isCurrent()) return;
|
|
2106
|
+
const older = reconcileFeedback(threadTimelineToMessages(page.items), stateRef.current.messages);
|
|
2107
|
+
update((current) => ({
|
|
2108
|
+
...current,
|
|
2109
|
+
messages: prependThreadTimeline(current.messages, older),
|
|
2110
|
+
threads: current.threads.map((item) => item.id === thread.id ? {
|
|
2111
|
+
...item,
|
|
2112
|
+
hasOlderTurns: page.paging.has_more,
|
|
2113
|
+
nextBeforeTurnIndex: page.paging.next_before_turn_index
|
|
2114
|
+
} : item)
|
|
2115
|
+
}));
|
|
2116
|
+
} catch (error) {
|
|
2117
|
+
if (isCurrent() && !isAbortLike(error)) {
|
|
2118
|
+
failOperation("load_older_turns", error);
|
|
2119
|
+
throw error;
|
|
2120
|
+
}
|
|
2121
|
+
} finally {
|
|
2122
|
+
if (olderRequestRef.current?.key === key) {
|
|
2123
|
+
olderRequestRef.current = null;
|
|
2124
|
+
setIsLoadingOlder(false);
|
|
2125
|
+
}
|
|
1854
2126
|
}
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
}, [clearOperationError, client, failOperation, pageSize, update]);
|
|
1859
|
-
const regenerate =
|
|
2127
|
+
})();
|
|
2128
|
+
olderRequestRef.current = { key, promise };
|
|
2129
|
+
return promise;
|
|
2130
|
+
}, [clearOperationError, client, failOperation, ownOperation, pageSize, update, captureFeedbackRead]);
|
|
2131
|
+
const regenerate = React7.useCallback(async (message) => {
|
|
1860
2132
|
const index = stateRef.current.messages.findIndex((item) => item.id === message.id);
|
|
1861
2133
|
const user = stateRef.current.messages.slice(0, index).reverse().find((item) => item.role === "user");
|
|
1862
2134
|
if (user) await submit({
|
|
@@ -1865,51 +2137,68 @@ function useAgentChat(options = {}) {
|
|
|
1865
2137
|
attachments: user.attachments
|
|
1866
2138
|
});
|
|
1867
2139
|
}, [submit]);
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
promotedAttachRef.current = promotedRunId;
|
|
1898
|
-
await attachRunRef.current(promotedRunId);
|
|
1899
|
-
}
|
|
1900
|
-
})().catch(() => void 0);
|
|
1901
|
-
}
|
|
1902
|
-
},
|
|
1903
|
-
onError(error) {
|
|
1904
|
-
update((current) => withAgentChatBackgroundError(current, error));
|
|
1905
|
-
},
|
|
1906
|
-
onRecovered() {
|
|
1907
|
-
clearBackgroundError();
|
|
2140
|
+
useChatObservation({
|
|
2141
|
+
state: {
|
|
2142
|
+
threadId: state.activeThreadId,
|
|
2143
|
+
running: Boolean(state.activeRunId) && !["idle", "completed", "failed", "cancelled"].includes(state.runState),
|
|
2144
|
+
waiting: ["paused", "waiting", "interrupted"].includes(state.runState),
|
|
2145
|
+
pending: pendingMessages.length > 0,
|
|
2146
|
+
paused: Boolean(messageQueue?.paused),
|
|
2147
|
+
degraded: state.runState === "reconnecting",
|
|
2148
|
+
mode: options.observation
|
|
2149
|
+
},
|
|
2150
|
+
visibility: runtime.visibility,
|
|
2151
|
+
createAbortController: runtime.createAbortController,
|
|
2152
|
+
events: (input, handler) => client.threads.events(input, handler),
|
|
2153
|
+
async refresh(signal) {
|
|
2154
|
+
const target = stateRef.current.activeThreadId;
|
|
2155
|
+
if (!target) return;
|
|
2156
|
+
const isCurrent = ownOperation();
|
|
2157
|
+
const detail = await client.threads.get({ threadId: target, limit: 1, signal });
|
|
2158
|
+
if (!isCurrent() || signal.aborted) return;
|
|
2159
|
+
const summary = summaryToStored(detail);
|
|
2160
|
+
update((current) => ({ ...current, threads: applyThreadSummaryEvent(current.threads, {
|
|
2161
|
+
schema_version: "agents24.thread_summary_event.v1",
|
|
2162
|
+
event: "thread.summary.updated",
|
|
2163
|
+
cursor: 0,
|
|
2164
|
+
thread: detail
|
|
2165
|
+
}) }));
|
|
2166
|
+
const runId = activeRunIdFromThread(summary);
|
|
2167
|
+
if (runId && !streamControllerRef.current) {
|
|
2168
|
+
void attachRunRef.current(runId, runId === stateRef.current.activeRunId ? stateRef.current.cursor ?? void 0 : 0).catch(() => void 0);
|
|
1908
2169
|
}
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
2170
|
+
await refreshMessages(target, runId || void 0, signal);
|
|
2171
|
+
},
|
|
2172
|
+
onEvent(event) {
|
|
2173
|
+
if (event.event === "thread.deleted" && event.thread_id === stateRef.current.activeThreadId) startNewThread();
|
|
2174
|
+
threadEventsDuringRefreshRef.current?.push(event);
|
|
2175
|
+
update((current) => ({
|
|
2176
|
+
...clearAgentChatBackgroundError(current),
|
|
2177
|
+
threads: applyThreadSummaryEvent(current.threads, event)
|
|
2178
|
+
}));
|
|
2179
|
+
if (event.event === "thread.message.updated" && event.thread_id === stateRef.current.activeThreadId) {
|
|
2180
|
+
queue.acceptNotice({
|
|
2181
|
+
messageId: event.message_id,
|
|
2182
|
+
status: event.status,
|
|
2183
|
+
runId: event.run_id,
|
|
2184
|
+
cursor: event.cursor,
|
|
2185
|
+
clientSubmissionId: event.client_submission_id
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
},
|
|
2189
|
+
onError(error) {
|
|
2190
|
+
if (!isAbortLike(error)) update((current) => withAgentChatBackgroundError(current, error));
|
|
2191
|
+
},
|
|
2192
|
+
onRecovered: clearBackgroundError,
|
|
2193
|
+
onDetach: detach,
|
|
2194
|
+
onResume() {
|
|
2195
|
+
const current = stateRef.current;
|
|
2196
|
+
if (current.activeRunId && !streamControllerRef.current && !["paused", "waiting", "interrupted", "completed", "idle"].includes(current.runState)) {
|
|
2197
|
+
void attachRunRef.current(current.activeRunId, current.cursor ?? void 0).catch(() => void 0);
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
});
|
|
2201
|
+
React7.useEffect(() => {
|
|
1913
2202
|
const threadId = options.activeThreadId;
|
|
1914
2203
|
const activeThread2 = stateRef.current.threads.find((thread) => thread.id === stateRef.current.activeThreadId);
|
|
1915
2204
|
if (shouldHydrateSelectedThread(threadId, stateRef.current.activeThreadId, Boolean(activeThread2?.isHydrated))) {
|
|
@@ -1956,15 +2245,15 @@ function useAgentChat(options = {}) {
|
|
|
1956
2245
|
}
|
|
1957
2246
|
|
|
1958
2247
|
// src/use-agent-resources.ts
|
|
1959
|
-
import * as
|
|
2248
|
+
import * as React8 from "react";
|
|
1960
2249
|
function useAgentThreads(options = {}) {
|
|
1961
2250
|
const { client, createAbortController } = useAgents24Runtime();
|
|
1962
|
-
const [threads, setThreads] =
|
|
1963
|
-
const [total, setTotal] =
|
|
1964
|
-
const [isLoading, setIsLoading] =
|
|
1965
|
-
const [error, setError] =
|
|
1966
|
-
const controllerRef =
|
|
1967
|
-
const load =
|
|
2251
|
+
const [threads, setThreads] = React8.useState([]);
|
|
2252
|
+
const [total, setTotal] = React8.useState();
|
|
2253
|
+
const [isLoading, setIsLoading] = React8.useState(true);
|
|
2254
|
+
const [error, setError] = React8.useState(null);
|
|
2255
|
+
const controllerRef = React8.useRef(null);
|
|
2256
|
+
const load = React8.useCallback(async () => {
|
|
1968
2257
|
controllerRef.current?.abort();
|
|
1969
2258
|
const controller = createAbortController();
|
|
1970
2259
|
controllerRef.current = controller;
|
|
@@ -1983,7 +2272,7 @@ function useAgentThreads(options = {}) {
|
|
|
1983
2272
|
}
|
|
1984
2273
|
}
|
|
1985
2274
|
}, [client, createAbortController, options.limit, options.skip]);
|
|
1986
|
-
|
|
2275
|
+
React8.useEffect(() => {
|
|
1987
2276
|
void load();
|
|
1988
2277
|
return () => controllerRef.current?.abort();
|
|
1989
2278
|
}, [load]);
|
|
@@ -1991,11 +2280,11 @@ function useAgentThreads(options = {}) {
|
|
|
1991
2280
|
}
|
|
1992
2281
|
function useAgentThread(threadId, options = {}) {
|
|
1993
2282
|
const { client, createAbortController } = useAgents24Runtime();
|
|
1994
|
-
const [thread, setThread] =
|
|
1995
|
-
const [isLoading, setIsLoading] =
|
|
1996
|
-
const [error, setError] =
|
|
1997
|
-
const controllerRef =
|
|
1998
|
-
const load =
|
|
2283
|
+
const [thread, setThread] = React8.useState(null);
|
|
2284
|
+
const [isLoading, setIsLoading] = React8.useState(Boolean(threadId));
|
|
2285
|
+
const [error, setError] = React8.useState(null);
|
|
2286
|
+
const controllerRef = React8.useRef(null);
|
|
2287
|
+
const load = React8.useCallback(async () => {
|
|
1999
2288
|
if (!threadId) {
|
|
2000
2289
|
controllerRef.current?.abort();
|
|
2001
2290
|
controllerRef.current = null;
|
|
@@ -2022,7 +2311,7 @@ function useAgentThread(threadId, options = {}) {
|
|
|
2022
2311
|
}
|
|
2023
2312
|
}
|
|
2024
2313
|
}, [client, createAbortController, options.beforeTurnIndex, options.limit, threadId]);
|
|
2025
|
-
|
|
2314
|
+
React8.useEffect(() => {
|
|
2026
2315
|
void load();
|
|
2027
2316
|
return () => controllerRef.current?.abort();
|
|
2028
2317
|
}, [load]);
|
|
@@ -2030,11 +2319,11 @@ function useAgentThread(threadId, options = {}) {
|
|
|
2030
2319
|
}
|
|
2031
2320
|
function useThreadComputer(threadId) {
|
|
2032
2321
|
const { client, createAbortController } = useAgents24Runtime();
|
|
2033
|
-
const [computer, setComputer] =
|
|
2034
|
-
const [isLoading, setIsLoading] =
|
|
2035
|
-
const [error, setError] =
|
|
2036
|
-
const controllerRef =
|
|
2037
|
-
const refresh =
|
|
2322
|
+
const [computer, setComputer] = React8.useState(null);
|
|
2323
|
+
const [isLoading, setIsLoading] = React8.useState(Boolean(threadId));
|
|
2324
|
+
const [error, setError] = React8.useState(null);
|
|
2325
|
+
const controllerRef = React8.useRef(null);
|
|
2326
|
+
const refresh = React8.useCallback(async () => {
|
|
2038
2327
|
controllerRef.current?.abort();
|
|
2039
2328
|
if (!threadId) {
|
|
2040
2329
|
setComputer(null);
|
|
@@ -2059,19 +2348,19 @@ function useThreadComputer(threadId) {
|
|
|
2059
2348
|
}
|
|
2060
2349
|
}
|
|
2061
2350
|
}, [client, createAbortController, threadId]);
|
|
2062
|
-
|
|
2351
|
+
React8.useEffect(() => {
|
|
2063
2352
|
void refresh();
|
|
2064
2353
|
return () => controllerRef.current?.abort();
|
|
2065
2354
|
}, [refresh]);
|
|
2066
|
-
const list =
|
|
2355
|
+
const list = React8.useCallback((input) => {
|
|
2067
2356
|
if (!threadId) throw new Error("A thread is required to list computer files");
|
|
2068
2357
|
return client.threads.computer.list({ threadId, ...input });
|
|
2069
2358
|
}, [client, threadId]);
|
|
2070
|
-
const read =
|
|
2359
|
+
const read = React8.useCallback((path) => {
|
|
2071
2360
|
if (!threadId) throw new Error("A thread is required to read a computer file");
|
|
2072
2361
|
return client.threads.computer.read({ threadId, path });
|
|
2073
2362
|
}, [client, threadId]);
|
|
2074
|
-
const createContentAccess =
|
|
2363
|
+
const createContentAccess = React8.useCallback((path, download = false) => {
|
|
2075
2364
|
if (!threadId) throw new Error("A thread is required to access a computer file");
|
|
2076
2365
|
return client.threads.computer.createContentAccess({ threadId, path, download });
|
|
2077
2366
|
}, [client, threadId]);
|
|
@@ -2079,16 +2368,16 @@ function useThreadComputer(threadId) {
|
|
|
2079
2368
|
}
|
|
2080
2369
|
function useAgentRun() {
|
|
2081
2370
|
const { client, createAbortController, createId } = useAgents24Runtime();
|
|
2082
|
-
const controllerRef =
|
|
2083
|
-
const [isAttached, setIsAttached] =
|
|
2084
|
-
const [error, setError] =
|
|
2085
|
-
|
|
2086
|
-
const detach =
|
|
2371
|
+
const controllerRef = React8.useRef(null);
|
|
2372
|
+
const [isAttached, setIsAttached] = React8.useState(false);
|
|
2373
|
+
const [error, setError] = React8.useState(null);
|
|
2374
|
+
React8.useEffect(() => () => controllerRef.current?.abort(), []);
|
|
2375
|
+
const detach = React8.useCallback(() => {
|
|
2087
2376
|
controllerRef.current?.abort();
|
|
2088
2377
|
controllerRef.current = null;
|
|
2089
2378
|
setIsAttached(false);
|
|
2090
2379
|
}, []);
|
|
2091
|
-
const attach =
|
|
2380
|
+
const attach = React8.useCallback(async (runId, onEvent, cursor) => {
|
|
2092
2381
|
detach();
|
|
2093
2382
|
const controller = createAbortController();
|
|
2094
2383
|
controllerRef.current = controller;
|
|
@@ -2104,7 +2393,7 @@ function useAgentRun() {
|
|
|
2104
2393
|
setIsAttached(false);
|
|
2105
2394
|
}
|
|
2106
2395
|
}, [client, createAbortController, detach]);
|
|
2107
|
-
const cancel =
|
|
2396
|
+
const cancel = React8.useCallback(async (runId) => {
|
|
2108
2397
|
const result = await client.runs.cancel({ runId, idempotencyKey: createId() });
|
|
2109
2398
|
detach();
|
|
2110
2399
|
return result;
|
|
@@ -2113,9 +2402,9 @@ function useAgentRun() {
|
|
|
2113
2402
|
}
|
|
2114
2403
|
function useAgentAttachments() {
|
|
2115
2404
|
const { client, createId } = useAgents24Runtime();
|
|
2116
|
-
const [uploads, setUploads] =
|
|
2117
|
-
const [isUploading, setIsUploading] =
|
|
2118
|
-
const uploadBatch =
|
|
2405
|
+
const [uploads, setUploads] = React8.useState({});
|
|
2406
|
+
const [isUploading, setIsUploading] = React8.useState(false);
|
|
2407
|
+
const uploadBatch = React8.useCallback(async (input) => {
|
|
2119
2408
|
setIsUploading(true);
|
|
2120
2409
|
try {
|
|
2121
2410
|
const results = await client.attachments.uploadBatch({ uploads: input.uploads, threadId: input.threadId, requestedAgentId: input.requestedAgentId, idempotencyKey: createId(), signal: input.signal });
|
|
@@ -2128,7 +2417,7 @@ function useAgentAttachments() {
|
|
|
2128
2417
|
setIsUploading(false);
|
|
2129
2418
|
}
|
|
2130
2419
|
}, [client, createId]);
|
|
2131
|
-
const createContentAccess =
|
|
2420
|
+
const createContentAccess = React8.useCallback(
|
|
2132
2421
|
(input) => client.attachments.createContentAccess(input),
|
|
2133
2422
|
[client]
|
|
2134
2423
|
);
|
|
@@ -2136,9 +2425,9 @@ function useAgentAttachments() {
|
|
|
2136
2425
|
}
|
|
2137
2426
|
function useAgentHitl() {
|
|
2138
2427
|
const { client, createId } = useAgents24Runtime();
|
|
2139
|
-
const [isResolving, setIsResolving] =
|
|
2140
|
-
const [lastResult, setLastResult] =
|
|
2141
|
-
const resume =
|
|
2428
|
+
const [isResolving, setIsResolving] = React8.useState(false);
|
|
2429
|
+
const [lastResult, setLastResult] = React8.useState(null);
|
|
2430
|
+
const resume = React8.useCallback(async (input) => {
|
|
2142
2431
|
setIsResolving(true);
|
|
2143
2432
|
try {
|
|
2144
2433
|
const result = await client.hitl.resume({ ...input, idempotencyKey: createId() });
|
|
@@ -2152,8 +2441,8 @@ function useAgentHitl() {
|
|
|
2152
2441
|
}
|
|
2153
2442
|
function useAgentMcp() {
|
|
2154
2443
|
const { client, createId } = useAgents24Runtime();
|
|
2155
|
-
const [isAuthorizing, setIsAuthorizing] =
|
|
2156
|
-
const startAuthorization =
|
|
2444
|
+
const [isAuthorizing, setIsAuthorizing] = React8.useState(false);
|
|
2445
|
+
const startAuthorization = React8.useCallback(async (input) => {
|
|
2157
2446
|
setIsAuthorizing(true);
|
|
2158
2447
|
try {
|
|
2159
2448
|
return await client.mcp.startAuthorization({ ...input, idempotencyKey: createId() });
|
|
@@ -2161,7 +2450,7 @@ function useAgentMcp() {
|
|
|
2161
2450
|
setIsAuthorizing(false);
|
|
2162
2451
|
}
|
|
2163
2452
|
}, [client, createId]);
|
|
2164
|
-
const redeemCallback =
|
|
2453
|
+
const redeemCallback = React8.useCallback(async (input) => client.mcp.redeemCallback({ ...input, idempotencyKey: createId() }), [client, createId]);
|
|
2165
2454
|
return { isAuthorizing, redeemCallback, startAuthorization };
|
|
2166
2455
|
}
|
|
2167
2456
|
export {
|
|
@@ -2174,11 +2463,13 @@ export {
|
|
|
2174
2463
|
assistantProjectionFromEvent,
|
|
2175
2464
|
attachmentResultToChatAttachment,
|
|
2176
2465
|
captureAgentChatThreadSelection,
|
|
2466
|
+
captureChatOperation,
|
|
2177
2467
|
chatAttachmentIds,
|
|
2178
2468
|
clearAgentChatBackgroundError,
|
|
2179
2469
|
clearAgentChatOperationError,
|
|
2180
2470
|
computerResourceTargets,
|
|
2181
2471
|
contextCompressionPartFromEvent,
|
|
2472
|
+
createChatObservation,
|
|
2182
2473
|
createPortableId,
|
|
2183
2474
|
createRuntimeEventFrameBuffer,
|
|
2184
2475
|
discardOptimisticSubmission,
|
|
@@ -2191,6 +2482,7 @@ export {
|
|
|
2191
2482
|
optimisticSubmissionTurn,
|
|
2192
2483
|
partsFromResponseBlocks,
|
|
2193
2484
|
preferNewestThreadSummary,
|
|
2485
|
+
prependThreadTimeline,
|
|
2194
2486
|
preserveMountedMessageIds,
|
|
2195
2487
|
promoteThreadMessage,
|
|
2196
2488
|
reconcileRefreshedThreads,
|
|
@@ -2203,6 +2495,7 @@ export {
|
|
|
2203
2495
|
upsertStableAssistantMessage,
|
|
2204
2496
|
useAgentAttachments,
|
|
2205
2497
|
useAgentChat,
|
|
2498
|
+
useAgentChatFeedback,
|
|
2206
2499
|
useAgentClient,
|
|
2207
2500
|
useAgentHitl,
|
|
2208
2501
|
useAgentMcp,
|
|
@@ -2210,6 +2503,7 @@ export {
|
|
|
2210
2503
|
useAgentThread,
|
|
2211
2504
|
useAgentThreads,
|
|
2212
2505
|
useAgents24Runtime,
|
|
2506
|
+
useChatObservation,
|
|
2213
2507
|
useThreadComputer,
|
|
2214
2508
|
useThreadMessageQueue,
|
|
2215
2509
|
withAgentChatBackgroundError,
|