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

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 (452) hide show
  1. package/README.md +16 -43
  2. package/dist/index.d.ts +2 -3
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +3 -91
  5. package/dist/index.js.map +1 -1
  6. package/dist/proxy.d.ts +9 -0
  7. package/dist/proxy.d.ts.map +1 -0
  8. package/dist/proxy.js +76 -0
  9. package/dist/proxy.js.map +1 -0
  10. package/package.json +8 -12
  11. package/src/index.ts +3 -115
  12. package/src/proxy.test.ts +250 -0
  13. package/src/proxy.ts +111 -0
  14. package/.docs/organized/code-examples/waterfall.md +0 -807
  15. package/.docs/organized/code-examples/with-a2a.md +0 -675
  16. package/.docs/organized/code-examples/with-ag-ui.md +0 -553
  17. package/.docs/organized/code-examples/with-ai-sdk-v7.md +0 -466
  18. package/.docs/organized/code-examples/with-artifacts.md +0 -815
  19. package/.docs/organized/code-examples/with-assistant-transport.md +0 -576
  20. package/.docs/organized/code-examples/with-browser-extension.md +0 -371
  21. package/.docs/organized/code-examples/with-chain-of-thought.md +0 -962
  22. package/.docs/organized/code-examples/with-cloud-standalone.md +0 -681
  23. package/.docs/organized/code-examples/with-cloud.md +0 -439
  24. package/.docs/organized/code-examples/with-custom-thread-list.md +0 -569
  25. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +0 -527
  26. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +0 -697
  27. package/.docs/organized/code-examples/with-eve.md +0 -405
  28. package/.docs/organized/code-examples/with-expo.md +0 -2246
  29. package/.docs/organized/code-examples/with-external-store.md +0 -421
  30. package/.docs/organized/code-examples/with-ffmpeg.md +0 -829
  31. package/.docs/organized/code-examples/with-generative-ui.md +0 -1810
  32. package/.docs/organized/code-examples/with-google-adk.md +0 -368
  33. package/.docs/organized/code-examples/with-heat-graph.md +0 -304
  34. package/.docs/organized/code-examples/with-image-generation.md +0 -463
  35. package/.docs/organized/code-examples/with-interactables.md +0 -733
  36. package/.docs/organized/code-examples/with-langchain.md +0 -446
  37. package/.docs/organized/code-examples/with-langgraph.md +0 -855
  38. package/.docs/organized/code-examples/with-livekit.md +0 -643
  39. package/.docs/organized/code-examples/with-mcp.md +0 -782
  40. package/.docs/organized/code-examples/with-nuxt.md +0 -2428
  41. package/.docs/organized/code-examples/with-opencode.md +0 -1974
  42. package/.docs/organized/code-examples/with-openui.md +0 -450
  43. package/.docs/organized/code-examples/with-pi.md +0 -2084
  44. package/.docs/organized/code-examples/with-react-hook-form.md +0 -727
  45. package/.docs/organized/code-examples/with-react-ink-web.md +0 -740
  46. package/.docs/organized/code-examples/with-react-ink.md +0 -473
  47. package/.docs/organized/code-examples/with-react-router.md +0 -936
  48. package/.docs/organized/code-examples/with-resumable-stream.md +0 -668
  49. package/.docs/organized/code-examples/with-store.md +0 -679
  50. package/.docs/organized/code-examples/with-svelte.md +0 -415
  51. package/.docs/organized/code-examples/with-sveltekit.md +0 -1061
  52. package/.docs/organized/code-examples/with-tanstack.md +0 -812
  53. package/.docs/organized/code-examples/with-tap-runtime.md +0 -813
  54. package/.docs/organized/code-examples/with-virtualized-thread.md +0 -681
  55. package/.docs/organized/code-examples/with-vue.md +0 -408
  56. package/.docs/raw/docs/(getting-started)/architecture.mdx +0 -146
  57. package/.docs/raw/docs/(getting-started)/base-ui.mdx +0 -39
  58. package/.docs/raw/docs/(getting-started)/cli.mdx +0 -542
  59. package/.docs/raw/docs/(getting-started)/devtools.mdx +0 -79
  60. package/.docs/raw/docs/(getting-started)/index.mdx +0 -23
  61. package/.docs/raw/docs/(getting-started)/installation.mdx +0 -483
  62. package/.docs/raw/docs/(getting-started)/llm.mdx +0 -211
  63. package/.docs/raw/docs/(getting-started)/rtl.mdx +0 -78
  64. package/.docs/raw/docs/(reference)/api-reference/adapters/attachments.mdx +0 -36
  65. package/.docs/raw/docs/(reference)/api-reference/adapters/feedback.mdx +0 -20
  66. package/.docs/raw/docs/(reference)/api-reference/adapters/index.mdx +0 -34
  67. package/.docs/raw/docs/(reference)/api-reference/adapters/model.mdx +0 -44
  68. package/.docs/raw/docs/(reference)/api-reference/adapters/persistence.mdx +0 -59
  69. package/.docs/raw/docs/(reference)/api-reference/adapters/runtime.mdx +0 -20
  70. package/.docs/raw/docs/(reference)/api-reference/adapters/suggestions.mdx +0 -26
  71. package/.docs/raw/docs/(reference)/api-reference/context-providers/assistant-runtime-provider.mdx +0 -84
  72. package/.docs/raw/docs/(reference)/api-reference/context-providers/index.mdx +0 -22
  73. package/.docs/raw/docs/(reference)/api-reference/context-providers/scoped-providers.mdx +0 -64
  74. package/.docs/raw/docs/(reference)/api-reference/external-store/index.mdx +0 -22
  75. package/.docs/raw/docs/(reference)/api-reference/external-store/message-conversion.mdx +0 -57
  76. package/.docs/raw/docs/(reference)/api-reference/external-store/runtime.mdx +0 -49
  77. package/.docs/raw/docs/(reference)/api-reference/generative-ui/a2ui.mdx +0 -40
  78. package/.docs/raw/docs/(reference)/api-reference/generative-ui/actions.mdx +0 -56
  79. package/.docs/raw/docs/(reference)/api-reference/generative-ui/components.mdx +0 -86
  80. package/.docs/raw/docs/(reference)/api-reference/generative-ui/index.mdx +0 -39
  81. package/.docs/raw/docs/(reference)/api-reference/generative-ui/json-generative-ui.mdx +0 -42
  82. package/.docs/raw/docs/(reference)/api-reference/generative-ui/rendering.mdx +0 -84
  83. package/.docs/raw/docs/(reference)/api-reference/generative-ui/slack.mdx +0 -85
  84. package/.docs/raw/docs/(reference)/api-reference/generative-ui/spec.mdx +0 -45
  85. package/.docs/raw/docs/(reference)/api-reference/generative-ui/teams.mdx +0 -93
  86. package/.docs/raw/docs/(reference)/api-reference/generative-ui/tokens.mdx +0 -62
  87. package/.docs/raw/docs/(reference)/api-reference/hooks/composer-triggers.mdx +0 -129
  88. package/.docs/raw/docs/(reference)/api-reference/hooks/index.mdx +0 -31
  89. package/.docs/raw/docs/(reference)/api-reference/hooks/model-context.mdx +0 -34
  90. package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +0 -350
  91. package/.docs/raw/docs/(reference)/api-reference/hooks/runtimes.mdx +0 -28
  92. package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +0 -97
  93. package/.docs/raw/docs/(reference)/api-reference/integrations/ai-sdk.mdx +0 -200
  94. package/.docs/raw/docs/(reference)/api-reference/integrations/cloud-ai-sdk.mdx +0 -24
  95. package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +0 -118
  96. package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +0 -28
  97. package/.docs/raw/docs/(reference)/api-reference/integrations/react-data-stream.mdx +0 -37
  98. package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +0 -50
  99. package/.docs/raw/docs/(reference)/api-reference/model-context/index.mdx +0 -22
  100. package/.docs/raw/docs/(reference)/api-reference/model-context/registry.mdx +0 -20
  101. package/.docs/raw/docs/(reference)/api-reference/overview.mdx +0 -566
  102. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar-more.mdx +0 -152
  103. package/.docs/raw/docs/(reference)/api-reference/primitives/action-bar.mdx +0 -239
  104. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +0 -208
  105. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-modal.mdx +0 -108
  106. package/.docs/raw/docs/(reference)/api-reference/primitives/attachment.mdx +0 -99
  107. package/.docs/raw/docs/(reference)/api-reference/primitives/branch-picker.mdx +0 -145
  108. package/.docs/raw/docs/(reference)/api-reference/primitives/chain-of-thought.mdx +0 -82
  109. package/.docs/raw/docs/(reference)/api-reference/primitives/composer.mdx +0 -637
  110. package/.docs/raw/docs/(reference)/api-reference/primitives/composition.mdx +0 -22
  111. package/.docs/raw/docs/(reference)/api-reference/primitives/error.mdx +0 -65
  112. package/.docs/raw/docs/(reference)/api-reference/primitives/index.mdx +0 -73
  113. package/.docs/raw/docs/(reference)/api-reference/primitives/message-part.mdx +0 -117
  114. package/.docs/raw/docs/(reference)/api-reference/primitives/message.mdx +0 -339
  115. package/.docs/raw/docs/(reference)/api-reference/primitives/queue-item.mdx +0 -77
  116. package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +0 -88
  117. package/.docs/raw/docs/(reference)/api-reference/primitives/suggestion.mdx +0 -81
  118. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list-item-more.mdx +0 -129
  119. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list-item.mdx +0 -138
  120. package/.docs/raw/docs/(reference)/api-reference/primitives/thread-list.mdx +0 -122
  121. package/.docs/raw/docs/(reference)/api-reference/primitives/thread.mdx +0 -289
  122. package/.docs/raw/docs/(reference)/api-reference/runtimes/assistant-runtime.mdx +0 -20
  123. package/.docs/raw/docs/(reference)/api-reference/runtimes/attachment-runtime.mdx +0 -24
  124. package/.docs/raw/docs/(reference)/api-reference/runtimes/composer-runtime.mdx +0 -32
  125. package/.docs/raw/docs/(reference)/api-reference/runtimes/index.mdx +0 -43
  126. package/.docs/raw/docs/(reference)/api-reference/runtimes/message-part-runtime.mdx +0 -43
  127. package/.docs/raw/docs/(reference)/api-reference/runtimes/message-runtime.mdx +0 -24
  128. package/.docs/raw/docs/(reference)/api-reference/runtimes/queue-state.mdx +0 -20
  129. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-item-runtime.mdx +0 -24
  130. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-list-runtime.mdx +0 -24
  131. package/.docs/raw/docs/(reference)/api-reference/runtimes/thread-runtime.mdx +0 -36
  132. package/.docs/raw/docs/(reference)/api-reference/tools/component-tools.mdx +0 -85
  133. package/.docs/raw/docs/(reference)/api-reference/tools/index.mdx +0 -45
  134. package/.docs/raw/docs/(reference)/api-reference/tools/interactables-legacy.mdx +0 -55
  135. package/.docs/raw/docs/(reference)/api-reference/tools/interactables.mdx +0 -151
  136. package/.docs/raw/docs/(reference)/api-reference/tools/rendering.mdx +0 -106
  137. package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +0 -62
  138. package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +0 -178
  139. package/.docs/raw/docs/(reference)/api-reference/transport/assistant-transport.mdx +0 -48
  140. package/.docs/raw/docs/(reference)/api-reference/transport/frame.mdx +0 -62
  141. package/.docs/raw/docs/(reference)/api-reference/transport/index.mdx +0 -22
  142. package/.docs/raw/docs/(reference)/api-reference/utilities/index.mdx +0 -19
  143. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +0 -152
  144. package/.docs/raw/docs/(reference)/api-reference/voice/index.mdx +0 -22
  145. package/.docs/raw/docs/(reference)/api-reference/voice/session.mdx +0 -54
  146. package/.docs/raw/docs/(reference)/api-reference/voice/speech-dictation.mdx +0 -36
  147. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +0 -527
  148. package/.docs/raw/docs/cloud/ai-sdk.mdx +0 -604
  149. package/.docs/raw/docs/cloud/authorization.mdx +0 -252
  150. package/.docs/raw/docs/cloud/index.mdx +0 -46
  151. package/.docs/raw/docs/cloud/langgraph.mdx +0 -554
  152. package/.docs/raw/docs/copilots/assistant-frame.mdx +0 -411
  153. package/.docs/raw/docs/copilots/make-assistant-visible.mdx +0 -82
  154. package/.docs/raw/docs/copilots/model-context.mdx +0 -156
  155. package/.docs/raw/docs/copilots/motivation.mdx +0 -202
  156. package/.docs/raw/docs/copilots/use-assistant-instructions.mdx +0 -64
  157. package/.docs/raw/docs/guides/attachments.mdx +0 -619
  158. package/.docs/raw/docs/guides/branching.mdx +0 -76
  159. package/.docs/raw/docs/guides/chain-of-thought.mdx +0 -164
  160. package/.docs/raw/docs/guides/chatgpt-subscription.mdx +0 -108
  161. package/.docs/raw/docs/guides/context-api.mdx +0 -619
  162. package/.docs/raw/docs/guides/dictation.mdx +0 -298
  163. package/.docs/raw/docs/guides/editing.mdx +0 -102
  164. package/.docs/raw/docs/guides/electron.mdx +0 -369
  165. package/.docs/raw/docs/guides/headless-composer-input.mdx +0 -113
  166. package/.docs/raw/docs/guides/image-generation.mdx +0 -74
  167. package/.docs/raw/docs/guides/index.mdx +0 -117
  168. package/.docs/raw/docs/guides/input-history.mdx +0 -55
  169. package/.docs/raw/docs/guides/latex.mdx +0 -158
  170. package/.docs/raw/docs/guides/mentions.mdx +0 -575
  171. package/.docs/raw/docs/guides/message-timing.mdx +0 -215
  172. package/.docs/raw/docs/guides/quoting.mdx +0 -169
  173. package/.docs/raw/docs/guides/resumable-stream-deployment.mdx +0 -212
  174. package/.docs/raw/docs/guides/resumable-stream-stores.mdx +0 -152
  175. package/.docs/raw/docs/guides/resumable-streams.mdx +0 -292
  176. package/.docs/raw/docs/guides/slash-commands.mdx +0 -358
  177. package/.docs/raw/docs/guides/speech.mdx +0 -172
  178. package/.docs/raw/docs/guides/suggestions.mdx +0 -384
  179. package/.docs/raw/docs/guides/virtualization.mdx +0 -133
  180. package/.docs/raw/docs/guides/voice.mdx +0 -310
  181. package/.docs/raw/docs/ink/adapters.mdx +0 -99
  182. package/.docs/raw/docs/ink/custom-backend.mdx +0 -254
  183. package/.docs/raw/docs/ink/hooks.mdx +0 -469
  184. package/.docs/raw/docs/ink/index.mdx +0 -237
  185. package/.docs/raw/docs/ink/migration.mdx +0 -138
  186. package/.docs/raw/docs/ink/primitives.mdx +0 -1259
  187. package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +0 -528
  188. package/.docs/raw/docs/integrations/auth/better-auth.mdx +0 -188
  189. package/.docs/raw/docs/integrations/auth/clerk.mdx +0 -166
  190. package/.docs/raw/docs/integrations/auth/next-auth.mdx +0 -191
  191. package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +0 -88
  192. package/.docs/raw/docs/integrations/frameworks/cloudflare-agents.mdx +0 -283
  193. package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +0 -188
  194. package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +0 -57
  195. package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +0 -201
  196. package/.docs/raw/docs/integrations/gateways/index.mdx +0 -162
  197. package/.docs/raw/docs/integrations/index.mdx +0 -178
  198. package/.docs/raw/docs/integrations/observability/helicone.mdx +0 -129
  199. package/.docs/raw/docs/integrations/observability/langfuse.mdx +0 -161
  200. package/.docs/raw/docs/integrations/observability/langsmith.mdx +0 -151
  201. package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +0 -773
  202. package/.docs/raw/docs/migrations/deprecation-policy.mdx +0 -42
  203. package/.docs/raw/docs/migrations/index.mdx +0 -50
  204. package/.docs/raw/docs/migrations/react-compatibility.mdx +0 -74
  205. package/.docs/raw/docs/migrations/react-langgraph-v0-7.mdx +0 -328
  206. package/.docs/raw/docs/migrations/toolkit-tools.mdx +0 -236
  207. package/.docs/raw/docs/migrations/v0-11.mdx +0 -172
  208. package/.docs/raw/docs/migrations/v0-12.mdx +0 -302
  209. package/.docs/raw/docs/migrations/v0-14.mdx +0 -297
  210. package/.docs/raw/docs/migrations/v0-15.mdx +0 -270
  211. package/.docs/raw/docs/primitives/action-bar.mdx +0 -352
  212. package/.docs/raw/docs/primitives/assistant-modal.mdx +0 -216
  213. package/.docs/raw/docs/primitives/attachment.mdx +0 -217
  214. package/.docs/raw/docs/primitives/branch-picker.mdx +0 -222
  215. package/.docs/raw/docs/primitives/chain-of-thought.mdx +0 -315
  216. package/.docs/raw/docs/primitives/composer.mdx +0 -659
  217. package/.docs/raw/docs/primitives/error.mdx +0 -142
  218. package/.docs/raw/docs/primitives/index.mdx +0 -99
  219. package/.docs/raw/docs/primitives/message.mdx +0 -618
  220. package/.docs/raw/docs/primitives/selection-toolbar.mdx +0 -191
  221. package/.docs/raw/docs/primitives/suggestion.mdx +0 -254
  222. package/.docs/raw/docs/primitives/thread-list.mdx +0 -467
  223. package/.docs/raw/docs/primitives/thread.mdx +0 -509
  224. package/.docs/raw/docs/react-native/adapters.mdx +0 -94
  225. package/.docs/raw/docs/react-native/custom-backend.mdx +0 -207
  226. package/.docs/raw/docs/react-native/hooks.mdx +0 -339
  227. package/.docs/raw/docs/react-native/index.mdx +0 -289
  228. package/.docs/raw/docs/react-native/migration.mdx +0 -142
  229. package/.docs/raw/docs/react-native/primitives.mdx +0 -976
  230. package/.docs/raw/docs/runtimes/a2a/client-and-hooks.mdx +0 -399
  231. package/.docs/raw/docs/runtimes/a2a/overview.mdx +0 -60
  232. package/.docs/raw/docs/runtimes/a2a/quickstart.mdx +0 -216
  233. package/.docs/raw/docs/runtimes/ag-ui/agent-state.mdx +0 -124
  234. package/.docs/raw/docs/runtimes/ag-ui/overview.mdx +0 -70
  235. package/.docs/raw/docs/runtimes/ag-ui/quickstart.mdx +0 -243
  236. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +0 -267
  237. package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +0 -61
  238. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +0 -122
  239. package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +0 -135
  240. package/.docs/raw/docs/runtimes/ai-sdk/v6-legacy.mdx +0 -636
  241. package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +0 -731
  242. package/.docs/raw/docs/runtimes/claude-managed-agents.mdx +0 -118
  243. package/.docs/raw/docs/runtimes/concepts/adapters.mdx +0 -265
  244. package/.docs/raw/docs/runtimes/concepts/architecture.mdx +0 -167
  245. package/.docs/raw/docs/runtimes/concepts/stability.mdx +0 -68
  246. package/.docs/raw/docs/runtimes/concepts/threads.mdx +0 -513
  247. package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +0 -763
  248. package/.docs/raw/docs/runtimes/custom/data-stream.mdx +0 -339
  249. package/.docs/raw/docs/runtimes/custom/external-store.mdx +0 -957
  250. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +0 -902
  251. package/.docs/raw/docs/runtimes/custom/overview.mdx +0 -71
  252. package/.docs/raw/docs/runtimes/eve/overview.mdx +0 -151
  253. package/.docs/raw/docs/runtimes/eve/quickstart.mdx +0 -192
  254. package/.docs/raw/docs/runtimes/google-adk/api.mdx +0 -256
  255. package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +0 -784
  256. package/.docs/raw/docs/runtimes/google-adk/overview.mdx +0 -69
  257. package/.docs/raw/docs/runtimes/google-adk/quickstart.mdx +0 -229
  258. package/.docs/raw/docs/runtimes/langchain.mdx +0 -901
  259. package/.docs/raw/docs/runtimes/langgraph/agent-state.mdx +0 -181
  260. package/.docs/raw/docs/runtimes/langgraph/generative-ui.mdx +0 -305
  261. package/.docs/raw/docs/runtimes/langgraph/interrupts.mdx +0 -104
  262. package/.docs/raw/docs/runtimes/langgraph/overview.mdx +0 -84
  263. package/.docs/raw/docs/runtimes/langgraph/quickstart.mdx +0 -496
  264. package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +0 -144
  265. package/.docs/raw/docs/runtimes/langgraph/threads.mdx +0 -113
  266. package/.docs/raw/docs/runtimes/langgraph/tutorial/introduction.mdx +0 -29
  267. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-1.mdx +0 -93
  268. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +0 -347
  269. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +0 -393
  270. package/.docs/raw/docs/runtimes/opencode/hooks.mdx +0 -194
  271. package/.docs/raw/docs/runtimes/opencode/overview.mdx +0 -58
  272. package/.docs/raw/docs/runtimes/opencode/quickstart.mdx +0 -119
  273. package/.docs/raw/docs/runtimes/pick-a-runtime.mdx +0 -133
  274. package/.docs/raw/docs/tools/a2ui.mdx +0 -107
  275. package/.docs/raw/docs/tools/backend.mdx +0 -147
  276. package/.docs/raw/docs/tools/defining-tools.mdx +0 -574
  277. package/.docs/raw/docs/tools/dynamic-tools.mdx +0 -112
  278. package/.docs/raw/docs/tools/generative-ui-primitive.mdx +0 -180
  279. package/.docs/raw/docs/tools/generative-ui-slack.mdx +0 -167
  280. package/.docs/raw/docs/tools/generative-ui-teams.mdx +0 -160
  281. package/.docs/raw/docs/tools/generative-ui.mdx +0 -322
  282. package/.docs/raw/docs/tools/index.mdx +0 -72
  283. package/.docs/raw/docs/tools/interactables.mdx +0 -1084
  284. package/.docs/raw/docs/tools/mcp-apps.mdx +0 -363
  285. package/.docs/raw/docs/tools/mcp.mdx +0 -466
  286. package/.docs/raw/docs/tools/multi-agent.mdx +0 -237
  287. package/.docs/raw/docs/tools/openui.mdx +0 -175
  288. package/.docs/raw/docs/tools/tool-ui.mdx +0 -1020
  289. package/.docs/raw/docs/tools/user-managed-mcp.mdx +0 -428
  290. package/.docs/raw/docs/ui/assistant-modal.mdx +0 -166
  291. package/.docs/raw/docs/ui/assistant-sidebar.mdx +0 -89
  292. package/.docs/raw/docs/ui/attachment.mdx +0 -279
  293. package/.docs/raw/docs/ui/composer-trigger-popover.mdx +0 -247
  294. package/.docs/raw/docs/ui/context-display.mdx +0 -148
  295. package/.docs/raw/docs/ui/directive-text.mdx +0 -114
  296. package/.docs/raw/docs/ui/file.mdx +0 -159
  297. package/.docs/raw/docs/ui/follow-up-suggestions.mdx +0 -82
  298. package/.docs/raw/docs/ui/image.mdx +0 -102
  299. package/.docs/raw/docs/ui/markdown.mdx +0 -103
  300. package/.docs/raw/docs/ui/mcp-config.mdx +0 -107
  301. package/.docs/raw/docs/ui/mermaid.mdx +0 -92
  302. package/.docs/raw/docs/ui/message-timing.mdx +0 -93
  303. package/.docs/raw/docs/ui/model-selector.mdx +0 -455
  304. package/.docs/raw/docs/ui/part-grouping.mdx +0 -614
  305. package/.docs/raw/docs/ui/quote.mdx +0 -211
  306. package/.docs/raw/docs/ui/reasoning.mdx +0 -214
  307. package/.docs/raw/docs/ui/scrollbar.mdx +0 -77
  308. package/.docs/raw/docs/ui/sources.mdx +0 -186
  309. package/.docs/raw/docs/ui/streamdown.mdx +0 -392
  310. package/.docs/raw/docs/ui/syntax-highlighting.mdx +0 -205
  311. package/.docs/raw/docs/ui/thread-list.mdx +0 -411
  312. package/.docs/raw/docs/ui/thread.mdx +0 -559
  313. package/.docs/raw/docs/ui/tool-fallback.mdx +0 -141
  314. package/.docs/raw/docs/ui/tool-group.mdx +0 -240
  315. package/.docs/raw/docs/ui/voice.mdx +0 -173
  316. package/.docs/raw/docs/utilities/heat-graph.mdx +0 -237
  317. package/.docs/raw/docs/utilities/react-o11y.mdx +0 -362
  318. package/.docs/raw/docs/utilities/tw-shimmer.mdx +0 -212
  319. package/dist/constants.d.ts +0 -14
  320. package/dist/constants.d.ts.map +0 -1
  321. package/dist/constants.js +0 -19
  322. package/dist/constants.js.map +0 -1
  323. package/dist/prepare-docs/code-examples.d.ts +0 -5
  324. package/dist/prepare-docs/code-examples.d.ts.map +0 -1
  325. package/dist/prepare-docs/code-examples.js +0 -117
  326. package/dist/prepare-docs/code-examples.js.map +0 -1
  327. package/dist/prepare-docs/copy-raw.d.ts +0 -5
  328. package/dist/prepare-docs/copy-raw.d.ts.map +0 -1
  329. package/dist/prepare-docs/copy-raw.js +0 -49
  330. package/dist/prepare-docs/copy-raw.js.map +0 -1
  331. package/dist/prepare-docs/prepare.d.ts +0 -1
  332. package/dist/prepare-docs/prepare.js +0 -22
  333. package/dist/prepare-docs/prepare.js.map +0 -1
  334. package/dist/prompts/xulux-playground.d.ts +0 -12
  335. package/dist/prompts/xulux-playground.d.ts.map +0 -1
  336. package/dist/prompts/xulux-playground.js +0 -33
  337. package/dist/prompts/xulux-playground.js.map +0 -1
  338. package/dist/tools/docs.d.ts +0 -22
  339. package/dist/tools/docs.d.ts.map +0 -1
  340. package/dist/tools/docs.js +0 -177
  341. package/dist/tools/docs.js.map +0 -1
  342. package/dist/tools/examples.d.ts +0 -24
  343. package/dist/tools/examples.d.ts.map +0 -1
  344. package/dist/tools/examples.js +0 -87
  345. package/dist/tools/examples.js.map +0 -1
  346. package/dist/tools/resources.d.ts +0 -6
  347. package/dist/tools/resources.d.ts.map +0 -1
  348. package/dist/tools/resources.js +0 -75
  349. package/dist/tools/resources.js.map +0 -1
  350. package/dist/tools/search.d.ts +0 -24
  351. package/dist/tools/search.d.ts.map +0 -1
  352. package/dist/tools/search.js +0 -39
  353. package/dist/tools/search.js.map +0 -1
  354. package/dist/tools/tests/mcp-test-client.d.ts +0 -15
  355. package/dist/tools/tests/mcp-test-client.d.ts.map +0 -1
  356. package/dist/tools/tests/mcp-test-client.js +0 -68
  357. package/dist/tools/tests/mcp-test-client.js.map +0 -1
  358. package/dist/tools/tests/test-setup.d.ts +0 -7
  359. package/dist/tools/tests/test-setup.d.ts.map +0 -1
  360. package/dist/tools/tests/test-setup.js +0 -36
  361. package/dist/tools/tests/test-setup.js.map +0 -1
  362. package/dist/tools/xulux-templates.d.ts +0 -58
  363. package/dist/tools/xulux-templates.d.ts.map +0 -1
  364. package/dist/tools/xulux-templates.js +0 -84
  365. package/dist/tools/xulux-templates.js.map +0 -1
  366. package/dist/utils/cache.d.ts +0 -5
  367. package/dist/utils/cache.d.ts.map +0 -1
  368. package/dist/utils/cache.js +0 -18
  369. package/dist/utils/cache.js.map +0 -1
  370. package/dist/utils/logger.d.ts +0 -10
  371. package/dist/utils/logger.d.ts.map +0 -1
  372. package/dist/utils/logger.js +0 -20
  373. package/dist/utils/logger.js.map +0 -1
  374. package/dist/utils/mcp-format.d.ts +0 -11
  375. package/dist/utils/mcp-format.d.ts.map +0 -1
  376. package/dist/utils/mcp-format.js +0 -14
  377. package/dist/utils/mcp-format.js.map +0 -1
  378. package/dist/utils/mdx.d.ts +0 -12
  379. package/dist/utils/mdx.d.ts.map +0 -1
  380. package/dist/utils/mdx.js +0 -45
  381. package/dist/utils/mdx.js.map +0 -1
  382. package/dist/utils/paths.d.ts +0 -12
  383. package/dist/utils/paths.d.ts.map +0 -1
  384. package/dist/utils/paths.js +0 -93
  385. package/dist/utils/paths.js.map +0 -1
  386. package/dist/utils/search.d.ts +0 -10
  387. package/dist/utils/search.d.ts.map +0 -1
  388. package/dist/utils/search.js +0 -97
  389. package/dist/utils/search.js.map +0 -1
  390. package/dist/utils/security.d.ts +0 -5
  391. package/dist/utils/security.d.ts.map +0 -1
  392. package/dist/utils/security.js +0 -22
  393. package/dist/utils/security.js.map +0 -1
  394. package/dist/xulux/catalog-client.d.ts +0 -14
  395. package/dist/xulux/catalog-client.d.ts.map +0 -1
  396. package/dist/xulux/catalog-client.js +0 -112
  397. package/dist/xulux/catalog-client.js.map +0 -1
  398. package/dist/xulux/fallback-catalog.d.ts +0 -7
  399. package/dist/xulux/fallback-catalog.d.ts.map +0 -1
  400. package/dist/xulux/fallback-catalog.js +0 -47
  401. package/dist/xulux/fallback-catalog.js.map +0 -1
  402. package/dist/xulux/fetch-sandbox.d.ts +0 -5
  403. package/dist/xulux/fetch-sandbox.d.ts.map +0 -1
  404. package/dist/xulux/fetch-sandbox.js +0 -40
  405. package/dist/xulux/fetch-sandbox.js.map +0 -1
  406. package/dist/xulux/template-service.d.ts +0 -84
  407. package/dist/xulux/template-service.d.ts.map +0 -1
  408. package/dist/xulux/template-service.js +0 -223
  409. package/dist/xulux/template-service.js.map +0 -1
  410. package/dist/xulux/types.d.ts +0 -55
  411. package/dist/xulux/types.d.ts.map +0 -1
  412. package/dist/xulux/types.js +0 -6
  413. package/dist/xulux/types.js.map +0 -1
  414. package/src/constants.ts +0 -24
  415. package/src/prepare-docs/code-examples.ts +0 -158
  416. package/src/prepare-docs/copy-raw.ts +0 -50
  417. package/src/prepare-docs/prepare.ts +0 -24
  418. package/src/prompts/xulux-playground.ts +0 -36
  419. package/src/tools/docs.ts +0 -255
  420. package/src/tools/examples.ts +0 -114
  421. package/src/tools/resources.ts +0 -111
  422. package/src/tools/search.ts +0 -46
  423. package/src/tools/tests/completions.test.ts +0 -60
  424. package/src/tools/tests/directory-size-cap.test.ts +0 -50
  425. package/src/tools/tests/docs.test.ts +0 -147
  426. package/src/tools/tests/examples.test.ts +0 -94
  427. package/src/tools/tests/integration.test.ts +0 -45
  428. package/src/tools/tests/json-parsing.test.ts +0 -23
  429. package/src/tools/tests/listings-cache.test.ts +0 -19
  430. package/src/tools/tests/mcp-protocol.test.ts +0 -216
  431. package/src/tools/tests/mcp-test-client.ts +0 -111
  432. package/src/tools/tests/path-traversal.test.ts +0 -84
  433. package/src/tools/tests/resources.test.ts +0 -133
  434. package/src/tools/tests/search.test.ts +0 -37
  435. package/src/tools/tests/test-setup.ts +0 -50
  436. package/src/tools/tests/xulux-templates.test.ts +0 -325
  437. package/src/tools/xulux-templates.ts +0 -141
  438. package/src/utils/cache.ts +0 -20
  439. package/src/utils/logger.ts +0 -20
  440. package/src/utils/mcp-format.ts +0 -14
  441. package/src/utils/mdx.ts +0 -59
  442. package/src/utils/paths.ts +0 -139
  443. package/src/utils/search.ts +0 -131
  444. package/src/utils/security.ts +0 -52
  445. package/src/utils/tests/cache.test.ts +0 -51
  446. package/src/utils/tests/mcp-format.test.ts +0 -22
  447. package/src/utils/tests/security.test.ts +0 -119
  448. package/src/xulux/catalog-client.ts +0 -150
  449. package/src/xulux/fallback-catalog.ts +0 -63
  450. package/src/xulux/fetch-sandbox.ts +0 -56
  451. package/src/xulux/template-service.ts +0 -406
  452. package/src/xulux/types.ts +0 -64
