@assistant-ui/react-google-adk 0.0.20 → 0.0.22
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/AdkEventAccumulator.d.ts +1 -0
- package/dist/AdkEventAccumulator.d.ts.map +1 -1
- package/dist/AdkEventAccumulator.js +20 -4
- package/dist/AdkEventAccumulator.js.map +1 -1
- package/dist/AdkSessionAdapter.d.ts +4 -4
- package/dist/AdkSessionAdapter.d.ts.map +1 -1
- package/dist/AdkSessionAdapter.js +16 -3
- package/dist/AdkSessionAdapter.js.map +1 -1
- package/dist/convertAdkMessages.d.ts.map +1 -1
- package/dist/convertAdkMessages.js +18 -21
- package/dist/convertAdkMessages.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/server/adkEventStream.d.ts.map +1 -1
- package/dist/server/adkEventStream.js +11 -1
- package/dist/server/adkEventStream.js.map +1 -1
- package/dist/types.d.ts +22 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/useAdkMessages.d.ts +2 -1
- package/dist/useAdkMessages.d.ts.map +1 -1
- package/dist/useAdkMessages.js +19 -1
- package/dist/useAdkMessages.js.map +1 -1
- package/dist/useAdkRuntime.d.ts +10 -4
- package/dist/useAdkRuntime.d.ts.map +1 -1
- package/dist/useAdkRuntime.js +68 -29
- package/dist/useAdkRuntime.js.map +1 -1
- package/package.json +8 -5
- package/src/AdkEventAccumulator.test.ts +127 -0
- package/src/AdkEventAccumulator.ts +31 -5
- package/src/AdkSessionAdapter.test.ts +43 -0
- package/src/AdkSessionAdapter.ts +21 -4
- package/src/convertAdkMessages.test.ts +40 -6
- package/src/convertAdkMessages.ts +16 -15
- package/src/index.ts +1 -0
- package/src/server/adkEventStream.test.ts +38 -0
- package/src/server/adkEventStream.ts +16 -1
- package/src/types.ts +21 -0
- package/src/useAdkMessages.ts +24 -1
- package/src/useAdkRuntime.refetch.test.tsx +336 -0
- package/src/useAdkRuntime.test.ts +161 -0
- package/src/useAdkRuntime.ts +127 -28
package/src/useAdkRuntime.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import {
|
|
3
3
|
getExternalStoreMessages,
|
|
4
4
|
pickExternalStoreSharedOptions,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
type ToolExecutionStatus,
|
|
14
14
|
generateId,
|
|
15
15
|
} from "@assistant-ui/core";
|
|
16
|
-
import { parseDataUrl } from "@assistant-ui/core/internal";
|
|
16
|
+
import { httpUrlPattern, parseDataUrl } from "@assistant-ui/core/internal";
|
|
17
17
|
import {
|
|
18
18
|
useCloudThreadListAdapter,
|
|
19
19
|
useRemoteThreadListRuntime,
|
|
@@ -25,6 +25,7 @@ import type { AssistantCloud } from "assistant-cloud";
|
|
|
25
25
|
import type { RemoteThreadListAdapter } from "@assistant-ui/core";
|
|
26
26
|
import type {
|
|
27
27
|
AdkMessage,
|
|
28
|
+
AdkThreadSnapshot,
|
|
28
29
|
AdkSendMessageConfig,
|
|
29
30
|
AdkStreamCallback,
|
|
30
31
|
OnAdkErrorCallback,
|
|
@@ -50,10 +51,19 @@ export const getMessageContent = (msg: AppendMessage) => {
|
|
|
50
51
|
case "image":
|
|
51
52
|
return { type: "image_url" as const, url: part.image };
|
|
52
53
|
case "file":
|
|
54
|
+
if (part.sourceType === "url" || httpUrlPattern.test(part.data)) {
|
|
55
|
+
return {
|
|
56
|
+
type: "file_url" as const,
|
|
57
|
+
url: part.data,
|
|
58
|
+
mimeType: part.mimeType,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
53
61
|
return {
|
|
54
62
|
type: "file" as const,
|
|
55
63
|
mimeType: part.mimeType,
|
|
56
|
-
|
|
64
|
+
// Lands in Gemini `inlineData.data`, which takes bare base64, so a
|
|
65
|
+
// data URL envelope is stripped rather than forwarded.
|
|
66
|
+
data: parseDataUrl(part.data)?.data ?? part.data,
|
|
57
67
|
...(part.filename != null && { filename: part.filename }),
|
|
58
68
|
};
|
|
59
69
|
case "audio": {
|
|
@@ -171,7 +181,16 @@ export type UseAdkRuntimeOptions = ExternalStoreSharedOptions & {
|
|
|
171
181
|
threadId: string,
|
|
172
182
|
parentMessages: AdkMessage[],
|
|
173
183
|
) => Promise<string | null>;
|
|
174
|
-
|
|
184
|
+
/**
|
|
185
|
+
* Loads a thread's stored state. Called when the thread opens, and again for
|
|
186
|
+
* `threads.reloadMainThread()`, which refetches in place rather than
|
|
187
|
+
* remounting the runtime; the signal aborts a load the runtime no longer
|
|
188
|
+
* needs.
|
|
189
|
+
*/
|
|
190
|
+
load?: (
|
|
191
|
+
threadId: string,
|
|
192
|
+
options?: { signal?: AbortSignal | undefined },
|
|
193
|
+
) => Promise<AdkThreadSnapshot>;
|
|
175
194
|
create?: () => Promise<{ externalId: string }>;
|
|
176
195
|
delete?: (threadId: string) => Promise<void>;
|
|
177
196
|
adapters?:
|
|
@@ -223,11 +242,26 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
|
|
|
223
242
|
cancel,
|
|
224
243
|
setMessages,
|
|
225
244
|
replaceMessages,
|
|
245
|
+
applySnapshot,
|
|
226
246
|
} = useAdkMessages({
|
|
227
247
|
stream,
|
|
228
248
|
...(eventHandlers && { eventHandlers }),
|
|
229
249
|
});
|
|
230
250
|
|
|
251
|
+
const loadRef = useRef(load);
|
|
252
|
+
loadRef.current = load;
|
|
253
|
+
const loadControllerRef = useRef<{
|
|
254
|
+
controller: AbortController;
|
|
255
|
+
purpose: "initial" | "reload";
|
|
256
|
+
promise?: Promise<void> | undefined;
|
|
257
|
+
} | null>(null);
|
|
258
|
+
const messagesRef = useRef(messages);
|
|
259
|
+
messagesRef.current = messages;
|
|
260
|
+
const [isLoadingThread, setIsLoadingThread] = useState(
|
|
261
|
+
() =>
|
|
262
|
+
load !== undefined && aui.threadListItem.getState().externalId != null,
|
|
263
|
+
);
|
|
264
|
+
|
|
231
265
|
const [isRunning, setIsRunning] = useState(false);
|
|
232
266
|
const [toolStatuses, setToolStatuses] = useState<
|
|
233
267
|
Record<string, ToolExecutionStatus>
|
|
@@ -236,6 +270,8 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
|
|
|
236
270
|
(s) => s?.type === "executing",
|
|
237
271
|
);
|
|
238
272
|
const effectiveIsRunning = isRunning || hasExecutingTools;
|
|
273
|
+
const isRunningRef = useRef(effectiveIsRunning);
|
|
274
|
+
isRunningRef.current = effectiveIsRunning;
|
|
239
275
|
|
|
240
276
|
const handleSendMessage = async (
|
|
241
277
|
msgs: AdkMessage[],
|
|
@@ -302,9 +338,93 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
|
|
|
302
338
|
setMessages(nextMessages);
|
|
303
339
|
};
|
|
304
340
|
|
|
341
|
+
// The scoped client, not `aui` itself: useAui returns a render-bound
|
|
342
|
+
// instance, so depending on it would re-run the load on every render.
|
|
343
|
+
const threadListItem =
|
|
344
|
+
aui.threadListItem.source !== null ? aui.threadListItem : undefined;
|
|
345
|
+
|
|
346
|
+
const runLoad = useCallback(
|
|
347
|
+
(purpose: "initial" | "reload" = "initial") => {
|
|
348
|
+
const loadFn = loadRef.current;
|
|
349
|
+
if (!loadFn || !threadListItem) return Promise.resolve();
|
|
350
|
+
|
|
351
|
+
const externalId = threadListItem.getState().externalId;
|
|
352
|
+
if (externalId == null) return Promise.resolve();
|
|
353
|
+
|
|
354
|
+
// The initial load is already fetching what a refetch would ask for, and
|
|
355
|
+
// taking it over strands the thread's history if the refetch then fails.
|
|
356
|
+
if (
|
|
357
|
+
purpose === "reload" &&
|
|
358
|
+
loadControllerRef.current?.purpose === "initial"
|
|
359
|
+
)
|
|
360
|
+
return loadControllerRef.current.promise ?? Promise.resolve();
|
|
361
|
+
|
|
362
|
+
loadControllerRef.current?.controller.abort();
|
|
363
|
+
const controller = new AbortController();
|
|
364
|
+
const record: NonNullable<typeof loadControllerRef.current> = {
|
|
365
|
+
controller,
|
|
366
|
+
purpose,
|
|
367
|
+
};
|
|
368
|
+
loadControllerRef.current = record;
|
|
369
|
+
|
|
370
|
+
const messagesAtLoadStart = messagesRef.current;
|
|
371
|
+
if (purpose === "initial") setIsLoadingThread(true);
|
|
372
|
+
|
|
373
|
+
const task = loadFn(externalId, { signal: controller.signal })
|
|
374
|
+
.then((snapshot) => {
|
|
375
|
+
if (controller.signal.aborted) return;
|
|
376
|
+
// A snapshot the session assembled before a run cannot speak for what
|
|
377
|
+
// that run has since produced, and an ADK id cannot correlate a
|
|
378
|
+
// message sent optimistically with the one the session stored for it,
|
|
379
|
+
// so there is nothing here that could merge the two. A refetch that
|
|
380
|
+
// raced a run therefore defers to the run, whether the run started
|
|
381
|
+
// during the load or was already streaming when it began.
|
|
382
|
+
if (
|
|
383
|
+
purpose === "reload" &&
|
|
384
|
+
(isRunningRef.current ||
|
|
385
|
+
messagesRef.current !== messagesAtLoadStart)
|
|
386
|
+
)
|
|
387
|
+
return;
|
|
388
|
+
applySnapshot(snapshot);
|
|
389
|
+
})
|
|
390
|
+
.catch((error: unknown) => {
|
|
391
|
+
// Aborting a load the runtime no longer needs is not a failure.
|
|
392
|
+
if (controller.signal.aborted) return;
|
|
393
|
+
throw error;
|
|
394
|
+
})
|
|
395
|
+
.finally(() => {
|
|
396
|
+
if (loadControllerRef.current?.controller === controller) {
|
|
397
|
+
loadControllerRef.current = null;
|
|
398
|
+
}
|
|
399
|
+
if (controller.signal.aborted) return;
|
|
400
|
+
setIsLoadingThread(false);
|
|
401
|
+
});
|
|
402
|
+
record.promise = task;
|
|
403
|
+
|
|
404
|
+
// A refetch reports the failure to whoever awaited it; the initial load
|
|
405
|
+
// has no caller to tell.
|
|
406
|
+
if (purpose === "reload") return task;
|
|
407
|
+
return task.catch((e: unknown) => {
|
|
408
|
+
console.warn("Failed to load ADK session:", e);
|
|
409
|
+
});
|
|
410
|
+
},
|
|
411
|
+
[threadListItem, applySnapshot],
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
useEffect(() => {
|
|
415
|
+
runLoad();
|
|
416
|
+
return () => {
|
|
417
|
+
// Whatever is current, not this effect's own controller: a refetch swaps
|
|
418
|
+
// the ref, and one in flight at unmount must be aborted too.
|
|
419
|
+
loadControllerRef.current?.controller.abort();
|
|
420
|
+
setIsLoadingThread(false);
|
|
421
|
+
};
|
|
422
|
+
}, [runLoad]);
|
|
423
|
+
|
|
305
424
|
const runtime = useExternalStoreRuntime({
|
|
306
425
|
...pickExternalStoreSharedOptions(options),
|
|
307
426
|
isRunning: effectiveIsRunning,
|
|
427
|
+
isLoading: isLoadingThread,
|
|
308
428
|
messages: threadMessages,
|
|
309
429
|
unstable_enableToolInvocations: true,
|
|
310
430
|
setToolStatuses,
|
|
@@ -441,32 +561,11 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
|
|
|
441
561
|
cancel();
|
|
442
562
|
}
|
|
443
563
|
: undefined,
|
|
564
|
+
...(load !== undefined && {
|
|
565
|
+
onRefetchThread: () => runLoad("reload"),
|
|
566
|
+
}),
|
|
444
567
|
});
|
|
445
568
|
|
|
446
|
-
{
|
|
447
|
-
const loadRef = useRef(load);
|
|
448
|
-
useEffect(() => {
|
|
449
|
-
loadRef.current = load;
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
useEffect(() => {
|
|
453
|
-
const loadFn = loadRef.current;
|
|
454
|
-
if (!loadFn) return;
|
|
455
|
-
|
|
456
|
-
const externalId = aui.threadListItem.getState().externalId;
|
|
457
|
-
if (externalId == null) return;
|
|
458
|
-
|
|
459
|
-
loadFn(externalId).then(
|
|
460
|
-
({ messages: msgs }) => {
|
|
461
|
-
replaceMessages(msgs);
|
|
462
|
-
},
|
|
463
|
-
(e) => {
|
|
464
|
-
console.warn("Failed to load ADK session:", e);
|
|
465
|
-
},
|
|
466
|
-
);
|
|
467
|
-
}, [aui, replaceMessages]);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
569
|
return runtime;
|
|
471
570
|
};
|
|
472
571
|
|