@assistant-ui/react-google-adk 0.0.1

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 (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/AdkClient.d.ts +45 -0
  4. package/dist/AdkClient.d.ts.map +1 -0
  5. package/dist/AdkClient.js +204 -0
  6. package/dist/AdkClient.js.map +1 -0
  7. package/dist/AdkEventAccumulator.d.ts +45 -0
  8. package/dist/AdkEventAccumulator.d.ts.map +1 -0
  9. package/dist/AdkEventAccumulator.js +508 -0
  10. package/dist/AdkEventAccumulator.js.map +1 -0
  11. package/dist/AdkSessionAdapter.d.ts +61 -0
  12. package/dist/AdkSessionAdapter.d.ts.map +1 -0
  13. package/dist/AdkSessionAdapter.js +159 -0
  14. package/dist/AdkSessionAdapter.js.map +1 -0
  15. package/dist/convertAdkMessages.d.ts +4 -0
  16. package/dist/convertAdkMessages.d.ts.map +1 -0
  17. package/dist/convertAdkMessages.js +75 -0
  18. package/dist/convertAdkMessages.js.map +1 -0
  19. package/dist/hooks.d.ts +50 -0
  20. package/dist/hooks.d.ts.map +1 -0
  21. package/dist/hooks.js +173 -0
  22. package/dist/hooks.js.map +1 -0
  23. package/dist/index.d.ts +11 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +10 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/server/adkEventStream.d.ts +42 -0
  28. package/dist/server/adkEventStream.d.ts.map +1 -0
  29. package/dist/server/adkEventStream.js +135 -0
  30. package/dist/server/adkEventStream.js.map +1 -0
  31. package/dist/server/createAdkApiRoute.d.ts +47 -0
  32. package/dist/server/createAdkApiRoute.d.ts.map +1 -0
  33. package/dist/server/createAdkApiRoute.js +41 -0
  34. package/dist/server/createAdkApiRoute.js.map +1 -0
  35. package/dist/server/index.d.ts +4 -0
  36. package/dist/server/index.d.ts.map +1 -0
  37. package/dist/server/index.js +4 -0
  38. package/dist/server/index.js.map +1 -0
  39. package/dist/server/parseAdkRequest.d.ts +56 -0
  40. package/dist/server/parseAdkRequest.d.ts.map +1 -0
  41. package/dist/server/parseAdkRequest.js +93 -0
  42. package/dist/server/parseAdkRequest.js.map +1 -0
  43. package/dist/structuredEvents.d.ts +7 -0
  44. package/dist/structuredEvents.d.ts.map +1 -0
  45. package/dist/structuredEvents.js +79 -0
  46. package/dist/structuredEvents.js.map +1 -0
  47. package/dist/types.d.ts +253 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +14 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/useAdkMessages.d.ts +28 -0
  52. package/dist/useAdkMessages.d.ts.map +1 -0
  53. package/dist/useAdkMessages.js +198 -0
  54. package/dist/useAdkMessages.js.map +1 -0
  55. package/dist/useAdkRuntime.d.ts +36 -0
  56. package/dist/useAdkRuntime.d.ts.map +1 -0
  57. package/dist/useAdkRuntime.js +252 -0
  58. package/dist/useAdkRuntime.js.map +1 -0
  59. package/package.json +83 -0
  60. package/server/package.json +4 -0
  61. package/src/AdkClient.test.ts +662 -0
  62. package/src/AdkClient.ts +274 -0
  63. package/src/AdkEventAccumulator.test.ts +591 -0
  64. package/src/AdkEventAccumulator.ts +602 -0
  65. package/src/AdkSessionAdapter.test.ts +362 -0
  66. package/src/AdkSessionAdapter.ts +245 -0
  67. package/src/convertAdkMessages.test.ts +209 -0
  68. package/src/convertAdkMessages.ts +93 -0
  69. package/src/hooks.ts +217 -0
  70. package/src/index.ts +66 -0
  71. package/src/server/adkEventStream.test.ts +78 -0
  72. package/src/server/adkEventStream.ts +161 -0
  73. package/src/server/createAdkApiRoute.test.ts +370 -0
  74. package/src/server/createAdkApiRoute.ts +86 -0
  75. package/src/server/index.ts +6 -0
  76. package/src/server/parseAdkRequest.test.ts +152 -0
  77. package/src/server/parseAdkRequest.ts +122 -0
  78. package/src/structuredEvents.ts +81 -0
  79. package/src/types.ts +265 -0
  80. package/src/useAdkMessages.ts +259 -0
  81. package/src/useAdkRuntime.ts +398 -0
@@ -0,0 +1,602 @@
1
+ import { v4 as uuidv4 } from "uuid";
2
+ import type { MessageStatus } from "@assistant-ui/core";
3
+ import type {
4
+ AdkEvent,
5
+ AdkEventPart,
6
+ AdkMessage,
7
+ AdkMessageContentPart,
8
+ AdkToolCall,
9
+ AdkToolConfirmation,
10
+ AdkAuthRequest,
11
+ AdkMessageMetadata,
12
+ } from "./types";
13
+ import type { ReadonlyJSONObject } from "assistant-stream/utils";
14
+
15
+ type InProgressMessage = AdkMessage & { type: "ai" };
16
+
17
+ const ADK_REQUEST_CONFIRMATION = "adk_request_confirmation";
18
+ const ADK_REQUEST_CREDENTIAL = "adk_request_credential";
19
+
20
+ /**
21
+ * Checks if an event is a final response using the same logic as ADK's
22
+ * `isFinalResponse()`.
23
+ */
24
+ export const isFinalResponse = (event: AdkEvent): boolean => {
25
+ if (
26
+ event.actions?.skipSummarization ||
27
+ (event.longRunningToolIds && event.longRunningToolIds.length > 0)
28
+ ) {
29
+ return true;
30
+ }
31
+
32
+ const parts = event.content?.parts;
33
+ return (
34
+ !event.partial &&
35
+ (parts?.length ?? 0) > 0 &&
36
+ !parts!.some((p) => p.functionCall) &&
37
+ !parts!.some((p) => p.functionResponse) &&
38
+ !parts![parts!.length - 1]?.codeExecutionResult
39
+ );
40
+ };
41
+
42
+ const CONTENT_FILTER_REASONS = new Set([
43
+ "SAFETY",
44
+ "RECITATION",
45
+ "BLOCKLIST",
46
+ "PROHIBITED_CONTENT",
47
+ "SPII",
48
+ "LANGUAGE",
49
+ "IMAGE_SAFETY",
50
+ "IMAGE_RECITATION",
51
+ "IMAGE_OTHER",
52
+ "IMAGE_PROHIBITED_CONTENT",
53
+ ]);
54
+
55
+ const ERROR_REASONS = new Set([
56
+ "MALFORMED_FUNCTION_CALL",
57
+ "UNEXPECTED_TOOL_CALL",
58
+ ]);
59
+
60
+ const finishReasonToStatus = (
61
+ finishReason: string | undefined,
62
+ ): MessageStatus => {
63
+ if (finishReason === "MAX_TOKENS") {
64
+ return { type: "incomplete", reason: "length" };
65
+ }
66
+ if (finishReason && CONTENT_FILTER_REASONS.has(finishReason)) {
67
+ return {
68
+ type: "incomplete",
69
+ reason: "content-filter",
70
+ error: `Content filtered: ${finishReason}`,
71
+ };
72
+ }
73
+ if (finishReason && ERROR_REASONS.has(finishReason)) {
74
+ return {
75
+ type: "incomplete",
76
+ reason: "error",
77
+ error: `LLM error: ${finishReason}`,
78
+ };
79
+ }
80
+ return { type: "complete", reason: "stop" };
81
+ };
82
+
83
+ // ── Snake_case normalization ──
84
+
85
+ const normalizeEventPart = (part: AdkEventPart): AdkEventPart => {
86
+ const p = part as Record<string, unknown>;
87
+ const result: Record<string, unknown> = { ...p };
88
+ if ("function_call" in p && !("functionCall" in p))
89
+ result.functionCall = p.function_call;
90
+ if ("function_response" in p && !("functionResponse" in p))
91
+ result.functionResponse = p.function_response;
92
+ if ("inline_data" in p && !("inlineData" in p))
93
+ result.inlineData = p.inline_data;
94
+ if ("file_data" in p && !("fileData" in p)) result.fileData = p.file_data;
95
+ if ("executable_code" in p && !("executableCode" in p))
96
+ result.executableCode = p.executable_code;
97
+ if ("code_execution_result" in p && !("codeExecutionResult" in p))
98
+ result.codeExecutionResult = p.code_execution_result;
99
+ return result as AdkEventPart;
100
+ };
101
+
102
+ const normalizeEvent = (event: AdkEvent): AdkEvent => {
103
+ const e = event as Record<string, unknown>;
104
+ const result: Record<string, unknown> = { ...e };
105
+ if ("error_code" in e && !("errorCode" in e)) result.errorCode = e.error_code;
106
+ if ("error_message" in e && !("errorMessage" in e))
107
+ result.errorMessage = e.error_message;
108
+ if ("long_running_tool_ids" in e && !("longRunningToolIds" in e))
109
+ result.longRunningToolIds = e.long_running_tool_ids;
110
+ if ("turn_complete" in e && !("turnComplete" in e))
111
+ result.turnComplete = e.turn_complete;
112
+ if ("finish_reason" in e && !("finishReason" in e))
113
+ result.finishReason = e.finish_reason;
114
+ if ("invocation_id" in e && !("invocationId" in e))
115
+ result.invocationId = e.invocation_id;
116
+ if ("custom_metadata" in e && !("customMetadata" in e))
117
+ result.customMetadata = e.custom_metadata;
118
+ if ("grounding_metadata" in e && !("groundingMetadata" in e))
119
+ result.groundingMetadata = e.grounding_metadata;
120
+ if ("citation_metadata" in e && !("citationMetadata" in e))
121
+ result.citationMetadata = e.citation_metadata;
122
+ if ("usage_metadata" in e && !("usageMetadata" in e))
123
+ result.usageMetadata = e.usage_metadata;
124
+
125
+ if (result.actions) {
126
+ const a = result.actions as Record<string, unknown>;
127
+ const na: Record<string, unknown> = { ...a };
128
+ if ("state_delta" in a && !("stateDelta" in a))
129
+ na.stateDelta = a.state_delta;
130
+ if ("artifact_delta" in a && !("artifactDelta" in a))
131
+ na.artifactDelta = a.artifact_delta;
132
+ if ("transfer_to_agent" in a && !("transferToAgent" in a))
133
+ na.transferToAgent = a.transfer_to_agent;
134
+ if ("skip_summarization" in a && !("skipSummarization" in a))
135
+ na.skipSummarization = a.skip_summarization;
136
+ if ("requested_auth_configs" in a && !("requestedAuthConfigs" in a))
137
+ na.requestedAuthConfigs = a.requested_auth_configs;
138
+ if (
139
+ "requested_tool_confirmations" in a &&
140
+ !("requestedToolConfirmations" in a)
141
+ )
142
+ na.requestedToolConfirmations = a.requested_tool_confirmations;
143
+ result.actions = na;
144
+ }
145
+
146
+ if (result.content && (result.content as Record<string, unknown>).parts) {
147
+ const content = result.content as Record<string, unknown>;
148
+ const parts = content.parts as AdkEventPart[];
149
+ result.content = { ...content, parts: parts.map(normalizeEventPart) };
150
+ }
151
+
152
+ return result as AdkEvent;
153
+ };
154
+
155
+ // ── Accumulator ──
156
+
157
+ export class AdkEventAccumulator {
158
+ private messagesMap = new Map<string, AdkMessage>();
159
+ private currentMessageId: string | null = null;
160
+ private partialTextBuffer = "";
161
+ private partialReasoningBuffer = "";
162
+ private accumulatedStateDelta: Record<string, unknown> = {};
163
+ private accumulatedArtifactDelta: Record<string, number> = {};
164
+ private lastAgentInfo: {
165
+ name?: string | undefined;
166
+ branch?: string | undefined;
167
+ } = {};
168
+ private lastTransferToAgent: string | undefined;
169
+ private pendingLongRunningToolIds: string[] = [];
170
+ private toolConfirmations: AdkToolConfirmation[] = [];
171
+ private authRequests: AdkAuthRequest[] = [];
172
+ private escalated = false;
173
+ private messageMetadataMap = new Map<string, AdkMessageMetadata>();
174
+ constructor(initialMessages?: AdkMessage[]) {
175
+ if (initialMessages) {
176
+ for (const msg of initialMessages) {
177
+ this.messagesMap.set(msg.id, msg);
178
+ }
179
+ }
180
+ }
181
+
182
+ processEvent(rawEvent: AdkEvent): AdkMessage[] {
183
+ const event = normalizeEvent(rawEvent);
184
+
185
+ // Accumulate state delta
186
+ if (event.actions?.stateDelta) {
187
+ Object.assign(this.accumulatedStateDelta, event.actions.stateDelta);
188
+ }
189
+
190
+ // Accumulate artifact delta
191
+ if (event.actions?.artifactDelta) {
192
+ Object.assign(this.accumulatedArtifactDelta, event.actions.artifactDelta);
193
+ }
194
+
195
+ // Track escalation
196
+ if (event.actions?.escalate) {
197
+ this.escalated = true;
198
+ }
199
+
200
+ // Track agent transfer
201
+ if (event.actions?.transferToAgent) {
202
+ this.lastTransferToAgent = event.actions.transferToAgent;
203
+ }
204
+
205
+ // Track long-running tool IDs
206
+ if (event.longRunningToolIds?.length) {
207
+ this.pendingLongRunningToolIds = event.longRunningToolIds;
208
+ }
209
+
210
+ // Track tool confirmations from actions
211
+ if (event.actions?.requestedToolConfirmations) {
212
+ for (const [tcId, conf] of Object.entries(
213
+ event.actions.requestedToolConfirmations,
214
+ )) {
215
+ const c = conf as Record<string, unknown>;
216
+ this.toolConfirmations.push({
217
+ toolCallId: tcId,
218
+ toolName: "",
219
+ args: {},
220
+ hint: (c.hint as string) ?? "",
221
+ confirmed: false,
222
+ payload: c.payload,
223
+ });
224
+ }
225
+ }
226
+
227
+ // Track auth requests from actions
228
+ if (event.actions?.requestedAuthConfigs) {
229
+ for (const [tcId, authConf] of Object.entries(
230
+ event.actions.requestedAuthConfigs,
231
+ )) {
232
+ this.authRequests.push({ toolCallId: tcId, authConfig: authConf });
233
+ }
234
+ }
235
+
236
+ // Track agent info
237
+ if (event.author && event.author !== "user") {
238
+ this.lastAgentInfo = {
239
+ name: event.author ?? undefined,
240
+ branch: event.branch ?? undefined,
241
+ };
242
+ }
243
+
244
+ // Handle interrupted events
245
+ if (event.interrupted) {
246
+ if (this.currentMessageId) {
247
+ const msg = this.messagesMap.get(this.currentMessageId);
248
+ if (msg && msg.type === "ai" && !msg.status) {
249
+ const updated: InProgressMessage = {
250
+ ...msg,
251
+ content: [...this.getContentArray(msg)],
252
+ status: { type: "incomplete", reason: "cancelled" },
253
+ };
254
+ this.messagesMap.set(updated.id, updated);
255
+ }
256
+ }
257
+ this.finalizeCurrentMessage();
258
+ return this.getMessages();
259
+ }
260
+
261
+ // Handle error events
262
+ if (event.errorCode || event.errorMessage) {
263
+ this.finalizeCurrentMessage();
264
+ const errorMsg = this.getOrCreateAiMessage(event);
265
+ const errorText =
266
+ event.errorMessage ?? event.errorCode ?? "Unknown error";
267
+ const updated: InProgressMessage = {
268
+ ...errorMsg,
269
+ content: [
270
+ ...this.getContentArray(errorMsg),
271
+ { type: "text", text: errorText },
272
+ ],
273
+ status: { type: "incomplete", reason: "error", error: errorText },
274
+ };
275
+ this.messagesMap.set(updated.id, updated);
276
+ this.currentMessageId = null;
277
+ return this.getMessages();
278
+ }
279
+
280
+ const parts = event.content?.parts;
281
+ if (!parts?.length) {
282
+ // Track metadata even for content-less events (e.g. turnComplete)
283
+ this.trackMessageMetadata(event);
284
+ return this.getMessages();
285
+ }
286
+
287
+ // If author changed, finalize previous message
288
+ if (this.currentMessageId && event.author && event.author !== "user") {
289
+ const current = this.messagesMap.get(this.currentMessageId);
290
+ if (current && current.type === "ai" && current.author !== event.author) {
291
+ this.finalizeCurrentMessage();
292
+ }
293
+ }
294
+
295
+ for (const part of parts) {
296
+ this.processPart(part, event);
297
+ }
298
+
299
+ // Track per-message metadata (grounding, citation, usage)
300
+ this.trackMessageMetadata(event);
301
+
302
+ // Check isFinalResponse (can be true even for partial events via skipSummarization/longRunningToolIds)
303
+ if (isFinalResponse(event) && this.currentMessageId) {
304
+ const msg = this.messagesMap.get(this.currentMessageId);
305
+ if (msg && msg.type === "ai" && !msg.status) {
306
+ const status = finishReasonToStatus(event.finishReason);
307
+ const updated: InProgressMessage = {
308
+ ...msg,
309
+ content: [...this.getContentArray(msg)],
310
+ status,
311
+ };
312
+ this.messagesMap.set(updated.id, updated);
313
+ }
314
+ }
315
+
316
+ // Non-partial event finalizes the current message
317
+ if (!event.partial || isFinalResponse(event)) {
318
+ this.finalizeCurrentMessage();
319
+ }
320
+
321
+ return this.getMessages();
322
+ }
323
+
324
+ private processPart(part: AdkEventPart, event: AdkEvent): void {
325
+ // Detect special ADK function calls
326
+ if (part.functionCall) {
327
+ const name = part.functionCall.name;
328
+
329
+ // Tool confirmation request
330
+ if (name === ADK_REQUEST_CONFIRMATION) {
331
+ const callArgs = part.functionCall.args;
332
+ // ADK JS: args keys are "originalFunctionCall" and "toolConfirmation"
333
+ // ADK Python: args keys are "original_function_call" and "tool_confirmation"
334
+ const original =
335
+ (callArgs.originalFunctionCall as Record<string, unknown>) ??
336
+ (callArgs.original_function_call as Record<string, unknown>);
337
+ const conf =
338
+ (callArgs.toolConfirmation as Record<string, unknown>) ??
339
+ (callArgs.tool_confirmation as Record<string, unknown>);
340
+ this.toolConfirmations.push({
341
+ toolCallId: part.functionCall.id ?? "",
342
+ toolName: (original?.name as string) ?? "",
343
+ args: (original?.args as Record<string, unknown>) ?? {},
344
+ hint: (conf?.hint as string) ?? "",
345
+ confirmed: false,
346
+ payload: conf?.payload,
347
+ });
348
+ }
349
+
350
+ // Auth credential request
351
+ if (name === ADK_REQUEST_CREDENTIAL) {
352
+ const credArgs = part.functionCall.args;
353
+ // ADK JS: args keys are "function_call_id" and "auth_config"
354
+ const originalToolCallId =
355
+ (credArgs.function_call_id as string) ?? part.functionCall.id ?? "";
356
+ const authConfig = credArgs.auth_config ?? credArgs;
357
+ this.authRequests.push({
358
+ toolCallId: originalToolCallId,
359
+ authConfig,
360
+ });
361
+ }
362
+ }
363
+
364
+ // Text with thought=true → reasoning (accumulated)
365
+ if (part.text != null && part.thought) {
366
+ const msg = this.getOrCreateAiMessage(event);
367
+ if (event.partial) {
368
+ this.partialReasoningBuffer += part.text;
369
+ this.replaceLastReasoningContent(msg, this.partialReasoningBuffer);
370
+ } else {
371
+ this.partialReasoningBuffer = "";
372
+ this.replaceLastReasoningContent(msg, part.text);
373
+ }
374
+ return;
375
+ }
376
+
377
+ // Regular text
378
+ if (part.text != null) {
379
+ const msg = this.getOrCreateAiMessage(event);
380
+ if (event.partial) {
381
+ this.partialTextBuffer += part.text;
382
+ this.replaceLastTextContent(msg, this.partialTextBuffer);
383
+ } else {
384
+ this.partialTextBuffer = "";
385
+ this.replaceLastTextContent(msg, part.text);
386
+ }
387
+ return;
388
+ }
389
+
390
+ // Function call (including special ones above — still create tool-call parts)
391
+ if (part.functionCall) {
392
+ const msg = this.getOrCreateAiMessage(event);
393
+ const toolCall: AdkToolCall = {
394
+ id: part.functionCall.id ?? uuidv4(),
395
+ name: part.functionCall.name,
396
+ args: part.functionCall.args as ReadonlyJSONObject,
397
+ argsText: JSON.stringify(part.functionCall.args),
398
+ };
399
+ const existing = [...(msg.tool_calls ?? [])];
400
+ const idx = existing.findIndex((tc) => tc.id === toolCall.id);
401
+ if (idx >= 0) {
402
+ existing[idx] = toolCall;
403
+ } else {
404
+ existing.push(toolCall);
405
+ }
406
+ const updated: InProgressMessage = {
407
+ ...msg,
408
+ content: [...this.getContentArray(msg)],
409
+ tool_calls: existing,
410
+ };
411
+ this.messagesMap.set(updated.id, updated);
412
+ return;
413
+ }
414
+
415
+ // Function response → tool message
416
+ if (part.functionResponse) {
417
+ this.finalizeCurrentMessage();
418
+ const toolMsg: AdkMessage = {
419
+ id: uuidv4(),
420
+ type: "tool",
421
+ tool_call_id: part.functionResponse.id ?? "",
422
+ name: part.functionResponse.name,
423
+ content: JSON.stringify(part.functionResponse.response),
424
+ status: "success",
425
+ };
426
+ this.messagesMap.set(toolMsg.id, toolMsg);
427
+ return;
428
+ }
429
+
430
+ // Executable code
431
+ if (part.executableCode) {
432
+ const msg = this.getOrCreateAiMessage(event);
433
+ this.appendContent(msg, {
434
+ type: "code",
435
+ code: part.executableCode.code,
436
+ language: part.executableCode.language ?? "python",
437
+ });
438
+ return;
439
+ }
440
+
441
+ // Code execution result
442
+ if (part.codeExecutionResult) {
443
+ const msg = this.getOrCreateAiMessage(event);
444
+ this.appendContent(msg, {
445
+ type: "code_result",
446
+ output: part.codeExecutionResult.output,
447
+ outcome: part.codeExecutionResult.outcome ?? "OUTCOME_OK",
448
+ });
449
+ return;
450
+ }
451
+
452
+ // Inline data (images etc)
453
+ if (part.inlineData) {
454
+ const msg = this.getOrCreateAiMessage(event);
455
+ this.appendContent(msg, {
456
+ type: "image",
457
+ mimeType: part.inlineData.mimeType,
458
+ data: part.inlineData.data,
459
+ });
460
+ return;
461
+ }
462
+
463
+ // File data (URI reference)
464
+ if (part.fileData) {
465
+ const msg = this.getOrCreateAiMessage(event);
466
+ this.appendContent(msg, {
467
+ type: "image_url",
468
+ url: part.fileData.fileUri,
469
+ });
470
+ }
471
+ }
472
+
473
+ private trackMessageMetadata(event: AdkEvent): void {
474
+ if (
475
+ !this.currentMessageId ||
476
+ (!event.groundingMetadata &&
477
+ !event.citationMetadata &&
478
+ !event.usageMetadata)
479
+ )
480
+ return;
481
+
482
+ const existing = this.messageMetadataMap.get(this.currentMessageId) ?? {};
483
+ const updated: AdkMessageMetadata = { ...existing };
484
+ if (event.groundingMetadata)
485
+ updated.groundingMetadata = event.groundingMetadata;
486
+ if (event.citationMetadata)
487
+ updated.citationMetadata = event.citationMetadata;
488
+ if (event.usageMetadata) updated.usageMetadata = event.usageMetadata;
489
+ this.messageMetadataMap.set(this.currentMessageId, updated);
490
+ }
491
+
492
+ private getContentArray(msg: AdkMessage): AdkMessageContentPart[] {
493
+ return Array.isArray(msg.content)
494
+ ? (msg.content as AdkMessageContentPart[])
495
+ : [];
496
+ }
497
+
498
+ private getOrCreateAiMessage(event: AdkEvent): InProgressMessage {
499
+ if (this.currentMessageId) {
500
+ const existing = this.messagesMap.get(this.currentMessageId);
501
+ if (existing && existing.type === "ai") {
502
+ return existing;
503
+ }
504
+ }
505
+
506
+ const id = uuidv4();
507
+ const msg: InProgressMessage = {
508
+ id,
509
+ type: "ai",
510
+ content: [] as AdkMessageContentPart[],
511
+ ...(event.author != null && { author: event.author }),
512
+ ...(event.branch != null && { branch: event.branch }),
513
+ };
514
+ this.messagesMap.set(id, msg);
515
+ this.currentMessageId = id;
516
+ this.partialTextBuffer = "";
517
+ this.partialReasoningBuffer = "";
518
+ return msg;
519
+ }
520
+
521
+ private appendContent(
522
+ msg: InProgressMessage,
523
+ part: AdkMessageContentPart,
524
+ ): void {
525
+ const content = [...this.getContentArray(msg), part];
526
+ const updated: InProgressMessage = { ...msg, content };
527
+ this.messagesMap.set(updated.id, updated);
528
+ }
529
+
530
+ private replaceLastTextContent(msg: InProgressMessage, text: string): void {
531
+ const content = [...this.getContentArray(msg)];
532
+ const lastTextIdx = content.findLastIndex((p) => p.type === "text");
533
+ if (lastTextIdx >= 0) {
534
+ content[lastTextIdx] = { type: "text", text };
535
+ } else {
536
+ content.push({ type: "text", text });
537
+ }
538
+ const updated: InProgressMessage = { ...msg, content };
539
+ this.messagesMap.set(updated.id, updated);
540
+ }
541
+
542
+ private replaceLastReasoningContent(
543
+ msg: InProgressMessage,
544
+ text: string,
545
+ ): void {
546
+ const content = [...this.getContentArray(msg)];
547
+ const lastIdx = content.findLastIndex((p) => p.type === "reasoning");
548
+ if (lastIdx >= 0) {
549
+ content[lastIdx] = { type: "reasoning", text };
550
+ } else {
551
+ content.push({ type: "reasoning", text });
552
+ }
553
+ const updated: InProgressMessage = { ...msg, content };
554
+ this.messagesMap.set(updated.id, updated);
555
+ }
556
+
557
+ private finalizeCurrentMessage(): void {
558
+ this.partialTextBuffer = "";
559
+ this.partialReasoningBuffer = "";
560
+ this.currentMessageId = null;
561
+ }
562
+
563
+ getMessages(): AdkMessage[] {
564
+ return [...this.messagesMap.values()];
565
+ }
566
+
567
+ getStateDelta(): Record<string, unknown> {
568
+ return { ...this.accumulatedStateDelta };
569
+ }
570
+
571
+ getArtifactDelta(): Record<string, number> {
572
+ return { ...this.accumulatedArtifactDelta };
573
+ }
574
+
575
+ getAgentInfo(): { name?: string | undefined; branch?: string | undefined } {
576
+ return { ...this.lastAgentInfo };
577
+ }
578
+
579
+ getLastTransferToAgent(): string | undefined {
580
+ return this.lastTransferToAgent;
581
+ }
582
+
583
+ getLongRunningToolIds(): string[] {
584
+ return [...this.pendingLongRunningToolIds];
585
+ }
586
+
587
+ getToolConfirmations(): AdkToolConfirmation[] {
588
+ return [...this.toolConfirmations];
589
+ }
590
+
591
+ getAuthRequests(): AdkAuthRequest[] {
592
+ return [...this.authRequests];
593
+ }
594
+
595
+ isEscalated(): boolean {
596
+ return this.escalated;
597
+ }
598
+
599
+ getMessageMetadata(): Map<string, AdkMessageMetadata> {
600
+ return new Map(this.messageMetadataMap);
601
+ }
602
+ }