@assistant-ui/mcp-docs-server 0.2.0 → 0.2.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.
Files changed (155) hide show
  1. package/.docs/organized/code-examples/waterfall.md +12 -13
  2. package/.docs/organized/code-examples/with-a2a.md +16 -11
  3. package/.docs/organized/code-examples/with-ag-ui.md +15 -13
  4. package/.docs/organized/code-examples/with-ai-sdk-v7.md +14 -13
  5. package/.docs/organized/code-examples/with-artifacts.md +14 -13
  6. package/.docs/organized/code-examples/with-assistant-transport.md +20 -28
  7. package/.docs/organized/code-examples/with-browser-extension.md +18 -11
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +17 -15
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +10 -11
  10. package/.docs/organized/code-examples/with-cloud.md +19 -14
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +15 -14
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +14 -14
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +19 -17
  14. package/.docs/organized/code-examples/with-eve.md +66 -12
  15. package/.docs/organized/code-examples/with-expo.md +26 -31
  16. package/.docs/organized/code-examples/with-external-store.md +17 -12
  17. package/.docs/organized/code-examples/with-ffmpeg.md +21 -15
  18. package/.docs/organized/code-examples/with-generative-ui.md +271 -36
  19. package/.docs/organized/code-examples/with-google-adk.md +17 -12
  20. package/.docs/organized/code-examples/with-heat-graph.md +6 -7
  21. package/.docs/organized/code-examples/with-image-generation.md +9 -10
  22. package/.docs/organized/code-examples/with-interactables.md +14 -13
  23. package/.docs/organized/code-examples/with-langchain.md +12 -13
  24. package/.docs/organized/code-examples/with-langgraph.md +19 -13
  25. package/.docs/organized/code-examples/with-livekit.md +13 -13
  26. package/.docs/organized/code-examples/with-mcp.md +14 -15
  27. package/.docs/organized/code-examples/with-nuxt.md +2428 -0
  28. package/.docs/organized/code-examples/with-opencode.md +23 -14
  29. package/.docs/organized/code-examples/with-openui.md +449 -0
  30. package/.docs/organized/code-examples/with-pi.md +59 -57
  31. package/.docs/organized/code-examples/with-react-hook-form.md +16 -15
  32. package/.docs/organized/code-examples/with-react-ink-web.md +7 -8
  33. package/.docs/organized/code-examples/with-react-ink.md +6 -6
  34. package/.docs/organized/code-examples/with-react-router.md +17 -11
  35. package/.docs/organized/code-examples/with-resumable-stream.md +12 -13
  36. package/.docs/organized/code-examples/with-store.md +27 -16
  37. package/.docs/organized/code-examples/with-svelte.md +415 -0
  38. package/.docs/organized/code-examples/with-sveltekit.md +1061 -0
  39. package/.docs/organized/code-examples/with-tanstack.md +19 -13
  40. package/.docs/organized/code-examples/with-tap-runtime.md +15 -15
  41. package/.docs/organized/code-examples/with-virtualized-thread.md +8 -9
  42. package/.docs/organized/code-examples/with-vue.md +408 -0
  43. package/.docs/raw/docs/(docs)/cli.mdx +7 -2
  44. package/.docs/raw/docs/(docs)/index.mdx +9 -76
  45. package/.docs/raw/docs/(docs)/installation.mdx +6 -20
  46. package/.docs/raw/docs/(reference)/api-reference/context-providers/assistant-runtime-provider.mdx +24 -4
  47. package/.docs/raw/docs/(reference)/api-reference/generative-ui/slack.mdx +5 -1
  48. package/.docs/raw/docs/(reference)/api-reference/generative-ui/teams.mdx +9 -2
  49. package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +2 -2
  50. package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +47 -1
  51. package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +29 -4
  52. package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +1 -1
  53. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +52 -1
  54. package/.docs/raw/docs/(reference)/api-reference/voice/session.mdx +1 -1
  55. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +2 -2
  56. package/.docs/raw/docs/cloud/ai-sdk.mdx +4 -4
  57. package/.docs/raw/docs/cloud/index.mdx +1 -1
  58. package/.docs/raw/docs/copilots/model-context.mdx +4 -3
  59. package/.docs/raw/docs/copilots/motivation.mdx +4 -4
  60. package/.docs/raw/docs/guides/attachments.mdx +2 -2
  61. package/.docs/raw/docs/guides/chain-of-thought.mdx +1 -1
  62. package/.docs/raw/docs/guides/context-api.mdx +15 -17
  63. package/.docs/raw/docs/guides/dictation.mdx +1 -1
  64. package/.docs/raw/docs/guides/electron.mdx +1 -1
  65. package/.docs/raw/docs/guides/mentions.mdx +2 -0
  66. package/.docs/raw/docs/guides/resumable-streams.mdx +74 -3
  67. package/.docs/raw/docs/guides/suggestions.mdx +15 -12
  68. package/.docs/raw/docs/ink/hooks.mdx +9 -4
  69. package/.docs/raw/docs/ink/primitives.mdx +5 -4
  70. package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +16 -0
  71. package/.docs/raw/docs/integrations/auth/better-auth.mdx +1 -1
  72. package/.docs/raw/docs/integrations/auth/clerk.mdx +1 -1
  73. package/.docs/raw/docs/integrations/auth/next-auth.mdx +1 -1
  74. package/.docs/raw/docs/integrations/frameworks/cloudflare-agents/overview.mdx +1 -1
  75. package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +2 -2
  76. package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +1 -1
  77. package/.docs/raw/docs/integrations/gateways/index.mdx +2 -2
  78. package/.docs/raw/docs/integrations/observability/helicone.mdx +2 -2
  79. package/.docs/raw/docs/integrations/observability/langfuse.mdx +1 -1
  80. package/.docs/raw/docs/integrations/observability/langsmith.mdx +2 -2
  81. package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +146 -128
  82. package/.docs/raw/docs/migrations/toolkit-tools.mdx +15 -13
  83. package/.docs/raw/docs/migrations/v0-15.mdx +118 -4
  84. package/.docs/raw/docs/primitives/attachment.mdx +2 -2
  85. package/.docs/raw/docs/primitives/composer.mdx +2 -2
  86. package/.docs/raw/docs/primitives/message.mdx +33 -1
  87. package/.docs/raw/docs/primitives/suggestion.mdx +4 -2
  88. package/.docs/raw/docs/primitives/thread.mdx +1 -1
  89. package/.docs/raw/docs/react-native/hooks.mdx +14 -4
  90. package/.docs/raw/docs/react-native/index.mdx +1 -1
  91. package/.docs/raw/docs/react-native/primitives.mdx +2 -2
  92. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +35 -5
  93. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +1 -1
  94. package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +1 -1
  95. package/.docs/raw/docs/runtimes/ai-sdk/v6-legacy.mdx +9 -10
  96. package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +9 -10
  97. package/.docs/raw/docs/runtimes/claude-managed-agents.mdx +118 -0
  98. package/.docs/raw/docs/runtimes/concepts/stability.mdx +2 -1
  99. package/.docs/raw/docs/runtimes/concepts/threads.mdx +106 -27
  100. package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +19 -1
  101. package/.docs/raw/docs/runtimes/custom/external-store.mdx +34 -1
  102. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +36 -9
  103. package/.docs/raw/docs/runtimes/eve/overview.mdx +51 -0
  104. package/.docs/raw/docs/runtimes/eve/quickstart.mdx +51 -2
  105. package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +9 -12
  106. package/.docs/raw/docs/runtimes/langchain.mdx +1 -1
  107. package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +5 -1
  108. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +7 -7
  109. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +4 -4
  110. package/.docs/raw/docs/runtimes/opencode/hooks.mdx +1 -0
  111. package/.docs/raw/docs/runtimes/opencode/overview.mdx +10 -0
  112. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +8 -1
  113. package/.docs/raw/docs/tools/backend.mdx +2 -2
  114. package/.docs/raw/docs/tools/defining-tools.mdx +28 -7
  115. package/.docs/raw/docs/tools/dynamic-tools.mdx +6 -4
  116. package/.docs/raw/docs/tools/generative-ui-primitive.mdx +180 -0
  117. package/.docs/raw/docs/tools/generative-ui-slack.mdx +167 -0
  118. package/.docs/raw/docs/tools/generative-ui-teams.mdx +160 -0
  119. package/.docs/raw/docs/tools/generative-ui.mdx +224 -211
  120. package/.docs/raw/docs/tools/index.mdx +2 -1
  121. package/.docs/raw/docs/tools/interactables.mdx +29 -17
  122. package/.docs/raw/docs/tools/mcp-apps.mdx +35 -6
  123. package/.docs/raw/docs/tools/mcp.mdx +9 -7
  124. package/.docs/raw/docs/tools/openui.mdx +175 -0
  125. package/.docs/raw/docs/tools/tool-ui.mdx +27 -24
  126. package/.docs/raw/docs/tools/user-managed-mcp.mdx +17 -8
  127. package/.docs/raw/docs/ui/attachment.mdx +27 -0
  128. package/.docs/raw/docs/ui/file.mdx +7 -2
  129. package/.docs/raw/docs/ui/image.mdx +1 -1
  130. package/.docs/raw/docs/ui/mcp-config.mdx +8 -3
  131. package/.docs/raw/docs/ui/model-selector.mdx +8 -8
  132. package/.docs/raw/docs/ui/part-grouping.mdx +1 -1
  133. package/.docs/raw/docs/ui/thread.mdx +24 -5
  134. package/.docs/raw/docs/utilities/react-o11y.mdx +7 -9
  135. package/dist/constants.js +2 -2
  136. package/dist/constants.js.map +1 -1
  137. package/dist/index.js.map +1 -1
  138. package/dist/prepare-docs/prepare.js.map +1 -1
  139. package/dist/tools/docs.js +4 -2
  140. package/dist/tools/docs.js.map +1 -1
  141. package/dist/tools/examples.js +2 -1
  142. package/dist/tools/examples.js.map +1 -1
  143. package/dist/tools/resources.js +2 -1
  144. package/dist/tools/resources.js.map +1 -1
  145. package/dist/tools/tests/test-setup.js +2 -1
  146. package/dist/tools/tests/test-setup.js.map +1 -1
  147. package/dist/tools/xulux-templates.js +4 -2
  148. package/dist/tools/xulux-templates.js.map +1 -1
  149. package/dist/utils/mdx.js +2 -1
  150. package/dist/utils/mdx.js.map +1 -1
  151. package/dist/xulux/catalog-client.js +1 -1
  152. package/dist/xulux/catalog-client.js.map +1 -1
  153. package/package.json +4 -4
  154. package/src/tools/tests/docs.test.ts +2 -2
  155. package/.docs/raw/docs/tools/interactables-legacy.mdx +0 -410
