@assistant-ui/react-google-adk 0.0.25 → 0.0.26

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 (50) hide show
  1. package/dist/AdkClient.d.ts.map +1 -1
  2. package/dist/AdkClient.js +13 -3
  3. package/dist/AdkClient.js.map +1 -1
  4. package/dist/AdkEventAccumulator.d.ts.map +1 -1
  5. package/dist/AdkEventAccumulator.js +11 -1
  6. package/dist/AdkEventAccumulator.js.map +1 -1
  7. package/dist/AdkSessionAdapter.d.ts.map +1 -1
  8. package/dist/AdkSessionAdapter.js +2 -1
  9. package/dist/AdkSessionAdapter.js.map +1 -1
  10. package/dist/adkToolApproval.d.ts +40 -0
  11. package/dist/adkToolApproval.d.ts.map +1 -0
  12. package/dist/adkToolApproval.js +166 -0
  13. package/dist/adkToolApproval.js.map +1 -0
  14. package/dist/convertAdkMessages.d.ts +3 -1
  15. package/dist/convertAdkMessages.d.ts.map +1 -1
  16. package/dist/convertAdkMessages.js +27 -16
  17. package/dist/convertAdkMessages.js.map +1 -1
  18. package/dist/hooks.d.ts.map +1 -1
  19. package/dist/hooks.js +2 -11
  20. package/dist/hooks.js.map +1 -1
  21. package/dist/parseAdkEvent.d.ts +6 -0
  22. package/dist/parseAdkEvent.d.ts.map +1 -0
  23. package/dist/parseAdkEvent.js +16 -0
  24. package/dist/parseAdkEvent.js.map +1 -0
  25. package/dist/server/parseAdkRequest.d.ts.map +1 -1
  26. package/dist/server/parseAdkRequest.js +79 -13
  27. package/dist/server/parseAdkRequest.js.map +1 -1
  28. package/dist/useAdkMessages.js +104 -64
  29. package/dist/useAdkMessages.js.map +1 -1
  30. package/dist/useAdkRuntime.d.ts.map +1 -1
  31. package/dist/useAdkRuntime.js +11 -3
  32. package/dist/useAdkRuntime.js.map +1 -1
  33. package/package.json +5 -5
  34. package/src/AdkClient.test.ts +137 -0
  35. package/src/AdkClient.ts +15 -3
  36. package/src/AdkEventAccumulator.test.ts +77 -0
  37. package/src/AdkEventAccumulator.ts +24 -1
  38. package/src/AdkSessionAdapter.test.ts +145 -1
  39. package/src/AdkSessionAdapter.ts +7 -2
  40. package/src/adkToolApproval.test.ts +345 -0
  41. package/src/adkToolApproval.ts +215 -0
  42. package/src/convertAdkMessages.ts +72 -43
  43. package/src/hooks.ts +4 -16
  44. package/src/parseAdkEvent.ts +40 -0
  45. package/src/server/parseAdkRequest.test.ts +141 -0
  46. package/src/server/parseAdkRequest.ts +145 -13
  47. package/src/useAdkMessages.test.ts +313 -0
  48. package/src/useAdkMessages.ts +62 -4
  49. package/src/useAdkRuntime.ts +29 -3
  50. package/src/useAdkRuntimeApproval.test.tsx +344 -0
