@assistant-ui/mcp-docs-server 0.2.1 → 0.2.3

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 (187) hide show
  1. package/.docs/organized/code-examples/waterfall.md +3 -3
  2. package/.docs/organized/code-examples/with-a2a.md +5 -4
  3. package/.docs/organized/code-examples/with-ag-ui.md +7 -6
  4. package/.docs/organized/code-examples/with-ai-sdk-v7.md +14 -13
  5. package/.docs/organized/code-examples/with-artifacts.md +12 -11
  6. package/.docs/organized/code-examples/with-assistant-transport.md +6 -5
  7. package/.docs/organized/code-examples/with-browser-extension.md +6 -6
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +16 -14
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +7 -7
  10. package/.docs/organized/code-examples/with-cloud.md +11 -10
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +11 -10
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +14 -13
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +15 -14
  14. package/.docs/organized/code-examples/with-eve.md +6 -5
  15. package/.docs/organized/code-examples/with-expo.md +213 -36
  16. package/.docs/organized/code-examples/with-external-store.md +6 -5
  17. package/.docs/organized/code-examples/with-ffmpeg.md +11 -10
  18. package/.docs/organized/code-examples/with-generative-ui.md +263 -29
  19. package/.docs/organized/code-examples/with-google-adk.md +5 -4
  20. package/.docs/organized/code-examples/with-heat-graph.md +3 -3
  21. package/.docs/organized/code-examples/with-image-generation.md +8 -7
  22. package/.docs/organized/code-examples/with-interactables.md +11 -10
  23. package/.docs/organized/code-examples/with-langchain.md +10 -9
  24. package/.docs/organized/code-examples/with-langgraph.md +7 -6
  25. package/.docs/organized/code-examples/with-livekit.md +15 -14
  26. package/.docs/organized/code-examples/with-mcp.md +14 -12
  27. package/.docs/organized/code-examples/with-nuxt.md +511 -575
  28. package/.docs/organized/code-examples/with-opencode.md +4 -4
  29. package/.docs/organized/code-examples/with-openui.md +450 -0
  30. package/.docs/organized/code-examples/with-pi.md +6 -5
  31. package/.docs/organized/code-examples/with-react-hook-form.md +16 -13
  32. package/.docs/organized/code-examples/with-react-ink-web.md +65 -15
  33. package/.docs/organized/code-examples/with-react-ink.md +5 -4
  34. package/.docs/organized/code-examples/with-react-router.md +8 -7
  35. package/.docs/organized/code-examples/with-resumable-stream.md +13 -12
  36. package/.docs/organized/code-examples/with-store.md +3 -3
  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 +10 -9
  40. package/.docs/organized/code-examples/with-tap-runtime.md +5 -4
  41. package/.docs/organized/code-examples/with-virtualized-thread.md +6 -5
  42. package/.docs/organized/code-examples/with-vue.md +2 -2
  43. package/.docs/raw/docs/{(docs) → (getting-started)}/cli.mdx +6 -1
  44. package/.docs/raw/docs/{(docs) → (getting-started)}/devtools.mdx +1 -1
  45. package/.docs/raw/docs/{(docs) → (getting-started)}/index.mdx +4 -2
  46. package/.docs/raw/docs/{(docs) → (getting-started)}/installation.mdx +26 -26
  47. package/.docs/raw/docs/(reference)/api-reference/adapters/suggestions.mdx +6 -0
  48. package/.docs/raw/docs/(reference)/api-reference/context-providers/assistant-runtime-provider.mdx +1 -1
  49. package/.docs/raw/docs/(reference)/api-reference/external-store/message-conversion.mdx +6 -1
  50. package/.docs/raw/docs/(reference)/api-reference/generative-ui/slack.mdx +5 -1
  51. package/.docs/raw/docs/(reference)/api-reference/generative-ui/teams.mdx +9 -2
  52. package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +2 -2
  53. package/.docs/raw/docs/(reference)/api-reference/integrations/{react-ai-sdk.mdx → ai-sdk.mdx} +43 -18
  54. package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +61 -2
  55. package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +3 -3
  56. package/.docs/raw/docs/(reference)/api-reference/overview.mdx +2 -2
  57. package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +20 -2
  58. package/.docs/raw/docs/(reference)/api-reference/tools/interactables.mdx +1 -1
  59. package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +1 -1
  60. package/.docs/raw/docs/(reference)/api-reference/transport/frame.mdx +1 -1
  61. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +32 -1
  62. package/.docs/raw/docs/(reference)/api-reference/voice/session.mdx +1 -1
  63. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +10 -10
  64. package/.docs/raw/docs/cloud/ai-sdk.mdx +4 -2
  65. package/.docs/raw/docs/cloud/index.mdx +1 -1
  66. package/.docs/raw/docs/copilots/assistant-frame.mdx +19 -8
  67. package/.docs/raw/docs/guides/attachments.mdx +4 -4
  68. package/.docs/raw/docs/guides/branching.mdx +1 -1
  69. package/.docs/raw/docs/guides/chatgpt-subscription.mdx +1 -1
  70. package/.docs/raw/docs/guides/context-api.mdx +15 -17
  71. package/.docs/raw/docs/guides/dictation.mdx +2 -2
  72. package/.docs/raw/docs/guides/electron.mdx +1 -1
  73. package/.docs/raw/docs/guides/latex.mdx +1 -1
  74. package/.docs/raw/docs/guides/mentions.mdx +2 -0
  75. package/.docs/raw/docs/guides/message-timing.mdx +11 -5
  76. package/.docs/raw/docs/guides/quoting.mdx +1 -1
  77. package/.docs/raw/docs/guides/resumable-streams.mdx +3 -3
  78. package/.docs/raw/docs/guides/speech.mdx +1 -1
  79. package/.docs/raw/docs/guides/suggestions.mdx +8 -5
  80. package/.docs/raw/docs/guides/voice.mdx +1 -1
  81. package/.docs/raw/docs/ink/hooks.mdx +1 -1
  82. package/.docs/raw/docs/ink/index.mdx +3 -2
  83. package/.docs/raw/docs/ink/migration.mdx +1 -1
  84. package/.docs/raw/docs/ink/primitives.mdx +1 -1
  85. package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +3 -3
  86. package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +3 -3
  87. package/.docs/raw/docs/integrations/frameworks/{cloudflare-agents/overview.mdx → cloudflare-agents.mdx} +4 -3
  88. package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +1 -1
  89. package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +1 -1
  90. package/.docs/raw/docs/integrations/index.mdx +2 -2
  91. package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +149 -131
  92. package/.docs/raw/docs/migrations/toolkit-tools.mdx +2 -2
  93. package/.docs/raw/docs/migrations/v0-15.mdx +34 -0
  94. package/.docs/raw/docs/primitives/action-bar.mdx +1 -1
  95. package/.docs/raw/docs/primitives/assistant-modal.mdx +1 -1
  96. package/.docs/raw/docs/primitives/attachment.mdx +1 -1
  97. package/.docs/raw/docs/primitives/branch-picker.mdx +1 -1
  98. package/.docs/raw/docs/primitives/chain-of-thought.mdx +3 -3
  99. package/.docs/raw/docs/primitives/composer.mdx +1 -1
  100. package/.docs/raw/docs/primitives/error.mdx +1 -1
  101. package/.docs/raw/docs/primitives/message.mdx +1 -1
  102. package/.docs/raw/docs/primitives/selection-toolbar.mdx +1 -1
  103. package/.docs/raw/docs/primitives/suggestion.mdx +4 -2
  104. package/.docs/raw/docs/primitives/thread-list.mdx +1 -1
  105. package/.docs/raw/docs/primitives/thread.mdx +2 -2
  106. package/.docs/raw/docs/react-native/adapters.mdx +1 -1
  107. package/.docs/raw/docs/react-native/index.mdx +2 -2
  108. package/.docs/raw/docs/react-native/migration.mdx +2 -2
  109. package/.docs/raw/docs/react-native/primitives.mdx +142 -5
  110. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +48 -6
  111. package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +3 -3
  112. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +4 -4
  113. package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +31 -16
  114. package/.docs/raw/docs/runtimes/claude-managed-agents.mdx +118 -0
  115. package/.docs/raw/docs/runtimes/concepts/adapters.mdx +3 -3
  116. package/.docs/raw/docs/runtimes/concepts/architecture.mdx +3 -3
  117. package/.docs/raw/docs/runtimes/concepts/stability.mdx +2 -1
  118. package/.docs/raw/docs/runtimes/concepts/threads.mdx +73 -33
  119. package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +1 -1
  120. package/.docs/raw/docs/runtimes/custom/external-store.mdx +6 -2
  121. package/.docs/raw/docs/runtimes/langchain.mdx +2 -2
  122. package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +4 -0
  123. package/.docs/raw/docs/runtimes/opencode/quickstart.mdx +1 -1
  124. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +9 -2
  125. package/.docs/raw/docs/tools/backend.mdx +4 -4
  126. package/.docs/raw/docs/tools/defining-tools.mdx +21 -2
  127. package/.docs/raw/docs/tools/generative-ui-primitive.mdx +180 -0
  128. package/.docs/raw/docs/tools/generative-ui-slack.mdx +167 -0
  129. package/.docs/raw/docs/tools/generative-ui-teams.mdx +160 -0
  130. package/.docs/raw/docs/tools/generative-ui.mdx +224 -211
  131. package/.docs/raw/docs/tools/index.mdx +2 -1
  132. package/.docs/raw/docs/tools/interactables.mdx +8 -7
  133. package/.docs/raw/docs/tools/mcp-apps.mdx +18 -1
  134. package/.docs/raw/docs/tools/mcp.mdx +2 -2
  135. package/.docs/raw/docs/tools/openui.mdx +175 -0
  136. package/.docs/raw/docs/tools/tool-ui.mdx +18 -7
  137. package/.docs/raw/docs/tools/user-managed-mcp.mdx +7 -3
  138. package/.docs/raw/docs/ui/assistant-modal.mdx +3 -3
  139. package/.docs/raw/docs/ui/assistant-sidebar.mdx +1 -1
  140. package/.docs/raw/docs/ui/attachment.mdx +50 -3
  141. package/.docs/raw/docs/ui/composer-trigger-popover.mdx +36 -1
  142. package/.docs/raw/docs/ui/context-display.mdx +1 -1
  143. package/.docs/raw/docs/ui/directive-text.mdx +1 -1
  144. package/.docs/raw/docs/ui/file.mdx +2 -2
  145. package/.docs/raw/docs/ui/follow-up-suggestions.mdx +1 -1
  146. package/.docs/raw/docs/ui/image.mdx +2 -2
  147. package/.docs/raw/docs/ui/markdown.mdx +40 -1
  148. package/.docs/raw/docs/ui/mermaid.mdx +1 -1
  149. package/.docs/raw/docs/ui/message-timing.mdx +1 -1
  150. package/.docs/raw/docs/ui/model-selector.mdx +4 -4
  151. package/.docs/raw/docs/ui/part-grouping.mdx +1 -1
  152. package/.docs/raw/docs/ui/quote.mdx +3 -3
  153. package/.docs/raw/docs/ui/reasoning.mdx +31 -3
  154. package/.docs/raw/docs/ui/scrollbar.mdx +1 -1
  155. package/.docs/raw/docs/ui/sources.mdx +10 -1
  156. package/.docs/raw/docs/ui/streamdown.mdx +2 -2
  157. package/.docs/raw/docs/ui/syntax-highlighting.mdx +1 -1
  158. package/.docs/raw/docs/ui/thread-list.mdx +40 -1
  159. package/.docs/raw/docs/ui/thread.mdx +49 -2
  160. package/.docs/raw/docs/ui/tool-fallback.mdx +23 -9
  161. package/.docs/raw/docs/ui/tool-group.mdx +1 -1
  162. package/.docs/raw/docs/ui/voice.mdx +1 -1
  163. package/.docs/raw/docs/utilities/react-o11y.mdx +2 -2
  164. package/dist/xulux/catalog-client.d.ts.map +1 -1
  165. package/dist/xulux/catalog-client.js +51 -6
  166. package/dist/xulux/catalog-client.js.map +1 -1
  167. package/dist/xulux/types.d.ts +7 -7
  168. package/dist/xulux/types.d.ts.map +1 -1
  169. package/dist/xulux/types.js.map +1 -1
  170. package/package.json +5 -5
  171. package/src/tools/tests/docs.test.ts +10 -7
  172. package/src/tools/tests/path-traversal.test.ts +5 -2
  173. package/src/tools/tests/xulux-templates.test.ts +63 -0
  174. package/src/xulux/catalog-client.ts +68 -23
  175. package/src/xulux/types.ts +17 -13
  176. package/.docs/raw/docs/tools/interactables-legacy.mdx +0 -423
  177. package/.docs/raw/docs/ui/accordion.mdx +0 -266
  178. package/.docs/raw/docs/ui/badge.mdx +0 -150
  179. package/.docs/raw/docs/ui/diff-viewer.mdx +0 -280
  180. package/.docs/raw/docs/ui/dot-matrix.mdx +0 -133
  181. package/.docs/raw/docs/ui/number-roll.mdx +0 -154
  182. package/.docs/raw/docs/ui/select.mdx +0 -254
  183. package/.docs/raw/docs/ui/tabs.mdx +0 -271
  184. /package/.docs/raw/docs/{(docs) → (getting-started)}/architecture.mdx +0 -0
  185. /package/.docs/raw/docs/{(docs) → (getting-started)}/base-ui.mdx +0 -0
  186. /package/.docs/raw/docs/{(docs) → (getting-started)}/llm.mdx +0 -0
  187. /package/.docs/raw/docs/{(docs) → (getting-started)}/rtl.mdx +0 -0
