@assistant-ui/react-google-adk 0.0.29 → 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 (93) hide show
  1. package/dist/AdkClient.d.ts +2 -3
  2. package/dist/AdkClient.d.ts.map +1 -1
  3. package/dist/AdkClient.js +2 -1
  4. package/dist/AdkClient.js.map +1 -1
  5. package/dist/AdkEventAccumulator.d.ts +4 -6
  6. package/dist/AdkEventAccumulator.d.ts.map +1 -1
  7. package/dist/AdkEventAccumulator.js +80 -95
  8. package/dist/AdkEventAccumulator.js.map +1 -1
  9. package/dist/AdkSessionAdapter.d.ts +7 -4
  10. package/dist/AdkSessionAdapter.d.ts.map +1 -1
  11. package/dist/AdkSessionAdapter.js +7 -4
  12. package/dist/AdkSessionAdapter.js.map +1 -1
  13. package/dist/adkExtras.d.ts +1 -2
  14. package/dist/adkExtras.d.ts.map +1 -1
  15. package/dist/adkToolApproval.d.ts +6 -7
  16. package/dist/adkToolApproval.d.ts.map +1 -1
  17. package/dist/adkToolApproval.js +1 -3
  18. package/dist/adkToolApproval.js.map +1 -1
  19. package/dist/contentToParts.d.ts +1 -2
  20. package/dist/contentToParts.d.ts.map +1 -1
  21. package/dist/convertAdkMessages.d.ts +2 -3
  22. package/dist/convertAdkMessages.d.ts.map +1 -1
  23. package/dist/convertToAdkMessages.d.ts +15 -7
  24. package/dist/convertToAdkMessages.d.ts.map +1 -1
  25. package/dist/convertToAdkMessages.js +20 -6
  26. package/dist/convertToAdkMessages.js.map +1 -1
  27. package/dist/hooks.d.ts +15 -16
  28. package/dist/hooks.d.ts.map +1 -1
  29. package/dist/hooks.js +1 -1
  30. package/dist/normalizeAdkPart.d.ts +4 -0
  31. package/dist/normalizeAdkPart.d.ts.map +1 -0
  32. package/dist/normalizeAdkPart.js +31 -0
  33. package/dist/normalizeAdkPart.js.map +1 -0
  34. package/dist/parseAdkEvent.d.ts +1 -2
  35. package/dist/parseAdkEvent.d.ts.map +1 -1
  36. package/dist/sdkIdentity.d.ts +5 -0
  37. package/dist/sdkIdentity.d.ts.map +1 -0
  38. package/dist/sdkIdentity.js +9 -0
  39. package/dist/sdkIdentity.js.map +1 -0
  40. package/dist/server/adkEventStream.d.ts +2 -3
  41. package/dist/server/adkEventStream.d.ts.map +1 -1
  42. package/dist/server/createAdkApiRoute.d.ts +2 -3
  43. package/dist/server/createAdkApiRoute.d.ts.map +1 -1
  44. package/dist/server/parseAdkRequest.d.ts +2 -3
  45. package/dist/server/parseAdkRequest.d.ts.map +1 -1
  46. package/dist/server/parseAdkRequest.js +2 -1
  47. package/dist/server/parseAdkRequest.js.map +1 -1
  48. package/dist/structuredEvents.d.ts +1 -2
  49. package/dist/structuredEvents.d.ts.map +1 -1
  50. package/dist/structuredEvents.js +1 -1
  51. package/dist/structuredEvents.js.map +1 -1
  52. package/dist/toAdkFunctionResponse.d.ts +5 -0
  53. package/dist/toAdkFunctionResponse.d.ts.map +1 -0
  54. package/dist/toAdkFunctionResponse.js +11 -0
  55. package/dist/toAdkFunctionResponse.js.map +1 -0
  56. package/dist/trimTrailingSlashes.d.ts +1 -2
  57. package/dist/trimTrailingSlashes.d.ts.map +1 -1
  58. package/dist/types.d.ts +22 -23
  59. package/dist/types.d.ts.map +1 -1
  60. package/dist/types.js.map +1 -1
  61. package/dist/useAdkMessages.d.ts +2 -3
  62. package/dist/useAdkMessages.d.ts.map +1 -1
  63. package/dist/useAdkMessages.js +16 -5
  64. package/dist/useAdkMessages.js.map +1 -1
  65. package/dist/useAdkRuntime.d.ts +2 -3
  66. package/dist/useAdkRuntime.d.ts.map +1 -1
  67. package/dist/useAdkRuntime.js +3 -1
  68. package/dist/useAdkRuntime.js.map +1 -1
  69. package/package.json +10 -9
  70. package/src/AdkClient.test.ts +139 -2
  71. package/src/AdkClient.ts +2 -1
  72. package/src/AdkEventAccumulator.test.ts +588 -11
  73. package/src/AdkEventAccumulator.ts +85 -130
  74. package/src/AdkSessionAdapter.test.ts +355 -1
  75. package/src/AdkSessionAdapter.ts +24 -5
  76. package/src/adkToolApproval.test.ts +0 -4
  77. package/src/adkToolApproval.ts +1 -3
  78. package/src/convertToAdkMessages.test.ts +70 -0
  79. package/src/convertToAdkMessages.ts +19 -4
  80. package/src/normalizeAdkPart.ts +35 -0
  81. package/src/sdkIdentity.ts +9 -0
  82. package/src/server/parseAdkRequest.test.ts +63 -0
  83. package/src/server/parseAdkRequest.ts +2 -1
  84. package/src/structuredEvents.test.ts +69 -0
  85. package/src/structuredEvents.ts +4 -1
  86. package/src/toAdkFunctionResponse.test.ts +46 -0
  87. package/src/toAdkFunctionResponse.ts +18 -0
  88. package/src/types.ts +1 -1
  89. package/src/useAdkMessages.test.ts +249 -2
  90. package/src/useAdkMessages.ts +24 -6
  91. package/src/useAdkRuntime.replacement.test.tsx +11 -7
  92. package/src/useAdkRuntime.ts +2 -0
  93. package/src/useAdkRuntimeApproval.test.tsx +0 -1