@@ -0,0 +1,166 @@
1
+ import { v4 } from "uuid";
2
+ //#region src/adkToolApproval.ts
3
+ const ADK_REQUEST_CONFIRMATION = "adk_request_confirmation";
4
+ const parseJson = (raw) => {
5
+ try {
6
+ return JSON.parse(raw);
7
+ } catch {
8
+ return;
9
+ }
10
+ };
11
+ /**
12
+ * The value the transport puts on `functionResponse.response`: the reply
13
+ * content parsed, or the raw text where it is not JSON.
14
+ */
15
+ const wireResponseOf = (content) => {
16
+ const parsed = parseJson(content);
17
+ return parsed === void 0 ? content : parsed;
18
+ };
19
+ /**
20
+ * Mirrors ADK's own reading of a confirmation reply, whose three outcomes the
21
+ * gate has to distinguish. A falsy response records nothing, so the gate stays
22
+ * open and its siblings are untouched. Otherwise ADK either unwraps its client
23
+ * wrapper — a lone `response` key whose value it parses as JSON text — or reads
24
+ * `confirmed` off the response directly, and resumes the tool unconfirmed
25
+ * (a denial) for anything that is not explicitly `true`; a bare string, number,
26
+ * or array is such a denial, not an unreadable reply. Only where that reading
27
+ * raises is the reply unreadable, and the raise is uncaught in ADK, so the
28
+ * whole event it belongs to is abandoned.
29
+ */
30
+ const readConfirmation = (content) => {
31
+ if (typeof content !== "string") return {
32
+ readable: false,
33
+ confirmed: void 0
34
+ };
35
+ const response = wireResponseOf(content);
36
+ try {
37
+ if (!response) return {
38
+ readable: true,
39
+ confirmed: void 0
40
+ };
41
+ const record = response;
42
+ const decide = (value) => typeof value === "object" && value !== null && value.confirmed === true;
43
+ if (Object.keys(record).length === 1 && "response" in record) {
44
+ const inner = parseJson(String(record.response));
45
+ if (inner === void 0) return {
46
+ readable: false,
47
+ confirmed: void 0
48
+ };
49
+ if (!inner) return {
50
+ readable: true,
51
+ confirmed: void 0
52
+ };
53
+ return {
54
+ readable: true,
55
+ confirmed: decide(inner)
56
+ };
57
+ }
58
+ return {
59
+ readable: true,
60
+ confirmed: decide(record)
61
+ };
62
+ } catch {
63
+ return {
64
+ readable: false,
65
+ confirmed: void 0
66
+ };
67
+ }
68
+ };
69
+ /**
70
+ * ADK's confirmation processor parses every function response in an event
71
+ * before running any tool, so one unreadable reply aborts the event and none of
72
+ * its siblings execute. The accumulator mints a tool message id of
73
+ * `<eventId>:<partIndex>` per response, so that prefix is the batch ADK either
74
+ * runs whole or not at all; a locally minted reply carries a bare uuid and is
75
+ * its own batch.
76
+ */
77
+ const sourceEventOf = (toolMessageId) => toolMessageId.replace(/:\d+$/, "");
78
+ /**
79
+ * ADK builds the confirmation request around the call it gates, carrying that
80
+ * call verbatim in `originalFunctionCall` — spelled `original_function_call` by
81
+ * ADK Python, which the accumulator reads the same way.
82
+ */
83
+ const gatedCallIdOf = (args) => {
84
+ if (typeof args !== "object" || args === null) return void 0;
85
+ const record = args;
86
+ const original = record.originalFunctionCall ?? record.original_function_call;
87
+ if (typeof original !== "object" || original === null) return void 0;
88
+ const id = original.id;
89
+ return typeof id === "string" && id.length > 0 ? id : void 0;
90
+ };
91
+ /**
92
+ * ADK requests a confirmation by emitting a synthetic `adk_request_confirmation`
93
+ * tool call carrying a fresh id, and resumes only when a reply quotes that id.
94
+ * The gated tool's own id is never answerable, so every approval carries the
95
+ * synthetic id — but ADK yields the gated call to the client first, in its own
96
+ * event, so both calls sit result-less in the transcript and both would render
97
+ * an approval control. The gated call is therefore mapped to the same approval
98
+ * rather than left bare, which would let it answer by writing a tool result the
99
+ * agent never produced.
100
+ */
101
+ const projectAdkToolApprovals = (messages) => {
102
+ const batches = /* @__PURE__ */ new Map();
103
+ for (const message of messages) {
104
+ if (message.type !== "tool") continue;
105
+ if (message.name !== "adk_request_confirmation") continue;
106
+ const key = sourceEventOf(message.id);
107
+ let batch = batches.get(key);
108
+ if (batch === void 0) {
109
+ batch = {
110
+ readable: true,
111
+ entries: []
112
+ };
113
+ batches.set(key, batch);
114
+ }
115
+ const { readable, confirmed } = readConfirmation(message.content);
116
+ if (!readable) batch.readable = false;
117
+ batch.entries.push([message.tool_call_id, confirmed]);
118
+ }
119
+ const replies = /* @__PURE__ */ new Map();
120
+ for (const batch of batches.values()) for (const [toolCallId, confirmed] of batch.entries) if (batch.readable && confirmed !== void 0) replies.set(toolCallId, confirmed);
121
+ else replies.delete(toolCallId);
122
+ const approvals = /* @__PURE__ */ new Map();
123
+ for (const message of messages) {
124
+ if (message.type !== "ai") continue;
125
+ for (const call of message.tool_calls ?? []) {
126
+ if (call.name !== "adk_request_confirmation") continue;
127
+ const approved = replies.get(call.id);
128
+ const approval = {
129
+ id: call.id,
130
+ ...approved !== void 0 && { approved }
131
+ };
132
+ approvals.set(call.id, approval);
133
+ const gatedId = gatedCallIdOf(call.args);
134
+ if (gatedId !== void 0) approvals.set(gatedId, approval);
135
+ }
136
+ }
137
+ return {
138
+ approvals,
139
+ key: approvals.size === 0 ? "" : JSON.stringify([...approvals])
140
+ };
141
+ };
142
+ const toAdkConfirmationReply = (toolCallId, confirmed, payload) => ({
143
+ id: v4(),
144
+ type: "tool",
145
+ tool_call_id: toolCallId,
146
+ name: ADK_REQUEST_CONFIRMATION,
147
+ content: JSON.stringify({
148
+ confirmed,
149
+ ...payload != null && { payload }
150
+ }),
151
+ status: "success"
152
+ });
153
+ /**
154
+ * ADK confirmations are a plain confirm/reject pair, so no option or reason
155
+ * from core has a field to land in. An id that is not an open gate would send a
156
+ * reply ADK silently ignores, so it throws instead.
157
+ */
158
+ const toAdkToolConfirmationReply = ({ approvalId, approved }, approvals) => {
159
+ const approval = approvals.get(approvalId);
160
+ if (approval === void 0 || approval.approved !== void 0) throw new Error(`No pending ADK tool confirmation for approval id "${approvalId}"`);
161
+ return toAdkConfirmationReply(approvalId, approved);
162
+ };
163
+ //#endregion
164
+ export { ADK_REQUEST_CONFIRMATION, projectAdkToolApprovals, toAdkConfirmationReply, toAdkToolConfirmationReply };
165
+
166
+ //# sourceMappingURL=adkToolApproval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adkToolApproval.js","names":["uuidv4"],"sources":["../src/adkToolApproval.ts"],"sourcesContent":["import type {\n RespondToToolApprovalOptions,\n ToolCallMessagePart,\n} from \"@assistant-ui/core\";\nimport type { ReadonlyJSONValue } from \"assistant-stream/utils\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport type { AdkMessage } from \"./types\";\n\nexport type AdkToolApproval = NonNullable<ToolCallMessagePart[\"approval\"]>;\n\nexport const ADK_REQUEST_CONFIRMATION = \"adk_request_confirmation\";\n\nconst parseJson = (raw: string): unknown => {\n try {\n return JSON.parse(raw);\n } catch {\n return undefined;\n }\n};\n\ntype AdkConfirmationRead = {\n /**\n * False only where ADK raises reading the reply, which aborts every function\n * response in the same event rather than denying this one.\n */\n readable: boolean;\n /** Undefined where ADK records no confirmation and the gate stays open. */\n confirmed: boolean | undefined;\n};\n\n/**\n * The value the transport puts on `functionResponse.response`: the reply\n * content parsed, or the raw text where it is not JSON.\n */\nconst wireResponseOf = (content: string): unknown => {\n const parsed = parseJson(content);\n return parsed === undefined ? content : parsed;\n};\n\n/**\n * Mirrors ADK's own reading of a confirmation reply, whose three outcomes the\n * gate has to distinguish. A falsy response records nothing, so the gate stays\n * open and its siblings are untouched. Otherwise ADK either unwraps its client\n * wrapper — a lone `response` key whose value it parses as JSON text — or reads\n * `confirmed` off the response directly, and resumes the tool unconfirmed\n * (a denial) for anything that is not explicitly `true`; a bare string, number,\n * or array is such a denial, not an unreadable reply. Only where that reading\n * raises is the reply unreadable, and the raise is uncaught in ADK, so the\n * whole event it belongs to is abandoned.\n */\nconst readConfirmation = (content: unknown): AdkConfirmationRead => {\n if (typeof content !== \"string\")\n return { readable: false, confirmed: undefined };\n const response = wireResponseOf(content);\n try {\n if (!response) return { readable: true, confirmed: undefined };\n const record = response as Record<string, unknown>;\n const decide = (value: unknown) =>\n typeof value === \"object\" &&\n value !== null &&\n (value as { confirmed?: unknown }).confirmed === true;\n // `in` raises on a primitive response, as ADK's own wrapper test does.\n if (Object.keys(record).length === 1 && \"response\" in record) {\n const inner = parseJson(String(record.response));\n // ADK parses the wrapped text without a `try`, so it raises here.\n if (inner === undefined) return { readable: false, confirmed: undefined };\n if (!inner) return { readable: true, confirmed: undefined };\n return { readable: true, confirmed: decide(inner) };\n }\n return { readable: true, confirmed: decide(record) };\n } catch {\n return { readable: false, confirmed: undefined };\n }\n};\n\n/**\n * ADK's confirmation processor parses every function response in an event\n * before running any tool, so one unreadable reply aborts the event and none of\n * its siblings execute. The accumulator mints a tool message id of\n * `<eventId>:<partIndex>` per response, so that prefix is the batch ADK either\n * runs whole or not at all; a locally minted reply carries a bare uuid and is\n * its own batch.\n */\nconst sourceEventOf = (toolMessageId: string): string =>\n toolMessageId.replace(/:\\d+$/, \"\");\n\n/**\n * ADK builds the confirmation request around the call it gates, carrying that\n * call verbatim in `originalFunctionCall` — spelled `original_function_call` by\n * ADK Python, which the accumulator reads the same way.\n */\nconst gatedCallIdOf = (args: unknown): string | undefined => {\n if (typeof args !== \"object\" || args === null) return undefined;\n const record = args as {\n originalFunctionCall?: unknown;\n original_function_call?: unknown;\n };\n const original = record.originalFunctionCall ?? record.original_function_call;\n if (typeof original !== \"object\" || original === null) return undefined;\n const id = (original as { id?: unknown }).id;\n return typeof id === \"string\" && id.length > 0 ? id : undefined;\n};\n\nexport type AdkToolApprovalProjection = {\n approvals: ReadonlyMap<string, AdkToolApproval>;\n /**\n * Identity of the decision state. Core re-converts a cached message object\n * only when the converter callback changes, so the callback is rebuilt when\n * this key moves rather than on every render.\n */\n key: string;\n};\n\n/**\n * ADK requests a confirmation by emitting a synthetic `adk_request_confirmation`\n * tool call carrying a fresh id, and resumes only when a reply quotes that id.\n * The gated tool's own id is never answerable, so every approval carries the\n * synthetic id — but ADK yields the gated call to the client first, in its own\n * event, so both calls sit result-less in the transcript and both would render\n * an approval control. The gated call is therefore mapped to the same approval\n * rather than left bare, which would let it answer by writing a tool result the\n * agent never produced.\n */\nexport const projectAdkToolApprovals = (\n messages: readonly AdkMessage[],\n): AdkToolApprovalProjection => {\n const batches = new Map<\n string,\n { readable: boolean; entries: [string, boolean | undefined][] }\n >();\n for (const message of messages) {\n if (message.type !== \"tool\") continue;\n if (message.name !== ADK_REQUEST_CONFIRMATION) continue;\n const key = sourceEventOf(message.id);\n let batch = batches.get(key);\n if (batch === undefined) {\n batch = { readable: true, entries: [] };\n batches.set(key, batch);\n }\n const { readable, confirmed } = readConfirmation(message.content);\n if (!readable) batch.readable = false;\n batch.entries.push([message.tool_call_id, confirmed]);\n }\n\n const replies = new Map<string, boolean>();\n for (const batch of batches.values()) {\n for (const [toolCallId, confirmed] of batch.entries) {\n if (batch.readable && confirmed !== undefined)\n replies.set(toolCallId, confirmed);\n else replies.delete(toolCallId);\n }\n }\n\n const approvals = new Map<string, AdkToolApproval>();\n for (const message of messages) {\n if (message.type !== \"ai\") continue;\n for (const call of message.tool_calls ?? []) {\n if (call.name !== ADK_REQUEST_CONFIRMATION) continue;\n const approved = replies.get(call.id);\n const approval: AdkToolApproval = {\n id: call.id,\n ...(approved !== undefined && { approved }),\n };\n approvals.set(call.id, approval);\n // ADK yields the gated call in its own event before the confirmation is\n // requested, so that call is also in the transcript, result-less, and\n // inherits the message's `requires-action` status. Left ungated it\n // renders a second approval control with no approval behind it, which\n // answers by writing a tool result the agent never produced. The same\n // approval carries the synthetic id, so answering either one replies to\n // the gate ADK is actually waiting on.\n const gatedId = gatedCallIdOf(call.args);\n if (gatedId !== undefined) approvals.set(gatedId, approval);\n }\n }\n\n return {\n approvals,\n key: approvals.size === 0 ? \"\" : JSON.stringify([...approvals]),\n };\n};\n\nexport const toAdkConfirmationReply = (\n toolCallId: string,\n confirmed: boolean,\n payload?: ReadonlyJSONValue,\n): AdkMessage & { type: \"tool\" } => ({\n id: uuidv4(),\n type: \"tool\",\n tool_call_id: toolCallId,\n name: ADK_REQUEST_CONFIRMATION,\n content: JSON.stringify({\n confirmed,\n ...(payload != null && { payload }),\n }),\n status: \"success\",\n});\n\n/**\n * ADK confirmations are a plain confirm/reject pair, so no option or reason\n * from core has a field to land in. An id that is not an open gate would send a\n * reply ADK silently ignores, so it throws instead.\n */\nexport const toAdkToolConfirmationReply = (\n { approvalId, approved }: RespondToToolApprovalOptions,\n approvals: ReadonlyMap<string, AdkToolApproval>,\n): AdkMessage & { type: \"tool\" } => {\n const approval = approvals.get(approvalId);\n if (approval === undefined || approval.approved !== undefined)\n throw new Error(\n `No pending ADK tool confirmation for approval id \"${approvalId}\"`,\n );\n\n return toAdkConfirmationReply(approvalId, approved);\n};\n"],"mappings":"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"}
@@ -1,7 +1,9 @@
1
1
  import { AdkMessage } from "./types.js";
2
+ import { AdkToolApproval } from "./adkToolApproval.js";
2
3
  import { useExternalMessageConverter } from "@assistant-ui/core/react";
3
4
  //#region src/convertAdkMessages.d.ts
