@assistant-ui/x-generative-compiler 0.0.5 → 0.0.6
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 +247 -36
- package/dist/compile.js.map +1 -1
- package/dist/package.js +1 -1
- package/package.json +9 -9
- package/src/compile.test.ts +390 -4
- package/src/compile.ts +397 -36
package/src/compile.ts
CHANGED
|
@@ -43,6 +43,13 @@ const DISTRIBUTION_PACKAGES = [
|
|
|
43
43
|
* through untouched — the library, not this compiler, routes its halves.
|
|
44
44
|
*/
|
|
45
45
|
const GENERATIVE_FACTORY = "JSONGenerativeUI";
|
|
46
|
+
/**
|
|
47
|
+
* The factory producing an interactable's complete tool entry. Unlike
|
|
48
|
+
* `JSONGenerativeUI`, its package has no per-target builds, so this compiler
|
|
49
|
+
* splits the inline config: the client keeps `render`, the server drops it.
|
|
50
|
+
* The factory's own `execute` is internal and client-safe (frontend tool).
|
|
51
|
+
*/
|
|
52
|
+
const INTERACTABLE_TOOL_FACTORY = "unstable_interactableTool";
|
|
46
53
|
|
|
47
54
|
/** Mutable per-build outcomes the toolkit pass reports back for directive/guard injection. */
|
|
48
55
|
interface TargetFlags {
|
|
@@ -78,7 +85,9 @@ export interface CompileResult {
|
|
|
78
85
|
/** Thrown when a `"use generative"` file violates an authoring constraint. */
|
|
79
86
|
export class GenerativeCompileError extends Error {
|
|
80
87
|
constructor(message: string, filename?: string) {
|
|
81
|
-
super(
|
|
88
|
+
super(
|
|
89
|
+
`[assistant-ui/use-generative]${filename ? ` ${filename}:` : ""} ${message}`,
|
|
90
|
+
);
|
|
82
91
|
this.name = "GenerativeCompileError";
|
|
83
92
|
}
|
|
84
93
|
}
|
|
@@ -103,11 +112,126 @@ export function isGenerativeModule(code: string): boolean {
|
|
|
103
112
|
break;
|
|
104
113
|
}
|
|
105
114
|
}
|
|
115
|
+
|
|
116
|
+
const quote = code[i];
|
|
117
|
+
if (quote !== '"' && quote !== "'") return false;
|
|
118
|
+
|
|
119
|
+
const directiveStart = i + 1;
|
|
120
|
+
if (!code.startsWith(DIRECTIVE, directiveStart)) return false;
|
|
121
|
+
|
|
122
|
+
const directiveEnd = directiveStart + DIRECTIVE.length;
|
|
123
|
+
if (code[directiveEnd] !== quote) return false;
|
|
124
|
+
|
|
125
|
+
return hasDirectiveTerminator(code, directiveEnd + 1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function hasDirectiveTerminator(code: string, start: number): boolean {
|
|
129
|
+
let i = start;
|
|
130
|
+
let sawLineTerminator = false;
|
|
131
|
+
for (;;) {
|
|
132
|
+
if (i >= code.length) return true;
|
|
133
|
+
|
|
134
|
+
const char = code.charCodeAt(i);
|
|
135
|
+
if (isSemicolon(char)) return true;
|
|
136
|
+
if (isLineTerminator(char)) {
|
|
137
|
+
sawLineTerminator = true;
|
|
138
|
+
i++;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (code.startsWith("//", i)) {
|
|
143
|
+
const lineEnd = nextLineTerminatorIndex(code, i + 2);
|
|
144
|
+
if (lineEnd === -1) return true;
|
|
145
|
+
sawLineTerminator = true;
|
|
146
|
+
i = lineEnd + 1;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (code.startsWith("/*", i)) {
|
|
151
|
+
const end = code.indexOf("*/", i + 2);
|
|
152
|
+
if (end === -1) return false;
|
|
153
|
+
sawLineTerminator ||= containsLineTerminator(code, i + 2, end);
|
|
154
|
+
i = end + 2;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (/\s/.test(code[i]!)) {
|
|
159
|
+
i++;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return sawLineTerminator && !startsExpressionContinuation(code, i);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function nextLineTerminatorIndex(code: string, start: number): number {
|
|
168
|
+
for (let i = start; i < code.length; i++) {
|
|
169
|
+
if (isLineTerminator(code.charCodeAt(i))) return i;
|
|
170
|
+
}
|
|
171
|
+
return -1;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function containsLineTerminator(
|
|
175
|
+
code: string,
|
|
176
|
+
start: number,
|
|
177
|
+
end: number,
|
|
178
|
+
): boolean {
|
|
179
|
+
for (let i = start; i < end; i++) {
|
|
180
|
+
if (isLineTerminator(code.charCodeAt(i))) return true;
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function startsExpressionContinuation(code: string, start: number): boolean {
|
|
186
|
+
switch (code[start]) {
|
|
187
|
+
case ".":
|
|
188
|
+
case "+":
|
|
189
|
+
case "-":
|
|
190
|
+
case "*":
|
|
191
|
+
case "/":
|
|
192
|
+
case "%":
|
|
193
|
+
case "<":
|
|
194
|
+
case ">":
|
|
195
|
+
case "=":
|
|
196
|
+
case "!":
|
|
197
|
+
case "&":
|
|
198
|
+
case "|":
|
|
199
|
+
case "^":
|
|
200
|
+
case "?":
|
|
201
|
+
case ":":
|
|
202
|
+
case ",":
|
|
203
|
+
case "[":
|
|
204
|
+
case "(":
|
|
205
|
+
case "`":
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
106
209
|
return (
|
|
107
|
-
code
|
|
210
|
+
startsKeywordContinuation(code, start, "as") ||
|
|
211
|
+
startsKeywordContinuation(code, start, "in") ||
|
|
212
|
+
startsKeywordContinuation(code, start, "instanceof") ||
|
|
213
|
+
startsKeywordContinuation(code, start, "satisfies")
|
|
108
214
|
);
|
|
109
215
|
}
|
|
110
216
|
|
|
217
|
+
function startsKeywordContinuation(
|
|
218
|
+
code: string,
|
|
219
|
+
start: number,
|
|
220
|
+
keyword: string,
|
|
221
|
+
): boolean {
|
|
222
|
+
if (!code.startsWith(keyword, start)) return false;
|
|
223
|
+
const next = code[start + keyword.length];
|
|
224
|
+
return next === undefined || !/[\p{ID_Continue}$]/u.test(next);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function isSemicolon(char: number): boolean {
|
|
228
|
+
return char === 59;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function isLineTerminator(char: number): boolean {
|
|
232
|
+
return char === 10 || char === 13 || char === 0x2028 || char === 0x2029;
|
|
233
|
+
}
|
|
234
|
+
|
|
111
235
|
/**
|
|
112
236
|
* Rewrites a `"use generative"` module for a single build target, keeping only the
|
|
113
237
|
* regions that target needs and pruning the imports the dropped regions used.
|
|
@@ -140,7 +264,12 @@ export function compileGenerative(
|
|
|
140
264
|
// such entries pass through.
|
|
141
265
|
ensureDefaultExport(ast, filename);
|
|
142
266
|
const generativeInstances = collectGenerativeInstances(ast);
|
|
143
|
-
const
|
|
267
|
+
const interactableToolImports = collectInteractableToolImports(ast);
|
|
268
|
+
const toolkitSpreadNames = collectToolkitSpreadNames(
|
|
269
|
+
ast,
|
|
270
|
+
filename,
|
|
271
|
+
createToolkitNameContext(),
|
|
272
|
+
);
|
|
144
273
|
|
|
145
274
|
const flags: TargetFlags = { keptRender: false, keptBackendExecute: false };
|
|
146
275
|
|
|
@@ -177,7 +306,8 @@ export function compileGenerative(
|
|
|
177
306
|
object,
|
|
178
307
|
target,
|
|
179
308
|
generativeInstances,
|
|
180
|
-
|
|
309
|
+
interactableToolImports,
|
|
310
|
+
toolkitSpreadNames,
|
|
181
311
|
flags,
|
|
182
312
|
filename,
|
|
183
313
|
);
|
|
@@ -486,8 +616,63 @@ function collectGenerativeInstances(ast: t.File): Set<string> {
|
|
|
486
616
|
return names;
|
|
487
617
|
}
|
|
488
618
|
|
|
619
|
+
type ToolkitStaticNames = readonly string[] | null;
|
|
620
|
+
type ToolkitSpreadNames = Map<string, ToolkitStaticNames>;
|
|
621
|
+
|
|
622
|
+
interface ToolkitNameContext {
|
|
623
|
+
importedToolkitNamesByFile: Map<string, ToolkitStaticNames | undefined>;
|
|
624
|
+
resolvingImportedToolkitNames: Set<string>;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Collects the local names `unstable_interactableTool` is imported under from a
|
|
629
|
+
* distribution package, so toolkit entries calling it can be recognized
|
|
630
|
+
* (and a same-named local function can't smuggle an arbitrary call through).
|
|
631
|
+
*/
|
|
632
|
+
function collectInteractableToolImports(ast: t.File): Set<string> {
|
|
633
|
+
const names = new Set<string>();
|
|
634
|
+
for (const statement of ast.program.body) {
|
|
635
|
+
if (!t.isImportDeclaration(statement)) continue;
|
|
636
|
+
if (!packageNameFromSpecifier(statement.source.value)) continue;
|
|
637
|
+
for (const specifier of statement.specifiers) {
|
|
638
|
+
if (
|
|
639
|
+
t.isImportSpecifier(specifier) &&
|
|
640
|
+
t.isIdentifier(specifier.imported, { name: INTERACTABLE_TOOL_FACTORY })
|
|
641
|
+
) {
|
|
642
|
+
names.add(specifier.local.name);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
return names;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function createToolkitNameContext(): ToolkitNameContext {
|
|
650
|
+
return {
|
|
651
|
+
importedToolkitNamesByFile: new Map(),
|
|
652
|
+
resolvingImportedToolkitNames: new Set(),
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
489
656
|
/**
|
|
490
|
-
*
|
|
657
|
+
* The inline config of an `unstable_interactableTool({ ... })` toolkit entry, or `null`
|
|
658
|
+
* when the entry is some other expression.
|
|
659
|
+
*/
|
|
660
|
+
function interactableToolConfig(
|
|
661
|
+
value: t.Node,
|
|
662
|
+
imports: Set<string>,
|
|
663
|
+
): t.ObjectExpression | null {
|
|
664
|
+
return t.isCallExpression(value) &&
|
|
665
|
+
t.isIdentifier(value.callee) &&
|
|
666
|
+
imports.has(value.callee.name) &&
|
|
667
|
+
t.isObjectExpression(value.arguments[0])
|
|
668
|
+
? value.arguments[0]
|
|
669
|
+
: null;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Toolkit identifiers that are safe to spread into a `defineToolkit({ ... })`,
|
|
674
|
+
* paired with the static tool names they contain. A `null` name list means the
|
|
675
|
+
* spread is safe, but its names are not statically known for duplicate checks.
|
|
491
676
|
*
|
|
492
677
|
* Two kinds qualify:
|
|
493
678
|
*
|
|
@@ -500,19 +685,21 @@ function collectGenerativeInstances(ast: t.File): Set<string> {
|
|
|
500
685
|
* export crosses the generative-module boundary, so named imports don't
|
|
501
686
|
* qualify — they would be `undefined` once that module is build-split.
|
|
502
687
|
*/
|
|
503
|
-
function
|
|
688
|
+
function collectToolkitSpreadNames(
|
|
504
689
|
ast: t.File,
|
|
505
690
|
filename: string | undefined,
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
const
|
|
691
|
+
context: ToolkitNameContext,
|
|
692
|
+
): ToolkitSpreadNames {
|
|
693
|
+
const spreadNames: ToolkitSpreadNames = new Map();
|
|
694
|
+
const localToolkitCalls = new Map<string, t.CallExpression>();
|
|
509
695
|
|
|
510
696
|
for (const statement of ast.program.body) {
|
|
511
697
|
if (t.isVariableDeclaration(statement)) {
|
|
512
698
|
for (const declaration of statement.declarations) {
|
|
513
699
|
const { id, init } = declaration;
|
|
514
|
-
if (t.isIdentifier(id) && init
|
|
515
|
-
|
|
700
|
+
if (t.isIdentifier(id) && init) {
|
|
701
|
+
const toolkitCall = unwrapToToolkitCall(init);
|
|
702
|
+
if (toolkitCall) localToolkitCalls.set(id.name, toolkitCall);
|
|
516
703
|
}
|
|
517
704
|
}
|
|
518
705
|
continue;
|
|
@@ -525,17 +712,40 @@ function collectSafeToolkitSpreads(
|
|
|
525
712
|
);
|
|
526
713
|
if (!defaultSpecifier) continue;
|
|
527
714
|
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
715
|
+
const names = getGenerativeImportToolkitNames(
|
|
716
|
+
statement.source.value,
|
|
717
|
+
filename,
|
|
718
|
+
context,
|
|
719
|
+
);
|
|
720
|
+
if (names !== undefined) {
|
|
721
|
+
spreadNames.set(defaultSpecifier.local.name, names);
|
|
533
722
|
}
|
|
534
|
-
if (isGenerative) names.add(defaultSpecifier.local.name);
|
|
535
723
|
}
|
|
536
724
|
}
|
|
537
725
|
|
|
538
|
-
|
|
726
|
+
const resolveLocal = (name: string): ToolkitStaticNames | undefined => {
|
|
727
|
+
if (spreadNames.has(name)) return spreadNames.get(name);
|
|
728
|
+
|
|
729
|
+
const call = localToolkitCalls.get(name);
|
|
730
|
+
if (!call) return undefined;
|
|
731
|
+
|
|
732
|
+
const object = t.isObjectExpression(call.arguments[0])
|
|
733
|
+
? call.arguments[0]
|
|
734
|
+
: null;
|
|
735
|
+
const names = object
|
|
736
|
+
? collectToolkitObjectNames(object, spreadNames)
|
|
737
|
+
: null;
|
|
738
|
+
const publicNames = uniqueToolkitNames(names);
|
|
739
|
+
|
|
740
|
+
spreadNames.set(name, publicNames);
|
|
741
|
+
return publicNames;
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
for (const name of localToolkitCalls.keys()) {
|
|
745
|
+
resolveLocal(name);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return spreadNames;
|
|
539
749
|
}
|
|
540
750
|
|
|
541
751
|
const MODULE_EXTENSIONS = [
|
|
@@ -553,26 +763,78 @@ const MODULE_EXTENSIONS = [
|
|
|
553
763
|
const REWRITABLE_JS_EXTENSIONS = new Set([".js", ".jsx", ".mjs", ".cjs"]);
|
|
554
764
|
|
|
555
765
|
/**
|
|
556
|
-
*
|
|
557
|
-
* Relative specifiers and `tsconfig` path aliases
|
|
558
|
-
* anything else (a bare package, an unresolvable
|
|
559
|
-
* non-generative, and thus an unsafe spread.
|
|
766
|
+
* Reads the static tool names from the default export of an imported
|
|
767
|
+
* `"use generative"` module. Relative specifiers and `tsconfig` path aliases
|
|
768
|
+
* (e.g. `@/tools`) are resolved; anything else (a bare package, an unresolvable
|
|
769
|
+
* alias) is treated as non-generative, and thus an unsafe spread.
|
|
560
770
|
*/
|
|
561
|
-
function
|
|
771
|
+
function getGenerativeImportToolkitNames(
|
|
562
772
|
source: string,
|
|
563
773
|
filename: string | undefined,
|
|
564
|
-
|
|
774
|
+
context: ToolkitNameContext,
|
|
775
|
+
): ToolkitStaticNames | undefined {
|
|
565
776
|
const cleanFilename = cleanAbsoluteFilename(filename);
|
|
566
|
-
if (!cleanFilename) return
|
|
777
|
+
if (!cleanFilename) return undefined;
|
|
567
778
|
|
|
568
779
|
const resolved = resolveImportedModuleFile(source, cleanFilename);
|
|
569
|
-
if (!resolved) return
|
|
780
|
+
if (!resolved) return undefined;
|
|
570
781
|
|
|
782
|
+
if (context.importedToolkitNamesByFile.has(resolved)) {
|
|
783
|
+
return context.importedToolkitNamesByFile.get(resolved);
|
|
784
|
+
}
|
|
785
|
+
if (context.resolvingImportedToolkitNames.has(resolved)) return null;
|
|
786
|
+
|
|
787
|
+
let code: string;
|
|
571
788
|
try {
|
|
572
|
-
|
|
789
|
+
code = readFileSync(resolved, "utf8");
|
|
573
790
|
} catch {
|
|
574
|
-
|
|
791
|
+
context.importedToolkitNamesByFile.set(resolved, undefined);
|
|
792
|
+
return undefined;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (!isGenerativeModule(code)) {
|
|
796
|
+
context.importedToolkitNamesByFile.set(resolved, undefined);
|
|
797
|
+
return undefined;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
context.resolvingImportedToolkitNames.add(resolved);
|
|
801
|
+
try {
|
|
802
|
+
const importedAst = parse(code, {
|
|
803
|
+
sourceType: "module",
|
|
804
|
+
plugins: ["typescript", "jsx", "explicitResourceManagement"],
|
|
805
|
+
});
|
|
806
|
+
const names = getDefaultExportToolkitNames(importedAst, resolved, context);
|
|
807
|
+
context.importedToolkitNamesByFile.set(resolved, names);
|
|
808
|
+
return names;
|
|
809
|
+
} catch (error) {
|
|
810
|
+
if (error instanceof GenerativeCompileError) throw error;
|
|
811
|
+
context.importedToolkitNamesByFile.set(resolved, null);
|
|
812
|
+
return null;
|
|
813
|
+
} finally {
|
|
814
|
+
context.resolvingImportedToolkitNames.delete(resolved);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function getDefaultExportToolkitNames(
|
|
819
|
+
ast: t.File,
|
|
820
|
+
filename: string | undefined,
|
|
821
|
+
context: ToolkitNameContext,
|
|
822
|
+
): ToolkitStaticNames {
|
|
823
|
+
const def = ast.program.body.find(
|
|
824
|
+
(stmt): stmt is t.ExportDefaultDeclaration =>
|
|
825
|
+
t.isExportDefaultDeclaration(stmt),
|
|
826
|
+
);
|
|
827
|
+
if (!def) return null;
|
|
828
|
+
|
|
829
|
+
const toolkitCall = unwrapToToolkitCall(def.declaration);
|
|
830
|
+
if (!toolkitCall || !t.isObjectExpression(toolkitCall.arguments[0])) {
|
|
831
|
+
return null;
|
|
575
832
|
}
|
|
833
|
+
|
|
834
|
+
const spreadNames = collectToolkitSpreadNames(ast, filename, context);
|
|
835
|
+
return uniqueToolkitNames(
|
|
836
|
+
collectToolkitObjectNames(toolkitCall.arguments[0], spreadNames),
|
|
837
|
+
);
|
|
576
838
|
}
|
|
577
839
|
|
|
578
840
|
/** Resolves an import specifier (relative or `tsconfig`-aliased) to a file on disk. */
|
|
@@ -797,12 +1059,90 @@ function unwrapToToolkitCall(node: t.Node): t.CallExpression | null {
|
|
|
797
1059
|
);
|
|
798
1060
|
}
|
|
799
1061
|
|
|
1062
|
+
function collectToolkitObjectNames(
|
|
1063
|
+
object: t.ObjectExpression,
|
|
1064
|
+
toolkitSpreadNames: ToolkitSpreadNames,
|
|
1065
|
+
): ToolkitStaticNames {
|
|
1066
|
+
const names: string[] = [];
|
|
1067
|
+
|
|
1068
|
+
for (const entry of object.properties) {
|
|
1069
|
+
const entryNames = toolkitEntryNames(entry, toolkitSpreadNames);
|
|
1070
|
+
if (!entryNames) return null;
|
|
1071
|
+
names.push(...entryNames);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
return names;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
function uniqueToolkitNames(names: ToolkitStaticNames): ToolkitStaticNames {
|
|
1078
|
+
return names ? [...new Set(names)] : names;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
function toolkitEntryNames(
|
|
1082
|
+
entry: t.ObjectExpression["properties"][number],
|
|
1083
|
+
toolkitSpreadNames: ToolkitSpreadNames,
|
|
1084
|
+
): ToolkitStaticNames {
|
|
1085
|
+
if (t.isSpreadElement(entry)) {
|
|
1086
|
+
if (t.isIdentifier(entry.argument)) {
|
|
1087
|
+
return toolkitSpreadNames.get(entry.argument.name) ?? null;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
const directMcpToolkit = unwrapToCall(entry.argument, MCP_TOOLKIT_WRAPPER);
|
|
1091
|
+
if (
|
|
1092
|
+
directMcpToolkit &&
|
|
1093
|
+
t.isObjectExpression(directMcpToolkit.arguments[0])
|
|
1094
|
+
) {
|
|
1095
|
+
return collectToolkitObjectNames(
|
|
1096
|
+
directMcpToolkit.arguments[0],
|
|
1097
|
+
toolkitSpreadNames,
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
if (t.isObjectProperty(entry) || t.isObjectMethod(entry)) {
|
|
1105
|
+
const name = memberName(entry.key, entry.computed);
|
|
1106
|
+
return name ? [name] : [];
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
return [];
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function warnDuplicateToolkitNames(
|
|
1113
|
+
object: t.ObjectExpression,
|
|
1114
|
+
toolkitSpreadNames: ToolkitSpreadNames,
|
|
1115
|
+
filename: string | undefined,
|
|
1116
|
+
): void {
|
|
1117
|
+
const names = collectToolkitObjectNames(object, toolkitSpreadNames);
|
|
1118
|
+
if (!names) return;
|
|
1119
|
+
|
|
1120
|
+
const seen = new Set<string>();
|
|
1121
|
+
const warned = new Set<string>();
|
|
1122
|
+
for (const name of names) {
|
|
1123
|
+
if (seen.has(name)) {
|
|
1124
|
+
if (!warned.has(name)) {
|
|
1125
|
+
console.warn(
|
|
1126
|
+
new GenerativeCompileError(
|
|
1127
|
+
`Duplicate tool name "${name}" while composing toolkits. ` +
|
|
1128
|
+
"JavaScript object spread keeps the last definition.",
|
|
1129
|
+
filename,
|
|
1130
|
+
).message,
|
|
1131
|
+
);
|
|
1132
|
+
warned.add(name);
|
|
1133
|
+
}
|
|
1134
|
+
continue;
|
|
1135
|
+
}
|
|
1136
|
+
seen.add(name);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
800
1140
|
function isSafeToolkitSpread(
|
|
801
1141
|
entry: t.SpreadElement,
|
|
802
|
-
|
|
1142
|
+
toolkitSpreadNames: ToolkitSpreadNames,
|
|
803
1143
|
): boolean {
|
|
804
1144
|
if (t.isIdentifier(entry.argument)) {
|
|
805
|
-
return
|
|
1145
|
+
return toolkitSpreadNames.has(entry.argument.name);
|
|
806
1146
|
}
|
|
807
1147
|
|
|
808
1148
|
const directMcpToolkit = unwrapToCall(entry.argument, MCP_TOOLKIT_WRAPPER);
|
|
@@ -871,10 +1211,17 @@ function compileToolkit(
|
|
|
871
1211
|
object: t.ObjectExpression,
|
|
872
1212
|
target: Target,
|
|
873
1213
|
instances: Set<string>,
|
|
874
|
-
|
|
1214
|
+
interactableToolImports: Set<string>,
|
|
1215
|
+
toolkitSpreadNames: ToolkitSpreadNames,
|
|
875
1216
|
flags: TargetFlags,
|
|
876
1217
|
filename: string | undefined,
|
|
877
1218
|
): void {
|
|
1219
|
+
// Split builds compile both targets; emit target-independent warnings from
|
|
1220
|
+
// the client pass so each duplicate is logged once.
|
|
1221
|
+
if (target === "client") {
|
|
1222
|
+
warnDuplicateToolkitNames(object, toolkitSpreadNames, filename);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
878
1225
|
const nextProperties: t.ObjectExpression["properties"] = [];
|
|
879
1226
|
|
|
880
1227
|
for (const entry of object.properties) {
|
|
@@ -882,7 +1229,7 @@ function compileToolkit(
|
|
|
882
1229
|
if (!value) {
|
|
883
1230
|
if (
|
|
884
1231
|
t.isSpreadElement(entry) &&
|
|
885
|
-
isSafeToolkitSpread(entry,
|
|
1232
|
+
isSafeToolkitSpread(entry, toolkitSpreadNames)
|
|
886
1233
|
) {
|
|
887
1234
|
nextProperties.push(entry);
|
|
888
1235
|
continue;
|
|
@@ -896,11 +1243,20 @@ function compileToolkit(
|
|
|
896
1243
|
nextProperties.push(entry);
|
|
897
1244
|
continue;
|
|
898
1245
|
}
|
|
1246
|
+
const config =
|
|
1247
|
+
raw && interactableToolConfig(raw, interactableToolImports);
|
|
1248
|
+
if (config) {
|
|
1249
|
+
if (target === "client") flags.keptRender = true;
|
|
1250
|
+
else removeMember(config, "render");
|
|
1251
|
+
nextProperties.push(entry);
|
|
1252
|
+
continue;
|
|
1253
|
+
}
|
|
899
1254
|
throw new GenerativeCompileError(
|
|
900
1255
|
"each tool must be an inline object literal (`name: { ... }`) or a " +
|
|
901
1256
|
"compiler-visible toolkit spread / generative tool (e.g. " +
|
|
902
|
-
"`...defineMcpToolkit(...)`, `...baseToolkit`,
|
|
903
|
-
"`generative.present()`) so its
|
|
1257
|
+
"`...defineMcpToolkit(...)`, `...baseToolkit`, " +
|
|
1258
|
+
"`generative.present()`, or `unstable_interactableTool(...)`) so its " +
|
|
1259
|
+
"`execute` can be routed",
|
|
904
1260
|
filename,
|
|
905
1261
|
);
|
|
906
1262
|
}
|
|
@@ -930,7 +1286,10 @@ function compileToolkit(
|
|
|
930
1286
|
}
|
|
931
1287
|
|
|
932
1288
|
if (type === "provider" && execute) {
|
|
933
|
-
|
|
1289
|
+
const toolName = t.isObjectProperty(entry)
|
|
1290
|
+
? memberName(entry.key, entry.computed)
|
|
1291
|
+
: undefined;
|
|
1292
|
+
applyProviderToolConfig(value, execute, toolName, filename);
|
|
934
1293
|
}
|
|
935
1294
|
|
|
936
1295
|
if (isExternal) {
|
|
@@ -972,6 +1331,7 @@ function compileToolkit(
|
|
|
972
1331
|
function applyProviderToolConfig(
|
|
973
1332
|
object: t.ObjectExpression,
|
|
974
1333
|
execute: t.ObjectProperty | t.ObjectMethod,
|
|
1334
|
+
toolName: string | undefined,
|
|
975
1335
|
filename: string | undefined,
|
|
976
1336
|
): void {
|
|
977
1337
|
if (
|
|
@@ -1019,8 +1379,9 @@ function applyProviderToolConfig(
|
|
|
1019
1379
|
);
|
|
1020
1380
|
}
|
|
1021
1381
|
if (existingNames.has(name) || configNames.has(name)) {
|
|
1382
|
+
const toolLabel = toolName ? ` for "${toolName}"` : "";
|
|
1022
1383
|
throw new GenerativeCompileError(
|
|
1023
|
-
|
|
1384
|
+
`\`providerTool(...)\` config${toolLabel} duplicates "${name}"`,
|
|
1024
1385
|
filename,
|
|
1025
1386
|
);
|
|
1026
1387
|
}
|