@agents24/react 0.2.14 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compatibility.json +5 -5
- package/dist/chat-observation.d.ts +27 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +770 -404
- 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 +12 -5
- 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,42 @@ 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
|
|
857
|
-
const
|
|
858
|
-
const
|
|
859
|
-
const
|
|
860
|
-
const
|
|
861
|
-
const
|
|
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 admissionRef = React3.useRef(null);
|
|
1102
|
+
const initialAdmissionRef = React3.useRef(null);
|
|
1103
|
+
const versionRef = React3.useRef(0);
|
|
1104
|
+
const cursorRef = React3.useRef(0);
|
|
1105
|
+
const promotedRef = React3.useRef(/* @__PURE__ */ new Set());
|
|
1106
|
+
const threadRef = React3.useRef(threadId);
|
|
1107
|
+
const selectionGenerationRef = React3.useRef(0);
|
|
1108
|
+
if (threadRef.current !== threadId && initialAdmissionRef.current?.threadId !== threadId) selectionGenerationRef.current += 1;
|
|
1109
|
+
const transportRef = React3.useRef(transport);
|
|
1110
|
+
const onPromotedRef = React3.useRef(onPromoted);
|
|
862
1111
|
threadRef.current = threadId;
|
|
863
1112
|
transportRef.current = transport;
|
|
864
1113
|
onPromotedRef.current = onPromoted;
|
|
865
|
-
const
|
|
866
|
-
|
|
1114
|
+
const ownSelectionRef = React3.useRef(ownSelection);
|
|
1115
|
+
ownSelectionRef.current = ownSelection;
|
|
1116
|
+
const callbacksRef = React3.useRef({ onOptimistic, onDiscard, onUnknownPromotion });
|
|
1117
|
+
callbacksRef.current = { onOptimistic, onDiscard, onUnknownPromotion };
|
|
1118
|
+
const ownOperation = React3.useCallback((options) => ownSelectionRef.current?.(options) ?? captureChatOperation(() => ({
|
|
1119
|
+
generation: selectionGenerationRef.current,
|
|
1120
|
+
threadId: threadRef.current
|
|
1121
|
+
}), options), []);
|
|
1122
|
+
const confirmThreadAdmission = React3.useCallback((admittedThreadId) => {
|
|
1123
|
+
const initial = initialAdmissionRef.current;
|
|
1124
|
+
if (!initial || !initial.isCurrent()) return;
|
|
1125
|
+
initial.threadId = admittedThreadId;
|
|
1126
|
+
initial.resolve(admittedThreadId);
|
|
1127
|
+
}, []);
|
|
1128
|
+
const assertCurrent = (isCurrent) => {
|
|
1129
|
+
if (!isCurrent()) throw Object.assign(new Error("Thread selection changed."), { name: "AbortError" });
|
|
1130
|
+
};
|
|
867
1131
|
const knownMessage = (id, submissionId) => pendingRef.current.get(id) || (submissionId ? [...pendingRef.current.values()].find((item) => item.client_submission_id === submissionId) : void 0);
|
|
868
|
-
const hydrate =
|
|
1132
|
+
const hydrate = React3.useCallback((page, selectedThreadId) => {
|
|
869
1133
|
if (threadRef.current !== selectedThreadId) return;
|
|
870
1134
|
const pending = page.pending.filter((message) => !promotedRef.current.has(message.id)).map((message) => {
|
|
871
1135
|
const known = knownMessage(message.id, message.client_submission_id);
|
|
@@ -877,11 +1141,12 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
877
1141
|
setPendingMessages([...pending, ...optimistic]);
|
|
878
1142
|
setMessageQueue(page.queue);
|
|
879
1143
|
}, []);
|
|
880
|
-
const refresh =
|
|
1144
|
+
const refresh = React3.useCallback(async (selectedThreadId = threadRef.current, signal) => {
|
|
881
1145
|
if (!selectedThreadId) return null;
|
|
1146
|
+
const isCurrent = ownOperation();
|
|
882
1147
|
const version = ++versionRef.current;
|
|
883
|
-
const page = await transportRef.current.list(selectedThreadId);
|
|
884
|
-
if (version !== versionRef.current || threadRef.current !== selectedThreadId) return null;
|
|
1148
|
+
const page = await transportRef.current.list(selectedThreadId, signal);
|
|
1149
|
+
if (signal?.aborted || !isCurrent() || version !== versionRef.current || threadRef.current !== selectedThreadId) return null;
|
|
885
1150
|
for (const item of page.items) {
|
|
886
1151
|
if (item.role === "user" && item.status === "delivered" && item.run_id) {
|
|
887
1152
|
const pending = knownMessage(item.id, item.client_submission_id);
|
|
@@ -899,23 +1164,24 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
899
1164
|
}
|
|
900
1165
|
hydrate(page, selectedThreadId);
|
|
901
1166
|
return page;
|
|
902
|
-
}, [hydrate]);
|
|
903
|
-
const acceptNotice =
|
|
1167
|
+
}, [hydrate, ownOperation]);
|
|
1168
|
+
const acceptNotice = React3.useCallback((notice) => {
|
|
904
1169
|
if (notice.cursor && notice.cursor <= cursorRef.current) return false;
|
|
905
1170
|
if (notice.cursor) cursorRef.current = notice.cursor;
|
|
906
1171
|
const pending = knownMessage(notice.messageId, notice.clientSubmissionId);
|
|
907
|
-
if (notice.status === "delivered" && notice.runId &&
|
|
1172
|
+
if (notice.status === "delivered" && notice.runId && !promotedRef.current.has(notice.messageId)) {
|
|
908
1173
|
promotedRef.current.add(notice.messageId);
|
|
909
|
-
pendingRef.current.delete(pending.id);
|
|
910
1174
|
versionRef.current += 1;
|
|
911
|
-
|
|
912
|
-
|
|
1175
|
+
if (pending) {
|
|
1176
|
+
pendingRef.current.delete(pending.id);
|
|
1177
|
+
setPendingMessages((current) => current.filter((message) => message.id !== pending.id));
|
|
1178
|
+
onPromotedRef.current({ ...pending, id: notice.messageId, run_id: notice.runId, status: "delivered" }, notice.runId);
|
|
1179
|
+
} else if (threadRef.current) callbacksRef.current.onUnknownPromotion?.(notice.runId, threadRef.current);
|
|
913
1180
|
}
|
|
914
1181
|
if (notice.status === "removed") pendingRef.current.delete(notice.messageId);
|
|
915
|
-
void refresh().catch(() => void 0);
|
|
916
1182
|
return true;
|
|
917
|
-
}, [
|
|
918
|
-
const beginSubmission =
|
|
1183
|
+
}, []);
|
|
1184
|
+
const beginSubmission = React3.useCallback((input, queued = false) => {
|
|
919
1185
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
920
1186
|
const message = {
|
|
921
1187
|
id: input.clientSubmissionId,
|
|
@@ -937,34 +1203,60 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
937
1203
|
updated_at: now
|
|
938
1204
|
};
|
|
939
1205
|
pendingRef.current.set(message.id, { ...message, optimisticQueued: queued });
|
|
1206
|
+
if (!queued && !threadRef.current && !initialAdmissionRef.current) {
|
|
1207
|
+
let resolve;
|
|
1208
|
+
const done = new Promise((callback) => {
|
|
1209
|
+
resolve = callback;
|
|
1210
|
+
});
|
|
1211
|
+
initialAdmissionRef.current = { id: message.id, done, resolve, isCurrent: ownOperation({ adoptThread: true }) };
|
|
1212
|
+
}
|
|
940
1213
|
versionRef.current += 1;
|
|
941
1214
|
if (queued) setPendingMessages((current) => [...current, message]);
|
|
942
1215
|
else callbacksRef.current.onOptimistic?.(message, Boolean(input.uploading));
|
|
943
1216
|
return message;
|
|
944
|
-
}, []);
|
|
945
|
-
const discardSubmission =
|
|
1217
|
+
}, [ownOperation]);
|
|
1218
|
+
const discardSubmission = React3.useCallback((message, failure) => {
|
|
1219
|
+
if (initialAdmissionRef.current?.id === message.id) {
|
|
1220
|
+
initialAdmissionRef.current.failure = failure;
|
|
1221
|
+
initialAdmissionRef.current.resolve(null);
|
|
1222
|
+
initialAdmissionRef.current = null;
|
|
1223
|
+
}
|
|
946
1224
|
if (!pendingRef.current.has(message.id)) return;
|
|
947
1225
|
pendingRef.current.delete(message.id);
|
|
948
1226
|
callbacksRef.current.onDiscard?.(message);
|
|
949
1227
|
versionRef.current += 1;
|
|
950
1228
|
setPendingMessages((current) => current.filter((item) => item.client_submission_id !== message.client_submission_id));
|
|
951
1229
|
}, []);
|
|
952
|
-
const submit =
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
1230
|
+
const submit = React3.useCallback(async (input) => {
|
|
1231
|
+
const initial = !threadRef.current ? initialAdmissionRef.current : null;
|
|
1232
|
+
let selectedThreadId = threadRef.current;
|
|
1233
|
+
if (!selectedThreadId && !initial) throw new Error("No active Agent thread is available.");
|
|
1234
|
+
const isCurrent = ownOperation({ adoptThread: Boolean(initial) });
|
|
1235
|
+
const { queued: requestedQueued, prepare, attachments, uploading, ...submission } = input;
|
|
1236
|
+
const priorAdmission = admissionRef.current?.threadId === (selectedThreadId || "") ? admissionRef.current.done : void 0;
|
|
1237
|
+
const queued = Boolean(requestedQueued || priorAdmission || initial);
|
|
956
1238
|
const optimistic = beginSubmission({
|
|
957
1239
|
...submission,
|
|
958
1240
|
attachments,
|
|
959
1241
|
uploading,
|
|
960
1242
|
clientSubmissionId: submission.clientSubmissionId || submission.idempotencyKey
|
|
961
1243
|
}, queued);
|
|
1244
|
+
let releaseAdmission;
|
|
1245
|
+
const admission = { threadId: selectedThreadId || "", done: new Promise((resolve) => {
|
|
1246
|
+
releaseAdmission = resolve;
|
|
1247
|
+
}) };
|
|
1248
|
+
admissionRef.current = admission;
|
|
962
1249
|
try {
|
|
963
1250
|
const prepared = prepare ? await prepare() : {};
|
|
964
|
-
if (
|
|
1251
|
+
if (initial) selectedThreadId = await initial.done;
|
|
1252
|
+
if (priorAdmission) await priorAdmission;
|
|
1253
|
+
assertCurrent(isCurrent);
|
|
1254
|
+
if (!selectedThreadId) throw Object.assign(new Error("Initial message admission did not complete."), { name: "AbortError" });
|
|
1255
|
+
admission.threadId = selectedThreadId;
|
|
1256
|
+
pendingRef.current.set(optimistic.id, { ...optimistic, optimisticQueued: queued, attachment_ids: prepared.attachmentIds || optimistic.attachment_ids, attachments: prepared.attachments || attachments });
|
|
965
1257
|
const request = { ...submission, ...prepared, clientSubmissionId: optimistic.client_submission_id, threadId: selectedThreadId };
|
|
966
1258
|
const result = await retrySubmissionAdmission(() => transportRef.current.submit(request));
|
|
967
|
-
if (
|
|
1259
|
+
if (!isCurrent()) return result;
|
|
968
1260
|
versionRef.current += 1;
|
|
969
1261
|
const bound = {
|
|
970
1262
|
...optimistic,
|
|
@@ -991,53 +1283,105 @@ function useThreadMessageQueue({ threadId, transport, onPromoted, onOptimistic,
|
|
|
991
1283
|
void refresh().catch(() => void 0);
|
|
992
1284
|
return result;
|
|
993
1285
|
} catch (error) {
|
|
994
|
-
if (
|
|
1286
|
+
if (isCurrent()) discardSubmission(optimistic);
|
|
995
1287
|
throw error;
|
|
1288
|
+
} finally {
|
|
1289
|
+
void Promise.resolve(priorAdmission).then(() => {
|
|
1290
|
+
releaseAdmission();
|
|
1291
|
+
if (admissionRef.current === admission) admissionRef.current = null;
|
|
1292
|
+
});
|
|
996
1293
|
}
|
|
997
|
-
}, [beginSubmission, discardSubmission, refresh]);
|
|
998
|
-
const edit =
|
|
1294
|
+
}, [beginSubmission, discardSubmission, refresh, ownOperation]);
|
|
1295
|
+
const edit = React3.useCallback(async (message, input) => {
|
|
1296
|
+
const isCurrent = ownOperation();
|
|
999
1297
|
const updated = await transportRef.current.edit(message, input);
|
|
1298
|
+
assertCurrent(isCurrent);
|
|
1000
1299
|
versionRef.current += 1;
|
|
1001
1300
|
pendingRef.current.set(updated.id, updated);
|
|
1002
1301
|
await refresh(message.thread_id);
|
|
1003
1302
|
return updated;
|
|
1004
|
-
}, [refresh]);
|
|
1005
|
-
const remove =
|
|
1303
|
+
}, [refresh, ownOperation]);
|
|
1304
|
+
const remove = React3.useCallback(async (message) => {
|
|
1305
|
+
const isCurrent = ownOperation();
|
|
1006
1306
|
await transportRef.current.remove(message);
|
|
1307
|
+
assertCurrent(isCurrent);
|
|
1007
1308
|
versionRef.current += 1;
|
|
1008
1309
|
pendingRef.current.delete(message.id);
|
|
1009
1310
|
setPendingMessages((current) => current.filter((item) => item.id !== message.id));
|
|
1010
1311
|
await refresh(message.thread_id);
|
|
1011
|
-
}, [refresh]);
|
|
1012
|
-
const retractForEdit =
|
|
1312
|
+
}, [refresh, ownOperation]);
|
|
1313
|
+
const retractForEdit = React3.useCallback(async (message) => {
|
|
1314
|
+
const isCurrent = ownOperation();
|
|
1013
1315
|
await remove(message);
|
|
1316
|
+
assertCurrent(isCurrent);
|
|
1014
1317
|
return { text: message.text, attachment_ids: message.attachment_ids, attachments: message.attachments };
|
|
1015
|
-
}, [remove]);
|
|
1016
|
-
const resume =
|
|
1318
|
+
}, [remove, ownOperation]);
|
|
1319
|
+
const resume = React3.useCallback(async () => {
|
|
1017
1320
|
const selectedThreadId = threadRef.current;
|
|
1018
1321
|
if (!selectedThreadId) return null;
|
|
1322
|
+
const isCurrent = ownOperation();
|
|
1019
1323
|
const result = await transportRef.current.resume(selectedThreadId);
|
|
1324
|
+
if (!isCurrent()) return null;
|
|
1020
1325
|
await refresh(selectedThreadId);
|
|
1021
1326
|
return result;
|
|
1022
|
-
}, [refresh]);
|
|
1023
|
-
|
|
1327
|
+
}, [refresh, ownOperation]);
|
|
1328
|
+
const reset = React3.useCallback(() => {
|
|
1329
|
+
initialAdmissionRef.current?.resolve(null);
|
|
1330
|
+
initialAdmissionRef.current = null;
|
|
1331
|
+
admissionRef.current = null;
|
|
1332
|
+
selectionGenerationRef.current += 1;
|
|
1024
1333
|
versionRef.current += 1;
|
|
1025
1334
|
cursorRef.current = 0;
|
|
1026
1335
|
promotedRef.current.clear();
|
|
1027
1336
|
pendingRef.current.clear();
|
|
1028
1337
|
setPendingMessages([]);
|
|
1029
1338
|
setMessageQueue(null);
|
|
1339
|
+
}, []);
|
|
1340
|
+
React3.useEffect(() => {
|
|
1341
|
+
const initial = initialAdmissionRef.current;
|
|
1342
|
+
const adopting = Boolean(initial?.threadId && initial.threadId === threadId && initial.isCurrent());
|
|
1343
|
+
if (adopting) {
|
|
1344
|
+
for (const [id, message] of pendingRef.current) pendingRef.current.set(id, { ...message, thread_id: threadId });
|
|
1345
|
+
initialAdmissionRef.current = null;
|
|
1346
|
+
} else {
|
|
1347
|
+
initial?.resolve(null);
|
|
1348
|
+
initialAdmissionRef.current = null;
|
|
1349
|
+
pendingRef.current.clear();
|
|
1350
|
+
setPendingMessages([]);
|
|
1351
|
+
}
|
|
1352
|
+
versionRef.current += 1;
|
|
1353
|
+
cursorRef.current = 0;
|
|
1354
|
+
promotedRef.current.clear();
|
|
1355
|
+
setMessageQueue(null);
|
|
1030
1356
|
if (threadId) void refresh(threadId).catch(() => void 0);
|
|
1357
|
+
return () => {
|
|
1358
|
+
initialAdmissionRef.current?.resolve(null);
|
|
1359
|
+
};
|
|
1031
1360
|
}, [threadId, refresh]);
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1361
|
+
return {
|
|
1362
|
+
pendingMessages,
|
|
1363
|
+
messageQueue,
|
|
1364
|
+
refresh,
|
|
1365
|
+
hydrate,
|
|
1366
|
+
acceptNotice,
|
|
1367
|
+
beginSubmission,
|
|
1368
|
+
discardSubmission,
|
|
1369
|
+
reset,
|
|
1370
|
+
confirmThreadAdmission,
|
|
1371
|
+
hasPendingAdmission: () => Boolean(initialAdmissionRef.current?.isCurrent()),
|
|
1372
|
+
waitForInitialAdmission: () => {
|
|
1373
|
+
const initial = initialAdmissionRef.current;
|
|
1374
|
+
return initial ? initial.done.then((threadId2) => {
|
|
1375
|
+
if (!threadId2) throw initial.failure || Object.assign(new Error("Initial message admission detached."), { name: "AbortError" });
|
|
1376
|
+
return threadId2;
|
|
1377
|
+
}) : Promise.resolve(threadRef.current);
|
|
1378
|
+
},
|
|
1379
|
+
submit,
|
|
1380
|
+
edit,
|
|
1381
|
+
remove,
|
|
1382
|
+
retractForEdit,
|
|
1383
|
+
resume
|
|
1384
|
+
};
|
|
1041
1385
|
}
|
|
1042
1386
|
function optimisticSubmissionTurn(message) {
|
|
1043
1387
|
const user = {
|
|
@@ -1085,72 +1429,20 @@ function discardOptimisticSubmission(state, message) {
|
|
|
1085
1429
|
}
|
|
1086
1430
|
|
|
1087
1431
|
// 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
|
-
}
|
|
1432
|
+
import * as React7 from "react";
|
|
1141
1433
|
|
|
1142
1434
|
// src/use-agent-chat-thread-list.ts
|
|
1143
|
-
import * as
|
|
1435
|
+
import * as React4 from "react";
|
|
1144
1436
|
function summaryToStored(thread) {
|
|
1145
1437
|
return { ...thread, id: String(thread.id), messages: [], isHydrated: false };
|
|
1146
1438
|
}
|
|
1147
1439
|
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 =
|
|
1440
|
+
const [isLoadingThreads, setIsLoadingThreads] = React4.useState(false);
|
|
1441
|
+
const [isLoadingMoreThreads, setIsLoadingMoreThreads] = React4.useState(false);
|
|
1442
|
+
const [threadTotal, setThreadTotal] = React4.useState();
|
|
1443
|
+
const refreshPromiseRef = React4.useRef(null);
|
|
1444
|
+
const eventsDuringRefreshRef = React4.useRef(null);
|
|
1445
|
+
const refreshThreads = React4.useCallback(() => {
|
|
1154
1446
|
if (refreshPromiseRef.current) return refreshPromiseRef.current;
|
|
1155
1447
|
clearOperationError();
|
|
1156
1448
|
setIsLoadingThreads(true);
|
|
@@ -1185,7 +1477,7 @@ function useAgentChatThreadList({ client, threadPageSize, stateRef, update, clea
|
|
|
1185
1477
|
}).catch(() => void 0);
|
|
1186
1478
|
return request;
|
|
1187
1479
|
}, [clearOperationError, client, failOperation, threadPageSize, update]);
|
|
1188
|
-
const loadMoreThreads =
|
|
1480
|
+
const loadMoreThreads = React4.useCallback(async () => {
|
|
1189
1481
|
if (isLoadingMoreThreads) return;
|
|
1190
1482
|
const skip = stateRef.current.threads.length;
|
|
1191
1483
|
if (threadTotal !== void 0 && skip >= threadTotal) return;
|
|
@@ -1220,15 +1512,30 @@ function useAgentChatThreadList({ client, threadPageSize, stateRef, update, clea
|
|
|
1220
1512
|
}
|
|
1221
1513
|
|
|
1222
1514
|
// src/use-agent-chat-feedback.ts
|
|
1223
|
-
import * as
|
|
1224
|
-
function useAgentChatFeedback({ client, update, clearOperationError, failOperation }) {
|
|
1225
|
-
|
|
1515
|
+
import * as React5 from "react";
|
|
1516
|
+
function useAgentChatFeedback({ client, update, clearOperationError, failOperation, ownOperation, refreshMessages }) {
|
|
1517
|
+
const mutations = React5.useRef(/* @__PURE__ */ new Map());
|
|
1518
|
+
const captureRead = React5.useCallback(() => {
|
|
1519
|
+
const revisions = new Map([...mutations.current].map(([run, mutation]) => [run, mutation.readRevision]));
|
|
1520
|
+
return (incoming, current) => incoming.map((message) => {
|
|
1521
|
+
const mutation = message.runId ? mutations.current.get(message.runId) : void 0;
|
|
1522
|
+
const preserve = mutation && (mutation.pending > 0 || mutation.readRevision !== revisions.get(message.runId));
|
|
1523
|
+
const live = preserve && current.find((item) => item.role === "assistant" && item.runId === message.runId);
|
|
1524
|
+
return live ? { ...message, feedback: live.feedback } : message;
|
|
1525
|
+
});
|
|
1526
|
+
}, []);
|
|
1527
|
+
const setFeedback = React5.useCallback(async (message, input) => {
|
|
1226
1528
|
const runId = message.runId;
|
|
1227
1529
|
if (!runId || message.role !== "assistant" || message.isFinal === false) {
|
|
1228
1530
|
const error = new Error("Only persisted final responses can receive feedback.");
|
|
1229
1531
|
failOperation("set_feedback", error);
|
|
1230
1532
|
throw error;
|
|
1231
1533
|
}
|
|
1534
|
+
const isCurrent = ownOperation();
|
|
1535
|
+
const revision = (mutations.current.get(runId)?.revision ?? 0) + 1;
|
|
1536
|
+
const pending = (mutations.current.get(runId)?.pending ?? 0) + 1;
|
|
1537
|
+
mutations.current.set(runId, { revision, pending, readRevision: (mutations.current.get(runId)?.readRevision ?? 0) + 1 });
|
|
1538
|
+
const isLatest = () => mutations.current.get(runId)?.revision === revision;
|
|
1232
1539
|
const previous = message.feedback ?? null;
|
|
1233
1540
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1234
1541
|
const optimistic = input.rating ? {
|
|
@@ -1240,26 +1547,34 @@ function useAgentChatFeedback({ client, update, clearOperationError, failOperati
|
|
|
1240
1547
|
} : null;
|
|
1241
1548
|
const replaceFeedback = (current, feedback) => ({
|
|
1242
1549
|
...current,
|
|
1243
|
-
messages: current.messages.map((item) => item.
|
|
1550
|
+
messages: current.messages.map((item) => item.role === "assistant" && item.runId === runId ? { ...item, feedback } : item),
|
|
1244
1551
|
threads: current.threads.map((thread) => ({
|
|
1245
1552
|
...thread,
|
|
1246
|
-
messages: thread.messages.map((item) => item.
|
|
1553
|
+
messages: thread.messages.map((item) => item.role === "assistant" && item.runId === runId ? { ...item, feedback } : item)
|
|
1247
1554
|
}))
|
|
1248
1555
|
});
|
|
1249
1556
|
clearOperationError();
|
|
1250
1557
|
update((current) => replaceFeedback(current, optimistic));
|
|
1251
1558
|
try {
|
|
1252
1559
|
const result = await client.runs.setFeedback({ runId, ...input });
|
|
1253
|
-
update((current) => replaceFeedback(current, result.feedback));
|
|
1560
|
+
if (isLatest() && isCurrent()) update((current) => replaceFeedback(current, result.feedback));
|
|
1254
1561
|
} catch (error) {
|
|
1255
|
-
|
|
1562
|
+
if (isLatest() && isCurrent()) update((current) => {
|
|
1563
|
+
const restored = replaceFeedback(current, previous);
|
|
1564
|
+
return isCurrent() ? withAgentChatOperationError(restored, "set_feedback", error) : restored;
|
|
1565
|
+
});
|
|
1256
1566
|
throw error;
|
|
1567
|
+
} finally {
|
|
1568
|
+
const mutation = mutations.current.get(runId);
|
|
1569
|
+
mutations.current.set(runId, { ...mutation, readRevision: mutation.readRevision + 1, pending: mutation.pending - 1 });
|
|
1570
|
+
if (mutation.pending === 1 && isCurrent()) await refreshMessages().catch(() => void 0);
|
|
1257
1571
|
}
|
|
1258
|
-
}, [clearOperationError, client, failOperation, update]);
|
|
1572
|
+
}, [clearOperationError, client, failOperation, update, ownOperation, refreshMessages]);
|
|
1573
|
+
return { setFeedback, captureRead };
|
|
1259
1574
|
}
|
|
1260
1575
|
|
|
1261
1576
|
// src/use-agent-chat-queue-actions.ts
|
|
1262
|
-
import * as
|
|
1577
|
+
import * as React6 from "react";
|
|
1263
1578
|
function useAgentChatQueueActions({
|
|
1264
1579
|
queue,
|
|
1265
1580
|
stateRef,
|
|
@@ -1267,21 +1582,23 @@ function useAgentChatQueueActions({
|
|
|
1267
1582
|
clearOperationError,
|
|
1268
1583
|
failOperation,
|
|
1269
1584
|
refreshMessages,
|
|
1270
|
-
attachRun
|
|
1585
|
+
attachRun,
|
|
1586
|
+
ownOperation
|
|
1271
1587
|
}) {
|
|
1272
|
-
const steer =
|
|
1588
|
+
const steer = React6.useCallback(async (text2) => {
|
|
1273
1589
|
const threadId = stateRef.current.activeThreadId;
|
|
1274
1590
|
if (!threadId || !text2.trim()) throw new Error("An active thread and text are required to steer.");
|
|
1591
|
+
const isCurrent = ownOperation();
|
|
1275
1592
|
clearOperationError();
|
|
1276
1593
|
try {
|
|
1277
1594
|
await queue.submit({ mode: "steer", input: text2.trim(), idempotencyKey: createId() });
|
|
1278
|
-
await refreshMessages(threadId);
|
|
1595
|
+
if (isCurrent()) await refreshMessages(threadId);
|
|
1279
1596
|
} catch (error) {
|
|
1280
|
-
failOperation("submit", error);
|
|
1597
|
+
if (isCurrent()) failOperation("submit", error);
|
|
1281
1598
|
throw error;
|
|
1282
1599
|
}
|
|
1283
|
-
}, [clearOperationError, createId, failOperation, queue.submit, refreshMessages, stateRef]);
|
|
1284
|
-
const editPendingMessage =
|
|
1600
|
+
}, [clearOperationError, createId, failOperation, queue.submit, refreshMessages, stateRef, ownOperation]);
|
|
1601
|
+
const editPendingMessage = React6.useCallback(async (message, input) => {
|
|
1285
1602
|
await queue.edit(message, {
|
|
1286
1603
|
text: input.text,
|
|
1287
1604
|
attachmentIds: input.attachmentIds ?? message.attachment_ids,
|
|
@@ -1289,14 +1606,15 @@ function useAgentChatQueueActions({
|
|
|
1289
1606
|
requestedModelId: input.requestedModelId
|
|
1290
1607
|
});
|
|
1291
1608
|
}, [queue.edit]);
|
|
1292
|
-
const steerPendingMessage =
|
|
1609
|
+
const steerPendingMessage = React6.useCallback(async (message) => {
|
|
1293
1610
|
await queue.edit(message, { text: message.text, attachmentIds: message.attachment_ids, mode: "steer" });
|
|
1294
1611
|
}, [queue.edit]);
|
|
1295
|
-
const resumeQueue =
|
|
1612
|
+
const resumeQueue = React6.useCallback(async () => {
|
|
1296
1613
|
if (!stateRef.current.activeThreadId) return;
|
|
1614
|
+
const isCurrent = ownOperation();
|
|
1297
1615
|
const result = await queue.resume();
|
|
1298
|
-
if (result?.run_id) void attachRun(result.run_id).catch(() => void 0);
|
|
1299
|
-
}, [attachRun, queue.resume, stateRef]);
|
|
1616
|
+
if (isCurrent() && result?.run_id && stateRef.current.activeRunId !== result.run_id) void attachRun(result.run_id).catch(() => void 0);
|
|
1617
|
+
}, [attachRun, queue.resume, stateRef, ownOperation]);
|
|
1300
1618
|
return { steer, editPendingMessage, steerPendingMessage, removePendingMessage: queue.remove, resumeQueue };
|
|
1301
1619
|
}
|
|
1302
1620
|
|
|
@@ -1307,9 +1625,6 @@ function resolveInitialActiveThreadId(options, storedThreadId) {
|
|
|
1307
1625
|
function shouldHydrateSelectedThread(selectedThreadId, activeThreadId, activeThreadIsHydrated) {
|
|
1308
1626
|
return Boolean(selectedThreadId) && (selectedThreadId !== activeThreadId || !activeThreadIsHydrated);
|
|
1309
1627
|
}
|
|
1310
|
-
function isCurrentThreadOpenRequest(input) {
|
|
1311
|
-
return !input.signalAborted && input.requestGeneration === input.currentGeneration && input.requestedThreadId === input.activeThreadId;
|
|
1312
|
-
}
|
|
1313
1628
|
|
|
1314
1629
|
// src/use-agent-chat.ts
|
|
1315
1630
|
function activeRunIdFromThread(thread) {
|
|
@@ -1322,7 +1637,7 @@ function useAgentChat(options = {}) {
|
|
|
1322
1637
|
const client = options.client || runtime.client;
|
|
1323
1638
|
const pageSize = options.pageSize ?? 30;
|
|
1324
1639
|
const threadPageSize = options.threadPageSize ?? 50;
|
|
1325
|
-
const [state, setState] =
|
|
1640
|
+
const [state, setState] = React7.useState(() => ({
|
|
1326
1641
|
...initialAgentChatState,
|
|
1327
1642
|
activeThreadId: resolveInitialActiveThreadId(
|
|
1328
1643
|
options,
|
|
@@ -1330,34 +1645,38 @@ function useAgentChat(options = {}) {
|
|
|
1330
1645
|
),
|
|
1331
1646
|
threads: runtime.storage?.loadThreads() ?? []
|
|
1332
1647
|
}));
|
|
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 stateRef =
|
|
1648
|
+
const [isLoadingHistory, setIsLoadingHistory] = React7.useState(false);
|
|
1649
|
+
const [isLoadingOlder, setIsLoadingOlder] = React7.useState(false);
|
|
1650
|
+
const [isSubmitting, setIsSubmitting] = React7.useState(false);
|
|
1651
|
+
const streamControllerRef = React7.useRef(null);
|
|
1652
|
+
const threadOpenControllerRef = React7.useRef(null);
|
|
1653
|
+
const threadOpenGenerationRef = React7.useRef(0);
|
|
1654
|
+
const attachRunRef = React7.useRef(async () => void 0);
|
|
1655
|
+
const messageRefreshVersionRef = React7.useRef(0);
|
|
1656
|
+
const promotedRunHintRef = React7.useRef(null);
|
|
1657
|
+
const promotedAttachRef = React7.useRef(null);
|
|
1658
|
+
const olderRequestRef = React7.useRef(null);
|
|
1659
|
+
const submissionGenerationRef = React7.useRef(0);
|
|
1660
|
+
const stateRef = React7.useRef(state);
|
|
1346
1661
|
stateRef.current = state;
|
|
1347
|
-
const
|
|
1662
|
+
const ownOperation = React7.useCallback((options2) => captureChatOperation(() => ({
|
|
1663
|
+
generation: threadOpenGenerationRef.current,
|
|
1664
|
+
threadId: stateRef.current.activeThreadId,
|
|
1665
|
+
runId: stateRef.current.activeRunId,
|
|
1666
|
+
stream: streamControllerRef.current
|
|
1667
|
+
}), options2), []);
|
|
1668
|
+
const persist = React7.useCallback((next) => {
|
|
1348
1669
|
runtime.storage?.saveThreads(next.threads);
|
|
1349
1670
|
runtime.storage?.saveActiveThreadId?.(next.activeThreadId);
|
|
1350
1671
|
}, [runtime.storage]);
|
|
1351
|
-
const update =
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
return next;
|
|
1357
|
-
});
|
|
1672
|
+
const update = React7.useCallback((updater) => {
|
|
1673
|
+
const next = updater(stateRef.current);
|
|
1674
|
+
stateRef.current = next;
|
|
1675
|
+
persist(next);
|
|
1676
|
+
setState(next);
|
|
1358
1677
|
}, [persist]);
|
|
1359
|
-
const queueTransport =
|
|
1360
|
-
list: (threadId) => client.threads.messages.list({ threadId, limit: pageSize }),
|
|
1678
|
+
const queueTransport = React7.useMemo(() => ({
|
|
1679
|
+
list: (threadId, signal) => client.threads.messages.list({ threadId, limit: pageSize, signal }),
|
|
1361
1680
|
submit: (input) => client.threads.messages.submit(input),
|
|
1362
1681
|
edit: (message, input) => client.threads.messages.edit({
|
|
1363
1682
|
threadId: message.thread_id,
|
|
@@ -1374,40 +1693,46 @@ function useAgentChat(options = {}) {
|
|
|
1374
1693
|
},
|
|
1375
1694
|
resume: (threadId) => client.threads.resumeQueue({ threadId })
|
|
1376
1695
|
}), [client, pageSize]);
|
|
1377
|
-
const
|
|
1378
|
-
if (stateRef.current.activeThreadId !==
|
|
1696
|
+
const attachPromotion = React7.useCallback((runId, threadId) => {
|
|
1697
|
+
if (stateRef.current.activeThreadId !== threadId) return;
|
|
1379
1698
|
promotedRunHintRef.current = runId;
|
|
1699
|
+
if (stateRef.current.activeRunId === runId || promotedAttachRef.current === runId) return;
|
|
1700
|
+
promotedAttachRef.current = runId;
|
|
1701
|
+
const isCurrent = ownOperation();
|
|
1702
|
+
void attachRunRef.current(runId, 0).catch(() => {
|
|
1703
|
+
if (isCurrent() && promotedAttachRef.current === runId) promotedAttachRef.current = null;
|
|
1704
|
+
});
|
|
1705
|
+
}, [ownOperation]);
|
|
1706
|
+
const onQueuePromotion = React7.useCallback((message, runId) => {
|
|
1707
|
+
if (stateRef.current.activeThreadId !== message.thread_id) return;
|
|
1380
1708
|
const userMessage = threadTimelineToMessages([{ ...message, run_id: runId, status: "delivered" }])[0];
|
|
1381
1709
|
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]);
|
|
1710
|
+
attachPromotion(runId, message.thread_id);
|
|
1711
|
+
}, [update, attachPromotion]);
|
|
1389
1712
|
const queue = useThreadMessageQueue({
|
|
1390
1713
|
threadId: state.activeThreadId,
|
|
1391
1714
|
transport: queueTransport,
|
|
1715
|
+
ownOperation,
|
|
1392
1716
|
onPromoted: onQueuePromotion,
|
|
1717
|
+
onUnknownPromotion: attachPromotion,
|
|
1393
1718
|
onOptimistic: (message, uploading) => update((current) => optimisticSubmissionState(current, message, uploading)),
|
|
1394
1719
|
onDiscard: (message) => update((current) => discardOptimisticSubmission(current, message))
|
|
1395
1720
|
});
|
|
1396
1721
|
const { pendingMessages, messageQueue } = queue;
|
|
1397
|
-
const eventBuffer =
|
|
1722
|
+
const eventBuffer = React7.useMemo(
|
|
1398
1723
|
() => createRuntimeEventFrameBuffer(
|
|
1399
1724
|
(events) => update((current) => applyRuntimeEvents(current, events)),
|
|
1400
1725
|
runtimeEventFrameScheduler()
|
|
1401
1726
|
),
|
|
1402
1727
|
[update]
|
|
1403
1728
|
);
|
|
1404
|
-
const clearOperationError =
|
|
1729
|
+
const clearOperationError = React7.useCallback((operation) => {
|
|
1405
1730
|
update((current) => clearAgentChatOperationError(current, operation));
|
|
1406
1731
|
}, [update]);
|
|
1407
|
-
const clearBackgroundError =
|
|
1732
|
+
const clearBackgroundError = React7.useCallback(() => {
|
|
1408
1733
|
update(clearAgentChatBackgroundError);
|
|
1409
1734
|
}, [update]);
|
|
1410
|
-
const failOperation =
|
|
1735
|
+
const failOperation = React7.useCallback((operation, cause) => {
|
|
1411
1736
|
update((current) => withAgentChatOperationError(current, operation, cause));
|
|
1412
1737
|
}, [update]);
|
|
1413
1738
|
const {
|
|
@@ -1418,60 +1743,66 @@ function useAgentChat(options = {}) {
|
|
|
1418
1743
|
refreshThreads,
|
|
1419
1744
|
loadMoreThreads
|
|
1420
1745
|
} = useAgentChatThreadList({ client, threadPageSize, stateRef, update, clearOperationError, failOperation });
|
|
1421
|
-
const invalidateThreadOpen =
|
|
1746
|
+
const invalidateThreadOpen = React7.useCallback(() => {
|
|
1422
1747
|
threadOpenGenerationRef.current += 1;
|
|
1423
1748
|
threadOpenControllerRef.current?.abort("Thread selection changed.");
|
|
1424
1749
|
threadOpenControllerRef.current = null;
|
|
1750
|
+
messageRefreshVersionRef.current += 1;
|
|
1751
|
+
promotedAttachRef.current = null;
|
|
1752
|
+
promotedRunHintRef.current = null;
|
|
1753
|
+
olderRequestRef.current = null;
|
|
1754
|
+
queue.reset();
|
|
1755
|
+
setIsSubmitting(false);
|
|
1756
|
+
setIsLoadingOlder(false);
|
|
1425
1757
|
setIsLoadingHistory(false);
|
|
1426
|
-
}, []);
|
|
1427
|
-
const detach =
|
|
1758
|
+
}, [queue.reset]);
|
|
1759
|
+
const detach = React7.useCallback(() => {
|
|
1428
1760
|
eventBuffer.flush();
|
|
1429
1761
|
streamControllerRef.current?.abort();
|
|
1430
1762
|
streamControllerRef.current = null;
|
|
1431
1763
|
update((current) => ({ ...current, streamingMessageId: null, runState: current.activeRunId ? "reconnecting" : "idle" }));
|
|
1432
1764
|
}, [eventBuffer, update]);
|
|
1433
|
-
const
|
|
1765
|
+
const refreshMessagesRef = React7.useRef(async () => void 0);
|
|
1766
|
+
const { setFeedback, captureRead: captureFeedbackRead } = useAgentChatFeedback({
|
|
1767
|
+
client,
|
|
1768
|
+
update,
|
|
1769
|
+
clearOperationError,
|
|
1770
|
+
failOperation,
|
|
1771
|
+
ownOperation,
|
|
1772
|
+
refreshMessages: React7.useCallback(() => refreshMessagesRef.current(), [])
|
|
1773
|
+
});
|
|
1774
|
+
const refreshMessages = React7.useCallback(async (threadId, promotedRunId, signal) => {
|
|
1434
1775
|
const target = threadId || stateRef.current.activeThreadId;
|
|
1435
1776
|
if (!target) return;
|
|
1777
|
+
const isCurrent = ownOperation();
|
|
1778
|
+
const reconcileFeedback = captureFeedbackRead();
|
|
1436
1779
|
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;
|
|
1780
|
+
const page = await client.threads.messages.list({ threadId: target, limit: pageSize, ...signal ? { signal } : {} });
|
|
1781
|
+
if (!isCurrent() || stateRef.current.activeThreadId !== target || version !== messageRefreshVersionRef.current) return;
|
|
1439
1782
|
queue.hydrate(page, target);
|
|
1440
1783
|
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(() => () => {
|
|
1784
|
+
if (promoted && page.items.some((message) => message.run_id === promoted)) promotedRunHintRef.current = null;
|
|
1785
|
+
update((current) => {
|
|
1786
|
+
if (!isCurrent() || current.activeThreadId !== target) return current;
|
|
1787
|
+
const messages = reconcileThreadTimeline(
|
|
1788
|
+
current.messages,
|
|
1789
|
+
reconcileFeedback(threadTimelineToMessages(page.items), current.messages),
|
|
1790
|
+
current.activeRunId,
|
|
1791
|
+
current.streamingMessageId
|
|
1792
|
+
);
|
|
1793
|
+
return { ...current, messages, threads: current.threads.map((thread) => thread.id === target ? { ...thread, messages } : thread) };
|
|
1794
|
+
});
|
|
1795
|
+
}, [client, ownOperation, pageSize, queue.hydrate, update, captureFeedbackRead]);
|
|
1796
|
+
refreshMessagesRef.current = refreshMessages;
|
|
1797
|
+
React7.useEffect(() => () => {
|
|
1798
|
+
threadOpenGenerationRef.current += 1;
|
|
1468
1799
|
eventBuffer.dispose();
|
|
1469
1800
|
streamControllerRef.current?.abort();
|
|
1470
1801
|
threadOpenControllerRef.current?.abort();
|
|
1471
1802
|
}, [eventBuffer]);
|
|
1472
|
-
const openThread =
|
|
1803
|
+
const openThread = React7.useCallback(async (threadId) => {
|
|
1473
1804
|
const previousSelection = captureAgentChatThreadSelection(stateRef.current);
|
|
1474
|
-
|
|
1805
|
+
invalidateThreadOpen();
|
|
1475
1806
|
const requestController = runtime.createAbortController();
|
|
1476
1807
|
threadOpenControllerRef.current = requestController;
|
|
1477
1808
|
const requestGeneration = threadOpenGenerationRef.current + 1;
|
|
@@ -1491,6 +1822,8 @@ function useAgentChat(options = {}) {
|
|
|
1491
1822
|
runState: "idle",
|
|
1492
1823
|
streamingMessageId: null
|
|
1493
1824
|
}));
|
|
1825
|
+
const isCurrent = ownOperation();
|
|
1826
|
+
const reconcileFeedback = captureFeedbackRead();
|
|
1494
1827
|
setIsLoadingHistory(true);
|
|
1495
1828
|
try {
|
|
1496
1829
|
const detail = await client.threads.get({
|
|
@@ -1498,39 +1831,21 @@ function useAgentChat(options = {}) {
|
|
|
1498
1831
|
limit: pageSize,
|
|
1499
1832
|
signal: requestController.signal
|
|
1500
1833
|
});
|
|
1501
|
-
if (!
|
|
1502
|
-
activeThreadId: stateRef.current.activeThreadId,
|
|
1503
|
-
currentGeneration: threadOpenGenerationRef.current,
|
|
1504
|
-
requestGeneration,
|
|
1505
|
-
requestedThreadId: threadId,
|
|
1506
|
-
signalAborted: requestController.signal.aborted
|
|
1507
|
-
})) return;
|
|
1834
|
+
if (!isCurrent()) return;
|
|
1508
1835
|
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;
|
|
1836
|
+
if (!isCurrent()) return;
|
|
1516
1837
|
queue.hydrate(timeline, threadId);
|
|
1517
|
-
const messages =
|
|
1838
|
+
const messages = reconcileFeedback(threadTimelineToMessages(timeline.items), stateRef.current.messages);
|
|
1518
1839
|
const context = latestThreadContext(detail);
|
|
1519
1840
|
const stored = {
|
|
1520
1841
|
...detail,
|
|
1521
1842
|
messages,
|
|
1522
1843
|
isHydrated: true,
|
|
1523
|
-
hasOlderTurns: Boolean(
|
|
1524
|
-
nextBeforeTurnIndex:
|
|
1844
|
+
hasOlderTurns: Boolean(timeline.paging.has_more),
|
|
1845
|
+
nextBeforeTurnIndex: timeline.paging.next_before_turn_index,
|
|
1525
1846
|
...context
|
|
1526
1847
|
};
|
|
1527
|
-
update((current) =>
|
|
1528
|
-
activeThreadId: current.activeThreadId,
|
|
1529
|
-
currentGeneration: threadOpenGenerationRef.current,
|
|
1530
|
-
requestGeneration,
|
|
1531
|
-
requestedThreadId: threadId,
|
|
1532
|
-
signalAborted: requestController.signal.aborted
|
|
1533
|
-
}) ? {
|
|
1848
|
+
update((current) => isCurrent() ? {
|
|
1534
1849
|
...current,
|
|
1535
1850
|
activeThreadId: threadId,
|
|
1536
1851
|
messages,
|
|
@@ -1540,13 +1855,7 @@ function useAgentChat(options = {}) {
|
|
|
1540
1855
|
threads: current.threads.some((thread) => thread.id === threadId) ? current.threads.map((thread) => thread.id === threadId ? stored : thread) : [stored, ...current.threads]
|
|
1541
1856
|
} : current);
|
|
1542
1857
|
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);
|
|
1858
|
+
if (activeRunId && isCurrent()) void attachRunRef.current(activeRunId).catch(() => void 0);
|
|
1550
1859
|
} catch (error) {
|
|
1551
1860
|
if (!requestController.signal.aborted && !isAbortLike(error) && requestGeneration === threadOpenGenerationRef.current) {
|
|
1552
1861
|
failOperation("open_thread", error);
|
|
@@ -1562,28 +1871,34 @@ function useAgentChat(options = {}) {
|
|
|
1562
1871
|
setIsLoadingHistory(false);
|
|
1563
1872
|
}
|
|
1564
1873
|
}
|
|
1565
|
-
}, [clearOperationError, client, detach, failOperation, pageSize, queue.hydrate, runtime, update]);
|
|
1566
|
-
const consumeEvent =
|
|
1874
|
+
}, [clearOperationError, client, detach, failOperation, pageSize, queue.hydrate, runtime, update, invalidateThreadOpen, ownOperation, captureFeedbackRead]);
|
|
1875
|
+
const consumeEvent = React7.useCallback((event) => {
|
|
1567
1876
|
const eventThreadId = typeof event.payload?.thread_id === "string" ? event.payload.thread_id.trim() : "";
|
|
1568
1877
|
if (eventThreadId && !stateRef.current.activeThreadId) {
|
|
1878
|
+
queue.confirmThreadAdmission(eventThreadId);
|
|
1569
1879
|
update((current) => adoptStreamingThread(current, eventThreadId));
|
|
1570
|
-
|
|
1880
|
+
setIsSubmitting(false);
|
|
1571
1881
|
}
|
|
1572
1882
|
eventBuffer.push(event);
|
|
1573
|
-
}, [eventBuffer, update]);
|
|
1574
|
-
const attachRun =
|
|
1883
|
+
}, [eventBuffer, update, queue.confirmThreadAdmission]);
|
|
1884
|
+
const attachRun = React7.useCallback(async (runId, cursor) => {
|
|
1885
|
+
eventBuffer.flush();
|
|
1575
1886
|
streamControllerRef.current?.abort();
|
|
1576
1887
|
const controller = runtime.createAbortController();
|
|
1577
1888
|
streamControllerRef.current = controller;
|
|
1578
|
-
|
|
1889
|
+
const isCurrent = ownOperation({ stream: true });
|
|
1890
|
+
clearOperationError("attach_run");
|
|
1579
1891
|
update((current) => ({ ...current, activeRunId: runId, runState: "reconnecting", error: null }));
|
|
1580
1892
|
try {
|
|
1581
|
-
const result = await client.runs.attach({ runId, cursor, signal: controller.signal },
|
|
1893
|
+
const result = await client.runs.attach({ runId, cursor, leaseSeconds: 60, signal: controller.signal }, (event) => {
|
|
1894
|
+
if (isCurrent()) consumeEvent(event);
|
|
1895
|
+
});
|
|
1896
|
+
if (!isCurrent()) return;
|
|
1582
1897
|
eventBuffer.flush();
|
|
1583
1898
|
update((current) => ({ ...current, activeRunId: result.detached ? runId : current.activeRunId, cursor: result.cursor ?? current.cursor, runState: result.detached ? "reconnecting" : current.runState }));
|
|
1584
1899
|
if (!result.detached && result.threadId) await refreshMessages(result.threadId);
|
|
1585
1900
|
} catch (error) {
|
|
1586
|
-
if (!isAbortLike(error)) {
|
|
1901
|
+
if (isCurrent() && !isAbortLike(error)) {
|
|
1587
1902
|
update((current) => ({ ...current, runState: "failed", streamingMessageId: null }));
|
|
1588
1903
|
failOperation("attach_run", error);
|
|
1589
1904
|
throw error;
|
|
@@ -1591,15 +1906,14 @@ function useAgentChat(options = {}) {
|
|
|
1591
1906
|
} finally {
|
|
1592
1907
|
if (streamControllerRef.current === controller) streamControllerRef.current = null;
|
|
1593
1908
|
}
|
|
1594
|
-
}, [clearOperationError, client, consumeEvent, eventBuffer, failOperation, refreshMessages, runtime, update]);
|
|
1909
|
+
}, [clearOperationError, client, consumeEvent, eventBuffer, failOperation, refreshMessages, runtime, update, ownOperation]);
|
|
1595
1910
|
attachRunRef.current = attachRun;
|
|
1596
|
-
const submit =
|
|
1911
|
+
const submit = React7.useCallback(async (input) => {
|
|
1597
1912
|
if (!input.text.trim() && !input.attachmentIds?.length && !input.uploads?.length) return;
|
|
1598
|
-
if (firstSubmissionRef.current && !stateRef.current.activeThreadId) {
|
|
1599
|
-
await firstSubmissionRef.current;
|
|
1600
|
-
}
|
|
1601
1913
|
const existingThreadId = stateRef.current.activeThreadId;
|
|
1602
|
-
if (existingThreadId) {
|
|
1914
|
+
if (existingThreadId || queue.hasPendingAdmission()) {
|
|
1915
|
+
const isCurrent = ownOperation({ adoptThread: !existingThreadId });
|
|
1916
|
+
const submissionGeneration = ++submissionGenerationRef.current;
|
|
1603
1917
|
clearOperationError();
|
|
1604
1918
|
setIsSubmitting(true);
|
|
1605
1919
|
try {
|
|
@@ -1627,7 +1941,7 @@ function useAgentChat(options = {}) {
|
|
|
1627
1941
|
prepare: async () => {
|
|
1628
1942
|
const uploaded = input.uploads?.length ? await client.attachments.uploadBatch({
|
|
1629
1943
|
uploads: input.uploads,
|
|
1630
|
-
threadId: existingThreadId,
|
|
1944
|
+
threadId: existingThreadId || void 0,
|
|
1631
1945
|
requestedAgentId: input.requestedAgentId,
|
|
1632
1946
|
idempotencyKey: runtime.createId()
|
|
1633
1947
|
}) : [];
|
|
@@ -1644,21 +1958,21 @@ function useAgentChat(options = {}) {
|
|
|
1644
1958
|
}
|
|
1645
1959
|
});
|
|
1646
1960
|
} catch (error) {
|
|
1647
|
-
failOperation("submit", error);
|
|
1961
|
+
if (isCurrent()) failOperation("submit", error);
|
|
1648
1962
|
throw error;
|
|
1649
1963
|
} finally {
|
|
1650
|
-
setIsSubmitting(false);
|
|
1964
|
+
if (isCurrent() && submissionGeneration === submissionGenerationRef.current) setIsSubmitting(false);
|
|
1651
1965
|
}
|
|
1652
1966
|
return;
|
|
1653
1967
|
}
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
});
|
|
1968
|
+
invalidateThreadOpen();
|
|
1969
|
+
const submissionOwner = ownOperation({ adoptThread: true });
|
|
1657
1970
|
const stream = (async () => {
|
|
1658
|
-
invalidateThreadOpen();
|
|
1659
1971
|
detach();
|
|
1660
1972
|
const controller = runtime.createAbortController();
|
|
1661
1973
|
streamControllerRef.current = controller;
|
|
1974
|
+
const isCurrent = ownOperation({ stream: true, adoptThread: true });
|
|
1975
|
+
const submissionGeneration = ++submissionGenerationRef.current;
|
|
1662
1976
|
const ticket = queue.beginSubmission({
|
|
1663
1977
|
input: input.text.trim(),
|
|
1664
1978
|
clientSubmissionId: runtime.createId(),
|
|
@@ -1689,6 +2003,7 @@ function useAgentChat(options = {}) {
|
|
|
1689
2003
|
idempotencyKey: runtime.createId(),
|
|
1690
2004
|
signal: controller.signal
|
|
1691
2005
|
}) : [];
|
|
2006
|
+
if (!isCurrent()) return;
|
|
1692
2007
|
if (hasPendingUploads && uploaded.length !== input.uploads?.length) {
|
|
1693
2008
|
throw new Error("Attachment upload returned an incomplete batch");
|
|
1694
2009
|
}
|
|
@@ -1701,7 +2016,20 @@ function useAgentChat(options = {}) {
|
|
|
1701
2016
|
runState: "streaming"
|
|
1702
2017
|
}));
|
|
1703
2018
|
}
|
|
1704
|
-
const result = await client.chat.stream({
|
|
2019
|
+
const result = await client.chat.stream({
|
|
2020
|
+
leaseSeconds: 60,
|
|
2021
|
+
clientSubmissionId: ticket.client_submission_id,
|
|
2022
|
+
input: input.text.trim(),
|
|
2023
|
+
threadId: stateRef.current.activeThreadId || void 0,
|
|
2024
|
+
attachmentIds,
|
|
2025
|
+
requestedModelId: input.requestedModelId,
|
|
2026
|
+
requestedAgentId: input.requestedAgentId,
|
|
2027
|
+
idempotencyKey,
|
|
2028
|
+
signal: controller.signal
|
|
2029
|
+
}, (event) => {
|
|
2030
|
+
if (isCurrent()) consumeEvent(event);
|
|
2031
|
+
});
|
|
2032
|
+
if (!isCurrent()) return;
|
|
1705
2033
|
eventBuffer.flush();
|
|
1706
2034
|
update((current) => {
|
|
1707
2035
|
const threadId = result.threadId || current.activeThreadId;
|
|
@@ -1717,8 +2045,9 @@ function useAgentChat(options = {}) {
|
|
|
1717
2045
|
if (result.threadId) await refreshThreads().catch(() => void 0);
|
|
1718
2046
|
if (result.threadId && !result.detached) await refreshMessages(result.threadId);
|
|
1719
2047
|
} catch (error) {
|
|
2048
|
+
if (!isCurrent()) return;
|
|
1720
2049
|
const admitted = Boolean(stateRef.current.messages.find((message) => message.id === userId)?.runId);
|
|
1721
|
-
if (!admitted) queue.discardSubmission(ticket);
|
|
2050
|
+
if (!admitted) queue.discardSubmission(ticket, error);
|
|
1722
2051
|
if (!isAbortLike(error)) {
|
|
1723
2052
|
update((current) => ({
|
|
1724
2053
|
...current,
|
|
@@ -1729,19 +2058,17 @@ function useAgentChat(options = {}) {
|
|
|
1729
2058
|
throw error;
|
|
1730
2059
|
}
|
|
1731
2060
|
} finally {
|
|
1732
|
-
|
|
2061
|
+
if (!stateRef.current.messages.some((message) => (message.id === ticket.id || message.clientSubmissionId === ticket.client_submission_id) && message.runId)) queue.discardSubmission(ticket);
|
|
2062
|
+
if (isCurrent() && submissionGeneration === submissionGenerationRef.current) setIsSubmitting(false);
|
|
1733
2063
|
if (streamControllerRef.current === controller) streamControllerRef.current = null;
|
|
1734
2064
|
}
|
|
1735
2065
|
})();
|
|
1736
|
-
const accepted = Promise.race([stream, admission]);
|
|
1737
|
-
firstSubmissionRef.current = accepted;
|
|
1738
2066
|
try {
|
|
1739
|
-
await
|
|
1740
|
-
}
|
|
1741
|
-
if (
|
|
1742
|
-
firstSubmissionAdmittedRef.current = null;
|
|
2067
|
+
await Promise.race([stream, queue.waitForInitialAdmission()]);
|
|
2068
|
+
} catch (error) {
|
|
2069
|
+
if (submissionOwner() || !isAbortLike(error)) throw error;
|
|
1743
2070
|
}
|
|
1744
|
-
}, [clearOperationError, client, consumeEvent, detach, eventBuffer, failOperation, invalidateThreadOpen, queue.submit, refreshMessages, refreshThreads, runtime, update]);
|
|
2071
|
+
}, [clearOperationError, client, consumeEvent, detach, eventBuffer, failOperation, invalidateThreadOpen, queue.submit, refreshMessages, refreshThreads, runtime, update, ownOperation]);
|
|
1745
2072
|
const { steer, editPendingMessage, steerPendingMessage, removePendingMessage, resumeQueue } = useAgentChatQueueActions({
|
|
1746
2073
|
queue,
|
|
1747
2074
|
stateRef,
|
|
@@ -1749,42 +2076,38 @@ function useAgentChat(options = {}) {
|
|
|
1749
2076
|
clearOperationError,
|
|
1750
2077
|
failOperation,
|
|
1751
2078
|
refreshMessages,
|
|
1752
|
-
attachRun
|
|
2079
|
+
attachRun,
|
|
2080
|
+
ownOperation
|
|
1753
2081
|
});
|
|
1754
|
-
const startNewThread =
|
|
2082
|
+
const startNewThread = React7.useCallback(() => {
|
|
1755
2083
|
invalidateThreadOpen();
|
|
1756
2084
|
detach();
|
|
1757
2085
|
update((current) => ({ ...initialAgentChatState, threads: current.threads }));
|
|
1758
2086
|
}, [detach, invalidateThreadOpen, update]);
|
|
1759
|
-
const deleteThread =
|
|
1760
|
-
|
|
2087
|
+
const deleteThread = React7.useCallback(async (threadId) => {
|
|
2088
|
+
const isCurrent = ownOperation();
|
|
1761
2089
|
clearOperationError();
|
|
1762
2090
|
try {
|
|
1763
2091
|
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
|
-
}));
|
|
2092
|
+
if (stateRef.current.activeThreadId === threadId) startNewThread();
|
|
2093
|
+
update((current) => ({ ...current, threads: current.threads.filter((thread) => thread.id !== threadId) }));
|
|
1773
2094
|
} catch (error) {
|
|
1774
2095
|
if (!isAbortLike(error)) {
|
|
1775
|
-
failOperation("delete_thread", error);
|
|
2096
|
+
if (isCurrent()) failOperation("delete_thread", error);
|
|
1776
2097
|
throw error;
|
|
1777
2098
|
}
|
|
1778
2099
|
}
|
|
1779
|
-
}, [clearOperationError, client, failOperation,
|
|
1780
|
-
const cancelRun =
|
|
2100
|
+
}, [clearOperationError, client, failOperation, ownOperation, runtime, startNewThread, update]);
|
|
2101
|
+
const cancelRun = React7.useCallback(async () => {
|
|
1781
2102
|
const runId = stateRef.current.activeRunId;
|
|
1782
2103
|
if (!runId || stateRef.current.runState === "cancelling") return;
|
|
2104
|
+
const isCurrent = ownOperation({ run: true, stream: true });
|
|
1783
2105
|
const previousRunState = stateRef.current.runState;
|
|
1784
2106
|
clearOperationError();
|
|
1785
2107
|
update((current) => current.activeRunId === runId ? { ...current, runState: "cancelling" } : current);
|
|
1786
2108
|
try {
|
|
1787
2109
|
const result = await client.runs.cancel({ runId, idempotencyKey: runtime.createId() });
|
|
2110
|
+
if (!isCurrent()) return;
|
|
1788
2111
|
if (result.status === "cancelled") {
|
|
1789
2112
|
detach();
|
|
1790
2113
|
update((current) => ({
|
|
@@ -1809,14 +2132,15 @@ function useAgentChat(options = {}) {
|
|
|
1809
2132
|
}));
|
|
1810
2133
|
}
|
|
1811
2134
|
} catch (error) {
|
|
1812
|
-
if (!isAbortLike(error)) {
|
|
2135
|
+
if (isCurrent() && !isAbortLike(error)) {
|
|
1813
2136
|
update((current) => current.activeRunId === runId ? { ...current, runState: previousRunState } : current);
|
|
1814
2137
|
failOperation("cancel_run", error);
|
|
1815
2138
|
throw error;
|
|
1816
2139
|
}
|
|
1817
2140
|
}
|
|
1818
|
-
}, [clearOperationError, client, detach, failOperation, runtime, update]);
|
|
1819
|
-
const resumeHitl =
|
|
2141
|
+
}, [clearOperationError, client, detach, failOperation, runtime, update, ownOperation]);
|
|
2142
|
+
const resumeHitl = React7.useCallback(async (part, response) => {
|
|
2143
|
+
const isCurrent = ownOperation({ run: true, stream: true });
|
|
1820
2144
|
const runId = stateRef.current.activeRunId || stateRef.current.messages.find((message) => message.parts.includes(part))?.runId;
|
|
1821
2145
|
clearOperationError();
|
|
1822
2146
|
if (!runId) {
|
|
@@ -1831,32 +2155,52 @@ function useAgentChat(options = {}) {
|
|
|
1831
2155
|
...response,
|
|
1832
2156
|
idempotencyKey: runtime.createId()
|
|
1833
2157
|
});
|
|
1834
|
-
await attachRun(result.run_id || runId);
|
|
2158
|
+
if (isCurrent()) await attachRun(result.run_id || runId);
|
|
1835
2159
|
} catch (error) {
|
|
1836
|
-
if (!isAbortLike(error)) failOperation("resume_hitl", error);
|
|
2160
|
+
if (isCurrent() && !isAbortLike(error)) failOperation("resume_hitl", error);
|
|
1837
2161
|
throw error;
|
|
1838
2162
|
}
|
|
1839
|
-
}, [attachRun, clearOperationError, client, failOperation, runtime]);
|
|
1840
|
-
const
|
|
1841
|
-
const loadOlder = React6.useCallback(async () => {
|
|
2163
|
+
}, [attachRun, clearOperationError, client, failOperation, runtime, ownOperation]);
|
|
2164
|
+
const loadOlder = React7.useCallback(() => {
|
|
1842
2165
|
const thread = stateRef.current.threads.find((item) => item.id === stateRef.current.activeThreadId);
|
|
1843
|
-
if (
|
|
2166
|
+
if (thread?.nextBeforeTurnIndex == null) return Promise.resolve();
|
|
2167
|
+
const cursor = thread.nextBeforeTurnIndex;
|
|
2168
|
+
const reconcileFeedback = captureFeedbackRead();
|
|
2169
|
+
const key = `${threadOpenGenerationRef.current}:${thread.id}:${cursor}`;
|
|
2170
|
+
if (olderRequestRef.current?.key === key) return olderRequestRef.current.promise;
|
|
2171
|
+
const isCurrent = ownOperation();
|
|
1844
2172
|
clearOperationError();
|
|
1845
2173
|
setIsLoadingOlder(true);
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
2174
|
+
const promise = (async () => {
|
|
2175
|
+
try {
|
|
2176
|
+
const page = await client.threads.messages.list({ threadId: thread.id, limit: pageSize, before: cursor });
|
|
2177
|
+
if (!isCurrent()) return;
|
|
2178
|
+
const older = reconcileFeedback(threadTimelineToMessages(page.items), stateRef.current.messages);
|
|
2179
|
+
update((current) => ({
|
|
2180
|
+
...current,
|
|
2181
|
+
messages: prependThreadTimeline(current.messages, older),
|
|
2182
|
+
threads: current.threads.map((item) => item.id === thread.id ? {
|
|
2183
|
+
...item,
|
|
2184
|
+
hasOlderTurns: page.paging.has_more,
|
|
2185
|
+
nextBeforeTurnIndex: page.paging.next_before_turn_index
|
|
2186
|
+
} : item)
|
|
2187
|
+
}));
|
|
2188
|
+
} catch (error) {
|
|
2189
|
+
if (isCurrent() && !isAbortLike(error)) {
|
|
2190
|
+
failOperation("load_older_turns", error);
|
|
2191
|
+
throw error;
|
|
2192
|
+
}
|
|
2193
|
+
} finally {
|
|
2194
|
+
if (olderRequestRef.current?.key === key) {
|
|
2195
|
+
olderRequestRef.current = null;
|
|
2196
|
+
setIsLoadingOlder(false);
|
|
2197
|
+
}
|
|
1854
2198
|
}
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
}, [clearOperationError, client, failOperation, pageSize, update]);
|
|
1859
|
-
const regenerate =
|
|
2199
|
+
})();
|
|
2200
|
+
olderRequestRef.current = { key, promise };
|
|
2201
|
+
return promise;
|
|
2202
|
+
}, [clearOperationError, client, failOperation, ownOperation, pageSize, update, captureFeedbackRead]);
|
|
2203
|
+
const regenerate = React7.useCallback(async (message) => {
|
|
1860
2204
|
const index = stateRef.current.messages.findIndex((item) => item.id === message.id);
|
|
1861
2205
|
const user = stateRef.current.messages.slice(0, index).reverse().find((item) => item.role === "user");
|
|
1862
2206
|
if (user) await submit({
|
|
@@ -1865,51 +2209,68 @@ function useAgentChat(options = {}) {
|
|
|
1865
2209
|
attachments: user.attachments
|
|
1866
2210
|
});
|
|
1867
2211
|
}, [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();
|
|
2212
|
+
useChatObservation({
|
|
2213
|
+
state: {
|
|
2214
|
+
threadId: state.activeThreadId,
|
|
2215
|
+
running: Boolean(state.activeRunId) && !["idle", "completed", "failed", "cancelled"].includes(state.runState),
|
|
2216
|
+
waiting: ["paused", "waiting", "interrupted"].includes(state.runState),
|
|
2217
|
+
pending: pendingMessages.length > 0,
|
|
2218
|
+
paused: Boolean(messageQueue?.paused),
|
|
2219
|
+
degraded: state.runState === "reconnecting",
|
|
2220
|
+
mode: options.observation
|
|
2221
|
+
},
|
|
2222
|
+
visibility: runtime.visibility,
|
|
2223
|
+
createAbortController: runtime.createAbortController,
|
|
2224
|
+
events: (input, handler) => client.threads.events(input, handler),
|
|
2225
|
+
async refresh(signal) {
|
|
2226
|
+
const target = stateRef.current.activeThreadId;
|
|
2227
|
+
if (!target) return;
|
|
2228
|
+
const isCurrent = ownOperation();
|
|
2229
|
+
const detail = await client.threads.get({ threadId: target, limit: 1, signal });
|
|
2230
|
+
if (!isCurrent() || signal.aborted) return;
|
|
2231
|
+
const summary = summaryToStored(detail);
|
|
2232
|
+
update((current) => ({ ...current, threads: applyThreadSummaryEvent(current.threads, {
|
|
2233
|
+
schema_version: "agents24.thread_summary_event.v1",
|
|
2234
|
+
event: "thread.summary.updated",
|
|
2235
|
+
cursor: 0,
|
|
2236
|
+
thread: detail
|
|
2237
|
+
}) }));
|
|
2238
|
+
const runId = activeRunIdFromThread(summary);
|
|
2239
|
+
if (runId && !streamControllerRef.current) {
|
|
2240
|
+
void attachRunRef.current(runId, runId === stateRef.current.activeRunId ? stateRef.current.cursor ?? void 0 : 0).catch(() => void 0);
|
|
1908
2241
|
}
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
2242
|
+
await refreshMessages(target, runId || void 0, signal);
|
|
2243
|
+
},
|
|
2244
|
+
onEvent(event) {
|
|
2245
|
+
if (event.event === "thread.deleted" && event.thread_id === stateRef.current.activeThreadId) startNewThread();
|
|
2246
|
+
threadEventsDuringRefreshRef.current?.push(event);
|
|
2247
|
+
update((current) => ({
|
|
2248
|
+
...clearAgentChatBackgroundError(current),
|
|
2249
|
+
threads: applyThreadSummaryEvent(current.threads, event)
|
|
2250
|
+
}));
|
|
2251
|
+
if (event.event === "thread.message.updated" && event.thread_id === stateRef.current.activeThreadId) {
|
|
2252
|
+
queue.acceptNotice({
|
|
2253
|
+
messageId: event.message_id,
|
|
2254
|
+
status: event.status,
|
|
2255
|
+
runId: event.run_id,
|
|
2256
|
+
cursor: event.cursor,
|
|
2257
|
+
clientSubmissionId: event.client_submission_id
|
|
2258
|
+
});
|
|
2259
|
+
}
|
|
2260
|
+
},
|
|
2261
|
+
onError(error) {
|
|
2262
|
+
if (!isAbortLike(error)) update((current) => withAgentChatBackgroundError(current, error));
|
|
2263
|
+
},
|
|
2264
|
+
onRecovered: clearBackgroundError,
|
|
2265
|
+
onDetach: detach,
|
|
2266
|
+
onResume() {
|
|
2267
|
+
const current = stateRef.current;
|
|
2268
|
+
if (current.activeRunId && !streamControllerRef.current && !["paused", "waiting", "interrupted", "completed", "idle"].includes(current.runState)) {
|
|
2269
|
+
void attachRunRef.current(current.activeRunId, current.cursor ?? void 0).catch(() => void 0);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
});
|
|
2273
|
+
React7.useEffect(() => {
|
|
1913
2274
|
const threadId = options.activeThreadId;
|
|
1914
2275
|
const activeThread2 = stateRef.current.threads.find((thread) => thread.id === stateRef.current.activeThreadId);
|
|
1915
2276
|
if (shouldHydrateSelectedThread(threadId, stateRef.current.activeThreadId, Boolean(activeThread2?.isHydrated))) {
|
|
@@ -1956,15 +2317,15 @@ function useAgentChat(options = {}) {
|
|
|
1956
2317
|
}
|
|
1957
2318
|
|
|
1958
2319
|
// src/use-agent-resources.ts
|
|
1959
|
-
import * as
|
|
2320
|
+
import * as React8 from "react";
|
|
1960
2321
|
function useAgentThreads(options = {}) {
|
|
1961
2322
|
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 =
|
|
2323
|
+
const [threads, setThreads] = React8.useState([]);
|
|
2324
|
+
const [total, setTotal] = React8.useState();
|
|
2325
|
+
const [isLoading, setIsLoading] = React8.useState(true);
|
|
2326
|
+
const [error, setError] = React8.useState(null);
|
|
2327
|
+
const controllerRef = React8.useRef(null);
|
|
2328
|
+
const load = React8.useCallback(async () => {
|
|
1968
2329
|
controllerRef.current?.abort();
|
|
1969
2330
|
const controller = createAbortController();
|
|
1970
2331
|
controllerRef.current = controller;
|
|
@@ -1983,7 +2344,7 @@ function useAgentThreads(options = {}) {
|
|
|
1983
2344
|
}
|
|
1984
2345
|
}
|
|
1985
2346
|
}, [client, createAbortController, options.limit, options.skip]);
|
|
1986
|
-
|
|
2347
|
+
React8.useEffect(() => {
|
|
1987
2348
|
void load();
|
|
1988
2349
|
return () => controllerRef.current?.abort();
|
|
1989
2350
|
}, [load]);
|
|
@@ -1991,11 +2352,11 @@ function useAgentThreads(options = {}) {
|
|
|
1991
2352
|
}
|
|
1992
2353
|
function useAgentThread(threadId, options = {}) {
|
|
1993
2354
|
const { client, createAbortController } = useAgents24Runtime();
|
|
1994
|
-
const [thread, setThread] =
|
|
1995
|
-
const [isLoading, setIsLoading] =
|
|
1996
|
-
const [error, setError] =
|
|
1997
|
-
const controllerRef =
|
|
1998
|
-
const load =
|
|
2355
|
+
const [thread, setThread] = React8.useState(null);
|
|
2356
|
+
const [isLoading, setIsLoading] = React8.useState(Boolean(threadId));
|
|
2357
|
+
const [error, setError] = React8.useState(null);
|
|
2358
|
+
const controllerRef = React8.useRef(null);
|
|
2359
|
+
const load = React8.useCallback(async () => {
|
|
1999
2360
|
if (!threadId) {
|
|
2000
2361
|
controllerRef.current?.abort();
|
|
2001
2362
|
controllerRef.current = null;
|
|
@@ -2022,7 +2383,7 @@ function useAgentThread(threadId, options = {}) {
|
|
|
2022
2383
|
}
|
|
2023
2384
|
}
|
|
2024
2385
|
}, [client, createAbortController, options.beforeTurnIndex, options.limit, threadId]);
|
|
2025
|
-
|
|
2386
|
+
React8.useEffect(() => {
|
|
2026
2387
|
void load();
|
|
2027
2388
|
return () => controllerRef.current?.abort();
|
|
2028
2389
|
}, [load]);
|
|
@@ -2030,11 +2391,11 @@ function useAgentThread(threadId, options = {}) {
|
|
|
2030
2391
|
}
|
|
2031
2392
|
function useThreadComputer(threadId) {
|
|
2032
2393
|
const { client, createAbortController } = useAgents24Runtime();
|
|
2033
|
-
const [computer, setComputer] =
|
|
2034
|
-
const [isLoading, setIsLoading] =
|
|
2035
|
-
const [error, setError] =
|
|
2036
|
-
const controllerRef =
|
|
2037
|
-
const refresh =
|
|
2394
|
+
const [computer, setComputer] = React8.useState(null);
|
|
2395
|
+
const [isLoading, setIsLoading] = React8.useState(Boolean(threadId));
|
|
2396
|
+
const [error, setError] = React8.useState(null);
|
|
2397
|
+
const controllerRef = React8.useRef(null);
|
|
2398
|
+
const refresh = React8.useCallback(async () => {
|
|
2038
2399
|
controllerRef.current?.abort();
|
|
2039
2400
|
if (!threadId) {
|
|
2040
2401
|
setComputer(null);
|
|
@@ -2059,19 +2420,19 @@ function useThreadComputer(threadId) {
|
|
|
2059
2420
|
}
|
|
2060
2421
|
}
|
|
2061
2422
|
}, [client, createAbortController, threadId]);
|
|
2062
|
-
|
|
2423
|
+
React8.useEffect(() => {
|
|
2063
2424
|
void refresh();
|
|
2064
2425
|
return () => controllerRef.current?.abort();
|
|
2065
2426
|
}, [refresh]);
|
|
2066
|
-
const list =
|
|
2427
|
+
const list = React8.useCallback((input) => {
|
|
2067
2428
|
if (!threadId) throw new Error("A thread is required to list computer files");
|
|
2068
2429
|
return client.threads.computer.list({ threadId, ...input });
|
|
2069
2430
|
}, [client, threadId]);
|
|
2070
|
-
const read =
|
|
2431
|
+
const read = React8.useCallback((path) => {
|
|
2071
2432
|
if (!threadId) throw new Error("A thread is required to read a computer file");
|
|
2072
2433
|
return client.threads.computer.read({ threadId, path });
|
|
2073
2434
|
}, [client, threadId]);
|
|
2074
|
-
const createContentAccess =
|
|
2435
|
+
const createContentAccess = React8.useCallback((path, download = false) => {
|
|
2075
2436
|
if (!threadId) throw new Error("A thread is required to access a computer file");
|
|
2076
2437
|
return client.threads.computer.createContentAccess({ threadId, path, download });
|
|
2077
2438
|
}, [client, threadId]);
|
|
@@ -2079,16 +2440,16 @@ function useThreadComputer(threadId) {
|
|
|
2079
2440
|
}
|
|
2080
2441
|
function useAgentRun() {
|
|
2081
2442
|
const { client, createAbortController, createId } = useAgents24Runtime();
|
|
2082
|
-
const controllerRef =
|
|
2083
|
-
const [isAttached, setIsAttached] =
|
|
2084
|
-
const [error, setError] =
|
|
2085
|
-
|
|
2086
|
-
const detach =
|
|
2443
|
+
const controllerRef = React8.useRef(null);
|
|
2444
|
+
const [isAttached, setIsAttached] = React8.useState(false);
|
|
2445
|
+
const [error, setError] = React8.useState(null);
|
|
2446
|
+
React8.useEffect(() => () => controllerRef.current?.abort(), []);
|
|
2447
|
+
const detach = React8.useCallback(() => {
|
|
2087
2448
|
controllerRef.current?.abort();
|
|
2088
2449
|
controllerRef.current = null;
|
|
2089
2450
|
setIsAttached(false);
|
|
2090
2451
|
}, []);
|
|
2091
|
-
const attach =
|
|
2452
|
+
const attach = React8.useCallback(async (runId, onEvent, cursor) => {
|
|
2092
2453
|
detach();
|
|
2093
2454
|
const controller = createAbortController();
|
|
2094
2455
|
controllerRef.current = controller;
|
|
@@ -2104,7 +2465,7 @@ function useAgentRun() {
|
|
|
2104
2465
|
setIsAttached(false);
|
|
2105
2466
|
}
|
|
2106
2467
|
}, [client, createAbortController, detach]);
|
|
2107
|
-
const cancel =
|
|
2468
|
+
const cancel = React8.useCallback(async (runId) => {
|
|
2108
2469
|
const result = await client.runs.cancel({ runId, idempotencyKey: createId() });
|
|
2109
2470
|
detach();
|
|
2110
2471
|
return result;
|
|
@@ -2113,9 +2474,9 @@ function useAgentRun() {
|
|
|
2113
2474
|
}
|
|
2114
2475
|
function useAgentAttachments() {
|
|
2115
2476
|
const { client, createId } = useAgents24Runtime();
|
|
2116
|
-
const [uploads, setUploads] =
|
|
2117
|
-
const [isUploading, setIsUploading] =
|
|
2118
|
-
const uploadBatch =
|
|
2477
|
+
const [uploads, setUploads] = React8.useState({});
|
|
2478
|
+
const [isUploading, setIsUploading] = React8.useState(false);
|
|
2479
|
+
const uploadBatch = React8.useCallback(async (input) => {
|
|
2119
2480
|
setIsUploading(true);
|
|
2120
2481
|
try {
|
|
2121
2482
|
const results = await client.attachments.uploadBatch({ uploads: input.uploads, threadId: input.threadId, requestedAgentId: input.requestedAgentId, idempotencyKey: createId(), signal: input.signal });
|
|
@@ -2128,7 +2489,7 @@ function useAgentAttachments() {
|
|
|
2128
2489
|
setIsUploading(false);
|
|
2129
2490
|
}
|
|
2130
2491
|
}, [client, createId]);
|
|
2131
|
-
const createContentAccess =
|
|
2492
|
+
const createContentAccess = React8.useCallback(
|
|
2132
2493
|
(input) => client.attachments.createContentAccess(input),
|
|
2133
2494
|
[client]
|
|
2134
2495
|
);
|
|
@@ -2136,9 +2497,9 @@ function useAgentAttachments() {
|
|
|
2136
2497
|
}
|
|
2137
2498
|
function useAgentHitl() {
|
|
2138
2499
|
const { client, createId } = useAgents24Runtime();
|
|
2139
|
-
const [isResolving, setIsResolving] =
|
|
2140
|
-
const [lastResult, setLastResult] =
|
|
2141
|
-
const resume =
|
|
2500
|
+
const [isResolving, setIsResolving] = React8.useState(false);
|
|
2501
|
+
const [lastResult, setLastResult] = React8.useState(null);
|
|
2502
|
+
const resume = React8.useCallback(async (input) => {
|
|
2142
2503
|
setIsResolving(true);
|
|
2143
2504
|
try {
|
|
2144
2505
|
const result = await client.hitl.resume({ ...input, idempotencyKey: createId() });
|
|
@@ -2152,8 +2513,8 @@ function useAgentHitl() {
|
|
|
2152
2513
|
}
|
|
2153
2514
|
function useAgentMcp() {
|
|
2154
2515
|
const { client, createId } = useAgents24Runtime();
|
|
2155
|
-
const [isAuthorizing, setIsAuthorizing] =
|
|
2156
|
-
const startAuthorization =
|
|
2516
|
+
const [isAuthorizing, setIsAuthorizing] = React8.useState(false);
|
|
2517
|
+
const startAuthorization = React8.useCallback(async (input) => {
|
|
2157
2518
|
setIsAuthorizing(true);
|
|
2158
2519
|
try {
|
|
2159
2520
|
return await client.mcp.startAuthorization({ ...input, idempotencyKey: createId() });
|
|
@@ -2161,7 +2522,7 @@ function useAgentMcp() {
|
|
|
2161
2522
|
setIsAuthorizing(false);
|
|
2162
2523
|
}
|
|
2163
2524
|
}, [client, createId]);
|
|
2164
|
-
const redeemCallback =
|
|
2525
|
+
const redeemCallback = React8.useCallback(async (input) => client.mcp.redeemCallback({ ...input, idempotencyKey: createId() }), [client, createId]);
|
|
2165
2526
|
return { isAuthorizing, redeemCallback, startAuthorization };
|
|
2166
2527
|
}
|
|
2167
2528
|
export {
|
|
@@ -2174,11 +2535,13 @@ export {
|
|
|
2174
2535
|
assistantProjectionFromEvent,
|
|
2175
2536
|
attachmentResultToChatAttachment,
|
|
2176
2537
|
captureAgentChatThreadSelection,
|
|
2538
|
+
captureChatOperation,
|
|
2177
2539
|
chatAttachmentIds,
|
|
2178
2540
|
clearAgentChatBackgroundError,
|
|
2179
2541
|
clearAgentChatOperationError,
|
|
2180
2542
|
computerResourceTargets,
|
|
2181
2543
|
contextCompressionPartFromEvent,
|
|
2544
|
+
createChatObservation,
|
|
2182
2545
|
createPortableId,
|
|
2183
2546
|
createRuntimeEventFrameBuffer,
|
|
2184
2547
|
discardOptimisticSubmission,
|
|
@@ -2191,6 +2554,7 @@ export {
|
|
|
2191
2554
|
optimisticSubmissionTurn,
|
|
2192
2555
|
partsFromResponseBlocks,
|
|
2193
2556
|
preferNewestThreadSummary,
|
|
2557
|
+
prependThreadTimeline,
|
|
2194
2558
|
preserveMountedMessageIds,
|
|
2195
2559
|
promoteThreadMessage,
|
|
2196
2560
|
reconcileRefreshedThreads,
|
|
@@ -2203,6 +2567,7 @@ export {
|
|
|
2203
2567
|
upsertStableAssistantMessage,
|
|
2204
2568
|
useAgentAttachments,
|
|
2205
2569
|
useAgentChat,
|
|
2570
|
+
useAgentChatFeedback,
|
|
2206
2571
|
useAgentClient,
|
|
2207
2572
|
useAgentHitl,
|
|
2208
2573
|
useAgentMcp,
|
|
@@ -2210,6 +2575,7 @@ export {
|
|
|
2210
2575
|
useAgentThread,
|
|
2211
2576
|
useAgentThreads,
|
|
2212
2577
|
useAgents24Runtime,
|
|
2578
|
+
useChatObservation,
|
|
2213
2579
|
useThreadComputer,
|
|
2214
2580
|
useThreadMessageQueue,
|
|
2215
2581
|
withAgentChatBackgroundError,
|