@@ -48,17 +48,17 @@ npm init -y
48
48
  <PlatformTabs>
49
49
  <Tab value="React">
50
50
 
51
- <InstallCommand npm={["@assistant-ui/react", "@assistant-ui/react-ai-sdk", "ai@^7", "@ai-sdk/react@^4", "@ai-sdk/openai", "zod"]} />
51
+ <InstallCommand npm={["@assistant-ui/react", "@assistant-ui/ai-sdk", "ai@^7", "@ai-sdk/react@^4", "@ai-sdk/openai", "zod"]} />
52
52
 
53
53
  </Tab>
54
54
  <Tab value="React Native">
55
55
 
56
- <InstallCommand expo={["@assistant-ui/react-native", "@assistant-ui/react-ai-sdk", "ai@^7", "@ai-sdk/react@^4", "@ai-sdk/openai", "zod"]} />
56
+ <InstallCommand expo={["@assistant-ui/react-native", "@assistant-ui/ai-sdk", "ai@^7", "@ai-sdk/react@^4", "@ai-sdk/openai", "zod"]} />
57
57
 
58
58
  </Tab>
59
59
  <Tab value="React Ink">
60
60
 
61
- <InstallCommand npm={["@assistant-ui/react-ink", "@assistant-ui/react-ai-sdk", "ai@^7", "@ai-sdk/react@^4", "@ai-sdk/openai", "zod", "ink", "react"]} />
61
+ <InstallCommand npm={["@assistant-ui/react-ink", "@assistant-ui/ai-sdk", "ai@^7", "@ai-sdk/react@^4", "@ai-sdk/openai", "zod", "ink", "react"]} />
62
62
 
