@assistant-ui/mcp-docs-server 0.1.31 → 0.1.33

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 (281) hide show
  1. package/.docs/organized/code-examples/waterfall.md +23 -25
  2. package/.docs/organized/code-examples/with-a2a.md +28 -28
  3. package/.docs/organized/code-examples/with-ag-ui.md +35 -30
  4. package/.docs/organized/code-examples/with-ai-sdk-v6.md +16 -14
  5. package/.docs/organized/code-examples/with-artifacts.md +44 -38
  6. package/.docs/organized/code-examples/with-assistant-transport.md +68 -56
  7. package/.docs/organized/code-examples/with-browser-extension.md +357 -0
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +425 -90
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +30 -29
  10. package/.docs/organized/code-examples/with-cloud.md +16 -14
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +19 -17
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +29 -26
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +20 -18
  14. package/.docs/organized/code-examples/with-expo.md +37 -31
  15. package/.docs/organized/code-examples/with-external-store.md +13 -11
  16. package/.docs/organized/code-examples/with-ffmpeg.md +327 -290
  17. package/.docs/organized/code-examples/with-generative-ui.md +1195 -194
  18. package/.docs/organized/code-examples/with-google-adk.md +13 -11
  19. package/.docs/organized/code-examples/with-heat-graph.md +16 -14
  20. package/.docs/organized/code-examples/with-image-generation.md +456 -0
  21. package/.docs/organized/code-examples/with-interactables.md +322 -244
  22. package/.docs/organized/code-examples/with-langchain.md +18 -16
  23. package/.docs/organized/code-examples/with-langgraph.md +95 -88
  24. package/.docs/organized/code-examples/with-livekit.md +30 -27
  25. package/.docs/organized/code-examples/with-mcp.md +754 -0
  26. package/.docs/organized/code-examples/with-opencode.md +70 -70
  27. package/.docs/organized/code-examples/with-react-hook-form.md +24 -22
  28. package/.docs/organized/code-examples/with-react-ink.md +4 -4
  29. package/.docs/organized/code-examples/with-react-router.md +20 -18
  30. package/.docs/organized/code-examples/with-resumable-stream.md +661 -0
  31. package/.docs/organized/code-examples/with-store.md +22 -20
  32. package/.docs/organized/code-examples/with-tanstack.md +18 -19
  33. package/.docs/organized/code-examples/with-tap-runtime.md +31 -28
  34. package/.docs/raw/docs/(docs)/architecture.mdx +43 -2
  35. package/.docs/raw/docs/(docs)/cli.mdx +2 -2
  36. package/.docs/raw/docs/(docs)/copilots/model-context.mdx +34 -26
  37. package/.docs/raw/docs/(docs)/copilots/motivation.mdx +32 -26
  38. package/.docs/raw/docs/(docs)/index.mdx +2 -2
  39. package/.docs/raw/docs/(docs)/installation.mdx +2 -2
  40. package/.docs/raw/docs/(reference)/api-reference/adapters/attachments.mdx +26 -24
  41. package/.docs/raw/docs/(reference)/api-reference/adapters/feedback.mdx +20 -0
  42. package/.docs/raw/docs/(reference)/api-reference/adapters/index.mdx +20 -12
  43. package/.docs/raw/docs/(reference)/api-reference/adapters/model.mdx +44 -0
  44. package/.docs/raw/docs/(reference)/api-reference/adapters/persistence.mdx +41 -16
  45. package/.docs/raw/docs/(reference)/api-reference/adapters/runtime.mdx +12 -23
  46. package/.docs/raw/docs/(reference)/api-reference/adapters/suggestions.mdx +20 -0
  47. package/.docs/raw/docs/(reference)/api-reference/context-providers/assistant-runtime-provider.mdx +37 -8
  48. package/.docs/raw/docs/(reference)/api-reference/context-providers/index.mdx +11 -9
  49. package/.docs/raw/docs/(reference)/api-reference/context-providers/scoped-providers.mdx +64 -0
  50. package/.docs/raw/docs/(reference)/api-reference/external-store/index.mdx +22 -0
  51. package/.docs/raw/docs/(reference)/api-reference/external-store/message-conversion.mdx +52 -0
  52. package/.docs/raw/docs/(reference)/api-reference/external-store/runtime.mdx +46 -0
  53. package/.docs/raw/docs/(reference)/api-reference/generative-ui/index.mdx +18 -0
  54. package/.docs/raw/docs/(reference)/api-reference/generative-ui/rendering.mdx +33 -0
  55. package/.docs/raw/docs/(reference)/api-reference/generative-ui/spec.mdx +45 -0
  56. package/.docs/raw/docs/(reference)/api-reference/hooks/composer-triggers.mdx +98 -0
  57. package/.docs/raw/docs/(reference)/api-reference/hooks/index.mdx +18 -13
  58. package/.docs/raw/docs/(reference)/api-reference/hooks/model-context.mdx +31 -52
  59. package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +640 -0
  60. package/.docs/raw/docs/(reference)/api-reference/hooks/runtimes.mdx +15 -28
  61. package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +64 -18
  62. package/.docs/raw/docs/(reference)/api-reference/hooks/utilities.mdx +434 -0
  63. package/.docs/raw/docs/(reference)/api-reference/integrations/cloud-ai-sdk.mdx +24 -0
  64. package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +11 -12
  65. package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +109 -0
  66. package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +58 -0
  67. package/.docs/raw/docs/(reference)/api-reference/model-context/index.mdx +22 -0
  68. package/.docs/raw/docs/(reference)/api-reference/model-context/registry.mdx +20 -0
  69. package/.docs/raw/docs/(reference)/api-reference/overview.mdx +109 -132
  70. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar-more.mdx +78 -221
  71. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar.mdx +127 -242
  72. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +39 -20
  73. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-modal.mdx +66 -87
  74. package/.docs/raw/docs/(reference)/api-reference/primitives/attachment.mdx +50 -58
  75. package/.docs/raw/docs/(reference)/api-reference/primitives/branch-picker.mdx +80 -48
  76. package/.docs/raw/docs/(reference)/api-reference/primitives/chain-of-thought.mdx +67 -0
  77. package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +323 -461
  78. package/.docs/raw/docs/(reference)/api-reference/primitives/error.mdx +36 -43
  79. package/.docs/raw/docs/(reference)/api-reference/primitives/index.mdx +29 -24
  80. package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +63 -245
  81. package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +202 -588
  82. package/.docs/raw/docs/(reference)/api-reference/primitives/queue-item.mdx +65 -0
  83. package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +35 -22
  84. package/.docs/raw/docs/(reference)/api-reference/primitives/suggestion.mdx +57 -140
  85. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list-item-more.mdx +70 -161
  86. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list-item.mdx +84 -108
  87. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list.mdx +77 -96
  88. package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +173 -349
  89. package/.docs/raw/docs/(reference)/api-reference/runtimes/assistant-runtime.mdx +9 -21
  90. package/.docs/raw/docs/(reference)/api-reference/runtimes/attachment-runtime.mdx +10 -21
  91. package/.docs/raw/docs/(reference)/api-reference/runtimes/composer-runtime.mdx +15 -70
  92. package/.docs/raw/docs/(reference)/api-reference/runtimes/index.mdx +18 -13
  93. package/.docs/raw/docs/(reference)/api-reference/runtimes/message-part-runtime.mdx +25 -28
  94. package/.docs/raw/docs/(reference)/api-reference/runtimes/message-runtime.mdx +11 -63
  95. package/.docs/raw/docs/(reference)/api-reference/runtimes/queue-state.mdx +20 -0
  96. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-item-runtime.mdx +11 -48
  97. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-runtime.mdx +10 -47
  98. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-runtime.mdx +18 -30
  99. package/.docs/raw/docs/(reference)/api-reference/tools/component-tools.mdx +85 -0
  100. package/.docs/raw/docs/(reference)/api-reference/tools/index.mdx +39 -0
  101. package/.docs/raw/docs/(reference)/api-reference/tools/rendering.mdx +127 -0
  102. package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +42 -0
  103. package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +91 -0
  104. package/.docs/raw/docs/(reference)/api-reference/transport/assistant-transport.mdx +48 -0
  105. package/.docs/raw/docs/(reference)/api-reference/transport/frame.mdx +62 -0
  106. package/.docs/raw/docs/(reference)/api-reference/transport/index.mdx +22 -0
  107. package/.docs/raw/docs/(reference)/api-reference/utilities/index.mdx +19 -0
  108. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +143 -0
  109. package/.docs/raw/docs/(reference)/api-reference/voice/index.mdx +22 -0
  110. package/.docs/raw/docs/(reference)/api-reference/voice/session.mdx +54 -0
  111. package/.docs/raw/docs/(reference)/api-reference/voice/speech-dictation.mdx +36 -0
  112. package/.docs/raw/docs/cloud/ai-sdk.mdx +1 -3
  113. package/.docs/raw/docs/cloud/index.mdx +2 -2
  114. package/.docs/raw/docs/guides/attachments.mdx +4 -4
  115. package/.docs/raw/docs/guides/branching.mdx +1 -1
  116. package/.docs/raw/docs/guides/chain-of-thought.mdx +12 -14
  117. package/.docs/raw/docs/guides/context-api.mdx +6 -5
  118. package/.docs/raw/docs/guides/dictation.mdx +2 -2
  119. package/.docs/raw/docs/guides/editing.mdx +1 -1
  120. package/.docs/raw/docs/guides/image-generation.mdx +74 -0
  121. package/.docs/raw/docs/guides/index.mdx +5 -14
  122. package/.docs/raw/docs/guides/latex.mdx +2 -2
  123. package/.docs/raw/docs/guides/mentions.mdx +6 -6
  124. package/.docs/raw/docs/guides/message-timing.mdx +38 -5
  125. package/.docs/raw/docs/guides/quoting.mdx +1 -1
  126. package/.docs/raw/docs/guides/resumable-stream-deployment.mdx +212 -0
  127. package/.docs/raw/docs/guides/resumable-stream-stores.mdx +152 -0
  128. package/.docs/raw/docs/guides/resumable-streams.mdx +210 -0
  129. package/.docs/raw/docs/guides/slash-commands.mdx +2 -2
  130. package/.docs/raw/docs/guides/speech.mdx +2 -2
  131. package/.docs/raw/docs/guides/suggestions.mdx +89 -5
  132. package/.docs/raw/docs/guides/voice.mdx +2 -2
  133. package/.docs/raw/docs/ink/adapters.mdx +37 -1
  134. package/.docs/raw/docs/ink/custom-backend.mdx +59 -8
  135. package/.docs/raw/docs/ink/hooks.mdx +98 -85
  136. package/.docs/raw/docs/ink/index.mdx +10 -11
  137. package/.docs/raw/docs/ink/migration.mdx +1 -1
  138. package/.docs/raw/docs/ink/primitives.mdx +350 -9
  139. package/.docs/raw/docs/integrations/auth/better-auth.mdx +1 -1
  140. package/.docs/raw/docs/integrations/auth/clerk.mdx +1 -1
  141. package/.docs/raw/docs/integrations/auth/next-auth.mdx +1 -1
  142. package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +3 -3
  143. package/.docs/raw/docs/integrations/frameworks/cloudflare-agents/overview.mdx +282 -0
  144. package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +2 -2
  145. package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +1 -1
  146. package/.docs/raw/docs/integrations/gateways/index.mdx +9 -4
  147. package/.docs/raw/docs/integrations/index.mdx +17 -5
  148. package/.docs/raw/docs/integrations/observability/langfuse.mdx +8 -1
  149. package/.docs/raw/docs/integrations/observability/langsmith.mdx +8 -4
  150. package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +17 -2
  151. package/.docs/raw/docs/migrations/toolkit-tools.mdx +226 -0
  152. package/.docs/raw/docs/primitives/chain-of-thought.mdx +10 -16
  153. package/.docs/raw/docs/primitives/composer.mdx +53 -0
  154. package/.docs/raw/docs/primitives/index.mdx +2 -2
  155. package/.docs/raw/docs/primitives/message.mdx +9 -10
  156. package/.docs/raw/docs/primitives/suggestion.mdx +9 -0
  157. package/.docs/raw/docs/react-native/hooks.mdx +57 -82
  158. package/.docs/raw/docs/react-native/index.mdx +4 -4
  159. package/.docs/raw/docs/react-native/migration.mdx +1 -1
  160. package/.docs/raw/docs/react-native/primitives.mdx +2 -2
  161. package/.docs/raw/docs/runtimes/a2a/client-and-hooks.mdx +7 -4
  162. package/.docs/raw/docs/runtimes/a2a/overview.mdx +2 -2
  163. package/.docs/raw/docs/runtimes/ag-ui/overview.mdx +2 -2
  164. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +32 -1
  165. package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +2 -2
  166. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +7 -2
  167. package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +1 -1
  168. package/.docs/raw/docs/runtimes/ai-sdk/v6.mdx +129 -1
  169. package/.docs/raw/docs/runtimes/concepts/threads.mdx +7 -1
  170. package/.docs/raw/docs/runtimes/custom/data-stream.mdx +27 -2
  171. package/.docs/raw/docs/runtimes/custom/external-store.mdx +9 -1
  172. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +18 -7
  173. package/.docs/raw/docs/runtimes/custom/overview.mdx +2 -2
  174. package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +108 -38
  175. package/.docs/raw/docs/runtimes/google-adk/overview.mdx +2 -2
  176. package/.docs/raw/docs/runtimes/langchain.mdx +2 -2
  177. package/.docs/raw/docs/runtimes/langgraph/generative-ui.mdx +2 -2
  178. package/.docs/raw/docs/runtimes/langgraph/overview.mdx +2 -2
  179. package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +1 -1
  180. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +64 -50
  181. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +98 -86
  182. package/.docs/raw/docs/runtimes/opencode/overview.mdx +2 -2
  183. package/.docs/raw/docs/runtimes/opencode/quickstart.mdx +1 -1
  184. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +12 -5
  185. package/.docs/raw/docs/tools/backend.mdx +136 -0
  186. package/.docs/raw/docs/tools/defining-tools.mdx +413 -0
  187. package/.docs/raw/docs/tools/dynamic-tools.mdx +110 -0
  188. package/.docs/raw/docs/tools/generative-ui.mdx +214 -0
  189. package/.docs/raw/docs/tools/index.mdx +76 -0
  190. package/.docs/raw/docs/{guides → tools}/interactables.mdx +3 -3
  191. package/.docs/raw/docs/tools/mcp-apps.mdx +231 -0
  192. package/.docs/raw/docs/{integrations/tools → tools}/mcp.mdx +77 -50
  193. package/.docs/raw/docs/{guides → tools}/multi-agent.mdx +19 -21
  194. package/.docs/raw/docs/tools/tool-ui.mdx +967 -0
  195. package/.docs/raw/docs/tools/user-managed-mcp.mdx +337 -0
  196. package/.docs/raw/docs/ui/composer-trigger-popover.mdx +11 -1
  197. package/.docs/raw/docs/ui/directive-text.mdx +3 -3
  198. package/.docs/raw/docs/ui/mcp-config.mdx +102 -0
  199. package/.docs/raw/docs/ui/model-selector.mdx +8 -8
  200. package/.docs/raw/docs/ui/part-grouping.mdx +84 -50
  201. package/.docs/raw/docs/ui/reasoning.mdx +4 -5
  202. package/.docs/raw/docs/ui/sources.mdx +17 -0
  203. package/.docs/raw/docs/ui/streamdown.mdx +34 -2
  204. package/.docs/raw/docs/ui/thread-list.mdx +2 -2
  205. package/.docs/raw/docs/ui/thread.mdx +2 -2
  206. package/.docs/raw/docs/ui/tool-group.mdx +5 -6
  207. package/.docs/raw/docs/utilities/heat-graph.mdx +1 -1
  208. package/README.md +14 -72
  209. package/dist/constants.d.ts +12 -9
  210. package/dist/constants.d.ts.map +1 -1
  211. package/dist/constants.js +13 -9
  212. package/dist/constants.js.map +1 -1
  213. package/dist/index.d.ts +7 -3
  214. package/dist/index.d.ts.map +1 -1
  215. package/dist/index.js +25 -24
  216. package/dist/index.js.map +1 -1
  217. package/dist/prepare-docs/code-examples.d.ts +4 -1
  218. package/dist/prepare-docs/code-examples.d.ts.map +1 -1
  219. package/dist/prepare-docs/code-examples.js +109 -121
  220. package/dist/prepare-docs/code-examples.js.map +1 -1
  221. package/dist/prepare-docs/copy-raw.d.ts +4 -1
  222. package/dist/prepare-docs/copy-raw.d.ts.map +1 -1
  223. package/dist/prepare-docs/copy-raw.js +45 -42
  224. package/dist/prepare-docs/copy-raw.js.map +1 -1
  225. package/dist/prepare-docs/prepare.d.ts +1 -2
  226. package/dist/prepare-docs/prepare.js +17 -17
  227. package/dist/prepare-docs/prepare.js.map +1 -1
  228. package/dist/stdio.d.ts +1 -3
  229. package/dist/stdio.js +6 -3
  230. package/dist/stdio.js.map +1 -1
  231. package/dist/tools/docs.d.ts +20 -15
  232. package/dist/tools/docs.d.ts.map +1 -1
  233. package/dist/tools/docs.js +140 -161
  234. package/dist/tools/docs.js.map +1 -1
  235. package/dist/tools/examples.d.ts +20 -15
  236. package/dist/tools/examples.d.ts.map +1 -1
  237. package/dist/tools/examples.js +74 -86
  238. package/dist/tools/examples.js.map +1 -1
  239. package/dist/tools/tests/test-setup.d.ts +5 -2
  240. package/dist/tools/tests/test-setup.d.ts.map +1 -1
  241. package/dist/tools/tests/test-setup.js +21 -28
  242. package/dist/tools/tests/test-setup.js.map +1 -1
  243. package/dist/utils/logger.d.ts +8 -5
  244. package/dist/utils/logger.d.ts.map +1 -1
  245. package/dist/utils/logger.js +17 -17
  246. package/dist/utils/logger.js.map +1 -1
  247. package/dist/utils/mcp-format.d.ts +8 -5
  248. package/dist/utils/mcp-format.d.ts.map +1 -1
  249. package/dist/utils/mcp-format.js +9 -9
  250. package/dist/utils/mcp-format.js.map +1 -1
  251. package/dist/utils/mdx.d.ts +8 -6
  252. package/dist/utils/mdx.d.ts.map +1 -1
  253. package/dist/utils/mdx.js +22 -22
  254. package/dist/utils/mdx.js.map +1 -1
  255. package/dist/utils/paths.d.ts +9 -6
  256. package/dist/utils/paths.d.ts.map +1 -1
  257. package/dist/utils/paths.js +66 -76
  258. package/dist/utils/paths.js.map +1 -1
  259. package/dist/utils/security.d.ts +4 -1
  260. package/dist/utils/security.d.ts.map +1 -1
  261. package/dist/utils/security.js +19 -40
  262. package/dist/utils/security.js.map +1 -1
  263. package/package.json +5 -5
  264. package/.docs/organized/code-examples/with-parent-id-grouping.md +0 -596
  265. package/.docs/raw/docs/(docs)/copilots/make-assistant-tool-ui.mdx +0 -151
  266. package/.docs/raw/docs/(docs)/copilots/make-assistant-tool.mdx +0 -230
  267. package/.docs/raw/docs/(reference)/api-reference/adapters/feedback-speech.mdx +0 -41
  268. package/.docs/raw/docs/(reference)/api-reference/context-providers/text-message-part-provider.mdx +0 -40
  269. package/.docs/raw/docs/(reference)/api-reference/integrations/react-data-stream.mdx +0 -260
  270. package/.docs/raw/docs/(reference)/api-reference/integrations/react-hook-form.mdx +0 -103
  271. package/.docs/raw/docs/(reference)/api-reference/integrations/vercel-ai-sdk.mdx +0 -254
  272. package/.docs/raw/docs/guides/tool-ui.mdx +0 -858
  273. package/.docs/raw/docs/guides/tools.mdx +0 -675
  274. package/dist/prepare-docs/prepare.d.ts.map +0 -1
  275. package/dist/stdio.d.ts.map +0 -1
  276. /package/.docs/raw/docs/{(reference)/migrations → migrations}/deprecation-policy.mdx +0 -0
  277. /package/.docs/raw/docs/{(reference) → migrations}/react-compatibility.mdx +0 -0
  278. /package/.docs/raw/docs/{(reference)/migrations → migrations}/react-langgraph-v0-7.mdx +0 -0
  279. /package/.docs/raw/docs/{(reference)/migrations → migrations}/v0-11.mdx +0 -0
  280. /package/.docs/raw/docs/{(reference)/migrations → migrations}/v0-12.mdx +0 -0
  281. /package/.docs/raw/docs/{(reference)/migrations → migrations}/v0-14.mdx +0 -0
