@assistant-ui/react-google-adk 0.0.35 → 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 +9 -7
  29. package/dist/useAdkMessages.d.ts.map +1 -1
  30. package/dist/useAdkMessages.js +55 -77
  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 +134 -56
  35. package/dist/useAdkRuntime.js.map +1 -1
  36. package/package.json +5 -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 +1 -0
  53. package/src/useAdkMessages.ts +61 -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 +169 -73
  59. package/src/useAdkRuntimeApproval.test.tsx +87 -1
  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?: {
@@ -11,6 +12,7 @@ type UseAdkMessagesInternalOptions = UseAdkMessagesOptions & {
11
12
  onMessages?: (messages: AdkMessage[], runConfig: unknown) => void;
12
13
  };
13
14
  declare const useAdkMessagesInternal: ({ stream, eventHandlers, onMessages, }: UseAdkMessagesInternalOptions) => {
15
+ controller: AdkThreadController;
14
16
  messages: AdkMessage[];
15
17
  stateDelta: Record<string, unknown>;
16
18
  agentInfo: {
@@ -19,10 +21,10 @@ declare const useAdkMessagesInternal: ({ stream, eventHandlers, onMessages, }: U
19
21
  };
20
22
  longRunningToolIds: string[];
21
23
  artifactDelta: Record<string, number>;
22
- toolConfirmations: AdkToolConfirmation[];
23
- authRequests: AdkAuthRequest[];
24
+ toolConfirmations: import("./types.js").AdkToolConfirmation[];
25
+ authRequests: import("./types.js").AdkAuthRequest[];
24
26
  escalated: boolean;
25
- messageMetadata: Map<string, AdkMessageMetadata>;
27
+ messageMetadata: Map<string, import("./types.js").AdkMessageMetadata>;
26
28
  sendMessage: (newMessages: AdkMessage[], config: AdkSendMessageConfig) => Promise<void>;
27
29
  cancel: () => void;
28
30
  setMessages: (msgs: AdkMessage[]) => void;
@@ -38,10 +40,10 @@ export declare const useAdkMessages: ({ stream, eventHandlers, }: UseAdkMessages
38
40
  };
39
41
  longRunningToolIds: string[];
40
42
  artifactDelta: Record<string, number>;
41
- toolConfirmations: AdkToolConfirmation[];
42
- authRequests: AdkAuthRequest[];
43
+ toolConfirmations: import("./types.js").AdkToolConfirmation[];
44
+ authRequests: import("./types.js").AdkAuthRequest[];
43
45
  escalated: boolean;
44
- messageMetadata: Map<string, AdkMessageMetadata>;
46
+ messageMetadata: Map<string, import("./types.js").AdkMessageMetadata>;
45
47
  sendMessage: (newMessages: AdkMessage[], config: AdkSendMessageConfig) => Promise<void>;
46
48
  cancel: () => void;
47
49
  setMessages: (msgs: AdkMessage[]) => void;
@@ -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;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;;;;eAIrB,MAAM,GAAG,SAAS;iBAChB,MAAM,GAAG,SAAS;;;;;;;;+BAqFP,UAAU,EAAE,UAAU,oBAAoB;;wBAvDhB,UAAU,EAAE;4BAmCnD,UAAU,EAAE;8BAnBR,iBAAiB;CAmP/B,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,4BAG5B,qBAAqB;;;;eAvSb,MAAM,GAAG,SAAS;iBAChB,MAAM,GAAG,SAAS;;;;;;;;+BAqFP,UAAU,EAAE,UAAU,oBAAoB;;wBAvDhB,UAAU,EAAE;4BAmCnD,UAAU,EAAE;8BAnBR,iBAAiB;CA4P5B,CAAC;AAEL,OAAO,EAAE,sBAAsB,EAAE,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
14
  const useAdkMessagesInternal = ({ stream, eventHandlers, onMessages }) => {
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]);
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 useAdkMessagesInternal = ({ stream, eventHandlers, onMessages }) => {
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 useAdkMessagesInternal = ({ stream, eventHandlers, onMessages }) => {
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();
@@ -105,24 +80,20 @@ const useAdkMessagesInternal = ({ stream, eventHandlers, onMessages }) => {
105
80
  const affectedMessageCount = Math.max(event.content?.parts?.length ?? 0, 1);
106
81
  const affectedMessages = updatedMessages.slice(-affectedMessageCount);
107
82
  if (affectedMessages.length > 0) onMessages?.(affectedMessages, config.runConfig);
108
- setMessagesImmediate(updatedMessages);
109
- setStateDelta({
110
- ...stateDeltaRef.current,
111
- ...accumulator.getStateDelta()
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
+ }
112
96
  });
113
- setAgentInfo(accumulator.getAgentInfo());
114
- setLongRunningToolIds(accumulator.getLongRunningToolIds());
115
- setArtifactDelta({
116
- ...artifactDeltaRef.current,
117
- ...accumulator.getArtifactDelta()
118
- });
119
- setToolConfirmations(accumulator.getToolConfirmations());
120
- setAuthRequests(accumulator.getAuthRequests());
121
- setEscalated(accumulator.isEscalated());
122
- {
123
- const newMeta = accumulator.getMessageMetadata();
124
- if (newMeta.size > 0) setMessageMetadata(new Map([...messageMetadataRef.current, ...newMeta]));
125
- }
126
97
  const transfer = accumulator.getLastTransferToAgent();
127
98
  if (transfer && transfer !== lastTransferToAgent) {
128
99
  lastTransferToAgent = transfer;
@@ -136,8 +107,11 @@ const useAdkMessagesInternal = ({ stream, eventHandlers, onMessages }) => {
136
107
  } finally {
137
108
  if (abortControllerRef.current === abortController) {
138
109
  if (abortController.signal.aborted) {
139
- setLongRunningToolIds(accumulator.getLongRunningToolIds().filter((id) => initialLongRunningToolIds.includes(id)));
140
- 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;
141
115
  const lastAssistantMessage = updatedMessages.findLast((m) => m.type === "ai");
142
116
  if (lastAssistantMessage && !initialMessageIds.has(lastAssistantMessage.id) && !lastAssistantMessage.status) setMessagesImmediate(updatedMessages.map((m) => m === lastAssistantMessage ? {
143
117
  ...lastAssistantMessage,
@@ -152,8 +126,8 @@ const useAdkMessagesInternal = ({ stream, eventHandlers, onMessages }) => {
152
126
  }
153
127
  }, [
154
128
  aui,
129
+ controller,
155
130
  setMessagesImmediate,
156
- setLongRunningToolIds,
157
131
  stream,
158
132
  onError,
159
133
  onCustomEvent,
@@ -165,6 +139,7 @@ const useAdkMessagesInternal = ({ stream, eventHandlers, onMessages }) => {
165
139
  }, []);
166
140
  useReplaySafeEffect(() => cancel, []);
167
141
  return {
142
+ controller,
168
143
  messages,
169
144
  stateDelta,
170
145
  agentInfo,
@@ -181,10 +156,13 @@ const useAdkMessagesInternal = ({ stream, eventHandlers, onMessages }) => {
181
156
  applySnapshot
182
157
  };
183
158
  };
184
- const useAdkMessages = ({ stream, eventHandlers }) => useAdkMessagesInternal({
185
- stream,
186
- ...eventHandlers !== void 0 && { eventHandlers }
187
- });
159
+ const useAdkMessages = ({ stream, eventHandlers }) => {
160
+ const { controller: _controller, ...result } = useAdkMessagesInternal({
161
+ stream,
162
+ ...eventHandlers !== void 0 && { eventHandlers }
163
+ });
164
+ return result;
165
+ };
188
166
  /**
189
167
  * Transport sends every human and tool message of one `send` call as a single
190
168
  * ADK `Content`, and ADK parses that event's function responses before running