assistant-ui 0.0.123 → 0.0.125

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 (54) hide show
  1. package/README.md +7 -0
  2. package/dist/codemods/v0-11/content-part-to-message-part.d.ts.map +1 -1
  3. package/dist/codemods/v0-11/content-part-to-message-part.js +126 -40
  4. package/dist/codemods/v0-11/content-part-to-message-part.js.map +1 -1
  5. package/dist/codemods/v0-12/event-names-to-camelcase.d.ts.map +1 -1
  6. package/dist/codemods/v0-12/event-names-to-camelcase.js +2 -2
  7. package/dist/codemods/v0-12/event-names-to-camelcase.js.map +1 -1
  8. package/dist/codemods/v0-12/primitive-if-to-aui-if.d.ts.map +1 -1
  9. package/dist/codemods/v0-12/primitive-if-to-aui-if.js +57 -41
  10. package/dist/codemods/v0-12/primitive-if-to-aui-if.js.map +1 -1
  11. package/dist/codemods/v0-8/ui-package-split.d.ts.map +1 -1
  12. package/dist/codemods/v0-8/ui-package-split.js +11 -35
  13. package/dist/codemods/v0-8/ui-package-split.js.map +1 -1
  14. package/dist/codemods/v0-9/edge-package-split.d.ts.map +1 -1
  15. package/dist/codemods/v0-9/edge-package-split.js +15 -69
  16. package/dist/codemods/v0-9/edge-package-split.js.map +1 -1
  17. package/dist/commands/cloud.d.ts.map +1 -1
  18. package/dist/commands/cloud.js +12 -3
  19. package/dist/commands/cloud.js.map +1 -1
  20. package/dist/commands/info.d.ts.map +1 -1
  21. package/dist/commands/info.js +8 -1
  22. package/dist/commands/info.js.map +1 -1
  23. package/dist/lib/cloud-auth.d.ts +1 -0
  24. package/dist/lib/cloud-auth.d.ts.map +1 -1
  25. package/dist/lib/cloud-auth.js +4 -3
  26. package/dist/lib/cloud-auth.js.map +1 -1
  27. package/dist/lib/cloud-remote.d.ts +22 -0
  28. package/dist/lib/cloud-remote.d.ts.map +1 -0
  29. package/dist/lib/cloud-remote.js +74 -0
  30. package/dist/lib/cloud-remote.js.map +1 -0
  31. package/dist/lib/utils/package-installer.d.ts.map +1 -1
  32. package/dist/lib/utils/package-installer.js +3 -2
  33. package/dist/lib/utils/package-installer.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/codemods/v0-11/__tests__/content-part-to-message-part.test.ts +268 -2
  36. package/src/codemods/v0-11/content-part-to-message-part.ts +211 -80
  37. package/src/codemods/v0-12/__tests__/event-names-to-camelcase.test.ts +23 -0
  38. package/src/codemods/v0-12/__tests__/primitive-if-to-aui-if.test.ts +150 -1
  39. package/src/codemods/v0-12/event-names-to-camelcase.ts +2 -8
  40. package/src/codemods/v0-12/primitive-if-to-aui-if.ts +92 -61
  41. package/src/codemods/v0-8/__tests__/ui-package-split.test.ts +83 -0
  42. package/src/codemods/v0-8/ui-package-split.ts +21 -59
  43. package/src/codemods/v0-9/edge-package-split.test.ts +100 -0
  44. package/src/codemods/v0-9/edge-package-split.ts +30 -139
  45. package/src/commands/cloud.test.ts +58 -0
  46. package/src/commands/cloud.ts +19 -2
  47. package/src/commands/info.ts +26 -1
  48. package/src/lib/cloud-auth.ts +14 -8
  49. package/src/lib/cloud-remote.test.ts +156 -0
  50. package/src/lib/cloud-remote.ts +150 -0
  51. package/src/lib/utils/package-installer.test.ts +39 -0
  52. package/src/lib/utils/package-installer.ts +3 -1
  53. package/templates/cloud-harness/components/assistant-ui/elements/tool-fallback.aui.tsx +17 -8
  54. 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;
