@assistant-ui/mcp-docs-server 0.2.1 → 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 (89) hide show
  1. package/.docs/organized/code-examples/waterfall.md +1 -1
  2. package/.docs/organized/code-examples/with-a2a.md +2 -2
  3. package/.docs/organized/code-examples/with-ag-ui.md +3 -3
  4. package/.docs/organized/code-examples/with-ai-sdk-v7.md +5 -5
  5. package/.docs/organized/code-examples/with-artifacts.md +5 -5
  6. package/.docs/organized/code-examples/with-assistant-transport.md +3 -3
  7. package/.docs/organized/code-examples/with-browser-extension.md +4 -4
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +5 -5
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +5 -5
  10. package/.docs/organized/code-examples/with-cloud.md +5 -5
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +6 -6
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +7 -7
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +8 -8
  14. package/.docs/organized/code-examples/with-eve.md +3 -3
  15. package/.docs/organized/code-examples/with-expo.md +13 -19
  16. package/.docs/organized/code-examples/with-external-store.md +3 -3
  17. package/.docs/organized/code-examples/with-ffmpeg.md +5 -5
  18. package/.docs/organized/code-examples/with-generative-ui.md +259 -23
  19. package/.docs/organized/code-examples/with-google-adk.md +2 -2
  20. package/.docs/organized/code-examples/with-heat-graph.md +1 -1
  21. package/.docs/organized/code-examples/with-image-generation.md +4 -4
  22. package/.docs/organized/code-examples/with-interactables.md +5 -5
  23. package/.docs/organized/code-examples/with-langchain.md +7 -7
  24. package/.docs/organized/code-examples/with-langgraph.md +4 -4
  25. package/.docs/organized/code-examples/with-livekit.md +7 -7
  26. package/.docs/organized/code-examples/with-mcp.md +6 -6
  27. package/.docs/organized/code-examples/with-nuxt.md +500 -564
  28. package/.docs/organized/code-examples/with-opencode.md +2 -2
  29. package/.docs/organized/code-examples/with-openui.md +449 -0
  30. package/.docs/organized/code-examples/with-pi.md +2 -2
  31. package/.docs/organized/code-examples/with-react-hook-form.md +7 -7
  32. package/.docs/organized/code-examples/with-react-ink-web.md +4 -4
  33. package/.docs/organized/code-examples/with-react-ink.md +2 -2
  34. package/.docs/organized/code-examples/with-react-router.md +4 -4
  35. package/.docs/organized/code-examples/with-resumable-stream.md +7 -7
  36. package/.docs/organized/code-examples/with-store.md +1 -1
  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 +5 -5
  40. package/.docs/organized/code-examples/with-tap-runtime.md +2 -2
  41. package/.docs/organized/code-examples/with-virtualized-thread.md +2 -2
  42. package/.docs/organized/code-examples/with-vue.md +1 -1
  43. package/.docs/raw/docs/(docs)/cli.mdx +6 -1
  44. package/.docs/raw/docs/(docs)/installation.mdx +2 -2
  45. package/.docs/raw/docs/(reference)/api-reference/generative-ui/slack.mdx +5 -1
  46. package/.docs/raw/docs/(reference)/api-reference/generative-ui/teams.mdx +9 -2
  47. package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +2 -2
  48. package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +39 -1
  49. package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +29 -4
  50. package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +1 -1
  51. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +29 -1
  52. package/.docs/raw/docs/(reference)/api-reference/voice/session.mdx +1 -1
  53. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +1 -1
  54. package/.docs/raw/docs/cloud/ai-sdk.mdx +2 -2
  55. package/.docs/raw/docs/cloud/index.mdx +1 -1
  56. package/.docs/raw/docs/guides/attachments.mdx +2 -2
  57. package/.docs/raw/docs/guides/context-api.mdx +15 -17
  58. package/.docs/raw/docs/guides/dictation.mdx +1 -1
  59. package/.docs/raw/docs/guides/mentions.mdx +2 -0
  60. package/.docs/raw/docs/guides/suggestions.mdx +6 -3
  61. package/.docs/raw/docs/ink/primitives.mdx +1 -1
  62. package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +146 -128
  63. package/.docs/raw/docs/migrations/v0-15.mdx +34 -0
  64. package/.docs/raw/docs/primitives/suggestion.mdx +3 -1
  65. package/.docs/raw/docs/primitives/thread.mdx +1 -1
  66. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +35 -5
  67. package/.docs/raw/docs/runtimes/claude-managed-agents.mdx +118 -0
  68. package/.docs/raw/docs/runtimes/concepts/stability.mdx +2 -1
  69. package/.docs/raw/docs/runtimes/concepts/threads.mdx +68 -28
  70. package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +1 -1
  71. package/.docs/raw/docs/runtimes/custom/external-store.mdx +6 -2
  72. package/.docs/raw/docs/runtimes/langchain.mdx +1 -1
  73. package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +4 -0
  74. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +8 -1
  75. package/.docs/raw/docs/tools/defining-tools.mdx +19 -0
  76. package/.docs/raw/docs/tools/generative-ui-primitive.mdx +180 -0
  77. package/.docs/raw/docs/tools/generative-ui-slack.mdx +167 -0
  78. package/.docs/raw/docs/tools/generative-ui-teams.mdx +160 -0
  79. package/.docs/raw/docs/tools/generative-ui.mdx +224 -211
  80. package/.docs/raw/docs/tools/index.mdx +2 -1
  81. package/.docs/raw/docs/tools/interactables.mdx +5 -4
  82. package/.docs/raw/docs/tools/openui.mdx +175 -0
  83. package/.docs/raw/docs/tools/tool-ui.mdx +1 -2
  84. package/.docs/raw/docs/tools/user-managed-mcp.mdx +5 -1
  85. package/.docs/raw/docs/ui/attachment.mdx +27 -0
  86. package/.docs/raw/docs/ui/file.mdx +1 -1
  87. package/.docs/raw/docs/ui/image.mdx +1 -1
  88. package/package.json +4 -4
  89. package/.docs/raw/docs/tools/interactables-legacy.mdx +0 -423