5
+ declare const createAdkMessageConverter: (approvals: ReadonlyMap<string, AdkToolApproval>) => useExternalMessageConverter.Callback<AdkMessage>;
4
6
  declare const convertAdkMessage: useExternalMessageConverter.Callback<AdkMessage>;
5
7
  //#endregion
6
- export { convertAdkMessage };
8
+ export { convertAdkMessage, createAdkMessageConverter };
7
9
  //# sourceMappingURL=convertAdkMessages.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"convertAdkMessages.d.ts","names":[],"sources":["../src/convertAdkMessages.ts"],"mappings":";;;cAmFa,mBAAmB,4BAA4B,SAC1D"}
1
+ {"version":3,"file":"convertAdkMessages.d.ts","names":[],"sources":["../src/convertAdkMessages.ts"],"mappings":";;;;cAyFa,4BAET,WAAW,oBAAoB,qBAC9B,4BAA4B,SAAS;cAiE7B,mBAAmB,4BAA4B,SAAS"}
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import "./adkToolApproval.js";
2
3
  //#region src/convertAdkMessages.ts
3
4
  const contentToParts = (content, role) => {
4
5
  if (typeof content === "string") return [{
@@ -64,7 +65,8 @@ const contentToParts = (content, role) => {
64
65
  }
65
66
  }).filter((p) => p !== null);
66
67
  };
67
- const convertAdkMessage = (message) => {
68
+ const EMPTY_APPROVALS = /* @__PURE__ */ new Map();
69
+ const createAdkMessageConverter = (approvals) => (message) => {
68
70
  switch (message.type) {
69
71
  case "human": return {
70
72
  role: "user",
@@ -72,13 +74,17 @@ const convertAdkMessage = (message) => {
72
74
  content: contentToParts(message.content, "user")
73
75
  };
74
76
  case "ai": {
75
- const toolCallParts = message.tool_calls?.map((tc) => ({
76
- type: "tool-call",
77
- toolCallId: tc.id,
78
- toolName: tc.name,
79
- args: tc.args,
80
- argsText: tc.argsText ?? JSON.stringify(tc.args)
81
- })) ?? [];
77
+ const toolCallParts = message.tool_calls?.map((tc) => {
78
+ const approval = approvals.get(tc.id);
79
+ return {
80
+ type: "tool-call",
81
+ toolCallId: tc.id,
82
+ toolName: tc.name,
83
+ args: tc.args,
84
+ argsText: tc.argsText ?? JSON.stringify(tc.args),
85
+ ...approval && { approval }
86
+ };
87
+ }) ?? [];
82
88
  return {
83
89
  role: "assistant",
84
90
  id: message.id,
@@ -90,16 +96,21 @@ const convertAdkMessage = (message) => {
90
96
  } } }
91
97
  };
92
98
  }
93
- case "tool": return {
94
- role: "tool",
95
- toolCallId: message.tool_call_id,
96
- toolName: message.name,
97
- result: message.content,
98
- isError: message.status === "error"
99
- };
99
+ case "tool": {
100
+ const approval = approvals.get(message.tool_call_id);
101
+ if (message.name === "adk_request_confirmation" && approval !== void 0 && approval.approved === void 0) return [];
102
+ return {
103
+ role: "tool",
104
+ toolCallId: message.tool_call_id,
105
+ toolName: message.name,
106
+ result: message.content,
107
+ isError: message.status === "error"
108
+ };
109
+ }
100
110
  }
101
111
  };
112
+ const convertAdkMessage = createAdkMessageConverter(EMPTY_APPROVALS);
102
113
  //#endregion
103
- export { convertAdkMessage };
114
+ export { convertAdkMessage, createAdkMessageConverter };
104
115
 
105
116
  //# sourceMappingURL=convertAdkMessages.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"convertAdkMessages.js","names":[],"sources":["../src/convertAdkMessages.ts"],"sourcesContent":["\"use client\";\n\nimport type { ToolCallMessagePart } from \"@assistant-ui/core\";\nimport type { useExternalMessageConverter } from \"@assistant-ui/core/react\";\nimport type { AdkMessage, AdkMessageContentPart } from \"./types\";\n\ntype ContentPart =\n | { type: \"text\"; text: string }\n | { type: \"reasoning\"; text: string }\n | { type: \"image\"; image: string }\n | {\n type: \"file\";\n data: string;\n mimeType: string;\n filename?: string;\n sourceType?: \"url\";\n }\n | { type: \"data\"; name: string; data: unknown };\n\nconst contentToParts = (\n content: AdkMessage[\"content\"],\n role: \"user\" | \"assistant\",\n): ContentPart[] => {\n if (typeof content === \"string\")\n return [{ type: \"text\" as const, text: content }];\n\n return (content as AdkMessageContentPart[])\n .map((part): ContentPart | null => {\n switch (part.type) {\n case \"text\":\n return { type: \"text\", text: part.text };\n case \"reasoning\":\n return { type: \"reasoning\", text: part.text };\n case \"image\":\n return {\n type: \"image\",\n image: `data:${part.mimeType};base64,${part.data}`,\n };\n case \"image_url\":\n return { type: \"image\", image: part.url };\n case \"file\":\n return {\n type: \"file\",\n data: part.data,\n mimeType: part.mimeType,\n ...(part.filename != null && { filename: part.filename }),\n };\n case \"file_url\":\n if (role === \"user\") {\n return {\n type: \"file\",\n data: part.url,\n mimeType: part.mimeType ?? \"application/octet-stream\",\n sourceType: \"url\",\n };\n }\n return {\n type: \"data\",\n name: \"file_url\",\n data: {\n url: part.url,\n ...(part.mimeType != null && { mimeType: part.mimeType }),\n },\n };\n case \"code\":\n return {\n type: \"data\",\n name: \"executable_code\",\n data: { code: part.code, language: part.language },\n };\n case \"code_result\":\n return {\n type: \"data\",\n name: \"code_execution_result\",\n data: { output: part.output, outcome: part.outcome },\n };\n default:\n return null;\n }\n })\n .filter((p): p is NonNullable<typeof p> => p !== null);\n};\n\nexport const convertAdkMessage: useExternalMessageConverter.Callback<\n AdkMessage\n> = (message) => {\n switch (message.type) {\n case \"human\":\n return {\n role: \"user\",\n id: message.id,\n content: contentToParts(message.content, \"user\"),\n };\n\n case \"ai\": {\n const toolCallParts: ToolCallMessagePart[] =\n message.tool_calls?.map((tc) => ({\n type: \"tool-call\",\n toolCallId: tc.id,\n toolName: tc.name,\n args: tc.args,\n argsText: tc.argsText ?? JSON.stringify(tc.args),\n })) ?? [];\n\n return {\n role: \"assistant\",\n id: message.id,\n content: [\n ...contentToParts(message.content, \"assistant\"),\n ...toolCallParts,\n ],\n ...(message.status && { status: message.status }),\n ...(message.author && {\n metadata: {\n custom: { author: message.author, branch: message.branch },\n },\n }),\n };\n }\n\n case \"tool\":\n return {\n role: \"tool\",\n toolCallId: message.tool_call_id,\n toolName: message.name,\n result: message.content,\n isError: message.status === \"error\",\n };\n }\n};\n"],"mappings":";;AAmBA,MAAM,kBACJ,SACA,SACkB;CAClB,IAAI,OAAO,YAAY,UACrB,OAAO,CAAC;EAAE,MAAM;EAAiB,MAAM;CAAQ,CAAC;CAElD,OAAQ,QACL,KAAK,SAA6B;EACjC,QAAQ,KAAK,MAAb;GACE,KAAK,QACH,OAAO;IAAE,MAAM;IAAQ,MAAM,KAAK;GAAK;GACzC,KAAK,aACH,OAAO;IAAE,MAAM;IAAa,MAAM,KAAK;GAAK;GAC9C,KAAK,SACH,OAAO;IACL,MAAM;IACN,OAAO,QAAQ,KAAK,SAAS,UAAU,KAAK;GAC9C;GACF,KAAK,aACH,OAAO;IAAE,MAAM;IAAS,OAAO,KAAK;GAAI;GAC1C,KAAK,QACH,OAAO;IACL,MAAM;IACN,MAAM,KAAK;IACX,UAAU,KAAK;IACf,GAAI,KAAK,YAAY,QAAQ,EAAE,UAAU,KAAK,SAAS;GACzD;GACF,KAAK;IACH,IAAI,SAAS,QACX,OAAO;KACL,MAAM;KACN,MAAM,KAAK;KACX,UAAU,KAAK,YAAY;KAC3B,YAAY;IACd;IAEF,OAAO;KACL,MAAM;KACN,MAAM;KACN,MAAM;MACJ,KAAK,KAAK;MACV,GAAI,KAAK,YAAY,QAAQ,EAAE,UAAU,KAAK,SAAS;KACzD;IACF;GACF,KAAK,QACH,OAAO;IACL,MAAM;IACN,MAAM;IACN,MAAM;KAAE,MAAM,KAAK;KAAM,UAAU,KAAK;IAAS;GACnD;GACF,KAAK,eACH,OAAO;IACL,MAAM;IACN,MAAM;IACN,MAAM;KAAE,QAAQ,KAAK;KAAQ,SAAS,KAAK;IAAQ;GACrD;GACF,SACE,OAAO;EACX;CACF,CAAC,CAAC,CACD,QAAQ,MAAkC,MAAM,IAAI;AACzD;AAEA,MAAa,qBAER,YAAY;CACf,QAAQ,QAAQ,MAAhB;EACE,KAAK,SACH,OAAO;GACL,MAAM;GACN,IAAI,QAAQ;GACZ,SAAS,eAAe,QAAQ,SAAS,MAAM;EACjD;EAEF,KAAK,MAAM;GACT,MAAM,gBACJ,QAAQ,YAAY,KAAK,QAAQ;IAC/B,MAAM;IACN,YAAY,GAAG;IACf,UAAU,GAAG;IACb,MAAM,GAAG;IACT,UAAU,GAAG,YAAY,KAAK,UAAU,GAAG,IAAI;GACjD,EAAE,KAAK,CAAC;GAEV,OAAO;IACL,MAAM;IACN,IAAI,QAAQ;IACZ,SAAS,CACP,GAAG,eAAe,QAAQ,SAAS,WAAW,GAC9C,GAAG,aACL;IACA,GAAI,QAAQ,UAAU,EAAE,QAAQ,QAAQ,OAAO;IAC/C,GAAI,QAAQ,UAAU,EACpB,UAAU,EACR,QAAQ;KAAE,QAAQ,QAAQ;KAAQ,QAAQ,QAAQ;IAAO,EAC3D,EACF;GACF;EACF;EAEA,KAAK,QACH,OAAO;GACL,MAAM;GACN,YAAY,QAAQ;GACpB,UAAU,QAAQ;GAClB,QAAQ,QAAQ;GAChB,SAAS,QAAQ,WAAW;EAC9B;CACJ;AACF"}