@@ -1,4 +1,5 @@
1
1
  import { generateId } from "@assistant-ui/core";
2
+ import { isRecord } from "@assistant-ui/core/internal";
2
3
  import type { MessageStatus } from "@assistant-ui/core";
3
4
  import type {
4
5
  AdkEvent,
@@ -11,6 +12,9 @@ import type {
11
12
  AdkMessageMetadata,
12
13
  } from "./types";
13
14
  import type { ReadonlyJSONObject } from "assistant-stream/utils";
15
+ import { normalizeAdkPart } from "./normalizeAdkPart";
16
+ import { projectAdkToolApprovals } from "./adkToolApproval";
17
+ import { isAdkFunctionError } from "./toAdkFunctionResponse";
14
18
 
15
19
  type InProgressMessage = AdkMessage & { type: "ai" };
16
20
 
@@ -97,41 +101,27 @@ const finishReasonToStatus = (
97
101
  return { type: "complete", reason: "stop" };
98
102
  };
99
103
 
100
- const inlineDataToPart = (
101
- mimeType: string,
102
- data: string,
103
- ): AdkMessageContentPart =>
104
- mimeType.startsWith("image/")
105
- ? { type: "image", mimeType, data }
106
- : { type: "file", mimeType, data };
107
-
108
- const fileDataToPart = (
109
- fileUri: string,
110
- mimeType: string | undefined,
111
- ): AdkMessageContentPart =>
112
- 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/")
113
119
  ? { type: "image_url", url: fileUri }
114
120
  : { type: "file_url", url: fileUri, mimeType };
121
+ };
115
122
 
116
123
  // ── Snake_case normalization ──
117
124
 
