@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.
- package/dist/AdkClient.d.ts +0 -1
- package/dist/AdkClient.d.ts.map +1 -1
- package/dist/AdkClient.js +11 -10
- package/dist/AdkClient.js.map +1 -1
- package/dist/AdkEventAccumulator.d.ts +0 -1
- package/dist/AdkEventAccumulator.d.ts.map +1 -1
- package/dist/AdkEventAccumulator.js +2 -2
- package/dist/AdkEventAccumulator.js.map +1 -1
- package/dist/AdkSessionAdapter.d.ts +0 -1
- package/dist/AdkSessionAdapter.d.ts.map +1 -1
- package/dist/AdkSessionAdapter.js +5 -2
- package/dist/AdkSessionAdapter.js.map +1 -1
- package/dist/adkExtras.d.ts +6 -0
- package/dist/adkExtras.d.ts.map +1 -0
- package/dist/adkExtras.js +7 -0
- package/dist/adkExtras.js.map +1 -0
- package/dist/contentToParts.d.ts +0 -1
- package/dist/contentToParts.d.ts.map +1 -1
- package/dist/convertAdkMessages.d.ts +0 -1
- package/dist/convertAdkMessages.d.ts.map +1 -1
- package/dist/hooks.d.ts +1 -18
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +48 -128
- package/dist/hooks.js.map +1 -1
- package/dist/server/adkEventStream.d.ts.map +1 -1
- package/dist/server/createAdkApiRoute.d.ts +0 -1
- package/dist/server/createAdkApiRoute.d.ts.map +1 -1
- package/dist/server/parseAdkRequest.d.ts +0 -1
- package/dist/server/parseAdkRequest.d.ts.map +1 -1
- package/dist/server/parseAdkRequest.js +2 -2
- package/dist/server/parseAdkRequest.js.map +1 -1
- package/dist/structuredEvents.d.ts +0 -1
- package/dist/structuredEvents.d.ts.map +1 -1
- package/dist/trimTrailingSlashes.d.ts +5 -0
- package/dist/trimTrailingSlashes.d.ts.map +1 -0
- package/dist/trimTrailingSlashes.js +10 -0
- package/dist/trimTrailingSlashes.js.map +1 -0
- package/dist/types.d.ts +17 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/useAdkMessages.d.ts +1 -5
- package/dist/useAdkMessages.d.ts.map +1 -1
- package/dist/useAdkMessages.js +2 -1
- package/dist/useAdkMessages.js.map +1 -1
- package/dist/useAdkRuntime.d.ts +10 -8
- package/dist/useAdkRuntime.d.ts.map +1 -1
- package/dist/useAdkRuntime.js +68 -11
- package/dist/useAdkRuntime.js.map +1 -1
- package/package.json +7 -7
- package/src/AdkClient.test.ts +134 -0
- package/src/AdkClient.ts +15 -20
- package/src/AdkSessionAdapter.test.ts +26 -5
- package/src/AdkSessionAdapter.ts +10 -2
- package/src/adkExtras.ts +4 -0
- package/src/hooks.ts +46 -125
- package/src/server/parseAdkRequest.test.ts +4 -2
- package/src/server/parseAdkRequest.ts +4 -2
- package/src/trimTrailingSlashes.ts +5 -0
- package/src/types.ts +13 -0
- package/src/useAdkRuntime.ts +118 -20
package/dist/useAdkRuntime.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { adkExtras } from "./adkExtras.js";
|
|
1
2
|
import { convertAdkMessage } from "./convertAdkMessages.js";
|
|
2
|
-
import { symbolAdkRuntimeExtras } from "./hooks.js";
|
|
3
3
|
import { useAdkMessages } from "./useAdkMessages.js";
|
|
4
4
|
import { v4 } from "uuid";
|
|
5
5
|
import { useAui } from "@assistant-ui/store";
|
|
6
6
|
import { useEffect, useRef, useState } from "react";
|
|
7
|
-
import { getExternalStoreMessages, pickExternalStoreSharedOptions } from "@assistant-ui/core";
|
|
7
|
+
import { generateId, getExternalStoreMessages, pickExternalStoreSharedOptions } from "@assistant-ui/core";
|
|
8
8
|
import { useCloudThreadListAdapter, useExternalMessageConverter, useExternalStoreRuntime, useRemoteThreadListRuntime } from "@assistant-ui/core/react";
|
|
9
9
|
//#region src/useAdkRuntime.ts
|
|
10
10
|
/** @internal — exported for unit tests. */
|
|
@@ -70,10 +70,15 @@ const truncateAdkMessages = (threadMessages, parentId) => {
|
|
|
70
70
|
for (let i = 0; i <= parentIndex && i < threadMessages.length; i++) truncated.push(...getExternalStoreMessages(threadMessages[i]));
|
|
71
71
|
return truncated;
|
|
72
72
|
};
|
|
73
|
+
const toAdkUserMessage = (msg, id = generateId()) => ({
|
|
74
|
+
id,
|
|
75
|
+
type: "human",
|
|
76
|
+
content: getMessageContent(msg)
|
|
77
|
+
});
|
|
73
78
|
const useAdkRuntimeImpl = (options) => {
|
|
74
79
|
const { autoCancelPendingToolCalls, adapters: { attachments, dictation, feedback, speech, voice } = {}, unstable_allowCancellation, stream, load, getCheckpointId, eventHandlers } = options;
|
|
75
80
|
const aui = useAui();
|
|
76
|
-
const { messages, stateDelta, agentInfo, longRunningToolIds, artifactDelta, toolConfirmations, authRequests, escalated, messageMetadata, sendMessage, cancel, replaceMessages } = useAdkMessages({
|
|
81
|
+
const { messages, stateDelta, agentInfo, longRunningToolIds, artifactDelta, toolConfirmations, authRequests, escalated, messageMetadata, sendMessage, cancel, setMessages, replaceMessages } = useAdkMessages({
|
|
77
82
|
stream,
|
|
78
83
|
...eventHandlers && { eventHandlers }
|
|
79
84
|
});
|
|
@@ -96,6 +101,34 @@ const useAdkRuntimeImpl = (options) => {
|
|
|
96
101
|
});
|
|
97
102
|
const threadMessagesRef = useRef(threadMessages);
|
|
98
103
|
threadMessagesRef.current = threadMessages;
|
|
104
|
+
const adkMessagesRef = useRef(messages);
|
|
105
|
+
adkMessagesRef.current = messages;
|
|
106
|
+
const stagedMessagesRef = useRef(/* @__PURE__ */ new Map());
|
|
107
|
+
const [stagedMessageCount, setStagedMessageCount] = useState(0);
|
|
108
|
+
const hasStagedMessages = stagedMessageCount > 0;
|
|
109
|
+
const getStagedRun = (parentId) => {
|
|
110
|
+
if (!parentId || !stagedMessagesRef.current.has(parentId)) return null;
|
|
111
|
+
const staged = [];
|
|
112
|
+
for (const message of adkMessagesRef.current) {
|
|
113
|
+
if (message.id && stagedMessagesRef.current.has(message.id)) staged.push(stagedMessagesRef.current.get(message.id).message);
|
|
114
|
+
if (message.id === parentId) break;
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
messages: staged,
|
|
118
|
+
runConfig: stagedMessagesRef.current.get(parentId).runConfig
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
const stageUserMessage = (msg) => {
|
|
122
|
+
const stagedMessage = toAdkUserMessage(msg);
|
|
123
|
+
stagedMessagesRef.current.set(stagedMessage.id, {
|
|
124
|
+
message: stagedMessage,
|
|
125
|
+
runConfig: msg.runConfig
|
|
126
|
+
});
|
|
127
|
+
setStagedMessageCount(stagedMessagesRef.current.size);
|
|
128
|
+
const nextMessages = [...adkMessagesRef.current, stagedMessage];
|
|
129
|
+
adkMessagesRef.current = nextMessages;
|
|
130
|
+
setMessages(nextMessages);
|
|
131
|
+
};
|
|
99
132
|
const runtime = useExternalStoreRuntime({
|
|
100
133
|
...pickExternalStoreSharedOptions(options),
|
|
101
134
|
isRunning: effectiveIsRunning,
|
|
@@ -109,8 +142,7 @@ const useAdkRuntimeImpl = (options) => {
|
|
|
109
142
|
speech,
|
|
110
143
|
voice
|
|
111
144
|
},
|
|
112
|
-
extras: {
|
|
113
|
-
[symbolAdkRuntimeExtras]: true,
|
|
145
|
+
extras: adkExtras.provide({
|
|
114
146
|
agentInfo,
|
|
115
147
|
stateDelta,
|
|
116
148
|
artifactDelta,
|
|
@@ -120,9 +152,14 @@ const useAdkRuntimeImpl = (options) => {
|
|
|
120
152
|
escalated,
|
|
121
153
|
messageMetadata,
|
|
122
154
|
send: handleSendMessage
|
|
123
|
-
},
|
|
155
|
+
}),
|
|
124
156
|
onNew: async (msg) => {
|
|
125
|
-
|
|
157
|
+
if (!(msg.startRun ?? msg.role === "user")) {
|
|
158
|
+
stageUserMessage(msg);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const cancellations = autoCancelPendingToolCalls !== false ? getPendingCancellations(messages, longRunningToolIds) : [];
|
|
162
|
+
return handleSendMessage([...cancellations, {
|
|
126
163
|
id: v4(),
|
|
127
164
|
type: "human",
|
|
128
165
|
content: getMessageContent(msg)
|
|
@@ -131,6 +168,18 @@ const useAdkRuntimeImpl = (options) => {
|
|
|
131
168
|
onEdit: getCheckpointId ? async (msg) => {
|
|
132
169
|
const truncated = truncateAdkMessages(threadMessagesRef.current, msg.parentId);
|
|
133
170
|
replaceMessages(truncated);
|
|
171
|
+
if (!(msg.startRun ?? msg.role === "user")) {
|
|
172
|
+
const stagedMessage = toAdkUserMessage(msg);
|
|
173
|
+
stagedMessagesRef.current.set(stagedMessage.id, {
|
|
174
|
+
message: stagedMessage,
|
|
175
|
+
runConfig: msg.runConfig
|
|
176
|
+
});
|
|
177
|
+
setStagedMessageCount(stagedMessagesRef.current.size);
|
|
178
|
+
const nextMessages = [...truncated, stagedMessage];
|
|
179
|
+
adkMessagesRef.current = nextMessages;
|
|
180
|
+
setMessages(nextMessages);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
134
183
|
const externalId = aui.threadListItem().getState().externalId;
|
|
135
184
|
const checkpointId = externalId ? await getCheckpointId(externalId, truncated) : null;
|
|
136
185
|
return handleSendMessage([{
|
|
@@ -142,7 +191,14 @@ const useAdkRuntimeImpl = (options) => {
|
|
|
142
191
|
...checkpointId && { checkpointId }
|
|
143
192
|
});
|
|
144
193
|
} : void 0,
|
|
145
|
-
|
|
194
|
+
...getCheckpointId || hasStagedMessages ? { onReload: async (parentId, config) => {
|
|
195
|
+
const stagedRun = getStagedRun(parentId);
|
|
196
|
+
if (stagedRun) {
|
|
197
|
+
for (const message of stagedRun.messages) stagedMessagesRef.current.delete(message.id);
|
|
198
|
+
setStagedMessageCount(stagedMessagesRef.current.size);
|
|
199
|
+
return handleSendMessage(stagedRun.messages, { runConfig: config.runConfig ?? stagedRun.runConfig });
|
|
200
|
+
}
|
|
201
|
+
if (!getCheckpointId) throw new Error("Runtime does not support reloading messages.");
|
|
146
202
|
const truncated = truncateAdkMessages(threadMessagesRef.current, parentId);
|
|
147
203
|
replaceMessages(truncated);
|
|
148
204
|
const externalId = aui.threadListItem().getState().externalId;
|
|
@@ -151,7 +207,7 @@ const useAdkRuntimeImpl = (options) => {
|
|
|
151
207
|
runConfig: config.runConfig,
|
|
152
208
|
...checkpointId && { checkpointId }
|
|
153
209
|
});
|
|
154
|
-
} :
|
|
210
|
+
} } : {},
|
|
155
211
|
onAddToolResult: async ({ toolCallId, toolName, result, isError, artifact }) => {
|
|
156
212
|
await handleSendMessage([{
|
|
157
213
|
id: v4(),
|
|
@@ -186,7 +242,7 @@ const useAdkRuntimeImpl = (options) => {
|
|
|
186
242
|
}
|
|
187
243
|
return runtime;
|
|
188
244
|
};
|
|
189
|
-
const useAdkRuntime = ({ cloud, sessionAdapter, create, delete: deleteFn, ...options }) => {
|
|
245
|
+
const useAdkRuntime = ({ cloud, sessionAdapter, create, delete: deleteFn, onThreadIdChange, ...options }) => {
|
|
190
246
|
const aui = useAui();
|
|
191
247
|
const cloudAdapter = useCloudThreadListAdapter({
|
|
192
248
|
cloud,
|
|
@@ -202,7 +258,8 @@ const useAdkRuntime = ({ cloud, sessionAdapter, create, delete: deleteFn, ...opt
|
|
|
202
258
|
return useAdkRuntimeImpl(options);
|
|
203
259
|
},
|
|
204
260
|
adapter: sessionAdapter ?? cloudAdapter,
|
|
205
|
-
allowNesting: true
|
|
261
|
+
allowNesting: true,
|
|
262
|
+
onThreadIdChange
|
|
206
263
|
});
|
|
207
264
|
};
|
|
208
265
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAdkRuntime.js","names":["_exhaustiveCheck","uuidv4"],"sources":["../src/useAdkRuntime.ts"],"sourcesContent":["import { 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} from \"@assistant-ui/core\";\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 AdkSendMessageConfig,\n AdkStreamCallback,\n OnAdkErrorCallback,\n OnAdkCustomEventCallback,\n OnAdkAgentTransferCallback,\n} from \"./types\";\nimport { useAdkMessages } from \"./useAdkMessages\";\nimport { convertAdkMessage } from \"./convertAdkMessages\";\nimport { symbolAdkRuntimeExtras, type AdkRuntimeExtras } from \"./hooks\";\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.map((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 return {\n type: \"file\" as const,\n mimeType: part.mimeType,\n data: part.data,\n ...(part.filename != null && { filename: part.filename }),\n };\n\n case \"tool-call\":\n throw new Error(\"Tool call appends are not supported.\");\n\n default: {\n const _exhaustiveCheck:\n | \"reasoning\"\n | \"source\"\n | \"audio\"\n | \"data\"\n | \"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\nexport type UseAdkRuntimeOptions = ExternalStoreSharedOptions & {\n stream: AdkStreamCallback;\n autoCancelPendingToolCalls?: boolean | undefined;\n unstable_allowCancellation?: boolean | undefined;\n getCheckpointId?: (\n threadId: string,\n parentMessages: AdkMessage[],\n ) => Promise<string | null>;\n load?: (threadId: string) => Promise<{ messages: AdkMessage[] }>;\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 replaceMessages,\n } = useAdkMessages({\n stream,\n ...(eventHandlers && { eventHandlers }),\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\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 runtime = useExternalStoreRuntime({\n ...pickExternalStoreSharedOptions(options),\n isRunning: effectiveIsRunning,\n messages: threadMessages,\n unstable_enableToolInvocations: true,\n setToolStatuses,\n adapters: { attachments, dictation, feedback, speech, voice },\n extras: {\n [symbolAdkRuntimeExtras]: true,\n agentInfo,\n stateDelta,\n artifactDelta,\n longRunningToolIds,\n toolConfirmations,\n authRequests,\n escalated,\n messageMetadata,\n send: handleSendMessage,\n } satisfies AdkRuntimeExtras,\n onNew: async (msg) => {\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 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 onReload: getCheckpointId\n ? async (parentId, config) => {\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 : undefined,\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 });\n\n {\n const loadRef = useRef(load);\n useEffect(() => {\n loadRef.current = load;\n });\n\n useEffect(() => {\n const loadFn = loadRef.current;\n if (!loadFn) return;\n\n const externalId = aui.threadListItem().getState().externalId;\n if (externalId == null) return;\n\n loadFn(externalId).then(\n ({ messages: msgs }) => {\n replaceMessages(msgs);\n },\n (e) => {\n console.warn(\"Failed to load ADK session:\", e);\n },\n );\n }, [aui, replaceMessages]);\n }\n\n return runtime;\n};\n\nexport const useAdkRuntime = ({\n cloud,\n sessionAdapter,\n create,\n delete: deleteFn,\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 });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useAdkRuntime.js","names":["_exhaustiveCheck","uuidv4"],"sources":["../src/useAdkRuntime.ts"],"sourcesContent":["import { 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 {\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 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.map((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 return {\n type: \"file\" as const,\n mimeType: part.mimeType,\n data: part.data,\n ...(part.filename != null && { filename: part.filename }),\n };\n\n case \"tool-call\":\n throw new Error(\"Tool call appends are not supported.\");\n\n default: {\n const _exhaustiveCheck:\n | \"reasoning\"\n | \"source\"\n | \"audio\"\n | \"data\"\n | \"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 load?: (threadId: string) => Promise<{ messages: AdkMessage[] }>;\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 } = useAdkMessages({\n stream,\n ...(eventHandlers && { eventHandlers }),\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\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 const runtime = useExternalStoreRuntime({\n ...pickExternalStoreSharedOptions(options),\n isRunning: effectiveIsRunning,\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 });\n\n {\n const loadRef = useRef(load);\n useEffect(() => {\n loadRef.current = load;\n });\n\n useEffect(() => {\n const loadFn = loadRef.current;\n if (!loadFn) return;\n\n const externalId = aui.threadListItem().getState().externalId;\n if (externalId == null) return;\n\n loadFn(externalId).then(\n ({ messages: msgs }) => {\n replaceMessages(msgs);\n },\n (e) => {\n console.warn(\"Failed to load ADK session:\", e);\n },\n );\n }, [aui, replaceMessages]);\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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"}
|
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.17",
|
|
4
4
|
"description": "Google ADK adapter for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"google-adk",
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
],
|
|
35
35
|
"sideEffects": false,
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@assistant-ui/core": "^0.2.
|
|
38
|
-
"@assistant-ui/store": "^0.2.
|
|
37
|
+
"@assistant-ui/core": "^0.2.21",
|
|
38
|
+
"@assistant-ui/store": "^0.2.20",
|
|
39
39
|
"assistant-cloud": "*",
|
|
40
|
-
"assistant-stream": "^0.3.
|
|
41
|
-
"uuid": "^14.0.
|
|
40
|
+
"assistant-stream": "^0.3.26",
|
|
41
|
+
"uuid": "^14.0.1"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@google/adk": ">=0.5.0",
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
"@types/react": "^19.2.17",
|
|
58
58
|
"@types/uuid": "^11.0.0",
|
|
59
59
|
"react": "^19.2.7",
|
|
60
|
-
"vitest": "^4.1.
|
|
61
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
60
|
+
"vitest": "^4.1.10",
|
|
61
|
+
"@assistant-ui/x-buildutils": "0.0.18"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public",
|
package/src/AdkClient.test.ts
CHANGED
|
@@ -34,6 +34,17 @@ const sseBody = (text: string): ReadableStream<Uint8Array> => {
|
|
|
34
34
|
});
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
const nextWithTimeout = async <T>(
|
|
38
|
+
promise: Promise<IteratorResult<T>>,
|
|
39
|
+
): Promise<IteratorResult<T> | "timeout"> => {
|
|
40
|
+
return Promise.race([
|
|
41
|
+
promise,
|
|
42
|
+
new Promise<"timeout">((resolve) => {
|
|
43
|
+
setTimeout(() => resolve("timeout"), 100);
|
|
44
|
+
}),
|
|
45
|
+
]);
|
|
46
|
+
};
|
|
47
|
+
|
|
37
48
|
const mockFetch =
|
|
38
49
|
vi.fn<(input: RequestInfo | URL, init?: RequestInit) => Promise<Response>>();
|
|
39
50
|
|
|
@@ -250,6 +261,30 @@ describe("createAdkStream - direct mode", () => {
|
|
|
250
261
|
});
|
|
251
262
|
});
|
|
252
263
|
|
|
264
|
+
it.each(["http://localhost:8000/", "http://localhost:8000//"])(
|
|
265
|
+
"normalizes trailing slashes in the api URL: %s",
|
|
266
|
+
async (api) => {
|
|
267
|
+
mockFetch.mockResolvedValueOnce(
|
|
268
|
+
new Response(sseBody(""), { status: 200 }),
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const stream = createAdkStream({
|
|
272
|
+
api,
|
|
273
|
+
appName: "my-app",
|
|
274
|
+
userId: "user-1",
|
|
275
|
+
});
|
|
276
|
+
const gen = await stream(
|
|
277
|
+
[{ id: "m1", type: "human", content: "Hello" }],
|
|
278
|
+
makeConfig(),
|
|
279
|
+
);
|
|
280
|
+
for await (const _ of gen) {
|
|
281
|
+
/* noop */
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
expect(mockFetch.mock.calls[0]![0]).toBe("http://localhost:8000/run_sse");
|
|
285
|
+
},
|
|
286
|
+
);
|
|
287
|
+
|
|
253
288
|
it("calls config.initialize() to get the sessionId", async () => {
|
|
254
289
|
const initialize = vi
|
|
255
290
|
.fn()
|
|
@@ -398,6 +433,78 @@ describe("createAdkStream - SSE parsing", () => {
|
|
|
398
433
|
expect(collected[0]!.id).toBe("e1");
|
|
399
434
|
});
|
|
400
435
|
|
|
436
|
+
it("parses data fields without a space after the colon", async () => {
|
|
437
|
+
const text = `data:${JSON.stringify({ id: "e1" })}\n\n`;
|
|
438
|
+
mockFetch.mockResolvedValueOnce(
|
|
439
|
+
new Response(sseBody(text), { status: 200 }),
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
const stream = createAdkStream({ api: "/api/adk" });
|
|
443
|
+
const gen = await stream(
|
|
444
|
+
[{ id: "m1", type: "human", content: "Hi" }],
|
|
445
|
+
makeConfig(),
|
|
446
|
+
);
|
|
447
|
+
const collected: AdkEvent[] = [];
|
|
448
|
+
for await (const evt of gen) {
|
|
449
|
+
collected.push(evt);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
expect(collected).toHaveLength(1);
|
|
453
|
+
expect(collected[0]!.id).toBe("e1");
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it("parses CR-delimited SSE events", async () => {
|
|
457
|
+
const events: AdkEvent[] = [{ id: "e1" }, { id: "e2" }];
|
|
458
|
+
const text = events.map((e) => `data: ${JSON.stringify(e)}\r\r`).join("");
|
|
459
|
+
mockFetch.mockResolvedValueOnce(
|
|
460
|
+
new Response(sseBody(text), { status: 200 }),
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
const stream = createAdkStream({ api: "/api/adk" });
|
|
464
|
+
const gen = await stream(
|
|
465
|
+
[{ id: "m1", type: "human", content: "Hi" }],
|
|
466
|
+
makeConfig(),
|
|
467
|
+
);
|
|
468
|
+
const collected: AdkEvent[] = [];
|
|
469
|
+
for await (const evt of gen) {
|
|
470
|
+
collected.push(evt);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
expect(collected).toHaveLength(2);
|
|
474
|
+
expect(collected[0]!.id).toBe("e1");
|
|
475
|
+
expect(collected[1]!.id).toBe("e2");
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it("emits CRLF-delimited SSE events before the response closes", async () => {
|
|
479
|
+
const encoder = new TextEncoder();
|
|
480
|
+
let controller!: ReadableStreamDefaultController<Uint8Array>;
|
|
481
|
+
const body = new ReadableStream<Uint8Array>({
|
|
482
|
+
start(nextController) {
|
|
483
|
+
controller = nextController;
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
mockFetch.mockResolvedValueOnce(new Response(body, { status: 200 }));
|
|
487
|
+
|
|
488
|
+
const stream = createAdkStream({ api: "/api/adk" });
|
|
489
|
+
const gen = await stream(
|
|
490
|
+
[{ id: "m1", type: "human", content: "Hi" }],
|
|
491
|
+
makeConfig(),
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
const first = gen.next();
|
|
495
|
+
controller.enqueue(
|
|
496
|
+
encoder.encode(`data: ${JSON.stringify({ id: "e1" })}\r`),
|
|
497
|
+
);
|
|
498
|
+
controller.enqueue(encoder.encode("\n\r\n"));
|
|
499
|
+
|
|
500
|
+
await expect(nextWithTimeout(first)).resolves.toMatchObject({
|
|
501
|
+
done: false,
|
|
502
|
+
value: { id: "e1" },
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
controller.close();
|
|
506
|
+
});
|
|
507
|
+
|
|
401
508
|
it("handles partial chunks that split across reads", async () => {
|
|
402
509
|
const event = { id: "e1", content: { parts: [{ text: "split" }] } };
|
|
403
510
|
const fullText = `data: ${JSON.stringify(event)}\n\n`;
|
|
@@ -429,6 +536,33 @@ describe("createAdkStream - SSE parsing", () => {
|
|
|
429
536
|
expect(collected[0]!.id).toBe("e1");
|
|
430
537
|
});
|
|
431
538
|
|
|
539
|
+
it("parses CR-delimited events split across chunks", async () => {
|
|
540
|
+
const encoder = new TextEncoder();
|
|
541
|
+
const body = new ReadableStream<Uint8Array>({
|
|
542
|
+
start(controller) {
|
|
543
|
+
controller.enqueue(
|
|
544
|
+
encoder.encode(`data: ${JSON.stringify({ id: "e1" })}\r`),
|
|
545
|
+
);
|
|
546
|
+
controller.enqueue(encoder.encode("\r"));
|
|
547
|
+
controller.close();
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
mockFetch.mockResolvedValueOnce(new Response(body, { status: 200 }));
|
|
551
|
+
|
|
552
|
+
const stream = createAdkStream({ api: "/api/adk" });
|
|
553
|
+
const gen = await stream(
|
|
554
|
+
[{ id: "m1", type: "human", content: "Hi" }],
|
|
555
|
+
makeConfig(),
|
|
556
|
+
);
|
|
557
|
+
const collected: AdkEvent[] = [];
|
|
558
|
+
for await (const evt of gen) {
|
|
559
|
+
collected.push(evt);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
expect(collected).toHaveLength(1);
|
|
563
|
+
expect(collected[0]!.id).toBe("e1");
|
|
564
|
+
});
|
|
565
|
+
|
|
432
566
|
it("handles remaining buffer at end of stream", async () => {
|
|
433
567
|
// No trailing \n\n
|
|
434
568
|
const event = { id: "e1" };
|
package/src/AdkClient.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { SSEEventDecoder } from "assistant-stream/utils";
|
|
1
2
|
import { contentToParts } from "./contentToParts";
|
|
3
|
+
import { trimTrailingSlashes } from "./trimTrailingSlashes";
|
|
2
4
|
import type {
|
|
3
5
|
AdkEvent,
|
|
4
6
|
AdkEventPart,
|
|
@@ -68,7 +70,7 @@ export function createAdkStream(
|
|
|
68
70
|
|
|
69
71
|
if (isDirect) {
|
|
70
72
|
// Direct mode: POST to ADK server's /run_sse
|
|
71
|
-
url = `${options.api}/run_sse`;
|
|
73
|
+
url = `${trimTrailingSlashes(options.api)}/run_sse`;
|
|
72
74
|
const { externalId } = await config.initialize();
|
|
73
75
|
body = {
|
|
74
76
|
appName: options.appName,
|
|
@@ -213,34 +215,27 @@ function messagesToProxyBody(
|
|
|
213
215
|
async function* parseSSEResponse(response: Response): AsyncGenerator<AdkEvent> {
|
|
214
216
|
const reader = response.body!.getReader();
|
|
215
217
|
const decoder = new TextDecoder();
|
|
216
|
-
|
|
218
|
+
const sseDecoder = new SSEEventDecoder({ trailing: "dispatch" });
|
|
217
219
|
|
|
218
220
|
try {
|
|
219
221
|
while (true) {
|
|
220
222
|
const { done, value } = await reader.read();
|
|
221
|
-
if (done)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const parts = buffer.split("\n\n");
|
|
225
|
-
buffer = parts.pop() ?? "";
|
|
226
|
-
|
|
227
|
-
for (const part of parts) {
|
|
228
|
-
for (const line of part.split("\n")) {
|
|
229
|
-
if (line.startsWith("data: ")) {
|
|
230
|
-
yield JSON.parse(line.slice(6)) as AdkEvent;
|
|
231
|
-
}
|
|
223
|
+
if (done) {
|
|
224
|
+
for (const event of sseDecoder.push(decoder.decode())) {
|
|
225
|
+
yield JSON.parse(event.data) as AdkEvent;
|
|
232
226
|
}
|
|
227
|
+
break;
|
|
233
228
|
}
|
|
234
|
-
}
|
|
235
229
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
yield JSON.parse(line.slice(6)) as AdkEvent;
|
|
241
|
-
}
|
|
230
|
+
for (const event of sseDecoder.push(
|
|
231
|
+
decoder.decode(value, { stream: true }),
|
|
232
|
+
)) {
|
|
233
|
+
yield JSON.parse(event.data) as AdkEvent;
|
|
242
234
|
}
|
|
243
235
|
}
|
|
236
|
+
|
|
237
|
+
const trailing = sseDecoder.flush();
|
|
238
|
+
if (trailing !== null) yield JSON.parse(trailing.data) as AdkEvent;
|
|
244
239
|
} finally {
|
|
245
240
|
reader.releaseLock();
|
|
246
241
|
}
|
|
@@ -210,10 +210,14 @@ describe("createAdkSessionAdapter - no-op methods", () => {
|
|
|
210
210
|
// ── adapter.generateTitle ──
|
|
211
211
|
|
|
212
212
|
describe("createAdkSessionAdapter - generateTitle", () => {
|
|
213
|
-
it("
|
|
213
|
+
it("closes its title stream when title generation is unsupported", async () => {
|
|
214
214
|
const { adapter } = createAdkSessionAdapter(baseOptions);
|
|
215
|
-
const
|
|
216
|
-
|
|
215
|
+
const stream = await adapter.generateTitle("s1", []);
|
|
216
|
+
|
|
217
|
+
await expect(stream.getReader().read()).resolves.toEqual({
|
|
218
|
+
done: true,
|
|
219
|
+
value: undefined,
|
|
220
|
+
});
|
|
217
221
|
});
|
|
218
222
|
});
|
|
219
223
|
|
|
@@ -339,9 +343,26 @@ describe("createAdkSessionAdapter - load", () => {
|
|
|
339
343
|
});
|
|
340
344
|
});
|
|
341
345
|
|
|
342
|
-
// ── URL
|
|
346
|
+
// ── URL construction ──
|
|
347
|
+
|
|
348
|
+
describe("createAdkSessionAdapter - URL construction", () => {
|
|
349
|
+
it.each(["http://localhost:8000/", "http://localhost:8000//"])(
|
|
350
|
+
"normalizes trailing slashes in apiUrl: %s",
|
|
351
|
+
async (apiUrl) => {
|
|
352
|
+
mockFetch.mockResolvedValueOnce(
|
|
353
|
+
new Response(JSON.stringify([]), { status: 200 }),
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
const { adapter } = createAdkSessionAdapter({
|
|
357
|
+
...baseOptions,
|
|
358
|
+
apiUrl,
|
|
359
|
+
});
|
|
360
|
+
await adapter.list();
|
|
361
|
+
|
|
362
|
+
expect(mockFetch.mock.calls[0]![0]).toBe(expectedBaseUrl);
|
|
363
|
+
},
|
|
364
|
+
);
|
|
343
365
|
|
|
344
|
-
describe("createAdkSessionAdapter - URL encoding", () => {
|
|
345
366
|
it("encodes special characters in appName and userId", async () => {
|
|
346
367
|
mockFetch.mockResolvedValueOnce(
|
|
347
368
|
new Response(JSON.stringify([]), { status: 200 }),
|
package/src/AdkSessionAdapter.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
} from "@assistant-ui/core";
|
|
8
8
|
import { AdkEventAccumulator } from "./AdkEventAccumulator";
|
|
9
9
|
import type { AdkEvent, AdkMessage } from "./types";
|
|
10
|
+
import { trimTrailingSlashes } from "./trimTrailingSlashes";
|
|
10
11
|
|
|
11
12
|
export type AdkSessionAdapterOptions = {
|
|
12
13
|
/**
|
|
@@ -79,7 +80,8 @@ export function createAdkSessionAdapter(
|
|
|
79
80
|
options: AdkSessionAdapterOptions,
|
|
80
81
|
): AdkSessionAdapterResult {
|
|
81
82
|
const { apiUrl, appName, userId } = options;
|
|
82
|
-
const
|
|
83
|
+
const normalizedApiUrl = trimTrailingSlashes(apiUrl);
|
|
84
|
+
const baseUrl = `${normalizedApiUrl}/apps/${encodeURIComponent(appName)}/users/${encodeURIComponent(userId)}/sessions`;
|
|
83
85
|
|
|
84
86
|
const getHeaders = async (): Promise<Record<string, string>> => {
|
|
85
87
|
if (!options.headers) return {};
|
|
@@ -152,7 +154,13 @@ export function createAdkSessionAdapter(
|
|
|
152
154
|
|
|
153
155
|
generateTitle(): Promise<AssistantStream> {
|
|
154
156
|
// Title generation not supported without assistant-cloud
|
|
155
|
-
return Promise.resolve(
|
|
157
|
+
return Promise.resolve(
|
|
158
|
+
new ReadableStream<AssistantStreamChunk>({
|
|
159
|
+
start(controller) {
|
|
160
|
+
controller.close();
|
|
161
|
+
},
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
156
164
|
},
|
|
157
165
|
|
|
158
166
|
async fetch(threadId: string): Promise<RemoteThreadMetadata> {
|
package/src/adkExtras.ts
ADDED