@assistant-ui/react-google-adk 0.0.5 → 0.0.6

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.
@@ -399,7 +399,7 @@ describe("AdkEventAccumulator - isFinalResponse logic", () => {
399
399
  });
400
400
  });
401
401
 
402
- it("marks as final when longRunningToolIds is non-empty", () => {
402
+ it("HITL event leaves status undefined for auto-status", () => {
403
403
  const acc = new AdkEventAccumulator();
404
404
  const msgs = acc.processEvent(
405
405
  makeEvent({
@@ -413,10 +413,283 @@ describe("AdkEventAccumulator - isFinalResponse logic", () => {
413
413
  },
414
414
  }),
415
415
  );
416
+ expect((msgs[0] as AdkMessage & { type: "ai" }).status).toBeUndefined();
417
+ });
418
+ });
419
+
420
+ describe("AdkEventAccumulator - HITL requires-action", () => {
421
+ const makeHitlEvent = (
422
+ toolCallId: string,
423
+ overrides: Partial<AdkEvent> = {},
424
+ ): AdkEvent =>
425
+ makeEvent({
426
+ author: "agent",
427
+ longRunningToolIds: [toolCallId],
428
+ content: {
429
+ parts: [
430
+ {
431
+ functionCall: {
432
+ id: toolCallId,
433
+ name: "adk_request_input",
434
+ args: { message: "What's the forecast period?" },
435
+ },
436
+ },
437
+ ],
438
+ },
439
+ ...overrides,
440
+ });
441
+
442
+ it("produces an AI message with tool call and no manual status", () => {
443
+ const acc = new AdkEventAccumulator();
444
+ const msgs = acc.processEvent(makeHitlEvent("tc-1"));
445
+
446
+ expect(msgs).toHaveLength(1);
447
+ const aiMsg = msgs[0] as AdkMessage & { type: "ai" };
448
+ expect(aiMsg.type).toBe("ai");
449
+ expect(aiMsg.tool_calls).toHaveLength(1);
450
+ expect(aiMsg.tool_calls![0]!.id).toBe("tc-1");
451
+ expect(aiMsg.tool_calls![0]!.name).toBe("adk_request_input");
452
+ expect(aiMsg.status).toBeUndefined();
453
+ });
454
+
455
+ it("matches tool call id to the longRunningToolIds entry", () => {
456
+ const acc = new AdkEventAccumulator();
457
+ const msgs = acc.processEvent(makeHitlEvent("tc-1"));
458
+ const aiMsg = msgs[0] as AdkMessage & { type: "ai" };
459
+ expect(aiMsg.tool_calls![0]!.id).toBe("tc-1");
460
+ });
461
+
462
+ it("stays pending across a subsequent bookkeeping event", () => {
463
+ const acc = new AdkEventAccumulator();
464
+ acc.processEvent(makeHitlEvent("tc-1", { author: "WorkflowA" }));
465
+
466
+ const msgs = acc.processEvent(
467
+ makeEvent({
468
+ id: "evt-bookkeeping",
469
+ author: "WorkflowA",
470
+ actions: { stateDelta: { waiting_for_user: true } },
471
+ }),
472
+ );
473
+
474
+ const aiMsg = msgs.find(
475
+ (m): m is AdkMessage & { type: "ai" } =>
476
+ m.type === "ai" && (m.tool_calls?.length ?? 0) > 0,
477
+ );
478
+ expect(aiMsg).toBeDefined();
479
+ expect(aiMsg!.tool_calls![0]!.id).toBe("tc-1");
480
+ expect(aiMsg!.status).toBeUndefined();
481
+ });
482
+
483
+ it("assigns manual complete status on non-HITL final event", () => {
484
+ const acc = new AdkEventAccumulator();
485
+ const msgs = acc.processEvent(
486
+ makeEvent({
487
+ author: "agent",
488
+ content: { role: "model", parts: [{ text: "Done." }] },
489
+ }),
490
+ );
491
+
416
492
  expect(msgs[0]).toMatchObject({
417
493
  status: { type: "complete", reason: "stop" },
418
494
  });
419
495
  });
496
+
497
+ it("assigns manual complete status on skipSummarization final event", () => {
498
+ const acc = new AdkEventAccumulator();
499
+ const msgs = acc.processEvent(
500
+ makeEvent({
501
+ author: "agent",
502
+ actions: { skipSummarization: true },
503
+ content: { role: "model", parts: [{ text: "skipped" }] },
504
+ }),
505
+ );
506
+
507
+ expect(msgs[0]).toMatchObject({
508
+ status: { type: "complete", reason: "stop" },
509
+ });
510
+ });
511
+
512
+ it("prioritizes skipSummarization over longRunningToolIds", () => {
513
+ const acc = new AdkEventAccumulator();
514
+ const msgs = acc.processEvent(
515
+ makeEvent({
516
+ author: "agent",
517
+ longRunningToolIds: ["lrt-both"],
518
+ actions: { skipSummarization: true },
519
+ content: {
520
+ role: "model",
521
+ parts: [
522
+ { functionCall: { name: "slow_tool", id: "lrt-both", args: {} } },
523
+ ],
524
+ },
525
+ }),
526
+ );
527
+
528
+ expect(msgs[0]).toMatchObject({
529
+ status: { type: "complete", reason: "stop" },
530
+ });
531
+ });
532
+
533
+ it("ignores empty longRunningToolIds array for HITL guard", () => {
534
+ const acc = new AdkEventAccumulator();
535
+ const msgs = acc.processEvent(
536
+ makeEvent({
537
+ author: "agent",
538
+ longRunningToolIds: [],
539
+ content: { role: "model", parts: [{ text: "Done." }] },
540
+ }),
541
+ );
542
+
543
+ expect(msgs[0]).toMatchObject({
544
+ status: { type: "complete", reason: "stop" },
545
+ });
546
+ });
547
+
548
+ it("preserves text and skips status when partial text precedes HITL event", () => {
549
+ const acc = new AdkEventAccumulator();
550
+ acc.processEvent(
551
+ makeEvent({
552
+ author: "ClarifyAgent",
553
+ partial: true,
554
+ content: {
555
+ role: "model",
556
+ parts: [{ text: "Let me ask about " }],
557
+ },
558
+ }),
559
+ );
560
+
561
+ const msgs = acc.processEvent(
562
+ makeHitlEvent("tc-1", { author: "ClarifyAgent" }),
563
+ );
564
+
565
+ const aiMsg = msgs.find(
566
+ (m): m is AdkMessage & { type: "ai" } =>
567
+ m.type === "ai" && (m.tool_calls?.length ?? 0) > 0,
568
+ );
569
+ expect(aiMsg).toBeDefined();
570
+ expect(aiMsg!.tool_calls).toHaveLength(1);
571
+ expect(
572
+ aiMsg!.content.some(
573
+ (c) => c.type === "text" && c.text.includes("Let me ask"),
574
+ ),
575
+ ).toBe(true);
576
+ expect(aiMsg!.status).toBeUndefined();
577
+ });
578
+
579
+ it("keeps status undefined for mixed text and functionCall content", () => {
580
+ const acc = new AdkEventAccumulator();
581
+ const msgs = acc.processEvent(
582
+ makeEvent({
583
+ author: "agent",
584
+ longRunningToolIds: ["mixed-tc-1"],
585
+ content: {
586
+ role: "model",
587
+ parts: [
588
+ { text: "I need some clarification." },
589
+ {
590
+ functionCall: {
591
+ id: "mixed-tc-1",
592
+ name: "adk_request_input",
593
+ args: { message: "Which region?" },
594
+ },
595
+ },
596
+ ],
597
+ },
598
+ }),
599
+ );
600
+
601
+ const aiMsg = msgs[0] as AdkMessage & { type: "ai" };
602
+ expect(aiMsg.content.some((c) => c.type === "text")).toBe(true);
603
+ expect(aiMsg.tool_calls).toHaveLength(1);
604
+ expect(aiMsg.tool_calls![0]!.id).toBe("mixed-tc-1");
605
+ expect(aiMsg.status).toBeUndefined();
606
+ });
607
+
608
+ it("handles multiple tool calls with partial longRunningToolIds overlap", () => {
609
+ const acc = new AdkEventAccumulator();
610
+ const msgs = acc.processEvent(
611
+ makeEvent({
612
+ author: "agent",
613
+ longRunningToolIds: ["tc-hitl"],
614
+ content: {
615
+ role: "model",
616
+ parts: [
617
+ {
618
+ functionCall: {
619
+ id: "tc-hitl",
620
+ name: "adk_request_input",
621
+ args: { message: "Confirm?" },
622
+ },
623
+ },
624
+ {
625
+ functionCall: {
626
+ id: "tc-regular",
627
+ name: "fetch_data",
628
+ args: { url: "https://example.com" },
629
+ },
630
+ },
631
+ ],
632
+ },
633
+ }),
634
+ );
635
+
636
+ const aiMsg = msgs[0] as AdkMessage & { type: "ai" };
637
+ expect(aiMsg.tool_calls).toHaveLength(2);
638
+ expect(aiMsg.tool_calls!.map((tc) => tc.id).sort()).toEqual(
639
+ ["tc-hitl", "tc-regular"].sort(),
640
+ );
641
+ expect(aiMsg.status).toBeUndefined();
642
+ });
643
+
644
+ it("does not override HITL guard with explicit finishReason", () => {
645
+ const acc = new AdkEventAccumulator();
646
+ const msgs = acc.processEvent(
647
+ makeHitlEvent("tc-fr", { finishReason: "STOP" }),
648
+ );
649
+
650
+ const aiMsg = msgs[0] as AdkMessage & { type: "ai" };
651
+ expect(aiMsg.status).toBeUndefined();
652
+ });
653
+
654
+ it("produces separate messages without status for sequential HITL events", () => {
655
+ const acc = new AdkEventAccumulator();
656
+ acc.processEvent(
657
+ makeHitlEvent("tc-seq-1", { id: "evt-1", author: "WorkflowA" }),
658
+ );
659
+
660
+ const msgs = acc.processEvent(
661
+ makeEvent({
662
+ id: "evt-2",
663
+ author: "WorkflowB",
664
+ longRunningToolIds: ["tc-seq-2"],
665
+ content: {
666
+ role: "model",
667
+ parts: [
668
+ {
669
+ functionCall: {
670
+ id: "tc-seq-2",
671
+ name: "adk_request_confirmation",
672
+ args: {
673
+ originalFunctionCall: { name: "delete_all", args: {} },
674
+ toolConfirmation: { hint: "Sure?", payload: {} },
675
+ },
676
+ },
677
+ },
678
+ ],
679
+ },
680
+ }),
681
+ );
682
+
683
+ const hitlMsgs = msgs.filter(
684
+ (m): m is AdkMessage & { type: "ai" } =>
685
+ m.type === "ai" && (m.tool_calls?.length ?? 0) > 0,
686
+ );
687
+ expect(hitlMsgs).toHaveLength(2);
688
+ expect(hitlMsgs[0]!.status).toBeUndefined();
689
+ expect(hitlMsgs[1]!.status).toBeUndefined();
690
+ expect(hitlMsgs[0]!.tool_calls![0]!.id).toBe("tc-seq-1");
691
+ expect(hitlMsgs[1]!.tool_calls![0]!.id).toBe("tc-seq-2");
692
+ });
420
693
  });
421
694
 
422
695
  describe("AdkEventAccumulator - actions tracking", () => {
@@ -487,6 +760,44 @@ describe("AdkEventAccumulator - actions tracking", () => {
487
760
  );
488
761
  expect(acc.getLongRunningToolIds()).toEqual(["lrt-1", "lrt-2"]);
489
762
  });
763
+
764
+ it("unions longRunningToolIds across multiple events", () => {
765
+ const acc = new AdkEventAccumulator();
766
+ acc.processEvent(
767
+ makeEvent({
768
+ longRunningToolIds: ["lrt-1"],
769
+ author: "agent",
770
+ content: { parts: [{ text: "x" }] },
771
+ }),
772
+ );
773
+ acc.processEvent(
774
+ makeEvent({
775
+ longRunningToolIds: ["lrt-2"],
776
+ author: "agent",
777
+ content: { parts: [{ text: "y" }] },
778
+ }),
779
+ );
780
+ expect(acc.getLongRunningToolIds()).toEqual(["lrt-1", "lrt-2"]);
781
+ });
782
+
783
+ it("deduplicates repeated longRunningToolIds", () => {
784
+ const acc = new AdkEventAccumulator();
785
+ acc.processEvent(
786
+ makeEvent({
787
+ longRunningToolIds: ["lrt-1"],
788
+ author: "agent",
789
+ content: { parts: [{ text: "x" }] },
790
+ }),
791
+ );
792
+ acc.processEvent(
793
+ makeEvent({
794
+ longRunningToolIds: ["lrt-1"],
795
+ author: "agent",
796
+ content: { parts: [{ text: "y" }] },
797
+ }),
798
+ );
799
+ expect(acc.getLongRunningToolIds()).toEqual(["lrt-1"]);
800
+ });
490
801
  });
