@assistant-ui/react-google-adk 0.0.15 → 0.0.17

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.
Files changed (60) hide show
  1. package/dist/AdkClient.d.ts +0 -1
  2. package/dist/AdkClient.d.ts.map +1 -1
  3. package/dist/AdkClient.js +11 -10
  4. package/dist/AdkClient.js.map +1 -1
  5. package/dist/AdkEventAccumulator.d.ts +0 -1
  6. package/dist/AdkEventAccumulator.d.ts.map +1 -1
  7. package/dist/AdkEventAccumulator.js +2 -2
  8. package/dist/AdkEventAccumulator.js.map +1 -1
  9. package/dist/AdkSessionAdapter.d.ts +0 -1
  10. package/dist/AdkSessionAdapter.d.ts.map +1 -1
  11. package/dist/AdkSessionAdapter.js +5 -2
  12. package/dist/AdkSessionAdapter.js.map +1 -1
  13. package/dist/adkExtras.d.ts +6 -0
  14. package/dist/adkExtras.d.ts.map +1 -0
  15. package/dist/adkExtras.js +7 -0
  16. package/dist/adkExtras.js.map +1 -0
  17. package/dist/contentToParts.d.ts +0 -1
  18. package/dist/contentToParts.d.ts.map +1 -1
  19. package/dist/convertAdkMessages.d.ts +0 -1
  20. package/dist/convertAdkMessages.d.ts.map +1 -1
  21. package/dist/hooks.d.ts +1 -18
  22. package/dist/hooks.d.ts.map +1 -1
  23. package/dist/hooks.js +48 -128
  24. package/dist/hooks.js.map +1 -1
  25. package/dist/server/adkEventStream.d.ts.map +1 -1
  26. package/dist/server/createAdkApiRoute.d.ts +0 -1
  27. package/dist/server/createAdkApiRoute.d.ts.map +1 -1
  28. package/dist/server/parseAdkRequest.d.ts +0 -1
  29. package/dist/server/parseAdkRequest.d.ts.map +1 -1
  30. package/dist/server/parseAdkRequest.js +2 -2
  31. package/dist/server/parseAdkRequest.js.map +1 -1
  32. package/dist/structuredEvents.d.ts +0 -1
  33. package/dist/structuredEvents.d.ts.map +1 -1
  34. package/dist/trimTrailingSlashes.d.ts +5 -0
  35. package/dist/trimTrailingSlashes.d.ts.map +1 -0
  36. package/dist/trimTrailingSlashes.js +10 -0
  37. package/dist/trimTrailingSlashes.js.map +1 -0
  38. package/dist/types.d.ts +17 -3
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/types.js.map +1 -1
  41. package/dist/useAdkMessages.d.ts +1 -5
  42. package/dist/useAdkMessages.d.ts.map +1 -1
  43. package/dist/useAdkMessages.js +2 -1
  44. package/dist/useAdkMessages.js.map +1 -1
  45. package/dist/useAdkRuntime.d.ts +10 -8
  46. package/dist/useAdkRuntime.d.ts.map +1 -1
  47. package/dist/useAdkRuntime.js +68 -11
  48. package/dist/useAdkRuntime.js.map +1 -1
  49. package/package.json +7 -7
  50. package/src/AdkClient.test.ts +134 -0
  51. package/src/AdkClient.ts +15 -20
  52. package/src/AdkSessionAdapter.test.ts +26 -5
  53. package/src/AdkSessionAdapter.ts +10 -2
  54. package/src/adkExtras.ts +4 -0
  55. package/src/hooks.ts +46 -125
  56. package/src/server/parseAdkRequest.test.ts +4 -2
  57. package/src/server/parseAdkRequest.ts +4 -2
  58. package/src/trimTrailingSlashes.ts +5 -0
  59. package/src/types.ts +13 -0
  60. package/src/useAdkRuntime.ts +118 -20
package/src/hooks.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { useAui, useAuiState } from "@assistant-ui/store";
1
+ import { useAui } from "@assistant-ui/store";
2
2
  import { v4 as uuidv4 } from "uuid";
3
3
  import type { ReadonlyJSONValue } from "assistant-stream/utils";
