@assistant-ui/react-google-adk 0.0.34 → 0.0.36

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 (65) hide show
  1. package/README.md +12 -2
  2. package/dist/AdkClient.d.ts +4 -0
  3. package/dist/AdkClient.d.ts.map +1 -1
  4. package/dist/AdkClient.js +12 -7
  5. package/dist/AdkClient.js.map +1 -1
  6. package/dist/AdkSessionAdapter.d.ts.map +1 -1
  7. package/dist/AdkSessionAdapter.js +7 -7
  8. package/dist/AdkSessionAdapter.js.map +1 -1
  9. package/dist/AdkThreadController.d.ts +15 -0
  10. package/dist/AdkThreadController.d.ts.map +1 -0
  11. package/dist/AdkThreadController.js +35 -0
  12. package/dist/AdkThreadController.js.map +1 -0
  13. package/dist/adkThreadState.d.ts +54 -0
  14. package/dist/adkThreadState.d.ts.map +1 -0
  15. package/dist/adkThreadState.js +93 -0
  16. package/dist/adkThreadState.js.map +1 -0
  17. package/dist/convertToAdkMessages.js +1 -1
  18. package/dist/convertToAdkMessages.js.map +1 -1
  19. package/dist/sdkIdentity.js +1 -1
  20. package/dist/server/createAdkApiRoute.d.ts +37 -6
  21. package/dist/server/createAdkApiRoute.d.ts.map +1 -1
  22. package/dist/server/createAdkApiRoute.js +55 -5
  23. package/dist/server/createAdkApiRoute.js.map +1 -1
  24. package/dist/server/parseAdkRequest.d.ts +4 -1
  25. package/dist/server/parseAdkRequest.d.ts.map +1 -1
  26. package/dist/server/parseAdkRequest.js +5 -1
  27. package/dist/server/parseAdkRequest.js.map +1 -1
  28. package/dist/useAdkMessages.d.ts +29 -4
  29. package/dist/useAdkMessages.d.ts.map +1 -1
  30. package/dist/useAdkMessages.js +62 -76
  31. package/dist/useAdkMessages.js.map +1 -1
  32. package/dist/useAdkRuntime.d.ts +7 -1
  33. package/dist/useAdkRuntime.d.ts.map +1 -1
  34. package/dist/useAdkRuntime.js +182 -54
  35. package/dist/useAdkRuntime.js.map +1 -1
  36. package/package.json +6 -5
  37. package/src/AdkClient.test.ts +78 -2
  38. package/src/AdkClient.ts +24 -6
  39. package/src/AdkSessionAdapter.ts +1 -1
  40. package/src/AdkThreadController.test.ts +90 -0
  41. package/src/AdkThreadController.ts +45 -0
  42. package/src/adkThreadState.test.ts +207 -0
  43. package/src/adkThreadState.ts +124 -0
  44. package/src/convertToAdkMessages.test.ts +19 -0
  45. package/src/convertToAdkMessages.ts +1 -1
  46. package/src/hooks.test.tsx +1 -0
  47. package/src/server/createAdkApiRoute.controls.test.ts +66 -0
  48. package/src/server/createAdkApiRoute.test.ts +282 -0
  49. package/src/server/createAdkApiRoute.ts +119 -11
  50. package/src/server/parseAdkRequest.test.ts +11 -3
  51. package/src/server/parseAdkRequest.ts +7 -1
  52. package/src/useAdkMessages.test.ts +43 -0
  53. package/src/useAdkMessages.ts +89 -96
  54. package/src/useAdkRuntime.cancellation.test.tsx +4 -3
  55. package/src/useAdkRuntime.cloud-options.test.tsx +59 -0
  56. package/src/useAdkRuntime.refetch.test.tsx +548 -4
  57. package/src/useAdkRuntime.replacement.test.tsx +718 -1
  58. package/src/useAdkRuntime.ts +253 -75
  59. package/src/useAdkRuntimeApproval.test.tsx +390 -35
  60. package/dist/raceWithAbortSignal.d.ts +0 -2
  61. package/dist/raceWithAbortSignal.d.ts.map +0 -1
  62. package/dist/raceWithAbortSignal.js +0 -45
  63. package/dist/raceWithAbortSignal.js.map +0 -1
  64. package/src/raceWithAbortSignal.test.ts +0 -73
  65. package/src/raceWithAbortSignal.ts +0 -48
@@ -4,6 +4,19 @@ import { type AdkEventStreamOptions } from "./adkEventStream.js";
4
4
  * Avoids requiring `@google/adk` as a dependency.
5
5
  */
6
6
  type AdkRunner = {
7
+ readonly appName?: string;
8
+ readonly sessionService?: {
9
+ getSession(options: {
10
+ appName: string;
11
+ userId: string;
12
+ sessionId: string;
13
+ }): Promise<unknown | undefined>;
14
+ createSession(options: {
15
+ appName: string;
16
+ userId: string;
17
+ sessionId: string;
18
+ }): Promise<unknown>;
19
+ };
7
20
  runAsync(options: Record<string, unknown>): AsyncGenerator<any, void, undefined>;
8
21
  };