@@ -3,46 +3,132 @@
3
3
  ## app/api/chat/route.ts
4
4
 
5
5
  ```typescript
6
- import { openai } from "@ai-sdk/openai";
6
+ import { createOpenAI } from "@ai-sdk/openai";
7
7
  import {
8
8
  streamText,
9
9
  convertToModelMessages,
10
10
  tool,
11
11
  stepCountIs,
12
12
  zodSchema,
13
+ createUIMessageStream,
14
+ JsonToSseTransformStream,
13
15
  } from "ai";
14
- import type { UIMessage } from "ai";
16
+ import type { UIMessage, UIMessageStreamWriter } from "ai";
15
17
  import { frontendTools } from "@assistant-ui/react-ai-sdk";
16
18
  import { z } from "zod";
17
19
 
18
20
  export const maxDuration = 30;
19
21
 
22
+ type SearchResult = { id: string; url: string; title: string };
23
+
24
+ const searchSources = (query: string): SearchResult[] => {
25
+ const q = query.toLowerCase();
26
+ if (q.includes("fibonacci")) {
27
+ return [
28
+ {
29
+ id: "src-fib-1",
30
+ url: "https://en.wikipedia.org/wiki/Fibonacci_number",
31
+ title: "Fibonacci number (Wikipedia)",
32
+ },
33
+ {
34
+ id: "src-fib-2",
35
+ url: "https://oeis.org/A000045",
36
+ title: "OEIS A000045: Fibonacci numbers",
37
+ },
38
+ ];
39
+ }
40
+ if (
41
+ q.includes("renewable") ||
42
+ q.includes("energy") ||
43
+ q.includes("climate")
44
+ ) {
45
+ return [
46
+ {
47
+ id: "src-iea",
48
+ url: "https://www.iea.org/reports/renewables-2024",
49
+ title: "IEA Renewables 2024",
50
+ },
51
+ {
52
+ id: "src-irena",
53
+ url: "https://www.irena.org/Publications",
54
+ title: "IRENA Publications",
55
+ },
56
+ ];
57
+ }
58
+ return [
59
+ {
60
+ id: "src-wiki",
61
+ url: `https://en.wikipedia.org/wiki/Special:Search?search=${encodeURIComponent(query)}`,
62
+ title: "Wikipedia search results",
63
+ },
64
+ {
65
+ id: "src-web",
66
+ url: `https://duckduckgo.com/?q=${encodeURIComponent(query)}`,
67
+ title: `Web results for: ${query}`,
68
+ },
69
+ ];
70
+ };
71
+
20
72
  export async function POST(req: Request) {
21
73
  const {
22
74
  messages,
23
75
  tools,
24
76
  }: { messages: UIMessage[]; tools: Record<string, any> } = await req.json();
25
77
 
78
+ const uiStream = createUIMessageStream({
79
+ execute: async ({ writer }) => {
80
+ if (!process.env.OPENAI_API_KEY) {
81
+ await streamFallback(writer, messages);
82
+ return;
83
+ }
84
+ await streamModel(writer, messages, tools);
85
+ },
86
+ });
87
+
88
+ return new Response(
89
+ uiStream
90
+ .pipeThrough(new JsonToSseTransformStream())
91
+ .pipeThrough(new TextEncoderStream()),
92
+ {
93
+ headers: {
94
+ "Content-Type": "text/event-stream",
95
+ "Cache-Control": "no-cache",
96
+ Connection: "keep-alive",
97
+ },
98
+ },
99
+ );
100
+ }
101
+
102
+ async function streamModel(
103
+ writer: UIMessageStreamWriter,
104
+ messages: UIMessage[],
105
+ frontendToolDefs: Record<string, any>,
106
+ ) {
107
+ const toolNameByCall = new Map<string, string>();
108
+ const openai = createOpenAI({
109
+ apiKey: process.env.OPENAI_API_KEY!,
110
+ ...(process.env.OPENAI_BASE_URL && {
111
+ baseURL: process.env.OPENAI_BASE_URL,
112
+ }),
113
+ });
114
+
26
115
  const result = streamText({
27
- // Use a reasoning model to showcase chain of thought
116
+ // Reasoning model so the chain-of-thought group has real content.
28
117
  model: openai("gpt-5.4-mini"),
29
118
  messages: await convertToModelMessages(messages),
30
119
  stopWhen: stepCountIs(10),
31
120
  tools: {
32
- // Frontend tools registered via makeAssistantTool (e.g. execute_js)
33
- ...frontendTools(tools),
34
-
35
- // Backend-only tools
121
+ ...frontendTools(frontendToolDefs),
36
122
  get_current_weather: tool({
37
123
  description: "Get the current weather for a city",
38
- inputSchema: zodSchema(
39
- z.object({
40
- city: z.string(),
41
- }),
42
- ),
43
- execute: async ({ city }) => {
44
- return `The weather in ${city} is sunny, 72°F`;
45
- },
124
+ inputSchema: zodSchema(z.object({ city: z.string() })),
125
+ execute: async ({ city }) => `The weather in ${city} is sunny, 72°F`,
126
+ }),
127
+ search_web: tool({
128
+ description:
129
+ "Search the web for citations on a topic. Returns a list of source URLs and titles the assistant should consult.",
130
+ inputSchema: zodSchema(z.object({ query: z.string() })),
131
+ execute: async ({ query }) => ({ sources: searchSources(query) }),
46
132
  }),
47
133
  },
48
134
  providerOptions: {
@@ -53,9 +139,106 @@ export async function POST(req: Request) {
53
139
  },
54
140
  });
55
141
 
56
- return result.toUIMessageStreamResponse({ sendReasoning: true });
142
+ // Forward model chunks, then synthesize `source-url` parts from each
143
+ // `search_web` tool output so the UI renders them as `source` parts. OpenAI
144
+ // does not natively emit `source-url` chunks without web search, so the route
145
+ // produces them deterministically from the tool result.
146
+ for await (const chunk of result.toUIMessageStream({ sendReasoning: true })) {
147
+ writer.write(chunk);
148
+
149
+ if (chunk.type === "tool-input-available") {
150
+ toolNameByCall.set(chunk.toolCallId, chunk.toolName);
151
+ }
152
+ if (
153
+ chunk.type === "tool-output-available" &&
154
+ toolNameByCall.get(chunk.toolCallId) === "search_web"
155
+ ) {
156
+ const output = chunk.output as { sources?: SearchResult[] };
157
+ for (const source of output.sources ?? []) {
158
+ writer.write({
159
+ type: "source-url",
160
+ sourceId: `${source.id}-${crypto.randomUUID()}`,
161
+ url: source.url,
162
+ title: source.title,
163
+ });
164
+ }
165
+ }
166
+ }
167
+ }
168
+
169
+ async function streamFallback(
170
+ writer: UIMessageStreamWriter,
171
+ messages: UIMessage[],
172
+ ) {
173
+ const lastUserText =
174
+ messages
175
+ .filter((m) => m.role === "user")
176
+ .at(-1)
177
+ ?.parts.flatMap((p) => (p.type === "text" ? [p.text] : []))
178
+ .join(" ") ?? "your question";
179
+
180
+ const messageId = `msg-${crypto.randomUUID()}`;
181
+ const reasoningId = `r-${crypto.randomUUID()}`;
182
+ const textId = `t-${crypto.randomUUID()}`;
183
+ const toolCallId = `call-${crypto.randomUUID()}`;
184
+ const sources = searchSources(lastUserText);
185
+
186
+ writer.write({ type: "start", messageId });
187
+ writer.write({ type: "start-step" });
188
+
189
+ writer.write({ type: "reasoning-start", id: reasoningId });
190
+ const reasoning =
191
+ "OPENAI_API_KEY is not set, so this response is generated deterministically by the route. " +
192
+ "A real run emits the same chunk shapes from the model: a reasoning step, " +
193
+ "a search_web tool call, source-url parts derived from the tool output, " +
194
+ "and a final answer that summarises the sources.";
195
+ for (const word of reasoning.match(/\S+\s*|\s+/g) ?? [reasoning]) {
196
+ writer.write({ type: "reasoning-delta", id: reasoningId, delta: word });
197
+ await sleep(15);
198
+ }
199
+ writer.write({ type: "reasoning-end", id: reasoningId });
200
+
201
+ writer.write({
202
+ type: "tool-input-available",
203
+ toolCallId,
204
+ toolName: "search_web",
205
+ input: { query: lastUserText },
206
+ });
207
+ await sleep(150);
208
+ writer.write({
209
+ type: "tool-output-available",
210
+ toolCallId,
211
+ output: { sources },
212
+ });
213
+
214
+ for (const source of sources) {
215
+ writer.write({
216
+ type: "source-url",
217
+ sourceId: `${source.id}-${crypto.randomUUID()}`,
218
+ url: source.url,
219
+ title: source.title,
220
+ });
221
+ }
222
+
223
+ writer.write({ type: "text-start", id: textId });
224
+ const answer =
225
+ `\n\nMock answer for: "${lastUserText}". ` +
226
+ "The badges above are structured `source` message parts emitted as " +
227
+ "`source-url` chunks, not URLs scraped from text. Set `OPENAI_API_KEY` " +
228
+ "to switch to a real model run.";
229
+ for (const word of answer.match(/\S+\s*|\s+/g) ?? [answer]) {
230
+ writer.write({ type: "text-delta", id: textId, delta: word });
231
+ await sleep(15);
232
+ }
233
+ writer.write({ type: "text-end", id: textId });
234
+
235
+ writer.write({ type: "finish-step" });
236
+ writer.write({ type: "finish" });
57
237
  }
58
238
 
239
+ const sleep = (ms: number) =>
240
+ new Promise<void>((resolve) => setTimeout(resolve, ms));
241
+
59
242
  ```
60
243
 
61
244
  ## app/globals.css
@@ -194,7 +377,8 @@ import "./globals.css";
194
377
 
195
378
  export const metadata: Metadata = {
196
379
  title: "Chain of Thought Example",
197
- description: "Example using MessagePrimitive.GroupedParts with AI SDK",
380
+ description:
381
+ "Reasoning, tool calls, and source citations rendered as grouped message parts.",
198
382
  };
199
383
 
200
384
  export default function RootLayout({
@@ -218,13 +402,16 @@ export default function RootLayout({
218
402
 
219
403
  import { type FC, type PropsWithChildren, useState } from "react";
220
404
  import { MarkdownText } from "@/components/assistant-ui/markdown-text";
405
+ import { Sources } from "@/components/assistant-ui/sources";
221
406
  import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
222
407
  import { Button } from "@/components/ui/button";
223
408
  import {
224
- AuiIf,
225
409
  ComposerPrimitive,
410
+ groupPartByType,
226
411
  MessagePrimitive,
412
+ SuggestionPrimitive,
227
413
  ThreadPrimitive,
414
+ useThread,
228
415
  } from "@assistant-ui/react";
229
416
  import {
230
417
  ArrowDownIcon,
@@ -237,10 +424,14 @@ import {
237
424
  export const MyThread: FC = () => {
238
425
  return (
239
426
  <ThreadPrimitive.Root
240
- className="flex h-full flex-col bg-background"
427
+ className="bg-background flex h-full flex-col"
241
428
  style={{ ["--thread-max-width" as string]: "44rem" }}
242
429
  >
243
430
  <ThreadPrimitive.Viewport className="flex flex-1 flex-col overflow-y-scroll scroll-smooth px-4 pt-8">
431
+ <ThreadPrimitive.Empty>
432
+ <ThreadWelcome />
433
+ </ThreadPrimitive.Empty>
434
+
244
435
  <ThreadPrimitive.Messages>
245
436
  {({ message }) => {
246
437
  if (message.role === "user") return <UserMessage />;
@@ -269,11 +460,43 @@ const Text: FC<{ text: string }> = ({ text }) => {
269
460
  return <p>{text}</p>;
270
461
  };
271
462
 
463
+ const ThreadWelcome: FC = () => {
464
+ return (
465
+ <div className="mx-auto flex w-full max-w-(--thread-max-width) grow flex-col justify-center gap-6">
466
+ <div>
467
+ <h1 className="text-2xl font-semibold">
468
+ What should we think through?
469
+ </h1>
470
+ </div>
471
+ <div className="flex flex-wrap gap-2">
472
+ <ThreadPrimitive.Suggestions>
473
+ {() => <ThreadSuggestionItem />}
474
+ </ThreadPrimitive.Suggestions>
475
+ </div>
476
+ </div>
477
+ );
478
+ };
479
+
480
+ const ThreadSuggestionItem: FC = () => {
481
+ return (
482
+ <SuggestionPrimitive.Trigger send asChild>
483
+ <Button
484
+ type="button"
485
+ variant="ghost"
486
+ className="bg-background hover:bg-muted h-auto w-full flex-col items-start justify-start gap-1 rounded-2xl border px-4 py-3 text-start text-sm sm:w-[calc(50%-0.25rem)]"
487
+ >
488
+ <SuggestionPrimitive.Title className="font-medium" />
489
+ <SuggestionPrimitive.Description className="text-muted-foreground empty:hidden" />
490
+ </Button>
491
+ </SuggestionPrimitive.Trigger>
492
+ );
493
+ };
494
+
272
495
  const UserMessage: FC = () => {
273
496
  return (
274
497
  <MessagePrimitive.Root className="mx-auto w-full max-w-(--thread-max-width) py-3">
275
498
  <div className="flex justify-end">
276
- <div className="max-w-[80%] rounded-2xl bg-primary px-4 py-2 text-primary-foreground">
499
+ <div className="bg-primary text-primary-foreground max-w-[80%] rounded-2xl px-4 py-2">
277
500
  <MessagePrimitive.Parts>
278
501
  {({ part }) => {
279
502
  if (part.type === "text") return <Text {...part} />;
@@ -291,13 +514,11 @@ const AssistantMessage: FC = () => {
291
514
  <MessagePrimitive.Root className="mx-auto w-full max-w-(--thread-max-width) py-3">
292
515
  <div className="flex flex-col gap-2 px-2 leading-relaxed">
293
516
  <MessagePrimitive.GroupedParts
294
- groupBy={(part) => {
295
- if (part.type === "reasoning")
296
- return ["group-chainOfThought", "group-reasoning"];
297
- if (part.type === "tool-call")
298
- return ["group-chainOfThought", "group-tool"];
299
- return null;
300
- }}
517
+ groupBy={groupPartByType({
518
+ reasoning: ["group-chainOfThought", "group-reasoning"],
519
+ "tool-call": ["group-chainOfThought", "group-tool"],
520
+ source: ["group-sources"],
521
+ })}
301
522
  >
302
523
  {({ part, children }) => {
303
524
  switch (part.type) {
@@ -309,12 +530,16 @@ const AssistantMessage: FC = () => {
309
530
  return (
310
531
  <PartLayout label="Taking action">{children}</PartLayout>
311
532
  );
533
+ case "group-sources":
534
+ return <SourcesLayout>{children}</SourcesLayout>;
312
535
  case "text":
313
536
  return <MarkdownText />;
314
537
  case "reasoning":
315
538
  return <Reasoning {...part} />;
316
539
  case "tool-call":
317
540
  return <ToolCall {...part} />;
541
+ case "source":
542
+ return <Sources {...part} />;
318
543
  default:
319
544
  return null;
320
545
  }
@@ -332,7 +557,7 @@ const ChainOfThoughtGroup: FC<PropsWithChildren> = ({ children }) => {
332
557
  <div className="my-2 rounded-lg border">
333
558
  <button
334
559
  type="button"
335
- className="flex w-full cursor-pointer items-center gap-2 px-4 py-2 font-medium text-sm hover:bg-muted/50"
560
+ className="hover:bg-muted/50 flex w-full cursor-pointer items-center gap-2 px-4 py-2 text-sm font-medium"
336
561
  onClick={() => setOpen((o) => !o)}
337
562
  >
338
563
  {open ? (
@@ -347,6 +572,15 @@ const ChainOfThoughtGroup: FC<PropsWithChildren> = ({ children }) => {
347
572
  );
348
573
  };
349
574
 
575
+ const SourcesLayout: FC<PropsWithChildren> = ({ children }) => {
576
+ return (
577
+ <div className="flex flex-wrap items-center gap-1.5">
578
+ <span className="text-muted-foreground mr-1 text-xs">Sources</span>
579
+ {children}
580
+ </div>
581
+ );
582
+ };
583
+
350
584
  const PartLayout: FC<PropsWithChildren<{ label: string }>> = ({
351
585
  children,
352
586
  label,
@@ -357,7 +591,7 @@ const PartLayout: FC<PropsWithChildren<{ label: string }>> = ({
357
591
  <div className="border-t">
358
592
  <button
359
593
  type="button"
360
- className="flex w-full cursor-pointer items-center gap-2 px-4 py-1.5 text-muted-foreground text-xs hover:bg-muted/50"
594
+ className="text-muted-foreground hover:bg-muted/50 flex w-full cursor-pointer items-center gap-2 px-4 py-1.5 text-xs"
361
595
  onClick={() => setOpen((o) => !o)}
362
596
  >
363
597
  {open ? (
@@ -374,7 +608,7 @@ const PartLayout: FC<PropsWithChildren<{ label: string }>> = ({
374
608
 
375
609
  const Reasoning: FC<{ text: string }> = ({ text }) => {
376
610
  return (
377
- <p className="whitespace-pre-wrap px-4 py-2 text-muted-foreground text-sm italic">
611
+ <p className="text-muted-foreground px-4 py-2 text-sm whitespace-pre-wrap italic">
378
612
  {text}
379
613
  </p>
380
614
  );
@@ -398,16 +632,18 @@ const ToolCall: FC<{ toolName: string; status: { type: string } }> = ({
398
632
  };
399
633
 
400
634
  const Composer: FC = () => {
635
+ const isRunning = useThread((state) => state.isRunning);
636
+
401
637
  return (
402
- <ComposerPrimitive.Root className="flex w-full flex-col rounded-2xl border border-input bg-background px-1 pt-2 outline-none transition-shadow has-[textarea:focus-visible]:border-ring has-[textarea:focus-visible]:ring-2 has-[textarea:focus-visible]:ring-ring/20">
638
+ <ComposerPrimitive.Root className="border-input bg-background has-[textarea:focus-visible]:border-ring has-[textarea:focus-visible]:ring-ring/20 flex w-full flex-col rounded-2xl border px-1 pt-2 transition-shadow outline-none has-[textarea:focus-visible]:ring-2">
403
639
  <ComposerPrimitive.Input
404
640
  placeholder="Send a message..."
405
- className="mb-1 max-h-32 min-h-14 w-full resize-none bg-transparent px-4 pt-2 pb-3 text-sm outline-none placeholder:text-muted-foreground"
641
+ className="placeholder:text-muted-foreground mb-1 max-h-32 min-h-14 w-full resize-none bg-transparent px-4 pt-2 pb-3 text-sm outline-none"
406
642
  rows={1}
407
643
  autoFocus
408
644
  />
409
645
  <div className="relative mx-2 mb-2 flex items-center justify-end">
410
- <AuiIf condition={(s) => !s.thread.isRunning}>
646
+ {!isRunning ? (
411
647
  <ComposerPrimitive.Send asChild>
412
648
  <TooltipIconButton
413
649
  tooltip="Send"
@@ -419,8 +655,7 @@ const Composer: FC = () => {
419
655
  <ArrowUpIcon className="size-4" />
420
656
  </TooltipIconButton>
421
657
  </ComposerPrimitive.Send>
422
- </AuiIf>
423
- <AuiIf condition={(s) => s.thread.isRunning}>
658
+ ) : (
424
659
  <ComposerPrimitive.Cancel asChild>
425
660
  <Button
426
661
  type="button"
@@ -431,7 +666,7 @@ const Composer: FC = () => {
431
666
  <SquareIcon className="size-3 fill-current" />
432
667
  </Button>
433
668
  </ComposerPrimitive.Cancel>
434
- </AuiIf>
669
+ )}
435
670
  </div>
436
671
  </ComposerPrimitive.Root>
437
672
  );
@@ -446,7 +681,8 @@ const Composer: FC = () => {
446
681
 
447
682
  import {
448
683
  AssistantRuntimeProvider,
449
- makeAssistantTool,
684
+ Tools,
685
+ type Toolkit,
450
686
  useAui,
451
687
  AuiProvider,
452
688
  Suggestions,
@@ -456,53 +692,56 @@ import { z } from "zod";
456
692
  import { MyThread } from "./MyThread";
457
693
  import { lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
458
694
 
459
- const ExecuteJsTool = makeAssistantTool({
460
- toolName: "execute_js",
461
- description: "Execute JavaScript code and return the result",
462
- parameters: z.object({
463
- code: z.string().describe("The JavaScript code to execute"),
464
- }),
465
- execute: async ({ code }) => {
466
- try {
467
- // biome-ignore lint/security/noGlobalEval: example code
468
- const result = eval(code);
469
- return { success: true, result: String(result) };
470
- } catch (e) {
471
- return { success: false, error: String(e) };
472
- }
695
+ const toolkit = {
696
+ execute_js: {
697
+ type: "frontend",
698
+ description: "Execute JavaScript code and return the result",
699
+ parameters: z.object({
700
+ code: z.string().describe("The JavaScript code to execute"),
701
+ }),
702
+ execute: async ({ code }) => {
703
+ try {
704
+ const result = eval(code);
705
+ return { success: true, result: String(result) };
706
+ } catch (e) {
707
+ return { success: false, error: String(e) };
708
+ }
709
+ },
710
+ render: ({ args, result, status }) => (
711
+ <div className="bg-muted/30 my-2 rounded-lg border p-4 text-sm">
712
+ <p className="mb-1 font-semibold">execute_js</p>
713
+ <pre className="bg-background rounded p-2 font-mono text-xs whitespace-pre-wrap">
714
+ {args.code}
715
+ </pre>
716
+ {status.type !== "running" && result && (
717
+ <div className="mt-2 border-t pt-2">
718
+ <p className="text-muted-foreground font-semibold">
719
+ {result.success ? "Result:" : "Error:"}
720
+ </p>
721
+ <pre className="font-mono text-xs whitespace-pre-wrap">
722
+ {result.success ? result.result : result.error}
723
+ </pre>
724
+ </div>
725
+ )}
726
+ </div>
727
+ ),
473
728
  },
474
- render: ({ args, result, status }) => (
475
- <div className="my-2 rounded-lg border bg-muted/30 p-4 text-sm">
476
- <p className="mb-1 font-semibold">execute_js</p>
477
- <pre className="whitespace-pre-wrap rounded bg-background p-2 font-mono text-xs">
478
- {args.code}
479
- </pre>
480
- {status.type !== "running" && result && (
481
- <div className="mt-2 border-t pt-2">
482
- <p className="font-semibold text-muted-foreground">
483
- {result.success ? "Result:" : "Error:"}
484
- </p>
485
- <pre className="whitespace-pre-wrap font-mono text-xs">
486
- {result.success ? result.result : result.error}
487
- </pre>
488
- </div>
489
- )}
490
- </div>
491
- ),
492
- });
729
+ } satisfies Toolkit;
493
730
 
494
731
  function MyThreadWithSuggestions() {
495
732
  const aui = useAui({
496
733
  suggestions: Suggestions([
497
734
  {
498
735
  title: "Calculate Fibonacci(20)",
499
- label: "step by step with code",
500
- prompt: "Calculate the 20th Fibonacci number using JavaScript.",
736
+ label: "with chain of thought",
737
+ prompt:
738
+ "Calculate the 20th Fibonacci number using JavaScript and show your reasoning.",
501
739
  },
502
740
  {
503
- title: "What is 2^16?",
504
- label: "show your reasoning",
505
- prompt: "What is 2 to the power of 16? Show your work step by step.",
741
+ title: "Research with citations",
742
+ label: "show sources",
743
+ prompt:
744
+ "Research recent developments in renewable energy and cite your sources.",
506
745
  },
507
746
  ]),
508
747
  });
@@ -517,10 +756,12 @@ export default function Home() {
517
756
  const runtime = useChatRuntime({
518
757
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
519
758
  });
759
+ const aui = useAui({
760
+ tools: Tools({ toolkit }),
761
+ });
520
762
 
521
763
  return (
522
- <AssistantRuntimeProvider runtime={runtime}>
523
- <ExecuteJsTool />
764
+ <AssistantRuntimeProvider aui={aui} runtime={runtime}>
524
765
  <div className="h-full">
525
766
  <MyThreadWithSuggestions />
526
767
  </div>
@@ -563,12 +804,13 @@ export default function Home() {
563
804
  ## next.config.js
564
805
 
565
806
  ```javascript
807
+ import { withAui } from "@assistant-ui/next";
566
808
  /** @type {import('next').NextConfig} */
567
809
  const nextConfig = {
568
810
  transpilePackages: ["@assistant-ui/react", "@assistant-ui/react-ai-sdk"],
569
811
  };
570
812
 
571
- export default nextConfig;
813
+ export default withAui(nextConfig);
572
814
 
573
815
  ```
574
816
 
@@ -586,29 +828,30 @@ export default nextConfig;
586
828
  "start": "next start"
587
829
  },
588
830
  "dependencies": {
589
- "@ai-sdk/openai": "^3.0.61",
831
+ "@ai-sdk/openai": "^3.0.67",
590
832
  "@assistant-ui/react": "workspace:*",
591
833
  "@assistant-ui/react-ai-sdk": "workspace:*",
592
834
  "@assistant-ui/react-markdown": "workspace:*",
593
835
  "@assistant-ui/ui": "workspace:*",
594
- "ai": "^6.0.175",
836
+ "ai": "^6.0.193",
595
837
  "class-variance-authority": "^0.7.1",
596
838
  "clsx": "^2.1.1",
597
- "lucide-react": "^1.14.0",
598
- "next": "^16.2.4",
599
- "react": "^19.2.5",
600
- "react-dom": "^19.2.5",
601
- "tailwind-merge": "^3.5.0",
839
+ "lucide-react": "^1.17.0",
840
+ "next": "^16.2.6",
841
+ "react": "^19.2.6",
842
+ "react-dom": "^19.2.6",
843
+ "tailwind-merge": "^3.6.0",
602
844
  "zod": "^4.4.3"
603
845
  },
604
846
  "devDependencies": {
847
+ "@assistant-ui/next": "workspace:*",
605
848
  "@assistant-ui/x-buildutils": "workspace:*",
606
- "@tailwindcss/postcss": "^4.2.4",
607
- "@types/node": "^25.6.0",
608
- "@types/react": "^19.2.14",
849
+ "@tailwindcss/postcss": "^4.3.0",
850
+ "@types/node": "^25.9.1",
851
+ "@types/react": "^19.2.15",
609
852
  "@types/react-dom": "^19.2.3",
610
- "postcss": "^8.5.14",
611
- "tailwindcss": "^4.2.4",
853
+ "postcss": "^8.5.15",
854
+ "tailwindcss": "^4.3.0",
612
855
  "tw-animate-css": "^1.4.0",
613
856
  "typescript": "^6.0.3"
614
857
  }
@@ -616,6 +859,97 @@ export default nextConfig;
616
859
 
617
860
  ```
618
861
 
862
+ ## README.md
863
+
864
+ ```markdown
865
+ # Chain of Thought Example
866
+
867
+ A real chat flow that renders reasoning, tool calls, and source citations as grouped message parts. Demonstrates `MessagePrimitive.GroupedParts` with a nested `groupBy` path, plus structured `source` parts emitted from a search tool.
868
+
869
+ ## Quick start
870
+
871
+ ### Using the CLI
872
+
873
+ ```bash
874
+ npx assistant-ui@latest create my-app --example with-chain-of-thought
875
+ cd my-app
876
+ ```
877
+
878
+ ### Environment variables
879
+
880
+ `OPENAI_API_KEY` is optional. With the key set the route runs a real reasoning model and a `search_web` tool; without it the route streams a deterministic mock response with the same chunk shapes, so the UI is fully demonstrable offline.
881
+
882
+ Create `.env.local`:
883
+
884
+ ```
885
+ OPENAI_API_KEY=sk-...
886
+ ```
887
+
888
+ ### Run
889
+
890
+ ```bash
891
+ npm install
892
+ npm run dev
893
+ ```
894
+
895
+ Open [http://localhost:3000](http://localhost:3000).
896
+
897
+ ## What the example demonstrates
898
+
899
+ | Concern | Where it lives |
900
+ |---|---|
901
+ | Real chat flow with `useChatRuntime` | `app/page.tsx` |
902
+ | Reasoning, tool calls, source citations streamed as message parts | `app/api/chat/route.ts` |
903
+ | Nested adjacency-based grouping with `MessagePrimitive.GroupedParts` | `app/MyThread.tsx` |
904
+ | `Sources` component rendering `source` parts | `app/MyThread.tsx` |
905
+ | Fallback path that runs without an API key | `app/api/chat/route.ts` (`streamFallback`) |
906
+
907
+ ## How parts are grouped
908
+
909
+ The thread groups three part types under one collapsible "Thinking" section, then renders source badges in a row below it. The grouping is configured on `MessagePrimitive.GroupedParts`:
910
+
911
+ ```tsx
912
+ <MessagePrimitive.GroupedParts
913
+ groupBy={(part) => {
914
+ if (part.type === "reasoning") return ["group-chainOfThought", "group-reasoning"];
915
+ if (part.type === "tool-call") return ["group-chainOfThought", "group-tool"];
916
+ if (part.type === "source") return ["group-chainOfThought", "group-sources"];
917
+ return null;
918
+ }}
919
+ >
920
+ ```
921
+
922
+ Each part is mapped to a nested group key path. Adjacent parts that share a path are coalesced and rendered through the matching `group-*` case in the render function. `null` leaves a part ungrouped.
923
+
924
+ This is the canonical pattern. For non-adjacent clustering (parts that share an identifier but appear at different positions in the message), see [`/docs/ui/part-grouping`](https://www.assistant-ui.com/docs/ui/part-grouping).
925
+
926
+ ## How sources are emitted
927
+
928
+ The route emits structured `source-url` chunks; `@assistant-ui/react-ai-sdk` converts them to `source` message parts that the `Sources` component renders as badges. They are not URLs scraped from assistant text.
929
+
930
+ ```ts
931
+ writer.write({
932
+ type: "source-url",
933
+ sourceId: "src-1",
934
+ url: "https://example.com/report",
935
+ title: "Example report",
936
+ });
937
+ ```
938
+
939
+ In the real path the `search_web` tool returns sources in its output, and the route writes one `source-url` chunk per result immediately after the `tool-output-available` chunk. In the fallback path the same shape is written directly. The UI rendering is identical in both modes.
940
+
941
+ ## Why these features live together
942
+
943
+ Chain-of-thought, tool calls, and citation sources are the parts of an assistant turn a user typically wants to inspect together. Grouping them under one collapsible keeps the message tidy by default and reveals the full reasoning trail on demand.
944
+
945
+ ## Related documentation
946
+
947
+ - [Message Part Grouping](https://www.assistant-ui.com/docs/ui/part-grouping)
948
+ - [Sources component](https://www.assistant-ui.com/docs/ui/sources)
949
+ - [AI SDK v6 runtime](https://www.assistant-ui.com/docs/runtimes/ai-sdk/v6)
950
+
951
+ ```
952
+
619
953
  ## tsconfig.json
620
954
 
621
955
  ```json
@@ -631,6 +965,7 @@ export default nextConfig;
631
965
  "@/lib/utils": ["../../packages/ui/src/lib/utils"],
632
966
  "@assistant-ui/ui/*": ["../../packages/ui/src/*"],
633
967
  "@assistant-ui/*": ["../../packages/*/src"],
968
+ "@assistant-ui/core/*": ["../../packages/core/src/*"],
634
969
  "@assistant-ui/react/*": ["../../packages/react/src/*"],
635
970
  "@assistant-ui/tap/*": ["../../packages/tap/src/*"],
636
971
  "assistant-stream": ["../../packages/assistant-stream/src"]