@assistant-ui/react-google-adk 0.0.33 → 0.0.34
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/dist/AdkEventAccumulator.d.ts.map +1 -1
- package/dist/AdkEventAccumulator.js +4 -1
- package/dist/AdkEventAccumulator.js.map +1 -1
- package/dist/adkToolApproval.js +1 -1
- package/dist/adkToolApproval.js.map +1 -1
- package/dist/convertAdkMessages.d.ts.map +1 -1
- package/dist/convertAdkMessages.js +30 -19
- package/dist/convertAdkMessages.js.map +1 -1
- package/dist/convertToAdkMessages.d.ts.map +1 -1
- package/dist/convertToAdkMessages.js +2 -2
- package/dist/convertToAdkMessages.js.map +1 -1
- package/dist/sdkIdentity.js +1 -1
- package/dist/useAdkMessages.d.ts.map +1 -1
- package/dist/useAdkMessages.js +23 -6
- package/dist/useAdkMessages.js.map +1 -1
- package/dist/useAdkRuntime.d.ts.map +1 -1
- package/dist/useAdkRuntime.js +5 -4
- package/dist/useAdkRuntime.js.map +1 -1
- package/package.json +6 -6
- package/src/AdkEventAccumulator.test.ts +32 -0
- package/src/AdkEventAccumulator.ts +1 -0
- package/src/adkToolApproval.test.ts +19 -0
- package/src/adkToolApproval.ts +1 -1
- package/src/convertAdkMessages.test.ts +129 -0
- package/src/convertAdkMessages.ts +19 -1
- package/src/convertToAdkMessages.test.ts +14 -0
- package/src/convertToAdkMessages.ts +4 -1
- package/src/useAdkMessages.test.ts +13 -0
- package/src/useAdkMessages.ts +35 -4
- package/src/useAdkRuntime.cancellation.test.tsx +544 -0
- package/src/useAdkRuntime.fast-refresh.test.tsx +166 -0
- package/src/useAdkRuntime.refetch.test.tsx +1 -0
- package/src/useAdkRuntime.ts +4 -4
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { act } from "react";
|
|
4
|
+
import { afterAll, afterEach, expect, it, vi } from "vitest";
|
|
5
|
+
import type { AdkThreadSnapshot } from "./types";
|
|
6
|
+
|
|
7
|
+
type Family = { current: unknown };
|
|
8
|
+
type RendererInternals = {
|
|
9
|
+
setRefreshHandler: (resolve: (type: unknown) => Family | undefined) => void;
|
|
10
|
+
scheduleRefresh: (
|
|
11
|
+
root: unknown,
|
|
12
|
+
update: { staleFamilies: Set<Family>; updatedFamilies: Set<Family> },
|
|
13
|
+
) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const refreshHarness = vi.hoisted(() => {
|
|
17
|
+
const state: {
|
|
18
|
+
renderer: RendererInternals | undefined;
|
|
19
|
+
roots: Set<unknown>;
|
|
20
|
+
} = { renderer: undefined, roots: new Set() };
|
|
21
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
22
|
+
vi.stubGlobal("__REACT_DEVTOOLS_GLOBAL_HOOK__", {
|
|
23
|
+
supportsFiber: true,
|
|
24
|
+
inject: (internals: RendererInternals) => {
|
|
25
|
+
state.renderer = internals;
|
|
26
|
+
return 1;
|
|
27
|
+
},
|
|
28
|
+
onScheduleFiberRoot: () => {},
|
|
29
|
+
onCommitFiberRoot: (_id: number, root: unknown) => state.roots.add(root),
|
|
30
|
+
onCommitFiberUnmount: () => {},
|
|
31
|
+
});
|
|
32
|
+
return state;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const { aui } = vi.hoisted(() => ({
|
|
36
|
+
aui: {
|
|
37
|
+
threadListItem: {
|
|
38
|
+
source: {},
|
|
39
|
+
getState: () => ({ externalId: "adk-1" }),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => ({
|
|
45
|
+
...(await importOriginal<typeof import("@assistant-ui/store")>()),
|
|
46
|
+
useAui: () => aui,
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
vi.mock("@assistant-ui/core/react", async (importOriginal) => ({
|
|
50
|
+
...(await importOriginal<typeof import("@assistant-ui/core/react")>()),
|
|
51
|
+
useCloudThreadListAdapter: () => ({}),
|
|
52
|
+
useRemoteThreadListRuntime: ({
|
|
53
|
+
runtimeHook,
|
|
54
|
+
}: {
|
|
55
|
+
runtimeHook: () => unknown;
|
|
56
|
+
}) => runtimeHook(),
|
|
57
|
+
useExternalMessageConverter: ({ messages }: { messages: unknown }) =>
|
|
58
|
+
messages,
|
|
59
|
+
useExternalStoreRuntime: (options: unknown) => options,
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
const { cleanup, render, waitFor } = await import("@testing-library/react");
|
|
63
|
+
const { useAdkRuntime } = await import("./useAdkRuntime");
|
|
64
|
+
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
cleanup();
|
|
67
|
+
refreshHarness.renderer?.setRefreshHandler(() => undefined);
|
|
68
|
+
refreshHarness.roots.clear();
|
|
69
|
+
});
|
|
70
|
+
afterAll(() => vi.unstubAllGlobals());
|
|
71
|
+
|
|
72
|
+
const deferred = <T,>() => {
|
|
73
|
+
let resolve!: (value: T) => void;
|
|
74
|
+
const promise = new Promise<T>((res) => {
|
|
75
|
+
resolve = res;
|
|
76
|
+
});
|
|
77
|
+
return { promise, resolve };
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const refresh = async (Before: unknown, After: unknown) => {
|
|
81
|
+
const family: Family = { current: After };
|
|
82
|
+
refreshHarness.renderer!.setRefreshHandler((type) =>
|
|
83
|
+
type === Before || type === After ? family : undefined,
|
|
84
|
+
);
|
|
85
|
+
await act(async () => {
|
|
86
|
+
for (const root of refreshHarness.roots) {
|
|
87
|
+
refreshHarness.renderer!.scheduleRefresh(root, {
|
|
88
|
+
staleFamilies: new Set(),
|
|
89
|
+
updatedFamilies: new Set([family]),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
await act(async () => {});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
it("keeps an initial thread load through Fast Refresh and aborts a later load on unmount", async () => {
|
|
97
|
+
const initial = deferred<AdkThreadSnapshot>();
|
|
98
|
+
const refetch = deferred<AdkThreadSnapshot>();
|
|
99
|
+
const signals: AbortSignal[] = [];
|
|
100
|
+
const load = vi.fn(
|
|
101
|
+
(_id: string, options?: { signal?: AbortSignal | undefined }) => {
|
|
102
|
+
signals.push(options!.signal!);
|
|
103
|
+
return signals.length === 1 ? initial.promise : refetch.promise;
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
let runtime:
|
|
107
|
+
| {
|
|
108
|
+
messages: unknown;
|
|
109
|
+
onRefetchThread: () => Promise<void>;
|
|
110
|
+
}
|
|
111
|
+
| undefined;
|
|
112
|
+
let rendered: string | undefined;
|
|
113
|
+
const host = (name: string) => () => {
|
|
114
|
+
rendered = name;
|
|
115
|
+
runtime = useAdkRuntime({
|
|
116
|
+
stream: async function* () {},
|
|
117
|
+
load,
|
|
118
|
+
}) as unknown as typeof runtime;
|
|
119
|
+
return null;
|
|
120
|
+
};
|
|
121
|
+
const Before = host("before");
|
|
122
|
+
const After = host("after");
|
|
123
|
+
const view = render(<Before />);
|
|
124
|
+
await waitFor(() => expect(load).toHaveBeenCalledTimes(1));
|
|
125
|
+
expect(signals[0]!.aborted).toBe(false);
|
|
126
|
+
|
|
127
|
+
await refresh(Before, After);
|
|
128
|
+
expect(rendered).toBe("after");
|
|
129
|
+
expect(signals[0]!.aborted).toBe(false);
|
|
130
|
+
expect(load).toHaveBeenCalledTimes(1);
|
|
131
|
+
|
|
132
|
+
let refetchSettled = false;
|
|
133
|
+
act(() => {
|
|
134
|
+
void runtime!.onRefetchThread().then(() => {
|
|
135
|
+
refetchSettled = true;
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
expect(load).toHaveBeenCalledTimes(1);
|
|
139
|
+
expect(refetchSettled).toBe(false);
|
|
140
|
+
|
|
141
|
+
await act(async () => {
|
|
142
|
+
initial.resolve({
|
|
143
|
+
messages: [
|
|
144
|
+
{
|
|
145
|
+
id: "history-1",
|
|
146
|
+
type: "ai",
|
|
147
|
+
content: [{ type: "text", text: "history landed" }],
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
await waitFor(() => expect(refetchSettled).toBe(true));
|
|
153
|
+
await waitFor(() =>
|
|
154
|
+
expect(JSON.stringify(runtime!.messages)).toContain("history landed"),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
act(() => {
|
|
158
|
+
void runtime!.onRefetchThread();
|
|
159
|
+
});
|
|
160
|
+
await waitFor(() => expect(load).toHaveBeenCalledTimes(2));
|
|
161
|
+
expect(signals[1]!.aborted).toBe(false);
|
|
162
|
+
view.unmount();
|
|
163
|
+
await act(async () => {});
|
|
164
|
+
expect(signals[1]!.aborted).toBe(true);
|
|
165
|
+
refetch.resolve({ messages: [] });
|
|
166
|
+
});
|
package/src/useAdkRuntime.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useCallback,
|
|
3
|
-
useEffect,
|
|
4
3
|
useInsertionEffect,
|
|
5
4
|
useMemo,
|
|
6
5
|
useRef,
|
|
@@ -30,6 +29,7 @@ import {
|
|
|
30
29
|
useExternalStoreRuntime,
|
|
31
30
|
} from "@assistant-ui/core/react";
|
|
32
31
|
import { useAui } from "@assistant-ui/store";
|
|
32
|
+
import { useReplaySafeEffect } from "@assistant-ui/store/internal";
|
|
33
33
|
import type { AssistantCloud } from "assistant-cloud";
|
|
34
34
|
import type { RemoteThreadListAdapter } from "@assistant-ui/core";
|
|
35
35
|
import type {
|
|
@@ -147,7 +147,7 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
|
|
|
147
147
|
useInsertionEffect(() => {
|
|
148
148
|
loadRef.current = load;
|
|
149
149
|
}, [load]);
|
|
150
|
-
const loadController =
|
|
150
|
+
const [loadController] = useState(createAbortableThreadLoad);
|
|
151
151
|
const messagesRef = useRef(messages);
|
|
152
152
|
useInsertionEffect(() => {
|
|
153
153
|
messagesRef.current = messages;
|
|
@@ -325,7 +325,7 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
|
|
|
325
325
|
[threadListItem, loadController, applySnapshot],
|
|
326
326
|
);
|
|
327
327
|
|
|
328
|
-
|
|
328
|
+
useReplaySafeEffect(() => {
|
|
329
329
|
runLoad();
|
|
330
330
|
return () => {
|
|
331
331
|
// Whatever is current, not this effect's own controller: a refetch swaps
|
|
@@ -333,7 +333,7 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
|
|
|
333
333
|
loadController.abort();
|
|
334
334
|
setIsLoadingThread(false);
|
|
335
335
|
};
|
|
336
|
-
}, [
|
|
336
|
+
}, [threadListItem]);
|
|
337
337
|
|
|
338
338
|
const runtime = useExternalStoreRuntime({
|
|
339
339
|
...pickExternalStoreSharedOptions(options),
|