@assistant-ui/core 0.3.6 → 0.3.8

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 (89) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/react/RuntimeAdapter.js +1 -1
  3. package/dist/react/RuntimeAdapter.js.map +1 -1
  4. package/dist/react/client/Interactables.js +1 -1
  5. package/dist/react/client/Interactables.js.map +1 -1
  6. package/dist/react/client/Tools.d.ts +1 -1
  7. package/dist/react/client/Tools.js +1 -1
  8. package/dist/react/client/Tools.js.map +1 -1
  9. package/dist/react/index.d.ts +9 -9
  10. package/dist/react/index.js +4 -4
  11. package/dist/react/model-context/interactableTool.d.ts +1 -1
  12. package/dist/react/model-context/useAssistantTool.d.ts +1 -1
  13. package/dist/react/runtimes/cloud/auiV0.d.ts +1 -0
  14. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  15. package/dist/react/runtimes/cloud/auiV0.js +2 -1
  16. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  17. package/dist/react/runtimes/createMessageConverter.d.ts +1 -1
  18. package/dist/react/runtimes/createRuntimeExtras.d.ts +4 -1
  19. package/dist/react/runtimes/createRuntimeExtras.d.ts.map +1 -1
  20. package/dist/react/runtimes/createRuntimeExtras.js +6 -17
  21. package/dist/react/runtimes/createRuntimeExtras.js.map +1 -1
  22. package/dist/react/types/scopes/dataRenderers.d.ts +1 -1
  23. package/dist/react/types/scopes/interactables.d.ts +1 -1
  24. package/dist/react/types/scopes/tools.d.ts +1 -1
  25. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -1
  26. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts +0 -2
  27. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
  28. package/dist/runtime/base/default-edit-composer-runtime-core.js +18 -25
  29. package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
  30. package/dist/runtime/utils/runtime-extras-brand.d.ts +21 -0
  31. package/dist/runtime/utils/runtime-extras-brand.d.ts.map +1 -0
  32. package/dist/runtime/utils/runtime-extras-brand.js +37 -0
  33. package/dist/runtime/utils/runtime-extras-brand.js.map +1 -0
  34. package/dist/runtime/utils/thread-message-like.js +3 -1
  35. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  36. package/dist/store/clients/runtime-adapter.d.ts.map +1 -1
  37. package/dist/store/clients/runtime-adapter.js +1 -1
  38. package/dist/store/clients/runtime-adapter.js.map +1 -1
  39. package/dist/store/clients/suggestions.js +1 -1
  40. package/dist/store/clients/suggestions.js.map +1 -1
  41. package/dist/store/clients/thread-message-client.js +1 -1
  42. package/dist/store/clients/thread-message-client.js.map +1 -1
  43. package/dist/store/index.d.ts +10 -1
  44. package/dist/store/index.js +7 -1
  45. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  46. package/dist/store/runtime-clients/composer-runtime-client.js +1 -1
  47. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  48. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  49. package/dist/store/runtime-clients/message-runtime-client.js +1 -1
  50. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  51. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  52. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
  53. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  54. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  55. package/dist/store/runtime-clients/thread-list-runtime-client.js +1 -1
  56. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  57. package/dist/store/runtime-clients/thread-runtime-client.js +1 -1
  58. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  59. package/dist/types/message.d.ts +1 -0
  60. package/dist/types/message.d.ts.map +1 -1
  61. package/dist/types/message.js.map +1 -1
  62. package/package.json +3 -3
  63. package/src/react/RuntimeAdapter.test.ts +85 -0
  64. package/src/react/RuntimeAdapter.ts +1 -1
  65. package/src/react/client/Interactables.test.ts +3 -2
  66. package/src/react/client/Interactables.ts +4 -4
  67. package/src/react/client/Tools.ts +4 -4
  68. package/src/react/index.ts +1 -0
  69. package/src/react/runtimes/cloud/auiV0.ts +8 -1
  70. package/src/react/runtimes/createRuntimeExtras.test.ts +14 -1
  71. package/src/react/runtimes/createRuntimeExtras.ts +15 -26
  72. package/src/runtime/base/default-edit-composer-runtime-core.ts +32 -50
  73. package/src/runtime/utils/runtime-extras-brand.ts +47 -0
  74. package/src/runtime/utils/thread-message-like.ts +5 -1
  75. package/src/store/clients/runtime-adapter.ts +2 -5
  76. package/src/store/clients/suggestions.ts +1 -1
  77. package/src/store/clients/thread-message-client.ts +1 -1
  78. package/src/store/index.ts +31 -0
  79. package/src/store/runtime-clients/composer-runtime-client.ts +2 -5
  80. package/src/store/runtime-clients/lookup-domains.test.ts +109 -0
  81. package/src/store/runtime-clients/message-runtime-client.ts +2 -5
  82. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +2 -1
  83. package/src/store/runtime-clients/thread-list-runtime-client.ts +2 -5
  84. package/src/store/runtime-clients/thread-runtime-client.ts +2 -2
  85. package/src/tests/auiV0Encode.test.ts +97 -0
  86. package/src/tests/default-edit-composer-runtime-core.test.ts +6 -2
  87. package/src/tests/framework-neutral-boundary.test.ts +60 -24
  88. package/src/tests/thread-message-like.test.ts +43 -0
  89. package/src/types/message.ts +1 -0
