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

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 (185) hide show
  1. package/.docs/organized/code-examples/waterfall.md +17 -18
  2. package/.docs/organized/code-examples/with-a2a.md +20 -15
  3. package/.docs/organized/code-examples/with-ag-ui.md +19 -17
  4. package/.docs/organized/code-examples/with-ai-sdk-v7.md +17 -16
  5. package/.docs/organized/code-examples/with-artifacts.md +471 -145
  6. package/.docs/organized/code-examples/with-assistant-transport.md +23 -31
  7. package/.docs/organized/code-examples/with-browser-extension.md +21 -14
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +22 -20
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +13 -14
  10. package/.docs/organized/code-examples/with-cloud.md +22 -17
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +17 -16
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +17 -17
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +21 -19
  14. package/.docs/organized/code-examples/with-eve.md +70 -16
  15. package/.docs/organized/code-examples/with-expo.md +32 -51
  16. package/.docs/organized/code-examples/with-external-store.md +20 -15
  17. package/.docs/organized/code-examples/with-ffmpeg.md +24 -18
  18. package/.docs/organized/code-examples/with-generative-ui.md +20 -21
  19. package/.docs/organized/code-examples/with-google-adk.md +21 -16
  20. package/.docs/organized/code-examples/with-heat-graph.md +10 -11
  21. package/.docs/organized/code-examples/with-image-generation.md +13 -14
  22. package/.docs/organized/code-examples/with-interactables.md +17 -16
  23. package/.docs/organized/code-examples/with-langchain.md +13 -14
  24. package/.docs/organized/code-examples/with-langgraph.md +23 -17
  25. package/.docs/organized/code-examples/with-livekit.md +17 -17
  26. package/.docs/organized/code-examples/with-mcp.md +36 -32
  27. package/.docs/organized/code-examples/with-nuxt.md +2492 -0
  28. package/.docs/organized/code-examples/with-opencode.md +27 -21
  29. package/.docs/organized/code-examples/with-pi.md +69 -67
  30. package/.docs/organized/code-examples/with-react-hook-form.md +18 -17
  31. package/.docs/organized/code-examples/with-react-ink-web.md +10 -11
  32. package/.docs/organized/code-examples/with-react-ink.md +7 -7
  33. package/.docs/organized/code-examples/with-react-router.md +23 -17
  34. package/.docs/organized/code-examples/with-resumable-stream.md +15 -16
  35. package/.docs/organized/code-examples/with-store.md +31 -20
  36. package/.docs/organized/code-examples/with-tanstack.md +22 -16
  37. package/.docs/organized/code-examples/with-tap-runtime.md +19 -19
  38. package/.docs/organized/code-examples/with-virtualized-thread.md +12 -13
  39. package/.docs/organized/code-examples/with-vue.md +408 -0
  40. package/.docs/raw/docs/(docs)/cli.mdx +3 -1
  41. package/.docs/raw/docs/(docs)/devtools.mdx +7 -2
  42. package/.docs/raw/docs/(docs)/index.mdx +9 -76
  43. package/.docs/raw/docs/(docs)/installation.mdx +4 -18
  44. package/.docs/raw/docs/(reference)/api-reference/context-providers/assistant-runtime-provider.mdx +24 -4
  45. package/.docs/raw/docs/(reference)/api-reference/generative-ui/a2ui.mdx +40 -0
  46. package/.docs/raw/docs/(reference)/api-reference/generative-ui/index.mdx +3 -0
  47. package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +19 -420
  48. package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +4 -1
  49. package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +8 -0
  50. package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +21 -0
  51. package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -9
  52. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +1 -18
  53. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +24 -1
  54. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +1 -1
  55. package/.docs/raw/docs/cloud/ai-sdk.mdx +2 -2
  56. package/.docs/raw/docs/cloud/langgraph.mdx +1 -1
  57. package/.docs/raw/docs/copilots/model-context.mdx +5 -4
  58. package/.docs/raw/docs/copilots/motivation.mdx +5 -5
  59. package/.docs/raw/docs/guides/attachments.mdx +3 -3
  60. package/.docs/raw/docs/guides/branching.mdx +2 -2
  61. package/.docs/raw/docs/guides/chain-of-thought.mdx +1 -1
  62. package/.docs/raw/docs/guides/context-api.mdx +89 -111
  63. package/.docs/raw/docs/guides/editing.mdx +5 -5
  64. package/.docs/raw/docs/guides/electron.mdx +369 -0
  65. package/.docs/raw/docs/guides/index.mdx +10 -0
  66. package/.docs/raw/docs/guides/quoting.mdx +3 -3
  67. package/.docs/raw/docs/guides/resumable-stream-deployment.mdx +2 -2
  68. package/.docs/raw/docs/guides/resumable-streams.mdx +74 -3
  69. package/.docs/raw/docs/guides/suggestions.mdx +9 -9
  70. package/.docs/raw/docs/ink/hooks.mdx +12 -7
  71. package/.docs/raw/docs/ink/primitives.mdx +18 -10
  72. package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +16 -0
  73. package/.docs/raw/docs/integrations/auth/better-auth.mdx +3 -3
  74. package/.docs/raw/docs/integrations/auth/clerk.mdx +3 -3
  75. package/.docs/raw/docs/integrations/auth/next-auth.mdx +4 -4
  76. package/.docs/raw/docs/integrations/frameworks/cloudflare-agents/overview.mdx +1 -1
  77. package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +2 -2
  78. package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +1 -1
  79. package/.docs/raw/docs/integrations/gateways/index.mdx +2 -2
  80. package/.docs/raw/docs/integrations/observability/helicone.mdx +2 -2
  81. package/.docs/raw/docs/integrations/observability/langfuse.mdx +1 -1
  82. package/.docs/raw/docs/integrations/observability/langsmith.mdx +3 -3
  83. package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +36 -9
  84. package/.docs/raw/docs/migrations/toolkit-tools.mdx +15 -13
  85. package/.docs/raw/docs/migrations/v0-15.mdx +236 -0
  86. package/.docs/raw/docs/primitives/attachment.mdx +2 -2
  87. package/.docs/raw/docs/primitives/composer.mdx +3 -3
  88. package/.docs/raw/docs/primitives/message.mdx +33 -1
  89. package/.docs/raw/docs/primitives/suggestion.mdx +1 -1
  90. package/.docs/raw/docs/primitives/thread-list.mdx +2 -2
  91. package/.docs/raw/docs/react-native/hooks.mdx +17 -7
  92. package/.docs/raw/docs/react-native/index.mdx +3 -3
  93. package/.docs/raw/docs/react-native/primitives.mdx +41 -7
  94. package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +3 -3
  95. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +5 -5
  96. package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +5 -5
  97. package/.docs/raw/docs/runtimes/ai-sdk/v6-legacy.mdx +13 -14
  98. package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +11 -12
  99. package/.docs/raw/docs/runtimes/concepts/threads.mdx +50 -11
  100. package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +20 -2
  101. package/.docs/raw/docs/runtimes/custom/external-store.mdx +31 -2
  102. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +39 -12
  103. package/.docs/raw/docs/runtimes/eve/overview.mdx +51 -0
  104. package/.docs/raw/docs/runtimes/eve/quickstart.mdx +51 -2
  105. package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +9 -12
  106. package/.docs/raw/docs/runtimes/langchain.mdx +2 -2
  107. package/.docs/raw/docs/runtimes/langgraph/overview.mdx +1 -1
  108. package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +1 -1
  109. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +7 -7
  110. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +4 -4
  111. package/.docs/raw/docs/runtimes/opencode/hooks.mdx +4 -1
  112. package/.docs/raw/docs/runtimes/opencode/overview.mdx +10 -0
  113. package/.docs/raw/docs/tools/a2ui.mdx +107 -0
  114. package/.docs/raw/docs/tools/backend.mdx +2 -2
  115. package/.docs/raw/docs/tools/defining-tools.mdx +9 -7
  116. package/.docs/raw/docs/tools/dynamic-tools.mdx +6 -4
  117. package/.docs/raw/docs/tools/interactables-legacy.mdx +28 -15
  118. package/.docs/raw/docs/tools/interactables.mdx +27 -16
  119. package/.docs/raw/docs/tools/mcp-apps.mdx +96 -8
  120. package/.docs/raw/docs/tools/mcp.mdx +9 -7
  121. package/.docs/raw/docs/tools/tool-ui.mdx +26 -22
  122. package/.docs/raw/docs/tools/user-managed-mcp.mdx +64 -14
  123. package/.docs/raw/docs/ui/file.mdx +6 -1
  124. package/.docs/raw/docs/ui/follow-up-suggestions.mdx +2 -0
  125. package/.docs/raw/docs/ui/mcp-config.mdx +8 -3
  126. package/.docs/raw/docs/ui/model-selector.mdx +9 -9
  127. package/.docs/raw/docs/ui/part-grouping.mdx +1 -5
  128. package/.docs/raw/docs/ui/reasoning.mdx +1 -1
  129. package/.docs/raw/docs/ui/thread.mdx +24 -5
  130. package/.docs/raw/docs/utilities/react-o11y.mdx +7 -9
  131. package/dist/constants.js +2 -2
  132. package/dist/constants.js.map +1 -1
  133. package/dist/index.d.ts +1 -1
  134. package/dist/index.js +2 -2
  135. package/dist/index.js.map +1 -1
  136. package/dist/prepare-docs/code-examples.js.map +1 -1
  137. package/dist/prepare-docs/prepare.d.ts +1 -1
  138. package/dist/prepare-docs/prepare.js.map +1 -1
  139. package/dist/stdio.d.ts +1 -1
  140. package/dist/tools/docs.d.ts +6 -10
  141. package/dist/tools/docs.d.ts.map +1 -1
  142. package/dist/tools/docs.js +6 -4
  143. package/dist/tools/docs.js.map +1 -1
  144. package/dist/tools/examples.d.ts +4 -8
  145. package/dist/tools/examples.d.ts.map +1 -1
  146. package/dist/tools/examples.js +4 -3
  147. package/dist/tools/examples.js.map +1 -1
  148. package/dist/tools/resources.d.ts +1 -1
  149. package/dist/tools/resources.d.ts.map +1 -1
  150. package/dist/tools/resources.js +3 -2
  151. package/dist/tools/resources.js.map +1 -1
  152. package/dist/tools/search.d.ts +4 -10
  153. package/dist/tools/search.d.ts.map +1 -1
  154. package/dist/tools/search.js +2 -2
  155. package/dist/tools/search.js.map +1 -1
  156. package/dist/tools/tests/mcp-test-client.d.ts +15 -0
  157. package/dist/tools/tests/mcp-test-client.d.ts.map +1 -0
  158. package/dist/tools/tests/mcp-test-client.js +68 -0
  159. package/dist/tools/tests/mcp-test-client.js.map +1 -0
  160. package/dist/tools/tests/test-setup.d.ts.map +1 -1
  161. package/dist/tools/tests/test-setup.js +2 -1
  162. package/dist/tools/tests/test-setup.js.map +1 -1
  163. package/dist/tools/xulux-templates.d.ts +9 -23
  164. package/dist/tools/xulux-templates.d.ts.map +1 -1
  165. package/dist/tools/xulux-templates.js +8 -6
  166. package/dist/tools/xulux-templates.js.map +1 -1
  167. package/dist/utils/logger.d.ts.map +1 -1
  168. package/dist/utils/mdx.js +2 -1
  169. package/dist/utils/mdx.js.map +1 -1
  170. package/dist/utils/security.js.map +1 -1
  171. package/dist/xulux/catalog-client.js +1 -1
  172. package/dist/xulux/catalog-client.js.map +1 -1
  173. package/package.json +6 -5
  174. package/src/index.ts +2 -2
  175. package/src/tools/docs.ts +2 -2
  176. package/src/tools/examples.ts +2 -2
  177. package/src/tools/resources.ts +1 -4
  178. package/src/tools/search.ts +2 -2
  179. package/src/tools/tests/completions.test.ts +40 -26
  180. package/src/tools/tests/docs.test.ts +2 -2
  181. package/src/tools/tests/integration.test.ts +3 -4
  182. package/src/tools/tests/mcp-protocol.test.ts +160 -175
  183. package/src/tools/tests/mcp-test-client.ts +111 -0
  184. package/src/tools/tests/resources.test.ts +97 -66
  185. package/src/tools/xulux-templates.ts +4 -4