491
802
 
492
803
  describe("AdkEventAccumulator - special function calls", () => {
@@ -576,7 +887,12 @@ describe("AdkEventAccumulator - author/agent tracking", () => {
576
887
 
577
888
  it("does not track user as agent info", () => {
578
889
  const acc = new AdkEventAccumulator();
579
- acc.processEvent(makeTextEvent("hello", false, "user"));
890
+ acc.processEvent(
891
+ makeEvent({
892
+ author: "user",
893
+ content: { role: "user", parts: [{ text: "hello" }] },
894
+ }),
895
+ );
580
896
  expect(acc.getAgentInfo()).toEqual({});
581
897
  });
582
898
 
@@ -667,3 +983,206 @@ describe("AdkEventAccumulator - initial messages", () => {
667
983
  expect(msgs).toHaveLength(2);
668
984
  });
669
985
  });
986
+
987
+ describe("AdkEventAccumulator - user message handling", () => {
988
+ it("creates a human message for user-authored text events", () => {
989
+ const acc = new AdkEventAccumulator();
990
+ const msgs = acc.processEvent(
991
+ makeEvent({
992
+ author: "user",
993
+ content: { role: "user", parts: [{ text: "hello" }] },
994
+ }),
995
+ );
996
+ expect(msgs).toHaveLength(1);
997
+ expect(msgs[0]).toMatchObject({
998
+ type: "human",
999
+ content: "hello",
1000
+ });
1001
+ });
1002
+
1003
+ it("creates separate human and AI messages for a user/agent turn", () => {
1004
+ const acc = new AdkEventAccumulator();
1005
+ acc.processEvent(
1006
+ makeEvent({
1007
+ id: "u1",
1008
+ author: "user",
1009
+ content: { role: "user", parts: [{ text: "help" }] },
1010
+ }),
1011
+ );
1012
+ const msgs = acc.processEvent(makeTextEvent("How can I help?"));
1013
+ expect(msgs).toHaveLength(2);
1014
+ expect(msgs[0]).toMatchObject({ type: "human", content: "help" });
1015
+ expect(msgs[1]).toMatchObject({
1016
+ type: "ai",
1017
+ content: [{ type: "text", text: "How can I help?" }],
1018
+ });
1019
+ });
1020
+
1021
+ it("handles multiple user/agent turns with correct message types", () => {
1022
+ const acc = new AdkEventAccumulator();
1023
+ acc.processEvent(
1024
+ makeEvent({
1025
+ id: "u1",
1026
+ author: "user",
1027
+ content: { role: "user", parts: [{ text: "hello" }] },
1028
+ }),
1029
+ );
1030
+ acc.processEvent(makeTextEvent("Hi there!"));
1031
+ acc.processEvent(
1032
+ makeEvent({
1033
+ id: "u2",
1034
+ author: "user",
1035
+ content: { role: "user", parts: [{ text: "help" }] },
1036
+ }),
1037
+ );
1038
+ const msgs = acc.processEvent(makeTextEvent("How can I help?"));
1039
+ expect(msgs).toHaveLength(4);
1040
+ expect(msgs[0]).toMatchObject({ type: "human", content: "hello" });
1041
+ expect(msgs[1]).toMatchObject({ type: "ai" });
1042
+ expect(msgs[2]).toMatchObject({ type: "human", content: "help" });
1043
+ expect(msgs[3]).toMatchObject({ type: "ai" });
1044
+ });
1045
+
1046
+ it("creates human message with image content", () => {
1047
+ const acc = new AdkEventAccumulator();
1048
+ const msgs = acc.processEvent(
1049
+ makeEvent({
1050
+ author: "user",
1051
+ content: {
1052
+ role: "user",
1053
+ parts: [{ inlineData: { mimeType: "image/png", data: "abc123" } }],
1054
+ },
1055
+ }),
1056
+ );
1057
+ expect(msgs).toHaveLength(1);
1058
+ expect(msgs[0]).toMatchObject({
1059
+ type: "human",
1060
+ content: [{ type: "image", mimeType: "image/png", data: "abc123" }],
1061
+ });
1062
+ });
1063
+
1064
+ it("creates human message with mixed text and image content", () => {
1065
+ const acc = new AdkEventAccumulator();
1066
+ const msgs = acc.processEvent(
1067
+ makeEvent({
1068
+ author: "user",
1069
+ content: {
1070
+ role: "user",
1071
+ parts: [
1072
+ { text: "Look at this" },
1073
+ { inlineData: { mimeType: "image/png", data: "abc123" } },
1074
+ ],
1075
+ },
1076
+ }),
1077
+ );
1078
+ expect(msgs).toHaveLength(1);
1079
+ expect(msgs[0]).toMatchObject({
1080
+ type: "human",
1081
+ content: [
1082
+ { type: "text", text: "Look at this" },
1083
+ { type: "image", mimeType: "image/png", data: "abc123" },
1084
+ ],
1085
+ });
1086
+ });
1087
+
1088
+ it("creates human message with fileData content", () => {
1089
+ const acc = new AdkEventAccumulator();
1090
+ const msgs = acc.processEvent(
1091
+ makeEvent({
1092
+ author: "user",
1093
+ content: {
1094
+ role: "user",
1095
+ parts: [{ fileData: { fileUri: "gs://bucket/image.png" } }],
1096
+ },
1097
+ }),
1098
+ );
1099
+ expect(msgs).toHaveLength(1);
1100
+ expect(msgs[0]).toMatchObject({
1101
+ type: "human",
1102
+ content: [{ type: "image_url", url: "gs://bucket/image.png" }],
1103
+ });
1104
+ });
1105
+
1106
+ it("tool result events (no author, role:'user') still create tool messages", () => {
1107
+ // Regression: the user-author check must not hijack tool events.
1108
+ // messageToEvent for `type:'tool'` emits events without `author`,
1109
+ // with content.role:'user' and a functionResponse part.
1110
+ const acc = new AdkEventAccumulator();
1111
+ const msgs = acc.processEvent(
1112
+ makeEvent({
1113
+ content: {
1114
+ role: "user",
1115
+ parts: [
1116
+ {
1117
+ functionResponse: {
1118
+ name: "get_weather",
1119
+ id: "call_1",
1120
+ response: { temp: 72 },
1121
+ },
1122
+ },
1123
+ ],
1124
+ },
1125
+ }),
1126
+ );
1127
+ expect(msgs).toHaveLength(1);
1128
+ expect(msgs[0]).toMatchObject({
1129
+ type: "tool",
1130
+ name: "get_weather",
1131
+ tool_call_id: "call_1",
1132
+ content: JSON.stringify({ temp: 72 }),
1133
+ });
1134
+ });
1135
+
1136
+ it("session replay produces correct message types", () => {
1137
+ // Simulate loading a session with alternating user/agent events
1138
+ const acc = new AdkEventAccumulator();
1139
+ const events: AdkEvent[] = [
1140
+ makeEvent({
1141
+ id: "e1",
1142
+ author: "user",
1143
+ content: { role: "user", parts: [{ text: "good morning" }] },
1144
+ }),
1145
+ // Workflow state-only events (no content)
1146
+ makeEvent({
1147
+ id: "e2",
1148
+ author: "WorkflowAgent",
1149
+ actions: { stateDelta: { intent: "greeting" } },
1150
+ }),
1151
+ // Final response with content
1152
+ makeEvent({
1153
+ id: "e3",
1154
+ author: "WorkflowAgent",
1155
+ content: { role: "model", parts: [{ text: "Hello! How can I help?" }] },
1156
+ }),
1157
+ // Second turn
1158
+ makeEvent({
1159
+ id: "e4",
1160
+ author: "user",
1161
+ content: { role: "user", parts: [{ text: "help" }] },
1162
+ }),
1163
+ makeEvent({
1164
+ id: "e5",
1165
+ author: "WorkflowAgent",
1166
+ content: {
1167
+ role: "model",
1168
+ parts: [{ text: "What do you need help with?" }],
1169
+ },
1170
+ }),
1171
+ ];
1172
+ let msgs: AdkMessage[] = [];
1173
+ for (const event of events) {
1174
+ msgs = acc.processEvent(event);
1175
+ }
1176
+ expect(msgs).toHaveLength(4);
1177
+ expect(msgs[0]).toMatchObject({ type: "human", content: "good morning" });
1178
+ expect(msgs[1]).toMatchObject({
1179
+ type: "ai",
1180
+ content: [{ type: "text", text: "Hello! How can I help?" }],
1181
+ });
1182
+ expect(msgs[2]).toMatchObject({ type: "human", content: "help" });
1183
+ expect(msgs[3]).toMatchObject({
1184
+ type: "ai",
1185
+ content: [{ type: "text", text: "What do you need help with?" }],
1186
+ });
1187
+ });
1188
+ });
@@ -166,7 +166,7 @@ export class AdkEventAccumulator {
166
166
  branch?: string | undefined;
167
167
  } = {};
168
168
  private lastTransferToAgent: string | undefined;
169
- private pendingLongRunningToolIds: string[] = [];
169
+ private pendingLongRunningToolIds = new Set<string>();
170
170
  private toolConfirmations: AdkToolConfirmation[] = [];
171
171
  private authRequests: AdkAuthRequest[] = [];
172
172
  private escalated = false;
@@ -202,9 +202,13 @@ export class AdkEventAccumulator {
202
202
  this.lastTransferToAgent = event.actions.transferToAgent;
203
203
  }
204
204
 
205
- // Track long-running tool IDs
205
+ // Track long-running tool IDs. Accumulate across events so that multiple
206
+ // HITL interrupts emitted in the same turn are all tracked — a
207
+ // single-event replacement would drop earlier ids.
206
208
  if (event.longRunningToolIds?.length) {
207
- this.pendingLongRunningToolIds = event.longRunningToolIds;
209
+ for (const id of event.longRunningToolIds) {
210
+ this.pendingLongRunningToolIds.add(id);
211
+ }
208
212
  }
209
213
 
210
214
  // Track tool confirmations from actions
@@ -284,6 +288,41 @@ export class AdkEventAccumulator {
284
288
  return this.getMessages();
285
289
  }
286
290
 
291
+ // User-authored events → create human message, not AI.
292
+ // Without this, user events fall through to processPart →
293
+ // getOrCreateAiMessage, producing type:"ai" messages that
294
+ // convertAdkMessage maps to role:"assistant".
295
+ if (event.author === "user") {
296
+ this.finalizeCurrentMessage();
297
+ const humanParts: AdkMessageContentPart[] = [];
298
+ for (const part of parts) {
299
+ if (part.text != null && !part.thought) {
300
+ humanParts.push({ type: "text", text: part.text });
301
+ } else if (part.inlineData) {
302
+ humanParts.push({
303
+ type: "image",
304
+ mimeType: part.inlineData.mimeType,
305
+ data: part.inlineData.data,
306
+ });
307
+ } else if (part.fileData) {
308
+ humanParts.push({
309
+ type: "image_url",
310
+ url: part.fileData.fileUri,
311
+ });
312
+ }
313
+ }
314
+ if (humanParts.length > 0) {
315
+ const id = event.id ?? uuidv4();
316
+ const first = humanParts[0];
317
+ const content: string | AdkMessageContentPart[] =
318
+ humanParts.length === 1 && first?.type === "text"
319
+ ? first.text
320
+ : humanParts;
321
+ this.messagesMap.set(id, { id, type: "human", content });
322
+ }
323
+ return this.getMessages();
324
+ }
325
+
287
326
  // If author changed, finalize previous message
288
327
  if (this.currentMessageId && event.author && event.author !== "user") {
289
328
  const current = this.messagesMap.get(this.currentMessageId);
@@ -302,7 +341,13 @@ export class AdkEventAccumulator {
302
341
  // Check isFinalResponse (can be true even for partial events via skipSummarization/longRunningToolIds)
303
342
  if (isFinalResponse(event) && this.currentMessageId) {
304
343
  const msg = this.messagesMap.get(this.currentMessageId);
305
- if (msg && msg.type === "ai" && !msg.status) {
344
+ // Skip manual "complete" when longRunningToolIds is the sole reason for
345
+ // isFinalResponse — let auto-status apply requires-action for pending tool calls.
346
+ const isHitlOnly =
347
+ event.longRunningToolIds &&
348
+ event.longRunningToolIds.length > 0 &&
349
+ !event.actions?.skipSummarization;
350
+ if (msg && msg.type === "ai" && !msg.status && !isHitlOnly) {
306
351
  const status = finishReasonToStatus(event.finishReason);
307
352
  const updated: InProgressMessage = {
308
353
  ...msg,
package/src/hooks.ts CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useAui, useAuiState } from "@assistant-ui/store";
4
4
  import { v4 as uuidv4 } from "uuid";
5
+ import type { ReadonlyJSONValue } from "assistant-stream/utils";
5
6
  import type {
6
7
  AdkMessage,
7
8
  AdkSendMessageConfig,
@@ -126,7 +127,11 @@ export const useAdkMessageMetadata = () => {
126
127
  /** Returns a function to confirm or deny a pending tool confirmation. */
127
128
  export const useAdkConfirmTool = () => {
128
129
  const aui = useAui();
129
- return (toolCallId: string, confirmed: boolean, payload?: unknown) => {
130
+ return (
131
+ toolCallId: string,
132
+ confirmed: boolean,
133
+ payload?: ReadonlyJSONValue,
134
+ ) => {
130
135
  const extras = aui.thread().getState().extras;
131
136
  const { send } = asAdkRuntimeExtras(extras);
132
137
  return send(
@@ -170,6 +175,28 @@ export const useAdkSubmitAuth = () => {
170
175
  };
171
176
  };
172
177
 
178
+ /** Returns a function to submit the user's answer for a pending `adk_request_input` HITL interrupt. */
179
+ export const useAdkSubmitInput = () => {
180
+ const aui = useAui();
181
+ return (toolCallId: string, result: ReadonlyJSONValue) => {
182
+ const extras = aui.thread().getState().extras;
183
+ const { send } = asAdkRuntimeExtras(extras);
184
+ return send(
185
+ [
186
+ {
187
+ id: uuidv4(),
188
+ type: "tool",
189
+ tool_call_id: toolCallId,
190
+ name: "adk_request_input",
191
+ content: JSON.stringify({ result }),
192
+ status: "success",
193
+ },
194
+ ],
195
+ {},
196
+ );
197
+ };
198
+ };
199
+
173
200
  // ── State prefix helpers ──
174
201
 
175
202
  const APP_PREFIX = "app:";
package/src/index.ts CHANGED
@@ -60,6 +60,7 @@ export {
60
60
  useAdkMessageMetadata,
61
61
  useAdkConfirmTool,
62
62
  useAdkSubmitAuth,
63
+ useAdkSubmitInput,
63
64
  useAdkAppState,
64
65
  useAdkUserState,
65
66
  useAdkTempState,