@deuz-sdk/react 1.8.0 → 2.0.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 -135
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +197 -10
- package/dist/index.d.ts +197 -10
- package/dist/index.js +422 -138
- 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,50 +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 [plan, setPlan] = useState(void 0);
|
|
35
|
-
const [activity, setActivity] = useState([]);
|
|
36
|
-
const uiRef = useRef(messages);
|
|
37
|
-
const canonicalRef = useRef(
|
|
38
|
-
options.initialMessages ? [...options.initialMessages] : []
|
|
39
|
-
);
|
|
54
|
+
const [pendingToolCalls, setPendingToolCalls] = useState([]);
|
|
55
|
+
const snapshotRef = useRef(snapshot);
|
|
40
56
|
const turnRef = useRef(void 0);
|
|
57
|
+
const parkedTurnRef = useRef(void 0);
|
|
58
|
+
const clientToolsRef = useRef([]);
|
|
59
|
+
const toolResultsRef = useRef(
|
|
60
|
+
[]
|
|
61
|
+
);
|
|
41
62
|
const approvalsRef = useRef([]);
|
|
42
63
|
const verdictsRef = useRef([]);
|
|
43
64
|
const abortRef = useRef(null);
|
|
44
65
|
const lastEventIdRef = useRef(options.resume?.lastEventId);
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
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);
|
|
48
74
|
}, []);
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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;
|
|
57
93
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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([]);
|
|
63
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
|
+
);
|
|
64
180
|
const stop = useCallback(() => {
|
|
65
181
|
abortRef.current?.abort();
|
|
66
182
|
}, []);
|
|
@@ -77,7 +193,7 @@ function useChat(options) {
|
|
|
77
193
|
method: "POST",
|
|
78
194
|
headers: { "content-type": "application/json", ...options.headers },
|
|
79
195
|
body: JSON.stringify({
|
|
80
|
-
messages:
|
|
196
|
+
messages: snapshotRef.current.history.canonical,
|
|
81
197
|
...options.chatId !== void 0 ? { chatId: options.chatId } : {},
|
|
82
198
|
...approvalResponses?.length ? { approvalResponses } : {},
|
|
83
199
|
...options.body
|
|
@@ -87,15 +203,25 @@ function useChat(options) {
|
|
|
87
203
|
approvalResponses = void 0;
|
|
88
204
|
let turn = createAssistantTurn(genId());
|
|
89
205
|
turnRef.current = turn;
|
|
90
|
-
|
|
206
|
+
placedIdRef.current = void 0;
|
|
91
207
|
syncTurn(turn);
|
|
92
|
-
|
|
93
|
-
|
|
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;
|
|
94
219
|
turnRef.current = turn;
|
|
95
220
|
syncTurn(turn);
|
|
96
221
|
}
|
|
222
|
+
flush();
|
|
97
223
|
if (turn.error !== void 0) throw new Error(turn.error);
|
|
98
|
-
|
|
224
|
+
appendCanonical(assistantMessageFromTurn(turn));
|
|
99
225
|
turnRef.current = void 0;
|
|
100
226
|
if (turn.approvals.length > 0) {
|
|
101
227
|
approvalsRef.current = turn.approvals;
|
|
@@ -106,7 +232,14 @@ function useChat(options) {
|
|
|
106
232
|
const toolCalls = turn.message.toolCalls ?? [];
|
|
107
233
|
const serverResults = new Set(turn.serverResults);
|
|
108
234
|
const clientPending = toolCalls.filter((t) => !serverResults.has(t.toolCallId));
|
|
109
|
-
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
|
+
}
|
|
110
243
|
const results = [];
|
|
111
244
|
for (const call of clientPending) {
|
|
112
245
|
try {
|
|
@@ -135,10 +268,18 @@ function useChat(options) {
|
|
|
135
268
|
}
|
|
136
269
|
syncTurn(turn);
|
|
137
270
|
}
|
|
138
|
-
|
|
271
|
+
appendCanonical(clientToolResultMessage(results));
|
|
139
272
|
}
|
|
140
273
|
} catch (err) {
|
|
141
|
-
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
|
+
}
|
|
142
283
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
143
284
|
setError(e);
|
|
144
285
|
setStatus("error");
|
|
@@ -146,6 +287,7 @@ function useChat(options) {
|
|
|
146
287
|
return;
|
|
147
288
|
} finally {
|
|
148
289
|
setStatus((s) => s === "error" ? s : "idle");
|
|
290
|
+
flush();
|
|
149
291
|
}
|
|
150
292
|
},
|
|
151
293
|
[
|
|
@@ -157,47 +299,112 @@ function useChat(options) {
|
|
|
157
299
|
options.generateId,
|
|
158
300
|
options.onToolCall,
|
|
159
301
|
options.onError,
|
|
160
|
-
|
|
302
|
+
options.onHttpError,
|
|
303
|
+
appendCanonical,
|
|
304
|
+
fold,
|
|
305
|
+
flush,
|
|
161
306
|
syncTurn
|
|
162
307
|
]
|
|
163
308
|
);
|
|
164
309
|
const sendMessage = useCallback(
|
|
165
|
-
async (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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();
|
|
171
323
|
await run();
|
|
172
324
|
},
|
|
173
|
-
[run,
|
|
325
|
+
[run, commit, clearGates, flush, options.generateId]
|
|
174
326
|
);
|
|
175
327
|
const regenerate = useCallback(async () => {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
approvalsRef.current = [];
|
|
181
|
-
verdictsRef.current = [];
|
|
182
|
-
setPendingApprovals([]);
|
|
328
|
+
const previous = snapshotRef.current;
|
|
329
|
+
commit({ ...previous, history: dropTrailingAssistant(previous.history) });
|
|
330
|
+
clearGates();
|
|
331
|
+
flush();
|
|
183
332
|
await run();
|
|
184
|
-
}, [run]);
|
|
333
|
+
}, [run, commit, clearGates, flush]);
|
|
185
334
|
const editAndResend = useCallback(
|
|
186
|
-
async (messageId,
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
messageId
|
|
190
|
-
);
|
|
335
|
+
async (messageId, input) => {
|
|
336
|
+
const previous = snapshotRef.current;
|
|
337
|
+
const cut = branchBeforeUserMessage(previous.history, messageId);
|
|
191
338
|
if (!cut) return;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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();
|
|
199
356
|
},
|
|
200
|
-
[
|
|
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]
|
|
201
408
|
);
|
|
202
409
|
const addToolApprovalResponse = useCallback(
|
|
203
410
|
async (response) => {
|
|
@@ -219,79 +426,118 @@ function useChat(options) {
|
|
|
219
426
|
},
|
|
220
427
|
[run]
|
|
221
428
|
);
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
turn
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const parts = connectDeuzStream(resume.endpoint, {
|
|
240
|
-
...cursor !== void 0 ? { lastEventId: cursor } : {},
|
|
241
|
-
onCursor: (id) => {
|
|
242
|
-
lastEventIdRef.current = id;
|
|
243
|
-
},
|
|
244
|
-
signal: controller.signal,
|
|
245
|
-
...options.fetch ? { fetch: options.fetch } : {},
|
|
246
|
-
...options.headers ? { headers: options.headers } : {}
|
|
247
|
-
});
|
|
248
|
-
for await (const part of parts) {
|
|
249
|
-
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
|
+
}
|
|
250
446
|
turnRef.current = turn;
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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();
|
|
260
492
|
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
options.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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]);
|
|
280
513
|
return {
|
|
281
|
-
messages,
|
|
514
|
+
messages: snapshot.history.ui,
|
|
515
|
+
history: snapshot.history,
|
|
282
516
|
status,
|
|
283
517
|
error,
|
|
284
518
|
pendingApprovals,
|
|
285
|
-
|
|
286
|
-
...
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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 } : {},
|
|
291
533
|
sendMessage,
|
|
292
534
|
stop,
|
|
293
535
|
regenerate,
|
|
294
536
|
editAndResend,
|
|
537
|
+
setHistory,
|
|
538
|
+
setMessages,
|
|
539
|
+
addToolResult,
|
|
540
|
+
clearError,
|
|
295
541
|
addToolApprovalResponse,
|
|
296
542
|
reconnect
|
|
297
543
|
};
|
|
@@ -301,6 +547,40 @@ function useObject(options) {
|
|
|
301
547
|
const [isLoading, setLoading] = useState(false);
|
|
302
548
|
const [error, setError] = useState(void 0);
|
|
303
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
|
+
);
|
|
304
584
|
const stop = useCallback(() => {
|
|
305
585
|
abortRef.current?.abort();
|
|
306
586
|
}, []);
|
|
@@ -311,7 +591,8 @@ function useObject(options) {
|
|
|
311
591
|
abortRef.current = controller;
|
|
312
592
|
setLoading(true);
|
|
313
593
|
setError(void 0);
|
|
314
|
-
|
|
594
|
+
commit(void 0);
|
|
595
|
+
flush();
|
|
315
596
|
try {
|
|
316
597
|
const doFetch = options.fetch ?? fetch;
|
|
317
598
|
const res = await doFetch(options.api, {
|
|
@@ -320,9 +601,11 @@ function useObject(options) {
|
|
|
320
601
|
body: JSON.stringify({ input }),
|
|
321
602
|
signal: controller.signal
|
|
322
603
|
});
|
|
323
|
-
for await (const part of readDeuzStream(res
|
|
604
|
+
for await (const part of readDeuzStream(res, {
|
|
605
|
+
...options.onHttpError ? { onHttpError: options.onHttpError } : {}
|
|
606
|
+
})) {
|
|
324
607
|
if (part.type === "object-delta") {
|
|
325
|
-
|
|
608
|
+
commit(part.object);
|
|
326
609
|
} else if (part.type === "error") {
|
|
327
610
|
setError(new Error(part.message));
|
|
328
611
|
break;
|
|
@@ -334,9 +617,10 @@ function useObject(options) {
|
|
|
334
617
|
}
|
|
335
618
|
} finally {
|
|
336
619
|
setLoading(false);
|
|
620
|
+
flush();
|
|
337
621
|
}
|
|
338
622
|
},
|
|
339
|
-
[options.api, options.fetch, options.headers]
|
|
623
|
+
[options.api, options.fetch, options.headers, options.onHttpError, commit, flush]
|
|
340
624
|
);
|
|
341
625
|
return { object, isLoading, error, submit, stop };
|
|
342
626
|
}
|
|
@@ -364,6 +648,6 @@ function CostBadge({ cost, format }) {
|
|
|
364
648
|
return /* @__PURE__ */ jsx("span", { "data-deuz": "cost-badge", children: `$${cost.costUsd.toFixed(4)}${saved}` });
|
|
365
649
|
}
|
|
366
650
|
|
|
367
|
-
export { CostBadge, ToolApprovalCard, useChat, useObject };
|
|
651
|
+
export { CostBadge, ToolApprovalCard, partsFromFiles, useChat, useObject };
|
|
368
652
|
//# sourceMappingURL=index.js.map
|
|
369
653
|
//# sourceMappingURL=index.js.map
|