@agents24/react 0.2.13 → 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 +690 -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));
|
|
@@ -675,6 +909,19 @@ function reconcileModelTurnParts(parts, event) {
|
|
|
675
909
|
return active ? upsertResponseBlockParts(next, [active]) : next;
|
|
676
910
|
}
|
|
677
911
|
function applyRuntimeEvent(state, event) {
|
|
912
|
+
if (state.activeRunId && event.run_id && event.run_id !== state.activeRunId) {
|
|
913
|
+
const previous = state.messages.find((message) => message.role === "assistant" && message.runId === event.run_id);
|
|
914
|
+
if (event.event !== "run.accepted") {
|
|
915
|
+
if (!previous) return state;
|
|
916
|
+
const reconciled = applyRuntimeEvent({
|
|
917
|
+
...state,
|
|
918
|
+
activeRunId: event.run_id,
|
|
919
|
+
streamingMessageId: previous.id
|
|
920
|
+
}, event);
|
|
921
|
+
return { ...state, messages: reconciled.messages };
|
|
922
|
+
}
|
|
923
|
+
if (previous?.isFinal) return state;
|
|
924
|
+
}
|
|
678
925
|
if (event.event === "run.accepted" && typeof event.payload?.message_id === "string") {
|
|
679
926
|
const messageId2 = event.payload.message_id;
|
|
680
927
|
let optimisticIndex = -1;
|
|
@@ -826,7 +1073,18 @@ function applyThreadSummaryEvent(threads, event) {
|
|
|
826
1073
|
}
|
|
827
1074
|
|
|
828
1075
|
// src/thread-message-queue.ts
|
|
829
|
-
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
|
|
830
1088
|
async function retrySubmissionAdmission(admit) {
|
|
831
1089
|
try {
|
|
832
1090
|
return await admit();
|
|
@@ -836,23 +1094,34 @@ async function retrySubmissionAdmission(admit) {
|
|
|
836
1094
|
return admit();
|
|
837
1095
|
}
|
|
838
1096
|
}
|
|
839
|
-
function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic, onDiscard }) {
|
|
840
|
-
const [pendingMessages, setPendingMessages] =
|
|
841
|
-
const [messageQueue, setMessageQueue] =
|
|
842
|
-
const pendingRef =
|
|
843
|
-
const versionRef =
|
|
844
|
-
const cursorRef =
|
|
845
|
-
const promotedRef =
|
|
846
|
-
const threadRef =
|
|
847
|
-
const
|
|
848
|
-
|
|
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);
|
|
849
1109
|
threadRef.current = threadId;
|
|
850
1110
|
transportRef.current = transport;
|
|
851
1111
|
onPromotedRef.current = onPromoted;
|
|
852
|
-
const
|
|
853
|
-
|
|
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
|
+
};
|
|
854
1123
|
const knownMessage = (id, submissionId) => pendingRef.current.get(id) || (submissionId ? [...pendingRef.current.values()].find((item) => item.client_submission_id === submissionId) : void 0);
|
|
855
|
-
const hydrate =
|
|
1124
|
+
const hydrate = React3.useCallback((page, selectedThreadId) => {
|
|
856
1125
|
if (threadRef.current !== selectedThreadId) return;
|
|
857
1126
|
const pending = page.pending.filter((message) => !promotedRef.current.has(message.id)).map((message) => {
|
|
858
1127
|
const known = knownMessage(message.id, message.client_submission_id);
|
|
@@ -864,11 +1133,12 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
864
1133
|
setPendingMessages([...pending, ...optimistic]);
|
|
865
1134
|
setMessageQueue(page.queue);
|
|
866
1135
|
}, []);
|
|
867
|
-
const refresh =
|
|
1136
|
+
const refresh = React3.useCallback(async (selectedThreadId = threadRef.current, signal) => {
|
|
868
1137
|
if (!selectedThreadId) return null;
|
|
1138
|
+
const isCurrent = ownOperation();
|
|
869
1139
|
const version = ++versionRef.current;
|
|
870
|
-
const page = await transportRef.current.list(selectedThreadId);
|
|
871
|
-
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;
|
|
872
1142
|
for (const item of page.items) {
|
|
873
1143
|
if (item.role === "user" && item.status === "delivered" && item.run_id) {
|
|
874
1144
|
const pending = knownMessage(item.id, item.client_submission_id);
|
|
@@ -886,23 +1156,24 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
886
1156
|
}
|
|
887
1157
|
hydrate(page, selectedThreadId);
|
|
888
1158
|
return page;
|
|
889
|
-
}, [hydrate]);
|
|
890
|
-
const acceptNotice =
|
|
1159
|
+
}, [hydrate, ownOperation]);
|
|
1160
|
+
const acceptNotice = React3.useCallback((notice) => {
|
|
891
1161
|
if (notice.cursor && notice.cursor <= cursorRef.current) return false;
|
|
892
1162
|
if (notice.cursor) cursorRef.current = notice.cursor;
|
|
893
1163
|
const pending = knownMessage(notice.messageId, notice.clientSubmissionId);
|
|
894
|
-
if (notice.status === "delivered" && notice.runId &&
|
|
1164
|
+
if (notice.status === "delivered" && notice.runId && !promotedRef.current.has(notice.messageId)) {
|
|
895
1165
|
promotedRef.current.add(notice.messageId);
|
|
896
|
-
pendingRef.current.delete(pending.id);
|
|
897
1166
|
versionRef.current += 1;
|
|
898
|
-
|
|
899
|
-
|
|
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);
|
|
900
1172
|
}
|
|
901
1173
|
if (notice.status === "removed") pendingRef.current.delete(notice.messageId);
|
|
902
|
-
void refresh().catch(() => void 0);
|
|
903
1174
|
return true;
|
|
904
|
-
}, [
|
|
905
|
-
const beginSubmission =
|
|
1175
|
+
}, []);
|
|
1176
|
+
const beginSubmission = React3.useCallback((input, queued = false) => {
|
|
906
1177
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
907
1178
|
const message = {
|
|
908
1179
|
id: input.clientSubmissionId,
|
|
@@ -929,16 +1200,17 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
929
1200
|
else callbacksRef.current.onOptimistic?.(message, Boolean(input.uploading));
|
|
930
1201
|
return message;
|
|
931
1202
|
}, []);
|
|
932
|
-
const discardSubmission =
|
|
1203
|
+
const discardSubmission = React3.useCallback((message) => {
|
|
933
1204
|
if (!pendingRef.current.has(message.id)) return;
|
|
934
1205
|
pendingRef.current.delete(message.id);
|
|
935
1206
|
callbacksRef.current.onDiscard?.(message);
|
|
936
1207
|
versionRef.current += 1;
|
|
937
1208
|
setPendingMessages((current) => current.filter((item) => item.client_submission_id !== message.client_submission_id));
|
|
938
1209
|
}, []);
|
|
939
|
-
const submit =
|
|
1210
|
+
const submit = React3.useCallback(async (input) => {
|
|
940
1211
|
const selectedThreadId = threadRef.current;
|
|
941
1212
|
if (!selectedThreadId) throw new Error("No active Agent thread is available.");
|
|
1213
|
+
const isCurrent = ownOperation();
|
|
942
1214
|
const { queued, prepare, attachments, uploading, ...submission } = input;
|
|
943
1215
|
const optimistic = beginSubmission({
|
|
944
1216
|
...submission,
|
|
@@ -948,10 +1220,11 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
948
1220
|
}, queued);
|
|
949
1221
|
try {
|
|
950
1222
|
const prepared = prepare ? await prepare() : {};
|
|
951
|
-
|
|
1223
|
+
assertCurrent(isCurrent);
|
|
1224
|
+
pendingRef.current.set(optimistic.id, { ...optimistic, optimisticQueued: queued, attachment_ids: prepared.attachmentIds || optimistic.attachment_ids, attachments: prepared.attachments || attachments });
|
|
952
1225
|
const request = { ...submission, ...prepared, clientSubmissionId: optimistic.client_submission_id, threadId: selectedThreadId };
|
|
953
1226
|
const result = await retrySubmissionAdmission(() => transportRef.current.submit(request));
|
|
954
|
-
if (
|
|
1227
|
+
if (!isCurrent()) return result;
|
|
955
1228
|
versionRef.current += 1;
|
|
956
1229
|
const bound = {
|
|
957
1230
|
...optimistic,
|
|
@@ -978,36 +1251,44 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
978
1251
|
void refresh().catch(() => void 0);
|
|
979
1252
|
return result;
|
|
980
1253
|
} catch (error) {
|
|
981
|
-
if (
|
|
1254
|
+
if (isCurrent()) discardSubmission(optimistic);
|
|
982
1255
|
throw error;
|
|
983
1256
|
}
|
|
984
|
-
}, [beginSubmission, discardSubmission, refresh]);
|
|
985
|
-
const edit =
|
|
1257
|
+
}, [beginSubmission, discardSubmission, refresh, ownOperation]);
|
|
1258
|
+
const edit = React3.useCallback(async (message, input) => {
|
|
1259
|
+
const isCurrent = ownOperation();
|
|
986
1260
|
const updated = await transportRef.current.edit(message, input);
|
|
1261
|
+
assertCurrent(isCurrent);
|
|
987
1262
|
versionRef.current += 1;
|
|
988
1263
|
pendingRef.current.set(updated.id, updated);
|
|
989
1264
|
await refresh(message.thread_id);
|
|
990
1265
|
return updated;
|
|
991
|
-
}, [refresh]);
|
|
992
|
-
const remove =
|
|
1266
|
+
}, [refresh, ownOperation]);
|
|
1267
|
+
const remove = React3.useCallback(async (message) => {
|
|
1268
|
+
const isCurrent = ownOperation();
|
|
993
1269
|
await transportRef.current.remove(message);
|
|
1270
|
+
assertCurrent(isCurrent);
|
|
994
1271
|
versionRef.current += 1;
|
|
995
1272
|
pendingRef.current.delete(message.id);
|
|
996
1273
|
setPendingMessages((current) => current.filter((item) => item.id !== message.id));
|
|
997
1274
|
await refresh(message.thread_id);
|
|
998
|
-
}, [refresh]);
|
|
999
|
-
const retractForEdit =
|
|
1275
|
+
}, [refresh, ownOperation]);
|
|
1276
|
+
const retractForEdit = React3.useCallback(async (message) => {
|
|
1277
|
+
const isCurrent = ownOperation();
|
|
1000
1278
|
await remove(message);
|
|
1279
|
+
assertCurrent(isCurrent);
|
|
1001
1280
|
return { text: message.text, attachment_ids: message.attachment_ids, attachments: message.attachments };
|
|
1002
|
-
}, [remove]);
|
|
1003
|
-
const resume =
|
|
1281
|
+
}, [remove, ownOperation]);
|
|
1282
|
+
const resume = React3.useCallback(async () => {
|
|
1004
1283
|
const selectedThreadId = threadRef.current;
|
|
1005
1284
|
if (!selectedThreadId) return null;
|
|
1285
|
+
const isCurrent = ownOperation();
|
|
1006
1286
|
const result = await transportRef.current.resume(selectedThreadId);
|
|
1287
|
+
if (!isCurrent()) return null;
|
|
1007
1288
|
await refresh(selectedThreadId);
|
|
1008
1289
|
return result;
|
|
1009
|
-
}, [refresh]);
|
|
1010
|
-
|
|
1290
|
+
}, [refresh, ownOperation]);
|
|
1291
|
+
React3.useEffect(() => {
|
|
1011
1292
|
versionRef.current += 1;
|
|
1012
1293
|
cursorRef.current = 0;
|
|
1013
1294
|
promotedRef.current.clear();
|
|
@@ -1016,14 +1297,6 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
1016
1297
|
setMessageQueue(null);
|
|
1017
1298
|
if (threadId) void refresh(threadId).catch(() => void 0);
|
|
1018
1299
|
}, [threadId, refresh]);
|
|
1019
|
-
React2.useEffect(() => {
|
|
1020
|
-
if (!threadId || !pendingMessages.length || messageQueue?.paused) return;
|
|
1021
|
-
const clock = globalThis;
|
|
1022
|
-
const timer = clock.setInterval(() => {
|
|
1023
|
-
void refresh(threadId).catch(() => void 0);
|
|
1024
|
-
}, 3e3);
|
|
1025
|
-
return () => clock.clearInterval(timer);
|
|
1026
|
-
}, [threadId, pendingMessages.length, messageQueue?.paused, refresh]);
|
|
1027
1300
|
return { pendingMessages, messageQueue, refresh, hydrate, acceptNotice, beginSubmission, discardSubmission, submit, edit, remove, retractForEdit, resume };
|
|
1028
1301
|
}
|
|
1029
1302
|
function optimisticSubmissionTurn(message) {
|
|
@@ -1072,72 +1345,20 @@ function discardOptimisticSubmission(state, message) {
|
|
|
1072
1345
|
}
|
|
1073
1346
|
|
|
1074
1347
|
// src/use-agent-chat.ts
|
|
1075
|
-
import * as
|
|
1076
|
-
|
|
1077
|
-
// src/thread-summary-subscription.ts
|
|
1078
|
-
function waitForReconnect(signal, delay) {
|
|
1079
|
-
return new Promise((resolve) => {
|
|
1080
|
-
if (signal.aborted) return resolve();
|
|
1081
|
-
const clock = globalThis;
|
|
1082
|
-
const finish = () => {
|
|
1083
|
-
clock.clearTimeout(timer);
|
|
1084
|
-
signal.removeEventListener("abort", finish);
|
|
1085
|
-
resolve();
|
|
1086
|
-
};
|
|
1087
|
-
const timer = clock.setTimeout(finish, delay);
|
|
1088
|
-
signal.addEventListener("abort", finish, { once: true });
|
|
1089
|
-
});
|
|
1090
|
-
}
|
|
1091
|
-
async function subscribeToThreadSummaries(input) {
|
|
1092
|
-
let cursor;
|
|
1093
|
-
let failures = 0;
|
|
1094
|
-
while (!input.signal.aborted) {
|
|
1095
|
-
let refreshedFromSnapshot = false;
|
|
1096
|
-
try {
|
|
1097
|
-
await input.client.threads.events({ cursor, signal: input.signal }, async (event) => {
|
|
1098
|
-
if (event.event === "snapshot_required") {
|
|
1099
|
-
await input.refresh();
|
|
1100
|
-
cursor = void 0;
|
|
1101
|
-
refreshedFromSnapshot = true;
|
|
1102
|
-
return;
|
|
1103
|
-
}
|
|
1104
|
-
cursor = event.cursor;
|
|
1105
|
-
failures = 0;
|
|
1106
|
-
await input.onEvent(event);
|
|
1107
|
-
});
|
|
1108
|
-
if (input.signal.aborted) break;
|
|
1109
|
-
if (!refreshedFromSnapshot) await input.refresh();
|
|
1110
|
-
input.onRecovered();
|
|
1111
|
-
failures = 0;
|
|
1112
|
-
} catch (error) {
|
|
1113
|
-
if (input.signal.aborted || isAbortLike(error)) break;
|
|
1114
|
-
input.onError(error);
|
|
1115
|
-
failures += 1;
|
|
1116
|
-
try {
|
|
1117
|
-
await input.refresh();
|
|
1118
|
-
input.onRecovered();
|
|
1119
|
-
} catch (refreshError) {
|
|
1120
|
-
if (!input.signal.aborted && !isAbortLike(refreshError)) input.onError(refreshError);
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
if (!input.signal.aborted) {
|
|
1124
|
-
await waitForReconnect(input.signal, Math.min(3e4, 1e3 * 2 ** Math.min(failures, 5)));
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1348
|
+
import * as React7 from "react";
|
|
1128
1349
|
|
|
1129
1350
|
// src/use-agent-chat-thread-list.ts
|
|
1130
|
-
import * as
|
|
1351
|
+
import * as React4 from "react";
|
|
1131
1352
|
function summaryToStored(thread) {
|
|
1132
1353
|
return { ...thread, id: String(thread.id), messages: [], isHydrated: false };
|
|
1133
1354
|
}
|
|
1134
1355
|
function useAgentChatThreadList({ client, threadPageSize, stateRef, update, clearOperationError, failOperation }) {
|
|
1135
|
-
const [isLoadingThreads, setIsLoadingThreads] =
|
|
1136
|
-
const [isLoadingMoreThreads, setIsLoadingMoreThreads] =
|
|
1137
|
-
const [threadTotal, setThreadTotal] =
|
|
1138
|
-
const refreshPromiseRef =
|
|
1139
|
-
const eventsDuringRefreshRef =
|
|
1140
|
-
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(() => {
|
|
1141
1362
|
if (refreshPromiseRef.current) return refreshPromiseRef.current;
|
|
1142
1363
|
clearOperationError();
|
|
1143
1364
|
setIsLoadingThreads(true);
|
|
@@ -1172,7 +1393,7 @@ function useAgentChatThreadList({ client, threadPageSize, stateRef, update, clea
|
|
|
1172
1393
|
}).catch(() => void 0);
|
|
1173
1394
|
return request;
|
|
1174
1395
|
}, [clearOperationError, client, failOperation, threadPageSize, update]);
|
|
1175
|
-
const loadMoreThreads =
|
|
1396
|
+
const loadMoreThreads = React4.useCallback(async () => {
|
|
1176
1397
|
if (isLoadingMoreThreads) return;
|
|
1177
1398
|
const skip = stateRef.current.threads.length;
|
|
1178
1399
|
if (threadTotal !== void 0 && skip >= threadTotal) return;
|
|
@@ -1207,15 +1428,30 @@ function useAgentChatThreadList({ client, threadPageSize, stateRef, update, clea
|
|
|
1207
1428
|
}
|
|
1208
1429
|
|
|
1209
1430
|
// src/use-agent-chat-feedback.ts
|
|
1210
|
-
import * as
|
|
1211
|
-
function useAgentChatFeedback({ client, update, clearOperationError, failOperation }) {
|
|
1212
|
-
|
|
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) => {
|
|
1213
1444
|
const runId = message.runId;
|
|
1214
1445
|
if (!runId || message.role !== "assistant" || message.isFinal === false) {
|
|
1215
1446
|
const error = new Error("Only persisted final responses can receive feedback.");
|
|
1216
1447
|
failOperation("set_feedback", error);
|
|
1217
1448
|
throw error;
|
|
1218
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;
|
|
1219
1455
|
const previous = message.feedback ?? null;
|
|
1220
1456
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1221
1457
|
const optimistic = input.rating ? {
|
|
@@ -1227,26 +1463,34 @@ function useAgentChatFeedback({ client, update, clearOperationError, failOperati
|
|
|
1227
1463
|
} : null;
|
|
1228
1464
|
const replaceFeedback = (current, feedback) => ({
|
|
1229
1465
|
...current,
|
|
1230
|
-
messages: current.messages.map((item) => item.
|
|
1466
|
+
messages: current.messages.map((item) => item.role === "assistant" && item.runId === runId ? { ...item, feedback } : item),
|
|
1231
1467
|
threads: current.threads.map((thread) => ({
|
|
1232
1468
|
...thread,
|
|
1233
|
-
messages: thread.messages.map((item) => item.
|
|
1469
|
+
messages: thread.messages.map((item) => item.role === "assistant" && item.runId === runId ? { ...item, feedback } : item)
|
|
1234
1470
|
}))
|
|
1235
1471
|
});
|
|
1236
1472
|
clearOperationError();
|
|
1237
1473
|
update((current) => replaceFeedback(current, optimistic));
|
|
1238
1474
|
try {
|
|
1239
1475
|
const result = await client.runs.setFeedback({ runId, ...input });
|
|
1240
|
-
update((current) => replaceFeedback(current, result.feedback));
|
|
1476
|
+
if (isLatest() && isCurrent()) update((current) => replaceFeedback(current, result.feedback));
|
|
1241
1477
|
} catch (error) {
|
|
1242
|
-
|
|
1478
|
+
if (isLatest() && isCurrent()) update((current) => {
|
|
1479
|
+
const restored = replaceFeedback(current, previous);
|
|
1480
|
+
return isCurrent() ? withAgentChatOperationError(restored, "set_feedback", error) : restored;
|
|
1481
|
+
});
|
|
1243
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);
|
|
1244
1487
|
}
|
|
1245
|
-
}, [clearOperationError, client, failOperation, update]);
|
|
1488
|
+
}, [clearOperationError, client, failOperation, update, ownOperation, refreshMessages]);
|
|
1489
|
+
return { setFeedback, captureRead };
|
|
1246
1490
|
}
|
|
1247
1491
|
|
|
1248
1492
|
// src/use-agent-chat-queue-actions.ts
|
|
1249
|
-
import * as
|
|
1493
|
+
import * as React6 from "react";
|
|
1250
1494
|
function useAgentChatQueueActions({
|
|
1251
1495
|
queue,
|
|
1252
1496
|
stateRef,
|
|
@@ -1254,21 +1498,23 @@ function useAgentChatQueueActions({
|
|
|
1254
1498
|
clearOperationError,
|
|
1255
1499
|
failOperation,
|
|
1256
1500
|
refreshMessages,
|
|
1257
|
-
attachRun
|
|
1501
|
+
attachRun,
|
|
1502
|
+
ownOperation
|
|
1258
1503
|
}) {
|
|
1259
|
-
const steer =
|
|
1504
|
+
const steer = React6.useCallback(async (text2) => {
|
|
1260
1505
|
const threadId = stateRef.current.activeThreadId;
|
|
1261
1506
|
if (!threadId || !text2.trim()) throw new Error("An active thread and text are required to steer.");
|
|
1507
|
+
const isCurrent = ownOperation();
|
|
1262
1508
|
clearOperationError();
|
|
1263
1509
|
try {
|
|
1264
1510
|
await queue.submit({ mode: "steer", input: text2.trim(), idempotencyKey: createId() });
|
|
1265
|
-
await refreshMessages(threadId);
|
|
1511
|
+
if (isCurrent()) await refreshMessages(threadId);
|
|
1266
1512
|
} catch (error) {
|
|
1267
|
-
failOperation("submit", error);
|
|
1513
|
+
if (isCurrent()) failOperation("submit", error);
|
|
1268
1514
|
throw error;
|
|
1269
1515
|
}
|
|
1270
|
-
}, [clearOperationError, createId, failOperation, queue.submit, refreshMessages, stateRef]);
|
|
1271
|
-
const editPendingMessage =
|
|
1516
|
+
}, [clearOperationError, createId, failOperation, queue.submit, refreshMessages, stateRef, ownOperation]);
|
|
1517
|
+
const editPendingMessage = React6.useCallback(async (message, input) => {
|
|
1272
1518
|
await queue.edit(message, {
|
|
1273
1519
|
text: input.text,
|
|
1274
1520
|
attachmentIds: input.attachmentIds ?? message.attachment_ids,
|
|
@@ -1276,14 +1522,15 @@ function useAgentChatQueueActions({
|
|
|
1276
1522
|
requestedModelId: input.requestedModelId
|
|
1277
1523
|
});
|
|
1278
1524
|
}, [queue.edit]);
|
|
1279
|
-
const steerPendingMessage =
|
|
1525
|
+
const steerPendingMessage = React6.useCallback(async (message) => {
|
|
1280
1526
|
await queue.edit(message, { text: message.text, attachmentIds: message.attachment_ids, mode: "steer" });
|
|
1281
1527
|
}, [queue.edit]);
|
|
1282
|
-
const resumeQueue =
|
|
1528
|
+
const resumeQueue = React6.useCallback(async () => {
|
|
1283
1529
|
if (!stateRef.current.activeThreadId) return;
|
|
1530
|
+
const isCurrent = ownOperation();
|
|
1284
1531
|
const result = await queue.resume();
|
|
1285
|
-
if (result?.run_id) void attachRun(result.run_id).catch(() => void 0);
|
|
1286
|
-
}, [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]);
|
|
1287
1534
|
return { steer, editPendingMessage, steerPendingMessage, removePendingMessage: queue.remove, resumeQueue };
|
|
1288
1535
|
}
|
|
1289
1536
|
|
|
@@ -1294,9 +1541,6 @@ function resolveInitialActiveThreadId(options, storedThreadId) {
|
|
|
1294
1541
|
function shouldHydrateSelectedThread(selectedThreadId, activeThreadId, activeThreadIsHydrated) {
|
|
1295
1542
|
return Boolean(selectedThreadId) && (selectedThreadId !== activeThreadId || !activeThreadIsHydrated);
|
|
1296
1543
|
}
|
|
1297
|
-
function isCurrentThreadOpenRequest(input) {
|
|
1298
|
-
return !input.signalAborted && input.requestGeneration === input.currentGeneration && input.requestedThreadId === input.activeThreadId;
|
|
1299
|
-
}
|
|
1300
1544
|
|
|
1301
1545
|
// src/use-agent-chat.ts
|
|
1302
1546
|
function activeRunIdFromThread(thread) {
|
|
@@ -1309,7 +1553,7 @@ function useAgentChat(options = {}) {
|
|
|
1309
1553
|
const client = options.client || runtime.client;
|
|
1310
1554
|
const pageSize = options.pageSize ?? 30;
|
|
1311
1555
|
const threadPageSize = options.threadPageSize ?? 50;
|
|
1312
|
-
const [state, setState] =
|
|
1556
|
+
const [state, setState] = React7.useState(() => ({
|
|
1313
1557
|
...initialAgentChatState,
|
|
1314
1558
|
activeThreadId: resolveInitialActiveThreadId(
|
|
1315
1559
|
options,
|
|
@@ -1317,34 +1561,40 @@ function useAgentChat(options = {}) {
|
|
|
1317
1561
|
),
|
|
1318
1562
|
threads: runtime.storage?.loadThreads() ?? []
|
|
1319
1563
|
}));
|
|
1320
|
-
const [isLoadingHistory, setIsLoadingHistory] =
|
|
1321
|
-
const [isLoadingOlder, setIsLoadingOlder] =
|
|
1322
|
-
const [isSubmitting, setIsSubmitting] =
|
|
1323
|
-
const streamControllerRef =
|
|
1324
|
-
const threadOpenControllerRef =
|
|
1325
|
-
const threadOpenGenerationRef =
|
|
1326
|
-
const attachRunRef =
|
|
1327
|
-
const messageRefreshVersionRef =
|
|
1328
|
-
const promotedRunHintRef =
|
|
1329
|
-
const promotedAttachRef =
|
|
1330
|
-
const
|
|
1331
|
-
const
|
|
1332
|
-
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);
|
|
1333
1579
|
stateRef.current = state;
|
|
1334
|
-
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) => {
|
|
1335
1587
|
runtime.storage?.saveThreads(next.threads);
|
|
1336
1588
|
runtime.storage?.saveActiveThreadId?.(next.activeThreadId);
|
|
1337
1589
|
}, [runtime.storage]);
|
|
1338
|
-
const update =
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
return next;
|
|
1344
|
-
});
|
|
1590
|
+
const update = React7.useCallback((updater) => {
|
|
1591
|
+
const next = updater(stateRef.current);
|
|
1592
|
+
stateRef.current = next;
|
|
1593
|
+
persist(next);
|
|
1594
|
+
setState(next);
|
|
1345
1595
|
}, [persist]);
|
|
1346
|
-
const queueTransport =
|
|
1347
|
-
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 }),
|
|
1348
1598
|
submit: (input) => client.threads.messages.submit(input),
|
|
1349
1599
|
edit: (message, input) => client.threads.messages.edit({
|
|
1350
1600
|
threadId: message.thread_id,
|
|
@@ -1361,40 +1611,46 @@ function useAgentChat(options = {}) {
|
|
|
1361
1611
|
},
|
|
1362
1612
|
resume: (threadId) => client.threads.resumeQueue({ threadId })
|
|
1363
1613
|
}), [client, pageSize]);
|
|
1364
|
-
const
|
|
1365
|
-
if (stateRef.current.activeThreadId !==
|
|
1614
|
+
const attachPromotion = React7.useCallback((runId, threadId) => {
|
|
1615
|
+
if (stateRef.current.activeThreadId !== threadId) return;
|
|
1366
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;
|
|
1367
1626
|
const userMessage = threadTimelineToMessages([{ ...message, run_id: runId, status: "delivered" }])[0];
|
|
1368
1627
|
update((current) => current.activeThreadId === message.thread_id ? { ...current, messages: promoteThreadMessage(current.messages, userMessage, runId) } : current);
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
void attachRunRef.current(runId).catch(() => {
|
|
1372
|
-
promotedAttachRef.current = null;
|
|
1373
|
-
});
|
|
1374
|
-
}
|
|
1375
|
-
}, [update]);
|
|
1628
|
+
attachPromotion(runId, message.thread_id);
|
|
1629
|
+
}, [update, attachPromotion]);
|
|
1376
1630
|
const queue = useThreadMessageQueue({
|
|
1377
1631
|
threadId: state.activeThreadId,
|
|
1378
1632
|
transport: queueTransport,
|
|
1633
|
+
ownOperation,
|
|
1379
1634
|
onPromoted: onQueuePromotion,
|
|
1635
|
+
onUnknownPromotion: attachPromotion,
|
|
1380
1636
|
onOptimistic: (message, uploading) => update((current) => optimisticSubmissionState(current, message, uploading)),
|
|
1381
1637
|
onDiscard: (message) => update((current) => discardOptimisticSubmission(current, message))
|
|
1382
1638
|
});
|
|
1383
1639
|
const { pendingMessages, messageQueue } = queue;
|
|
1384
|
-
const eventBuffer =
|
|
1640
|
+
const eventBuffer = React7.useMemo(
|
|
1385
1641
|
() => createRuntimeEventFrameBuffer(
|
|
1386
1642
|
(events) => update((current) => applyRuntimeEvents(current, events)),
|
|
1387
1643
|
runtimeEventFrameScheduler()
|
|
1388
1644
|
),
|
|
1389
1645
|
[update]
|
|
1390
1646
|
);
|
|
1391
|
-
const clearOperationError =
|
|
1647
|
+
const clearOperationError = React7.useCallback((operation) => {
|
|
1392
1648
|
update((current) => clearAgentChatOperationError(current, operation));
|
|
1393
1649
|
}, [update]);
|
|
1394
|
-
const clearBackgroundError =
|
|
1650
|
+
const clearBackgroundError = React7.useCallback(() => {
|
|
1395
1651
|
update(clearAgentChatBackgroundError);
|
|
1396
1652
|
}, [update]);
|
|
1397
|
-
const failOperation =
|
|
1653
|
+
const failOperation = React7.useCallback((operation, cause) => {
|
|
1398
1654
|
update((current) => withAgentChatOperationError(current, operation, cause));
|
|
1399
1655
|
}, [update]);
|
|
1400
1656
|
const {
|
|
@@ -1405,60 +1661,67 @@ function useAgentChat(options = {}) {
|
|
|
1405
1661
|
refreshThreads,
|
|
1406
1662
|
loadMoreThreads
|
|
1407
1663
|
} = useAgentChatThreadList({ client, threadPageSize, stateRef, update, clearOperationError, failOperation });
|
|
1408
|
-
const invalidateThreadOpen =
|
|
1664
|
+
const invalidateThreadOpen = React7.useCallback(() => {
|
|
1409
1665
|
threadOpenGenerationRef.current += 1;
|
|
1410
1666
|
threadOpenControllerRef.current?.abort("Thread selection changed.");
|
|
1411
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);
|
|
1412
1676
|
setIsLoadingHistory(false);
|
|
1413
1677
|
}, []);
|
|
1414
|
-
const detach =
|
|
1678
|
+
const detach = React7.useCallback(() => {
|
|
1415
1679
|
eventBuffer.flush();
|
|
1416
1680
|
streamControllerRef.current?.abort();
|
|
1417
1681
|
streamControllerRef.current = null;
|
|
1418
1682
|
update((current) => ({ ...current, streamingMessageId: null, runState: current.activeRunId ? "reconnecting" : "idle" }));
|
|
1419
1683
|
}, [eventBuffer, update]);
|
|
1420
|
-
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) => {
|
|
1421
1694
|
const target = threadId || stateRef.current.activeThreadId;
|
|
1422
1695
|
if (!target) return;
|
|
1696
|
+
const isCurrent = ownOperation();
|
|
1697
|
+
const reconcileFeedback = captureFeedbackRead();
|
|
1423
1698
|
const version = ++messageRefreshVersionRef.current;
|
|
1424
|
-
const page = await client.threads.messages.list({ threadId: target, limit: pageSize });
|
|
1425
|
-
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;
|
|
1426
1701
|
queue.hydrate(page, target);
|
|
1427
1702
|
const promoted = promotedRunId || promotedRunHintRef.current;
|
|
1428
|
-
if (promoted && page.items.some((message) => message.
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
return;
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
const additions = delivered.filter((item) => !current.messages.some((existing) => existing.id === item.id));
|
|
1444
|
-
return additions.length ? { ...current, messages: [...current.messages, ...additions] } : current;
|
|
1445
|
-
});
|
|
1446
|
-
}
|
|
1447
|
-
if (!stateRef.current.activeRunId && page.items.length) {
|
|
1448
|
-
update((current) => current.activeThreadId === target && !current.activeRunId ? (() => {
|
|
1449
|
-
const messages = reconcileThreadTimeline(current.messages, threadTimelineToMessages(page.items), null, null);
|
|
1450
|
-
return { ...current, messages, threads: current.threads.map((thread) => thread.id === target ? { ...thread, messages } : thread) };
|
|
1451
|
-
})() : current);
|
|
1452
|
-
}
|
|
1453
|
-
}, [client, pageSize, queue.hydrate, update]);
|
|
1454
|
-
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;
|
|
1455
1718
|
eventBuffer.dispose();
|
|
1456
1719
|
streamControllerRef.current?.abort();
|
|
1457
1720
|
threadOpenControllerRef.current?.abort();
|
|
1458
1721
|
}, [eventBuffer]);
|
|
1459
|
-
const openThread =
|
|
1722
|
+
const openThread = React7.useCallback(async (threadId) => {
|
|
1460
1723
|
const previousSelection = captureAgentChatThreadSelection(stateRef.current);
|
|
1461
|
-
|
|
1724
|
+
invalidateThreadOpen();
|
|
1462
1725
|
const requestController = runtime.createAbortController();
|
|
1463
1726
|
threadOpenControllerRef.current = requestController;
|
|
1464
1727
|
const requestGeneration = threadOpenGenerationRef.current + 1;
|
|
@@ -1478,6 +1741,8 @@ function useAgentChat(options = {}) {
|
|
|
1478
1741
|
runState: "idle",
|
|
1479
1742
|
streamingMessageId: null
|
|
1480
1743
|
}));
|
|
1744
|
+
const isCurrent = ownOperation();
|
|
1745
|
+
const reconcileFeedback = captureFeedbackRead();
|
|
1481
1746
|
setIsLoadingHistory(true);
|
|
1482
1747
|
try {
|
|
1483
1748
|
const detail = await client.threads.get({
|
|
@@ -1485,39 +1750,21 @@ function useAgentChat(options = {}) {
|
|
|
1485
1750
|
limit: pageSize,
|
|
1486
1751
|
signal: requestController.signal
|
|
1487
1752
|
});
|
|
1488
|
-
if (!
|
|
1489
|
-
activeThreadId: stateRef.current.activeThreadId,
|
|
1490
|
-
currentGeneration: threadOpenGenerationRef.current,
|
|
1491
|
-
requestGeneration,
|
|
1492
|
-
requestedThreadId: threadId,
|
|
1493
|
-
signalAborted: requestController.signal.aborted
|
|
1494
|
-
})) return;
|
|
1753
|
+
if (!isCurrent()) return;
|
|
1495
1754
|
const timeline = await client.threads.messages.list({ threadId, limit: pageSize, signal: requestController.signal });
|
|
1496
|
-
if (!
|
|
1497
|
-
activeThreadId: stateRef.current.activeThreadId,
|
|
1498
|
-
currentGeneration: threadOpenGenerationRef.current,
|
|
1499
|
-
requestGeneration,
|
|
1500
|
-
requestedThreadId: threadId,
|
|
1501
|
-
signalAborted: requestController.signal.aborted
|
|
1502
|
-
})) return;
|
|
1755
|
+
if (!isCurrent()) return;
|
|
1503
1756
|
queue.hydrate(timeline, threadId);
|
|
1504
|
-
const messages =
|
|
1757
|
+
const messages = reconcileFeedback(threadTimelineToMessages(timeline.items), stateRef.current.messages);
|
|
1505
1758
|
const context = latestThreadContext(detail);
|
|
1506
1759
|
const stored = {
|
|
1507
1760
|
...detail,
|
|
1508
1761
|
messages,
|
|
1509
1762
|
isHydrated: true,
|
|
1510
|
-
hasOlderTurns: Boolean(
|
|
1511
|
-
nextBeforeTurnIndex:
|
|
1763
|
+
hasOlderTurns: Boolean(timeline.paging.has_more),
|
|
1764
|
+
nextBeforeTurnIndex: timeline.paging.next_before_turn_index,
|
|
1512
1765
|
...context
|
|
1513
1766
|
};
|
|
1514
|
-
update((current) =>
|
|
1515
|
-
activeThreadId: current.activeThreadId,
|
|
1516
|
-
currentGeneration: threadOpenGenerationRef.current,
|
|
1517
|
-
requestGeneration,
|
|
1518
|
-
requestedThreadId: threadId,
|
|
1519
|
-
signalAborted: requestController.signal.aborted
|
|
1520
|
-
}) ? {
|
|
1767
|
+
update((current) => isCurrent() ? {
|
|
1521
1768
|
...current,
|
|
1522
1769
|
activeThreadId: threadId,
|
|
1523
1770
|
messages,
|
|
@@ -1527,13 +1774,7 @@ function useAgentChat(options = {}) {
|
|
|
1527
1774
|
threads: current.threads.some((thread) => thread.id === threadId) ? current.threads.map((thread) => thread.id === threadId ? stored : thread) : [stored, ...current.threads]
|
|
1528
1775
|
} : current);
|
|
1529
1776
|
const activeRunId = activeRunIdFromThread(stored);
|
|
1530
|
-
if (activeRunId &&
|
|
1531
|
-
activeThreadId: stateRef.current.activeThreadId,
|
|
1532
|
-
currentGeneration: threadOpenGenerationRef.current,
|
|
1533
|
-
requestGeneration,
|
|
1534
|
-
requestedThreadId: threadId,
|
|
1535
|
-
signalAborted: requestController.signal.aborted
|
|
1536
|
-
})) await attachRunRef.current(activeRunId).catch(() => void 0);
|
|
1777
|
+
if (activeRunId && isCurrent()) void attachRunRef.current(activeRunId).catch(() => void 0);
|
|
1537
1778
|
} catch (error) {
|
|
1538
1779
|
if (!requestController.signal.aborted && !isAbortLike(error) && requestGeneration === threadOpenGenerationRef.current) {
|
|
1539
1780
|
failOperation("open_thread", error);
|
|
@@ -1549,28 +1790,34 @@ function useAgentChat(options = {}) {
|
|
|
1549
1790
|
setIsLoadingHistory(false);
|
|
1550
1791
|
}
|
|
1551
1792
|
}
|
|
1552
|
-
}, [clearOperationError, client, detach, failOperation, pageSize, queue.hydrate, runtime, update]);
|
|
1553
|
-
const consumeEvent =
|
|
1793
|
+
}, [clearOperationError, client, detach, failOperation, pageSize, queue.hydrate, runtime, update, invalidateThreadOpen, ownOperation, captureFeedbackRead]);
|
|
1794
|
+
const consumeEvent = React7.useCallback((event) => {
|
|
1554
1795
|
const eventThreadId = typeof event.payload?.thread_id === "string" ? event.payload.thread_id.trim() : "";
|
|
1555
1796
|
if (eventThreadId && !stateRef.current.activeThreadId) {
|
|
1556
1797
|
update((current) => adoptStreamingThread(current, eventThreadId));
|
|
1798
|
+
setIsSubmitting(false);
|
|
1557
1799
|
firstSubmissionAdmittedRef.current?.();
|
|
1558
1800
|
}
|
|
1559
1801
|
eventBuffer.push(event);
|
|
1560
1802
|
}, [eventBuffer, update]);
|
|
1561
|
-
const attachRun =
|
|
1803
|
+
const attachRun = React7.useCallback(async (runId, cursor) => {
|
|
1804
|
+
eventBuffer.flush();
|
|
1562
1805
|
streamControllerRef.current?.abort();
|
|
1563
1806
|
const controller = runtime.createAbortController();
|
|
1564
1807
|
streamControllerRef.current = controller;
|
|
1565
|
-
|
|
1808
|
+
const isCurrent = ownOperation({ stream: true });
|
|
1809
|
+
clearOperationError("attach_run");
|
|
1566
1810
|
update((current) => ({ ...current, activeRunId: runId, runState: "reconnecting", error: null }));
|
|
1567
1811
|
try {
|
|
1568
|
-
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;
|
|
1569
1816
|
eventBuffer.flush();
|
|
1570
1817
|
update((current) => ({ ...current, activeRunId: result.detached ? runId : current.activeRunId, cursor: result.cursor ?? current.cursor, runState: result.detached ? "reconnecting" : current.runState }));
|
|
1571
1818
|
if (!result.detached && result.threadId) await refreshMessages(result.threadId);
|
|
1572
1819
|
} catch (error) {
|
|
1573
|
-
if (!isAbortLike(error)) {
|
|
1820
|
+
if (isCurrent() && !isAbortLike(error)) {
|
|
1574
1821
|
update((current) => ({ ...current, runState: "failed", streamingMessageId: null }));
|
|
1575
1822
|
failOperation("attach_run", error);
|
|
1576
1823
|
throw error;
|
|
@@ -1578,15 +1825,17 @@ function useAgentChat(options = {}) {
|
|
|
1578
1825
|
} finally {
|
|
1579
1826
|
if (streamControllerRef.current === controller) streamControllerRef.current = null;
|
|
1580
1827
|
}
|
|
1581
|
-
}, [clearOperationError, client, consumeEvent, eventBuffer, failOperation, refreshMessages, runtime, update]);
|
|
1828
|
+
}, [clearOperationError, client, consumeEvent, eventBuffer, failOperation, refreshMessages, runtime, update, ownOperation]);
|
|
1582
1829
|
attachRunRef.current = attachRun;
|
|
1583
|
-
const submit =
|
|
1830
|
+
const submit = React7.useCallback(async (input) => {
|
|
1584
1831
|
if (!input.text.trim() && !input.attachmentIds?.length && !input.uploads?.length) return;
|
|
1585
1832
|
if (firstSubmissionRef.current && !stateRef.current.activeThreadId) {
|
|
1586
1833
|
await firstSubmissionRef.current;
|
|
1587
1834
|
}
|
|
1588
1835
|
const existingThreadId = stateRef.current.activeThreadId;
|
|
1589
1836
|
if (existingThreadId) {
|
|
1837
|
+
const isCurrent = ownOperation();
|
|
1838
|
+
const submissionGeneration = ++submissionGenerationRef.current;
|
|
1590
1839
|
clearOperationError();
|
|
1591
1840
|
setIsSubmitting(true);
|
|
1592
1841
|
try {
|
|
@@ -1631,21 +1880,23 @@ function useAgentChat(options = {}) {
|
|
|
1631
1880
|
}
|
|
1632
1881
|
});
|
|
1633
1882
|
} catch (error) {
|
|
1634
|
-
failOperation("submit", error);
|
|
1883
|
+
if (isCurrent()) failOperation("submit", error);
|
|
1635
1884
|
throw error;
|
|
1636
1885
|
} finally {
|
|
1637
|
-
setIsSubmitting(false);
|
|
1886
|
+
if (isCurrent() && submissionGeneration === submissionGenerationRef.current) setIsSubmitting(false);
|
|
1638
1887
|
}
|
|
1639
1888
|
return;
|
|
1640
1889
|
}
|
|
1890
|
+
invalidateThreadOpen();
|
|
1641
1891
|
const admission = new Promise((resolve) => {
|
|
1642
1892
|
firstSubmissionAdmittedRef.current = resolve;
|
|
1643
1893
|
});
|
|
1644
1894
|
const stream = (async () => {
|
|
1645
|
-
invalidateThreadOpen();
|
|
1646
1895
|
detach();
|
|
1647
1896
|
const controller = runtime.createAbortController();
|
|
1648
1897
|
streamControllerRef.current = controller;
|
|
1898
|
+
const isCurrent = ownOperation({ stream: true, adoptThread: true });
|
|
1899
|
+
const submissionGeneration = ++submissionGenerationRef.current;
|
|
1649
1900
|
const ticket = queue.beginSubmission({
|
|
1650
1901
|
input: input.text.trim(),
|
|
1651
1902
|
clientSubmissionId: runtime.createId(),
|
|
@@ -1676,6 +1927,7 @@ function useAgentChat(options = {}) {
|
|
|
1676
1927
|
idempotencyKey: runtime.createId(),
|
|
1677
1928
|
signal: controller.signal
|
|
1678
1929
|
}) : [];
|
|
1930
|
+
if (!isCurrent()) return;
|
|
1679
1931
|
if (hasPendingUploads && uploaded.length !== input.uploads?.length) {
|
|
1680
1932
|
throw new Error("Attachment upload returned an incomplete batch");
|
|
1681
1933
|
}
|
|
@@ -1688,7 +1940,20 @@ function useAgentChat(options = {}) {
|
|
|
1688
1940
|
runState: "streaming"
|
|
1689
1941
|
}));
|
|
1690
1942
|
}
|
|
1691
|
-
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;
|
|
1692
1957
|
eventBuffer.flush();
|
|
1693
1958
|
update((current) => {
|
|
1694
1959
|
const threadId = result.threadId || current.activeThreadId;
|
|
@@ -1704,6 +1969,7 @@ function useAgentChat(options = {}) {
|
|
|
1704
1969
|
if (result.threadId) await refreshThreads().catch(() => void 0);
|
|
1705
1970
|
if (result.threadId && !result.detached) await refreshMessages(result.threadId);
|
|
1706
1971
|
} catch (error) {
|
|
1972
|
+
if (!isCurrent()) return;
|
|
1707
1973
|
const admitted = Boolean(stateRef.current.messages.find((message) => message.id === userId)?.runId);
|
|
1708
1974
|
if (!admitted) queue.discardSubmission(ticket);
|
|
1709
1975
|
if (!isAbortLike(error)) {
|
|
@@ -1716,7 +1982,7 @@ function useAgentChat(options = {}) {
|
|
|
1716
1982
|
throw error;
|
|
1717
1983
|
}
|
|
1718
1984
|
} finally {
|
|
1719
|
-
setIsSubmitting(false);
|
|
1985
|
+
if (isCurrent() && submissionGeneration === submissionGenerationRef.current) setIsSubmitting(false);
|
|
1720
1986
|
if (streamControllerRef.current === controller) streamControllerRef.current = null;
|
|
1721
1987
|
}
|
|
1722
1988
|
})();
|
|
@@ -1725,10 +1991,12 @@ function useAgentChat(options = {}) {
|
|
|
1725
1991
|
try {
|
|
1726
1992
|
await accepted;
|
|
1727
1993
|
} finally {
|
|
1728
|
-
if (firstSubmissionRef.current === accepted)
|
|
1729
|
-
|
|
1994
|
+
if (firstSubmissionRef.current === accepted) {
|
|
1995
|
+
firstSubmissionRef.current = null;
|
|
1996
|
+
firstSubmissionAdmittedRef.current = null;
|
|
1997
|
+
}
|
|
1730
1998
|
}
|
|
1731
|
-
}, [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]);
|
|
1732
2000
|
const { steer, editPendingMessage, steerPendingMessage, removePendingMessage, resumeQueue } = useAgentChatQueueActions({
|
|
1733
2001
|
queue,
|
|
1734
2002
|
stateRef,
|
|
@@ -1736,42 +2004,38 @@ function useAgentChat(options = {}) {
|
|
|
1736
2004
|
clearOperationError,
|
|
1737
2005
|
failOperation,
|
|
1738
2006
|
refreshMessages,
|
|
1739
|
-
attachRun
|
|
2007
|
+
attachRun,
|
|
2008
|
+
ownOperation
|
|
1740
2009
|
});
|
|
1741
|
-
const startNewThread =
|
|
2010
|
+
const startNewThread = React7.useCallback(() => {
|
|
1742
2011
|
invalidateThreadOpen();
|
|
1743
2012
|
detach();
|
|
1744
2013
|
update((current) => ({ ...initialAgentChatState, threads: current.threads }));
|
|
1745
2014
|
}, [detach, invalidateThreadOpen, update]);
|
|
1746
|
-
const deleteThread =
|
|
1747
|
-
|
|
2015
|
+
const deleteThread = React7.useCallback(async (threadId) => {
|
|
2016
|
+
const isCurrent = ownOperation();
|
|
1748
2017
|
clearOperationError();
|
|
1749
2018
|
try {
|
|
1750
2019
|
await client.threads.delete({ threadId, idempotencyKey: runtime.createId() });
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
activeThreadId: current.activeThreadId === threadId ? null : current.activeThreadId,
|
|
1754
|
-
messages: current.activeThreadId === threadId ? [] : current.messages,
|
|
1755
|
-
contextWindow: current.activeThreadId === threadId ? null : current.contextWindow,
|
|
1756
|
-
contextCompression: current.activeThreadId === threadId ? null : current.contextCompression,
|
|
1757
|
-
contextCompressionRevision: current.activeThreadId === threadId ? 0 : current.contextCompressionRevision,
|
|
1758
|
-
threads: current.threads.filter((thread) => thread.id !== threadId)
|
|
1759
|
-
}));
|
|
2020
|
+
if (stateRef.current.activeThreadId === threadId) startNewThread();
|
|
2021
|
+
update((current) => ({ ...current, threads: current.threads.filter((thread) => thread.id !== threadId) }));
|
|
1760
2022
|
} catch (error) {
|
|
1761
2023
|
if (!isAbortLike(error)) {
|
|
1762
|
-
failOperation("delete_thread", error);
|
|
2024
|
+
if (isCurrent()) failOperation("delete_thread", error);
|
|
1763
2025
|
throw error;
|
|
1764
2026
|
}
|
|
1765
2027
|
}
|
|
1766
|
-
}, [clearOperationError, client, failOperation,
|
|
1767
|
-
const cancelRun =
|
|
2028
|
+
}, [clearOperationError, client, failOperation, ownOperation, runtime, startNewThread, update]);
|
|
2029
|
+
const cancelRun = React7.useCallback(async () => {
|
|
1768
2030
|
const runId = stateRef.current.activeRunId;
|
|
1769
2031
|
if (!runId || stateRef.current.runState === "cancelling") return;
|
|
2032
|
+
const isCurrent = ownOperation({ run: true, stream: true });
|
|
1770
2033
|
const previousRunState = stateRef.current.runState;
|
|
1771
2034
|
clearOperationError();
|
|
1772
2035
|
update((current) => current.activeRunId === runId ? { ...current, runState: "cancelling" } : current);
|
|
1773
2036
|
try {
|
|
1774
2037
|
const result = await client.runs.cancel({ runId, idempotencyKey: runtime.createId() });
|
|
2038
|
+
if (!isCurrent()) return;
|
|
1775
2039
|
if (result.status === "cancelled") {
|
|
1776
2040
|
detach();
|
|
1777
2041
|
update((current) => ({
|
|
@@ -1796,14 +2060,15 @@ function useAgentChat(options = {}) {
|
|
|
1796
2060
|
}));
|
|
1797
2061
|
}
|
|
1798
2062
|
} catch (error) {
|
|
1799
|
-
if (!isAbortLike(error)) {
|
|
2063
|
+
if (isCurrent() && !isAbortLike(error)) {
|
|
1800
2064
|
update((current) => current.activeRunId === runId ? { ...current, runState: previousRunState } : current);
|
|
1801
2065
|
failOperation("cancel_run", error);
|
|
1802
2066
|
throw error;
|
|
1803
2067
|
}
|
|
1804
2068
|
}
|
|
1805
|
-
}, [clearOperationError, client, detach, failOperation, runtime, update]);
|
|
1806
|
-
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 });
|
|
1807
2072
|
const runId = stateRef.current.activeRunId || stateRef.current.messages.find((message) => message.parts.includes(part))?.runId;
|
|
1808
2073
|
clearOperationError();
|
|
1809
2074
|
if (!runId) {
|
|
@@ -1818,32 +2083,52 @@ function useAgentChat(options = {}) {
|
|
|
1818
2083
|
...response,
|
|
1819
2084
|
idempotencyKey: runtime.createId()
|
|
1820
2085
|
});
|
|
1821
|
-
await attachRun(result.run_id || runId);
|
|
2086
|
+
if (isCurrent()) await attachRun(result.run_id || runId);
|
|
1822
2087
|
} catch (error) {
|
|
1823
|
-
if (!isAbortLike(error)) failOperation("resume_hitl", error);
|
|
2088
|
+
if (isCurrent() && !isAbortLike(error)) failOperation("resume_hitl", error);
|
|
1824
2089
|
throw error;
|
|
1825
2090
|
}
|
|
1826
|
-
}, [attachRun, clearOperationError, client, failOperation, runtime]);
|
|
1827
|
-
const
|
|
1828
|
-
const loadOlder = React6.useCallback(async () => {
|
|
2091
|
+
}, [attachRun, clearOperationError, client, failOperation, runtime, ownOperation]);
|
|
2092
|
+
const loadOlder = React7.useCallback(() => {
|
|
1829
2093
|
const thread = stateRef.current.threads.find((item) => item.id === stateRef.current.activeThreadId);
|
|
1830
|
-
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();
|
|
1831
2100
|
clearOperationError();
|
|
1832
2101
|
setIsLoadingOlder(true);
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
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
|
+
}
|
|
1841
2126
|
}
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
}, [clearOperationError, client, failOperation, pageSize, update]);
|
|
1846
|
-
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) => {
|
|
1847
2132
|
const index = stateRef.current.messages.findIndex((item) => item.id === message.id);
|
|
1848
2133
|
const user = stateRef.current.messages.slice(0, index).reverse().find((item) => item.role === "user");
|
|
1849
2134
|
if (user) await submit({
|
|
@@ -1852,51 +2137,68 @@ function useAgentChat(options = {}) {
|
|
|
1852
2137
|
attachments: user.attachments
|
|
1853
2138
|
});
|
|
1854
2139
|
}, [submit]);
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
promotedAttachRef.current = promotedRunId;
|
|
1885
|
-
await attachRunRef.current(promotedRunId);
|
|
1886
|
-
}
|
|
1887
|
-
})().catch(() => void 0);
|
|
1888
|
-
}
|
|
1889
|
-
},
|
|
1890
|
-
onError(error) {
|
|
1891
|
-
update((current) => withAgentChatBackgroundError(current, error));
|
|
1892
|
-
},
|
|
1893
|
-
onRecovered() {
|
|
1894
|
-
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);
|
|
1895
2169
|
}
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
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(() => {
|
|
1900
2202
|
const threadId = options.activeThreadId;
|
|
1901
2203
|
const activeThread2 = stateRef.current.threads.find((thread) => thread.id === stateRef.current.activeThreadId);
|
|
1902
2204
|
if (shouldHydrateSelectedThread(threadId, stateRef.current.activeThreadId, Boolean(activeThread2?.isHydrated))) {
|
|
@@ -1943,15 +2245,15 @@ function useAgentChat(options = {}) {
|
|
|
1943
2245
|
}
|
|
1944
2246
|
|
|
1945
2247
|
// src/use-agent-resources.ts
|
|
1946
|
-
import * as
|
|
2248
|
+
import * as React8 from "react";
|
|
1947
2249
|
function useAgentThreads(options = {}) {
|
|
1948
2250
|
const { client, createAbortController } = useAgents24Runtime();
|
|
1949
|
-
const [threads, setThreads] =
|
|
1950
|
-
const [total, setTotal] =
|
|
1951
|
-
const [isLoading, setIsLoading] =
|
|
1952
|
-
const [error, setError] =
|
|
1953
|
-
const controllerRef =
|
|
1954
|
-
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 () => {
|
|
1955
2257
|
controllerRef.current?.abort();
|
|
1956
2258
|
const controller = createAbortController();
|
|
1957
2259
|
controllerRef.current = controller;
|
|
@@ -1970,7 +2272,7 @@ function useAgentThreads(options = {}) {
|
|
|
1970
2272
|
}
|
|
1971
2273
|
}
|
|
1972
2274
|
}, [client, createAbortController, options.limit, options.skip]);
|
|
1973
|
-
|
|
2275
|
+
React8.useEffect(() => {
|
|
1974
2276
|
void load();
|
|
1975
2277
|
return () => controllerRef.current?.abort();
|
|
1976
2278
|
}, [load]);
|
|
@@ -1978,11 +2280,11 @@ function useAgentThreads(options = {}) {
|
|
|
1978
2280
|
}
|
|
1979
2281
|
function useAgentThread(threadId, options = {}) {
|
|
1980
2282
|
const { client, createAbortController } = useAgents24Runtime();
|
|
1981
|
-
const [thread, setThread] =
|
|
1982
|
-
const [isLoading, setIsLoading] =
|
|
1983
|
-
const [error, setError] =
|
|
1984
|
-
const controllerRef =
|
|
1985
|
-
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 () => {
|
|
1986
2288
|
if (!threadId) {
|
|
1987
2289
|
controllerRef.current?.abort();
|
|
1988
2290
|
controllerRef.current = null;
|
|
@@ -2009,7 +2311,7 @@ function useAgentThread(threadId, options = {}) {
|
|
|
2009
2311
|
}
|
|
2010
2312
|
}
|
|
2011
2313
|
}, [client, createAbortController, options.beforeTurnIndex, options.limit, threadId]);
|
|
2012
|
-
|
|
2314
|
+
React8.useEffect(() => {
|
|
2013
2315
|
void load();
|
|
2014
2316
|
return () => controllerRef.current?.abort();
|
|
2015
2317
|
}, [load]);
|
|
@@ -2017,11 +2319,11 @@ function useAgentThread(threadId, options = {}) {
|
|
|
2017
2319
|
}
|
|
2018
2320
|
function useThreadComputer(threadId) {
|
|
2019
2321
|
const { client, createAbortController } = useAgents24Runtime();
|
|
2020
|
-
const [computer, setComputer] =
|
|
2021
|
-
const [isLoading, setIsLoading] =
|
|
2022
|
-
const [error, setError] =
|
|
2023
|
-
const controllerRef =
|
|
2024
|
-
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 () => {
|
|
2025
2327
|
controllerRef.current?.abort();
|
|
2026
2328
|
if (!threadId) {
|
|
2027
2329
|
setComputer(null);
|
|
@@ -2046,19 +2348,19 @@ function useThreadComputer(threadId) {
|
|
|
2046
2348
|
}
|
|
2047
2349
|
}
|
|
2048
2350
|
}, [client, createAbortController, threadId]);
|
|
2049
|
-
|
|
2351
|
+
React8.useEffect(() => {
|
|
2050
2352
|
void refresh();
|
|
2051
2353
|
return () => controllerRef.current?.abort();
|
|
2052
2354
|
}, [refresh]);
|
|
2053
|
-
const list =
|
|
2355
|
+
const list = React8.useCallback((input) => {
|
|
2054
2356
|
if (!threadId) throw new Error("A thread is required to list computer files");
|
|
2055
2357
|
return client.threads.computer.list({ threadId, ...input });
|
|
2056
2358
|
}, [client, threadId]);
|
|
2057
|
-
const read =
|
|
2359
|
+
const read = React8.useCallback((path) => {
|
|
2058
2360
|
if (!threadId) throw new Error("A thread is required to read a computer file");
|
|
2059
2361
|
return client.threads.computer.read({ threadId, path });
|
|
2060
2362
|
}, [client, threadId]);
|
|
2061
|
-
const createContentAccess =
|
|
2363
|
+
const createContentAccess = React8.useCallback((path, download = false) => {
|
|
2062
2364
|
if (!threadId) throw new Error("A thread is required to access a computer file");
|
|
2063
2365
|
return client.threads.computer.createContentAccess({ threadId, path, download });
|
|
2064
2366
|
}, [client, threadId]);
|
|
@@ -2066,16 +2368,16 @@ function useThreadComputer(threadId) {
|
|
|
2066
2368
|
}
|
|
2067
2369
|
function useAgentRun() {
|
|
2068
2370
|
const { client, createAbortController, createId } = useAgents24Runtime();
|
|
2069
|
-
const controllerRef =
|
|
2070
|
-
const [isAttached, setIsAttached] =
|
|
2071
|
-
const [error, setError] =
|
|
2072
|
-
|
|
2073
|
-
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(() => {
|
|
2074
2376
|
controllerRef.current?.abort();
|
|
2075
2377
|
controllerRef.current = null;
|
|
2076
2378
|
setIsAttached(false);
|
|
2077
2379
|
}, []);
|
|
2078
|
-
const attach =
|
|
2380
|
+
const attach = React8.useCallback(async (runId, onEvent, cursor) => {
|
|
2079
2381
|
detach();
|
|
2080
2382
|
const controller = createAbortController();
|
|
2081
2383
|
controllerRef.current = controller;
|
|
@@ -2091,7 +2393,7 @@ function useAgentRun() {
|
|
|
2091
2393
|
setIsAttached(false);
|
|
2092
2394
|
}
|
|
2093
2395
|
}, [client, createAbortController, detach]);
|
|
2094
|
-
const cancel =
|
|
2396
|
+
const cancel = React8.useCallback(async (runId) => {
|
|
2095
2397
|
const result = await client.runs.cancel({ runId, idempotencyKey: createId() });
|
|
2096
2398
|
detach();
|
|
2097
2399
|
return result;
|
|
@@ -2100,9 +2402,9 @@ function useAgentRun() {
|
|
|
2100
2402
|
}
|
|
2101
2403
|
function useAgentAttachments() {
|
|
2102
2404
|
const { client, createId } = useAgents24Runtime();
|
|
2103
|
-
const [uploads, setUploads] =
|
|
2104
|
-
const [isUploading, setIsUploading] =
|
|
2105
|
-
const uploadBatch =
|
|
2405
|
+
const [uploads, setUploads] = React8.useState({});
|
|
2406
|
+
const [isUploading, setIsUploading] = React8.useState(false);
|
|
2407
|
+
const uploadBatch = React8.useCallback(async (input) => {
|
|
2106
2408
|
setIsUploading(true);
|
|
2107
2409
|
try {
|
|
2108
2410
|
const results = await client.attachments.uploadBatch({ uploads: input.uploads, threadId: input.threadId, requestedAgentId: input.requestedAgentId, idempotencyKey: createId(), signal: input.signal });
|
|
@@ -2115,7 +2417,7 @@ function useAgentAttachments() {
|
|
|
2115
2417
|
setIsUploading(false);
|
|
2116
2418
|
}
|
|
2117
2419
|
}, [client, createId]);
|
|
2118
|
-
const createContentAccess =
|
|
2420
|
+
const createContentAccess = React8.useCallback(
|
|
2119
2421
|
(input) => client.attachments.createContentAccess(input),
|
|
2120
2422
|
[client]
|
|
2121
2423
|
);
|
|
@@ -2123,9 +2425,9 @@ function useAgentAttachments() {
|
|
|
2123
2425
|
}
|
|
2124
2426
|
function useAgentHitl() {
|
|
2125
2427
|
const { client, createId } = useAgents24Runtime();
|
|
2126
|
-
const [isResolving, setIsResolving] =
|
|
2127
|
-
const [lastResult, setLastResult] =
|
|
2128
|
-
const resume =
|
|
2428
|
+
const [isResolving, setIsResolving] = React8.useState(false);
|
|
2429
|
+
const [lastResult, setLastResult] = React8.useState(null);
|
|
2430
|
+
const resume = React8.useCallback(async (input) => {
|
|
2129
2431
|
setIsResolving(true);
|
|
2130
2432
|
try {
|
|
2131
2433
|
const result = await client.hitl.resume({ ...input, idempotencyKey: createId() });
|
|
@@ -2139,8 +2441,8 @@ function useAgentHitl() {
|
|
|
2139
2441
|
}
|
|
2140
2442
|
function useAgentMcp() {
|
|
2141
2443
|
const { client, createId } = useAgents24Runtime();
|
|
2142
|
-
const [isAuthorizing, setIsAuthorizing] =
|
|
2143
|
-
const startAuthorization =
|
|
2444
|
+
const [isAuthorizing, setIsAuthorizing] = React8.useState(false);
|
|
2445
|
+
const startAuthorization = React8.useCallback(async (input) => {
|
|
2144
2446
|
setIsAuthorizing(true);
|
|
2145
2447
|
try {
|
|
2146
2448
|
return await client.mcp.startAuthorization({ ...input, idempotencyKey: createId() });
|
|
@@ -2148,7 +2450,7 @@ function useAgentMcp() {
|
|
|
2148
2450
|
setIsAuthorizing(false);
|
|
2149
2451
|
}
|
|
2150
2452
|
}, [client, createId]);
|
|
2151
|
-
const redeemCallback =
|
|
2453
|
+
const redeemCallback = React8.useCallback(async (input) => client.mcp.redeemCallback({ ...input, idempotencyKey: createId() }), [client, createId]);
|
|
2152
2454
|
return { isAuthorizing, redeemCallback, startAuthorization };
|
|
2153
2455
|
}
|
|
2154
2456
|
export {
|
|
@@ -2161,11 +2463,13 @@ export {
|
|
|
2161
2463
|
assistantProjectionFromEvent,
|
|
2162
2464
|
attachmentResultToChatAttachment,
|
|
2163
2465
|
captureAgentChatThreadSelection,
|
|
2466
|
+
captureChatOperation,
|
|
2164
2467
|
chatAttachmentIds,
|
|
2165
2468
|
clearAgentChatBackgroundError,
|
|
2166
2469
|
clearAgentChatOperationError,
|
|
2167
2470
|
computerResourceTargets,
|
|
2168
2471
|
contextCompressionPartFromEvent,
|
|
2472
|
+
createChatObservation,
|
|
2169
2473
|
createPortableId,
|
|
2170
2474
|
createRuntimeEventFrameBuffer,
|
|
2171
2475
|
discardOptimisticSubmission,
|
|
@@ -2178,6 +2482,7 @@ export {
|
|
|
2178
2482
|
optimisticSubmissionTurn,
|
|
2179
2483
|
partsFromResponseBlocks,
|
|
2180
2484
|
preferNewestThreadSummary,
|
|
2485
|
+
prependThreadTimeline,
|
|
2181
2486
|
preserveMountedMessageIds,
|
|
2182
2487
|
promoteThreadMessage,
|
|
2183
2488
|
reconcileRefreshedThreads,
|
|
@@ -2190,6 +2495,7 @@ export {
|
|
|
2190
2495
|
upsertStableAssistantMessage,
|
|
2191
2496
|
useAgentAttachments,
|
|
2192
2497
|
useAgentChat,
|
|
2498
|
+
useAgentChatFeedback,
|
|
2193
2499
|
useAgentClient,
|
|
2194
2500
|
useAgentHitl,
|
|
2195
2501
|
useAgentMcp,
|
|
@@ -2197,6 +2503,7 @@ export {
|
|
|
2197
2503
|
useAgentThread,
|
|
2198
2504
|
useAgentThreads,
|
|
2199
2505
|
useAgents24Runtime,
|
|
2506
|
+
useChatObservation,
|
|
2200
2507
|
useThreadComputer,
|
|
2201
2508
|
useThreadMessageQueue,
|
|
2202
2509
|
withAgentChatBackgroundError,
|