@assistant-ui/ai-sdk 0.0.9 → 0.0.11

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.
Files changed (35) hide show
  1. package/dist/converters/convertMessage.d.ts.map +1 -1
  2. package/dist/converters/convertMessage.js +59 -6
  3. package/dist/converters/convertMessage.js.map +1 -1
  4. package/dist/converters/toCreateMessage.d.ts.map +1 -1
  5. package/dist/converters/toCreateMessage.js +2 -1
  6. package/dist/converters/toCreateMessage.js.map +1 -1
  7. package/dist/converters/toolApprovalAnswers.d.ts +3 -0
  8. package/dist/converters/toolApprovalAnswers.d.ts.map +1 -0
  9. package/dist/converters/toolApprovalAnswers.js +17 -0
  10. package/dist/converters/toolApprovalAnswers.js.map +1 -0
  11. package/dist/runtime/AISDKChat.js.map +1 -1
  12. package/dist/runtime/AISDKThreads.js.map +1 -1
  13. package/dist/runtime/sdkIdentity.js +1 -1
  14. package/dist/runtime/useAISDKRuntime.js.map +1 -1
  15. package/dist/runtime/useChatRuntime.js.map +1 -1
  16. package/dist/runtime/useExternalHistory.d.ts.map +1 -1
  17. package/dist/runtime/useExternalHistory.js +4 -0
  18. package/dist/runtime/useExternalHistory.js.map +1 -1
  19. package/dist/runtime/useResourceCleanup.js.map +1 -1
  20. package/dist/runtime/useStreamingTiming.js.map +1 -1
  21. package/dist/tools/generativeTools.d.ts.map +1 -1
  22. package/dist/tools/generativeTools.js +12 -1
  23. package/dist/tools/generativeTools.js.map +1 -1
  24. package/dist/transport/resumable.js.map +1 -1
  25. package/dist/usage.js.map +1 -1
  26. package/package.json +12 -11
  27. package/src/converters/convertMessage.test.ts +266 -0
  28. package/src/converters/convertMessage.ts +97 -4
  29. package/src/converters/toCreateMessage.test.ts +13 -0
  30. package/src/converters/toCreateMessage.ts +1 -0
  31. package/src/converters/toolApprovalAnswers.ts +27 -0
  32. package/src/runtime/useExternalHistory.test.ts +94 -0
  33. package/src/runtime/useExternalHistory.ts +4 -0
  34. package/src/tools/generativeTools.test.ts +189 -1
  35. package/src/tools/generativeTools.ts +23 -2
@@ -1,5 +1,9 @@
1
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
- import { defineMcpToolkit } from "@assistant-ui/core/react";
2
+ import {
3
+ defineMcpToolkit,
4
+ defineToolkit,
5
+ type ToolkitDefinition,
6
+ } from "@assistant-ui/core/react";
3
7
  import { AISDKToolkit } from "./generativeTools";
4
8
  import { wrapModelContentEnvelope } from "../converters/modelContentEnvelope";
5
9
 
@@ -130,6 +134,190 @@ describe("AISDKToolkit.tools()", () => {
130
134
  supportsDeferredResults: false,
131
135
  });
132
136
  });
