assistant-ui 0.0.123 → 0.0.124

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.
Files changed (33) hide show
  1. package/dist/codemods/v0-11/content-part-to-message-part.d.ts.map +1 -1
  2. package/dist/codemods/v0-11/content-part-to-message-part.js +126 -40
  3. package/dist/codemods/v0-11/content-part-to-message-part.js.map +1 -1
  4. package/dist/codemods/v0-12/event-names-to-camelcase.d.ts.map +1 -1
  5. package/dist/codemods/v0-12/event-names-to-camelcase.js +2 -2
  6. package/dist/codemods/v0-12/event-names-to-camelcase.js.map +1 -1
  7. package/dist/codemods/v0-12/primitive-if-to-aui-if.d.ts.map +1 -1
  8. package/dist/codemods/v0-12/primitive-if-to-aui-if.js +53 -37
  9. package/dist/codemods/v0-12/primitive-if-to-aui-if.js.map +1 -1
  10. package/dist/codemods/v0-8/ui-package-split.d.ts.map +1 -1
  11. package/dist/codemods/v0-8/ui-package-split.js +11 -35
  12. package/dist/codemods/v0-8/ui-package-split.js.map +1 -1
  13. package/dist/codemods/v0-9/edge-package-split.d.ts.map +1 -1
  14. package/dist/codemods/v0-9/edge-package-split.js +15 -69
  15. package/dist/codemods/v0-9/edge-package-split.js.map +1 -1
  16. package/dist/lib/utils/package-installer.d.ts.map +1 -1
  17. package/dist/lib/utils/package-installer.js +3 -2
  18. package/dist/lib/utils/package-installer.js.map +1 -1
  19. package/package.json +1 -1
  20. package/src/codemods/v0-11/__tests__/content-part-to-message-part.test.ts +268 -2
  21. package/src/codemods/v0-11/content-part-to-message-part.ts +211 -80
  22. package/src/codemods/v0-12/__tests__/event-names-to-camelcase.test.ts +23 -0
  23. package/src/codemods/v0-12/__tests__/primitive-if-to-aui-if.test.ts +115 -0
  24. package/src/codemods/v0-12/event-names-to-camelcase.ts +2 -8
  25. package/src/codemods/v0-12/primitive-if-to-aui-if.ts +87 -57
  26. package/src/codemods/v0-8/__tests__/ui-package-split.test.ts +83 -0
  27. package/src/codemods/v0-8/ui-package-split.ts +21 -59
  28. package/src/codemods/v0-9/edge-package-split.test.ts +100 -0
  29. package/src/codemods/v0-9/edge-package-split.ts +30 -139
  30. package/src/lib/utils/package-installer.test.ts +39 -0
  31. package/src/lib/utils/package-installer.ts +3 -1
  32. package/templates/cloud-harness/components/assistant-ui/elements/tool-fallback.aui.tsx +17 -8
  33. package/templates/cloud-harness/package.json +2 -2
@@ -1,4 +1,5 @@
1
1
  import { createTransformer } from "../utils/createTransformer";
2
+ import { resolveBinding } from "../utils/resolveBinding";
2
3
 