@@ -4,7 +4,7 @@ description: Reference for projects still on AI SDK v6. New projects should use
4
4
  ---
5
5
 
6
6
  <Callout type="warn">
7
- AI SDK v6 is a legacy version. New projects should use [AI SDK v7](/docs/runtimes/ai-sdk/v7). v6 users stay on `@assistant-ui/react-ai-sdk@1.x`; the latest line targets v7+.
7
+ AI SDK v6 is a legacy version. New projects should use [AI SDK v7](/docs/runtimes/ai-sdk/v7). v6 users pin `@assistant-ui/react-ai-sdk@1.3.40` (the last v6-compatible release); later releases target v7.
8
8
  </Callout>
9
9
 
10
10
  Requires `ai@^6` and `@ai-sdk/react@^3`. For other versions see the [overview](/docs/runtimes/ai-sdk/overview).
@@ -52,17 +52,17 @@ npm init -y
52
52
  <PlatformTabs>
53
53
  <Tab value="React">
54
54
 
55
- <InstallCommand npm={["@assistant-ui/react", "@assistant-ui/react-ai-sdk", "ai@^6", "@ai-sdk/react@^3", "@ai-sdk/openai", "zod"]} />
55
+ <InstallCommand npm={["@assistant-ui/react", "@assistant-ui/react-ai-sdk@1.3.40", "ai@^6", "@ai-sdk/react@^3", "@ai-sdk/openai", "zod"]} />
56
56
 
