@assistant-ui/react-ag-ui 0.0.53 → 0.0.56
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/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts +23 -2
- package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtime/AgUiThreadRuntimeCore.js +183 -39
- package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
- package/dist/runtime/adapter/conversions.d.ts +19 -3
- package/dist/runtime/adapter/conversions.d.ts.map +1 -1
- package/dist/runtime/adapter/conversions.js +197 -36
- package/dist/runtime/adapter/conversions.js.map +1 -1
- package/dist/runtime/adapter/run-aggregator.d.ts +15 -2
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +62 -10
- package/dist/runtime/adapter/run-aggregator.js.map +1 -1
- package/dist/runtime/adapter/subscriber.d.ts +6 -0
- package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
- package/dist/runtime/adapter/subscriber.js +10 -0
- package/dist/runtime/adapter/subscriber.js.map +1 -1
- package/dist/runtime/adapter/tool-approval.d.ts +56 -0
- package/dist/runtime/adapter/tool-approval.d.ts.map +1 -0
- package/dist/runtime/adapter/tool-approval.js +244 -0
- package/dist/runtime/adapter/tool-approval.js.map +1 -0
- package/dist/runtime/event-parser.d.ts.map +1 -1
- package/dist/runtime/event-parser.js +15 -1
- package/dist/runtime/event-parser.js.map +1 -1
- package/dist/runtime/interrupt-internals.d.ts +8 -0
- package/dist/runtime/interrupt-internals.d.ts.map +1 -0
- package/dist/runtime/interrupt-internals.js +25 -0
- package/dist/runtime/interrupt-internals.js.map +1 -0
- package/dist/runtime/types.d.ts +5 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/useAgUiRuntime.js +4 -1
- package/dist/useAgUiRuntime.js.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +8 -2
- package/src/runtime/AgUiThreadRuntimeCore.ts +335 -42
- package/src/runtime/adapter/conversions.content.test.ts +278 -0
- package/src/runtime/adapter/conversions.ts +298 -50
- package/src/runtime/adapter/run-aggregator.approval.test.ts +103 -0
- package/src/runtime/adapter/run-aggregator.ts +96 -5
- package/src/runtime/adapter/subscriber.ts +17 -0
- package/src/runtime/adapter/tool-approval.test.ts +681 -0
- package/src/runtime/adapter/tool-approval.ts +366 -0
- package/src/runtime/event-parser.ts +23 -1
- package/src/runtime/interrupt-internals.ts +35 -0
- package/src/runtime/types.ts +6 -0
- package/src/useAgUiRuntime.approval.test.tsx +885 -0
- package/src/useAgUiRuntime.queue.test.tsx +68 -1
- package/src/useAgUiRuntime.ts +5 -1
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { describe, expect, expectTypeOf, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
MessageSchema,
|
|
4
|
+
UserMessageSchema,
|
|
5
|
+
type Message as AgUiWireMessage,
|
|
6
|
+
} from "@ag-ui/client";
|
|
7
|
+
import type { AppendMessage } from "@assistant-ui/core";
|
|
8
|
+
import {
|
|
9
|
+
fromAgUiMessages,
|
|
10
|
+
toAgUiMessages,
|
|
11
|
+
type AgUiMessage,
|
|
12
|
+
} from "./conversions";
|
|
13
|
+
|
|
14
|
+
type Message = Parameters<typeof toAgUiMessages>[0][number];
|
|
15
|
+
|
|
16
|
+
const userMessage = (
|
|
17
|
+
content: unknown,
|
|
18
|
+
attachments?: Message["attachments"],
|
|
19
|
+
): Message =>
|
|
20
|
+
({
|
|
21
|
+
id: "u1",
|
|
22
|
+
role: "user",
|
|
23
|
+
content,
|
|
24
|
+
...(attachments && { attachments }),
|
|
25
|
+
}) as Message;
|
|
26
|
+
|
|
27
|
+
const contentOf = (message: Message) => {
|
|
28
|
+
const [converted] = toAgUiMessages([message]);
|
|
29
|
+
return (converted as { content: unknown }).content;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const appendMessage = (): AppendMessage => ({
|
|
33
|
+
role: "user",
|
|
34
|
+
content: [{ type: "text", text: "Hi" }],
|
|
35
|
+
attachments: [],
|
|
36
|
+
createdAt: new Date(),
|
|
37
|
+
parentId: null,
|
|
38
|
+
sourceId: null,
|
|
39
|
+
runConfig: undefined,
|
|
40
|
+
metadata: { custom: {} },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("toAgUiMessages content metadata", () => {
|
|
44
|
+
it("emits AgUiMessage values assignable to AG-UI Message", () => {
|
|
45
|
+
expectTypeOf<AgUiMessage>().toExtend<AgUiWireMessage>();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("emits records MessageSchema accepts", () => {
|
|
49
|
+
const converted = toAgUiMessages([
|
|
50
|
+
{ id: "u-1", role: "user", content: "Hi" },
|
|
51
|
+
{ id: "a-1", role: "assistant", content: "Hello" },
|
|
52
|
+
{ id: "s-1", role: "system", content: "Be brief" },
|
|
53
|
+
{ id: "t-1", role: "tool", content: "ok", toolCallId: "c-1" },
|
|
54
|
+
{ id: "r-1", role: "reasoning", content: "hmm" },
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
expect(converted.map((message) => MessageSchema.parse(message))).toEqual(
|
|
58
|
+
converted,
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("normalizes an AppendMessage without an id", () => {
|
|
63
|
+
const converted = toAgUiMessages([appendMessage()])[0];
|
|
64
|
+
if (!converted) throw new Error("expected a converted message");
|
|
65
|
+
|
|
66
|
+
expect(UserMessageSchema.parse(converted)).toMatchObject({
|
|
67
|
+
role: "user",
|
|
68
|
+
content: "Hi",
|
|
69
|
+
});
|
|
70
|
+
expect(converted.id).toEqual(expect.any(String));
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("preserves a caller-supplied id", () => {
|
|
74
|
+
const converted = toAgUiMessages([
|
|
75
|
+
{ ...appendMessage(), id: "stable-user-1" },
|
|
76
|
+
])[0];
|
|
77
|
+
if (!converted) throw new Error("expected a converted message");
|
|
78
|
+
|
|
79
|
+
expect(UserMessageSchema.parse(converted).id).toBe("stable-user-1");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("carries a part's agui provider metadata onto an image item", () => {
|
|
83
|
+
expect(
|
|
84
|
+
contentOf(
|
|
85
|
+
userMessage([
|
|
86
|
+
{
|
|
87
|
+
type: "image",
|
|
88
|
+
image: "https://example.com/cat.png",
|
|
89
|
+
providerMetadata: { agui: { file_id: "f_1" } },
|
|
90
|
+
},
|
|
91
|
+
]),
|
|
92
|
+
),
|
|
93
|
+
).toEqual([
|
|
94
|
+
{
|
|
95
|
+
type: "image",
|
|
96
|
+
source: { type: "url", value: "https://example.com/cat.png" },
|
|
97
|
+
metadata: { file_id: "f_1" },
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("preserves a direct image part's filename through a snapshot round trip", () => {
|
|
103
|
+
const sent = toAgUiMessages([
|
|
104
|
+
userMessage([
|
|
105
|
+
{
|
|
106
|
+
type: "image",
|
|
107
|
+
image: "https://example.com/cat.png",
|
|
108
|
+
filename: "cat.png",
|
|
109
|
+
},
|
|
110
|
+
]),
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
expect((sent[0] as { content: unknown }).content).toEqual([
|
|
114
|
+
{
|
|
115
|
+
type: "image",
|
|
116
|
+
source: { type: "url", value: "https://example.com/cat.png" },
|
|
117
|
+
metadata: { filename: "cat.png" },
|
|
118
|
+
},
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
const rebuilt = fromAgUiMessages(sent as never);
|
|
122
|
+
const attachment = (rebuilt[0] as unknown as { attachments: unknown[] })
|
|
123
|
+
.attachments[0];
|
|
124
|
+
|
|
125
|
+
expect(attachment).toMatchObject({
|
|
126
|
+
name: "cat.png",
|
|
127
|
+
content: [{ type: "image", filename: "cat.png" }],
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("merges a file part's metadata with its filename", () => {
|
|
132
|
+
expect(
|
|
133
|
+
contentOf(
|
|
134
|
+
userMessage([
|
|
135
|
+
{
|
|
136
|
+
type: "file",
|
|
137
|
+
data: "https://example.com/spec.pdf",
|
|
138
|
+
mimeType: "application/pdf",
|
|
139
|
+
filename: "spec.pdf",
|
|
140
|
+
providerMetadata: { agui: { file_id: "f_2" } },
|
|
141
|
+
},
|
|
142
|
+
]),
|
|
143
|
+
),
|
|
144
|
+
).toEqual([
|
|
145
|
+
{
|
|
146
|
+
type: "document",
|
|
147
|
+
source: {
|
|
148
|
+
type: "url",
|
|
149
|
+
value: "https://example.com/spec.pdf",
|
|
150
|
+
mimeType: "application/pdf",
|
|
151
|
+
},
|
|
152
|
+
metadata: { file_id: "f_2", filename: "spec.pdf" },
|
|
153
|
+
},
|
|
154
|
+
]);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("keeps the part's own filename over one in the metadata", () => {
|
|
158
|
+
expect(
|
|
159
|
+
contentOf(
|
|
160
|
+
userMessage([
|
|
161
|
+
{
|
|
162
|
+
type: "file",
|
|
163
|
+
data: "https://example.com/spec.pdf",
|
|
164
|
+
mimeType: "application/pdf",
|
|
165
|
+
filename: "spec.pdf",
|
|
166
|
+
providerMetadata: { agui: { filename: "renamed.pdf" } },
|
|
167
|
+
},
|
|
168
|
+
]),
|
|
169
|
+
),
|
|
170
|
+
).toMatchObject([{ metadata: { filename: "spec.pdf" } }]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("carries metadata from an attachment's content part", () => {
|
|
174
|
+
expect(
|
|
175
|
+
contentOf(
|
|
176
|
+
userMessage([], [
|
|
177
|
+
{
|
|
178
|
+
name: "spec.pdf",
|
|
179
|
+
contentType: "application/pdf",
|
|
180
|
+
content: [
|
|
181
|
+
{
|
|
182
|
+
type: "file",
|
|
183
|
+
data: "https://example.com/spec.pdf",
|
|
184
|
+
mimeType: "application/pdf",
|
|
185
|
+
providerMetadata: { agui: { file_id: "f_3" } },
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
] as Message["attachments"]),
|
|
190
|
+
),
|
|
191
|
+
).toMatchObject([{ type: "document", metadata: { file_id: "f_3" } }]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("ignores metadata namespaced for another provider", () => {
|
|
195
|
+
expect(
|
|
196
|
+
contentOf(
|
|
197
|
+
userMessage([
|
|
198
|
+
{
|
|
199
|
+
type: "image",
|
|
200
|
+
image: "https://example.com/cat.png",
|
|
201
|
+
providerMetadata: { openai: { file_id: "f_4" } },
|
|
202
|
+
},
|
|
203
|
+
]),
|
|
204
|
+
),
|
|
205
|
+
).toEqual([
|
|
206
|
+
{
|
|
207
|
+
type: "image",
|
|
208
|
+
source: { type: "url", value: "https://example.com/cat.png" },
|
|
209
|
+
},
|
|
210
|
+
]);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("emits no metadata channel for text, which the schema has no field for", () => {
|
|
214
|
+
expect(
|
|
215
|
+
contentOf(
|
|
216
|
+
userMessage([
|
|
217
|
+
{
|
|
218
|
+
type: "text",
|
|
219
|
+
text: "hello",
|
|
220
|
+
providerMetadata: { agui: { file_id: "f_5" } },
|
|
221
|
+
},
|
|
222
|
+
{ type: "image", image: "https://example.com/cat.png" },
|
|
223
|
+
]),
|
|
224
|
+
),
|
|
225
|
+
).toEqual([
|
|
226
|
+
{ type: "text", text: "hello" },
|
|
227
|
+
{
|
|
228
|
+
type: "image",
|
|
229
|
+
source: { type: "url", value: "https://example.com/cat.png" },
|
|
230
|
+
},
|
|
231
|
+
]);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("stays valid against the upstream user message schema", () => {
|
|
235
|
+
const [converted] = toAgUiMessages([
|
|
236
|
+
userMessage([
|
|
237
|
+
{
|
|
238
|
+
type: "file",
|
|
239
|
+
data: "https://example.com/spec.pdf",
|
|
240
|
+
mimeType: "application/pdf",
|
|
241
|
+
filename: "spec.pdf",
|
|
242
|
+
providerMetadata: { agui: { file_id: "f_6", nested: { a: 1 } } },
|
|
243
|
+
},
|
|
244
|
+
]),
|
|
245
|
+
]);
|
|
246
|
+
|
|
247
|
+
expect(
|
|
248
|
+
UserMessageSchema.safeParse(JSON.parse(JSON.stringify(converted)))
|
|
249
|
+
.success,
|
|
250
|
+
).toBe(true);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("survives a snapshot round trip", () => {
|
|
254
|
+
const sent = toAgUiMessages([
|
|
255
|
+
userMessage([
|
|
256
|
+
{
|
|
257
|
+
type: "file",
|
|
258
|
+
data: "https://example.com/spec.pdf",
|
|
259
|
+
mimeType: "application/pdf",
|
|
260
|
+
filename: "spec.pdf",
|
|
261
|
+
providerMetadata: { agui: { file_id: "f_7" } },
|
|
262
|
+
},
|
|
263
|
+
]),
|
|
264
|
+
]);
|
|
265
|
+
|
|
266
|
+
const rebuilt = fromAgUiMessages(sent as never);
|
|
267
|
+
const attachment = (rebuilt[0] as unknown as { attachments: unknown[] })
|
|
268
|
+
.attachments[0] as { content: unknown[] };
|
|
269
|
+
|
|
270
|
+
expect(attachment.content[0]).toMatchObject({
|
|
271
|
+
type: "file",
|
|
272
|
+
filename: "spec.pdf",
|
|
273
|
+
providerMetadata: { agui: { file_id: "f_7" } },
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
expect(toAgUiMessages(rebuilt as never)).toEqual(sent);
|
|
277
|
+
});
|
|
278
|
+
});
|