@assistant-ui/react-google-adk 0.0.28 → 0.0.29
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 +2 -2
- package/dist/AdkEventAccumulator.js.map +1 -1
- package/dist/convertToAdkMessages.js +1 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +6 -8
- package/dist/hooks.js.map +1 -1
- package/dist/parseAdkEvent.d.ts.map +1 -1
- package/dist/parseAdkEvent.js +15 -2
- package/dist/parseAdkEvent.js.map +1 -1
- package/dist/useAdkMessages.d.ts.map +1 -1
- package/dist/useAdkMessages.js +8 -5
- package/dist/useAdkMessages.js.map +1 -1
- package/dist/useAdkRuntime.d.ts.map +1 -1
- package/dist/useAdkRuntime.js +4 -2
- package/dist/useAdkRuntime.js.map +1 -1
- package/package.json +5 -5
- package/src/AdkClient.test.ts +53 -0
- package/src/AdkEventAccumulator.test.ts +21 -0
- package/src/AdkEventAccumulator.ts +3 -2
- package/src/hooks.render.test.tsx +74 -0
- package/src/hooks.test.tsx +33 -0
- package/src/hooks.ts +17 -22
- package/src/parseAdkEvent.ts +36 -7
- package/src/useAdkMessages.test.ts +99 -0
- package/src/useAdkMessages.ts +29 -9
- package/src/useAdkRuntime.replacement.test.tsx +138 -0
- package/src/useAdkRuntime.ts +3 -1
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { act, render, waitFor } from "@testing-library/react";
|
|
4
|
+
import { type FC } from "react";
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { AssistantRuntimeProvider } from "@assistant-ui/core/react";
|
|
7
|
+
import type {
|
|
8
|
+
AssistantRuntime,
|
|
9
|
+
RemoteThreadListAdapter,
|
|
10
|
+
} from "@assistant-ui/core";
|
|
11
|
+
import { useAdkRuntime } from "./useAdkRuntime";
|
|
12
|
+
import type { AdkEvent } from "./types";
|
|
13
|
+
|
|
14
|
+
const makeThreadListAdapter = (): RemoteThreadListAdapter => ({
|
|
15
|
+
list: vi.fn(async () => ({
|
|
16
|
+
threads: [
|
|
17
|
+
{
|
|
18
|
+
status: "regular" as const,
|
|
19
|
+
remoteId: "adk-1",
|
|
20
|
+
externalId: "adk-1",
|
|
21
|
+
title: "ADK session",
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
})),
|
|
25
|
+
initialize: vi.fn(async () => ({
|
|
26
|
+
remoteId: "adk-1",
|
|
27
|
+
externalId: "adk-1",
|
|
28
|
+
})),
|
|
29
|
+
rename: vi.fn(async () => {}),
|
|
30
|
+
archive: vi.fn(async () => {}),
|
|
31
|
+
unarchive: vi.fn(async () => {}),
|
|
32
|
+
delete: vi.fn(async () => {}),
|
|
33
|
+
generateTitle: vi.fn(async () => new ReadableStream() as never),
|
|
34
|
+
fetch: vi.fn(async () => ({
|
|
35
|
+
status: "regular" as const,
|
|
36
|
+
remoteId: "adk-1",
|
|
37
|
+
externalId: "adk-1",
|
|
38
|
+
title: "ADK session",
|
|
39
|
+
})),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const deferred = () => {
|
|
43
|
+
let resolve!: () => void;
|
|
44
|
+
const promise = new Promise<void>((r) => {
|
|
45
|
+
resolve = r;
|
|
46
|
+
});
|
|
47
|
+
return { promise, resolve };
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
describe("useAdkRuntime replacement runs", () => {
|
|
51
|
+
it.each([
|
|
52
|
+
{ label: "events after cancellation", cancelFirst: true, failFirst: false },
|
|
53
|
+
{
|
|
54
|
+
label: "events without cancellation",
|
|
55
|
+
cancelFirst: false,
|
|
56
|
+
failFirst: false,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
label: "errors without cancellation",
|
|
60
|
+
cancelFirst: false,
|
|
61
|
+
failFirst: true,
|
|
62
|
+
},
|
|
63
|
+
])("ignores superseded run $label", async ({ cancelFirst, failFirst }) => {
|
|
64
|
+
const gates = [deferred(), deferred()];
|
|
65
|
+
let calls = 0;
|
|
66
|
+
const stream = vi.fn(async function* (): AsyncGenerator<AdkEvent> {
|
|
67
|
+
const call = calls++;
|
|
68
|
+
await gates[call]!.promise;
|
|
69
|
+
if (call === 0 && failFirst) throw new Error("stale run failed");
|
|
70
|
+
yield {
|
|
71
|
+
id: `event-${call}`,
|
|
72
|
+
invocationId: `run-${call}`,
|
|
73
|
+
author: "agent",
|
|
74
|
+
content: { role: "model", parts: [{ text: `done-${call}` }] },
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
const sessionAdapter = makeThreadListAdapter();
|
|
78
|
+
const capture: { runtime: AssistantRuntime | null } = { runtime: null };
|
|
79
|
+
|
|
80
|
+
const Inner: FC = () => {
|
|
81
|
+
const runtime = useAdkRuntime({
|
|
82
|
+
stream,
|
|
83
|
+
sessionAdapter,
|
|
84
|
+
unstable_allowCancellation: true,
|
|
85
|
+
});
|
|
86
|
+
capture.runtime = runtime;
|
|
87
|
+
return <AssistantRuntimeProvider runtime={runtime} />;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
await act(async () => {
|
|
91
|
+
render(<Inner />);
|
|
92
|
+
});
|
|
93
|
+
await waitFor(() => expect(capture.runtime).not.toBeNull());
|
|
94
|
+
await act(async () => {
|
|
95
|
+
await capture.runtime!.threads.switchToThread("adk-1");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
let firstSend!: Promise<void>;
|
|
99
|
+
act(() => {
|
|
100
|
+
firstSend = capture.runtime!.thread.append({
|
|
101
|
+
role: "user",
|
|
102
|
+
content: [{ type: "text", text: "first" }],
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
await waitFor(() => expect(stream).toHaveBeenCalledTimes(1));
|
|
106
|
+
|
|
107
|
+
let secondSend!: Promise<void>;
|
|
108
|
+
await act(async () => {
|
|
109
|
+
if (cancelFirst) await capture.runtime!.thread.cancelRun();
|
|
110
|
+
secondSend = capture.runtime!.thread.append({
|
|
111
|
+
role: "user",
|
|
112
|
+
content: [{ type: "text", text: "second" }],
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
await waitFor(() => expect(stream).toHaveBeenCalledTimes(2));
|
|
116
|
+
|
|
117
|
+
await act(async () => {
|
|
118
|
+
gates[0]!.resolve();
|
|
119
|
+
await firstSend;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const messagesAfterFirstSettles = JSON.stringify(
|
|
123
|
+
capture.runtime!.thread.getState().messages,
|
|
124
|
+
);
|
|
125
|
+
expect(messagesAfterFirstSettles).toContain("second");
|
|
126
|
+
expect(messagesAfterFirstSettles).not.toContain("done-0");
|
|
127
|
+
expect(capture.runtime!.thread.getState().isRunning).toBe(true);
|
|
128
|
+
|
|
129
|
+
await act(async () => {
|
|
130
|
+
gates[1]!.resolve();
|
|
131
|
+
await secondSend;
|
|
132
|
+
});
|
|
133
|
+
expect(
|
|
134
|
+
JSON.stringify(capture.runtime!.thread.getState().messages),
|
|
135
|
+
).toContain("done-1");
|
|
136
|
+
expect(capture.runtime!.thread.getState().isRunning).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
});
|
package/src/useAdkRuntime.ts
CHANGED
|
@@ -168,16 +168,18 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
|
|
|
168
168
|
useInsertionEffect(() => {
|
|
169
169
|
isRunningRef.current = effectiveIsRunning;
|
|
170
170
|
}, [effectiveIsRunning]);
|
|
171
|
+
const runGenerationRef = useRef(0);
|
|
171
172
|
|
|
172
173
|
const handleSendMessage = async (
|
|
173
174
|
msgs: AdkMessage[],
|
|
174
175
|
config: AdkSendMessageConfig,
|
|
175
176
|
) => {
|
|
177
|
+
const generation = ++runGenerationRef.current;
|
|
176
178
|
try {
|
|
177
179
|
setIsRunning(true);
|
|
178
180
|
await sendMessage(msgs, config);
|
|
179
181
|
} finally {
|
|
180
|
-
setIsRunning(false);
|
|
182
|
+
if (runGenerationRef.current === generation) setIsRunning(false);
|
|
181
183
|
}
|
|
182
184
|
};
|
|
183
185
|
|