@assistant-ui/next 0.0.1 → 0.0.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 AgentbaseAI Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -33,7 +33,7 @@ by opting in with `"use client"`, so secrets can't leak by omission.
33
33
  ```tsx
34
34
  "use generative";
35
35
  import { z } from "zod";
36
- import { defineToolkit } from "@assistant-ui/react";
36
+ import { defineToolkit } from "@assistant-ui/next";
37
37
  import { db } from "@/db"; // server-only
38
38
  import { Chart } from "@/ui/chart"; // client-only
39
39
 
package/SPEC.md CHANGED
@@ -28,7 +28,7 @@ A module opts in with a leading directive and a single default export wrapped in
28
28
  ```tsx
29
29
  "use generative";
30
30
  import { z } from "zod";
31
- import { defineToolkit } from "@assistant-ui/react";
31
+ import { defineToolkit } from "@assistant-ui/next";
32
32
  import { db } from "@/db"; // server-only dependency
33
33
  import { Chart } from "@/ui/chart"; // client-only dependency
34
34
 
@@ -0,0 +1,30 @@
1
+ import { Target } from "./constants.js";
2
+
3
+ //#region src/compile.d.ts
4
+ type ToolType = "frontend" | "backend" | "human";
5
+ interface CompileOptions {
6
+ /** Which build target to emit. */
7
+ target: Target;
8
+ /** Source filename, used for error messages and source maps. */
9
+ filename?: string;
10
+ /** Emit a source map alongside the code. */
11
+ sourceMaps?: boolean;
12
+ }
13
+ interface CompileResult {
14
+ code: string;
15
+ map?: object | null;
16
+ }
17
+ /** Thrown when a `"use generative"` file violates an authoring constraint. */
18
+ declare class GenerativeCompileError extends Error {
19
+ constructor(message: string, filename?: string);
20
+ }
21
+ /** Whether a source string opts into generative compilation via the directive. */
22
+ declare function isGenerativeModule(code: string): boolean;
23
+ /**
24
+ * Rewrites a `"use generative"` module for a single build target, keeping only the
25
+ * regions that target needs and pruning the imports the dropped regions used.
26
+ */
27
+ declare function compileGenerative(code: string, options: CompileOptions): CompileResult;
28
+ //#endregion
29
+ export { CompileOptions, CompileResult, GenerativeCompileError, ToolType, compileGenerative, isGenerativeModule };
30
+ //# sourceMappingURL=compile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile.d.ts","names":[],"sources":["../src/compile.ts"],"mappings":";;;KAeY,QAAA;AAAA,UAEK,cAAA;EAFL;EAIV,MAAA,EAAQ,MAAM;;EAEd,QAAA;EANkB;EAQlB,UAAA;AAAA;AAAA,UAGe,aAAA;EACf,IAAA;EACA,GAAG;AAAA;;cAIQ,sBAAA,SAA+B,KAAK;cACnC,OAAA,UAAiB,QAAA;AAAA;AAP/B;AAAA,iBAcgB,kBAAA,CAAmB,IAAY;;;AAZ1C;AAIL;iBAoCgB,iBAAA,CACd,IAAA,UACA,OAAA,EAAS,cAAA,GACR,aAAa"}
@@ -0,0 +1,235 @@
1
+ import { DIRECTIVE } from "./constants.js";
2
+ import { parse } from "@babel/parser";
3
+ import _traverse from "@babel/traverse";
4
+ import _generate from "@babel/generator";
5
+ import * as t from "@babel/types";
6
+ //#region src/compile.ts
7
+ const traverse = typeof _traverse === "function" ? _traverse : _traverse.default;
8
+ const generate = typeof _generate === "function" ? _generate : _generate.default;
9
+ /** Thrown when a `"use generative"` file violates an authoring constraint. */
10
+ var GenerativeCompileError = class extends Error {
11
+ constructor(message, filename) {
12
+ super(`[assistant-ui/next]${filename ? ` ${filename}:` : ""} ${message}`);
13
+ this.name = "GenerativeCompileError";
14
+ }
15
+ };
16
+ /** Whether a source string opts into generative compilation via the directive. */
17
+ function isGenerativeModule(code) {
18
+ let i = code.charCodeAt(0) === 65279 ? 1 : 0;
19
+ for (;;) {
20
+ while (i < code.length && /\s/.test(code[i])) i++;
21
+ if (code.startsWith("//", i)) {
22
+ const nl = code.indexOf("\n", i);
23
+ if (nl === -1) return false;
24
+ i = nl + 1;
25
+ } else if (code.startsWith("/*", i)) {
26
+ const end = code.indexOf("*/", i + 2);
27
+ if (end === -1) return false;
28
+ i = end + 2;
29
+ } else break;
30
+ }
31
+ return code.startsWith(`"use generative"`, i) || code.startsWith(`'use generative'`, i);
32
+ }
33
+ /**
34
+ * Rewrites a `"use generative"` module for a single build target, keeping only the
35
+ * regions that target needs and pruning the imports the dropped regions used.
36
+ */
37
+ function compileGenerative(code, options) {
38
+ const { target, filename } = options;
39
+ const ast = parse(code, {
40
+ sourceType: "module",
41
+ plugins: [
42
+ "typescript",
43
+ "jsx",
44
+ "explicitResourceManagement"
45
+ ]
46
+ });
47
+ if (!ast.program.directives.some((d) => d.value.value === "use generative")) throw new GenerativeCompileError(`missing "${DIRECTIVE}" directive`, filename);
48
+ const object = findDefaultExportObject(ast, filename);
49
+ let keptRender = false;
50
+ let keptBackendExecute = false;
51
+ for (const entry of object.properties) {
52
+ const value = entryValue(entry);
53
+ if (!value) throw new GenerativeCompileError("each tool must be an inline object literal (`name: { ... }`) so its `execute` can be routed", filename);
54
+ const type = inferToolType(value, filename);
55
+ const hasRender = !!findMember(value, "render");
56
+ const execute = findMember(value, "execute");
57
+ if ((type === "frontend" || type === "human") && !hasRender) throw new GenerativeCompileError(`a ${type} tool must declare a \`render\` (it has no server execute to show otherwise)`, filename);
58
+ if (target === "client") {
59
+ if (execute && type === "frontend") stripUseClient(execute);
60
+ else if (execute) removeMember(value, "execute");
61
+ if (hasRender) keptRender = true;
62
+ } else {
63
+ if (hasRender) removeMember(value, "render");
64
+ if (execute && type !== "backend") removeMember(value, "execute");
65
+ if (execute && type === "backend") keptBackendExecute = true;
66
+ }
67
+ setToolType(value, type);
68
+ }
69
+ pruneUnused(ast);
70
+ ast.program.directives = ast.program.directives.filter((d) => d.value.value !== "use generative" && d.value.value !== "use client");
71
+ if (target === "client" && keptRender) ast.program.directives.unshift(t.directive(t.directiveLiteral("use client")));
72
+ if (target === "server" && keptBackendExecute) ast.program.body.unshift(t.importDeclaration([], t.stringLiteral("server-only")));
73
+ const result = generate(ast, {
74
+ sourceMaps: options.sourceMaps ?? false,
75
+ filename,
76
+ jsescOption: { minimal: true }
77
+ }, code);
78
+ return {
79
+ code: result.code,
80
+ map: result.map
81
+ };
82
+ }
83
+ function findDefaultExportObject(ast, filename) {
84
+ let object = null;
85
+ let sawDefault = false;
86
+ for (const stmt of ast.program.body) {
87
+ if (!t.isExportDefaultDeclaration(stmt)) continue;
88
+ sawDefault = true;
89
+ object = unwrapDefineToolkit(stmt.declaration);
90
+ if (object) stmt.declaration = object;
91
+ }
92
+ if (!sawDefault) throw new GenerativeCompileError("missing a default export", filename);
93
+ if (!object) throw new GenerativeCompileError("the default export must be `defineToolkit({ ... })` (imported from \"@assistant-ui/next\"); wrapping is required so a backend `execute` can't be authored in a way that reaches the client", filename);
94
+ return object;
95
+ }
96
+ /**
97
+ * Unwraps the required `defineToolkit({ ... })` wrapper (through `satisfies`/`as`
98
+ * and parens) to the underlying object literal. Anything else — a bare object, a
99
+ * `satisfies Toolkit` without the wrapper, some other call — yields `null` so the
100
+ * caller errors.
101
+ */
102
+ function unwrapDefineToolkit(node) {
103
+ if (t.isTSSatisfiesExpression(node) || t.isTSAsExpression(node)) return unwrapDefineToolkit(node.expression);
104
+ if (t.isParenthesizedExpression(node)) return unwrapDefineToolkit(node.expression);
105
+ if (t.isCallExpression(node) && t.isIdentifier(node.callee, { name: "defineToolkit" }) && t.isObjectExpression(node.arguments[0])) return node.arguments[0];
106
+ return null;
107
+ }
108
+ function entryValue(entry) {
109
+ if (t.isObjectProperty(entry) && t.isObjectExpression(entry.value)) return entry.value;
110
+ return null;
111
+ }
112
+ /** A member of an entry object: `render`/`execute`/`type`, as property or method. */
113
+ function findMember(object, name) {
114
+ return object.properties.find((p) => (t.isObjectProperty(p) || t.isObjectMethod(p)) && memberName(p.key, p.computed) === name);
115
+ }
116
+ function removeMember(object, name) {
117
+ object.properties = object.properties.filter((p) => !((t.isObjectProperty(p) || t.isObjectMethod(p)) && memberName(p.key, p.computed) === name));
118
+ }
119
+ /** The `BlockStatement` body of an `execute` member, if it has one. */
120
+ function executeBody(member) {
121
+ if (t.isObjectMethod(member)) return member.body;
122
+ const value = member.value;
123
+ if ((t.isArrowFunctionExpression(value) || t.isFunctionExpression(value)) && t.isBlockStatement(value.body)) return value.body;
124
+ }
125
+ /** Whether an `execute` opts into the client via a leading `"use client"`. */
126
+ function executeIsClient(member) {
127
+ return !!executeBody(member)?.directives.some((d) => d.value.value === "use client");
128
+ }
129
+ /** Whether an `execute` is the `hitl()` human-in-the-loop sentinel. */
130
+ function executeIsHitl(member) {
131
+ return t.isObjectProperty(member) && t.isCallExpression(member.value) && t.isIdentifier(member.value.callee, { name: "hitl" });
132
+ }
133
+ /** Drops the `"use client"` directive from an `execute` body (kept frontend). */
134
+ function stripUseClient(member) {
135
+ const body = executeBody(member);
136
+ if (body) body.directives = body.directives.filter((d) => d.value.value !== "use client");
137
+ }
138
+ /**
139
+ * The tool's nature, inferred from its (mandatory) `execute` rather than an
140
+ * authored `type`: `hitl()` → `human`; `"use client"` → `frontend`; otherwise
141
+ * `backend`. The loader writes the result back as a `type` field (see
142
+ * {@link setToolType}) so the runtime keeps it.
143
+ */
144
+ function inferToolType(object, filename) {
145
+ const execute = findMember(object, "execute");
146
+ if (!execute) throw new GenerativeCompileError("every tool must declare an `execute`; use `hitl()` for a human-in-the-loop tool", filename);
147
+ if (executeIsHitl(execute)) return "human";
148
+ return executeIsClient(execute) ? "frontend" : "backend";
149
+ }
150
+ /** Writes the resolved `type` back onto the tool object (replacing any author's). */
151
+ function setToolType(object, type) {
152
+ removeMember(object, "type");
153
+ object.properties.push(t.objectProperty(t.identifier("type"), t.stringLiteral(type)));
154
+ }
155
+ function memberName(key, computed) {
156
+ if (computed) return void 0;
157
+ if (t.isIdentifier(key)) return key.name;
158
+ if (t.isStringLiteral(key)) return key.value;
159
+ }
160
+ /**
161
+ * Removes declarations and import specifiers left unreferenced after region
162
+ * removal, to a fixpoint (a dropped helper frees what it used). Keeps exports,
163
+ * side-effect imports, and possibly-side-effectful initializers.
164
+ */
165
+ function pruneUnused(ast) {
166
+ const hadSpecifiers = /* @__PURE__ */ new WeakSet();
167
+ for (const stmt of ast.program.body) if (t.isImportDeclaration(stmt) && stmt.specifiers.length > 0) hadSpecifiers.add(stmt);
168
+ let removedSomething = true;
169
+ while (removedSomething) {
170
+ removedSomething = false;
171
+ traverse(ast, { Program(path) {
172
+ path.scope.crawl();
173
+ const isUnused = (name) => {
174
+ const binding = path.scope.getBinding(name);
175
+ return !!binding && !binding.referenced;
176
+ };
177
+ path.node.body = path.node.body.filter((stmt) => {
178
+ if ((t.isFunctionDeclaration(stmt) || t.isClassDeclaration(stmt)) && stmt.id && isUnused(stmt.id.name)) {
179
+ removedSomething = true;
180
+ return false;
181
+ }
182
+ if (t.isVariableDeclaration(stmt)) {
183
+ stmt.declarations = stmt.declarations.filter((decl) => {
184
+ const names = Object.keys(t.getBindingIdentifiers(decl.id));
185
+ if (names.length > 0 && isPlainPattern(decl.id) && isRemovableInit(decl.init) && names.every(isUnused)) {
186
+ removedSomething = true;
187
+ return false;
188
+ }
189
+ return true;
190
+ });
191
+ if (stmt.declarations.length === 0) return false;
192
+ }
193
+ return true;
194
+ });
195
+ path.stop();
196
+ } });
197
+ }
198
+ traverse(ast, { Program(path) {
199
+ path.scope.crawl();
200
+ for (const stmt of path.node.body) {
201
+ if (!t.isImportDeclaration(stmt)) continue;
202
+ stmt.specifiers = stmt.specifiers.filter((spec) => {
203
+ const binding = path.scope.getBinding(spec.local.name);
204
+ return binding ? binding.referenced : true;
205
+ });
206
+ }
207
+ path.node.body = path.node.body.filter((stmt) => !(t.isImportDeclaration(stmt) && stmt.specifiers.length === 0 && hadSpecifiers.has(stmt)));
208
+ path.stop();
209
+ } });
210
+ }
211
+ /**
212
+ * Whether a binding pattern is side-effect-free to drop: a plain identifier, or
213
+ * an object/array pattern of plain bindings — no default values
214
+ * (`{ a = expr }`) or computed keys (`{ [expr]: a }`), which would evaluate (and
215
+ * thus could have side effects) at destructuring time.
216
+ */
217
+ function isPlainPattern(node) {
218
+ if (t.isIdentifier(node)) return true;
219
+ if (t.isObjectPattern(node)) return node.properties.every((p) => t.isRestElement(p) ? isPlainPattern(p.argument) : !p.computed && isPlainPattern(p.value));
220
+ if (t.isArrayPattern(node)) return node.elements.every((el) => el == null || isPlainPattern(el));
221
+ return false;
222
+ }
223
+ /** Whether a variable initializer is safe to drop (no observable side effects). */
224
+ function isRemovableInit(node) {
225
+ if (node == null) return true;
226
+ if (t.isTSAsExpression(node) || t.isTSSatisfiesExpression(node)) return isRemovableInit(node.expression);
227
+ if (t.isArrayExpression(node)) return node.elements.every((el) => el == null || t.isExpression(el) && isRemovableInit(el));
228
+ if (t.isObjectExpression(node)) return node.properties.every((p) => t.isObjectProperty(p) && !p.computed && t.isExpression(p.value) && isRemovableInit(p.value));
229
+ if (t.isTemplateLiteral(node)) return node.expressions.every((e) => t.isExpression(e) && isRemovableInit(e));
230
+ return t.isArrowFunctionExpression(node) || t.isFunctionExpression(node) || t.isClassExpression(node) || t.isIdentifier(node) || t.isMemberExpression(node) && !node.computed || t.isJSXElement(node) || t.isJSXFragment(node) || t.isLiteral(node);
231
+ }
232
+ //#endregion
233
+ export { GenerativeCompileError, compileGenerative, isGenerativeModule };
234
+
235
+ //# sourceMappingURL=compile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile.js","names":[],"sources":["../src/compile.ts"],"sourcesContent":["import { parse } from \"@babel/parser\";\nimport _traverse, { type NodePath } from \"@babel/traverse\";\nimport _generate from \"@babel/generator\";\nimport * as t from \"@babel/types\";\nimport { DIRECTIVE, type Target } from \"./constants\";\n\n// @babel/traverse and @babel/generator are CJS; their default export is the\n// function itself under some interop and `{ default }` under others.\nconst traverse = (\n typeof _traverse === \"function\" ? _traverse : (_traverse as any).default\n) as typeof _traverse;\nconst generate = (\n typeof _generate === \"function\" ? _generate : (_generate as any).default\n) as typeof _generate;\n\nexport type ToolType = \"frontend\" | \"backend\" | \"human\";\n\nexport interface CompileOptions {\n /** Which build target to emit. */\n target: Target;\n /** Source filename, used for error messages and source maps. */\n filename?: string;\n /** Emit a source map alongside the code. */\n sourceMaps?: boolean;\n}\n\nexport interface CompileResult {\n code: string;\n map?: object | null;\n}\n\n/** Thrown when a `\"use generative\"` file violates an authoring constraint. */\nexport class GenerativeCompileError extends Error {\n constructor(message: string, filename?: string) {\n super(`[assistant-ui/next]${filename ? ` ${filename}:` : \"\"} ${message}`);\n this.name = \"GenerativeCompileError\";\n }\n}\n\n/** Whether a source string opts into generative compilation via the directive. */\nexport function isGenerativeModule(code: string): boolean {\n // The directive must be a leading statement, preceded only by an optional BOM,\n // whitespace, and line/block comments. Scanned linearly — a regex over the\n // comment prefix is prone to catastrophic backtracking on crafted input.\n let i = code.charCodeAt(0) === 0xfeff ? 1 : 0;\n for (;;) {\n while (i < code.length && /\\s/.test(code[i]!)) i++;\n if (code.startsWith(\"//\", i)) {\n const nl = code.indexOf(\"\\n\", i);\n if (nl === -1) return false;\n i = nl + 1;\n } else if (code.startsWith(\"/*\", i)) {\n const end = code.indexOf(\"*/\", i + 2);\n if (end === -1) return false;\n i = end + 2;\n } else {\n break;\n }\n }\n return (\n code.startsWith(`\"${DIRECTIVE}\"`, i) || code.startsWith(`'${DIRECTIVE}'`, i)\n );\n}\n\n/**\n * Rewrites a `\"use generative\"` module for a single build target, keeping only the\n * regions that target needs and pruning the imports the dropped regions used.\n */\nexport function compileGenerative(\n code: string,\n options: CompileOptions,\n): CompileResult {\n const { target, filename } = options;\n\n const ast = parse(code, {\n sourceType: \"module\",\n plugins: [\"typescript\", \"jsx\", \"explicitResourceManagement\"],\n });\n\n if (!ast.program.directives.some((d) => d.value.value === DIRECTIVE)) {\n throw new GenerativeCompileError(\n `missing \"${DIRECTIVE}\" directive`,\n filename,\n );\n }\n\n const object = findDefaultExportObject(ast, filename);\n\n let keptRender = false;\n let keptBackendExecute = false;\n\n for (const entry of object.properties) {\n const value = entryValue(entry);\n if (!value) {\n // A non-inline tool (spread, method, or a call like `makeTool()`) can't be\n // analyzed, so its `execute` would pass through to the client unstripped.\n throw new GenerativeCompileError(\n \"each tool must be an inline object literal (`name: { ... }`) so its \" +\n \"`execute` can be routed\",\n filename,\n );\n }\n\n // Nature is inferred from `execute` (see inferToolType), not an authored\n // `type`. The resolved type is written back below so the runtime keeps it.\n const type = inferToolType(value, filename);\n const hasRender = !!findMember(value, \"render\");\n const execute = findMember(value, \"execute\");\n\n if ((type === \"frontend\" || type === \"human\") && !hasRender) {\n throw new GenerativeCompileError(\n `a ${type} tool must declare a \\`render\\` (it has no server execute to show otherwise)`,\n filename,\n );\n }\n\n if (target === \"client\") {\n // A frontend execute stays (its `\"use client\"` marker is no longer needed\n // once the module is client); a backend execute and a human `hitl()`\n // sentinel are both dropped.\n if (execute && type === \"frontend\") stripUseClient(execute);\n else if (execute) removeMember(value, \"execute\");\n if (hasRender) keptRender = true;\n } else {\n // server: render is never needed; only a backend execute survives.\n if (hasRender) removeMember(value, \"render\");\n if (execute && type !== \"backend\") removeMember(value, \"execute\");\n if (execute && type === \"backend\") keptBackendExecute = true;\n }\n\n setToolType(value, type);\n }\n\n pruneUnused(ast);\n\n // Replace the module directives with the target-appropriate one.\n ast.program.directives = ast.program.directives.filter(\n (d) => d.value.value !== DIRECTIVE && d.value.value !== \"use client\",\n );\n if (target === \"client\" && keptRender) {\n ast.program.directives.unshift(\n t.directive(t.directiveLiteral(\"use client\")),\n );\n }\n if (target === \"server\" && keptBackendExecute) {\n ast.program.body.unshift(\n t.importDeclaration([], t.stringLiteral(\"server-only\")),\n );\n }\n\n const result = generate(\n ast,\n {\n sourceMaps: options.sourceMaps ?? false,\n filename,\n jsescOption: { minimal: true },\n },\n code,\n );\n\n return { code: result.code, map: result.map };\n}\n\nfunction findDefaultExportObject(\n ast: t.File,\n filename: string | undefined,\n): t.ObjectExpression {\n let object: t.ObjectExpression | null = null;\n let sawDefault = false;\n\n for (const stmt of ast.program.body) {\n if (!t.isExportDefaultDeclaration(stmt)) continue;\n sawDefault = true;\n object = unwrapDefineToolkit(stmt.declaration);\n // Emit the bare object literal, dropping the `defineToolkit(...)` wrapper\n // (and the import it pulled).\n if (object) stmt.declaration = object;\n }\n\n if (!sawDefault) {\n throw new GenerativeCompileError(\"missing a default export\", filename);\n }\n if (!object) {\n throw new GenerativeCompileError(\n \"the default export must be `defineToolkit({ ... })` (imported from \" +\n '\"@assistant-ui/next\"); wrapping is required so a backend `execute` ' +\n \"can't be authored in a way that reaches the client\",\n filename,\n );\n }\n return object;\n}\n\n/**\n * Unwraps the required `defineToolkit({ ... })` wrapper (through `satisfies`/`as`\n * and parens) to the underlying object literal. Anything else — a bare object, a\n * `satisfies Toolkit` without the wrapper, some other call — yields `null` so the\n * caller errors.\n */\nfunction unwrapDefineToolkit(node: t.Node): t.ObjectExpression | null {\n if (t.isTSSatisfiesExpression(node) || t.isTSAsExpression(node)) {\n return unwrapDefineToolkit(node.expression);\n }\n if (t.isParenthesizedExpression(node)) {\n return unwrapDefineToolkit(node.expression);\n }\n if (\n t.isCallExpression(node) &&\n t.isIdentifier(node.callee, { name: \"defineToolkit\" }) &&\n t.isObjectExpression(node.arguments[0])\n ) {\n return node.arguments[0];\n }\n return null;\n}\n\ntype Entry = t.ObjectExpression[\"properties\"][number];\n\nfunction entryValue(entry: Entry): t.ObjectExpression | null {\n if (t.isObjectProperty(entry) && t.isObjectExpression(entry.value)) {\n return entry.value;\n }\n return null;\n}\n\n/** A member of an entry object: `render`/`execute`/`type`, as property or method. */\nfunction findMember(\n object: t.ObjectExpression,\n name: string,\n): t.ObjectProperty | t.ObjectMethod | undefined {\n return object.properties.find(\n (p): p is t.ObjectProperty | t.ObjectMethod =>\n (t.isObjectProperty(p) || t.isObjectMethod(p)) &&\n memberName(p.key, p.computed) === name,\n );\n}\n\nfunction removeMember(object: t.ObjectExpression, name: string): void {\n object.properties = object.properties.filter(\n (p) =>\n !(\n (t.isObjectProperty(p) || t.isObjectMethod(p)) &&\n memberName(p.key, p.computed) === name\n ),\n );\n}\n\n/** The `BlockStatement` body of an `execute` member, if it has one. */\nfunction executeBody(\n member: t.ObjectProperty | t.ObjectMethod,\n): t.BlockStatement | undefined {\n if (t.isObjectMethod(member)) return member.body;\n const value = member.value;\n if (\n (t.isArrowFunctionExpression(value) || t.isFunctionExpression(value)) &&\n t.isBlockStatement(value.body)\n ) {\n return value.body;\n }\n return undefined;\n}\n\n/** Whether an `execute` opts into the client via a leading `\"use client\"`. */\nfunction executeIsClient(member: t.ObjectProperty | t.ObjectMethod): boolean {\n return !!executeBody(member)?.directives.some(\n (d) => d.value.value === \"use client\",\n );\n}\n\n/** Whether an `execute` is the `hitl()` human-in-the-loop sentinel. */\nfunction executeIsHitl(member: t.ObjectProperty | t.ObjectMethod): boolean {\n return (\n t.isObjectProperty(member) &&\n t.isCallExpression(member.value) &&\n t.isIdentifier(member.value.callee, { name: \"hitl\" })\n );\n}\n\n/** Drops the `\"use client\"` directive from an `execute` body (kept frontend). */\nfunction stripUseClient(member: t.ObjectProperty | t.ObjectMethod): void {\n const body = executeBody(member);\n if (body) {\n body.directives = body.directives.filter(\n (d) => d.value.value !== \"use client\",\n );\n }\n}\n\n/**\n * The tool's nature, inferred from its (mandatory) `execute` rather than an\n * authored `type`: `hitl()` → `human`; `\"use client\"` → `frontend`; otherwise\n * `backend`. The loader writes the result back as a `type` field (see\n * {@link setToolType}) so the runtime keeps it.\n */\nfunction inferToolType(\n object: t.ObjectExpression,\n filename: string | undefined,\n): ToolType {\n const execute = findMember(object, \"execute\");\n if (!execute) {\n throw new GenerativeCompileError(\n \"every tool must declare an `execute`; use `hitl()` for a \" +\n \"human-in-the-loop tool\",\n filename,\n );\n }\n if (executeIsHitl(execute)) return \"human\";\n return executeIsClient(execute) ? \"frontend\" : \"backend\";\n}\n\n/** Writes the resolved `type` back onto the tool object (replacing any author's). */\nfunction setToolType(object: t.ObjectExpression, type: ToolType): void {\n removeMember(object, \"type\");\n // Append (not prepend) so the inferred type wins over any earlier spread.\n object.properties.push(\n t.objectProperty(t.identifier(\"type\"), t.stringLiteral(type)),\n );\n}\n\nfunction memberName(\n key: t.Node,\n computed: boolean | undefined,\n): string | undefined {\n if (computed) return undefined;\n if (t.isIdentifier(key)) return key.name;\n if (t.isStringLiteral(key)) return key.value;\n return undefined;\n}\n\n/**\n * Removes declarations and import specifiers left unreferenced after region\n * removal, to a fixpoint (a dropped helper frees what it used). Keeps exports,\n * side-effect imports, and possibly-side-effectful initializers.\n */\nfunction pruneUnused(ast: t.File): void {\n const hadSpecifiers = new WeakSet<t.ImportDeclaration>();\n for (const stmt of ast.program.body) {\n if (t.isImportDeclaration(stmt) && stmt.specifiers.length > 0) {\n hadSpecifiers.add(stmt);\n }\n }\n\n let removedSomething = true;\n while (removedSomething) {\n removedSomething = false;\n traverse(ast, {\n Program(path: NodePath<t.Program>) {\n path.scope.crawl();\n const isUnused = (name: string): boolean => {\n const binding = path.scope.getBinding(name);\n return !!binding && !binding.referenced;\n };\n\n path.node.body = path.node.body.filter((stmt) => {\n if (\n (t.isFunctionDeclaration(stmt) || t.isClassDeclaration(stmt)) &&\n stmt.id &&\n isUnused(stmt.id.name)\n ) {\n removedSomething = true;\n return false;\n }\n if (t.isVariableDeclaration(stmt)) {\n stmt.declarations = stmt.declarations.filter((decl) => {\n // Handles destructuring too: drop the declarator only when *every*\n // bound name is unused (else a `const { a } = server` survives and\n // keeps a server import in the client graph). Restricted to plain\n // patterns so a default/computed-key side effect isn't dropped.\n const names = Object.keys(t.getBindingIdentifiers(decl.id));\n if (\n names.length > 0 &&\n isPlainPattern(decl.id) &&\n isRemovableInit(decl.init) &&\n names.every(isUnused)\n ) {\n removedSomething = true;\n return false;\n }\n return true;\n });\n if (stmt.declarations.length === 0) return false;\n }\n return true;\n });\n path.stop();\n },\n });\n }\n\n traverse(ast, {\n Program(path: NodePath<t.Program>) {\n path.scope.crawl();\n for (const stmt of path.node.body) {\n if (!t.isImportDeclaration(stmt)) continue;\n stmt.specifiers = stmt.specifiers.filter((spec) => {\n const binding = path.scope.getBinding(spec.local.name);\n return binding ? binding.referenced : true;\n });\n }\n path.node.body = path.node.body.filter(\n (stmt) =>\n !(\n t.isImportDeclaration(stmt) &&\n stmt.specifiers.length === 0 &&\n hadSpecifiers.has(stmt)\n ),\n );\n path.stop();\n },\n });\n}\n\n/**\n * Whether a binding pattern is side-effect-free to drop: a plain identifier, or\n * an object/array pattern of plain bindings — no default values\n * (`{ a = expr }`) or computed keys (`{ [expr]: a }`), which would evaluate (and\n * thus could have side effects) at destructuring time.\n */\nfunction isPlainPattern(node: t.Node): boolean {\n if (t.isIdentifier(node)) return true;\n if (t.isObjectPattern(node)) {\n return node.properties.every((p) =>\n t.isRestElement(p)\n ? isPlainPattern(p.argument)\n : !p.computed && isPlainPattern(p.value),\n );\n }\n if (t.isArrayPattern(node)) {\n return node.elements.every((el) => el == null || isPlainPattern(el));\n }\n return false; // AssignmentPattern (default), member expr, etc.\n}\n\n/** Whether a variable initializer is safe to drop (no observable side effects). */\nfunction isRemovableInit(node: t.Expression | null | undefined): boolean {\n if (node == null) return true;\n if (t.isTSAsExpression(node) || t.isTSSatisfiesExpression(node)) {\n return isRemovableInit(node.expression);\n }\n // Containers are removable only if every element is — a nested call (e.g.\n // `[track()]`) has an observable side effect that dropping would lose.\n if (t.isArrayExpression(node)) {\n return node.elements.every(\n (el) => el == null || (t.isExpression(el) && isRemovableInit(el)),\n );\n }\n if (t.isObjectExpression(node)) {\n return node.properties.every(\n (p) =>\n t.isObjectProperty(p) &&\n !p.computed &&\n t.isExpression(p.value) &&\n isRemovableInit(p.value),\n );\n }\n if (t.isTemplateLiteral(node)) {\n return node.expressions.every(\n (e) => t.isExpression(e) && isRemovableInit(e),\n );\n }\n return (\n t.isArrowFunctionExpression(node) ||\n t.isFunctionExpression(node) ||\n t.isClassExpression(node) ||\n t.isIdentifier(node) ||\n // non-computed only — `obj[fn()]` could hide a side-effectful key\n (t.isMemberExpression(node) && !node.computed) ||\n t.isJSXElement(node) ||\n t.isJSXFragment(node) ||\n t.isLiteral(node)\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ //#region src/constants.d.ts
2
+ /** The directive a module uses to opt into generative compilation. */
3
+ declare const DIRECTIVE = "use generative";
4
+ /** Build target a generative module is compiled for. */
5
+ type Target = "client" | "server";
6
+ //#endregion
7
+ export { DIRECTIVE, Target };
8
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","names":[],"sources":["../src/constants.ts"],"mappings":";;cACa,SAAA;;KAGD,MAAA"}
@@ -0,0 +1,7 @@
1
+ //#region src/constants.ts
2
+ /** The directive a module uses to opt into generative compilation. */
3
+ const DIRECTIVE = "use generative";
4
+ //#endregion
5
+ export { DIRECTIVE };
6
+
7
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","names":[],"sources":["../src/constants.ts"],"sourcesContent":["/** The directive a module uses to opt into generative compilation. */\nexport const DIRECTIVE = \"use generative\";\n\n/** Build target a generative module is compiled for. */\nexport type Target = \"client\" | \"server\";\n"],"mappings":";;AACA,MAAa,YAAY"}
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","names":[],"sources":["../src/loader.ts"],"mappings":";;UAeU,uBAAA;EACR,YAAA;EACA,aAAA;EACA,SAAA;EACA,UAAA;IAAiB,IAAA;EAAA;EACjB,KAAA,KAAU,GAAA,WAAc,IAAA,WAAe,GAAA;AAAA;;iBAuDjB,gBAAA,CACtB,IAAA,EAAM,uBAAuB,EAC7B,MAAA"}
1
+ {"version":3,"file":"loader.d.ts","names":[],"sources":["../src/loader.ts"],"mappings":";;UAYU,uBAAA;EACR,YAAA;EACA,aAAA;EACA,SAAA;EACA,UAAA;IAAiB,IAAA;EAAA;EACjB,KAAA,KAAU,GAAA,WAAc,IAAA,WAAe,GAAA;AAAA;;iBAuDjB,gBAAA,CACtB,IAAA,EAAM,uBAAuB,EAC7B,MAAA"}
package/dist/loader.js CHANGED
@@ -1,5 +1,5 @@
1
+ import { compileGenerative, isGenerativeModule } from "./compile.js";
1
2
  import * as nodePath from "node:path";