1
+ {"version":3,"file":"convertAdkMessages.js","names":[],"sources":["../src/convertAdkMessages.ts"],"sourcesContent":["\"use client\";\n\nimport type { ToolCallMessagePart } from \"@assistant-ui/core\";\nimport type { useExternalMessageConverter } from \"@assistant-ui/core/react\";\nimport {\n ADK_REQUEST_CONFIRMATION,\n type AdkToolApproval,\n} from \"./adkToolApproval\";\nimport type { AdkMessage, AdkMessageContentPart } from \"./types\";\n\ntype ContentPart =\n | { type: \"text\"; text: string }\n | { type: \"reasoning\"; text: string }\n | { type: \"image\"; image: string }\n | {\n type: \"file\";\n data: string;\n mimeType: string;\n filename?: string;\n sourceType?: \"url\";\n }\n | { type: \"data\"; name: string; data: unknown };\n\nconst contentToParts = (\n content: AdkMessage[\"content\"],\n role: \"user\" | \"assistant\",\n): ContentPart[] => {\n if (typeof content === \"string\")\n return [{ type: \"text\" as const, text: content }];\n\n return (content as AdkMessageContentPart[])\n .map((part): ContentPart | null => {\n switch (part.type) {\n case \"text\":\n return { type: \"text\", text: part.text };\n case \"reasoning\":\n return { type: \"reasoning\", text: part.text };\n case \"image\":\n return {\n type: \"image\",\n image: `data:${part.mimeType};base64,${part.data}`,\n };\n case \"image_url\":\n return { type: \"image\", image: part.url };\n case \"file\":\n return {\n type: \"file\",\n data: part.data,\n mimeType: part.mimeType,\n ...(part.filename != null && { filename: part.filename }),\n };\n case \"file_url\":\n if (role === \"user\") {\n return {\n type: \"file\",\n data: part.url,\n mimeType: part.mimeType ?? \"application/octet-stream\",\n sourceType: \"url\",\n };\n }\n return {\n type: \"data\",\n name: \"file_url\",\n data: {\n url: part.url,\n ...(part.mimeType != null && { mimeType: part.mimeType }),\n },\n };\n case \"code\":\n return {\n type: \"data\",\n name: \"executable_code\",\n data: { code: part.code, language: part.language },\n };\n case \"code_result\":\n return {\n type: \"data\",\n name: \"code_execution_result\",\n data: { output: part.output, outcome: part.outcome },\n };\n default:\n return null;\n }\n })\n .filter((p): p is NonNullable<typeof p> => p !== null);\n};\n\nconst EMPTY_APPROVALS: ReadonlyMap<string, AdkToolApproval> = new Map();\n\nexport const createAdkMessageConverter =\n (\n approvals: ReadonlyMap<string, AdkToolApproval>,\n ): useExternalMessageConverter.Callback<AdkMessage> =>\n (message) => {\n switch (message.type) {\n case \"human\":\n return {\n role: \"user\",\n id: message.id,\n content: contentToParts(message.content, \"user\"),\n };\n\n case \"ai\": {\n const toolCallParts: ToolCallMessagePart[] =\n message.tool_calls?.map((tc) => {\n const approval = approvals.get(tc.id);\n return {\n type: \"tool-call\",\n toolCallId: tc.id,\n toolName: tc.name,\n args: tc.args,\n argsText: tc.argsText ?? JSON.stringify(tc.args),\n ...(approval && { approval }),\n };\n }) ?? [];\n\n return {\n role: \"assistant\",\n id: message.id,\n content: [\n ...contentToParts(message.content, \"assistant\"),\n ...toolCallParts,\n ],\n ...(message.status && { status: message.status }),\n ...(message.author && {\n metadata: {\n custom: { author: message.author, branch: message.branch },\n },\n }),\n };\n }\n\n case \"tool\": {\n // A confirmation reply ADK could not read leaves its gate undecided.\n // Any result settles the tool call in core, so the reply is dropped\n // here to keep the gate requiring action and answerable again. Only a\n // reply to the confirmation itself is dropped: the gated call carries\n // the same approval, and its own result is the agent's real output.\n const approval = approvals.get(message.tool_call_id);\n if (\n message.name === ADK_REQUEST_CONFIRMATION &&\n approval !== undefined &&\n approval.approved === undefined\n )\n return [];\n\n return {\n role: \"tool\",\n toolCallId: message.tool_call_id,\n toolName: message.name,\n result: message.content,\n isError: message.status === \"error\",\n };\n }\n }\n };\n\nexport const convertAdkMessage: useExternalMessageConverter.Callback<AdkMessage> =\n createAdkMessageConverter(EMPTY_APPROVALS);\n"],"mappings":";;;AAuBA,MAAM,kBACJ,SACA,SACkB;CAClB,IAAI,OAAO,YAAY,UACrB,OAAO,CAAC;EAAE,MAAM;EAAiB,MAAM;CAAQ,CAAC;CAElD,OAAQ,QACL,KAAK,SAA6B;EACjC,QAAQ,KAAK,MAAb;GACE,KAAK,QACH,OAAO;IAAE,MAAM;IAAQ,MAAM,KAAK;GAAK;GACzC,KAAK,aACH,OAAO;IAAE,MAAM;IAAa,MAAM,KAAK;GAAK;GAC9C,KAAK,SACH,OAAO;IACL,MAAM;IACN,OAAO,QAAQ,KAAK,SAAS,UAAU,KAAK;GAC9C;GACF,KAAK,aACH,OAAO;IAAE,MAAM;IAAS,OAAO,KAAK;GAAI;GAC1C,KAAK,QACH,OAAO;IACL,MAAM;IACN,MAAM,KAAK;IACX,UAAU,KAAK;IACf,GAAI,KAAK,YAAY,QAAQ,EAAE,UAAU,KAAK,SAAS;GACzD;GACF,KAAK;IACH,IAAI,SAAS,QACX,OAAO;KACL,MAAM;KACN,MAAM,KAAK;KACX,UAAU,KAAK,YAAY;KAC3B,YAAY;IACd;IAEF,OAAO;KACL,MAAM;KACN,MAAM;KACN,MAAM;MACJ,KAAK,KAAK;MACV,GAAI,KAAK,YAAY,QAAQ,EAAE,UAAU,KAAK,SAAS;KACzD;IACF;GACF,KAAK,QACH,OAAO;IACL,MAAM;IACN,MAAM;IACN,MAAM;KAAE,MAAM,KAAK;KAAM,UAAU,KAAK;IAAS;GACnD;GACF,KAAK,eACH,OAAO;IACL,MAAM;IACN,MAAM;IACN,MAAM;KAAE,QAAQ,KAAK;KAAQ,SAAS,KAAK;IAAQ;GACrD;GACF,SACE,OAAO;EACX;CACF,CAAC,CAAC,CACD,QAAQ,MAAkC,MAAM,IAAI;AACzD;AAEA,MAAM,kCAAwD,IAAI,IAAI;AAEtE,MAAa,6BAET,eAED,YAAY;CACX,QAAQ,QAAQ,MAAhB;EACE,KAAK,SACH,OAAO;GACL,MAAM;GACN,IAAI,QAAQ;GACZ,SAAS,eAAe,QAAQ,SAAS,MAAM;EACjD;EAEF,KAAK,MAAM;GACT,MAAM,gBACJ,QAAQ,YAAY,KAAK,OAAO;IAC9B,MAAM,WAAW,UAAU,IAAI,GAAG,EAAE;IACpC,OAAO;KACL,MAAM;KACN,YAAY,GAAG;KACf,UAAU,GAAG;KACb,MAAM,GAAG;KACT,UAAU,GAAG,YAAY,KAAK,UAAU,GAAG,IAAI;KAC/C,GAAI,YAAY,EAAE,SAAS;IAC7B;GACF,CAAC,KAAK,CAAC;GAET,OAAO;IACL,MAAM;IACN,IAAI,QAAQ;IACZ,SAAS,CACP,GAAG,eAAe,QAAQ,SAAS,WAAW,GAC9C,GAAG,aACL;IACA,GAAI,QAAQ,UAAU,EAAE,QAAQ,QAAQ,OAAO;IAC/C,GAAI,QAAQ,UAAU,EACpB,UAAU,EACR,QAAQ;KAAE,QAAQ,QAAQ;KAAQ,QAAQ,QAAQ;IAAO,EAC3D,EACF;GACF;EACF;EAEA,KAAK,QAAQ;GAMX,MAAM,WAAW,UAAU,IAAI,QAAQ,YAAY;GACnD,IACE,QAAQ,SAAA,8BACR,aAAa,KAAA,KACb,SAAS,aAAa,KAAA,GAEtB,OAAO,CAAC;GAEV,OAAO;IACL,MAAM;IACN,YAAY,QAAQ;IACpB,UAAU,QAAQ;IAClB,QAAQ,QAAQ;IAChB,SAAS,QAAQ,WAAW;GAC9B;EACF;CACF;AACF;AAEF,MAAa,oBACX,0BAA0B,eAAe"}
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","names":[],"sources":["../src/hooks.ts"],"mappings":";;;;cAqBa;EAAA;EAAA;;;cAIA,0BAAkB;;cAIlB,mBAEH,UAAU,cAAc,QAAQ,yBAAoB;;cAKjD;;cAIA,+BAAuB;;cAIvB,0BAAkB;;cAIlB,uBAAe;;cAIf;;cAGA,6BAAqB,YAAA;;cAMrB,0BAGT,oBACA,oBACA,UAAU,sBAAiB;;cAqBlB,yBAEH,oBAAoB,YAAY,sBAAiB;;cAiB9C,0BAEH,oBAAoB,QAAQ,sBAAiB;;cAoC1C,sBAAc;;cAOd,uBAAe;;cAOf,uBAAe"}
1
+ {"version":3,"file":"hooks.d.ts","names":[],"sources":["../src/hooks.ts"],"mappings":";;;;cAsBa;EAAA;EAAA;;;cAIA,0BAAkB;;cAIlB,mBAEH,UAAU,cAAc,QAAQ,yBAAoB;;cAKjD;;cAIA,+BAAuB;;cAIvB,0BAAkB;;cAIlB,uBAAe;;cAIf;;cAGA,6BAAqB,YAAA;;cAMrB,0BAGT,oBACA,oBACA,UAAU,sBAAiB;;cAQlB,yBAEH,oBAAoB,YAAY,sBAAiB;;cAiB9C,0BAEH,oBAAoB,QAAQ,sBAAiB;;cAoC1C,sBAAc;;cAOd,uBAAe;;cAOf,uBAAe"}
package/dist/hooks.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { adkExtras } from "./adkExtras.js";
2
+ import { toAdkConfirmationReply } from "./adkToolApproval.js";
2
3
  import { v4 } from "uuid";