@@ -1,1974 +0,0 @@
1
- # Example: with-opencode
2
-
3
- ## app/globals.css
4
-
5
- ```css
6
- @import "tailwindcss";
7
- @import "tw-animate-css";
8
-
9
- @source "../../../packages/ui/src";
10
-
11
- @custom-variant dark (&:is(.dark *));
12
-
13
- @theme inline {
14
- --color-background: var(--background);
15
- --color-foreground: var(--foreground);
16
- --font-sans: var(--font-geist-sans);
17
- --font-mono: var(--font-geist-mono);
18
- --color-sidebar-ring: var(--sidebar-ring);
19
- --color-sidebar-border: var(--sidebar-border);
20
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
21
- --color-sidebar-accent: var(--sidebar-accent);
22
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
23
- --color-sidebar-primary: var(--sidebar-primary);
24
- --color-sidebar-foreground: var(--sidebar-foreground);
25
- --color-sidebar: var(--sidebar);
26
- --color-chart-5: var(--chart-5);
27
- --color-chart-4: var(--chart-4);
28
- --color-chart-3: var(--chart-3);
29
- --color-chart-2: var(--chart-2);
30
- --color-chart-1: var(--chart-1);
31
- --color-ring: var(--ring);
32
- --color-input: var(--input);
33
- --color-border: var(--border);
34
- --color-destructive: var(--destructive);
35
- --color-accent-foreground: var(--accent-foreground);
36
- --color-accent: var(--accent);
37
- --color-muted-foreground: var(--muted-foreground);
38
- --color-muted: var(--muted);
39
- --color-secondary-foreground: var(--secondary-foreground);
40
- --color-secondary: var(--secondary);
41
- --color-primary-foreground: var(--primary-foreground);
42
- --color-primary: var(--primary);
43
- --color-popover-foreground: var(--popover-foreground);
44
- --color-popover: var(--popover);
45
- --color-card-foreground: var(--card-foreground);
46
- --color-card: var(--card);
47
- --radius-sm: calc(var(--radius) - 4px);
48
- --radius-md: calc(var(--radius) - 2px);
49
- --radius-lg: var(--radius);
50
- --radius-xl: calc(var(--radius) + 4px);
51
- }
52
-
53
- :root {
54
- --radius: 0.625rem;
55
- --background: oklch(1 0 0);
56
- --foreground: oklch(0.141 0.005 285.823);
57
- --card: oklch(1 0 0);
58
- --card-foreground: oklch(0.141 0.005 285.823);
59
- --popover: oklch(1 0 0);
60
- --popover-foreground: oklch(0.141 0.005 285.823);
61
- --primary: oklch(0.21 0.006 285.885);
62
- --primary-foreground: oklch(0.985 0 0);
63
- --secondary: oklch(0.967 0.001 286.375);
64
- --secondary-foreground: oklch(0.21 0.006 285.885);
65
- --muted: oklch(0.967 0.001 286.375);
66
- --muted-foreground: oklch(0.552 0.016 285.938);
67
- --accent: oklch(0.967 0.001 286.375);
68
- --accent-foreground: oklch(0.21 0.006 285.885);
69
- --destructive: oklch(0.577 0.245 27.325);
70
- --border: oklch(0.92 0.004 286.32);
71
- --input: oklch(0.92 0.004 286.32);
72
- --ring: oklch(0.705 0.015 286.067);
73
- --chart-1: oklch(0.646 0.222 41.116);
74
- --chart-2: oklch(0.6 0.118 184.704);
75
- --chart-3: oklch(0.398 0.07 227.392);
76
- --chart-4: oklch(0.828 0.189 84.429);
77
- --chart-5: oklch(0.769 0.188 70.08);
78
- --sidebar: oklch(0.985 0 0);
79
- --sidebar-foreground: oklch(0.141 0.005 285.823);
80
- --sidebar-primary: oklch(0.21 0.006 285.885);
81
- --sidebar-primary-foreground: oklch(0.985 0 0);
82
- --sidebar-accent: oklch(0.967 0.001 286.375);
83
- --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
84
- --sidebar-border: oklch(0.92 0.004 286.32);
85
- --sidebar-ring: oklch(0.705 0.015 286.067);
86
- }
87
-
88
- :root.dark,
89
- html.dark,
90
- body.dark,
91
- .dark {
92
- --background: oklch(0.141 0.005 285.823);
93
- --foreground: oklch(0.985 0 0);
94
- --card: oklch(0.21 0.006 285.885);
95
- --card-foreground: oklch(0.985 0 0);
96
- --popover: oklch(0.21 0.006 285.885);
97
- --popover-foreground: oklch(0.985 0 0);
98
- --primary: oklch(0.92 0.004 286.32);
99
- --primary-foreground: oklch(0.21 0.006 285.885);
100
- --secondary: oklch(0.274 0.006 286.033);
101
- --secondary-foreground: oklch(0.985 0 0);
102
- --muted: oklch(0.274 0.006 286.033);
103
- --muted-foreground: oklch(0.705 0.015 286.067);
104
- --accent: oklch(0.274 0.006 286.033);
105
- --accent-foreground: oklch(0.985 0 0);
106
- --destructive: oklch(0.704 0.191 22.216);
107
- --border: oklch(1 0 0 / 10%);
108
- --input: oklch(1 0 0 / 15%);
109
- --ring: oklch(0.552 0.016 285.938);
110
- --chart-1: oklch(0.488 0.243 264.376);
111
- --chart-2: oklch(0.696 0.17 162.48);
112
- --chart-3: oklch(0.769 0.188 70.08);
113
- --chart-4: oklch(0.627 0.265 303.9);
114
- --chart-5: oklch(0.645 0.246 16.439);
115
- --sidebar: oklch(0.21 0.006 285.885);
116
- --sidebar-foreground: oklch(0.985 0 0);
117
- --sidebar-primary: oklch(0.488 0.243 264.376);
118
- --sidebar-primary-foreground: oklch(0.985 0 0);
119
- --sidebar-accent: oklch(0.274 0.006 286.033);
120
- --sidebar-accent-foreground: oklch(0.985 0 0);
121
- --sidebar-border: oklch(1 0 0 / 10%);
122
- --sidebar-ring: oklch(0.552 0.016 285.938);
123
- }
124
-
125
- @layer base {
126
- * {
127
- @apply border-border outline-ring/50;
128
- }
129
-
130
- :root {
131
- color-scheme: light;
132
- }
133
-
134
- :root.dark {
135
- color-scheme: dark;
136
- }
137
-
138
- body {
139
- @apply bg-background text-foreground;
140
- }
141
- }
142
-
143
- ```
144
-
145
- ## app/layout.tsx
146
-
147
- ```tsx
148
- import type { Metadata } from "next";
149
- import { Geist, Geist_Mono } from "next/font/google";
150
- import { TooltipProvider } from "@/components/ui/tooltip";
151
- import "./globals.css";
152
-
153
- const geistSans = Geist({
154
- variable: "--font-geist-sans",
155
- subsets: ["latin"],
156
- });
157
-
158
- const geistMono = Geist_Mono({
159
- variable: "--font-geist-mono",
160
- subsets: ["latin"],
161
- });
162
-
163
- export const metadata: Metadata = {
164
- title: "assistant-ui OpenCode Example",
165
- description: "An OpenCode runtime example built with assistant-ui",
166
- };
167
-
168
- export default function RootLayout({
169
- children,
170
- }: Readonly<{
171
- children: React.ReactNode;
172
- }>) {
173
- return (
174
- <html lang="en" className="dark h-full">
175
- <body
176
- className={`${geistSans.variable} ${geistMono.variable} dark bg-background text-foreground h-full overflow-hidden antialiased`}
177
- >
178
- <TooltipProvider>{children}</TooltipProvider>
179
- </body>
180
- </html>
181
- );
182
- }
183
-
184
- ```
185
-
186
- ## app/page.tsx
187
-
188
- ```tsx
189
- "use client";
190
-
191
- import {
192
- AssistantRuntimeProvider,
193
- AuiConfig,
194
- Tools,
195
- useAui,
196
- } from "@assistant-ui/react";
197
- import {
198
- SidebarInset,
199
- SidebarProvider,
200
- SidebarTrigger,
201
- } from "@/components/ui/radix/sidebar";
202
- import { Separator } from "@/components/ui/radix/separator";
203
- import { ThreadListSidebar } from "@/components/assistant-ui/threadlist-sidebar.radix";
204
- import {
205
- useOpenCodeRuntime,
206
- useOpenCodeSession,
207
- } from "@assistant-ui/react-opencode";
208
- import {
209
- Thread,
210
- type ThreadComponents,
211
- } from "@/components/assistant-ui/thread";
212
- import { OpenCodeDataPart } from "@/components/opencode-data-part";
213
- import { FallbackTool } from "@/components/tools/opencode-tools";
214
- import { ReasoningGroup } from "@/components/tools/reasoning-ghost";
215
- import { ToolGroup } from "@/components/tools/tool-group";
216
- import toolkit from "@/components/tools/toolkit";
217
- import { useEffect } from "react";
218
-
219
- const SetFallbackDataUI = () => {
220
- const aui = useAui();
221
- useEffect(() => aui.dataRenderers.setFallbackDataUI(OpenCodeDataPart), [aui]);
222
- return null;
223
- };
224
-
225
- const THREAD_COMPONENTS: ThreadComponents = {
226
- ToolFallback: FallbackTool,
227
- ToolGroup,
228
- ReasoningGroup,
229
- };
230
-
231
- export default function Home() {
232
- const runtime = useOpenCodeRuntime({
233
- baseUrl:
234
- process.env.NEXT_PUBLIC_OPENCODE_BASE_URL ?? "http://localhost:4096",
235
- });
236
-
237
- const config = AuiConfig({
238
- tools: Tools({ toolkit }),
239
- });
240
-
241
- return (
242
- <AssistantRuntimeProvider config={config} runtime={runtime}>
243
- <SetFallbackDataUI />
244
- <SidebarProvider>
245
- <div className="flex h-dvh w-full overflow-hidden pr-0.5">
246
- <ThreadListSidebar />
247
- <SidebarInset>
248
- <header className="flex h-16 shrink-0 items-center gap-2 border-b px-4">
249
- <SidebarTrigger />
250
- <Separator orientation="vertical" className="mr-2 h-4" />
251
- <div className="min-w-0">
252
- <div className="text-sm font-medium">OpenCode</div>
253
- <SelectedSessionTitle />
254
- </div>
255
- </header>
256
- <div className="min-h-0 flex-1 overflow-hidden">
257
- <Thread components={THREAD_COMPONENTS} />
258
- </div>
259
- </SidebarInset>
260
- </div>
261
- </SidebarProvider>
262
- </AssistantRuntimeProvider>
263
- );
264
- }
265
-
266
- function SelectedSessionTitle() {
267
- const session = useOpenCodeSession();
268
- const title = session?.title?.trim();
269
-
270
- if (!title) return null;
271
-
272
- return (
273
- <div className="text-muted-foreground truncate text-xs leading-tight">
274
- {title}
275
- </div>
276
- );
277
- }
278
-
279
- ```
280
-
281
- ## components.json
282
-
283
- ```json
284
- {
285
- "$schema": "https://ui.shadcn.com/schema.json",
286
- "style": "new-york",
287
- "rsc": true,
288
- "tsx": true,
289
- "tailwind": {
290
- "config": "",
291
- "css": "app/globals.css",
292
- "baseColor": "zinc",
293
- "cssVariables": true,
294
- "prefix": ""
295
- },
296
- "aliases": {
297
- "components": "@/components",
298
- "utils": "@/lib/utils",
299
- "ui": "@/components/ui",
300
- "lib": "@/lib",
301
- "hooks": "@/hooks"
302
- },
303
- "iconLibrary": "lucide",
304
- "registries": {
305
- "@assistant-ui": "https://r.assistant-ui.com/{name}.json"
306
- }
307
- }
308
-
309
- ```
310
-
311
- ## components/opencode-data-part.tsx
312
-
313
- ```tsx
314
- "use client";
315
-
316
- import { WrenchIcon } from "lucide-react";
317
- import type { FC } from "react";
318
-
319
- type OpenCodeDataPartProps = {
320
- name: string;
321
- data: unknown;
322
- };
323
-
324
- const getSummary = (name: string, data: unknown) => {
325
- if (
326
- name === "opencode-unsupported-part" &&
327
- typeof data === "object" &&
328
- data !== null &&
329
- "type" in data &&
330
- typeof data.type === "string"
331
- ) {
332
- return `Unsupported part: ${data.type}`;
333
- }
334
-
335
- if (name === "opencode-snapshot") return "Snapshot";
336
- if (name === "opencode-retry") return "Retry";
337
- if (name === "opencode-compaction") return "Compaction";
338
- if (name === "opencode-agent") return "Agent event";
339
- if (name === "opencode-subtask") return "Subtask";
340
-
341
- return name.replace(/^opencode-/, "").replace(/-/g, " ");
342
- };
343
-
344
- export const OpenCodeDataPart: FC<OpenCodeDataPartProps> = ({ name, data }) => {
345
- const shouldHide =
346
- name === "opencode-step-start" ||
347
- name === "opencode-step-finish" ||
348
- name === "opencode-patch";
349
-
350
- if (shouldHide) {
351
- return null;
352
- }
353
-
354
- const summary = getSummary(name, data);
355
-
356
- return (
357
- <div className="bg-muted/30 text-muted-foreground my-3 flex w-min items-center gap-2 rounded-full border px-2 py-1 text-xs text-nowrap">
358
- <WrenchIcon className="size-3" />
359
- {summary}
360
- </div>
361
- );
362
- };
363
-
364
- ```
365
-
366
- ## components/tools/opencode-permission-card.tsx
367
-
368
- ```tsx
369
- "use client";
370
-
371
- import { useState } from "react";
372
- import {
373
- type OpenCodePermissionRequest,
374
- type OpenCodePermissionResponse,
375
- useOpenCodeRuntimeExtras,
376
- } from "@assistant-ui/react-opencode";
377
- import {
378
- CheckCircle2Icon,
379
- LoaderIcon,
380
- ShieldAlertIcon,
381
- XCircleIcon,
382
- } from "lucide-react";
383
- import { Badge } from "@/components/ui/badge";
384
- import { Button } from "@/components/ui/button";
385
- import {
386
- Card,
387
- CardContent,
388
- CardDescription,
389
- CardFooter,
390
- CardHeader,
391
- CardTitle,
392
- } from "@/components/ui/card";
393
-
394
- const formatPermissionReply = (reply: OpenCodePermissionResponse) => {
395
- switch (reply) {
396
- case "always":
397
- return "Always allow";
398
- case "once":
399
- return "Allowed once";
400
- case "reject":
401
- return "Rejected";
402
- }
403
- };
404
-
405
- export const OpenCodePermissionCard = ({
406
- request,
407
- state,
408
- reply,
409
- }: {
410
- request: OpenCodePermissionRequest;
411
- state: "pending" | "resolved";
412
- reply?: OpenCodePermissionResponse;
413
- }) => {
414
- const { replyToPermission } = useOpenCodeRuntimeExtras();
415
- const [submittingReply, setSubmittingReply] =
416
- useState<OpenCodePermissionResponse | null>(null);
417
- const [error, setError] = useState<string | null>(null);
418
-
419
- const submitReply = async (nextReply: OpenCodePermissionResponse) => {
420
- setSubmittingReply(nextReply);
421
- setError(null);
422
-
423
- try {
424
- await replyToPermission(request.id, nextReply);
425
- } catch (err) {
426
- setError(err instanceof Error ? err.message : "Failed to submit reply.");
427
- setSubmittingReply(null);
428
- }
429
- };
430
-
431
- const isPending = state === "pending";
432
-
433
- return (
434
- <Card className="gap-4 py-4">
435
- <CardHeader className="px-4">
436
- <div className="flex items-start gap-3">
437
- <div className="rounded-md bg-amber-500/10 p-2 text-amber-700">
438
- {isPending ? (
439
- <ShieldAlertIcon className="size-4" />
440
- ) : reply === "reject" ? (
441
- <XCircleIcon className="size-4" />
442
- ) : (
443
- <CheckCircle2Icon className="size-4" />
444
- )}
445
- </div>
446
- <div className="min-w-0 flex-1">
447
- <CardTitle className="text-sm">
448
- {isPending ? "Approval required" : formatPermissionReply(reply!)}
449
- </CardTitle>
450
- <CardDescription className="mt-1 text-xs">
451
- {request.title ??
452
- `OpenCode requested approval for ${request.toolName ?? request.permission}.`}
453
- </CardDescription>
454
- </div>
455
- <Badge variant={isPending ? "secondary" : "outline"}>
456
- {request.toolName ?? request.permission}
457
- </Badge>
458
- </div>
459
- </CardHeader>
460
-
461
- {(request.patterns.length > 0 || request.toolInput !== undefined) && (
462
- <CardContent className="space-y-3 px-4">
463
- {request.patterns.length > 0 ? (
464
- <div className="flex flex-wrap gap-2">
465
- {request.patterns.map((pattern) => (
466
- <Badge key={pattern} variant="outline" className="font-mono">
467
- {pattern}
468
- </Badge>
469
- ))}
470
- </div>
471
- ) : null}
472
- {request.toolInput !== undefined ? (
473
- <pre className="bg-muted overflow-x-auto rounded-md p-3 text-xs">
474
- {JSON.stringify(request.toolInput, null, 2)}
475
- </pre>
476
- ) : null}
477
- </CardContent>
478
- )}
479
-
480
- {isPending ? (
481
- <CardFooter className="flex flex-wrap gap-2 px-4">
482
- <Button
483
- size="sm"
484
- onClick={() => submitReply("once")}
485
- disabled={submittingReply !== null}
486
- >
487
- {submittingReply === "once" && (
488
- <LoaderIcon className="size-4 animate-spin" />
489
- )}
490
- Allow once
491
- </Button>
492
- <Button
493
- size="sm"
494
- variant="secondary"
495
- onClick={() => submitReply("always")}
496
- disabled={submittingReply !== null}
497
- >
498
- {submittingReply === "always" && (
499
- <LoaderIcon className="size-4 animate-spin" />
500
- )}
501
- Always allow
502
- </Button>
503
- <Button
504
- size="sm"
505
- variant="outline"
506
- onClick={() => submitReply("reject")}
507
- disabled={submittingReply !== null}
508
- >
509
- {submittingReply === "reject" && (
510
- <LoaderIcon className="size-4 animate-spin" />
511
- )}
512
- Reject
513
- </Button>
514
- {error ? (
515
- <p className="text-destructive w-full text-xs">{error}</p>
516
- ) : null}
517
- </CardFooter>
518
- ) : null}
519
- </Card>
520
- );
521
- };
522
-
523
- ```
524
-
525
- ## components/tools/opencode-question-card.tsx
526
-
527
- ```tsx
528
- "use client";
529
-
530
- import { useMemo, useState } from "react";
531
- import {
532
- type OpenCodeQuestionRequest,
533
- type QuestionAnswer,
534
- useOpenCodeRuntimeExtras,
535
- } from "@assistant-ui/react-opencode";
536
- import {
537
- AlertCircleIcon,
538
- CheckCircle2Icon,
539
- CircleHelpIcon,
540
- LoaderIcon,
541
- XCircleIcon,
542
- } from "lucide-react";
543
- import { Badge } from "@/components/ui/badge";
544
- import { Button } from "@/components/ui/button";
545
- import {
546
- Card,
547
- CardContent,
548
- CardDescription,
549
- CardFooter,
550
- CardHeader,
551
- CardTitle,
552
- } from "@/components/ui/card";
553
- import { Checkbox } from "@/components/ui/checkbox";
554
- import { Label } from "@/components/ui/label";
555
- import { Textarea } from "@/components/ui/textarea";
556
- import { cn } from "@/lib/utils";
557
-
558
- type PendingQuestionState = {
559
- selectedLabels: string[];
560
- customAnswer: string;
561
- };
562
-
563
- const buildEmptyQuestionState = (
564
- request: OpenCodeQuestionRequest,
565
- ): PendingQuestionState[] =>
566
- request.questions.map(() => ({
567
- selectedLabels: [],
568
- customAnswer: "",
569
- }));
570
-
571
- const encodeQuestionAnswers = (
572
- request: OpenCodeQuestionRequest,
573
- formState: PendingQuestionState[],
574
- ): QuestionAnswer[] =>
575
- request.questions.map((question, index) => {
576
- const current = formState[index];
577
- if (!current) return [];
578
-
579
- const answers = [...current.selectedLabels];
580
- const customValue = current.customAnswer.trim();
581
- const customAllowed = question.custom !== false;
582
-
583
- if (customAllowed && customValue) {
584
- answers.push(`Other: ${customValue}`);
585
- }
586
-
587
- return answers;
588
- });
589
-
590
- export const getQuestionSummary = (request?: OpenCodeQuestionRequest) => {
591
- if (!request || request.questions.length === 0) return "Waiting for input";
592
- if (request.questions.length === 1) {
593
- return request.questions[0]?.header || "1 question";
594
- }
595
- return `${request.questions.length} questions`;
596
- };
597
-
598
- export const OpenCodeQuestionCard = ({
599
- request,
600
- state,
601
- answers,
602
- }: {
603
- request: OpenCodeQuestionRequest;
604
- state: "pending" | "answered" | "rejected";
605
- answers?: readonly QuestionAnswer[];
606
- }) => {
607
- const { replyToQuestion, rejectQuestion } = useOpenCodeRuntimeExtras();
608
- const [formState, setFormState] = useState(() =>
609
- buildEmptyQuestionState(request),
610
- );
611
- const [submissionState, setSubmissionState] = useState<
612
- "reply" | "reject" | null
613
- >(null);
614
- const [error, setError] = useState<string | null>(null);
615
- const isPending = state === "pending";
616
-
617
- const encodedAnswers = useMemo(
618
- () => encodeQuestionAnswers(request, formState),
619
- [formState, request],
620
- );
621
-
622
- const canSubmit =
623
- isPending && encodedAnswers.every((answer) => answer.length > 0);
624
-
625
- const updateQuestionState = (
626
- index: number,
627
- updater: (current: PendingQuestionState) => PendingQuestionState,
628
- ) => {
629
- setFormState((current) =>
630
- current.map((item, itemIndex) =>
631
- itemIndex === index ? updater(item) : item,
632
- ),
633
- );
634
- };
635
-
636
- const submitAnswers = async () => {
637
- if (!canSubmit) return;
638
- setSubmissionState("reply");
639
- setError(null);
640
-
641
- try {
642
- await replyToQuestion(request.id, encodedAnswers);
643
- } catch (err) {
644
- setError(
645
- err instanceof Error ? err.message : "Failed to submit answers.",
646
- );
647
- setSubmissionState(null);
648
- }
649
- };
650
-
651
- const rejectRequest = async () => {
652
- setSubmissionState("reject");
653
- setError(null);
654
-
655
- try {
656
- await rejectQuestion(request.id);
657
- } catch (err) {
658
- setError(
659
- err instanceof Error ? err.message : "Failed to reject request.",
660
- );
661
- setSubmissionState(null);
662
- }
663
- };
664
-
665
- return (
666
- <Card className="gap-4 py-4">
667
- <CardHeader className="px-4">
668
- <div className="flex items-start gap-3">
669
- <div
670
- className={cn(
671
- "rounded-md p-2",
672
- isPending
673
- ? "bg-sky-500/10 text-sky-700"
674
- : state === "rejected"
675
- ? "bg-muted text-muted-foreground"
676
- : "bg-emerald-500/10 text-emerald-700",
677
- )}
678
- >
679
- {isPending ? (
680
- <CircleHelpIcon className="size-4" />
681
- ) : state === "rejected" ? (
682
- <XCircleIcon className="size-4" />
683
- ) : (
684
- <CheckCircle2Icon className="size-4" />
685
- )}
686
- </div>
687
- <div className="min-w-0 flex-1">
688
- <CardTitle className="text-sm">
689
- {isPending
690
- ? "Answer required"
691
- : state === "rejected"
692
- ? "Question dismissed"
693
- : "Answers submitted"}
694
- </CardTitle>
695
- <CardDescription className="mt-1 text-xs">
696
- {getQuestionSummary(request)}
697
- </CardDescription>
698
- </div>
699
- <Badge variant={isPending ? "secondary" : "outline"}>
700
- {request.questions.length === 1
701
- ? "1 prompt"
702
- : `${request.questions.length} prompts`}
703
- </Badge>
704
- </div>
705
- </CardHeader>
706
-
707
- <CardContent className="space-y-4 px-4">
708
- {request.questions.map((question, index) => {
709
- const current = formState[index] ?? {
710
- selectedLabels: [],
711
- customAnswer: "",
712
- };
713
- const answeredValues = answers?.[index] ?? [];
714
- const customAllowed = question.custom !== false;
715
-
716
- return (
717
- <section key={`${request.id}-${index}`} className="space-y-3">
718
- <div className="space-y-1">
719
- <p className="text-sm font-medium">{question.header}</p>
720
- <p className="text-muted-foreground text-xs">
721
- {question.question}
722
- </p>
723
- </div>
724
-
725
- {isPending ? (
726
- <>
727
- {question.options.length > 0 ? (
728
- question.multiple ? (
729
- <div className="space-y-2">
730
- {question.options.map((option) => {
731
- const checked = current.selectedLabels.includes(
732
- option.label,
733
- );
734
- const optionId = `${request.id}-${index}-${option.label}`;
735
-
736
- return (
737
- <div
738
- key={option.label}
739
- className="flex items-start gap-3 rounded-lg border p-3 text-sm"
740
- >
741
- <Checkbox
742
- id={optionId}
743
- checked={checked}
744
- onCheckedChange={(nextChecked) => {
745
- updateQuestionState(index, (item) => ({
746
- ...item,
747
- selectedLabels:
748
- nextChecked === true
749
- ? [...item.selectedLabels, option.label]
750
- : item.selectedLabels.filter(
751
- (label) => label !== option.label,
752
- ),
753
- }));
754
- }}
755
- />
756
- <div className="space-y-1">
757
- <Label
758
- htmlFor={optionId}
759
- className="text-sm font-medium"
760
- >
761
- {option.label}
762
- </Label>
763
- <p className="text-muted-foreground text-xs">
764
- {option.description}
765
- </p>
766
- </div>
767
- </div>
768
- );
769
- })}
770
- </div>
771
- ) : (
772
- <div className="flex flex-wrap gap-2">
773
- {question.options.map((option) => {
774
- const selected = current.selectedLabels.includes(
775
- option.label,
776
- );
777
-
778
- return (
779
- <Button
780
- key={option.label}
781
- type="button"
782
- size="sm"
783
- variant={selected ? "default" : "outline"}
784
- onClick={() => {
785
- updateQuestionState(index, (item) => ({
786
- ...item,
787
- selectedLabels: [option.label],
788
- customAnswer: "",
789
- }));
790
- }}
791
- >
792
- {option.label}
793
- </Button>
794
- );
795
- })}
796
- </div>
797
- )
798
- ) : null}
799
-
800
- {customAllowed ? (
801
- <div className="space-y-2">
802
- <Label htmlFor={`${request.id}-${index}-custom`}>
803
- Other
804
- </Label>
805
- <Textarea
806
- id={`${request.id}-${index}-custom`}
807
- value={current.customAnswer}
808
- onChange={(event) => {
809
- const nextValue = event.target.value;
810
- updateQuestionState(index, (item) => ({
811
- ...item,
812
- customAnswer: nextValue,
813
- selectedLabels:
814
- question.multiple || nextValue.trim().length === 0
815
- ? item.selectedLabels
816
- : [],
817
- }));
818
- }}
819
- placeholder="Add your answer"
820
- rows={3}
821
- />
822
- </div>
823
- ) : null}
824
- </>
825
- ) : state === "rejected" ? (
826
- <p className="text-muted-foreground text-xs">
827
- No answer was submitted.
828
- </p>
829
- ) : (
830
- <div className="flex flex-wrap gap-2">
831
- {answeredValues.map((answer) => (
832
- <Badge key={answer} variant="outline">
833
- {answer}
834
- </Badge>
835
- ))}
836
- </div>
837
- )}
838
- </section>
839
- );
840
- })}
841
- </CardContent>
842
-
843
- {isPending ? (
844
- <CardFooter className="flex flex-wrap gap-2 px-4">
845
- <Button
846
- size="sm"
847
- onClick={submitAnswers}
848
- disabled={!canSubmit || submissionState !== null}
849
- >
850
- {submissionState === "reply" && (
851
- <LoaderIcon className="size-4 animate-spin" />
852
- )}
853
- Submit answers
854
- </Button>
855
- <Button
856
- size="sm"
857
- variant="outline"
858
- onClick={rejectRequest}
859
- disabled={submissionState !== null}
860
- >
861
- {submissionState === "reject" && (
862
- <LoaderIcon className="size-4 animate-spin" />
863
- )}
864
- Dismiss
865
- </Button>
866
- {error ? (
867
- <div className="text-destructive flex w-full items-center gap-2 text-xs">
868
- <AlertCircleIcon className="size-3.5" />
869
- <span>{error}</span>
870
- </div>
871
- ) : null}
872
- </CardFooter>
873
- ) : null}
874
- </Card>
875
- );
876
- };
877
-
878
- ```
879
-
880
- ## components/tools/opencode-tool-interactions.tsx
881
-
882
- ```tsx
883
- "use client";
884
-
885
- import { memo, useMemo } from "react";
886
- import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
887
- import {
888
- type OpenCodePermissionRequest,
889
- type OpenCodePermissionResponse,
890
- type OpenCodeQuestionRequest,
891
- type QuestionAnswer,
892
- useOpenCodeThreadState,
893
- } from "@assistant-ui/react-opencode";
894
- import {
895
- CheckCircle2Icon,
896
- CircleHelpIcon,
897
- LoaderIcon,
898
- XCircleIcon,
899
- } from "lucide-react";
900
- import { OpenCodePermissionCard } from "@/components/tools/opencode-permission-card";
901
- import {
902
- OpenCodeQuestionCard,
903
- getQuestionSummary,
904
- } from "@/components/tools/opencode-question-card";
905
-
906
- type ToolPermissionInteraction = {
907
- request: OpenCodePermissionRequest;
908
- state: "pending" | "resolved";
909
- reply?: OpenCodePermissionResponse;
910
- };
911
-
912
- type ToolQuestionInteraction = {
913
- request: OpenCodeQuestionRequest;
914
- state: "pending" | "answered" | "rejected";
915
- answers?: readonly QuestionAnswer[];
916
- };
917
-
918
- const ASK_QUESTION_TOOL_NAMES = [
919
- "ask_question",
920
- "request_user_input",
921
- "requestUserInput",
922
- ] as const;
923
-
924
- const useOpenCodeToolInteractions = (toolCallId: string) => {
925
- const interactions = useOpenCodeThreadState((state) => state.interactions);
926
-
927
- return useMemo(() => {
928
- const permissions: ToolPermissionInteraction[] = [];
929
- const questions: ToolQuestionInteraction[] = [];
930
-
931
- for (const request of Object.values(interactions.permissions.pending)) {
932
- if (request.tool?.callID === toolCallId) {
933
- permissions.push({ request, state: "pending" });
934
- }
935
- }
936
-
937
- for (const resolved of Object.values(interactions.permissions.resolved)) {
938
- if (resolved.request.tool?.callID === toolCallId) {
939
- permissions.push({
940
- request: resolved.request,
941
- state: "resolved",
942
- reply: resolved.reply,
943
- });
944
- }
945
- }
946
-
947
- for (const request of Object.values(interactions.questions.pending)) {
948
- if (request.tool?.callID === toolCallId) {
949
- questions.push({ request, state: "pending" });
950
- }
951
- }
952
-
953
- for (const answered of Object.values(interactions.questions.answered)) {
954
- if (answered.request.tool?.callID === toolCallId) {
955
- questions.push({
956
- request: answered.request,
957
- state: "answered",
958
- answers: answered.answers,
959
- });
960
- }
961
- }
962
-
963
- for (const rejected of Object.values(interactions.questions.rejected)) {
964
- if (rejected.request.tool?.callID === toolCallId) {
965
- questions.push({
966
- request: rejected.request,
967
- state: "rejected",
968
- });
969
- }
970
- }
971
-
972
- return { permissions, questions };
973
- }, [interactions, toolCallId]);
974
- };
975
-
976
- const ToolInteractionStack = ({ toolCallId }: { toolCallId: string }) => {
977
- const { permissions, questions } = useOpenCodeToolInteractions(toolCallId);
978
-
979
- if (permissions.length === 0 && questions.length === 0) {
980
- return null;
981
- }
982
-
983
- return (
984
- <div className="space-y-3">
985
- {permissions.map((permission) => (
986
- <OpenCodePermissionCard
987
- key={`${permission.request.id}-${permission.state}`}
988
- request={permission.request}
989
- state={permission.state}
990
- {...(permission.reply ? { reply: permission.reply } : {})}
991
- />
992
- ))}
993
- {questions.map((question) => (
994
- <OpenCodeQuestionCard
995
- key={`${question.request.id}-${question.state}`}
996
- request={question.request}
997
- state={question.state}
998
- {...(question.answers ? { answers: question.answers } : {})}
999
- />
1000
- ))}
1001
- </div>
1002
- );
1003
- };
1004
-
1005
- export const withOpenCodeToolInteractions = <TArgs = any, TResult = any>(
1006
- BaseComponent: ToolCallMessagePartComponent<TArgs, TResult>,
1007
- ): ToolCallMessagePartComponent<TArgs, TResult> => {
1008
- const WrappedComponent: ToolCallMessagePartComponent<TArgs, TResult> = memo(
1009
- (props) => {
1010
- return (
1011
- <div className="space-y-3">
1012
- <BaseComponent {...props} />
1013
- <ToolInteractionStack toolCallId={props.toolCallId} />
1014
- </div>
1015
- );
1016
- },
1017
- );
1018
-
1019
- WrappedComponent.displayName = `withOpenCodeToolInteractions(${
1020
- BaseComponent.displayName || BaseComponent.name || "Tool"
1021
- })`;
1022
-
1023
- return WrappedComponent;
1024
- };
1025
-
1026
- export const AskQuestionInline: ToolCallMessagePartComponent = memo(
1027
- ({ toolCallId, toolName, status }) => {
1028
- const { questions } = useOpenCodeToolInteractions(toolCallId);
1029
- const primaryQuestion = questions[0]?.request;
1030
- const label = getQuestionSummary(primaryQuestion);
1031
- const statusType = status?.type ?? "complete";
1032
-
1033
- return (
1034
- <div className="text-muted-foreground flex items-center gap-2 py-0.5 text-sm">
1035
- {statusType === "requires-action" ? (
1036
- <CircleHelpIcon className="size-3.5 shrink-0" />
1037
- ) : statusType === "running" ? (
1038
- <LoaderIcon className="size-3.5 shrink-0 animate-spin" />
1039
- ) : statusType === "incomplete" ? (
1040
- <XCircleIcon className="text-destructive size-3.5 shrink-0" />
1041
- ) : (
1042
- <CheckCircle2Icon className="size-3.5 shrink-0" />
1043
- )}
1044
-
1045
- <span className="flex items-center gap-1.5 truncate">
1046
- <span className="font-medium">
1047
- {ASK_QUESTION_TOOL_NAMES.includes(
1048
- toolName as (typeof ASK_QUESTION_TOOL_NAMES)[number],
1049
- )
1050
- ? "ask_question"
1051
- : toolName}
1052
- </span>
1053
- <span className="opacity-60">{label}</span>
1054
- </span>
1055
- </div>
1056
- );
1057
- },
1058
- );
1059
- AskQuestionInline.displayName = "AskQuestionInline";
1060
-
1061
- ```
1062
-
1063
- ## components/tools/opencode-tools.tsx
1064
-
1065
- ```tsx
1066
- "use client";
1067
-
1068
- import {
1069
- AskQuestionInline,
1070
- withOpenCodeToolInteractions,
1071
- } from "./opencode-tool-interactions";
1072
- import { ApplyPatchDiff } from "./tool-ui-apply-patch";
1073
- import { BashTerminal } from "./tool-ui-bash";
1074
- import {
1075
- EditInline,
1076
- GlobInline,
1077
- GrepInline,
1078
- ReadInline,
1079
- ToolCallFallback,
1080
- WebFetchInline,
1081
- WebSearchInline,
1082
- WriteInline,
1083
- } from "./tool-ui-inline";
1084
-
1085
- export const ReadTool = withOpenCodeToolInteractions(ReadInline);
1086
- export const EditTool = withOpenCodeToolInteractions(EditInline);
1087
- export const WriteTool = withOpenCodeToolInteractions(WriteInline);
1088
- export const BashTool = withOpenCodeToolInteractions(BashTerminal);
1089
- export const GrepTool = withOpenCodeToolInteractions(GrepInline);
1090
- export const GlobTool = withOpenCodeToolInteractions(GlobInline);
1091
- export const WebSearchTool = withOpenCodeToolInteractions(WebSearchInline);
1092
- export const WebFetchTool = withOpenCodeToolInteractions(WebFetchInline);
1093
- export const ApplyPatchTool = withOpenCodeToolInteractions(ApplyPatchDiff);
1094
- export const AskQuestionTool = withOpenCodeToolInteractions(AskQuestionInline);
1095
- export const FallbackTool = withOpenCodeToolInteractions(ToolCallFallback);
1096
-
1097
- ```
1098
-
1099
- ## components/tools/reasoning-ghost.tsx
1100
-
1101
- ```tsx
1102
- "use client";
1103
-
1104
- import { memo, type FC, type PropsWithChildren } from "react";
1105
- import { BrainIcon } from "lucide-react";
1106
- import { useAuiState } from "@assistant-ui/react";
1107
- import { cn } from "@/lib/utils";
1108
-
1109
- type GroupedPartsGroup = {
1110
- indices: readonly number[];
1111
- };
1112
-
1113
- const ReasoningGroupImpl: FC<
1114
- PropsWithChildren<{ group: GroupedPartsGroup }>
1115
- > = ({ children, group }) => {
1116
- const startIndex = group.indices[0]!;
1117
- const endIndex = group.indices.at(-1)!;
1118
-
1119
- const isMultiLine = useAuiState((s) => {
1120
- let totalLength = 0;
1121
- for (let i = startIndex; i <= endIndex; i++) {
1122
- const part = s.message.parts[i];
1123
- if (part?.type === "reasoning") {
1124
- if (part.text.includes("\n\n")) return true;
1125
- totalLength += part.text.length;
1126
- }
1127
- }
1128
- return totalLength > 120;
1129
- });
1130
-
1131
- const textAfter = useAuiState((s) => {
1132
- for (let i = endIndex + 1; i < s.message.parts.length; i++) {
1133
- const type = s.message.parts[i]?.type;
1134
- if (type === "data") continue;
1135
- return type === "text";
1136
- }
1137
- return false;
1138
- });
1139
-
1140
- return (
1141
- <div
1142
- data-slot="reasoning-group"
1143
- className={cn(
1144
- "text-muted-foreground mt-4 mb-2 flex items-start gap-2 text-sm leading-relaxed first:mt-0",
1145
- isMultiLine && "mt-6 mb-4",
1146
- textAfter && "mb-4",
1147
- )}
1148
- >
1149
- <BrainIcon className="mt-[3.25px] size-3.5 shrink-0" />
1150
- <div className="space-y-4.5">{children}</div>
1151
- </div>
1152
- );
1153
- };
1154
-
1155
- export const ReasoningGroup = memo(ReasoningGroupImpl);
1156
- ReasoningGroup.displayName = "ReasoningGroup";
1157
-
1158
- ```
1159
-
1160
- ## components/tools/tool-group.tsx
1161
-
1162
- ```tsx
1163
- import { cn } from "@/lib/utils";
1164
- import { useAuiState } from "@assistant-ui/react";
1165
- import { memo, type FC, type PropsWithChildren } from "react";
1166
-
1167
- type GroupedPartsGroup = {
1168
- indices: readonly number[];
1169
- };
1170
-
1171
- export const ToolGroupImpl: FC<
1172
- PropsWithChildren<{ group: GroupedPartsGroup }>
1173
- > = ({ children, group }) => {
1174
- const startIndex = group.indices[0]!;
1175
- const endIndex = group.indices.at(-1)!;
1176
-
1177
- const textBefore = useAuiState((s) => {
1178
- for (let i = startIndex - 1; i >= 0; i--) {
1179
- const type = s.message.parts[i]?.type;
1180
- if (type === "data") continue;
1181
- return type === "text";
1182
- }
1183
- return false;
1184
- });
1185
- const textAfter = useAuiState((s) => {
1186
- for (let i = endIndex + 1; i < s.message.parts.length; i++) {
1187
- const type = s.message.parts[i]?.type;
1188
- if (type === "data") continue;
1189
- return type === "text";
1190
- }
1191
- return false;
1192
- });
1193
-
1194
- return (
1195
- <div
1196
- data-slot="tool-group"
1197
- className={cn(textBefore && "mt-4", textAfter && "mb-4")}
1198
- >
1199
- {children}
1200
- </div>
1201
- );
1202
- };
1203
-
1204
- export const ToolGroup = memo(ToolGroupImpl);
1205
-
1206
- ```
1207
-
1208
- ## components/tools/tool-ui-apply-patch.tsx
1209
-
1210
- ```tsx
1211
- "use client";
1212
-
1213
- import { memo, useMemo, useState } from "react";
1214
- import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
1215
- import { CheckIcon, ChevronRightIcon } from "lucide-react";
1216
- import {
1217
- Collapsible,
1218
- CollapsibleContent,
1219
- CollapsibleTrigger,
1220
- } from "@/components/ui/radix/collapsible";
1221
- import { cn } from "@/lib/utils";
1222
- import { PatchDiff } from "@pierre/diffs/react";
1223
- import {
1224
- ToolStatusIcon,
1225
- getPatchInfo,
1226
- isCancelledToolStatus,
1227
- str,
1228
- } from "@/components/tools/tool-ui-shared";
1229
-
1230
- // ── Patch format conversion ──────────────────────────────────────────────
1231
- // Claude Code's apply_patch uses a custom "v2" format:
1232
- // *** Begin Patch
1233
- // *** Update File: path/to/file.ts
1234
- // @@ context search text @@
1235
- // context line
1236
- // -removed
1237
- // +added
1238
- // *** End Patch
1239
- //
1240
- // The @@ markers are context-search strings, NOT standard unified diff
1241
- // hunk headers. We convert to proper unified diff for @pierre/diffs.
1242
-
1243
- interface PatchBlock {
1244
- type: string;
1245
- path: string;
1246
- body: string;
1247
- }
1248
-
1249
- function parseClaudePatchBlocks(patchText: string): PatchBlock[] {
1250
- // Strip wrapper lines
1251
- const cleaned = patchText
1252
- .replace(/^\*\*\*\s*Begin Patch\s*$/gm, "")
1253
- .replace(/^\*\*\*\s*End Patch\s*$/gm, "")
1254
- .trim();
1255
-
1256
- const headerRegex = /^\*\*\*\s+(Update|Add|Delete)\s+File:\s+(.+)$/gm;
1257
- const blocks: PatchBlock[] = [];
1258
- let lastIndex = 0;
1259
- for (const match of cleaned.matchAll(headerRegex)) {
1260
- if (blocks.length > 0) {
1261
- blocks[blocks.length - 1]!.body = cleaned.slice(lastIndex, match.index);
1262
- }
1263
- blocks.push({ type: match[1]!, path: match[2]!.trim(), body: "" });
1264
- lastIndex = match.index + match[0].length;
1265
- }
1266
- if (blocks.length > 0) {
1267
- blocks[blocks.length - 1]!.body = cleaned.slice(lastIndex);
1268
- }
1269
-
1270
- return blocks;
1271
- }
1272
-
1273
- function buildHunkHeader(
1274
- lines: string[],
1275
- oldStart: number,
1276
- newStart: number,
1277
- ): { header: string; oldCount: number; newCount: number } {
1278
- let oldCount = 0;
1279
- let newCount = 0;
1280
- for (const line of lines) {
1281
- if (line.startsWith("+")) newCount++;
1282
- else if (line.startsWith("-")) oldCount++;
1283
- else {
1284
- // context line (space prefix or empty)
1285
- oldCount++;
1286
- newCount++;
1287
- }
1288
- }
1289
- return {
1290
- header: `@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`,
1291
- oldCount,
1292
- newCount,
1293
- };
1294
- }
1295
-
1296
- function claudeBlockToUnifiedDiff(block: PatchBlock): string {
1297
- const { type, path, body } = block;
1298
- const bodyLines = body
1299
- .split("\n")
1300
- .filter((l) => l.trim() !== "" || l.startsWith(" "));
1301
-
1302
- // Filter out empty trailing lines but keep intentional blank context lines
1303
- const diffLines: string[] = [];
1304
- for (const line of bodyLines) {
1305
- // Skip Claude's @@ context search @@ markers (e.g. "@@ function foo() { @@")
1306
- // These are NOT standard unified diff hunk headers (@@ -N,M +N,M @@)
1307
- if (/^@@.*@@\s*$/.test(line)) continue;
1308
- // Keep actual diff lines: +, -, or space-prefixed context
1309
- if (line.startsWith("+") || line.startsWith("-") || line.startsWith(" ")) {
1310
- diffLines.push(line);
1311
- }
1312
- }
1313
-
1314
- let gitHeader: string;
1315
- let fileHeader: string;
1316
- switch (type) {
1317
- case "Add":
1318
- gitHeader = `diff --git a/${path} b/${path}\nnew file mode 100644`;
1319
- fileHeader = `--- /dev/null\n+++ b/${path}`;
1320
- break;
1321
- case "Delete":
1322
- gitHeader = `diff --git a/${path} b/${path}\ndeleted file mode 100644`;
1323
- fileHeader = `--- a/${path}\n+++ /dev/null`;
1324
- break;
1325
- default:
1326
- gitHeader = `diff --git a/${path} b/${path}`;
1327
- fileHeader = `--- a/${path}\n+++ b/${path}`;
1328
- }
1329
-
1330
- if (diffLines.length === 0) return `${gitHeader}\n${fileHeader}`;
1331
-
1332
- // For Add files, all lines should be additions
1333
- if (type === "Add") {
1334
- const addLines = diffLines.map((l) => (l.startsWith("+") ? l : `+${l}`));
1335
- const count = addLines.length;
1336
- return `${gitHeader}\n${fileHeader}\n@@ -0,0 +1,${count} @@\n${addLines.join("\n")}`;
1337
- }
1338
-
1339
- // For Delete files, all lines should be deletions
1340
- if (type === "Delete") {
1341
- const delLines = diffLines.map((l) => (l.startsWith("-") ? l : `-${l}`));
1342
- const count = delLines.length;
1343
- return `${gitHeader}\n${fileHeader}\n@@ -1,${count} +0,0 @@\n${delLines.join("\n")}`;
1344
- }
1345
-
1346
- // For Update files, synthesize proper hunk headers
1347
- const { header: hunkHeader } = buildHunkHeader(diffLines, 1, 1);
1348
- return `${gitHeader}\n${fileHeader}\n${hunkHeader}\n${diffLines.join("\n")}`;
1349
- }
1350
-
1351
- // ── Component ────────────────────────────────────────────────────────────
1352
-
1353
- export const ApplyPatchDiff: ToolCallMessagePartComponent = memo(
1354
- ({ toolName, args, status }) => {
1355
- const [open, setOpen] = useState(false);
1356
- const patchText = str(args?.patchText);
1357
-
1358
- const patchInfo = useMemo(() => getPatchInfo(patchText), [patchText]);
1359
-
1360
- const unifiedPatch = useMemo(() => {
1361
- if (!patchText) return "";
1362
- const blocks = parseClaudePatchBlocks(patchText);
1363
- return blocks.map(claudeBlockToUnifiedDiff).join("\n");
1364
- }, [patchText]);
1365
-
1366
- const isRunning = status?.type === "running";
1367
- const isCancelled = isCancelledToolStatus(status);
1368
- const hasDiff = unifiedPatch.length > 0 && !isRunning;
1369
-
1370
- return (
1371
- <Collapsible open={open} onOpenChange={setOpen}>
1372
- <CollapsibleTrigger asChild>
1373
- <button
1374
- type="button"
1375
- className="group text-muted-foreground hover:text-foreground flex w-full items-center gap-2 py-0.5 text-sm transition-colors"
1376
- >
1377
- <ToolStatusIcon
1378
- status={status}
1379
- completeIcon={<CheckIcon className="size-3 shrink-0" />}
1380
- />
1381
-
1382
- <span
1383
- className={cn(
1384
- "flex items-center gap-1.5 truncate",
1385
- isCancelled && "line-through opacity-50",
1386
- )}
1387
- >
1388
- <span className="font-medium">{toolName}</span>
1389
- {patchInfo.files.length > 0 && (
1390
- <span className="opacity-60">
1391
- {patchInfo.files.length === 1
1392
- ? patchInfo.files[0]
1393
- : `${patchInfo.files.length} files`}
1394
- </span>
1395
- )}
1396
- {(patchInfo.added > 0 || patchInfo.removed > 0) && !isRunning && (
1397
- <span className="ml-0.5 flex items-center gap-1 font-mono text-xs">
1398
- {patchInfo.added > 0 && (
1399
- <span className="text-green-500">+{patchInfo.added}</span>
1400
- )}
1401
- {patchInfo.removed > 0 && (
1402
- <span className="text-red-500">-{patchInfo.removed}</span>
1403
- )}
1404
- </span>
1405
- )}
1406
- </span>
1407
-
1408
- {hasDiff && (
1409
- <>
1410
- <span className="border-muted-foreground/20 group-hover:border-muted-foreground/50 mt-0.5 min-w-4 flex-1 self-center border-b-[0.5px] transition-colors" />
1411
- <ChevronRightIcon
1412
- className={cn(
1413
- "stroke-muted-foreground/60 group-hover:stroke-foreground/60 mt-0.5 size-3.75 shrink-0 transition-[transform,stroke]",
1414
- open && "rotate-90",
1415
- )}
1416
- />
1417
- </>
1418
- )}
1419
- </button>
1420
- </CollapsibleTrigger>
1421
-
1422
- {hasDiff && (
1423
- <CollapsibleContent className="data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down overflow-hidden data-[state=closed]:ease-out data-[state=open]:ease-in">
1424
- <div className="mt-1 ml-5 overflow-hidden rounded-md border">
1425
- <PatchDiff
1426
- patch={unifiedPatch}
1427
- options={{
1428
- theme: { dark: "pierre-dark", light: "pierre-light" },
1429
- diffStyle: "unified",
1430
- overflow: "wrap",
1431
- }}
1432
- />
1433
- </div>
1434
- </CollapsibleContent>
1435
- )}
1436
- </Collapsible>
1437
- );
1438
- },
1439
- );
1440
- ApplyPatchDiff.displayName = "ApplyPatchDiff";
1441
-
1442
- ```
1443
-
1444
- ## components/tools/tool-ui-bash.tsx
1445
-
1446
- ```tsx
1447
- "use client";
1448
-
1449
- import { memo, useState } from "react";
1450
- import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
1451
- import { ChevronRightIcon, DollarSignIcon, XCircleIcon } from "lucide-react";
1452
- import {
1453
- Collapsible,
1454
- CollapsibleContent,
1455
- CollapsibleTrigger,
1456
- } from "@/components/ui/radix/collapsible";
1457
- import { cn } from "@/lib/utils";
1458
- import {
1459
- ToolStatusIcon,
1460
- isCancelledToolStatus,
1461
- truncate,
1462
- } from "@/components/tools/tool-ui-shared";
1463
-
1464
- const parseResult = (result: unknown) => {
1465
- if (!result) return {};
1466
- if (typeof result === "string") {
1467
- try {
1468
- const parsed = JSON.parse(result);
1469
- if (typeof parsed === "object" && parsed !== null) return parsed;
1470
- } catch {
1471
- // plain text output
1472
- }
1473
- return { stdout: result };
1474
- }
1475
- if (typeof result === "object") return result as Record<string, unknown>;
1476
- return {};
1477
- };
1478
-
1479
- export const BashTerminal: ToolCallMessagePartComponent = memo(
1480
- ({ args, result, status }) => {
1481
- const [open, setOpen] = useState(false);
1482
-
1483
- const command = typeof args?.command === "string" ? args.command : "";
1484
- const description =
1485
- typeof args?.description === "string" ? args.description : "";
1486
- const isRunning = status?.type === "running";
1487
- const isCancelled = isCancelledToolStatus(status);
1488
-
1489
- const parsed = isRunning ? {} : parseResult(result);
1490
- const stdout =
1491
- typeof parsed.stdout === "string" ? parsed.stdout : undefined;
1492
- const stderr =
1493
- typeof parsed.stderr === "string" ? parsed.stderr : undefined;
1494
- const exitCode = typeof parsed.exitCode === "number" ? parsed.exitCode : 0;
1495
- const hasOutput = Boolean(stdout || stderr);
1496
- const isError = !isRunning && exitCode !== 0;
1497
-
1498
- const summaryText = description || truncate(command);
1499
-
1500
- return (
1501
- <Collapsible open={open} onOpenChange={setOpen}>
1502
- <CollapsibleTrigger asChild>
1503
- <button
1504
- type="button"
1505
- className="group text-muted-foreground hover:text-foreground flex w-full items-center gap-2 py-0.5 text-sm transition-colors"
1506
- >
1507
- {isRunning ? (
1508
- <ToolStatusIcon status={status} />
1509
- ) : isError ? (
1510
- <XCircleIcon className="text-destructive size-3 shrink-0" />
1511
- ) : (
1512
- <ToolStatusIcon
1513
- status={status}
1514
- completeIcon={<DollarSignIcon className="size-3.5 shrink-0" />}
1515
- />
1516
- )}
1517
-
1518
- <span
1519
- className={cn(
1520
- "flex items-center gap-1.5 truncate",
1521
- isCancelled && "line-through opacity-50",
1522
- )}
1523
- >
1524
- <span className="font-medium">bash</span>
1525
- {summaryText && <span className="opacity-60">{summaryText}</span>}
1526
- </span>
1527
-
1528
- {hasOutput && (
1529
- <>
1530
- <span className="border-muted-foreground/20 group-hover:border-muted-foreground/50 mt-0.5 min-w-4 flex-1 self-center border-b-[0.5px] transition-colors" />
1531
- <ChevronRightIcon
1532
- className={cn(
1533
- "stroke-muted-foreground/60 group-hover:stroke-foreground/60 mt-0.5 size-3.75 shrink-0 transition-[transform,stroke]",
1534
- open && "rotate-90",
1535
- )}
1536
- />
1537
- </>
1538
- )}
1539
- </button>
1540
- </CollapsibleTrigger>
1541
-
1542
- {hasOutput && (
1543
- <CollapsibleContent className="data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down overflow-hidden data-[state=closed]:ease-out data-[state=open]:ease-in">
1544
- <div className="bg-muted/50 mt-1 ml-5 max-h-96 overflow-y-auto rounded-md border p-3 font-mono text-xs">
1545
- {command && (
1546
- <div className="text-muted-foreground mb-2">$ {command}</div>
1547
- )}
1548
- {stdout && (
1549
- <pre className="text-foreground wrap-break-word whitespace-pre-wrap">
1550
- {stdout}
1551
- </pre>
1552
- )}
1553
- {stderr && (
1554
- <pre
1555
- className={cn(
1556
- "text-destructive wrap-break-word whitespace-pre-wrap",
1557
- stdout && "mt-2",
1558
- )}
1559
- >
1560
- {stderr}
1561
- </pre>
1562
- )}
1563
- </div>
1564
- </CollapsibleContent>
1565
- )}
1566
- </Collapsible>
1567
- );
1568
- },
1569
- );
1570
- BashTerminal.displayName = "BashTerminal";
1571
-
1572
- ```
1573
-
1574
- ## components/tools/tool-ui-inline.tsx
1575
-
1576
- ```tsx
1577
- "use client";
1578
-
1579
- import { memo, useMemo } from "react";
1580
- import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
1581
- import { cn } from "@/lib/utils";
1582
- import {
1583
- ToolStatusIcon,
1584
- basename,
1585
- getPatchInfo,
1586
- isCancelledToolStatus,
1587
- str,
1588
- truncate,
1589
- type ToolCallStatusLike,
1590
- } from "@/components/tools/tool-ui-shared";
1591
-
1592
- // ── Helpers ────────────────────────────────────────────────────────────
1593
-
1594
- const shortenPath = (filepath: string, depth = 2): string => {
1595
- const parts = filepath.split("/").filter(Boolean);
1596
- if (parts.length <= depth) return filepath;
1597
- return parts.slice(-depth).join("/");
1598
- };
1599
-
1600
- // ── ToolCallShell ──────────────────────────────────────────────────────
1601
-
1602
- const ToolCallShell = ({
1603
- toolName,
1604
- status,
1605
- children,
1606
- }: {
1607
- toolName: string;
1608
- status?: ToolCallStatusLike;
1609
- children?: React.ReactNode;
1610
- }) => {
1611
- const isCancelled = isCancelledToolStatus(status);
1612
-
1613
- return (
1614
- <div className="text-muted-foreground flex items-center gap-2 py-0.5 text-sm">
1615
- <ToolStatusIcon status={status} />
1616
- <span
1617
- className={cn(
1618
- "flex items-center gap-1.5 truncate",
1619
- isCancelled && "line-through opacity-50",
1620
- )}
1621
- >
1622
- <span className="font-medium">{toolName}</span>
1623
- {children}
1624
- </span>
1625
- </div>
1626
- );
1627
- };
1628
-
1629
- // ── Factory for simple inline tools ────────────────────────────────────
1630
-
1631
- const inlineTool = (
1632
- argKeys: string | string[],
1633
- format: (v: string) => string = truncate,
1634
- ): ToolCallMessagePartComponent => {
1635
- const keys = Array.isArray(argKeys) ? argKeys : [argKeys];
1636
- const Component: ToolCallMessagePartComponent = memo(
1637
- ({ toolName, args, status }) => {
1638
- const value = keys.reduce<string>(
1639
- (acc, key) => acc || str((args as Record<string, unknown>)?.[key]),
1640
- "",
1641
- );
1642
- return (
1643
- <ToolCallShell toolName={toolName} status={status}>
1644
- {value && <span className="opacity-60">{format(value)}</span>}
1645
- </ToolCallShell>
1646
- );
1647
- },
1648
- );
1649
- return Component;
1650
- };
1651
-
1652
- // ── Per-tool inline components ─────────────────────────────────────────
1653
-
1654
- export const ReadInline = inlineTool(
1655
- ["file_path", "filePath", "path", "file"],
1656
- (v) => truncate(shortenPath(v)),
1657
- );
1658
- ReadInline.displayName = "ReadInline";
1659
-
1660
- export const EditInline = inlineTool("file_path", basename);
1661
- EditInline.displayName = "EditInline";
1662
-
1663
- export const WriteInline = inlineTool("file_path", basename);
1664
- WriteInline.displayName = "WriteInline";
1665
-
1666
- export const GrepInline = inlineTool("pattern");
1667
- GrepInline.displayName = "GrepInline";
1668
-
1669
- export const GlobInline = inlineTool("pattern");
1670
- GlobInline.displayName = "GlobInline";
1671
-
1672
- export const WebSearchInline = inlineTool("query");
1673
- WebSearchInline.displayName = "WebSearchInline";
1674
-
1675
- export const WebFetchInline = inlineTool("url");
1676
- WebFetchInline.displayName = "WebFetchInline";
1677
-
1678
- // Kept as a compact reference implementation even though the example
1679
- // currently uses the richer diff viewer from tool-ui-apply-patch.tsx.
1680
- export const ApplyPatchInline: ToolCallMessagePartComponent = memo(
1681
- ({ toolName, args, status }) => {
1682
- const patchText = str(args?.patchText);
1683
- const patchInfo = useMemo(() => getPatchInfo(patchText), [patchText]);
1684
- const isRunning = status?.type === "running";
1685
-
1686
- return (
1687
- <ToolCallShell toolName={toolName} status={status}>
1688
- {patchInfo.files.length > 0 && (
1689
- <span className="opacity-60">
1690
- {patchInfo.files.length === 1
1691
- ? patchInfo.files[0]
1692
- : `${patchInfo.files.length} files`}
1693
- </span>
1694
- )}
1695
- {(patchInfo.added > 0 || patchInfo.removed > 0) && !isRunning && (
1696
- <span className="ml-0.5 flex items-center gap-1 font-mono text-xs">
1697
- {patchInfo.added > 0 && (
1698
- <span className="text-green-500">+{patchInfo.added}</span>
1699
- )}
1700
- {patchInfo.removed > 0 && (
1701
- <span className="text-red-500">-{patchInfo.removed}</span>
1702
- )}
1703
- </span>
1704
- )}
1705
- </ToolCallShell>
1706
- );
1707
- },
1708
- );
1709
- ApplyPatchInline.displayName = "ApplyPatchInline";
1710
-
1711
- // ── Fallback — generic, for unknown/MCP tools ─────────────────────────
1712
-
1713
- const SUMMARY_KEYS = [
1714
- "file_path",
1715
- "path",
1716
- "pattern",
1717
- "command",
1718
- "query",
1719
- "glob",
1720
- "url",
1721
- ] as const;
1722
-
1723
- const ToolCallFallbackImpl: ToolCallMessagePartComponent = ({
1724
- toolName,
1725
- args,
1726
- status,
1727
- }) => {
1728
- const summary = useMemo(() => {
1729
- if (!args || typeof args !== "object") return "";
1730
- for (const key of SUMMARY_KEYS) {
1731
- const v = (args as Record<string, unknown>)[key];
1732
- if (typeof v === "string" && v) return truncate(v);
1733
- }
1734
- return "";
1735
- }, [args]);
1736
-
1737
- return (
1738
- <ToolCallShell toolName={toolName} status={status}>
1739
- {summary && <span className="opacity-60">{summary}</span>}
1740
- </ToolCallShell>
1741
- );
1742
- };
1743
-
1744
- export const ToolCallFallback = memo(ToolCallFallbackImpl);
1745
- ToolCallFallback.displayName = "ToolCallFallback";
1746
-
1747
- ```
1748
-
1749
- ## components/tools/tool-ui-shared.tsx
1750
-
1751
- ```tsx
1752
- "use client";
1753
-
1754
- import type { ReactNode } from "react";
1755
- import {
1756
- AlertCircleIcon,
1757
- CheckIcon,
1758
- LoaderIcon,
1759
- XCircleIcon,
1760
- } from "lucide-react";
1761
- import { cn } from "@/lib/utils";
1762
-
1763
- export type ToolCallStatusLike =
1764
- | {
1765
- type: string;
1766
- reason?: string;
1767
- }
1768
- | undefined;
1769
-
1770
- export const truncate = (value: string, max = 80): string =>
1771
- value.length > max ? `${value.slice(0, max - 3)}...` : value;
1772
-
1773
- export const str = (value: unknown): string =>
1774
- typeof value === "string" ? value : "";
1775
-
1776
- export const basename = (filepath: string): string => {
1777
- const parts = filepath.split("/").filter(Boolean);
1778
- return parts[parts.length - 1] ?? filepath;
1779
- };
1780
-
1781
- const unique = <T,>(items: readonly T[]) => [...new Set(items)];
1782
-
1783
- export const getPatchInfo = (patchText: string) => {
1784
- if (!patchText) return { files: [] as string[], added: 0, removed: 0 };
1785
-
1786
- const files = unique(
1787
- [...patchText.matchAll(/^\*\*\*\s+(?:Update|Add|Delete)\s+File:\s+(.+)$/gm)]
1788
- .map((match) => basename(match[1]!.trim()))
1789
- .filter(Boolean),
1790
- );
1791
-
1792
- let added = 0;
1793
- let removed = 0;
1794
- for (const line of patchText.split("\n")) {
1795
- if (line.startsWith("+")) added += 1;
1796
- else if (line.startsWith("-")) removed += 1;
1797
- }
1798
-
1799
- return { files, added, removed };
1800
- };
1801
-
1802
- export const isCancelledToolStatus = (status?: ToolCallStatusLike) =>
1803
- status?.type === "incomplete" && status.reason === "cancelled";
1804
-
1805
- export const ToolStatusIcon = ({
1806
- status,
1807
- completeIcon,
1808
- }: {
1809
- status?: ToolCallStatusLike;
1810
- completeIcon?: ReactNode;
1811
- }) => {
1812
- const statusType = status?.type ?? "complete";
1813
- const isCancelled = isCancelledToolStatus(status);
1814
-
1815
- if (statusType === "running") {
1816
- return <LoaderIcon className="size-3 shrink-0 animate-spin" />;
1817
- }
1818
-
1819
- if (statusType === "requires-action") {
1820
- return <AlertCircleIcon className="size-3 shrink-0 text-amber-600" />;
1821
- }
1822
-
1823
- if (statusType === "incomplete") {
1824
- return (
1825
- <XCircleIcon
1826
- className={cn("size-3 shrink-0", !isCancelled && "text-destructive")}
1827
- />
1828
- );
1829
- }
1830
-
1831
- return completeIcon ?? <CheckIcon className="size-3 shrink-0" />;
1832
- };
1833
-
1834
- ```
1835
-
1836
- ## components/tools/toolkit.tsx
1837
-
1838
- ```tsx
1839
- "use generative";
1840
-
1841
- import { defineToolkit, externalTool } from "@assistant-ui/react";
1842
- import {
1843
- ApplyPatchTool,
1844
- AskQuestionTool,
1845
- BashTool,
1846
- EditTool,
1847
- GlobTool,
1848
- GrepTool,
1849
- ReadTool,
1850
- WebFetchTool,
1851
- WebSearchTool,
1852
- WriteTool,
1853
- } from "./opencode-tools";
1854
-
1855
- export default defineToolkit({
1856
- read: { execute: externalTool(), render: ReadTool },
1857
- edit: { execute: externalTool(), render: EditTool },
1858
- write: { execute: externalTool(), render: WriteTool },
1859
- bash: { execute: externalTool(), render: BashTool },
1860
- grep: { execute: externalTool(), render: GrepTool },
1861
- glob: { execute: externalTool(), render: GlobTool },
1862
- webSearch: { execute: externalTool(), render: WebSearchTool },
1863
- webFetch: { execute: externalTool(), render: WebFetchTool },
1864
- apply_patch: { execute: externalTool(), render: ApplyPatchTool },
1865
- ask_question: { execute: externalTool(), render: AskQuestionTool },
1866
- request_user_input: { execute: externalTool(), render: AskQuestionTool },
1867
- requestUserInput: { execute: externalTool(), render: AskQuestionTool },
1868
- });
1869
-
1870
- ```
1871
-
1872
- ## next.config.ts
1873
-
1874
- ```typescript
1875
- import { withAui } from "@assistant-ui/next";
1876
- import type { NextConfig } from "next";
1877
-
1878
- const nextConfig: NextConfig = {/* config options here */};
1879
-
1880
- export default withAui(nextConfig);
1881
-
1882
- ```
1883
-
1884
- ## package.json
1885
-
1886
- ```json
1887
- {
1888
- "name": "with-opencode",
1889
- "version": "0.0.0",
1890
- "private": true,
1891
- "type": "module",
1892
- "scripts": {
1893
- "dev": "next dev",
1894
- "build": "next build",
1895
- "start": "next start"
1896
- },
1897
- "dependencies": {
1898
- "@assistant-ui/react": "workspace:*",
1899
- "@assistant-ui/react-markdown": "workspace:*",
1900
- "@assistant-ui/react-opencode": "workspace:*",
1901
- "@assistant-ui/ui": "workspace:*",
1902
- "@pierre/diffs": "^1.3.5",
1903
- "class-variance-authority": "^0.7.1",
1904
- "clsx": "^2.1.1",
1905
- "lucide-react": "^1.33.0",
1906
- "next": "^16.3.2",
1907
- "react": "^19.2.8",
1908
- "react-dom": "^19.2.8",
1909
- "tailwind-merge": "^3.6.0",
1910
- "zod": "^4.4.3"
1911
- },
1912
- "devDependencies": {
1913
- "@assistant-ui/next": "workspace:*",
1914
- "@assistant-ui/x-buildutils": "workspace:*",
1915
- "@tailwindcss/postcss": "^4.3.3",
1916
- "@types/node": "^26.2.0",
1917
- "@types/react": "^19.2.18",
1918
- "@types/react-dom": "^19.2.5",
1919
- "postcss": "^8.5.26",
1920
- "tailwindcss": "^4.3.3",
1921
- "tw-animate-css": "^1.4.0",
1922
- "typescript": "^7.0.2"
1923
- }
1924
- }
1925
-
1926
- ```
1927
-
1928
- ## README.md
1929
-
1930
- ```markdown
1931
- # with-opencode
1932
-
1933
- > [!NOTE] This integration/example is experimental, there may be bugs or issues.
1934
-
1935
- Next.js example for the OpenCode runtime adapter built with assistant-ui.
1936
-
1937
- Set `NEXT_PUBLIC_OPENCODE_BASE_URL` if your OpenCode server is not running on
1938
- `http://localhost:4096`.
1939
-
1940
- ```
1941
-
1942
- ## tsconfig.json
1943
-
1944
- ```json
1945
- {
1946
- "extends": "@assistant-ui/x-buildutils/ts/next",
1947
- "compilerOptions": {
1948
- "paths": {
1949
- "@/*": ["./*"],
1950
- "@assistant-ui/react-opencode": [
1951
- "../../packages/react-opencode/src/index.ts"
1952
- ],
1953
- "@/components/assistant-ui/*": [
1954
- "../../packages/ui/src/components/assistant-ui/*"
1955
- ],
1956
- "@/components/icons/*": ["../../packages/ui/src/components/icons/*"],
1957
- "@/components/ui/*": [
1958
- "../../packages/ui/src/components/ui/base/*",
1959
- "../../packages/ui/src/components/ui/radix/*"
1960
- ],
1961
- "@/components/ui/radix/*": [
1962
- "../../packages/ui/src/components/ui/radix/*"
1963
- ],
1964
- "@/hooks/*": ["../../packages/ui/src/hooks/*"],
1965
- "@/lib/utils": ["../../packages/ui/src/lib/utils"],
1966
- "@assistant-ui/ui/*": ["../../packages/ui/src/*"]
1967
- }
1968
- },
1969
- "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
1970
- "exclude": ["node_modules"]
1971
- }
1972
-
1973
- ```
1974
-