@assistant-ui/react-google-adk 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/AdkClient.d.ts +45 -0
  4. package/dist/AdkClient.d.ts.map +1 -0
  5. package/dist/AdkClient.js +204 -0
  6. package/dist/AdkClient.js.map +1 -0
  7. package/dist/AdkEventAccumulator.d.ts +45 -0
  8. package/dist/AdkEventAccumulator.d.ts.map +1 -0
  9. package/dist/AdkEventAccumulator.js +508 -0
  10. package/dist/AdkEventAccumulator.js.map +1 -0
  11. package/dist/AdkSessionAdapter.d.ts +61 -0
  12. package/dist/AdkSessionAdapter.d.ts.map +1 -0
  13. package/dist/AdkSessionAdapter.js +159 -0
  14. package/dist/AdkSessionAdapter.js.map +1 -0
  15. package/dist/convertAdkMessages.d.ts +4 -0
  16. package/dist/convertAdkMessages.d.ts.map +1 -0
  17. package/dist/convertAdkMessages.js +75 -0
  18. package/dist/convertAdkMessages.js.map +1 -0
  19. package/dist/hooks.d.ts +50 -0
  20. package/dist/hooks.d.ts.map +1 -0
  21. package/dist/hooks.js +173 -0
  22. package/dist/hooks.js.map +1 -0
  23. package/dist/index.d.ts +11 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +10 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/server/adkEventStream.d.ts +42 -0
  28. package/dist/server/adkEventStream.d.ts.map +1 -0
  29. package/dist/server/adkEventStream.js +135 -0
  30. package/dist/server/adkEventStream.js.map +1 -0
  31. package/dist/server/createAdkApiRoute.d.ts +47 -0
  32. package/dist/server/createAdkApiRoute.d.ts.map +1 -0
  33. package/dist/server/createAdkApiRoute.js +41 -0
  34. package/dist/server/createAdkApiRoute.js.map +1 -0
  35. package/dist/server/index.d.ts +4 -0
  36. package/dist/server/index.d.ts.map +1 -0
  37. package/dist/server/index.js +4 -0
  38. package/dist/server/index.js.map +1 -0
  39. package/dist/server/parseAdkRequest.d.ts +56 -0
  40. package/dist/server/parseAdkRequest.d.ts.map +1 -0
  41. package/dist/server/parseAdkRequest.js +93 -0
  42. package/dist/server/parseAdkRequest.js.map +1 -0
  43. package/dist/structuredEvents.d.ts +7 -0
  44. package/dist/structuredEvents.d.ts.map +1 -0
  45. package/dist/structuredEvents.js +79 -0
  46. package/dist/structuredEvents.js.map +1 -0
  47. package/dist/types.d.ts +253 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +14 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/useAdkMessages.d.ts +28 -0
  52. package/dist/useAdkMessages.d.ts.map +1 -0
  53. package/dist/useAdkMessages.js +198 -0
  54. package/dist/useAdkMessages.js.map +1 -0
  55. package/dist/useAdkRuntime.d.ts +36 -0
  56. package/dist/useAdkRuntime.d.ts.map +1 -0
  57. package/dist/useAdkRuntime.js +252 -0
  58. package/dist/useAdkRuntime.js.map +1 -0
  59. package/package.json +83 -0
  60. package/server/package.json +4 -0
  61. package/src/AdkClient.test.ts +662 -0
  62. package/src/AdkClient.ts +274 -0
  63. package/src/AdkEventAccumulator.test.ts +591 -0
  64. package/src/AdkEventAccumulator.ts +602 -0
  65. package/src/AdkSessionAdapter.test.ts +362 -0
  66. package/src/AdkSessionAdapter.ts +245 -0
  67. package/src/convertAdkMessages.test.ts +209 -0
  68. package/src/convertAdkMessages.ts +93 -0
  69. package/src/hooks.ts +217 -0
  70. package/src/index.ts +66 -0
  71. package/src/server/adkEventStream.test.ts +78 -0
  72. package/src/server/adkEventStream.ts +161 -0
  73. package/src/server/createAdkApiRoute.test.ts +370 -0
  74. package/src/server/createAdkApiRoute.ts +86 -0
  75. package/src/server/index.ts +6 -0
  76. package/src/server/parseAdkRequest.test.ts +152 -0
  77. package/src/server/parseAdkRequest.ts +122 -0
  78. package/src/structuredEvents.ts +81 -0
  79. package/src/types.ts +265 -0
  80. package/src/useAdkMessages.ts +259 -0
  81. package/src/useAdkRuntime.ts +398 -0