63
63
  </Tab>
64
64
  </PlatformTabs>
@@ -122,7 +122,7 @@ export async function POST(req: Request) {
122
122
 
123
123
  import { Thread } from "@/components/assistant-ui/thread";
124
124
  import { AssistantRuntimeProvider } from "@assistant-ui/react";
125
- import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
125
+ import { useChatRuntime } from "@assistant-ui/ai-sdk";
126
126
 
127
127
  export default function Home() {
128
128
  const runtime = useChatRuntime();
@@ -144,7 +144,7 @@ import { AssistantRuntimeProvider } from "@assistant-ui/react-native";
144
144
  import {
145
145
  AssistantChatTransport,
146
146
  useChatRuntime,
147
- } from "@assistant-ui/react-ai-sdk";
147
+ } from "@assistant-ui/ai-sdk";
148
148
  import { View } from "react-native";
149
149
  import { Thread } from "@/components/assistant-ui/thread";
150
150
 
@@ -174,7 +174,7 @@ import { AssistantRuntimeProvider } from "@assistant-ui/react-ink";
174
174
  import {
175
175
  AssistantChatTransport,
176
176
  useChatRuntime,
177
- } from "@assistant-ui/react-ai-sdk";
177
+ } from "@assistant-ui/ai-sdk";
178
178
  import { Box } from "ink";
179
179
  import { Thread } from "./components/thread.js";
180
180
 
@@ -231,7 +231,7 @@ Follow the [Ink setup](/docs/ink) to add a terminal Thread, composer, and suppor
231
231
  import { openai } from "@ai-sdk/openai";
232
232
  import { streamText, convertToModelMessages, zodSchema, createUIMessageStreamResponse, toUIMessageStream } from "ai";
233
233
  import type { UIMessage } from "ai";
234
- import { frontendTools } from "@assistant-ui/react-ai-sdk";
234
+ import { frontendTools } from "@assistant-ui/ai-sdk";
235
235
 
