@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.
- package/LICENSE +1 -1
- package/dist/AdkClient.d.ts +24 -26
- package/dist/AdkClient.d.ts.map +1 -1
- package/dist/AdkClient.js +5 -3
- package/dist/AdkClient.js.map +1 -1
- package/dist/AdkEventAccumulator.d.ts +39 -41
- package/dist/AdkEventAccumulator.d.ts.map +1 -1
- package/dist/AdkSessionAdapter.d.ts +38 -39
- package/dist/AdkSessionAdapter.d.ts.map +1 -1
- package/dist/AdkSessionAdapter.js +4 -3
- package/dist/AdkSessionAdapter.js.map +1 -1
- package/dist/adkAuthRequest.d.ts +17 -0
- package/dist/adkAuthRequest.d.ts.map +1 -0
- package/dist/adkAuthRequest.js +45 -0
- package/dist/adkAuthRequest.js.map +1 -0
- package/dist/adkExtras.d.ts +1 -3
- package/dist/adkExtras.d.ts.map +1 -1
- package/dist/adkToolApproval.d.ts +12 -14
- package/dist/adkToolApproval.d.ts.map +1 -1
- package/dist/contentToParts.d.ts +1 -3
- package/dist/contentToParts.d.ts.map +1 -1
- package/dist/contentToParts.js +1 -0
- package/dist/contentToParts.js.map +1 -1
- package/dist/convertAdkMessages.d.ts +3 -5
- package/dist/convertAdkMessages.d.ts.map +1 -1
- package/dist/convertAdkMessages.js +8 -3
- package/dist/convertAdkMessages.js.map +1 -1
- package/dist/convertToAdkMessages.d.ts +32 -34
- package/dist/convertToAdkMessages.d.ts.map +1 -1
- package/dist/hooks.d.ts +10 -7
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +11 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +11 -10
- package/dist/index.d.ts.map +1 -0
- package/dist/normalizeAdkPart.d.ts +0 -2
- package/dist/normalizeAdkPart.d.ts.map +1 -1
- package/dist/parseAdkEvent.d.ts +1 -3
- package/dist/parseAdkEvent.d.ts.map +1 -1
- package/dist/raceWithAbortSignal.d.ts +2 -0
- package/dist/raceWithAbortSignal.d.ts.map +1 -0
- package/dist/raceWithAbortSignal.js +45 -0
- package/dist/raceWithAbortSignal.js.map +1 -0
- package/dist/sdkIdentity.d.ts +1 -3
- package/dist/sdkIdentity.d.ts.map +1 -1
- package/dist/sdkIdentity.js +1 -1
- package/dist/server/adkEventStream.d.ts +23 -24
- package/dist/server/adkEventStream.d.ts.map +1 -1
- package/dist/server/adkEventStream.js +23 -15
- package/dist/server/adkEventStream.js.map +1 -1
- package/dist/server/createAdkApiRoute.d.ts +21 -22
- package/dist/server/createAdkApiRoute.d.ts.map +1 -1
- package/dist/server/index.d.ts +4 -4
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/parseAdkRequest.d.ts +16 -17
- package/dist/server/parseAdkRequest.d.ts.map +1 -1
- package/dist/structuredEvents.d.ts +1 -3
- package/dist/structuredEvents.d.ts.map +1 -1
- package/dist/toAdkFunctionResponse.d.ts +0 -2
- package/dist/toAdkFunctionResponse.d.ts.map +1 -1
- package/dist/trimTrailingSlashes.d.ts +0 -2
- package/dist/trimTrailingSlashes.d.ts.map +1 -1
- package/dist/types.d.ts +232 -234
- package/dist/types.d.ts.map +1 -1
- package/dist/useAdkMessages.d.ts +25 -27
- package/dist/useAdkMessages.d.ts.map +1 -1
- package/dist/useAdkMessages.js +6 -5
- package/dist/useAdkMessages.js.map +1 -1
- package/dist/useAdkRuntime.d.ts +48 -49
- package/dist/useAdkRuntime.d.ts.map +1 -1
- package/package.json +8 -5
- package/src/AdkClient.test.ts +50 -0
- package/src/AdkClient.ts +10 -3
- package/src/AdkSessionAdapter.test.ts +17 -0
- package/src/AdkSessionAdapter.ts +8 -3
- package/src/adkAuthRequest.test.ts +101 -0
- package/src/adkAuthRequest.ts +50 -0
- package/src/contentToParts.ts +2 -0
- package/src/convertAdkMessages.test.ts +54 -0
- package/src/convertAdkMessages.ts +24 -7
- package/src/convertToAdkMessages.test.ts +6 -0
- package/src/hooks.render.test.tsx +95 -2
- package/src/hooks.ts +12 -13
- package/src/raceWithAbortSignal.test.ts +73 -0
- package/src/raceWithAbortSignal.ts +48 -0
- package/src/server/adkEventStream.test.ts +147 -0
- package/src/server/adkEventStream.ts +27 -20
- package/src/tests/settleOutsideAct.ts +15 -0
- package/src/useAdkMessages.fast-refresh.test.tsx +114 -0
- package/src/useAdkMessages.test.ts +35 -0
- package/src/useAdkMessages.ts +5 -5
- package/src/useAdkRuntime.refetch.test.tsx +7 -6
- package/src/useAdkRuntime.replacement.test.tsx +4 -3
- 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();
|
package/src/useAdkMessages.ts
CHANGED
|
@@ -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 !==
|
|
230
|
-
|
|
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
|
-
|
|
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
|
|
101
|
-
|
|
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
|
|
254
|
-
|
|
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
|
|
101
|
-
|
|
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
|
|
157
|
-
|
|
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
|
});
|