@assistant-ui/react-google-adk 0.0.1

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 (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/AdkClient.d.ts +45 -0
  4. package/dist/AdkClient.d.ts.map +1 -0
  5. package/dist/AdkClient.js +204 -0
  6. package/dist/AdkClient.js.map +1 -0
  7. package/dist/AdkEventAccumulator.d.ts +45 -0
  8. package/dist/AdkEventAccumulator.d.ts.map +1 -0
  9. package/dist/AdkEventAccumulator.js +508 -0
  10. package/dist/AdkEventAccumulator.js.map +1 -0
  11. package/dist/AdkSessionAdapter.d.ts +61 -0
  12. package/dist/AdkSessionAdapter.d.ts.map +1 -0
  13. package/dist/AdkSessionAdapter.js +159 -0
  14. package/dist/AdkSessionAdapter.js.map +1 -0
  15. package/dist/convertAdkMessages.d.ts +4 -0
  16. package/dist/convertAdkMessages.d.ts.map +1 -0
  17. package/dist/convertAdkMessages.js +75 -0
  18. package/dist/convertAdkMessages.js.map +1 -0
  19. package/dist/hooks.d.ts +50 -0
  20. package/dist/hooks.d.ts.map +1 -0
  21. package/dist/hooks.js +173 -0
  22. package/dist/hooks.js.map +1 -0
  23. package/dist/index.d.ts +11 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +10 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/server/adkEventStream.d.ts +42 -0
  28. package/dist/server/adkEventStream.d.ts.map +1 -0
  29. package/dist/server/adkEventStream.js +135 -0
  30. package/dist/server/adkEventStream.js.map +1 -0
  31. package/dist/server/createAdkApiRoute.d.ts +47 -0
  32. package/dist/server/createAdkApiRoute.d.ts.map +1 -0
  33. package/dist/server/createAdkApiRoute.js +41 -0
  34. package/dist/server/createAdkApiRoute.js.map +1 -0
  35. package/dist/server/index.d.ts +4 -0
  36. package/dist/server/index.d.ts.map +1 -0
  37. package/dist/server/index.js +4 -0
  38. package/dist/server/index.js.map +1 -0
  39. package/dist/server/parseAdkRequest.d.ts +56 -0
  40. package/dist/server/parseAdkRequest.d.ts.map +1 -0
  41. package/dist/server/parseAdkRequest.js +93 -0
  42. package/dist/server/parseAdkRequest.js.map +1 -0
  43. package/dist/structuredEvents.d.ts +7 -0
  44. package/dist/structuredEvents.d.ts.map +1 -0
  45. package/dist/structuredEvents.js +79 -0
  46. package/dist/structuredEvents.js.map +1 -0
  47. package/dist/types.d.ts +253 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +14 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/useAdkMessages.d.ts +28 -0
  52. package/dist/useAdkMessages.d.ts.map +1 -0
  53. package/dist/useAdkMessages.js +198 -0
  54. package/dist/useAdkMessages.js.map +1 -0
  55. package/dist/useAdkRuntime.d.ts +36 -0
  56. package/dist/useAdkRuntime.d.ts.map +1 -0
  57. package/dist/useAdkRuntime.js +252 -0
  58. package/dist/useAdkRuntime.js.map +1 -0
  59. package/package.json +83 -0
  60. package/server/package.json +4 -0
  61. package/src/AdkClient.test.ts +662 -0
  62. package/src/AdkClient.ts +274 -0
  63. package/src/AdkEventAccumulator.test.ts +591 -0
  64. package/src/AdkEventAccumulator.ts +602 -0
  65. package/src/AdkSessionAdapter.test.ts +362 -0
  66. package/src/AdkSessionAdapter.ts +245 -0
  67. package/src/convertAdkMessages.test.ts +209 -0
  68. package/src/convertAdkMessages.ts +93 -0
  69. package/src/hooks.ts +217 -0
  70. package/src/index.ts +66 -0
  71. package/src/server/adkEventStream.test.ts +78 -0
  72. package/src/server/adkEventStream.ts +161 -0
  73. package/src/server/createAdkApiRoute.test.ts +370 -0
  74. package/src/server/createAdkApiRoute.ts +86 -0
  75. package/src/server/index.ts +6 -0
  76. package/src/server/parseAdkRequest.test.ts +152 -0
  77. package/src/server/parseAdkRequest.ts +122 -0
  78. package/src/structuredEvents.ts +81 -0
  79. package/src/types.ts +265 -0
  80. package/src/useAdkMessages.ts +259 -0
  81. package/src/useAdkRuntime.ts +398 -0
@@ -0,0 +1,259 @@
1
+ /// <reference types="@assistant-ui/core/store" />
2
+ import { useState, useCallback, useRef, useMemo } from "react";
3
+ import { v4 as uuidv4 } from "uuid";
4
+ import { useAui } from "@assistant-ui/store";
5
+ import { AdkEventAccumulator } from "./AdkEventAccumulator";
6
+ import type {
7
+ AdkEvent,
8
+ AdkEventPart,
9
+ AdkMessage,
10
+ AdkMessageContentPart,
11
+ AdkMessageMetadata,
12
+ AdkSendMessageConfig,
13
+ AdkStreamCallback,
14
+ AdkToolConfirmation,
15
+ AdkAuthRequest,
16
+ OnAdkErrorCallback,
17
+ OnAdkCustomEventCallback,
18
+ OnAdkAgentTransferCallback,
19
+ } from "./types";
20
+
21
+ export type UseAdkMessagesOptions = {
22
+ stream: AdkStreamCallback;
23
+ eventHandlers?: {
24
+ onError?: OnAdkErrorCallback;
25
+ onCustomEvent?: OnAdkCustomEventCallback;
26
+ onAgentTransfer?: OnAdkAgentTransferCallback;
27
+ };
28
+ };
29
+
30
+ export const useAdkMessages = ({
31
+ stream,
32
+ eventHandlers,
33
+ }: UseAdkMessagesOptions) => {
34
+ const [messages, _setMessages] = useState<AdkMessage[]>([]);
35
+ const [stateDelta, setStateDelta] = useState<Record<string, unknown>>({});
36
+ const [agentInfo, setAgentInfo] = useState<{
37
+ name?: string | undefined;
38
+ branch?: string | undefined;
39
+ }>({});
40
+ const [longRunningToolIds, setLongRunningToolIds] = useState<string[]>([]);
41
+ const [artifactDelta, setArtifactDelta] = useState<Record<string, number>>(
42
+ {},
43
+ );
44
+ const [toolConfirmations, setToolConfirmations] = useState<
45
+ AdkToolConfirmation[]
46
+ >([]);
47
+ const [authRequests, setAuthRequests] = useState<AdkAuthRequest[]>([]);
48
+ const [escalated, setEscalated] = useState(false);
49
+ const [messageMetadata, setMessageMetadata] = useState<
50
+ Map<string, AdkMessageMetadata>
51
+ >(new Map());
52
+ const lastTransferToAgentRef = useRef<string | undefined>(undefined);
53
+ const messagesRef = useRef(messages);
54
+ messagesRef.current = messages;
55
+ const stateDeltaRef = useRef(stateDelta);
56
+ stateDeltaRef.current = stateDelta;
57
+ const artifactDeltaRef = useRef(artifactDelta);
58
+ artifactDeltaRef.current = artifactDelta;
59
+ const messageMetadataRef = useRef(messageMetadata);
60
+ messageMetadataRef.current = messageMetadata;
61
+
62
+ const setMessagesImmediate = useCallback((msgs: AdkMessage[]) => {
63
+ messagesRef.current = msgs;
64
+ _setMessages(msgs);
65
+ }, []);
66
+
67
+ const abortControllerRef = useRef<AbortController | null>(null);
68
+
69
+ const { onError, onCustomEvent, onAgentTransfer } = useMemo(
70
+ () => eventHandlers ?? {},
71
+ [eventHandlers],
72
+ );
73
+
74
+ const aui = useAui();
75
+ const sendMessage = useCallback(
76
+ async (newMessages: AdkMessage[], config: AdkSendMessageConfig) => {
77
+ const newMessagesWithId = newMessages.map((m) =>
78
+ m.id ? m : { ...m, id: uuidv4() },
79
+ ) as AdkMessage[];
80
+
81
+ const accumulator = new AdkEventAccumulator(messagesRef.current);
82
+ for (const msg of newMessagesWithId) {
83
+ accumulator.processEvent(messageToEvent(msg));
84
+ }
85
+ setMessagesImmediate(accumulator.getMessages());
86
+
87
+ const abortController = new AbortController();
88
+ abortControllerRef.current = abortController;
89
+
90
+ try {
91
+ const response = await stream(newMessagesWithId, {
92
+ ...config,
93
+ abortSignal: abortController.signal,
94
+ initialize: async () => {
95
+ return await aui.threadListItem().initialize();
96
+ },
97
+ });
98
+
99
+ for await (const event of response) {
100
+ const updatedMessages = accumulator.processEvent(event);
101
+ setMessagesImmediate(updatedMessages);
102
+ setStateDelta({
103
+ ...stateDeltaRef.current,
104
+ ...accumulator.getStateDelta(),
105
+ });
106
+ setAgentInfo(accumulator.getAgentInfo());
107
+ setLongRunningToolIds(accumulator.getLongRunningToolIds());
108
+ setArtifactDelta({
109
+ ...artifactDeltaRef.current,
110
+ ...accumulator.getArtifactDelta(),
111
+ });
112
+ setToolConfirmations(accumulator.getToolConfirmations());
113
+ setAuthRequests(accumulator.getAuthRequests());
114
+ setEscalated(accumulator.isEscalated());
115
+ {
116
+ const newMeta = accumulator.getMessageMetadata();
117
+ if (newMeta.size > 0) {
118
+ setMessageMetadata(
119
+ new Map([...messageMetadataRef.current, ...newMeta]),
120
+ );
121
+ }
122
+ }
123
+
124
+ const transfer = accumulator.getLastTransferToAgent();
125
+ if (transfer && transfer !== lastTransferToAgentRef.current) {
126
+ lastTransferToAgentRef.current = transfer;
127
+ onAgentTransfer?.(transfer);
128
+ }
129
+
130
+ // Fire custom event callback for events with customMetadata
131
+ if (event.customMetadata && onCustomEvent) {
132
+ for (const [key, value] of Object.entries(event.customMetadata)) {
133
+ onCustomEvent(key, value);
134
+ }
135
+ }
136
+
137
+ if (event.errorCode || event.errorMessage) {
138
+ onError?.(event.errorMessage ?? event.errorCode);
139
+ }
140
+ }
141
+ } catch (error) {
142
+ if (
143
+ !abortController.signal.aborted &&
144
+ !(error instanceof Error && error.name === "AbortError")
145
+ ) {
146
+ throw error;
147
+ }
148
+ } finally {
149
+ if (abortControllerRef.current === abortController) {
150
+ abortControllerRef.current = null;
151
+ }
152
+ }
153
+ },
154
+ [
155
+ aui,
156
+ setMessagesImmediate,
157
+ stream,
158
+ onError,
159
+ onCustomEvent,
160
+ onAgentTransfer,
161
+ ],
162
+ );
163
+
164
+ const cancel = useCallback(() => {
165
+ if (abortControllerRef.current) {
166
+ abortControllerRef.current.abort();
167
+ }
168
+ }, []);
169
+
170
+ return {
171
+ messages,
172
+ stateDelta,
173
+ agentInfo,
174
+ longRunningToolIds,
175
+ artifactDelta,
176
+ toolConfirmations,
177
+ authRequests,
178
+ escalated,
179
+ messageMetadata,
180
+ sendMessage,
181
+ cancel,
182
+ setMessages: setMessagesImmediate,
183
+ };
184
+ };
185
+
186
+ const messageToEvent = (msg: AdkMessage): AdkEvent => {
187
+ if (msg.type === "human") {
188
+ return {
189
+ id: msg.id ?? uuidv4(),
190
+ author: "user",
191
+ content: { role: "user", parts: contentToParts(msg.content) },
192
+ };
193
+ }
194
+
195
+ if (msg.type === "tool") {
196
+ let response: unknown;
197
+ try {
198
+ response = JSON.parse(msg.content);
199
+ } catch {
200
+ response = msg.content;
201
+ }
202
+ return {
203
+ id: msg.id ?? uuidv4(),
204
+ content: {
205
+ role: "user",
206
+ parts: [
207
+ {
208
+ functionResponse: {
209
+ name: msg.name,
210
+ id: msg.tool_call_id,
211
+ response,
212
+ },
213
+ },
214
+ ],
215
+ },
216
+ };
217
+ }
218
+
219
+ const result: AdkEvent = { id: msg.id ?? uuidv4() };
220
+ if (msg.author != null) result.author = msg.author;
221
+ result.content = {
222
+ role: "model",
223
+ parts: [
224
+ ...contentToParts(msg.content),
225
+ ...(msg.tool_calls?.map((tc) => ({
226
+ functionCall: { name: tc.name, id: tc.id, args: { ...tc.args } },
227
+ })) ?? []),
228
+ ],
229
+ };
230
+ return result;
231
+ };
232
+
233
+ const contentToParts = (
234
+ content: string | AdkMessageContentPart[],
235
+ ): AdkEventPart[] => {
236
+ if (typeof content === "string") return [{ text: content }];
237
+ return content.map((part) => {
238
+ switch (part.type) {
239
+ case "text":
240
+ return { text: part.text };
241
+ case "reasoning":
242
+ return { text: part.text, thought: true };
243
+ case "image":
244
+ return { inlineData: { mimeType: part.mimeType, data: part.data } };
245
+ case "image_url":
246
+ return { fileData: { fileUri: part.url } };
247
+ case "code":
248
+ return {
249
+ executableCode: { code: part.code, language: part.language },
250
+ };
251
+ case "code_result":
252
+ return {
253
+ codeExecutionResult: { output: part.output, outcome: part.outcome },
254
+ };
255
+ default:
256
+ return { text: "" };
257
+ }
258
+ });
259
+ };
@@ -0,0 +1,398 @@
1
+ /// <reference types="@assistant-ui/core/store" />
2
+ import { useEffect, useRef, useState } from "react";
3
+ import {
4
+ getExternalStoreMessages,
5
+ type AttachmentAdapter,
6
+ type FeedbackAdapter,
7
+ type SpeechSynthesisAdapter,
8
+ type AppendMessage,
9
+ type ThreadMessage,
10
+ } from "@assistant-ui/core";
11
+ import {
12
+ type ToolExecutionStatus,
13
+ useCloudThreadListAdapter,
14
+ useRemoteThreadListRuntime,
15
+ useExternalMessageConverter,
16
+ useExternalStoreRuntime,
17
+ useToolInvocations,
18
+ } from "@assistant-ui/core/react";
19
+ import { useAui } from "@assistant-ui/store";
20
+ import type { AssistantCloud } from "assistant-cloud";
21
+ import type { RemoteThreadListAdapter } from "@assistant-ui/core";
22
+ import type {
23
+ AdkMessage,
24
+ AdkSendMessageConfig,
25
+ AdkStreamCallback,
26
+ OnAdkErrorCallback,
27
+ OnAdkCustomEventCallback,
28
+ OnAdkAgentTransferCallback,
29
+ } from "./types";
30
+ import { useAdkMessages } from "./useAdkMessages";
31
+ import { convertAdkMessage } from "./convertAdkMessages";
32
+ import { symbolAdkRuntimeExtras, type AdkRuntimeExtras } from "./hooks";
33
+ import { v4 as uuidv4 } from "uuid";
34
+
35
+ const getMessageContent = (msg: AppendMessage) => {
36
+ const allContent = [
37
+ ...msg.content,
38
+ ...(msg.attachments?.flatMap((a) => a.content) ?? []),
39
+ ];
40
+ const content = allContent.map((part) => {
41
+ const type = part.type;
42
+ switch (type) {
43
+ case "text":
44
+ return { type: "text" as const, text: part.text };
45
+ case "image":
46
+ return { type: "image_url" as const, url: part.image };
47
+ case "file":
48
+ return {
49
+ type: "text" as const,
50
+ text: `[File: ${part.filename ?? "file"}]`,
51
+ };
52
+
53
+ case "tool-call":
54
+ throw new Error("Tool call appends are not supported.");
55
+
56
+ default:
57
+ const _exhaustiveCheck: "reasoning" | "source" | "audio" | "data" =
58
+ type;
59
+ throw new Error(
60
+ `Unsupported append message part type: ${_exhaustiveCheck}`,
61
+ );
62
+ }
63
+ });
64
+
65
+ if (content.length === 1 && content[0]?.type === "text") {
66
+ return content[0].text ?? "";
67
+ }
68
+
69
+ return content;
70
+ };
71
+
72
+ const getPendingToolCalls = (messages: AdkMessage[]) => {
73
+ const pending = new Map<string, { id: string; name: string }>();
74
+ for (const msg of messages) {
75
+ if (msg.type === "ai" && msg.tool_calls) {
76
+ for (const tc of msg.tool_calls) {
77
+ pending.set(tc.id, tc);
78
+ }
79
+ }
80
+ if (msg.type === "tool") {
81
+ pending.delete(msg.tool_call_id);
82
+ }
83
+ }
84
+ return [...pending.values()];
85
+ };
86
+
87
+ const truncateAdkMessages = (
88
+ threadMessages: readonly ThreadMessage[],
89
+ parentId: string | null,
90
+ ): AdkMessage[] => {
91
+ if (parentId === null) return [];
92
+ const parentIndex = threadMessages.findIndex((m) => m.id === parentId);
93
+ if (parentIndex === -1) return [];
94
+ const truncated: AdkMessage[] = [];
95
+ for (let i = 0; i <= parentIndex && i < threadMessages.length; i++) {
96
+ truncated.push(...getExternalStoreMessages<AdkMessage>(threadMessages[i]!));
97
+ }
98
+ return truncated;
99
+ };
100
+
101
+ export type UseAdkRuntimeOptions = {
102
+ stream: AdkStreamCallback;
103
+ autoCancelPendingToolCalls?: boolean | undefined;
104
+ unstable_allowCancellation?: boolean | undefined;
105
+ getCheckpointId?: (
106
+ threadId: string,
107
+ parentMessages: AdkMessage[],
108
+ ) => Promise<string | null>;
109
+ load?: (threadId: string) => Promise<{ messages: AdkMessage[] }>;
110
+ create?: () => Promise<{ externalId: string }>;
111
+ delete?: (threadId: string) => Promise<void>;
112
+ adapters?:
113
+ | {
114
+ attachments?: AttachmentAdapter;
115
+ speech?: SpeechSynthesisAdapter;
116
+ feedback?: FeedbackAdapter;
117
+ }
118
+ | undefined;
119
+ eventHandlers?:
120
+ | {
121
+ onError?: OnAdkErrorCallback;
122
+ onCustomEvent?: OnAdkCustomEventCallback;
123
+ onAgentTransfer?: OnAdkAgentTransferCallback;
124
+ }
125
+ | undefined;
126
+ cloud?: AssistantCloud | undefined;
127
+ /**
128
+ * A `RemoteThreadListAdapter` to use instead of the cloud adapter.
129
+ * Use with `createAdkSessionAdapter` for ADK session-backed persistence.
130
+ */
131
+ sessionAdapter?: RemoteThreadListAdapter | undefined;
132
+ };
133
+
134
+ const useAdkRuntimeImpl = ({
135
+ autoCancelPendingToolCalls,
136
+ adapters: { attachments, feedback, speech } = {},
137
+ unstable_allowCancellation,
138
+ stream,
139
+ load,
140
+ getCheckpointId,
141
+ eventHandlers,
142
+ }: UseAdkRuntimeOptions) => {
143
+ const aui = useAui();
144
+ const {
145
+ messages,
146
+ stateDelta,
147
+ agentInfo,
148
+ longRunningToolIds,
149
+ artifactDelta,
150
+ toolConfirmations,
151
+ authRequests,
152
+ escalated,
153
+ messageMetadata,
154
+ sendMessage,
155
+ cancel,
156
+ setMessages,
157
+ } = useAdkMessages({
158
+ stream,
159
+ ...(eventHandlers && { eventHandlers }),
160
+ });
161
+
162
+ const [isRunning, setIsRunning] = useState(false);
163
+ const [toolStatuses, setToolStatuses] = useState<
164
+ Record<string, ToolExecutionStatus>
165
+ >({});
166
+ const hasExecutingTools = Object.values(toolStatuses).some(
167
+ (s) => s?.type === "executing",
168
+ );
169
+ const effectiveIsRunning = isRunning || hasExecutingTools;
170
+
171
+ const handleSendMessage = async (
172
+ msgs: AdkMessage[],
173
+ config: AdkSendMessageConfig,
174
+ ) => {
175
+ try {
176
+ setIsRunning(true);
177
+ await sendMessage(msgs, config);
178
+ } finally {
179
+ setIsRunning(false);
180
+ }
181
+ };
182
+
183
+ const threadMessages = useExternalMessageConverter({
184
+ callback: convertAdkMessage,
185
+ messages,
186
+ isRunning: effectiveIsRunning,
187
+ });
188
+
189
+ const threadMessagesRef = useRef(threadMessages);
190
+ threadMessagesRef.current = threadMessages;
191
+
192
+ const [runtimeRef] = useState(() => ({
193
+ get current() {
194
+ return runtime;
195
+ },
196
+ }));
197
+
198
+ const toolInvocations = useToolInvocations({
199
+ state: { messages: threadMessages, isRunning: effectiveIsRunning },
200
+ getTools: () => runtimeRef.current.thread.getModelContext().tools,
201
+ onResult: (command) => {
202
+ if (command.type === "add-tool-result") {
203
+ void handleSendMessage(
204
+ [
205
+ {
206
+ id: uuidv4(),
207
+ type: "tool",
208
+ name: command.toolName,
209
+ tool_call_id: command.toolCallId,
210
+ content: JSON.stringify(command.result),
211
+ status: command.isError ? "error" : "success",
212
+ },
213
+ ],
214
+ {},
215
+ );
216
+ }
217
+ },
218
+ setToolStatuses,
219
+ });
220
+
221
+ const runtime = useExternalStoreRuntime({
222
+ isRunning: effectiveIsRunning,
223
+ messages: threadMessages,
224
+ adapters: { attachments, feedback, speech },
225
+ extras: {
226
+ [symbolAdkRuntimeExtras]: true,
227
+ agentInfo,
228
+ stateDelta,
229
+ artifactDelta,
230
+ longRunningToolIds,
231
+ toolConfirmations,
232
+ authRequests,
233
+ escalated,
234
+ messageMetadata,
235
+ send: handleSendMessage,
236
+ } satisfies AdkRuntimeExtras,
237
+ onNew: async (msg) => {
238
+ await toolInvocations.abort();
239
+
240
+ const cancellations =
241
+ autoCancelPendingToolCalls !== false
242
+ ? getPendingToolCalls(messages).map(
243
+ (t) =>
244
+ ({
245
+ id: uuidv4(),
246
+ type: "tool",
247
+ name: t.name,
248
+ tool_call_id: t.id,
249
+ content: JSON.stringify({ cancelled: true }),
250
+ status: "error",
251
+ }) satisfies AdkMessage & { type: "tool" },
252
+ )
253
+ : [];
254
+
255
+ return handleSendMessage(
256
+ [
257
+ ...cancellations,
258
+ {
259
+ id: uuidv4(),
260
+ type: "human",
261
+ content: getMessageContent(msg),
262
+ },
263
+ ],
264
+ { runConfig: msg.runConfig },
265
+ );
266
+ },
267
+ onEdit: getCheckpointId
268
+ ? async (msg) => {
269
+ await toolInvocations.abort();
270
+ const truncated = truncateAdkMessages(
271
+ threadMessagesRef.current,
272
+ msg.parentId,
273
+ );
274
+ setMessages(truncated);
275
+ const externalId = aui.threadListItem().getState().externalId;
276
+ const checkpointId = externalId
277
+ ? await getCheckpointId(externalId, truncated)
278
+ : null;
279
+ return handleSendMessage(
280
+ [
281
+ {
282
+ id: uuidv4(),
283
+ type: "human",
284
+ content: getMessageContent(msg),
285
+ },
286
+ ],
287
+ {
288
+ runConfig: msg.runConfig,
289
+ ...(checkpointId && { checkpointId }),
290
+ },
291
+ );
292
+ }
293
+ : undefined,
294
+ onReload: getCheckpointId
295
+ ? async (parentId, config) => {
296
+ await toolInvocations.abort();
297
+ const truncated = truncateAdkMessages(
298
+ threadMessagesRef.current,
299
+ parentId,
300
+ );
301
+ setMessages(truncated);
302
+ const externalId = aui.threadListItem().getState().externalId;
303
+ const checkpointId = externalId
304
+ ? await getCheckpointId(externalId, truncated)
305
+ : null;
306
+ return handleSendMessage([], {
307
+ runConfig: config.runConfig,
308
+ ...(checkpointId && { checkpointId }),
309
+ });
310
+ }
311
+ : undefined,
312
+ onAddToolResult: async ({
313
+ toolCallId,
314
+ toolName,
315
+ result,
316
+ isError,
317
+ artifact,
318
+ }) => {
319
+ await handleSendMessage(
320
+ [
321
+ {
322
+ id: uuidv4(),
323
+ type: "tool",
324
+ name: toolName,
325
+ tool_call_id: toolCallId,
326
+ content: JSON.stringify(result),
327
+ artifact,
328
+ status: isError ? "error" : "success",
329
+ },
330
+ ],
331
+ {},
332
+ );
333
+ },
334
+ onResumeToolCall: (options) =>
335
+ toolInvocations.resume(options.toolCallId, options.payload),
336
+ onCancel: unstable_allowCancellation
337
+ ? async () => {
338
+ cancel();
339
+ await toolInvocations.abort();
340
+ }
341
+ : undefined,
342
+ });
343
+
344
+ {
345
+ const loadRef = useRef(load);
346
+ useEffect(() => {
347
+ loadRef.current = load;
348
+ });
349
+
350
+ useEffect(() => {
351
+ const loadFn = loadRef.current;
352
+ if (!loadFn) return;
353
+
354
+ const externalId = aui.threadListItem().getState().externalId;
355
+ if (externalId == null) return;
356
+
357
+ loadFn(externalId).then(
358
+ ({ messages: msgs }) => {
359
+ setMessages(msgs);
360
+ },
361
+ (e) => {
362
+ console.warn("Failed to load ADK session:", e);
363
+ },
364
+ );
365
+ }, [aui, setMessages]);
366
+ }
367
+
368
+ return runtime;
369
+ };
370
+
371
+ export const useAdkRuntime = ({
372
+ cloud,
373
+ sessionAdapter,
374
+ create,
375
+ delete: deleteFn,
376
+ ...options
377
+ }: UseAdkRuntimeOptions) => {
378
+ const aui = useAui();
379
+ const cloudAdapter = useCloudThreadListAdapter({
380
+ cloud,
381
+ create: async () => {
382
+ if (create) return create();
383
+ if (aui.threadListItem.source) return aui.threadListItem().initialize();
384
+ return { externalId: undefined };
385
+ },
386
+ delete: deleteFn,
387
+ });
388
+
389
+ const adapter = sessionAdapter ?? cloudAdapter;
390
+
391
+ return useRemoteThreadListRuntime({
392
+ runtimeHook: function RuntimeHook() {
393
+ return useAdkRuntimeImpl(options);
394
+ },
395
+ adapter,
396
+ allowNesting: true,
397
+ });
398
+ };