@assistant-ui/react-google-adk 0.0.25 → 0.0.27
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/AdkClient.d.ts.map +1 -1
- package/dist/AdkClient.js +13 -3
- package/dist/AdkClient.js.map +1 -1
- package/dist/AdkEventAccumulator.d.ts +2 -0
- package/dist/AdkEventAccumulator.d.ts.map +1 -1
- package/dist/AdkEventAccumulator.js +32 -10
- package/dist/AdkEventAccumulator.js.map +1 -1
- package/dist/AdkSessionAdapter.d.ts.map +1 -1
- package/dist/AdkSessionAdapter.js +2 -1
- package/dist/AdkSessionAdapter.js.map +1 -1
- package/dist/adkToolApproval.d.ts +40 -0
- package/dist/adkToolApproval.d.ts.map +1 -0
- package/dist/adkToolApproval.js +166 -0
- package/dist/adkToolApproval.js.map +1 -0
- package/dist/convertAdkMessages.d.ts +3 -1
- package/dist/convertAdkMessages.d.ts.map +1 -1
- package/dist/convertAdkMessages.js +27 -16
- package/dist/convertAdkMessages.js.map +1 -1
- package/dist/convertToAdkMessages.d.ts +49 -0
- package/dist/convertToAdkMessages.d.ts.map +1 -0
- package/dist/convertToAdkMessages.js +86 -0
- package/dist/convertToAdkMessages.js.map +1 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +5 -14
- package/dist/hooks.js.map +1 -1
- package/dist/parseAdkEvent.d.ts +6 -0
- package/dist/parseAdkEvent.d.ts.map +1 -0
- package/dist/parseAdkEvent.js +16 -0
- package/dist/parseAdkEvent.js.map +1 -0
- package/dist/server/parseAdkRequest.d.ts.map +1 -1
- package/dist/server/parseAdkRequest.js +79 -13
- package/dist/server/parseAdkRequest.js.map +1 -1
- package/dist/useAdkMessages.d.ts.map +1 -1
- package/dist/useAdkMessages.js +110 -79
- package/dist/useAdkMessages.js.map +1 -1
- package/dist/useAdkRuntime.d.ts.map +1 -1
- package/dist/useAdkRuntime.js +44 -129
- package/dist/useAdkRuntime.js.map +1 -1
- package/package.json +6 -7
- package/src/AdkClient.test.ts +137 -0
- package/src/AdkClient.ts +15 -3
- package/src/AdkEventAccumulator.test.ts +161 -1
- package/src/AdkEventAccumulator.ts +44 -8
- package/src/AdkSessionAdapter.test.ts +145 -1
- package/src/AdkSessionAdapter.ts +7 -2
- package/src/adkToolApproval.test.ts +345 -0
- package/src/adkToolApproval.ts +215 -0
- package/src/convertAdkMessages.ts +72 -43
- package/src/{useAdkRuntime.test.ts → convertToAdkMessages.test.ts} +1 -1
- package/src/convertToAdkMessages.ts +139 -0
- package/src/hooks.ts +7 -19
- package/src/parseAdkEvent.ts +40 -0
- package/src/server/parseAdkRequest.test.ts +141 -0
- package/src/server/parseAdkRequest.ts +145 -13
- package/src/useAdkMessages.test.ts +313 -0
- package/src/useAdkMessages.ts +72 -25
- package/src/useAdkRuntime.ts +66 -188
- package/src/useAdkRuntimeApproval.test.tsx +344 -0
package/dist/useAdkRuntime.js
CHANGED
|
@@ -1,96 +1,14 @@
|
|
|
1
1
|
import { adkExtras } from "./adkExtras.js";
|
|
2
|
-
import {
|
|
2
|
+
import { projectAdkToolApprovals, toAdkToolConfirmationReply } from "./adkToolApproval.js";
|
|
3
|
+
import { convertAdkMessage, createAdkMessageConverter } from "./convertAdkMessages.js";
|
|
4
|
+
import { getMessageContent, getPendingCancellations, toAdkUserMessage, truncateAdkMessages } from "./convertToAdkMessages.js";
|
|
3
5
|
import { useAdkMessages } from "./useAdkMessages.js";
|
|
4
|
-
import {
|
|
6
|
+
import { generateId, pickExternalStoreSharedOptions } from "@assistant-ui/core";
|
|
5
7
|
import { useCloudThreadListAdapter, useExternalMessageConverter, useExternalStoreRuntime, useRemoteThreadListRuntime } from "@assistant-ui/core/react";
|
|
8
|
+
import { createAbortableThreadLoad, createCloudThreadListAdapterCreateFallback } from "@assistant-ui/core/internal";
|
|
6
9
|
import { useAui } from "@assistant-ui/store";
|
|
7
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
8
|
-
import { generateId, getExternalStoreMessages, pickExternalStoreSharedOptions } from "@assistant-ui/core";
|
|
9
|
-
import { httpUrlPattern, parseDataUrl } from "@assistant-ui/core/internal";
|
|
10
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
10
11
|
//#region src/useAdkRuntime.ts
|
|
11
|
-
/** @internal — exported for unit tests. */
|
|
12
|
-
const getMessageContent = (msg) => {
|
|
13
|
-
const content = [...msg.content, ...msg.attachments?.flatMap((a) => a.content) ?? []].flatMap((part) => {
|
|
14
|
-
const type = part.type;
|
|
15
|
-
switch (type) {
|
|
16
|
-
case "text": return {
|
|
17
|
-
type: "text",
|
|
18
|
-
text: part.text
|
|
19
|
-
};
|
|
20
|
-
case "image": return {
|
|
21
|
-
type: "image_url",
|
|
22
|
-
url: part.image
|
|
23
|
-
};
|
|
24
|
-
case "file":
|
|
25
|
-
if (part.sourceType === "url" || httpUrlPattern.test(part.data)) return {
|
|
26
|
-
type: "file_url",
|
|
27
|
-
url: part.data,
|
|
28
|
-
mimeType: part.mimeType
|
|
29
|
-
};
|
|
30
|
-
return {
|
|
31
|
-
type: "file",
|
|
32
|
-
mimeType: part.mimeType,
|
|
33
|
-
data: parseDataUrl(part.data)?.data ?? part.data,
|
|
34
|
-
...part.filename != null && { filename: part.filename }
|
|
35
|
-
};
|
|
36
|
-
case "audio": {
|
|
37
|
-
const parsed = parseDataUrl(part.audio.data);
|
|
38
|
-
return {
|
|
39
|
-
type: "file",
|
|
40
|
-
mimeType: `audio/${part.audio.format}`,
|
|
41
|
-
data: parsed?.data ?? part.audio.data
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
case "data": return [];
|
|
45
|
-
case "tool-call": throw new Error("Tool call appends are not supported.");
|
|
46
|
-
default: throw new Error(`Unsupported append message part type: ${type}`);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
if (content.length === 1 && content[0]?.type === "text") return content[0].text ?? "";
|
|
50
|
-
return content;
|
|
51
|
-
};
|
|
52
|
-
/** @internal — exported for unit tests. */
|
|
53
|
-
const getPendingToolCalls = (messages) => {
|
|
54
|
-
const pending = /* @__PURE__ */ new Map();
|
|
55
|
-
for (const msg of messages) {
|
|
56
|
-
if (msg.type === "ai" && msg.tool_calls) for (const tc of msg.tool_calls) pending.set(tc.id, tc);
|
|
57
|
-
if (msg.type === "tool") pending.delete(msg.tool_call_id);
|
|
58
|
-
}
|
|
59
|
-
return [...pending.values()];
|
|
60
|
-
};
|
|
61
|
-
/**
|
|
62
|
-
* @internal — exported for unit tests.
|
|
63
|
-
*
|
|
64
|
-
* Returns `{cancelled: true}` tool responses for pending tool calls when the
|
|
65
|
-
* user sends a new turn, EXCEPT for HITL interrupts marked via
|
|
66
|
-
* `long_running_tool_ids` (`adk_request_input`, `adk_request_confirmation`,
|
|
67
|
-
* `adk_request_credential`). Those must be answered through a dedicated tool
|
|
68
|
-
* UI + submit helper, not auto-cancelled.
|
|
69
|
-
*/
|
|
70
|
-
const getPendingCancellations = (messages, longRunningToolIds) => {
|
|
71
|
-
const longRunningSet = new Set(longRunningToolIds);
|
|
72
|
-
return getPendingToolCalls(messages).filter((t) => !longRunningSet.has(t.id)).map((t) => ({
|
|
73
|
-
id: v4(),
|
|
74
|
-
type: "tool",
|
|
75
|
-
name: t.name,
|
|
76
|
-
tool_call_id: t.id,
|
|
77
|
-
content: JSON.stringify({ cancelled: true }),
|
|
78
|
-
status: "error"
|
|
79
|
-
}));
|
|
80
|
-
};
|
|
81
|
-
const truncateAdkMessages = (threadMessages, parentId) => {
|
|
82
|
-
if (parentId === null) return [];
|
|
83
|
-
const parentIndex = threadMessages.findIndex((m) => m.id === parentId);
|
|
84
|
-
if (parentIndex === -1) return [];
|
|
85
|
-
const truncated = [];
|
|
86
|
-
for (let i = 0; i <= parentIndex && i < threadMessages.length; i++) truncated.push(...getExternalStoreMessages(threadMessages[i]));
|
|
87
|
-
return truncated;
|
|
88
|
-
};
|
|
89
|
-
const toAdkUserMessage = (msg, id = generateId()) => ({
|
|
90
|
-
id,
|
|
91
|
-
type: "human",
|
|
92
|
-
content: getMessageContent(msg)
|
|
93
|
-
});
|
|
94
12
|
const useAdkRuntimeImpl = (options) => {
|
|
95
13
|
const { autoCancelPendingToolCalls, adapters: { attachments, dictation, feedback, speech, voice } = {}, unstable_allowCancellation, stream, load, getCheckpointId, eventHandlers } = options;
|
|
96
14
|
const aui = useAui();
|
|
@@ -100,7 +18,7 @@ const useAdkRuntimeImpl = (options) => {
|
|
|
100
18
|
});
|
|
101
19
|
const loadRef = useRef(load);
|
|
102
20
|
loadRef.current = load;
|
|
103
|
-
const
|
|
21
|
+
const loadController = useMemo(createAbortableThreadLoad, []);
|
|
104
22
|
const messagesRef = useRef(messages);
|
|
105
23
|
messagesRef.current = messages;
|
|
106
24
|
const [isLoadingThread, setIsLoadingThread] = useState(() => load !== void 0 && aui.threadListItem.getState().externalId != null);
|
|
@@ -118,8 +36,12 @@ const useAdkRuntimeImpl = (options) => {
|
|
|
118
36
|
setIsRunning(false);
|
|
119
37
|
}
|
|
120
38
|
};
|
|
39
|
+
const { approvals: toolApprovals, key: toolApprovalsKey } = projectAdkToolApprovals(messages);
|
|
40
|
+
const toolApprovalsRef = useRef(toolApprovals);
|
|
41
|
+
toolApprovalsRef.current = toolApprovals;
|
|
42
|
+
const messageConverter = useMemo(() => toolApprovalsKey === "" ? convertAdkMessage : createAdkMessageConverter(toolApprovalsRef.current), [toolApprovalsKey]);
|
|
121
43
|
const threadMessages = useExternalMessageConverter({
|
|
122
|
-
callback:
|
|
44
|
+
callback: messageConverter,
|
|
123
45
|
messages,
|
|
124
46
|
isRunning: effectiveIsRunning
|
|
125
47
|
});
|
|
@@ -159,44 +81,38 @@ const useAdkRuntimeImpl = (options) => {
|
|
|
159
81
|
if (!loadFn || !threadListItem) return Promise.resolve();
|
|
160
82
|
const externalId = threadListItem.getState().externalId;
|
|
161
83
|
if (externalId == null) return Promise.resolve();
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
throw error;
|
|
179
|
-
}).finally(() => {
|
|
180
|
-
if (loadControllerRef.current?.controller === controller) loadControllerRef.current = null;
|
|
181
|
-
if (controller.signal.aborted) return;
|
|
182
|
-
setIsLoadingThread(false);
|
|
183
|
-
});
|
|
184
|
-
record.promise = task;
|
|
185
|
-
if (purpose === "reload") return task;
|
|
186
|
-
return task.catch((e) => {
|
|
187
|
-
console.warn("Failed to load ADK session:", e);
|
|
84
|
+
return loadController.run({
|
|
85
|
+
purpose,
|
|
86
|
+
load: async (signal) => {
|
|
87
|
+
const messagesAtLoadStart = messagesRef.current;
|
|
88
|
+
if (purpose === "initial") setIsLoadingThread(true);
|
|
89
|
+
const snapshot = await loadFn(externalId, { signal });
|
|
90
|
+
if (signal.aborted) return;
|
|
91
|
+
if (purpose === "reload" && (isRunningRef.current || messagesRef.current !== messagesAtLoadStart)) return;
|
|
92
|
+
applySnapshot(snapshot);
|
|
93
|
+
},
|
|
94
|
+
onSettled: () => {
|
|
95
|
+
setIsLoadingThread(false);
|
|
96
|
+
},
|
|
97
|
+
onInitialError: (error) => {
|
|
98
|
+
console.warn("Failed to load ADK session:", error);
|
|
99
|
+
}
|
|
188
100
|
});
|
|
189
|
-
}, [
|
|
101
|
+
}, [
|
|
102
|
+
threadListItem,
|
|
103
|
+
loadController,
|
|
104
|
+
applySnapshot
|
|
105
|
+
]);
|
|
190
106
|
useEffect(() => {
|
|
191
107
|
runLoad();
|
|
192
108
|
return () => {
|
|
193
|
-
|
|
109
|
+
loadController.abort();
|
|
194
110
|
setIsLoadingThread(false);
|
|
195
111
|
};
|
|
196
|
-
}, [runLoad]);
|
|
112
|
+
}, [loadController, runLoad]);
|
|
197
113
|
return useExternalStoreRuntime({
|
|
198
114
|
...pickExternalStoreSharedOptions(options),
|
|
199
|
-
isRunning
|
|
115
|
+
isRunning,
|
|
200
116
|
isLoading: isLoadingThread,
|
|
201
117
|
messages: threadMessages,
|
|
202
118
|
unstable_enableToolInvocations: true,
|
|
@@ -226,7 +142,7 @@ const useAdkRuntimeImpl = (options) => {
|
|
|
226
142
|
}
|
|
227
143
|
const cancellations = autoCancelPendingToolCalls !== false ? getPendingCancellations(messages, longRunningToolIds) : [];
|
|
228
144
|
return handleSendMessage([...cancellations, {
|
|
229
|
-
id:
|
|
145
|
+
id: generateId(),
|
|
230
146
|
type: "human",
|
|
231
147
|
content: getMessageContent(msg)
|
|
232
148
|
}], { runConfig: msg.runConfig });
|
|
@@ -249,7 +165,7 @@ const useAdkRuntimeImpl = (options) => {
|
|
|
249
165
|
const externalId = aui.threadListItem.getState().externalId;
|
|
250
166
|
const checkpointId = externalId ? await getCheckpointId(externalId, truncated) : null;
|
|
251
167
|
return handleSendMessage([{
|
|
252
|
-
id:
|
|
168
|
+
id: generateId(),
|
|
253
169
|
type: "human",
|
|
254
170
|
content: getMessageContent(msg)
|
|
255
171
|
}], {
|
|
@@ -276,7 +192,7 @@ const useAdkRuntimeImpl = (options) => {
|
|
|
276
192
|
} } : {},
|
|
277
193
|
onAddToolResult: async ({ toolCallId, toolName, result, isError, artifact }) => {
|
|
278
194
|
await handleSendMessage([{
|
|
279
|
-
id:
|
|
195
|
+
id: generateId(),
|
|
280
196
|
type: "tool",
|
|
281
197
|
name: toolName,
|
|
282
198
|
tool_call_id: toolCallId,
|
|
@@ -285,6 +201,9 @@ const useAdkRuntimeImpl = (options) => {
|
|
|
285
201
|
status: isError ? "error" : "success"
|
|
286
202
|
}], {});
|
|
287
203
|
},
|
|
204
|
+
onRespondToToolApproval: async (options) => {
|
|
205
|
+
await handleSendMessage([toAdkToolConfirmationReply(options, toolApprovalsRef.current)], {});
|
|
206
|
+
},
|
|
288
207
|
onCancel: unstable_allowCancellation ? async () => {
|
|
289
208
|
cancel();
|
|
290
209
|
} : void 0,
|
|
@@ -295,11 +214,7 @@ const useAdkRuntime = ({ cloud, sessionAdapter, create, delete: deleteFn, onThre
|
|
|
295
214
|
const aui = useAui();
|
|
296
215
|
const cloudAdapter = useCloudThreadListAdapter({
|
|
297
216
|
cloud,
|
|
298
|
-
create:
|
|
299
|
-
if (create) return create();
|
|
300
|
-
if (aui.threadListItem.source) return aui.threadListItem.initialize();
|
|
301
|
-
return { externalId: void 0 };
|
|
302
|
-
},
|
|
217
|
+
create: createCloudThreadListAdapterCreateFallback(create, aui.threadListItem),
|
|
303
218
|
delete: deleteFn
|
|
304
219
|
});
|
|
305
220
|
return useRemoteThreadListRuntime({
|
|
@@ -312,6 +227,6 @@ const useAdkRuntime = ({ cloud, sessionAdapter, create, delete: deleteFn, onThre
|
|
|
312
227
|
});
|
|
313
228
|
};
|
|
314
229
|
//#endregion
|
|
315
|
-
export {
|
|
230
|
+
export { useAdkRuntime };
|
|
316
231
|
|
|
317
232
|
//# sourceMappingURL=useAdkRuntime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAdkRuntime.js","names":["_exhaustiveCheck","uuidv4"],"sources":["../src/useAdkRuntime.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n getExternalStoreMessages,\n pickExternalStoreSharedOptions,\n type AttachmentAdapter,\n type DictationAdapter,\n type ExternalStoreSharedOptions,\n type FeedbackAdapter,\n type RealtimeVoiceAdapter,\n type SpeechSynthesisAdapter,\n type AppendMessage,\n type ThreadMessage,\n type ToolExecutionStatus,\n generateId,\n} from \"@assistant-ui/core\";\nimport { httpUrlPattern, parseDataUrl } from \"@assistant-ui/core/internal\";\nimport {\n useCloudThreadListAdapter,\n useRemoteThreadListRuntime,\n useExternalMessageConverter,\n useExternalStoreRuntime,\n} from \"@assistant-ui/core/react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport type { AssistantCloud } from \"assistant-cloud\";\nimport type { RemoteThreadListAdapter } from \"@assistant-ui/core\";\nimport type {\n AdkMessage,\n AdkThreadSnapshot,\n AdkSendMessageConfig,\n AdkStreamCallback,\n OnAdkErrorCallback,\n OnAdkCustomEventCallback,\n OnAdkAgentTransferCallback,\n} from \"./types\";\nimport { useAdkMessages } from \"./useAdkMessages\";\nimport { convertAdkMessage } from \"./convertAdkMessages\";\nimport { adkExtras } from \"./adkExtras\";\nimport { v4 as uuidv4 } from \"uuid\";\n\n/** @internal — exported for unit tests. */\nexport const getMessageContent = (msg: AppendMessage) => {\n const allContent = [\n ...msg.content,\n ...(msg.attachments?.flatMap((a) => a.content) ?? []),\n ];\n const content = allContent.flatMap((part) => {\n const type = part.type;\n switch (type) {\n case \"text\":\n return { type: \"text\" as const, text: part.text };\n case \"image\":\n return { type: \"image_url\" as const, url: part.image };\n case \"file\":\n if (part.sourceType === \"url\" || httpUrlPattern.test(part.data)) {\n return {\n type: \"file_url\" as const,\n url: part.data,\n mimeType: part.mimeType,\n };\n }\n return {\n type: \"file\" as const,\n mimeType: part.mimeType,\n // Lands in Gemini `inlineData.data`, which takes bare base64, so a\n // data URL envelope is stripped rather than forwarded.\n data: parseDataUrl(part.data)?.data ?? part.data,\n ...(part.filename != null && { filename: part.filename }),\n };\n case \"audio\": {\n const parsed = parseDataUrl(part.audio.data);\n return {\n type: \"file\" as const,\n mimeType: `audio/${part.audio.format}`,\n data: parsed?.data ?? part.audio.data,\n };\n }\n case \"data\":\n return [];\n\n case \"tool-call\":\n throw new Error(\"Tool call appends are not supported.\");\n\n default: {\n const _exhaustiveCheck: \"reasoning\" | \"source\" | \"generative-ui\" = type;\n throw new Error(\n `Unsupported append message part type: ${_exhaustiveCheck}`,\n );\n }\n }\n });\n\n if (content.length === 1 && content[0]?.type === \"text\") {\n return content[0].text ?? \"\";\n }\n\n return content;\n};\n\n/** @internal — exported for unit tests. */\nexport const getPendingToolCalls = (messages: AdkMessage[]) => {\n const pending = new Map<string, { id: string; name: string }>();\n for (const msg of messages) {\n if (msg.type === \"ai\" && msg.tool_calls) {\n for (const tc of msg.tool_calls) {\n pending.set(tc.id, tc);\n }\n }\n if (msg.type === \"tool\") {\n pending.delete(msg.tool_call_id);\n }\n }\n return [...pending.values()];\n};\n\n/**\n * @internal — exported for unit tests.\n *\n * Returns `{cancelled: true}` tool responses for pending tool calls when the\n * user sends a new turn, EXCEPT for HITL interrupts marked via\n * `long_running_tool_ids` (`adk_request_input`, `adk_request_confirmation`,\n * `adk_request_credential`). Those must be answered through a dedicated tool\n * UI + submit helper, not auto-cancelled.\n */\nexport const getPendingCancellations = (\n messages: AdkMessage[],\n longRunningToolIds: readonly string[],\n): Array<AdkMessage & { type: \"tool\" }> => {\n const longRunningSet = new Set(longRunningToolIds);\n return getPendingToolCalls(messages)\n .filter((t) => !longRunningSet.has(t.id))\n .map(\n (t) =>\n ({\n id: uuidv4(),\n type: \"tool\",\n name: t.name,\n tool_call_id: t.id,\n content: JSON.stringify({ cancelled: true }),\n status: \"error\",\n }) satisfies AdkMessage & { type: \"tool\" },\n );\n};\n\nconst truncateAdkMessages = (\n threadMessages: readonly ThreadMessage[],\n parentId: string | null,\n): AdkMessage[] => {\n if (parentId === null) return [];\n const parentIndex = threadMessages.findIndex((m) => m.id === parentId);\n if (parentIndex === -1) return [];\n const truncated: AdkMessage[] = [];\n for (let i = 0; i <= parentIndex && i < threadMessages.length; i++) {\n truncated.push(...getExternalStoreMessages<AdkMessage>(threadMessages[i]!));\n }\n return truncated;\n};\n\nconst toAdkUserMessage = (\n msg: AppendMessage,\n id = generateId(),\n): AdkMessage & { type: \"human\"; id: string } => ({\n id,\n type: \"human\",\n content: getMessageContent(msg),\n});\n\nexport type UseAdkRuntimeOptions = ExternalStoreSharedOptions & {\n stream: AdkStreamCallback;\n /**\n * Called whenever the active thread's canonical (remote) ID changes, so the\n * value can be treated as a managed/controlled variable (e.g. synced to a URL\n * query param). Only the settled remote ID is emitted: while a freshly created\n * thread is still optimistic the value is `undefined`, and the real ID is\n * emitted once the thread is initialized; the transient local ID is never\n * surfaced.\n */\n onThreadIdChange?: ((threadId: string | undefined) => void) | undefined;\n autoCancelPendingToolCalls?: boolean | undefined;\n unstable_allowCancellation?: boolean | undefined;\n getCheckpointId?: (\n threadId: string,\n parentMessages: AdkMessage[],\n ) => Promise<string | null>;\n /**\n * Loads a thread's stored state. Called when the thread opens, and again for\n * `threads.reloadMainThread()`, which refetches in place rather than\n * remounting the runtime; the signal aborts a load the runtime no longer\n * needs.\n */\n load?: (\n threadId: string,\n options?: { signal?: AbortSignal | undefined },\n ) => Promise<AdkThreadSnapshot>;\n create?: () => Promise<{ externalId: string }>;\n delete?: (threadId: string) => Promise<void>;\n adapters?:\n | {\n attachments?: AttachmentAdapter;\n speech?: SpeechSynthesisAdapter;\n dictation?: DictationAdapter;\n voice?: RealtimeVoiceAdapter;\n feedback?: FeedbackAdapter;\n }\n | undefined;\n eventHandlers?:\n | {\n onError?: OnAdkErrorCallback;\n onCustomEvent?: OnAdkCustomEventCallback;\n onAgentTransfer?: OnAdkAgentTransferCallback;\n }\n | undefined;\n cloud?: AssistantCloud | undefined;\n /**\n * A `RemoteThreadListAdapter` to use instead of the cloud adapter.\n * Use with `createAdkSessionAdapter` for ADK session-backed persistence.\n */\n sessionAdapter?: RemoteThreadListAdapter | undefined;\n};\n\nconst useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {\n const {\n autoCancelPendingToolCalls,\n adapters: { attachments, dictation, feedback, speech, voice } = {},\n unstable_allowCancellation,\n stream,\n load,\n getCheckpointId,\n eventHandlers,\n } = options;\n const aui = useAui();\n const {\n messages,\n stateDelta,\n agentInfo,\n longRunningToolIds,\n artifactDelta,\n toolConfirmations,\n authRequests,\n escalated,\n messageMetadata,\n sendMessage,\n cancel,\n setMessages,\n replaceMessages,\n applySnapshot,\n } = useAdkMessages({\n stream,\n ...(eventHandlers && { eventHandlers }),\n });\n\n const loadRef = useRef(load);\n loadRef.current = load;\n const loadControllerRef = useRef<{\n controller: AbortController;\n purpose: \"initial\" | \"reload\";\n promise?: Promise<void> | undefined;\n } | null>(null);\n const messagesRef = useRef(messages);\n messagesRef.current = messages;\n const [isLoadingThread, setIsLoadingThread] = useState(\n () =>\n load !== undefined && aui.threadListItem.getState().externalId != null,\n );\n\n const [isRunning, setIsRunning] = useState(false);\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const hasExecutingTools = Object.values(toolStatuses).some(\n (s) => s?.type === \"executing\",\n );\n const effectiveIsRunning = isRunning || hasExecutingTools;\n const isRunningRef = useRef(effectiveIsRunning);\n isRunningRef.current = effectiveIsRunning;\n\n const handleSendMessage = async (\n msgs: AdkMessage[],\n config: AdkSendMessageConfig,\n ) => {\n try {\n setIsRunning(true);\n await sendMessage(msgs, config);\n } finally {\n setIsRunning(false);\n }\n };\n\n const threadMessages = useExternalMessageConverter({\n callback: convertAdkMessage,\n messages,\n isRunning: effectiveIsRunning,\n });\n\n const threadMessagesRef = useRef(threadMessages);\n threadMessagesRef.current = threadMessages;\n\n const adkMessagesRef = useRef(messages);\n adkMessagesRef.current = messages;\n\n const stagedMessagesRef = useRef(\n new Map<\n string,\n {\n message: AdkMessage & { id: string };\n runConfig: AppendMessage[\"runConfig\"];\n }\n >(),\n );\n const [stagedMessageCount, setStagedMessageCount] = useState(0);\n const hasStagedMessages = stagedMessageCount > 0;\n\n const getStagedRun = (parentId: string | null) => {\n if (!parentId || !stagedMessagesRef.current.has(parentId)) return null;\n\n const staged: AdkMessage[] = [];\n for (const message of adkMessagesRef.current) {\n if (message.id && stagedMessagesRef.current.has(message.id)) {\n staged.push(stagedMessagesRef.current.get(message.id)!.message);\n }\n if (message.id === parentId) break;\n }\n\n return {\n messages: staged,\n runConfig: stagedMessagesRef.current.get(parentId)!.runConfig,\n };\n };\n\n const stageUserMessage = (msg: AppendMessage) => {\n const stagedMessage = toAdkUserMessage(msg);\n stagedMessagesRef.current.set(stagedMessage.id, {\n message: stagedMessage,\n runConfig: msg.runConfig,\n });\n setStagedMessageCount(stagedMessagesRef.current.size);\n const nextMessages = [...adkMessagesRef.current, stagedMessage];\n adkMessagesRef.current = nextMessages;\n setMessages(nextMessages);\n };\n\n // The scoped client, not `aui` itself: useAui returns a render-bound\n // instance, so depending on it would re-run the load on every render.\n const threadListItem =\n aui.threadListItem.source !== null ? aui.threadListItem : undefined;\n\n const runLoad = useCallback(\n (purpose: \"initial\" | \"reload\" = \"initial\") => {\n const loadFn = loadRef.current;\n if (!loadFn || !threadListItem) return Promise.resolve();\n\n const externalId = threadListItem.getState().externalId;\n if (externalId == null) return Promise.resolve();\n\n // The initial load is already fetching what a refetch would ask for, and\n // taking it over strands the thread's history if the refetch then fails.\n if (\n purpose === \"reload\" &&\n loadControllerRef.current?.purpose === \"initial\"\n )\n return loadControllerRef.current.promise ?? Promise.resolve();\n\n loadControllerRef.current?.controller.abort();\n const controller = new AbortController();\n const record: NonNullable<typeof loadControllerRef.current> = {\n controller,\n purpose,\n };\n loadControllerRef.current = record;\n\n const messagesAtLoadStart = messagesRef.current;\n if (purpose === \"initial\") setIsLoadingThread(true);\n\n const task = loadFn(externalId, { signal: controller.signal })\n .then((snapshot) => {\n if (controller.signal.aborted) return;\n // A snapshot the session assembled before a run cannot speak for what\n // that run has since produced, and an ADK id cannot correlate a\n // message sent optimistically with the one the session stored for it,\n // so there is nothing here that could merge the two. A refetch that\n // raced a run therefore defers to the run, whether the run started\n // during the load or was already streaming when it began.\n if (\n purpose === \"reload\" &&\n (isRunningRef.current ||\n messagesRef.current !== messagesAtLoadStart)\n )\n return;\n applySnapshot(snapshot);\n })\n .catch((error: unknown) => {\n // Aborting a load the runtime no longer needs is not a failure.\n if (controller.signal.aborted) return;\n throw error;\n })\n .finally(() => {\n if (loadControllerRef.current?.controller === controller) {\n loadControllerRef.current = null;\n }\n if (controller.signal.aborted) return;\n setIsLoadingThread(false);\n });\n record.promise = task;\n\n // A refetch reports the failure to whoever awaited it; the initial load\n // has no caller to tell.\n if (purpose === \"reload\") return task;\n return task.catch((e: unknown) => {\n console.warn(\"Failed to load ADK session:\", e);\n });\n },\n [threadListItem, applySnapshot],\n );\n\n useEffect(() => {\n runLoad();\n return () => {\n // Whatever is current, not this effect's own controller: a refetch swaps\n // the ref, and one in flight at unmount must be aborted too.\n loadControllerRef.current?.controller.abort();\n setIsLoadingThread(false);\n };\n }, [runLoad]);\n\n const runtime = useExternalStoreRuntime({\n ...pickExternalStoreSharedOptions(options),\n isRunning: effectiveIsRunning,\n isLoading: isLoadingThread,\n messages: threadMessages,\n unstable_enableToolInvocations: true,\n setToolStatuses,\n adapters: { attachments, dictation, feedback, speech, voice },\n extras: adkExtras.provide({\n agentInfo,\n stateDelta,\n artifactDelta,\n longRunningToolIds,\n toolConfirmations,\n authRequests,\n escalated,\n messageMetadata,\n send: handleSendMessage,\n }),\n onNew: async (msg) => {\n if (!(msg.startRun ?? msg.role === \"user\")) {\n stageUserMessage(msg);\n return;\n }\n\n const cancellations =\n autoCancelPendingToolCalls !== false\n ? getPendingCancellations(messages, longRunningToolIds)\n : [];\n\n return handleSendMessage(\n [\n ...cancellations,\n {\n id: uuidv4(),\n type: \"human\",\n content: getMessageContent(msg),\n },\n ],\n { runConfig: msg.runConfig },\n );\n },\n onEdit: getCheckpointId\n ? async (msg) => {\n const truncated = truncateAdkMessages(\n threadMessagesRef.current,\n msg.parentId,\n );\n replaceMessages(truncated);\n if (!(msg.startRun ?? msg.role === \"user\")) {\n const stagedMessage = toAdkUserMessage(msg);\n stagedMessagesRef.current.set(stagedMessage.id, {\n message: stagedMessage,\n runConfig: msg.runConfig,\n });\n setStagedMessageCount(stagedMessagesRef.current.size);\n const nextMessages = [...truncated, stagedMessage];\n adkMessagesRef.current = nextMessages;\n setMessages(nextMessages);\n return;\n }\n const externalId = aui.threadListItem.getState().externalId;\n const checkpointId = externalId\n ? await getCheckpointId(externalId, truncated)\n : null;\n return handleSendMessage(\n [\n {\n id: uuidv4(),\n type: \"human\",\n content: getMessageContent(msg),\n },\n ],\n {\n runConfig: msg.runConfig,\n ...(checkpointId && { checkpointId }),\n },\n );\n }\n : undefined,\n ...(getCheckpointId || hasStagedMessages\n ? {\n onReload: async (parentId, config) => {\n const stagedRun = getStagedRun(parentId);\n if (stagedRun) {\n for (const message of stagedRun.messages) {\n stagedMessagesRef.current.delete(message.id);\n }\n setStagedMessageCount(stagedMessagesRef.current.size);\n return handleSendMessage(stagedRun.messages, {\n runConfig: config.runConfig ?? stagedRun.runConfig,\n });\n }\n\n if (!getCheckpointId)\n throw new Error(\"Runtime does not support reloading messages.\");\n\n const truncated = truncateAdkMessages(\n threadMessagesRef.current,\n parentId,\n );\n replaceMessages(truncated);\n const externalId = aui.threadListItem.getState().externalId;\n const checkpointId = externalId\n ? await getCheckpointId(externalId, truncated)\n : null;\n return handleSendMessage([], {\n runConfig: config.runConfig,\n ...(checkpointId && { checkpointId }),\n });\n },\n }\n : {}),\n onAddToolResult: async ({\n toolCallId,\n toolName,\n result,\n isError,\n artifact,\n }) => {\n await handleSendMessage(\n [\n {\n id: uuidv4(),\n type: \"tool\",\n name: toolName,\n tool_call_id: toolCallId,\n content: JSON.stringify(result),\n artifact,\n status: isError ? \"error\" : \"success\",\n },\n ],\n {},\n );\n },\n onCancel: unstable_allowCancellation\n ? async () => {\n cancel();\n }\n : undefined,\n ...(load !== undefined && {\n onRefetchThread: () => runLoad(\"reload\"),\n }),\n });\n\n return runtime;\n};\n\nexport const useAdkRuntime = ({\n cloud,\n sessionAdapter,\n create,\n delete: deleteFn,\n onThreadIdChange,\n ...options\n}: UseAdkRuntimeOptions) => {\n const aui = useAui();\n const cloudAdapter = useCloudThreadListAdapter({\n cloud,\n create: async () => {\n if (create) return create();\n if (aui.threadListItem.source) return aui.threadListItem.initialize();\n return { externalId: undefined };\n },\n delete: deleteFn,\n });\n\n const adapter = sessionAdapter ?? cloudAdapter;\n\n return useRemoteThreadListRuntime({\n runtimeHook: function RuntimeHook() {\n return useAdkRuntimeImpl(options);\n },\n adapter,\n allowNesting: true,\n onThreadIdChange,\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useAdkRuntime.js","names":[],"sources":["../src/useAdkRuntime.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n pickExternalStoreSharedOptions,\n type AttachmentAdapter,\n type DictationAdapter,\n type ExternalStoreSharedOptions,\n type FeedbackAdapter,\n type RealtimeVoiceAdapter,\n type SpeechSynthesisAdapter,\n type AppendMessage,\n type ToolExecutionStatus,\n generateId,\n} from \"@assistant-ui/core\";\nimport {\n createAbortableThreadLoad,\n createCloudThreadListAdapterCreateFallback,\n} from \"@assistant-ui/core/internal\";\nimport {\n useCloudThreadListAdapter,\n useRemoteThreadListRuntime,\n useExternalMessageConverter,\n useExternalStoreRuntime,\n} from \"@assistant-ui/core/react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport type { AssistantCloud } from \"assistant-cloud\";\nimport type { RemoteThreadListAdapter } from \"@assistant-ui/core\";\nimport type {\n AdkMessage,\n AdkThreadSnapshot,\n AdkSendMessageConfig,\n AdkStreamCallback,\n OnAdkErrorCallback,\n OnAdkCustomEventCallback,\n OnAdkAgentTransferCallback,\n} from \"./types\";\nimport { useAdkMessages } from \"./useAdkMessages\";\nimport {\n convertAdkMessage,\n createAdkMessageConverter,\n} from \"./convertAdkMessages\";\nimport {\n getMessageContent,\n getPendingCancellations,\n toAdkUserMessage,\n truncateAdkMessages,\n} from \"./convertToAdkMessages\";\nimport {\n projectAdkToolApprovals,\n toAdkToolConfirmationReply,\n} from \"./adkToolApproval\";\nimport { adkExtras } from \"./adkExtras\";\n\nexport type UseAdkRuntimeOptions = ExternalStoreSharedOptions & {\n stream: AdkStreamCallback;\n /**\n * Called whenever the active thread's canonical (remote) ID changes, so the\n * value can be treated as a managed/controlled variable (e.g. synced to a URL\n * query param). Only the settled remote ID is emitted: while a freshly created\n * thread is still optimistic the value is `undefined`, and the real ID is\n * emitted once the thread is initialized; the transient local ID is never\n * surfaced.\n */\n onThreadIdChange?: ((threadId: string | undefined) => void) | undefined;\n autoCancelPendingToolCalls?: boolean | undefined;\n unstable_allowCancellation?: boolean | undefined;\n getCheckpointId?: (\n threadId: string,\n parentMessages: AdkMessage[],\n ) => Promise<string | null>;\n /**\n * Loads a thread's stored state. Called when the thread opens, and again for\n * `threads.reloadMainThread()`, which refetches in place rather than\n * remounting the runtime; the signal aborts a load the runtime no longer\n * needs.\n */\n load?: (\n threadId: string,\n options?: { signal?: AbortSignal | undefined },\n ) => Promise<AdkThreadSnapshot>;\n create?: () => Promise<{ externalId: string }>;\n delete?: (threadId: string) => Promise<void>;\n adapters?:\n | {\n attachments?: AttachmentAdapter;\n speech?: SpeechSynthesisAdapter;\n dictation?: DictationAdapter;\n voice?: RealtimeVoiceAdapter;\n feedback?: FeedbackAdapter;\n }\n | undefined;\n eventHandlers?:\n | {\n onError?: OnAdkErrorCallback;\n onCustomEvent?: OnAdkCustomEventCallback;\n onAgentTransfer?: OnAdkAgentTransferCallback;\n }\n | undefined;\n cloud?: AssistantCloud | undefined;\n /**\n * A `RemoteThreadListAdapter` to use instead of the cloud adapter.\n * Use with `createAdkSessionAdapter` for ADK session-backed persistence.\n */\n sessionAdapter?: RemoteThreadListAdapter | undefined;\n};\n\nconst useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {\n const {\n autoCancelPendingToolCalls,\n adapters: { attachments, dictation, feedback, speech, voice } = {},\n unstable_allowCancellation,\n stream,\n load,\n getCheckpointId,\n eventHandlers,\n } = options;\n const aui = useAui();\n const {\n messages,\n stateDelta,\n agentInfo,\n longRunningToolIds,\n artifactDelta,\n toolConfirmations,\n authRequests,\n escalated,\n messageMetadata,\n sendMessage,\n cancel,\n setMessages,\n replaceMessages,\n applySnapshot,\n } = useAdkMessages({\n stream,\n ...(eventHandlers && { eventHandlers }),\n });\n\n const loadRef = useRef(load);\n loadRef.current = load;\n const loadController = useMemo(createAbortableThreadLoad, []);\n const messagesRef = useRef(messages);\n messagesRef.current = messages;\n const [isLoadingThread, setIsLoadingThread] = useState(\n () =>\n load !== undefined && aui.threadListItem.getState().externalId != null,\n );\n\n const [isRunning, setIsRunning] = useState(false);\n const [toolStatuses, setToolStatuses] = useState<\n Record<string, ToolExecutionStatus>\n >({});\n const hasExecutingTools = Object.values(toolStatuses).some(\n (s) => s?.type === \"executing\",\n );\n const effectiveIsRunning = isRunning || hasExecutingTools;\n const isRunningRef = useRef(effectiveIsRunning);\n isRunningRef.current = effectiveIsRunning;\n\n const handleSendMessage = async (\n msgs: AdkMessage[],\n config: AdkSendMessageConfig,\n ) => {\n try {\n setIsRunning(true);\n await sendMessage(msgs, config);\n } finally {\n setIsRunning(false);\n }\n };\n\n const { approvals: toolApprovals, key: toolApprovalsKey } =\n projectAdkToolApprovals(messages);\n const toolApprovalsRef = useRef(toolApprovals);\n toolApprovalsRef.current = toolApprovals;\n\n const messageConverter = useMemo(\n () =>\n toolApprovalsKey === \"\"\n ? convertAdkMessage\n : createAdkMessageConverter(toolApprovalsRef.current),\n [toolApprovalsKey],\n );\n\n const threadMessages = useExternalMessageConverter({\n callback: messageConverter,\n messages,\n isRunning: effectiveIsRunning,\n });\n\n const threadMessagesRef = useRef(threadMessages);\n threadMessagesRef.current = threadMessages;\n\n const adkMessagesRef = useRef(messages);\n adkMessagesRef.current = messages;\n\n const stagedMessagesRef = useRef(\n new Map<\n string,\n {\n message: AdkMessage & { id: string };\n runConfig: AppendMessage[\"runConfig\"];\n }\n >(),\n );\n const [stagedMessageCount, setStagedMessageCount] = useState(0);\n const hasStagedMessages = stagedMessageCount > 0;\n\n const getStagedRun = (parentId: string | null) => {\n if (!parentId || !stagedMessagesRef.current.has(parentId)) return null;\n\n const staged: AdkMessage[] = [];\n for (const message of adkMessagesRef.current) {\n if (message.id && stagedMessagesRef.current.has(message.id)) {\n staged.push(stagedMessagesRef.current.get(message.id)!.message);\n }\n if (message.id === parentId) break;\n }\n\n return {\n messages: staged,\n runConfig: stagedMessagesRef.current.get(parentId)!.runConfig,\n };\n };\n\n const stageUserMessage = (msg: AppendMessage) => {\n const stagedMessage = toAdkUserMessage(msg);\n stagedMessagesRef.current.set(stagedMessage.id, {\n message: stagedMessage,\n runConfig: msg.runConfig,\n });\n setStagedMessageCount(stagedMessagesRef.current.size);\n const nextMessages = [...adkMessagesRef.current, stagedMessage];\n adkMessagesRef.current = nextMessages;\n setMessages(nextMessages);\n };\n\n // The scoped client, not `aui` itself: useAui returns a render-bound\n // instance, so depending on it would re-run the load on every render.\n const threadListItem =\n aui.threadListItem.source !== null ? aui.threadListItem : undefined;\n\n const runLoad = useCallback(\n (purpose: \"initial\" | \"reload\" = \"initial\") => {\n const loadFn = loadRef.current;\n if (!loadFn || !threadListItem) return Promise.resolve();\n\n const externalId = threadListItem.getState().externalId;\n if (externalId == null) return Promise.resolve();\n\n // The initial load is already fetching what a refetch would ask for, and\n // taking it over strands the thread's history if the refetch then fails.\n // Aborting a load the runtime no longer needs is not a failure.\n // A refetch reports the failure to whoever awaited it; the initial load\n // has no caller to tell.\n return loadController.run({\n purpose,\n load: async (signal) => {\n const messagesAtLoadStart = messagesRef.current;\n if (purpose === \"initial\") setIsLoadingThread(true);\n\n const snapshot = await loadFn(externalId, { signal });\n if (signal.aborted) return;\n // A snapshot the session assembled before a run cannot speak for what\n // that run has since produced, and an ADK id cannot correlate a\n // message sent optimistically with the one the session stored for it,\n // so there is nothing here that could merge the two. A refetch that\n // raced a run therefore defers to the run, whether the run started\n // during the load or was already streaming when it began.\n if (\n purpose === \"reload\" &&\n (isRunningRef.current ||\n messagesRef.current !== messagesAtLoadStart)\n )\n return;\n applySnapshot(snapshot);\n },\n onSettled: () => {\n setIsLoadingThread(false);\n },\n onInitialError: (error) => {\n console.warn(\"Failed to load ADK session:\", error);\n },\n });\n },\n [threadListItem, loadController, applySnapshot],\n );\n\n useEffect(() => {\n runLoad();\n return () => {\n // Whatever is current, not this effect's own controller: a refetch swaps\n // the ref, and one in flight at unmount must be aborted too.\n loadController.abort();\n setIsLoadingThread(false);\n };\n }, [loadController, runLoad]);\n\n const runtime = useExternalStoreRuntime({\n ...pickExternalStoreSharedOptions(options),\n isRunning,\n isLoading: isLoadingThread,\n messages: threadMessages,\n unstable_enableToolInvocations: true,\n setToolStatuses,\n adapters: { attachments, dictation, feedback, speech, voice },\n extras: adkExtras.provide({\n agentInfo,\n stateDelta,\n artifactDelta,\n longRunningToolIds,\n toolConfirmations,\n authRequests,\n escalated,\n messageMetadata,\n send: handleSendMessage,\n }),\n onNew: async (msg) => {\n if (!(msg.startRun ?? msg.role === \"user\")) {\n stageUserMessage(msg);\n return;\n }\n\n const cancellations =\n autoCancelPendingToolCalls !== false\n ? getPendingCancellations(messages, longRunningToolIds)\n : [];\n\n return handleSendMessage(\n [\n ...cancellations,\n {\n id: generateId(),\n type: \"human\",\n content: getMessageContent(msg),\n },\n ],\n { runConfig: msg.runConfig },\n );\n },\n onEdit: getCheckpointId\n ? async (msg) => {\n const truncated = truncateAdkMessages(\n threadMessagesRef.current,\n msg.parentId,\n );\n replaceMessages(truncated);\n if (!(msg.startRun ?? msg.role === \"user\")) {\n const stagedMessage = toAdkUserMessage(msg);\n stagedMessagesRef.current.set(stagedMessage.id, {\n message: stagedMessage,\n runConfig: msg.runConfig,\n });\n setStagedMessageCount(stagedMessagesRef.current.size);\n const nextMessages = [...truncated, stagedMessage];\n adkMessagesRef.current = nextMessages;\n setMessages(nextMessages);\n return;\n }\n const externalId = aui.threadListItem.getState().externalId;\n const checkpointId = externalId\n ? await getCheckpointId(externalId, truncated)\n : null;\n return handleSendMessage(\n [\n {\n id: generateId(),\n type: \"human\",\n content: getMessageContent(msg),\n },\n ],\n {\n runConfig: msg.runConfig,\n ...(checkpointId && { checkpointId }),\n },\n );\n }\n : undefined,\n ...(getCheckpointId || hasStagedMessages\n ? {\n onReload: async (parentId, config) => {\n const stagedRun = getStagedRun(parentId);\n if (stagedRun) {\n for (const message of stagedRun.messages) {\n stagedMessagesRef.current.delete(message.id);\n }\n setStagedMessageCount(stagedMessagesRef.current.size);\n return handleSendMessage(stagedRun.messages, {\n runConfig: config.runConfig ?? stagedRun.runConfig,\n });\n }\n\n if (!getCheckpointId)\n throw new Error(\"Runtime does not support reloading messages.\");\n\n const truncated = truncateAdkMessages(\n threadMessagesRef.current,\n parentId,\n );\n replaceMessages(truncated);\n const externalId = aui.threadListItem.getState().externalId;\n const checkpointId = externalId\n ? await getCheckpointId(externalId, truncated)\n : null;\n return handleSendMessage([], {\n runConfig: config.runConfig,\n ...(checkpointId && { checkpointId }),\n });\n },\n }\n : {}),\n onAddToolResult: async ({\n toolCallId,\n toolName,\n result,\n isError,\n artifact,\n }) => {\n await handleSendMessage(\n [\n {\n id: generateId(),\n type: \"tool\",\n name: toolName,\n tool_call_id: toolCallId,\n content: JSON.stringify(result),\n artifact,\n status: isError ? \"error\" : \"success\",\n },\n ],\n {},\n );\n },\n onRespondToToolApproval: async (options) => {\n await handleSendMessage(\n [toAdkToolConfirmationReply(options, toolApprovalsRef.current)],\n {},\n );\n },\n onCancel: unstable_allowCancellation\n ? async () => {\n cancel();\n }\n : undefined,\n ...(load !== undefined && {\n onRefetchThread: () => runLoad(\"reload\"),\n }),\n });\n\n return runtime;\n};\n\nexport const useAdkRuntime = ({\n cloud,\n sessionAdapter,\n create,\n delete: deleteFn,\n onThreadIdChange,\n ...options\n}: UseAdkRuntimeOptions) => {\n const aui = useAui();\n const cloudAdapter = useCloudThreadListAdapter({\n cloud,\n create: createCloudThreadListAdapterCreateFallback(\n create,\n aui.threadListItem,\n ),\n delete: deleteFn,\n });\n\n const adapter = sessionAdapter ?? cloudAdapter;\n\n return useRemoteThreadListRuntime({\n runtimeHook: function RuntimeHook() {\n return useAdkRuntimeImpl(options);\n },\n adapter,\n allowNesting: true,\n onThreadIdChange,\n });\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-google-adk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.27",
|
|
4
4
|
"description": "Google ADK adapter for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"google-adk",
|
|
@@ -34,11 +34,10 @@
|
|
|
34
34
|
],
|
|
35
35
|
"sideEffects": false,
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@assistant-ui/core": "^0.3.
|
|
38
|
-
"@assistant-ui/store": "^0.3.
|
|
37
|
+
"@assistant-ui/core": "^0.3.16",
|
|
38
|
+
"@assistant-ui/store": "^0.3.11",
|
|
39
39
|
"assistant-cloud": "*",
|
|
40
|
-
"assistant-stream": "^0.3.
|
|
41
|
-
"uuid": "^14.0.1"
|
|
40
|
+
"assistant-stream": "^0.3.40"
|
|
42
41
|
},
|
|
43
42
|
"peerDependencies": {
|
|
44
43
|
"@google/adk": ">=0.5.0",
|
|
@@ -54,13 +53,13 @@
|
|
|
54
53
|
}
|
|
55
54
|
},
|
|
56
55
|
"devDependencies": {
|
|
56
|
+
"@assistant-ui/x-buildutils": "0.0.24",
|
|
57
57
|
"@testing-library/dom": "^10.4.1",
|
|
58
58
|
"@testing-library/react": "^16.3.2",
|
|
59
59
|
"@types/react": "^19.2.18",
|
|
60
60
|
"jsdom": "^30.0.1",
|
|
61
61
|
"react": "^19.2.8",
|
|
62
|
-
"vitest": "^4.1.
|
|
63
|
-
"@assistant-ui/x-buildutils": "0.0.22"
|
|
62
|
+
"vitest": "^4.1.11"
|
|
64
63
|
},
|
|
65
64
|
"publishConfig": {
|
|
66
65
|
"access": "public",
|
package/src/AdkClient.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
2
|
import { createAdkStream } from "./AdkClient";
|
|
3
|
+
import { adkEventStream } from "./server/adkEventStream";
|
|
3
4
|
import type { AdkEvent, AdkMessage, AdkSendMessageConfig } from "./types";
|
|
4
5
|
|
|
5
6
|
// ── Helpers ──
|
|
@@ -447,6 +448,142 @@ describe("createAdkStream - SSE parsing", () => {
|
|
|
447
448
|
expect(collected[1]!.id).toBe("e2");
|
|
448
449
|
});
|
|
449
450
|
|
|
451
|
+
it.each(["{}", "null", "[]", '["event"]', '"event"'])(
|
|
452
|
+
"rejects empty or non-object stream events: %s",
|
|
453
|
+
async (payload) => {
|
|
454
|
+
mockFetch.mockResolvedValueOnce(
|
|
455
|
+
sseResponse(sseBody(`data: ${payload}\n\n`)),
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
const stream = createAdkStream({ api: "/api/adk" });
|
|
459
|
+
const consume = async () => {
|
|
460
|
+
const gen = await stream(
|
|
461
|
+
[{ id: "m1", type: "human", content: "Hi" }],
|
|
462
|
+
makeConfig(),
|
|
463
|
+
);
|
|
464
|
+
for await (const _event of gen) {
|
|
465
|
+
void _event;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
await expect(consume()).rejects.toThrow(
|
|
470
|
+
"Invalid ADK stream event: expected a non-empty object.",
|
|
471
|
+
);
|
|
472
|
+
},
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
it("reports invalid JSON as an ADK stream event error", async () => {
|
|
476
|
+
mockFetch.mockResolvedValueOnce(sseResponse(sseBody("data: not-json\n\n")));
|
|
477
|
+
|
|
478
|
+
const stream = createAdkStream({ api: "/api/adk" });
|
|
479
|
+
const gen = await stream(
|
|
480
|
+
[{ id: "m1", type: "human", content: "Hi" }],
|
|
481
|
+
makeConfig(),
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
await expect(gen.next()).rejects.toThrow(
|
|
485
|
+
"Invalid ADK stream event: expected valid JSON.",
|
|
486
|
+
);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it("preserves internal stream error events with an empty id", async () => {
|
|
490
|
+
const event: AdkEvent = {
|
|
491
|
+
id: "",
|
|
492
|
+
errorCode: "STREAM_ERROR",
|
|
493
|
+
errorMessage: "stream failed",
|
|
494
|
+
};
|
|
495
|
+
async function* failingEvents(): AsyncGenerator<never, void, undefined> {
|
|
496
|
+
throw new Error(event.errorMessage);
|
|
497
|
+
}
|
|
498
|
+
mockFetch.mockResolvedValueOnce(adkEventStream(failingEvents()));
|
|
499
|
+
|
|
500
|
+
const stream = createAdkStream({ api: "/api/adk" });
|
|
501
|
+
const gen = await stream(
|
|
502
|
+
[{ id: "m1", type: "human", content: "Hi" }],
|
|
503
|
+
makeConfig(),
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
await expect(gen.next()).resolves.toEqual({ done: false, value: event });
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it("normalizes id-less Python ADK error events", async () => {
|
|
510
|
+
const event = {
|
|
511
|
+
error: "ValueError: stream failed",
|
|
512
|
+
error_details: "traceback",
|
|
513
|
+
};
|
|
514
|
+
mockFetch.mockResolvedValueOnce(
|
|
515
|
+
sseResponse(sseBody(`data: ${JSON.stringify(event)}\n\n`)),
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
const stream = createAdkStream({ api: "/api/adk" });
|
|
519
|
+
const gen = await stream(
|
|
520
|
+
[{ id: "m1", type: "human", content: "Hi" }],
|
|
521
|
+
makeConfig(),
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
await expect(gen.next()).resolves.toEqual({
|
|
525
|
+
done: false,
|
|
526
|
+
value: {
|
|
527
|
+
...event,
|
|
528
|
+
errorMessage: event.error,
|
|
529
|
+
},
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it.each([
|
|
534
|
+
["camel-case", { errorCode: "PROVIDER_ERROR" }],
|
|
535
|
+
["snake-case", { error_code: "PROVIDER_ERROR" }],
|
|
536
|
+
])("preserves %s provider error codes", async (_, providerError) => {
|
|
537
|
+
const event = { error: "stream failed", ...providerError };
|
|
538
|
+
mockFetch.mockResolvedValueOnce(
|
|
539
|
+
sseResponse(sseBody(`data: ${JSON.stringify(event)}\n\n`)),
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
const stream = createAdkStream({ api: "/api/adk" });
|
|
543
|
+
const gen = await stream(
|
|
544
|
+
[{ id: "m1", type: "human", content: "Hi" }],
|
|
545
|
+
makeConfig(),
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
await expect(gen.next()).resolves.toEqual({
|
|
549
|
+
done: false,
|
|
550
|
+
value: { ...event, errorMessage: event.error },
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it("normalizes numeric event ids", async () => {
|
|
555
|
+
mockFetch.mockResolvedValueOnce(
|
|
556
|
+
sseResponse(sseBody('data: {"id":123}\n\n')),
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
const stream = createAdkStream({ api: "/api/adk" });
|
|
560
|
+
const gen = await stream(
|
|
561
|
+
[{ id: "m1", type: "human", content: "Hi" }],
|
|
562
|
+
makeConfig(),
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
await expect(gen.next()).resolves.toEqual({
|
|
566
|
+
done: false,
|
|
567
|
+
value: { id: "123" },
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it("rejects unsupported event id types", async () => {
|
|
572
|
+
mockFetch.mockResolvedValueOnce(
|
|
573
|
+
sseResponse(sseBody('data: {"id":true}\n\n')),
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
const stream = createAdkStream({ api: "/api/adk" });
|
|
577
|
+
const gen = await stream(
|
|
578
|
+
[{ id: "m1", type: "human", content: "Hi" }],
|
|
579
|
+
makeConfig(),
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
await expect(gen.next()).rejects.toThrow(
|
|
583
|
+
'Invalid ADK stream event: expected "id" to be a string or finite number when present.',
|
|
584
|
+
);
|
|
585
|
+
});
|
|
586
|
+
|
|
450
587
|
it("accepts parameterized event-stream content types", async () => {
|
|
451
588
|
const event: AdkEvent = { id: "e1" };
|
|
452
589
|
mockFetch.mockResolvedValueOnce(
|
package/src/AdkClient.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SSEEventDecoder } from "assistant-stream/utils";
|
|
2
2
|
import { contentToParts } from "./contentToParts";
|
|
3
|
+
import { parseAdkEventValue } from "./parseAdkEvent";
|
|
3
4
|
import { trimTrailingSlashes } from "./trimTrailingSlashes";
|
|
4
5
|
import type {
|
|
5
6
|
AdkEvent,
|
|
@@ -131,6 +132,17 @@ function validateEventStreamContentType(response: Response): void {
|
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
function parseAdkEvent(data: string): AdkEvent {
|
|
136
|
+
let value: unknown;
|
|
137
|
+
try {
|
|
138
|
+
value = JSON.parse(data);
|
|
139
|
+
} catch {
|
|
140
|
+
throw new Error("Invalid ADK stream event: expected valid JSON.");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return parseAdkEventValue(value, "Invalid ADK stream event");
|
|
144
|
+
}
|
|
145
|
+
|
|
134
146
|
async function resolveHeaders(
|
|
135
147
|
headers:
|
|
136
148
|
| Record<string, string>
|
|
@@ -258,7 +270,7 @@ async function* parseSSEResponse(response: Response): AsyncGenerator<AdkEvent> {
|
|
|
258
270
|
if (done) {
|
|
259
271
|
shouldCancel = false;
|
|
260
272
|
for (const event of sseDecoder.push(decoder.decode())) {
|
|
261
|
-
yield
|
|
273
|
+
yield parseAdkEvent(event.data);
|
|
262
274
|
}
|
|
263
275
|
break;
|
|
264
276
|
}
|
|
@@ -266,12 +278,12 @@ async function* parseSSEResponse(response: Response): AsyncGenerator<AdkEvent> {
|
|
|
266
278
|
for (const event of sseDecoder.push(
|
|
267
279
|
decoder.decode(value, { stream: true }),
|
|
268
280
|
)) {
|
|
269
|
-
yield
|
|
281
|
+
yield parseAdkEvent(event.data);
|
|
270
282
|
}
|
|
271
283
|
}
|
|
272
284
|
|
|
273
285
|
const trailing = sseDecoder.flush();
|
|
274
|
-
if (trailing !== null) yield
|
|
286
|
+
if (trailing !== null) yield parseAdkEvent(trailing.data);
|
|
275
287
|
} finally {
|
|
276
288
|
try {
|
|
277
289
|
if (shouldCancel) await reader.cancel().catch(() => undefined);
|