118
- const normalizeEventPart = (part: AdkEventPart): AdkEventPart => {
119
- const p = part as Record<string, unknown>;
120
- const result: Record<string, unknown> = { ...p };
121
- if ("function_call" in p && !("functionCall" in p))
122
- result.functionCall = p.function_call;
123
- if ("function_response" in p && !("functionResponse" in p))
124
- result.functionResponse = p.function_response;
125
- if ("inline_data" in p && !("inlineData" in p))
126
- result.inlineData = p.inline_data;
127
- if ("file_data" in p && !("fileData" in p)) result.fileData = p.file_data;
128
- if ("executable_code" in p && !("executableCode" in p))
129
- result.executableCode = p.executable_code;
130
- if ("code_execution_result" in p && !("codeExecutionResult" in p))
131
- result.codeExecutionResult = p.code_execution_result;
132
- return result as AdkEventPart;
133
- };
134
-
135
125
  const normalizeEvent = (event: AdkEvent): AdkEvent => {
136
126
  const e = event as Record<string, unknown>;
137
127
  const result: Record<string, unknown> = { ...e };
@@ -178,8 +168,8 @@ const normalizeEvent = (event: AdkEvent): AdkEvent => {
178
168
 
179
169
  if (result.content && (result.content as Record<string, unknown>).parts) {
180
170
  const content = result.content as Record<string, unknown>;
181
- const parts = content.parts as AdkEventPart[];
182
- result.content = { ...content, parts: parts.map(normalizeEventPart) };
171
+ const parts = content.parts as Record<string, unknown>[];
172
+ result.content = { ...content, parts: parts.map(normalizeAdkPart) };
183
173
  }
184
174
 
185
175
  return result as AdkEvent;
@@ -203,19 +193,23 @@ export class AdkEventAccumulator {
203
193
  } = {};
204
194
  private lastTransferToAgent: string | undefined;
205
195
  private pendingLongRunningToolIds = new Set<string>();
206
- private toolConfirmations: AdkToolConfirmation[] = [];
207
- private authRequests: AdkAuthRequest[] = [];
208
196
  private escalated = false;
209
197
  private messageMetadataMap = new Map<string, AdkMessageMetadata>();
210
198
  // How many assistant messages each event has opened, so a replay of that
211
199
  // event opens them with the same ids.
212
200
  private aiMessageOrdinals = new Map<string, number>();
213
- constructor(initialMessages?: AdkMessage[]) {
201
+ constructor(
202
+ initialMessages?: AdkMessage[],
203
+ initialLongRunningToolIds?: readonly string[],
204
+ ) {
214
205
  if (initialMessages) {
215
206
  for (const msg of initialMessages) {
216
207
  this.messagesMap.set(msg.id, msg);
217
208
  }
218
209
  }
210
+ if (initialLongRunningToolIds) {
211
+ this.pendingLongRunningToolIds = new Set(initialLongRunningToolIds);
212
+ }
219
213
  }
220
214
 
221
215
  processEvent(rawEvent: AdkEvent): AdkMessage[] {
@@ -250,32 +244,6 @@ export class AdkEventAccumulator {
250
244
  }
251
245
  }
252
246
 
253
- // Track tool confirmations from actions
254
- if (event.actions?.requestedToolConfirmations) {
255
- for (const [tcId, conf] of Object.entries(
256
- event.actions.requestedToolConfirmations,
257
- )) {
258
- const c = conf as Record<string, unknown>;
259
- this.toolConfirmations.push({
260
- toolCallId: tcId,
261
- toolName: "",
262
- args: {},
263
- hint: (c.hint as string) ?? "",
264
- confirmed: false,
265
- payload: c.payload,
266
- });
267
- }
268
- }
269
-
270
- // Track auth requests from actions
271
- if (event.actions?.requestedAuthConfigs) {
272
- for (const [tcId, authConf] of Object.entries(
273
- event.actions.requestedAuthConfigs,
274
- )) {
275
- this.authRequests.push({ toolCallId: tcId, authConfig: authConf });
276
- }
277
- }
278
-
279
247
  // Track agent info
280
248
  if (event.author && event.author !== "user") {
281
249
  this.lastAgentInfo = {
@@ -338,14 +306,9 @@ export class AdkEventAccumulator {
338
306
  for (const [index, part] of parts.entries()) {
339
307
  if (part.text != null && !part.thought) {
340
308
  humanParts.push({ type: "text", text: part.text });
341
- } else if (part.inlineData) {
342
- humanParts.push(
343
- inlineDataToPart(part.inlineData.mimeType, part.inlineData.data),
344
- );
345
- } else if (part.fileData) {
346
- humanParts.push(
347
- fileDataToPart(part.fileData.fileUri, part.fileData.mimeType),
348
- );
309
+ } else if (part.inlineData || part.fileData) {
310
+ const mediaPart = mediaToContentPart(part);
311
+ if (mediaPart) humanParts.push(mediaPart);
349
312
  } else if (part.functionResponse?.id) {
350
313
  // ADK records tool confirmation and other client-supplied tool
351
314
  // results as user-authored function responses, and its request
@@ -359,8 +322,12 @@ export class AdkEventAccumulator {
359
322
  tool_call_id: part.functionResponse.id,
360
323
  name: part.functionResponse.name,
361
324
  content: JSON.stringify(part.functionResponse.response),
362
- status: "success",
325
+ status: isAdkFunctionError(part.functionResponse.response)
326
+ ? "error"
327
+ : "success",
363
328
  });
329
+ // Only a user-authored response settles a long-running call; the response ADK authors for one is the tool's interim result.
330
+ this.pendingLongRunningToolIds.delete(part.functionResponse.id);
364
331
  }
365
332
  }
366
333
  // The replies answer the preceding assistant turn, so they are emitted
@@ -435,45 +402,6 @@ export class AdkEventAccumulator {
435
402
  event: AdkEvent,
436
403
  partIndex: number,
437
404
  ): void {
438
- // Detect special ADK function calls
439
- if (part.functionCall && !event.partial) {
440
- const name = part.functionCall.name;
441
-
442
- // Tool confirmation request
443
- if (name === ADK_REQUEST_CONFIRMATION) {
444
- const callArgs = part.functionCall.args;
445
- // ADK JS: args keys are "originalFunctionCall" and "toolConfirmation"
446
- // ADK Python: args keys are "original_function_call" and "tool_confirmation"
447
- const original =
448
- (callArgs.originalFunctionCall as Record<string, unknown>) ??
449
- (callArgs.original_function_call as Record<string, unknown>);
450
- const conf =
451
- (callArgs.toolConfirmation as Record<string, unknown>) ??
452
- (callArgs.tool_confirmation as Record<string, unknown>);
453
- this.toolConfirmations.push({
454
- toolCallId: part.functionCall.id ?? "",
455
- toolName: (original?.name as string) ?? "",
456
- args: (original?.args as Record<string, unknown>) ?? {},
457
- hint: (conf?.hint as string) ?? "",
458
- confirmed: false,
459
- payload: conf?.payload,
460
- });
461
- }
462
-
463
- // Auth credential request
464
- if (name === ADK_REQUEST_CREDENTIAL) {
465
- const credArgs = part.functionCall.args;
466
- // ADK JS: args keys are "function_call_id" and "auth_config"
467
- const originalToolCallId =
468
- (credArgs.function_call_id as string) ?? part.functionCall.id ?? "";
469
- const authConfig = credArgs.auth_config ?? credArgs;
470
- this.authRequests.push({
471
- toolCallId: originalToolCallId,
472
- authConfig,
473
- });
474
- }
475
- }
476
-
477
405
  // Text with thought=true → reasoning (accumulated)
478
406
  if (part.text != null && part.thought) {
479
407
  const msg = this.getOrCreateAiMessage(event);
@@ -513,8 +441,8 @@ export class AdkEventAccumulator {
513
441
  const toolCall: AdkToolCall = {
514
442
  id: part.functionCall.id ?? generateId(),
515
443
  name: part.functionCall.name,
516
- args: part.functionCall.args as ReadonlyJSONObject,
517
- argsText: JSON.stringify(part.functionCall.args),
444
+ args: (part.functionCall.args ?? {}) as ReadonlyJSONObject,
445
+ argsText: JSON.stringify(part.functionCall.args ?? {}),
518
446
  };
519
447
  const existing = [...(msg.tool_calls ?? [])];
520
448
  const idx = existing.findIndex((tc) => tc.id === toolCall.id);
@@ -541,7 +469,9 @@ export class AdkEventAccumulator {
541
469
  tool_call_id: part.functionResponse.id ?? "",
542
470
  name: part.functionResponse.name,
543
471
  content: JSON.stringify(part.functionResponse.response),
544
- status: "success",
472
+ status: isAdkFunctionError(part.functionResponse.response)
473
+ ? "error"
474
+ : "success",
545
475
  };
546
476
  this.messagesMap.set(toolMsg.id, toolMsg);
547
477
  return;
@@ -569,22 +499,10 @@ export class AdkEventAccumulator {
569
499
  return;
570
500
  }
571
501
 
572
- if (part.inlineData) {
573
- const msg = this.getOrCreateAiMessage(event);
574
- this.appendContent(
575
- msg,
576
- inlineDataToPart(part.inlineData.mimeType, part.inlineData.data),
577
- );
578
- return;
579
- }
580
-
581
- if (part.fileData) {
582
- const msg = this.getOrCreateAiMessage(event);
583
- this.appendContent(
584
- msg,
585
- fileDataToPart(part.fileData.fileUri, part.fileData.mimeType),
586
- );
587
- }
502
+ const mediaPart = mediaToContentPart(part);
503
+ if (!mediaPart) return;
504
+ const msg = this.getOrCreateAiMessage(event);
505
+ this.appendContent(msg, mediaPart);
588
506
  }
589
507
 
590
508
  private trackMessageMetadata(event: AdkEvent): void {
@@ -679,6 +597,18 @@ export class AdkEventAccumulator {
679
597
  this.currentMessageId = null;
680
598
  }
681
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
+
682
612
  getMessages(): AdkMessage[] {
683
613
  return [...this.messagesMap.values()];
684
614
  }
@@ -704,11 +634,36 @@ export class AdkEventAccumulator {
704
634
  }
705
635
 
706
636
  getToolConfirmations(): AdkToolConfirmation[] {
707
- 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
+ });
708
654
  }
709
655
 
710
656
  getAuthRequests(): AdkAuthRequest[] {
711
- 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
+ }));
712
667
  }
713
668
 
714
669
  isEscalated(): boolean {