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

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 (94) hide show
  1. package/LICENSE +1 -1
  2. package/dist/AdkClient.d.ts +24 -26
  3. package/dist/AdkClient.d.ts.map +1 -1
  4. package/dist/AdkClient.js +5 -3
  5. package/dist/AdkClient.js.map +1 -1
  6. package/dist/AdkEventAccumulator.d.ts +39 -41
  7. package/dist/AdkEventAccumulator.d.ts.map +1 -1
  8. package/dist/AdkSessionAdapter.d.ts +38 -39
  9. package/dist/AdkSessionAdapter.d.ts.map +1 -1
  10. package/dist/AdkSessionAdapter.js +4 -3
  11. package/dist/AdkSessionAdapter.js.map +1 -1
  12. package/dist/adkAuthRequest.d.ts +17 -0
  13. package/dist/adkAuthRequest.d.ts.map +1 -0
  14. package/dist/adkAuthRequest.js +45 -0
  15. package/dist/adkAuthRequest.js.map +1 -0
  16. package/dist/adkExtras.d.ts +1 -3
  17. package/dist/adkExtras.d.ts.map +1 -1
  18. package/dist/adkToolApproval.d.ts +12 -14
  19. package/dist/adkToolApproval.d.ts.map +1 -1
  20. package/dist/contentToParts.d.ts +1 -3
  21. package/dist/contentToParts.d.ts.map +1 -1
  22. package/dist/contentToParts.js +1 -0
  23. package/dist/contentToParts.js.map +1 -1
  24. package/dist/convertAdkMessages.d.ts +3 -5
  25. package/dist/convertAdkMessages.d.ts.map +1 -1
  26. package/dist/convertAdkMessages.js +8 -3
  27. package/dist/convertAdkMessages.js.map +1 -1
  28. package/dist/convertToAdkMessages.d.ts +32 -34
  29. package/dist/convertToAdkMessages.d.ts.map +1 -1
  30. package/dist/hooks.d.ts +10 -7
  31. package/dist/hooks.d.ts.map +1 -1
  32. package/dist/hooks.js +11 -9
  33. package/dist/hooks.js.map +1 -1
  34. package/dist/index.d.ts +11 -10
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/normalizeAdkPart.d.ts +0 -2
  37. package/dist/normalizeAdkPart.d.ts.map +1 -1
  38. package/dist/parseAdkEvent.d.ts +1 -3
  39. package/dist/parseAdkEvent.d.ts.map +1 -1
  40. package/dist/raceWithAbortSignal.d.ts +2 -0
  41. package/dist/raceWithAbortSignal.d.ts.map +1 -0
  42. package/dist/raceWithAbortSignal.js +45 -0
  43. package/dist/raceWithAbortSignal.js.map +1 -0
  44. package/dist/sdkIdentity.d.ts +1 -3
  45. package/dist/sdkIdentity.d.ts.map +1 -1
  46. package/dist/sdkIdentity.js +1 -1
  47. package/dist/server/adkEventStream.d.ts +23 -24
  48. package/dist/server/adkEventStream.d.ts.map +1 -1
  49. package/dist/server/adkEventStream.js +23 -15
  50. package/dist/server/adkEventStream.js.map +1 -1
  51. package/dist/server/createAdkApiRoute.d.ts +21 -22
  52. package/dist/server/createAdkApiRoute.d.ts.map +1 -1
  53. package/dist/server/index.d.ts +4 -4
  54. package/dist/server/index.d.ts.map +1 -0
  55. package/dist/server/parseAdkRequest.d.ts +16 -17
  56. package/dist/server/parseAdkRequest.d.ts.map +1 -1
  57. package/dist/structuredEvents.d.ts +1 -3
  58. package/dist/structuredEvents.d.ts.map +1 -1
  59. package/dist/toAdkFunctionResponse.d.ts +0 -2
  60. package/dist/toAdkFunctionResponse.d.ts.map +1 -1
  61. package/dist/trimTrailingSlashes.d.ts +0 -2
  62. package/dist/trimTrailingSlashes.d.ts.map +1 -1
  63. package/dist/types.d.ts +232 -234
  64. package/dist/types.d.ts.map +1 -1
  65. package/dist/useAdkMessages.d.ts +25 -27
  66. package/dist/useAdkMessages.d.ts.map +1 -1
  67. package/dist/useAdkMessages.js +6 -5
  68. package/dist/useAdkMessages.js.map +1 -1
  69. package/dist/useAdkRuntime.d.ts +48 -49
  70. package/dist/useAdkRuntime.d.ts.map +1 -1
  71. package/package.json +8 -5
  72. package/src/AdkClient.test.ts +50 -0
  73. package/src/AdkClient.ts +10 -3
  74. package/src/AdkSessionAdapter.test.ts +17 -0
  75. package/src/AdkSessionAdapter.ts +8 -3
  76. package/src/adkAuthRequest.test.ts +101 -0
  77. package/src/adkAuthRequest.ts +50 -0
  78. package/src/contentToParts.ts +2 -0
  79. package/src/convertAdkMessages.test.ts +54 -0
  80. package/src/convertAdkMessages.ts +24 -7
  81. package/src/convertToAdkMessages.test.ts +6 -0
  82. package/src/hooks.render.test.tsx +95 -2
  83. package/src/hooks.ts +12 -13
  84. package/src/raceWithAbortSignal.test.ts +73 -0
  85. package/src/raceWithAbortSignal.ts +48 -0
  86. package/src/server/adkEventStream.test.ts +147 -0
  87. package/src/server/adkEventStream.ts +27 -20
  88. package/src/tests/settleOutsideAct.ts +15 -0
  89. package/src/useAdkMessages.fast-refresh.test.tsx +114 -0
  90. package/src/useAdkMessages.test.ts +35 -0
  91. package/src/useAdkMessages.ts +5 -5
  92. package/src/useAdkRuntime.refetch.test.tsx +7 -6
  93. package/src/useAdkRuntime.replacement.test.tsx +4 -3
  94. package/src/useAdkRuntime.toolOwnership.test.tsx +4 -3
