@assistant-ui/react-google-adk 0.0.34 → 0.0.36
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/README.md +12 -2
- package/dist/AdkClient.d.ts +4 -0
- package/dist/AdkClient.d.ts.map +1 -1
- package/dist/AdkClient.js +12 -7
- package/dist/AdkClient.js.map +1 -1
- package/dist/AdkSessionAdapter.d.ts.map +1 -1
- package/dist/AdkSessionAdapter.js +7 -7
- package/dist/AdkSessionAdapter.js.map +1 -1
- package/dist/AdkThreadController.d.ts +15 -0
- package/dist/AdkThreadController.d.ts.map +1 -0
- package/dist/AdkThreadController.js +35 -0
- package/dist/AdkThreadController.js.map +1 -0
- package/dist/adkThreadState.d.ts +54 -0
- package/dist/adkThreadState.d.ts.map +1 -0
- package/dist/adkThreadState.js +93 -0
- package/dist/adkThreadState.js.map +1 -0
- package/dist/convertToAdkMessages.js +1 -1
- package/dist/convertToAdkMessages.js.map +1 -1
- package/dist/sdkIdentity.js +1 -1
- package/dist/server/createAdkApiRoute.d.ts +37 -6
- package/dist/server/createAdkApiRoute.d.ts.map +1 -1
- package/dist/server/createAdkApiRoute.js +55 -5
- package/dist/server/createAdkApiRoute.js.map +1 -1
- package/dist/server/parseAdkRequest.d.ts +4 -1
- package/dist/server/parseAdkRequest.d.ts.map +1 -1
- package/dist/server/parseAdkRequest.js +5 -1
- package/dist/server/parseAdkRequest.js.map +1 -1
- package/dist/useAdkMessages.d.ts +29 -4
- package/dist/useAdkMessages.d.ts.map +1 -1
- package/dist/useAdkMessages.js +62 -76
- package/dist/useAdkMessages.js.map +1 -1
- package/dist/useAdkRuntime.d.ts +7 -1
- package/dist/useAdkRuntime.d.ts.map +1 -1
- package/dist/useAdkRuntime.js +182 -54
- package/dist/useAdkRuntime.js.map +1 -1
- package/package.json +6 -5
- package/src/AdkClient.test.ts +78 -2
- package/src/AdkClient.ts +24 -6
- package/src/AdkSessionAdapter.ts +1 -1
- package/src/AdkThreadController.test.ts +90 -0
- package/src/AdkThreadController.ts +45 -0
- package/src/adkThreadState.test.ts +207 -0
- package/src/adkThreadState.ts +124 -0
- package/src/convertToAdkMessages.test.ts +19 -0
- package/src/convertToAdkMessages.ts +1 -1
- package/src/hooks.test.tsx +1 -0
- package/src/server/createAdkApiRoute.controls.test.ts +66 -0
- package/src/server/createAdkApiRoute.test.ts +282 -0
- package/src/server/createAdkApiRoute.ts +119 -11
- package/src/server/parseAdkRequest.test.ts +11 -3
- package/src/server/parseAdkRequest.ts +7 -1
- package/src/useAdkMessages.test.ts +43 -0
- package/src/useAdkMessages.ts +89 -96
- package/src/useAdkRuntime.cancellation.test.tsx +4 -3
- package/src/useAdkRuntime.cloud-options.test.tsx +59 -0
- package/src/useAdkRuntime.refetch.test.tsx +548 -4
- package/src/useAdkRuntime.replacement.test.tsx +718 -1
- package/src/useAdkRuntime.ts +253 -75
- package/src/useAdkRuntimeApproval.test.tsx +390 -35
- package/dist/raceWithAbortSignal.d.ts +0 -2
- package/dist/raceWithAbortSignal.d.ts.map +0 -1
- package/dist/raceWithAbortSignal.js +0 -45
- package/dist/raceWithAbortSignal.js.map +0 -1
- package/src/raceWithAbortSignal.test.ts +0 -73
- package/src/raceWithAbortSignal.ts +0 -48
|
@@ -28,11 +28,18 @@ describe("parseAdkRequest", () => {
|
|
|
28
28
|
});
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
it("parses a message request with stateDelta", async () => {
|
|
31
|
+
it("parses a message request with sessionId and stateDelta", async () => {
|
|
32
32
|
const result = await parseAdkRequest(
|
|
33
|
-
makeRequest({
|
|
33
|
+
makeRequest({
|
|
34
|
+
message: "Hello",
|
|
35
|
+
sessionId: "session-1",
|
|
36
|
+
stateDelta: { count: 1 },
|
|
37
|
+
}),
|
|
34
38
|
);
|
|
35
|
-
expect(result).toMatchObject({
|
|
39
|
+
expect(result).toMatchObject({
|
|
40
|
+
sessionId: "session-1",
|
|
41
|
+
stateDelta: { count: 1 },
|
|
42
|
+
});
|
|
36
43
|
});
|
|
37
44
|
|
|
38
45
|
it("parses a message request with parts (multimodal)", async () => {
|
|
@@ -130,6 +137,7 @@ describe("parseAdkRequest", () => {
|
|
|
130
137
|
[{ parts: [null] }, 'field "parts"'],
|
|
131
138
|
[{ type: "unknown", message: "hello" }, 'field "type"'],
|
|
132
139
|
[{ message: "hello", checkpointId: 42 }, 'field "checkpointId"'],
|
|
140
|
+
[{ message: "hello", sessionId: 42 }, 'field "sessionId"'],
|
|
133
141
|
[{ message: "hello", stateDelta: [] }, 'field "stateDelta"'],
|
|
134
142
|
])("rejects malformed message requests %#", async (body, error) => {
|
|
135
143
|
await expect(parseAdkRequest(makeRequest(body))).rejects.toThrow(error);
|
|
@@ -7,6 +7,7 @@ type ParsedAdkRequest =
|
|
|
7
7
|
type: "message";
|
|
8
8
|
text: string;
|
|
9
9
|
parts?: Array<Record<string, unknown>> | undefined;
|
|
10
|
+
sessionId?: string | undefined;
|
|
10
11
|
config: AdkSendMessageConfig;
|
|
11
12
|
stateDelta?: Record<string, unknown> | undefined;
|
|
12
13
|
}
|
|
@@ -16,6 +17,7 @@ type ParsedAdkRequest =
|
|
|
16
17
|
toolName: string;
|
|
17
18
|
result: unknown;
|
|
18
19
|
isError: boolean;
|
|
20
|
+
sessionId?: string | undefined;
|
|
19
21
|
config: AdkSendMessageConfig;
|
|
20
22
|
stateDelta?: Record<string, unknown> | undefined;
|
|
21
23
|
};
|
|
@@ -162,6 +164,7 @@ export const parseAdkRequest = async (
|
|
|
162
164
|
if (body.runConfig !== undefined) config.runConfig = body.runConfig;
|
|
163
165
|
const checkpointId = readOptionalString(body, "checkpointId");
|
|
164
166
|
if (checkpointId !== undefined) config.checkpointId = checkpointId;
|
|
167
|
+
const sessionId = readOptionalString(body, "sessionId");
|
|
165
168
|
|
|
166
169
|
const stateDelta = body.stateDelta;
|
|
167
170
|
if (stateDelta !== undefined && !isRecord(stateDelta)) {
|
|
@@ -181,6 +184,7 @@ export const parseAdkRequest = async (
|
|
|
181
184
|
toolName: readString(body, "toolName"),
|
|
182
185
|
result: body.result,
|
|
183
186
|
isError: body.isError ?? false,
|
|
187
|
+
...(sessionId !== undefined && { sessionId }),
|
|
184
188
|
config,
|
|
185
189
|
...(stateDelta != null && { stateDelta }),
|
|
186
190
|
};
|
|
@@ -213,6 +217,7 @@ export const parseAdkRequest = async (
|
|
|
213
217
|
type: "message",
|
|
214
218
|
text: text ?? "",
|
|
215
219
|
...(parts !== undefined && { parts }),
|
|
220
|
+
...(sessionId !== undefined && { sessionId }),
|
|
216
221
|
config,
|
|
217
222
|
...(stateDelta != null && { stateDelta }),
|
|
218
223
|
};
|
|
@@ -226,7 +231,8 @@ export const parseAdkRequest = async (
|
|
|
226
231
|
* ```ts
|
|
227
232
|
* const parsed = await parseAdkRequest(req);
|
|
228
233
|
* const newMessage = toAdkContent(parsed);
|
|
229
|
-
* const
|
|
234
|
+
* const stateDelta = validateSessionState(parsed.stateDelta);
|
|
235
|
+
* const events = runner.runAsync({ userId, sessionId, newMessage, stateDelta });
|
|
230
236
|
* return adkEventStream(events);
|
|
231
237
|
* ```
|
|
232
238
|
*/
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @vitest-environment jsdom */
|
|
1
2
|
import { act, renderHook } from "@testing-library/react";
|
|
2
3
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
4
|
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
messageToEvent,
|
|
18
19
|
messagesToEvents,
|
|
19
20
|
useAdkMessages,
|
|
21
|
+
useAdkMessagesInternal,
|
|
20
22
|
} from "./useAdkMessages";
|
|
21
23
|
import { projectAdkToolApprovals } from "./adkToolApproval";
|
|
22
24
|
import { createAdkStream } from "./AdkClient";
|
|
@@ -212,6 +214,47 @@ describe("ADK runtime callbacks", () => {
|
|
|
212
214
|
});
|
|
213
215
|
|
|
214
216
|
describe("ADK stream lifecycle", () => {
|
|
217
|
+
it("reports streamed tool calls with the run config that produced them", async () => {
|
|
218
|
+
const runConfig = { custom: { model: "model-a" } };
|
|
219
|
+
const onMessages = vi.fn();
|
|
220
|
+
const stream: AdkStreamCallback = async function* () {
|
|
221
|
+
yield {
|
|
222
|
+
id: "event-1",
|
|
223
|
+
author: "agent",
|
|
224
|
+
content: {
|
|
225
|
+
role: "model",
|
|
226
|
+
parts: [
|
|
227
|
+
{ functionCall: { id: "tool-1", name: "lookup", args: {} } },
|
|
228
|
+
{ functionCall: { id: "tool-2", name: "search", args: {} } },
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
const { result } = renderHook(() =>
|
|
234
|
+
useAdkMessagesInternal({ stream, onMessages }),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
await act(async () => {
|
|
238
|
+
await result.current.sendMessage(
|
|
239
|
+
[{ id: "user-1", type: "human", content: "look it up" }],
|
|
240
|
+
{ runConfig },
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
expect(onMessages).toHaveBeenLastCalledWith(
|
|
245
|
+
expect.arrayContaining([
|
|
246
|
+
expect.objectContaining({
|
|
247
|
+
type: "ai",
|
|
248
|
+
tool_calls: [
|
|
249
|
+
expect.objectContaining({ id: "tool-1" }),
|
|
250
|
+
expect.objectContaining({ id: "tool-2" }),
|
|
251
|
+
],
|
|
252
|
+
}),
|
|
253
|
+
]),
|
|
254
|
+
runConfig,
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
|
|
215
258
|
it("settles a superseded send while its stream is still opening", async () => {
|
|
216
259
|
const signals: AbortSignal[] = [];
|
|
217
260
|
const parked = new Promise<AsyncGenerator<AdkEvent>>(() => {});
|
package/src/useAdkMessages.ts
CHANGED
|
@@ -2,9 +2,9 @@ import { isRecord } from "@assistant-ui/core/internal";
|
|
|
2
2
|
import {
|
|
3
3
|
useState,
|
|
4
4
|
useCallback,
|
|
5
|
-
useInsertionEffect,
|
|
6
5
|
useRef,
|
|
7
6
|
useMemo,
|
|
7
|
+
useSyncExternalStore,
|
|
8
8
|
} from "react";
|
|
9
9
|
import { generateId } from "@assistant-ui/core";
|
|
10
10
|
import { useReplaySafeEffect } from "@assistant-ui/store/internal";
|
|
@@ -15,16 +15,14 @@ import {
|
|
|
15
15
|
openAbortableIterable,
|
|
16
16
|
} from "@assistant-ui/core/internal";
|
|
17
17
|
import { AdkEventAccumulator } from "./AdkEventAccumulator";
|
|
18
|
+
import { AdkThreadController } from "./AdkThreadController";
|
|
18
19
|
import { contentToParts } from "./contentToParts";
|
|
19
20
|
import { toAdkFunctionResponse } from "./toAdkFunctionResponse";
|
|
20
21
|
import type {
|
|
21
22
|
AdkEvent,
|
|
22
23
|
AdkMessage,
|
|
23
|
-
AdkMessageMetadata,
|
|
24
24
|
AdkSendMessageConfig,
|
|
25
25
|
AdkStreamCallback,
|
|
26
|
-
AdkToolConfirmation,
|
|
27
|
-
AdkAuthRequest,
|
|
28
26
|
AdkThreadSnapshot,
|
|
29
27
|
OnAdkErrorCallback,
|
|
30
28
|
OnAdkCustomEventCallback,
|
|
@@ -40,6 +38,10 @@ export type UseAdkMessagesOptions = {
|
|
|
40
38
|
};
|
|
41
39
|
};
|
|
42
40
|
|
|
41
|
+
type UseAdkMessagesInternalOptions = UseAdkMessagesOptions & {
|
|
42
|
+
onMessages?: (messages: AdkMessage[], runConfig: unknown) => void;
|
|
43
|
+
};
|
|
44
|
+
|
|
43
45
|
type AdkRuntimeCallbackName = "onError" | "onCustomEvent" | "onAgentTransfer";
|
|
44
46
|
|
|
45
47
|
const invokeAdkRuntimeCallback = <TArgs extends readonly unknown[]>(
|
|
@@ -50,53 +52,34 @@ const invokeAdkRuntimeCallback = <TArgs extends readonly unknown[]>(
|
|
|
50
52
|
void invokeUserCallback("react-google-adk", name, callback, ...args);
|
|
51
53
|
};
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
const useAdkMessagesInternal = ({
|
|
54
56
|
stream,
|
|
55
57
|
eventHandlers,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const [
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
onMessages,
|
|
59
|
+
}: UseAdkMessagesInternalOptions) => {
|
|
60
|
+
const [controller] = useState(() => new AdkThreadController());
|
|
61
|
+
const {
|
|
62
|
+
messages,
|
|
63
|
+
stateDelta,
|
|
64
|
+
agentInfo,
|
|
65
|
+
longRunningToolIds,
|
|
66
|
+
artifactDelta,
|
|
67
|
+
toolConfirmations,
|
|
68
|
+
authRequests,
|
|
69
|
+
escalated,
|
|
70
|
+
messageMetadata,
|
|
71
|
+
} = useSyncExternalStore(
|
|
72
|
+
controller.subscribe,
|
|
73
|
+
controller.getState,
|
|
74
|
+
controller.getState,
|
|
66
75
|
);
|
|
67
|
-
const [toolConfirmations, setToolConfirmations] = useState<
|
|
68
|
-
AdkToolConfirmation[]
|
|
69
|
-
>([]);
|
|
70
|
-
const [authRequests, setAuthRequests] = useState<AdkAuthRequest[]>([]);
|
|
71
|
-
const [escalated, setEscalated] = useState(false);
|
|
72
|
-
const [messageMetadata, setMessageMetadata] = useState<
|
|
73
|
-
Map<string, AdkMessageMetadata>
|
|
74
|
-
>(new Map());
|
|
75
|
-
// setMessagesImmediate and setLongRunningToolIds are the only writers of their state and publish these refs with it, so neither ref trails a commit.
|
|
76
|
-
const messagesRef = useRef(messages);
|
|
77
|
-
const longRunningToolIdsRef = useRef(longRunningToolIds);
|
|
78
|
-
const stateDeltaRef = useRef(stateDelta);
|
|
79
|
-
useInsertionEffect(() => {
|
|
80
|
-
stateDeltaRef.current = stateDelta;
|
|
81
|
-
}, [stateDelta]);
|
|
82
|
-
const artifactDeltaRef = useRef(artifactDelta);
|
|
83
|
-
useInsertionEffect(() => {
|
|
84
|
-
artifactDeltaRef.current = artifactDelta;
|
|
85
|
-
}, [artifactDelta]);
|
|
86
|
-
const messageMetadataRef = useRef(messageMetadata);
|
|
87
|
-
useInsertionEffect(() => {
|
|
88
|
-
messageMetadataRef.current = messageMetadata;
|
|
89
|
-
}, [messageMetadata]);
|
|
90
|
-
|
|
91
|
-
const setMessagesImmediate = useCallback((msgs: AdkMessage[]) => {
|
|
92
|
-
messagesRef.current = msgs;
|
|
93
|
-
_setMessages(msgs);
|
|
94
|
-
}, []);
|
|
95
|
-
const setLongRunningToolIds = useCallback((ids: string[]) => {
|
|
96
|
-
longRunningToolIdsRef.current = ids;
|
|
97
|
-
_setLongRunningToolIds(ids);
|
|
98
|
-
}, []);
|
|
99
76
|
|
|
77
|
+
const setMessagesImmediate = useCallback(
|
|
78
|
+
(msgs: AdkMessage[]) => {
|
|
79
|
+
controller.dispatch({ type: "messages.set", messages: msgs });
|
|
80
|
+
},
|
|
81
|
+
[controller],
|
|
82
|
+
);
|
|
100
83
|
/**
|
|
101
84
|
* Swap the thread over to a loaded snapshot in one commit. Unlike
|
|
102
85
|
* {@link replaceMessages} this never passes through a cleared state, so a
|
|
@@ -105,17 +88,9 @@ export const useAdkMessages = ({
|
|
|
105
88
|
*/
|
|
106
89
|
const applySnapshot = useCallback(
|
|
107
90
|
(snapshot: AdkThreadSnapshot) => {
|
|
108
|
-
|
|
109
|
-
setLongRunningToolIds(snapshot.longRunningToolIds ?? []);
|
|
110
|
-
setToolConfirmations(snapshot.toolConfirmations ?? []);
|
|
111
|
-
setAuthRequests(snapshot.authRequests ?? []);
|
|
112
|
-
setEscalated(snapshot.escalated ?? false);
|
|
113
|
-
setMessageMetadata(snapshot.messageMetadata ?? new Map());
|
|
114
|
-
setStateDelta(snapshot.stateDelta ?? {});
|
|
115
|
-
setArtifactDelta(snapshot.artifactDelta ?? {});
|
|
116
|
-
setAgentInfo(snapshot.agentInfo ?? {});
|
|
91
|
+
controller.dispatch({ type: "snapshot.applied", snapshot });
|
|
117
92
|
},
|
|
118
|
-
[
|
|
93
|
+
[controller],
|
|
119
94
|
);
|
|
120
95
|
|
|
121
96
|
// Replace the message list AND reset derived per-turn HITL state.
|
|
@@ -124,14 +99,9 @@ export const useAdkMessages = ({
|
|
|
124
99
|
// into the next turn.
|
|
125
100
|
const replaceMessages = useCallback(
|
|
126
101
|
(msgs: AdkMessage[]) => {
|
|
127
|
-
|
|
128
|
-
setLongRunningToolIds([]);
|
|
129
|
-
setToolConfirmations([]);
|
|
130
|
-
setAuthRequests([]);
|
|
131
|
-
setEscalated(false);
|
|
132
|
-
setMessageMetadata(new Map());
|
|
102
|
+
controller.dispatch({ type: "messages.replaced", messages: msgs });
|
|
133
103
|
},
|
|
134
|
-
[
|
|
104
|
+
[controller],
|
|
135
105
|
);
|
|
136
106
|
|
|
137
107
|
const abortControllerRef = useRef<AbortController | null>(null);
|
|
@@ -159,9 +129,10 @@ export const useAdkMessages = ({
|
|
|
159
129
|
m.type === "tool" ? [m.tool_call_id] : [],
|
|
160
130
|
),
|
|
161
131
|
);
|
|
132
|
+
const currentState = controller.getState();
|
|
162
133
|
const accumulator = new AdkEventAccumulator(
|
|
163
|
-
|
|
164
|
-
|
|
134
|
+
currentState.messages.filter((m) => !resentIds.has(m.id)),
|
|
135
|
+
currentState.longRunningToolIds.filter(
|
|
165
136
|
(id) => !answeredToolCallIds.has(id),
|
|
166
137
|
),
|
|
167
138
|
);
|
|
@@ -171,10 +142,13 @@ export const useAdkMessages = ({
|
|
|
171
142
|
const initialMessages = accumulator.getMessages();
|
|
172
143
|
const initialMessageIds = new Set(initialMessages.map((m) => m.id));
|
|
173
144
|
const initialLongRunningToolIds = accumulator.getLongRunningToolIds();
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
145
|
+
controller.dispatch({
|
|
146
|
+
type: "run.started",
|
|
147
|
+
messages: initialMessages,
|
|
148
|
+
longRunningToolIds: initialLongRunningToolIds,
|
|
149
|
+
toolConfirmations: accumulator.getToolConfirmations(),
|
|
150
|
+
authRequests: accumulator.getAuthRequests(),
|
|
151
|
+
});
|
|
178
152
|
let lastTransferToAgent: string | undefined;
|
|
179
153
|
|
|
180
154
|
// Google ADK replaces active runs, while React LangGraph queues sends.
|
|
@@ -206,28 +180,31 @@ export const useAdkMessages = ({
|
|
|
206
180
|
break;
|
|
207
181
|
}
|
|
208
182
|
const updatedMessages = accumulator.processEvent(event);
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
});
|
|
220
|
-
setToolConfirmations(accumulator.getToolConfirmations());
|
|
221
|
-
setAuthRequests(accumulator.getAuthRequests());
|
|
222
|
-
setEscalated(accumulator.isEscalated());
|
|
223
|
-
{
|
|
224
|
-
const newMeta = accumulator.getMessageMetadata();
|
|
225
|
-
if (newMeta.size > 0) {
|
|
226
|
-
setMessageMetadata(
|
|
227
|
-
new Map([...messageMetadataRef.current, ...newMeta]),
|
|
228
|
-
);
|
|
229
|
-
}
|
|
183
|
+
// Each event part can append at most one message, and a function call
|
|
184
|
+
// stays on the current assistant message until a later part finalizes
|
|
185
|
+
// it, so every message touched by this event is within this tail.
|
|
186
|
+
const affectedMessageCount = Math.max(
|
|
187
|
+
event.content?.parts?.length ?? 0,
|
|
188
|
+
1,
|
|
189
|
+
);
|
|
190
|
+
const affectedMessages = updatedMessages.slice(-affectedMessageCount);
|
|
191
|
+
if (affectedMessages.length > 0) {
|
|
192
|
+
onMessages?.(affectedMessages, config.runConfig);
|
|
230
193
|
}
|
|
194
|
+
controller.dispatch({
|
|
195
|
+
type: "event.published",
|
|
196
|
+
state: {
|
|
197
|
+
messages: updatedMessages,
|
|
198
|
+
stateDelta: accumulator.getStateDelta(),
|
|
199
|
+
agentInfo: accumulator.getAgentInfo(),
|
|
200
|
+
longRunningToolIds: accumulator.getLongRunningToolIds(),
|
|
201
|
+
artifactDelta: accumulator.getArtifactDelta(),
|
|
202
|
+
toolConfirmations: accumulator.getToolConfirmations(),
|
|
203
|
+
authRequests: accumulator.getAuthRequests(),
|
|
204
|
+
escalated: accumulator.isEscalated(),
|
|
205
|
+
messageMetadata: accumulator.getMessageMetadata(),
|
|
206
|
+
},
|
|
207
|
+
});
|
|
231
208
|
|
|
232
209
|
const transfer = accumulator.getLastTransferToAgent();
|
|
233
210
|
if (transfer && transfer !== lastTransferToAgent) {
|
|
@@ -270,12 +247,13 @@ export const useAdkMessages = ({
|
|
|
270
247
|
} finally {
|
|
271
248
|
if (abortControllerRef.current === abortController) {
|
|
272
249
|
if (abortController.signal.aborted) {
|
|
273
|
-
|
|
274
|
-
|
|
250
|
+
controller.dispatch({
|
|
251
|
+
type: "longRunningToolIds.set",
|
|
252
|
+
ids: accumulator
|
|
275
253
|
.getLongRunningToolIds()
|
|
276
254
|
.filter((id) => initialLongRunningToolIds.includes(id)),
|
|
277
|
-
);
|
|
278
|
-
const updatedMessages =
|
|
255
|
+
});
|
|
256
|
+
const updatedMessages = controller.getState().messages;
|
|
279
257
|
const lastAssistantMessage = updatedMessages.findLast(
|
|
280
258
|
(m) => m.type === "ai",
|
|
281
259
|
);
|
|
@@ -302,12 +280,13 @@ export const useAdkMessages = ({
|
|
|
302
280
|
},
|
|
303
281
|
[
|
|
304
282
|
aui,
|
|
283
|
+
controller,
|
|
305
284
|
setMessagesImmediate,
|
|
306
|
-
setLongRunningToolIds,
|
|
307
285
|
stream,
|
|
308
286
|
onError,
|
|
309
287
|
onCustomEvent,
|
|
310
288
|
onAgentTransfer,
|
|
289
|
+
onMessages,
|
|
311
290
|
],
|
|
312
291
|
);
|
|
313
292
|
|
|
@@ -320,6 +299,7 @@ export const useAdkMessages = ({
|
|
|
320
299
|
useReplaySafeEffect(() => cancel, []);
|
|
321
300
|
|
|
322
301
|
return {
|
|
302
|
+
controller,
|
|
323
303
|
messages,
|
|
324
304
|
stateDelta,
|
|
325
305
|
agentInfo,
|
|
@@ -337,6 +317,19 @@ export const useAdkMessages = ({
|
|
|
337
317
|
};
|
|
338
318
|
};
|
|
339
319
|
|
|
320
|
+
export const useAdkMessages = ({
|
|
321
|
+
stream,
|
|
322
|
+
eventHandlers,
|
|
323
|
+
}: UseAdkMessagesOptions) => {
|
|
324
|
+
const { controller: _controller, ...result } = useAdkMessagesInternal({
|
|
325
|
+
stream,
|
|
326
|
+
...(eventHandlers !== undefined && { eventHandlers }),
|
|
327
|
+
});
|
|
328
|
+
return result;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
export { useAdkMessagesInternal };
|
|
332
|
+
|
|
340
333
|
/**
|
|
341
334
|
* Transport sends every human and tool message of one `send` call as a single
|
|
342
335
|
* ADK `Content`, and ADK parses that event's function responses before running
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
import { useAdkRuntime } from "./useAdkRuntime";
|
|
12
12
|
import { useAdkLongRunningToolIds, useAdkSend } from "./hooks";
|
|
13
13
|
import { AdkEventAccumulator } from "./AdkEventAccumulator";
|
|
14
|
+
import { settleOutsideAct } from "./tests/settleOutsideAct";
|
|
14
15
|
import type { AdkEvent, AdkStreamCallback, AdkThreadSnapshot } from "./types";
|
|
15
16
|
|
|
16
17
|
const makeThreadListAdapter = (): RemoteThreadListAdapter => ({
|
|
@@ -85,9 +86,9 @@ const renderAdkRuntime = async (
|
|
|
85
86
|
render(<Inner />);
|
|
86
87
|
});
|
|
87
88
|
await waitFor(() => expect(capture.runtime).not.toBeNull());
|
|
88
|
-
await
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
await settleOutsideAct(() =>
|
|
90
|
+
capture.runtime!.threads.switchToThread("adk-1"),
|
|
91
|
+
);
|
|
91
92
|
|
|
92
93
|
return capture;
|
|
93
94
|
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { renderHook } from "@testing-library/react";
|
|
4
|
+
import { describe, expect, it, vi } from "vitest";
|
|
5
|
+
import type { AssistantCloud } from "assistant-cloud";
|
|
6
|
+
import type { AssistantRuntime } from "@assistant-ui/core";
|
|
7
|
+
|
|
8
|
+
const mocks = vi.hoisted(() => ({
|
|
9
|
+
cloudAdapter: {},
|
|
10
|
+
runtime: {},
|
|
11
|
+
useCloudThreadListAdapter: vi.fn(() => ({})),
|
|
12
|
+
useRemoteThreadListRuntime: vi.fn(() => ({})),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock("@assistant-ui/core/react", async (importOriginal) => ({
|
|
16
|
+
...(await importOriginal<typeof import("@assistant-ui/core/react")>()),
|
|
17
|
+
useCloudThreadListAdapter: mocks.useCloudThreadListAdapter,
|
|
18
|
+
useRemoteThreadListRuntime: mocks.useRemoteThreadListRuntime,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => ({
|
|
22
|
+
...(await importOriginal<typeof import("@assistant-ui/store")>()),
|
|
23
|
+
useAui: () => ({
|
|
24
|
+
threadListItem: {
|
|
25
|
+
source: null,
|
|
26
|
+
getState: () => ({ externalId: undefined }),
|
|
27
|
+
initialize: vi.fn(),
|
|
28
|
+
},
|
|
29
|
+
}),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
import { ADK_SDK } from "./sdkIdentity";
|
|
33
|
+
import { useAdkRuntime } from "./useAdkRuntime";
|
|
34
|
+
|
|
35
|
+
describe("useAdkRuntime Cloud options", () => {
|
|
36
|
+
it("forwards the Cloud scope to the thread-list adapter", () => {
|
|
37
|
+
const cloud = {} as AssistantCloud;
|
|
38
|
+
mocks.useCloudThreadListAdapter.mockReturnValue(mocks.cloudAdapter);
|
|
39
|
+
mocks.useRemoteThreadListRuntime.mockReturnValue(
|
|
40
|
+
mocks.runtime as AssistantRuntime,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
renderHook(() =>
|
|
44
|
+
useAdkRuntime({
|
|
45
|
+
stream: vi.fn(),
|
|
46
|
+
cloud,
|
|
47
|
+
scopeId: "workspace-1",
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(mocks.useCloudThreadListAdapter).toHaveBeenCalledWith(
|
|
52
|
+
expect.objectContaining({
|
|
53
|
+
cloud,
|
|
54
|
+
scopeId: "workspace-1",
|
|
55
|
+
sdk: ADK_SDK,
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
});
|