2
- import { compileGenerative, isGenerativeModule } from "@assistant-ui/x-generative-compiler";
3
3
  //#region src/loader.ts
4
4
  /** This package's name, used in the facade's re-export specifier. */
5
5
  const PKG = "@assistant-ui/next";
@@ -1 +1 @@
1
- {"version":3,"file":"loader.js","names":[],"sources":["../src/loader.ts"],"sourcesContent":["import * as nodePath from \"node:path\";\nimport {\n compileGenerative,\n isGenerativeModule,\n type Target,\n} from \"@assistant-ui/x-generative-compiler\";\n\n/** This package's name, used in the facade's re-export specifier. */\nconst PKG = \"@assistant-ui/next\";\n\n/** Basenames of the react-server-conditioned indirection modules (see with-aui.ts). */\nconst SERVER_INDIRECTION = \"bundler-redirect.server\";\nconst CLIENT_INDIRECTION = \"bundler-redirect.client\";\n\n/** The subset of the webpack/Turbopack loader context this loader reads. */\ninterface GenerativeLoaderContext {\n resourcePath?: string;\n resourceQuery?: string;\n sourceMap?: boolean;\n getOptions?(): { path?: string } | undefined;\n async(): (err: unknown, code?: string, map?: object | null) => void;\n}\n\n/** Whether this resolution is one of the package's indirection modules. */\nfunction indirectionVariant(resourcePath: string): Target | null {\n const base = nodePath.basename(resourcePath);\n if (base.startsWith(SERVER_INDIRECTION)) return \"server\";\n if (base.startsWith(CLIENT_INDIRECTION)) return \"client\";\n return null;\n}\n\n/** The concrete build forced by a `?generative-env=client|server` resource query. */\nfunction queryTarget(resourceQuery: string | undefined): Target | null {\n if (!resourceQuery) return null;\n // URLSearchParams strips a leading \"?\" per spec.\n const g = new URLSearchParams(resourceQuery).get(\"generative-env\");\n return g === \"server\" || g === \"client\" ? g : null;\n}\n\n/**\n * Facade for a bare generative import: delegates build selection to the\n * `react-server`-conditioned `/bundler-redirect` subpath, passing the module's\n * path via a Turbopack import attribute. See DESIGN.md.\n */\nfunction buildFacade(resourcePath: string): string {\n const options = JSON.stringify(JSON.stringify({ path: resourcePath }));\n const attr =\n `with { turbopackLoader: \"${PKG}/loader\", ` +\n `turbopackLoaderOptions: ${options} }`;\n return [\n `import toolkit from \"${PKG}/bundler-redirect\" ${attr};`,\n `export default toolkit;`,\n ``,\n ].join(\"\\n\");\n}\n\n/**\n * Replaces an indirection module with a re-export of the chosen concrete build,\n * via a relative specifier (Turbopack won't resolve an absolute one). See\n * DESIGN.md.\n */\nfunction buildIndirection(\n variant: Target,\n fromPath: string,\n toPath: string,\n): string {\n let rel = nodePath\n .relative(nodePath.dirname(fromPath), toPath)\n .replace(/\\\\/g, \"/\");\n if (!rel.startsWith(\".\")) rel = `./${rel}`;\n const spec = JSON.stringify(`${rel}?generative-env=${variant}`);\n return `export { default } from ${spec};\\n`;\n}\n\n/** Webpack/Turbopack loader for `\"use generative\"` modules. See DESIGN.md. */\nexport default function generativeLoader(\n this: GenerativeLoaderContext,\n source: string,\n): void {\n const callback = this.async();\n const resourcePath = this.resourcePath ?? \"\";\n\n // 1) Package indirection (resolved via the `react-server` condition).\n const variant = indirectionVariant(resourcePath);\n if (variant) {\n const path = this.getOptions?.()?.path;\n if (!path) {\n callback(\n new Error(\n \"[assistant-ui/next] indirection module loaded without a `path` \" +\n \"option; it must be imported via the generated facade.\",\n ),\n );\n return;\n }\n callback(null, buildIndirection(variant, resourcePath, path));\n return;\n }\n\n // 2) Explicit concrete-build query (used by the indirection).\n const target = queryTarget(this.resourceQuery);\n if (target) {\n if (!isGenerativeModule(source)) {\n callback(null, source);\n return;\n }\n try {\n const { code, map } = compileGenerative(source, {\n target,\n filename: resourcePath,\n sourceMaps: this.sourceMap ?? false,\n });\n callback(null, code, map);\n } catch (error) {\n callback(error);\n }\n return;\n }\n\n // 3) Bare import of a generative module → facade.\n if (isGenerativeModule(source)) {\n callback(null, buildFacade(resourcePath));\n return;\n }\n\n // 4) Not a generative module.\n callback(null, source);\n}\n"],"mappings":";;;;AAQA,MAAM,MAAM;;AAGZ,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;;AAY3B,SAAS,mBAAmB,cAAqC;CAC/D,MAAM,OAAO,SAAS,SAAS,YAAY;CAC3C,IAAI,KAAK,WAAW,kBAAkB,GAAG,OAAO;CAChD,IAAI,KAAK,WAAW,kBAAkB,GAAG,OAAO;CAChD,OAAO;AACT;;AAGA,SAAS,YAAY,eAAkD;CACrE,IAAI,CAAC,eAAe,OAAO;CAE3B,MAAM,IAAI,IAAI,gBAAgB,aAAa,EAAE,IAAI,gBAAgB;CACjE,OAAO,MAAM,YAAY,MAAM,WAAW,IAAI;AAChD;;;;;;AAOA,SAAS,YAAY,cAA8B;CAKjD,OAAO;EACL,wBAAwB,IAAI,qBAAqB,4BAHrB,IAAI,oCAFlB,KAAK,UAAU,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC,CAGjC,EAAE,IAEmB;EACtD;EACA;CACF,EAAE,KAAK,IAAI;AACb;;;;;;AAOA,SAAS,iBACP,SACA,UACA,QACQ;CACR,IAAI,MAAM,SACP,SAAS,SAAS,QAAQ,QAAQ,GAAG,MAAM,EAC3C,QAAQ,OAAO,GAAG;CACrB,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG,MAAM,KAAK;CAErC,OAAO,2BADM,KAAK,UAAU,GAAG,IAAI,kBAAkB,SAChB,EAAE;AACzC;;AAGA,SAAwB,iBAEtB,QACM;CACN,MAAM,WAAW,KAAK,MAAM;CAC5B,MAAM,eAAe,KAAK,gBAAgB;CAG1C,MAAM,UAAU,mBAAmB,YAAY;CAC/C,IAAI,SAAS;EACX,MAAM,OAAO,KAAK,aAAa,GAAG;EAClC,IAAI,CAAC,MAAM;GACT,yBACE,IAAI,MACF,sHAEF,CACF;GACA;EACF;EACA,SAAS,MAAM,iBAAiB,SAAS,cAAc,IAAI,CAAC;EAC5D;CACF;CAGA,MAAM,SAAS,YAAY,KAAK,aAAa;CAC7C,IAAI,QAAQ;EACV,IAAI,CAAC,mBAAmB,MAAM,GAAG;GAC/B,SAAS,MAAM,MAAM;GACrB;EACF;EACA,IAAI;GACF,MAAM,EAAE,MAAM,QAAQ,kBAAkB,QAAQ;IAC9C;IACA,UAAU;IACV,YAAY,KAAK,aAAa;GAChC,CAAC;GACD,SAAS,MAAM,MAAM,GAAG;EAC1B,SAAS,OAAO;GACd,SAAS,KAAK;EAChB;EACA;CACF;CAGA,IAAI,mBAAmB,MAAM,GAAG;EAC9B,SAAS,MAAM,YAAY,YAAY,CAAC;EACxC;CACF;CAGA,SAAS,MAAM,MAAM;AACvB"}
1
+ {"version":3,"file":"loader.js","names":[],"sources":["../src/loader.ts"],"sourcesContent":["import * as nodePath from \"node:path\";\nimport { compileGenerative, isGenerativeModule } from \"./compile\";\nimport type { Target } from \"./constants\";\n\n/** This package's name, used in the facade's re-export specifier. */\nconst PKG = \"@assistant-ui/next\";\n\n/** Basenames of the react-server-conditioned indirection modules (see with-aui.ts). */\nconst SERVER_INDIRECTION = \"bundler-redirect.server\";\nconst CLIENT_INDIRECTION = \"bundler-redirect.client\";\n\n/** The subset of the webpack/Turbopack loader context this loader reads. */\ninterface GenerativeLoaderContext {\n resourcePath?: string;\n resourceQuery?: string;\n sourceMap?: boolean;\n getOptions?(): { path?: string } | undefined;\n async(): (err: unknown, code?: string, map?: object | null) => void;\n}\n\n/** Whether this resolution is one of the package's indirection modules. */\nfunction indirectionVariant(resourcePath: string): Target | null {\n const base = nodePath.basename(resourcePath);\n if (base.startsWith(SERVER_INDIRECTION)) return \"server\";\n if (base.startsWith(CLIENT_INDIRECTION)) return \"client\";\n return null;\n}\n\n/** The concrete build forced by a `?generative-env=client|server` resource query. */\nfunction queryTarget(resourceQuery: string | undefined): Target | null {\n if (!resourceQuery) return null;\n // URLSearchParams strips a leading \"?\" per spec.\n const g = new URLSearchParams(resourceQuery).get(\"generative-env\");\n return g === \"server\" || g === \"client\" ? g : null;\n}\n\n/**\n * Facade for a bare generative import: delegates build selection to the\n * `react-server`-conditioned `/bundler-redirect` subpath, passing the module's\n * path via a Turbopack import attribute. See DESIGN.md.\n */\nfunction buildFacade(resourcePath: string): string {\n const options = JSON.stringify(JSON.stringify({ path: resourcePath }));\n const attr =\n `with { turbopackLoader: \"${PKG}/loader\", ` +\n `turbopackLoaderOptions: ${options} }`;\n return [\n `import toolkit from \"${PKG}/bundler-redirect\" ${attr};`,\n `export default toolkit;`,\n ``,\n ].join(\"\\n\");\n}\n\n/**\n * Replaces an indirection module with a re-export of the chosen concrete build,\n * via a relative specifier (Turbopack won't resolve an absolute one). See\n * DESIGN.md.\n */\nfunction buildIndirection(\n variant: Target,\n fromPath: string,\n toPath: string,\n): string {\n let rel = nodePath\n .relative(nodePath.dirname(fromPath), toPath)\n .replace(/\\\\/g, \"/\");\n if (!rel.startsWith(\".\")) rel = `./${rel}`;\n const spec = JSON.stringify(`${rel}?generative-env=${variant}`);\n return `export { default } from ${spec};\\n`;\n}\n\n/** Webpack/Turbopack loader for `\"use generative\"` modules. See DESIGN.md. */\nexport default function generativeLoader(\n this: GenerativeLoaderContext,\n source: string,\n): void {\n const callback = this.async();\n const resourcePath = this.resourcePath ?? \"\";\n\n // 1) Package indirection (resolved via the `react-server` condition).\n const variant = indirectionVariant(resourcePath);\n if (variant) {\n const path = this.getOptions?.()?.path;\n if (!path) {\n callback(\n new Error(\n \"[assistant-ui/next] indirection module loaded without a `path` \" +\n \"option; it must be imported via the generated facade.\",\n ),\n );\n return;\n }\n callback(null, buildIndirection(variant, resourcePath, path));\n return;\n }\n\n // 2) Explicit concrete-build query (used by the indirection).\n const target = queryTarget(this.resourceQuery);\n if (target) {\n if (!isGenerativeModule(source)) {\n callback(null, source);\n return;\n }\n try {\n const { code, map } = compileGenerative(source, {\n target,\n filename: resourcePath,\n sourceMaps: this.sourceMap ?? false,\n });\n callback(null, code, map);\n } catch (error) {\n callback(error);\n }\n return;\n }\n\n // 3) Bare import of a generative module → facade.\n if (isGenerativeModule(source)) {\n callback(null, buildFacade(resourcePath));\n return;\n }\n\n // 4) Not a generative module.\n callback(null, source);\n}\n"],"mappings":";;;;AAKA,MAAM,MAAM;;AAGZ,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;;AAY3B,SAAS,mBAAmB,cAAqC;CAC/D,MAAM,OAAO,SAAS,SAAS,YAAY;CAC3C,IAAI,KAAK,WAAW,kBAAkB,GAAG,OAAO;CAChD,IAAI,KAAK,WAAW,kBAAkB,GAAG,OAAO;CAChD,OAAO;AACT;;AAGA,SAAS,YAAY,eAAkD;CACrE,IAAI,CAAC,eAAe,OAAO;CAE3B,MAAM,IAAI,IAAI,gBAAgB,aAAa,EAAE,IAAI,gBAAgB;CACjE,OAAO,MAAM,YAAY,MAAM,WAAW,IAAI;AAChD;;;;;;AAOA,SAAS,YAAY,cAA8B;CAKjD,OAAO;EACL,wBAAwB,IAAI,qBAAqB,4BAHrB,IAAI,oCAFlB,KAAK,UAAU,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC,CAGjC,EAAE,IAEmB;EACtD;EACA;CACF,EAAE,KAAK,IAAI;AACb;;;;;;AAOA,SAAS,iBACP,SACA,UACA,QACQ;CACR,IAAI,MAAM,SACP,SAAS,SAAS,QAAQ,QAAQ,GAAG,MAAM,EAC3C,QAAQ,OAAO,GAAG;CACrB,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG,MAAM,KAAK;CAErC,OAAO,2BADM,KAAK,UAAU,GAAG,IAAI,kBAAkB,SAChB,EAAE;AACzC;;AAGA,SAAwB,iBAEtB,QACM;CACN,MAAM,WAAW,KAAK,MAAM;CAC5B,MAAM,eAAe,KAAK,gBAAgB;CAG1C,MAAM,UAAU,mBAAmB,YAAY;CAC/C,IAAI,SAAS;EACX,MAAM,OAAO,KAAK,aAAa,GAAG;EAClC,IAAI,CAAC,MAAM;GACT,yBACE,IAAI,MACF,sHAEF,CACF;GACA;EACF;EACA,SAAS,MAAM,iBAAiB,SAAS,cAAc,IAAI,CAAC;EAC5D;CACF;CAGA,MAAM,SAAS,YAAY,KAAK,aAAa;CAC7C,IAAI,QAAQ;EACV,IAAI,CAAC,mBAAmB,MAAM,GAAG;GAC/B,SAAS,MAAM,MAAM;GACrB;EACF;EACA,IAAI;GACF,MAAM,EAAE,MAAM,QAAQ,kBAAkB,QAAQ;IAC9C;IACA,UAAU;IACV,YAAY,KAAK,aAAa;GAChC,CAAC;GACD,SAAS,MAAM,MAAM,GAAG;EAC1B,SAAS,OAAO;GACd,SAAS,KAAK;EAChB;EACA;CACF;CAGA,IAAI,mBAAmB,MAAM,GAAG;EAC9B,SAAS,MAAM,YAAY,YAAY,CAAC;EACxC;CACF;CAGA,SAAS,MAAM,MAAM;AACvB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/next",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Next.js integration for assistant-ui: the withAui() config wrapper and the \"use generative\" directive compiler that colocates a tool's schema, server-only execute, and client-only render in one file.",
5
5
  "keywords": [
6
6
  "assistant-ui",
@@ -39,18 +39,22 @@
39
39
  "README.md"
40
40
  ],
41
41
  "sideEffects": false,
42
- "scripts": {
43
- "build": "aui-build",
44
- "test": "vitest run --passWithNoTests",
45
- "test:watch": "vitest"
46
- },
47
42
  "dependencies": {
48
- "@assistant-ui/x-generative-compiler": "workspace:^"
43
+ "@babel/generator": "^7.29.7",
44
+ "@babel/parser": "^7.29.7",
45
+ "@babel/traverse": "^7.29.7",
46
+ "@babel/types": "^7.29.7"
47
+ },
48
+ "peerDependencies": {
49
+ "@assistant-ui/core": "^0.2.8"
49
50
  },
50
51
  "devDependencies": {
51
- "@assistant-ui/x-buildutils": "workspace:*",
52
+ "@types/babel__generator": "^7.27.0",
53
+ "@types/babel__traverse": "^7.28.0",
52
54
  "@types/node": "^25.9.1",
53
- "vitest": "^4.1.7"
55
+ "vitest": "^4.1.7",
56
+ "@assistant-ui/core": "0.2.8",
57
+ "@assistant-ui/x-buildutils": "0.0.10"
54
58
  },
55
59
  "publishConfig": {
56
60
  "access": "public",
@@ -64,5 +68,10 @@
64
68
  },
65
69
  "bugs": {
66
70
  "url": "https://github.com/assistant-ui/assistant-ui/issues"
71
+ },
72
+ "scripts": {
73
+ "build": "aui-build",
74
+ "test": "vitest run",
75
+ "test:watch": "vitest"
67
76
  }
68
- }
77
+ }
@@ -0,0 +1,267 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ compileGenerative,
4
+ isGenerativeModule,
5
+ GenerativeCompileError,
6
+ } from "./compile";
7
+
8
+ const source = `"use generative";
9
+ import { z } from "zod";
10
+ import { db } from "@/db";
11
+ import { track } from "@/analytics";
12
+ import { Chart } from "@/ui/chart";
13
+ import { defineToolkit } from "@assistant-ui/next";
14
+
15
+ export default defineToolkit({
16
+ weather: {
17
+ description: "Show the weather.",
18
+ parameters: z.object({ city: z.string() }),
19
+ execute: async ({ city }) => db.weather.get(city),
20
+ render: (props) => <Chart data={props} />,
21
+ },
22
+ toast: {
23
+ description: "Show a toast.",
24
+ parameters: z.object({ msg: z.string() }),
25
+ execute: async ({ msg }) => {
26
+ "use client";
27
+ return track(msg);
28
+ },
29
+ render: (props) => <div>{props.msg}</div>,
30
+ },
31
+ });
32
+ `;
33
+
34
+ const server = () => compileGenerative(source, { target: "server" }).code;
35
+ const client = () => compileGenerative(source, { target: "client" }).code;
36
+
37
+ describe("compileGenerative — server target", () => {
38
+ const code = server();
39
+
40
+ it("keeps the schema", () => {
41
+ expect(code).toContain('import { z } from "zod"');
42
+ expect(code).toContain("z.object");
43
+ expect(code).toContain('description: "Show the weather."');
44
+ });
45
+
46
+ it("keeps a backend execute and guards it with server-only", () => {
47
+ expect(code).toContain('import "server-only"');
48
+ expect(code).toContain("db.weather.get");
49
+ expect(code).toContain('import { db } from "@/db"');
50
+ });
51
+
52
+ it("writes the inferred type back onto each tool", () => {
53
+ expect(code).toContain('type: "backend"'); // weather: execute, no "use client"
54
+ expect(code).toContain('type: "frontend"'); // toast: execute + "use client"
55
+ });
56
+
57
+ it("drops all render and its client imports", () => {
58
+ expect(code).not.toContain("Chart");
59
+ expect(code).not.toContain("<div");
60
+ expect(code).not.toMatch(/render\s*:/);
61
+ });
62
+
63
+ it("drops a frontend execute and its client imports", () => {
64
+ expect(code).not.toContain("track");
65
+ expect(code).not.toContain("@/analytics");
66
+ });
67
+
68
+ it("strips the use generative directive and adds no use client", () => {
69
+ expect(code).not.toContain("use generative");
70
+ expect(code).not.toContain("use client");
71
+ });
72
+ });
73
+
74
+ describe("compileGenerative — client target", () => {
75
+ const code = client();
76
+
77
+ it("marks the module use client and keeps render", () => {
78
+ expect(code.trimStart().startsWith('"use client"')).toBe(true);
79
+ expect(code).toContain("<Chart");
80
+ expect(code).toContain("<div");
81
+ expect(code).toContain('import { Chart } from "@/ui/chart"');
82
+ });
83
+
84
+ it("keeps the schema for parsing", () => {
85
+ expect(code).toContain('import { z } from "zod"');
86
+ expect(code).toContain("z.object");
87
+ });
88
+
89
+ it("keeps a frontend execute (and drops its `use client` marker)", () => {
90
+ expect(code).toContain("track(msg)");
91
+ expect(code).toContain("@/analytics");
92
+ // exactly one "use client" — the module directive, not the execute body's
93
+ expect(code.match(/use client/g)?.length).toBe(1);
94
+ });
95
+
96
+ it("writes the inferred type back onto each tool", () => {
97
+ expect(code).toContain('type: "backend"'); // weather (schema-only on client)
98
+ expect(code).toContain('type: "frontend"'); // toast
99
+ });
100
+
101
+ it("drops a backend execute and its server-only imports", () => {
102
+ expect(code).not.toContain("db.weather.get");
103
+ expect(code).not.toContain("@/db");
104
+ expect(code).not.toContain("server-only");
105
+ });
106
+ });
107
+
108
+ describe("compileGenerative — local dead-code elimination", () => {
109
+ const withHelpers = `"use generative";
110
+ import { z } from "zod";
111
+ import { cn } from "@/lib/utils";
112
+ import { db } from "@/db";
113
+
114
+ const Badge = ({ label }) => <span className={cn("badge")}>{label}</span>;
115
+ import { defineToolkit } from "@assistant-ui/next";
116
+
117
+ export default defineToolkit({
118
+ weather: {
119
+ description: "weather",
120
+ parameters: z.object({ city: z.string() }),
121
+ execute: async ({ city }) => db.weather.get(city),
122
+ render: (props) => <Badge label={props.city} />,
123
+ },
124
+ });
125
+ `;
126
+
127
+ it("strips a local helper component (and its imports) from the server", () => {
128
+ const code = compileGenerative(withHelpers, { target: "server" }).code;
129
+ expect(code).not.toContain("Badge");
130
+ expect(code).not.toContain("@/lib/utils");
131
+ expect(code).not.toContain("className");
132
+ expect(code).toContain("db.weather.get");
133
+ expect(code).toContain('import "server-only"');
134
+ });
135
+
136
+ it("keeps the local helper on the client", () => {
137
+ const code = compileGenerative(withHelpers, { target: "client" }).code;
138
+ expect(code).toContain("Badge");
139
+ expect(code).toContain("@/lib/utils");
140
+ expect(code).not.toContain("@/db");
141
+ });
142
+
143
+ it("prunes an unused destructured server binding from the client", () => {
144
+ const src = `"use generative";
145
+ import { defineToolkit } from "@assistant-ui/next";
146
+ import { db } from "@/db";
147
+ const { getWeather } = db;
148
+ export default defineToolkit({
149
+ weather: {
150
+ execute: async ({ city }) => getWeather(city),
151
+ render: () => null,
152
+ },
153
+ });`;
154
+ const client = compileGenerative(src, { target: "client" }).code;
155
+ expect(client).not.toContain("getWeather");
156
+ expect(client).not.toContain("@/db");
157
+ expect(compileGenerative(src, { target: "server" }).code).toContain(
158
+ "getWeather",
159
+ );
160
+ });
161
+ });
162
+
163
+ describe("compileGenerative — diagnostics", () => {
164
+ it("rejects a module without the directive", () => {
165
+ expect(() =>
166
+ compileGenerative(`export default {} satisfies Toolkit;`, {
167
+ target: "server",
168
+ }),
169
+ ).toThrow(GenerativeCompileError);
170
+ });
171
+
172
+ it("strips a define-style wrapper and prunes its import", () => {
173
+ const wrapped = `"use generative";
174
+ import { z } from "zod";
175
+ import { db } from "@/db";
176
+ import { defineToolkit } from "@assistant-ui/next";
177
+ export default defineToolkit({
178
+ weather: {
179
+ parameters: z.object({ city: z.string() }),
180
+ execute: async ({ city }) => db.get(city),
181
+ render: (props) => <span>{props.city}</span>,
182
+ },
183
+ });`;
184
+ const serverCode = compileGenerative(wrapped, { target: "server" }).code;
185
+ // wrapper + its import gone; bare object with execute remains.
186
+ expect(serverCode).not.toContain("defineToolkit");
187
+ expect(serverCode).not.toContain("@assistant-ui/next");
188
+ expect(serverCode).toContain("db.get");
189
+ expect(serverCode).not.toContain("<span");
190
+
191
+ const clientCode = compileGenerative(wrapped, { target: "client" }).code;
192
+ expect(clientCode).not.toContain("defineToolkit");
193
+ expect(clientCode).not.toContain("@assistant-ui/next");
194
+ expect(clientCode).toContain("<span");
195
+ expect(clientCode).not.toContain("@/db");
196
+ });
197
+
198
+ it("requires the defineToolkit() wrapper", () => {
199
+ expect(() =>
200
+ compileGenerative(
201
+ `"use generative";\nexport default { a: { execute: async () => 1 } };`,
202
+ { target: "server" },
203
+ ),
204
+ ).toThrow(/defineToolkit/);
205
+ expect(() =>
206
+ compileGenerative(`"use generative";\nexport default makeToolkit();`, {
207
+ target: "server",
208
+ }),
209
+ ).toThrow(/defineToolkit/);
210
+ });
211
+
212
+ it("rejects a tool that isn't an inline object literal", () => {
213
+ expect(() =>
214
+ compileGenerative(
215
+ `"use generative";\nimport { defineToolkit } from "@assistant-ui/next";\nexport default defineToolkit({ weather: makeTool() });`,
216
+ { target: "server" },
217
+ ),
218
+ ).toThrow(/inline object literal/);
219
+ });
220
+
221
+ it("requires a render for human/frontend tools", () => {
222
+ expect(() =>
223
+ compileGenerative(
224
+ `"use generative";\nimport { defineToolkit, hitl } from "@assistant-ui/next";\nexport default defineToolkit({ ask: { execute: hitl() } });`,
225
+ { target: "client" },
226
+ ),
227
+ ).toThrow(/must declare a `render`/);
228
+ });
229
+
230
+ it("requires every tool to declare an execute", () => {
231
+ expect(() =>
232
+ compileGenerative(
233
+ `"use generative";\nimport { defineToolkit } from "@assistant-ui/next";\nexport default defineToolkit({ ask: { render: () => null } });`,
234
+ { target: "client" },
235
+ ),
236
+ ).toThrow(/must declare an `execute`/);
237
+ });
238
+
239
+ it("infers `human` from execute: hitl() and drops it on both builds", () => {
240
+ const src = `"use generative";\nimport { defineToolkit, hitl } from "@assistant-ui/next";\nexport default defineToolkit({ ask: { execute: hitl(), render: () => null } });`;
241
+ const server = compileGenerative(src, { target: "server" }).code;
242
+ expect(server).toContain('type: "human"');
243
+ expect(server).not.toContain("hitl"); // sentinel + its import pruned
244
+ const client = compileGenerative(src, { target: "client" }).code;
245
+ expect(client).toContain('type: "human"');
246
+ expect(client).not.toContain("hitl");
247
+ expect(client).toContain("render");
248
+ });
249
+
250
+ it("infers `frontend` from a `use client` execute and keeps it client-side", () => {
251
+ const src = `"use generative";\nimport { defineToolkit } from "@assistant-ui/next";\nimport { track } from "@/a";\nexport default defineToolkit({ t: { execute: async () => { "use client"; return track(); }, render: () => null } });`;
252
+ const server = compileGenerative(src, { target: "server" }).code;
253
+ expect(server).toContain('type: "frontend"');
254
+ expect(server).not.toContain("track"); // frontend execute dropped on server
255
+ const client = compileGenerative(src, { target: "client" }).code;
256
+ expect(client).toContain('type: "frontend"');
257
+ expect(client).toContain("track()");
258
+ });
259
+
260
+ it("detects generative modules by directive", () => {
261
+ expect(isGenerativeModule(`"use generative";\nexport default {};`)).toBe(
262
+ true,
263
+ );
264
+ expect(isGenerativeModule(`// a comment\n"use generative";\n`)).toBe(true);
265
+ expect(isGenerativeModule(`export default {};`)).toBe(false);
266
+ });
267
+ });
package/src/compile.ts ADDED
@@ -0,0 +1,472 @@
1
+ import { parse } from "@babel/parser";
2
+ import _traverse, { type NodePath } from "@babel/traverse";
3
+ import _generate from "@babel/generator";
4
+ import * as t from "@babel/types";
5
+ import { DIRECTIVE, type Target } from "./constants";
6
+
7
+ // @babel/traverse and @babel/generator are CJS; their default export is the
8
+ // function itself under some interop and `{ default }` under others.
9
+ const traverse = (
10
+ typeof _traverse === "function" ? _traverse : (_traverse as any).default
11
+ ) as typeof _traverse;
12
+ const generate = (
13
+ typeof _generate === "function" ? _generate : (_generate as any).default
14
+ ) as typeof _generate;
15
+
16
+ export type ToolType = "frontend" | "backend" | "human";
17
+
18
+ export interface CompileOptions {
19
+ /** Which build target to emit. */
20
+ target: Target;
21
+ /** Source filename, used for error messages and source maps. */
22
+ filename?: string;
23
+ /** Emit a source map alongside the code. */
24
+ sourceMaps?: boolean;
25
+ }
26
+
27
+ export interface CompileResult {
28
+ code: string;
29
+ map?: object | null;
30
+ }
31
+
32
+ /** Thrown when a `"use generative"` file violates an authoring constraint. */
33
+ export class GenerativeCompileError extends Error {
34
+ constructor(message: string, filename?: string) {
35
+ super(`[assistant-ui/next]${filename ? ` ${filename}:` : ""} ${message}`);
36
+ this.name = "GenerativeCompileError";
37
+ }
38
+ }
39
+
40
+ /** Whether a source string opts into generative compilation via the directive. */
41
+ export function isGenerativeModule(code: string): boolean {
42
+ // The directive must be a leading statement, preceded only by an optional BOM,
43
+ // whitespace, and line/block comments. Scanned linearly — a regex over the
44
+ // comment prefix is prone to catastrophic backtracking on crafted input.
45
+ let i = code.charCodeAt(0) === 0xfeff ? 1 : 0;
46
+ for (;;) {
47
+ while (i < code.length && /\s/.test(code[i]!)) i++;
48
+ if (code.startsWith("//", i)) {
49
+ const nl = code.indexOf("\n", i);
50
+ if (nl === -1) return false;
51
+ i = nl + 1;
52
+ } else if (code.startsWith("/*", i)) {
53
+ const end = code.indexOf("*/", i + 2);
54
+ if (end === -1) return false;
55
+ i = end + 2;
56
+ } else {
57
+ break;
58
+ }
59
+ }
60
+ return (
61
+ code.startsWith(`"${DIRECTIVE}"`, i) || code.startsWith(`'${DIRECTIVE}'`, i)
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Rewrites a `"use generative"` module for a single build target, keeping only the
67
+ * regions that target needs and pruning the imports the dropped regions used.
68
+ */
69
+ export function compileGenerative(
70
+ code: string,
71
+ options: CompileOptions,
72
+ ): CompileResult {
73
+ const { target, filename } = options;
74
+
75
+ const ast = parse(code, {
76
+ sourceType: "module",
77
+ plugins: ["typescript", "jsx", "explicitResourceManagement"],
78
+ });
79
+
80
+ if (!ast.program.directives.some((d) => d.value.value === DIRECTIVE)) {
81
+ throw new GenerativeCompileError(
82
+ `missing "${DIRECTIVE}" directive`,
83
+ filename,
84
+ );
85
+ }
86
+
87
+ const object = findDefaultExportObject(ast, filename);
88
+
89
+ let keptRender = false;
90
+ let keptBackendExecute = false;
91
+
92
+ for (const entry of object.properties) {
93
+ const value = entryValue(entry);
94
+ if (!value) {
95
+ // A non-inline tool (spread, method, or a call like `makeTool()`) can't be
96
+ // analyzed, so its `execute` would pass through to the client unstripped.
97
+ throw new GenerativeCompileError(
98
+ "each tool must be an inline object literal (`name: { ... }`) so its " +
99
+ "`execute` can be routed",
100
+ filename,
101
+ );
102
+ }
103
+
104
+ // Nature is inferred from `execute` (see inferToolType), not an authored
105
+ // `type`. The resolved type is written back below so the runtime keeps it.
106
+ const type = inferToolType(value, filename);
107
+ const hasRender = !!findMember(value, "render");
108
+ const execute = findMember(value, "execute");
109
+
110
+ if ((type === "frontend" || type === "human") && !hasRender) {
111
+ throw new GenerativeCompileError(
112
+ `a ${type} tool must declare a \`render\` (it has no server execute to show otherwise)`,
113
+ filename,
114
+ );
115
+ }
116
+
117
+ if (target === "client") {
118
+ // A frontend execute stays (its `"use client"` marker is no longer needed
119
+ // once the module is client); a backend execute and a human `hitl()`
120
+ // sentinel are both dropped.
121
+ if (execute && type === "frontend") stripUseClient(execute);
122
+ else if (execute) removeMember(value, "execute");
123
+ if (hasRender) keptRender = true;
124
+ } else {
125
+ // server: render is never needed; only a backend execute survives.
126
+ if (hasRender) removeMember(value, "render");
127
+ if (execute && type !== "backend") removeMember(value, "execute");
128
+ if (execute && type === "backend") keptBackendExecute = true;
129
+ }
130
+
131
+ setToolType(value, type);
132
+ }
133
+
134
+ pruneUnused(ast);
135
+
136
+ // Replace the module directives with the target-appropriate one.
137
+ ast.program.directives = ast.program.directives.filter(
138
+ (d) => d.value.value !== DIRECTIVE && d.value.value !== "use client",
139
+ );
140
+ if (target === "client" && keptRender) {
141
+ ast.program.directives.unshift(
142
+ t.directive(t.directiveLiteral("use client")),
143
+ );
144
+ }
145
+ if (target === "server" && keptBackendExecute) {
146
+ ast.program.body.unshift(
147
+ t.importDeclaration([], t.stringLiteral("server-only")),
148
+ );
149
+ }
150
+
151
+ const result = generate(
152
+ ast,
153
+ {
154
+ sourceMaps: options.sourceMaps ?? false,
155
+ filename,
156
+ jsescOption: { minimal: true },
157
+ },
158
+ code,
159
+ );
160
+
161
+ return { code: result.code, map: result.map };
162
+ }
163
+
164
+ function findDefaultExportObject(
165
+ ast: t.File,
166
+ filename: string | undefined,
167
+ ): t.ObjectExpression {
168
+ let object: t.ObjectExpression | null = null;
169
+ let sawDefault = false;
170
+
171
+ for (const stmt of ast.program.body) {
172
+ if (!t.isExportDefaultDeclaration(stmt)) continue;
173
+ sawDefault = true;
174
+ object = unwrapDefineToolkit(stmt.declaration);
175
+ // Emit the bare object literal, dropping the `defineToolkit(...)` wrapper
176
+ // (and the import it pulled).
177
+ if (object) stmt.declaration = object;
178
+ }
179
+
180
+ if (!sawDefault) {
181
+ throw new GenerativeCompileError("missing a default export", filename);
182
+ }
183
+ if (!object) {
184
+ throw new GenerativeCompileError(
185
+ "the default export must be `defineToolkit({ ... })` (imported from " +
186
+ '"@assistant-ui/next"); wrapping is required so a backend `execute` ' +
187
+ "can't be authored in a way that reaches the client",
188
+ filename,
189
+ );
190
+ }
191
+ return object;
192
+ }
193
+
194
+ /**
195
+ * Unwraps the required `defineToolkit({ ... })` wrapper (through `satisfies`/`as`
196
+ * and parens) to the underlying object literal. Anything else — a bare object, a
197
+ * `satisfies Toolkit` without the wrapper, some other call — yields `null` so the
198
+ * caller errors.
199
+ */
200
+ function unwrapDefineToolkit(node: t.Node): t.ObjectExpression | null {
201
+ if (t.isTSSatisfiesExpression(node) || t.isTSAsExpression(node)) {
202
+ return unwrapDefineToolkit(node.expression);
203
+ }
204
+ if (t.isParenthesizedExpression(node)) {
205
+ return unwrapDefineToolkit(node.expression);
206
+ }
207
+ if (
208
+ t.isCallExpression(node) &&
209
+ t.isIdentifier(node.callee, { name: "defineToolkit" }) &&
210
+ t.isObjectExpression(node.arguments[0])
211
+ ) {
212
+ return node.arguments[0];
213
+ }
214
+ return null;
215
+ }
216
+
217
+ type Entry = t.ObjectExpression["properties"][number];
218
+
219
+ function entryValue(entry: Entry): t.ObjectExpression | null {
220
+ if (t.isObjectProperty(entry) && t.isObjectExpression(entry.value)) {
221
+ return entry.value;
222
+ }
223
+ return null;
224
+ }
225
+
226
+ /** A member of an entry object: `render`/`execute`/`type`, as property or method. */
227
+ function findMember(
228
+ object: t.ObjectExpression,
229
+ name: string,
230
+ ): t.ObjectProperty | t.ObjectMethod | undefined {
231
+ return object.properties.find(
232
+ (p): p is t.ObjectProperty | t.ObjectMethod =>
233
+ (t.isObjectProperty(p) || t.isObjectMethod(p)) &&
234
+ memberName(p.key, p.computed) === name,
235
+ );
236
+ }
237
+
238
+ function removeMember(object: t.ObjectExpression, name: string): void {
239
+ object.properties = object.properties.filter(
240
+ (p) =>
241
+ !(
242
+ (t.isObjectProperty(p) || t.isObjectMethod(p)) &&
243
+ memberName(p.key, p.computed) === name
244
+ ),
245
+ );
246
+ }
247
+
248
+ /** The `BlockStatement` body of an `execute` member, if it has one. */
249
+ function executeBody(
250
+ member: t.ObjectProperty | t.ObjectMethod,
251
+ ): t.BlockStatement | undefined {
252
+ if (t.isObjectMethod(member)) return member.body;
253
+ const value = member.value;
254
+ if (
255
+ (t.isArrowFunctionExpression(value) || t.isFunctionExpression(value)) &&
256
+ t.isBlockStatement(value.body)
257
+ ) {
258
+ return value.body;
259
+ }
260
+ return undefined;
261
+ }
262
+
263
+ /** Whether an `execute` opts into the client via a leading `"use client"`. */
264
+ function executeIsClient(member: t.ObjectProperty | t.ObjectMethod): boolean {
265
+ return !!executeBody(member)?.directives.some(
266
+ (d) => d.value.value === "use client",
267
+ );
268
+ }
269
+
270
+ /** Whether an `execute` is the `hitl()` human-in-the-loop sentinel. */
271
+ function executeIsHitl(member: t.ObjectProperty | t.ObjectMethod): boolean {
272
+ return (
273
+ t.isObjectProperty(member) &&
274
+ t.isCallExpression(member.value) &&
275
+ t.isIdentifier(member.value.callee, { name: "hitl" })
276
+ );
277
+ }
278
+
279
+ /** Drops the `"use client"` directive from an `execute` body (kept frontend). */
280
+ function stripUseClient(member: t.ObjectProperty | t.ObjectMethod): void {
281
+ const body = executeBody(member);
282
+ if (body) {
283
+ body.directives = body.directives.filter(
284
+ (d) => d.value.value !== "use client",
285
+ );
286
+ }
287
+ }
288
+
289
+ /**
290
+ * The tool's nature, inferred from its (mandatory) `execute` rather than an
291
+ * authored `type`: `hitl()` → `human`; `"use client"` → `frontend`; otherwise
292
+ * `backend`. The loader writes the result back as a `type` field (see
293
+ * {@link setToolType}) so the runtime keeps it.
294
+ */
295
+ function inferToolType(
296
+ object: t.ObjectExpression,
297
+ filename: string | undefined,
298
+ ): ToolType {
299
+ const execute = findMember(object, "execute");
300
+ if (!execute) {
301
+ throw new GenerativeCompileError(
302
+ "every tool must declare an `execute`; use `hitl()` for a " +
303
+ "human-in-the-loop tool",
304
+ filename,
305
+ );
306
+ }
307
+ if (executeIsHitl(execute)) return "human";
308
+ return executeIsClient(execute) ? "frontend" : "backend";
309
+ }
310
+
311
+ /** Writes the resolved `type` back onto the tool object (replacing any author's). */
312
+ function setToolType(object: t.ObjectExpression, type: ToolType): void {
313
+ removeMember(object, "type");
314
+ // Append (not prepend) so the inferred type wins over any earlier spread.
315
+ object.properties.push(
316
+ t.objectProperty(t.identifier("type"), t.stringLiteral(type)),
317
+ );
318
+ }
319
+
320
+ function memberName(
321
+ key: t.Node,
322
+ computed: boolean | undefined,
323
+ ): string | undefined {
324
+ if (computed) return undefined;
325
+ if (t.isIdentifier(key)) return key.name;
326
+ if (t.isStringLiteral(key)) return key.value;
327
+ return undefined;
328
+ }
329
+
330
+ /**
331
+ * Removes declarations and import specifiers left unreferenced after region
332
+ * removal, to a fixpoint (a dropped helper frees what it used). Keeps exports,
333
+ * side-effect imports, and possibly-side-effectful initializers.
334
+ */
335
+ function pruneUnused(ast: t.File): void {
336
+ const hadSpecifiers = new WeakSet<t.ImportDeclaration>();
337
+ for (const stmt of ast.program.body) {
338
+ if (t.isImportDeclaration(stmt) && stmt.specifiers.length > 0) {
339
+ hadSpecifiers.add(stmt);
340
+ }
341
+ }
342
+
343
+ let removedSomething = true;
344
+ while (removedSomething) {
345
+ removedSomething = false;
346
+ traverse(ast, {
347
+ Program(path: NodePath<t.Program>) {
348
+ path.scope.crawl();
349
+ const isUnused = (name: string): boolean => {
350
+ const binding = path.scope.getBinding(name);
351
+ return !!binding && !binding.referenced;
352
+ };
353
+
354
+ path.node.body = path.node.body.filter((stmt) => {
355
+ if (
356
+ (t.isFunctionDeclaration(stmt) || t.isClassDeclaration(stmt)) &&
357
+ stmt.id &&
358
+ isUnused(stmt.id.name)
359
+ ) {
360
+ removedSomething = true;
361
+ return false;
362
+ }
363
+ if (t.isVariableDeclaration(stmt)) {
364
+ stmt.declarations = stmt.declarations.filter((decl) => {
365
+ // Handles destructuring too: drop the declarator only when *every*
366
+ // bound name is unused (else a `const { a } = server` survives and
367
+ // keeps a server import in the client graph). Restricted to plain
368
+ // patterns so a default/computed-key side effect isn't dropped.
369
+ const names = Object.keys(t.getBindingIdentifiers(decl.id));
370
+ if (
371
+ names.length > 0 &&
372
+ isPlainPattern(decl.id) &&
373
+ isRemovableInit(decl.init) &&
374
+ names.every(isUnused)
375
+ ) {
376
+ removedSomething = true;
377
+ return false;
378
+ }
379
+ return true;
380
+ });
381
+ if (stmt.declarations.length === 0) return false;
382
+ }
383
+ return true;
384
+ });
385
+ path.stop();
386
+ },
387
+ });
388
+ }
389
+
390
+ traverse(ast, {
391
+ Program(path: NodePath<t.Program>) {
392
+ path.scope.crawl();
393
+ for (const stmt of path.node.body) {
394
+ if (!t.isImportDeclaration(stmt)) continue;
395
+ stmt.specifiers = stmt.specifiers.filter((spec) => {
396
+ const binding = path.scope.getBinding(spec.local.name);
397
+ return binding ? binding.referenced : true;
398
+ });
399
+ }
400
+ path.node.body = path.node.body.filter(
401
+ (stmt) =>
402
+ !(
403
+ t.isImportDeclaration(stmt) &&
404
+ stmt.specifiers.length === 0 &&
405
+ hadSpecifiers.has(stmt)
406
+ ),
407
+ );
408
+ path.stop();
409
+ },
410
+ });
411
+ }
412
+
413
+ /**
414
+ * Whether a binding pattern is side-effect-free to drop: a plain identifier, or
415
+ * an object/array pattern of plain bindings — no default values
416
+ * (`{ a = expr }`) or computed keys (`{ [expr]: a }`), which would evaluate (and
417
+ * thus could have side effects) at destructuring time.
418
+ */
419
+ function isPlainPattern(node: t.Node): boolean {
420
+ if (t.isIdentifier(node)) return true;
421
+ if (t.isObjectPattern(node)) {
422
+ return node.properties.every((p) =>
423
+ t.isRestElement(p)
424
+ ? isPlainPattern(p.argument)
425
+ : !p.computed && isPlainPattern(p.value),
426
+ );
427
+ }
428
+ if (t.isArrayPattern(node)) {
429
+ return node.elements.every((el) => el == null || isPlainPattern(el));
430
+ }
431
+ return false; // AssignmentPattern (default), member expr, etc.
432
+ }
433
+
434
+ /** Whether a variable initializer is safe to drop (no observable side effects). */
435
+ function isRemovableInit(node: t.Expression | null | undefined): boolean {
436
+ if (node == null) return true;
437
+ if (t.isTSAsExpression(node) || t.isTSSatisfiesExpression(node)) {
438
+ return isRemovableInit(node.expression);
439
+ }
440
+ // Containers are removable only if every element is — a nested call (e.g.
441
+ // `[track()]`) has an observable side effect that dropping would lose.
442
+ if (t.isArrayExpression(node)) {
443
+ return node.elements.every(
444
+ (el) => el == null || (t.isExpression(el) && isRemovableInit(el)),
445
+ );
446
+ }
447
+ if (t.isObjectExpression(node)) {
448
+ return node.properties.every(
449
+ (p) =>
450
+ t.isObjectProperty(p) &&
451
+ !p.computed &&
452
+ t.isExpression(p.value) &&
453
+ isRemovableInit(p.value),
454
+ );
455
+ }
456
+ if (t.isTemplateLiteral(node)) {
457
+ return node.expressions.every(
458
+ (e) => t.isExpression(e) && isRemovableInit(e),
459
+ );
460
+ }
461
+ return (
462
+ t.isArrowFunctionExpression(node) ||
463
+ t.isFunctionExpression(node) ||
464
+ t.isClassExpression(node) ||
465
+ t.isIdentifier(node) ||
466
+ // non-computed only — `obj[fn()]` could hide a side-effectful key
467
+ (t.isMemberExpression(node) && !node.computed) ||
468
+ t.isJSXElement(node) ||
469
+ t.isJSXFragment(node) ||
470
+ t.isLiteral(node)
471
+ );
472
+ }
@@ -0,0 +1,5 @@
1
+ /** The directive a module uses to opt into generative compilation. */
2
+ export const DIRECTIVE = "use generative";
3
+
4
+ /** Build target a generative module is compiled for. */
5
+ export type Target = "client" | "server";
@@ -0,0 +1,8 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { defineToolkit } from "./define-toolkit";
3
+
4
+ describe("defineToolkit", () => {
5
+ it("throws at runtime — it must be stripped by the compiler, never called", () => {
6
+ expect(() => defineToolkit({})).toThrow(/no runtime implementation/);
7
+ });
8
+ });
@@ -0,0 +1,22 @@
1
+ import type { Toolkit, ToolkitDeclaration } from "@assistant-ui/core/react";
2
+
3
+ /**
4
+ * Authoring helper for a `"use generative"` toolkit. Accepts the permissive
5
+ * {@link ToolkitDeclaration} (a `backend` tool may carry its server `execute`)
6
+ * and types the result as the canonical {@link Toolkit}.
7
+ *
8
+ * It has **no runtime implementation**. The `@assistant-ui/next` compiler strips
9
+ * the `defineToolkit(...)` wrapper (and its import) per build, so a correctly
10
+ * compiled `export default defineToolkit({...})` never calls this. If it *does*
11
+ * run, the module was not compiled by the use-generative loader — e.g.
12
+ * `defineToolkit` used outside a `"use generative"` file — which would ship a
13
+ * backend `execute` to the client. So it throws instead of silently leaking.
14
+ */
15
+ export function defineToolkit(_declaration: ToolkitDeclaration): Toolkit {
16
+ throw new Error(
17
+ "[assistant-ui/next] defineToolkit() has no runtime implementation — it is " +
18
+ "stripped at build time by the use-generative compiler. Reaching it means " +
19
+ 'this module was not compiled (e.g. defineToolkit used outside a "use ' +
20
+ 'generative" file). Add the directive, or do not use defineToolkit here.',
21
+ );
22
+ }
package/src/hitl.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Marks a tool as **human-in-the-loop**: the agent pauses and the UI (`render`)
3
+ * supplies the result instead of code. Use it as the tool's `execute`:
4
+ *
5
+ * ```tsx
6
+ * confirm: { execute: hitl(), render: (props) => <Confirm {...props} /> }
7
+ * ```
8
+ *
9
+ * Like {@link defineToolkit}, it has **no runtime implementation**: the
10
+ * `@assistant-ui/next` compiler detects `execute: hitl()`, drops it, and stamps
11
+ * the tool `type: "human"`. Reaching it at runtime means the module wasn't
12
+ * compiled (used outside a `"use generative"` file), so it throws.
13
+ */
14
+ export function hitl(): never {
15
+ throw new Error(
16
+ "[assistant-ui/next] hitl() has no runtime implementation — it marks a " +
17
+ "human-in-the-loop tool and is stripped at build time by the " +
18
+ "use-generative compiler. Reaching it means this module was not compiled " +
19
+ '(e.g. hitl() used outside a "use generative" file).',
20
+ );
21
+ }
package/src/loader.ts CHANGED
@@ -1,9 +1,6 @@
1
1
  import * as nodePath from "node:path";
2
- import {
3
- compileGenerative,
4
- isGenerativeModule,
5
- type Target,
6
- } from "@assistant-ui/x-generative-compiler";
2
+ import { compileGenerative, isGenerativeModule } from "./compile";
3
+ import type { Target } from "./constants";
7
4
 
8
5
  /** This package's name, used in the facade's re-export specifier. */
9
6
  const PKG = "@assistant-ui/next";