57
57
  </Tab>
58
58
  <Tab value="React Native">
59
59
 
60
- <InstallCommand expo={["@assistant-ui/react-native", "@assistant-ui/react-ai-sdk", "ai@^6", "@ai-sdk/react@^3", "@ai-sdk/openai", "zod"]} />
60
+ <InstallCommand expo={["@assistant-ui/react-native", "@assistant-ui/react-ai-sdk@1.3.40", "ai@^6", "@ai-sdk/react@^3", "@ai-sdk/openai", "zod"]} />
61
61
 
62
62
  </Tab>
63
63
  <Tab value="React Ink">
64
64
 
65
- <InstallCommand npm={["@assistant-ui/react-ink", "@assistant-ui/react-ai-sdk", "ai@^6", "@ai-sdk/react@^3", "@ai-sdk/openai", "zod", "ink", "react"]} />
65
+ <InstallCommand npm={["@assistant-ui/react-ink", "@assistant-ui/react-ai-sdk@1.3.40", "ai@^6", "@ai-sdk/react@^3", "@ai-sdk/openai", "zod", "ink", "react"]} />
66
66
 
67
67
  </Tab>
68
68
  </PlatformTabs>
@@ -92,7 +92,7 @@ export async function POST(req: Request) {
92
92
  const { messages }: { messages: UIMessage[] } = await req.json();
93
93
 
94
94
  const result = streamText({
95
- model: openai("gpt-5.4-mini"),
95
+ model: openai("gpt-5.6-luna"),
96
96
  messages: await convertToModelMessages(messages), // async in v6
97
97
  tools: {
98
98
  get_current_weather: tool({
@@ -245,7 +245,7 @@ export async function POST(req: Request) {
245
245
  } = await req.json();
246
246
 
247
247
  const result = streamText({
248
- model: openai("gpt-5.4-mini"),
248
+ model: openai("gpt-5.6-luna"),
249
249
  system,
250
250
  messages: await convertToModelMessages(messages),
251
251
  tools: {
@@ -258,7 +258,7 @@ export async function POST(req: Request) {
258
258
  }
259
259
  ```
260
260
 
261
- Frontend tools are registered through `useAui` (see the [tools guide](/docs/tools/defining-tools)) and serialized for the backend via `frontendTools`.
261
+ Frontend tools are registered through the provider's `config` (see the [tools guide](/docs/tools/defining-tools)) and serialized for the backend via `frontendTools`.
262
262
 
263
263
  ## Multi-step tool calls
264
264
 
@@ -276,7 +276,7 @@ export async function POST(req: Request) {
276
276
  const { messages } = await req.json();
277
277
 
278
278
  const result = streamText({
279
- model: openai("gpt-5.4-mini"),
279
+ model: openai("gpt-5.6-luna"),
280
280
  messages: await convertToModelMessages(messages),
281
281
  tools: {
282
282
  /* ... */
@@ -305,7 +305,7 @@ export async function POST(req: Request) {
305
305
  const { messages } = await req.json();
306
306
 
307
307
  const result = streamText({
308
- model: openai("gpt-5.4-mini"),
308
+ model: openai("gpt-5.6-luna"),
309
309
  messages: await convertToModelMessages(messages),
310
310
  tools: {
311
311
  deploy: tool({
@@ -356,9 +356,9 @@ Render the gate with a toolkit entry. The `approval` field carries the gate stat
356
356
  import { useChat } from "@ai-sdk/react";
357
357
  import {
358
358
  AssistantRuntimeProvider,
359
+ AuiConfig,
359
360
  defineToolkit,
360
361
  Tools,
361
- useAui,
362
362
  } from "@assistant-ui/react";
363
363
  import { useAISDKRuntime } from "@assistant-ui/react-ai-sdk";
364
364
  import { lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
@@ -397,10 +397,9 @@ export default function Page() {
397
397
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
398
398
  });
399
399
  const runtime = useAISDKRuntime(chat);
400
- const aui = useAui({ tools: Tools({ toolkit }) });
401
-
400
+ const config = AuiConfig({ tools: Tools({ toolkit }) });
402
401
  return (
403
- <AssistantRuntimeProvider aui={aui} runtime={runtime}>
402
+ <AssistantRuntimeProvider runtime={runtime} config={config}>
404
403
  <Thread />
405
404
  </AssistantRuntimeProvider>
406
405
  );
@@ -424,7 +423,7 @@ import { openai } from "@ai-sdk/openai";
424
423
  export async function POST(req: Request) {
425
424
  const { messages } = await req.json();
426
425
  const result = streamText({
427
- model: openai("gpt-5.4-mini"),
426
+ model: openai("gpt-5.6-luna"),
428
427
  messages: await convertToModelMessages(injectQuoteContext(messages)),
429
428
  });
430
429
  return result.toUIMessageStreamResponse();
@@ -3,7 +3,7 @@ title: AI SDK v7
3
3
  description: Integrate Vercel AI SDK v7 with assistant-ui for streaming chat.
4
4
  ---
5
5
 
6
- Current-version integration. Requires `ai@^7` and `@ai-sdk/react@^4`. For older versions see the [overview](/docs/runtimes/ai-sdk).
6
+ Current-version integration. Requires `ai@^7` and `@ai-sdk/react@^4`. For older versions see the [overview](/docs/runtimes/ai-sdk/overview).
7
7
 
8
8
  ## Quickstart
9
9
 
@@ -90,7 +90,7 @@ export async function POST(req: Request) {
90
90
  const { messages }: { messages: UIMessage[] } = await req.json();
91
91
 
92
92
  const result = streamText({
93
- model: openai("gpt-5.4-mini"),
93
+ model: openai("gpt-5.6-luna"),
94
94
  messages: await convertToModelMessages(messages), // async in v7
95
95
  tools: {
96
96
  get_current_weather: tool({
@@ -245,7 +245,7 @@ export async function POST(req: Request) {
245
245
  } = await req.json();
246
246
 
247
247
  const result = streamText({
248
- model: openai("gpt-5.4-mini"),
248
+ model: openai("gpt-5.6-luna"),
249
249
  system,
250
250
  messages: await convertToModelMessages(messages),
251
251
  tools: {
@@ -260,7 +260,7 @@ export async function POST(req: Request) {
260
260
  }
261
261
  ```
262
262
 
263
- Frontend tools are registered through `useAui` (see the [tools guide](/docs/tools/defining-tools)) and serialized for the backend via `frontendTools`.
263
+ Frontend tools are registered through the provider's `config` (see the [tools guide](/docs/tools/defining-tools)) and serialized for the backend via `frontendTools`.
264
264
 
265
265
  ## Multi-step tool calls
266
266
 
@@ -280,7 +280,7 @@ export async function POST(req: Request) {
280
280
  const { messages } = await req.json();
281
281
 
282
282
  const result = streamText({
283
- model: openai("gpt-5.4-mini"),
283
+ model: openai("gpt-5.6-luna"),
284
284
  messages: await convertToModelMessages(messages),
285
285
  tools: {
286
286
  /* ... */
@@ -311,7 +311,7 @@ export async function POST(req: Request) {
311
311
  const { messages } = await req.json();
312
312
 
313
313
  const result = streamText({
314
- model: openai("gpt-5.4-mini"),
314
+ model: openai("gpt-5.6-luna"),
315
315
  messages: await convertToModelMessages(messages),
316
316
  tools: {
317
317
  deploy: tool({
@@ -368,9 +368,9 @@ Render the gate with a toolkit entry. The `approval` field carries the gate stat
368
368
  import { useChat } from "@ai-sdk/react";
369
369
  import {
370
370
  AssistantRuntimeProvider,
371
+ AuiConfig,
371
372
  defineToolkit,
372
373
  Tools,
373
- useAui,
374
374
  } from "@assistant-ui/react";
375
375
  import { useAISDKRuntime } from "@assistant-ui/react-ai-sdk";
376
376
  import { lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
@@ -410,10 +410,9 @@ export default function Page() {
410
410
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
411
411
  });
412
412
  const runtime = useAISDKRuntime(chat);
413
- const aui = useAui({ tools: Tools({ toolkit }) });
414
-
413
+ const config = AuiConfig({ tools: Tools({ toolkit }) });
415
414
  return (
416
- <AssistantRuntimeProvider aui={aui} runtime={runtime}>
415
+ <AssistantRuntimeProvider runtime={runtime} config={config}>
417
416
  <Thread />
418
417
  </AssistantRuntimeProvider>
419
418
  );
@@ -439,7 +438,7 @@ import { openai } from "@ai-sdk/openai";
439
438
  export async function POST(req: Request) {
440
439
  const { messages } = await req.json();
441
440
  const result = streamText({
442
- model: openai("gpt-5.4-mini"),
441
+ model: openai("gpt-5.6-luna"),
443
442
  messages: await convertToModelMessages(injectQuoteContext(messages)),
444
443
  });
445
444
  return createUIMessageStreamResponse({
@@ -601,7 +600,7 @@ Beyond transports and adapters, `useChatRuntime` accepts options that control th
601
600
 
602
601
  ### onThreadIdChange
603
602
 
604
- Called whenever the active thread's settled ID changes, e.g. to sync the active thread to a URL query param:
603
+ Called whenever the runtime changes the active thread's settled ID, e.g. to sync the active thread to a URL query param. Changes supplied through the controlled `threadId` option are not echoed back through this callback.
605
604
 
606
605
  ```tsx
607
606
  const runtime = useChatRuntime({
@@ -149,7 +149,7 @@ const adapterWithHistory: RemoteThreadListAdapter = {
149
149
  const history = useMemo<ThreadHistoryAdapter>(
150
150
  () => ({
151
151
  async load() {
152
- const { remoteId } = aui.threadListItem().getState();
152
+ const { remoteId } = aui.threadListItem.getState();
153
153
  if (!remoteId) return { messages: [] };
154
154
  const rows = await fetch(
155
155
  `/api/threads/${remoteId}/messages`,
@@ -157,7 +157,7 @@ const adapterWithHistory: RemoteThreadListAdapter = {
157
157
  return { messages: rows.map(toThreadMessage) };
158
158
  },
159
159
  async append({ message, parentId }) {
160
- const { remoteId } = await aui.threadListItem().initialize();
160
+ const { remoteId } = await aui.threadListItem.initialize();
161
161
  await fetch(`/api/threads/${remoteId}/messages`, {
162
162
  method: "POST",
163
163
  body: JSON.stringify({ message, parentId }),
@@ -181,11 +181,11 @@ const adapterWithHistory: RemoteThreadListAdapter = {
181
181
 
182
182
  ### Avoiding the first-message race
183
183
 
184
- `append` may be called before the thread record exists in your backend. Always await `aui.threadListItem().initialize()` before writing:
184
+ `append` may be called before the thread record exists in your backend. Always await `aui.threadListItem.initialize()` before writing:
185
185
 
186
186
  ```ts
187
187
  async append({ message, parentId }) {
188
- const { remoteId } = await aui.threadListItem().initialize();
188
+ const { remoteId } = await aui.threadListItem.initialize();
189
189
  await saveMessage(remoteId, parentId, message);
190
190
  }
191
191
  ```
@@ -194,14 +194,14 @@ async append({ message, parentId }) {
194
194
 
195
195
  ### Reloading after async authentication
196
196
 
197
- 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:
197
+ 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:
198
198
 
199
199
  ```tsx
200
200
  function ReloadOnAuth() {
201
201
  const aui = useAui();
202
202
  const { isLoading, user } = useAuth();
203
203
  useEffect(() => {
204
- if (!isLoading && user) aui.threads().reload();
204
+ if (!isLoading && user) aui.threads.reload();
205
205
  }, [isLoading, user?.id]);
206
206
  return null;
207
207
  }
@@ -209,9 +209,48 @@ function ReloadOnAuth() {
209
209
 
210
210
  `reload()` discards in-flight responses from superseded calls, so it is safe to invoke on every auth transition.
211
211
 
212
+ ### Refetching the open thread
213
+
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()`:
215
+
216
+ ```tsx
217
+ function RefetchOnInterrupt({ status }: { status: string }) {
218
+ const aui = useAui();
219
+ useEffect(() => {
220
+ if (status !== "interrupted") return;
221
+ aui.threads.reloadMainThread().catch(reportError);
222
+ }, [status]);
223
+ return null;
224
+ }
225
+ ```
226
+
227
+ What the promise means depends on the path. On the remount path, it resolves as soon as the replacement runtime attaches, which happens before that runtime's `load()` has started, so awaiting it does not mean the thread is fresh and a failed load cannot reach the caller. On the in-place path, it settles with the refetch itself and rejects when it fails, which is why the example above still handles the rejection.
228
+
229
+ A thread that has not been sent yet is left alone because it holds no remote state.
230
+
231
+ 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
+
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.
234
+
235
+ `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
+
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.
238
+
239
+ For an external store runtime, declare it with `onRefetchThread`, which is unrelated to `onReload` (that one re-generates an assistant message):
240
+
241
+ ```ts
242
+ useExternalStoreRuntime({
243
+ messages,
244
+ onNew,
245
+ onRefetchThread: async () => {
246
+ setMessages(await fetchMessages(threadId));
247
+ },
248
+ });
249
+ ```
250
+
212
251
  ### Paginating the thread list
213
252
 
214
- If your backend returns thread pages, return a `nextCursor` from `list()` and consume `aui.threads().hasMore` plus `aui.threads().loadMore()` in the UI. The runtime threads `params.after` back through `list()` on every `loadMore()`; the initial call passes no `params`, so treat a missing `after` as "first page". `reload()` resets the cursor so the next load starts from page 1 again.
253
+ If your backend returns thread pages, return a `nextCursor` from `list()` and consume `aui.threads.hasMore` plus `aui.threads.loadMore()` in the UI. The runtime threads `params.after` back through `list()` on every `loadMore()`; the initial call passes no `params`, so treat a missing `after` as "first page". `reload()` resets the cursor so the next load starts from page 1 again.
215
254
 
216
255
  ```ts title="threadListAdapter.ts (excerpt)"
217
256
  async list({ after } = {}) {
@@ -271,7 +310,7 @@ A few invariants worth knowing when wiring a custom UI on top of `loadMore()`:
271
310
  name: "list",
272
311
  type: "(params?: { after?: string }) => Promise<{ threads: RemoteThreadMetadata[]; nextCursor?: string }>",
273
312
  description:
274
- "Hydrate threads on mount. Each thread must include status and remoteId; title, externalId, and custom are optional. Return a `nextCursor` to enable `aui.threads().loadMore()`; the runtime will pass it back as `params.after` on the next call.",
313
+ "Hydrate threads on mount. Each thread must include status and remoteId; title, externalId, and custom are optional. Return a `nextCursor` to enable `aui.threads.loadMore()`; the runtime will pass it back as `params.after` on the next call.",
275
314
  required: true,
276
315
  },
277
316
  {
@@ -291,7 +330,7 @@ A few invariants worth knowing when wiring a custom UI on top of `loadMore()`:
291
330
  name: "updateCustom",
292
331
  type: "(remoteId: string, custom: Record<string, unknown> | undefined) => Promise<void>",
293
332
  description:
294
- "Optional. Persist replacement custom metadata from `aui.threadListItem().updateCustom(custom)`.",
333
+ "Optional. Persist replacement custom metadata from `aui.threadListItem.updateCustom(custom)`.",
295
334
  },
296
335
  {
297
336
  name: "archive",
@@ -326,7 +365,7 @@ A few invariants worth knowing when wiring a custom UI on top of `loadMore()`:
326
365
  },
327
366
  {
328
367
  name: "unstable_Provider",
329
- type: "ComponentType<PropsWithChildren>",
368
+ type: "RemoteThreadListProviderComponent",
330
369
  description:
331
370
  "Optional wrapper rendered around each active thread. Inject thread-scoped adapters (history, attachments) here.",
332
371
  },
@@ -361,7 +400,7 @@ function ThreadListItemMeta() {
361
400
  }
362
401
  ```
363
402
 
364
- `custom` is preserved across `rename`, `archive`, `unarchive`, and `generateTitle`. To replace it from your UI, implement `RemoteThreadListAdapter.updateCustom` and call `aui.threadListItem().updateCustom(custom)`. The cloud adapter persists this through `cloud.threads.update(threadId, { metadata })`. If your adapter mutates thread metadata through a separate application path, return the updated values from `fetch()` or call `aui.threads().reload()` to re-run `list()`.
403
+ `custom` is preserved across `rename`, `archive`, `unarchive`, and `generateTitle`. To replace it from your UI, implement `RemoteThreadListAdapter.updateCustom` and call `aui.threadListItem.updateCustom(custom)`. The cloud adapter persists this through `cloud.threads.update(threadId, { metadata })`. If your adapter mutates thread metadata through a separate application path, return the updated values from `fetch()` or call `aui.threads.reload()` to re-run `list()`.
365
404
 
366
405
  ## ExternalStoreThreadListAdapter
367
406
 
@@ -300,6 +300,7 @@ aui-state:[{"type":"set","path":["status"],"value":"completed"}]
300
300
  initialState: T,
301
301
  api: string,
302
302
  resumeApi?: string,
303
+ resumeStateApi?: string,
303
304
  protocol?: "data-stream" | "assistant-transport",
304
305
  converter: (state: T, connectionMetadata: ConnectionMetadata) => AssistantTransportState,
305
306
  headers?: Record<string, string> | Headers | (() => Promise<Record<string, string> | Headers>),
@@ -519,6 +520,23 @@ const runtime = useAssistantTransportRuntime({
519
520
  });
520
521
  ```
521
522
 
523
+ Setting `resumeStateApi` additionally hydrates resumed runs from the server-retained starting state. It requires a sync server that serves the initial-state route; do not configure it against a server without one, since a failing preflight fails the resume closed:
524
+
525
+ ```tsx
526
+ const runtime = useAssistantTransportRuntime({
527
+ // ...
528
+ resumeStateApi: "http://localhost:8010/initial-state",
529
+ });
530
+ ```
531
+
532
+ Before resuming, the runtime posts `{ threadId }` to `resumeStateApi`. The endpoint must return the state that started the active run together with its identity:
533
+
534
+ ```json
535
+ { "runId": "8b3a...", "state": { "messages": [] } }
536
+ ```
537
+
538
+ The runtime replaces its local base with this snapshot and includes `runId` in the resume request; the request carries no `state`, since the server replays from the snapshot it retained. A sync server should reject the resume when that ID no longer identifies the same run, preventing replay operations from being applied to a drifted or replaced base state. When no run is active, the endpoint responds `204 No Content` and the runtime skips the resume without raising an error. On a resume, `runId` takes precedence over fields supplied through `body` and survives `prepareSendCommandsRequest`; any `state` either of them supplies is stripped.
539
+
522
540
  ```tsx
523
541
  import { useAui } from "@assistant-ui/react";
524
542
  import { useEffect, useRef } from "react";
@@ -536,8 +554,8 @@ function useResumeOnMount(threadId: string) {
536
554
  r.json(),
537
555
  );
538
556
  if (status.isRunning) {
539
- const parentId = aui.thread().getState().messages.at(-1)?.id ?? null;
540
- aui.thread().resumeRun({ parentId });
557
+ const parentId = aui.thread.getState().messages.at(-1)?.id ?? null;
558
+ aui.thread.resumeRun({ parentId });
541
559
  }
542
560
  })();
543
561
  }, [aui, threadId]);
@@ -207,6 +207,7 @@ Each handler enables a specific UI feature.
207
207
  | `onEdit` | Message edit button |
208
208
  | `onReload` | Regenerate button |
209
209
  | `onCancel` | Cancel button while generating |
210
+ | `onRefetchThread` | `threads.reloadMainThread()` refetching the open thread in place |
210
211
  | `onAddToolResult` | Client-side tool result handoff |
211
212
  | `queue` | Queueing messages sent while a run is in progress |
212
213
 
@@ -395,7 +396,9 @@ const runtime = useExternalStoreRuntime({
395
396
 
396
397
  By default, sending while the thread is running is disabled. Provide a `queue` adapter to buffer a message sent during a run and process it once the run settles. The pending message is exposed on `composer.queue` and renders through [`ComposerPrimitive.Queue`](/docs/api-reference/primitives/composer).
397
398
 
398
- The `createMessageQueue` helper owns the FIFO ordering and the in-flight guard. Supply a driver that runs a message, pass its `adapter` to the runtime, and tell the queue when a run starts (`notifyBusy()`, so concurrent sends buffer) and ends (`notifyIdle()`).
399
+ The `createMessageQueue` helper owns the two-lane ordering and the in-flight guard: `steerItems` drain before `items`, and within each lane items drain in order. Supply a driver that runs a message, pass its `adapter` to the runtime, and tell the queue when a run starts (`notifyBusy()`, so concurrent sends buffer) and ends (`notifyIdle()`).
400
+
401
+ A hand-rolled adapter must implement the same contract: `items` and `steerItems` expose the lanes (each item carries a required `parts` projection of its content), `enqueue`/`steer` add to a lane, and `move(queueItemId, placement)` repositions with fail-fast anchors — unknown ids or anchors throw rather than being coerced. Individual items are addressable through `composer.queueItem({ id })` (or by index).
399
402
 
400
403
  ```tsx
401
404
  import { useEffect, useRef, useState } from "react";
@@ -418,6 +421,26 @@ useEffect(() => {
418
421
  }, [isRunning, queue]);
419
422
  ```
420
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.
425
+
426
+ ```tsx
427
+ const runtime = useExternalStoreRuntime({
428
+ // ...
429
+ onCancel: async () => {
430
+ queue.notifyCancelled(); // or queue.clear() to drop pending items
431
+ await cancelRun();
432
+ },
433
+ onEdit: async (message) => {
434
+ queue.clear();
435
+ // ...
436
+ },
437
+ onReload: async (parentId) => {
438
+ queue.clear();
439
+ // ...
440
+ },
441
+ });
442
+ ```
443
+
421
444
  ## Multi-thread
422
445
 
423
446
  `ExternalStoreRuntime` uses `ExternalStoreThreadListAdapter` (synchronous, inline). See [threads](/docs/runtimes/concepts/threads#externalstorethreadlistadapter) for the contract and best practices on keeping `currentThreadId` in sync with your store.
@@ -741,7 +764,7 @@ useExternalStoreRuntime({
741
764
  name: "isSendDisabled",
742
765
  type: "boolean",
743
766
  description:
744
- "Blocks new-message sending while leaving the input usable. When true, the thread composer's canSend becomes false, the Send button is disabled, Enter and the steer hotkey are no-ops, and aui.composer().send() short-circuits. Edit composers (saving message edits) ignore this flag. Use this to gate sending on external React state (e.g. while tools or auth are still loading).",
767
+ "Blocks new-message sending while leaving the input usable. When true, the thread composer's canSend becomes false, the Send button is disabled, Enter and the steer hotkey are no-ops, and aui.composer.send() short-circuits. Edit composers (saving message edits) ignore this flag. Use this to gate sending on external React state (e.g. while tools or auth are still loading).",
745
768
  default: "false",
746
769
  },
747
770
  {
@@ -787,6 +810,12 @@ useExternalStoreRuntime({
787
810
  type: "() => Promise<void>",
788
811
  description: "Handler for cancelling the current generation.",
789
812
  },
813
+ {
814
+ name: "onRefetchThread",
815
+ type: "() => Promise<void>",
816
+ description:
817
+ "Handler for re-fetching this thread's state in place, driving threads.reloadMainThread(). Unrelated to onReload, which re-generates an assistant message.",
818
+ },
790
819
  {
791
820
  name: "onAddToolResult",
792
821
  type: "(options: AddToolResultOptions) => Promise<void> | Void",
@@ -353,7 +353,7 @@ const openai = new OpenAI();
353
353
  const MyModelAdapter: ChatModelAdapter = {
354
354
  async *run({ messages, abortSignal, context }) {
355
355
  const stream = await openai.chat.completions.create({
356
- model: "gpt-5.4-mini",
356
+ model: "gpt-5.6-luna",
357
357
  messages: convertToOpenAIMessages(messages),
358
358
  stream: true,
359
359
  signal: abortSignal,
@@ -372,6 +372,34 @@ const MyModelAdapter: ChatModelAdapter = {
372
372
 
373
373
  Each yield replaces the previous content. Yield the full state every time, not deltas.
374
374
 
375
+ ### Only the last part streams
376
+
377
+ A text or reasoning part reports `status.type === "running"` only while it is the last part of the message. Every earlier one reads as complete, and the components that react to streaming follow that status: the reasoning disclosure auto-opens and pins its live preview while its part is running, and part status indicators resolve the same way. Tool-call parts are exempt, since their status comes from whether they have a result.
378
+
379
+ That makes an empty trailing part destructive. Yielding `[reasoning, text("")]` to reserve the text slot marks the reasoning part complete the moment the empty text part appears, even though reasoning is still streaming, and nothing in the UI signals why. Add a part on its first token instead of reserving it:
380
+
381
+ ```tsx
382
+ let reasoning = "";
383
+ let text = "";
384
+
385
+ for await (const chunk of stream) {
386
+ const delta = chunk.choices[0]?.delta;
387
+ text += delta?.content ?? "";
388
+ // Reasoning deltas are provider-specific; read whichever field yours sends
389
+ reasoning += (delta as { reasoning?: string })?.reasoning ?? "";
390
+
391
+ // Include each part only once it has content
392
+ yield {
393
+ content: [
394
+ ...(reasoning ? [{ type: "reasoning" as const, text: reasoning }] : []),
395
+ ...(text ? [{ type: "text" as const, text }] : []),
396
+ ],
397
+ };
398
+ }
399
+ ```
400
+
401
+ Guard every text or reasoning part this way, not just the trailing one. An empty part that is never filled also renders as an empty block, and whichever part ends up last is the only one that can report `running`.
402
+
375
403
  ### Streaming with tool calls
376
404
 
377
405
  Accumulate tool calls in a `Map` outside the streaming loop so they persist across chunks:
@@ -379,7 +407,7 @@ Accumulate tool calls in a `Map` outside the streaming loop so they persist acro
379
407
  ```tsx
380
408
  async *run({ messages, abortSignal, context }) {
381
409
  const stream = await openai.chat.completions.create({
382
- model: "gpt-5.4-mini",
410
+ model: "gpt-5.6-luna",
383
411
  messages: convertToOpenAIMessages(messages),
384
412
  tools: context.tools,
385
413
  stream: true,
@@ -415,7 +443,7 @@ If you build the `content` array fresh from the current chunk each iteration, to
415
443
 
416
444
  ## Tool calling
417
445
 
418
- `LocalRuntime` supports OpenAI-compatible function calling. Register tools through `useAui` so the runtime exposes them to your adapter via `context.tools`:
446
+ `LocalRuntime` supports OpenAI-compatible function calling. Register tools through the provider's `config` so the runtime exposes them to your adapter via `context.tools`:
419
447
 
420
448
  ```tsx title="app/toolkit.tsx"
421
449
  "use generative";
@@ -440,15 +468,14 @@ export default defineToolkit({
440
468
  ```
441
469
 
442
470
  ```tsx title="app/MyRuntimeProvider.tsx"
443
- import { useAui, Tools } from "@assistant-ui/react";
471
+ import { AuiConfig, Tools } from "@assistant-ui/react";
444
472
  import toolkit from "./toolkit";
445
473
 
446
474
  function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
447
475
  const runtime = useLocalRuntime(MyModelAdapter);
448
- const aui = useAui({ tools: Tools({ toolkit }) });
449
-
476
+ const config = AuiConfig({ tools: Tools({ toolkit }) });
450
477
  return (
451
- <AssistantRuntimeProvider aui={aui} runtime={runtime}>
478
+ <AssistantRuntimeProvider runtime={runtime} config={config}>
452
479
  {children}
453
480
  </AssistantRuntimeProvider>
454
481
  );
@@ -595,7 +622,7 @@ async function* createCustomStream(): AsyncGenerator<ChatModelRunResult> {
595
622
  };
596
623
  }
597
624
 
598
- aui.thread().resumeRun({
625
+ aui.thread.resumeRun({
599
626
  parentId: "message-id",
600
627
  stream: createCustomStream,
601
628
  });
@@ -617,8 +644,8 @@ function useStreamReconnect(threadId: string) {
617
644
  r.json(),
618
645
  );
619
646
  if (status.isRunning) {
620
- const parentId = aui.thread().getState().messages.at(-1)?.id ?? null;
621
- aui.thread().resumeRun({ parentId });
647
+ const parentId = aui.thread.getState().messages.at(-1)?.id ?? null;
648
+ aui.thread.resumeRun({ parentId });
622
649
  }
623
650
  })();
624
651
  }, [aui, threadId]);
@@ -627,7 +654,7 @@ function useStreamReconnect(threadId: string) {
627
654
 
628
655
  ## Queueing messages during a run
629
656
 
630
- Set `unstable_enableMessageQueue` to keep the composer usable while a run is in progress. A message sent during a run is held in `composer.queue` and sent once the run settles; steering a queued message runs it next.
657
+ Set `unstable_enableMessageQueue` to keep the composer usable while a run is in progress. A message sent during a queued run steers by default — it lands in the steer lane, ahead of previously queued items; `send({ steer: false })` queues a follow-up behind them instead. Pending items are held in `composer.queue` and drain once the run settles.
631
658
 
632
659
  ```tsx
633
660
  const runtime = useLocalRuntime(MyModelAdapter, {
@@ -674,7 +701,7 @@ const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
674
701
  const OpenAIAdapter: ChatModelAdapter = {
675
702
  async *run({ messages, abortSignal, context }) {
676
703
  const stream = await openai.chat.completions.create({
677
- model: "gpt-5.4-mini",
704
+ model: "gpt-5.6-luna",
678
705
  messages: messages.map((m) => ({
679
706
  role: m.role,
680
707
  content: m.content
@@ -49,6 +49,57 @@ export default function Home() {
49
49
  }
50
50
  ```
51
51
 
52
+ The `custom` bag of a per-turn `runConfig` is forwarded to Eve as
53
+ `clientContext`, so `runConfig: { custom: { page: "/pricing" } }` arrives as
54
+ `clientContext: { page: "/pricing" }`. An empty or absent bag is omitted, and
55
+ every value must be JSON-serializable.
56
+
57
+ Eve turns that object into a model context message, which is why the
58
+ assistant-ui `custom` envelope is unwrapped: the envelope would put a literal
59
+ `custom` key in the prompt. It is page or client context rather than run
60
+ configuration, and it cannot select a model or change how the turn executes.
61
+ Keep secrets, credentials, and personal data out of it, since the model reads
62
+ it as message text.
63
+
64
+ ## Connector authorization
65
+
66
+ When Eve pauses for connector authorization, the runtime emits an assistant
67
+ data part named `authorization`. Its data includes `state`, `name`, and the
68
+ available display fields such as `displayName`, `description`, `url`,
69
+ `userCode`, `instructions`, and `expiresAt`. Completed parts may also include
70
+ `outcome` and `reason`. `url` is present only when the connector supplied an
71
+ `http(s)` address; the adapter drops other schemes so a renderer can link to it
72
+ directly.
73
+
74
+ The Eve template ships this renderer as `components/eve-authorization.tsx` and
75
+ mounts it next to `<Thread />`. To add it to an existing app, register a data UI
76
+ for the part and type it with the exported `EveAuthorizationData`:
77
+
78
+ ```tsx title="authorization-ui.tsx"
79
+ import type { EveAuthorizationData } from "@assistant-ui/eve";
80
+ import { makeAssistantDataUI } from "@assistant-ui/react";
81
+
82
+ export const AuthorizationUI = makeAssistantDataUI<EveAuthorizationData>({
83
+ name: "authorization",
84
+ render: ({ data }) => (
85
+ <div>
86
+ {data.state === "required" ? (
87
+ <>
88
+ <p>{data.instructions ?? `Sign in to ${data.displayName ?? data.name}`}</p>
89
+ {data.userCode && <code>{data.userCode}</code>}
90
+ {data.url && <a href={data.url}>Continue to sign in</a>}
91
+ </>
92
+ ) : (
93
+ <p>{data.outcome ?? "Authorization completed"}</p>
94
+ )}
95
+ </div>
96
+ ),
97
+ });
98
+ ```
99
+
100
+ Mount `<AuthorizationUI />` inside the `AssistantRuntimeProvider` tree. The
101
+ standard thread intentionally leaves unmatched data parts unrendered.
102
+
52
103
  ## Requirements
53
104
 
54
105
  - Node.js 24 or higher.