@assistant-ui/x-generative-compiler 0.0.2 → 0.0.4
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/README.md +7 -5
- package/dist/compile.d.ts +1 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +542 -51
- package/dist/compile.js.map +1 -1
- package/dist/package.js +6 -0
- package/dist/package.js.map +1 -0
- package/package.json +10 -8
- package/src/compile.test.ts +841 -6
- package/src/compile.ts +957 -77
package/src/compile.test.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import * as nodePath from "node:path";
|
|
1
4
|
import { describe, it, expect } from "vitest";
|
|
2
5
|
import {
|
|
3
6
|
compileGenerative,
|
|
@@ -5,6 +8,115 @@ import {
|
|
|
5
8
|
GenerativeCompileError,
|
|
6
9
|
} from "./compile";
|
|
7
10
|
|
|
11
|
+
const minimalSource = `"use generative";
|
|
12
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
13
|
+
export default defineToolkit({
|
|
14
|
+
weather: {
|
|
15
|
+
execute: async () => 1,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
function createPackage(
|
|
21
|
+
root: string,
|
|
22
|
+
name: string,
|
|
23
|
+
packageJson: Record<string, unknown> = {},
|
|
24
|
+
): string {
|
|
25
|
+
const packageRoot = nodePath.join(root, "node_modules", ...name.split("/"));
|
|
26
|
+
const distRoot = nodePath.join(packageRoot, "dist");
|
|
27
|
+
mkdirSync(distRoot, { recursive: true });
|
|
28
|
+
writeFileSync(nodePath.join(distRoot, "index.js"), "export {};\n");
|
|
29
|
+
writeFileSync(
|
|
30
|
+
nodePath.join(packageRoot, "package.json"),
|
|
31
|
+
JSON.stringify(
|
|
32
|
+
{
|
|
33
|
+
name,
|
|
34
|
+
version: "0.0.0",
|
|
35
|
+
type: "module",
|
|
36
|
+
exports: {
|
|
37
|
+
".": "./dist/index.js",
|
|
38
|
+
},
|
|
39
|
+
...packageJson,
|
|
40
|
+
},
|
|
41
|
+
null,
|
|
42
|
+
2,
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
return packageRoot;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createCompatibilityFixture(range: string | null): string {
|
|
49
|
+
const appRoot = mkdtempSync(
|
|
50
|
+
nodePath.join(tmpdir(), "aui-generative-compiler-"),
|
|
51
|
+
);
|
|
52
|
+
const srcRoot = nodePath.join(appRoot, "src");
|
|
53
|
+
mkdirSync(srcRoot, { recursive: true });
|
|
54
|
+
const filename = nodePath.join(srcRoot, "toolkit.tsx");
|
|
55
|
+
writeFileSync(filename, minimalSource);
|
|
56
|
+
|
|
57
|
+
const reactRoot = createPackage(appRoot, "@assistant-ui/react", {
|
|
58
|
+
dependencies: {
|
|
59
|
+
"@assistant-ui/core": "0.0.0",
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
createPackage(reactRoot, "@assistant-ui/core", {
|
|
63
|
+
version: "0.2.10",
|
|
64
|
+
...(range
|
|
65
|
+
? {
|
|
66
|
+
optionalDevDependencies: {
|
|
67
|
+
"@assistant-ui/x-generative-compiler": range,
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
: {}),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return filename;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Writes a child module to disk next to a (not-yet-written) `toolkit.tsx` and
|
|
78
|
+
* returns the parent's filename, so a parent compiled from a string can resolve
|
|
79
|
+
* `./tools/child` relative to it. Used to exercise cross-file toolkit spreads.
|
|
80
|
+
*/
|
|
81
|
+
function createMergeFixture(childSource: string): string {
|
|
82
|
+
const appRoot = mkdtempSync(nodePath.join(tmpdir(), "aui-generative-merge-"));
|
|
83
|
+
const toolsRoot = nodePath.join(appRoot, "src", "tools");
|
|
84
|
+
mkdirSync(toolsRoot, { recursive: true });
|
|
85
|
+
writeFileSync(nodePath.join(toolsRoot, "weather.tsx"), childSource);
|
|
86
|
+
return nodePath.join(appRoot, "src", "toolkit.tsx");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Like {@link createMergeFixture}, but also writes a (JSONC) `tsconfig.json`
|
|
91
|
+
* with a `@/*` path alias so alias resolution can be exercised. The child lives
|
|
92
|
+
* at `src/tools/weather.tsx` and `@/*` maps to `./src/*`.
|
|
93
|
+
*/
|
|
94
|
+
function createAliasMergeFixture(childSource: string): string {
|
|
95
|
+
const filename = createMergeFixture(childSource);
|
|
96
|
+
const appRoot = nodePath.dirname(nodePath.dirname(filename));
|
|
97
|
+
writeFileSync(
|
|
98
|
+
nodePath.join(appRoot, "tsconfig.json"),
|
|
99
|
+
`{
|
|
100
|
+
// path aliases
|
|
101
|
+
"compilerOptions": {
|
|
102
|
+
"baseUrl": ".",
|
|
103
|
+
"paths": { "@/*": ["./src/*"] },
|
|
104
|
+
}
|
|
105
|
+
}`,
|
|
106
|
+
);
|
|
107
|
+
return filename;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const generativeChild = `"use generative";
|
|
111
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
112
|
+
export default defineToolkit({
|
|
113
|
+
get_weather: {
|
|
114
|
+
execute: async () => 1,
|
|
115
|
+
render: () => null,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
`;
|
|
119
|
+
|
|
8
120
|
const source = `"use generative";
|
|
9
121
|
import { z } from "zod";
|
|
10
122
|
import { db } from "@/db";
|
|
@@ -34,6 +146,49 @@ export default defineToolkit({
|
|
|
34
146
|
const server = () => compileGenerative(source, { target: "server" }).code;
|
|
35
147
|
const client = () => compileGenerative(source, { target: "client" }).code;
|
|
36
148
|
|
|
149
|
+
describe("compileGenerative — core/compiler compatibility", () => {
|
|
150
|
+
it("allows a compiler version that satisfies core's optionalDevDependencies range", () => {
|
|
151
|
+
const filename = createCompatibilityFixture(">=0.0.0");
|
|
152
|
+
|
|
153
|
+
expect(() =>
|
|
154
|
+
compileGenerative(minimalSource, { target: "server", filename }),
|
|
155
|
+
).not.toThrow();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("throws when core requires a different compiler range", () => {
|
|
159
|
+
const filename = createCompatibilityFixture("<0.0.3");
|
|
160
|
+
|
|
161
|
+
expect(() =>
|
|
162
|
+
compileGenerative(minimalSource, { target: "server", filename }),
|
|
163
|
+
).toThrow(/requires @assistant-ui\/x-generative-compiler <0\.0\.3/);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("skips the check for older core packages without compatibility metadata", () => {
|
|
167
|
+
const filename = createCompatibilityFixture(null);
|
|
168
|
+
|
|
169
|
+
expect(() =>
|
|
170
|
+
compileGenerative(minimalSource, { target: "server", filename }),
|
|
171
|
+
).not.toThrow();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("wraps malformed package metadata in a compile error", () => {
|
|
175
|
+
const filename = createCompatibilityFixture(">=0.0.0");
|
|
176
|
+
const packageJsonPath = nodePath.join(
|
|
177
|
+
nodePath.dirname(filename),
|
|
178
|
+
"..",
|
|
179
|
+
"node_modules",
|
|
180
|
+
"@assistant-ui",
|
|
181
|
+
"react",
|
|
182
|
+
"package.json",
|
|
183
|
+
);
|
|
184
|
+
writeFileSync(packageJsonPath, "{");
|
|
185
|
+
|
|
186
|
+
expect(() =>
|
|
187
|
+
compileGenerative(minimalSource, { target: "server", filename }),
|
|
188
|
+
).toThrow(GenerativeCompileError);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
37
192
|
describe("compileGenerative — server target", () => {
|
|
38
193
|
const code = server();
|
|
39
194
|
|
|
@@ -54,6 +209,10 @@ describe("compileGenerative — server target", () => {
|
|
|
54
209
|
expect(code).toContain('type: "frontend"'); // toast: execute + "use client"
|
|
55
210
|
});
|
|
56
211
|
|
|
212
|
+
it("does not mark server tools with backend defaults", () => {
|
|
213
|
+
expect(code).not.toContain("unstable_backendDefault");
|
|
214
|
+
});
|
|
215
|
+
|
|
57
216
|
it("drops all render and its client imports", () => {
|
|
58
217
|
expect(code).not.toContain("Chart");
|
|
59
218
|
expect(code).not.toContain("<div");
|
|
@@ -98,6 +257,29 @@ describe("compileGenerative — client target", () => {
|
|
|
98
257
|
expect(code).toContain('type: "frontend"'); // toast
|
|
99
258
|
});
|
|
100
259
|
|
|
260
|
+
it("marks generated frontend tools with backend parameter defaults", () => {
|
|
261
|
+
expect(code).toContain("unstable_backendDefault: {");
|
|
262
|
+
expect(code).toContain("parameters: true");
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("marks generated human tools with backend parameter defaults", () => {
|
|
266
|
+
const src = `"use generative";
|
|
267
|
+
import { defineToolkit, humanTool } from "@assistant-ui/react";
|
|
268
|
+
export default defineToolkit({
|
|
269
|
+
ask: {
|
|
270
|
+
parameters: { type: "object", properties: {} },
|
|
271
|
+
execute: humanTool(),
|
|
272
|
+
render: () => null,
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
`;
|
|
276
|
+
|
|
277
|
+
const code = compileGenerative(src, { target: "client" }).code;
|
|
278
|
+
expect(code).toContain('type: "human"');
|
|
279
|
+
expect(code).toContain("unstable_backendDefault: {");
|
|
280
|
+
expect(code).toContain("parameters: true");
|
|
281
|
+
});
|
|
282
|
+
|
|
101
283
|
it("drops a backend execute and its server-only imports", () => {
|
|
102
284
|
expect(code).not.toContain("db.weather.get");
|
|
103
285
|
expect(code).not.toContain("@/db");
|
|
@@ -105,6 +287,553 @@ describe("compileGenerative — client target", () => {
|
|
|
105
287
|
});
|
|
106
288
|
});
|
|
107
289
|
|
|
290
|
+
describe("compileGenerative — generative UI library", () => {
|
|
291
|
+
const generative = `"use generative";
|
|
292
|
+
import { z } from "zod";
|
|
293
|
+
import { Chart } from "@/ui/chart";
|
|
294
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
295
|
+
import {
|
|
296
|
+
JSONGenerativeUI,
|
|
297
|
+
defineGenerativeComponents,
|
|
298
|
+
} from "@assistant-ui/react-generative-ui";
|
|
299
|
+
|
|
300
|
+
const generative = new JSONGenerativeUI({
|
|
301
|
+
library: defineGenerativeComponents({
|
|
302
|
+
Chart: {
|
|
303
|
+
description: "A chart.",
|
|
304
|
+
properties: z.object({ data: z.array(z.number()) }),
|
|
305
|
+
render: (props) => <Chart {...props} />,
|
|
306
|
+
},
|
|
307
|
+
}),
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
export default defineToolkit({
|
|
311
|
+
present: generative.present(),
|
|
312
|
+
prompt_user: generative.promptUser(),
|
|
313
|
+
});
|
|
314
|
+
`;
|
|
315
|
+
|
|
316
|
+
it("keeps the schema and the JSONGenerativeUI instance on the server, drops render", () => {
|
|
317
|
+
const code = compileGenerative(generative, { target: "server" }).code;
|
|
318
|
+
expect(code).toContain("new JSONGenerativeUI");
|
|
319
|
+
expect(code).toContain("generative.present()");
|
|
320
|
+
expect(code).toContain("generative.promptUser()");
|
|
321
|
+
expect(code).toContain("z.object");
|
|
322
|
+
expect(code).toContain('description: "A chart."');
|
|
323
|
+
// render and its client-only import are gone on the server
|
|
324
|
+
expect(code).not.toMatch(/render\s*:/);
|
|
325
|
+
expect(code).not.toContain("Chart }"); // the @/ui/chart import specifier
|
|
326
|
+
expect(code).not.toContain("@/ui/chart");
|
|
327
|
+
expect(code).not.toContain("<Chart");
|
|
328
|
+
// the markers themselves are unwrapped and their import pruned
|
|
329
|
+
expect(code).not.toContain("defineToolkit");
|
|
330
|
+
expect(code).not.toContain("defineGenerativeComponents");
|
|
331
|
+
// no backend execute anywhere → no server-only guard, no use client
|
|
332
|
+
expect(code).not.toContain("server-only");
|
|
333
|
+
expect(code).not.toContain("use client");
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("keeps render (and marks use client) on the client", () => {
|
|
337
|
+
const code = compileGenerative(generative, { target: "client" }).code;
|
|
338
|
+
expect(code.trimStart().startsWith('"use client"')).toBe(true);
|
|
339
|
+
expect(code).toContain("<Chart");
|
|
340
|
+
expect(code).toContain('import { Chart } from "@/ui/chart"');
|
|
341
|
+
expect(code).toContain("new JSONGenerativeUI");
|
|
342
|
+
expect(code).toContain("generative.present()");
|
|
343
|
+
// the JSONGenerativeUI import survives on both builds
|
|
344
|
+
expect(code).toContain("@assistant-ui/react-generative-ui");
|
|
345
|
+
expect(code).not.toContain("defineGenerativeComponents");
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("rejects an unknown method on a JSONGenerativeUI instance", () => {
|
|
349
|
+
const src = `"use generative";
|
|
350
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
351
|
+
import { JSONGenerativeUI } from "@assistant-ui/react-generative-ui";
|
|
352
|
+
const ui = new JSONGenerativeUI({ library: {} });
|
|
353
|
+
export default defineToolkit({ present: ui.notARealMethod() });`;
|
|
354
|
+
expect(() => compileGenerative(src, { target: "server" })).toThrow(
|
|
355
|
+
/inline object literal/,
|
|
356
|
+
);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("rejects a method call on an unknown (non-JSONGenerativeUI) object", () => {
|
|
360
|
+
const src = `"use generative";
|
|
361
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
362
|
+
import { other } from "@/other";
|
|
363
|
+
export default defineToolkit({ present: other.present() });`;
|
|
364
|
+
expect(() => compileGenerative(src, { target: "server" })).toThrow(
|
|
365
|
+
/inline object literal/,
|
|
366
|
+
);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("does not treat nested JSONGenerativeUI instances as module-scope instances", () => {
|
|
370
|
+
const src = `"use generative";
|
|
371
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
372
|
+
import { JSONGenerativeUI } from "@assistant-ui/react-generative-ui";
|
|
373
|
+
function create() {
|
|
374
|
+
const ui = new JSONGenerativeUI({ library: {} });
|
|
375
|
+
return ui;
|
|
376
|
+
}
|
|
377
|
+
export default defineToolkit({ present: ui.present() });`;
|
|
378
|
+
expect(() => compileGenerative(src, { target: "server" })).toThrow(
|
|
379
|
+
/inline object literal/,
|
|
380
|
+
);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("allows a generative tool alongside an inline tool", () => {
|
|
384
|
+
const src = `"use generative";
|
|
385
|
+
import { z } from "zod";
|
|
386
|
+
import { db } from "@/db";
|
|
387
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
388
|
+
import { JSONGenerativeUI, defineGenerativeComponents } from "@assistant-ui/react-generative-ui";
|
|
389
|
+
const ui = new JSONGenerativeUI({ library: defineGenerativeComponents({ Box: { description: "b", properties: z.object({}), render: () => null } }) });
|
|
390
|
+
export default defineToolkit({
|
|
391
|
+
present: ui.present(),
|
|
392
|
+
weather: {
|
|
393
|
+
description: "w",
|
|
394
|
+
parameters: z.object({ city: z.string() }),
|
|
395
|
+
execute: async ({ city }) => db.get(city),
|
|
396
|
+
render: () => null,
|
|
397
|
+
},
|
|
398
|
+
});`;
|
|
399
|
+
const server = compileGenerative(src, { target: "server" }).code;
|
|
400
|
+
expect(server).toContain("ui.present()");
|
|
401
|
+
expect(server).toContain("db.get");
|
|
402
|
+
expect(server).toContain('type: "backend"');
|
|
403
|
+
expect(server).toContain("server-only");
|
|
404
|
+
const client = compileGenerative(src, { target: "client" }).code;
|
|
405
|
+
expect(client).toContain("ui.present()");
|
|
406
|
+
expect(client).not.toContain("db.get");
|
|
407
|
+
expect(client.trimStart().startsWith('"use client"')).toBe(true);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("routes provider tool config", () => {
|
|
411
|
+
const src = `"use generative";
|
|
412
|
+
import { defineToolkit, providerTool } from "@assistant-ui/react";
|
|
413
|
+
export default defineToolkit({
|
|
414
|
+
web_search: {
|
|
415
|
+
execute: providerTool({
|
|
416
|
+
providerId: "openai.web_search_preview",
|
|
417
|
+
args: { searchContextSize: "low" },
|
|
418
|
+
providerOptions: { openai: { rankingOptions: { scoreThreshold: 0.5 } } },
|
|
419
|
+
}),
|
|
420
|
+
},
|
|
421
|
+
});`;
|
|
422
|
+
|
|
423
|
+
const server = compileGenerative(src, { target: "server" }).code;
|
|
424
|
+
expect(server).toContain('type: "provider"');
|
|
425
|
+
expect(server).toContain("openai.web_search_preview");
|
|
426
|
+
expect(server).toContain("searchContextSize");
|
|
427
|
+
expect(server).toContain("providerOptions");
|
|
428
|
+
expect(server).not.toContain("providerTool");
|
|
429
|
+
|
|
430
|
+
const client = compileGenerative(src, { target: "client" }).code;
|
|
431
|
+
expect(client).toContain('type: "provider"');
|
|
432
|
+
expect(client).toContain("openai.web_search_preview");
|
|
433
|
+
expect(client).toContain("providerOptions");
|
|
434
|
+
expect(client).not.toContain("providerTool");
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it("routes flat toolkit tools and preserves spread MCP config", () => {
|
|
438
|
+
const src = `"use generative";
|
|
439
|
+
import { z } from "zod";
|
|
440
|
+
import { db } from "@/db";
|
|
441
|
+
import { defineMcpToolkit, defineToolkit } from "@assistant-ui/react";
|
|
442
|
+
const mcp = defineMcpToolkit({
|
|
443
|
+
docs: { type: "http", url: "http://localhost:3001/mcp" },
|
|
444
|
+
});
|
|
445
|
+
export default defineToolkit({
|
|
446
|
+
...mcp,
|
|
447
|
+
weather: {
|
|
448
|
+
parameters: z.object({ city: z.string() }),
|
|
449
|
+
execute: async ({ city }) => db.get(city),
|
|
450
|
+
render: () => null,
|
|
451
|
+
},
|
|
452
|
+
});`;
|
|
453
|
+
|
|
454
|
+
const server = compileGenerative(src, { target: "server" }).code;
|
|
455
|
+
expect(server).toContain("...mcp");
|
|
456
|
+
expect(server).toContain("db.get");
|
|
457
|
+
expect(server).not.toContain("render");
|
|
458
|
+
|
|
459
|
+
const client = compileGenerative(src, { target: "client" }).code;
|
|
460
|
+
expect(client).toContain("...mcp");
|
|
461
|
+
expect(client).toContain("render");
|
|
462
|
+
expect(client).not.toContain("db.get");
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it("allows spreading a compiler-visible local toolkit", () => {
|
|
466
|
+
const src = `"use generative";
|
|
467
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
468
|
+
import { db } from "@/db";
|
|
469
|
+
const base = defineToolkit({
|
|
470
|
+
get_weather: {
|
|
471
|
+
execute: async () => db.getWeather(),
|
|
472
|
+
render: () => null,
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
export default defineToolkit({
|
|
476
|
+
...base,
|
|
477
|
+
get_time: {
|
|
478
|
+
execute: async () => db.getTime(),
|
|
479
|
+
render: () => null,
|
|
480
|
+
},
|
|
481
|
+
});`;
|
|
482
|
+
|
|
483
|
+
const server = compileGenerative(src, { target: "server" }).code;
|
|
484
|
+
expect(server).toContain("...base");
|
|
485
|
+
expect(server).toContain("db.getWeather");
|
|
486
|
+
expect(server).toContain("db.getTime");
|
|
487
|
+
expect(server).not.toContain("render");
|
|
488
|
+
|
|
489
|
+
const client = compileGenerative(src, { target: "client" }).code;
|
|
490
|
+
expect(client).toContain("...base");
|
|
491
|
+
expect(client).not.toContain("db.getWeather");
|
|
492
|
+
expect(client).not.toContain("db.getTime");
|
|
493
|
+
expect(client).toContain("render");
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("allows directly spreading an MCP toolkit fragment", () => {
|
|
497
|
+
const src = `"use generative";
|
|
498
|
+
import { defineMcpToolkit, defineToolkit } from "@assistant-ui/react";
|
|
499
|
+
export default defineToolkit({
|
|
500
|
+
...defineMcpToolkit({
|
|
501
|
+
docs: { type: "http", url: "http://localhost:3001/mcp" },
|
|
502
|
+
}),
|
|
503
|
+
});`;
|
|
504
|
+
|
|
505
|
+
const server = compileGenerative(src, { target: "server" }).code;
|
|
506
|
+
expect(server).toContain("defineMcpToolkit");
|
|
507
|
+
expect(server).toContain("docs");
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it("rejects opaque toolkit spreads", () => {
|
|
511
|
+
const src = `"use generative";
|
|
512
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
513
|
+
import { backendTools } from "@/backend-tools";
|
|
514
|
+
export default defineToolkit({
|
|
515
|
+
...backendTools,
|
|
516
|
+
});`;
|
|
517
|
+
|
|
518
|
+
expect(() => compileGenerative(src, { target: "client" })).toThrow(
|
|
519
|
+
/compiler-visible toolkit spread/,
|
|
520
|
+
);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it("does not treat nested toolkit declarations as compiler-visible spreads", () => {
|
|
524
|
+
const src = `"use generative";
|
|
525
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
526
|
+
function createToolkit() {
|
|
527
|
+
const base = defineToolkit({
|
|
528
|
+
get_weather: {
|
|
529
|
+
execute: async () => 1,
|
|
530
|
+
render: () => null,
|
|
531
|
+
},
|
|
532
|
+
});
|
|
533
|
+
return base;
|
|
534
|
+
}
|
|
535
|
+
export default defineToolkit({
|
|
536
|
+
...base,
|
|
537
|
+
});`;
|
|
538
|
+
|
|
539
|
+
expect(() => compileGenerative(src, { target: "client" })).toThrow(
|
|
540
|
+
/compiler-visible toolkit spread/,
|
|
541
|
+
);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it("allows spreading the default import of a generative module", () => {
|
|
545
|
+
const filename = createMergeFixture(generativeChild);
|
|
546
|
+
const src = `"use generative";
|
|
547
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
548
|
+
import weatherTools from "./tools/weather";
|
|
549
|
+
export default defineToolkit({
|
|
550
|
+
...weatherTools,
|
|
551
|
+
});`;
|
|
552
|
+
|
|
553
|
+
const server = compileGenerative(src, { target: "server", filename }).code;
|
|
554
|
+
expect(server).toContain("...weatherTools");
|
|
555
|
+
expect(server).toContain('from "./tools/weather"');
|
|
556
|
+
|
|
557
|
+
const client = compileGenerative(src, { target: "client", filename }).code;
|
|
558
|
+
expect(client).toContain("...weatherTools");
|
|
559
|
+
expect(client).toContain('from "./tools/weather"');
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it("rejects spreading the default import of a non-generative module", () => {
|
|
563
|
+
const filename = createMergeFixture(
|
|
564
|
+
`export default { get_weather: { execute: async () => 1 } };\n`,
|
|
565
|
+
);
|
|
566
|
+
const src = `"use generative";
|
|
567
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
568
|
+
import weatherTools from "./tools/weather";
|
|
569
|
+
export default defineToolkit({
|
|
570
|
+
...weatherTools,
|
|
571
|
+
});`;
|
|
572
|
+
|
|
573
|
+
expect(() =>
|
|
574
|
+
compileGenerative(src, { target: "client", filename }),
|
|
575
|
+
).toThrow(/compiler-visible toolkit spread/);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it("rejects spreading a named import even from a generative module", () => {
|
|
579
|
+
const filename = createMergeFixture(generativeChild);
|
|
580
|
+
const src = `"use generative";
|
|
581
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
582
|
+
import { weatherTools } from "./tools/weather";
|
|
583
|
+
export default defineToolkit({
|
|
584
|
+
...weatherTools,
|
|
585
|
+
});`;
|
|
586
|
+
|
|
587
|
+
expect(() =>
|
|
588
|
+
compileGenerative(src, { target: "client", filename }),
|
|
589
|
+
).toThrow(/compiler-visible toolkit spread/);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it("resolves a tsconfig path alias when spreading a generative module", () => {
|
|
593
|
+
const filename = createAliasMergeFixture(generativeChild);
|
|
594
|
+
const src = `"use generative";
|
|
595
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
596
|
+
import weatherTools from "@/tools/weather";
|
|
597
|
+
export default defineToolkit({
|
|
598
|
+
...weatherTools,
|
|
599
|
+
});`;
|
|
600
|
+
|
|
601
|
+
const client = compileGenerative(src, { target: "client", filename }).code;
|
|
602
|
+
expect(client).toContain("...weatherTools");
|
|
603
|
+
expect(client).toContain('from "@/tools/weather"');
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it("prefers the most specific tsconfig path alias", () => {
|
|
607
|
+
const appRoot = mkdtempSync(
|
|
608
|
+
nodePath.join(tmpdir(), "aui-generative-alias-spec-"),
|
|
609
|
+
);
|
|
610
|
+
mkdirSync(nodePath.join(appRoot, "src", "tools"), { recursive: true });
|
|
611
|
+
mkdirSync(nodePath.join(appRoot, "generated"), { recursive: true });
|
|
612
|
+
// The broader `@/*` alias would resolve here — a non-generative module.
|
|
613
|
+
writeFileSync(
|
|
614
|
+
nodePath.join(appRoot, "src", "tools", "weather.tsx"),
|
|
615
|
+
`export default { get_weather: { execute: async () => 1 } };\n`,
|
|
616
|
+
);
|
|
617
|
+
// The more specific `@/tools/*` alias resolves to the generative module.
|
|
618
|
+
writeFileSync(
|
|
619
|
+
nodePath.join(appRoot, "generated", "weather.tsx"),
|
|
620
|
+
generativeChild,
|
|
621
|
+
);
|
|
622
|
+
writeFileSync(
|
|
623
|
+
nodePath.join(appRoot, "tsconfig.json"),
|
|
624
|
+
`{
|
|
625
|
+
"compilerOptions": {
|
|
626
|
+
"baseUrl": ".",
|
|
627
|
+
"paths": {
|
|
628
|
+
"@/*": ["./src/*"],
|
|
629
|
+
"@/tools/*": ["./generated/*"]
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}`,
|
|
633
|
+
);
|
|
634
|
+
const filename = nodePath.join(appRoot, "src", "toolkit.tsx");
|
|
635
|
+
const src = `"use generative";
|
|
636
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
637
|
+
import weatherTools from "@/tools/weather";
|
|
638
|
+
export default defineToolkit({
|
|
639
|
+
...weatherTools,
|
|
640
|
+
});`;
|
|
641
|
+
|
|
642
|
+
// `@/tools/*` is more specific than `@/*`, so it wins and the spread is the
|
|
643
|
+
// generative module (allowed) rather than the non-generative `@/*` target.
|
|
644
|
+
const client = compileGenerative(src, { target: "client", filename }).code;
|
|
645
|
+
expect(client).toContain("...weatherTools");
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
it("resolves a .js specifier to its .tsx source when spreading", () => {
|
|
649
|
+
const filename = createMergeFixture(generativeChild);
|
|
650
|
+
const src = `"use generative";
|
|
651
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
652
|
+
import weatherTools from "./tools/weather.js";
|
|
653
|
+
export default defineToolkit({
|
|
654
|
+
...weatherTools,
|
|
655
|
+
});`;
|
|
656
|
+
|
|
657
|
+
const client = compileGenerative(src, { target: "client", filename }).code;
|
|
658
|
+
expect(client).toContain("...weatherTools");
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it("allows defineMcpToolkit as the default export", () => {
|
|
662
|
+
const src = `"use generative";
|
|
663
|
+
import { defineMcpToolkit } from "@assistant-ui/react";
|
|
664
|
+
export default defineMcpToolkit({
|
|
665
|
+
docs: { type: "http", url: "https://mcp.example.com/mcp" },
|
|
666
|
+
});`;
|
|
667
|
+
|
|
668
|
+
const server = compileGenerative(src, { target: "server" }).code;
|
|
669
|
+
expect(server).toContain("defineMcpToolkit");
|
|
670
|
+
expect(server).toContain("docs");
|
|
671
|
+
|
|
672
|
+
const client = compileGenerative(src, { target: "client" }).code;
|
|
673
|
+
expect(client).toContain("defineMcpToolkit");
|
|
674
|
+
expect(client).toContain("docs");
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it("still allows a flat toolkit tool named tools", () => {
|
|
678
|
+
const src = `"use generative";
|
|
679
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
680
|
+
import { db } from "@/db";
|
|
681
|
+
export default defineToolkit({
|
|
682
|
+
tools: {
|
|
683
|
+
execute: async () => db.get(),
|
|
684
|
+
render: () => null,
|
|
685
|
+
},
|
|
686
|
+
});`;
|
|
687
|
+
|
|
688
|
+
const server = compileGenerative(src, { target: "server" }).code;
|
|
689
|
+
expect(server).toContain("db.get");
|
|
690
|
+
expect(server).toContain('type: "backend"');
|
|
691
|
+
|
|
692
|
+
const client = compileGenerative(src, { target: "client" }).code;
|
|
693
|
+
expect(client).toContain("render");
|
|
694
|
+
expect(client).not.toContain("db.get");
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it("routes renderText like a lightweight tool renderer", () => {
|
|
698
|
+
const src = `"use generative";
|
|
699
|
+
import { z } from "zod";
|
|
700
|
+
import { db } from "@/db";
|
|
701
|
+
import { formatResult } from "@/ui/format";
|
|
702
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
703
|
+
export default defineToolkit({
|
|
704
|
+
search: {
|
|
705
|
+
parameters: z.object({ query: z.string() }),
|
|
706
|
+
execute: async ({ query }) => db.search(query),
|
|
707
|
+
renderText: {
|
|
708
|
+
running: ({ args }) => \`Searching \${args.query}...\`,
|
|
709
|
+
complete: ({ args, result }) => formatResult(args.query, result),
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
});`;
|
|
713
|
+
|
|
714
|
+
const server = compileGenerative(src, { target: "server" }).code;
|
|
715
|
+
expect(server).toContain("db.search");
|
|
716
|
+
expect(server).toContain('type: "backend"');
|
|
717
|
+
expect(server).not.toContain("renderText");
|
|
718
|
+
expect(server).not.toContain("@/ui/format");
|
|
719
|
+
|
|
720
|
+
const client = compileGenerative(src, { target: "client" }).code;
|
|
721
|
+
expect(client.trimStart().startsWith('"use client"')).toBe(true);
|
|
722
|
+
expect(client).toContain("renderText");
|
|
723
|
+
expect(client).toContain("formatResult");
|
|
724
|
+
expect(client).toContain("@/ui/format");
|
|
725
|
+
expect(client).not.toContain("db.search");
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
it("allows frontend tools to use renderText instead of render", () => {
|
|
729
|
+
const src = `"use generative";
|
|
730
|
+
import { z } from "zod";
|
|
731
|
+
import { track } from "@/analytics";
|
|
732
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
733
|
+
export default defineToolkit({
|
|
734
|
+
toast: {
|
|
735
|
+
parameters: z.object({ msg: z.string() }),
|
|
736
|
+
execute: async ({ msg }) => {
|
|
737
|
+
"use client";
|
|
738
|
+
return track(msg);
|
|
739
|
+
},
|
|
740
|
+
renderText: {
|
|
741
|
+
running: "Showing toast...",
|
|
742
|
+
complete: "Toast shown",
|
|
743
|
+
},
|
|
744
|
+
},
|
|
745
|
+
});`;
|
|
746
|
+
|
|
747
|
+
const client = compileGenerative(src, { target: "client" }).code;
|
|
748
|
+
expect(client).toContain("track(msg)");
|
|
749
|
+
expect(client).toContain("renderText");
|
|
750
|
+
expect(client).toContain('type: "frontend"');
|
|
751
|
+
|
|
752
|
+
const server = compileGenerative(src, { target: "server" }).code;
|
|
753
|
+
expect(server).not.toContain("track");
|
|
754
|
+
expect(server).not.toContain("renderText");
|
|
755
|
+
expect(server).toContain('type: "frontend"');
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
it("infers `frontend` from execute: stubTool() and strips the executor", () => {
|
|
759
|
+
const src = `"use generative";
|
|
760
|
+
import { z } from "zod";
|
|
761
|
+
import { defineToolkit, stubTool } from "@assistant-ui/react";
|
|
762
|
+
export default defineToolkit({
|
|
763
|
+
add_task: {
|
|
764
|
+
description: "Add a task.",
|
|
765
|
+
parameters: z.object({ title: z.string() }),
|
|
766
|
+
execute: stubTool(),
|
|
767
|
+
renderText: {
|
|
768
|
+
running: "Adding task",
|
|
769
|
+
complete: "Task added",
|
|
770
|
+
},
|
|
771
|
+
},
|
|
772
|
+
});`;
|
|
773
|
+
|
|
774
|
+
const server = compileGenerative(src, { target: "server" }).code;
|
|
775
|
+
expect(server).toContain('type: "frontend"');
|
|
776
|
+
expect(server).not.toContain("disabled");
|
|
777
|
+
expect(server).not.toContain("stubTool");
|
|
778
|
+
expect(server).not.toContain("execute");
|
|
779
|
+
expect(server).not.toContain("renderText");
|
|
780
|
+
|
|
781
|
+
const client = compileGenerative(src, { target: "client" }).code;
|
|
782
|
+
expect(client).toContain('type: "frontend"');
|
|
783
|
+
expect(client).not.toContain("disabled");
|
|
784
|
+
expect(client).toContain("renderText");
|
|
785
|
+
expect(client).toContain("unstable_backendDefault");
|
|
786
|
+
expect(client).not.toContain("stubTool");
|
|
787
|
+
expect(client).not.toContain("execute");
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
it("infers `backend` from execute: externalTool(), renders on the client, and omits from the server", () => {
|
|
791
|
+
const src = `"use generative";
|
|
792
|
+
import { z } from "zod";
|
|
793
|
+
import { SearchResults } from "@/ui/search-results";
|
|
794
|
+
import { defineToolkit, externalTool } from "@assistant-ui/react";
|
|
795
|
+
export default defineToolkit({
|
|
796
|
+
web_search: {
|
|
797
|
+
description: "Search the web.",
|
|
798
|
+
parameters: z.object({ query: z.string() }),
|
|
799
|
+
execute: externalTool(),
|
|
800
|
+
render: ({ args, result }) => <SearchResults query={args.query} results={result} />,
|
|
801
|
+
},
|
|
802
|
+
});`;
|
|
803
|
+
|
|
804
|
+
const server = compileGenerative(src, { target: "server" }).code;
|
|
805
|
+
expect(server).not.toContain("web_search");
|
|
806
|
+
expect(server).not.toContain('type: "backend"');
|
|
807
|
+
expect(server).not.toContain("server-only");
|
|
808
|
+
expect(server).not.toContain("description");
|
|
809
|
+
expect(server).not.toContain("parameters");
|
|
810
|
+
expect(server).not.toContain("execute");
|
|
811
|
+
expect(server).not.toContain("externalTool");
|
|
812
|
+
expect(server).not.toContain("SearchResults");
|
|
813
|
+
expect(server).not.toContain('from "zod"');
|
|
814
|
+
|
|
815
|
+
const client = compileGenerative(src, { target: "client" }).code;
|
|
816
|
+
expect(client.trimStart().startsWith('"use client"')).toBe(true);
|
|
817
|
+
expect(client).toContain('type: "backend"');
|
|
818
|
+
expect(client).toContain("render");
|
|
819
|
+
expect(client).toContain("SearchResults");
|
|
820
|
+
expect(client).not.toContain("description");
|
|
821
|
+
expect(client).not.toContain("parameters");
|
|
822
|
+
expect(client).not.toContain("execute");
|
|
823
|
+
expect(client).not.toContain("externalTool");
|
|
824
|
+
expect(client).not.toContain('from "zod"');
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it("requires a renderer for external tools", () => {
|
|
828
|
+
expect(() =>
|
|
829
|
+
compileGenerative(
|
|
830
|
+
`"use generative";\nimport { defineToolkit, externalTool } from "@assistant-ui/react";\nexport default defineToolkit({ search: { execute: externalTool() } });`,
|
|
831
|
+
{ target: "client" },
|
|
832
|
+
),
|
|
833
|
+
).toThrow(/external tool must declare a `render` or `renderText`/);
|
|
834
|
+
});
|
|
835
|
+
});
|
|
836
|
+
|
|
108
837
|
describe("compileGenerative — local dead-code elimination", () => {
|
|
109
838
|
const withHelpers = `"use generative";
|
|
110
839
|
import { z } from "zod";
|
|
@@ -209,6 +938,18 @@ export default defineToolkit({
|
|
|
209
938
|
).toThrow(/defineToolkit/);
|
|
210
939
|
});
|
|
211
940
|
|
|
941
|
+
it("rejects a raw default export even when a defineToolkit exists elsewhere", () => {
|
|
942
|
+
// The default export is what the runtime registers, so it must itself be
|
|
943
|
+
// wrapped — an unrelated defineToolkit() must not let a bare object through.
|
|
944
|
+
const src = `"use generative";
|
|
945
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
946
|
+
const unused = defineToolkit({ x: { execute: async () => 1, render: () => null } });
|
|
947
|
+
export default { weather: { execute: async () => 1, render: () => null } };`;
|
|
948
|
+
expect(() => compileGenerative(src, { target: "client" })).toThrow(
|
|
949
|
+
/default export must be defineToolkit/,
|
|
950
|
+
);
|
|
951
|
+
});
|
|
952
|
+
|
|
212
953
|
it("rejects a tool that isn't an inline object literal", () => {
|
|
213
954
|
expect(() =>
|
|
214
955
|
compileGenerative(
|
|
@@ -218,15 +959,24 @@ export default defineToolkit({
|
|
|
218
959
|
).toThrow(/inline object literal/);
|
|
219
960
|
});
|
|
220
961
|
|
|
221
|
-
it("requires a render for human
|
|
962
|
+
it("requires a render for human tools", () => {
|
|
222
963
|
expect(() =>
|
|
223
964
|
compileGenerative(
|
|
224
|
-
`"use generative";\nimport { defineToolkit,
|
|
965
|
+
`"use generative";\nimport { defineToolkit, humanTool } from "@assistant-ui/react";\nexport default defineToolkit({ ask: { execute: humanTool() } });`,
|
|
225
966
|
{ target: "client" },
|
|
226
967
|
),
|
|
227
968
|
).toThrow(/must declare a `render`/);
|
|
228
969
|
});
|
|
229
970
|
|
|
971
|
+
it("requires a render or renderText for frontend tools", () => {
|
|
972
|
+
expect(() =>
|
|
973
|
+
compileGenerative(
|
|
974
|
+
`"use generative";\nimport { defineToolkit } from "@assistant-ui/react";\nexport default defineToolkit({ toast: { execute: async () => { "use client"; return 1; } } });`,
|
|
975
|
+
{ target: "client" },
|
|
976
|
+
),
|
|
977
|
+
).toThrow(/must declare a `render` or `renderText`/);
|
|
978
|
+
});
|
|
979
|
+
|
|
230
980
|
it("requires every tool to declare an execute", () => {
|
|
231
981
|
expect(() =>
|
|
232
982
|
compileGenerative(
|
|
@@ -236,17 +986,102 @@ export default defineToolkit({
|
|
|
236
986
|
).toThrow(/must declare an `execute`/);
|
|
237
987
|
});
|
|
238
988
|
|
|
239
|
-
it("infers `human` from execute:
|
|
240
|
-
const src = `"use generative";\nimport { defineToolkit,
|
|
989
|
+
it("infers `human` from execute: humanTool() and drops it on both builds", () => {
|
|
990
|
+
const src = `"use generative";\nimport { defineToolkit, humanTool } from "@assistant-ui/react";\nexport default defineToolkit({ ask: { execute: humanTool(), render: () => null } });`;
|
|
241
991
|
const server = compileGenerative(src, { target: "server" }).code;
|
|
242
992
|
expect(server).toContain('type: "human"');
|
|
243
|
-
expect(server).not.toContain("
|
|
993
|
+
expect(server).not.toContain("humanTool"); // sentinel + its import pruned
|
|
244
994
|
const client = compileGenerative(src, { target: "client" }).code;
|
|
245
995
|
expect(client).toContain('type: "human"');
|
|
246
|
-
expect(client).not.toContain("
|
|
996
|
+
expect(client).not.toContain("humanTool");
|
|
247
997
|
expect(client).toContain("render");
|
|
248
998
|
});
|
|
249
999
|
|
|
1000
|
+
it("keeps deprecated human-tool sentinels working", () => {
|
|
1001
|
+
for (const sentinel of ["hitl", "hitlTool"]) {
|
|
1002
|
+
const src = `"use generative";\nimport { defineToolkit, ${sentinel} } from "@assistant-ui/react";\nexport default defineToolkit({ ask: { execute: ${sentinel}(), render: () => null } });`;
|
|
1003
|
+
for (const target of ["client", "server"] as const) {
|
|
1004
|
+
const code = compileGenerative(src, { target }).code;
|
|
1005
|
+
expect(code).toContain('type: "human"');
|
|
1006
|
+
expect(code).not.toContain(sentinel);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
it("rejects spread properties in providerTool config", () => {
|
|
1012
|
+
const src = `"use generative";
|
|
1013
|
+
import { defineToolkit, providerTool } from "@assistant-ui/react";
|
|
1014
|
+
const config = { providerId: "openai.web_search_preview", args: {} };
|
|
1015
|
+
export default defineToolkit({
|
|
1016
|
+
web_search: {
|
|
1017
|
+
execute: providerTool({
|
|
1018
|
+
...config,
|
|
1019
|
+
}),
|
|
1020
|
+
},
|
|
1021
|
+
});`;
|
|
1022
|
+
|
|
1023
|
+
expect(() => compileGenerative(src, { target: "server" })).toThrow(
|
|
1024
|
+
/can only contain object properties/,
|
|
1025
|
+
);
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
it("rejects object methods in providerTool config", () => {
|
|
1029
|
+
const src = `"use generative";
|
|
1030
|
+
import { defineToolkit, providerTool } from "@assistant-ui/react";
|
|
1031
|
+
export default defineToolkit({
|
|
1032
|
+
web_search: {
|
|
1033
|
+
execute: providerTool({
|
|
1034
|
+
providerId: "openai.web_search_preview",
|
|
1035
|
+
args: {},
|
|
1036
|
+
get providerOptions() {
|
|
1037
|
+
return {};
|
|
1038
|
+
},
|
|
1039
|
+
}),
|
|
1040
|
+
},
|
|
1041
|
+
});`;
|
|
1042
|
+
|
|
1043
|
+
expect(() => compileGenerative(src, { target: "server" })).toThrow(
|
|
1044
|
+
/can only contain object properties/,
|
|
1045
|
+
);
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
it("rejects function-valued properties in providerTool config", () => {
|
|
1049
|
+
const src = `"use generative";
|
|
1050
|
+
import { defineToolkit, providerTool } from "@assistant-ui/react";
|
|
1051
|
+
export default defineToolkit({
|
|
1052
|
+
web_search: {
|
|
1053
|
+
execute: providerTool({
|
|
1054
|
+
providerId: "openai.web_search_preview",
|
|
1055
|
+
args: {},
|
|
1056
|
+
providerOptions: () => ({}),
|
|
1057
|
+
}),
|
|
1058
|
+
},
|
|
1059
|
+
});`;
|
|
1060
|
+
|
|
1061
|
+
expect(() => compileGenerative(src, { target: "server" })).toThrow(
|
|
1062
|
+
/cannot contain function-valued properties/,
|
|
1063
|
+
);
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
it("rejects providerTool config properties that duplicate tool properties", () => {
|
|
1067
|
+
const src = `"use generative";
|
|
1068
|
+
import { defineToolkit, providerTool } from "@assistant-ui/react";
|
|
1069
|
+
export default defineToolkit({
|
|
1070
|
+
web_search: {
|
|
1071
|
+
render: () => null,
|
|
1072
|
+
execute: providerTool({
|
|
1073
|
+
providerId: "openai.web_search_preview",
|
|
1074
|
+
args: {},
|
|
1075
|
+
render: "duplicate",
|
|
1076
|
+
}),
|
|
1077
|
+
},
|
|
1078
|
+
});`;
|
|
1079
|
+
|
|
1080
|
+
expect(() => compileGenerative(src, { target: "server" })).toThrow(
|
|
1081
|
+
/cannot duplicate tool properties/,
|
|
1082
|
+
);
|
|
1083
|
+
});
|
|
1084
|
+
|
|
250
1085
|
it("infers `frontend` from a `use client` execute and keeps it client-side", () => {
|
|
251
1086
|
const src = `"use generative";\nimport { defineToolkit } from "@assistant-ui/react";\nimport { track } from "@/a";\nexport default defineToolkit({ t: { execute: async () => { "use client"; return track(); }, render: () => null } });`;
|
|
252
1087
|
const server = compileGenerative(src, { target: "server" }).code;
|