137
+
138
+ it("converts unchanged provider and backend schemas once", async () => {
139
+ const toBackendJSONSchema = vi.fn(() => ({
140
+ type: "object" as const,
141
+ properties: {},
142
+ }));
143
+ const toProviderJSONSchema = vi.fn(() => ({
144
+ type: "object" as const,
145
+ properties: {},
146
+ }));
147
+ const toolkitDefinition = {
148
+ serverTool: {
149
+ type: "backend",
150
+ parameters: { toJSONSchema: toBackendJSONSchema },
151
+ execute: async () => "ok",
152
+ } as never,
153
+ providerTool: {
154
+ type: "provider",
155
+ providerId: "provider.tool",
156
+ args: {},
157
+ parameters: { toJSONSchema: toProviderJSONSchema },
158
+ } as never,
159
+ };
160
+ const firstToolkit = new AISDKToolkit({ toolkit: toolkitDefinition });
161
+ const secondToolkit = new AISDKToolkit({ toolkit: toolkitDefinition });
162
+
163
+ const first = await firstToolkit.tools();
164
+ const second = await secondToolkit.tools();
165
+
166
+ expect(toBackendJSONSchema).toHaveBeenCalledTimes(1);
167
+ expect(toProviderJSONSchema).toHaveBeenCalledTimes(1);
168
+ expect(second.serverTool).not.toBe(first.serverTool);
169
+ expect(second.providerTool).not.toBe(first.providerTool);
170
+ expect(second.serverTool!.inputSchema).not.toBe(
171
+ first.serverTool!.inputSchema,
172
+ );
173
+ expect(
174
+ (second.serverTool!.inputSchema as { jsonSchema: unknown }).jsonSchema,
175
+ ).toBe(
176
+ (first.serverTool!.inputSchema as { jsonSchema: unknown }).jsonSchema,
177
+ );
178
+ });
179
+
180
+ it("converts the current frontend tools on every call", async () => {
181
+ const toolkit = new AISDKToolkit({ toolkit: {} });
182
+
183
+ const first = await toolkit.tools({
184
+ frontend: {
185
+ firstClientTool: {
186
+ parameters: { type: "object", properties: {} },
187
+ },
188
+ },
189
+ });
190
+ const second = await toolkit.tools({
191
+ frontend: {
192
+ secondClientTool: {
193
+ parameters: { type: "object", properties: {} },
194
+ },
195
+ },
196
+ });
197
+
198
+ expect(first).toHaveProperty("firstClientTool");
199
+ expect(first).not.toHaveProperty("secondClientTool");
200
+ expect(second).toHaveProperty("secondClientTool");
201
+ expect(second).not.toHaveProperty("firstClientTool");
202
+ });
203
+
204
+ it("does not convert disabled static toolkit entries", async () => {
205
+ const toJSONSchema = vi.fn(() => ({
206
+ type: "object" as const,
207
+ properties: {},
208
+ }));
209
+ const toolkit = new AISDKToolkit({
210
+ toolkit: {
211
+ disabledServerTool: {
212
+ type: "backend",
213
+ disabled: true,
214
+ parameters: { toJSONSchema },
215
+ } as never,
216
+ disabledProviderTool: {
217
+ type: "provider",
218
+ disabled: true,
219
+ providerId: "provider.tool",
220
+ args: {},
221
+ parameters: { toJSONSchema },
222
+ } as never,
223
+ },
224
+ });
225
+
226
+ await expect(toolkit.tools()).resolves.toEqual({});
227
+ expect(toJSONSchema).not.toHaveBeenCalled();
228
+ });
229
+
230
+ it("reflects replaced toolkit entries on the next call", async () => {
231
+ const definition: ToolkitDefinition = {
232
+ serverTool: {
233
+ description: "Original tool",
234
+ parameters: { type: "object" as const, properties: {} },
235
+ execute: async () => "original",
236
+ },
237
+ };
238
+ const toolkit = new AISDKToolkit({ toolkit: defineToolkit(definition) });
239
+
240
+ const first = await toolkit.tools();
241
+ definition.serverTool = {
242
+ description: "Replacement tool",
243
+ parameters: { type: "object" as const, properties: {} },
244
+ execute: async () => "replacement",
245
+ };
246
+
247
+ const refreshed = await toolkit.tools();
248
+
249
+ expect(first.serverTool?.description).toBe("Original tool");
250
+ expect(refreshed.serverTool?.description).toBe("Replacement tool");
251
+ });
252
+
253
+ it("reconverts a replaced parameters object", async () => {
254
+ const firstConversion = vi.fn(() => ({
255
+ type: "object" as const,
256
+ properties: { first: { type: "string" } },
257
+ }));
258
+ const secondConversion = vi.fn(() => ({
259
+ type: "object" as const,
260
+ properties: { second: { type: "string" } },
261
+ }));
262
+ const definition: ToolkitDefinition = {
263
+ serverTool: {
264
+ parameters: { toJSONSchema: firstConversion },
265
+ execute: async () => "ok",
266
+ } as never,
267
+ };
268
+ const toolkit = new AISDKToolkit({ toolkit: defineToolkit(definition) });
269
+
270
+ await toolkit.tools();
271
+ definition.serverTool!.parameters = {
272
+ toJSONSchema: secondConversion,
273
+ } as never;
274
+ await toolkit.tools();
275
+
276
+ expect(firstConversion).toHaveBeenCalledOnce();
277
+ expect(secondConversion).toHaveBeenCalledOnce();
278
+ });
279
+
280
+ it("reflects in-place toolkit entry changes on the next call", async () => {
281
+ const definition: ToolkitDefinition = {
282
+ serverTool: {
283
+ description: "Original tool",
284
+ parameters: { type: "object" as const, properties: {} },
285
+ execute: async () => "ok",
286
+ },
287
+ };
288
+ const toolkit = new AISDKToolkit({ toolkit: defineToolkit(definition) });
289
+
290
+ expect((await toolkit.tools()).serverTool?.description).toBe(
291
+ "Original tool",
292
+ );
293
+
294
+ definition.serverTool!.description = "Updated tool";
295
+ expect((await toolkit.tools()).serverTool?.description).toBe(
296
+ "Updated tool",
297
+ );
298
+
299
+ definition.serverTool!.disabled = true;
300
+ expect(await toolkit.tools()).not.toHaveProperty("serverTool");
301
+ });
302
+
303
+ it("does not share mutable tool entries between calls", async () => {
304
+ const toolkit = new AISDKToolkit({
305
+ toolkit: defineToolkit({
306
+ serverTool: {
307
+ description: "Original tool",
308
+ parameters: { type: "object" as const, properties: {} },
309
+ execute: async () => "ok",
310
+ },
311
+ }),
312
+ });
313
+
314
+ const first = await toolkit.tools();
315
+ first.serverTool!.description = "Changed returned tool";
316
+
317
+ const second = await toolkit.tools();
318
+
319
+ expect(second.serverTool?.description).toBe("Original tool");
320
+ });
133
321
  });