@@ -41,8 +42,8 @@ const messagePropMap: Record<
41
42
  expression: `s.message.role ${v === false ? "!==" : "==="} "system"`,
42
43
  negated: false,
43
44
  }),
44
- hasBranches: () => ({
45
- expression: "s.message.branchCount >= 2",
45
+ hasBranches: (v) => ({
46
+ expression: v === false ? "true" : "s.message.branchCount >= 2",
46
47
  negated: false,
47
48
  }),
48
49
  copied: (v) => ({
@@ -53,8 +54,9 @@ const messagePropMap: Record<
53
54
  expression: "s.message.isLast",
54
55
  negated: v === false,
55
56
  }),
56
- lastOrHover: () => ({
57
- expression: "s.message.isHovering || s.message.isLast",
57
+ lastOrHover: (v) => ({
58
+ expression:
59
+ v === false ? "true" : "s.message.isHovering || s.message.isLast",
58
60
  negated: false,
59
61
  }),
60
62
  speaking: (v) => ({
@@ -185,37 +187,92 @@ const buildConditionString = (
185
187
 
186
188
  const migratePrimitiveIfToAuiIf = createTransformer(
187
189
  ({ j, root, markAsChanged }) => {
188
- let needsAuiIfImport = false;
190
+ const importedPrimitives = new Map<
191
+ string,
192
+ { name: string; declaration: any; binding: any }
193
+ >();
194
+ const auiIfImports: { source: string; binding: any }[] = [];
195
+ const usedNames = new Set<string>();
196
+ root
197
+ .find(j.Identifier)
198
+ .forEach((path: any) => usedNames.add(path.value.name));
199
+ root
200
+ .find(j.JSXIdentifier)
201
+ .forEach((path: any) => usedNames.add(path.value.name));
189
202
 
190
- // Track which primitive namespaces are imported
191
- const importedPrimitives = new Set<string>();
192
203
  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
- }
204
+ const declaration = path.value;
205
+ const source = declaration.source.value;
206
+ if (
207
+ typeof source !== "string" ||
208
+ !source.startsWith("@assistant-ui/") ||
209
+ declaration.importKind === "type"
210
+ )
211
+ return;
212
+
213
+ declaration.specifiers?.forEach((specifier: any) => {
214
+ if (
215
+ !j.ImportSpecifier.check(specifier) ||
216
+ !j.Identifier.check(specifier.imported) ||
217
+ !j.Identifier.check(specifier.local) ||
218
+ (specifier as { importKind?: string }).importKind === "type"
219
+ )
220
+ return;
221
+ const name = specifier.imported.name;
222
+ const binding = specifier.local;
223
+ if (primitiveMap[name] || fixedConditionMap[name]) {
224
+ importedPrimitives.set(binding.name, { name, declaration, binding });
225
+ }
226
+ if (name === "AuiIf") auiIfImports.push({ source, binding });
227
+ });
206
228
  });
207
229
 
208
230
  if (importedPrimitives.size === 0) return;
209
231
 
232
+ const getPrimitive = (path: any, localName: string) => {
233
+ const primitive = importedPrimitives.get(localName);
234
+ return primitive &&
235
+ resolveBinding(j, path, localName) === primitive.binding
236
+ ? primitive
237
+ : undefined;
238
+ };
239
+
240
+ const getAuiIfName = (path: any, declaration: any) => {
241
+ const source = declaration.source.value;
242
+ const existing = auiIfImports.find(
243
+ (entry) =>
244
+ entry.source === source &&
245
+ !/^[a-z]/.test(entry.binding.name) &&
246
+ resolveBinding(j, path, entry.binding.name) === entry.binding,
247
+ );
248
+ if (existing) return existing.binding.name;
249
+
250
+ let name = "AuiIf";
251
+ for (let suffix = 1; usedNames.has(name); suffix++)
252
+ name = `AuiIf${suffix}`;
253
+ usedNames.add(name);
254
+ const binding = j.identifier(name);
255
+ declaration.specifiers.push(
256
+ j.importSpecifier(j.identifier("AuiIf"), binding),
257
+ );
258
+ auiIfImports.push({ source, binding });
259
+ return name;
260
+ };
261
+
210
262
  // Opening and closing tags are rewritten together per element, so a
211
263
  // skipped element can never be left with a mismatched closing tag.
212
- const convertElementToAuiIf = (elementPath: any, conditionBody: string) => {
264
+ const convertElementToAuiIf = (
265
+ elementPath: any,
266
+ conditionBody: string,
267
+ declaration: any,
268
+ ) => {
269
+ const localName = getAuiIfName(elementPath, declaration);
213
270
  const arrowFnAst = j(`(s) => ${conditionBody}`)
214
271
  .find(j.ArrowFunctionExpression)
215
272
  .paths()[0]!.value;
216
273
 
217
274
  const opening = elementPath.value.openingElement;
218
- opening.name = j.jsxIdentifier("AuiIf");
275
+ opening.name = j.jsxIdentifier(localName);
219
276
  opening.attributes = [
220
277
  j.jsxAttribute(
221
278
  j.jsxIdentifier("condition"),
@@ -223,10 +280,9 @@ const migratePrimitiveIfToAuiIf = createTransformer(
223
280
  ),
224
281
  ];
225
282
  if (elementPath.value.closingElement) {
226
- elementPath.value.closingElement.name = j.jsxIdentifier("AuiIf");
283
+ elementPath.value.closingElement.name = j.jsxIdentifier(localName);
227
284
  }
228
285
 
229
- needsAuiIfImport = true;
230
286
  markAsChanged();
231
287
  };
232
288
 
@@ -237,17 +293,17 @@ const migratePrimitiveIfToAuiIf = createTransformer(
237
293
  if (!j.JSXIdentifier.check(name.object)) return;
238
294
  if (!j.JSXIdentifier.check(name.property)) return;
239
295
 
240
- const primitiveName = name.object.name as string;
296
+ const primitive = getPrimitive(path, name.object.name);
297
+ if (!primitive) return;
241
298
  const propertyName = name.property.name as string;
242
- const conditionBody = fixedConditionMap[primitiveName]?.[propertyName];
299
+ const conditionBody = fixedConditionMap[primitive.name]?.[propertyName];
243
300
  if (!conditionBody) return;
244
- if (!importedPrimitives.has(primitiveName)) return;
245
301
 
246
302
  // Only transform if there are no props (other than children, which are implicit)
247
303
  const attrs: any[] = path.value.openingElement.attributes || [];
248
304
  if (attrs.length > 0) return;
249
305
 
250
- convertElementToAuiIf(path, conditionBody);
306
+ convertElementToAuiIf(path, conditionBody, primitive.declaration);
251
307
  });
252
308
 
253
309
  // Process JSX elements: <ThreadPrimitive.If ...> → <AuiIf condition={...}>
@@ -260,10 +316,10 @@ const migratePrimitiveIfToAuiIf = createTransformer(
260
316
  if (!j.JSXIdentifier.check(name.property)) return;
261
317
  if (name.property.name !== "If") return;
262
318
 
263
- const primitiveName = name.object.name;
264
- const propMap = primitiveMap[primitiveName];
319
+ const primitive = getPrimitive(path, name.object.name);
320
+ if (!primitive) return;
321
+ const propMap = primitiveMap[primitive.name];
265
322
  if (!propMap) return;
266
- if (!importedPrimitives.has(primitiveName)) return;
267
323
 
268
324
  // Extract props
269
325
  const attrs: any[] = path.value.openingElement.attributes || [];
@@ -304,37 +360,12 @@ const migratePrimitiveIfToAuiIf = createTransformer(
304
360
  // If we couldn't map all props, skip this element
305
361
  if (hasUnknownProp || fragments.length === 0) return;
306
362
 
307
- convertElementToAuiIf(path, buildConditionString(j, fragments));
363
+ convertElementToAuiIf(
364
+ path,
365
+ buildConditionString(j, fragments),
366
+ primitive.declaration,
367
+ );
308
368
  });
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
369
  },
339
370
  );
340
371
 
@@ -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
+ );