@assistant-ui/next 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/SPEC.md +1 -1
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -3
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +1 -1
- package/dist/loader.js.map +1 -1
- package/package.json +3 -12
- package/src/index.ts +0 -2
- package/src/loader.ts +5 -2
- package/dist/compile.d.ts +0 -30
- package/dist/compile.d.ts.map +0 -1
- package/dist/compile.js +0 -235
- package/dist/compile.js.map +0 -1
- package/dist/constants.d.ts +0 -8
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -7
- package/dist/constants.js.map +0 -1
- package/dist/define-toolkit.d.ts +0 -19
- package/dist/define-toolkit.d.ts.map +0 -1
- package/dist/define-toolkit.js +0 -20
- package/dist/define-toolkit.js.map +0 -1
- package/dist/hitl.d.ts +0 -18
- package/dist/hitl.d.ts.map +0 -1
- package/dist/hitl.js +0 -21
- package/dist/hitl.js.map +0 -1
- package/src/compile.test.ts +0 -267
- package/src/compile.ts +0 -472
- package/src/constants.ts +0 -5
- package/src/define-toolkit.test.ts +0 -8
- package/src/define-toolkit.ts +0 -22
- package/src/hitl.ts +0 -21
package/src/compile.ts
DELETED
|
@@ -1,472 +0,0 @@
|
|
|
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
|
-
}
|
package/src/constants.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
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
|
-
});
|
package/src/define-toolkit.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
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
|
-
}
|