3
4
  type ConditionFragment = {
4
5
  expression: string;
@@ -185,37 +186,92 @@ const buildConditionString = (
185
186
 
186
187
  const migratePrimitiveIfToAuiIf = createTransformer(
187
188
  ({ j, root, markAsChanged }) => {
188
- let needsAuiIfImport = false;
189
+ const importedPrimitives = new Map<
190
+ string,
191
+ { name: string; declaration: any; binding: any }
192
+ >();
193
+ const auiIfImports: { source: string; binding: any }[] = [];
194
+ const usedNames = new Set<string>();
195
+ root
196
+ .find(j.Identifier)
197
+ .forEach((path: any) => usedNames.add(path.value.name));
198
+ root
199
+ .find(j.JSXIdentifier)
200
+ .forEach((path: any) => usedNames.add(path.value.name));
189
201
 
190
- // Track which primitive namespaces are imported
191
- const importedPrimitives = new Set<string>();
192
202
  root.find(j.ImportDeclaration).forEach((path: any) => {
193
- const source = path.value.source.value;
194
- if (typeof source === "string" && source.startsWith("@assistant-ui/")) {
195
- path.value.specifiers?.forEach((specifier: any) => {
196
- if (j.ImportSpecifier.check(specifier)) {
197
- const name = String(
198
- specifier.local?.name ?? specifier.imported.name,
199
- );
200
- if (primitiveMap[name] || fixedConditionMap[name]) {
201
- importedPrimitives.add(name);
202
- }
203
- }
204
- });
205
- }
203
+ const declaration = path.value;
204
+ const source = declaration.source.value;
205
+ if (
206
+ typeof source !== "string" ||
207
+ !source.startsWith("@assistant-ui/") ||
208
+ declaration.importKind === "type"
209
+ )
210
+ return;
211
+
212
+ declaration.specifiers?.forEach((specifier: any) => {
213
+ if (
214
+ !j.ImportSpecifier.check(specifier) ||
215
+ !j.Identifier.check(specifier.imported) ||
216
+ !j.Identifier.check(specifier.local) ||
217
+ (specifier as { importKind?: string }).importKind === "type"
218
+ )
219
+ return;
220
+ const name = specifier.imported.name;
221
+ const binding = specifier.local;
222
+ if (primitiveMap[name] || fixedConditionMap[name]) {
223
+ importedPrimitives.set(binding.name, { name, declaration, binding });
224
+ }
225
+ if (name === "AuiIf") auiIfImports.push({ source, binding });
226
+ });
206
227
  });
207
228
 
208
229
  if (importedPrimitives.size === 0) return;
209
230
 
231
+ const getPrimitive = (path: any, localName: string) => {
232
+ const primitive = importedPrimitives.get(localName);
233
+ return primitive &&
234
+ resolveBinding(j, path, localName) === primitive.binding
235
+ ? primitive
236
+ : undefined;
237
+ };
238
+
239
+ const getAuiIfName = (path: any, declaration: any) => {
240
+ const source = declaration.source.value;
241
+ const existing = auiIfImports.find(
242
+ (entry) =>
243
+ entry.source === source &&
244
+ !/^[a-z]/.test(entry.binding.name) &&
245
+ resolveBinding(j, path, entry.binding.name) === entry.binding,
246
+ );
247
+ if (existing) return existing.binding.name;
248
+
249
+ let name = "AuiIf";
250
+ for (let suffix = 1; usedNames.has(name); suffix++)
251
+ name = `AuiIf${suffix}`;
252
+ usedNames.add(name);
253
+ const binding = j.identifier(name);
254
+ declaration.specifiers.push(
255
+ j.importSpecifier(j.identifier("AuiIf"), binding),
256
+ );
257
+ auiIfImports.push({ source, binding });
258
+ return name;
259
+ };
260
+
210
261
  // Opening and closing tags are rewritten together per element, so a
211
262
  // skipped element can never be left with a mismatched closing tag.
212
- const convertElementToAuiIf = (elementPath: any, conditionBody: string) => {
263
+ const convertElementToAuiIf = (
264
+ elementPath: any,
265
+ conditionBody: string,
266
+ declaration: any,
267
+ ) => {
268
+ const localName = getAuiIfName(elementPath, declaration);
213
269
  const arrowFnAst = j(`(s) => ${conditionBody}`)
214
270
  .find(j.ArrowFunctionExpression)
215
271
  .paths()[0]!.value;
216
272
 
217
273
  const opening = elementPath.value.openingElement;
218
- opening.name = j.jsxIdentifier("AuiIf");
274
+ opening.name = j.jsxIdentifier(localName);
219
275
  opening.attributes = [
220
276
  j.jsxAttribute(
221
277
  j.jsxIdentifier("condition"),
@@ -223,10 +279,9 @@ const migratePrimitiveIfToAuiIf = createTransformer(
223
279
  ),
224
280
  ];
225
281
  if (elementPath.value.closingElement) {
226
- elementPath.value.closingElement.name = j.jsxIdentifier("AuiIf");
282
+ elementPath.value.closingElement.name = j.jsxIdentifier(localName);
227
283
  }
228
284
 
229
- needsAuiIfImport = true;
230
285
  markAsChanged();
231
286
  };
232
287
 
@@ -237,17 +292,17 @@ const migratePrimitiveIfToAuiIf = createTransformer(
237
292
  if (!j.JSXIdentifier.check(name.object)) return;
238
293
  if (!j.JSXIdentifier.check(name.property)) return;
239
294
 
240
- const primitiveName = name.object.name as string;
295
+ const primitive = getPrimitive(path, name.object.name);
296
+ if (!primitive) return;
241
297
  const propertyName = name.property.name as string;
242
- const conditionBody = fixedConditionMap[primitiveName]?.[propertyName];
298
+ const conditionBody = fixedConditionMap[primitive.name]?.[propertyName];
243
299
  if (!conditionBody) return;
244
- if (!importedPrimitives.has(primitiveName)) return;
245
300
 
246
301
  // Only transform if there are no props (other than children, which are implicit)
247
302
  const attrs: any[] = path.value.openingElement.attributes || [];
248
303
  if (attrs.length > 0) return;
249
304
 
250
- convertElementToAuiIf(path, conditionBody);
305
+ convertElementToAuiIf(path, conditionBody, primitive.declaration);
251
306
  });
252
307
 
253
308
  // Process JSX elements: <ThreadPrimitive.If ...> → <AuiIf condition={...}>
@@ -260,10 +315,10 @@ const migratePrimitiveIfToAuiIf = createTransformer(
260
315
  if (!j.JSXIdentifier.check(name.property)) return;
261
316
  if (name.property.name !== "If") return;
262
317
 
263
- const primitiveName = name.object.name;
264
- const propMap = primitiveMap[primitiveName];
318
+ const primitive = getPrimitive(path, name.object.name);
319
+ if (!primitive) return;
320
+ const propMap = primitiveMap[primitive.name];
265
321
  if (!propMap) return;
266
- if (!importedPrimitives.has(primitiveName)) return;
267
322
 
268
323
  // Extract props
269
324
  const attrs: any[] = path.value.openingElement.attributes || [];
@@ -304,37 +359,12 @@ const migratePrimitiveIfToAuiIf = createTransformer(
304
359
  // If we couldn't map all props, skip this element
305
360
  if (hasUnknownProp || fragments.length === 0) return;
306
361
 
307
- convertElementToAuiIf(path, buildConditionString(j, fragments));
362
+ convertElementToAuiIf(
363
+ path,
364
+ buildConditionString(j, fragments),
365
+ primitive.declaration,
366
+ );
308
367
  });
309
-
310
- // Add AuiIf import if needed
311
- if (needsAuiIfImport) {
312
- let hasAuiIfImport = false;
313
- let assistantUiImport: any = null;
314
-
315
- root.find(j.ImportDeclaration).forEach((path: any) => {
316
- const source = path.value.source.value;
317
- if (typeof source === "string" && source.startsWith("@assistant-ui/")) {
318
- assistantUiImport = path;
319
- path.value.specifiers?.forEach((specifier: any) => {
320
- if (
321
- j.ImportSpecifier.check(specifier) &&
322
- (specifier.imported.name === "AuiIf" ||
323
- specifier.local?.name === "AuiIf")
324
- ) {
325
- hasAuiIfImport = true;
326
- }
327
- });
328
- }
329
- });
330
-
331
- if (!hasAuiIfImport && assistantUiImport) {
332
- assistantUiImport.value.specifiers.push(
333
- j.importSpecifier(j.identifier("AuiIf")),
334
- );
335
- markAsChanged();
336
- }
337
- }
338
368
  },
339
369
  );
340
370
 
@@ -0,0 +1,83 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import jscodeshift, { type API } from "jscodeshift";
3
+ import transform from "../ui-package-split";
4
+
5
+ const j = jscodeshift.withParser("tsx");
6
+ const api: API = { jscodeshift: j, j, stats: () => {}, report: () => {} };
7
+ const apply = (source: string) =>
8
+ transform({ path: "app.tsx", source }, api, {});
9
+
10
+ describe("ui package split", () => {
11
+ it("preserves an aliased moved binding beside the same exported name", () => {
12
+ const output =
13
+ apply(`import { Thread as MigratedThread } from "@assistant-ui/react";
14
+ import { Thread as ExistingThread } from "@assistant-ui/react-ui";
15
+ const thread = MigratedThread;`)!;
16
+ const imports = j(output)
17
+ .find(j.ImportDeclaration, {
18
+ source: { value: "@assistant-ui/react-ui" },
19
+ })
20
+ .nodes();
21
+
22
+ expect(
23
+ imports.flatMap((declaration) =>
24
+ declaration.specifiers?.map((specifier) => specifier.local?.name),
25
+ ),
26
+ ).toEqual(expect.arrayContaining(["MigratedThread", "ExistingThread"]));
27
+ expect(output).toContain("const thread = MigratedThread;");
28
+ });
29
+
30
+ it("preserves a moved binding beside a namespace import", () => {
31
+ const output = apply(`import { Thread } from "@assistant-ui/react";
32
+ import * as ui from "@assistant-ui/react-ui";
33
+ const thread = Thread;`)!;
34
+ const imports = j(output)
35
+ .find(j.ImportDeclaration, {
36
+ source: { value: "@assistant-ui/react-ui" },
37
+ })
38
+ .nodes();
39
+
40
+ expect(
41
+ imports.flatMap((declaration) =>
42
+ declaration.specifiers?.map((specifier) => specifier.local?.name),
43
+ ),
44
+ ).toEqual(expect.arrayContaining(["Thread", "ui"]));
45
+ expect(output).toContain("const thread = Thread;");
46
+ });
47
+
48
+ it("keeps a moved value import out of an existing type declaration", () => {
49
+ const output = apply(`import { Composer } from "@assistant-ui/react";
50
+ import type { ThreadConfig } from "@assistant-ui/react-ui";
51
+ const composer = Composer;`)!;
52
+ const imports = j(output)
53
+ .find(j.ImportDeclaration, {
54
+ source: { value: "@assistant-ui/react-ui" },
55
+ })
56
+ .nodes();
57
+ const valueImport = imports.find((declaration) =>
58
+ declaration.specifiers?.some(
59
+ (specifier) => specifier.local?.name === "Composer",
60
+ ),
61
+ );
62
+
63
+ expect(valueImport).toBeDefined();
64
+ expect(valueImport?.importKind).not.toBe("type");
65
+ expect(output).toContain("const composer = Composer;");
66
+ });
67
+
68
+ it("keeps the declaration-level type kind when splitting an import", () => {
69
+ const output = apply(
70
+ 'import type { ThreadConfig, AssistantRuntime } from "@assistant-ui/react";',
71
+ )!;
72
+ const declarations = j(output).find(j.ImportDeclaration).nodes();
73
+
74
+ expect(declarations).toHaveLength(2);
75
+ expect(declarations.map((declaration) => declaration.importKind)).toEqual([
76
+ "type",
77
+ "type",
78
+ ]);
79
+ expect(declarations.map((declaration) => declaration.source.value)).toEqual(
80
+ ["@assistant-ui/react", "@assistant-ui/react-ui"],
81
+ );
82
+ });
83
+ });
@@ -33,72 +33,34 @@ const migrateAssistantUI = createTransformer(({ j, root, markAsChanged }) => {
33
33
  "@assistant-ui/react",
34
34
  "@assistant-ui/react-markdown",
35
35
  ];
36
- const movedSpecifiers: any[] = [];
37
- let lastMigratedImportPath: any = null;
38
36
 
39
37
  root
40
38
  .find(j.ImportDeclaration)
41
- .filter((path: any) => sourcesToMigrate.includes(path.value.source.value))
42
- .forEach((path: any) => {
43
- let hadMigratedSpecifiers = false;
44
- const remainingSpecifiers: any[] = [];
45
- path.value.specifiers.forEach((specifier: any) => {
46
- if (
39
+ .filter((path) =>
40
+ sourcesToMigrate.includes(path.node.source.value as string),
41
+ )
42
+ .forEach((path) => {
43
+ const specifiers = path.node.specifiers ?? [];
44
+ const moved = specifiers.filter(
45
+ (specifier) =>
47
46
  j.ImportSpecifier.check(specifier) &&
48
- reactUIExports.includes(specifier.imported.name as string)
49
- ) {
50
- movedSpecifiers.push(specifier);
51
- hadMigratedSpecifiers = true;
52
- } else {
53
- remainingSpecifiers.push(specifier);
54
- }
55
- });
56
- if (hadMigratedSpecifiers) {
57
- lastMigratedImportPath = path;
58
- }
59
- if (remainingSpecifiers.length === 0) {
60
- j(path).remove();
61
- markAsChanged();
62
- } else if (remainingSpecifiers.length !== path.value.specifiers.length) {
63
- path.value.specifiers = remainingSpecifiers;
64
- markAsChanged();
65
- }
66
- });
67
-
68
- if (movedSpecifiers.length > 0) {
69
- const existingReactUIImport = root.find(j.ImportDeclaration, {
70
- source: { value: "@assistant-ui/react-ui" },
71
- });
72
- if (existingReactUIImport.size() > 0) {
73
- existingReactUIImport.forEach((path: any) => {
74
- movedSpecifiers.forEach((specifier: any) => {
75
- if (
76
- !path.value.specifiers.some(
77
- (s: any) => s.imported.name === specifier.imported.name,
78
- )
79
- ) {
80
- path.value.specifiers.push(specifier);
81
- }
82
- });
83
- });
84
- } else {
85
- const newImport = j.importDeclaration(
86
- movedSpecifiers,
87
- j.literal("@assistant-ui/react-ui"),
47
+ reactUIExports.includes(specifier.imported.name as string),
88
48
  );
89
- if (lastMigratedImportPath) {
90
- j(lastMigratedImportPath).insertAfter(newImport);
49
+ if (moved.length === 0) return;
50
+
51
+ const destination = j.literal("@assistant-ui/react-ui");
52
+ if (moved.length === specifiers.length) {
53
+ path.node.source = destination;
91
54
  } else {
92
- const firstImport = root.find(j.ImportDeclaration).at(0);
93
- if (firstImport.size() > 0) {
94
- firstImport.insertBefore(newImport);
95
- } else {
96
- root.get().node.program.body.unshift(newImport);
97
- }
55
+ path.node.specifiers = specifiers.filter(
56
+ (specifier) => !moved.includes(specifier),
57
+ );
58
+ const declaration = j.importDeclaration(moved, destination);
59
+ declaration.importKind = path.node.importKind ?? "value";
60
+ j(path).insertAfter(declaration);
98
61
  }
99
- }
100
- markAsChanged();
101
- }
62
+ markAsChanged();
63
+ });
102
64
 
103
65
  const cssReplacements: Record<string, string> = {
104
66
  "@assistant-ui/react/styles/index.css":
@@ -0,0 +1,100 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import jscodeshift, { type API } from "jscodeshift";
3
+ import transform from "./edge-package-split";
4
+
5
+ const j = jscodeshift.withParser("tsx");
6
+ const api: API = { jscodeshift: j, j, stats: () => {}, report: () => {} };
7
+ const apply = (source: string) =>
8
+ transform({ path: "app.tsx", source }, api, {});
9
+
10
+ describe.each(["@assistant-ui/react", "@assistant-ui/react-edge"])(
11
+ "edge migration from %s",
12
+ (source) => {
13
+ it.each([
14
+ 'import { useChatRuntime as existingRuntime } from "@assistant-ui/react-ai-sdk";',
15
+ 'import * as sdk from "@assistant-ui/react-ai-sdk";',
16
+ 'import { useChatRuntime as first } from "@assistant-ui/react-ai-sdk";\nimport { useChatRuntime as second } from "@assistant-ui/react-ai-sdk";',
17
+ 'import type { CoreMessage } from "@assistant-ui/react-ai-sdk";',
18
+ ])("preserves the moved local binding beside %s", (existing) => {
19
+ const output = apply(`import { useChatRuntime } from "${source}";
20
+ ${existing}
21
+ const runtime = useChatRuntime;`)!;
22
+
23
+ expect(output).toContain(existing);
24
+ const imports = j(output)
25
+ .find(j.ImportDeclaration, {
26
+ source: { value: "@assistant-ui/react-ai-sdk" },
27
+ })
28
+ .nodes();
29
+ const binding = imports.filter((declaration) =>
30
+ declaration.specifiers?.some(
31
+ (specifier) => specifier.local?.name === "useChatRuntime",
32
+ ),
33
+ );
34
+ expect(binding).toHaveLength(1);
35
+ expect(binding[0]!.importKind).not.toBe("type");
36
+ expect(output).toContain("const runtime = useChatRuntime;");
37
+ expect(apply(output)).toBeNull();
38
+ });
39
+
40
+ it("keeps declaration-level type imports type-only", () => {
41
+ const output = apply(`import type { CoreMessage } from "${source}";
42
+ type Message = CoreMessage;`)!;
43
+ const declaration = j(output).find(j.ImportDeclaration).nodes()[0]!;
44
+ expect(declaration.source.value).toBe("@assistant-ui/react-ai-sdk");
45
+ expect(declaration.importKind).toBe("type");
46
+ expect(output).toContain("type Message = CoreMessage;");
47
+ });
48
+ },
49
+ );
50
+
51
+ it("keeps unrelated bindings and specifier-level type imports", () => {
52
+ const output =
53
+ apply(`import { AssistantRuntimeProvider, type CoreMessage, useChatRuntime as useRuntime } from "@assistant-ui/react";
54
+ export { AssistantRuntimeProvider, useRuntime };
55
+ export type { CoreMessage };`)!;
56
+ const root = j(output);
57
+ const declarations = root.find(j.ImportDeclaration).nodes();
58
+ const retained = declarations.find(
59
+ (declaration) => declaration.source.value === "@assistant-ui/react",
60
+ )!;
61
+ expect(
62
+ retained.specifiers?.map((specifier) => specifier.local?.name),
63
+ ).toEqual(["AssistantRuntimeProvider"]);
64
+ const moved = root.find(j.ImportSpecifier).nodes();
65
+ expect(
66
+ moved.find((specifier) => specifier.local?.name === "CoreMessage"),
67
+ ).toMatchObject({ importKind: "type" });
68
+ expect(
69
+ moved.find((specifier) => specifier.local?.name === "useRuntime")?.imported
70
+ .name,
71
+ ).toBe("useChatRuntime");
72
+ expect(apply(output)).toBeNull();
73
+ });
74
+
75
+ it("keeps both halves of a split type declaration type-only", () => {
76
+ const output = apply(
77
+ 'import type { AssistantRuntime, CoreMessage } from "@assistant-ui/react";',
78
+ )!;
79
+ const declarations = j(output).find(j.ImportDeclaration).nodes();
80
+ expect(declarations).toHaveLength(2);
81
+ expect(declarations.map((declaration) => declaration.importKind)).toEqual([
82
+ "type",
83
+ "type",
84
+ ]);
85
+ expect(declarations.map((declaration) => declaration.source.value)).toEqual([
86
+ "@assistant-ui/react",
87
+ "@assistant-ui/react-ai-sdk",
88
+ ]);
89
+ });
90
+
91
+ it.each(["edge/converters", "dangerous-in-browser/converters"])(
92
+ "continues migrating the %s subpath",
93
+ (subpath) => {
94
+ expect(
95
+ apply(
96
+ `export { toCoreMessages } from "@assistant-ui/react/${subpath}";\nimport { fromCoreMessages } from "@assistant-ui/react/${subpath}";`,
97
+ ),
98
+ ).toContain(`from "@assistant-ui/react-ai-sdk/${subpath}"`);
99
+ },
100
+ );