@assistant-ui/eve 0.0.13 → 0.0.14
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 +17 -3
- package/dist/convertEveMessages.d.ts.map +1 -1
- package/dist/convertEveMessages.js +11 -6
- package/dist/convertEveMessages.js.map +1 -1
- package/dist/eveExtras.d.ts +7 -0
- package/dist/eveExtras.d.ts.map +1 -0
- package/dist/eveExtras.js +7 -0
- package/dist/eveExtras.js.map +1 -0
- package/dist/hooks.d.ts +25 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +32 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/useEveAgentRuntime.d.ts.map +1 -1
- package/dist/useEveAgentRuntime.js +37 -14
- package/dist/useEveAgentRuntime.js.map +1 -1
- package/package.json +6 -5
- package/src/convertEveMessages.test.ts +299 -4
- package/src/convertEveMessages.ts +42 -6
- package/src/eveExtras.ts +10 -0
- package/src/hooks.test.tsx +84 -0
- package/src/hooks.ts +36 -0
- package/src/index.ts +3 -0
- package/src/useEveAgentRuntime.test.tsx +492 -61
- package/src/useEveAgentRuntime.ts +85 -26
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import {
|
|
5
5
|
fromThreadMessageLike,
|
|
6
6
|
generateId,
|
|
@@ -26,12 +26,12 @@ import {
|
|
|
26
26
|
type UseEveAgentOptions,
|
|
27
27
|
type UseEveAgentStatus,
|
|
28
28
|
} from "eve/react";
|
|
29
|
-
import type { SendTurnPayload } from "eve/client";
|
|
30
29
|
import {
|
|
31
30
|
convertEveMessages,
|
|
32
31
|
getEveMessageContent,
|
|
33
32
|
toEveInputResponse,
|
|
34
33
|
} from "./convertEveMessages";
|
|
34
|
+
import { eveExtras } from "./eveExtras";
|
|
35
35
|
|
|
36
36
|
const USER_STAGED_STATUS = {
|
|
37
37
|
type: "complete",
|
|
@@ -83,22 +83,35 @@ const hasRunConfig = (
|
|
|
83
83
|
): runConfig is NonNullable<AppendMessage["runConfig"]> =>
|
|
84
84
|
runConfig?.custom !== undefined && Object.keys(runConfig.custom).length > 0;
|
|
85
85
|
|
|
86
|
+
type EveJsonValue =
|
|
87
|
+
| string
|
|
88
|
+
| number
|
|
89
|
+
| boolean
|
|
90
|
+
| null
|
|
91
|
+
| readonly EveJsonValue[]
|
|
92
|
+
| { readonly [key: string]: EveJsonValue };
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Structural equivalent of eve's `JsonObject`. Eve demoted its public send
|
|
96
|
+
* payload type once already (`SendTurnPayload`, public through 0.30, internal
|
|
97
|
+
* from 0.31), so the helpers declare the shapes they forward instead of
|
|
98
|
+
* re-exporting eve's option types, and assignability is checked at the send
|
|
99
|
+
* call sites against the installed version.
|
|
100
|
+
*/
|
|
101
|
+
type EveClientContext = { readonly [key: string]: EveJsonValue };
|
|
102
|
+
|
|
86
103
|
/**
|
|
87
104
|
* Only the `custom` bag crosses the wire. Eve reads `clientContext` as its own
|
|
88
105
|
* namespace and serializes it into a model-visible context message, so sending
|
|
89
106
|
* the assistant-ui envelope would surface a literal `"custom"` key in the
|
|
90
107
|
* prompt and to every eve-side handler.
|
|
91
108
|
*/
|
|
92
|
-
const
|
|
109
|
+
const toEveSendOptions = (
|
|
93
110
|
runConfig: AppendMessage["runConfig"],
|
|
94
|
-
):
|
|
111
|
+
): { readonly clientContext: EveClientContext } | undefined =>
|
|
95
112
|
hasRunConfig(runConfig)
|
|
96
|
-
? {
|
|
97
|
-
|
|
98
|
-
SendTurnPayload["clientContext"]
|
|
99
|
-
>,
|
|
100
|
-
}
|
|
101
|
-
: {};
|
|
113
|
+
? { clientContext: runConfig.custom as EveClientContext }
|
|
114
|
+
: undefined;
|
|
102
115
|
|
|
103
116
|
export type UseEveAgentRuntimeOptions = Omit<
|
|
104
117
|
UseEveAgentOptions<EveMessageData>,
|
|
@@ -218,20 +231,27 @@ export const useEveAgentRuntime = (options: UseEveAgentRuntimeOptions = {}) => {
|
|
|
218
231
|
const messagesRef = useRef(messages);
|
|
219
232
|
messagesRef.current = messages;
|
|
220
233
|
|
|
221
|
-
// Upstream `EveAgentStore
|
|
222
|
-
//
|
|
223
|
-
// an active turn; chaining every
|
|
224
|
-
// status.
|
|
234
|
+
// Upstream `EveAgentStore` `send` and `respond` reject while a turn is in
|
|
235
|
+
// flight and only resolve once the turn's stream parks, so a pending chain
|
|
236
|
+
// link is exactly an active turn; chaining every dispatch serializes them
|
|
237
|
+
// without watching status.
|
|
225
238
|
const sendChainRef = useRef<Promise<void>>(Promise.resolve());
|
|
226
239
|
const sendEpochRef = useRef(0);
|
|
240
|
+
// A cancel drops the queued send but keeps its draft for a later promotion;
|
|
241
|
+
// only a reset discards the draft with the session, so the two need separate
|
|
242
|
+
// counters.
|
|
243
|
+
const resetEpochRef = useRef(0);
|
|
227
244
|
const isMountedRef = useRef(true);
|
|
245
|
+
const runtimeRef = useRef<ReturnType<typeof useExternalStoreRuntime> | null>(
|
|
246
|
+
null,
|
|
247
|
+
);
|
|
228
248
|
|
|
229
|
-
const enqueueSend = (
|
|
249
|
+
const enqueueSend = (dispatch: () => Promise<void>) => {
|
|
230
250
|
const epoch = sendEpochRef.current;
|
|
231
251
|
const next = sendChainRef.current.then(() => {
|
|
232
252
|
if (epoch !== sendEpochRef.current)
|
|
233
253
|
throw isMountedRef.current ? sendCancelledError : sendAbandonedError;
|
|
234
|
-
return
|
|
254
|
+
return dispatch();
|
|
235
255
|
});
|
|
236
256
|
sendChainRef.current = next.catch(() => {});
|
|
237
257
|
return next;
|
|
@@ -290,10 +310,37 @@ export const useEveAgentRuntime = (options: UseEveAgentRuntimeOptions = {}) => {
|
|
|
290
310
|
setStagedMessages(nextMessages);
|
|
291
311
|
};
|
|
292
312
|
|
|
293
|
-
|
|
313
|
+
const reset = useCallback(() => {
|
|
314
|
+
runtimeRef.current?.thread.unstable_notifySessionReset();
|
|
315
|
+
// Sends parked behind an active turn captured the pre-reset epoch, so the
|
|
316
|
+
// epoch has to advance before the session is torn down or they dispatch
|
|
317
|
+
// into the new one; `lastFinishStatusRef` is run-scoped for the same
|
|
318
|
+
// reason.
|
|
319
|
+
sendEpochRef.current += 1;
|
|
320
|
+
resetEpochRef.current += 1;
|
|
321
|
+
lastFinishStatusRef.current = null;
|
|
322
|
+
setStagedMessages(null);
|
|
323
|
+
stagedInputsRef.current.clear();
|
|
324
|
+
setToolStatuses({});
|
|
325
|
+
agent.reset();
|
|
326
|
+
}, [agent]);
|
|
327
|
+
|
|
328
|
+
const extras = useMemo(
|
|
329
|
+
() =>
|
|
330
|
+
eveExtras.provide({
|
|
331
|
+
error: agent.error,
|
|
332
|
+
events: agent.events,
|
|
333
|
+
session: agent.session,
|
|
334
|
+
reset,
|
|
335
|
+
}),
|
|
336
|
+
[agent.error, agent.events, agent.session, reset],
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
const runtime = useExternalStoreRuntime({
|
|
294
340
|
...pickExternalStoreSharedOptions(options),
|
|
295
341
|
messages,
|
|
296
342
|
isRunning,
|
|
343
|
+
extras,
|
|
297
344
|
unstable_enableToolInvocations: true,
|
|
298
345
|
setToolStatuses,
|
|
299
346
|
adapters: {
|
|
@@ -309,10 +356,12 @@ export const useEveAgentRuntime = (options: UseEveAgentRuntimeOptions = {}) => {
|
|
|
309
356
|
return;
|
|
310
357
|
}
|
|
311
358
|
try {
|
|
312
|
-
await enqueueSend(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
359
|
+
await enqueueSend(() =>
|
|
360
|
+
agent.send(
|
|
361
|
+
getEveMessageContent(message),
|
|
362
|
+
toEveSendOptions(message.runConfig),
|
|
363
|
+
),
|
|
364
|
+
);
|
|
316
365
|
} catch (error) {
|
|
317
366
|
// A cancelled send never reached the session, so it rethrows for the
|
|
318
367
|
// composer to take the draft back; an unmounted one has no composer
|
|
@@ -328,6 +377,7 @@ export const useEveAgentRuntime = (options: UseEveAgentRuntimeOptions = {}) => {
|
|
|
328
377
|
if (!stagedRun)
|
|
329
378
|
throw new Error("Runtime does not support reloading messages.");
|
|
330
379
|
const epoch = sendEpochRef.current;
|
|
380
|
+
const resetEpoch = resetEpochRef.current;
|
|
331
381
|
for (const { message: stagedMessage, input } of stagedRun) {
|
|
332
382
|
if (epoch !== sendEpochRef.current) return;
|
|
333
383
|
const previousMessages = messagesRef.current;
|
|
@@ -347,11 +397,17 @@ export const useEveAgentRuntime = (options: UseEveAgentRuntimeOptions = {}) => {
|
|
|
347
397
|
? config.runConfig
|
|
348
398
|
: input.runConfig;
|
|
349
399
|
try {
|
|
350
|
-
await enqueueSend(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
400
|
+
await enqueueSend(() =>
|
|
401
|
+
agent.send(
|
|
402
|
+
getEveMessageContent(input.message),
|
|
403
|
+
toEveSendOptions(runConfig),
|
|
404
|
+
),
|
|
405
|
+
);
|
|
354
406
|
} catch (error) {
|
|
407
|
+
// A reset discarded the draft along with the session, so
|
|
408
|
+
// restoring it here would refloat a message the thread no
|
|
409
|
+
// longer has. A cancel still restores.
|
|
410
|
+
if (resetEpoch !== resetEpochRef.current) return;
|
|
355
411
|
stagedInputsRef.current.set(stagedMessage.id, input);
|
|
356
412
|
messagesRef.current = previousMessages;
|
|
357
413
|
setStagedMessages(previousMessages);
|
|
@@ -370,10 +426,13 @@ export const useEveAgentRuntime = (options: UseEveAgentRuntimeOptions = {}) => {
|
|
|
370
426
|
},
|
|
371
427
|
onRespondToToolApproval: async (response) => {
|
|
372
428
|
try {
|
|
373
|
-
await enqueueSend(
|
|
429
|
+
await enqueueSend(() => agent.respond([toEveInputResponse(response)]));
|
|
374
430
|
} catch (error) {
|
|
375
431
|
if (!isDroppedSend(error)) throw error;
|
|
376
432
|
}
|
|
377
433
|
},
|
|
378
434
|
});
|
|
435
|
+
runtimeRef.current = runtime;
|
|
436
|
+
|
|
437
|
+
return runtime;
|
|
379
438
|
};
|