@@ -0,0 +1,114 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { act, StrictMode, useEffect } from "react";
4
+ import { afterAll, afterEach, expect, it, vi } from "vitest";
5
+
6
+ type Family = { current: unknown };
7
+ type RendererInternals = {
8
+ setRefreshHandler: (resolve: (type: unknown) => Family | undefined) => void;
9
+ scheduleRefresh: (
10
+ root: unknown,
11
+ update: { staleFamilies: Set<Family>; updatedFamilies: Set<Family> },
12
+ ) => void;
13
+ };
14
+
15
+ let renderer: RendererInternals | undefined;
16
+ const fiberRoots = new Set<unknown>();
17
+ vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
18
+ vi.stubGlobal("__REACT_DEVTOOLS_GLOBAL_HOOK__", {
19
+ supportsFiber: true,
20
+ inject: (internals: RendererInternals) => {
21
+ renderer = internals;
22
+ return 1;
23
+ },
24
+ onScheduleFiberRoot: () => {},
25
+ onCommitFiberRoot: (_id: number, root: unknown) => fiberRoots.add(root),
26
+ onCommitFiberUnmount: () => {},
27
+ });
28
+ const { cleanup, render, waitFor } = await import("@testing-library/react");
29
+ const { useAdkMessages } = await import("./useAdkMessages");
30
+
31
+ afterEach(() => {
32
+ cleanup();
33
+ renderer?.setRefreshHandler(() => undefined);
34
+ fiberRoots.clear();
35
+ });
36
+ afterAll(() => vi.unstubAllGlobals());
37
+
38
+ const refresh = async (Before: unknown, After: unknown) => {
39
+ const family: Family = { current: After };
40
+ renderer!.setRefreshHandler((type) =>
41
+ type === Before || type === After ? family : undefined,
42
+ );
43
+ await act(async () => {
44
+ for (const root of fiberRoots) {
45
+ renderer!.scheduleRefresh(root, {
46
+ staleFamilies: new Set(),
47
+ updatedFamilies: new Set([family]),
48
+ });
49
+ }
50
+ });
51
+ await act(async () => {});
52
+ };
53
+
54
+ it("keeps a direct useAdkMessages stream through Fast Refresh and cancels on unmount", async () => {
55
+ let signal: AbortSignal | undefined;
56
+ let send: (() => void) | undefined;
57
+ let rendered: string | undefined;
58
+ const stream = vi.fn((_messages, config: { abortSignal: AbortSignal }) => {
59
+ signal = config.abortSignal;
60
+ return new Promise<never>(() => {});
61
+ });
62
+ const host = (name: string) => () => {
63
+ rendered = name;
64
+ const { sendMessage } = useAdkMessages({ stream: stream as never });
65
+ send = () => {
66
+ void sendMessage([{ id: "user", type: "human", content: "hello" }], {});
67
+ };
68
+ return null;
69
+ };
70
+ const Before = host("before");
71
+ const After = host("after");
72
+ const view = render(<Before />);
73
+
74
+ act(() => send!());
75
+ await waitFor(() => expect(stream).toHaveBeenCalledTimes(1));
76
+ expect(signal!.aborted).toBe(false);
77
+
78
+ await refresh(Before, After);
79
+ expect(rendered).toBe("after");
80
+ expect(signal!.aborted).toBe(false);
81
+ expect(stream).toHaveBeenCalledTimes(1);
82
+
83
+ view.unmount();
84
+ await act(async () => {});
85
+ expect(signal!.aborted).toBe(true);
86
+ });
87
+
88
+ it("keeps a stream started during StrictMode's effect replay", async () => {
89
+ let signal: AbortSignal | undefined;
90
+ let launched = false;
91
+ const stream = vi.fn((_messages, config: { abortSignal: AbortSignal }) => {
92
+ signal = config.abortSignal;
93
+ return new Promise<never>(() => {});
94
+ });
95
+ const Host = () => {
96
+ const { sendMessage } = useAdkMessages({ stream: stream as never });
97
+ useEffect(() => {
98
+ if (launched) return;
99
+ launched = true;
100
+ void sendMessage([{ id: "user", type: "human", content: "hello" }], {});
101
+ }, [sendMessage]);
102
+ return null;
103
+ };
104
+ const view = render(
105
+ <StrictMode>
106
+ <Host />
107
+ </StrictMode>,
108
+ );
109
+ await waitFor(() => expect(stream).toHaveBeenCalledTimes(1));
110
+ expect(signal!.aborted).toBe(false);
111
+ view.unmount();
112
+ await act(async () => {});
113
+ expect(signal!.aborted).toBe(true);
114
+ });
@@ -77,6 +77,40 @@ describe("optimistic tool outcomes", () => {
77
77
  });
