@assistant-ui/react-google-adk 0.0.15 → 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.
@@ -11,6 +11,7 @@ import {
11
11
  type AppendMessage,
12
12
  type ThreadMessage,
13
13
  type ToolExecutionStatus,
14
+ generateId,
14
15
  } from "@assistant-ui/core";
15
16
  import {
16
17
  useCloudThreadListAdapter,
@@ -31,7 +32,7 @@ import type {
31
32
  } from "./types";
32
33
  import { useAdkMessages } from "./useAdkMessages";
33
34
  import { convertAdkMessage } from "./convertAdkMessages";
34
- import { symbolAdkRuntimeExtras, type AdkRuntimeExtras } from "./hooks";
35
+ import { adkExtras } from "./adkExtras";
35
36
  import { v4 as uuidv4 } from "uuid";
36
37
 
37
38
  /** @internal — exported for unit tests. */
@@ -138,8 +139,26 @@ const truncateAdkMessages = (
138
139
  return truncated;
139
140
  };
140
141
 
142
+ const toAdkUserMessage = (
143
+ msg: AppendMessage,
144
+ id = generateId(),
145
+ ): AdkMessage & { type: "human"; id: string } => ({
146
+ id,
147
+ type: "human",
148
+ content: getMessageContent(msg),
149
+ });
150
+
141
151
  export type UseAdkRuntimeOptions = ExternalStoreSharedOptions & {
142
152
  stream: AdkStreamCallback;
153
+ /**
154
+ * Called whenever the active thread's canonical (remote) ID changes, so the
155
+ * value can be treated as a managed/controlled variable (e.g. synced to a URL
156
+ * query param). Only the settled remote ID is emitted: while a freshly created
157
+ * thread is still optimistic the value is `undefined`, and the real ID is
158
+ * emitted once the thread is initialized; the transient local ID is never
159
+ * surfaced.
160
+ */
161
+ onThreadIdChange?: ((threadId: string | undefined) => void) | undefined;
143
162
  autoCancelPendingToolCalls?: boolean | undefined;
144
163
  unstable_allowCancellation?: boolean | undefined;
145
164
  getCheckpointId?: (
@@ -196,6 +215,7 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
196
215
  messageMetadata,
197
216
  sendMessage,
198
217
  cancel,
218
+ setMessages,
199
219
  replaceMessages,
200
220
  } = useAdkMessages({
201
221
  stream,
@@ -232,6 +252,50 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
232
252
  const threadMessagesRef = useRef(threadMessages);
233
253
  threadMessagesRef.current = threadMessages;
234
254
 
255
+ const adkMessagesRef = useRef(messages);
256
+ adkMessagesRef.current = messages;
257
+
258
+ const stagedMessagesRef = useRef(
259
+ new Map<
260
+ string,
261
+ {
262
+ message: AdkMessage & { id: string };
263
+ runConfig: AppendMessage["runConfig"];
264
+ }
265
+ >(),
266
+ );
267
+ const [stagedMessageCount, setStagedMessageCount] = useState(0);
268
+ const hasStagedMessages = stagedMessageCount > 0;
269
+
270
+ const getStagedRun = (parentId: string | null) => {
271
+ if (!parentId || !stagedMessagesRef.current.has(parentId)) return null;
272
+
273
+ const staged: AdkMessage[] = [];
274
+ for (const message of adkMessagesRef.current) {
275
+ if (message.id && stagedMessagesRef.current.has(message.id)) {
276
+ staged.push(stagedMessagesRef.current.get(message.id)!.message);
277
+ }
278
+ if (message.id === parentId) break;
279
+ }
280
+
281
+ return {
282
+ messages: staged,
283
+ runConfig: stagedMessagesRef.current.get(parentId)!.runConfig,
284
+ };
285
+ };
286
+
287
+ const stageUserMessage = (msg: AppendMessage) => {
288
+ const stagedMessage = toAdkUserMessage(msg);
289
+ stagedMessagesRef.current.set(stagedMessage.id, {
290
+ message: stagedMessage,
291
+ runConfig: msg.runConfig,
292
+ });
293
+ setStagedMessageCount(stagedMessagesRef.current.size);
294
+ const nextMessages = [...adkMessagesRef.current, stagedMessage];
295
+ adkMessagesRef.current = nextMessages;
296
+ setMessages(nextMessages);
297
+ };
298
+
235
299
  const runtime = useExternalStoreRuntime({
236
300
  ...pickExternalStoreSharedOptions(options),
237
301
  isRunning: effectiveIsRunning,
@@ -239,8 +303,7 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
239
303
  unstable_enableToolInvocations: true,
240
304
  setToolStatuses,
241
305
  adapters: { attachments, dictation, feedback, speech, voice },
242
- extras: {
243
- [symbolAdkRuntimeExtras]: true,
306
+ extras: adkExtras.provide({
244
307
  agentInfo,
245
308
  stateDelta,
246
309
  artifactDelta,
@@ -250,8 +313,13 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
250
313
  escalated,
251
314
  messageMetadata,
252
315
  send: handleSendMessage,
253
- } satisfies AdkRuntimeExtras,
316
+ }),
254
317
  onNew: async (msg) => {
318
+ if (!(msg.startRun ?? msg.role === "user")) {
319
+ stageUserMessage(msg);
320
+ return;
321
+ }
322
+
255
323
  const cancellations =
256
324
  autoCancelPendingToolCalls !== false
257
325
  ? getPendingCancellations(messages, longRunningToolIds)
@@ -276,6 +344,18 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
276
344
  msg.parentId,
277
345
  );
278
346
  replaceMessages(truncated);
347
+ if (!(msg.startRun ?? msg.role === "user")) {
348
+ const stagedMessage = toAdkUserMessage(msg);
349
+ stagedMessagesRef.current.set(stagedMessage.id, {
350
+ message: stagedMessage,
351
+ runConfig: msg.runConfig,
352
+ });
353
+ setStagedMessageCount(stagedMessagesRef.current.size);
354
+ const nextMessages = [...truncated, stagedMessage];
355
+ adkMessagesRef.current = nextMessages;
356
+ setMessages(nextMessages);
357
+ return;
358
+ }
279
359
  const externalId = aui.threadListItem().getState().externalId;
280
360
  const checkpointId = externalId
281
361
  ? await getCheckpointId(externalId, truncated)
@@ -295,23 +375,39 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
295
375
  );
296
376
  }
297
377
  : undefined,
298
- onReload: getCheckpointId
299
- ? async (parentId, config) => {
300
- const truncated = truncateAdkMessages(
301
- threadMessagesRef.current,
302
- parentId,
303
- );
304
- replaceMessages(truncated);
305
- const externalId = aui.threadListItem().getState().externalId;
306
- const checkpointId = externalId
307
- ? await getCheckpointId(externalId, truncated)
308
- : null;
309
- return handleSendMessage([], {
310
- runConfig: config.runConfig,
311
- ...(checkpointId && { checkpointId }),
312
- });
378
+ ...(getCheckpointId || hasStagedMessages
379
+ ? {
380
+ onReload: async (parentId, config) => {
381
+ const stagedRun = getStagedRun(parentId);
382
+ if (stagedRun) {
383
+ for (const message of stagedRun.messages) {
384
+ stagedMessagesRef.current.delete(message.id);
385
+ }
386
+ setStagedMessageCount(stagedMessagesRef.current.size);
387
+ return handleSendMessage(stagedRun.messages, {
388
+ runConfig: config.runConfig ?? stagedRun.runConfig,
389
+ });
390
+ }
391
+
392
+ if (!getCheckpointId)
393
+ throw new Error("Runtime does not support reloading messages.");
394
+
395
+ const truncated = truncateAdkMessages(
396
+ threadMessagesRef.current,
397
+ parentId,
398
+ );
399
+ replaceMessages(truncated);
400
+ const externalId = aui.threadListItem().getState().externalId;
401
+ const checkpointId = externalId
402
+ ? await getCheckpointId(externalId, truncated)
403
+ : null;
404
+ return handleSendMessage([], {
405
+ runConfig: config.runConfig,
406
+ ...(checkpointId && { checkpointId }),
407
+ });
408
+ },
313
409
  }
314
- : undefined,
410
+ : {}),
315
411
  onAddToolResult: async ({
316
412
  toolCallId,
317
413
  toolName,
@@ -373,6 +469,7 @@ export const useAdkRuntime = ({
373
469
  sessionAdapter,
374
470
  create,
375
471
  delete: deleteFn,
472
+ onThreadIdChange,
376
473
  ...options
377
474
  }: UseAdkRuntimeOptions) => {
378
475
  const aui = useAui();
@@ -394,5 +491,6 @@ export const useAdkRuntime = ({
394
491
  },
395
492
  adapter,
396
493
  allowNesting: true,
494
+ onThreadIdChange,
397
495
  });
398
496
  };