@assistant-ui/react-google-adk 0.0.18 → 0.0.20
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.js +15 -2
- package/dist/AdkClient.js.map +1 -1
- package/dist/adkExtras.d.ts +1 -1
- package/dist/adkExtras.d.ts.map +1 -1
- package/dist/adkExtras.js +1 -1
- package/dist/adkExtras.js.map +1 -1
- package/dist/convertAdkMessages.d.ts.map +1 -1
- package/dist/convertAdkMessages.js +19 -9
- package/dist/convertAdkMessages.js.map +1 -1
- package/dist/useAdkMessages.js +1 -1
- package/dist/useAdkMessages.js.map +1 -1
- package/dist/useAdkRuntime.d.ts.map +1 -1
- package/dist/useAdkRuntime.js +16 -6
- package/dist/useAdkRuntime.js.map +1 -1
- package/package.json +5 -5
- package/src/AdkClient.test.ts +55 -22
- package/src/AdkClient.ts +16 -2
- package/src/AdkEventAccumulator.test.ts +0 -36
- package/src/AdkSessionAdapter.test.ts +1 -11
- package/src/adkExtras.ts +1 -1
- package/src/convertAdkMessages.test.ts +79 -0
- package/src/convertAdkMessages.ts +23 -4
- package/src/useAdkMessages.ts +1 -1
- package/src/useAdkRuntime.test.ts +61 -0
- package/src/useAdkRuntime.ts +17 -11
- package/src/server/createAdkApiRoute.test.ts +0 -370
|
@@ -452,13 +452,6 @@ describe("AdkEventAccumulator - HITL requires-action", () => {
|
|
|
452
452
|
expect(aiMsg.status).toBeUndefined();
|
|
453
453
|
});
|
|
454
454
|
|
|
455
|
-
it("matches tool call id to the longRunningToolIds entry", () => {
|
|
456
|
-
const acc = new AdkEventAccumulator();
|
|
457
|
-
const msgs = acc.processEvent(makeHitlEvent("tc-1"));
|
|
458
|
-
const aiMsg = msgs[0] as AdkMessage & { type: "ai" };
|
|
459
|
-
expect(aiMsg.tool_calls![0]!.id).toBe("tc-1");
|
|
460
|
-
});
|
|
461
|
-
|
|
462
455
|
it("stays pending across a subsequent bookkeeping event", () => {
|
|
463
456
|
const acc = new AdkEventAccumulator();
|
|
464
457
|
acc.processEvent(makeHitlEvent("tc-1", { author: "WorkflowA" }));
|
|
@@ -480,35 +473,6 @@ describe("AdkEventAccumulator - HITL requires-action", () => {
|
|
|
480
473
|
expect(aiMsg!.status).toBeUndefined();
|
|
481
474
|
});
|
|
482
475
|
|
|
483
|
-
it("assigns manual complete status on non-HITL final event", () => {
|
|
484
|
-
const acc = new AdkEventAccumulator();
|
|
485
|
-
const msgs = acc.processEvent(
|
|
486
|
-
makeEvent({
|
|
487
|
-
author: "agent",
|
|
488
|
-
content: { role: "model", parts: [{ text: "Done." }] },
|
|
489
|
-
}),
|
|
490
|
-
);
|
|
491
|
-
|
|
492
|
-
expect(msgs[0]).toMatchObject({
|
|
493
|
-
status: { type: "complete", reason: "stop" },
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
it("assigns manual complete status on skipSummarization final event", () => {
|
|
498
|
-
const acc = new AdkEventAccumulator();
|
|
499
|
-
const msgs = acc.processEvent(
|
|
500
|
-
makeEvent({
|
|
501
|
-
author: "agent",
|
|
502
|
-
actions: { skipSummarization: true },
|
|
503
|
-
content: { role: "model", parts: [{ text: "skipped" }] },
|
|
504
|
-
}),
|
|
505
|
-
);
|
|
506
|
-
|
|
507
|
-
expect(msgs[0]).toMatchObject({
|
|
508
|
-
status: { type: "complete", reason: "stop" },
|
|
509
|
-
});
|
|
510
|
-
});
|
|
511
|
-
|
|
512
476
|
it("prioritizes skipSummarization over longRunningToolIds", () => {
|
|
513
477
|
const acc = new AdkEventAccumulator();
|
|
514
478
|
const msgs = acc.processEvent(
|
|
@@ -224,20 +224,10 @@ describe("createAdkSessionAdapter - delete", () => {
|
|
|
224
224
|
// ── adapter.rename / archive / unarchive ──
|
|
225
225
|
|
|
226
226
|
describe("createAdkSessionAdapter - no-op methods", () => {
|
|
227
|
-
it("rename
|
|
227
|
+
it("rename/archive/unarchive resolve without hitting the network", async () => {
|
|
228
228
|
const { adapter } = createAdkSessionAdapter(baseOptions);
|
|
229
229
|
await expect(adapter.rename("s1", "New Title")).resolves.toBeUndefined();
|
|
230
|
-
expect(mockFetch).not.toHaveBeenCalled();
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it("archive resolves without error", async () => {
|
|
234
|
-
const { adapter } = createAdkSessionAdapter(baseOptions);
|
|
235
230
|
await expect(adapter.archive("s1")).resolves.toBeUndefined();
|
|
236
|
-
expect(mockFetch).not.toHaveBeenCalled();
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it("unarchive resolves without error", async () => {
|
|
240
|
-
const { adapter } = createAdkSessionAdapter(baseOptions);
|
|
241
231
|
await expect(adapter.unarchive("s1")).resolves.toBeUndefined();
|
|
242
232
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
243
233
|
});
|
package/src/adkExtras.ts
CHANGED
|
@@ -25,6 +25,72 @@ describe("convertAdkMessage - human messages", () => {
|
|
|
25
25
|
content: [{ type: "text", text: "Hello" }],
|
|
26
26
|
});
|
|
27
27
|
});
|
|
28
|
+
|
|
29
|
+
it("restores an audio/mp3 file part as an audio message part", () => {
|
|
30
|
+
const msg: AdkMessage = {
|
|
31
|
+
id: "m1",
|
|
32
|
+
type: "human",
|
|
33
|
+
content: [{ type: "file", mimeType: "audio/mp3", data: "QUJD" }],
|
|
34
|
+
};
|
|
35
|
+
const result = convertAdkMessage(msg, {});
|
|
36
|
+
expect(result).toMatchObject({
|
|
37
|
+
role: "user",
|
|
38
|
+
content: [{ type: "audio", audio: { data: "QUJD", format: "mp3" } }],
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("restores an audio/wav file part as an audio message part", () => {
|
|
43
|
+
const msg: AdkMessage = {
|
|
44
|
+
id: "m1",
|
|
45
|
+
type: "human",
|
|
46
|
+
content: [{ type: "file", mimeType: "audio/wav", data: "QUJD" }],
|
|
47
|
+
};
|
|
48
|
+
const result = convertAdkMessage(msg, {});
|
|
49
|
+
expect(result).toMatchObject({
|
|
50
|
+
role: "user",
|
|
51
|
+
content: [{ type: "audio", audio: { data: "QUJD", format: "wav" } }],
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("keeps attachment-derived audio file parts (with filename) as file parts", () => {
|
|
56
|
+
const msg: AdkMessage = {
|
|
57
|
+
id: "m1",
|
|
58
|
+
type: "human",
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: "file",
|
|
62
|
+
mimeType: "audio/wav",
|
|
63
|
+
data: "QUJD",
|
|
64
|
+
filename: "memo.wav",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
const result = convertAdkMessage(msg, {});
|
|
69
|
+
expect(result).toMatchObject({
|
|
70
|
+
role: "user",
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "file",
|
|
74
|
+
mimeType: "audio/wav",
|
|
75
|
+
data: "QUJD",
|
|
76
|
+
filename: "memo.wav",
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("keeps file parts with other audio mime types as file parts", () => {
|
|
83
|
+
const msg: AdkMessage = {
|
|
84
|
+
id: "m1",
|
|
85
|
+
type: "human",
|
|
86
|
+
content: [{ type: "file", mimeType: "audio/ogg", data: "QUJD" }],
|
|
87
|
+
};
|
|
88
|
+
const result = convertAdkMessage(msg, {});
|
|
89
|
+
expect(result).toMatchObject({
|
|
90
|
+
role: "user",
|
|
91
|
+
content: [{ type: "file", mimeType: "audio/ogg", data: "QUJD" }],
|
|
92
|
+
});
|
|
93
|
+
});
|
|
28
94
|
});
|
|
29
95
|
|
|
30
96
|
describe("convertAdkMessage - ai messages", () => {
|
|
@@ -78,6 +144,19 @@ describe("convertAdkMessage - ai messages", () => {
|
|
|
78
144
|
});
|
|
79
145
|
});
|
|
80
146
|
|
|
147
|
+
it("keeps audio/mp3 file parts as file parts on assistant messages", () => {
|
|
148
|
+
const msg: AdkMessage = {
|
|
149
|
+
id: "m1",
|
|
150
|
+
type: "ai",
|
|
151
|
+
content: [{ type: "file", mimeType: "audio/mp3", data: "QUJD" }],
|
|
152
|
+
};
|
|
153
|
+
const result = convertAdkMessage(msg, {});
|
|
154
|
+
expect(result).toMatchObject({
|
|
155
|
+
role: "assistant",
|
|
156
|
+
content: [{ type: "file", mimeType: "audio/mp3", data: "QUJD" }],
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
81
160
|
it("converts file content parts to file message parts", () => {
|
|
82
161
|
const msg: AdkMessage = {
|
|
83
162
|
id: "m1",
|
|
@@ -9,9 +9,13 @@ type ContentPart =
|
|
|
9
9
|
| { type: "reasoning"; text: string }
|
|
10
10
|
| { type: "image"; image: string }
|
|
11
11
|
| { type: "file"; data: string; mimeType: string; filename?: string }
|
|
12
|
+
| { type: "audio"; audio: { data: string; format: "mp3" | "wav" } }
|
|
12
13
|
| { type: "data"; name: string; data: unknown };
|
|
13
14
|
|
|
14
|
-
const contentToParts = (
|
|
15
|
+
const contentToParts = (
|
|
16
|
+
content: AdkMessage["content"],
|
|
17
|
+
role: "user" | "assistant",
|
|
18
|
+
): ContentPart[] => {
|
|
15
19
|
if (typeof content === "string")
|
|
16
20
|
return [{ type: "text" as const, text: content }];
|
|
17
21
|
|
|
@@ -29,13 +33,25 @@ const contentToParts = (content: AdkMessage["content"]): ContentPart[] => {
|
|
|
29
33
|
};
|
|
30
34
|
case "image_url":
|
|
31
35
|
return { type: "image", image: part.url };
|
|
32
|
-
case "file":
|
|
36
|
+
case "file": {
|
|
37
|
+
const format =
|
|
38
|
+
role === "user" && part.filename == null
|
|
39
|
+
? part.mimeType === "audio/wav"
|
|
40
|
+
? ("wav" as const)
|
|
41
|
+
: part.mimeType === "audio/mp3"
|
|
42
|
+
? ("mp3" as const)
|
|
43
|
+
: null
|
|
44
|
+
: null;
|
|
45
|
+
if (format) {
|
|
46
|
+
return { type: "audio", audio: { data: part.data, format } };
|
|
47
|
+
}
|
|
33
48
|
return {
|
|
34
49
|
type: "file",
|
|
35
50
|
data: part.data,
|
|
36
51
|
mimeType: part.mimeType,
|
|
37
52
|
...(part.filename != null && { filename: part.filename }),
|
|
38
53
|
};
|
|
54
|
+
}
|
|
39
55
|
case "file_url":
|
|
40
56
|
return {
|
|
41
57
|
type: "data",
|
|
@@ -72,7 +88,7 @@ export const convertAdkMessage: useExternalMessageConverter.Callback<
|
|
|
72
88
|
return {
|
|
73
89
|
role: "user",
|
|
74
90
|
id: message.id,
|
|
75
|
-
content: contentToParts(message.content),
|
|
91
|
+
content: contentToParts(message.content, "user"),
|
|
76
92
|
};
|
|
77
93
|
|
|
78
94
|
case "ai": {
|
|
@@ -88,7 +104,10 @@ export const convertAdkMessage: useExternalMessageConverter.Callback<
|
|
|
88
104
|
return {
|
|
89
105
|
role: "assistant",
|
|
90
106
|
id: message.id,
|
|
91
|
-
content: [
|
|
107
|
+
content: [
|
|
108
|
+
...contentToParts(message.content, "assistant"),
|
|
109
|
+
...toolCallParts,
|
|
110
|
+
],
|
|
92
111
|
...(message.status && { status: message.status }),
|
|
93
112
|
...(message.author && {
|
|
94
113
|
metadata: {
|
package/src/useAdkMessages.ts
CHANGED
|
@@ -208,4 +208,65 @@ describe("getMessageContent", () => {
|
|
|
208
208
|
{ type: "file", mimeType: "application/pdf", data: "AAAA" },
|
|
209
209
|
]);
|
|
210
210
|
});
|
|
211
|
+
|
|
212
|
+
it("forwards an audio part as a file block with the format-derived mime type", () => {
|
|
213
|
+
const result = getMessageContent(
|
|
214
|
+
makeAppendMessage([
|
|
215
|
+
{ type: "audio", audio: { data: "QUJD", format: "mp3" } },
|
|
216
|
+
]),
|
|
217
|
+
);
|
|
218
|
+
expect(result).toEqual([
|
|
219
|
+
{ type: "file", mimeType: "audio/mp3", data: "QUJD" },
|
|
220
|
+
]);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("forwards a wav audio part with the audio/wav mime type", () => {
|
|
224
|
+
const result = getMessageContent(
|
|
225
|
+
makeAppendMessage([
|
|
226
|
+
{ type: "audio", audio: { data: "QUJD", format: "wav" } },
|
|
227
|
+
]),
|
|
228
|
+
);
|
|
229
|
+
expect(result).toEqual([
|
|
230
|
+
{ type: "file", mimeType: "audio/wav", data: "QUJD" },
|
|
231
|
+
]);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("strips a data URL envelope from audio data", () => {
|
|
235
|
+
const result = getMessageContent(
|
|
236
|
+
makeAppendMessage([
|
|
237
|
+
{
|
|
238
|
+
type: "audio",
|
|
239
|
+
audio: { data: "data:audio/mp3;base64,QUJD", format: "mp3" },
|
|
240
|
+
},
|
|
241
|
+
]),
|
|
242
|
+
);
|
|
243
|
+
expect(result).toEqual([
|
|
244
|
+
{ type: "file", mimeType: "audio/mp3", data: "QUJD" },
|
|
245
|
+
]);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("skips data parts while keeping surrounding text", () => {
|
|
249
|
+
const result = getMessageContent(
|
|
250
|
+
makeAppendMessage([
|
|
251
|
+
{ type: "text", text: "hi" },
|
|
252
|
+
{ type: "data", name: "chart", data: { x: 1 } },
|
|
253
|
+
]),
|
|
254
|
+
);
|
|
255
|
+
expect(result).toBe("hi");
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("returns empty content for a data-only message", () => {
|
|
259
|
+
const result = getMessageContent(
|
|
260
|
+
makeAppendMessage([{ type: "data", name: "chart", data: { x: 1 } }]),
|
|
261
|
+
);
|
|
262
|
+
expect(result).toEqual([]);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("still throws on assistant-only part types", () => {
|
|
266
|
+
expect(() =>
|
|
267
|
+
getMessageContent(
|
|
268
|
+
makeAppendMessage([{ type: "reasoning", text: "thinking" }]),
|
|
269
|
+
),
|
|
270
|
+
).toThrow("Unsupported append message part type: reasoning");
|
|
271
|
+
});
|
|
211
272
|
});
|
package/src/useAdkRuntime.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type ToolExecutionStatus,
|
|
14
14
|
generateId,
|
|
15
15
|
} from "@assistant-ui/core";
|
|
16
|
+
import { parseDataUrl } from "@assistant-ui/core/internal";
|
|
16
17
|
import {
|
|
17
18
|
useCloudThreadListAdapter,
|
|
18
19
|
useRemoteThreadListRuntime,
|
|
@@ -41,7 +42,7 @@ export const getMessageContent = (msg: AppendMessage) => {
|
|
|
41
42
|
...msg.content,
|
|
42
43
|
...(msg.attachments?.flatMap((a) => a.content) ?? []),
|
|
43
44
|
];
|
|
44
|
-
const content = allContent.
|
|
45
|
+
const content = allContent.flatMap((part) => {
|
|
45
46
|
const type = part.type;
|
|
46
47
|
switch (type) {
|
|
47
48
|
case "text":
|
|
@@ -55,17 +56,22 @@ export const getMessageContent = (msg: AppendMessage) => {
|
|
|
55
56
|
data: part.data,
|
|
56
57
|
...(part.filename != null && { filename: part.filename }),
|
|
57
58
|
};
|
|
59
|
+
case "audio": {
|
|
60
|
+
const parsed = parseDataUrl(part.audio.data);
|
|
61
|
+
return {
|
|
62
|
+
type: "file" as const,
|
|
63
|
+
mimeType: `audio/${part.audio.format}`,
|
|
64
|
+
data: parsed?.data ?? part.audio.data,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
case "data":
|
|
68
|
+
return [];
|
|
58
69
|
|
|
59
70
|
case "tool-call":
|
|
60
71
|
throw new Error("Tool call appends are not supported.");
|
|
61
72
|
|
|
62
73
|
default: {
|
|
63
|
-
const _exhaustiveCheck:
|
|
64
|
-
| "reasoning"
|
|
65
|
-
| "source"
|
|
66
|
-
| "audio"
|
|
67
|
-
| "data"
|
|
68
|
-
| "generative-ui" = type;
|
|
74
|
+
const _exhaustiveCheck: "reasoning" | "source" | "generative-ui" = type;
|
|
69
75
|
throw new Error(
|
|
70
76
|
`Unsupported append message part type: ${_exhaustiveCheck}`,
|
|
71
77
|
);
|
|
@@ -356,7 +362,7 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
|
|
|
356
362
|
setMessages(nextMessages);
|
|
357
363
|
return;
|
|
358
364
|
}
|
|
359
|
-
const externalId = aui.threadListItem
|
|
365
|
+
const externalId = aui.threadListItem.getState().externalId;
|
|
360
366
|
const checkpointId = externalId
|
|
361
367
|
? await getCheckpointId(externalId, truncated)
|
|
362
368
|
: null;
|
|
@@ -397,7 +403,7 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
|
|
|
397
403
|
parentId,
|
|
398
404
|
);
|
|
399
405
|
replaceMessages(truncated);
|
|
400
|
-
const externalId = aui.threadListItem
|
|
406
|
+
const externalId = aui.threadListItem.getState().externalId;
|
|
401
407
|
const checkpointId = externalId
|
|
402
408
|
? await getCheckpointId(externalId, truncated)
|
|
403
409
|
: null;
|
|
@@ -447,7 +453,7 @@ const useAdkRuntimeImpl = (options: UseAdkRuntimeOptions) => {
|
|
|
447
453
|
const loadFn = loadRef.current;
|
|
448
454
|
if (!loadFn) return;
|
|
449
455
|
|
|
450
|
-
const externalId = aui.threadListItem
|
|
456
|
+
const externalId = aui.threadListItem.getState().externalId;
|
|
451
457
|
if (externalId == null) return;
|
|
452
458
|
|
|
453
459
|
loadFn(externalId).then(
|
|
@@ -477,7 +483,7 @@ export const useAdkRuntime = ({
|
|
|
477
483
|
cloud,
|
|
478
484
|
create: async () => {
|
|
479
485
|
if (create) return create();
|
|
480
|
-
if (aui.threadListItem.source) return aui.threadListItem
|
|
486
|
+
if (aui.threadListItem.source) return aui.threadListItem.initialize();
|
|
481
487
|
return { externalId: undefined };
|
|
482
488
|
},
|
|
483
489
|
delete: deleteFn,
|