@assistant-ui/mcp-docs-server 0.2.2 → 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 (459) 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 +9 -13
  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 -674
  16. package/.docs/organized/code-examples/with-ag-ui.md +0 -552
  17. package/.docs/organized/code-examples/with-ai-sdk-v7.md +0 -465
  18. package/.docs/organized/code-examples/with-artifacts.md +0 -814
  19. package/.docs/organized/code-examples/with-assistant-transport.md +0 -575
  20. package/.docs/organized/code-examples/with-browser-extension.md +0 -371
  21. package/.docs/organized/code-examples/with-chain-of-thought.md +0 -960
  22. package/.docs/organized/code-examples/with-cloud-standalone.md +0 -681
  23. package/.docs/organized/code-examples/with-cloud.md +0 -438
  24. package/.docs/organized/code-examples/with-custom-thread-list.md +0 -568
  25. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +0 -526
  26. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +0 -696
  27. package/.docs/organized/code-examples/with-eve.md +0 -404
  28. package/.docs/organized/code-examples/with-expo.md +0 -2063
  29. package/.docs/organized/code-examples/with-external-store.md +0 -420
  30. package/.docs/organized/code-examples/with-ffmpeg.md +0 -828
  31. package/.docs/organized/code-examples/with-generative-ui.md +0 -1812
  32. package/.docs/organized/code-examples/with-google-adk.md +0 -367
  33. package/.docs/organized/code-examples/with-heat-graph.md +0 -304
  34. package/.docs/organized/code-examples/with-image-generation.md +0 -462
  35. package/.docs/organized/code-examples/with-interactables.md +0 -732
  36. package/.docs/organized/code-examples/with-langchain.md +0 -445
  37. package/.docs/organized/code-examples/with-langgraph.md +0 -854
  38. package/.docs/organized/code-examples/with-livekit.md +0 -642
  39. package/.docs/organized/code-examples/with-mcp.md +0 -780
  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 -449
  43. package/.docs/organized/code-examples/with-pi.md +0 -2083
  44. package/.docs/organized/code-examples/with-react-hook-form.md +0 -724
  45. package/.docs/organized/code-examples/with-react-ink-web.md +0 -690
  46. package/.docs/organized/code-examples/with-react-ink.md +0 -472
  47. package/.docs/organized/code-examples/with-react-router.md +0 -935
  48. package/.docs/organized/code-examples/with-resumable-stream.md +0 -667
  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 -811
  53. package/.docs/organized/code-examples/with-tap-runtime.md +0 -812
  54. package/.docs/organized/code-examples/with-virtualized-thread.md +0 -680
  55. package/.docs/organized/code-examples/with-vue.md +0 -408
  56. package/.docs/raw/docs/(docs)/architecture.mdx +0 -146
  57. package/.docs/raw/docs/(docs)/base-ui.mdx +0 -39
  58. package/.docs/raw/docs/(docs)/cli.mdx +0 -542
  59. package/.docs/raw/docs/(docs)/devtools.mdx +0 -79
  60. package/.docs/raw/docs/(docs)/index.mdx +0 -21
  61. package/.docs/raw/docs/(docs)/installation.mdx +0 -483
  62. package/.docs/raw/docs/(docs)/llm.mdx +0 -211
  63. package/.docs/raw/docs/(docs)/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 -20
  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 -52
  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/cloud-ai-sdk.mdx +0 -24
  94. package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +0 -97
  95. package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +0 -28
  96. package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +0 -200
  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 -619
  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 -149
  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 -602
  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 -400
  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 -209
  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 -236
  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/overview.mdx +0 -282
  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 -839
  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 -255
  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 -716
  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 -346
  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 -1008
  289. package/.docs/raw/docs/tools/user-managed-mcp.mdx +0 -428
  290. package/.docs/raw/docs/ui/accordion.mdx +0 -266
  291. package/.docs/raw/docs/ui/assistant-modal.mdx +0 -166
  292. package/.docs/raw/docs/ui/assistant-sidebar.mdx +0 -89
  293. package/.docs/raw/docs/ui/attachment.mdx +0 -259
  294. package/.docs/raw/docs/ui/badge.mdx +0 -150
  295. package/.docs/raw/docs/ui/composer-trigger-popover.mdx +0 -212
  296. package/.docs/raw/docs/ui/context-display.mdx +0 -148
  297. package/.docs/raw/docs/ui/diff-viewer.mdx +0 -280
  298. package/.docs/raw/docs/ui/directive-text.mdx +0 -114
  299. package/.docs/raw/docs/ui/dot-matrix.mdx +0 -133
  300. package/.docs/raw/docs/ui/file.mdx +0 -159
  301. package/.docs/raw/docs/ui/follow-up-suggestions.mdx +0 -82
  302. package/.docs/raw/docs/ui/image.mdx +0 -102
  303. package/.docs/raw/docs/ui/markdown.mdx +0 -64
  304. package/.docs/raw/docs/ui/mcp-config.mdx +0 -107
  305. package/.docs/raw/docs/ui/mermaid.mdx +0 -92
  306. package/.docs/raw/docs/ui/message-timing.mdx +0 -93
  307. package/.docs/raw/docs/ui/model-selector.mdx +0 -455
  308. package/.docs/raw/docs/ui/number-roll.mdx +0 -154
  309. package/.docs/raw/docs/ui/part-grouping.mdx +0 -614
  310. package/.docs/raw/docs/ui/quote.mdx +0 -211
  311. package/.docs/raw/docs/ui/reasoning.mdx +0 -186
  312. package/.docs/raw/docs/ui/scrollbar.mdx +0 -77
  313. package/.docs/raw/docs/ui/select.mdx +0 -254
  314. package/.docs/raw/docs/ui/sources.mdx +0 -177
  315. package/.docs/raw/docs/ui/streamdown.mdx +0 -392
  316. package/.docs/raw/docs/ui/syntax-highlighting.mdx +0 -205
  317. package/.docs/raw/docs/ui/tabs.mdx +0 -271
  318. package/.docs/raw/docs/ui/thread-list.mdx +0 -372
  319. package/.docs/raw/docs/ui/thread.mdx +0 -512
  320. package/.docs/raw/docs/ui/tool-fallback.mdx +0 -127
  321. package/.docs/raw/docs/ui/tool-group.mdx +0 -240
  322. package/.docs/raw/docs/ui/voice.mdx +0 -173
  323. package/.docs/raw/docs/utilities/heat-graph.mdx +0 -237
  324. package/.docs/raw/docs/utilities/react-o11y.mdx +0 -362
  325. package/.docs/raw/docs/utilities/tw-shimmer.mdx +0 -212
  326. package/dist/constants.d.ts +0 -14
  327. package/dist/constants.d.ts.map +0 -1
  328. package/dist/constants.js +0 -19
  329. package/dist/constants.js.map +0 -1
  330. package/dist/prepare-docs/code-examples.d.ts +0 -5
  331. package/dist/prepare-docs/code-examples.d.ts.map +0 -1
  332. package/dist/prepare-docs/code-examples.js +0 -117
  333. package/dist/prepare-docs/code-examples.js.map +0 -1
  334. package/dist/prepare-docs/copy-raw.d.ts +0 -5
  335. package/dist/prepare-docs/copy-raw.d.ts.map +0 -1
  336. package/dist/prepare-docs/copy-raw.js +0 -49
  337. package/dist/prepare-docs/copy-raw.js.map +0 -1
  338. package/dist/prepare-docs/prepare.d.ts +0 -1
  339. package/dist/prepare-docs/prepare.js +0 -22
  340. package/dist/prepare-docs/prepare.js.map +0 -1
  341. package/dist/prompts/xulux-playground.d.ts +0 -12
  342. package/dist/prompts/xulux-playground.d.ts.map +0 -1
  343. package/dist/prompts/xulux-playground.js +0 -33
  344. package/dist/prompts/xulux-playground.js.map +0 -1
  345. package/dist/tools/docs.d.ts +0 -22
  346. package/dist/tools/docs.d.ts.map +0 -1
  347. package/dist/tools/docs.js +0 -177
  348. package/dist/tools/docs.js.map +0 -1
  349. package/dist/tools/examples.d.ts +0 -24
  350. package/dist/tools/examples.d.ts.map +0 -1
  351. package/dist/tools/examples.js +0 -87
  352. package/dist/tools/examples.js.map +0 -1
  353. package/dist/tools/resources.d.ts +0 -6
  354. package/dist/tools/resources.d.ts.map +0 -1
  355. package/dist/tools/resources.js +0 -75
  356. package/dist/tools/resources.js.map +0 -1
  357. package/dist/tools/search.d.ts +0 -24
  358. package/dist/tools/search.d.ts.map +0 -1
  359. package/dist/tools/search.js +0 -39
  360. package/dist/tools/search.js.map +0 -1
  361. package/dist/tools/tests/mcp-test-client.d.ts +0 -15
  362. package/dist/tools/tests/mcp-test-client.d.ts.map +0 -1
  363. package/dist/tools/tests/mcp-test-client.js +0 -68
  364. package/dist/tools/tests/mcp-test-client.js.map +0 -1
  365. package/dist/tools/tests/test-setup.d.ts +0 -7
  366. package/dist/tools/tests/test-setup.d.ts.map +0 -1
  367. package/dist/tools/tests/test-setup.js +0 -36
  368. package/dist/tools/tests/test-setup.js.map +0 -1
  369. package/dist/tools/xulux-templates.d.ts +0 -58
  370. package/dist/tools/xulux-templates.d.ts.map +0 -1
  371. package/dist/tools/xulux-templates.js +0 -84
  372. package/dist/tools/xulux-templates.js.map +0 -1
  373. package/dist/utils/cache.d.ts +0 -5
  374. package/dist/utils/cache.d.ts.map +0 -1
  375. package/dist/utils/cache.js +0 -18
  376. package/dist/utils/cache.js.map +0 -1
  377. package/dist/utils/logger.d.ts +0 -10
  378. package/dist/utils/logger.d.ts.map +0 -1
  379. package/dist/utils/logger.js +0 -20
  380. package/dist/utils/logger.js.map +0 -1
  381. package/dist/utils/mcp-format.d.ts +0 -11
  382. package/dist/utils/mcp-format.d.ts.map +0 -1
  383. package/dist/utils/mcp-format.js +0 -14
  384. package/dist/utils/mcp-format.js.map +0 -1
  385. package/dist/utils/mdx.d.ts +0 -12
  386. package/dist/utils/mdx.d.ts.map +0 -1
  387. package/dist/utils/mdx.js +0 -45
  388. package/dist/utils/mdx.js.map +0 -1
  389. package/dist/utils/paths.d.ts +0 -12
  390. package/dist/utils/paths.d.ts.map +0 -1
  391. package/dist/utils/paths.js +0 -93
  392. package/dist/utils/paths.js.map +0 -1
  393. package/dist/utils/search.d.ts +0 -10
  394. package/dist/utils/search.d.ts.map +0 -1
  395. package/dist/utils/search.js +0 -97
  396. package/dist/utils/search.js.map +0 -1
  397. package/dist/utils/security.d.ts +0 -5
  398. package/dist/utils/security.d.ts.map +0 -1
  399. package/dist/utils/security.js +0 -22
  400. package/dist/utils/security.js.map +0 -1
  401. package/dist/xulux/catalog-client.d.ts +0 -14
  402. package/dist/xulux/catalog-client.d.ts.map +0 -1
  403. package/dist/xulux/catalog-client.js +0 -67
  404. package/dist/xulux/catalog-client.js.map +0 -1
  405. package/dist/xulux/fallback-catalog.d.ts +0 -7
  406. package/dist/xulux/fallback-catalog.d.ts.map +0 -1
  407. package/dist/xulux/fallback-catalog.js +0 -47
  408. package/dist/xulux/fallback-catalog.js.map +0 -1
  409. package/dist/xulux/fetch-sandbox.d.ts +0 -5
  410. package/dist/xulux/fetch-sandbox.d.ts.map +0 -1
  411. package/dist/xulux/fetch-sandbox.js +0 -40
  412. package/dist/xulux/fetch-sandbox.js.map +0 -1
  413. package/dist/xulux/template-service.d.ts +0 -84
  414. package/dist/xulux/template-service.d.ts.map +0 -1
  415. package/dist/xulux/template-service.js +0 -223
  416. package/dist/xulux/template-service.js.map +0 -1
  417. package/dist/xulux/types.d.ts +0 -55
  418. package/dist/xulux/types.d.ts.map +0 -1
  419. package/dist/xulux/types.js +0 -6
  420. package/dist/xulux/types.js.map +0 -1
  421. package/src/constants.ts +0 -24
  422. package/src/prepare-docs/code-examples.ts +0 -158
  423. package/src/prepare-docs/copy-raw.ts +0 -50
  424. package/src/prepare-docs/prepare.ts +0 -24
  425. package/src/prompts/xulux-playground.ts +0 -36
  426. package/src/tools/docs.ts +0 -255
  427. package/src/tools/examples.ts +0 -114
  428. package/src/tools/resources.ts +0 -111
  429. package/src/tools/search.ts +0 -46
  430. package/src/tools/tests/completions.test.ts +0 -60
  431. package/src/tools/tests/directory-size-cap.test.ts +0 -50
  432. package/src/tools/tests/docs.test.ts +0 -144
  433. package/src/tools/tests/examples.test.ts +0 -94
  434. package/src/tools/tests/integration.test.ts +0 -45
  435. package/src/tools/tests/json-parsing.test.ts +0 -23
  436. package/src/tools/tests/listings-cache.test.ts +0 -19
  437. package/src/tools/tests/mcp-protocol.test.ts +0 -216
  438. package/src/tools/tests/mcp-test-client.ts +0 -111
  439. package/src/tools/tests/path-traversal.test.ts +0 -81
  440. package/src/tools/tests/resources.test.ts +0 -133
  441. package/src/tools/tests/search.test.ts +0 -37
  442. package/src/tools/tests/test-setup.ts +0 -50
  443. package/src/tools/tests/xulux-templates.test.ts +0 -262
  444. package/src/tools/xulux-templates.ts +0 -141
  445. package/src/utils/cache.ts +0 -20
  446. package/src/utils/logger.ts +0 -20
  447. package/src/utils/mcp-format.ts +0 -14
  448. package/src/utils/mdx.ts +0 -59
  449. package/src/utils/paths.ts +0 -139
  450. package/src/utils/search.ts +0 -131
  451. package/src/utils/security.ts +0 -52
  452. package/src/utils/tests/cache.test.ts +0 -51
  453. package/src/utils/tests/mcp-format.test.ts +0 -22
  454. package/src/utils/tests/security.test.ts +0 -119
  455. package/src/xulux/catalog-client.ts +0 -105
  456. package/src/xulux/fallback-catalog.ts +0 -63
  457. package/src/xulux/fetch-sandbox.ts +0 -56
  458. package/src/xulux/template-service.ts +0 -406
  459. package/src/xulux/types.ts +0 -60
@@ -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.31.0",
1906
- "next": "^16.3.0",
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.4",
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
-