9
22
  export type CreateAdkApiRouteOptions = {
@@ -12,15 +25,29 @@ export type CreateAdkApiRouteOptions = {
12
25
  */
13
26
  runner: AdkRunner;
14
27
  /**
15
- * User ID to use for the ADK session. Can be a static string
16
- * or a function that extracts it from the request.
28
+ * User ID to use for the ADK session. Production routes should resolve this
29
+ * from the authenticated request. A static value is suitable only for a
30
+ * single-user development route.
17
31
  */
18
32
  userId: string | ((req: Request) => string | Promise<string>);
19
33
  /**
20
34
  * Session ID to use. Can be a static string or a function
21
- * that extracts it from the request (e.g. from query params or headers).
35
+ * that validates or transforms the client thread ID sent by
36
+ * `createAdkStream`. The client value is an identifier, not authorization;
37
+ * scope access with an authenticated `userId`.
22
38
  */
23
- sessionId: string | ((req: Request) => string | Promise<string>);
39
+ sessionId: string | ((req: Request, clientSessionId: string | undefined) => string | Promise<string>);
40
+ /**
41
+ * Validates or replaces the client-provided ADK run configuration.
42
+ * Client values are ignored unless this resolver is provided.
43
+ */
44
+ resolveRunConfig?: ((req: Request, runConfig: unknown) => unknown | Promise<unknown>) | undefined;
45
+ /**
46
+ * Validates or replaces the client-provided ADK state delta.
47
+ * Client values are ignored unless this resolver is provided. In particular,
48
+ * `app:` and `user:` keys affect state beyond the current session.
49
+ */
50
+ resolveStateDelta?: ((req: Request, stateDelta: Record<string, unknown> | undefined) => Record<string, unknown> | undefined | Promise<Record<string, unknown> | undefined>) | undefined;
24
51
  /**
25
52
  * Error handler for stream errors.
26
53
  */
@@ -34,11 +61,15 @@ export type CreateAdkApiRouteOptions = {
34
61
  * ```ts
35
62
  * import { createAdkApiRoute } from '@assistant-ui/react-google-adk/server';
36
63
  * import { runner } from './agent';
64
+ * import { requireUser } from './auth';
37
65
  *
38
66
  * export const POST = createAdkApiRoute({
39
67
  * runner,
40
- * userId: "default-user",
41
- * sessionId: (req) => new URL(req.url).searchParams.get("sessionId") ?? "default",
68
+ * userId: async (req) => (await requireUser(req)).id,
69
+ * sessionId: (_req, clientSessionId) => {
70
+ * if (!clientSessionId) throw new Error("Missing ADK session ID");
71
+ * return clientSessionId;
72
+ * },
42
73
  * });
43
74
  * ```
44
75
  */
@@ -1 +1 @@
1
- {"version":3,"file":"createAdkApiRoute.d.ts","sourceRoot":"","sources":["../../src/server/createAdkApiRoute.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,qBAAqB,EAAE,4BAAyB;AAE9E;;;GAGG;AACH,KAAK,SAAS,GAAG;IACf,QAAQ,CACN,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC;;OAEG;IACH,MAAM,EAAE,SAAS,CAAC;IAElB;;;OAGG;IACH,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAE9D;;;OAGG;IACH,SAAS,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAEjE;;OAEG;IACH,OAAO,CAAC,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAC;CAC5C,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,wBAAwB,GAChC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA8BrC"}
1
+ {"version":3,"file":"createAdkApiRoute.d.ts","sourceRoot":"","sources":["../../src/server/createAdkApiRoute.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,qBAAqB,EAAE,4BAAyB;AAE9E;;;GAGG;AACH,KAAK,SAAS,GAAG;IACf,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,CAAC,EAAE;QACxB,UAAU,CAAC,OAAO,EAAE;YAClB,OAAO,EAAE,MAAM,CAAC;YAChB,MAAM,EAAE,MAAM,CAAC;YACf,SAAS,EAAE,MAAM,CAAC;SACnB,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;QACjC,aAAa,CAAC,OAAO,EAAE;YACrB,OAAO,EAAE,MAAM,CAAC;YAChB,MAAM,EAAE,MAAM,CAAC;YACf,SAAS,EAAE,MAAM,CAAC;SACnB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KACtB,CAAC;IACF,QAAQ,CACN,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;CACzC,CAAC;AAuDF,MAAM,MAAM,wBAAwB,GAAG;IACrC;;OAEG;IACH,MAAM,EAAE,SAAS,CAAC;IAElB;;;;OAIG;IACH,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAE9D;;;;;OAKG;IACH,SAAS,EACL,MAAM,GACN,CAAC,CACC,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,MAAM,GAAG,SAAS,KAChC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAEnC;;;OAGG;IACH,gBAAgB,CAAC,EACb,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,GAClE,SAAS,CAAC;IAEd;;;;OAIG;IACH,iBAAiB,CAAC,EACd,CAAC,CACC,GAAG,EAAE,OAAO,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,KAE7C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACvB,SAAS,GACT,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,GACjD,SAAS,CAAC;IAEd;;OAEG;IACH,OAAO,CAAC,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAC;CAC5C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,wBAAwB,GAChC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAqCrC"}
@@ -1,6 +1,49 @@
1
1
  import { adkEventStream } from "./adkEventStream.js";
2
2
  import { parseAdkRequest, toAdkContent } from "./parseAdkRequest.js";
3
3
  //#region src/server/createAdkApiRoute.ts
4
+ const pendingSessions = /* @__PURE__ */ new WeakMap();
5
+ const ensureRunnerSession = async (runner, userId, sessionId) => {
6
+ const { appName, sessionService } = runner;
7
+ if (!appName || !sessionService) return;
8
+ let serviceSessions = pendingSessions.get(sessionService);
9
+ if (!serviceSessions) {
10
+ serviceSessions = /* @__PURE__ */ new Map();
11
+ pendingSessions.set(sessionService, serviceSessions);
12
+ }
13
+ const key = JSON.stringify([
14
+ appName,
15
+ userId,
16
+ sessionId
17
+ ]);
18
+ let pending = serviceSessions.get(key);
19
+ if (!pending) {
20
+ pending = (async () => {
21
+ if (!await sessionService.getSession({
22
+ appName,
23
+ userId,
24
+ sessionId
25
+ })) try {
26
+ await sessionService.createSession({
27
+ appName,
28
+ userId,
29
+ sessionId
30
+ });
31
+ } catch (error) {
32
+ if (!await sessionService.getSession({
33
+ appName,
34
+ userId,
35
+ sessionId
36
+ })) throw error;
37
+ }
38
+ })();
39
+ serviceSessions.set(key, pending);
40
+ }
41
+ try {
42
+ await pending;
43
+ } finally {
44
+ if (serviceSessions.get(key) === pending) serviceSessions.delete(key);
45
+ }
46
+ };
4
47
  /**
5
48
  * Creates a request handler that combines `parseAdkRequest`, `toAdkContent`,
6
49
  * and `adkEventStream` into a single function.
@@ -9,11 +52,15 @@ import { parseAdkRequest, toAdkContent } from "./parseAdkRequest.js";
9
52
  * ```ts
10
53
  * import { createAdkApiRoute } from '@assistant-ui/react-google-adk/server';
11
54
  * import { runner } from './agent';
55
+ * import { requireUser } from './auth';
12
56
  *
13
57
  * export const POST = createAdkApiRoute({
14
58
  * runner,
15
- * userId: "default-user",
16
- * sessionId: (req) => new URL(req.url).searchParams.get("sessionId") ?? "default",
59
+ * userId: async (req) => (await requireUser(req)).id,
60
+ * sessionId: (_req, clientSessionId) => {
61
+ * if (!clientSessionId) throw new Error("Missing ADK session ID");
62
+ * return clientSessionId;
63
+ * },
17
64
  * });
18
65
  * ```
19
66
  */
@@ -22,13 +69,16 @@ function createAdkApiRoute(options) {
22
69
  const parsed = await parseAdkRequest(req);
23
70
  const newMessage = toAdkContent(parsed);
24
71
  const userId = typeof options.userId === "function" ? await options.userId(req) : options.userId;
25
- const sessionId = typeof options.sessionId === "function" ? await options.sessionId(req) : options.sessionId;
72
+ const sessionId = typeof options.sessionId === "function" ? await options.sessionId(req, parsed.sessionId) : options.sessionId;
73
+ const runConfig = options.resolveRunConfig ? await options.resolveRunConfig(req, parsed.config.runConfig) : void 0;
74
+ const stateDelta = options.resolveStateDelta ? await options.resolveStateDelta(req, parsed.stateDelta) : void 0;
75
+ await ensureRunnerSession(options.runner, userId, sessionId);
26
76
  const events = options.runner.runAsync({
27
77
  userId,
28
78
  sessionId,
29
79
  newMessage,
30
- ...parsed.stateDelta != null && { stateDelta: parsed.stateDelta },
31
- ...parsed.config.runConfig != null && { runConfig: parsed.config.runConfig }
80
+ ...stateDelta != null && { stateDelta },
81
+ ...runConfig != null && { runConfig }
32
82
  });
33
83
  return adkEventStream(events, options.onError ? { onError: options.onError } : void 0);
34
84
  };
@@ -1 +1 @@
1
- {"version":3,"file":"createAdkApiRoute.js","names":[],"sources":["../../src/server/createAdkApiRoute.ts"],"sourcesContent":["import { parseAdkRequest, toAdkContent } from \"./parseAdkRequest\";\nimport { adkEventStream, type AdkEventStreamOptions } from \"./adkEventStream\";\n\n/**\n * Loose runner type matching the ADK SDK's Runner interface.\n * Avoids requiring `@google/adk` as a dependency.\n */\ntype AdkRunner = {\n runAsync(\n options: Record<string, unknown>,\n ): AsyncGenerator<any, void, undefined>;\n};\n\nexport type CreateAdkApiRouteOptions = {\n /**\n * ADK Runner instance.\n */\n runner: AdkRunner;\n\n /**\n * User ID to use for the ADK session. Can be a static string\n * or a function that extracts it from the request.\n */\n userId: string | ((req: Request) => string | Promise<string>);\n\n /**\n * Session ID to use. Can be a static string or a function\n * that extracts it from the request (e.g. from query params or headers).\n */\n sessionId: string | ((req: Request) => string | Promise<string>);\n\n /**\n * Error handler for stream errors.\n */\n onError?: AdkEventStreamOptions[\"onError\"];\n};\n\n/**\n * Creates a request handler that combines `parseAdkRequest`, `toAdkContent`,\n * and `adkEventStream` into a single function.\n *\n * @example Next.js App Router\n * ```ts\n * import { createAdkApiRoute } from '@assistant-ui/react-google-adk/server';\n * import { runner } from './agent';\n *\n * export const POST = createAdkApiRoute({\n * runner,\n * userId: \"default-user\",\n * sessionId: (req) => new URL(req.url).searchParams.get(\"sessionId\") ?? \"default\",\n * });\n * ```\n */\nexport function createAdkApiRoute(\n options: CreateAdkApiRouteOptions,\n): (req: Request) => Promise<Response> {\n return async (req: Request): Promise<Response> => {\n const parsed = await parseAdkRequest(req);\n const newMessage = toAdkContent(parsed);\n\n const userId =\n typeof options.userId === \"function\"\n ? await options.userId(req)\n : options.userId;\n\n const sessionId =\n typeof options.sessionId === \"function\"\n ? await options.sessionId(req)\n : options.sessionId;\n\n const events = options.runner.runAsync({\n userId,\n sessionId,\n newMessage,\n ...(parsed.stateDelta != null && { stateDelta: parsed.stateDelta }),\n ...(parsed.config.runConfig != null && {\n runConfig: parsed.config.runConfig,\n }),\n });\n\n return adkEventStream(\n events,\n options.onError ? { onError: options.onError } : undefined,\n );\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqDA,SAAgB,kBACd,SACqC;CACrC,OAAO,OAAO,QAAoC;EAChD,MAAM,SAAS,MAAM,gBAAgB,GAAG;EACxC,MAAM,aAAa,aAAa,MAAM;EAEtC,MAAM,SACJ,OAAO,QAAQ,WAAW,aACtB,MAAM,QAAQ,OAAO,GAAG,IACxB,QAAQ;EAEd,MAAM,YACJ,OAAO,QAAQ,cAAc,aACzB,MAAM,QAAQ,UAAU,GAAG,IAC3B,QAAQ;EAEd,MAAM,SAAS,QAAQ,OAAO,SAAS;GACrC;GACA;GACA;GACA,GAAI,OAAO,cAAc,QAAQ,EAAE,YAAY,OAAO,WAAW;GACjE,GAAI,OAAO,OAAO,aAAa,QAAQ,EACrC,WAAW,OAAO,OAAO,UAC3B;EACF,CAAC;EAED,OAAO,eACL,QACA,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,KAAA,CACnD;CACF;AACF"}
1
+ {"version":3,"file":"createAdkApiRoute.js","names":[],"sources":["../../src/server/createAdkApiRoute.ts"],"sourcesContent":["import { parseAdkRequest, toAdkContent } from \"./parseAdkRequest\";\nimport { adkEventStream, type AdkEventStreamOptions } from \"./adkEventStream\";\n\n/**\n * Loose runner type matching the ADK SDK's Runner interface.\n * Avoids requiring `@google/adk` as a dependency.\n */\ntype AdkRunner = {\n readonly appName?: string;\n readonly sessionService?: {\n getSession(options: {\n appName: string;\n userId: string;\n sessionId: string;\n }): Promise<unknown | undefined>;\n createSession(options: {\n appName: string;\n userId: string;\n sessionId: string;\n }): Promise<unknown>;\n };\n runAsync(\n options: Record<string, unknown>,\n ): AsyncGenerator<any, void, undefined>;\n};\n\ntype AdkSessionService = NonNullable<AdkRunner[\"sessionService\"]>;\n\nconst pendingSessions = new WeakMap<\n AdkSessionService,\n Map<string, Promise<void>>\n>();\n\nconst ensureRunnerSession = async (\n runner: AdkRunner,\n userId: string,\n sessionId: string,\n) => {\n const { appName, sessionService } = runner;\n if (!appName || !sessionService) return;\n\n let serviceSessions = pendingSessions.get(sessionService);\n if (!serviceSessions) {\n serviceSessions = new Map();\n pendingSessions.set(sessionService, serviceSessions);\n }\n\n const key = JSON.stringify([appName, userId, sessionId]);\n let pending = serviceSessions.get(key);\n if (!pending) {\n pending = (async () => {\n const session = await sessionService.getSession({\n appName,\n userId,\n sessionId,\n });\n if (!session) {\n try {\n await sessionService.createSession({ appName, userId, sessionId });\n } catch (error) {\n const existing = await sessionService.getSession({\n appName,\n userId,\n sessionId,\n });\n if (!existing) throw error;\n }\n }\n })();\n serviceSessions.set(key, pending);\n }\n\n try {\n await pending;\n } finally {\n if (serviceSessions.get(key) === pending) serviceSessions.delete(key);\n }\n};\n\nexport type CreateAdkApiRouteOptions = {\n /**\n * ADK Runner instance.\n */\n runner: AdkRunner;\n\n /**\n * User ID to use for the ADK session. Production routes should resolve this\n * from the authenticated request. A static value is suitable only for a\n * single-user development route.\n */\n userId: string | ((req: Request) => string | Promise<string>);\n\n /**\n * Session ID to use. Can be a static string or a function\n * that validates or transforms the client thread ID sent by\n * `createAdkStream`. The client value is an identifier, not authorization;\n * scope access with an authenticated `userId`.\n */\n sessionId:\n | string\n | ((\n req: Request,\n clientSessionId: string | undefined,\n ) => string | Promise<string>);\n\n /**\n * Validates or replaces the client-provided ADK run configuration.\n * Client values are ignored unless this resolver is provided.\n */\n resolveRunConfig?:\n | ((req: Request, runConfig: unknown) => unknown | Promise<unknown>)\n | undefined;\n\n /**\n * Validates or replaces the client-provided ADK state delta.\n * Client values are ignored unless this resolver is provided. In particular,\n * `app:` and `user:` keys affect state beyond the current session.\n */\n resolveStateDelta?:\n | ((\n req: Request,\n stateDelta: Record<string, unknown> | undefined,\n ) =>\n | Record<string, unknown>\n | undefined\n | Promise<Record<string, unknown> | undefined>)\n | undefined;\n\n /**\n * Error handler for stream errors.\n */\n onError?: AdkEventStreamOptions[\"onError\"];\n};\n\n/**\n * Creates a request handler that combines `parseAdkRequest`, `toAdkContent`,\n * and `adkEventStream` into a single function.\n *\n * @example Next.js App Router\n * ```ts\n * import { createAdkApiRoute } from '@assistant-ui/react-google-adk/server';\n * import { runner } from './agent';\n * import { requireUser } from './auth';\n *\n * export const POST = createAdkApiRoute({\n * runner,\n * userId: async (req) => (await requireUser(req)).id,\n * sessionId: (_req, clientSessionId) => {\n * if (!clientSessionId) throw new Error(\"Missing ADK session ID\");\n * return clientSessionId;\n * },\n * });\n * ```\n */\nexport function createAdkApiRoute(\n options: CreateAdkApiRouteOptions,\n): (req: Request) => Promise<Response> {\n return async (req: Request): Promise<Response> => {\n const parsed = await parseAdkRequest(req);\n const newMessage = toAdkContent(parsed);\n\n const userId =\n typeof options.userId === \"function\"\n ? await options.userId(req)\n : options.userId;\n\n const sessionId =\n typeof options.sessionId === \"function\"\n ? await options.sessionId(req, parsed.sessionId)\n : options.sessionId;\n\n const runConfig = options.resolveRunConfig\n ? await options.resolveRunConfig(req, parsed.config.runConfig)\n : undefined;\n const stateDelta = options.resolveStateDelta\n ? await options.resolveStateDelta(req, parsed.stateDelta)\n : undefined;\n\n await ensureRunnerSession(options.runner, userId, sessionId);\n\n const events = options.runner.runAsync({\n userId,\n sessionId,\n newMessage,\n ...(stateDelta != null && { stateDelta }),\n ...(runConfig != null && { runConfig }),\n });\n\n return adkEventStream(\n events,\n options.onError ? { onError: options.onError } : undefined,\n );\n };\n}\n"],"mappings":";;;AA4BA,MAAM,kCAAkB,IAAI,QAG1B;AAEF,MAAM,sBAAsB,OAC1B,QACA,QACA,cACG;CACH,MAAM,EAAE,SAAS,mBAAmB;CACpC,IAAI,CAAC,WAAW,CAAC,gBAAgB;CAEjC,IAAI,kBAAkB,gBAAgB,IAAI,cAAc;CACxD,IAAI,CAAC,iBAAiB;EACpB,kCAAkB,IAAI,IAAI;EAC1B,gBAAgB,IAAI,gBAAgB,eAAe;CACrD;CAEA,MAAM,MAAM,KAAK,UAAU;EAAC;EAAS;EAAQ;CAAS,CAAC;CACvD,IAAI,UAAU,gBAAgB,IAAI,GAAG;CACrC,IAAI,CAAC,SAAS;EACZ,WAAW,YAAY;GAMrB,IAAI,CAAC,MALiB,eAAe,WAAW;IAC9C;IACA;IACA;GACF,CAAC,GAEC,IAAI;IACF,MAAM,eAAe,cAAc;KAAE;KAAS;KAAQ;IAAU,CAAC;GACnE,SAAS,OAAO;IAMd,IAAI,CAAC,MALkB,eAAe,WAAW;KAC/C;KACA;KACA;IACF,CAAC,GACc,MAAM;GACvB;EAEJ,EAAA,CAAG;EACH,gBAAgB,IAAI,KAAK,OAAO;CAClC;CAEA,IAAI;EACF,MAAM;CACR,UAAU;EACR,IAAI,gBAAgB,IAAI,GAAG,MAAM,SAAS,gBAAgB,OAAO,GAAG;CACtE;AACF;;;;;;;;;;;;;;;;;;;;;AA6EA,SAAgB,kBACd,SACqC;CACrC,OAAO,OAAO,QAAoC;EAChD,MAAM,SAAS,MAAM,gBAAgB,GAAG;EACxC,MAAM,aAAa,aAAa,MAAM;EAEtC,MAAM,SACJ,OAAO,QAAQ,WAAW,aACtB,MAAM,QAAQ,OAAO,GAAG,IACxB,QAAQ;EAEd,MAAM,YACJ,OAAO,QAAQ,cAAc,aACzB,MAAM,QAAQ,UAAU,KAAK,OAAO,SAAS,IAC7C,QAAQ;EAEd,MAAM,YAAY,QAAQ,mBACtB,MAAM,QAAQ,iBAAiB,KAAK,OAAO,OAAO,SAAS,IAC3D,KAAA;EACJ,MAAM,aAAa,QAAQ,oBACvB,MAAM,QAAQ,kBAAkB,KAAK,OAAO,UAAU,IACtD,KAAA;EAEJ,MAAM,oBAAoB,QAAQ,QAAQ,QAAQ,SAAS;EAE3D,MAAM,SAAS,QAAQ,OAAO,SAAS;GACrC;GACA;GACA;GACA,GAAI,cAAc,QAAQ,EAAE,WAAW;GACvC,GAAI,aAAa,QAAQ,EAAE,UAAU;EACvC,CAAC;EAED,OAAO,eACL,QACA,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,KAAA,CACnD;CACF;AACF"}
@@ -3,6 +3,7 @@ type ParsedAdkRequest = {
3
3
  type: "message";
4
4
  text: string;
5
5
  parts?: Array<Record<string, unknown>> | undefined;
6
+ sessionId?: string | undefined;
6
7
  config: AdkSendMessageConfig;
7
8
  stateDelta?: Record<string, unknown> | undefined;
8
9
  } | {
@@ -11,6 +12,7 @@ type ParsedAdkRequest = {
11
12
  toolName: string;
12
13
  result: unknown;
13
14
  isError: boolean;
15
+ sessionId?: string | undefined;
14
16
  config: AdkSendMessageConfig;
15
17
  stateDelta?: Record<string, unknown> | undefined;
16
18
  };
@@ -44,7 +46,8 @@ export declare const parseAdkRequest: (request: Request) => Promise<ParsedAdkReq
44
46
  * ```ts
45
47
  * const parsed = await parseAdkRequest(req);
46
48
  * const newMessage = toAdkContent(parsed);
47
- * const events = runner.runAsync({ userId, sessionId, newMessage, stateDelta: parsed.stateDelta });
49
+ * const stateDelta = validateSessionState(parsed.stateDelta);
50
+ * const events = runner.runAsync({ userId, sessionId, newMessage, stateDelta });
48
51
  * return adkEventStream(events);
49
52
  * ```
50
53
  */
@@ -1 +1 @@
1
- {"version":3,"file":"parseAdkRequest.d.ts","sourceRoot":"","sources":["../../src/server/parseAdkRequest.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,oBAAiB;AAGrD,KAAK,gBAAgB,GACjB;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IACnD,MAAM,EAAE,oBAAoB,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CAClD,GACD;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,oBAAoB,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CAClD,CAAC;AAuGN;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,OAAO,KACf,OAAO,CAAC,gBAAgB,CAwE1B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,GACvB,QAAQ,gBAAgB,KACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAsBvD,CAAC"}
1
+ {"version":3,"file":"parseAdkRequest.d.ts","sourceRoot":"","sources":["../../src/server/parseAdkRequest.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,oBAAiB;AAGrD,KAAK,gBAAgB,GACjB;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IACnD,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,MAAM,EAAE,oBAAoB,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CAClD,GACD;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,MAAM,EAAE,oBAAoB,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CAClD,CAAC;AAuGN;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,OAAO,KACf,OAAO,CAAC,gBAAgB,CA2E1B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GACvB,QAAQ,gBAAgB,KACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAsBvD,CAAC"}
@@ -86,6 +86,7 @@ const parseAdkRequest = async (request) => {
86
86
  if (body.runConfig !== void 0) config.runConfig = body.runConfig;
87
87
  const checkpointId = readOptionalString(body, "checkpointId");
88
88
  if (checkpointId !== void 0) config.checkpointId = checkpointId;
89
+ const sessionId = readOptionalString(body, "sessionId");
89
90
  const stateDelta = body.stateDelta;
90
91
  if (stateDelta !== void 0 && !isRecord(stateDelta)) throw invalidField("stateDelta", "an object");
91
92
  if (body.type === "tool-result") {
@@ -97,6 +98,7 @@ const parseAdkRequest = async (request) => {
97
98
  toolName: readString(body, "toolName"),
98
99
  result: body.result,
99
100
  isError: body.isError ?? false,
101
+ ...sessionId !== void 0 && { sessionId },
100
102
  config,
101
103
  ...stateDelta != null && { stateDelta }
102
104
  };
@@ -112,6 +114,7 @@ const parseAdkRequest = async (request) => {
112
114
  type: "message",
113
115
  text: text ?? "",
114
116
  ...parts !== void 0 && { parts },
117
+ ...sessionId !== void 0 && { sessionId },
115
118
  config,
116
119
  ...stateDelta != null && { stateDelta }
117
120
  };
@@ -124,7 +127,8 @@ const parseAdkRequest = async (request) => {
124
127
  * ```ts
125
128
  * const parsed = await parseAdkRequest(req);
126
129
  * const newMessage = toAdkContent(parsed);
127
- * const events = runner.runAsync({ userId, sessionId, newMessage, stateDelta: parsed.stateDelta });
130
+ * const stateDelta = validateSessionState(parsed.stateDelta);
131
+ * const events = runner.runAsync({ userId, sessionId, newMessage, stateDelta });
128
132
  * return adkEventStream(events);
129
133
  * ```
130
134
  */
@@ -1 +1 @@
1
- {"version":3,"file":"parseAdkRequest.js","names":[],"sources":["../../src/server/parseAdkRequest.ts"],"sourcesContent":["import { isRecord } from \"@assistant-ui/core/internal\";\nimport type { AdkSendMessageConfig } from \"../types\";\nimport { toAdkFunctionResponse } from \"../toAdkFunctionResponse\";\n\ntype ParsedAdkRequest =\n | {\n type: \"message\";\n text: string;\n parts?: Array<Record<string, unknown>> | undefined;\n config: AdkSendMessageConfig;\n stateDelta?: Record<string, unknown> | undefined;\n }\n | {\n type: \"tool-result\";\n toolCallId: string;\n toolName: string;\n result: unknown;\n isError: boolean;\n config: AdkSendMessageConfig;\n stateDelta?: Record<string, unknown> | undefined;\n };\n\nconst invalidField = (field: string, expectation: string): Error =>\n new Error(\n `Invalid Google ADK proxy request field \"${field}\": expected ${expectation}.`,\n );\n\nconst readString = (body: Record<string, unknown>, field: string): string => {\n const value = body[field];\n if (typeof value !== \"string\") throw invalidField(field, \"a string\");\n return value;\n};\n\nconst readOptionalString = (\n body: Record<string, unknown>,\n field: string,\n): string | undefined => {\n const value = body[field];\n if (value === undefined) return undefined;\n if (typeof value !== \"string\") throw invalidField(field, \"a string\");\n return value;\n};\n\nconst validateOptionalPartString = (\n part: Record<string, unknown>,\n key: string,\n field: string,\n) => {\n if (part[key] !== undefined && typeof part[key] !== \"string\") {\n throw invalidField(field, \"a string\");\n }\n};\n\n// Nested fields are validated only when present: ADK serializes with\n// exclude_none semantics and the Part vocabulary grows upstream, so absent\n// fields and unknown part kinds pass through to the runner untouched.\nconst validatePart = (part: Record<string, unknown>, index: number) => {\n const field = (name: string) => `parts[${index}].${name}`;\n if (Object.keys(part).length === 0) {\n throw invalidField(`parts[${index}]`, \"a non-empty part object\");\n }\n if (part.thought !== undefined && typeof part.thought !== \"boolean\") {\n throw invalidField(field(\"thought\"), \"a boolean\");\n }\n if (part.text !== undefined && typeof part.text !== \"string\") {\n throw invalidField(field(\"text\"), \"a string\");\n }\n\n const validateRecordField = (\n key: string,\n validateContent: (content: Record<string, unknown>) => void,\n ) => {\n const value = part[key];\n if (value === undefined) return;\n if (!isRecord(value)) throw invalidField(field(key), \"an object\");\n validateContent(value);\n };\n\n validateRecordField(\"functionCall\", (content) => {\n validateOptionalPartString(content, \"name\", field(\"functionCall.name\"));\n validateOptionalPartString(content, \"id\", field(\"functionCall.id\"));\n if (content.args !== undefined && !isRecord(content.args)) {\n throw invalidField(field(\"functionCall.args\"), \"an object\");\n }\n });\n validateRecordField(\"functionResponse\", (content) => {\n validateOptionalPartString(content, \"name\", field(\"functionResponse.name\"));\n validateOptionalPartString(content, \"id\", field(\"functionResponse.id\"));\n });\n validateRecordField(\"executableCode\", (content) => {\n validateOptionalPartString(content, \"code\", field(\"executableCode.code\"));\n validateOptionalPartString(\n content,\n \"language\",\n field(\"executableCode.language\"),\n );\n });\n validateRecordField(\"codeExecutionResult\", (content) => {\n validateOptionalPartString(\n content,\n \"output\",\n field(\"codeExecutionResult.output\"),\n );\n validateOptionalPartString(\n content,\n \"outcome\",\n field(\"codeExecutionResult.outcome\"),\n );\n });\n validateRecordField(\"inlineData\", (content) => {\n validateOptionalPartString(\n content,\n \"mimeType\",\n field(\"inlineData.mimeType\"),\n );\n validateOptionalPartString(content, \"data\", field(\"inlineData.data\"));\n });\n validateRecordField(\"fileData\", (content) => {\n validateOptionalPartString(content, \"fileUri\", field(\"fileData.fileUri\"));\n validateOptionalPartString(content, \"mimeType\", field(\"fileData.mimeType\"));\n });\n};\n\n/**\n * Parses an incoming HTTP request into a structured ADK request.\n *\n * Supports two request shapes:\n *\n * 1. User message:\n * ```json\n * { \"message\": \"Hello\", \"runConfig\": {}, \"stateDelta\": {} }\n * ```\n *\n * 2. Tool result:\n * ```json\n * {\n * \"type\": \"tool-result\",\n * \"toolCallId\": \"call_123\",\n * \"toolName\": \"search\",\n * \"result\": { ... },\n * \"isError\": false\n * }\n * ```\n */\nexport const parseAdkRequest = async (\n request: Request,\n): Promise<ParsedAdkRequest> => {\n let body: unknown;\n try {\n body = await request.json();\n } catch {\n throw new Error(\n 'Invalid JSON in Google ADK proxy request body. Expected a JSON object like {\"message\":\"Hello\"} or {\"type\":\"tool-result\",...}.',\n );\n }\n\n if (!isRecord(body)) {\n throw new Error(\"Google ADK proxy request body must be a JSON object\");\n }\n\n const config: AdkSendMessageConfig = {};\n if (body.runConfig !== undefined) config.runConfig = body.runConfig;\n const checkpointId = readOptionalString(body, \"checkpointId\");\n if (checkpointId !== undefined) config.checkpointId = checkpointId;\n\n const stateDelta = body.stateDelta;\n if (stateDelta !== undefined && !isRecord(stateDelta)) {\n throw invalidField(\"stateDelta\", \"an object\");\n }\n\n if (body.type === \"tool-result\") {\n if (!(\"result\" in body)) {\n throw invalidField(\"result\", \"a value\");\n }\n if (body.isError !== undefined && typeof body.isError !== \"boolean\") {\n throw invalidField(\"isError\", \"a boolean\");\n }\n return {\n type: \"tool-result\",\n toolCallId: readString(body, \"toolCallId\"),\n toolName: readString(body, \"toolName\"),\n result: body.result,\n isError: body.isError ?? false,\n config,\n ...(stateDelta != null && { stateDelta }),\n };\n }\n\n if (body.type !== undefined && body.type !== \"message\") {\n throw invalidField(\"type\", '\"message\", \"tool-result\", or omitted');\n }\n\n const text = body.message;\n if (text !== undefined && typeof text !== \"string\") {\n throw invalidField(\"message\", \"a string\");\n }\n\n const parts = body.parts;\n if (\n parts !== undefined &&\n (!Array.isArray(parts) || !parts.every(isRecord))\n ) {\n throw invalidField(\"parts\", \"an array of objects\");\n }\n parts?.forEach(validatePart);\n if (!(\"message\" in body) && !(\"parts\" in body)) {\n throw new Error(\n 'Invalid Google ADK proxy request: expected a \"message\" string or a \"parts\" array.',\n );\n }\n\n return {\n type: \"message\",\n text: text ?? \"\",\n ...(parts !== undefined && { parts }),\n config,\n ...(stateDelta != null && { stateDelta }),\n };\n};\n\n/**\n * Converts a parsed ADK request into a Google GenAI Content object\n * suitable for `Runner.runAsync({ newMessage })`.\n *\n * @example\n * ```ts\n * const parsed = await parseAdkRequest(req);\n * const newMessage = toAdkContent(parsed);\n * const events = runner.runAsync({ userId, sessionId, newMessage, stateDelta: parsed.stateDelta });\n * return adkEventStream(events);\n * ```\n */\nexport const toAdkContent = (\n parsed: ParsedAdkRequest,\n): { role: string; parts: Array<Record<string, unknown>> } => {\n if (parsed.type === \"tool-result\") {\n return {\n role: \"user\",\n parts: [\n {\n functionResponse: {\n name: parsed.toolName,\n id: parsed.toolCallId,\n response: toAdkFunctionResponse(parsed.result, parsed.isError),\n },\n },\n ],\n };\n }\n\n // If raw parts are provided (multimodal), use them directly\n if (parsed.parts?.length) {\n return { role: \"user\", parts: parsed.parts };\n }\n\n return { role: \"user\", parts: [{ text: parsed.text }] };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"parseAdkRequest.js","names":[],"sources":["../../src/server/parseAdkRequest.ts"],"sourcesContent":["import { isRecord } from \"@assistant-ui/core/internal\";\nimport type { AdkSendMessageConfig } from \"../types\";\nimport { toAdkFunctionResponse } from \"../toAdkFunctionResponse\";\n\ntype ParsedAdkRequest =\n | {\n type: \"message\";\n text: string;\n parts?: Array<Record<string, unknown>> | undefined;\n sessionId?: string | undefined;\n config: AdkSendMessageConfig;\n stateDelta?: Record<string, unknown> | undefined;\n }\n | {\n type: \"tool-result\";\n toolCallId: string;\n toolName: string;\n result: unknown;\n isError: boolean;\n sessionId?: string | undefined;\n config: AdkSendMessageConfig;\n stateDelta?: Record<string, unknown> | undefined;\n };\n\nconst invalidField = (field: string, expectation: string): Error =>\n new Error(\n `Invalid Google ADK proxy request field \"${field}\": expected ${expectation}.`,\n );\n\nconst readString = (body: Record<string, unknown>, field: string): string => {\n const value = body[field];\n if (typeof value !== \"string\") throw invalidField(field, \"a string\");\n return value;\n};\n\nconst readOptionalString = (\n body: Record<string, unknown>,\n field: string,\n): string | undefined => {\n const value = body[field];\n if (value === undefined) return undefined;\n if (typeof value !== \"string\") throw invalidField(field, \"a string\");\n return value;\n};\n\nconst validateOptionalPartString = (\n part: Record<string, unknown>,\n key: string,\n field: string,\n) => {\n if (part[key] !== undefined && typeof part[key] !== \"string\") {\n throw invalidField(field, \"a string\");\n }\n};\n\n// Nested fields are validated only when present: ADK serializes with\n// exclude_none semantics and the Part vocabulary grows upstream, so absent\n// fields and unknown part kinds pass through to the runner untouched.\nconst validatePart = (part: Record<string, unknown>, index: number) => {\n const field = (name: string) => `parts[${index}].${name}`;\n if (Object.keys(part).length === 0) {\n throw invalidField(`parts[${index}]`, \"a non-empty part object\");\n }\n if (part.thought !== undefined && typeof part.thought !== \"boolean\") {\n throw invalidField(field(\"thought\"), \"a boolean\");\n }\n if (part.text !== undefined && typeof part.text !== \"string\") {\n throw invalidField(field(\"text\"), \"a string\");\n }\n\n const validateRecordField = (\n key: string,\n validateContent: (content: Record<string, unknown>) => void,\n ) => {\n const value = part[key];\n if (value === undefined) return;\n if (!isRecord(value)) throw invalidField(field(key), \"an object\");\n validateContent(value);\n };\n\n validateRecordField(\"functionCall\", (content) => {\n validateOptionalPartString(content, \"name\", field(\"functionCall.name\"));\n validateOptionalPartString(content, \"id\", field(\"functionCall.id\"));\n if (content.args !== undefined && !isRecord(content.args)) {\n throw invalidField(field(\"functionCall.args\"), \"an object\");\n }\n });\n validateRecordField(\"functionResponse\", (content) => {\n validateOptionalPartString(content, \"name\", field(\"functionResponse.name\"));\n validateOptionalPartString(content, \"id\", field(\"functionResponse.id\"));\n });\n validateRecordField(\"executableCode\", (content) => {\n validateOptionalPartString(content, \"code\", field(\"executableCode.code\"));\n validateOptionalPartString(\n content,\n \"language\",\n field(\"executableCode.language\"),\n );\n });\n validateRecordField(\"codeExecutionResult\", (content) => {\n validateOptionalPartString(\n content,\n \"output\",\n field(\"codeExecutionResult.output\"),\n );\n validateOptionalPartString(\n content,\n \"outcome\",\n field(\"codeExecutionResult.outcome\"),\n );\n });\n validateRecordField(\"inlineData\", (content) => {\n validateOptionalPartString(\n content,\n \"mimeType\",\n field(\"inlineData.mimeType\"),\n );\n validateOptionalPartString(content, \"data\", field(\"inlineData.data\"));\n });\n validateRecordField(\"fileData\", (content) => {\n validateOptionalPartString(content, \"fileUri\", field(\"fileData.fileUri\"));\n validateOptionalPartString(content, \"mimeType\", field(\"fileData.mimeType\"));\n });\n};\n\n/**\n * Parses an incoming HTTP request into a structured ADK request.\n *\n * Supports two request shapes:\n *\n * 1. User message:\n * ```json\n * { \"message\": \"Hello\", \"runConfig\": {}, \"stateDelta\": {} }\n * ```\n *\n * 2. Tool result:\n * ```json\n * {\n * \"type\": \"tool-result\",\n * \"toolCallId\": \"call_123\",\n * \"toolName\": \"search\",\n * \"result\": { ... },\n * \"isError\": false\n * }\n * ```\n */\nexport const parseAdkRequest = async (\n request: Request,\n): Promise<ParsedAdkRequest> => {\n let body: unknown;\n try {\n body = await request.json();\n } catch {\n throw new Error(\n 'Invalid JSON in Google ADK proxy request body. Expected a JSON object like {\"message\":\"Hello\"} or {\"type\":\"tool-result\",...}.',\n );\n }\n\n if (!isRecord(body)) {\n throw new Error(\"Google ADK proxy request body must be a JSON object\");\n }\n\n const config: AdkSendMessageConfig = {};\n if (body.runConfig !== undefined) config.runConfig = body.runConfig;\n const checkpointId = readOptionalString(body, \"checkpointId\");\n if (checkpointId !== undefined) config.checkpointId = checkpointId;\n const sessionId = readOptionalString(body, \"sessionId\");\n\n const stateDelta = body.stateDelta;\n if (stateDelta !== undefined && !isRecord(stateDelta)) {\n throw invalidField(\"stateDelta\", \"an object\");\n }\n\n if (body.type === \"tool-result\") {\n if (!(\"result\" in body)) {\n throw invalidField(\"result\", \"a value\");\n }\n if (body.isError !== undefined && typeof body.isError !== \"boolean\") {\n throw invalidField(\"isError\", \"a boolean\");\n }\n return {\n type: \"tool-result\",\n toolCallId: readString(body, \"toolCallId\"),\n toolName: readString(body, \"toolName\"),\n result: body.result,\n isError: body.isError ?? false,\n ...(sessionId !== undefined && { sessionId }),\n config,\n ...(stateDelta != null && { stateDelta }),\n };\n }\n\n if (body.type !== undefined && body.type !== \"message\") {\n throw invalidField(\"type\", '\"message\", \"tool-result\", or omitted');\n }\n\n const text = body.message;\n if (text !== undefined && typeof text !== \"string\") {\n throw invalidField(\"message\", \"a string\");\n }\n\n const parts = body.parts;\n if (\n parts !== undefined &&\n (!Array.isArray(parts) || !parts.every(isRecord))\n ) {\n throw invalidField(\"parts\", \"an array of objects\");\n }\n parts?.forEach(validatePart);\n if (!(\"message\" in body) && !(\"parts\" in body)) {\n throw new Error(\n 'Invalid Google ADK proxy request: expected a \"message\" string or a \"parts\" array.',\n );\n }\n\n return {\n type: \"message\",\n text: text ?? \"\",\n ...(parts !== undefined && { parts }),\n ...(sessionId !== undefined && { sessionId }),\n config,\n ...(stateDelta != null && { stateDelta }),\n };\n};\n\n/**\n * Converts a parsed ADK request into a Google GenAI Content object\n * suitable for `Runner.runAsync({ newMessage })`.\n *\n * @example\n * ```ts\n * const parsed = await parseAdkRequest(req);\n * const newMessage = toAdkContent(parsed);\n * const stateDelta = validateSessionState(parsed.stateDelta);\n * const events = runner.runAsync({ userId, sessionId, newMessage, stateDelta });\n * return adkEventStream(events);\n * ```\n */\nexport const toAdkContent = (\n parsed: ParsedAdkRequest,\n): { role: string; parts: Array<Record<string, unknown>> } => {\n if (parsed.type === \"tool-result\") {\n return {\n role: \"user\",\n parts: [\n {\n functionResponse: {\n name: parsed.toolName,\n id: parsed.toolCallId,\n response: toAdkFunctionResponse(parsed.result, parsed.isError),\n },\n },\n ],\n };\n }\n\n // If raw parts are provided (multimodal), use them directly\n if (parsed.parts?.length) {\n return { role: \"user\", parts: parsed.parts };\n }\n\n return { role: \"user\", parts: [{ text: parsed.text }] };\n};\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import type { AdkMessage, AdkMessageMetadata, AdkSendMessageConfig, AdkStreamCallback, AdkToolConfirmation, AdkAuthRequest, AdkThreadSnapshot, OnAdkErrorCallback, OnAdkCustomEventCallback, OnAdkAgentTransferCallback } from "./types.js";
1
+ import { AdkThreadController } from "./AdkThreadController.js";
2
+ import type { AdkMessage, AdkSendMessageConfig, AdkStreamCallback, AdkThreadSnapshot, OnAdkErrorCallback, OnAdkCustomEventCallback, OnAdkAgentTransferCallback } from "./types.js";
2
3
  export type UseAdkMessagesOptions = {
3
4
  stream: AdkStreamCallback;
4
5
  eventHandlers?: {
@@ -7,6 +8,29 @@ export type UseAdkMessagesOptions = {
7
8
  onAgentTransfer?: OnAdkAgentTransferCallback;
8
9
  };
9
10
  };
11
+ type UseAdkMessagesInternalOptions = UseAdkMessagesOptions & {
12
+ onMessages?: (messages: AdkMessage[], runConfig: unknown) => void;
13
+ };
14
+ declare const useAdkMessagesInternal: ({ stream, eventHandlers, onMessages, }: UseAdkMessagesInternalOptions) => {
15
+ controller: AdkThreadController;
16
+ messages: AdkMessage[];
17
+ stateDelta: Record<string, unknown>;
18
+ agentInfo: {
19
+ name?: string | undefined;
20
+ branch?: string | undefined;
21
+ };
22
+ longRunningToolIds: string[];
23
+ artifactDelta: Record<string, number>;
24
+ toolConfirmations: import("./types.js").AdkToolConfirmation[];
25
+ authRequests: import("./types.js").AdkAuthRequest[];
26
+ escalated: boolean;
27
+ messageMetadata: Map<string, import("./types.js").AdkMessageMetadata>;
28
+ sendMessage: (newMessages: AdkMessage[], config: AdkSendMessageConfig) => Promise<void>;
29
+ cancel: () => void;
30
+ setMessages: (msgs: AdkMessage[]) => void;
31
+ replaceMessages: (msgs: AdkMessage[]) => void;
32
+ applySnapshot: (snapshot: AdkThreadSnapshot) => void;
33
+ };
10
34
  export declare const useAdkMessages: ({ stream, eventHandlers, }: UseAdkMessagesOptions) => {
11
35
  messages: AdkMessage[];
12
36
  stateDelta: Record<string, unknown>;
@@ -16,14 +40,15 @@ export declare const useAdkMessages: ({ stream, eventHandlers, }: UseAdkMessages
16
40
  };
17
41
  longRunningToolIds: string[];
18
42
  artifactDelta: Record<string, number>;
19
- toolConfirmations: AdkToolConfirmation[];
20
- authRequests: AdkAuthRequest[];
43
+ toolConfirmations: import("./types.js").AdkToolConfirmation[];
44
+ authRequests: import("./types.js").AdkAuthRequest[];
21
45
  escalated: boolean;
22
- messageMetadata: Map<string, AdkMessageMetadata>;
46
+ messageMetadata: Map<string, import("./types.js").AdkMessageMetadata>;
23
47
  sendMessage: (newMessages: AdkMessage[], config: AdkSendMessageConfig) => Promise<void>;
24
48
  cancel: () => void;
25
49
  setMessages: (msgs: AdkMessage[]) => void;
26
50
  replaceMessages: (msgs: AdkMessage[]) => void;
27
51
  applySnapshot: (snapshot: AdkThreadSnapshot) => void;
28
52
  };
53
+ export { useAdkMessagesInternal };
29
54
  //# sourceMappingURL=useAdkMessages.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useAdkMessages.d.ts","sourceRoot":"","sources":["../src/useAdkMessages.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAEV,UAAU,EACV,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,0BAA0B,EAC3B,mBAAgB;AAEjB,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,aAAa,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,kBAAkB,CAAC;QAC7B,aAAa,CAAC,EAAE,wBAAwB,CAAC;QACzC,eAAe,CAAC,EAAE,0BAA0B,CAAC;KAC9C,CAAC;CACH,CAAC;AAYF,eAAO,MAAM,cAAc,GAAI,4BAG5B,qBAAqB;;;;eAIb,MAAM,GAAG,SAAS;iBAChB,MAAM,GAAG,SAAS;;;;;;;;+BAqFP,UAAU,EAAE,UAAU,oBAAoB;;wBAvDhB,UAAU,EAAE;4BAmCnD,UAAU,EAAE;8BAnBR,iBAAiB;CAuO/B,CAAC"}
1
+ {"version":3,"file":"useAdkMessages.d.ts","sourceRoot":"","sources":["../src/useAdkMessages.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,mBAAmB,EAAE,iCAA8B;AAG5D,OAAO,KAAK,EAEV,UAAU,EACV,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,0BAA0B,EAC3B,mBAAgB;AAEjB,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,aAAa,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,kBAAkB,CAAC;QAC7B,aAAa,CAAC,EAAE,wBAAwB,CAAC;QACzC,eAAe,CAAC,EAAE,0BAA0B,CAAC;KAC9C,CAAC;CACH,CAAC;AAEF,KAAK,6BAA6B,GAAG,qBAAqB,GAAG;IAC3D,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;CACnE,CAAC;AAYF,QAAA,MAAM,sBAAsB,GAAI,wCAI7B,6BAA6B;;;;;;;;;;;;;;+BAyDR,UAAU,EAAE,UAAU,oBAAoB;;wBAtCvD,UAAU,EAAE;4BAuBZ,UAAU,EAAE;8BAXR,iBAAiB;CAoO/B,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,4BAG5B,qBAAqB;;;;;;;;;;;;;+BA/MA,UAAU,EAAE,UAAU,oBAAoB;;wBAtCvD,UAAU,EAAE;4BAuBZ,UAAU,EAAE;8BAXR,iBAAiB;CA+O/B,CAAC;AAEF,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
@@ -1,47 +1,25 @@
1
1
  import { contentToParts } from "./contentToParts.js";
2
2
  import { toAdkFunctionResponse } from "./toAdkFunctionResponse.js";
3
3
  import { AdkEventAccumulator } from "./AdkEventAccumulator.js";
4
+ import { AdkThreadController } from "./AdkThreadController.js";
4
5
  import { abortableIterable, invokeUserCallback, isRecord, openAbortableIterable } from "@assistant-ui/core/internal";
5
6
  import { generateId } from "@assistant-ui/core";
6
7
  import { useAui } from "@assistant-ui/store";
7
8
  import { useReplaySafeEffect } from "@assistant-ui/store/internal";
8
- import { useCallback, useInsertionEffect, useMemo, useRef, useState } from "react";
9
+ import { useCallback, useMemo, useRef, useState, useSyncExternalStore } from "react";
9
10
  //#region src/useAdkMessages.ts
10
11
  const invokeAdkRuntimeCallback = (name, callback, ...args) => {
11
12
  invokeUserCallback("react-google-adk", name, callback, ...args);
12
13
  };
13
- const useAdkMessages = ({ stream, eventHandlers }) => {
14
- const [messages, _setMessages] = useState([]);
15
- const [stateDelta, setStateDelta] = useState({});
16
- const [agentInfo, setAgentInfo] = useState({});
17
- const [longRunningToolIds, _setLongRunningToolIds] = useState([]);
18
- const [artifactDelta, setArtifactDelta] = useState({});
19
- const [toolConfirmations, setToolConfirmations] = useState([]);
20
- const [authRequests, setAuthRequests] = useState([]);
21
- const [escalated, setEscalated] = useState(false);
22
- const [messageMetadata, setMessageMetadata] = useState(/* @__PURE__ */ new Map());
23
- const messagesRef = useRef(messages);
24
- const longRunningToolIdsRef = useRef(longRunningToolIds);
25
- const stateDeltaRef = useRef(stateDelta);
26
- useInsertionEffect(() => {
27
- stateDeltaRef.current = stateDelta;
28
- }, [stateDelta]);
29
- const artifactDeltaRef = useRef(artifactDelta);
30
- useInsertionEffect(() => {
31
- artifactDeltaRef.current = artifactDelta;
32
- }, [artifactDelta]);
33
- const messageMetadataRef = useRef(messageMetadata);
34
- useInsertionEffect(() => {
35
- messageMetadataRef.current = messageMetadata;
36
- }, [messageMetadata]);
14
+ const useAdkMessagesInternal = ({ stream, eventHandlers, onMessages }) => {
15
+ const [controller] = useState(() => new AdkThreadController());
16
+ const { messages, stateDelta, agentInfo, longRunningToolIds, artifactDelta, toolConfirmations, authRequests, escalated, messageMetadata } = useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);
37
17
  const setMessagesImmediate = useCallback((msgs) => {
38
- messagesRef.current = msgs;
39
- _setMessages(msgs);
40
- }, []);
41
- const setLongRunningToolIds = useCallback((ids) => {
42
- longRunningToolIdsRef.current = ids;
43
- _setLongRunningToolIds(ids);
44
- }, []);
18
+ controller.dispatch({
19
+ type: "messages.set",
20
+ messages: msgs
21
+ });
22
+ }, [controller]);
45
23
  /**
46
24
  * Swap the thread over to a loaded snapshot in one commit. Unlike
47
25
  * {@link replaceMessages} this never passes through a cleared state, so a
@@ -49,24 +27,17 @@ const useAdkMessages = ({ stream, eventHandlers }) => {
49
27
  * than blanking it first.
50
28
  */
51
29
  const applySnapshot = useCallback((snapshot) => {
52
- setMessagesImmediate(snapshot.messages);
53
- setLongRunningToolIds(snapshot.longRunningToolIds ?? []);
54
- setToolConfirmations(snapshot.toolConfirmations ?? []);
55
- setAuthRequests(snapshot.authRequests ?? []);
56
- setEscalated(snapshot.escalated ?? false);
57
- setMessageMetadata(snapshot.messageMetadata ?? /* @__PURE__ */ new Map());
58
- setStateDelta(snapshot.stateDelta ?? {});
59
- setArtifactDelta(snapshot.artifactDelta ?? {});
60
- setAgentInfo(snapshot.agentInfo ?? {});
61
- }, [setLongRunningToolIds, setMessagesImmediate]);
30
+ controller.dispatch({
31
+ type: "snapshot.applied",
32
+ snapshot
33
+ });
34
+ }, [controller]);
62
35
  const replaceMessages = useCallback((msgs) => {
63
- setMessagesImmediate(msgs);
64
- setLongRunningToolIds([]);
65
- setToolConfirmations([]);
66
- setAuthRequests([]);
67
- setEscalated(false);
68
- setMessageMetadata(/* @__PURE__ */ new Map());
69
- }, [setLongRunningToolIds, setMessagesImmediate]);
36
+ controller.dispatch({
37
+ type: "messages.replaced",
38
+ messages: msgs
39
+ });
40
+ }, [controller]);
70
41
  const abortControllerRef = useRef(null);
71
42
  const { onError, onCustomEvent, onAgentTransfer } = useMemo(() => eventHandlers ?? {}, [eventHandlers]);
72
43
  const aui = useAui();
@@ -77,15 +48,19 @@ const useAdkMessages = ({ stream, eventHandlers }) => {
77
48
  });
78
49
  const resentIds = new Set(newMessagesWithId.map((m) => m.id));
79
50
  const answeredToolCallIds = new Set(newMessagesWithId.flatMap((m) => m.type === "tool" ? [m.tool_call_id] : []));
80
- const accumulator = new AdkEventAccumulator(messagesRef.current.filter((m) => !resentIds.has(m.id)), longRunningToolIdsRef.current.filter((id) => !answeredToolCallIds.has(id)));
51
+ const currentState = controller.getState();
52
+ const accumulator = new AdkEventAccumulator(currentState.messages.filter((m) => !resentIds.has(m.id)), currentState.longRunningToolIds.filter((id) => !answeredToolCallIds.has(id)));
81
53
  for (const event of messagesToEvents(newMessagesWithId)) accumulator.processEvent(event);
82
54
  const initialMessages = accumulator.getMessages();
83
55
  const initialMessageIds = new Set(initialMessages.map((m) => m.id));
84
56
  const initialLongRunningToolIds = accumulator.getLongRunningToolIds();
85
- setMessagesImmediate(initialMessages);
86
- setLongRunningToolIds(initialLongRunningToolIds);
87
- setToolConfirmations(accumulator.getToolConfirmations());
88
- setAuthRequests(accumulator.getAuthRequests());
57
+ controller.dispatch({
58
+ type: "run.started",
59
+ messages: initialMessages,
60
+ longRunningToolIds: initialLongRunningToolIds,
61
+ toolConfirmations: accumulator.getToolConfirmations(),
62
+ authRequests: accumulator.getAuthRequests()
63
+ });
89
64
  let lastTransferToAgent;
90
65
  abortControllerRef.current?.abort();
91
66
  const abortController = new AbortController();
@@ -102,24 +77,23 @@ const useAdkMessages = ({ stream, eventHandlers }) => {
102
77
  for await (const event of abortableIterable(response, abortController.signal)) {
103
78
  if (abortController.signal.aborted || abortControllerRef.current !== abortController) break;
104
79
  const updatedMessages = accumulator.processEvent(event);
105
- setMessagesImmediate(updatedMessages);
106
- setStateDelta({
107
- ...stateDeltaRef.current,
108
- ...accumulator.getStateDelta()
80
+ const affectedMessageCount = Math.max(event.content?.parts?.length ?? 0, 1);
81
+ const affectedMessages = updatedMessages.slice(-affectedMessageCount);
82
+ if (affectedMessages.length > 0) onMessages?.(affectedMessages, config.runConfig);
83
+ controller.dispatch({
84
+ type: "event.published",
85
+ state: {
86
+ messages: updatedMessages,
87
+ stateDelta: accumulator.getStateDelta(),
88
+ agentInfo: accumulator.getAgentInfo(),
89
+ longRunningToolIds: accumulator.getLongRunningToolIds(),
90
+ artifactDelta: accumulator.getArtifactDelta(),
91
+ toolConfirmations: accumulator.getToolConfirmations(),
92
+ authRequests: accumulator.getAuthRequests(),
93
+ escalated: accumulator.isEscalated(),
94
+ messageMetadata: accumulator.getMessageMetadata()
95
+ }
109
96
  });
110
- setAgentInfo(accumulator.getAgentInfo());
111
- setLongRunningToolIds(accumulator.getLongRunningToolIds());
112
- setArtifactDelta({
113
- ...artifactDeltaRef.current,
114
- ...accumulator.getArtifactDelta()
115
- });
116
- setToolConfirmations(accumulator.getToolConfirmations());
117
- setAuthRequests(accumulator.getAuthRequests());
118
- setEscalated(accumulator.isEscalated());
119
- {
120
- const newMeta = accumulator.getMessageMetadata();
121
- if (newMeta.size > 0) setMessageMetadata(new Map([...messageMetadataRef.current, ...newMeta]));
122
- }
123
97
  const transfer = accumulator.getLastTransferToAgent();
124
98
  if (transfer && transfer !== lastTransferToAgent) {
125
99
  lastTransferToAgent = transfer;
@@ -133,8 +107,11 @@ const useAdkMessages = ({ stream, eventHandlers }) => {
133
107
  } finally {
134
108
  if (abortControllerRef.current === abortController) {
135
109
  if (abortController.signal.aborted) {
136
- setLongRunningToolIds(accumulator.getLongRunningToolIds().filter((id) => initialLongRunningToolIds.includes(id)));
137
- const updatedMessages = messagesRef.current;
110
+ controller.dispatch({
111
+ type: "longRunningToolIds.set",
112
+ ids: accumulator.getLongRunningToolIds().filter((id) => initialLongRunningToolIds.includes(id))
113
+ });
114
+ const updatedMessages = controller.getState().messages;
138
115
  const lastAssistantMessage = updatedMessages.findLast((m) => m.type === "ai");
139
116
  if (lastAssistantMessage && !initialMessageIds.has(lastAssistantMessage.id) && !lastAssistantMessage.status) setMessagesImmediate(updatedMessages.map((m) => m === lastAssistantMessage ? {
140
117
  ...lastAssistantMessage,
@@ -149,18 +126,20 @@ const useAdkMessages = ({ stream, eventHandlers }) => {
149
126
  }
150
127
  }, [
151
128
  aui,
129
+ controller,
152
130
  setMessagesImmediate,
153
- setLongRunningToolIds,
154
131
  stream,
155
132
  onError,
156
133
  onCustomEvent,
157
- onAgentTransfer
134
+ onAgentTransfer,
135
+ onMessages
158
136
  ]);
159
137
  const cancel = useCallback(() => {
160
138
  if (abortControllerRef.current) abortControllerRef.current.abort();
161
139
  }, []);
162
140
  useReplaySafeEffect(() => cancel, []);
163
141
  return {
142
+ controller,
164
143
  messages,
165
144
  stateDelta,
166
145
  agentInfo,
@@ -177,6 +156,13 @@ const useAdkMessages = ({ stream, eventHandlers }) => {
177
156
  applySnapshot
178
157
  };
179
158
  };
159
+ const useAdkMessages = ({ stream, eventHandlers }) => {
160
+ const { controller: _controller, ...result } = useAdkMessagesInternal({
161
+ stream,
162
+ ...eventHandlers !== void 0 && { eventHandlers }
163
+ });
164
+ return result;
165
+ };
180
166
  /**
181
167
  * Transport sends every human and tool message of one `send` call as a single
182
168
  * ADK `Content`, and ADK parses that event's function responses before running
@@ -255,6 +241,6 @@ const messageToEvent = (msg) => {
255
241
  return result;
256
242
  };
257
243
  //#endregion
258
- export { messageToEvent, messagesToEvents, useAdkMessages };
244
+ export { messageToEvent, messagesToEvents, useAdkMessages, useAdkMessagesInternal };
259
245
 
260
246
  //# sourceMappingURL=useAdkMessages.js.map