@assistant-ui/react-google-adk 0.0.30 → 0.0.31

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 (72) hide show
  1. package/dist/AdkClient.d.ts +2 -3
  2. package/dist/AdkClient.d.ts.map +1 -1
  3. package/dist/AdkEventAccumulator.d.ts +3 -5
  4. package/dist/AdkEventAccumulator.d.ts.map +1 -1
  5. package/dist/AdkEventAccumulator.js +63 -99
  6. package/dist/AdkEventAccumulator.js.map +1 -1
  7. package/dist/AdkSessionAdapter.d.ts +7 -4
  8. package/dist/AdkSessionAdapter.d.ts.map +1 -1
  9. package/dist/AdkSessionAdapter.js +7 -4
  10. package/dist/AdkSessionAdapter.js.map +1 -1
  11. package/dist/adkExtras.d.ts +1 -2
  12. package/dist/adkExtras.d.ts.map +1 -1
  13. package/dist/adkToolApproval.d.ts +6 -7
  14. package/dist/adkToolApproval.d.ts.map +1 -1
  15. package/dist/adkToolApproval.js +1 -3
  16. package/dist/adkToolApproval.js.map +1 -1
  17. package/dist/contentToParts.d.ts +1 -2
  18. package/dist/contentToParts.d.ts.map +1 -1
  19. package/dist/convertAdkMessages.d.ts +2 -3
  20. package/dist/convertAdkMessages.d.ts.map +1 -1
  21. package/dist/convertToAdkMessages.d.ts +5 -6
  22. package/dist/convertToAdkMessages.d.ts.map +1 -1
  23. package/dist/hooks.d.ts +15 -16
  24. package/dist/hooks.d.ts.map +1 -1
  25. package/dist/hooks.js +1 -1
  26. package/dist/normalizeAdkPart.d.ts +4 -0
  27. package/dist/normalizeAdkPart.d.ts.map +1 -0
  28. package/dist/normalizeAdkPart.js +31 -0
  29. package/dist/normalizeAdkPart.js.map +1 -0
  30. package/dist/parseAdkEvent.d.ts +1 -2
  31. package/dist/parseAdkEvent.d.ts.map +1 -1
  32. package/dist/sdkIdentity.d.ts +1 -2
  33. package/dist/sdkIdentity.d.ts.map +1 -1
  34. package/dist/sdkIdentity.js +1 -1
  35. package/dist/server/adkEventStream.d.ts +2 -3
  36. package/dist/server/adkEventStream.d.ts.map +1 -1
  37. package/dist/server/createAdkApiRoute.d.ts +2 -3
  38. package/dist/server/createAdkApiRoute.d.ts.map +1 -1
  39. package/dist/server/parseAdkRequest.d.ts +2 -3
  40. package/dist/server/parseAdkRequest.d.ts.map +1 -1
  41. package/dist/structuredEvents.d.ts +1 -2
  42. package/dist/structuredEvents.d.ts.map +1 -1
  43. package/dist/structuredEvents.js +1 -1
  44. package/dist/structuredEvents.js.map +1 -1
  45. package/dist/toAdkFunctionResponse.d.ts +2 -3
  46. package/dist/toAdkFunctionResponse.d.ts.map +1 -1
  47. package/dist/trimTrailingSlashes.d.ts +1 -2
  48. package/dist/trimTrailingSlashes.d.ts.map +1 -1
  49. package/dist/types.d.ts +22 -23
  50. package/dist/types.d.ts.map +1 -1
  51. package/dist/types.js.map +1 -1
  52. package/dist/useAdkMessages.d.ts +2 -3
  53. package/dist/useAdkMessages.d.ts.map +1 -1
  54. package/dist/useAdkMessages.js +2 -0
  55. package/dist/useAdkMessages.js.map +1 -1
  56. package/dist/useAdkRuntime.d.ts +2 -3
  57. package/dist/useAdkRuntime.d.ts.map +1 -1
  58. package/dist/useAdkRuntime.js +1 -1
  59. package/package.json +10 -9
  60. package/src/AdkEventAccumulator.test.ts +312 -11
  61. package/src/AdkEventAccumulator.ts +68 -142
  62. package/src/AdkSessionAdapter.test.ts +267 -1
  63. package/src/AdkSessionAdapter.ts +24 -5
  64. package/src/adkToolApproval.test.ts +0 -4
  65. package/src/adkToolApproval.ts +1 -3
  66. package/src/normalizeAdkPart.ts +35 -0
  67. package/src/structuredEvents.test.ts +69 -0
  68. package/src/structuredEvents.ts +4 -1
  69. package/src/types.ts +1 -1
  70. package/src/useAdkMessages.test.ts +102 -3
  71. package/src/useAdkMessages.ts +2 -0
  72. package/src/useAdkRuntime.replacement.test.tsx +11 -7
