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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/AdkClient.d.ts +0 -1
  2. package/dist/AdkClient.d.ts.map +1 -1
  3. package/dist/AdkClient.js +11 -10
  4. package/dist/AdkClient.js.map +1 -1
  5. package/dist/AdkEventAccumulator.d.ts +0 -1
  6. package/dist/AdkEventAccumulator.d.ts.map +1 -1
  7. package/dist/AdkEventAccumulator.js +2 -2
  8. package/dist/AdkEventAccumulator.js.map +1 -1
  9. package/dist/AdkSessionAdapter.d.ts +0 -1
  10. package/dist/AdkSessionAdapter.d.ts.map +1 -1
  11. package/dist/AdkSessionAdapter.js +5 -2
  12. package/dist/AdkSessionAdapter.js.map +1 -1
  13. package/dist/adkExtras.d.ts +6 -0
  14. package/dist/adkExtras.d.ts.map +1 -0
  15. package/dist/adkExtras.js +7 -0
  16. package/dist/adkExtras.js.map +1 -0
  17. package/dist/contentToParts.d.ts +0 -1
  18. package/dist/contentToParts.d.ts.map +1 -1
  19. package/dist/convertAdkMessages.d.ts +0 -1
  20. package/dist/convertAdkMessages.d.ts.map +1 -1
  21. package/dist/hooks.d.ts +1 -18
  22. package/dist/hooks.d.ts.map +1 -1
  23. package/dist/hooks.js +48 -128
  24. package/dist/hooks.js.map +1 -1
  25. package/dist/server/adkEventStream.d.ts.map +1 -1
  26. package/dist/server/createAdkApiRoute.d.ts +0 -1
  27. package/dist/server/createAdkApiRoute.d.ts.map +1 -1
  28. package/dist/server/parseAdkRequest.d.ts +0 -1
  29. package/dist/server/parseAdkRequest.d.ts.map +1 -1
  30. package/dist/server/parseAdkRequest.js +2 -2
  31. package/dist/server/parseAdkRequest.js.map +1 -1
  32. package/dist/structuredEvents.d.ts +0 -1
  33. package/dist/structuredEvents.d.ts.map +1 -1
  34. package/dist/trimTrailingSlashes.d.ts +5 -0
  35. package/dist/trimTrailingSlashes.d.ts.map +1 -0
  36. package/dist/trimTrailingSlashes.js +10 -0
  37. package/dist/trimTrailingSlashes.js.map +1 -0
  38. package/dist/types.d.ts +17 -3
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/types.js.map +1 -1
  41. package/dist/useAdkMessages.d.ts +1 -5
  42. package/dist/useAdkMessages.d.ts.map +1 -1
  43. package/dist/useAdkMessages.js +2 -1
  44. package/dist/useAdkMessages.js.map +1 -1
  45. package/dist/useAdkRuntime.d.ts +10 -8
  46. package/dist/useAdkRuntime.d.ts.map +1 -1
  47. package/dist/useAdkRuntime.js +68 -11
  48. package/dist/useAdkRuntime.js.map +1 -1
  49. package/package.json +7 -7
  50. package/src/AdkClient.test.ts +134 -0
  51. package/src/AdkClient.ts +15 -20
  52. package/src/AdkSessionAdapter.test.ts +26 -5
  53. package/src/AdkSessionAdapter.ts +10 -2
  54. package/src/adkExtras.ts +4 -0
  55. package/src/hooks.ts +46 -125
  56. package/src/server/parseAdkRequest.test.ts +4 -2
  57. package/src/server/parseAdkRequest.ts +4 -2
  58. package/src/trimTrailingSlashes.ts +5 -0
  59. package/src/types.ts +13 -0
  60. package/src/useAdkRuntime.ts +118 -20
@@ -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
- return handleSendMessage([...autoCancelPendingToolCalls !== false ? getPendingCancellations(messages, longRunningToolIds) : [], {
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
- onReload: getCheckpointId ? async (parentId, config) => {
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
- } : void 0,
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.15",
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.11",
38
- "@assistant-ui/store": "^0.2.14",
37
+ "@assistant-ui/core": "^0.2.21",
38
+ "@assistant-ui/store": "^0.2.20",
39
39
  "assistant-cloud": "*",
40
- "assistant-stream": "^0.3.21",
41
- "uuid": "^14.0.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.8",
61
- "@assistant-ui/x-buildutils": "0.0.12"
60
+ "vitest": "^4.1.10",
61
+ "@assistant-ui/x-buildutils": "0.0.18"
62
62
  },
63
63
  "publishConfig": {
64
64
  "access": "public",
@@ -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
- let buffer = "";
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) break;
222
-
223
- buffer += decoder.decode(value, { stream: true });
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
- // Handle remaining buffer
237
- if (buffer.trim()) {
238
- for (const line of buffer.split("\n")) {
239
- if (line.startsWith("data: ")) {
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("returns an empty ReadableStream", async () => {
213
+ it("closes its title stream when title generation is unsupported", async () => {
214
214
  const { adapter } = createAdkSessionAdapter(baseOptions);
215
- const result = await adapter.generateTitle("s1", []);
216
- expect(result).toBeInstanceOf(ReadableStream);
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 encoding ──
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 }),
@@ -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 baseUrl = `${apiUrl}/apps/${encodeURIComponent(appName)}/users/${encodeURIComponent(userId)}/sessions`;
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(new ReadableStream<AssistantStreamChunk>());
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> {
@@ -0,0 +1,4 @@
1
+ import { createRuntimeExtras } from "@assistant-ui/core/internal";
2
+ import type { AdkRuntimeExtras } from "./types";
3
+
4
+ export const adkExtras = createRuntimeExtras<AdkRuntimeExtras>("useAdkRuntime");