@@ -227,7 +227,7 @@ export async function POST(
227
227
 
228
228
  ### Implement `RemoteThreadListAdapter`
229
229
 
230
- The adapter calls your endpoints. `unstable_Provider` injects the per-thread history adapter so messages persist alongside metadata.
230
+ The adapter calls your endpoints. `unstable_useAdapters` injects the per-thread history adapter on the `RemoteThreadList` store entry and on `useRemoteThreadListRuntime` when `unstable_Provider` is omitted. `unstable_Provider` is an optional React face for that runtime. On the `RemoteThreadList` store entry, key the thread factory with `withKey(id, …)` so history reloads when the visible thread changes.
231
231
 
232
232
  <PlatformTabs>
233
233
  <Tab value="React">
@@ -244,6 +244,51 @@ import {
244
244
  import { createAssistantStream } from "assistant-stream";
245
245
  import { useMemo } from "react";
246
246
 
247
+ function useThreadListAdapters() {
248
+ const aui = useAui();
249
+ const history = useMemo<ThreadHistoryAdapter>(
250
+ () => ({
251
+ async load() {
252
+ return { messages: [] };
253
+ },
254
+ async append() {},
255
+ withFormat: (fmt) => ({
256
+ async load() {
257
+ const { remoteId } = aui.threadListItem.getState();
258
+ if (!remoteId) return { messages: [] };
259
+ const rows = await fetch(
260
+ `/api/threads/${remoteId}/messages`,
261
+ ).then((r) => r.json());
262
+ return {
263
+ messages: rows.map((row: any) =>
264
+ fmt.decode({
265
+ id: row.id,
266
+ parent_id: row.parent_id,
267
+ format: row.format,
268
+ content: row.content,
269
+ }),
270
+ ),
271
+ };
272
+ },
273
+ async append(item) {
274
+ const { remoteId } = await aui.threadListItem.initialize();
275
+ await fetch(`/api/threads/${remoteId}/messages`, {
276
+ method: "POST",
277
+ body: JSON.stringify({
278
+ id: fmt.getId(item.message),
279
+ parent_id: item.parentId,
280
+ format: fmt.format,
281
+ content: fmt.encode(item),
282
+ }),
283
+ });
284
+ },
285
+ }),
286
+ }),
287
+ [aui],
288
+ );
289
+ return useMemo(() => ({ history }), [history]);
290
+ }
291
+
247
292
  export const threadListAdapter: RemoteThreadListAdapter = {
248
293
  async list() {
249
294
  const rows = await fetch("/api/threads").then((r) => r.json());
@@ -295,50 +340,11 @@ export const threadListAdapter: RemoteThreadListAdapter = {
295
340
  controller.appendText(title);
296
341
  });
297
342
  },
343
+ unstable_useAdapters: useThreadListAdapters,
298
344
  unstable_Provider({ children }) {
299
- const aui = useAui();
300
- const history = useMemo<ThreadHistoryAdapter>(
301
- () => ({
302
- async load() {
303
- return { messages: [] };
304
- },
305
- async append() {},
306
- withFormat: (fmt) => ({
307
- async load() {
308
- const { remoteId } = aui.threadListItem.getState();
309
- if (!remoteId) return { messages: [] };
310
- const rows = await fetch(
311
- `/api/threads/${remoteId}/messages`,
312
- ).then((r) => r.json());
313
- return {
314
- messages: rows.map((row: any) =>
315
- fmt.decode({
316
- id: row.id,
317
- parent_id: row.parent_id,
318
- format: row.format,
319
- content: row.content,
320
- }),
321
- ),
322
- };
323
- },
324
- async append(item) {
325
- const { remoteId } = await aui.threadListItem.initialize();
326
- await fetch(`/api/threads/${remoteId}/messages`, {
327
- method: "POST",
328
- body: JSON.stringify({
329
- id: fmt.getId(item.message),
330
- parent_id: item.parentId,
331
- format: fmt.format,
332
- content: fmt.encode(item),
333
- }),
334
- });
335
- },
336
- }),
337
- }),
338
- [aui],
339
- );
345
+ const adapters = useThreadListAdapters();
340
346
  return (
341
- <RuntimeAdapterProvider adapters={{ history }}>
347
+ <RuntimeAdapterProvider adapters={adapters}>
342
348
  {children}
343
349
  </RuntimeAdapterProvider>
344
350
  );
@@ -359,6 +365,51 @@ import {
359
365
  import { createAssistantStream } from "assistant-stream";
360
366
  import { useMemo } from "react";
361
367
 
368
+ function useThreadListAdapters() {
369
+ const aui = useAui();
370
+ const history = useMemo<ThreadHistoryAdapter>(
371
+ () => ({
372
+ async load() {
373
+ return { messages: [] };
374
+ },
375
+ async append() {},
376
+ withFormat: (fmt) => ({
377
+ async load() {
378
+ const { remoteId } = aui.threadListItem.getState();
379
+ if (!remoteId) return { messages: [] };
380
+ const rows = await fetch(
381
+ `${API_URL}/threads/${remoteId}/messages`,
382
+ ).then((r) => r.json());
383
+ return {
384
+ messages: rows.map((row: any) =>
385
+ fmt.decode({
386
+ id: row.id,
387
+ parent_id: row.parent_id,
388
+ format: row.format,
389
+ content: row.content,
390
+ }),
391
+ ),
392
+ };
393
+ },
394
+ async append(item) {
395
+ const { remoteId } = await aui.threadListItem.initialize();
396
+ await fetch(`${API_URL}/threads/${remoteId}/messages`, {
397
+ method: "POST",
398
+ body: JSON.stringify({
399
+ id: fmt.getId(item.message),
400
+ parent_id: item.parentId,
401
+ format: fmt.format,
402
+ content: fmt.encode(item),
403
+ }),
404
+ });
405
+ },
406
+ }),
407
+ }),
408
+ [aui],
409
+ );
410
+ return useMemo(() => ({ history }), [history]);
411
+ }
412
+
362
413
  export const threadListAdapter: RemoteThreadListAdapter = {
363
414
  async list() {
364
415
  const rows = await fetch(`${API_URL}/threads`).then((r) => r.json());
@@ -412,50 +463,11 @@ export const threadListAdapter: RemoteThreadListAdapter = {
412
463
  controller.appendText(title);
413
464
  });
414
465
  },
466
+ unstable_useAdapters: useThreadListAdapters,
415
467
  unstable_Provider({ children }) {
416
- const aui = useAui();
417
- const history = useMemo<ThreadHistoryAdapter>(
418
- () => ({
419
- async load() {
420
- return { messages: [] };
421
- },
422
- async append() {},
423
- withFormat: (fmt) => ({
424
- async load() {
425
- const { remoteId } = aui.threadListItem.getState();
426
- if (!remoteId) return { messages: [] };
427
- const rows = await fetch(
428
- `${API_URL}/threads/${remoteId}/messages`,
429
- ).then((r) => r.json());
430
- return {
431
- messages: rows.map((row: any) =>
432
- fmt.decode({
433
- id: row.id,
434
- parent_id: row.parent_id,
435
- format: row.format,
436
- content: row.content,
437
- }),
438
- ),
439
- };
440
- },
441
- async append(item) {
442
- const { remoteId } = await aui.threadListItem.initialize();
443
- await fetch(`${API_URL}/threads/${remoteId}/messages`, {
444
- method: "POST",
445
- body: JSON.stringify({
446
- id: fmt.getId(item.message),
447
- parent_id: item.parentId,
448
- format: fmt.format,
449
- content: fmt.encode(item),
450
- }),
451
- });
452
- },
453
- }),
454
- }),
455
- [aui],
456
- );
468
+ const adapters = useThreadListAdapters();
457
469
  return (
458
- <RuntimeAdapterProvider adapters={{ history }}>
470
+ <RuntimeAdapterProvider adapters={adapters}>
459
471
  {children}
460
472
  </RuntimeAdapterProvider>
461
473
  );
@@ -478,6 +490,51 @@ import {
478
490
  import { createAssistantStream } from "assistant-stream";
479
491
  import { useMemo } from "react";
480
492
 
493
+ function useThreadListAdapters() {
494
+ const aui = useAui();
495
+ const history = useMemo<ThreadHistoryAdapter>(
496
+ () => ({
497
+ async load() {
498
+ return { messages: [] };
499
+ },
500
+ async append() {},
501
+ withFormat: (fmt) => ({
502
+ async load() {
503
+ const { remoteId } = aui.threadListItem.getState();
504
+ if (!remoteId) return { messages: [] };
505
+ const rows = await fetch(
506
+ `${API_URL}/threads/${remoteId}/messages`,
507
+ ).then((r) => r.json());
508
+ return {
509
+ messages: rows.map((row: any) =>
510
+ fmt.decode({
511
+ id: row.id,
512
+ parent_id: row.parent_id,
513
+ format: row.format,
514
+ content: row.content,
515
+ }),
516
+ ),
517
+ };
518
+ },
519
+ async append(item) {
520
+ const { remoteId } = await aui.threadListItem.initialize();
521
+ await fetch(`${API_URL}/threads/${remoteId}/messages`, {
522
+ method: "POST",
523
+ body: JSON.stringify({
524
+ id: fmt.getId(item.message),
525
+ parent_id: item.parentId,
526
+ format: fmt.format,
527
+ content: fmt.encode(item),
528
+ }),
529
+ });
530
+ },
531
+ }),
532
+ }),
533
+ [aui],
534
+ );
535
+ return useMemo(() => ({ history }), [history]);
536
+ }
537
+
481
538
  export const threadListAdapter: RemoteThreadListAdapter = {
482
539
  async list() {
483
540
  const rows = await fetch(`${API_URL}/threads`).then((r) => r.json());
@@ -531,50 +588,11 @@ export const threadListAdapter: RemoteThreadListAdapter = {
531
588
  controller.appendText(title);
532
589
  });
533
590
  },
591
+ unstable_useAdapters: useThreadListAdapters,
534
592
  unstable_Provider({ children }) {
535
- const aui = useAui();
536
- const history = useMemo<ThreadHistoryAdapter>(
537
- () => ({
538
- async load() {
539
- return { messages: [] };
540
- },
541
- async append() {},
542
- withFormat: (fmt) => ({
543
- async load() {
544
- const { remoteId } = aui.threadListItem.getState();
545
- if (!remoteId) return { messages: [] };
546
- const rows = await fetch(
547
- `${API_URL}/threads/${remoteId}/messages`,
548
- ).then((r) => r.json());
549
- return {
550
- messages: rows.map((row: any) =>
551
- fmt.decode({
552
- id: row.id,
553
- parent_id: row.parent_id,
554
- format: row.format,
555
- content: row.content,
556
- }),
557
- ),
558
- };
559
- },
560
- async append(item) {
561
- const { remoteId } = await aui.threadListItem.initialize();
562
- await fetch(`${API_URL}/threads/${remoteId}/messages`, {
563
- method: "POST",
564
- body: JSON.stringify({
565
- id: fmt.getId(item.message),
566
- parent_id: item.parentId,
567
- format: fmt.format,
568
- content: fmt.encode(item),
569
- }),
570
- });
571
- },
572
- }),
573
- }),
574
- [aui],
575
- );
593
+ const adapters = useThreadListAdapters();
576
594
  return (
577
- <RuntimeAdapterProvider adapters={{ history }}>
595
+ <RuntimeAdapterProvider adapters={adapters}>
578
596
  {children}
579
597
  </RuntimeAdapterProvider>
580
598
  );
@@ -728,7 +746,7 @@ Send a message in a fresh thread. Check the database:
728
746
 
729
747
  ## Notes
730
748
 
731
- - **First-message race.** `append` may fire before the thread row exists. The `unstable_Provider` example above always awaits `aui.threadListItem.initialize()` before writing; do the same in any custom implementation.
749
+ - **First-message race.** `append` may fire before the thread row exists. The example above always awaits `aui.threadListItem.initialize()` before writing; do the same in any custom implementation.
732
750
  - **Reload after async auth.** If `auth()` resolves after the initial `list()` call, threads won't appear until the user refreshes. Call `aui.threads.reload()` from a `useEffect` watching the session. Pattern is documented in [threads](/docs/runtimes/concepts/threads#reloading-after-async-authentication).
733
751
  - **Format string.** The `format` column is *not* a free-text label; it identifies the on-disk shape so multiple runtimes can coexist. Don't strip it. Don't make assumptions about its value (`useChatRuntime` is responsible for setting and decoding it).
734
752
  - **Pending approvals need `update`.** Tool call approvals persist across reloads only when the formatted adapter implements `update`; omitting it keeps the pre-approval snapshot out of storage by design.
@@ -73,20 +73,18 @@ export default defineToolkit({
73
73
 
74
74
  import {
75
75
  AssistantRuntimeProvider,
76
+ AuiConfig,
76
77
  Tools,
77
- useAui,
78
78
  } from "@assistant-ui/react";
79
79
  import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
80
80
  import toolkit from "./toolkit";
81
81
 
82
82
  export function App() {
83
83
  const runtime = useChatRuntime();
84
- const aui = useAui({
85
- tools: Tools({ toolkit }),
86
- });
84
+ const config = AuiConfig({ tools: Tools({ toolkit }) });
87
85
 
88
86
  return (
89
- <AssistantRuntimeProvider aui={aui} runtime={runtime}>
87
+ <AssistantRuntimeProvider runtime={runtime} config={config}>
90
88
  <Thread />
91
89
  </AssistantRuntimeProvider>
92
90
  );
@@ -100,7 +98,7 @@ export function App() {
100
98
  1. Create a `Toolkit` object.
101
99
  2. Move each `toolName` into the toolkit key.
102
100
  3. Move `description`, `parameters`, `execute`, `providerOptions`, `render`, `renderText`, and `display` onto the toolkit entry.
103
- 4. Register the toolkit once with `useAui({ tools: Tools({ toolkit }) })`.
101
+ 4. Register the toolkit once with `config={config}` on your runtime provider, where `const config = AuiConfig({ tools: Tools({ toolkit }) })`.
104
102
  5. Remove `<Tool />`, `<ToolUI />`, `useAssistantTool(...)`, and `useAssistantToolUI(...)` registrations.
105
103
 
106
104
  ## UI-Only Tool Renderers
@@ -126,7 +124,7 @@ export default defineToolkit({
126
124
  });
127
125
  ```
128
126
 
129
- Register it like any toolkit: `useAui({ tools: Tools({ toolkit }) })`.
127
+ Register it like any toolkit: `config={config}` where `const config = AuiConfig({ tools: Tools({ toolkit }) })`.
130
128
  Render-only entries upload no schema and run no browser code — they only attach
131
129
  UI for matching tool-call message parts. For MCP server catalogs, spread
132
130
  `defineMcpToolkit({ ... })` in the same generative toolkit.
@@ -163,20 +161,24 @@ export default defineToolkit({
163
161
  ```
164
162
 
165
163
  ```tsx title="TaskBoard.tsx"
166
- import { AuiProvider, Tools, useAui, useAuiToolOverrides } from "@assistant-ui/react";
164
+ import {
165
+ AuiConfig,
166
+ AuiProvider,
167
+ Tools,
168
+ useAui,
169
+ useAuiToolOverrides,
170
+ } from "@assistant-ui/react";
167
171
  import { useState, type Dispatch, type SetStateAction } from "react";
168
172
  import type { Task } from "./task-board-toolkit";
169
173
  import toolkit from "./task-board-toolkit";
170
174
 
171
175
  function TaskBoard() {
172
176
  const [tasks, setTasks] = useState<Task[]>([]);
173
-
174
- const aui = useAui({
175
- tools: Tools({ toolkit }),
176
- });
177
+ const aui = useAui();
178
+ const config = AuiConfig({ tools: Tools({ toolkit }) });
177
179
 
178
180
  return (
179
- <AuiProvider value={aui}>
181
+ <AuiProvider extends={aui} config={config}>
180
182
  <TaskBoardToolOverrides setTasks={setTasks} />
181
183
  <TaskList tasks={tasks} />
182
184
  </AuiProvider>
@@ -133,16 +133,130 @@ const aui = useAui(scopes, { parent });
133
133
 
134
134
  // After
135
135
  const Scoped = ({ children }) => {
136
- const aui = useAui(scopes); // extends the AuiProvider parent
137
- return <AuiProvider value={aui}>{children}</AuiProvider>;
136
+ const aui = useAui();
137
+ const config = AuiConfig(scopes);
138
+ return (
139
+ <AuiProvider extends={aui} config={config}>
140
+ {children}
141
+ </AuiProvider>
142
+ );
138
143
  };
139
144
 
140
- <AuiProvider value={parent}>
145
+ const rootConfig = AuiConfig({});
146
+
147
+ <AuiProvider extends={parent} config={rootConfig}>
141
148
  <Scoped />
142
149
  </AuiProvider>;
143
150
  ```
144
151
 
145
- Where `{ parent: null }` was used to detach from context, `<AuiProvider value={null}>` now provides an isolated empty root (experimental).
152
+ Where `{ parent: null }` was used to detach from context, `<AuiProvider extends={null} config={config}>` where `const config = AuiConfig({})` now provides an isolated empty root.
153
+
154
+ ## `AuiProvider` Grammar
155
+
156
+ `AuiProvider` takes a `config` built with `AuiConfig(...)` — raw object literals are a type error. At the top level, `config` alone creates the subtree's client. Nested under a parent provider, `extends` is mandatory: `extends={aui}` extends the parent, `extends={null}` isolates (dev-enforced). `ref` receives the resulting client after mount.
157
+
158
+ ```tsx
159
+ const aui = useAui();
160
+ const config = AuiConfig({ tools: Tools({ toolkit }) });
161
+
162
+ // Top-level root
163
+ <AuiProvider config={config}>
164
+
165
+ // Nested: extend the parent
166
+ <AuiProvider extends={aui} config={config}>
167
+
168
+ // Nested: isolate from the parent
169
+ <AuiProvider extends={null} config={config}>
170
+ ```
171
+
172
+ `AuiConfig` is exported from `@assistant-ui/store` and re-exported from `@assistant-ui/react`, `@assistant-ui/react-native`, and `@assistant-ui/react-ink`.
173
+
174
+ ### `value` Prop Deprecated
175
+
176
+ ```tsx
177
+ // Before
178
+ <AuiProvider value={client}>
179
+ <AuiProvider value={null}>
180
+
181
+ // After
182
+ const config = AuiConfig({});
183
+
184
+ <AuiProvider extends={client} config={config}>
185
+ <AuiProvider extends={null} config={config}>
186
+ ```
187
+
188
+ The replacement exposes a client extending the given one, not the same instance — `useAui()` beneath it returns the new client, with scope access delegating to `client`. The deprecated `value={client}` form behaves the same way: it also exposes a derived client rather than the exact instance, and the given client must implement `subscribe`.
189
+
190
+ ### `useAui({ ... })` Extension Overload Deprecated
191
+
192
+ ```tsx
193
+ // Before
194
+ const aui = useAui({ tools: Tools({ toolkit }) });
195
+ return <AuiProvider value={aui}>{children}</AuiProvider>;
196
+
197
+ // After
198
+ const aui = useAui();
199
+ const config = AuiConfig({ tools: Tools({ toolkit }) });
200
+ return (
201
+ <AuiProvider extends={aui} config={config}>
202
+ {children}
203
+ </AuiProvider>
204
+ );
205
+ ```
206
+
207
+ Where the extended client was passed to `<AssistantRuntimeProvider aui={aui}>`, use the new `config` prop instead — the scopes are provided alongside the runtime's threads scope:
208
+
209
+ ```tsx
210
+ // Before
211
+ const aui = useAui({ tools: Tools({ toolkit }) });
212
+ return (
213
+ <AssistantRuntimeProvider aui={aui} runtime={runtime}>
214
+ {children}
215
+ </AssistantRuntimeProvider>
216
+ );
217
+
218
+ // After
219
+ const config = AuiConfig({ tools: Tools({ toolkit }) });
220
+ return (
221
+ <AssistantRuntimeProvider runtime={runtime} config={config}>
222
+ {children}
223
+ </AssistantRuntimeProvider>
224
+ );
225
+ ```
226
+
227
+ ## Thread Switch Events: `threads.selectionChanged`
228
+
229
+ The per-item thread switch events are deprecated in favor of a single event on the thread list. `threads.selectionChanged` fires once per switch and carries both sides of the transition:
230
+
231
+ ```ts
232
+ // Before
233
+ useAuiEvent("threadListItem.switchedTo", ({ threadId }) => {
234
+ // threadId: the newly selected thread
235
+ });
236
+ useAuiEvent("threadListItem.switchedAway", ({ threadId }) => {
237
+ // threadId: the thread that was switched away from
238
+ });
239
+
240
+ // After
241
+ useAuiEvent("threads.selectionChanged", ({ threadId, previousThreadId }) => {
242
+ // threadId: the newly selected thread
243
+ // previousThreadId: the thread that was switched away from
244
+ });
245
+ ```
246
+
247
+ Like its predecessors, it does not fire for the initially selected thread on mount. The deprecated events still fire and will keep working until the next major.
248
+
249
+ The deprecated pair was scope-filtered: inside a per-item `threadListItem` scope (such as `ThreadListPrimitive.Items`), `threadListItem.switchedTo` only fired for that item. `threads.selectionChanged` resolves against the shared `threads` scope, so every listener fires on every switch. Filter by id to reproduce the per-item behavior:
250
+
251
+ ```ts
252
+ const id = useAuiState((s) => s.threadListItem.id);
253
+ useAuiEvent("threads.selectionChanged", ({ threadId }) => {
254
+ if (threadId !== id) return;
255
+ // this item became selected
256
+ });
257
+ ```
258
+
259
+ The new event also fires in situations where the deprecated pair did not: `InMemoryThreadList` emits on selection changes (it previously emitted no switch events), and `switchToNewThread()` emits for the newly created thread. Selection-driven defaults such as `scrollToBottomOnThreadSwitch` and `unstable_focusOnThreadSwitched` now engage in both situations. Runtimes that resolve a deep-linked `threadId`/`initialThreadId` after mount (such as `useRemoteThreadListRuntime`) start on a placeholder new thread, so the deep link's resolution also fires the event — `previousThreadId` is the placeholder in that case.
146
260
 
147
261
  ## Still Deprecated (not removed)
148
262
 
@@ -53,7 +53,7 @@ import { AttachmentPrimitive, ComposerPrimitive } from "@assistant-ui/react";
53
53
  </ComposerPrimitive.Root>
54
54
  ```
55
55
 
56
- `Root` renders a `<div>`, `unstable_Thumb` renders a `<div>` showing the file extension with a leading dot (e.g., `.pdf`), `Name` renders plain text, and `Remove` renders a `<button>`.
56
+ `Root` renders a `<div>`, `unstable_Thumb` renders a `<div>` showing the file extension with a leading dot (e.g., `.pdf`), or the attachment type (e.g., `image`) when the filename has no extension (including leading-dot names like `.env`), `Name` renders plain text, and `Remove` renders a `<button>`. If you pass `children` to `unstable_Thumb`, they override that text.
57
57
 
58
58
  <Callout type="info">
59
59
  Runtime setup: primitives require runtime context. Wrap your UI in `AssistantRuntimeProvider` with a runtime (for example `useLocalRuntime(...)`). See [Pick a Runtime](/docs/runtimes/pick-a-runtime).
@@ -124,7 +124,7 @@ Container for a single attachment item. Renders a `<div>` element unless `asChil
124
124
 
125
125
  ### unstable_Thumb
126
126
 
127
- Thumbnail slot for attachment previews. Renders a `<div>` element unless `asChild` is set.
127
+ Thumbnail slot for attachment previews. Renders a `<div>` element unless `asChild` is set. By default it shows the file extension with a leading dot (e.g., `.pdf`); if you pass `children`, they override the automatic text.
128
128
 
129
129
  ```tsx
130
130
  <AttachmentPrimitive.unstable_Thumb className="flex size-10 items-center justify-center rounded bg-muted text-xs" />
@@ -258,7 +258,7 @@ Renders a single attachment at a specific index.
258
258
 
259
259
  ### AttachmentDropzone
260
260
 
261
- Drag-and-drop zone for file attachments. Sets `data-dragging` when a file is being dragged over it. Renders a `<div>` element unless `asChild` is set.
261
+ Drag-and-drop zone for file attachments. Sets `data-dragging` when a file is being dragged over it and the runtime supports attachments; without the attachments capability the dropzone accepts no files and shows no highlight. Non-file drags (text, links) are ignored. Renders a `<div>` element unless `asChild` is set.
262
262
 
263
263
  ```tsx
264
264
  <ComposerPrimitive.AttachmentDropzone className="rounded-xl border-2 border-dashed data-[dragging]:border-primary data-[dragging]:bg-primary/5">
@@ -507,7 +507,7 @@ Wrap your composer with `AttachmentDropzone` to support dragging files directly
507
507
  </ComposerPrimitive.AttachmentDropzone>
508
508
  ```
509
509
 
510
- The dropzone sets `data-dragging` when a file is being dragged over it, so you can style the active state with CSS.
510
+ The dropzone sets `data-dragging` when a file is being dragged over it, so you can style the active state with CSS. It requires the runtime's attachments capability (an attachment adapter); without it the dropzone stays inert.
511
511
 
512
512
  ### With Voice Input
513
513
 
@@ -112,6 +112,37 @@ Runtime setup: primitives require runtime context. Wrap your UI in `AssistantRun
112
112
  For most new code, prefer `MessagePrimitive.Parts` with a `children` render function. When you need adjacent grouping, use `MessagePrimitive.GroupedParts`.
113
113
  </Callout>
114
114
 
115
+ ### Part Types
116
+
117
+ A message part is one of three kinds, and which kind a new capability belongs to is decided by the list below rather than case by case.
118
+
119
+ | Kind | Parts | Grows by |
120
+ |------|-------|----------|
121
+ | Modality | `text`, `image`, `file` | Never. `file` carries every non-image binary modality through its `mimeType`. |
122
+ | Provider channel | `reasoning`, `source`, `tool-call`, `generative-ui` | Never. These mirror channels a model already emits. |
123
+ | Extensibility | `data` | Freely, through `name`. This is the growth path for anything app-level. |
124
+
125
+ `file` is the media carrier. Audio, video, PDFs and everything else are `{ type: "file", mimeType: "audio/mpeg" }` rather than part types of their own, so one renderer and one converter branch handle them all. The payload can be inline base64 or a URL; `sourceType` additionally allows an opaque storage id, which the LangChain-family runtimes honor and other adapters ignore.
126
+
127
+ Anything that is not a modality the model consumes or a channel it emits is a `data` part, routed by `name`:
128
+
129
+ ```tsx
130
+ <MessagePrimitive.Parts
131
+ components={{
132
+ data: {
133
+ by_name: { citation: MyCitation },
134
+ Fallback: MyDataFallback,
135
+ },
136
+ }}
137
+ />
138
+ ```
139
+
140
+ <Callout type="warn">
141
+ `Unstable_AudioMessagePart` and the `Unstable_Audio` slot are deprecated. The audio part cannot carry a filename, has no way to declare how its payload goes on the wire (no `sourceType`, so no storage id), and exists only on user messages, so a model that returns audio has no way to express it. Send audio as a `file` part with an `audio/*` mime type instead, and give it a filename.
142
+
143
+ Adapter coverage for the file path is still uneven, so check your adapter's converter before relying on file parts. Known so far: `react-pi` and the assistant-transport runtime have no file part on their user-content surface at all, so one is dropped; `react-data-stream` carries the payload but not the filename. Existing audio parts keep working; they will not gain fields.
144
+ </Callout>
145
+
115
146
  ### Tool Resolution
116
147
 
117
148
  Tool call parts resolve in this order:
@@ -149,7 +180,8 @@ Returning `null` still allows registered tool UIs and data renderer UIs to rende
149
180
  - `components.ChainOfThought` takes over all reasoning and tool-call rendering (mutually exclusive with `ToolGroup`, `ReasoningGroup`, `tools`, and `Reasoning`). This legacy path is deprecated; use `MessagePrimitive.GroupedParts` for grouped Chain of Thought in new code.
150
181
  - `data.by_name` and `data.Fallback` let you route custom data part types
151
182
  - `Quote` renders quoted message references from metadata
152
- - `Empty` and `Unstable_Audio` are available for edge and experimental rendering paths
183
+ - `Empty` is available for edge rendering paths
184
+ - `Unstable_Audio` is deprecated; render `audio/*` from the `File` slot instead
153
185
 
154
186
  ```tsx
155
187
  <MessagePrimitive.Parts
@@ -106,7 +106,7 @@ Both render a `<span>` and accept `children` to override the value from state:
106
106
 
107
107
  `Trigger`'s `send` prop controls what happens on click:
108
108
 
109
- - **`send={true}`**: immediately sends the suggestion as a new message. When the thread is running, it falls back to populating the composer instead.
109
+ - **`send={true}`**: immediately sends the suggestion as a new message. While a run is in progress, the suggestion is queued on runtimes that support queueing (leaving the composer draft untouched); otherwise the trigger is disabled.
110
110
  - **`send={false}`** (default): populates the composer text so the user can edit before sending
111
111
 
112
112
  ```tsx
@@ -130,11 +130,13 @@ When `send={false}`, the `clearComposer` prop controls whether the suggestion re
130
130
 
131
131
  ### Static configuration vs. runtime suggestions
132
132
 
133
- There are two separate data flows for suggestions:
133
+ There are two data flows for suggestions:
134
134
 
135
135
  - **Static configuration** flows through the `suggestions` scope. Pass an array to `Suggestions(...)` in your runtime provider; render it with `ThreadPrimitive.Suggestions`. Best for welcome screen prompts.
136
136
  - **Runtime / dynamic suggestions** flow through `thread.suggestions`. Populate it via `SuggestionAdapter` (local runtime) or the `suggestions` field on `useExternalStoreRuntime`; render it with the shadcn `ThreadFollowupSuggestions` component or your own component reading `useAuiState((s) => s.thread.suggestions)`. Best for follow up prompts after a turn.
137
137
 
138
+ When no static configuration is provided, the `suggestions` scope derives from `thread.suggestions`, so runtime suggestions also render through `ThreadPrimitive.Suggestions`. A static `Suggestions(...)` configuration takes precedence over the derived values.
139
+
138
140
  See [Suggested Prompts](/docs/guides/suggestions) for end to end examples.
139
141
 
140
142
  ### ThreadPrimitive.Suggestion (Legacy)
@@ -143,7 +143,7 @@ Use `topAnchorMessageClamp` to control how much of a long user message remains v
143
143
 
144
144
  - `scrollToBottomOnRunStart` (default `true`): scrolls when `thread.runStart` fires
145
145
  - `scrollToBottomOnInitialize` (default `true`): scrolls when `thread.initialize` fires
146
- - `scrollToBottomOnThreadSwitch` (default `true`): scrolls when `threadListItem.switchedTo` fires
146
+ - `scrollToBottomOnThreadSwitch` (default `true`): scrolls when `threads.selectionChanged` fires
147
147
 
148
148
  These work alongside `autoScroll`. If `autoScroll` is omitted, it defaults to `true` for `turnAnchor="bottom"` and `false` for `turnAnchor="top"`.
149
149