@deuz-sdk/react 1.7.1 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +420 -129
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +201 -10
- package/dist/index.d.ts +201 -10
- package/dist/index.js +422 -132
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useState, useRef, useCallback } from 'react';
|
|
2
|
-
import { uiFromMessages, createAssistantTurn, assistantMessageFromTurn, applyUIPart, clientToolResultMessage, dropTrailingAssistant, branchBeforeUserMessage } from '@deuz-sdk/core/chat';
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
import { filesToImageParts, uiFromMessages, createAssistantTurn, sealAssistantTurn, assistantMessageFromTurn, applyUIPart, clientToolResultMessage, userMessageFromInput, dropTrailingAssistant, branchBeforeUserMessage, canonicalFromUI } from '@deuz-sdk/core/chat';
|
|
3
3
|
import { readDeuzStream, connectDeuzStream } from '@deuz-sdk/core/ui';
|
|
4
4
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
5
5
|
|
|
@@ -17,46 +17,166 @@ function foldPart(turn, part) {
|
|
|
17
17
|
return next;
|
|
18
18
|
}
|
|
19
19
|
var defaultGenerateId = () => typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `deuz-ui-${Math.random().toString(36).slice(2)}`;
|
|
20
|
+
async function partsFromFiles(files) {
|
|
21
|
+
if (!files) return [];
|
|
22
|
+
const list = Array.from(
|
|
23
|
+
files
|
|
24
|
+
);
|
|
25
|
+
return list.length === 0 ? [] : filesToImageParts(list);
|
|
26
|
+
}
|
|
27
|
+
function loadCursor(resume) {
|
|
28
|
+
try {
|
|
29
|
+
return resume.cursor?.load();
|
|
30
|
+
} catch {
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function saveCursor(resume, id) {
|
|
35
|
+
try {
|
|
36
|
+
resume.cursor?.save(id);
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
20
40
|
function useChat(options) {
|
|
21
41
|
const genId = options.generateId ?? defaultGenerateId;
|
|
22
|
-
const [
|
|
23
|
-
|
|
24
|
-
|
|
42
|
+
const [snapshot, setSnapshot] = useState(() => ({
|
|
43
|
+
history: {
|
|
44
|
+
ui: uiFromMessages(options.initialMessages ?? [], genId),
|
|
45
|
+
canonical: options.initialMessages ? [...options.initialMessages] : []
|
|
46
|
+
},
|
|
47
|
+
dataParts: [],
|
|
48
|
+
citations: [],
|
|
49
|
+
activity: []
|
|
50
|
+
}));
|
|
25
51
|
const [status, setStatus] = useState("idle");
|
|
26
52
|
const [error, setError] = useState(void 0);
|
|
27
53
|
const [pendingApprovals, setPendingApprovals] = useState([]);
|
|
28
|
-
const [
|
|
29
|
-
const
|
|
30
|
-
void 0
|
|
31
|
-
);
|
|
32
|
-
const [dataParts, setDataParts] = useState([]);
|
|
33
|
-
const [citations, setCitations] = useState([]);
|
|
34
|
-
const uiRef = useRef(messages);
|
|
35
|
-
const canonicalRef = useRef(
|
|
36
|
-
options.initialMessages ? [...options.initialMessages] : []
|
|
37
|
-
);
|
|
54
|
+
const [pendingToolCalls, setPendingToolCalls] = useState([]);
|
|
55
|
+
const snapshotRef = useRef(snapshot);
|
|
38
56
|
const turnRef = useRef(void 0);
|
|
57
|
+
const parkedTurnRef = useRef(void 0);
|
|
58
|
+
const clientToolsRef = useRef([]);
|
|
59
|
+
const toolResultsRef = useRef(
|
|
60
|
+
[]
|
|
61
|
+
);
|
|
39
62
|
const approvalsRef = useRef([]);
|
|
40
63
|
const verdictsRef = useRef([]);
|
|
41
64
|
const abortRef = useRef(null);
|
|
42
65
|
const lastEventIdRef = useRef(options.resume?.lastEventId);
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
66
|
+
const placedIdRef = useRef(void 0);
|
|
67
|
+
const throttleMs = options.throttleMs ?? 0;
|
|
68
|
+
const flushTimerRef = useRef(void 0);
|
|
69
|
+
const dirtyRef = useRef(false);
|
|
70
|
+
const publish = useCallback(() => {
|
|
71
|
+
flushTimerRef.current = void 0;
|
|
72
|
+
dirtyRef.current = false;
|
|
73
|
+
setSnapshot(snapshotRef.current);
|
|
46
74
|
}, []);
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
const commit = useCallback(
|
|
76
|
+
(next) => {
|
|
77
|
+
snapshotRef.current = next;
|
|
78
|
+
if (throttleMs <= 0) {
|
|
79
|
+
publish();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
dirtyRef.current = true;
|
|
83
|
+
if (flushTimerRef.current === void 0) {
|
|
84
|
+
flushTimerRef.current = setTimeout(publish, throttleMs);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
[throttleMs, publish]
|
|
88
|
+
);
|
|
89
|
+
const flush = useCallback(() => {
|
|
90
|
+
if (flushTimerRef.current !== void 0) {
|
|
91
|
+
clearTimeout(flushTimerRef.current);
|
|
92
|
+
flushTimerRef.current = void 0;
|
|
55
93
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
94
|
+
if (dirtyRef.current) publish();
|
|
95
|
+
}, [publish]);
|
|
96
|
+
useEffect(
|
|
97
|
+
() => () => {
|
|
98
|
+
if (flushTimerRef.current !== void 0) clearTimeout(flushTimerRef.current);
|
|
99
|
+
},
|
|
100
|
+
[]
|
|
101
|
+
);
|
|
102
|
+
const syncTurn = useCallback(
|
|
103
|
+
(turn) => {
|
|
104
|
+
const previous = snapshotRef.current;
|
|
105
|
+
const placed = placedIdRef.current;
|
|
106
|
+
const ui = previous.history.ui;
|
|
107
|
+
const index = placed === void 0 ? -1 : ui.findIndex((m) => m.id === placed);
|
|
108
|
+
placedIdRef.current = turn.message.id;
|
|
109
|
+
commit({
|
|
110
|
+
...previous,
|
|
111
|
+
history: {
|
|
112
|
+
// NEW array either way — the turn's message is never spliced in place.
|
|
113
|
+
ui: index === -1 ? [...ui, turn.message] : [...ui.slice(0, index), turn.message, ...ui.slice(index + 1)],
|
|
114
|
+
canonical: previous.history.canonical
|
|
115
|
+
},
|
|
116
|
+
// `cost` is CUMULATIVE for the chat, so an absent reading keeps the
|
|
117
|
+
// previous one (1.7 behaviour, verbatim); every other readout is
|
|
118
|
+
// turn-scoped and resets when the turn does.
|
|
119
|
+
cost: turn.costUsd !== void 0 ? {
|
|
120
|
+
costUsd: turn.costUsd,
|
|
121
|
+
...turn.cacheSavingsUsd !== void 0 ? { cacheSavingsUsd: turn.cacheSavingsUsd } : {}
|
|
122
|
+
} : previous.cost,
|
|
123
|
+
budgetExceeded: turn.budgetExceeded,
|
|
124
|
+
dataParts: turn.dataParts,
|
|
125
|
+
citations: turn.citations,
|
|
126
|
+
plan: turn.plan,
|
|
127
|
+
activity: turn.activity,
|
|
128
|
+
verifications: turn.verifications,
|
|
129
|
+
warnings: turn.warnings,
|
|
130
|
+
falseFinishes: turn.falseFinishes,
|
|
131
|
+
subAgents: turn.subAgents,
|
|
132
|
+
steps: turn.steps,
|
|
133
|
+
usage: turn.usage,
|
|
134
|
+
finishReason: turn.finishReason
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
[commit]
|
|
138
|
+
);
|
|
139
|
+
const appendCanonical = useCallback(
|
|
140
|
+
(message) => {
|
|
141
|
+
const previous = snapshotRef.current;
|
|
142
|
+
commit({
|
|
143
|
+
...previous,
|
|
144
|
+
history: {
|
|
145
|
+
ui: previous.history.ui,
|
|
146
|
+
canonical: [...previous.history.canonical, message]
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
[commit]
|
|
151
|
+
);
|
|
152
|
+
const clearGates = useCallback(() => {
|
|
153
|
+
approvalsRef.current = [];
|
|
154
|
+
verdictsRef.current = [];
|
|
155
|
+
clientToolsRef.current = [];
|
|
156
|
+
toolResultsRef.current = [];
|
|
157
|
+
parkedTurnRef.current = void 0;
|
|
158
|
+
setPendingApprovals([]);
|
|
159
|
+
setPendingToolCalls([]);
|
|
59
160
|
}, []);
|
|
161
|
+
const fold = useCallback(
|
|
162
|
+
(turn, part) => {
|
|
163
|
+
const next = foldPart(turn, part);
|
|
164
|
+
const onData = options.onData;
|
|
165
|
+
if (onData && typeof part.type === "string" && part.type.startsWith("data-")) {
|
|
166
|
+
const data = part;
|
|
167
|
+
try {
|
|
168
|
+
onData({
|
|
169
|
+
name: data.type.slice("data-".length),
|
|
170
|
+
...typeof data.id === "string" ? { id: data.id } : {},
|
|
171
|
+
payload: data.payload
|
|
172
|
+
});
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return next;
|
|
177
|
+
},
|
|
178
|
+
[options.onData]
|
|
179
|
+
);
|
|
60
180
|
const stop = useCallback(() => {
|
|
61
181
|
abortRef.current?.abort();
|
|
62
182
|
}, []);
|
|
@@ -73,7 +193,7 @@ function useChat(options) {
|
|
|
73
193
|
method: "POST",
|
|
74
194
|
headers: { "content-type": "application/json", ...options.headers },
|
|
75
195
|
body: JSON.stringify({
|
|
76
|
-
messages:
|
|
196
|
+
messages: snapshotRef.current.history.canonical,
|
|
77
197
|
...options.chatId !== void 0 ? { chatId: options.chatId } : {},
|
|
78
198
|
...approvalResponses?.length ? { approvalResponses } : {},
|
|
79
199
|
...options.body
|
|
@@ -83,15 +203,25 @@ function useChat(options) {
|
|
|
83
203
|
approvalResponses = void 0;
|
|
84
204
|
let turn = createAssistantTurn(genId());
|
|
85
205
|
turnRef.current = turn;
|
|
86
|
-
|
|
206
|
+
placedIdRef.current = void 0;
|
|
87
207
|
syncTurn(turn);
|
|
88
|
-
|
|
89
|
-
|
|
208
|
+
flush();
|
|
209
|
+
for await (const part of readDeuzStream(res, {
|
|
210
|
+
...options.onHttpError ? { onHttpError: options.onHttpError } : {}
|
|
211
|
+
})) {
|
|
212
|
+
turn = fold(turn, part);
|
|
213
|
+
turnRef.current = turn;
|
|
214
|
+
syncTurn(turn);
|
|
215
|
+
}
|
|
216
|
+
const sealed = sealAssistantTurn(turn);
|
|
217
|
+
if (sealed !== turn) {
|
|
218
|
+
turn = sealed;
|
|
90
219
|
turnRef.current = turn;
|
|
91
220
|
syncTurn(turn);
|
|
92
221
|
}
|
|
222
|
+
flush();
|
|
93
223
|
if (turn.error !== void 0) throw new Error(turn.error);
|
|
94
|
-
|
|
224
|
+
appendCanonical(assistantMessageFromTurn(turn));
|
|
95
225
|
turnRef.current = void 0;
|
|
96
226
|
if (turn.approvals.length > 0) {
|
|
97
227
|
approvalsRef.current = turn.approvals;
|
|
@@ -102,7 +232,14 @@ function useChat(options) {
|
|
|
102
232
|
const toolCalls = turn.message.toolCalls ?? [];
|
|
103
233
|
const serverResults = new Set(turn.serverResults);
|
|
104
234
|
const clientPending = toolCalls.filter((t) => !serverResults.has(t.toolCallId));
|
|
105
|
-
if (clientPending.length === 0
|
|
235
|
+
if (clientPending.length === 0) return;
|
|
236
|
+
if (!options.onToolCall) {
|
|
237
|
+
parkedTurnRef.current = turn;
|
|
238
|
+
clientToolsRef.current = clientPending;
|
|
239
|
+
toolResultsRef.current = [];
|
|
240
|
+
setPendingToolCalls(clientPending);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
106
243
|
const results = [];
|
|
107
244
|
for (const call of clientPending) {
|
|
108
245
|
try {
|
|
@@ -131,10 +268,18 @@ function useChat(options) {
|
|
|
131
268
|
}
|
|
132
269
|
syncTurn(turn);
|
|
133
270
|
}
|
|
134
|
-
|
|
271
|
+
appendCanonical(clientToolResultMessage(results));
|
|
135
272
|
}
|
|
136
273
|
} catch (err) {
|
|
137
|
-
if (controller.signal.aborted)
|
|
274
|
+
if (controller.signal.aborted) {
|
|
275
|
+
const open = turnRef.current;
|
|
276
|
+
if (open) {
|
|
277
|
+
const sealed = sealAssistantTurn(open);
|
|
278
|
+
turnRef.current = sealed;
|
|
279
|
+
if (sealed !== open) syncTurn(sealed);
|
|
280
|
+
}
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
138
283
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
139
284
|
setError(e);
|
|
140
285
|
setStatus("error");
|
|
@@ -142,6 +287,7 @@ function useChat(options) {
|
|
|
142
287
|
return;
|
|
143
288
|
} finally {
|
|
144
289
|
setStatus((s) => s === "error" ? s : "idle");
|
|
290
|
+
flush();
|
|
145
291
|
}
|
|
146
292
|
},
|
|
147
293
|
[
|
|
@@ -153,47 +299,112 @@ function useChat(options) {
|
|
|
153
299
|
options.generateId,
|
|
154
300
|
options.onToolCall,
|
|
155
301
|
options.onError,
|
|
156
|
-
|
|
302
|
+
options.onHttpError,
|
|
303
|
+
appendCanonical,
|
|
304
|
+
fold,
|
|
305
|
+
flush,
|
|
157
306
|
syncTurn
|
|
158
307
|
]
|
|
159
308
|
);
|
|
160
309
|
const sendMessage = useCallback(
|
|
161
|
-
async (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
310
|
+
async (input) => {
|
|
311
|
+
const message = userMessageFromInput(input);
|
|
312
|
+
const bubble = uiFromMessages([message], genId)[0];
|
|
313
|
+
const previous = snapshotRef.current;
|
|
314
|
+
commit({
|
|
315
|
+
...previous,
|
|
316
|
+
history: {
|
|
317
|
+
ui: bubble ? [...previous.history.ui, bubble] : previous.history.ui,
|
|
318
|
+
canonical: [...previous.history.canonical, message]
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
clearGates();
|
|
322
|
+
flush();
|
|
167
323
|
await run();
|
|
168
324
|
},
|
|
169
|
-
[run,
|
|
325
|
+
[run, commit, clearGates, flush, options.generateId]
|
|
170
326
|
);
|
|
171
327
|
const regenerate = useCallback(async () => {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
approvalsRef.current = [];
|
|
177
|
-
verdictsRef.current = [];
|
|
178
|
-
setPendingApprovals([]);
|
|
328
|
+
const previous = snapshotRef.current;
|
|
329
|
+
commit({ ...previous, history: dropTrailingAssistant(previous.history) });
|
|
330
|
+
clearGates();
|
|
331
|
+
flush();
|
|
179
332
|
await run();
|
|
180
|
-
}, [run]);
|
|
333
|
+
}, [run, commit, clearGates, flush]);
|
|
181
334
|
const editAndResend = useCallback(
|
|
182
|
-
async (messageId,
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
messageId
|
|
186
|
-
);
|
|
335
|
+
async (messageId, input) => {
|
|
336
|
+
const previous = snapshotRef.current;
|
|
337
|
+
const cut = branchBeforeUserMessage(previous.history, messageId);
|
|
187
338
|
if (!cut) return;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
339
|
+
commit({ ...previous, history: cut });
|
|
340
|
+
clearGates();
|
|
341
|
+
flush();
|
|
342
|
+
await sendMessage(input);
|
|
343
|
+
},
|
|
344
|
+
[sendMessage, commit, clearGates, flush]
|
|
345
|
+
);
|
|
346
|
+
const setHistory = useCallback(
|
|
347
|
+
(update) => {
|
|
348
|
+
const previous = snapshotRef.current;
|
|
349
|
+
const next = typeof update === "function" ? update(previous.history) : update;
|
|
350
|
+
commit({
|
|
351
|
+
...previous,
|
|
352
|
+
history: { ui: [...next.ui], canonical: [...next.canonical] }
|
|
353
|
+
});
|
|
354
|
+
clearGates();
|
|
355
|
+
flush();
|
|
195
356
|
},
|
|
196
|
-
[
|
|
357
|
+
[commit, clearGates, flush]
|
|
358
|
+
);
|
|
359
|
+
const setMessages = useCallback(
|
|
360
|
+
(update) => {
|
|
361
|
+
setHistory((prev) => {
|
|
362
|
+
const ui = typeof update === "function" ? update(prev.ui) : update;
|
|
363
|
+
return { ui, canonical: canonicalFromUI(ui) };
|
|
364
|
+
});
|
|
365
|
+
},
|
|
366
|
+
[setHistory]
|
|
367
|
+
);
|
|
368
|
+
const clearError = useCallback(() => {
|
|
369
|
+
setError(void 0);
|
|
370
|
+
setStatus((s) => s === "error" ? "idle" : s);
|
|
371
|
+
}, []);
|
|
372
|
+
const addToolResult = useCallback(
|
|
373
|
+
async (result) => {
|
|
374
|
+
const parked = clientToolsRef.current;
|
|
375
|
+
const call = parked.find((c) => c.toolCallId === result.toolCallId);
|
|
376
|
+
if (!call) return;
|
|
377
|
+
toolResultsRef.current = [
|
|
378
|
+
...toolResultsRef.current.filter((r) => r.toolCallId !== result.toolCallId),
|
|
379
|
+
{
|
|
380
|
+
toolCallId: result.toolCallId,
|
|
381
|
+
result: result.output,
|
|
382
|
+
...result.isError ? { isError: true } : {}
|
|
383
|
+
}
|
|
384
|
+
];
|
|
385
|
+
const turn = parkedTurnRef.current;
|
|
386
|
+
if (turn) {
|
|
387
|
+
const next = applyUIPart(turn, {
|
|
388
|
+
type: "tool-result",
|
|
389
|
+
toolCallId: result.toolCallId,
|
|
390
|
+
toolName: call.toolName,
|
|
391
|
+
output: result.output,
|
|
392
|
+
...result.isError ? { isError: true } : {}
|
|
393
|
+
});
|
|
394
|
+
parkedTurnRef.current = next;
|
|
395
|
+
syncTurn(next);
|
|
396
|
+
flush();
|
|
397
|
+
}
|
|
398
|
+
const answered = toolResultsRef.current;
|
|
399
|
+
if (!parked.every((c) => answered.some((r) => r.toolCallId === c.toolCallId))) return;
|
|
400
|
+
clientToolsRef.current = [];
|
|
401
|
+
toolResultsRef.current = [];
|
|
402
|
+
parkedTurnRef.current = void 0;
|
|
403
|
+
setPendingToolCalls([]);
|
|
404
|
+
appendCanonical(clientToolResultMessage(answered));
|
|
405
|
+
await run();
|
|
406
|
+
},
|
|
407
|
+
[run, appendCanonical, syncTurn, flush]
|
|
197
408
|
);
|
|
198
409
|
const addToolApprovalResponse = useCallback(
|
|
199
410
|
async (response) => {
|
|
@@ -215,77 +426,118 @@ function useChat(options) {
|
|
|
215
426
|
},
|
|
216
427
|
[run]
|
|
217
428
|
);
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
turn
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const parts = connectDeuzStream(resume.endpoint, {
|
|
236
|
-
...cursor !== void 0 ? { lastEventId: cursor } : {},
|
|
237
|
-
onCursor: (id) => {
|
|
238
|
-
lastEventIdRef.current = id;
|
|
239
|
-
},
|
|
240
|
-
signal: controller.signal,
|
|
241
|
-
...options.fetch ? { fetch: options.fetch } : {},
|
|
242
|
-
...options.headers ? { headers: options.headers } : {}
|
|
243
|
-
});
|
|
244
|
-
for await (const part of parts) {
|
|
245
|
-
turn = foldPart(turn, part);
|
|
429
|
+
const runResume = useCallback(
|
|
430
|
+
async (silent) => {
|
|
431
|
+
const resume = options.resume;
|
|
432
|
+
if (!resume) return;
|
|
433
|
+
const controller = new AbortController();
|
|
434
|
+
abortRef.current = controller;
|
|
435
|
+
setStatus("streaming");
|
|
436
|
+
setError(void 0);
|
|
437
|
+
let folded = 0;
|
|
438
|
+
try {
|
|
439
|
+
const cursor = lastEventIdRef.current ?? loadCursor(resume);
|
|
440
|
+
const inFlight = turnRef.current;
|
|
441
|
+
let turn = inFlight;
|
|
442
|
+
if (turn === void 0 || cursor === void 0) {
|
|
443
|
+
if (inFlight === void 0) placedIdRef.current = void 0;
|
|
444
|
+
turn = createAssistantTurn(genId());
|
|
445
|
+
}
|
|
246
446
|
turnRef.current = turn;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
447
|
+
const parts = connectDeuzStream(resume.endpoint, {
|
|
448
|
+
...cursor !== void 0 ? { lastEventId: cursor } : {},
|
|
449
|
+
onCursor: (id) => {
|
|
450
|
+
lastEventIdRef.current = id;
|
|
451
|
+
saveCursor(resume, id);
|
|
452
|
+
},
|
|
453
|
+
signal: controller.signal,
|
|
454
|
+
...options.fetch ? { fetch: options.fetch } : {},
|
|
455
|
+
...options.headers ? { headers: options.headers } : {}
|
|
456
|
+
});
|
|
457
|
+
for await (const part of parts) {
|
|
458
|
+
folded++;
|
|
459
|
+
turn = fold(turn, part);
|
|
460
|
+
turnRef.current = turn;
|
|
461
|
+
syncTurn(turn);
|
|
462
|
+
}
|
|
463
|
+
if (folded === 0) {
|
|
464
|
+
turnRef.current = inFlight === void 0 ? void 0 : sealAssistantTurn(inFlight);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
const sealed = sealAssistantTurn(turn);
|
|
468
|
+
if (sealed !== turn) {
|
|
469
|
+
turn = sealed;
|
|
470
|
+
syncTurn(turn);
|
|
471
|
+
}
|
|
472
|
+
turnRef.current = turn;
|
|
473
|
+
if (turn.error !== void 0) throw new Error(turn.error);
|
|
474
|
+
appendCanonical(assistantMessageFromTurn(turn));
|
|
475
|
+
turnRef.current = void 0;
|
|
476
|
+
if (turn.approvals.length > 0) {
|
|
477
|
+
approvalsRef.current = turn.approvals;
|
|
478
|
+
verdictsRef.current = [];
|
|
479
|
+
setPendingApprovals(turn.approvals);
|
|
480
|
+
}
|
|
481
|
+
} catch (err) {
|
|
482
|
+
if (controller.signal.aborted) return;
|
|
483
|
+
if (silent) return;
|
|
484
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
485
|
+
setError(e);
|
|
486
|
+
setStatus("error");
|
|
487
|
+
options.onError?.(e);
|
|
488
|
+
return;
|
|
489
|
+
} finally {
|
|
490
|
+
setStatus((s) => s === "error" ? s : "idle");
|
|
491
|
+
flush();
|
|
256
492
|
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
options.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
493
|
+
},
|
|
494
|
+
[
|
|
495
|
+
options.resume,
|
|
496
|
+
options.fetch,
|
|
497
|
+
options.headers,
|
|
498
|
+
options.generateId,
|
|
499
|
+
options.onError,
|
|
500
|
+
appendCanonical,
|
|
501
|
+
fold,
|
|
502
|
+
flush,
|
|
503
|
+
syncTurn
|
|
504
|
+
]
|
|
505
|
+
);
|
|
506
|
+
const reconnect = useCallback(() => runResume(false), [runResume]);
|
|
507
|
+
const autoResumedRef = useRef(false);
|
|
508
|
+
useEffect(() => {
|
|
509
|
+
if (options.resume?.auto !== true || autoResumedRef.current) return;
|
|
510
|
+
autoResumedRef.current = true;
|
|
511
|
+
void runResume(true);
|
|
512
|
+
}, [options.resume?.auto, runResume]);
|
|
276
513
|
return {
|
|
277
|
-
messages,
|
|
514
|
+
messages: snapshot.history.ui,
|
|
515
|
+
history: snapshot.history,
|
|
278
516
|
status,
|
|
279
517
|
error,
|
|
280
518
|
pendingApprovals,
|
|
281
|
-
|
|
282
|
-
...
|
|
283
|
-
|
|
284
|
-
|
|
519
|
+
pendingToolCalls,
|
|
520
|
+
...snapshot.cost !== void 0 ? { cost: snapshot.cost } : {},
|
|
521
|
+
...snapshot.budgetExceeded !== void 0 ? { budgetExceeded: snapshot.budgetExceeded } : {},
|
|
522
|
+
dataParts: snapshot.dataParts,
|
|
523
|
+
citations: snapshot.citations,
|
|
524
|
+
...snapshot.plan !== void 0 ? { plan: snapshot.plan } : {},
|
|
525
|
+
activity: snapshot.activity,
|
|
526
|
+
...snapshot.verifications !== void 0 ? { verifications: snapshot.verifications } : {},
|
|
527
|
+
...snapshot.warnings !== void 0 ? { warnings: snapshot.warnings } : {},
|
|
528
|
+
...snapshot.falseFinishes !== void 0 ? { falseFinishes: snapshot.falseFinishes } : {},
|
|
529
|
+
...snapshot.subAgents !== void 0 ? { subAgents: snapshot.subAgents } : {},
|
|
530
|
+
...snapshot.steps !== void 0 ? { steps: snapshot.steps } : {},
|
|
531
|
+
...snapshot.usage !== void 0 ? { usage: snapshot.usage } : {},
|
|
532
|
+
...snapshot.finishReason !== void 0 ? { finishReason: snapshot.finishReason } : {},
|
|
285
533
|
sendMessage,
|
|
286
534
|
stop,
|
|
287
535
|
regenerate,
|
|
288
536
|
editAndResend,
|
|
537
|
+
setHistory,
|
|
538
|
+
setMessages,
|
|
539
|
+
addToolResult,
|
|
540
|
+
clearError,
|
|
289
541
|
addToolApprovalResponse,
|
|
290
542
|
reconnect
|
|
291
543
|
};
|
|
@@ -295,6 +547,40 @@ function useObject(options) {
|
|
|
295
547
|
const [isLoading, setLoading] = useState(false);
|
|
296
548
|
const [error, setError] = useState(void 0);
|
|
297
549
|
const abortRef = useRef(null);
|
|
550
|
+
const throttleMs = options.throttleMs ?? 0;
|
|
551
|
+
const latestRef = useRef(void 0);
|
|
552
|
+
const timerRef = useRef(void 0);
|
|
553
|
+
const dirtyRef = useRef(false);
|
|
554
|
+
const publish = useCallback(() => {
|
|
555
|
+
timerRef.current = void 0;
|
|
556
|
+
dirtyRef.current = false;
|
|
557
|
+
setObject(latestRef.current);
|
|
558
|
+
}, []);
|
|
559
|
+
const commit = useCallback(
|
|
560
|
+
(next) => {
|
|
561
|
+
latestRef.current = next;
|
|
562
|
+
if (throttleMs <= 0) {
|
|
563
|
+
publish();
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
dirtyRef.current = true;
|
|
567
|
+
if (timerRef.current === void 0) timerRef.current = setTimeout(publish, throttleMs);
|
|
568
|
+
},
|
|
569
|
+
[throttleMs, publish]
|
|
570
|
+
);
|
|
571
|
+
const flush = useCallback(() => {
|
|
572
|
+
if (timerRef.current !== void 0) {
|
|
573
|
+
clearTimeout(timerRef.current);
|
|
574
|
+
timerRef.current = void 0;
|
|
575
|
+
}
|
|
576
|
+
if (dirtyRef.current) publish();
|
|
577
|
+
}, [publish]);
|
|
578
|
+
useEffect(
|
|
579
|
+
() => () => {
|
|
580
|
+
if (timerRef.current !== void 0) clearTimeout(timerRef.current);
|
|
581
|
+
},
|
|
582
|
+
[]
|
|
583
|
+
);
|
|
298
584
|
const stop = useCallback(() => {
|
|
299
585
|
abortRef.current?.abort();
|
|
300
586
|
}, []);
|
|
@@ -305,7 +591,8 @@ function useObject(options) {
|
|
|
305
591
|
abortRef.current = controller;
|
|
306
592
|
setLoading(true);
|
|
307
593
|
setError(void 0);
|
|
308
|
-
|
|
594
|
+
commit(void 0);
|
|
595
|
+
flush();
|
|
309
596
|
try {
|
|
310
597
|
const doFetch = options.fetch ?? fetch;
|
|
311
598
|
const res = await doFetch(options.api, {
|
|
@@ -314,9 +601,11 @@ function useObject(options) {
|
|
|
314
601
|
body: JSON.stringify({ input }),
|
|
315
602
|
signal: controller.signal
|
|
316
603
|
});
|
|
317
|
-
for await (const part of readDeuzStream(res
|
|
604
|
+
for await (const part of readDeuzStream(res, {
|
|
605
|
+
...options.onHttpError ? { onHttpError: options.onHttpError } : {}
|
|
606
|
+
})) {
|
|
318
607
|
if (part.type === "object-delta") {
|
|
319
|
-
|
|
608
|
+
commit(part.object);
|
|
320
609
|
} else if (part.type === "error") {
|
|
321
610
|
setError(new Error(part.message));
|
|
322
611
|
break;
|
|
@@ -328,9 +617,10 @@ function useObject(options) {
|
|
|
328
617
|
}
|
|
329
618
|
} finally {
|
|
330
619
|
setLoading(false);
|
|
620
|
+
flush();
|
|
331
621
|
}
|
|
332
622
|
},
|
|
333
|
-
[options.api, options.fetch, options.headers]
|
|
623
|
+
[options.api, options.fetch, options.headers, options.onHttpError, commit, flush]
|
|
334
624
|
);
|
|
335
625
|
return { object, isLoading, error, submit, stop };
|
|
336
626
|
}
|
|
@@ -358,6 +648,6 @@ function CostBadge({ cost, format }) {
|
|
|
358
648
|
return /* @__PURE__ */ jsx("span", { "data-deuz": "cost-badge", children: `$${cost.costUsd.toFixed(4)}${saved}` });
|
|
359
649
|
}
|
|
360
650
|
|
|
361
|
-
export { CostBadge, ToolApprovalCard, useChat, useObject };
|
|
651
|
+
export { CostBadge, ToolApprovalCard, partsFromFiles, useChat, useObject };
|
|
362
652
|
//# sourceMappingURL=index.js.map
|
|
363
653
|
//# sourceMappingURL=index.js.map
|