@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.
- package/README.md +12 -2
- package/dist/AdkClient.d.ts +4 -0
- package/dist/AdkClient.d.ts.map +1 -1
- package/dist/AdkClient.js +12 -7
- package/dist/AdkClient.js.map +1 -1
- package/dist/AdkSessionAdapter.d.ts.map +1 -1
- package/dist/AdkSessionAdapter.js +7 -7
- package/dist/AdkSessionAdapter.js.map +1 -1
- package/dist/AdkThreadController.d.ts +15 -0
- package/dist/AdkThreadController.d.ts.map +1 -0
- package/dist/AdkThreadController.js +35 -0
- package/dist/AdkThreadController.js.map +1 -0
- package/dist/adkThreadState.d.ts +54 -0
- package/dist/adkThreadState.d.ts.map +1 -0
- package/dist/adkThreadState.js +93 -0
- package/dist/adkThreadState.js.map +1 -0
- package/dist/convertToAdkMessages.js +1 -1
- package/dist/convertToAdkMessages.js.map +1 -1
- package/dist/sdkIdentity.js +1 -1
- package/dist/server/createAdkApiRoute.d.ts +37 -6
- package/dist/server/createAdkApiRoute.d.ts.map +1 -1
- package/dist/server/createAdkApiRoute.js +55 -5
- package/dist/server/createAdkApiRoute.js.map +1 -1
- package/dist/server/parseAdkRequest.d.ts +4 -1
- package/dist/server/parseAdkRequest.d.ts.map +1 -1
- package/dist/server/parseAdkRequest.js +5 -1
- package/dist/server/parseAdkRequest.js.map +1 -1
- package/dist/useAdkMessages.d.ts +9 -7
- package/dist/useAdkMessages.d.ts.map +1 -1
- package/dist/useAdkMessages.js +55 -77
- package/dist/useAdkMessages.js.map +1 -1
- package/dist/useAdkRuntime.d.ts +7 -1
- package/dist/useAdkRuntime.d.ts.map +1 -1
- package/dist/useAdkRuntime.js +134 -56
- package/dist/useAdkRuntime.js.map +1 -1
- package/package.json +5 -5
- package/src/AdkClient.test.ts +78 -2
- package/src/AdkClient.ts +24 -6
- package/src/AdkSessionAdapter.ts +1 -1
- package/src/AdkThreadController.test.ts +90 -0
- package/src/AdkThreadController.ts +45 -0
- package/src/adkThreadState.test.ts +207 -0
- package/src/adkThreadState.ts +124 -0
- package/src/convertToAdkMessages.test.ts +19 -0
- package/src/convertToAdkMessages.ts +1 -1
- package/src/hooks.test.tsx +1 -0
- package/src/server/createAdkApiRoute.controls.test.ts +66 -0
- package/src/server/createAdkApiRoute.test.ts +282 -0
- package/src/server/createAdkApiRoute.ts +119 -11
- package/src/server/parseAdkRequest.test.ts +11 -3
- package/src/server/parseAdkRequest.ts +7 -1
- package/src/useAdkMessages.test.ts +1 -0
- package/src/useAdkMessages.ts +61 -96
- package/src/useAdkRuntime.cancellation.test.tsx +4 -3
- package/src/useAdkRuntime.cloud-options.test.tsx +59 -0
- package/src/useAdkRuntime.refetch.test.tsx +548 -4
- package/src/useAdkRuntime.replacement.test.tsx +718 -1
- package/src/useAdkRuntime.ts +169 -73
- package/src/useAdkRuntimeApproval.test.tsx +87 -1
- package/dist/raceWithAbortSignal.d.ts +0 -2
- package/dist/raceWithAbortSignal.d.ts.map +0 -1
- package/dist/raceWithAbortSignal.js +0 -45
- package/dist/raceWithAbortSignal.js.map +0 -1
- package/src/raceWithAbortSignal.test.ts +0 -73
- 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.
|
|
16
|
-
*
|
|
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
|
|
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:
|
|
41
|
-
* sessionId: (
|
|
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;
|
|
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:
|
|
16
|
-
* sessionId: (
|
|
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
|
-
...
|
|
31
|
-
...
|
|
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.
|
|
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
|
|
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,
|
|
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
|
|
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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"}
|
package/dist/useAdkMessages.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
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":"
|
|
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"}
|
package/dist/useAdkMessages.js
CHANGED
|
@@ -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,
|
|
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 [
|
|
15
|
-
const
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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 }) =>
|
|
185
|
-
|
|
186
|
-
|
|
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
|