236
236
  export async function POST(req: Request) {
237
237
  const {
@@ -341,7 +341,7 @@ import { useChat } from "@ai-sdk/react";
341
341
  import {
342
342
  AssistantRuntimeProvider,
343
343
  useAISDKRuntime,
344
- } from "@assistant-ui/react-ai-sdk";
344
+ } from "@assistant-ui/ai-sdk";
345
345
  import { lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
346
346
  import { Thread } from "@/components/assistant-ui/thread";
347
347
 
@@ -372,7 +372,7 @@ import {
372
372
  defineToolkit,
373
373
  Tools,
374
374
  } from "@assistant-ui/react";
375
- import { useAISDKRuntime } from "@assistant-ui/react-ai-sdk";
375
+ import { useAISDKRuntime } from "@assistant-ui/ai-sdk";
376
376
  import { lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
377
377
  import { Thread } from "@/components/assistant-ui/thread";
378
378
 
@@ -432,7 +432,7 @@ import {
432
432
  createUIMessageStreamResponse,
433
433
  toUIMessageStream,
434
434
  } from "ai";
435
- import { injectQuoteContext } from "@assistant-ui/react-ai-sdk";
435
+ import { injectQuoteContext } from "@assistant-ui/ai-sdk";
436
436
  import { openai } from "@ai-sdk/openai";
437
437
 
438
438
  export async function POST(req: Request) {
@@ -455,7 +455,7 @@ export async function POST(req: Request) {
455
455
 
456
456
  ```ts title="@/app/api/chat/route.ts"
457
457
  import { streamText, convertToModelMessages, createUIMessageStreamResponse, toUIMessageStream } from "ai";
458
- import { frontendTools } from "@assistant-ui/react-ai-sdk";
458
+ import { frontendTools } from "@assistant-ui/ai-sdk";
459
459
 
460
460
  export async function POST(req: Request) {
461
461
  const { messages, tools, config } = await req.json();
@@ -484,7 +484,7 @@ export async function POST(req: Request) {
484
484
  ```tsx title="@/components/TokenCounter.tsx"
485
485
  "use client";
486
486
 
487
- import { useThreadTokenUsage } from "@assistant-ui/react-ai-sdk";
487
+ import { useThreadTokenUsage } from "@assistant-ui/ai-sdk";
488
488
 
489
489
  export function TokenCounter() {
490
490
  const usage = useThreadTokenUsage();
@@ -539,7 +539,7 @@ For server-side cloud persistence with zero adapter code, see the [AssistantClou
539
539
  ```tsx
540
540
  "use client";
541
541
 
542
- import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
542
+ import { useChatRuntime } from "@assistant-ui/ai-sdk";
543
543
  import type { ThreadHistoryAdapter } from "@assistant-ui/react";
544
544
 
545
545
  const historyAdapter: ThreadHistoryAdapter = {
@@ -585,7 +585,7 @@ Each persisted row follows `{ id, parent_id, format, content }`. `fmt.encode` pr
585
585
  import {
586
586
  useChatRuntime,
587
587
  AssistantChatTransport,
588
- } from "@assistant-ui/react-ai-sdk";
588
+ } from "@assistant-ui/ai-sdk";
589
589
 
590
590
  const runtime = useChatRuntime({
591
591
  transport: new AssistantChatTransport({ api: "/my-custom-api/chat" }),
@@ -644,13 +644,28 @@ const runtime = useChatRuntime({
644
644
 
645
645
  When omitted, `resumeRun` throws `"Runtime does not support resuming runs."`. For streams that should survive a page reload without a custom channel, use the built-in transport-level path instead: see [Resumable streams](/docs/guides/resumable-streams), which reconnects automatically on mount and reports failures via `onResumeError`.
646
646
 
647
+ ### messageRepository
648
+
649
+ Import a branch-aware AI SDK message tree once, and only when `useChat` is empty. After that seed, live updates come only from `useChat`. Clearing the chat or passing a new object identity does not reload the tree. Persisted threads that already go through `adapters.history` do not need this.
650
+
651
+ ```tsx
652
+ const runtime = useChatRuntime({
653
+ messageRepository: storedTree,
654
+ unstable_onBranchChange: ({ headId }) => {
655
+ saveSelectedBranch(headId);
656
+ },
657
+ });
658
+ ```
659
+
660
+ `unstable_onBranchChange` fires after an explicit `switchToBranch` (for example a BranchPicker click). It complements `setMessages` and does not enable switching by itself. The callback is unstable and may change without notice.
661
+
647
662
  ## Advanced: useAISDKRuntime
648
663
 
649
664
  When you need direct access to the `useChat` instance (e.g. to share it with non-assistant-ui code), drop down to `useAISDKRuntime`:
650
665
 
651
666
  ```tsx
652
667
  import { useChat } from "@ai-sdk/react";
653
- import { useAISDKRuntime } from "@assistant-ui/react-ai-sdk";
668
+ import { useAISDKRuntime } from "@assistant-ui/ai-sdk";
654
669
 
655
670
  const chat = useChat();
656
671
  const runtime = useAISDKRuntime(chat);
@@ -658,7 +673,7 @@ const runtime = useAISDKRuntime(chat);
658
673
 
659
674
  `useAISDKRuntime` does not provide `cloud` or the higher-level adapter slots; those are part of `useChatRuntime`. If you need both your own `useChat` access AND cloud thread support, you generally want `useChatRuntime` and to read the chat state through assistant-ui hooks instead.
660
675
 
661
- `useAISDKRuntime` accepts `joinStrategy` and `onResume` (see [Runtime options](#runtime-options)) plus one option of its own, `cancelPendingToolCallsOnSend`:
676
+ `useAISDKRuntime` accepts `joinStrategy`, `onResume`, `messageRepository`, and `unstable_onBranchChange` (see [Runtime options](#runtime-options)) plus one option of its own, `cancelPendingToolCallsOnSend`:
662
677
 
663
678
  ```tsx
664
679
  const runtime = useAISDKRuntime(chat, {
@@ -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).
@@ -21,7 +21,7 @@ const runtime = useChatRuntime({ adapters: { attachments, history } });
21
21
 
22
22
  ## Support matrix
23
23
 
24
- | Adapter | LocalRuntime | ExternalStoreRuntime | DataStream | AssistantTransport | react-ai-sdk | react-langgraph | react-langchain | react-google-adk | react-a2a | react-ag-ui | react-opencode |
24
+ | Adapter | LocalRuntime | ExternalStoreRuntime | DataStream | AssistantTransport | ai-sdk | react-langgraph | react-langchain | react-google-adk | react-a2a | react-ag-ui | react-opencode |
25
25
  | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
26
26
  | Attachments | Yes | Yes | Yes | Yes | Yes | (via thread state) | (via thread state) | Yes | Yes | Yes | (no) |
27
27
  | Speech | Yes | Yes | Yes | (no) | Yes | Yes | Yes | Yes | Yes | Yes | (no) |
@@ -173,7 +173,7 @@ const feedbackAdapter: FeedbackAdapter = {
173
173
 
174
174
  ## History adapter
175
175
 
176
- Per-thread message persistence. Used by `LocalRuntime` and adapters built on it (`react-ai-sdk`, `react-google-adk`, `react-a2a`, `useDataStreamRuntime`).
176
+ Per-thread message persistence. Used by `LocalRuntime` and adapters built on it (`ai-sdk`, `react-google-adk`, `react-a2a`, `useDataStreamRuntime`).
177
177
 
178
178
  `ExternalStoreRuntime` does not use a history adapter directly, since you already own the message array. Persist via your store instead. `react-langgraph` and `react-langchain` source persistence from server-side thread state, exposed through their `load` callbacks.
179
179
 
@@ -196,7 +196,7 @@ type ThreadHistoryAdapter = {
196
196
  `load` runs when a thread opens. `append` runs after each message completes.
197
197
 
198
198
  <Callout type="info">
199
- `react-ai-sdk` requires `withFormat` so messages round-trip as AI SDK `UIMessage` objects. An adapter without `withFormat` throws at runtime in the AI SDK path. See the [AI SDK history docs](/docs/runtimes/ai-sdk/v7) for the full pattern.
199
+ `ai-sdk` requires `withFormat` so messages round-trip as AI SDK `UIMessage` objects. An adapter without `withFormat` throws at runtime in the AI SDK path. See the [AI SDK history docs](/docs/runtimes/ai-sdk/v7) for the full pattern.
200
200
  </Callout>
201
201
 
202
202
  ## Suggestion adapter
@@ -10,7 +10,7 @@ assistant-ui exposes runtime integrations at three layers. Understanding which l
10
10
  <Flow.Root
11
11
  llm={`graph TD
12
12
  subgraph Framework["Framework adapters"]
13
- A1[react-ai-sdk]
13
+ A1[ai-sdk]
14
14
  A2[react-langgraph]
15
15
  A3[react-langchain]
16
16
  A4[react-google-adk]
@@ -58,7 +58,7 @@ assistant-ui exposes runtime integrations at three layers. Understanding which l
58
58
  <Flow.Group flowId="adapters">
59
59
  <Flow.GroupLabel>Framework adapters</Flow.GroupLabel>
60
60
  <Flow.Row className="flex-wrap justify-start">
61
- <Flow.Node>react-ai-sdk</Flow.Node>
61
+ <Flow.Node>ai-sdk</Flow.Node>
62
62
  <Flow.Node>react-langgraph</Flow.Node>
63
63
  <Flow.Node>react-langchain</Flow.Node>
64
64
  <Flow.Node>react-google-adk</Flow.Node>
@@ -118,7 +118,7 @@ The fastest path. Each adapter wraps one of the core or protocol layers and adds
118
118
 
119
119
  | Adapter | Layered on | Targets |
120
120
  | --- | --- | --- |
121
- | `react-ai-sdk` | `ExternalStoreRuntime` | Vercel AI SDK v7 (`useChat`) |
121
+ | `ai-sdk` | `ExternalStoreRuntime` | Vercel AI SDK v7 (`useChat`) |
122
122
  | `react-langgraph` | `ExternalStoreRuntime` | LangGraph Cloud via `@langchain/langgraph-sdk` |
123
123
  | `react-langchain` | `ExternalStoreRuntime` | LangGraph Cloud via `@langchain/react`'s `useStream` |
124
124
  | `react-google-adk` | `ExternalStoreRuntime` | Google ADK JS or Python agents |
@@ -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`. |
@@ -37,10 +37,11 @@ const cloud = new AssistantCloud({
37
37
  const runtime = useLocalRuntime(modelAdapter, { cloud });
38
38
  ```
39
39
 
40
- Framework adapters take `cloud` directly:
40
+ Framework adapters take `cloud` directly. `AISDKThreads({ cloud })` is the store-entry list for `AuiConfig` hosts. It mounts only the visible thread, so a switch cancels an in-flight run and that exchange is not persisted.
41
41
 
42
42
  ```tsx
43
43
  const runtime = useChatRuntime({ cloud });
44
+ const threads = AISDKThreads({ cloud });
44
45
  const runtime = useLangGraphRuntime({ cloud /* stream, load, ... */ });
45
46
  const runtime = useAdkRuntime({ cloud, stream });
46
47
  ```
@@ -49,9 +50,9 @@ See the [cloud documentation](/docs/cloud) for setup, auth, and self-host option
49
50
 
50
51
  ## RemoteThreadListRuntime (custom database)
51
52
 
52
- `useRemoteThreadListRuntime` lets you back the thread list with any database while keeping the per-thread runtime simple. You provide a `RemoteThreadListAdapter` describing how to list, create, rename, archive, and delete threads.
53
+ `useRemoteThreadListRuntime` lets you back the thread list with any database while keeping the per-thread runtime simple. You provide a `RemoteThreadListAdapter` describing how to list, create, rename, archive, and delete threads. Keep that adapter reference stable across renders (module scope or `useMemo`). Replacing it reloads the list and drops cached threads that are not in the replacement page.
53
54
 
54
- Works with any `LocalRuntime`-based runtime, including framework adapters that build on it (`react-ai-sdk`, `react-google-adk`, `react-a2a`, `useDataStreamRuntime`).
55
+ Works with any `LocalRuntime`-based runtime, including framework adapters that build on it (`ai-sdk`, `react-google-adk`, `react-a2a`, `useDataStreamRuntime`).
55
56
 
56
57
  ```tsx title="app/MyProvider.tsx"
57
58
  "use client";
@@ -130,44 +131,68 @@ export function MyProvider({ children }: { children: React.ReactNode }) {
130
131
  }
131
132
  ```
132
133
 
133
- ### Persisting messages with `unstable_Provider`
134
+ ### Persisting messages
134
135
 
135
- `RemoteThreadListAdapter` only manages thread metadata. To persist messages within each thread, expose a thread-scoped history adapter via the optional `unstable_Provider`:
136
+ `RemoteThreadListAdapter` only manages thread metadata. Per-thread history and attachments are a separate seam with two faces:
137
+
138
+ - `unstable_useAdapters` is a hook. The `RemoteThreadList` store entry calls it inside the client tree, so any `createAssistantClient` host gets the same adapters as a React hook host. `useRemoteThreadListRuntime` also calls it when `unstable_Provider` is omitted.
139
+ - `unstable_Provider` is a React component. `useRemoteThreadListRuntime` renders it when present. The store entry ignores it.
140
+
141
+ On the store entry, wrap the thread factory with `withKey` so the thread remounts on a switch. History adapters load once per mount. An unkeyed factory keeps one instance, and the next thread's messages never appear.
142
+
143
+ ```tsx
144
+ import { withKey } from "@assistant-ui/tap";
145
+ import { RemoteThreadList } from "@assistant-ui/react";
146
+
147
+ threads: RemoteThreadList({
148
+ adapter,
149
+ thread: (id) => withKey(id, MyThread({ threadId: id })),
150
+ }),
151
+ ```
152
+
153
+ Share one hook between both faces:
136
154
 
137
155
  ```tsx
138
156
  import {
139
157
  RuntimeAdapterProvider,
140
158
  useAui,
159
+ type RemoteThreadListAdapter,
141
160
  type ThreadHistoryAdapter,
142
161
  } from "@assistant-ui/react";
143
162
  import { useMemo } from "react";
144
163
 
164
+ function useThreadListAdapters() {
165
+ const aui = useAui();
166
+ const history = useMemo<ThreadHistoryAdapter>(
167
+ () => ({
168
+ async load() {
169
+ const { remoteId } = aui.threadListItem.getState();
170
+ if (!remoteId) return { messages: [] };
171
+ const rows = await fetch(
172
+ `/api/threads/${remoteId}/messages`,
173
+ ).then((r) => r.json());
174
+ return { messages: rows.map(toThreadMessage) };
175
+ },
176
+ async append({ message, parentId }) {
177
+ const { remoteId } = await aui.threadListItem.initialize();
178
+ await fetch(`/api/threads/${remoteId}/messages`, {
179
+ method: "POST",
180
+ body: JSON.stringify({ message, parentId }),
181
+ });
182
+ },
183
+ }),
184
+ [aui],
185
+ );
186
+ return useMemo(() => ({ history }), [history]);
187
+ }
188
+
145
189
  const adapterWithHistory: RemoteThreadListAdapter = {
146
190
  // ...metadata methods above...
191
+ unstable_useAdapters: useThreadListAdapters,
147
192
  unstable_Provider({ children }) {
148
- const aui = useAui();
149
- const history = useMemo<ThreadHistoryAdapter>(
150
- () => ({
151
- async load() {
152
- const { remoteId } = aui.threadListItem.getState();
153
- if (!remoteId) return { messages: [] };
154
- const rows = await fetch(
155
- `/api/threads/${remoteId}/messages`,
156
- ).then((r) => r.json());
157
- return { messages: rows.map(toThreadMessage) };
158
- },
159
- async append({ message, parentId }) {
160
- const { remoteId } = await aui.threadListItem.initialize();
161
- await fetch(`/api/threads/${remoteId}/messages`, {
162
- method: "POST",
163
- body: JSON.stringify({ message, parentId }),
164
- });
165
- },
166
- }),
167
- [aui],
168
- );
193
+ const adapters = useThreadListAdapters();
169
194
  return (
170
- <RuntimeAdapterProvider adapters={{ history }}>
195
+ <RuntimeAdapterProvider adapters={adapters}>
171
196
  {children}
172
197
  </RuntimeAdapterProvider>
173
198
  );
@@ -192,6 +217,15 @@ async append({ message, parentId }) {
192
217
 
193
218
  `initialize()` is safe to call multiple times. It always resolves to the same `remoteId` for the active thread.
194
219
 
220
+ The same rule applies to a custom external store's dispatch. The runtime does not hold `onNew` or `onEdit` until the thread record exists (that would keep the user's message off screen for the whole roundtrip), so a handler that talks to a backend keyed by the remote identity must await `initialize()` itself:
221
+
222
+ ```ts
223
+ onNew: async (message) => {
224
+ const { remoteId } = await aui.threadListItem.initialize();
225
+ await sendToBackend(remoteId, message);
226
+ },
227
+ ```
228
+
195
229
  ### Reloading after async authentication
196
230
 
197
231
  If your adapter depends on a user that resolves asynchronously (oidc, `next-auth`, `better-auth`), the initial `list()` may run before the user is available. Call `aui.threads.reload()` after auth completes:
@@ -207,11 +241,11 @@ function ReloadOnAuth() {
207
241
  }
208
242
  ```
209
243
 
210
- `reload()` discards in-flight responses from superseded calls, so it is safe to invoke on every auth transition.
244
+ `reload()` discards in-flight responses from superseded calls, so it is safe to invoke on every auth transition. On the `RemoteThreadList` store entry, a recreated adapter object is a no-op until you call `reload()`. `reload()` against the same instance refreshes the list and keeps the open thread. `reload()` after a different adapter instance resets selection and cached records, then loads the replacement store.
211
245
 
212
246
  ### Refetching the open thread
213
247
 
214
- `reload()` re-runs `list()`, which refreshes thread list metadata only. It does not touch the messages of the thread the user is looking at. When the open thread's server state changes out of band, so that nothing arrives over the stream (a human-in-the-loop interrupt raised by another process, a stalled stream, a status change picked up by polling), call `aui.threads.reloadMainThread()`:
248
+ `reload()` against the same adapter instance re-runs `list()`, which refreshes thread list metadata only. It does not touch the messages of the thread the user is looking at. When the open thread's server state changes out of band, so that nothing arrives over the stream (a human-in-the-loop interrupt raised by another process, a stalled stream, a status change picked up by polling), call `aui.threads.reloadMainThread()`:
215
249
 
216
250
  ```tsx
217
251
  function RefetchOnInterrupt({ status }: { status: string }) {
@@ -230,13 +264,13 @@ A thread that has not been sent yet is left alone because it holds no remote sta
230
264
 
231
265
  What happens to a run in progress depends on the path. The remount path drops the runtime that was rendering the run; whether the run itself stops is up to that hook's unmount cleanup, which core cannot enforce. On the in-place path the runtime that declared the capability decides, since core does not stop the run for it. Either way this belongs on an event rather than a short timer: drive it from a state change like the one above, or skip the call while `useAuiState((s) => s.thread.isRunning)` is true.
232
266
 
233
- How the refetch happens depends on the runtime, in one of three ways. When it declares the capability, the thread runtime is reused: composer drafts survive, existing messages stay rendered while the fresh state loads, and the returned promise settles with the refetch, rejecting if it fails. A remote thread list without the capability remounts the runtime hook instead, which re-runs `load()` at the cost of discarding unsent composer input, and resolves once the new runtime attaches. The single and in-memory thread lists hold no remote state and have no hook to remount, so there it resolves without doing anything.
267
+ How the refetch happens depends on the runtime, in one of three ways. When it declares the capability, the thread runtime is reused: composer drafts survive, existing messages stay rendered while the fresh state loads, and the returned promise settles with the refetch, rejecting if it fails. A remote thread list without the capability remounts the runtime hook instead, which re-runs `load()` at the cost of discarding unsent composer input, and resolves once the new runtime attaches. The single and in-memory thread lists have no hook to remount: they take the in-place path when their tap `ExternalThread` was given `onRefetchThread`, and resolve without doing anything when it was not.
234
268
 
235
269
  `useAuiState((s) => s.thread.capabilities.refetchThread)` reports which of those you would get, in place or not. It is not a signal for whether to offer a refresh at all: it is false on the remount path, where the call still does the work, and false again where the call does nothing.
236
270
 
237
- Both the LangGraph and Google ADK adapters register the in-place refetch capability when their runtime hook receives a `load` function; without one they fall back to the remount path. Other remote adapters take the remount path unless they provide the capability themselves.
271
+ Both the LangGraph and Google ADK adapters register the in-place refetch capability when their runtime hook receives a `load` function; without one they fall back to the remount path. The Eve adapter registers it when the installed `eve` exposes `resume()` (0.44.1 and later), with no option to pass; on older releases it falls back like any other adapter, which for the single and in-memory thread lists it is usually paired with means the call resolves without doing anything. A refetch that lands during a run also settles differently across the three: LangGraph and Google ADK issue the load right away, so the promise settles at fetch latency and each decides on arrival how much of the snapshot a racing run leaves standing, while Eve queues the replay behind the in-flight turn, so its promise settles only once that turn parks. Other remote adapters take the remount path unless they provide the capability themselves.
238
272
 
239
- For an external store runtime, declare it with `onRefetchThread`, which is unrelated to `onReload` (that one re-generates an assistant message):
273
+ For an external store runtime, declare it with `onRefetchThread`, which is unrelated to `onReload` (that one re-generates an assistant message); the tap `ExternalThread` accepts the same prop:
240
274
 
241
275
  ```ts
242
276
  useExternalStoreRuntime({
@@ -367,7 +401,13 @@ A few invariants worth knowing when wiring a custom UI on top of `loadMore()`:
367
401
  name: "unstable_Provider",
368
402
  type: "RemoteThreadListProviderComponent",
369
403
  description:
370
- "Optional wrapper rendered around each active thread. Inject thread-scoped adapters (history, attachments) here.",
404
+ "Optional React wrapper rendered around each active thread by useRemoteThreadListRuntime when present. Inject thread-scoped adapters here. Omit it to let that host use unstable_useAdapters.",
405
+ },
406
+ {
407
+ name: "unstable_useAdapters",
408
+ type: "() => RuntimeAdapters | null | undefined",
409
+ description:
410
+ "Optional hook called by the RemoteThreadList store entry, and by useRemoteThreadListRuntime when unstable_Provider is omitted. Per-thread history requires the thread factory to be keyed with withKey.",
371
411
  },
372
412
  ]}
373
413
  />
@@ -308,7 +308,7 @@ aui-state:[{"type":"set","path":["status"],"value":"completed"}]
308
308
  prepareSendCommandsRequest?: (body: SendCommandsRequestBody) => Record<string, unknown> | Promise<Record<string, unknown>>,
309
309
  capabilities?: { edit?: boolean },
310
310
  adapters?: { attachments?: AttachmentAdapter; history?: ThreadHistoryAdapter },
311
- onResponse?: (response: Response) => void,
311
+ onResponse?: (response: Response) => void | Promise<void>,
312
312
  onFinish?: () => void,
313
313
  onError?: (error: Error, params: { commands: AssistantTransportCommand[]; updateState: (updater: (state: T) => T) => void }) => void | Promise<void>,
314
314
  onCancel?: (params: { commands: AssistantTransportCommand[]; updateState: (updater: (state: T) => T) => void; error?: Error }) => void
@@ -421,13 +421,13 @@ useEffect(() => {
421
421
  }, [isRunning, queue]);
422
422
  ```
423
423
 
424
- Queue policy on cancel, edit, and reload is host-owned. Call `queue.notifyCancelled()` in your `onCancel` handler before aborting so the cancelled run's settle pauses draining instead of dispatching the next item (the next send or run start resumes it), or call `queue.clear()` to drop the pending items. Call `queue.clear()` in your `onEdit` and `onReload` handlers so stale items do not drain onto the new branch.
424
+ With a `createMessageQueue` adapter, cancelling pauses the queue for you: the runtime tells the queue before your `onCancel` runs, so the cancelled run's settle keeps the pending items instead of dispatching the next one, and the next send resumes draining. Call `queue.clear()` in `onCancel` instead if you want a cancel to drop them. A hand-rolled adapter has no such channel, so it owns its cancel policy the same way it owns the rest. Edit and reload stay host-owned: call `queue.clear()` in your `onEdit` and `onReload` handlers so stale items do not drain onto the new branch.
425
425
 
426
426
  ```tsx
427
427
  const runtime = useExternalStoreRuntime({
428
428
  // ...
429
429
  onCancel: async () => {
430
- queue.notifyCancelled(); // or queue.clear() to drop pending items
430
+ // the runtime already paused the queue; clear() here to drop the items
431
431
  await cancelRun();
432
432
  },
433
433
  onEdit: async (message) => {
@@ -447,6 +447,10 @@ const runtime = useExternalStoreRuntime({
447
447
 
448
448
  ## Integration examples
449
449
 
450
+ <Callout type="info">
451
+ **Real-world example:** the [Claude Managed Agents guide](/docs/runtimes/claude-managed-agents) maps Anthropic-hosted agent sessions onto this runtime, with Anthropic's official [quickstart](https://github.com/anthropics/claude-quickstarts/tree/main/managed-agents/assistant-ui) as the runnable reference.
452
+ </Callout>
453
+
450
454
  ### Redux
451
455
 
452
456
  ```tsx title="app/chatSlice.ts"
@@ -262,7 +262,7 @@ Follow the [Ink setup](/docs/ink).
262
262
 
263
263
  ### Forwarding per-run config
264
264
 
265
- When a message is sent, its `runConfig.custom` (for example a selected mode or model) is forwarded on the underlying `useStream().submit` call as `config.configurable`. Read it in the graph from `config["configurable"]`; on LangGraph v1 the same values are also reachable through the Runtime `context`, which `config.configurable` is aliased to. This lets per-run app config reach the graph without extra wiring.
265
+ When a message is sent, its `runConfig.custom` (for example a selected mode or model) is forwarded on the underlying `useStream().submit` call as `config.configurable`. Automatic tool-result resumes and the interrupt helpers (`useLangChainRespond`, `useLangChainRespondAll`, and `useLangChainSubmit(null, { command })`) reuse that same recorded `configurable` unless the caller passes `config`. Raw `useLangChainSubmit` / `useLangChainSend` calls that start a new run do not inherit it. The recording is session-scoped and does not survive a reload. Read it in the graph from `config["configurable"]`; on LangGraph v1 the same values are also reachable through the Runtime `context`, which `config.configurable` is aliased to. This lets per-run app config reach the graph without extra wiring.
266
266
 
267
267
  ## Reading custom state keys
268
268
 
@@ -744,8 +744,8 @@ Register the components with `makeAssistantDataUI`, keyed by the `UIMessage`'s `
744
744
  import {
745
745
  AssistantRuntimeProvider,
746
746
  makeAssistantDataUI,
747
- Thread,
748
747
  } from "@assistant-ui/react";
748
+ import { Thread } from "@/components/assistant-ui/thread";
749
749
  import { useStreamRuntime } from "@assistant-ui/react-langchain";
750
750
 
751
751
  const ChartUI = makeAssistantDataUI({
@@ -119,6 +119,10 @@ const runtime = useLangGraphRuntime({
119
119
 
120
120
  Render the pending messages with [`ComposerPrimitive.Queue`](/docs/api-reference/primitives/composer) and [`QueueItemPrimitive`](/docs/api-reference/primitives/queue-item).
121
121
 
122
+ ## Per-run config
123
+
124
+ When a message is sent, its `runConfig` is forwarded to `stream` and, if you use `unstable_createLangGraphStream`, posted as the LangGraph SDK run `config`. Automatic frontend tool-result resumes and `useLangGraphSendCommand` reuse the `runConfig` of the run that produced the pending tool call or interrupt. An explicit `runConfig` on `useLangGraphSend` still wins. A thread refetch that still carries the interrupt keeps that owner. History loaded without local ownership stays unconfigured on resume; the recording is session-scoped and does not survive a reload.
125
+
122
126
  ## Next
123
127
 
124
128
  <Cards>
@@ -41,7 +41,7 @@ export function MyRuntimeProvider({
41
41
  ### Render the Thread
42
42
 
43
43
  ```tsx title="app/page.tsx"
44
- import { Thread } from "@assistant-ui/react";
44
+ import { Thread } from "@/components/assistant-ui/thread";
45
45
  import { MyRuntimeProvider } from "./MyRuntimeProvider";
46
46
 
47
47
  export default function Page() {