@@ -246,6 +246,78 @@ describe("AdkEventAccumulator - function calls", () => {
246
246
  );
247
247
  expect(acc.getToolConfirmations()).toHaveLength(0);
248
248
  });
249
+
250
+ const requestEvent = (name: string) =>
251
+ makeEvent({
252
+ author: "agent",
253
+ content: {
254
+ role: "model",
255
+ parts: [{ functionCall: { name, id: "rc-1" } }, { text: "still here" }],
256
+ },
257
+ });
258
+
259
+ it("keeps an args-less confirmation call answerable", () => {
260
+ const acc = new AdkEventAccumulator();
261
+ const msgs = acc.processEvent(requestEvent("adk_request_confirmation"));
262
+
263
+ expect(acc.getToolConfirmations()).toEqual([
264
+ {
265
+ toolCallId: "rc-1",
266
+ toolName: "",
267
+ args: {},
268
+ hint: "",
269
+ confirmed: false,
270
+ },
271
+ ]);
272
+ expect((msgs[0] as AdkMessage & { type: "ai" }).tool_calls).toEqual([
273
+ {
274
+ id: "rc-1",
275
+ name: "adk_request_confirmation",
276
+ args: {},
277
+ argsText: "{}",
278
+ },
279
+ ]);
280
+ expect(msgs[0]).toMatchObject({
281
+ content: [{ type: "text", text: "still here" }],
282
+ });
283
+ });
284
+
285
+ it("keeps an args-less credential call answerable", () => {
286
+ const acc = new AdkEventAccumulator();
287
+ const msgs = acc.processEvent(requestEvent("adk_request_credential"));
288
+
289
+ expect(acc.getAuthRequests()).toEqual([
290
+ { toolCallId: "rc-1", authConfig: undefined },
291
+ ]);
292
+ expect((msgs[0] as AdkMessage & { type: "ai" }).tool_calls).toEqual([
293
+ {
294
+ id: "rc-1",
295
+ name: "adk_request_credential",
296
+ args: {},
297
+ argsText: "{}",
298
+ },
299
+ ]);
300
+ expect(msgs[0]).toMatchObject({
301
+ content: [{ type: "text", text: "still here" }],
302
+ });
303
+ });
304
+
305
+ it("defaults an ordinary call without args to empty args", () => {
306
+ const acc = new AdkEventAccumulator();
307
+ const msgs = acc.processEvent(
308
+ makeEvent({
309
+ author: "agent",
310
+ content: {
311
+ role: "model",
312
+ parts: [{ functionCall: { name: "search", id: "tc-1" } }],
313
+ },
314
+ }),
315
+ );
316
+
317
+ expect((msgs[0] as AdkMessage & { type: "ai" }).tool_calls).toEqual([
318
+ { id: "tc-1", name: "search", args: {}, argsText: "{}" },
319
+ ]);
320
+ });
249
321
  });
250
322
 
251
323
  describe("AdkEventAccumulator - function responses", () => {
@@ -1102,21 +1174,176 @@ describe("AdkEventAccumulator - special function calls", () => {
1102
1174
  });
1103
1175
  });