3
4
  import { useAui } from "@assistant-ui/store";
4
5
  //#region src/hooks.ts
@@ -32,17 +33,7 @@ const useAdkMessageMetadata = () => adkExtras.use((e) => e.messageMetadata, EMPT
32
33
  /** Returns a function to confirm or deny a pending tool confirmation. */
33
34
  const useAdkConfirmTool = () => {
34
35
  const aui = useAui();
35
- return (toolCallId, confirmed, payload) => adkExtras.get(aui).send([{
36
- id: v4(),
37
- type: "tool",
38
- tool_call_id: toolCallId,
39
- name: "adk_request_confirmation",
40
- content: JSON.stringify({
41
- confirmed,
42
- ...payload != null && { payload }
43
- }),
44
- status: "success"
45
- }], {});
36
+ return (toolCallId, confirmed, payload) => adkExtras.get(aui).send([toAdkConfirmationReply(toolCallId, confirmed, payload)], {});
46
37
  };
47
38
  /** Returns a function to submit auth credentials for a pending auth request. */
48
39
  const useAdkSubmitAuth = () => {
package/dist/hooks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","names":["uuidv4"],"sources":["../src/hooks.ts"],"sourcesContent":["import { useAui } from \"@assistant-ui/store\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport type { ReadonlyJSONValue } from \"assistant-stream/utils\";\nimport { adkExtras } from \"./adkExtras\";\nimport type {\n AdkMessage,\n AdkSendMessageConfig,\n AdkToolConfirmation,\n AdkAuthCredential,\n AdkAuthRequest,\n AdkMessageMetadata,\n} from \"./types\";\n\nconst EMPTY_STATE_DELTA: Record<string, unknown> = {};\nconst EMPTY_ARTIFACT_DELTA: Record<string, number> = {};\nconst EMPTY_LONG_RUNNING_TOOL_IDS: string[] = [];\nconst EMPTY_TOOL_CONFIRMATIONS: AdkToolConfirmation[] = [];\nconst EMPTY_AUTH_REQUESTS: AdkAuthRequest[] = [];\nconst EMPTY_MESSAGE_METADATA = new Map<string, AdkMessageMetadata>();\n\n/** Returns the name and branch of the currently active ADK agent. */\nexport const useAdkAgentInfo = () =>\n adkExtras.use((e) => e.agentInfo, undefined);\n\n/** Returns the accumulated session state delta from ADK events. */\nexport const useAdkSessionState = () =>\n adkExtras.use((e) => e.stateDelta, EMPTY_STATE_DELTA);\n\n/** Returns a function to send raw ADK messages. */\nexport const useAdkSend = () => {\n const aui = useAui();\n return (messages: AdkMessage[], config: AdkSendMessageConfig) =>\n adkExtras.get(aui).send(messages, config);\n};\n\n/** Returns the IDs of long-running tools awaiting external input. */\nexport const useAdkLongRunningToolIds = () =>\n adkExtras.use((e) => e.longRunningToolIds, EMPTY_LONG_RUNNING_TOOL_IDS);\n\n/** Returns pending tool confirmation requests (from SecurityPlugin etc). */\nexport const useAdkToolConfirmations = () =>\n adkExtras.use((e) => e.toolConfirmations, EMPTY_TOOL_CONFIRMATIONS);\n\n/** Returns pending auth credential requests from tools. */\nexport const useAdkAuthRequests = () =>\n adkExtras.use((e) => e.authRequests, EMPTY_AUTH_REQUESTS);\n\n/** Returns the accumulated artifact delta (filename → version). */\nexport const useAdkArtifacts = () =>\n adkExtras.use((e) => e.artifactDelta, EMPTY_ARTIFACT_DELTA);\n\n/** Returns whether any agent has escalated (requested human handoff). */\nexport const useAdkEscalation = () => adkExtras.use((e) => e.escalated, false);\n\n/** Returns per-message metadata (grounding, citation, usage). Keyed by message ID. */\nexport const useAdkMessageMetadata = () =>\n adkExtras.use((e) => e.messageMetadata, EMPTY_MESSAGE_METADATA);\n\n// ── Convenience helpers for interactive flows ──\n\n/** Returns a function to confirm or deny a pending tool confirmation. */\nexport const useAdkConfirmTool = () => {\n const aui = useAui();\n return (\n toolCallId: string,\n confirmed: boolean,\n payload?: ReadonlyJSONValue,\n ) =>\n adkExtras.get(aui).send(\n [\n {\n id: uuidv4(),\n type: \"tool\",\n tool_call_id: toolCallId,\n name: \"adk_request_confirmation\",\n content: JSON.stringify({\n confirmed,\n ...(payload != null && { payload }),\n }),\n status: \"success\",\n },\n ],\n {},\n );\n};\n\n/** Returns a function to submit auth credentials for a pending auth request. */\nexport const useAdkSubmitAuth = () => {\n const aui = useAui();\n return (toolCallId: string, credential: AdkAuthCredential) =>\n adkExtras.get(aui).send(\n [\n {\n id: uuidv4(),\n type: \"tool\",\n tool_call_id: toolCallId,\n name: \"adk_request_credential\",\n content: JSON.stringify(credential),\n status: \"success\",\n },\n ],\n {},\n );\n};\n\n/** Returns a function to submit the user's answer for a pending `adk_request_input` HITL interrupt. */\nexport const useAdkSubmitInput = () => {\n const aui = useAui();\n return (toolCallId: string, result: ReadonlyJSONValue) =>\n adkExtras.get(aui).send(\n [\n {\n id: uuidv4(),\n type: \"tool\",\n tool_call_id: toolCallId,\n name: \"adk_request_input\",\n content: JSON.stringify({ result }),\n status: \"success\",\n },\n ],\n {},\n );\n};\n\n// ── State prefix helpers ──\n\nconst APP_PREFIX = \"app:\";\nconst USER_PREFIX = \"user:\";\nconst TEMP_PREFIX = \"temp:\";\n\nconst filterByPrefix = (\n state: Record<string, unknown>,\n prefix: string,\n): Record<string, unknown> => {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(state)) {\n if (key.startsWith(prefix)) {\n result[key.slice(prefix.length)] = state[key];\n }\n }\n return result;\n};\n\n/** Returns app-level state (keys prefixed with `app:`, prefix stripped). */\nexport const useAdkAppState = () =>\n adkExtras.use(\n (e) => filterByPrefix(e.stateDelta, APP_PREFIX),\n EMPTY_STATE_DELTA,\n );\n\n/** Returns user-level state (keys prefixed with `user:`, prefix stripped). */\nexport const useAdkUserState = () =>\n adkExtras.use(\n (e) => filterByPrefix(e.stateDelta, USER_PREFIX),\n EMPTY_STATE_DELTA,\n );\n\n/** Returns temp state (keys prefixed with `temp:`, prefix stripped). Not persisted. */\nexport const useAdkTempState = () =>\n adkExtras.use(\n (e) => filterByPrefix(e.stateDelta, TEMP_PREFIX),\n EMPTY_STATE_DELTA,\n );\n"],"mappings":";;;;AAaA,MAAM,oBAA6C,CAAC;AACpD,MAAM,uBAA+C,CAAC;AACtD,MAAM,8BAAwC,CAAC;AAC/C,MAAM,2BAAkD,CAAC;AACzD,MAAM,sBAAwC,CAAC;AAC/C,MAAM,yCAAyB,IAAI,IAAgC;;AAGnE,MAAa,wBACX,UAAU,KAAK,MAAM,EAAE,WAAW,KAAA,CAAS;;AAG7C,MAAa,2BACX,UAAU,KAAK,MAAM,EAAE,YAAY,iBAAiB;;AAGtD,MAAa,mBAAmB;CAC9B,MAAM,MAAM,OAAO;CACnB,QAAQ,UAAwB,WAC9B,UAAU,IAAI,GAAG,CAAC,CAAC,KAAK,UAAU,MAAM;AAC5C;;AAGA,MAAa,iCACX,UAAU,KAAK,MAAM,EAAE,oBAAoB,2BAA2B;;AAGxE,MAAa,gCACX,UAAU,KAAK,MAAM,EAAE,mBAAmB,wBAAwB;;AAGpE,MAAa,2BACX,UAAU,KAAK,MAAM,EAAE,cAAc,mBAAmB;;AAG1D,MAAa,wBACX,UAAU,KAAK,MAAM,EAAE,eAAe,oBAAoB;;AAG5D,MAAa,yBAAyB,UAAU,KAAK,MAAM,EAAE,WAAW,KAAK;;AAG7E,MAAa,8BACX,UAAU,KAAK,MAAM,EAAE,iBAAiB,sBAAsB;;AAKhE,MAAa,0BAA0B;CACrC,MAAM,MAAM,OAAO;CACnB,QACE,YACA,WACA,YAEA,UAAU,IAAI,GAAG,CAAC,CAAC,KACjB,CACE;EACE,IAAIA,GAAO;EACX,MAAM;EACN,cAAc;EACd,MAAM;EACN,SAAS,KAAK,UAAU;GACtB;GACA,GAAI,WAAW,QAAQ,EAAE,QAAQ;EACnC,CAAC;EACD,QAAQ;CACV,CACF,GACA,CAAC,CACH;AACJ;;AAGA,MAAa,yBAAyB;CACpC,MAAM,MAAM,OAAO;CACnB,QAAQ,YAAoB,eAC1B,UAAU,IAAI,GAAG,CAAC,CAAC,KACjB,CACE;EACE,IAAIA,GAAO;EACX,MAAM;EACN,cAAc;EACd,MAAM;EACN,SAAS,KAAK,UAAU,UAAU;EAClC,QAAQ;CACV,CACF,GACA,CAAC,CACH;AACJ;;AAGA,MAAa,0BAA0B;CACrC,MAAM,MAAM,OAAO;CACnB,QAAQ,YAAoB,WAC1B,UAAU,IAAI,GAAG,CAAC,CAAC,KACjB,CACE;EACE,IAAIA,GAAO;EACX,MAAM;EACN,cAAc;EACd,MAAM;EACN,SAAS,KAAK,UAAU,EAAE,OAAO,CAAC;EAClC,QAAQ;CACV,CACF,GACA,CAAC,CACH;AACJ;AAIA,MAAM,aAAa;AACnB,MAAM,cAAc;AACpB,MAAM,cAAc;AAEpB,MAAM,kBACJ,OACA,WAC4B;CAC5B,MAAM,SAAkC,CAAC;CACzC,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,GACjC,IAAI,IAAI,WAAW,MAAM,GACvB,OAAO,IAAI,MAAM,OAAO,MAAM,KAAK,MAAM;CAG7C,OAAO;AACT;;AAGA,MAAa,uBACX,UAAU,KACP,MAAM,eAAe,EAAE,YAAY,UAAU,GAC9C,iBACF;;AAGF,MAAa,wBACX,UAAU,KACP,MAAM,eAAe,EAAE,YAAY,WAAW,GAC/C,iBACF;;AAGF,MAAa,wBACX,UAAU,KACP,MAAM,eAAe,EAAE,YAAY,WAAW,GAC/C,iBACF"}
1
+ {"version":3,"file":"hooks.js","names":["uuidv4"],"sources":["../src/hooks.ts"],"sourcesContent":["import { useAui } from \"@assistant-ui/store\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport type { ReadonlyJSONValue } from \"assistant-stream/utils\";\nimport { adkExtras } from \"./adkExtras\";\nimport { toAdkConfirmationReply } from \"./adkToolApproval\";\nimport type {\n AdkMessage,\n AdkSendMessageConfig,\n AdkToolConfirmation,\n AdkAuthCredential,\n AdkAuthRequest,\n AdkMessageMetadata,\n} from \"./types\";\n\nconst EMPTY_STATE_DELTA: Record<string, unknown> = {};\nconst EMPTY_ARTIFACT_DELTA: Record<string, number> = {};\nconst EMPTY_LONG_RUNNING_TOOL_IDS: string[] = [];\nconst EMPTY_TOOL_CONFIRMATIONS: AdkToolConfirmation[] = [];\nconst EMPTY_AUTH_REQUESTS: AdkAuthRequest[] = [];\nconst EMPTY_MESSAGE_METADATA = new Map<string, AdkMessageMetadata>();\n\n/** Returns the name and branch of the currently active ADK agent. */\nexport const useAdkAgentInfo = () =>\n adkExtras.use((e) => e.agentInfo, undefined);\n\n/** Returns the accumulated session state delta from ADK events. */\nexport const useAdkSessionState = () =>\n adkExtras.use((e) => e.stateDelta, EMPTY_STATE_DELTA);\n\n/** Returns a function to send raw ADK messages. */\nexport const useAdkSend = () => {\n const aui = useAui();\n return (messages: AdkMessage[], config: AdkSendMessageConfig) =>\n adkExtras.get(aui).send(messages, config);\n};\n\n/** Returns the IDs of long-running tools awaiting external input. */\nexport const useAdkLongRunningToolIds = () =>\n adkExtras.use((e) => e.longRunningToolIds, EMPTY_LONG_RUNNING_TOOL_IDS);\n\n/** Returns pending tool confirmation requests (from SecurityPlugin etc). */\nexport const useAdkToolConfirmations = () =>\n adkExtras.use((e) => e.toolConfirmations, EMPTY_TOOL_CONFIRMATIONS);\n\n/** Returns pending auth credential requests from tools. */\nexport const useAdkAuthRequests = () =>\n adkExtras.use((e) => e.authRequests, EMPTY_AUTH_REQUESTS);\n\n/** Returns the accumulated artifact delta (filename → version). */\nexport const useAdkArtifacts = () =>\n adkExtras.use((e) => e.artifactDelta, EMPTY_ARTIFACT_DELTA);\n\n/** Returns whether any agent has escalated (requested human handoff). */\nexport const useAdkEscalation = () => adkExtras.use((e) => e.escalated, false);\n\n/** Returns per-message metadata (grounding, citation, usage). Keyed by message ID. */\nexport const useAdkMessageMetadata = () =>\n adkExtras.use((e) => e.messageMetadata, EMPTY_MESSAGE_METADATA);\n\n// ── Convenience helpers for interactive flows ──\n\n/** Returns a function to confirm or deny a pending tool confirmation. */\nexport const useAdkConfirmTool = () => {\n const aui = useAui();\n return (\n toolCallId: string,\n confirmed: boolean,\n payload?: ReadonlyJSONValue,\n ) =>\n adkExtras\n .get(aui)\n .send([toAdkConfirmationReply(toolCallId, confirmed, payload)], {});\n};\n\n/** Returns a function to submit auth credentials for a pending auth request. */\nexport const useAdkSubmitAuth = () => {\n const aui = useAui();\n return (toolCallId: string, credential: AdkAuthCredential) =>\n adkExtras.get(aui).send(\n [\n {\n id: uuidv4(),\n type: \"tool\",\n tool_call_id: toolCallId,\n name: \"adk_request_credential\",\n content: JSON.stringify(credential),\n status: \"success\",\n },\n ],\n {},\n );\n};\n\n/** Returns a function to submit the user's answer for a pending `adk_request_input` HITL interrupt. */\nexport const useAdkSubmitInput = () => {\n const aui = useAui();\n return (toolCallId: string, result: ReadonlyJSONValue) =>\n adkExtras.get(aui).send(\n [\n {\n id: uuidv4(),\n type: \"tool\",\n tool_call_id: toolCallId,\n name: \"adk_request_input\",\n content: JSON.stringify({ result }),\n status: \"success\",\n },\n ],\n {},\n );\n};\n\n// ── State prefix helpers ──\n\nconst APP_PREFIX = \"app:\";\nconst USER_PREFIX = \"user:\";\nconst TEMP_PREFIX = \"temp:\";\n\nconst filterByPrefix = (\n state: Record<string, unknown>,\n prefix: string,\n): Record<string, unknown> => {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(state)) {\n if (key.startsWith(prefix)) {\n result[key.slice(prefix.length)] = state[key];\n }\n }\n return result;\n};\n\n/** Returns app-level state (keys prefixed with `app:`, prefix stripped). */\nexport const useAdkAppState = () =>\n adkExtras.use(\n (e) => filterByPrefix(e.stateDelta, APP_PREFIX),\n EMPTY_STATE_DELTA,\n );\n\n/** Returns user-level state (keys prefixed with `user:`, prefix stripped). */\nexport const useAdkUserState = () =>\n adkExtras.use(\n (e) => filterByPrefix(e.stateDelta, USER_PREFIX),\n EMPTY_STATE_DELTA,\n );\n\n/** Returns temp state (keys prefixed with `temp:`, prefix stripped). Not persisted. */\nexport const useAdkTempState = () =>\n adkExtras.use(\n (e) => filterByPrefix(e.stateDelta, TEMP_PREFIX),\n EMPTY_STATE_DELTA,\n );\n"],"mappings":";;;;;AAcA,MAAM,oBAA6C,CAAC;AACpD,MAAM,uBAA+C,CAAC;AACtD,MAAM,8BAAwC,CAAC;AAC/C,MAAM,2BAAkD,CAAC;AACzD,MAAM,sBAAwC,CAAC;AAC/C,MAAM,yCAAyB,IAAI,IAAgC;;AAGnE,MAAa,wBACX,UAAU,KAAK,MAAM,EAAE,WAAW,KAAA,CAAS;;AAG7C,MAAa,2BACX,UAAU,KAAK,MAAM,EAAE,YAAY,iBAAiB;;AAGtD,MAAa,mBAAmB;CAC9B,MAAM,MAAM,OAAO;CACnB,QAAQ,UAAwB,WAC9B,UAAU,IAAI,GAAG,CAAC,CAAC,KAAK,UAAU,MAAM;AAC5C;;AAGA,MAAa,iCACX,UAAU,KAAK,MAAM,EAAE,oBAAoB,2BAA2B;;AAGxE,MAAa,gCACX,UAAU,KAAK,MAAM,EAAE,mBAAmB,wBAAwB;;AAGpE,MAAa,2BACX,UAAU,KAAK,MAAM,EAAE,cAAc,mBAAmB;;AAG1D,MAAa,wBACX,UAAU,KAAK,MAAM,EAAE,eAAe,oBAAoB;;AAG5D,MAAa,yBAAyB,UAAU,KAAK,MAAM,EAAE,WAAW,KAAK;;AAG7E,MAAa,8BACX,UAAU,KAAK,MAAM,EAAE,iBAAiB,sBAAsB;;AAKhE,MAAa,0BAA0B;CACrC,MAAM,MAAM,OAAO;CACnB,QACE,YACA,WACA,YAEA,UACG,IAAI,GAAG,CAAC,CACR,KAAK,CAAC,uBAAuB,YAAY,WAAW,OAAO,CAAC,GAAG,CAAC,CAAC;AACxE;;AAGA,MAAa,yBAAyB;CACpC,MAAM,MAAM,OAAO;CACnB,QAAQ,YAAoB,eAC1B,UAAU,IAAI,GAAG,CAAC,CAAC,KACjB,CACE;EACE,IAAIA,GAAO;EACX,MAAM;EACN,cAAc;EACd,MAAM;EACN,SAAS,KAAK,UAAU,UAAU;EAClC,QAAQ;CACV,CACF,GACA,CAAC,CACH;AACJ;;AAGA,MAAa,0BAA0B;CACrC,MAAM,MAAM,OAAO;CACnB,QAAQ,YAAoB,WAC1B,UAAU,IAAI,GAAG,CAAC,CAAC,KACjB,CACE;EACE,IAAIA,GAAO;EACX,MAAM;EACN,cAAc;EACd,MAAM;EACN,SAAS,KAAK,UAAU,EAAE,OAAO,CAAC;EAClC,QAAQ;CACV,CACF,GACA,CAAC,CACH;AACJ;AAIA,MAAM,aAAa;AACnB,MAAM,cAAc;AACpB,MAAM,cAAc;AAEpB,MAAM,kBACJ,OACA,WAC4B;CAC5B,MAAM,SAAkC,CAAC;CACzC,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,GACjC,IAAI,IAAI,WAAW,MAAM,GACvB,OAAO,IAAI,MAAM,OAAO,MAAM,KAAK,MAAM;CAG7C,OAAO;AACT;;AAGA,MAAa,uBACX,UAAU,KACP,MAAM,eAAe,EAAE,YAAY,UAAU,GAC9C,iBACF;;AAGF,MAAa,wBACX,UAAU,KACP,MAAM,eAAe,EAAE,YAAY,WAAW,GAC/C,iBACF;;AAGF,MAAa,wBACX,UAAU,KACP,MAAM,eAAe,EAAE,YAAY,WAAW,GAC/C,iBACF"}
@@ -0,0 +1,6 @@
1
+ import { AdkEvent } from "./types.js";
2
+ //#region src/parseAdkEvent.d.ts
3
+ declare function parseAdkEventValue(value: unknown, errorPrefix: string): AdkEvent;
4
+ //#endregion
5
+ export { parseAdkEventValue };
6
+ //# sourceMappingURL=parseAdkEvent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseAdkEvent.d.ts","names":[],"sources":["../src/parseAdkEvent.ts"],"mappings":";;iBAEgB,mBACd,gBACA,sBACC"}
@@ -0,0 +1,16 @@
1
+ //#region src/parseAdkEvent.ts
2
+ function parseAdkEventValue(value, errorPrefix) {
3
+ if (typeof value !== "object" || value === null || Array.isArray(value) || Object.keys(value).length === 0) throw new Error(`${errorPrefix}: expected a non-empty object.`);
4
+ const { id: rawId, ...event } = value;
5
+ if (rawId != null && typeof rawId !== "string" && (typeof rawId !== "number" || !Number.isFinite(rawId))) throw new Error(`${errorPrefix}: expected "id" to be a string or finite number when present.`);
6
+ const errorMessage = "error" in event && typeof event.error === "string" ? event.error : void 0;
7
+ return {
8
+ ...event,
9
+ ...rawId != null && { id: String(rawId) },
10
+ ...errorMessage !== void 0 && !("errorMessage" in event) && !("error_message" in event) && { errorMessage }
11
+ };
12
+ }
13
+ //#endregion
14
+ export { parseAdkEventValue };
15
+
16
+ //# sourceMappingURL=parseAdkEvent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseAdkEvent.js","names":[],"sources":["../src/parseAdkEvent.ts"],"sourcesContent":["import type { AdkEvent } from \"./types\";\n\nexport function parseAdkEventValue(\n value: unknown,\n errorPrefix: string,\n): AdkEvent {\n if (\n typeof value !== \"object\" ||\n value === null ||\n Array.isArray(value) ||\n Object.keys(value).length === 0\n ) {\n throw new Error(`${errorPrefix}: expected a non-empty object.`);\n }\n\n const { id: rawId, ...event } = value as Record<string, unknown>;\n if (\n rawId != null &&\n typeof rawId !== \"string\" &&\n (typeof rawId !== \"number\" || !Number.isFinite(rawId))\n ) {\n throw new Error(\n `${errorPrefix}: expected \"id\" to be a string or finite number when present.`,\n );\n }\n\n const errorMessage =\n \"error\" in event && typeof event.error === \"string\"\n ? event.error\n : undefined;\n return {\n ...event,\n ...(rawId != null && { id: String(rawId) }),\n ...(errorMessage !== undefined &&\n !(\"errorMessage\" in event) &&\n !(\"error_message\" in event) && {\n errorMessage,\n }),\n } as AdkEvent;\n}\n"],"mappings":";AAEA,SAAgB,mBACd,OACA,aACU;CACV,IACE,OAAO,UAAU,YACjB,UAAU,QACV,MAAM,QAAQ,KAAK,KACnB,OAAO,KAAK,KAAK,CAAC,CAAC,WAAW,GAE9B,MAAM,IAAI,MAAM,GAAG,YAAY,+BAA+B;CAGhE,MAAM,EAAE,IAAI,OAAO,GAAG,UAAU;CAChC,IACE,SAAS,QACT,OAAO,UAAU,aAChB,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,IAEpD,MAAM,IAAI,MACR,GAAG,YAAY,8DACjB;CAGF,MAAM,eACJ,WAAW,SAAS,OAAO,MAAM,UAAU,WACvC,MAAM,QACN,KAAA;CACN,OAAO;EACL,GAAG;EACH,GAAI,SAAS,QAAQ,EAAE,IAAI,OAAO,KAAK,EAAE;EACzC,GAAI,iBAAiB,KAAA,KACnB,EAAE,kBAAkB,UACpB,EAAE,mBAAmB,UAAU,EAC7B,aACF;CACJ;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"parseAdkRequest.d.ts","names":[],"sources":["../../src/server/parseAdkRequest.ts"],"mappings":";;KAEK;EAEC;EACA;EACA,QAAQ,MAAM;EACd,QAAQ;EACR,aAAa;;EAGb;EACA;EACA;EACA;EACA;EACA,QAAQ;EACR,aAAa;;;;;;;;;;;;;;;;;;;;;;;cAwBN,kBACX,SAAS,YACR,QAAQ;;;;;;;;;;;;;cAwDE,eACX,QAAQ;EACL;EAAc,OAAO,MAAM"}
1
+ {"version":3,"file":"parseAdkRequest.d.ts","names":[],"sources":["../../src/server/parseAdkRequest.ts"],"mappings":";;KAGK;EAEC;EACA;EACA,QAAQ,MAAM;EACd,QAAQ;EACR,aAAa;;EAGb;EACA;EACA;EACA;EACA;EACA,QAAQ;EACR,aAAa;;;;;;;;;;;;;;;;;;;;;;;cA6HN,kBACX,SAAS,YACR,QAAQ;;;;;;;;;;;;;cAsFE,eACX,QAAQ;EACL;EAAc,OAAO,MAAM"}
@@ -1,4 +1,57 @@
1
+ import { isRecord } from "@assistant-ui/core/internal";
1
2
  //#region src/server/parseAdkRequest.ts