@@ -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.
@@ -224,6 +224,40 @@ return (
224
224
  );
225
225
  ```
226
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.
260
+
227
261
  ## Still Deprecated (not removed)
228
262
 
229
263
  - Primitive `If` components (`ThreadPrimitive.If`, `MessagePrimitive.If`, `ThreadPrimitive.Empty`) — replaced by `AuiIf`. The codemod migrates these.
@@ -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
 
@@ -16,7 +16,7 @@ Reference for the runtime's API surface. Start with [quickstart](/docs/runtimes/
16
16
  | `showThinking` | `boolean` | Whether to render `THINKING_*` and `REASONING_*` events as visible reasoning. Defaults to `true`. |
17
17
  | `autoCancelPendingToolCalls` | `boolean` | Cancel unresolved client-side tool calls automatically when the user sends, edits, or reloads a message. Defaults to `true`. See [below](#auto-cancelling-pending-tool-calls). |
18
18
  | `onError` | `(e: Error) => void` | Error callback fired on `RUN_ERROR` events and protocol errors. |
19
- | `onCancel` | `() => void` | Cancellation callback fired when the user cancels a run. |
19
+ | `onCancel` | `() => void` | Cancellation callback fired when a run is cancelled, including user cancel and runtime teardown. |
20
20
  | `adapters` | `UseAgUiRuntimeAdapters` | Standard adapter slots (see below). |
21
21
 
22
22
  ## Adapter slots
@@ -64,8 +64,20 @@ messages are gone on the next page load.
64
64
 
65
65
  `fromAgUiMessages` accepts an optional second argument: pass
66
66
  `{ showThinking: false }` to match a runtime configured with
67
- `showThinking: false`, so imported reasoning messages are dropped at conversion
68
- time, the same way a live run never stores them.
67
+ `showThinking: false`, so the readable text of an imported reasoning message is
68
+ dropped at conversion time, the same way a live run never stores it. An
69
+ `encryptedValue` on that message is kept, because it is opaque state the agent
70
+ needs back rather than something the option hides.
71
+
72
+ Reasoning makes the round trip in the shape it arrived in. `fromAgUiMessages` imports a `reasoning` record as an assistant message holding a reasoning part, and the run input converts that part back into a standalone `reasoning` record instead of dropping it, so a reloaded thread keeps its reasoning history on the next run. A reasoning part on an assistant message that also has text or tool calls leaves as its own `reasoning` record placed ahead of that assistant record; the AG-UI message body carries no run identity, so the original position of reasoning within a run is not recoverable.
73
+
74
+ The encrypted value survives with it. AG-UI describes it as an opaque chain-of-thought blob the client stores and forwards for state continuity, not as a signature computed over the text. An imported `ReasoningMessage` carrying `encryptedValue` keeps it at `providerMetadata.agui.encryptedValue` on the part, and a live run picks the same value up from the `REASONING_ENCRYPTED_VALUE` event (`subtype: "message"`, keyed by `entityId`), so reasoning from either source is re-emitted with the value intact and an agent that needs it back can replay it. A record whose readable `content` is empty and whose payload lives entirely in `encryptedValue`, the zero-data-retention shape AG-UI describes when an agent advertises `capabilities.reasoning.encrypted`, is preserved on the import path only. It has nothing to render by construction, so it never becomes a message or a part; it rides on `metadata.custom.agui.opaqueReasoning` of the message it sat next to on the wire and is replayed into the run input adjacent to that message. `showThinking` does not discard it. That option hides reasoning from the UI, and the encrypted value is opaque state the agent needs back rather than something rendered, so a hidden record keeps it and loses only the readable text. This is an import-path guarantee: a live run with `showThinking: false` opens no reasoning block, so a `REASONING_ENCRYPTED_VALUE` arriving during it resolves no slot and is not retained. The same thread therefore carries the value after a reload but not within the live session that produced it. A consumer calling `fromAgUiMessages` directly sees the metadata rather than a part.
75
+
76
+ Three limits apply to that record. A live stream that emits no readable content produces no reasoning part, so the runtime has nothing to attach the value to; only `fromAgUiMessages` preserves it, whether you call it yourself or the runtime calls it for you while importing a `MESSAGES_SNAPSHOT`. A record that sat between an assistant message and its own tool result is replayed after that tool result rather than between the two, because the import folds the result into the assistant message and the boundary is gone by export. A record in a snapshot that contains no other message has nothing to anchor to and is dropped.
77
+
78
+ Sending reasoning back is what the protocol asks for, and the inbound side has to handle it. `ag-ui-langgraph` is worth pinning for that reason: below 0.0.36 it raises `ValueError: Unsupported message role: reasoning` on the second turn of any thread that produced reasoning, because its converter recognised only the user, assistant, system, and tool roles. 0.0.36 skips inbound `reasoning` and `developer` records instead of raising, so the turn succeeds but the reasoning is discarded. 0.0.42 re-attaches an inbound `reasoning` record as a content block on the assistant message that follows it, encrypted content included, so the replay actually reaches the model; a record that no assistant message follows is still discarded there, which is what becomes of one replayed after the last message of a thread. Keep it at 0.0.36 or newer to avoid the error, and at 0.0.42 or newer for the replay to be worth anything.
79
+
80
+ What a server does with a replayed record remains its own choice, so treat continuity as best effort rather than guaranteed. `ag-ui-langgraph` covers all three behaviours across those three versions, and another integration may pick any of them; an `encryptedValue` reaches the provider only where the server forwards it.
69
81
 
70
82
  `fromAgUiMessages` reconstructs the text, reasoning, and tool calls of each message. Multimodal user input (`image`, `audio`, `video`, and `document` parts, as well as legacy `binary` parts) is restored as attachments on the user message, so a backend that persists multimodal messages shows them again on reload and re-sends them on the next run. Legacy `binary` parts that only reference a file id are not restored.
71
83
 
@@ -183,8 +195,25 @@ The runtime parses the AG-UI event stream and maps each event type to assistant-
183
195
  | `STATE_SNAPSHOT` | Replaces the agent's external state. |
184
196
  | `STATE_DELTA` | Applies a JSON-patch-style delta to the agent's state. |
185
197
  | `MESSAGES_SNAPSHOT` | Replaces the full message list (used for thread restore). |
186
- | `CUSTOM` | Forwarded to your custom event handling. |
187
- | `RAW` | Untyped passthrough for unrecognized events. |
198
+ | `CUSTOM` | Appended to the in-flight assistant message as a `data` part. |
199
+ | `RAW` | Parsed and ignored; unrecognized wire event types are normalized into `RAW`. |
200
+
201
+ ### Custom events
202
+
203
+ `CUSTOM` events are the protocol's extension mechanism for application-defined data. Each event is appended to the in-flight assistant message as a canonical `data` part in arrival order: `CUSTOM { name: "sources", value: {...} }` becomes `{ type: "data", name: "sources", data: {...} }`. Repeated names append separate parts, the `value` is passed through verbatim, and data parts reset with each run. Tool calls that carry a `parentMessageId` are anchored under that message rather than at their wire position, so a data part can render after a tool call that arrived later. A run that delivers its assistant message only through `MESSAGES_SNAPSHOT`, with no streamed text or tool calls, drops that run's data parts when the snapshot supersedes the in-flight message.
204
+
205
+ Render them by registering a per-name renderer; parts without a registered renderer are not displayed, unless a `Data` fallback component is registered, in which case the fallback receives every custom event name, including the framework plumbing listed below.
206
+
207
+ ```tsx
208
+ import { useAssistantDataUI } from "@assistant-ui/react";
209
+
210
+ useAssistantDataUI({
211
+ name: "sources",
212
+ render: ({ data }) => <SourceList sources={data.sources} />,
213
+ });
214
+ ```
215
+
216
+ Data parts stay in the assistant-ui message but are not sent back to the agent, since the AG-UI assistant record has no field for them. History adapters, including the assistant-cloud one, persist them as part of the message JSON. Framework integrations emit their own plumbing over this channel (`on_interrupt`, `PredictState`, `Exit`, `hook_error`, `state_update_error`, `system:*`, `MultiAgentHandoff`), and those names surface as data parts like any other, so only register renderers for names your backend owns.
188
217
 
189
218
  ## Feature support
190
219
 
@@ -195,6 +224,7 @@ The runtime parses the AG-UI event stream and maps each event type to assistant-
195
224
  | Tool calls and results | Yes |
196
225
  | Tool result handoff (client-side execution) | Yes |
197
226
  | State snapshots and deltas | Yes |
227
+ | Custom events (as `data` parts) | Yes |
198
228
  | Cancellation | Yes |
199
229
  | Message editing | Yes |
200
230
  | Message reload | Yes |
@@ -0,0 +1,118 @@
1
+ ---
2
+ title: Claude Managed Agents
3
+ description: Connect Anthropic's Managed Agents sessions to assistant-ui with the external store runtime, folding the session event log into messages, rendering approval gates, and using sessions as the thread list.
4
+ ---
5
+
6
+ [Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) is Anthropic's hosted agent platform: Anthropic runs the agent loop and provisions a sandboxed container per session, and your client drives the session over an event stream. A session holds the entire conversation server-side as a durable event log, streams every step (text, tool calls, approval stops, status) as typed events, and accepts user messages and tool confirmations back.
7
+
8
+ That shape maps directly onto two assistant-ui primitives, with no adapter package in between:
9
+
10
+ - [`useExternalStoreRuntime`](/docs/runtimes/custom/external-store) renders messages you derive from the session's event log. The log is the single source of truth, so replaying a stored session and tailing a live one run through the same pure function and can never disagree.
11
+ - [`useRemoteThreadListRuntime`](/docs/api-reference/hooks/runtimes#useremotethreadlistruntime) turns the session list into the thread sidebar. A thread is a session; there is no conversations table anywhere in the app.
12
+
13
+ Anthropic ships an official reference implementation of this integration: the [Claude Managed Agents quickstart](https://github.com/anthropics/claude-quickstarts/tree/main/managed-agents/assistant-ui) is a complete Next.js app (composer, thread, sidebar, tool cards, approval gate) built exactly this way. This page teaches the pattern; the quickstart is the runnable proof.
14
+
15
+ <Callout type="info">
16
+ Managed Agents is a beta API (`managed-agents-2026-04-01`; the SDK sets the header automatically). The quickstart declares `@anthropic-ai/sdk` `^0.113.0` (the session event helpers and token previews need 0.109.0 or later), and `@assistant-ui/react` 0.14.27 or later for the toolkit API and the approval gate on the external store runtime.
17
+ </Callout>
18
+
19
+ ## The event-to-message mapping
20
+
21
+ Everything the session emits arrives as a typed event. The integration is one pure fold from the event array to assistant-ui's message model:
22
+
23
+ | Managed Agents event | assistant-ui |
24
+ | --- | --- |
25
+ | `user.message` | A user message |
26
+ | `agent.message` | Assistant text (buffered, authoritative) |
27
+ | `event_start` / `event_delta` | The same text, streamed early as token previews |
28
+ | `agent.thinking` | A reasoning part (progress signal only; the API sends no reasoning text) |
29
+ | `agent.tool_use` / `agent.mcp_tool_use` / `agent.custom_tool_use` | A tool-call part; the `toolCallId` is the event id |
30
+ | `agent.tool_result` (and mcp / custom variants) | That part's result |
31
+ | `session.status_idle` with `stop_reason: requires_action` | `requires-action` message status, plus an approval on each blocked tool part |
32
+ | `user.tool_confirmation` | The approval, settled (allowed or denied) |
33
+ | `session.status_running` / `status_idle` | Whether the turn is live (`isRunning`) |
34
+ | `session.error` | An error status on the message, or a retry banner |
35
+
36
+ Because the fold is pure, opening an old chat replays `sessions.events.list()` through it, and a live chat feeds the SSE tail through it, and the two paths cannot render differently. Approvals, denials, and charts all come back after a reload because they are in the log, not in browser state.
37
+
38
+ The runtime wiring is a structural subset of `ThreadMessageLike`, handed to the external store as-is:
39
+
40
+ ```tsx
41
+ const runtime = useExternalStoreRuntime<ThreadMessageLike>({
42
+ messages: snapshot.messages,
43
+ convertMessage: (m) => m,
44
+ isRunning: isBusy(snapshot),
45
+
46
+ onNew: async (message) => {
47
+ const id = await ensureSession();
48
+ await sendMessage(id, textOf(message));
49
+ },
50
+
51
+ // The Stop button becomes a real server-side interrupt.
52
+ onCancel: async () => controller.interrupt(),
53
+
54
+ // The Allow / Deny click on a gated tool call. approvalId is the
55
+ // tool_use event id from session.status_idle { requires_action }.
56
+ onRespondToToolApproval: async ({ approvalId, approved, reason }) => {
57
+ controller.respondToApproval(approvalId, approved, reason);
58
+ },
59
+ });
60
+ ```
61
+
62
+ ## Sessions are the thread list
63
+
64
+ The sidebar is a `RemoteThreadListAdapter` over the Managed Agents session API. Thread id and session id are the same string, so nothing maps between the two worlds:
65
+
66
+ | Adapter method | Managed Agents call |
67
+ | --- | --- |
68
+ | `list` | `sessions.list()`, filtered to the sessions this app created (a `metadata` tag) |
69
+ | `initialize` | `sessions.create()`, invoked by assistant-ui on the first message of a new chat |
70
+ | `rename` | `sessions.update({ title })` |
71
+ | `archive` | `sessions.archive()` |
72
+ | `unarchive` | Throws. Managed Agents sessions cannot be unarchived, and switching to an archived thread auto-unarchives by default, so do not render archived sessions as switchable (the quickstart's ownership gate rejects archived ids outright) |
73
+ | `delete` | `sessions.delete()` |
74
+ | `fetch` | `sessions.retrieve()` |
75
+ | `generateTitle` | Reads the title back after the server retitles the session from the first message, so the sidebar row updates without a second model call |
76
+
77
+ A brand-new chat has no session until the first send: the composer works immediately, and `initialize()` creates the session lazily when the first message (or first attachment upload) needs one. Kill the server, restart, reload, and every conversation comes back, because none of it ever lived in the app.
78
+
79
+ ## The approval gate
80
+
81
+ Managed Agents supports per-tool [permission policies](https://platform.claude.com/docs/en/managed-agents/permission-policies). A tool configured as `always_ask` (the quickstart gates `bash` this way) does not run when the agent reaches for it. The session emits the `agent.tool_use` event, then parks:
82
+
83
+ ```json
84
+ { "type": "session.status_idle", "stop_reason": { "type": "requires_action", "event_ids": ["sevt_..."] } }
85
+ ```
86
+
87
+ The fold stamps an approval onto that tool part and sets the message status to `requires-action`, which is everything assistant-ui needs to render Allow and Deny on the tool card. The click flows back through `onRespondToToolApproval` as a `user.tool_confirmation` event:
88
+
89
+ ```json
90
+ { "type": "user.tool_confirmation", "tool_use_id": "sevt_...", "result": "deny", "deny_message": "Not on this box." }
91
+ ```
92
+
93
+ Two wire details matter. The `tool_use_id` is the tool-use event id, not an Anthropic `toolu_` id. And a denial reaches the agent as the tool's result, so it adjusts course instead of retrying the same command.
94
+
95
+ Custom client-executed tools ride the same `requires_action` stop but take a `user.custom_tool_result` instead of a confirmation; sending a confirmation for one is a 400. The quickstart's inline chart tool is the worked example: the session parks, the card renders the chart from the tool's input, and the client answers so the agent continues.
96
+
97
+ ## Token streaming
98
+
99
+ By default assistant text arrives as whole `agent.message` events when a model request finishes. Opting the stream into `event_deltas: ["agent.message", "agent.thinking"]` adds token previews: an `event_start` announces the upcoming event, `event_delta` fragments stream the text, and the buffered event lands last as the authoritative record. Concatenating a preview's deltas in arrival order yields a prefix of the final text, but under load the server may shed the remaining deltas for an event, so the prefix is not necessarily the whole message. The fold therefore appends fragments for display and discards the accumulated preview when the buffered event arrives; never treat a preview as final. When a turn errors or is interrupted, the buffered event may never arrive at all, but `span.model_request_end` still does, so close any unreconciled preview when you see it.
100
+
101
+ Previews are best-effort and gated per organization. Build against the buffered events and treat deltas as an enhancement: an org without the streaming gate runs the identical code path with replies arriving whole.
102
+
103
+ ## Security boundary
104
+
105
+ The Anthropic API key stays server-side; the browser talks only to your own route handlers, which relay to Managed Agents. Because a session id arrives from the browser and becomes an API path parameter, validate ownership on every route: the id must resolve, belong to your agent, and carry your app's metadata tag before any read or write. The API key can see the whole workspace; that gate is what keeps a guessed id from reading it. The quickstart's [`ownedSession()`](https://github.com/anthropics/claude-quickstarts/blob/main/managed-agents/assistant-ui/lib/owned-session.ts) is the reference shape.
106
+
107
+ ## Run the reference
108
+
109
+ ```bash
110
+ git clone https://github.com/anthropics/claude-quickstarts
111
+ cd claude-quickstarts/managed-agents/assistant-ui
112
+ npm install
113
+ cp .env.example .env # add ANTHROPIC_API_KEY, or `ant auth login` once
114
+ npm run setup # one-time: creates the agent + environment, paste the IDs into .env
115
+ npm run dev # drop sample_data/sales.csv into the chat
116
+ ```
117
+
118
+ The quickstart's [README](https://github.com/anthropics/claude-quickstarts/tree/main/managed-agents/assistant-ui#readme) walks through every file: the reducer, the session controller, the thread list adapter, the tool cards, and the attachment adapter that uploads composer files into the session sandbox. For the platform itself, start with Anthropic's [Managed Agents overview](https://platform.claude.com/docs/en/managed-agents/overview) and [events reference](https://platform.claude.com/docs/en/managed-agents/events-and-streaming).
@@ -33,7 +33,8 @@ A non-exhaustive list of `unstable_` exports surfaced in the runtime docs.
33
33
  | `unstable_humanToolNames` | `@assistant-ui/react` | Tool names that pause the run until a result is added via `addResult`. Only available on LocalRuntime; not supported in DataStream. |
34
34
  | `unstable_threadListAdapter` | `@assistant-ui/react-langgraph` | LangGraph thread-list adapter slot on `useLangGraphRuntime`. |
35
35
  | `unstable_createLangGraphStream` | `@assistant-ui/react-langgraph` | End-to-end cancellation primitive. |
36
- | `unstable_Provider` | Various adapters | Thread-scoped provider on `RemoteThreadListAdapter`. Must render children synchronously. |
36
+ | `unstable_Provider` | Various adapters | React-component face on `RemoteThreadListAdapter`. Must render children synchronously. `useRemoteThreadListRuntime` renders it when present; the `RemoteThreadList` store entry ignores it. |
37
+ | `unstable_useAdapters` | Various adapters | Hook face on `RemoteThreadListAdapter`. The `RemoteThreadList` store entry calls it. `useRemoteThreadListRuntime` synthesizes a `RuntimeAdapterProvider` from it when `unstable_Provider` is omitted. |
37
38
  | `unstable_capabilities` | `ExternalStoreRuntime` | Toggle copy and other thread capabilities. |
38
39
  | `unstable_state`, `unstable_annotations`, `unstable_data` | Message metadata | Runtime-internal fields exposed for advanced use cases. |
39
40
  | `unstable_assistantMessageId`, `unstable_threadId`, `unstable_parentId`, `unstable_getMessage` | `ChatModelRunOptions` | Identifiers and accessors passed to your `ChatModelAdapter.run`. |