1104
1176
 
1105
- it("records auth request from adk_request_credential", () => {
1177
+ it.each([
1178
+ [
1179
+ "snake_case",
1180
+ { function_call_id: "tc-original", auth_config: { type: "oauth2" } },
1181
+ ],
1182
+ [
1183
+ "camelCase",
1184
+ { functionCallId: "tc-original", authConfig: { type: "oauth2" } },
1185
+ ],
1186
+ ])(
1187
+ "records an auth request under its adk_request_credential call id (%s args)",
1188
+ (_, args) => {
1189
+ const acc = new AdkEventAccumulator();
1190
+ acc.processEvent(
1191
+ makeEvent({
1192
+ author: "agent",
1193
+ content: {
1194
+ role: "model",
1195
+ parts: [
1196
+ {
1197
+ functionCall: {
1198
+ name: "adk_request_credential",
1199
+ id: "cred-1",
1200
+ args,
1201
+ },
1202
+ },
1203
+ ],
1204
+ },
1205
+ }),
1206
+ );
1207
+ expect(acc.getAuthRequests()).toEqual([
1208
+ { toolCallId: "cred-1", authConfig: { type: "oauth2" } },
1209
+ ]);
1210
+ },
1211
+ );
1212
+ });
1213
+
1214
+ describe.each([
1215
+ {
1216
+ name: "adk_request_confirmation",
1217
+ args: {
1218
+ originalFunctionCall: { id: "gated", name: "transfer", args: {} },
1219
+ toolConfirmation: { hint: "Transfer?" },
1220
+ },
1221
+ actions: {
1222
+ requestedToolConfirmations: { gated: { hint: "Transfer?" } },
1223
+ },
1224
+ reply: { confirmed: true },
1225
+ pending: (acc: AdkEventAccumulator) =>
1226
+ acc.getToolConfirmations().map((c) => c.toolCallId),
1227
+ },
1228
+ {
1229
+ name: "adk_request_credential",
1230
+ args: { function_call_id: "gated", auth_config: { credentialKey: "k" } },
1231
+ actions: {
1232
+ requestedAuthConfigs: {
1233
+ gated: { credentialKey: "k" },
1234
+ "gated-2": { credentialKey: "k" },
1235
+ },
1236
+ },
1237
+ reply: { exchangedAuthCredential: { authType: "apiKey", apiKey: "key" } },
1238
+ pending: (acc: AdkEventAccumulator) =>
1239
+ acc.getAuthRequests().map((r) => r.toolCallId),
1240
+ },
1241
+ ])("AdkEventAccumulator - pending $name requests", (request) => {
1242
+ const requestEvents: AdkEvent[] = [
1243
+ makeEvent({
1244
+ id: "call",
1245
+ author: "agent",
1246
+ content: {
1247
+ role: "model",
1248
+ parts: [
1249
+ { functionCall: { name: "transfer", id: "gated", args: {} } },
1250
+ { functionCall: { name: "transfer", id: "gated-2", args: {} } },
1251
+ ],
1252
+ },
1253
+ }),
1254
+ makeEvent({
1255
+ id: "request",
1256
+ author: "agent",
1257
+ longRunningToolIds: ["req"],
1258
+ actions: request.actions,
1259
+ content: {
1260
+ role: "user",
1261
+ parts: [
1262
+ {
1263
+ functionCall: { name: request.name, id: "req", args: request.args },
1264
+ },
1265
+ ],
1266
+ },
1267
+ }),
1268
+ makeEvent({
1269
+ id: "interim",
1270
+ author: "agent",
1271
+ actions: request.actions,
1272
+ content: {
1273
+ role: "user",
1274
+ parts: [
1275
+ {
1276
+ functionResponse: {
1277
+ id: "gated",
1278
+ name: "transfer",
1279
+ response: { status: "pending" },
1280
+ },
1281
+ },
1282
+ ],
1283
+ },
1284
+ }),
1285
+ ];
1286
+ const replyPart = {
1287
+ functionResponse: {
1288
+ id: "req",
1289
+ name: request.name,
1290
+ response: request.reply,
1291
+ },
1292
+ };
1293
+
1294
+ it("lists the request once, under its call, until the user replies to that call", () => {
1295
+ const acc = new AdkEventAccumulator();
1296
+ for (const event of requestEvents) acc.processEvent(event);
1297
+ expect(request.pending(acc)).toEqual(["req"]);
1298
+
1299
+ acc.processEvent(
1300
+ makeEvent({
1301
+ id: "reply",
1302
+ author: "user",
1303
+ content: { role: "user", parts: [replyPart] },
1304
+ }),
1305
+ );
1306
+ expect(request.pending(acc)).toEqual([]);
1307
+ });
1308
+
1309
+ it("carries the request into an accumulator seeded with the thread, which an optimistic reply settles", () => {
1310
+ const replayed = new AdkEventAccumulator();
1311
+ let messages: AdkMessage[] = [];
1312
+ for (const event of requestEvents) messages = replayed.processEvent(event);
1313
+
1314
+ const acc = new AdkEventAccumulator(messages);
1315
+ expect(request.pending(acc)).toEqual(["req"]);
1316
+
1317
+ acc.processEvent(
1318
+ makeEvent({
1319
+ id: "optimistic",
1320
+ content: { role: "user", parts: [replyPart] },
1321
+ }),
1322
+ );
1323
+ expect(request.pending(acc)).toEqual([]);
1324
+ });
1325
+ });
1326
+
1327
+ describe("AdkEventAccumulator - confirmation replies", () => {
1328
+ it.each([
1329
+ [["req"], "an unreadable reply", { response: "not json" }],
1330
+ [[], "a denial", { confirmed: false }],
1331
+ ])("lists %j after %s", (pending, _, response) => {
1106
1332
  const acc = new AdkEventAccumulator();
1107
1333
  acc.processEvent(
1108
1334
  makeEvent({
1335
+ id: "request",
1109
1336
  author: "agent",
1337
+ longRunningToolIds: ["req"],
1110
1338
  content: {
1111
1339
  role: "model",
1112
1340
  parts: [
1113
1341
  {
1114
1342
  functionCall: {
1115
- name: "adk_request_credential",
1116
- id: "cred-1",
1343
+ name: "adk_request_confirmation",
1344
+ id: "req",
1117
1345
  args: {
1118
- function_call_id: "tc-original",
1119
- auth_config: { type: "oauth2" },
1346
+ originalFunctionCall: { id: "gated", name: "transfer" },
1120
1347
  },
1121
1348
  },
1122
1349
  },
@@ -1124,12 +1351,27 @@ describe("AdkEventAccumulator - special function calls", () => {
1124
1351
  },
1125
1352
  }),
1126
1353
  );
1127
- const reqs = acc.getAuthRequests();
1128
- expect(reqs).toHaveLength(1);
1129
- expect(reqs[0]).toMatchObject({
1130
- toolCallId: "tc-original",
1131
- authConfig: { type: "oauth2" },
1132
- });
1354
+ acc.processEvent(
1355
+ makeEvent({
1356
+ id: "reply",
1357
+ author: "user",
1358
+ content: {
1359
+ role: "user",
1360
+ parts: [
1361
+ {
1362
+ functionResponse: {
1363
+ id: "req",
1364
+ name: "adk_request_confirmation",
1365
+ response,
1366
+ },
1367
+ },
1368
+ ],
1369
+ },
1370
+ }),
1371
+ );
1372
+ expect(acc.getToolConfirmations().map((c) => c.toolCallId)).toEqual(
1373
+ pending,
1374
+ );
1133
1375
  });
1134
1376
  });
1135
1377
 
@@ -1691,6 +1933,65 @@ describe("AdkEventAccumulator - user message handling", () => {
1691
1933
  });
1692
1934
  });
1693
1935
 
1936
+ it.each([
1937
+ {
1938
+ name: "inlineData without mimeType",
1939
+ part: { inlineData: { data: "abc123" } },
1940
+ },
1941
+ {
1942
+ name: "inlineData without data",
1943
+ part: { inlineData: { mimeType: "image/png" } },
1944
+ },
1945
+ {
1946
+ name: "fileData without fileUri",
1947
+ part: { fileData: { mimeType: "image/png" } },
1948
+ },
1949
+ ])("skips malformed $name while preserving valid content", ({ part }) => {
1950
+ const acc = new AdkEventAccumulator();
1951
+ const msgs = acc.processEvent(
1952
+ makeEvent({
1953
+ author: "agent",
1954
+ content: {
1955
+ role: "model",
1956
+ parts: [part as any, { text: "still here" }],
1957
+ },
1958
+ }),
1959
+ );
1960
+
1961
+ expect(msgs).toMatchObject([
1962
+ { type: "ai", content: [{ type: "text", text: "still here" }] },
1963
+ ]);
1964
+ });
1965
+
1966
+ it("skips malformed user media without creating an empty human message", () => {
1967
+ const acc = new AdkEventAccumulator();
1968
+ const malformedMedia = [
1969
+ { inlineData: { data: "abc123" } },
1970
+ { fileData: { mimeType: "application/pdf" } },
1971
+ ] as any[];
1972
+
1973
+ expect(
1974
+ acc.processEvent(
1975
+ makeEvent({
1976
+ author: "user",
1977
+ content: { role: "user", parts: malformedMedia },
1978
+ }),
1979
+ ),
1980
+ ).toEqual([]);
1981
+
1982
+ const msgs = acc.processEvent(
1983
+ makeEvent({
1984
+ author: "user",
1985
+ content: {
1986
+ role: "user",
1987
+ parts: [...malformedMedia, { text: "still here" }],
1988
+ },
1989
+ }),
1990
+ );
1991
+
1992
+ expect(msgs).toMatchObject([{ type: "human", content: "still here" }]);
1993
+ });
1994
+
1694
1995
  it("tool result events (no author, role:'user') still create tool messages", () => {
1695
1996
  // Regression: the user-author check must not hijack tool events.
1696
1997
  // messageToEvent for `type:'tool'` emits events without `author`,
@@ -12,6 +12,8 @@ import type {
12
12
  AdkMessageMetadata,
13
13
  } from "./types";
14
14
  import type { ReadonlyJSONObject } from "assistant-stream/utils";
15
+ import { normalizeAdkPart } from "./normalizeAdkPart";
16
+ import { projectAdkToolApprovals } from "./adkToolApproval";
15
17
  import { isAdkFunctionError } from "./toAdkFunctionResponse";
16
18
 
17
19
  type InProgressMessage = AdkMessage & { type: "ai" };
@@ -99,56 +101,27 @@ const finishReasonToStatus = (
99
101
  return { type: "complete", reason: "stop" };
100
102
  };
101
103
 
102
- const inlineDataToPart = (
103
- mimeType: string,
104
- data: string,
105
- ): AdkMessageContentPart =>
106
- mimeType.startsWith("image/")
107
- ? { type: "image", mimeType, data }
108
- : { type: "file", mimeType, data };
109
-
110
- const fileDataToPart = (
111
- fileUri: string,
112
- mimeType: string | undefined,
113
- ): AdkMessageContentPart =>
114
- mimeType == null || mimeType.startsWith("image/")
104
+ const mediaToContentPart = ({
105
+ inlineData,
106
+ fileData,
107
+ }: Record<string, unknown>): AdkMessageContentPart | undefined => {
108
+ if (isRecord(inlineData)) {
109
+ const { mimeType, data } = inlineData;
110
+ if (typeof mimeType !== "string" || typeof data !== "string") return;
111
+ return mimeType.startsWith("image/")
112
+ ? { type: "image", mimeType, data }
113
+ : { type: "file", mimeType, data };
114
+ }
115
+ if (!isRecord(fileData)) return;
116
+ const { fileUri, mimeType } = fileData;
117
+ if (typeof fileUri !== "string") return;
118
+ return typeof mimeType !== "string" || mimeType.startsWith("image/")
115
119
  ? { type: "image_url", url: fileUri }
116
120
  : { type: "file_url", url: fileUri, mimeType };
121
+ };
117
122
 
118
123
  // ── Snake_case normalization ──
119
124
 
120
- const normalizeEventPart = (part: AdkEventPart): AdkEventPart => {
121
- const p = part as Record<string, unknown>;
122
- const result: Record<string, unknown> = { ...p };
123
- if ("function_call" in p && !("functionCall" in p))
124
- result.functionCall = p.function_call;
125
- if ("function_response" in p && !("functionResponse" in p))
126
- result.functionResponse = p.function_response;
127
- if ("inline_data" in p && !("inlineData" in p))
128
- result.inlineData = p.inline_data;
129
- if ("file_data" in p && !("fileData" in p)) result.fileData = p.file_data;
130
- if (isRecord(result.inlineData)) {
131
- const data = result.inlineData;
132
- if ("mime_type" in data && !("mimeType" in data))
133
- result.inlineData = { ...data, mimeType: data.mime_type };
134
- }
135
- if (isRecord(result.fileData)) {
136
- const data = result.fileData;
137
- result.fileData = {
138
- ...data,
139
- ...("mime_type" in data &&
140
- !("mimeType" in data) && { mimeType: data.mime_type }),
141
- ...("file_uri" in data &&
142
- !("fileUri" in data) && { fileUri: data.file_uri }),
143
- };
144
- }
145
- if ("executable_code" in p && !("executableCode" in p))
146
- result.executableCode = p.executable_code;
147
- if ("code_execution_result" in p && !("codeExecutionResult" in p))
148
- result.codeExecutionResult = p.code_execution_result;
149
- return result as AdkEventPart;
150
- };
151
-
152
125
  const normalizeEvent = (event: AdkEvent): AdkEvent => {
153
126
  const e = event as Record<string, unknown>;
154
127
  const result: Record<string, unknown> = { ...e };
@@ -195,8 +168,8 @@ const normalizeEvent = (event: AdkEvent): AdkEvent => {
195
168
 
196
169
  if (result.content && (result.content as Record<string, unknown>).parts) {
197
170
  const content = result.content as Record<string, unknown>;
198
- const parts = content.parts as AdkEventPart[];
199
- result.content = { ...content, parts: parts.map(normalizeEventPart) };
171
+ const parts = content.parts as Record<string, unknown>[];
172
+ result.content = { ...content, parts: parts.map(normalizeAdkPart) };
200
173
  }
201
174
 
202
175
  return result as AdkEvent;
@@ -220,8 +193,6 @@ export class AdkEventAccumulator {
220
193
  } = {};
221
194
  private lastTransferToAgent: string | undefined;
222
195
  private pendingLongRunningToolIds = new Set<string>();
223
- private toolConfirmations: AdkToolConfirmation[] = [];
224
- private authRequests: AdkAuthRequest[] = [];
225
196
  private escalated = false;
226
197
  private messageMetadataMap = new Map<string, AdkMessageMetadata>();
227
198
  // How many assistant messages each event has opened, so a replay of that
@@ -273,32 +244,6 @@ export class AdkEventAccumulator {
273
244
  }
274
245
  }
275
246
 
276
- // Track tool confirmations from actions
277
- if (event.actions?.requestedToolConfirmations) {
278
- for (const [tcId, conf] of Object.entries(
279
- event.actions.requestedToolConfirmations,
280
- )) {
281
- const c = conf as Record<string, unknown>;
282
- this.toolConfirmations.push({
283
- toolCallId: tcId,
284
- toolName: "",
285
- args: {},
286
- hint: (c.hint as string) ?? "",
287
- confirmed: false,
288
- payload: c.payload,
289
- });
290
- }
291
- }
292
-
293
- // Track auth requests from actions
294
- if (event.actions?.requestedAuthConfigs) {
295
- for (const [tcId, authConf] of Object.entries(
296
- event.actions.requestedAuthConfigs,
297
- )) {
298
- this.authRequests.push({ toolCallId: tcId, authConfig: authConf });
299
- }
300
- }
301
-
302
247
  // Track agent info
303
248
  if (event.author && event.author !== "user") {
304
249
  this.lastAgentInfo = {
@@ -361,14 +306,9 @@ export class AdkEventAccumulator {
361
306
  for (const [index, part] of parts.entries()) {
362
307
  if (part.text != null && !part.thought) {
363
308
  humanParts.push({ type: "text", text: part.text });
364
- } else if (part.inlineData) {
365
- humanParts.push(
366
- inlineDataToPart(part.inlineData.mimeType, part.inlineData.data),
367
- );
368
- } else if (part.fileData) {
369
- humanParts.push(
370
- fileDataToPart(part.fileData.fileUri, part.fileData.mimeType),
371
- );
309
+ } else if (part.inlineData || part.fileData) {
310
+ const mediaPart = mediaToContentPart(part);
311
+ if (mediaPart) humanParts.push(mediaPart);
372
312
  } else if (part.functionResponse?.id) {
373
313
  // ADK records tool confirmation and other client-supplied tool
374
314
  // results as user-authored function responses, and its request
@@ -462,45 +402,6 @@ export class AdkEventAccumulator {
462
402
  event: AdkEvent,
463
403
  partIndex: number,
464
404
  ): void {
465
- // Detect special ADK function calls
466
- if (part.functionCall && !event.partial) {
467
- const name = part.functionCall.name;
468
-
469
- // Tool confirmation request
470
- if (name === ADK_REQUEST_CONFIRMATION) {
471
- const callArgs = part.functionCall.args;
472
- // ADK JS: args keys are "originalFunctionCall" and "toolConfirmation"
473
- // ADK Python: args keys are "original_function_call" and "tool_confirmation"
474
- const original =
475
- (callArgs.originalFunctionCall as Record<string, unknown>) ??
476
- (callArgs.original_function_call as Record<string, unknown>);
477
- const conf =
478
- (callArgs.toolConfirmation as Record<string, unknown>) ??
479
- (callArgs.tool_confirmation as Record<string, unknown>);
480
- this.toolConfirmations.push({
481
- toolCallId: part.functionCall.id ?? "",
482
- toolName: (original?.name as string) ?? "",
483
- args: (original?.args as Record<string, unknown>) ?? {},
484
- hint: (conf?.hint as string) ?? "",
485
- confirmed: false,
486
- payload: conf?.payload,
487
- });
488
- }
489
-
490
- // Auth credential request
491
- if (name === ADK_REQUEST_CREDENTIAL) {
492
- const credArgs = part.functionCall.args;
493
- // ADK JS: args keys are "function_call_id" and "auth_config"
494
- const originalToolCallId =
495
- (credArgs.function_call_id as string) ?? part.functionCall.id ?? "";
496
- const authConfig = credArgs.auth_config ?? credArgs;
497
- this.authRequests.push({
498
- toolCallId: originalToolCallId,
499
- authConfig,
500
- });
501
- }
502
- }
503
-
504
405
  // Text with thought=true → reasoning (accumulated)
505
406
  if (part.text != null && part.thought) {
506
407
  const msg = this.getOrCreateAiMessage(event);
@@ -540,8 +441,8 @@ export class AdkEventAccumulator {
540
441
  const toolCall: AdkToolCall = {
541
442
  id: part.functionCall.id ?? generateId(),
542
443
  name: part.functionCall.name,
543
- args: part.functionCall.args as ReadonlyJSONObject,
544
- argsText: JSON.stringify(part.functionCall.args),
444
+ args: (part.functionCall.args ?? {}) as ReadonlyJSONObject,
445
+ argsText: JSON.stringify(part.functionCall.args ?? {}),
545
446
  };
546
447
  const existing = [...(msg.tool_calls ?? [])];
547
448
  const idx = existing.findIndex((tc) => tc.id === toolCall.id);
@@ -598,22 +499,10 @@ export class AdkEventAccumulator {
598
499
  return;
599
500
  }
600
501
 
601
- if (part.inlineData) {
602
- const msg = this.getOrCreateAiMessage(event);
603
- this.appendContent(
604
- msg,
605
- inlineDataToPart(part.inlineData.mimeType, part.inlineData.data),
606
- );
607
- return;
608
- }
609
-
610
- if (part.fileData) {
611
- const msg = this.getOrCreateAiMessage(event);
612
- this.appendContent(
613
- msg,
614
- fileDataToPart(part.fileData.fileUri, part.fileData.mimeType),
615
- );
616
- }
502
+ const mediaPart = mediaToContentPart(part);
503
+ if (!mediaPart) return;
504
+ const msg = this.getOrCreateAiMessage(event);
505
+ this.appendContent(msg, mediaPart);
617
506
  }
618
507
 
619
508
  private trackMessageMetadata(event: AdkEvent): void {
@@ -708,6 +597,18 @@ export class AdkEventAccumulator {
708
597
  this.currentMessageId = null;
709
598
  }
710
599
 
600
+ // ADK resumes a confirmation or credential request only on a reply that quotes its synthetic call's id, so those calls are the whole record; requestedToolConfirmations and requestedAuthConfigs key the gated call instead, which no reply answers.
601
+ private getRequestCalls(name: string): AdkToolCall[] {
602
+ const calls = new Map<string, AdkToolCall>();
603
+ for (const msg of this.messagesMap.values()) {
604
+ if (msg.type !== "ai") continue;
605
+ for (const call of msg.tool_calls ?? []) {
606
+ if (call.name === name && !calls.has(call.id)) calls.set(call.id, call);
607
+ }
608
+ }
609
+ return [...calls.values()];
610
+ }
611
+
711
612
  getMessages(): AdkMessage[] {
712
613
  return [...this.messagesMap.values()];
713
614
  }
@@ -733,11 +634,36 @@ export class AdkEventAccumulator {
733
634
  }
734
635
 
735
636
  getToolConfirmations(): AdkToolConfirmation[] {
736
- return [...this.toolConfirmations];
637
+ const { approvals } = projectAdkToolApprovals(this.getMessages());
638
+ return this.getRequestCalls(ADK_REQUEST_CONFIRMATION)
639
+ .filter(({ id }) => approvals.get(id)?.approved === undefined)
640
+ .map(({ id, args = {} }) => {
641
+ const original = (args.originalFunctionCall ??
642
+ args.original_function_call) as Record<string, unknown> | undefined;
643
+ const confirmation = (args.toolConfirmation ??
644
+ args.tool_confirmation) as Record<string, unknown> | undefined;
645
+ return {
646
+ toolCallId: id,
647
+ toolName: (original?.name as string) ?? "",
648
+ args: (original?.args as Record<string, unknown>) ?? {},
649
+ hint: (confirmation?.hint as string) ?? "",
650
+ confirmed: false,
651
+ payload: confirmation?.payload,
652
+ };
653
+ });
737
654
  }
738
655
 
739
656
  getAuthRequests(): AdkAuthRequest[] {
740
- return [...this.authRequests];
657
+ const answered = new Set<string>();
658
+ for (const msg of this.messagesMap.values()) {
659
+ if (msg.type === "tool") answered.add(msg.tool_call_id);
660
+ }
661
+ return this.getRequestCalls(ADK_REQUEST_CREDENTIAL)
662
+ .filter(({ id }) => !answered.has(id))
663
+ .map(({ id, args = {} }) => ({
664
+ toolCallId: id,
665
+ authConfig: args.auth_config ?? args.authConfig,
666
+ }));
741
667
  }
742
668
 
743
669
  isEscalated(): boolean {