@assistant-ui/x-generative-compiler 0.0.5 → 0.0.8
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/compile.d.ts.map +1 -1
- package/dist/compile.js +286 -45
- package/dist/compile.js.map +1 -1
- package/dist/package.js +1 -1
- package/package.json +9 -9
- package/src/compile.test.ts +477 -9
- package/src/compile.ts +474 -47
package/src/compile.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import * as nodePath from "node:path";
|
|
4
|
-
import { describe, it, expect } from "vitest";
|
|
4
|
+
import { describe, it, expect, vi } from "vitest";
|
|
5
5
|
import {
|
|
6
6
|
compileGenerative,
|
|
7
7
|
isGenerativeModule,
|
|
@@ -86,6 +86,19 @@ function createMergeFixture(childSource: string): string {
|
|
|
86
86
|
return nodePath.join(appRoot, "src", "toolkit.tsx");
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
function createMultiMergeFixture(files: Record<string, string>): string {
|
|
90
|
+
const appRoot = mkdtempSync(
|
|
91
|
+
nodePath.join(tmpdir(), "aui-generative-merge-many-"),
|
|
92
|
+
);
|
|
93
|
+
const srcRoot = nodePath.join(appRoot, "src");
|
|
94
|
+
for (const [relativePath, source] of Object.entries(files)) {
|
|
95
|
+
const file = nodePath.join(srcRoot, relativePath);
|
|
96
|
+
mkdirSync(nodePath.dirname(file), { recursive: true });
|
|
97
|
+
writeFileSync(file, source);
|
|
98
|
+
}
|
|
99
|
+
return nodePath.join(srcRoot, "toolkit.tsx");
|
|
100
|
+
}
|
|
101
|
+
|
|
89
102
|
/**
|
|
90
103
|
* Like {@link createMergeFixture}, but also writes a (JSONC) `tsconfig.json`
|
|
91
104
|
* with a `@/*` path alias so alias resolution can be exercised. The child lives
|
|
@@ -345,6 +358,21 @@ export default defineToolkit({
|
|
|
345
358
|
expect(code).not.toContain("defineGenerativeComponents");
|
|
346
359
|
});
|
|
347
360
|
|
|
361
|
+
it("allows an exported module-scope JSONGenerativeUI instance", () => {
|
|
362
|
+
const src = `"use generative";
|
|
363
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
364
|
+
import { JSONGenerativeUI } from "@assistant-ui/react-generative-ui";
|
|
365
|
+
export const ui = new JSONGenerativeUI({ library: {} });
|
|
366
|
+
export default defineToolkit({ present: ui.present() });`;
|
|
367
|
+
|
|
368
|
+
expect(compileGenerative(src, { target: "server" }).code).toContain(
|
|
369
|
+
"ui.present()",
|
|
370
|
+
);
|
|
371
|
+
expect(compileGenerative(src, { target: "client" }).code).toContain(
|
|
372
|
+
"ui.present()",
|
|
373
|
+
);
|
|
374
|
+
});
|
|
375
|
+
|
|
348
376
|
it("rejects an unknown method on a JSONGenerativeUI instance", () => {
|
|
349
377
|
const src = `"use generative";
|
|
350
378
|
import { defineToolkit } from "@assistant-ui/react";
|
|
@@ -356,6 +384,37 @@ export default defineToolkit({ present: ui.notARealMethod() });`;
|
|
|
356
384
|
);
|
|
357
385
|
});
|
|
358
386
|
|
|
387
|
+
it("allows an aliased JSONGenerativeUI import", () => {
|
|
388
|
+
const src = `"use generative";
|
|
389
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
390
|
+
import { JSONGenerativeUI as GenUI } from "@assistant-ui/react-generative-ui";
|
|
391
|
+
const ui = new GenUI({ library: {} });
|
|
392
|
+
export default defineToolkit({ present: ui.present() });`;
|
|
393
|
+
|
|
394
|
+
expect(compileGenerative(src, { target: "server" }).code).toContain(
|
|
395
|
+
"ui.present()",
|
|
396
|
+
);
|
|
397
|
+
expect(compileGenerative(src, { target: "client" }).code).toContain(
|
|
398
|
+
"ui.present()",
|
|
399
|
+
);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it("does not trust a local class named JSONGenerativeUI", () => {
|
|
403
|
+
const src = `"use generative";
|
|
404
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
405
|
+
class JSONGenerativeUI {
|
|
406
|
+
present() {
|
|
407
|
+
return makeTool();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const ui = new JSONGenerativeUI();
|
|
411
|
+
export default defineToolkit({ present: ui.present() });`;
|
|
412
|
+
|
|
413
|
+
expect(() => compileGenerative(src, { target: "server" })).toThrow(
|
|
414
|
+
/tool "present" cannot be `ui\.present\(\)`/,
|
|
415
|
+
);
|
|
416
|
+
});
|
|
417
|
+
|
|
359
418
|
it("rejects a method call on an unknown (non-JSONGenerativeUI) object", () => {
|
|
360
419
|
const src = `"use generative";
|
|
361
420
|
import { defineToolkit } from "@assistant-ui/react";
|
|
@@ -407,6 +466,41 @@ export default defineToolkit({
|
|
|
407
466
|
expect(client.trimStart().startsWith('"use client"')).toBe(true);
|
|
408
467
|
});
|
|
409
468
|
|
|
469
|
+
it("splits an unstable_interactableTool entry: client keeps render, server drops it", () => {
|
|
470
|
+
const src = `"use generative";
|
|
471
|
+
import { z } from "zod";
|
|
472
|
+
import { Notepad } from "@/ui/notepad";
|
|
473
|
+
import { defineToolkit, unstable_interactableTool } from "@assistant-ui/react";
|
|
474
|
+
export default defineToolkit({
|
|
475
|
+
notepad: unstable_interactableTool({
|
|
476
|
+
description: "A notepad.",
|
|
477
|
+
stateSchema: z.object({ content: z.string() }),
|
|
478
|
+
render: (props) => <Notepad {...props} />,
|
|
479
|
+
}),
|
|
480
|
+
});`;
|
|
481
|
+
const server = compileGenerative(src, { target: "server" }).code;
|
|
482
|
+
expect(server).toContain("unstable_interactableTool({");
|
|
483
|
+
expect(server).toContain('description: "A notepad."');
|
|
484
|
+
expect(server).toContain("z.object");
|
|
485
|
+
expect(server).not.toMatch(/render\s*:/);
|
|
486
|
+
expect(server).not.toContain("@/ui/notepad");
|
|
487
|
+
expect(server).not.toContain("server-only");
|
|
488
|
+
const client = compileGenerative(src, { target: "client" }).code;
|
|
489
|
+
expect(client.trimStart().startsWith('"use client"')).toBe(true);
|
|
490
|
+
expect(client).toContain("<Notepad");
|
|
491
|
+
expect(client).toContain('import { Notepad } from "@/ui/notepad"');
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it("rejects unstable_interactableTool not imported from an assistant-ui package", () => {
|
|
495
|
+
const src = `"use generative";
|
|
496
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
497
|
+
import { unstable_interactableTool } from "@/my-tools";
|
|
498
|
+
export default defineToolkit({ notepad: unstable_interactableTool({ render: () => null }) });`;
|
|
499
|
+
expect(() => compileGenerative(src, { target: "server" })).toThrow(
|
|
500
|
+
/inline object literal/,
|
|
501
|
+
);
|
|
502
|
+
});
|
|
503
|
+
|
|
410
504
|
it("routes provider tool config", () => {
|
|
411
505
|
const src = `"use generative";
|
|
412
506
|
import { defineToolkit, providerTool } from "@assistant-ui/react";
|
|
@@ -559,6 +653,291 @@ export default defineToolkit({
|
|
|
559
653
|
expect(client).toContain('from "./tools/weather"');
|
|
560
654
|
});
|
|
561
655
|
|
|
656
|
+
it("allows unique tool names across more than two imported generative spreads", () => {
|
|
657
|
+
const filename = createMultiMergeFixture({
|
|
658
|
+
"tools/weather.tsx": `"use generative";
|
|
659
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
660
|
+
export default defineToolkit({
|
|
661
|
+
get_weather: { execute: async () => 1, render: () => null },
|
|
662
|
+
});`,
|
|
663
|
+
"tools/database.tsx": `"use generative";
|
|
664
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
665
|
+
export default defineToolkit({
|
|
666
|
+
query_db: { execute: async () => 1, render: () => null },
|
|
667
|
+
});`,
|
|
668
|
+
"tools/calendar.tsx": `"use generative";
|
|
669
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
670
|
+
export default defineToolkit({
|
|
671
|
+
create_event: { execute: async () => 1, render: () => null },
|
|
672
|
+
});`,
|
|
673
|
+
"tools/email.tsx": `"use generative";
|
|
674
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
675
|
+
export default defineToolkit({
|
|
676
|
+
send_email: { execute: async () => 1, render: () => null },
|
|
677
|
+
});`,
|
|
678
|
+
});
|
|
679
|
+
const src = `"use generative";
|
|
680
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
681
|
+
import weatherTools from "./tools/weather";
|
|
682
|
+
import databaseTools from "./tools/database";
|
|
683
|
+
import calendarTools from "./tools/calendar";
|
|
684
|
+
import emailTools from "./tools/email";
|
|
685
|
+
export default defineToolkit({
|
|
686
|
+
...weatherTools,
|
|
687
|
+
...databaseTools,
|
|
688
|
+
...calendarTools,
|
|
689
|
+
...emailTools,
|
|
690
|
+
});`;
|
|
691
|
+
|
|
692
|
+
const client = compileGenerative(src, { target: "client", filename }).code;
|
|
693
|
+
expect(client).toContain("...weatherTools");
|
|
694
|
+
expect(client).toContain("...databaseTools");
|
|
695
|
+
expect(client).toContain("...calendarTools");
|
|
696
|
+
expect(client).toContain("...emailTools");
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
it("warns about duplicate tool names across more than two imported generative spreads", () => {
|
|
700
|
+
const filename = createMultiMergeFixture({
|
|
701
|
+
"tools/weather.tsx": `"use generative";
|
|
702
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
703
|
+
export default defineToolkit({
|
|
704
|
+
get_weather: { execute: async () => 1, render: () => null },
|
|
705
|
+
search: { execute: async () => 1, render: () => null },
|
|
706
|
+
});`,
|
|
707
|
+
"tools/database.tsx": `"use generative";
|
|
708
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
709
|
+
export default defineToolkit({
|
|
710
|
+
query_db: { execute: async () => 1, render: () => null },
|
|
711
|
+
});`,
|
|
712
|
+
"tools/calendar.tsx": `"use generative";
|
|
713
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
714
|
+
export default defineToolkit({
|
|
715
|
+
search: { execute: async () => 1, render: () => null },
|
|
716
|
+
});`,
|
|
717
|
+
"tools/email.tsx": `"use generative";
|
|
718
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
719
|
+
export default defineToolkit({
|
|
720
|
+
send_email: { execute: async () => 1, render: () => null },
|
|
721
|
+
});`,
|
|
722
|
+
});
|
|
723
|
+
const src = `"use generative";
|
|
724
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
725
|
+
import weatherTools from "./tools/weather";
|
|
726
|
+
import databaseTools from "./tools/database";
|
|
727
|
+
import calendarTools from "./tools/calendar";
|
|
728
|
+
import emailTools from "./tools/email";
|
|
729
|
+
export default defineToolkit({
|
|
730
|
+
...weatherTools,
|
|
731
|
+
...databaseTools,
|
|
732
|
+
...calendarTools,
|
|
733
|
+
...emailTools,
|
|
734
|
+
});`;
|
|
735
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
736
|
+
|
|
737
|
+
try {
|
|
738
|
+
const client = compileGenerative(src, {
|
|
739
|
+
target: "client",
|
|
740
|
+
filename,
|
|
741
|
+
}).code;
|
|
742
|
+
|
|
743
|
+
expect(client).toContain("...weatherTools");
|
|
744
|
+
expect(client).toContain("...databaseTools");
|
|
745
|
+
expect(client).toContain("...calendarTools");
|
|
746
|
+
expect(client).toContain("...emailTools");
|
|
747
|
+
expect(warn).toHaveBeenCalledWith(
|
|
748
|
+
expect.stringContaining("[assistant-ui/use-generative]"),
|
|
749
|
+
);
|
|
750
|
+
expect(warn).toHaveBeenCalledWith(
|
|
751
|
+
expect.stringContaining(
|
|
752
|
+
'Duplicate tool name "search" while composing toolkits. ' +
|
|
753
|
+
"JavaScript object spread keeps the last definition.",
|
|
754
|
+
),
|
|
755
|
+
);
|
|
756
|
+
} finally {
|
|
757
|
+
warn.mockRestore();
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
it("does not reuse imported toolkit names across compile calls", () => {
|
|
762
|
+
const filename = createMultiMergeFixture({
|
|
763
|
+
"tools/weather.tsx": `"use generative";
|
|
764
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
765
|
+
export default defineToolkit({
|
|
766
|
+
get_weather: { execute: async () => 1, render: () => null },
|
|
767
|
+
});`,
|
|
768
|
+
"tools/calendar.tsx": `"use generative";
|
|
769
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
770
|
+
export default defineToolkit({
|
|
771
|
+
search: { execute: async () => 1, render: () => null },
|
|
772
|
+
});`,
|
|
773
|
+
});
|
|
774
|
+
const src = `"use generative";
|
|
775
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
776
|
+
import weatherTools from "./tools/weather";
|
|
777
|
+
import calendarTools from "./tools/calendar";
|
|
778
|
+
export default defineToolkit({
|
|
779
|
+
...weatherTools,
|
|
780
|
+
...calendarTools,
|
|
781
|
+
});`;
|
|
782
|
+
|
|
783
|
+
compileGenerative(src, { target: "client", filename });
|
|
784
|
+
writeFileSync(
|
|
785
|
+
nodePath.join(nodePath.dirname(filename), "tools", "weather.tsx"),
|
|
786
|
+
`"use generative";
|
|
787
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
788
|
+
export default defineToolkit({
|
|
789
|
+
search: { execute: async () => 1, render: () => null },
|
|
790
|
+
});`,
|
|
791
|
+
);
|
|
792
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
793
|
+
|
|
794
|
+
try {
|
|
795
|
+
compileGenerative(src, { target: "client", filename });
|
|
796
|
+
|
|
797
|
+
expect(warn).toHaveBeenCalledWith(
|
|
798
|
+
expect.stringContaining(
|
|
799
|
+
'Duplicate tool name "search" while composing toolkits. ' +
|
|
800
|
+
"JavaScript object spread keeps the last definition.",
|
|
801
|
+
),
|
|
802
|
+
);
|
|
803
|
+
} finally {
|
|
804
|
+
warn.mockRestore();
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
it("warns about duplicate tool names between local toolkit spreads", () => {
|
|
809
|
+
const src = `"use generative";
|
|
810
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
811
|
+
const weatherTools = defineToolkit({
|
|
812
|
+
get_weather: { execute: async () => 1, render: () => null },
|
|
813
|
+
search: { execute: async () => 1, render: () => null },
|
|
814
|
+
});
|
|
815
|
+
const databaseTools = defineToolkit({
|
|
816
|
+
query_db: { execute: async () => 1, render: () => null },
|
|
817
|
+
});
|
|
818
|
+
const calendarTools = defineToolkit({
|
|
819
|
+
search: { execute: async () => 1, render: () => null },
|
|
820
|
+
});
|
|
821
|
+
const emailTools = defineToolkit({
|
|
822
|
+
send_email: { execute: async () => 1, render: () => null },
|
|
823
|
+
});
|
|
824
|
+
export default defineToolkit({
|
|
825
|
+
...weatherTools,
|
|
826
|
+
...databaseTools,
|
|
827
|
+
...calendarTools,
|
|
828
|
+
...emailTools,
|
|
829
|
+
});`;
|
|
830
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
831
|
+
|
|
832
|
+
try {
|
|
833
|
+
const client = compileGenerative(src, { target: "client" }).code;
|
|
834
|
+
|
|
835
|
+
expect(client).toContain("...weatherTools");
|
|
836
|
+
expect(client).toContain("...databaseTools");
|
|
837
|
+
expect(client).toContain("...calendarTools");
|
|
838
|
+
expect(client).toContain("...emailTools");
|
|
839
|
+
expect(warn).toHaveBeenCalledWith(
|
|
840
|
+
expect.stringContaining(
|
|
841
|
+
'[assistant-ui/use-generative] Duplicate tool name "search" while composing toolkits. ' +
|
|
842
|
+
"JavaScript object spread keeps the last definition.",
|
|
843
|
+
),
|
|
844
|
+
);
|
|
845
|
+
} finally {
|
|
846
|
+
warn.mockRestore();
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
it("warns once across server and client compiles", () => {
|
|
851
|
+
const src = `"use generative";
|
|
852
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
853
|
+
const weatherTools = defineToolkit({
|
|
854
|
+
search: { execute: async () => 1, render: () => null },
|
|
855
|
+
});
|
|
856
|
+
const calendarTools = defineToolkit({
|
|
857
|
+
search: { execute: async () => 1, render: () => null },
|
|
858
|
+
});
|
|
859
|
+
export default defineToolkit({
|
|
860
|
+
...weatherTools,
|
|
861
|
+
...calendarTools,
|
|
862
|
+
});`;
|
|
863
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
864
|
+
|
|
865
|
+
try {
|
|
866
|
+
compileGenerative(src, { target: "server" });
|
|
867
|
+
compileGenerative(src, { target: "client" });
|
|
868
|
+
|
|
869
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
870
|
+
expect(warn).toHaveBeenCalledWith(
|
|
871
|
+
expect.stringContaining(
|
|
872
|
+
'Duplicate tool name "search" while composing toolkits. ' +
|
|
873
|
+
"JavaScript object spread keeps the last definition.",
|
|
874
|
+
),
|
|
875
|
+
);
|
|
876
|
+
} finally {
|
|
877
|
+
warn.mockRestore();
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
it("warns about duplicate names through nested local toolkit spreads", () => {
|
|
882
|
+
const src = `"use generative";
|
|
883
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
884
|
+
const baseTools = defineToolkit({
|
|
885
|
+
search: { execute: async () => 1, render: () => null },
|
|
886
|
+
});
|
|
887
|
+
const weatherTools = defineToolkit({
|
|
888
|
+
...baseTools,
|
|
889
|
+
get_weather: { execute: async () => 1, render: () => null },
|
|
890
|
+
});
|
|
891
|
+
const calendarTools = defineToolkit({
|
|
892
|
+
search: { execute: async () => 1, render: () => null },
|
|
893
|
+
});
|
|
894
|
+
export default defineToolkit({
|
|
895
|
+
...weatherTools,
|
|
896
|
+
...calendarTools,
|
|
897
|
+
});`;
|
|
898
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
899
|
+
|
|
900
|
+
try {
|
|
901
|
+
compileGenerative(src, { target: "client" });
|
|
902
|
+
|
|
903
|
+
expect(warn).toHaveBeenCalledWith(
|
|
904
|
+
expect.stringContaining(
|
|
905
|
+
'Duplicate tool name "search" while composing toolkits. ' +
|
|
906
|
+
"JavaScript object spread keeps the last definition.",
|
|
907
|
+
),
|
|
908
|
+
);
|
|
909
|
+
} finally {
|
|
910
|
+
warn.mockRestore();
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
it("does not warn again when spreading a fragment with an internal duplicate", () => {
|
|
915
|
+
const src = `"use generative";
|
|
916
|
+
import { defineToolkit } from "@assistant-ui/react";
|
|
917
|
+
const baseTools = defineToolkit({
|
|
918
|
+
search: { execute: async () => 1, render: () => null },
|
|
919
|
+
search: { execute: async () => 2, render: () => null },
|
|
920
|
+
});
|
|
921
|
+
export default defineToolkit({
|
|
922
|
+
...baseTools,
|
|
923
|
+
});`;
|
|
924
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
925
|
+
|
|
926
|
+
try {
|
|
927
|
+
compileGenerative(src, { target: "client" });
|
|
928
|
+
|
|
929
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
930
|
+
expect(warn).toHaveBeenCalledWith(
|
|
931
|
+
expect.stringContaining(
|
|
932
|
+
'Duplicate tool name "search" while composing toolkits. ' +
|
|
933
|
+
"JavaScript object spread keeps the last definition.",
|
|
934
|
+
),
|
|
935
|
+
);
|
|
936
|
+
} finally {
|
|
937
|
+
warn.mockRestore();
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
|
|
562
941
|
it("rejects spreading the default import of a non-generative module", () => {
|
|
563
942
|
const filename = createMergeFixture(
|
|
564
943
|
`export default { get_weather: { execute: async () => 1 } };\n`,
|
|
@@ -830,7 +1209,16 @@ export default defineToolkit({
|
|
|
830
1209
|
`"use generative";\nimport { defineToolkit, externalTool } from "@assistant-ui/react";\nexport default defineToolkit({ search: { execute: externalTool() } });`,
|
|
831
1210
|
{ target: "client" },
|
|
832
1211
|
),
|
|
833
|
-
).toThrow(/external tool must declare a `render` or `renderText`/);
|
|
1212
|
+
).toThrow(/external tool "search" must declare a `render` or `renderText`/);
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
it("falls back to an unnamed external tool diagnostic for computed keys", () => {
|
|
1216
|
+
expect(() =>
|
|
1217
|
+
compileGenerative(
|
|
1218
|
+
`"use generative";\nimport { defineToolkit, externalTool } from "@assistant-ui/react";\nconst search = "search";\nexport default defineToolkit({ [search]: { execute: externalTool() } });`,
|
|
1219
|
+
{ target: "client" },
|
|
1220
|
+
),
|
|
1221
|
+
).toThrow(/an external tool must declare a `render` or `renderText`/);
|
|
834
1222
|
});
|
|
835
1223
|
});
|
|
836
1224
|
|
|
@@ -956,7 +1344,7 @@ export default { weather: { execute: async () => 1, render: () => null } };`;
|
|
|
956
1344
|
`"use generative";\nimport { defineToolkit } from "@assistant-ui/react";\nexport default defineToolkit({ weather: makeTool() });`,
|
|
957
1345
|
{ target: "server" },
|
|
958
1346
|
),
|
|
959
|
-
).toThrow(/
|
|
1347
|
+
).toThrow(/tool "weather" cannot be `makeTool\(\)`/);
|
|
960
1348
|
});
|
|
961
1349
|
|
|
962
1350
|
it("requires a render for human tools", () => {
|
|
@@ -965,7 +1353,16 @@ export default { weather: { execute: async () => 1, render: () => null } };`;
|
|
|
965
1353
|
`"use generative";\nimport { defineToolkit, humanTool } from "@assistant-ui/react";\nexport default defineToolkit({ ask: { execute: humanTool() } });`,
|
|
966
1354
|
{ target: "client" },
|
|
967
1355
|
),
|
|
968
|
-
).toThrow(/must declare a `render`/);
|
|
1356
|
+
).toThrow(/human tool "ask" must declare a `render`/);
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
it("falls back to an unnamed human tool diagnostic for computed keys", () => {
|
|
1360
|
+
expect(() =>
|
|
1361
|
+
compileGenerative(
|
|
1362
|
+
`"use generative";\nimport { defineToolkit, humanTool } from "@assistant-ui/react";\nconst ask = "ask";\nexport default defineToolkit({ [ask]: { execute: humanTool() } });`,
|
|
1363
|
+
{ target: "client" },
|
|
1364
|
+
),
|
|
1365
|
+
).toThrow(/a human tool must declare a `render`/);
|
|
969
1366
|
});
|
|
970
1367
|
|
|
971
1368
|
it("requires a render or renderText for frontend tools", () => {
|
|
@@ -974,7 +1371,16 @@ export default { weather: { execute: async () => 1, render: () => null } };`;
|
|
|
974
1371
|
`"use generative";\nimport { defineToolkit } from "@assistant-ui/react";\nexport default defineToolkit({ toast: { execute: async () => { "use client"; return 1; } } });`,
|
|
975
1372
|
{ target: "client" },
|
|
976
1373
|
),
|
|
977
|
-
).toThrow(/must declare a `render` or `renderText`/);
|
|
1374
|
+
).toThrow(/frontend tool "toast" must declare a `render` or `renderText`/);
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
it("falls back to an unnamed frontend tool diagnostic for computed keys", () => {
|
|
1378
|
+
expect(() =>
|
|
1379
|
+
compileGenerative(
|
|
1380
|
+
`"use generative";\nimport { defineToolkit } from "@assistant-ui/react";\nconst toast = "toast";\nexport default defineToolkit({ [toast]: { execute: async () => { "use client"; return 1; } } });`,
|
|
1381
|
+
{ target: "client" },
|
|
1382
|
+
),
|
|
1383
|
+
).toThrow(/a frontend tool must declare a `render` or `renderText`/);
|
|
978
1384
|
});
|
|
979
1385
|
|
|
980
1386
|
it("requires every tool to declare an execute", () => {
|
|
@@ -983,7 +1389,16 @@ export default { weather: { execute: async () => 1, render: () => null } };`;
|
|
|
983
1389
|
`"use generative";\nimport { defineToolkit } from "@assistant-ui/react";\nexport default defineToolkit({ ask: { render: () => null } });`,
|
|
984
1390
|
{ target: "client" },
|
|
985
1391
|
),
|
|
986
|
-
).toThrow(/must declare an `execute`/);
|
|
1392
|
+
).toThrow(/tool "ask" must declare an `execute`/);
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1395
|
+
it("falls back to an unnamed execute diagnostic for computed keys", () => {
|
|
1396
|
+
expect(() =>
|
|
1397
|
+
compileGenerative(
|
|
1398
|
+
`"use generative";\nimport { defineToolkit } from "@assistant-ui/react";\nconst ask = "ask";\nexport default defineToolkit({ [ask]: { render: () => null } });`,
|
|
1399
|
+
{ target: "client" },
|
|
1400
|
+
),
|
|
1401
|
+
).toThrow(/every tool must declare an `execute`/);
|
|
987
1402
|
});
|
|
988
1403
|
|
|
989
1404
|
it("infers `human` from execute: humanTool() and drops it on both builds", () => {
|
|
@@ -1068,17 +1483,34 @@ export default defineToolkit({
|
|
|
1068
1483
|
import { defineToolkit, providerTool } from "@assistant-ui/react";
|
|
1069
1484
|
export default defineToolkit({
|
|
1070
1485
|
web_search: {
|
|
1071
|
-
|
|
1486
|
+
providerId: "openai.web_search_preview",
|
|
1487
|
+
execute: providerTool({
|
|
1488
|
+
providerId: "openai.web_search_preview",
|
|
1489
|
+
args: {},
|
|
1490
|
+
}),
|
|
1491
|
+
},
|
|
1492
|
+
});`;
|
|
1493
|
+
|
|
1494
|
+
expect(() => compileGenerative(src, { target: "server" })).toThrow(
|
|
1495
|
+
/`providerTool\(\.\.\.\)` config for "web_search" duplicates "providerId"/,
|
|
1496
|
+
);
|
|
1497
|
+
});
|
|
1498
|
+
|
|
1499
|
+
it("rejects duplicate providerTool config properties with the duplicated key", () => {
|
|
1500
|
+
const src = `"use generative";
|
|
1501
|
+
import { defineToolkit, providerTool } from "@assistant-ui/react";
|
|
1502
|
+
export default defineToolkit({
|
|
1503
|
+
web_search: {
|
|
1072
1504
|
execute: providerTool({
|
|
1073
1505
|
providerId: "openai.web_search_preview",
|
|
1506
|
+
providerId: "openai.web_search_preview_2",
|
|
1074
1507
|
args: {},
|
|
1075
|
-
render: "duplicate",
|
|
1076
1508
|
}),
|
|
1077
1509
|
},
|
|
1078
1510
|
});`;
|
|
1079
1511
|
|
|
1080
1512
|
expect(() => compileGenerative(src, { target: "server" })).toThrow(
|
|
1081
|
-
|
|
1513
|
+
/`providerTool\(\.\.\.\)` config for "web_search" duplicates "providerId"/,
|
|
1082
1514
|
);
|
|
1083
1515
|
});
|
|
1084
1516
|
|
|
@@ -1097,6 +1529,42 @@ export default defineToolkit({
|
|
|
1097
1529
|
true,
|
|
1098
1530
|
);
|
|
1099
1531
|
expect(isGenerativeModule(`// a comment\n"use generative";\n`)).toBe(true);
|
|
1532
|
+
expect(isGenerativeModule(`"use generative"\nexport default {};`)).toBe(
|
|
1533
|
+
true,
|
|
1534
|
+
);
|
|
1535
|
+
expect(
|
|
1536
|
+
isGenerativeModule(`"use generative" // comment\nexport default {};`),
|
|
1537
|
+
).toBe(true);
|
|
1538
|
+
expect(
|
|
1539
|
+
isGenerativeModule(`"use generative" /* comment */;\nexport default {};`),
|
|
1540
|
+
).toBe(true);
|
|
1100
1541
|
expect(isGenerativeModule(`export default {};`)).toBe(false);
|
|
1542
|
+
expect(
|
|
1543
|
+
isGenerativeModule(`"use generative" + suffix;\nexport default {};`),
|
|
1544
|
+
).toBe(false);
|
|
1545
|
+
expect(
|
|
1546
|
+
isGenerativeModule(`"use generative"\n+ suffix;\nexport default {};`),
|
|
1547
|
+
).toBe(false);
|
|
1548
|
+
expect(
|
|
1549
|
+
isGenerativeModule(`"use generative".toString();\nexport default {};`),
|
|
1550
|
+
).toBe(false);
|
|
1551
|
+
expect(
|
|
1552
|
+
isGenerativeModule(`"use generative"\n.toString();\nexport default {};`),
|
|
1553
|
+
).toBe(false);
|
|
1554
|
+
expect(
|
|
1555
|
+
isGenerativeModule(
|
|
1556
|
+
`"use generative" // comment\n+ suffix;\nexport default {};`,
|
|
1557
|
+
),
|
|
1558
|
+
).toBe(false);
|
|
1559
|
+
expect(
|
|
1560
|
+
isGenerativeModule(
|
|
1561
|
+
`"use generative" /* no line break */ + suffix;\nexport default {};`,
|
|
1562
|
+
),
|
|
1563
|
+
).toBe(false);
|
|
1564
|
+
expect(
|
|
1565
|
+
isGenerativeModule(
|
|
1566
|
+
`"use generative" /* first line\nsecond line */ + suffix;\nexport default {};`,
|
|
1567
|
+
),
|
|
1568
|
+
).toBe(false);
|
|
1101
1569
|
});
|
|
1102
1570
|
});
|