4
+ import { adkExtras } from "./adkExtras";
4
5
  import type {
5
6
  AdkMessage,
6
7
  AdkSendMessageConfig,
@@ -10,115 +11,50 @@ import type {
10
11
  AdkMessageMetadata,
11
12
  } from "./types";
12
13
 
13
- export const symbolAdkRuntimeExtras = Symbol("adk-runtime-extras");
14
-
15
- export type AdkRuntimeExtras = {
16
- [symbolAdkRuntimeExtras]: true;
17
- send: (messages: AdkMessage[], config: AdkSendMessageConfig) => Promise<void>;
18
- agentInfo: { name?: string | undefined; branch?: string | undefined };
19
- stateDelta: Record<string, unknown>;
20
- artifactDelta: Record<string, number>;
21
- longRunningToolIds: string[];
22
- toolConfirmations: AdkToolConfirmation[];
23
- authRequests: AdkAuthRequest[];
24
- escalated: boolean;
25
- messageMetadata: Map<string, AdkMessageMetadata>;
26
- };
27
-
28
- const asAdkRuntimeExtras = (extras: unknown): AdkRuntimeExtras => {
29
- if (
30
- typeof extras !== "object" ||
31
- extras == null ||
32
- !(symbolAdkRuntimeExtras in extras)
33
- )
34
- throw new Error(
35
- "This method can only be called when you are using useAdkRuntime",
36
- );
37
-
38
- return extras as AdkRuntimeExtras;
39
- };
14
+ const EMPTY_STATE_DELTA: Record<string, unknown> = {};
15
+ const EMPTY_ARTIFACT_DELTA: Record<string, number> = {};
16
+ const EMPTY_LONG_RUNNING_TOOL_IDS: string[] = [];
17
+ const EMPTY_TOOL_CONFIRMATIONS: AdkToolConfirmation[] = [];
18
+ const EMPTY_AUTH_REQUESTS: AdkAuthRequest[] = [];
19
+ const EMPTY_MESSAGE_METADATA = new Map<string, AdkMessageMetadata>();
40
20
 
41
21
  /** Returns the name and branch of the currently active ADK agent. */
42
- export const useAdkAgentInfo = () => {
43
- return useAuiState((s) => {
44
- const extras = s.thread.extras;
45
- if (!extras) return undefined;
46
- return asAdkRuntimeExtras(extras).agentInfo;
47
- });
48
- };
22
+ export const useAdkAgentInfo = () =>
23
+ adkExtras.use((e) => e.agentInfo, undefined);
49
24
 
50
25
  /** Returns the accumulated session state delta from ADK events. */
51
- export const useAdkSessionState = () => {
52
- return useAuiState((s) => {
53
- const extras = s.thread.extras;
54
- if (!extras) return {};
55
- return asAdkRuntimeExtras(extras).stateDelta;
56
- });
57
- };
26
+ export const useAdkSessionState = () =>
27
+ adkExtras.use((e) => e.stateDelta, EMPTY_STATE_DELTA);
58
28
 
59
29
  /** Returns a function to send raw ADK messages. */
60
30
  export const useAdkSend = () => {
61
31
  const aui = useAui();
62
- return (messages: AdkMessage[], config: AdkSendMessageConfig) => {
63
- const extras = aui.thread().getState().extras;
64
- const { send } = asAdkRuntimeExtras(extras);
65
- return send(messages, config);
66
- };
32
+ return (messages: AdkMessage[], config: AdkSendMessageConfig) =>
33
+ adkExtras.get(aui).send(messages, config);
67
34
  };
68
35
 
69
36
  /** Returns the IDs of long-running tools awaiting external input. */
70
- export const useAdkLongRunningToolIds = () => {
71
- return useAuiState((s) => {
72
- const extras = s.thread.extras;
73
- if (!extras) return [];
74
- return asAdkRuntimeExtras(extras).longRunningToolIds;
75
- });
76
- };
37
+ export const useAdkLongRunningToolIds = () =>
38
+ adkExtras.use((e) => e.longRunningToolIds, EMPTY_LONG_RUNNING_TOOL_IDS);
77
39
 
78
40
  /** Returns pending tool confirmation requests (from SecurityPlugin etc). */
79
- export const useAdkToolConfirmations = () => {
80
- return useAuiState((s) => {
81
- const extras = s.thread.extras;
82
- if (!extras) return [];
83
- return asAdkRuntimeExtras(extras).toolConfirmations;
84
- });
85
- };
41
+ export const useAdkToolConfirmations = () =>
42
+ adkExtras.use((e) => e.toolConfirmations, EMPTY_TOOL_CONFIRMATIONS);
86
43
 
87
44
  /** Returns pending auth credential requests from tools. */
88
- export const useAdkAuthRequests = () => {
89
- return useAuiState((s) => {
90
- const extras = s.thread.extras;
91
- if (!extras) return [];
92
- return asAdkRuntimeExtras(extras).authRequests;
93
- });
94
- };
45
+ export const useAdkAuthRequests = () =>
46
+ adkExtras.use((e) => e.authRequests, EMPTY_AUTH_REQUESTS);
95
47
 
96
48
  /** Returns the accumulated artifact delta (filename → version). */
97
- export const useAdkArtifacts = () => {
98
- return useAuiState((s) => {
99
- const extras = s.thread.extras;
100
- if (!extras) return {};
101
- return asAdkRuntimeExtras(extras).artifactDelta;
102
- });
103
- };
49
+ export const useAdkArtifacts = () =>
50
+ adkExtras.use((e) => e.artifactDelta, EMPTY_ARTIFACT_DELTA);
104
51
 
105
52
  /** Returns whether any agent has escalated (requested human handoff). */
106
- export const useAdkEscalation = () => {
107
- return useAuiState((s) => {
108
- const extras = s.thread.extras;
109
- if (!extras) return false;
110
- return asAdkRuntimeExtras(extras).escalated;
111
- });
112
- };
53
+ export const useAdkEscalation = () => adkExtras.use((e) => e.escalated, false);
113
54
 
114
55
  /** Returns per-message metadata (grounding, citation, usage). Keyed by message ID. */
115
- export const useAdkMessageMetadata = () => {
116
- return useAuiState((s) => {
117
- const extras = s.thread.extras;
118
- if (!extras) return new Map<string, AdkMessageMetadata>();
119
- return asAdkRuntimeExtras(extras).messageMetadata;
120
- });
121
- };
56
+ export const useAdkMessageMetadata = () =>
57
+ adkExtras.use((e) => e.messageMetadata, EMPTY_MESSAGE_METADATA);
122
58
 
123
59
  // ── Convenience helpers for interactive flows ──
124
60
 
@@ -129,10 +65,8 @@ export const useAdkConfirmTool = () => {
129
65
  toolCallId: string,
130
66
  confirmed: boolean,
131
67
  payload?: ReadonlyJSONValue,
132
- ) => {
133
- const extras = aui.thread().getState().extras;
134
- const { send } = asAdkRuntimeExtras(extras);
135
- return send(
68
+ ) =>
69
+ adkExtras.get(aui).send(
136
70
  [
137
71
  {
138
72
  id: uuidv4(),
@@ -148,16 +82,13 @@ export const useAdkConfirmTool = () => {
148
82
  ],
149
83
  {},
150
84
  );
151
- };
152
85
  };
153
86
 
154
87
  /** Returns a function to submit auth credentials for a pending auth request. */
155
88
  export const useAdkSubmitAuth = () => {
156
89
  const aui = useAui();
157
- return (toolCallId: string, credential: AdkAuthCredential) => {
158
- const extras = aui.thread().getState().extras;
159
- const { send } = asAdkRuntimeExtras(extras);
160
- return send(
90
+ return (toolCallId: string, credential: AdkAuthCredential) =>
91
+ adkExtras.get(aui).send(
161
92
  [
162
93
  {
163
94
  id: uuidv4(),
@@ -170,16 +101,13 @@ export const useAdkSubmitAuth = () => {
170
101
  ],
171
102
  {},
172
103
  );
173
- };
174
104
  };
175
105
 
176
106
  /** Returns a function to submit the user's answer for a pending `adk_request_input` HITL interrupt. */
177
107
  export const useAdkSubmitInput = () => {
178
108
  const aui = useAui();
179
- return (toolCallId: string, result: ReadonlyJSONValue) => {
180
- const extras = aui.thread().getState().extras;
181
- const { send } = asAdkRuntimeExtras(extras);
182
- return send(
109
+ return (toolCallId: string, result: ReadonlyJSONValue) =>
110
+ adkExtras.get(aui).send(
183
111
  [
184
112
  {
185
113
  id: uuidv4(),
@@ -192,7 +120,6 @@ export const useAdkSubmitInput = () => {
192
120
  ],
193
121
  {},
194
122
  );
195
- };
196
123
  };
197
124
 
198
125
  // ── State prefix helpers ──
@@ -215,28 +142,22 @@ const filterByPrefix = (
215
142
  };
216
143
 
217
144
  /** Returns app-level state (keys prefixed with `app:`, prefix stripped). */
218
- export const useAdkAppState = () => {
219
- return useAuiState((s) => {
220
- const extras = s.thread.extras;
221
- if (!extras) return {};
222
- return filterByPrefix(asAdkRuntimeExtras(extras).stateDelta, APP_PREFIX);
223
- });
224
- };
145
+ export const useAdkAppState = () =>
146
+ adkExtras.use(
147
+ (e) => filterByPrefix(e.stateDelta, APP_PREFIX),
148
+ EMPTY_STATE_DELTA,
149
+ );
225
150
 
226
151
  /** Returns user-level state (keys prefixed with `user:`, prefix stripped). */
227
- export const useAdkUserState = () => {
228
- return useAuiState((s) => {
229
- const extras = s.thread.extras;
230
- if (!extras) return {};
231
- return filterByPrefix(asAdkRuntimeExtras(extras).stateDelta, USER_PREFIX);
232
- });
233
- };
152
+ export const useAdkUserState = () =>
153
+ adkExtras.use(
154
+ (e) => filterByPrefix(e.stateDelta, USER_PREFIX),
155
+ EMPTY_STATE_DELTA,
156
+ );
234
157
 
235
158
  /** Returns temp state (keys prefixed with `temp:`, prefix stripped). Not persisted. */
236
- export const useAdkTempState = () => {
237
- return useAuiState((s) => {
238
- const extras = s.thread.extras;
239
- if (!extras) return {};
240
- return filterByPrefix(asAdkRuntimeExtras(extras).stateDelta, TEMP_PREFIX);
241
- });
242
- };
159
+ export const useAdkTempState = () =>
160
+ adkExtras.use(
161
+ (e) => filterByPrefix(e.stateDelta, TEMP_PREFIX),
162
+ EMPTY_STATE_DELTA,
163
+ );
@@ -85,12 +85,14 @@ describe("parseAdkRequest", () => {
85
85
  method: "POST",
86
86
  body: "not json",
87
87
  });
88
- await expect(parseAdkRequest(req)).rejects.toThrow();
88
+ await expect(parseAdkRequest(req)).rejects.toThrow(
89
+ 'Invalid JSON in Google ADK proxy request body. Expected a JSON object like {"message":"Hello"} or {"type":"tool-result",...}.',
90
+ );
89
91
  });
90
92
 
91
93
  it("throws on non-object body", async () => {
92
94
  await expect(parseAdkRequest(makeRequest([1, 2, 3]))).rejects.toThrow(
93
- "Request body must be a JSON object",
95
+ "Google ADK proxy request body must be a JSON object",
94
96
  );
95
97
  });
96
98
  });
@@ -46,11 +46,13 @@ export const parseAdkRequest = async (
46
46
  try {
47
47
  body = (await request.json()) as Record<string, unknown>;
48
48
  } catch {
49
- throw new Error("Invalid JSON in request body");
49
+ throw new Error(
50
+ 'Invalid JSON in Google ADK proxy request body. Expected a JSON object like {"message":"Hello"} or {"type":"tool-result",...}.',
51
+ );
50
52
  }
51
53
 
52
54
  if (!body || typeof body !== "object" || Array.isArray(body)) {
53
- throw new Error("Request body must be a JSON object");
55
+ throw new Error("Google ADK proxy request body must be a JSON object");
54
56
  }
55
57
 
56
58
  const config: AdkSendMessageConfig = {};
@@ -0,0 +1,5 @@
1
+ export const trimTrailingSlashes = (value: string) => {
2
+ let end = value.length;
3
+ while (end > 0 && value[end - 1] === "/") end -= 1;
4
+ return value.slice(0, end);
5
+ };
package/src/types.ts CHANGED
@@ -274,3 +274,16 @@ export type OnAdkCustomEventCallback = (
274
274
  export type OnAdkAgentTransferCallback = (
275
275
  toAgent: string,
276
276
  ) => void | Promise<void>;
277
+
278
+ /** Private state and actions `useAdkRuntime` exposes through `thread.extras`. */
279
+ export type AdkRuntimeExtras = {
280
+ send: (messages: AdkMessage[], config: AdkSendMessageConfig) => Promise<void>;
281
+ agentInfo: { name?: string | undefined; branch?: string | undefined };
282
+ stateDelta: Record<string, unknown>;
283
+ artifactDelta: Record<string, number>;
284
+ longRunningToolIds: string[];
285
+ toolConfirmations: AdkToolConfirmation[];
286
+ authRequests: AdkAuthRequest[];
287
+ escalated: boolean;
288
+ messageMetadata: Map<string, AdkMessageMetadata>;
289
+ };
@@ -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
  };