78
78
 
79
79
  describe("ADK runtime callbacks", () => {
80
+ it("reports the same agent transfer again in a later run", async () => {
81
+ const onAgentTransfer = vi.fn();
82
+ const stream: AdkStreamCallback = async function* () {
83
+ yield {
84
+ id: "transfer",
85
+ actions: { transferToAgent: "researcher" },
86
+ };
87
+ yield {
88
+ id: "transfer-duplicate",
89
+ actions: { transferToAgent: "researcher" },
90
+ };
91
+ };
92
+ const { result } = renderHook(() =>
93
+ useAdkMessages({ stream, eventHandlers: { onAgentTransfer } }),
94
+ );
95
+
96
+ await act(async () => {
97
+ await result.current.sendMessage(
98
+ [{ id: "user-1", type: "human", content: "first" }],
99
+ {},
100
+ );
101
+ expect(onAgentTransfer).toHaveBeenCalledTimes(1);
102
+
103
+ await result.current.sendMessage(
104
+ [{ id: "user-2", type: "human", content: "second" }],
105
+ {},
106
+ );
107
+ });
108
+
109
+ expect(onAgentTransfer).toHaveBeenCalledTimes(2);
110
+ expect(onAgentTransfer).toHaveBeenNthCalledWith(1, "researcher");
111
+ expect(onAgentTransfer).toHaveBeenNthCalledWith(2, "researcher");
112
+ });
113
+
80
114
  it.each(["onAgentTransfer", "onCustomEvent", "onError"] as const)(
81
115
  "continues streaming when %s throws",
82
116
  async (callbackName) => {
@@ -305,6 +339,7 @@ describe("ADK stream lifecycle", () => {
305
339
  await started;
306
340
 
307
341
  unmount();
342
+ await act(async () => {});
308
343
 
309
344
  expect(runSignal?.aborted).toBe(true);
310
345
  await expect(sendPromise).resolves.toBeUndefined();
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  useState,
3
3
  useCallback,
4
- useEffect,
5
4
  useInsertionEffect,
6
5
  useRef,
7
6
  useMemo,
8
7
  } from "react";
9
8
  import { generateId } from "@assistant-ui/core";
9
+ import { useReplaySafeEffect } from "@assistant-ui/store/internal";
10
10
  import { useAui } from "@assistant-ui/store";
11
11
  import {
12
12
  abortableIterable,
@@ -71,7 +71,6 @@ export const useAdkMessages = ({
71
71
  const [messageMetadata, setMessageMetadata] = useState<
72
72
  Map<string, AdkMessageMetadata>
73
73
  >(new Map());
74
- const lastTransferToAgentRef = useRef<string | undefined>(undefined);
75
74
  // setMessagesImmediate and setLongRunningToolIds are the only writers of their state and publish these refs with it, so neither ref trails a commit.
76
75
  const messagesRef = useRef(messages);
77
76
  const longRunningToolIdsRef = useRef(longRunningToolIds);
@@ -172,6 +171,7 @@ export const useAdkMessages = ({
172
171
  setLongRunningToolIds(accumulator.getLongRunningToolIds());
173
172
  setToolConfirmations(accumulator.getToolConfirmations());
174
173
  setAuthRequests(accumulator.getAuthRequests());
174
+ let lastTransferToAgent: string | undefined;
175
175
 
176
176
  // Google ADK replaces active runs, while React LangGraph queues sends.
177
177
  abortControllerRef.current?.abort();
@@ -226,8 +226,8 @@ export const useAdkMessages = ({
226
226
  }
227
227
 
228
228
  const transfer = accumulator.getLastTransferToAgent();
229
- if (transfer && transfer !== lastTransferToAgentRef.current) {
230
- lastTransferToAgentRef.current = transfer;
229
+ if (transfer && transfer !== lastTransferToAgent) {
230
+ lastTransferToAgent = transfer;
231
231
  invokeAdkRuntimeCallback(
232
232
  "onAgentTransfer",
233
233
  onAgentTransfer,
@@ -286,7 +286,7 @@ export const useAdkMessages = ({
286
286
  }
287
287
  }, []);
288
288
 
289
- useEffect(() => cancel, [cancel]);
289
+ useReplaySafeEffect(() => cancel, []);
290
290
 
291
291
  return {
292
292
  messages,
@@ -11,6 +11,7 @@ import type {
11
11
  import { useAui } from "@assistant-ui/store";
12
12
  import { useAdkRuntime } from "./useAdkRuntime";
13
13
  import type { AdkMessage, AdkThreadSnapshot } from "./types";
14
+ import { settleOutsideAct } from "./tests/settleOutsideAct";
14
15
 
15
16
  const deferred = <T,>() => {
16
17
  let resolve!: (value: T) => void;
@@ -97,9 +98,9 @@ const renderAdk = async (
97
98
  });
98
99
  await waitFor(() => expect(capture.runtime).not.toBeNull());
99
100
 
100
- await act(async () => {
101
- await capture.runtime!.threads.switchToThread("adk-1");
102
- });
101
+ await settleOutsideAct(() =>
102
+ capture.runtime!.threads.switchToThread("adk-1"),
103
+ );
103
104
 
104
105
  return { capture, streamMock, unmount };
105
106
  };
@@ -250,9 +251,9 @@ describe("useAdkRuntime refetch", () => {
250
251
  await act(async () => {
251
252
  render(<Inner />);
252
253
  });
253
- await act(async () => {
254
- await capture.runtime!.threads.switchToThread("adk-1");
255
- });
254
+ await settleOutsideAct(() =>
255
+ capture.runtime!.threads.switchToThread("adk-1"),
256
+ );
256
257
  await waitFor(() => expect(load).toHaveBeenCalledTimes(1));
257
258
 
258
259
  let settled = false;
@@ -10,6 +10,7 @@ import type {
10
10
  } from "@assistant-ui/core";
11
11
  import { useAdkRuntime } from "./useAdkRuntime";
12
12
  import type { AdkEvent } from "./types";
13
+ import { settleOutsideAct } from "./tests/settleOutsideAct";
13
14
 
14
15
  const makeThreadListAdapter = (): RemoteThreadListAdapter => ({
15
16
  list: vi.fn(async () => ({
@@ -97,9 +98,9 @@ describe("useAdkRuntime replacement runs", () => {
97
98
  render(<Inner />);
98
99
  });
99
100
  await waitFor(() => expect(capture.runtime).not.toBeNull());
100
- await act(async () => {
101
- await capture.runtime!.threads.switchToThread("adk-1");
102
- });
101
+ await settleOutsideAct(() =>
102
+ capture.runtime!.threads.switchToThread("adk-1"),
103
+ );
103
104
 
104
105
  act(() => {
105
106
  capture.runtime!.thread.append({
@@ -14,6 +14,7 @@ import type {
14
14
  } from "@assistant-ui/core";
15
15
  import { useAdkRuntime } from "./useAdkRuntime";
16
16
  import type { AdkEvent } from "./types";
17
+ import { settleOutsideAct } from "./tests/settleOutsideAct";
17
18
 
18
19
  const makeThreadListAdapter = (): RemoteThreadListAdapter => ({
19
20
  list: vi.fn(async () => ({
@@ -153,9 +154,9 @@ const renderStreamingAdk = async (first: AdkEvent, second: AdkEvent) => {
153
154
  render(<Inner />);
154
155
  });
155
156
  await waitFor(() => expect(capture.runtime).not.toBeNull());
156
- await act(async () => {
157
- await capture.runtime!.threads.switchToThread("adk-1");
158
- });
157
+ await settleOutsideAct(() =>
158
+ capture.runtime!.threads.switchToThread("adk-1"),
159
+ );
159
160
  await act(async () => {
160
161
  await new Promise((r) => setTimeout(r, 0));
161
162
  });