3
+ const invalidField = (field, expectation) => /* @__PURE__ */ new Error(`Invalid Google ADK proxy request field "${field}": expected ${expectation}.`);
4
+ const readString = (body, field) => {
5
+ const value = body[field];
6
+ if (typeof value !== "string") throw invalidField(field, "a string");
7
+ return value;
8
+ };
9
+ const readOptionalString = (body, field) => {
10
+ const value = body[field];
11
+ if (value === void 0) return void 0;
12
+ if (typeof value !== "string") throw invalidField(field, "a string");
13
+ return value;
14
+ };
15
+ const validateOptionalPartString = (part, key, field) => {
16
+ if (part[key] !== void 0 && typeof part[key] !== "string") throw invalidField(field, "a string");
17
+ };
18
+ const validatePart = (part, index) => {
19
+ const field = (name) => `parts[${index}].${name}`;
20
+ if (Object.keys(part).length === 0) throw invalidField(`parts[${index}]`, "a non-empty part object");
21
+ if (part.thought !== void 0 && typeof part.thought !== "boolean") throw invalidField(field("thought"), "a boolean");
22
+ if (part.text !== void 0 && typeof part.text !== "string") throw invalidField(field("text"), "a string");
23
+ const validateRecordField = (key, validateContent) => {
24
+ const value = part[key];
25
+ if (value === void 0) return;
26
+ if (!isRecord(value)) throw invalidField(field(key), "an object");
27
+ validateContent(value);
28
+ };
29
+ validateRecordField("functionCall", (content) => {
30
+ validateOptionalPartString(content, "name", field("functionCall.name"));
31
+ validateOptionalPartString(content, "id", field("functionCall.id"));
32
+ if (content.args !== void 0 && !isRecord(content.args)) throw invalidField(field("functionCall.args"), "an object");
33
+ });
34
+ validateRecordField("functionResponse", (content) => {
35
+ validateOptionalPartString(content, "name", field("functionResponse.name"));
36
+ validateOptionalPartString(content, "id", field("functionResponse.id"));
37
+ });
38
+ validateRecordField("executableCode", (content) => {
39
+ validateOptionalPartString(content, "code", field("executableCode.code"));
40
+ validateOptionalPartString(content, "language", field("executableCode.language"));
41
+ });
42
+ validateRecordField("codeExecutionResult", (content) => {
43
+ validateOptionalPartString(content, "output", field("codeExecutionResult.output"));
44
+ validateOptionalPartString(content, "outcome", field("codeExecutionResult.outcome"));
45
+ });
46
+ validateRecordField("inlineData", (content) => {
47
+ validateOptionalPartString(content, "mimeType", field("inlineData.mimeType"));
48
+ validateOptionalPartString(content, "data", field("inlineData.data"));
49
+ });
50
+ validateRecordField("fileData", (content) => {
51
+ validateOptionalPartString(content, "fileUri", field("fileData.fileUri"));
52
+ validateOptionalPartString(content, "mimeType", field("fileData.mimeType"));
53
+ });
54
+ };
2
55
  /**
3
56
  * Parses an incoming HTTP request into a structured ADK request.
4
57
  *
@@ -27,24 +80,37 @@ const parseAdkRequest = async (request) => {
27
80
  } catch {
28
81
  throw new Error("Invalid JSON in Google ADK proxy request body. Expected a JSON object like {\"message\":\"Hello\"} or {\"type\":\"tool-result\",...}.");
29
82
  }
30
- if (!body || typeof body !== "object" || Array.isArray(body)) throw new Error("Google ADK proxy request body must be a JSON object");
83
+ if (!isRecord(body)) throw new Error("Google ADK proxy request body must be a JSON object");
31
84
  const config = {};
32
85
  if (body.runConfig !== void 0) config.runConfig = body.runConfig;
33
- if (body.checkpointId !== void 0) config.checkpointId = body.checkpointId;
86
+ const checkpointId = readOptionalString(body, "checkpointId");
87
+ if (checkpointId !== void 0) config.checkpointId = checkpointId;
34
88
  const stateDelta = body.stateDelta;
35
- if (body.type === "tool-result") return {
36
- type: "tool-result",
37
- toolCallId: body.toolCallId ?? "",
38
- toolName: body.toolName ?? "",
39
- result: body.result,
40
- isError: body.isError ?? false,
41
- config,
42
- ...stateDelta != null && { stateDelta }
43
- };
89
+ if (stateDelta !== void 0 && !isRecord(stateDelta)) throw invalidField("stateDelta", "an object");
90
+ if (body.type === "tool-result") {
91
+ if (!("result" in body)) throw invalidField("result", "a value");
92
+ if (body.isError !== void 0 && typeof body.isError !== "boolean") throw invalidField("isError", "a boolean");
93
+ return {
94
+ type: "tool-result",
95
+ toolCallId: readString(body, "toolCallId"),
96
+ toolName: readString(body, "toolName"),
97
+ result: body.result,
98
+ isError: body.isError ?? false,
99
+ config,
100
+ ...stateDelta != null && { stateDelta }
101
+ };
102
+ }
103
+ if (body.type !== void 0 && body.type !== "message") throw invalidField("type", "\"message\", \"tool-result\", or omitted");
104
+ const text = body.message;
105
+ if (text !== void 0 && typeof text !== "string") throw invalidField("message", "a string");
106
+ const parts = body.parts;
107
+ if (parts !== void 0 && (!Array.isArray(parts) || !parts.every(isRecord))) throw invalidField("parts", "an array of objects");
108
+ parts?.forEach(validatePart);
109
+ if (!("message" in body) && !("parts" in body)) throw new Error("Invalid Google ADK proxy request: expected a \"message\" string or a \"parts\" array.");
44
110
  return {
45
111
  type: "message",
46
- text: body.message ?? "",
47
- ...body.parts != null && { parts: body.parts },
112
+ text: text ?? "",
113
+ ...parts !== void 0 && { parts },
48
114
  config,
49
115
  ...stateDelta != null && { stateDelta }
50
116
  };
@@ -1 +1 @@
1
- {"version":3,"file":"parseAdkRequest.js","names":[],"sources":["../../src/server/parseAdkRequest.ts"],"sourcesContent":["import type { AdkSendMessageConfig } from \"../types\";\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\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: Record<string, unknown>;\n try {\n body = (await request.json()) as Record<string, unknown>;\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 (!body || typeof body !== \"object\" || Array.isArray(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 if (body.checkpointId !== undefined)\n config.checkpointId = body.checkpointId as string;\n\n const stateDelta = body.stateDelta as Record<string, unknown> | undefined;\n\n if (body.type === \"tool-result\") {\n return {\n type: \"tool-result\",\n toolCallId: (body.toolCallId as string) ?? \"\",\n toolName: (body.toolName as string) ?? \"\",\n result: body.result,\n isError: (body.isError as boolean) ?? false,\n config,\n ...(stateDelta != null && { stateDelta }),\n };\n }\n\n return {\n type: \"message\",\n text: (body.message as string) ?? \"\",\n ...(body.parts != null && {\n parts: body.parts as Array<Record<string, unknown>>,\n }),\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: parsed.result,\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":";;;;;;;;;;;;;;;;;;;;;;AAyCA,MAAa,kBAAkB,OAC7B,YAC8B;CAC9B,IAAI;CACJ,IAAI;EACF,OAAQ,MAAM,QAAQ,KAAK;CAC7B,QAAQ;EACN,MAAM,IAAI,MACR,uIACF;CACF;CAEA,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GACzD,MAAM,IAAI,MAAM,qDAAqD;CAGvE,MAAM,SAA+B,CAAC;CACtC,IAAI,KAAK,cAAc,KAAA,GAAW,OAAO,YAAY,KAAK;CAC1D,IAAI,KAAK,iBAAiB,KAAA,GACxB,OAAO,eAAe,KAAK;CAE7B,MAAM,aAAa,KAAK;CAExB,IAAI,KAAK,SAAS,eAChB,OAAO;EACL,MAAM;EACN,YAAa,KAAK,cAAyB;EAC3C,UAAW,KAAK,YAAuB;EACvC,QAAQ,KAAK;EACb,SAAU,KAAK,WAAuB;EACtC;EACA,GAAI,cAAc,QAAQ,EAAE,WAAW;CACzC;CAGF,OAAO;EACL,MAAM;EACN,MAAO,KAAK,WAAsB;EAClC,GAAI,KAAK,SAAS,QAAQ,EACxB,OAAO,KAAK,MACd;EACA;EACA,GAAI,cAAc,QAAQ,EAAE,WAAW;CACzC;AACF;;;;;;;;;;;;;AAcA,MAAa,gBACX,WAC4D;CAC5D,IAAI,OAAO,SAAS,eAClB,OAAO;EACL,MAAM;EACN,OAAO,CACL,EACE,kBAAkB;GAChB,MAAM,OAAO;GACb,IAAI,OAAO;GACX,UAAU,OAAO;EACnB,EACF,CACF;CACF;CAIF,IAAI,OAAO,OAAO,QAChB,OAAO;EAAE,MAAM;EAAQ,OAAO,OAAO;CAAM;CAG7C,OAAO;EAAE,MAAM;EAAQ,OAAO,CAAC,EAAE,MAAM,OAAO,KAAK,CAAC;CAAE;AACxD"}
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\";\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: parsed.result,\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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"}