@assistant-ui/eve 0.0.14 → 0.0.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/dist/convertEveMessages.d.ts +30 -3
- package/dist/convertEveMessages.d.ts.map +1 -1
- package/dist/convertEveMessages.js +109 -10
- package/dist/convertEveMessages.js.map +1 -1
- package/dist/useEveAgentRuntime.d.ts.map +1 -1
- package/dist/useEveAgentRuntime.js +30 -16
- package/dist/useEveAgentRuntime.js.map +1 -1
- package/package.json +7 -7
- package/src/convertEveMessages.test.ts +576 -4
- package/src/convertEveMessages.ts +187 -12
- package/src/useEveAgentRuntime.refetch.test.tsx +89 -0
- package/src/useEveAgentRuntime.test.tsx +574 -2
- package/src/useEveAgentRuntime.ts +80 -21
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useInsertionEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
} from "react";
|
|
4
11
|
import {
|
|
5
12
|
fromThreadMessageLike,
|
|
6
13
|
generateId,
|
|
@@ -28,6 +35,7 @@ import {
|
|
|
28
35
|
} from "eve/react";
|
|
29
36
|
import {
|
|
30
37
|
convertEveMessages,
|
|
38
|
+
findEveInputRequest,
|
|
31
39
|
getEveMessageContent,
|
|
32
40
|
toEveInputResponse,
|
|
33
41
|
} from "./convertEveMessages";
|
|
@@ -199,10 +207,9 @@ export const useEveAgentRuntime = (options: UseEveAgentRuntimeOptions = {}) => {
|
|
|
199
207
|
const hasExecutingTools = Object.values(toolStatuses).some(
|
|
200
208
|
(status) => status?.type === "executing",
|
|
201
209
|
);
|
|
202
|
-
const
|
|
203
|
-
agent.status === "submitted" ||
|
|
204
|
-
|
|
205
|
-
hasExecutingTools;
|
|
210
|
+
const providerIsRunning =
|
|
211
|
+
agent.status === "submitted" || agent.status === "streaming";
|
|
212
|
+
const isRunning = providerIsRunning || hasExecutingTools;
|
|
206
213
|
|
|
207
214
|
const convertedMessages = useMemo(() => {
|
|
208
215
|
const createdAtByMessageId = createdAtByMessageIdRef.current;
|
|
@@ -229,7 +236,13 @@ export const useEveAgentRuntime = (options: UseEveAgentRuntimeOptions = {}) => {
|
|
|
229
236
|
|
|
230
237
|
const messages = stagedMessages ?? convertedMessages;
|
|
231
238
|
const messagesRef = useRef(messages);
|
|
232
|
-
|
|
239
|
+
const agentRef = useRef(agent);
|
|
240
|
+
// Descendant layout effects can dispatch against these refs synchronously;
|
|
241
|
+
// this matches useA2ARuntime's commit-scoped ref publication.
|
|
242
|
+
useInsertionEffect(() => {
|
|
243
|
+
messagesRef.current = messages;
|
|
244
|
+
agentRef.current = agent;
|
|
245
|
+
}, [agent, messages]);
|
|
233
246
|
|
|
234
247
|
// Upstream `EveAgentStore` `send` and `respond` reject while a turn is in
|
|
235
248
|
// flight and only resolve once the turn's stream parks, so a pending chain
|
|
@@ -239,18 +252,22 @@ export const useEveAgentRuntime = (options: UseEveAgentRuntimeOptions = {}) => {
|
|
|
239
252
|
const sendEpochRef = useRef(0);
|
|
240
253
|
// A cancel drops the queued send but keeps its draft for a later promotion;
|
|
241
254
|
// only a reset discards the draft with the session, so the two need separate
|
|
242
|
-
// counters.
|
|
255
|
+
// counters. A queued read has no draft and outlives a cancel, so it waits on
|
|
256
|
+
// the reset counter instead.
|
|
243
257
|
const resetEpochRef = useRef(0);
|
|
244
258
|
const isMountedRef = useRef(true);
|
|
245
259
|
const runtimeRef = useRef<ReturnType<typeof useExternalStoreRuntime> | null>(
|
|
246
260
|
null,
|
|
247
261
|
);
|
|
248
262
|
|
|
249
|
-
const enqueueSend = (
|
|
250
|
-
|
|
263
|
+
const enqueueSend = (
|
|
264
|
+
dispatch: () => Promise<void>,
|
|
265
|
+
epochRef: { current: number } = sendEpochRef,
|
|
266
|
+
) => {
|
|
267
|
+
const epoch = epochRef.current;
|
|
251
268
|
const next = sendChainRef.current.then(() => {
|
|
252
|
-
if (
|
|
253
|
-
|
|
269
|
+
if (!isMountedRef.current) throw sendAbandonedError;
|
|
270
|
+
if (epoch !== epochRef.current) throw sendCancelledError;
|
|
254
271
|
return dispatch();
|
|
255
272
|
});
|
|
256
273
|
sendChainRef.current = next.catch(() => {});
|
|
@@ -339,7 +356,7 @@ export const useEveAgentRuntime = (options: UseEveAgentRuntimeOptions = {}) => {
|
|
|
339
356
|
const runtime = useExternalStoreRuntime({
|
|
340
357
|
...pickExternalStoreSharedOptions(options),
|
|
341
358
|
messages,
|
|
342
|
-
isRunning,
|
|
359
|
+
isRunning: providerIsRunning,
|
|
343
360
|
extras,
|
|
344
361
|
unstable_enableToolInvocations: true,
|
|
345
362
|
setToolStatuses,
|
|
@@ -419,18 +436,60 @@ export const useEveAgentRuntime = (options: UseEveAgentRuntimeOptions = {}) => {
|
|
|
419
436
|
},
|
|
420
437
|
}
|
|
421
438
|
: {}),
|
|
422
|
-
onCancel: () => {
|
|
439
|
+
onCancel: async () => {
|
|
423
440
|
sendEpochRef.current += 1;
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
441
|
+
// Eve 0.38 replaced the binding's local-abort `stop()` with the durable
|
|
442
|
+
// `cancel()`, so the adapter detects which side of that break the host's
|
|
443
|
+
// eve provides instead of pinning the peer range to one of them.
|
|
444
|
+
const controls = agent as
|
|
445
|
+
| { readonly cancel: () => Promise<unknown> }
|
|
446
|
+
| { readonly stop: () => void };
|
|
447
|
+
if ("cancel" in controls) {
|
|
448
|
+
await controls.cancel();
|
|
449
|
+
} else {
|
|
450
|
+
controls.stop();
|
|
432
451
|
}
|
|
433
452
|
},
|
|
453
|
+
// Hosts below eve 0.44.1 expose no `resume`; leaving the capability absent
|
|
454
|
+
// keeps `threads.reloadMainThread()` on core's no-capability no-op.
|
|
455
|
+
...("resume" in agent
|
|
456
|
+
? {
|
|
457
|
+
onRefetchThread: () =>
|
|
458
|
+
// `resume()` rejects during a turn, so the replay rides the send
|
|
459
|
+
// chain like every other dispatch and runs once the turn parks; a
|
|
460
|
+
// cancel of that turn does not drop it, or the caller would be told
|
|
461
|
+
// the thread was refetched when it was not. Upstream shares one
|
|
462
|
+
// replay across concurrent `resume()` calls, so a refetch
|
|
463
|
+
// dispatched while `resume: true` is replaying on mount joins that
|
|
464
|
+
// read instead of taking a fresh one. The session is read at
|
|
465
|
+
// dispatch time because nothing durable exists before the first
|
|
466
|
+
// send lands.
|
|
467
|
+
enqueueSend(async () => {
|
|
468
|
+
const live = agentRef.current;
|
|
469
|
+
if (live.session === undefined) return;
|
|
470
|
+
await live.resume();
|
|
471
|
+
}, resetEpochRef).catch((error) => {
|
|
472
|
+
if (isDroppedSend(error)) return;
|
|
473
|
+
throw error;
|
|
474
|
+
}),
|
|
475
|
+
}
|
|
476
|
+
: {}),
|
|
477
|
+
onRespondToToolApproval: (response) => {
|
|
478
|
+
// Eve resolves a request the moment any response for it arrives, and an
|
|
479
|
+
// empty one is recorded as an answer with no content. Mapping before the
|
|
480
|
+
// send is enqueued keeps an unmappable response unsent, so the request
|
|
481
|
+
// stays answerable; the mapper's error reaches the caller as the seam's
|
|
482
|
+
// rejection.
|
|
483
|
+
const inputResponse = toEveInputResponse(
|
|
484
|
+
response,
|
|
485
|
+
findEveInputRequest(agent.data, response.approvalId),
|
|
486
|
+
);
|
|
487
|
+
return enqueueSend(() => agent.respond([inputResponse])).catch(
|
|
488
|
+
(error) => {
|
|
489
|
+
if (!isDroppedSend(error)) throw error;
|
|
490
|
+
},
|
|
491
|
+
);
|
|
492
|
+
},
|
|
434
493
|
});
|
|
435
494
|
runtimeRef.current = runtime;
|
|
436
495
|
|