@assistant-ui/react-google-adk 0.0.1
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 +21 -0
- package/README.md +156 -0
- package/dist/AdkClient.d.ts +45 -0
- package/dist/AdkClient.d.ts.map +1 -0
- package/dist/AdkClient.js +204 -0
- package/dist/AdkClient.js.map +1 -0
- package/dist/AdkEventAccumulator.d.ts +45 -0
- package/dist/AdkEventAccumulator.d.ts.map +1 -0
- package/dist/AdkEventAccumulator.js +508 -0
- package/dist/AdkEventAccumulator.js.map +1 -0
- package/dist/AdkSessionAdapter.d.ts +61 -0
- package/dist/AdkSessionAdapter.d.ts.map +1 -0
- package/dist/AdkSessionAdapter.js +159 -0
- package/dist/AdkSessionAdapter.js.map +1 -0
- package/dist/convertAdkMessages.d.ts +4 -0
- package/dist/convertAdkMessages.d.ts.map +1 -0
- package/dist/convertAdkMessages.js +75 -0
- package/dist/convertAdkMessages.js.map +1 -0
- package/dist/hooks.d.ts +50 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +173 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/server/adkEventStream.d.ts +42 -0
- package/dist/server/adkEventStream.d.ts.map +1 -0
- package/dist/server/adkEventStream.js +135 -0
- package/dist/server/adkEventStream.js.map +1 -0
- package/dist/server/createAdkApiRoute.d.ts +47 -0
- package/dist/server/createAdkApiRoute.d.ts.map +1 -0
- package/dist/server/createAdkApiRoute.js +41 -0
- package/dist/server/createAdkApiRoute.js.map +1 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +4 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/parseAdkRequest.d.ts +56 -0
- package/dist/server/parseAdkRequest.d.ts.map +1 -0
- package/dist/server/parseAdkRequest.js +93 -0
- package/dist/server/parseAdkRequest.js.map +1 -0
- package/dist/structuredEvents.d.ts +7 -0
- package/dist/structuredEvents.d.ts.map +1 -0
- package/dist/structuredEvents.js +79 -0
- package/dist/structuredEvents.js.map +1 -0
- package/dist/types.d.ts +253 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/dist/useAdkMessages.d.ts +28 -0
- package/dist/useAdkMessages.d.ts.map +1 -0
- package/dist/useAdkMessages.js +198 -0
- package/dist/useAdkMessages.js.map +1 -0
- package/dist/useAdkRuntime.d.ts +36 -0
- package/dist/useAdkRuntime.d.ts.map +1 -0
- package/dist/useAdkRuntime.js +252 -0
- package/dist/useAdkRuntime.js.map +1 -0
- package/package.json +83 -0
- package/server/package.json +4 -0
- package/src/AdkClient.test.ts +662 -0
- package/src/AdkClient.ts +274 -0
- package/src/AdkEventAccumulator.test.ts +591 -0
- package/src/AdkEventAccumulator.ts +602 -0
- package/src/AdkSessionAdapter.test.ts +362 -0
- package/src/AdkSessionAdapter.ts +245 -0
- package/src/convertAdkMessages.test.ts +209 -0
- package/src/convertAdkMessages.ts +93 -0
- package/src/hooks.ts +217 -0
- package/src/index.ts +66 -0
- package/src/server/adkEventStream.test.ts +78 -0
- package/src/server/adkEventStream.ts +161 -0
- package/src/server/createAdkApiRoute.test.ts +370 -0
- package/src/server/createAdkApiRoute.ts +86 -0
- package/src/server/index.ts +6 -0
- package/src/server/parseAdkRequest.test.ts +152 -0
- package/src/server/parseAdkRequest.ts +122 -0
- package/src/structuredEvents.ts +81 -0
- package/src/types.ts +265 -0
- package/src/useAdkMessages.ts +259 -0
- package/src/useAdkRuntime.ts +398 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { AdkEventAccumulator } from "./AdkEventAccumulator";
|
|
3
|
+
import type { AdkEvent, AdkMessage } from "./types";
|
|
4
|
+
|
|
5
|
+
const makeEvent = (overrides: Partial<AdkEvent> = {}): AdkEvent => ({
|
|
6
|
+
id: "evt-1",
|
|
7
|
+
...overrides,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const makeTextEvent = (
|
|
11
|
+
text: string,
|
|
12
|
+
partial?: boolean,
|
|
13
|
+
author = "agent",
|
|
14
|
+
): AdkEvent =>
|
|
15
|
+
makeEvent({
|
|
16
|
+
author,
|
|
17
|
+
partial,
|
|
18
|
+
content: { role: "model", parts: [{ text }] },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("AdkEventAccumulator - text handling", () => {
|
|
22
|
+
it("accumulates a single non-partial text event into an AI message", () => {
|
|
23
|
+
const acc = new AdkEventAccumulator();
|
|
24
|
+
const msgs = acc.processEvent(makeTextEvent("Hello world"));
|
|
25
|
+
expect(msgs).toHaveLength(1);
|
|
26
|
+
expect(msgs[0]).toMatchObject({
|
|
27
|
+
type: "ai",
|
|
28
|
+
content: [{ type: "text", text: "Hello world" }],
|
|
29
|
+
status: { type: "complete", reason: "stop" },
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("accumulates partial text deltas into a single text content part", () => {
|
|
34
|
+
const acc = new AdkEventAccumulator();
|
|
35
|
+
acc.processEvent(makeTextEvent("Hel", true));
|
|
36
|
+
const msgs = acc.processEvent(makeTextEvent("lo", true));
|
|
37
|
+
expect(msgs).toHaveLength(1);
|
|
38
|
+
expect(msgs[0]).toMatchObject({
|
|
39
|
+
type: "ai",
|
|
40
|
+
content: [{ type: "text", text: "Hello" }],
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("replaces partial buffer with final non-partial text", () => {
|
|
45
|
+
const acc = new AdkEventAccumulator();
|
|
46
|
+
acc.processEvent(makeTextEvent("Hel", true));
|
|
47
|
+
acc.processEvent(makeTextEvent("lo", true));
|
|
48
|
+
const msgs = acc.processEvent(makeTextEvent("Hello world"));
|
|
49
|
+
expect(msgs).toHaveLength(1);
|
|
50
|
+
expect(msgs[0]).toMatchObject({
|
|
51
|
+
type: "ai",
|
|
52
|
+
content: [{ type: "text", text: "Hello world" }],
|
|
53
|
+
status: { type: "complete", reason: "stop" },
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("AdkEventAccumulator - reasoning/thought", () => {
|
|
59
|
+
it("accumulates thought text as reasoning content part", () => {
|
|
60
|
+
const acc = new AdkEventAccumulator();
|
|
61
|
+
const msgs = acc.processEvent(
|
|
62
|
+
makeEvent({
|
|
63
|
+
author: "agent",
|
|
64
|
+
content: {
|
|
65
|
+
role: "model",
|
|
66
|
+
parts: [{ text: "thinking...", thought: true }],
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
expect(msgs[0]).toMatchObject({
|
|
71
|
+
type: "ai",
|
|
72
|
+
content: [{ type: "reasoning", text: "thinking..." }],
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("accumulates partial reasoning deltas", () => {
|
|
77
|
+
const acc = new AdkEventAccumulator();
|
|
78
|
+
acc.processEvent(
|
|
79
|
+
makeEvent({
|
|
80
|
+
author: "agent",
|
|
81
|
+
partial: true,
|
|
82
|
+
content: { role: "model", parts: [{ text: "think", thought: true }] },
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
const msgs = acc.processEvent(
|
|
86
|
+
makeEvent({
|
|
87
|
+
author: "agent",
|
|
88
|
+
partial: true,
|
|
89
|
+
content: { role: "model", parts: [{ text: "ing", thought: true }] },
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
expect(msgs[0]).toMatchObject({
|
|
93
|
+
content: [{ type: "reasoning", text: "thinking" }],
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("AdkEventAccumulator - function calls", () => {
|
|
99
|
+
it("creates a tool_calls entry from a functionCall part", () => {
|
|
100
|
+
const acc = new AdkEventAccumulator();
|
|
101
|
+
const msgs = acc.processEvent(
|
|
102
|
+
makeEvent({
|
|
103
|
+
author: "agent",
|
|
104
|
+
content: {
|
|
105
|
+
role: "model",
|
|
106
|
+
parts: [
|
|
107
|
+
{
|
|
108
|
+
functionCall: { name: "search", id: "tc-1", args: { q: "test" } },
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
expect(msgs[0]).toMatchObject({
|
|
115
|
+
type: "ai",
|
|
116
|
+
tool_calls: [{ id: "tc-1", name: "search", args: { q: "test" } }],
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("generates a UUID for functionCall without an id", () => {
|
|
121
|
+
const acc = new AdkEventAccumulator();
|
|
122
|
+
const msgs = acc.processEvent(
|
|
123
|
+
makeEvent({
|
|
124
|
+
author: "agent",
|
|
125
|
+
content: {
|
|
126
|
+
role: "model",
|
|
127
|
+
parts: [{ functionCall: { name: "search", args: {} } }],
|
|
128
|
+
},
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
const tc = (msgs[0] as AdkMessage & { type: "ai" }).tool_calls;
|
|
132
|
+
expect(tc).toHaveLength(1);
|
|
133
|
+
expect(tc![0]!.id).toBeTruthy();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("AdkEventAccumulator - function responses", () => {
|
|
138
|
+
it("creates a tool message from a functionResponse part", () => {
|
|
139
|
+
const acc = new AdkEventAccumulator();
|
|
140
|
+
const msgs = acc.processEvent(
|
|
141
|
+
makeEvent({
|
|
142
|
+
author: "agent",
|
|
143
|
+
content: {
|
|
144
|
+
role: "model",
|
|
145
|
+
parts: [
|
|
146
|
+
{
|
|
147
|
+
functionResponse: {
|
|
148
|
+
name: "search",
|
|
149
|
+
id: "tc-1",
|
|
150
|
+
response: { results: [] },
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
expect(msgs[0]).toMatchObject({
|
|
158
|
+
type: "tool",
|
|
159
|
+
tool_call_id: "tc-1",
|
|
160
|
+
name: "search",
|
|
161
|
+
content: JSON.stringify({ results: [] }),
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe("AdkEventAccumulator - code execution", () => {
|
|
167
|
+
it("creates code and code_result content parts", () => {
|
|
168
|
+
const acc = new AdkEventAccumulator();
|
|
169
|
+
acc.processEvent(
|
|
170
|
+
makeEvent({
|
|
171
|
+
author: "agent",
|
|
172
|
+
partial: true,
|
|
173
|
+
content: {
|
|
174
|
+
role: "model",
|
|
175
|
+
parts: [{ executableCode: { code: "print(1)", language: "python" } }],
|
|
176
|
+
},
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
const msgs = acc.processEvent(
|
|
180
|
+
makeEvent({
|
|
181
|
+
author: "agent",
|
|
182
|
+
content: {
|
|
183
|
+
role: "model",
|
|
184
|
+
parts: [
|
|
185
|
+
{ codeExecutionResult: { output: "1", outcome: "OUTCOME_OK" } },
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
}),
|
|
189
|
+
);
|
|
190
|
+
expect(msgs[0]).toMatchObject({
|
|
191
|
+
type: "ai",
|
|
192
|
+
content: [
|
|
193
|
+
{ type: "code", code: "print(1)", language: "python" },
|
|
194
|
+
{ type: "code_result", output: "1", outcome: "OUTCOME_OK" },
|
|
195
|
+
],
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("AdkEventAccumulator - error handling", () => {
|
|
201
|
+
it("creates an error message with text and incomplete status", () => {
|
|
202
|
+
const acc = new AdkEventAccumulator();
|
|
203
|
+
const msgs = acc.processEvent(
|
|
204
|
+
makeEvent({
|
|
205
|
+
author: "agent",
|
|
206
|
+
errorCode: "500",
|
|
207
|
+
errorMessage: "Server error",
|
|
208
|
+
}),
|
|
209
|
+
);
|
|
210
|
+
expect(msgs[0]).toMatchObject({
|
|
211
|
+
type: "ai",
|
|
212
|
+
content: [{ type: "text", text: "Server error" }],
|
|
213
|
+
status: { type: "incomplete", reason: "error", error: "Server error" },
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("uses errorCode when errorMessage is absent", () => {
|
|
218
|
+
const acc = new AdkEventAccumulator();
|
|
219
|
+
const msgs = acc.processEvent(
|
|
220
|
+
makeEvent({ author: "agent", errorCode: "UNKNOWN_ERROR" }),
|
|
221
|
+
);
|
|
222
|
+
expect(msgs[0]).toMatchObject({
|
|
223
|
+
content: [{ type: "text", text: "UNKNOWN_ERROR" }],
|
|
224
|
+
status: { type: "incomplete", reason: "error", error: "UNKNOWN_ERROR" },
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe("AdkEventAccumulator - interrupted events", () => {
|
|
230
|
+
it("sets status to incomplete/cancelled on the current message", () => {
|
|
231
|
+
const acc = new AdkEventAccumulator();
|
|
232
|
+
acc.processEvent(makeTextEvent("Hello", true));
|
|
233
|
+
const msgs = acc.processEvent(
|
|
234
|
+
makeEvent({ author: "agent", interrupted: true }),
|
|
235
|
+
);
|
|
236
|
+
expect(msgs[0]).toMatchObject({
|
|
237
|
+
status: { type: "incomplete", reason: "cancelled" },
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe("AdkEventAccumulator - finish reasons", () => {
|
|
243
|
+
it("sets complete/stop for normal completion", () => {
|
|
244
|
+
const acc = new AdkEventAccumulator();
|
|
245
|
+
const msgs = acc.processEvent(makeTextEvent("Done"));
|
|
246
|
+
expect(msgs[0]).toMatchObject({
|
|
247
|
+
status: { type: "complete", reason: "stop" },
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("sets incomplete/length for MAX_TOKENS", () => {
|
|
252
|
+
const acc = new AdkEventAccumulator();
|
|
253
|
+
const msgs = acc.processEvent(
|
|
254
|
+
makeEvent({
|
|
255
|
+
author: "agent",
|
|
256
|
+
finishReason: "MAX_TOKENS",
|
|
257
|
+
content: { role: "model", parts: [{ text: "truncated" }] },
|
|
258
|
+
}),
|
|
259
|
+
);
|
|
260
|
+
expect(msgs[0]).toMatchObject({
|
|
261
|
+
status: { type: "incomplete", reason: "length" },
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("sets incomplete/content-filter for SAFETY", () => {
|
|
266
|
+
const acc = new AdkEventAccumulator();
|
|
267
|
+
const msgs = acc.processEvent(
|
|
268
|
+
makeEvent({
|
|
269
|
+
author: "agent",
|
|
270
|
+
finishReason: "SAFETY",
|
|
271
|
+
content: { role: "model", parts: [{ text: "" }] },
|
|
272
|
+
}),
|
|
273
|
+
);
|
|
274
|
+
expect(msgs[0]).toMatchObject({
|
|
275
|
+
status: { type: "incomplete", reason: "content-filter" },
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("sets incomplete/error for MALFORMED_FUNCTION_CALL", () => {
|
|
280
|
+
const acc = new AdkEventAccumulator();
|
|
281
|
+
const msgs = acc.processEvent(
|
|
282
|
+
makeEvent({
|
|
283
|
+
author: "agent",
|
|
284
|
+
finishReason: "MALFORMED_FUNCTION_CALL",
|
|
285
|
+
content: { role: "model", parts: [{ text: "bad" }] },
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
288
|
+
expect(msgs[0]).toMatchObject({
|
|
289
|
+
status: { type: "incomplete", reason: "error" },
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe("AdkEventAccumulator - isFinalResponse logic", () => {
|
|
295
|
+
it("does NOT mark as final when event has functionCall parts", () => {
|
|
296
|
+
const acc = new AdkEventAccumulator();
|
|
297
|
+
const msgs = acc.processEvent(
|
|
298
|
+
makeEvent({
|
|
299
|
+
author: "agent",
|
|
300
|
+
content: {
|
|
301
|
+
role: "model",
|
|
302
|
+
parts: [{ functionCall: { name: "tool", args: {} } }],
|
|
303
|
+
},
|
|
304
|
+
}),
|
|
305
|
+
);
|
|
306
|
+
expect((msgs[0] as AdkMessage & { type: "ai" }).status).toBeUndefined();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("marks as final when skipSummarization is true, even with partial", () => {
|
|
310
|
+
const acc = new AdkEventAccumulator();
|
|
311
|
+
const msgs = acc.processEvent(
|
|
312
|
+
makeEvent({
|
|
313
|
+
author: "agent",
|
|
314
|
+
partial: true,
|
|
315
|
+
actions: { skipSummarization: true },
|
|
316
|
+
content: { role: "model", parts: [{ text: "skipped" }] },
|
|
317
|
+
}),
|
|
318
|
+
);
|
|
319
|
+
expect(msgs[0]).toMatchObject({
|
|
320
|
+
status: { type: "complete", reason: "stop" },
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("marks as final when longRunningToolIds is non-empty", () => {
|
|
325
|
+
const acc = new AdkEventAccumulator();
|
|
326
|
+
const msgs = acc.processEvent(
|
|
327
|
+
makeEvent({
|
|
328
|
+
author: "agent",
|
|
329
|
+
longRunningToolIds: ["lrt-1"],
|
|
330
|
+
content: {
|
|
331
|
+
role: "model",
|
|
332
|
+
parts: [
|
|
333
|
+
{ functionCall: { name: "slow_tool", id: "lrt-1", args: {} } },
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
}),
|
|
337
|
+
);
|
|
338
|
+
expect(msgs[0]).toMatchObject({
|
|
339
|
+
status: { type: "complete", reason: "stop" },
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe("AdkEventAccumulator - actions tracking", () => {
|
|
345
|
+
it("accumulates stateDelta across multiple events", () => {
|
|
346
|
+
const acc = new AdkEventAccumulator();
|
|
347
|
+
acc.processEvent(
|
|
348
|
+
makeEvent({
|
|
349
|
+
actions: { stateDelta: { a: 1 } },
|
|
350
|
+
author: "agent",
|
|
351
|
+
content: { parts: [{ text: "x" }] },
|
|
352
|
+
}),
|
|
353
|
+
);
|
|
354
|
+
acc.processEvent(
|
|
355
|
+
makeEvent({
|
|
356
|
+
actions: { stateDelta: { b: 2 } },
|
|
357
|
+
author: "agent",
|
|
358
|
+
content: { parts: [{ text: "y" }] },
|
|
359
|
+
}),
|
|
360
|
+
);
|
|
361
|
+
expect(acc.getStateDelta()).toEqual({ a: 1, b: 2 });
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("accumulates artifactDelta", () => {
|
|
365
|
+
const acc = new AdkEventAccumulator();
|
|
366
|
+
acc.processEvent(
|
|
367
|
+
makeEvent({
|
|
368
|
+
actions: { artifactDelta: { "file.txt": 1 } },
|
|
369
|
+
author: "agent",
|
|
370
|
+
content: { parts: [{ text: "x" }] },
|
|
371
|
+
}),
|
|
372
|
+
);
|
|
373
|
+
expect(acc.getArtifactDelta()).toEqual({ "file.txt": 1 });
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("tracks escalation flag", () => {
|
|
377
|
+
const acc = new AdkEventAccumulator();
|
|
378
|
+
expect(acc.isEscalated()).toBe(false);
|
|
379
|
+
acc.processEvent(
|
|
380
|
+
makeEvent({
|
|
381
|
+
actions: { escalate: true },
|
|
382
|
+
author: "agent",
|
|
383
|
+
content: { parts: [{ text: "x" }] },
|
|
384
|
+
}),
|
|
385
|
+
);
|
|
386
|
+
expect(acc.isEscalated()).toBe(true);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it("tracks transferToAgent", () => {
|
|
390
|
+
const acc = new AdkEventAccumulator();
|
|
391
|
+
acc.processEvent(
|
|
392
|
+
makeEvent({
|
|
393
|
+
actions: { transferToAgent: "sub_agent" },
|
|
394
|
+
author: "agent",
|
|
395
|
+
content: { parts: [{ text: "x" }] },
|
|
396
|
+
}),
|
|
397
|
+
);
|
|
398
|
+
expect(acc.getLastTransferToAgent()).toBe("sub_agent");
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it("tracks longRunningToolIds", () => {
|
|
402
|
+
const acc = new AdkEventAccumulator();
|
|
403
|
+
acc.processEvent(
|
|
404
|
+
makeEvent({
|
|
405
|
+
longRunningToolIds: ["lrt-1", "lrt-2"],
|
|
406
|
+
author: "agent",
|
|
407
|
+
content: { parts: [{ text: "x" }] },
|
|
408
|
+
}),
|
|
409
|
+
);
|
|
410
|
+
expect(acc.getLongRunningToolIds()).toEqual(["lrt-1", "lrt-2"]);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe("AdkEventAccumulator - special function calls", () => {
|
|
415
|
+
it("records tool confirmation from adk_request_confirmation", () => {
|
|
416
|
+
const acc = new AdkEventAccumulator();
|
|
417
|
+
acc.processEvent(
|
|
418
|
+
makeEvent({
|
|
419
|
+
author: "agent",
|
|
420
|
+
longRunningToolIds: ["tc-1"],
|
|
421
|
+
content: {
|
|
422
|
+
role: "model",
|
|
423
|
+
parts: [
|
|
424
|
+
{
|
|
425
|
+
functionCall: {
|
|
426
|
+
name: "adk_request_confirmation",
|
|
427
|
+
id: "tc-1",
|
|
428
|
+
args: {
|
|
429
|
+
originalFunctionCall: {
|
|
430
|
+
name: "delete_file",
|
|
431
|
+
args: { path: "/tmp" },
|
|
432
|
+
},
|
|
433
|
+
toolConfirmation: { hint: "Are you sure?", payload: {} },
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
},
|
|
439
|
+
}),
|
|
440
|
+
);
|
|
441
|
+
const confs = acc.getToolConfirmations();
|
|
442
|
+
expect(confs).toHaveLength(1);
|
|
443
|
+
expect(confs[0]).toMatchObject({
|
|
444
|
+
toolCallId: "tc-1",
|
|
445
|
+
toolName: "delete_file",
|
|
446
|
+
args: { path: "/tmp" },
|
|
447
|
+
hint: "Are you sure?",
|
|
448
|
+
confirmed: false,
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it("records auth request from adk_request_credential", () => {
|
|
453
|
+
const acc = new AdkEventAccumulator();
|
|
454
|
+
acc.processEvent(
|
|
455
|
+
makeEvent({
|
|
456
|
+
author: "agent",
|
|
457
|
+
content: {
|
|
458
|
+
role: "model",
|
|
459
|
+
parts: [
|
|
460
|
+
{
|
|
461
|
+
functionCall: {
|
|
462
|
+
name: "adk_request_credential",
|
|
463
|
+
id: "cred-1",
|
|
464
|
+
args: {
|
|
465
|
+
function_call_id: "tc-original",
|
|
466
|
+
auth_config: { type: "oauth2" },
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
],
|
|
471
|
+
},
|
|
472
|
+
}),
|
|
473
|
+
);
|
|
474
|
+
const reqs = acc.getAuthRequests();
|
|
475
|
+
expect(reqs).toHaveLength(1);
|
|
476
|
+
expect(reqs[0]).toMatchObject({
|
|
477
|
+
toolCallId: "tc-original",
|
|
478
|
+
authConfig: { type: "oauth2" },
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
describe("AdkEventAccumulator - author/agent tracking", () => {
|
|
484
|
+
it("tracks agent name and branch", () => {
|
|
485
|
+
const acc = new AdkEventAccumulator();
|
|
486
|
+
acc.processEvent(
|
|
487
|
+
makeEvent({
|
|
488
|
+
author: "search_agent",
|
|
489
|
+
branch: "root.search_agent",
|
|
490
|
+
content: { parts: [{ text: "hi" }] },
|
|
491
|
+
}),
|
|
492
|
+
);
|
|
493
|
+
expect(acc.getAgentInfo()).toEqual({
|
|
494
|
+
name: "search_agent",
|
|
495
|
+
branch: "root.search_agent",
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it("does not track user as agent info", () => {
|
|
500
|
+
const acc = new AdkEventAccumulator();
|
|
501
|
+
acc.processEvent(makeTextEvent("hello", false, "user"));
|
|
502
|
+
expect(acc.getAgentInfo()).toEqual({});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it("finalizes current message when author changes", () => {
|
|
506
|
+
const acc = new AdkEventAccumulator();
|
|
507
|
+
acc.processEvent(makeTextEvent("From agent A", false, "agent_a"));
|
|
508
|
+
const msgs = acc.processEvent(
|
|
509
|
+
makeTextEvent("From agent B", false, "agent_b"),
|
|
510
|
+
);
|
|
511
|
+
expect(msgs).toHaveLength(2);
|
|
512
|
+
expect(msgs[0]).toMatchObject({ type: "ai", author: "agent_a" });
|
|
513
|
+
expect(msgs[1]).toMatchObject({ type: "ai", author: "agent_b" });
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
describe("AdkEventAccumulator - snake_case normalization", () => {
|
|
518
|
+
it("normalizes function_call to functionCall in parts", () => {
|
|
519
|
+
const acc = new AdkEventAccumulator();
|
|
520
|
+
const msgs = acc.processEvent(
|
|
521
|
+
makeEvent({
|
|
522
|
+
author: "agent",
|
|
523
|
+
content: {
|
|
524
|
+
role: "model",
|
|
525
|
+
parts: [{ function_call: { name: "test", args: {} } } as any],
|
|
526
|
+
},
|
|
527
|
+
}),
|
|
528
|
+
);
|
|
529
|
+
expect((msgs[0] as AdkMessage & { type: "ai" }).tool_calls).toHaveLength(1);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it("normalizes error_code on the event", () => {
|
|
533
|
+
const acc = new AdkEventAccumulator();
|
|
534
|
+
const msgs = acc.processEvent({ id: "e1", error_code: "ERR" } as any);
|
|
535
|
+
expect(msgs[0]).toMatchObject({
|
|
536
|
+
status: { type: "incomplete", reason: "error" },
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it("normalizes snake_case action keys", () => {
|
|
541
|
+
const acc = new AdkEventAccumulator();
|
|
542
|
+
acc.processEvent({
|
|
543
|
+
id: "e1",
|
|
544
|
+
author: "agent",
|
|
545
|
+
actions: { state_delta: { x: 1 }, transfer_to_agent: "sub" } as any,
|
|
546
|
+
content: { parts: [{ text: "hi" }] },
|
|
547
|
+
} as any);
|
|
548
|
+
expect(acc.getStateDelta()).toEqual({ x: 1 });
|
|
549
|
+
expect(acc.getLastTransferToAgent()).toBe("sub");
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
describe("AdkEventAccumulator - metadata tracking", () => {
|
|
554
|
+
it("tracks grounding and usage metadata per message", () => {
|
|
555
|
+
const acc = new AdkEventAccumulator();
|
|
556
|
+
acc.processEvent(
|
|
557
|
+
makeEvent({
|
|
558
|
+
author: "agent",
|
|
559
|
+
content: { role: "model", parts: [{ text: "hi" }] },
|
|
560
|
+
groundingMetadata: { sources: ["google.com"] },
|
|
561
|
+
usageMetadata: { promptTokenCount: 10 },
|
|
562
|
+
}),
|
|
563
|
+
);
|
|
564
|
+
const meta = acc.getMessageMetadata();
|
|
565
|
+
expect(meta.size).toBe(1);
|
|
566
|
+
const first = [...meta.values()][0];
|
|
567
|
+
expect(first).toMatchObject({
|
|
568
|
+
groundingMetadata: { sources: ["google.com"] },
|
|
569
|
+
usageMetadata: { promptTokenCount: 10 },
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
describe("AdkEventAccumulator - initial messages", () => {
|
|
575
|
+
it("initializes with provided messages", () => {
|
|
576
|
+
const initial: AdkMessage[] = [
|
|
577
|
+
{ id: "m1", type: "human", content: "Hello" },
|
|
578
|
+
];
|
|
579
|
+
const acc = new AdkEventAccumulator(initial);
|
|
580
|
+
expect(acc.getMessages()).toHaveLength(1);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it("appends new events after initial messages", () => {
|
|
584
|
+
const initial: AdkMessage[] = [
|
|
585
|
+
{ id: "m1", type: "human", content: "Hello" },
|
|
586
|
+
];
|
|
587
|
+
const acc = new AdkEventAccumulator(initial);
|
|
588
|
+
const msgs = acc.processEvent(makeTextEvent("Hi there"));
|
|
589
|
+
expect(msgs).toHaveLength(2);
|
|
590
|
+
});
|
|
591
|
+
});
|