@@ -236,6 +236,33 @@ describe("auiV0Encode", () => {
236
236
  ]);
237
237
  });
238
238
 
239
+ it("preserves reasoning summaries and omits absent summaries", () => {
240
+ const encoded = auiV0Encode({
241
+ id: "m1",
242
+ createdAt: new Date("2026-03-15T00:00:00.000Z"),
243
+ role: "assistant",
244
+ status: { type: "complete", reason: "stop" },
245
+ metadata: {
246
+ unstable_state: null,
247
+ unstable_annotations: [],
248
+ unstable_data: [],
249
+ steps: [],
250
+ custom: {},
251
+ },
252
+ content: [
253
+ { type: "reasoning", text: "thinking", unstable_summary: "Planning" },
254
+ { type: "reasoning", text: "more thinking", unstable_summary: "" },
255
+ { type: "reasoning", text: "no summary" },
256
+ ],
257
+ });
258
+
259
+ expect(encoded.content).toEqual([
260
+ { type: "reasoning", text: "thinking", unstable_summary: "Planning" },
261
+ { type: "reasoning", text: "more thinking", unstable_summary: "" },
262
+ { type: "reasoning", text: "no summary" },
263
+ ]);
264
+ });
265
+
239
266
  it("drops per-part status from attachment content in the core cloud encoder", () => {
240
267
  const encoded = auiV0Encode({
241
268
  id: "m1",
@@ -306,6 +333,76 @@ describe("auiV0Encode", () => {
306
333
  });
307
334
 
308
335
  describe("auiV0Decode", () => {
336
+ it("round-trips a reasoning summary", () => {
337
+ const encoded = auiV0Encode({
338
+ id: "local",
339
+ createdAt: new Date("2026-03-15T00:00:00.000Z"),
340
+ role: "assistant",
341
+ status: { type: "complete", reason: "stop" },
342
+ metadata: {
343
+ unstable_state: null,
344
+ unstable_annotations: [],
345
+ unstable_data: [],
346
+ steps: [],
347
+ custom: {},
348
+ },
349
+ content: [
350
+ { type: "reasoning", text: "thinking", unstable_summary: "Planning" },
351
+ ],
352
+ });
353
+
354
+ const { message } = auiV0Decode({
355
+ id: "cloud",
356
+ parent_id: null,
357
+ format: "aui/v0",
358
+ content: encoded,
359
+ created_at: new Date("2026-03-15T00:00:00.000Z"),
360
+ } as unknown as Parameters<typeof auiV0Decode>[0]);
361
+
362
+ expect(message.content).toEqual([
363
+ { type: "reasoning", text: "thinking", unstable_summary: "Planning" },
364
+ ]);
365
+ });
366
+
367
+ it("round-trips a reasoning summary without text", () => {
368
+ const encoded = auiV0Encode({
369
+ id: "local",
370
+ createdAt: new Date("2026-03-15T00:00:00.000Z"),
371
+ role: "assistant",
372
+ status: { type: "complete", reason: "stop" },
373
+ metadata: {
374
+ unstable_state: null,
375
+ unstable_annotations: [],
376
+ unstable_data: [],
377
+ steps: [],
378
+ custom: {},
379
+ },
380
+ content: [
381
+ {
382
+ type: "reasoning",
383
+ text: "",
384
+ unstable_summary: "Searching the codebase",
385
+ },
386
+ ],
387
+ });
388
+
389
+ const { message } = auiV0Decode({
390
+ id: "cloud",
391
+ parent_id: null,
392
+ format: "aui/v0",
393
+ content: encoded,
394
+ created_at: new Date("2026-03-15T00:00:00.000Z"),
395
+ } as unknown as Parameters<typeof auiV0Decode>[0]);
396
+
397
+ expect(message.content).toEqual([
398
+ {
399
+ type: "reasoning",
400
+ text: "",
401
+ unstable_summary: "Searching the codebase",
402
+ },
403
+ ]);
404
+ });
405
+
309
406
  it("round-trips a pending tool-call approval so a paused run stays resumable", () => {
310
407
  const content = auiV0Encode({
311
408
  id: "local",
@@ -132,7 +132,7 @@ describe("DefaultEditComposerRuntimeCore", () => {
132
132
  });
133
133
 
134
134
  describe("send behavior", () => {
135
- it("does not call append when nothing changed", async () => {
135
+ it("calls append even when nothing changed, branching with the same message", async () => {
136
136
  const { runtime, append } = makeRuntime();
137
137
  const composer = new DefaultEditComposerRuntimeCore(runtime, () => {}, {
138
138
  parentId: "p1",
@@ -141,7 +141,11 @@ describe("DefaultEditComposerRuntimeCore", () => {
141
141
  }),
142
142
  });
143
143
  await composer.send();
144
- expect(append).not.toHaveBeenCalled();
144
+ expect(append).toHaveBeenCalledTimes(1);
145
+ const appended = append.mock.calls[0]![0] as AppendMessage;
146
+ expect(appended.content).toEqual([{ type: "text", text: "same" }]);
147
+ expect(appended.parentId).toBe("p1");
148
+ expect(appended.sourceId).toBe("msg-1");
145
149
  });
146
150
 
147
151
  it("calls append when text changes", async () => {
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import { describe, expect, it } from "vitest";
3
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
+ import { existsSync, readFileSync, statSync } from "node:fs";
4
4
  import { dirname, join, relative, resolve } from "node:path";
5
5
 
6
6
  const SRC_DIR = resolve(__dirname, "..");
@@ -108,19 +108,6 @@ function walkFrom(entry: string): { forbidden: string[]; jsxFiles: string[] } {
108
108
  return { forbidden, jsxFiles };
109
109
  }
110
110
 
111
- function findFiles(dir: string, ext: string[]): string[] {
112
- const results: string[] = [];
113
- for (const entry of readdirSync(dir)) {
114
- const full = join(dir, entry);
115
- if (statSync(full).isDirectory()) {
116
- results.push(...findFiles(full, ext));
117
- } else if (ext.some((e) => full.endsWith(e))) {
118
- results.push(full);
119
- }
120
- }
121
- return results;
122
- }
123
-
124
111
  function nonTypeImportNames(clause: string): string[] {
125
112
  const names: string[] = [];
126
113
  const trimmed = clause.trim();
@@ -183,15 +170,60 @@ describe("framework neutral boundary", () => {
183
170
  ).toEqual([]);
184
171
  });
185
172
 
186
- it("src/store may only pull react names the tap shim dispatches", () => {
187
- const storeDir = resolve(SRC_DIR, "store");
188
- const files = findFiles(storeDir, [".ts"]).filter(
189
- (f) => !f.includes(".test."),
190
- );
173
+ it("the store entry graphs stay hostable inside a tap root", () => {
191
174
  const violations: string[] = [];
175
+ const jsxFiles: string[] = [];
176
+ const visited = new Set<string>();
177
+ const pending = [
178
+ resolve(SRC_DIR, "store/index.ts"),
179
+ resolve(SRC_DIR, "store/internal.ts"),
180
+ ];
181
+
182
+ // Walks the runtime graph only: type-only imports are erased at build, so
183
+ // they neither carry runtime dependencies nor get traversed. This is also
184
+ // why type imports may keep using the @assistant-ui/store barrel: the
185
+ // augmentations target that specifier, and splitting types across entries
186
+ // would fork the ScopeRegistry seen by src-mapped consumers.
187
+ const RUNTIME_IMPORT_RE =
188
+ /(?:^|\n)\s*(?:import|export)\s+(?!type\b)(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\*|\w+(?:\s*,\s*(?:\{[^}]*\}|\*\s+as\s+\w+))?)\s+from\s+)?["']([^"']+)["']/g;
189
+
190
+ const collectRuntimeImports = (content: string): string[] => {
191
+ const specs: string[] = [];
192
+ for (const re of [RUNTIME_IMPORT_RE, DYNAMIC_IMPORT_RE]) {
193
+ re.lastIndex = 0;
194
+ let match: RegExpExecArray | null;
195
+ while ((match = re.exec(content)) !== null) {
196
+ specs.push(match[1]!);
197
+ }
198
+ }
199
+ return specs;
200
+ };
201
+
202
+ while (pending.length > 0) {
203
+ const file = pending.pop()!;
204
+ if (visited.has(file)) continue;
205
+ visited.add(file);
206
+ if (file.endsWith(".tsx")) jsxFiles.push(relative(SRC_DIR, file));
192
207
 
193
- for (const file of files) {
194
208
  const content = readFileSync(file, "utf-8");
209
+
210
+ for (const spec of collectRuntimeImports(content)) {
211
+ if (spec === "react/jsx-runtime") {
212
+ violations.push(
213
+ `${relative(SRC_DIR, file)} imports "react/jsx-runtime"`,
214
+ );
215
+ }
216
+ if (spec === "@assistant-ui/store") {
217
+ violations.push(
218
+ `${relative(SRC_DIR, file)} value-imports the "@assistant-ui/store" barrel; use "@assistant-ui/store/client"`,
219
+ );
220
+ }
221
+ const resolved = resolveRelative(file, spec);
222
+ if (resolved && !visited.has(resolved)) {
223
+ pending.push(resolved);
224
+ }
225
+ }
226
+
195
227
  FROM_REACT_RE.lastIndex = 0;
196
228
  let match: RegExpExecArray | null;
197
229
  while ((match = FROM_REACT_RE.exec(content)) !== null) {
@@ -210,12 +242,16 @@ describe("framework neutral boundary", () => {
210
242
 
211
243
  expect(
212
244
  violations,
213
- `src/store may only use react names the tap shim dispatches ` +
214
- `(useState, useReducer, useRef, useMemo, useCallback, useEffect, useLayoutEffect, ` +
215
- `useInsertionEffect, useEffectEvent, useSyncExternalStore, useDebugValue, use, ` +
216
- `useContext, createContext).\n` +
245
+ `The store entry graphs run inside tap roots on every framework, so they may ` +
246
+ `only use react names the tap shim dispatches, never react/jsx-runtime, and ` +
247
+ `never the @assistant-ui/store barrel (whose module scope requires React).\n` +
217
248
  `Violations:\n` +
218
249
  violations.map((f) => ` - ${f}`).join("\n"),
219
250
  ).toEqual([]);
251
+ expect(
252
+ jsxFiles,
253
+ `The JSX transform injects react/jsx-runtime at build time, so the store entry graphs must not contain .tsx files:\n` +
254
+ jsxFiles.map((f) => ` - ${f}`).join("\n"),
255
+ ).toEqual([]);
220
256
  });
221
257
  });
@@ -147,6 +147,49 @@ describe("fromThreadMessageLike", () => {
147
147
  expect(result.content).toEqual([{ type: "reasoning", text: "hi" }]);
148
148
  });
149
149
 
150
+ it("keeps a reasoning part with a summary and no text", () => {
151
+ const result = fromThreadMessageLike(
152
+ {
153
+ role: "assistant",
154
+ content: [
155
+ {
156
+ type: "reasoning",
157
+ text: "",
158
+ unstable_summary: "Searching the codebase",
159
+ },
160
+ ],
161
+ },
162
+ fallbackId,
163
+ fallbackStatus,
164
+ );
165
+
166
+ expect(result.content).toEqual([
167
+ {
168
+ type: "reasoning",
169
+ text: "",
170
+ unstable_summary: "Searching the codebase",
171
+ },
172
+ ]);
173
+ });
174
+
175
+ it("drops a reasoning part with neither text nor a summary to show", () => {
176
+ // the transport layers round-trip an empty summary faithfully; this is
177
+ // the display normalizer, so it drops a part with nothing to render for
178
+ // the same reason it drops whitespace-only text.
179
+ const message = fromThreadMessageLike(
180
+ {
181
+ role: "assistant",
182
+ content: [
183
+ { type: "reasoning", text: "", unstable_summary: "" },
184
+ { type: "reasoning", text: " ", unstable_summary: " " },
185
+ ],
186
+ },
187
+ "m1",
188
+ { type: "complete", reason: "unknown" },
189
+ );
190
+
191
+ expect(message.content).toEqual([]);
192
+ });
150
193
  it("drops an assistant image part whose image is undefined", () => {
151
194
  const result = fromThreadMessageLike(
152
195
  {
@@ -23,6 +23,7 @@ export type ReasoningMessagePart = {
23
23
  readonly type: "reasoning";
24
24
  readonly text: string;
25
25
  readonly status?: MessagePartStreamStatus;
26
+ readonly unstable_summary?: string;
26
27
  readonly providerMetadata?: PartProviderMetadata;
27
28
  readonly parentId?: string;
28
29
  };