@assistant-ui/react-google-adk 0.0.29 → 0.0.31
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/AdkClient.d.ts +2 -3
- package/dist/AdkClient.d.ts.map +1 -1
- package/dist/AdkClient.js +2 -1
- package/dist/AdkClient.js.map +1 -1
- package/dist/AdkEventAccumulator.d.ts +4 -6
- package/dist/AdkEventAccumulator.d.ts.map +1 -1
- package/dist/AdkEventAccumulator.js +80 -95
- package/dist/AdkEventAccumulator.js.map +1 -1
- package/dist/AdkSessionAdapter.d.ts +7 -4
- package/dist/AdkSessionAdapter.d.ts.map +1 -1
- package/dist/AdkSessionAdapter.js +7 -4
- package/dist/AdkSessionAdapter.js.map +1 -1
- package/dist/adkExtras.d.ts +1 -2
- package/dist/adkExtras.d.ts.map +1 -1
- package/dist/adkToolApproval.d.ts +6 -7
- package/dist/adkToolApproval.d.ts.map +1 -1
- package/dist/adkToolApproval.js +1 -3
- package/dist/adkToolApproval.js.map +1 -1
- package/dist/contentToParts.d.ts +1 -2
- package/dist/contentToParts.d.ts.map +1 -1
- package/dist/convertAdkMessages.d.ts +2 -3
- package/dist/convertAdkMessages.d.ts.map +1 -1
- package/dist/convertToAdkMessages.d.ts +15 -7
- package/dist/convertToAdkMessages.d.ts.map +1 -1
- package/dist/convertToAdkMessages.js +20 -6
- package/dist/convertToAdkMessages.js.map +1 -1
- package/dist/hooks.d.ts +15 -16
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +1 -1
- package/dist/normalizeAdkPart.d.ts +4 -0
- package/dist/normalizeAdkPart.d.ts.map +1 -0
- package/dist/normalizeAdkPart.js +31 -0
- package/dist/normalizeAdkPart.js.map +1 -0
- package/dist/parseAdkEvent.d.ts +1 -2
- package/dist/parseAdkEvent.d.ts.map +1 -1
- package/dist/sdkIdentity.d.ts +5 -0
- package/dist/sdkIdentity.d.ts.map +1 -0
- package/dist/sdkIdentity.js +9 -0
- package/dist/sdkIdentity.js.map +1 -0
- package/dist/server/adkEventStream.d.ts +2 -3
- package/dist/server/adkEventStream.d.ts.map +1 -1
- package/dist/server/createAdkApiRoute.d.ts +2 -3
- package/dist/server/createAdkApiRoute.d.ts.map +1 -1
- package/dist/server/parseAdkRequest.d.ts +2 -3
- package/dist/server/parseAdkRequest.d.ts.map +1 -1
- package/dist/server/parseAdkRequest.js +2 -1
- package/dist/server/parseAdkRequest.js.map +1 -1
- package/dist/structuredEvents.d.ts +1 -2
- package/dist/structuredEvents.d.ts.map +1 -1
- package/dist/structuredEvents.js +1 -1
- package/dist/structuredEvents.js.map +1 -1
- package/dist/toAdkFunctionResponse.d.ts +5 -0
- package/dist/toAdkFunctionResponse.d.ts.map +1 -0
- package/dist/toAdkFunctionResponse.js +11 -0
- package/dist/toAdkFunctionResponse.js.map +1 -0
- package/dist/trimTrailingSlashes.d.ts +1 -2
- package/dist/trimTrailingSlashes.d.ts.map +1 -1
- package/dist/types.d.ts +22 -23
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/useAdkMessages.d.ts +2 -3
- package/dist/useAdkMessages.d.ts.map +1 -1
- package/dist/useAdkMessages.js +16 -5
- package/dist/useAdkMessages.js.map +1 -1
- package/dist/useAdkRuntime.d.ts +2 -3
- package/dist/useAdkRuntime.d.ts.map +1 -1
- package/dist/useAdkRuntime.js +3 -1
- package/dist/useAdkRuntime.js.map +1 -1
- package/package.json +10 -9
- package/src/AdkClient.test.ts +139 -2
- package/src/AdkClient.ts +2 -1
- package/src/AdkEventAccumulator.test.ts +588 -11
- package/src/AdkEventAccumulator.ts +85 -130
- package/src/AdkSessionAdapter.test.ts +355 -1
- package/src/AdkSessionAdapter.ts +24 -5
- package/src/adkToolApproval.test.ts +0 -4
- package/src/adkToolApproval.ts +1 -3
- package/src/convertToAdkMessages.test.ts +70 -0
- package/src/convertToAdkMessages.ts +19 -4
- package/src/normalizeAdkPart.ts +35 -0
- package/src/sdkIdentity.ts +9 -0
- package/src/server/parseAdkRequest.test.ts +63 -0
- package/src/server/parseAdkRequest.ts +2 -1
- package/src/structuredEvents.test.ts +69 -0
- package/src/structuredEvents.ts +4 -1
- package/src/toAdkFunctionResponse.test.ts +46 -0
- package/src/toAdkFunctionResponse.ts +18 -0
- package/src/types.ts +1 -1
- package/src/useAdkMessages.test.ts +249 -2
- package/src/useAdkMessages.ts +24 -6
- package/src/useAdkRuntime.replacement.test.tsx +11 -7
- package/src/useAdkRuntime.ts +2 -0
- package/src/useAdkRuntimeApproval.test.tsx +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { AdkEventAccumulator } from "./AdkEventAccumulator";
|
|
3
|
+
import { parseAdkEventValue } from "./parseAdkEvent";
|
|
3
4
|
import type { AdkEvent, AdkMessage, AdkMessageContentPart } from "./types";
|
|
4
5
|
|
|
5
6
|
const makeEvent = (overrides: Partial<AdkEvent> = {}): AdkEvent => ({
|
|
@@ -18,6 +19,41 @@ const makeTextEvent = (
|
|
|
18
19
|
content: { role: "model", parts: [{ text }] },
|
|
19
20
|
});
|
|
20
21
|
|
|
22
|
+
describe.each(["user", "agent"])("%s function response status", (author) => {
|
|
23
|
+
it.each([
|
|
24
|
+
[{ error: "denied" }, "error"],
|
|
25
|
+
[{ error: { message: "denied" }, output: "partial" }, "error"],
|
|
26
|
+
[{ error: null }, "error"],
|
|
27
|
+
[{ error: false }, "error"],
|
|
28
|
+
[{ error: "" }, "error"],
|
|
29
|
+
[{ output: { error: "application data" } }, "success"],
|
|
30
|
+
[{ result: "done" }, "success"],
|
|
31
|
+
[{}, "success"],
|
|
32
|
+
] as const)("classifies response %j as %s", (response, status) => {
|
|
33
|
+
const acc = new AdkEventAccumulator();
|
|
34
|
+
const messages = acc.processEvent(
|
|
35
|
+
makeEvent({
|
|
36
|
+
author,
|
|
37
|
+
content: {
|
|
38
|
+
parts: [
|
|
39
|
+
{
|
|
40
|
+
functionResponse: { id: "tc-1", name: "search", response },
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
expect(messages).toMatchObject([
|
|
47
|
+
{
|
|
48
|
+
type: "tool",
|
|
49
|
+
tool_call_id: "tc-1",
|
|
50
|
+
status,
|
|
51
|
+
content: JSON.stringify(response),
|
|
52
|
+
},
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
21
57
|
describe("AdkEventAccumulator - text handling", () => {
|
|
22
58
|
it("accumulates a single non-partial text event into an AI message", () => {
|
|
23
59
|
const acc = new AdkEventAccumulator();
|
|
@@ -210,6 +246,78 @@ describe("AdkEventAccumulator - function calls", () => {
|
|
|
210
246
|
);
|
|
211
247
|
expect(acc.getToolConfirmations()).toHaveLength(0);
|
|
212
248
|
});
|
|
249
|
+
|
|
250
|
+
const requestEvent = (name: string) =>
|
|
251
|
+
makeEvent({
|
|
252
|
+
author: "agent",
|
|
253
|
+
content: {
|
|
254
|
+
role: "model",
|
|
255
|
+
parts: [{ functionCall: { name, id: "rc-1" } }, { text: "still here" }],
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("keeps an args-less confirmation call answerable", () => {
|
|
260
|
+
const acc = new AdkEventAccumulator();
|
|
261
|
+
const msgs = acc.processEvent(requestEvent("adk_request_confirmation"));
|
|
262
|
+
|
|
263
|
+
expect(acc.getToolConfirmations()).toEqual([
|
|
264
|
+
{
|
|
265
|
+
toolCallId: "rc-1",
|
|
266
|
+
toolName: "",
|
|
267
|
+
args: {},
|
|
268
|
+
hint: "",
|
|
269
|
+
confirmed: false,
|
|
270
|
+
},
|
|
271
|
+
]);
|
|
272
|
+
expect((msgs[0] as AdkMessage & { type: "ai" }).tool_calls).toEqual([
|
|
273
|
+
{
|
|
274
|
+
id: "rc-1",
|
|
275
|
+
name: "adk_request_confirmation",
|
|
276
|
+
args: {},
|
|
277
|
+
argsText: "{}",
|
|
278
|
+
},
|
|
279
|
+
]);
|
|
280
|
+
expect(msgs[0]).toMatchObject({
|
|
281
|
+
content: [{ type: "text", text: "still here" }],
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("keeps an args-less credential call answerable", () => {
|
|
286
|
+
const acc = new AdkEventAccumulator();
|
|
287
|
+
const msgs = acc.processEvent(requestEvent("adk_request_credential"));
|
|
288
|
+
|
|
289
|
+
expect(acc.getAuthRequests()).toEqual([
|
|
290
|
+
{ toolCallId: "rc-1", authConfig: undefined },
|
|
291
|
+
]);
|
|
292
|
+
expect((msgs[0] as AdkMessage & { type: "ai" }).tool_calls).toEqual([
|
|
293
|
+
{
|
|
294
|
+
id: "rc-1",
|
|
295
|
+
name: "adk_request_credential",
|
|
296
|
+
args: {},
|
|
297
|
+
argsText: "{}",
|
|
298
|
+
},
|
|
299
|
+
]);
|
|
300
|
+
expect(msgs[0]).toMatchObject({
|
|
301
|
+
content: [{ type: "text", text: "still here" }],
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("defaults an ordinary call without args to empty args", () => {
|
|
306
|
+
const acc = new AdkEventAccumulator();
|
|
307
|
+
const msgs = acc.processEvent(
|
|
308
|
+
makeEvent({
|
|
309
|
+
author: "agent",
|
|
310
|
+
content: {
|
|
311
|
+
role: "model",
|
|
312
|
+
parts: [{ functionCall: { name: "search", id: "tc-1" } }],
|
|
313
|
+
},
|
|
314
|
+
}),
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
expect((msgs[0] as AdkMessage & { type: "ai" }).tool_calls).toEqual([
|
|
318
|
+
{ id: "tc-1", name: "search", args: {}, argsText: "{}" },
|
|
319
|
+
]);
|
|
320
|
+
});
|
|
213
321
|
});
|
|
214
322
|
|
|
215
323
|
describe("AdkEventAccumulator - function responses", () => {
|
|
@@ -933,6 +1041,99 @@ describe("AdkEventAccumulator - actions tracking", () => {
|
|
|
933
1041
|
);
|
|
934
1042
|
expect(acc.getLongRunningToolIds()).toEqual(["lrt-1"]);
|
|
935
1043
|
});
|
|
1044
|
+
|
|
1045
|
+
it("settles a long-running id when a user-authored response answers it", () => {
|
|
1046
|
+
const acc = new AdkEventAccumulator();
|
|
1047
|
+
acc.processEvent(
|
|
1048
|
+
makeEvent({
|
|
1049
|
+
author: "agent",
|
|
1050
|
+
longRunningToolIds: ["lrt-1", "lrt-2"],
|
|
1051
|
+
content: {
|
|
1052
|
+
parts: [
|
|
1053
|
+
{
|
|
1054
|
+
functionCall: { name: "ask_for_approval", id: "lrt-1", args: {} },
|
|
1055
|
+
},
|
|
1056
|
+
{
|
|
1057
|
+
functionCall: { name: "ask_for_approval", id: "lrt-2", args: {} },
|
|
1058
|
+
},
|
|
1059
|
+
],
|
|
1060
|
+
},
|
|
1061
|
+
}),
|
|
1062
|
+
);
|
|
1063
|
+
acc.processEvent(
|
|
1064
|
+
makeEvent({
|
|
1065
|
+
author: "user",
|
|
1066
|
+
content: {
|
|
1067
|
+
parts: [
|
|
1068
|
+
{
|
|
1069
|
+
functionResponse: {
|
|
1070
|
+
name: "ask_for_approval",
|
|
1071
|
+
id: "lrt-1",
|
|
1072
|
+
response: { status: "approved" },
|
|
1073
|
+
},
|
|
1074
|
+
},
|
|
1075
|
+
],
|
|
1076
|
+
},
|
|
1077
|
+
}),
|
|
1078
|
+
);
|
|
1079
|
+
expect(acc.getLongRunningToolIds()).toEqual(["lrt-2"]);
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
it("keeps a long-running id pending through the interim response ADK authors", () => {
|
|
1083
|
+
const acc = new AdkEventAccumulator();
|
|
1084
|
+
acc.processEvent(
|
|
1085
|
+
makeEvent({
|
|
1086
|
+
author: "agent",
|
|
1087
|
+
longRunningToolIds: ["lrt-1"],
|
|
1088
|
+
content: {
|
|
1089
|
+
parts: [
|
|
1090
|
+
{
|
|
1091
|
+
functionCall: { name: "ask_for_approval", id: "lrt-1", args: {} },
|
|
1092
|
+
},
|
|
1093
|
+
],
|
|
1094
|
+
},
|
|
1095
|
+
}),
|
|
1096
|
+
);
|
|
1097
|
+
acc.processEvent(
|
|
1098
|
+
makeEvent({
|
|
1099
|
+
author: "agent",
|
|
1100
|
+
content: {
|
|
1101
|
+
parts: [
|
|
1102
|
+
{
|
|
1103
|
+
functionResponse: {
|
|
1104
|
+
name: "ask_for_approval",
|
|
1105
|
+
id: "lrt-1",
|
|
1106
|
+
response: { status: "pending" },
|
|
1107
|
+
},
|
|
1108
|
+
},
|
|
1109
|
+
],
|
|
1110
|
+
},
|
|
1111
|
+
}),
|
|
1112
|
+
);
|
|
1113
|
+
expect(acc.getLongRunningToolIds()).toEqual(["lrt-1"]);
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
it("seeds and settles longRunningToolIds from a user response", () => {
|
|
1117
|
+
const acc = new AdkEventAccumulator([], ["lrt-1", "lrt-2"]);
|
|
1118
|
+
acc.processEvent(
|
|
1119
|
+
makeEvent({
|
|
1120
|
+
author: "user",
|
|
1121
|
+
content: {
|
|
1122
|
+
role: "user",
|
|
1123
|
+
parts: [
|
|
1124
|
+
{
|
|
1125
|
+
functionResponse: {
|
|
1126
|
+
id: "lrt-1",
|
|
1127
|
+
name: "adk_request_confirmation",
|
|
1128
|
+
response: { confirmed: true },
|
|
1129
|
+
},
|
|
1130
|
+
},
|
|
1131
|
+
],
|
|
1132
|
+
},
|
|
1133
|
+
}),
|
|
1134
|
+
);
|
|
1135
|
+
expect(acc.getLongRunningToolIds()).toEqual(["lrt-2"]);
|
|
1136
|
+
});
|
|
936
1137
|
});
|
|
937
1138
|
|
|
938
1139
|
describe("AdkEventAccumulator - special function calls", () => {
|
|
@@ -973,21 +1174,176 @@ describe("AdkEventAccumulator - special function calls", () => {
|
|
|
973
1174
|
});
|
|
974
1175
|
});
|
|
975
1176
|
|
|
976
|
-
it(
|
|
1177
|
+
it.each([
|
|
1178
|
+
[
|
|
1179
|
+
"snake_case",
|
|
1180
|
+
{ function_call_id: "tc-original", auth_config: { type: "oauth2" } },
|
|
1181
|
+
],
|
|
1182
|
+
[
|
|
1183
|
+
"camelCase",
|
|
1184
|
+
{ functionCallId: "tc-original", authConfig: { type: "oauth2" } },
|
|
1185
|
+
],
|
|
1186
|
+
])(
|
|
1187
|
+
"records an auth request under its adk_request_credential call id (%s args)",
|
|
1188
|
+
(_, args) => {
|
|
1189
|
+
const acc = new AdkEventAccumulator();
|
|
1190
|
+
acc.processEvent(
|
|
1191
|
+
makeEvent({
|
|
1192
|
+
author: "agent",
|
|
1193
|
+
content: {
|
|
1194
|
+
role: "model",
|
|
1195
|
+
parts: [
|
|
1196
|
+
{
|
|
1197
|
+
functionCall: {
|
|
1198
|
+
name: "adk_request_credential",
|
|
1199
|
+
id: "cred-1",
|
|
1200
|
+
args,
|
|
1201
|
+
},
|
|
1202
|
+
},
|
|
1203
|
+
],
|
|
1204
|
+
},
|
|
1205
|
+
}),
|
|
1206
|
+
);
|
|
1207
|
+
expect(acc.getAuthRequests()).toEqual([
|
|
1208
|
+
{ toolCallId: "cred-1", authConfig: { type: "oauth2" } },
|
|
1209
|
+
]);
|
|
1210
|
+
},
|
|
1211
|
+
);
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
describe.each([
|
|
1215
|
+
{
|
|
1216
|
+
name: "adk_request_confirmation",
|
|
1217
|
+
args: {
|
|
1218
|
+
originalFunctionCall: { id: "gated", name: "transfer", args: {} },
|
|
1219
|
+
toolConfirmation: { hint: "Transfer?" },
|
|
1220
|
+
},
|
|
1221
|
+
actions: {
|
|
1222
|
+
requestedToolConfirmations: { gated: { hint: "Transfer?" } },
|
|
1223
|
+
},
|
|
1224
|
+
reply: { confirmed: true },
|
|
1225
|
+
pending: (acc: AdkEventAccumulator) =>
|
|
1226
|
+
acc.getToolConfirmations().map((c) => c.toolCallId),
|
|
1227
|
+
},
|
|
1228
|
+
{
|
|
1229
|
+
name: "adk_request_credential",
|
|
1230
|
+
args: { function_call_id: "gated", auth_config: { credentialKey: "k" } },
|
|
1231
|
+
actions: {
|
|
1232
|
+
requestedAuthConfigs: {
|
|
1233
|
+
gated: { credentialKey: "k" },
|
|
1234
|
+
"gated-2": { credentialKey: "k" },
|
|
1235
|
+
},
|
|
1236
|
+
},
|
|
1237
|
+
reply: { exchangedAuthCredential: { authType: "apiKey", apiKey: "key" } },
|
|
1238
|
+
pending: (acc: AdkEventAccumulator) =>
|
|
1239
|
+
acc.getAuthRequests().map((r) => r.toolCallId),
|
|
1240
|
+
},
|
|
1241
|
+
])("AdkEventAccumulator - pending $name requests", (request) => {
|
|
1242
|
+
const requestEvents: AdkEvent[] = [
|
|
1243
|
+
makeEvent({
|
|
1244
|
+
id: "call",
|
|
1245
|
+
author: "agent",
|
|
1246
|
+
content: {
|
|
1247
|
+
role: "model",
|
|
1248
|
+
parts: [
|
|
1249
|
+
{ functionCall: { name: "transfer", id: "gated", args: {} } },
|
|
1250
|
+
{ functionCall: { name: "transfer", id: "gated-2", args: {} } },
|
|
1251
|
+
],
|
|
1252
|
+
},
|
|
1253
|
+
}),
|
|
1254
|
+
makeEvent({
|
|
1255
|
+
id: "request",
|
|
1256
|
+
author: "agent",
|
|
1257
|
+
longRunningToolIds: ["req"],
|
|
1258
|
+
actions: request.actions,
|
|
1259
|
+
content: {
|
|
1260
|
+
role: "user",
|
|
1261
|
+
parts: [
|
|
1262
|
+
{
|
|
1263
|
+
functionCall: { name: request.name, id: "req", args: request.args },
|
|
1264
|
+
},
|
|
1265
|
+
],
|
|
1266
|
+
},
|
|
1267
|
+
}),
|
|
1268
|
+
makeEvent({
|
|
1269
|
+
id: "interim",
|
|
1270
|
+
author: "agent",
|
|
1271
|
+
actions: request.actions,
|
|
1272
|
+
content: {
|
|
1273
|
+
role: "user",
|
|
1274
|
+
parts: [
|
|
1275
|
+
{
|
|
1276
|
+
functionResponse: {
|
|
1277
|
+
id: "gated",
|
|
1278
|
+
name: "transfer",
|
|
1279
|
+
response: { status: "pending" },
|
|
1280
|
+
},
|
|
1281
|
+
},
|
|
1282
|
+
],
|
|
1283
|
+
},
|
|
1284
|
+
}),
|
|
1285
|
+
];
|
|
1286
|
+
const replyPart = {
|
|
1287
|
+
functionResponse: {
|
|
1288
|
+
id: "req",
|
|
1289
|
+
name: request.name,
|
|
1290
|
+
response: request.reply,
|
|
1291
|
+
},
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
it("lists the request once, under its call, until the user replies to that call", () => {
|
|
977
1295
|
const acc = new AdkEventAccumulator();
|
|
1296
|
+
for (const event of requestEvents) acc.processEvent(event);
|
|
1297
|
+
expect(request.pending(acc)).toEqual(["req"]);
|
|
1298
|
+
|
|
978
1299
|
acc.processEvent(
|
|
979
1300
|
makeEvent({
|
|
1301
|
+
id: "reply",
|
|
1302
|
+
author: "user",
|
|
1303
|
+
content: { role: "user", parts: [replyPart] },
|
|
1304
|
+
}),
|
|
1305
|
+
);
|
|
1306
|
+
expect(request.pending(acc)).toEqual([]);
|
|
1307
|
+
});
|
|
1308
|
+
|
|
1309
|
+
it("carries the request into an accumulator seeded with the thread, which an optimistic reply settles", () => {
|
|
1310
|
+
const replayed = new AdkEventAccumulator();
|
|
1311
|
+
let messages: AdkMessage[] = [];
|
|
1312
|
+
for (const event of requestEvents) messages = replayed.processEvent(event);
|
|
1313
|
+
|
|
1314
|
+
const acc = new AdkEventAccumulator(messages);
|
|
1315
|
+
expect(request.pending(acc)).toEqual(["req"]);
|
|
1316
|
+
|
|
1317
|
+
acc.processEvent(
|
|
1318
|
+
makeEvent({
|
|
1319
|
+
id: "optimistic",
|
|
1320
|
+
content: { role: "user", parts: [replyPart] },
|
|
1321
|
+
}),
|
|
1322
|
+
);
|
|
1323
|
+
expect(request.pending(acc)).toEqual([]);
|
|
1324
|
+
});
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
describe("AdkEventAccumulator - confirmation replies", () => {
|
|
1328
|
+
it.each([
|
|
1329
|
+
[["req"], "an unreadable reply", { response: "not json" }],
|
|
1330
|
+
[[], "a denial", { confirmed: false }],
|
|
1331
|
+
])("lists %j after %s", (pending, _, response) => {
|
|
1332
|
+
const acc = new AdkEventAccumulator();
|
|
1333
|
+
acc.processEvent(
|
|
1334
|
+
makeEvent({
|
|
1335
|
+
id: "request",
|
|
980
1336
|
author: "agent",
|
|
1337
|
+
longRunningToolIds: ["req"],
|
|
981
1338
|
content: {
|
|
982
1339
|
role: "model",
|
|
983
1340
|
parts: [
|
|
984
1341
|
{
|
|
985
1342
|
functionCall: {
|
|
986
|
-
name: "
|
|
987
|
-
id: "
|
|
1343
|
+
name: "adk_request_confirmation",
|
|
1344
|
+
id: "req",
|
|
988
1345
|
args: {
|
|
989
|
-
|
|
990
|
-
auth_config: { type: "oauth2" },
|
|
1346
|
+
originalFunctionCall: { id: "gated", name: "transfer" },
|
|
991
1347
|
},
|
|
992
1348
|
},
|
|
993
1349
|
},
|
|
@@ -995,12 +1351,27 @@ describe("AdkEventAccumulator - special function calls", () => {
|
|
|
995
1351
|
},
|
|
996
1352
|
}),
|
|
997
1353
|
);
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1354
|
+
acc.processEvent(
|
|
1355
|
+
makeEvent({
|
|
1356
|
+
id: "reply",
|
|
1357
|
+
author: "user",
|
|
1358
|
+
content: {
|
|
1359
|
+
role: "user",
|
|
1360
|
+
parts: [
|
|
1361
|
+
{
|
|
1362
|
+
functionResponse: {
|
|
1363
|
+
id: "req",
|
|
1364
|
+
name: "adk_request_confirmation",
|
|
1365
|
+
response,
|
|
1366
|
+
},
|
|
1367
|
+
},
|
|
1368
|
+
],
|
|
1369
|
+
},
|
|
1370
|
+
}),
|
|
1371
|
+
);
|
|
1372
|
+
expect(acc.getToolConfirmations().map((c) => c.toolCallId)).toEqual(
|
|
1373
|
+
pending,
|
|
1374
|
+
);
|
|
1004
1375
|
});
|
|
1005
1376
|
});
|
|
1006
1377
|
|
|
@@ -1044,6 +1415,153 @@ describe("AdkEventAccumulator - author/agent tracking", () => {
|
|
|
1044
1415
|
});
|
|
1045
1416
|
|
|
1046
1417
|
describe("AdkEventAccumulator - snake_case normalization", () => {
|
|
1418
|
+
describe.each(["user", "agent"])("%s media", (author) => {
|
|
1419
|
+
it.each([
|
|
1420
|
+
{
|
|
1421
|
+
part: { inline_data: { mime_type: "image/png", data: "aGVsbG8=" } },
|
|
1422
|
+
expected: { type: "image", mimeType: "image/png", data: "aGVsbG8=" },
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
part: {
|
|
1426
|
+
inline_data: { mime_type: "application/pdf", data: "aGVsbG8=" },
|
|
1427
|
+
},
|
|
1428
|
+
expected: {
|
|
1429
|
+
type: "file",
|
|
1430
|
+
mimeType: "application/pdf",
|
|
1431
|
+
data: "aGVsbG8=",
|
|
1432
|
+
},
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
part: {
|
|
1436
|
+
file_data: {
|
|
1437
|
+
mime_type: "image/png",
|
|
1438
|
+
file_uri: "https://example.test/image.png",
|
|
1439
|
+
},
|
|
1440
|
+
},
|
|
1441
|
+
expected: { type: "image_url", url: "https://example.test/image.png" },
|
|
1442
|
+
},
|
|
1443
|
+
{
|
|
1444
|
+
part: {
|
|
1445
|
+
file_data: {
|
|
1446
|
+
mime_type: "application/pdf",
|
|
1447
|
+
file_uri: "https://example.test/report.pdf",
|
|
1448
|
+
},
|
|
1449
|
+
},
|
|
1450
|
+
expected: {
|
|
1451
|
+
type: "file_url",
|
|
1452
|
+
mimeType: "application/pdf",
|
|
1453
|
+
url: "https://example.test/report.pdf",
|
|
1454
|
+
},
|
|
1455
|
+
},
|
|
1456
|
+
{
|
|
1457
|
+
part: { inlineData: { mime_type: "image/png", data: "aGVsbG8=" } },
|
|
1458
|
+
expected: { type: "image", mimeType: "image/png", data: "aGVsbG8=" },
|
|
1459
|
+
},
|
|
1460
|
+
{
|
|
1461
|
+
part: {
|
|
1462
|
+
fileData: {
|
|
1463
|
+
mime_type: "application/pdf",
|
|
1464
|
+
file_uri: "https://example.test/report.pdf",
|
|
1465
|
+
},
|
|
1466
|
+
},
|
|
1467
|
+
expected: {
|
|
1468
|
+
type: "file_url",
|
|
1469
|
+
mimeType: "application/pdf",
|
|
1470
|
+
url: "https://example.test/report.pdf",
|
|
1471
|
+
},
|
|
1472
|
+
},
|
|
1473
|
+
{
|
|
1474
|
+
part: { inline_data: { mimeType: "image/png", data: "aGVsbG8=" } },
|
|
1475
|
+
expected: { type: "image", mimeType: "image/png", data: "aGVsbG8=" },
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
part: {
|
|
1479
|
+
file_data: {
|
|
1480
|
+
mimeType: "application/pdf",
|
|
1481
|
+
fileUri: "https://example.test/report.pdf",
|
|
1482
|
+
},
|
|
1483
|
+
},
|
|
1484
|
+
expected: {
|
|
1485
|
+
type: "file_url",
|
|
1486
|
+
mimeType: "application/pdf",
|
|
1487
|
+
url: "https://example.test/report.pdf",
|
|
1488
|
+
},
|
|
1489
|
+
},
|
|
1490
|
+
])(
|
|
1491
|
+
"normalizes media aliases without mutating the event: $part",
|
|
1492
|
+
({ part, expected }) => {
|
|
1493
|
+
const input = { id: "media", author, content: { parts: [part] } };
|
|
1494
|
+
const original = structuredClone(input);
|
|
1495
|
+
const event = parseAdkEventValue(input, "test");
|
|
1496
|
+
const messages = new AdkEventAccumulator().processEvent(event);
|
|
1497
|
+
expect(messages).toMatchObject([
|
|
1498
|
+
{ type: author === "user" ? "human" : "ai", content: [expected] },
|
|
1499
|
+
]);
|
|
1500
|
+
expect(input).toEqual(original);
|
|
1501
|
+
},
|
|
1502
|
+
);
|
|
1503
|
+
});
|
|
1504
|
+
|
|
1505
|
+
it("prefers camelCase media containers and nested values when both exist", () => {
|
|
1506
|
+
const event = parseAdkEventValue(
|
|
1507
|
+
{
|
|
1508
|
+
id: "media",
|
|
1509
|
+
author: "agent",
|
|
1510
|
+
content: {
|
|
1511
|
+
parts: [
|
|
1512
|
+
{
|
|
1513
|
+
inlineData: {
|
|
1514
|
+
mimeType: "image/png",
|
|
1515
|
+
mime_type: "application/pdf",
|
|
1516
|
+
data: "aGVsbG8=",
|
|
1517
|
+
},
|
|
1518
|
+
inline_data: { mime_type: "text/plain", data: "wrong" },
|
|
1519
|
+
},
|
|
1520
|
+
{
|
|
1521
|
+
fileData: {
|
|
1522
|
+
mimeType: "application/pdf",
|
|
1523
|
+
mime_type: "image/png",
|
|
1524
|
+
fileUri: "https://example.test/right.pdf",
|
|
1525
|
+
file_uri: "https://example.test/wrong.png",
|
|
1526
|
+
},
|
|
1527
|
+
file_data: { file_uri: "https://example.test/other.png" },
|
|
1528
|
+
},
|
|
1529
|
+
],
|
|
1530
|
+
},
|
|
1531
|
+
},
|
|
1532
|
+
"test",
|
|
1533
|
+
);
|
|
1534
|
+
expect(new AdkEventAccumulator().processEvent(event)).toMatchObject([
|
|
1535
|
+
{
|
|
1536
|
+
content: [
|
|
1537
|
+
{ type: "image", mimeType: "image/png", data: "aGVsbG8=" },
|
|
1538
|
+
{
|
|
1539
|
+
type: "file_url",
|
|
1540
|
+
mimeType: "application/pdf",
|
|
1541
|
+
url: "https://example.test/right.pdf",
|
|
1542
|
+
},
|
|
1543
|
+
],
|
|
1544
|
+
},
|
|
1545
|
+
]);
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
it("leaves similarly named tool arguments untouched", () => {
|
|
1549
|
+
const args = { inline_data: { mime_type: "custom", file_uri: "opaque" } };
|
|
1550
|
+
const event = parseAdkEventValue(
|
|
1551
|
+
{
|
|
1552
|
+
id: "call",
|
|
1553
|
+
author: "agent",
|
|
1554
|
+
content: {
|
|
1555
|
+
parts: [{ function_call: { name: "test", id: "tc-1", args } }],
|
|
1556
|
+
},
|
|
1557
|
+
},
|
|
1558
|
+
"test",
|
|
1559
|
+
);
|
|
1560
|
+
expect(new AdkEventAccumulator().processEvent(event)).toMatchObject([
|
|
1561
|
+
{ tool_calls: [{ args }] },
|
|
1562
|
+
]);
|
|
1563
|
+
});
|
|
1564
|
+
|
|
1047
1565
|
it("normalizes function_call to functionCall in parts", () => {
|
|
1048
1566
|
const acc = new AdkEventAccumulator();
|
|
1049
1567
|
const msgs = acc.processEvent(
|
|
@@ -1415,6 +1933,65 @@ describe("AdkEventAccumulator - user message handling", () => {
|
|
|
1415
1933
|
});
|
|
1416
1934
|
});
|
|
1417
1935
|
|
|
1936
|
+
it.each([
|
|
1937
|
+
{
|
|
1938
|
+
name: "inlineData without mimeType",
|
|
1939
|
+
part: { inlineData: { data: "abc123" } },
|
|
1940
|
+
},
|
|
1941
|
+
{
|
|
1942
|
+
name: "inlineData without data",
|
|
1943
|
+
part: { inlineData: { mimeType: "image/png" } },
|
|
1944
|
+
},
|
|
1945
|
+
{
|
|
1946
|
+
name: "fileData without fileUri",
|
|
1947
|
+
part: { fileData: { mimeType: "image/png" } },
|
|
1948
|
+
},
|
|
1949
|
+
])("skips malformed $name while preserving valid content", ({ part }) => {
|
|
1950
|
+
const acc = new AdkEventAccumulator();
|
|
1951
|
+
const msgs = acc.processEvent(
|
|
1952
|
+
makeEvent({
|
|
1953
|
+
author: "agent",
|
|
1954
|
+
content: {
|
|
1955
|
+
role: "model",
|
|
1956
|
+
parts: [part as any, { text: "still here" }],
|
|
1957
|
+
},
|
|
1958
|
+
}),
|
|
1959
|
+
);
|
|
1960
|
+
|
|
1961
|
+
expect(msgs).toMatchObject([
|
|
1962
|
+
{ type: "ai", content: [{ type: "text", text: "still here" }] },
|
|
1963
|
+
]);
|
|
1964
|
+
});
|
|
1965
|
+
|
|
1966
|
+
it("skips malformed user media without creating an empty human message", () => {
|
|
1967
|
+
const acc = new AdkEventAccumulator();
|
|
1968
|
+
const malformedMedia = [
|
|
1969
|
+
{ inlineData: { data: "abc123" } },
|
|
1970
|
+
{ fileData: { mimeType: "application/pdf" } },
|
|
1971
|
+
] as any[];
|
|
1972
|
+
|
|
1973
|
+
expect(
|
|
1974
|
+
acc.processEvent(
|
|
1975
|
+
makeEvent({
|
|
1976
|
+
author: "user",
|
|
1977
|
+
content: { role: "user", parts: malformedMedia },
|
|
1978
|
+
}),
|
|
1979
|
+
),
|
|
1980
|
+
).toEqual([]);
|
|
1981
|
+
|
|
1982
|
+
const msgs = acc.processEvent(
|
|
1983
|
+
makeEvent({
|
|
1984
|
+
author: "user",
|
|
1985
|
+
content: {
|
|
1986
|
+
role: "user",
|
|
1987
|
+
parts: [...malformedMedia, { text: "still here" }],
|
|
1988
|
+
},
|
|
1989
|
+
}),
|
|
1990
|
+
);
|
|
1991
|
+
|
|
1992
|
+
expect(msgs).toMatchObject([{ type: "human", content: "still here" }]);
|
|
1993
|
+
});
|
|
1994
|
+
|
|
1418
1995
|
it("tool result events (no author, role:'user') still create tool messages", () => {
|
|
1419
1996
|
// Regression: the user-author check must not hijack tool events.
|
|
1420
1997
|
// messageToEvent for `type:'tool'` emits events without `author`,
|