@@ -0,0 +1,161 @@
1
+ import type { AdkEvent, AdkEventActions, AdkEventPart } from "../types";
2
+
3
+ /**
4
+ * The ADK SDK Event type. Intentionally loose to avoid requiring @google/adk.
5
+ */
6
+ type AdkSdkEvent = {
7
+ id?: string;
8
+ invocationId?: string;
9
+ author?: string;
10
+ branch?: string;
11
+ partial?: boolean;
12
+ turnComplete?: boolean;
13
+ interrupted?: boolean;
14
+ finishReason?: string;
15
+ timestamp?: number;
16
+ content?: {
17
+ role?: string;
18
+ parts?: Array<Record<string, unknown>>;
19
+ };
20
+ actions?: Record<string, unknown>;
21
+ longRunningToolIds?: string[];
22
+ errorCode?: string;
23
+ errorMessage?: string;
24
+ groundingMetadata?: unknown;
25
+ citationMetadata?: unknown;
26
+ usageMetadata?: unknown;
27
+ customMetadata?: Record<string, unknown>;
28
+ };
29
+
30
+ const convertSdkPart = (part: Record<string, unknown>): AdkEventPart => {
31
+ const result: Record<string, unknown> = {};
32
+ if ("text" in part && typeof part.text === "string") result.text = part.text;
33
+ if ("thought" in part && typeof part.thought === "boolean")
34
+ result.thought = part.thought;
35
+ if ("functionCall" in part && part.functionCall)
36
+ result.functionCall = part.functionCall;
37
+ if ("functionResponse" in part && part.functionResponse)
38
+ result.functionResponse = part.functionResponse;
39
+ if ("executableCode" in part && part.executableCode)
40
+ result.executableCode = part.executableCode;
41
+ if ("codeExecutionResult" in part && part.codeExecutionResult)
42
+ result.codeExecutionResult = part.codeExecutionResult;
43
+ if ("inlineData" in part && part.inlineData)
44
+ result.inlineData = part.inlineData;
45
+ if ("fileData" in part && part.fileData) result.fileData = part.fileData;
46
+ return result as AdkEventPart;
47
+ };
48
+
49
+ const convertSdkActions = (
50
+ actions: Record<string, unknown>,
51
+ ): AdkEventActions => {
52
+ const result: Record<string, unknown> = {};
53
+ if (actions.stateDelta != null) result.stateDelta = actions.stateDelta;
54
+ if (actions.artifactDelta != null)
55
+ result.artifactDelta = actions.artifactDelta;
56
+ if (actions.transferToAgent != null)
57
+ result.transferToAgent = actions.transferToAgent;
58
+ if (actions.escalate != null) result.escalate = actions.escalate;
59
+ if (actions.skipSummarization != null)
60
+ result.skipSummarization = actions.skipSummarization;
61
+ if (actions.requestedAuthConfigs != null)
62
+ result.requestedAuthConfigs = actions.requestedAuthConfigs;
63
+ if (actions.requestedToolConfirmations != null)
64
+ result.requestedToolConfirmations = actions.requestedToolConfirmations;
65
+ return result as AdkEventActions;
66
+ };
67
+
68
+ const convertSdkEvent = (event: AdkSdkEvent): AdkEvent => {
69
+ const result: Record<string, unknown> = { id: event.id ?? "" };
70
+ if (event.invocationId != null) result.invocationId = event.invocationId;
71
+ if (event.author != null) result.author = event.author;
72
+ if (event.branch != null) result.branch = event.branch;
73
+ if (event.partial != null) result.partial = event.partial;
74
+ if (event.turnComplete != null) result.turnComplete = event.turnComplete;
75
+ if (event.interrupted != null) result.interrupted = event.interrupted;
76
+ if (event.finishReason != null) result.finishReason = event.finishReason;
77
+ if (event.timestamp != null) result.timestamp = event.timestamp;
78
+ if (event.content) {
79
+ const content: Record<string, unknown> = {};
80
+ if (event.content.role != null) content.role = event.content.role;
81
+ if (event.content.parts)
82
+ content.parts = event.content.parts.map(convertSdkPart);
83
+ result.content = content;
84
+ }
85
+ if (event.actions) result.actions = convertSdkActions(event.actions);
86
+ if (event.longRunningToolIds)
87
+ result.longRunningToolIds = event.longRunningToolIds;
88
+ if (event.errorCode != null) result.errorCode = event.errorCode;
89
+ if (event.errorMessage != null) result.errorMessage = event.errorMessage;
90
+ if (event.groundingMetadata != null)
91
+ result.groundingMetadata = event.groundingMetadata;
92
+ if (event.citationMetadata != null)
93
+ result.citationMetadata = event.citationMetadata;
94
+ if (event.usageMetadata != null) result.usageMetadata = event.usageMetadata;
95
+ if (event.customMetadata != null)
96
+ result.customMetadata = event.customMetadata;
97
+ return result as AdkEvent;
98
+ };
99
+
100
+ export type AdkEventStreamOptions = {
101
+ onError?: (error: unknown) => void;
102
+ };
103
+
104
+ /**
105
+ * Converts an AsyncGenerator of ADK SDK Events into an SSE Response.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * import { adkEventStream } from '@assistant-ui/react-google-adk/server';
110
+ * const events = runner.runAsync({ userId, sessionId, newMessage });
111
+ * return adkEventStream(events);
112
+ * ```
113
+ */
114
+ export const adkEventStream = (
115
+ events: AsyncGenerator<AdkSdkEvent, void, undefined>,
116
+ options?: AdkEventStreamOptions,
117
+ ): Response => {
118
+ const encoder = new TextEncoder();
119
+ let cancelled = false;
120
+ const stream = new ReadableStream({
121
+ async start(controller) {
122
+ // Initial SSE comment to keep connection alive through proxies
123
+ controller.enqueue(encoder.encode(":ok\n\n"));
124
+ try {
125
+ for await (const event of events) {
126
+ if (cancelled) break;
127
+ const wireEvent = convertSdkEvent(event);
128
+ controller.enqueue(
129
+ encoder.encode(`data: ${JSON.stringify(wireEvent)}\n\n`),
130
+ );
131
+ }
132
+ } catch (e) {
133
+ if (!cancelled) {
134
+ options?.onError?.(e);
135
+ const errorEvent: AdkEvent = {
136
+ id: "",
137
+ errorCode: "STREAM_ERROR",
138
+ errorMessage:
139
+ e instanceof Error ? e.message : "Unknown stream error",
140
+ };
141
+ controller.enqueue(
142
+ encoder.encode(`data: ${JSON.stringify(errorEvent)}\n\n`),
143
+ );
144
+ }
145
+ } finally {
146
+ controller.close();
147
+ }
148
+ },
149
+ async cancel() {
150
+ cancelled = true;
151
+ await events.return?.(undefined as any);
152
+ },
153
+ });
154
+
155
+ return new Response(stream, {
156
+ headers: {
157
+ "Content-Type": "text/event-stream",
158
+ "Cache-Control": "no-cache",
159
+ },
160
+ });
161
+ };
@@ -0,0 +1,370 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+
3
+ // ── Module mocks ──
4
+
5
+ vi.mock("./parseAdkRequest", () => ({
6
+ parseAdkRequest: vi.fn(),
7
+ toAdkContent: vi.fn(),
8
+ }));
9
+
10
+ vi.mock("./adkEventStream", () => ({
11
+ adkEventStream: vi.fn(),
12
+ }));
13
+
14
+ import { createAdkApiRoute } from "./createAdkApiRoute";
15
+ import { parseAdkRequest, toAdkContent } from "./parseAdkRequest";
16
+ import { adkEventStream } from "./adkEventStream";
17
+
18
+ const mockParseAdkRequest = vi.mocked(parseAdkRequest);
19
+ const mockToAdkContent = vi.mocked(toAdkContent);
20
+ const mockAdkEventStream = vi.mocked(adkEventStream);
21
+
22
+ const makeRequest = (body: unknown = { message: "Hello" }) =>
23
+ new Request("http://localhost/api/adk", {
24
+ method: "POST",
25
+ headers: { "Content-Type": "application/json" },
26
+ body: JSON.stringify(body),
27
+ });
28
+
29
+ const makeRunner = () => ({
30
+ runAsync: vi.fn().mockReturnValue(
31
+ (async function* () {
32
+ yield { id: "e1" };
33
+ })(),
34
+ ),
35
+ });
36
+
37
+ beforeEach(() => {
38
+ vi.clearAllMocks();
39
+ mockParseAdkRequest.mockResolvedValue({
40
+ type: "message",
41
+ text: "Hello",
42
+ config: {},
43
+ });
44
+ mockToAdkContent.mockReturnValue({
45
+ role: "user",
46
+ parts: [{ text: "Hello" }],
47
+ });
48
+ mockAdkEventStream.mockReturnValue(new Response("stream", { status: 200 }));
49
+ });
50
+
51
+ // ── Basic flow ──
52
+
53
+ describe("createAdkApiRoute - basic flow", () => {
54
+ it("calls parseAdkRequest with the request", async () => {
55
+ const runner = makeRunner();
56
+ const handler = createAdkApiRoute({
57
+ runner,
58
+ userId: "user-1",
59
+ sessionId: "session-1",
60
+ });
61
+
62
+ const req = makeRequest();
63
+ await handler(req);
64
+
65
+ expect(mockParseAdkRequest).toHaveBeenCalledOnce();
66
+ expect(mockParseAdkRequest).toHaveBeenCalledWith(req);
67
+ });
68
+
69
+ it("calls toAdkContent with the parsed request", async () => {
70
+ const parsed = { type: "message" as const, text: "Hi", config: {} };
71
+ mockParseAdkRequest.mockResolvedValue(parsed);
72
+
73
+ const runner = makeRunner();
74
+ const handler = createAdkApiRoute({
75
+ runner,
76
+ userId: "user-1",
77
+ sessionId: "session-1",
78
+ });
79
+
80
+ await handler(makeRequest());
81
+
82
+ expect(mockToAdkContent).toHaveBeenCalledOnce();
83
+ expect(mockToAdkContent).toHaveBeenCalledWith(parsed);
84
+ });
85
+
86
+ it("calls runner.runAsync with the correct arguments", async () => {
87
+ const newMessage = { role: "user", parts: [{ text: "Hi" }] };
88
+ mockToAdkContent.mockReturnValue(newMessage);
89
+
90
+ const runner = makeRunner();
91
+ const handler = createAdkApiRoute({
92
+ runner,
93
+ userId: "user-1",
94
+ sessionId: "session-1",
95
+ });
96
+
97
+ await handler(makeRequest());
98
+
99
+ expect(runner.runAsync).toHaveBeenCalledOnce();
100
+ expect(runner.runAsync).toHaveBeenCalledWith({
101
+ userId: "user-1",
102
+ sessionId: "session-1",
103
+ newMessage,
104
+ });
105
+ });
106
+
107
+ it("passes runner.runAsync result to adkEventStream and returns its response", async () => {
108
+ const runner = makeRunner();
109
+ const expectedResponse = new Response("ok", { status: 200 });
110
+ mockAdkEventStream.mockReturnValue(expectedResponse);
111
+
112
+ const handler = createAdkApiRoute({
113
+ runner,
114
+ userId: "user-1",
115
+ sessionId: "session-1",
116
+ });
117
+
118
+ const result = await handler(makeRequest());
119
+
120
+ expect(mockAdkEventStream).toHaveBeenCalledOnce();
121
+ expect(result).toBe(expectedResponse);
122
+ });
123
+ });
124
+
125
+ // ── userId and sessionId as functions ──
126
+
127
+ describe("createAdkApiRoute - dynamic userId/sessionId", () => {
128
+ it("resolves userId from a function", async () => {
129
+ const runner = makeRunner();
130
+ const handler = createAdkApiRoute({
131
+ runner,
132
+ userId: (req) => new URL(req.url).searchParams.get("user") ?? "default",
133
+ sessionId: "session-1",
134
+ });
135
+
136
+ const req = new Request("http://localhost/api/adk?user=alice", {
137
+ method: "POST",
138
+ headers: { "Content-Type": "application/json" },
139
+ body: JSON.stringify({ message: "Hi" }),
140
+ });
141
+ await handler(req);
142
+
143
+ expect(runner.runAsync).toHaveBeenCalledWith(
144
+ expect.objectContaining({ userId: "alice" }),
145
+ );
146
+ });
147
+
148
+ it("resolves sessionId from a function", async () => {
149
+ const runner = makeRunner();
150
+ const handler = createAdkApiRoute({
151
+ runner,
152
+ userId: "user-1",
153
+ sessionId: (req) =>
154
+ new URL(req.url).searchParams.get("session") ?? "default",
155
+ });
156
+
157
+ const req = new Request("http://localhost/api/adk?session=s42", {
158
+ method: "POST",
159
+ headers: { "Content-Type": "application/json" },
160
+ body: JSON.stringify({ message: "Hi" }),
161
+ });
162
+ await handler(req);
163
+
164
+ expect(runner.runAsync).toHaveBeenCalledWith(
165
+ expect.objectContaining({ sessionId: "s42" }),
166
+ );
167
+ });
168
+
169
+ it("resolves async userId function", async () => {
170
+ const runner = makeRunner();
171
+ const handler = createAdkApiRoute({
172
+ runner,
173
+ userId: async () => "async-user",
174
+ sessionId: "session-1",
175
+ });
176
+
177
+ await handler(makeRequest());
178
+
179
+ expect(runner.runAsync).toHaveBeenCalledWith(
180
+ expect.objectContaining({ userId: "async-user" }),
181
+ );
182
+ });
183
+
184
+ it("resolves async sessionId function", async () => {
185
+ const runner = makeRunner();
186
+ const handler = createAdkApiRoute({
187
+ runner,
188
+ userId: "user-1",
189
+ sessionId: async () => "async-session",
190
+ });
191
+
192
+ await handler(makeRequest());
193
+
194
+ expect(runner.runAsync).toHaveBeenCalledWith(
195
+ expect.objectContaining({ sessionId: "async-session" }),
196
+ );
197
+ });
198
+ });
199
+
200
+ // ── stateDelta passthrough ──
201
+
202
+ describe("createAdkApiRoute - stateDelta", () => {
203
+ it("passes stateDelta to runner.runAsync when present", async () => {
204
+ mockParseAdkRequest.mockResolvedValue({
205
+ type: "message",
206
+ text: "Hello",
207
+ config: {},
208
+ stateDelta: { count: 1 },
209
+ });
210
+
211
+ const runner = makeRunner();
212
+ const handler = createAdkApiRoute({
213
+ runner,
214
+ userId: "user-1",
215
+ sessionId: "session-1",
216
+ });
217
+
218
+ await handler(makeRequest());
219
+
220
+ expect(runner.runAsync).toHaveBeenCalledWith(
221
+ expect.objectContaining({ stateDelta: { count: 1 } }),
222
+ );
223
+ });
224
+
225
+ it("does not include stateDelta when it is null/undefined", async () => {
226
+ mockParseAdkRequest.mockResolvedValue({
227
+ type: "message",
228
+ text: "Hello",
229
+ config: {},
230
+ });
231
+
232
+ const runner = makeRunner();
233
+ const handler = createAdkApiRoute({
234
+ runner,
235
+ userId: "user-1",
236
+ sessionId: "session-1",
237
+ });
238
+
239
+ await handler(makeRequest());
240
+
241
+ const callArgs = runner.runAsync.mock.calls[0]![0];
242
+ expect(callArgs).not.toHaveProperty("stateDelta");
243
+ });
244
+ });
245
+
246
+ // ── runConfig passthrough ──
247
+
248
+ describe("createAdkApiRoute - runConfig", () => {
249
+ it("passes runConfig to runner.runAsync when present", async () => {
250
+ mockParseAdkRequest.mockResolvedValue({
251
+ type: "message",
252
+ text: "Hello",
253
+ config: { runConfig: { temperature: 0.5 } },
254
+ });
255
+
256
+ const runner = makeRunner();
257
+ const handler = createAdkApiRoute({
258
+ runner,
259
+ userId: "user-1",
260
+ sessionId: "session-1",
261
+ });
262
+
263
+ await handler(makeRequest());
264
+
265
+ expect(runner.runAsync).toHaveBeenCalledWith(
266
+ expect.objectContaining({ runConfig: { temperature: 0.5 } }),
267
+ );
268
+ });
269
+
270
+ it("does not include runConfig when it is null/undefined", async () => {
271
+ mockParseAdkRequest.mockResolvedValue({
272
+ type: "message",
273
+ text: "Hello",
274
+ config: {},
275
+ });
276
+
277
+ const runner = makeRunner();
278
+ const handler = createAdkApiRoute({
279
+ runner,
280
+ userId: "user-1",
281
+ sessionId: "session-1",
282
+ });
283
+
284
+ await handler(makeRequest());
285
+
286
+ const callArgs = runner.runAsync.mock.calls[0]![0];
287
+ expect(callArgs).not.toHaveProperty("runConfig");
288
+ });
289
+ });
290
+
291
+ // ── onError passthrough ──
292
+
293
+ describe("createAdkApiRoute - onError", () => {
294
+ it("passes onError to adkEventStream", async () => {
295
+ const onError = vi.fn();
296
+ const runner = makeRunner();
297
+ const handler = createAdkApiRoute({
298
+ runner,
299
+ userId: "user-1",
300
+ sessionId: "session-1",
301
+ onError,
302
+ });
303
+
304
+ await handler(makeRequest());
305
+
306
+ expect(mockAdkEventStream).toHaveBeenCalledWith(expect.anything(), {
307
+ onError,
308
+ });
309
+ });
310
+
311
+ it("does not pass onError when not provided", async () => {
312
+ const runner = makeRunner();
313
+ const handler = createAdkApiRoute({
314
+ runner,
315
+ userId: "user-1",
316
+ sessionId: "session-1",
317
+ });
318
+
319
+ await handler(makeRequest());
320
+
321
+ expect(mockAdkEventStream).toHaveBeenCalledWith(
322
+ expect.anything(),
323
+ undefined,
324
+ );
325
+ });
326
+ });
327
+
328
+ // ── tool-result flow ──
329
+
330
+ describe("createAdkApiRoute - tool-result flow", () => {
331
+ it("handles tool-result requests", async () => {
332
+ const toolParsed = {
333
+ type: "tool-result" as const,
334
+ toolCallId: "tc-1",
335
+ toolName: "search",
336
+ result: { found: true },
337
+ isError: false,
338
+ config: {},
339
+ };
340
+ mockParseAdkRequest.mockResolvedValue(toolParsed);
341
+ mockToAdkContent.mockReturnValue({
342
+ role: "user",
343
+ parts: [
344
+ {
345
+ functionResponse: {
346
+ name: "search",
347
+ id: "tc-1",
348
+ response: { found: true },
349
+ },
350
+ },
351
+ ],
352
+ });
353
+
354
+ const runner = makeRunner();
355
+ const handler = createAdkApiRoute({
356
+ runner,
357
+ userId: "user-1",
358
+ sessionId: "session-1",
359
+ });
360
+
361
+ await handler(makeRequest());
362
+
363
+ expect(mockToAdkContent).toHaveBeenCalledWith(toolParsed);
364
+ expect(runner.runAsync).toHaveBeenCalledOnce();
365
+ const callArgs = runner.runAsync.mock.calls[0]![0];
366
+ expect(callArgs.newMessage.parts[0]).toMatchObject({
367
+ functionResponse: { name: "search", id: "tc-1" },
368
+ });
369
+ });
370
+ });
@@ -0,0 +1,86 @@
1
+ import { parseAdkRequest, toAdkContent } from "./parseAdkRequest";
2
+ import { adkEventStream, type AdkEventStreamOptions } from "./adkEventStream";
3
+
4
+ /**
5
+ * Loose runner type matching the ADK SDK's Runner interface.
6
+ * Avoids requiring `@google/adk` as a dependency.
7
+ */
8
+ type AdkRunner = {
9
+ runAsync(
10
+ options: Record<string, unknown>,
11
+ ): AsyncGenerator<any, void, undefined>;
12
+ };
13
+
14
+ export type CreateAdkApiRouteOptions = {
15
+ /**
16
+ * ADK Runner instance.
17
+ */
18
+ runner: AdkRunner;
19
+
20
+ /**
21
+ * User ID to use for the ADK session. Can be a static string
22
+ * or a function that extracts it from the request.
23
+ */
24
+ userId: string | ((req: Request) => string | Promise<string>);
25
+
26
+ /**
27
+ * Session ID to use. Can be a static string or a function
28
+ * that extracts it from the request (e.g. from query params or headers).
29
+ */
30
+ sessionId: string | ((req: Request) => string | Promise<string>);
31
+
32
+ /**
33
+ * Error handler for stream errors.
34
+ */
35
+ onError?: AdkEventStreamOptions["onError"];
36
+ };
37
+
38
+ /**
39
+ * Creates a request handler that combines `parseAdkRequest`, `toAdkContent`,
40
+ * and `adkEventStream` into a single function.
41
+ *
42
+ * @example Next.js App Router
43
+ * ```ts
44
+ * import { createAdkApiRoute } from '@assistant-ui/react-google-adk/server';
45
+ * import { runner } from './agent';
46
+ *
47
+ * export const POST = createAdkApiRoute({
48
+ * runner,
49
+ * userId: "default-user",
50
+ * sessionId: (req) => new URL(req.url).searchParams.get("sessionId") ?? "default",
51
+ * });
52
+ * ```
53
+ */
54
+ export function createAdkApiRoute(
55
+ options: CreateAdkApiRouteOptions,
56
+ ): (req: Request) => Promise<Response> {
57
+ return async (req: Request): Promise<Response> => {
58
+ const parsed = await parseAdkRequest(req);
59
+ const newMessage = toAdkContent(parsed);
60
+
61
+ const userId =
62
+ typeof options.userId === "function"
63
+ ? await options.userId(req)
64
+ : options.userId;
65
+
66
+ const sessionId =
67
+ typeof options.sessionId === "function"
68
+ ? await options.sessionId(req)
69
+ : options.sessionId;
70
+
71
+ const events = options.runner.runAsync({
72
+ userId,
73
+ sessionId,
74
+ newMessage,
75
+ ...(parsed.stateDelta != null && { stateDelta: parsed.stateDelta }),
76
+ ...(parsed.config.runConfig != null && {
77
+ runConfig: parsed.config.runConfig,
78
+ }),
79
+ });
80
+
81
+ return adkEventStream(
82
+ events,
83
+ options.onError ? { onError: options.onError } : undefined,
84
+ );
85
+ };
86
+ }
@@ -0,0 +1,6 @@
1
+ export { adkEventStream, type AdkEventStreamOptions } from "./adkEventStream";
2
+ export { parseAdkRequest, toAdkContent } from "./parseAdkRequest";
3
+ export {
4
+ createAdkApiRoute,
5
+ type CreateAdkApiRouteOptions,
6
+ } from "./createAdkApiRoute";