134
322
 
135
323
  describe("AISDKToolkit", () => {
@@ -76,8 +76,29 @@ const withMcpConnectionTimeout = async <T>(
76
76
  }
77
77
  };
78
78
 
79
- const parametersToInputSchema = (parameters: Tool["parameters"] | undefined) =>
80
- jsonSchema(parameters ? toJSONSchema(parameters) : EMPTY_SCHEMA);
79
+ // Converted schemas are shared by parameter identity and treated as immutable.
80
+ const convertedParameterSchemas = new WeakMap<
81
+ object,
82
+ ReturnType<typeof toJSONSchema>
83
+ >();
84
+
85
+ const getOrConvertParameterSchema = (
86
+ parameters: NonNullable<Tool["parameters"]>,
87
+ ) => {
88
+ const cached = convertedParameterSchemas.get(parameters);
89
+ if (cached) return cached;
90
+
91
+ const converted = toJSONSchema(parameters);
92
+ convertedParameterSchemas.set(parameters, converted);
93
+ return converted;
94
+ };
95
+
96
+ const parametersToInputSchema = (
97
+ parameters: Tool["parameters"] | undefined,
98
+ ) => {
99
+ if (!parameters) return jsonSchema(EMPTY_SCHEMA);
100
+ return jsonSchema(getOrConvertParameterSchema(parameters));
101
+ };
81
102
 
82
103
  /**
83
104
  * @deprecated Options for the deprecated {@link generativeTools}. Use