@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,2084 +0,0 @@
1
- # Example: with-pi
2
-
3
- ## app/api/pi/fs/route.ts
4
-
5
- ```typescript
6
- import { readdir } from "node:fs/promises";
7
- import { homedir } from "node:os";
8
- import { resolve, dirname, join } from "node:path";
9
- import { withFail } from "@/lib/http";
10
-
11
- export const runtime = "nodejs";
12
- export const dynamic = "force-dynamic";
13
-
14
- export type FsEntry = { name: string; path: string };
15
- export type FsListing = {
16
- path: string;
17
- parent: string | null;
18
- entries: FsEntry[];
19
- };
20
-
21
- const expand = (p: string): string =>
22
- p.startsWith("~") ? join(homedir(), p.slice(1)) : p;
23
-
24
- export const GET = withFail(async (request: Request) => {
25
- // This lists arbitrary host directories for the local workspace picker —
26
- // never expose it from a deployed instance.
27
- if (process.env.NODE_ENV === "production") {
28
- return new Response(null, { status: 404 });
29
- }
30
- const raw = new URL(request.url).searchParams.get("path");
31
- const dir = resolve(raw ? expand(raw) : homedir());
32
- const dirents = await readdir(dir, { withFileTypes: true });
33
- const entries = dirents
34
- .filter((dirent) => dirent.isDirectory() && !dirent.name.startsWith("."))
35
- .map((dirent) => ({ name: dirent.name, path: join(dir, dirent.name) }))
36
- .sort((a, b) => a.name.localeCompare(b.name));
37
- const parent = dirname(dir);
38
- return Response.json({
39
- path: dir,
40
- parent: parent === dir ? null : parent,
41
- entries,
42
- } satisfies FsListing);
43
- });
44
-
45
- ```
46
-
47
- ## app/api/pi/handshake/route.ts
48
-
49
- ```typescript
50
- import { getHandshake } from "@/lib/pi-server";
51
- import { withFail } from "@/lib/http";
52
-
53
- export const runtime = "nodejs";
54
- export const dynamic = "force-dynamic";
55
-
56
- export const GET = withFail(async () => Response.json(await getHandshake()));
57
-
58
- ```
59
-
60
- ## app/api/pi/models/route.ts
61
-
62
- ```typescript
63
- import { piClient } from "@/lib/pi-server";
64
- import { withFail } from "@/lib/http";
65
-
66
- export const runtime = "nodejs";
67
- export const dynamic = "force-dynamic";
68
-
69
- export const GET = withFail(async () =>
70
- Response.json(await piClient.getAvailableModels()),
71
- );
72
-
73
- ```
74
-
75
- ## app/api/pi/threads/[threadId]/archive/route.ts
76
-
77
- ```typescript
78
- import type { NextRequest } from "next/server";
79
- import { piClient } from "@/lib/pi-server";
80
- import { noContent, withFail } from "@/lib/http";
81
-
82
- export const runtime = "nodejs";
83
- export const dynamic = "force-dynamic";
84
-
85
- type Context = { params: Promise<{ threadId: string }> };
86
-
87
- export const POST = withFail(async (_req: NextRequest, { params }: Context) => {
88
- const { threadId } = await params;
89
- await piClient.archiveThread(threadId);
90
- return noContent();
91
- });
92
-
93
- ```
94
-
95
- ## app/api/pi/threads/[threadId]/cancel/route.ts
96
-
97
- ```typescript
98
- import type { NextRequest } from "next/server";
99
- import { piClient } from "@/lib/pi-server";
100
- import { noContent, withFail } from "@/lib/http";
101
-
102
- export const runtime = "nodejs";
103
- export const dynamic = "force-dynamic";
104
-
105
- type Context = { params: Promise<{ threadId: string }> };
106
-
107
- export const POST = withFail(async (_req: NextRequest, { params }: Context) => {
108
- const { threadId } = await params;
109
- await piClient.cancelRun(threadId);
110
- return noContent();
111
- });
112
-
113
- ```
114
-
115
- ## app/api/pi/threads/[threadId]/events/route.ts
116
-
117
- ```typescript
118
- import type { NextRequest } from "next/server";
119
- import { piClient } from "@/lib/pi-server";
120
-
121
- export const runtime = "nodejs";
122
- export const dynamic = "force-dynamic";
123
-
124
- type Context = { params: Promise<{ threadId: string }> };
125
-
126
- /**
127
- * SSE stream of `PiClientEvent`s for one thread. Snapshot-first by default;
128
- * `?snapshot=false` lets a client that already called `GET /threads/:id` attach
129
- * only to subsequent live events. A client disconnect unsubscribes but does NOT
130
- * abort the Pi run — disconnect ≠ abort.
131
- */
132
- export async function GET(req: NextRequest, { params }: Context) {
133
- const { threadId } = await params;
134
- const encoder = new TextEncoder();
135
- let unsubscribe: (() => void) | undefined;
136
- let heartbeat: ReturnType<typeof setInterval> | undefined;
137
- const stream = new TransformStream<Uint8Array, Uint8Array>();
138
- const writer = stream.writable.getWriter();
139
-
140
- const write = (chunk: string) => {
141
- void writer.write(encoder.encode(chunk)).catch(() => {
142
- // Writer already closed (client gone); drop.
143
- });
144
- };
145
-
146
- const cleanup = () => {
147
- unsubscribe?.();
148
- if (heartbeat) clearInterval(heartbeat);
149
- void writer.close().catch(() => {
150
- // Already closed.
151
- });
152
- };
153
-
154
- // Flush headers through any buffering proxy immediately.
155
- write(": connected\n\n");
156
- // Keep-alive under typical proxy idle timeouts (30–60s) without the write
157
- // churn of a tighter interval.
158
- heartbeat = setInterval(() => write(": ping\n\n"), 20_000);
159
- unsubscribe = piClient.subscribe(
160
- threadId,
161
- (event) => {
162
- write(`data: ${JSON.stringify(event)}\n\n`);
163
- },
164
- { includeSnapshot: req.nextUrl.searchParams.get("snapshot") !== "false" },
165
- );
166
- req.signal.addEventListener("abort", cleanup, { once: true });
167
-
168
- return new Response(stream.readable, {
169
- headers: {
170
- "content-type": "text/event-stream; charset=utf-8",
171
- "cache-control": "no-cache, no-transform",
172
- connection: "keep-alive",
173
- },
174
- });
175
- }
176
-
177
- ```
178
-
179
- ## app/api/pi/threads/[threadId]/host-ui/route.ts
180
-
181
- ```typescript
182
- import type { NextRequest } from "next/server";
183
- import type { PiHostUiResponse } from "@assistant-ui/react-pi";
184
- import { piClient } from "@/lib/pi-server";
185
- import { noContent, withFail } from "@/lib/http";
186
-
187
- export const runtime = "nodejs";
188
- export const dynamic = "force-dynamic";
189
-
190
- type Context = { params: Promise<{ threadId: string }> };
191
-
192
- export const POST = withFail(async (req: NextRequest, { params }: Context) => {
193
- const { threadId } = await params;
194
- const { response } = (await req.json()) as { response: PiHostUiResponse };
195
- await piClient.respondToHostUiRequest(threadId, response);
196
- return noContent();
197
- });
198
-
199
- ```
200
-
201
- ## app/api/pi/threads/[threadId]/messages/route.ts
202
-
203
- ```typescript
204
- import type { NextRequest } from "next/server";
205
- import type { PiSendMessageInput } from "@assistant-ui/react-pi";
206
- import { piClient } from "@/lib/pi-server";
207
- import { noContent, withFail } from "@/lib/http";
208
-
209
- export const runtime = "nodejs";
210
- export const dynamic = "force-dynamic";
211
-
212
- type Context = { params: Promise<{ threadId: string }> };
213
-
214
- export const POST = withFail(async (req: NextRequest, { params }: Context) => {
215
- const { threadId } = await params;
216
- const { input } = (await req.json()) as { input: PiSendMessageInput };
217
- await piClient.sendMessage(threadId, input);
218
- return noContent();
219
- });
220
-
221
- ```
222
-
223
- ## app/api/pi/threads/[threadId]/model/route.ts
224
-
225
- ```typescript
226
- import type { NextRequest } from "next/server";
227
- import { piClient } from "@/lib/pi-server";
228
- import { noContent, withFail } from "@/lib/http";
229
-
230
- export const runtime = "nodejs";
231
- export const dynamic = "force-dynamic";
232
-
233
- type Context = { params: Promise<{ threadId: string }> };
234
-
235
- export const POST = withFail(async (req: NextRequest, { params }: Context) => {
236
- const { threadId } = await params;
237
- const input = (await req.json()) as { provider: string; modelId: string };
238
- await piClient.setModel(threadId, input);
239
- return noContent();
240
- });
241
-
242
- ```
243
-
244
- ## app/api/pi/threads/[threadId]/queue/clear/route.ts
245
-
246
- ```typescript
247
- import type { NextRequest } from "next/server";
248
- import { piClient } from "@/lib/pi-server";
249
- import { withFail } from "@/lib/http";
250
-
251
- export const runtime = "nodejs";
252
- export const dynamic = "force-dynamic";
253
-
254
- type Context = { params: Promise<{ threadId: string }> };
255
-
256
- export const POST = withFail(async (_req: NextRequest, { params }: Context) => {
257
- const { threadId } = await params;
258
- return Response.json(await piClient.clearQueue(threadId));
259
- });
260
-
261
- ```
262
-
263
- ## app/api/pi/threads/[threadId]/route.ts
264
-
265
- ```typescript
266
- import type { NextRequest } from "next/server";
267
- import { piClient } from "@/lib/pi-server";
268
- import { badRequest, noContent, withFail } from "@/lib/http";
269
-
270
- export const runtime = "nodejs";
271
- export const dynamic = "force-dynamic";
272
-
273
- type Context = { params: Promise<{ threadId: string }> };
274
-
275
- export const GET = withFail(async (_req: NextRequest, { params }: Context) => {
276
- const { threadId } = await params;
277
- return Response.json(await piClient.getThread(threadId));
278
- });
279
-
280
- export const PATCH = withFail(async (req: NextRequest, { params }: Context) => {
281
- const { threadId } = await params;
282
- const { title } = (await req.json()) as { title?: unknown };
283
- if (typeof title !== "string" || title.trim() === "") {
284
- return badRequest("title must be a non-empty string");
285
- }
286
- await piClient.renameThread(threadId, title);
287
- return noContent();
288
- });
289
-
290
- export const DELETE = withFail(
291
- async (_req: NextRequest, { params }: Context) => {
292
- const { threadId } = await params;
293
- await piClient.deleteThread(threadId);
294
- return noContent();
295
- },
296
- );
297
-
298
- ```
299
-
300
- ## app/api/pi/threads/[threadId]/thinking/route.ts
301
-
302
- ```typescript
303
- import type { NextRequest } from "next/server";
304
- import type { PiThinkingLevel } from "@assistant-ui/react-pi";
305
- import { piClient } from "@/lib/pi-server";
306
- import { noContent, withFail } from "@/lib/http";
307
-
308
- export const runtime = "nodejs";
309
- export const dynamic = "force-dynamic";
310
-
311
- type Context = { params: Promise<{ threadId: string }> };
312
-
313
- export const POST = withFail(async (req: NextRequest, { params }: Context) => {
314
- const { threadId } = await params;
315
- const { level } = (await req.json()) as { level: PiThinkingLevel };
316
- await piClient.setThinkingLevel(threadId, level);
317
- return noContent();
318
- });
319
-
320
- ```
321
-
322
- ## app/api/pi/threads/[threadId]/unarchive/route.ts
323
-
324
- ```typescript
325
- import type { NextRequest } from "next/server";
326
- import { piClient } from "@/lib/pi-server";
327
- import { noContent, withFail } from "@/lib/http";
328
-
329
- export const runtime = "nodejs";
330
- export const dynamic = "force-dynamic";
331
-
332
- type Context = { params: Promise<{ threadId: string }> };
333
-
334
- export const POST = withFail(async (_req: NextRequest, { params }: Context) => {
335
- const { threadId } = await params;
336
- await piClient.unarchiveThread(threadId);
337
- return noContent();
338
- });
339
-
340
- ```
341
-
342
- ## app/api/pi/threads/route.ts
343
-
344
- ```typescript
345
- import type { NextRequest } from "next/server";
346
- import { piClient } from "@/lib/pi-server";
347
- import { withFail } from "@/lib/http";
348
-
349
- export const runtime = "nodejs";
350
- export const dynamic = "force-dynamic";
351
-
352
- export const GET = withFail(async (req: NextRequest) => {
353
- const params = new URL(req.url).searchParams;
354
- const workspacePath = params.get("workspacePath") ?? undefined;
355
- const includeArchived = params.get("includeArchived") === "true";
356
- const threads = await piClient.listThreads({
357
- ...(workspacePath ? { workspacePath } : {}),
358
- includeArchived,
359
- });
360
- return Response.json(threads);
361
- });
362
-
363
- export const POST = withFail(async (req: NextRequest) => {
364
- const body = await req.json().catch(() => ({}));
365
- return Response.json(await piClient.createThread(body));
366
- });
367
-
368
- ```
369
-
370
- ## app/globals.css
371
-
372
- ```css
373
- @import "tailwindcss";
374
- @import "tw-animate-css";
375
-
376
- @custom-variant data-open (&:where([data-state="open"], [data-open]:not([data-open="false"])));
377
- @custom-variant data-closed (&:where([data-state="closed"], [data-closed]:not([data-closed="false"])));
378
-
379
- @theme inline {
380
- @keyframes collapsible-down {
381
- from {
382
- height: 0;
383
- }
384
- to {
385
- height: var(
386
- --radix-collapsible-content-height,
387
- var(--collapsible-panel-height, auto)
388
- );
389
- }
390
- }
391
- @keyframes collapsible-up {
392
- from {
393
- height: var(
394
- --radix-collapsible-content-height,
395
- var(--collapsible-panel-height, auto)
396
- );
397
- }
398
- to {
399
- height: 0;
400
- }
401
- }
402
- }
403
-
404
- @source "../../../packages/ui/src";
405
-
406
- @custom-variant dark (&:is(.dark *));
407
-
408
- @theme inline {
409
- --radius-sm: calc(var(--radius) - 4px);
410
- --radius-md: calc(var(--radius) - 2px);
411
- --radius-lg: var(--radius);
412
- --radius-xl: calc(var(--radius) + 4px);
413
- --color-background: var(--background);
414
- --color-foreground: var(--foreground);
415
- --color-card: var(--card);
416
- --color-card-foreground: var(--card-foreground);
417
- --color-popover: var(--popover);
418
- --color-popover-foreground: var(--popover-foreground);
419
- --color-primary: var(--primary);
420
- --color-primary-foreground: var(--primary-foreground);
421
- --color-secondary: var(--secondary);
422
- --color-secondary-foreground: var(--secondary-foreground);
423
- --color-muted: var(--muted);
424
- --color-muted-foreground: var(--muted-foreground);
425
- --color-accent: var(--accent);
426
- --color-accent-foreground: var(--accent-foreground);
427
- --color-destructive: var(--destructive);
428
- --color-border: var(--border);
429
- --color-input: var(--input);
430
- --color-ring: var(--ring);
431
- --color-chart-1: var(--chart-1);
432
- --color-chart-2: var(--chart-2);
433
- --color-chart-3: var(--chart-3);
434
- --color-chart-4: var(--chart-4);
435
- --color-chart-5: var(--chart-5);
436
- --color-sidebar: var(--sidebar);
437
- --color-sidebar-foreground: var(--sidebar-foreground);
438
- --color-sidebar-primary: var(--sidebar-primary);
439
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
440
- --color-sidebar-accent: var(--sidebar-accent);
441
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
442
- --color-sidebar-border: var(--sidebar-border);
443
- --color-sidebar-ring: var(--sidebar-ring);
444
- }
445
-
446
- :root {
447
- --radius: 0.625rem;
448
- --background: oklch(1 0 0);
449
- --foreground: oklch(0.141 0.005 285.823);
450
- --card: oklch(1 0 0);
451
- --card-foreground: oklch(0.141 0.005 285.823);
452
- --popover: oklch(1 0 0);
453
- --popover-foreground: oklch(0.141 0.005 285.823);
454
- --primary: oklch(0.21 0.006 285.885);
455
- --primary-foreground: oklch(0.985 0 0);
456
- --secondary: oklch(0.967 0.001 286.375);
457
- --secondary-foreground: oklch(0.21 0.006 285.885);
458
- --muted: oklch(0.967 0.001 286.375);
459
- --muted-foreground: oklch(0.552 0.016 285.938);
460
- --accent: oklch(0.967 0.001 286.375);
461
- --accent-foreground: oklch(0.21 0.006 285.885);
462
- --destructive: oklch(0.577 0.245 27.325);
463
- --border: oklch(0.92 0.004 286.32);
464
- --input: oklch(0.92 0.004 286.32);
465
- --ring: oklch(0.705 0.015 286.067);
466
- --chart-1: oklch(0.646 0.222 41.116);
467
- --chart-2: oklch(0.6 0.118 184.704);
468
- --chart-3: oklch(0.398 0.07 227.392);
469
- --chart-4: oklch(0.828 0.189 84.429);
470
- --chart-5: oklch(0.769 0.188 70.08);
471
- --sidebar: oklch(0.985 0 0);
472
- --sidebar-foreground: oklch(0.141 0.005 285.823);
473
- --sidebar-primary: oklch(0.21 0.006 285.885);
474
- --sidebar-primary-foreground: oklch(0.985 0 0);
475
- --sidebar-accent: oklch(0.967 0.001 286.375);
476
- --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
477
- --sidebar-border: oklch(0.92 0.004 286.32);
478
- --sidebar-ring: oklch(0.705 0.015 286.067);
479
- }
480
-
481
- .dark {
482
- --background: oklch(0.141 0.005 285.823);
483
- --foreground: oklch(0.985 0 0);
484
- --card: oklch(0.21 0.006 285.885);
485
- --card-foreground: oklch(0.985 0 0);
486
- --popover: oklch(0.21 0.006 285.885);
487
- --popover-foreground: oklch(0.985 0 0);
488
- --primary: oklch(0.92 0.004 286.32);
489
- --primary-foreground: oklch(0.21 0.006 285.885);
490
- --secondary: oklch(0.274 0.006 286.033);
491
- --secondary-foreground: oklch(0.985 0 0);
492
- --muted: oklch(0.274 0.006 286.033);
493
- --muted-foreground: oklch(0.705 0.015 286.067);
494
- --accent: oklch(0.274 0.006 286.033);
495
- --accent-foreground: oklch(0.985 0 0);
496
- --destructive: oklch(0.704 0.191 22.216);
497
- --border: oklch(1 0 0 / 10%);
498
- --input: oklch(1 0 0 / 15%);
499
- --ring: oklch(0.552 0.016 285.938);
500
- --chart-1: oklch(0.488 0.243 264.376);
501
- --chart-2: oklch(0.696 0.17 162.48);
502
- --chart-3: oklch(0.769 0.188 70.08);
503
- --chart-4: oklch(0.627 0.265 303.9);
504
- --chart-5: oklch(0.645 0.246 16.439);
505
- --sidebar: oklch(0.21 0.006 285.885);
506
- --sidebar-foreground: oklch(0.985 0 0);
507
- --sidebar-primary: oklch(0.488 0.243 264.376);
508
- --sidebar-primary-foreground: oklch(0.985 0 0);
509
- --sidebar-accent: oklch(0.274 0.006 286.033);
510
- --sidebar-accent-foreground: oklch(0.985 0 0);
511
- --sidebar-border: oklch(1 0 0 / 10%);
512
- --sidebar-ring: oklch(0.552 0.016 285.938);
513
- }
514
-
515
- @layer base {
516
- * {
517
- @apply border-border outline-ring/50;
518
- }
519
- body {
520
- @apply bg-background text-foreground;
521
- }
522
- }
523
-
524
- ```
525
-
526
- ## app/layout.tsx
527
-
528
- ```tsx
529
- import "./globals.css";
530
- import type { Metadata } from "next";
531
- import type { ReactNode } from "react";
532
-
533
- export const metadata: Metadata = {
534
- title: "assistant-ui × Pi",
535
- description: "Pi coding-agent runtime adapter for assistant-ui",
536
- };
537
-
538
- export default function RootLayout({ children }: { children: ReactNode }) {
539
- return (
540
- <html lang="en" className="h-dvh">
541
- <body className="h-dvh overflow-hidden">{children}</body>
542
- </html>
543
- );
544
- }
545
-
546
- ```
547
-
548
- ## app/page.tsx
549
-
550
- ```tsx
551
- "use client";
552
-
553
- import { useEffect, useState } from "react";
554
- import { Thread } from "../components/assistant-ui/thread";
555
- import { ThreadList } from "@/components/assistant-ui/thread-list";
556
- import { usePiThreadState } from "@assistant-ui/react-pi";
557
- import type { PiRuntimeReadiness } from "@assistant-ui/react-pi";
558
- // Type-only import — erased at build time, so no server code reaches the client.
559
- import type { PiHandshake } from "@/lib/pi-server";
560
- import { PiHandshakeProvider } from "../components/pi-handshake";
561
- import { PiRuntimeProvider } from "./PiRuntimeProvider";
562
- import { WorkspaceBrowser } from "../components/workspace-browser";
563
-
564
- export default function Home() {
565
- const [handshake, setHandshake] = useState<PiHandshake | null>(null);
566
- const [workspacePath, setWorkspacePath] = useState("");
567
-
568
- useEffect(() => {
569
- let active = true;
570
- fetch("/api/pi/handshake")
571
- .then((response) => response.json())
572
- .then((data: PiHandshake) => {
573
- if (!active) return;
574
- setHandshake(data);
575
- setWorkspacePath(data.workspacePath ?? "");
576
- })
577
- .catch(() => {
578
- /* handshake is best-effort; the runtime still works on env defaults. */
579
- });
580
- return () => {
581
- active = false;
582
- };
583
- }, []);
584
-
585
- return (
586
- <PiRuntimeProvider workspacePath={workspacePath || undefined}>
587
- <PiHandshakeProvider value={handshake}>
588
- <div className="flex h-dvh flex-col overflow-hidden">
589
- <Header
590
- workspacePath={workspacePath}
591
- onCommitWorkspace={setWorkspacePath}
592
- />
593
- <ReadinessBanner fallback={handshake?.readiness} />
594
- <div className="flex min-h-0 flex-grow">
595
- <aside className="flex w-72 shrink-0 flex-col overflow-hidden border-r">
596
- <div className="flex-1 overflow-y-auto p-3">
597
- <ThreadList />
598
- </div>
599
- </aside>
600
- <main className="flex min-h-0 min-w-0 flex-grow flex-col overflow-hidden">
601
- <Thread />
602
- </main>
603
- </div>
604
- </div>
605
- </PiHandshakeProvider>
606
- </PiRuntimeProvider>
607
- );
608
- }
609
-
610
- function Header({
611
- workspacePath,
612
- onCommitWorkspace,
613
- }: {
614
- workspacePath: string;
615
- onCommitWorkspace: (value: string) => void;
616
- }) {
617
- return (
618
- <header className="flex items-center gap-3 border-b px-4 py-2">
619
- <span className="font-semibold">assistant-ui × Pi</span>
620
-
621
- <div className="ml-auto flex items-center gap-2">
622
- <WorkspaceBrowser value={workspacePath} onCommit={onCommitWorkspace} />
623
- </div>
624
- </header>
625
- );
626
- }
627
-
628
- function ReadinessBanner({
629
- fallback,
630
- }: {
631
- fallback?: PiRuntimeReadiness | undefined;
632
- }) {
633
- const live = usePiThreadState((state) => state.readiness);
634
- const readiness = live ?? fallback;
635
- if (!readiness || readiness.state === "ready") return null;
636
- return (
637
- <div className="bg-destructive/10 text-destructive border-destructive/20 border-b px-4 py-2 text-sm">
638
- {readiness.message ?? "The Pi runtime has no usable model selected."}
639
- </div>
640
- );
641
- }
642
-
643
- ```
644
-
645
- ## app/PiRuntimeProvider.tsx
646
-
647
- ```tsx
648
- "use client";
649
-
650
- import {
651
- AssistantRuntimeProvider,
652
- SimpleImageAttachmentAdapter,
653
- } from "@assistant-ui/react";
654
- import { createPiHttpClient, usePiRuntime } from "@assistant-ui/react-pi";
655
- import { useMemo, type ReactNode } from "react";
656
-
657
- /**
658
- * Wires the browser `PiClient` (HTTP/SSE over `/api/pi`) into a Pi runtime. The
659
- * client is stable for the app's lifetime; `workspacePath` scopes the thread
660
- * list and seeds new sessions. Image attachments ride along as Pi
661
- * `ImageContent` (the adapter's data URLs pass through `buildPiSendInput`).
662
- */
663
- export function PiRuntimeProvider({
664
- workspacePath,
665
- children,
666
- }: {
667
- workspacePath?: string | undefined;
668
- children: ReactNode;
669
- }) {
670
- const client = useMemo(() => createPiHttpClient(), []);
671
- const adapters = useMemo(
672
- () => ({ attachments: new SimpleImageAttachmentAdapter() }),
673
- [],
674
- );
675
- const runtime = usePiRuntime({
676
- client,
677
- adapters,
678
- ...(workspacePath ? { workspacePath } : {}),
679
- });
680
-
681
- return (
682
- <AssistantRuntimeProvider runtime={runtime}>
683
- {children}
684
- </AssistantRuntimeProvider>
685
- );
686
- }
687
-
688
- ```
689
-
690
- ## components.json
691
-
692
- ```json
693
- {
694
- "$schema": "https://ui.shadcn.com/schema.json",
695
- "style": "new-york",
696
- "rsc": true,
697
- "tsx": true,
698
- "tailwind": {
699
- "config": "",
700
- "css": "app/globals.css",
701
- "baseColor": "zinc",
702
- "cssVariables": true,
703
- "prefix": ""
704
- },
705
- "aliases": {
706
- "components": "@/components",
707
- "utils": "@/lib/utils",
708
- "ui": "@/components/ui",
709
- "lib": "@/lib",
710
- "hooks": "@/hooks"
711
- },
712
- "iconLibrary": "lucide",
713
- "registries": {
714
- "@assistant-ui": "https://r.assistant-ui.com/{name}.json"
715
- }
716
- }
717
-
718
- ```
719
-
720
- ## components/assistant-ui/thinking-level-slider.tsx
721
-
722
- ```tsx
723
- "use client";
724
-
725
- import type { KeyboardEvent } from "react";
726
- import { useModelSelectorEfforts } from "@/components/assistant-ui/model-selector";
727
- import {
728
- Tooltip,
729
- TooltipContent,
730
- TooltipProvider,
731
- TooltipTrigger,
732
- } from "@/components/ui/radix/tooltip";
733
- import { cn } from "@/lib/utils";
734
-
735
- /**
736
- * Line-graph style thinking level picker, composed into ModelSelector.Content
737
- * via the useModelSelectorEfforts hook. Pi models expose up to six levels,
738
- * which overflow the built-in horizontal ModelSelector.Effort layout — here
739
- * each level is a thin vertical line that grows on hover and names itself in
740
- * a tooltip, with the active level standing taller in the accent color.
741
- */
742
- export const ThinkingLevelSlider = ({
743
- label = "Thinking",
744
- }: {
745
- label?: string;
746
- }) => {
747
- const { efforts, effort, setEffort } = useModelSelectorEfforts();
748
-
749
- if (!efforts?.length) return null;
750
-
751
- const activeIndex = efforts.findIndex((e) => e.id === effort);
752
- const lastIndex = efforts.length - 1;
753
-
754
- const selectIndex = (event: KeyboardEvent<HTMLDivElement>, index: number) => {
755
- const target = Math.min(Math.max(index, 0), lastIndex);
756
- const next = efforts[target];
757
- if (!next) return;
758
- event.preventDefault();
759
- setEffort(next.id);
760
- event.currentTarget
761
- .querySelectorAll<HTMLElement>('[role="radio"]')
762
- [target]?.focus();
763
- };
764
-
765
- return (
766
- <div className="flex items-center justify-between gap-3 border-t px-3 py-2">
767
- <span className="text-muted-foreground text-xs">{label}</span>
768
- <TooltipProvider>
769
- <div
770
- role="radiogroup"
771
- aria-label={label}
772
- className="flex items-end"
773
- onKeyDown={(event) => {
774
- if (event.key === "ArrowRight" || event.key === "ArrowUp") {
775
- selectIndex(event, activeIndex + 1);
776
- } else if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
777
- selectIndex(event, activeIndex - 1);
778
- } else if (event.key === "Home") {
779
- selectIndex(event, 0);
780
- } else if (event.key === "End") {
781
- selectIndex(event, lastIndex);
782
- }
783
- }}
784
- >
785
- {efforts.map((option, index) => {
786
- const isActive = index === activeIndex;
787
- return (
788
- <Tooltip key={option.id} disableHoverableContent>
789
- <TooltipTrigger asChild>
790
- <button
791
- type="button"
792
- role="radio"
793
- aria-checked={isActive}
794
- aria-label={option.name}
795
- tabIndex={index === Math.max(activeIndex, 0) ? 0 : -1}
796
- data-state={isActive ? "on" : "off"}
797
- onClick={() => setEffort(option.id)}
798
- className="group focus-visible:ring-ring/50 flex h-6 w-3.5 items-end justify-center rounded-sm outline-none focus-visible:ring-2"
799
- >
800
- <span
801
- className={cn(
802
- "rounded-full transition-all duration-150 ease-out",
803
- isActive
804
- ? "bg-primary h-4.5 w-0.5"
805
- : "bg-muted-foreground/40 group-hover:bg-foreground/70 h-2.5 w-0.5 group-hover:h-4 group-hover:w-[3px] group-focus-visible:h-4 group-focus-visible:w-[3px]",
806
- )}
807
- />
808
- </button>
809
- </TooltipTrigger>
810
- <TooltipContent side="top" sideOffset={2}>
811
- {option.name}
812
- </TooltipContent>
813
- </Tooltip>
814
- );
815
- })}
816
- </div>
817
- </TooltipProvider>
818
- <span className="text-foreground min-w-12 text-end text-xs">
819
- {efforts[activeIndex]?.name}
820
- </span>
821
- </div>
822
- );
823
- };
824
-
825
- ```
826
-
827
- ## components/assistant-ui/thread.tsx
828
-
829
- ```tsx
830
- "use client";
831
-
832
- import {
833
- ComposerAddAttachment,
834
- ComposerAttachments,
835
- UserMessageAttachments,
836
- } from "@/components/assistant-ui/attachment";
837
- import { ContextDisplay } from "@/components/assistant-ui/context-display";
838
- import { ModelSelector } from "@/components/assistant-ui/model-selector";
839
- import { MarkdownText } from "@/components/assistant-ui/markdown-text";
840
- import {
841
- isPiSteerQueueItemId,
842
- responseForRequest,
843
- usePiHostUiRequests,
844
- usePiRuntimeExtras,
845
- usePiThreadState,
846
- type PiHostUiRequest,
847
- type PiThinkingLevel,
848
- } from "@assistant-ui/react-pi";
849
- import { usePiHandshake } from "../pi-handshake";
850
- import { modelKey } from "@/lib/model-key";
851
- import { ThinkingLevelSlider } from "./thinking-level-slider";
852
- import {
853
- Reasoning,
854
- ReasoningContent,
855
- ReasoningRoot,
856
- ReasoningText,
857
- ReasoningTrigger,
858
- } from "@/components/assistant-ui/reasoning";
859
- import {
860
- ToolGroupContent,
861
- ToolGroupRoot,
862
- ToolGroupTrigger,
863
- } from "@/components/assistant-ui/tool-group";
864
- import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
865
- import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
866
- import { Button } from "@/components/ui/button";
867
- import { cn } from "@/lib/utils";
868
- import {
869
- ActionBarMorePrimitive,
870
- ActionBarPrimitive,
871
- AuiIf,
872
- BranchPickerPrimitive,
873
- ComposerPrimitive,
874
- ErrorPrimitive,
875
- groupPartByType,
876
- MessagePrimitive,
877
- SuggestionPrimitive,
878
- ThreadPrimitive,
879
- useAui,
880
- useAuiState,
881
- } from "@assistant-ui/react";
882
- import {
883
- ArrowDownIcon,
884
- ArrowUpIcon,
885
- CheckIcon,
886
- ChevronLeftIcon,
887
- ChevronRightIcon,
888
- CopyIcon,
889
- DownloadIcon,
890
- ListEndIcon,
891
- MoreHorizontalIcon,
892
- PencilIcon,
893
- RefreshCwIcon,
894
- SquareIcon,
895
- Trash2Icon,
896
- WrenchIcon,
897
- } from "lucide-react";
898
- import { useState, type FC } from "react";
899
-
900
- export const Thread: FC = () => {
901
- return (
902
- <ThreadPrimitive.Root
903
- className="aui-root aui-thread-root bg-background @container flex h-full flex-col"
904
- style={{
905
- ["--thread-max-width" as string]: "44rem",
906
- ["--composer-radius" as string]: "24px",
907
- ["--composer-padding" as string]: "10px",
908
- }}
909
- >
910
- <ThreadPrimitive.Viewport
911
- data-slot="aui_thread-viewport"
912
- className="relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-smooth"
913
- >
914
- <div className="mx-auto flex w-full max-w-(--thread-max-width) flex-1 flex-col px-4 pt-4">
915
- <AuiIf condition={(s) => s.thread.isEmpty}>
916
- <ThreadWelcome />
917
- </AuiIf>
918
-
919
- <div
920
- data-slot="aui_message-group"
921
- className="mb-10 flex flex-col gap-y-8 empty:hidden"
922
- >
923
- <ThreadPrimitive.Messages>
924
- {() => <ThreadMessage />}
925
- </ThreadPrimitive.Messages>
926
- </div>
927
-
928
- <ThreadPrimitive.ViewportFooter className="aui-thread-viewport-footer bg-background sticky bottom-0 mt-auto flex flex-col gap-4 overflow-visible rounded-t-(--composer-radius) pb-4 md:pb-6">
929
- <ThreadScrollToBottom />
930
- <HostUiRequests />
931
- <Composer />
932
- </ThreadPrimitive.ViewportFooter>
933
- </div>
934
- </ThreadPrimitive.Viewport>
935
- </ThreadPrimitive.Root>
936
- );
937
- };
938
-
939
- const ThreadMessage: FC = () => {
940
- const role = useAuiState((s) => s.message.role);
941
- const isEditing = useAuiState((s) => s.message.composer.isEditing);
942
-
943
- if (isEditing) return <EditComposer />;
944
- if (role === "user") return <UserMessage />;
945
- return <AssistantMessage />;
946
- };
947
-
948
- const ThreadScrollToBottom: FC = () => {
949
- return (
950
- <ThreadPrimitive.ScrollToBottom asChild>
951
- <TooltipIconButton
952
- tooltip="Scroll to bottom"
953
- variant="outline"
954
- className="aui-thread-scroll-to-bottom dark:border-border dark:bg-background dark:hover:bg-accent absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible"
955
- >
956
- <ArrowDownIcon />
957
- </TooltipIconButton>
958
- </ThreadPrimitive.ScrollToBottom>
959
- );
960
- };
961
-
962
- const ThreadWelcome: FC = () => {
963
- return (
964
- <div className="aui-thread-welcome-root my-auto flex grow flex-col">
965
- <div className="aui-thread-welcome-center flex w-full grow flex-col items-center justify-center">
966
- <div className="aui-thread-welcome-message flex size-full flex-col justify-center px-4">
967
- <h1 className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in fill-mode-both text-2xl font-semibold duration-200">
968
- Hello there!
969
- </h1>
970
- <p className="aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in fill-mode-both text-muted-foreground text-xl delay-75 duration-200">
971
- How can I help you today?
972
- </p>
973
- </div>
974
- </div>
975
- <ThreadSuggestions />
976
- </div>
977
- );
978
- };
979
-
980
- const ThreadSuggestions: FC = () => {
981
- return (
982
- <div className="aui-thread-welcome-suggestions grid w-full gap-2 pb-4 @md:grid-cols-2">
983
- <ThreadPrimitive.Suggestions>
984
- {() => <ThreadSuggestionItem />}
985
- </ThreadPrimitive.Suggestions>
986
- </div>
987
- );
988
- };
989
-
990
- const ThreadSuggestionItem: FC = () => {
991
- return (
992
- <div className="aui-thread-welcome-suggestion-display fade-in slide-in-from-bottom-2 animate-in fill-mode-both duration-200 nth-[n+3]:hidden @md:nth-[n+3]:block">
993
- <SuggestionPrimitive.Trigger send asChild>
994
- <Button
995
- variant="ghost"
996
- className="aui-thread-welcome-suggestion bg-background hover:bg-muted h-auto w-full flex-wrap items-start justify-start gap-1 rounded-3xl border px-4 py-3 text-start text-sm transition-colors @md:flex-col"
997
- >
998
- <SuggestionPrimitive.Title className="aui-thread-welcome-suggestion-text-1 font-medium" />
999
- <SuggestionPrimitive.Description className="aui-thread-welcome-suggestion-text-2 text-muted-foreground empty:hidden" />
1000
- </Button>
1001
- </SuggestionPrimitive.Trigger>
1002
- </div>
1003
- );
1004
- };
1005
-
1006
- const HostUiRequests: FC = () => {
1007
- const { requests, respond } = usePiHostUiRequests();
1008
- const request = requests[0];
1009
- if (!request) return null;
1010
- return (
1011
- <HostUiRequestCard key={request.id} request={request} respond={respond} />
1012
- );
1013
- };
1014
-
1015
- const HostUiRequestCard: FC<{
1016
- request: PiHostUiRequest;
1017
- respond: ReturnType<typeof usePiHostUiRequests>["respond"];
1018
- }> = ({ request, respond }) => {
1019
- const [value, setValue] = useState(
1020
- request.kind === "editor" ? (request.prefill ?? "") : "",
1021
- );
1022
-
1023
- const submitValue = () => {
1024
- void respond(responseForRequest(request, value));
1025
- };
1026
-
1027
- return (
1028
- <div className="border-border bg-background mx-2 rounded-2xl border p-3 text-sm shadow-sm">
1029
- <div className="font-medium">{request.title}</div>
1030
- {request.kind === "confirm" ? (
1031
- <p className="text-muted-foreground mt-1">{request.message}</p>
1032
- ) : request.kind === "select" ? (
1033
- <div className="mt-2 flex flex-wrap gap-2">
1034
- {request.options.map((option) => (
1035
- <Button
1036
- key={option}
1037
- size="sm"
1038
- variant="outline"
1039
- onClick={() => void respond(responseForRequest(request, option))}
1040
- >
1041
- {option}
1042
- </Button>
1043
- ))}
1044
- </div>
1045
- ) : request.kind === "editor" ? (
1046
- <textarea
1047
- value={value}
1048
- onChange={(event) => setValue(event.currentTarget.value)}
1049
- className="border-input mt-2 min-h-24 w-full rounded-md border bg-transparent p-2 font-mono text-xs"
1050
- />
1051
- ) : (
1052
- <input
1053
- value={value}
1054
- placeholder={request.placeholder}
1055
- onChange={(event) => setValue(event.currentTarget.value)}
1056
- className="border-input mt-2 w-full rounded-md border bg-transparent px-2 py-1"
1057
- />
1058
- )}
1059
- <div className="mt-3 flex justify-end gap-2">
1060
- <Button
1061
- size="sm"
1062
- variant="ghost"
1063
- onClick={() =>
1064
- void respond(
1065
- request.kind === "confirm"
1066
- ? responseForRequest(request, false)
1067
- : { requestId: request.id, dismissed: true },
1068
- )
1069
- }
1070
- >
1071
- {request.kind === "confirm" ? "Deny" : "Dismiss"}
1072
- </Button>
1073
- {request.kind !== "select" && (
1074
- <Button
1075
- size="sm"
1076
- onClick={() =>
1077
- request.kind === "confirm"
1078
- ? void respond(responseForRequest(request, true))
1079
- : submitValue()
1080
- }
1081
- >
1082
- {request.kind === "confirm" ? "Approve" : "Submit"}
1083
- </Button>
1084
- )}
1085
- </div>
1086
- </div>
1087
- );
1088
- };
1089
-
1090
- /** Mid-run sends sit in Pi's queue (not the thread); this card mirrors them
1091
- * above the composer. Pi only supports clearing the whole queue — no per-item
1092
- * remove/promote — so the lone action restores all queued text to the composer. */
1093
- const ComposerQueue: FC = () => {
1094
- const aui = useAui();
1095
- const { clearQueue } = usePiRuntimeExtras();
1096
- const queueLength = useAuiState((s) => s.composer.queue.length);
1097
- if (queueLength === 0) return null;
1098
-
1099
- const handleClear = () => {
1100
- clearQueue()
1101
- .then(({ steering, followUp }) => {
1102
- const restored = [...steering, ...followUp].join("\n");
1103
- if (!restored) return;
1104
- const composer = aui.composer;
1105
- const current = composer.getState().text;
1106
- composer.setText(current ? `${current}\n${restored}` : restored);
1107
- })
1108
- .catch((error: unknown) => console.error("Failed to clear queue", error));
1109
- };
1110
-
1111
- return (
1112
- <div className="bg-muted/50 border-border/60 text-muted-foreground -mb-4 flex flex-col gap-1.5 rounded-t-(--composer-radius) border border-b-0 px-4 pt-2.5 pb-7 text-sm">
1113
- <div className="flex items-center justify-between gap-2">
1114
- <span className="text-xs font-medium">Queued</span>
1115
- <Button
1116
- variant="ghost"
1117
- size="sm"
1118
- className="h-6 gap-1 px-2 text-xs"
1119
- onClick={handleClear}
1120
- >
1121
- <Trash2Icon className="size-3" />
1122
- Clear
1123
- </Button>
1124
- </div>
1125
- <ComposerPrimitive.Queue>
1126
- {({ queueItem }) => (
1127
- <div className="flex items-center gap-2">
1128
- <ListEndIcon className="size-3.5 shrink-0" />
1129
- <span className="min-w-0 flex-1 truncate">{queueItem.prompt}</span>
1130
- {isPiSteerQueueItemId(queueItem.id) && (
1131
- <span className="border-border rounded-full border px-1.5 text-[10px] uppercase">
1132
- steer
1133
- </span>
1134
- )}
1135
- </div>
1136
- )}
1137
- </ComposerPrimitive.Queue>
1138
- </div>
1139
- );
1140
- };
1141
-
1142
- const Composer: FC = () => {
1143
- return (
1144
- <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col">
1145
- <ComposerQueue />
1146
- <ComposerPrimitive.AttachmentDropzone asChild>
1147
- <div
1148
- data-slot="aui_composer-shell"
1149
- className="bg-background focus-within:border-ring/75 focus-within:ring-ring/20 data-[dragging=true]:border-ring data-[dragging=true]:bg-accent/50 flex w-full flex-col gap-2 rounded-(--composer-radius) border p-(--composer-padding) transition-shadow focus-within:ring-2 data-[dragging=true]:border-dashed"
1150
- >
1151
- <ComposerAttachments />
1152
- <ComposerPrimitive.Input
1153
- placeholder="Send a message..."
1154
- className="aui-composer-input placeholder:text-muted-foreground/80 max-h-32 min-h-10 w-full resize-none bg-transparent px-1.75 py-1 text-sm outline-none"
1155
- rows={1}
1156
- autoFocus
1157
- aria-label="Message input"
1158
- />
1159
- <ComposerAction />
1160
- </div>
1161
- </ComposerPrimitive.AttachmentDropzone>
1162
- </ComposerPrimitive.Root>
1163
- );
1164
- };
1165
-
1166
- const ComposerAction: FC = () => {
1167
- return (
1168
- <div className="aui-composer-action-wrapper relative flex items-center justify-between">
1169
- <div className="flex items-center gap-1">
1170
- <ComposerAddAttachment />
1171
- <ComposerModelSelector />
1172
- </div>
1173
- <div className="flex items-center gap-1.5">
1174
- <ComposerContextRing />
1175
- {/* Pi queues mid-run sends (follow-up/steer), so Send stays available
1176
- while text is typed even during a run; Stop shows when empty. */}
1177
- <AuiIf condition={(s) => !s.thread.isRunning || !s.composer.isEmpty}>
1178
- <ComposerPrimitive.Send asChild>
1179
- <TooltipIconButton
1180
- tooltip="Send message"
1181
- side="bottom"
1182
- type="button"
1183
- variant="default"
1184
- size="icon"
1185
- className="aui-composer-send size-8 rounded-full"
1186
- aria-label="Send message"
1187
- >
1188
- <ArrowUpIcon className="aui-composer-send-icon size-4" />
1189
- </TooltipIconButton>
1190
- </ComposerPrimitive.Send>
1191
- </AuiIf>
1192
- <AuiIf condition={(s) => s.thread.isRunning && s.composer.isEmpty}>
1193
- <ComposerPrimitive.Cancel asChild>
1194
- <Button
1195
- type="button"
1196
- variant="default"
1197
- size="icon"
1198
- className="aui-composer-cancel size-8 rounded-full"
1199
- aria-label="Stop generating"
1200
- >
1201
- <SquareIcon className="aui-composer-cancel-icon size-3 fill-current" />
1202
- </Button>
1203
- </ComposerPrimitive.Cancel>
1204
- </AuiIf>
1205
- </div>
1206
- </div>
1207
- );
1208
- };
1209
-
1210
- const selectedModelKey = (provider?: string, modelId?: string) =>
1211
- provider && modelId ? modelKey(provider, modelId) : undefined;
1212
-
1213
- /** Model + thinking-level picker seeded from the server handshake and wired to
1214
- * Pi's per-thread `session.setModel`/`session.setThinkingLevel`. Per-model
1215
- * thinking levels come from the handshake; Pi clamps unsupported levels and the
1216
- * effective config is reflected by the next snapshot/event. */
1217
- const ComposerModelSelector: FC = () => {
1218
- const handshake = usePiHandshake();
1219
- const { metadata, setModel, setThinkingLevel, status } = usePiRuntimeExtras();
1220
- const selected =
1221
- selectedModelKey(metadata.config?.provider, metadata.config?.modelId) ??
1222
- handshake?.selectedModelId;
1223
- const thinkingLevel = metadata.config?.thinkingLevel;
1224
- if (!handshake || handshake.models.length === 0) return null;
1225
-
1226
- return (
1227
- <ModelSelector.Root
1228
- models={handshake.models}
1229
- {...(selected ? { value: selected } : {})}
1230
- onValueChange={(value) => {
1231
- if (status === "running") return;
1232
- const model = handshake.models.find((m) => m.id === value);
1233
- if (!model) return;
1234
- void setModel({ provider: model.provider, modelId: model.modelId });
1235
- }}
1236
- effort={typeof thinkingLevel === "string" ? thinkingLevel : "medium"}
1237
- onEffortChange={(effort) => {
1238
- if (status === "running") return;
1239
- void setThinkingLevel(effort as PiThinkingLevel);
1240
- }}
1241
- >
1242
- <ModelSelector.Trigger variant="ghost" size="sm" />
1243
- <ModelSelector.Content>
1244
- <ModelSelector.Search />
1245
- <ModelSelector.List />
1246
- {/* Custom slider instead of ModelSelector.Effort: Pi's six thinking
1247
- levels overflow the horizontal segmented layout. */}
1248
- <ThinkingLevelSlider />
1249
- </ModelSelector.Content>
1250
- </ModelSelector.Root>
1251
- );
1252
- };
1253
-
1254
- /** Context-window usage as a ring beside the send button. Driven by Pi's
1255
- * `context_usage` events (`usePiThreadState`); hidden until the first run
1256
- * reports a window. */
1257
- const ComposerContextRing: FC = () => {
1258
- const usage = usePiThreadState((s) => s.contextUsage);
1259
- if (!usage || !usage.contextWindow) return null;
1260
- return (
1261
- <ContextDisplay.Ring
1262
- modelContextWindow={usage.contextWindow}
1263
- usage={{ totalTokens: usage.tokens ?? 0 }}
1264
- side="top"
1265
- />
1266
- );
1267
- };
1268
-
1269
- const MessageError: FC = () => {
1270
- return (
1271
- <MessagePrimitive.Error>
1272
- <ErrorPrimitive.Root className="aui-message-error-root border-destructive bg-destructive/10 text-destructive dark:bg-destructive/5 mt-2 rounded-md border p-3 text-sm dark:text-red-200">
1273
- <ErrorPrimitive.Message className="aui-message-error-message line-clamp-2" />
1274
- </ErrorPrimitive.Root>
1275
- </MessagePrimitive.Error>
1276
- );
1277
- };
1278
-
1279
- const AssistantMessage: FC = () => {
1280
- // reserves space for action bar and compensates with `-mb` for consistent msg spacing
1281
- // keeps hovered action bar from shifting layout (autohide doesn't support absolute positioning well)
1282
- // for pt-[n] use -mb-[n + 6] & min-h-[n + 6] to preserve compensation
1283
- const ACTION_BAR_PT = "pt-1.5";
1284
- const ACTION_BAR_HEIGHT = `-mb-7.5 min-h-7.5 ${ACTION_BAR_PT}`;
1285
-
1286
- return (
1287
- <MessagePrimitive.Root
1288
- data-slot="aui_assistant-message-root"
1289
- data-role="assistant"
1290
- className="fade-in slide-in-from-bottom-1 animate-in relative duration-150"
1291
- >
1292
- <div
1293
- data-slot="aui_assistant-message-content"
1294
- // [contain-intrinsic-size:auto_24px] fixes issue #4104, don't change without checking for regressions
1295
- className="text-foreground px-2 leading-relaxed wrap-break-word [contain-intrinsic-size:auto_24px] [content-visibility:auto]"
1296
- >
1297
- <MessagePrimitive.GroupedParts
1298
- groupBy={groupPartByType({
1299
- reasoning: ["group-chainOfThought", "group-reasoning"],
1300
- "tool-call": ["group-chainOfThought", "group-tool"],
1301
- "standalone-tool-call": [],
1302
- })}
1303
- >
1304
- {({ part, children }) => {
1305
- switch (part.type) {
1306
- case "group-chainOfThought":
1307
- return <div data-slot="aui_chain-of-thought">{children}</div>;
1308
- case "group-reasoning": {
1309
- const running = part.status.type === "running";
1310
- return (
1311
- <ReasoningRoot
1312
- defaultOpen={running}
1313
- variant="ghost"
1314
- className="my-1 mb-0"
1315
- >
1316
- <ReasoningTrigger active={running} />
1317
- <ReasoningContent aria-busy={running}>
1318
- <ReasoningText>{children}</ReasoningText>
1319
- </ReasoningContent>
1320
- </ReasoningRoot>
1321
- );
1322
- }
1323
- case "group-tool":
1324
- if (part.indices.length === 1) return <>{children}</>;
1325
- return (
1326
- <ToolGroupRoot variant="ghost" className="my-1">
1327
- <div className="text-muted-foreground flex items-center gap-2">
1328
- <WrenchIcon className="size-4 shrink-0" />
1329
- <ToolGroupTrigger
1330
- count={part.indices.length}
1331
- active={part.status.type === "running"}
1332
- />
1333
- </div>
1334
- <ToolGroupContent className="ml-5">
1335
- {children}
1336
- </ToolGroupContent>
1337
- </ToolGroupRoot>
1338
- );
1339
- case "text":
1340
- return <MarkdownText />;
1341
- case "reasoning":
1342
- return <Reasoning {...part} />;
1343
- case "tool-call":
1344
- return part.toolUI ?? <ToolFallback {...part} />;
1345
- case "indicator":
1346
- return (
1347
- <span
1348
- data-slot="aui_assistant-message-indicator"
1349
- className="animate-pulse font-sans"
1350
- aria-label="Assistant is working"
1351
- >
1352
- {"●"}
1353
- </span>
1354
- );
1355
- default:
1356
- return null;
1357
- }
1358
- }}
1359
- </MessagePrimitive.GroupedParts>
1360
- <MessageError />
1361
- </div>
1362
-
1363
- <div
1364
- data-slot="aui_assistant-message-footer"
1365
- className={cn("ms-2 flex items-center", ACTION_BAR_HEIGHT)}
1366
- >
1367
- <BranchPicker />
1368
- <AssistantActionBar />
1369
- </div>
1370
- </MessagePrimitive.Root>
1371
- );
1372
- };
1373
-
1374
- const AssistantActionBar: FC = () => {
1375
- return (
1376
- <ActionBarPrimitive.Root
1377
- hideWhenRunning
1378
- autohide="not-last"
1379
- className="aui-assistant-action-bar-root text-muted-foreground col-start-3 row-start-2 -ms-1 flex gap-1"
1380
- >
1381
- <ActionBarPrimitive.Copy asChild>
1382
- <TooltipIconButton tooltip="Copy">
1383
- <AuiIf condition={(s) => s.message.isCopied}>
1384
- <CheckIcon />
1385
- </AuiIf>
1386
- <AuiIf condition={(s) => !s.message.isCopied}>
1387
- <CopyIcon />
1388
- </AuiIf>
1389
- </TooltipIconButton>
1390
- </ActionBarPrimitive.Copy>
1391
- <ActionBarPrimitive.Reload asChild>
1392
- <TooltipIconButton tooltip="Refresh">
1393
- <RefreshCwIcon />
1394
- </TooltipIconButton>
1395
- </ActionBarPrimitive.Reload>
1396
- <ActionBarMorePrimitive.Root>
1397
- <ActionBarMorePrimitive.Trigger asChild>
1398
- <TooltipIconButton
1399
- tooltip="More"
1400
- className="data-[state=open]:bg-accent"
1401
- >
1402
- <MoreHorizontalIcon />
1403
- </TooltipIconButton>
1404
- </ActionBarMorePrimitive.Trigger>
1405
- <ActionBarMorePrimitive.Content
1406
- side="bottom"
1407
- align="start"
1408
- className="aui-action-bar-more-content bg-popover text-popover-foreground z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-md"
1409
- >
1410
- <ActionBarPrimitive.ExportMarkdown asChild>
1411
- <ActionBarMorePrimitive.Item className="aui-action-bar-more-item hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none">
1412
- <DownloadIcon className="size-4" />
1413
- Export as Markdown
1414
- </ActionBarMorePrimitive.Item>
1415
- </ActionBarPrimitive.ExportMarkdown>
1416
- </ActionBarMorePrimitive.Content>
1417
- </ActionBarMorePrimitive.Root>
1418
- </ActionBarPrimitive.Root>
1419
- );
1420
- };
1421
-
1422
- const UserMessage: FC = () => {
1423
- return (
1424
- <MessagePrimitive.Root
1425
- data-slot="aui_user-message-root"
1426
- className="fade-in slide-in-from-bottom-1 animate-in grid auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] content-start gap-y-2 px-2 duration-150 [contain-intrinsic-size:auto_60px] [content-visibility:auto] [&:where(>*)]:col-start-2"
1427
- data-role="user"
1428
- >
1429
- <UserMessageAttachments />
1430
-
1431
- <div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
1432
- <div className="aui-user-message-content peer bg-muted text-foreground rounded-2xl px-4 py-2.5 wrap-break-word empty:hidden">
1433
- <MessagePrimitive.Parts />
1434
- </div>
1435
- <div className="aui-user-action-bar-wrapper absolute start-0 top-1/2 -translate-x-full -translate-y-1/2 pe-2 peer-empty:hidden rtl:translate-x-full">
1436
- <UserActionBar />
1437
- </div>
1438
- </div>
1439
-
1440
- <BranchPicker
1441
- data-slot="aui_user-branch-picker"
1442
- className="col-span-full col-start-1 row-start-3 -me-1 justify-end"
1443
- />
1444
- </MessagePrimitive.Root>
1445
- );
1446
- };
1447
-
1448
- const UserActionBar: FC = () => {
1449
- return (
1450
- <ActionBarPrimitive.Root
1451
- hideWhenRunning
1452
- autohide="not-last"
1453
- className="aui-user-action-bar-root flex flex-col items-end"
1454
- >
1455
- <ActionBarPrimitive.Edit asChild>
1456
- <TooltipIconButton tooltip="Edit" className="aui-user-action-edit p-4">
1457
- <PencilIcon />
1458
- </TooltipIconButton>
1459
- </ActionBarPrimitive.Edit>
1460
- </ActionBarPrimitive.Root>
1461
- );
1462
- };
1463
-
1464
- const EditComposer: FC = () => {
1465
- return (
1466
- <MessagePrimitive.Root
1467
- data-slot="aui_edit-composer-wrapper"
1468
- className="flex flex-col px-2"
1469
- >
1470
- <ComposerPrimitive.Root className="aui-edit-composer-root bg-muted ms-auto flex w-full max-w-[85%] flex-col rounded-2xl">
1471
- <ComposerPrimitive.Input
1472
- className="aui-edit-composer-input text-foreground min-h-14 w-full resize-none bg-transparent p-4 text-sm outline-none"
1473
- autoFocus
1474
- />
1475
- <div className="aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end">
1476
- <ComposerPrimitive.Cancel asChild>
1477
- <Button variant="ghost" size="sm">
1478
- Cancel
1479
- </Button>
1480
- </ComposerPrimitive.Cancel>
1481
- <ComposerPrimitive.Send asChild>
1482
- <Button size="sm">Update</Button>
1483
- </ComposerPrimitive.Send>
1484
- </div>
1485
- </ComposerPrimitive.Root>
1486
- </MessagePrimitive.Root>
1487
- );
1488
- };
1489
-
1490
- const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
1491
- className,
1492
- ...rest
1493
- }) => {
1494
- return (
1495
- <BranchPickerPrimitive.Root
1496
- hideWhenSingleBranch
1497
- className={cn(
1498
- "aui-branch-picker-root text-muted-foreground -ms-2 me-2 inline-flex items-center text-xs",
1499
- className,
1500
- )}
1501
- {...rest}
1502
- >
1503
- <BranchPickerPrimitive.Previous asChild>
1504
- <TooltipIconButton tooltip="Previous">
1505
- <ChevronLeftIcon />
1506
- </TooltipIconButton>
1507
- </BranchPickerPrimitive.Previous>
1508
- <span className="aui-branch-picker-state font-medium">
1509
- <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
1510
- </span>
1511
- <BranchPickerPrimitive.Next asChild>
1512
- <TooltipIconButton tooltip="Next">
1513
- <ChevronRightIcon />
1514
- </TooltipIconButton>
1515
- </BranchPickerPrimitive.Next>
1516
- </BranchPickerPrimitive.Root>
1517
- );
1518
- };
1519
-
1520
- ```
1521
-
1522
- ## components/pi-handshake.tsx
1523
-
1524
- ```tsx
1525
- "use client";
1526
-
1527
- import { createContext, useContext, type ReactNode } from "react";
1528
- import type { ModelOption } from "@/components/assistant-ui/model-selector";
1529
-
1530
- /** The slice of the server handshake the composer needs to render its model
1531
- * selector. Provided by the page (which fetches `/api/pi/handshake`) and
1532
- * consumed deep inside the composer, so the model picker can live in the
1533
- * composer action bar without prop-drilling through the Thread. */
1534
- export type PiModelOption = ModelOption & {
1535
- provider: string;
1536
- modelId: string;
1537
- };
1538
-
1539
- export type PiHandshakeValue = {
1540
- models: PiModelOption[];
1541
- selectedModelId?: string | undefined;
1542
- };
1543
-
1544
- const PiHandshakeContext = createContext<PiHandshakeValue | null>(null);
1545
-
1546
- export function PiHandshakeProvider({
1547
- value,
1548
- children,
1549
- }: {
1550
- value: PiHandshakeValue | null;
1551
- children: ReactNode;
1552
- }) {
1553
- return (
1554
- <PiHandshakeContext.Provider value={value}>
1555
- {children}
1556
- </PiHandshakeContext.Provider>
1557
- );
1558
- }
1559
-
1560
- export function usePiHandshake(): PiHandshakeValue | null {
1561
- return useContext(PiHandshakeContext);
1562
- }
1563
-
1564
- ```
1565
-
1566
- ## components/workspace-browser.tsx
1567
-
1568
- ```tsx
1569
- "use client";
1570
-
1571
- import { useRef, useState } from "react";
1572
- import { ArrowLeft, Folder } from "lucide-react";
1573
- import type { FsListing } from "@/app/api/pi/fs/route";
1574
- import {
1575
- Dialog,
1576
- DialogClose,
1577
- DialogContent,
1578
- DialogDescription,
1579
- DialogFooter,
1580
- DialogHeader,
1581
- DialogTitle,
1582
- DialogTrigger,
1583
- } from "@/components/ui/radix/dialog";
1584
- import { Button } from "@/components/ui/button";
1585
- import { ScrollArea } from "@/components/ui/scroll-area";
1586
- import { Spinner } from "@/components/ui/spinner";
1587
-
1588
- /** Server-backed directory browser. Click a folder to navigate in; the footer
1589
- * commits whatever directory is currently in view. */
1590
- export function WorkspaceBrowser({
1591
- value,
1592
- onCommit,
1593
- }: {
1594
- value: string;
1595
- onCommit: (path: string) => void;
1596
- }) {
1597
- const [open, setOpen] = useState(false);
1598
- const [listing, setListing] = useState<FsListing | null>(null);
1599
- const [loading, setLoading] = useState(false);
1600
- const [error, setError] = useState<string | null>(null);
1601
- // Monotonic request token: rapid navigation fires overlapping fetches, and a
1602
- // slow earlier response must not overwrite the directory navigated to last.
1603
- const requestSeq = useRef(0);
1604
-
1605
- const load = (path?: string) => {
1606
- const seq = ++requestSeq.current;
1607
- setLoading(true);
1608
- setError(null);
1609
- const qs = path ? `?path=${encodeURIComponent(path)}` : "";
1610
- fetch(`/api/pi/fs${qs}`)
1611
- .then(async (response) => {
1612
- if (!response.ok) {
1613
- throw new Error(`Failed to list directory (HTTP ${response.status})`);
1614
- }
1615
- return (await response.json()) as FsListing;
1616
- })
1617
- .then((data) => {
1618
- if (seq === requestSeq.current) setListing(data);
1619
- })
1620
- .catch((err: unknown) => {
1621
- if (seq === requestSeq.current) {
1622
- setError(err instanceof Error ? err.message : String(err));
1623
- }
1624
- })
1625
- .finally(() => {
1626
- if (seq === requestSeq.current) setLoading(false);
1627
- });
1628
- };
1629
-
1630
- const currentName = value.split("/").filter(Boolean).at(-1);
1631
-
1632
- return (
1633
- <Dialog
1634
- open={open}
1635
- onOpenChange={(next) => {
1636
- setOpen(next);
1637
- if (next) load(value || undefined);
1638
- }}
1639
- >
1640
- <div className="flex items-center gap-2">
1641
- {currentName && (
1642
- <span
1643
- className="text-muted-foreground max-w-72 truncate font-mono text-xs"
1644
- title={value}
1645
- >
1646
- …/{currentName}
1647
- </span>
1648
- )}
1649
- <DialogTrigger asChild>
1650
- <Button variant="outline" size="sm">
1651
- Browse…
1652
- </Button>
1653
- </DialogTrigger>
1654
- </div>
1655
-
1656
- <DialogContent className="sm:max-w-lg">
1657
- <DialogHeader className="min-w-0">
1658
- <DialogTitle>Select workspace</DialogTitle>
1659
- <DialogDescription className="truncate font-mono text-xs">
1660
- {listing?.path ?? "…"}
1661
- </DialogDescription>
1662
- </DialogHeader>
1663
-
1664
- <div className="flex items-center gap-2">
1665
- <Button
1666
- variant="ghost"
1667
- size="xs"
1668
- disabled={!listing?.parent || loading}
1669
- onClick={() => listing?.parent && load(listing.parent)}
1670
- >
1671
- <ArrowLeft /> Back
1672
- </Button>
1673
- {loading && <Spinner className="size-4" />}
1674
- </div>
1675
-
1676
- <ScrollArea className="h-72 min-w-0 rounded-md border [&_[data-radix-scroll-area-viewport]>div]:!block">
1677
- {error && (
1678
- <p className="text-destructive px-3 py-2 text-xs">{error}</p>
1679
- )}
1680
- {listing?.entries.map((entry) => (
1681
- <button
1682
- key={entry.path}
1683
- type="button"
1684
- onClick={() => load(entry.path)}
1685
- className="hover:bg-accent flex w-full min-w-0 items-center gap-2 px-3 py-1.5 text-left font-mono text-xs"
1686
- >
1687
- <Folder className="size-3.5 shrink-0" />
1688
- <span className="truncate">{entry.name}</span>
1689
- </button>
1690
- ))}
1691
- {listing && listing.entries.length === 0 && (
1692
- <p className="text-muted-foreground px-3 py-2 text-xs">
1693
- No subfolders
1694
- </p>
1695
- )}
1696
- </ScrollArea>
1697
-
1698
- <DialogFooter>
1699
- <DialogClose asChild>
1700
- <Button variant="ghost" size="sm">
1701
- Cancel
1702
- </Button>
1703
- </DialogClose>
1704
- <Button
1705
- size="sm"
1706
- disabled={!listing}
1707
- onClick={() => {
1708
- if (listing) onCommit(listing.path);
1709
- setOpen(false);
1710
- }}
1711
- >
1712
- Select this folder
1713
- </Button>
1714
- </DialogFooter>
1715
- </DialogContent>
1716
- </Dialog>
1717
- );
1718
- }
1719
-
1720
- ```
1721
-
1722
- ## lib/http.ts
1723
-
1724
- ```typescript
1725
- /** Shared response helpers for the Pi example routes. */
1726
-
1727
- /** 500 with a JSON `{ error }` body — `createPiHttpClient` surfaces the text. */
1728
- export const fail = (error: unknown): Response =>
1729
- Response.json(
1730
- { error: error instanceof Error ? error.message : String(error) },
1731
- { status: 500 },
1732
- );
1733
-
1734
- /** 400 with a JSON `{ error }` body, for invalid request input. */
1735
- export const badRequest = (message: string): Response =>
1736
- Response.json({ error: message }, { status: 400 });
1737
-
1738
- /** 204 for write endpoints that return nothing. */
1739
- export const noContent = (): Response => new Response(null, { status: 204 });
1740
-
1741
- /** Wrap a route handler so any thrown error becomes a `fail()` response. */
1742
- export const withFail =
1743
- <Args extends unknown[]>(
1744
- handler: (...args: Args) => Response | Promise<Response>,
1745
- ) =>
1746
- async (...args: Args): Promise<Response> => {
1747
- try {
1748
- return await handler(...args);
1749
- } catch (error) {
1750
- return fail(error);
1751
- }
1752
- };
1753
-
1754
- ```
1755
-
1756
- ## lib/model-key.ts
1757
-
1758
- ```typescript
1759
- /** Stable, collision-free model option id — model ids repeat across providers.
1760
- * Shared by the server handshake and the composer's model selector; the two
1761
- * must agree for the seeded selection to preselect in the UI. */
1762
- export const modelKey = (provider: string, modelId: string): string =>
1763
- `${provider}:${modelId}`;
1764
-
1765
- ```
1766
-
1767
- ## lib/pi-server.ts
1768
-
1769
- ```typescript
1770
- /**
1771
- * Server-only Pi wiring for the example.
1772
- *
1773
- * Owns the process-singleton `createPiNodeClient` (the in-process SDK host) and
1774
- * the env-seeded model resolution. Imported only from route
1775
- * handlers — never from a client component — so the Pi SDK and its file I/O stay
1776
- * on the server.
1777
- *
1778
- * Model resolution mirrors what Pi's own `createAgentSession` does: an explicit
1779
- * PI_PROVIDER + PI_MODEL_ID wins, otherwise we fall back to Pi's configured
1780
- * default (`settings.json`'s `defaultProvider`/`defaultModel`, read via
1781
- * `SettingsManager`). The resolved `Model` is handed to the supervisor as the
1782
- * default for every new session, and powers the model-selector catalog and the
1783
- * readiness banner. So a user who is simply authenticated with `pi` (and has a
1784
- * default model picked) needs no env at all.
1785
- *
1786
- * `PI_CODING_AGENT_DIR` is Pi's own env var for the agent config dir
1787
- * (~/.pi/agent by default); when set, every Pi config source below points at it.
1788
- */
1789
- import {
1790
- ModelRegistry,
1791
- ModelRuntime,
1792
- SettingsManager,
1793
- } from "@earendil-works/pi-coding-agent";
1794
- import {
1795
- createPiNodeClient,
1796
- type PiModelInfo,
1797
- type PiRuntimeReadiness,
1798
- type PiThinkingLevel,
1799
- } from "@assistant-ui/react-pi/node";
1800
- import { modelKey } from "./model-key";
1801
-
1802
- const env = (key: string): string | undefined => {
1803
- const value = process.env[key];
1804
- return value && value.trim() ? value.trim() : undefined;
1805
- };
1806
-
1807
- const workspacePath = env("PI_WORKSPACE_PATH") ?? process.cwd();
1808
- const agentDir = env("PI_CODING_AGENT_DIR");
1809
-
1810
- const modelRuntime = await ModelRuntime.create(
1811
- agentDir ? { authPath: `${agentDir}/auth.json` } : {},
1812
- );
1813
- const modelRegistry = new ModelRegistry(modelRuntime);
1814
- const settingsManager = SettingsManager.create(workspacePath, agentDir);
1815
-
1816
- // Env wins; otherwise fall back to Pi's configured default (settings.json).
1817
- const provider = env("PI_PROVIDER") ?? settingsManager.getDefaultProvider();
1818
- const modelId = env("PI_MODEL_ID") ?? settingsManager.getDefaultModel();
1819
- const modelSource: "env" | "pi-default" =
1820
- env("PI_PROVIDER") && env("PI_MODEL_ID") ? "env" : "pi-default";
1821
-
1822
- const seededModel =
1823
- provider && modelId ? modelRegistry.find(provider, modelId) : undefined;
1824
-
1825
- export const piClient = createPiNodeClient({
1826
- workspacePath,
1827
- ...(agentDir ? { agentDir } : {}),
1828
- ...(seededModel ? { model: seededModel } : {}),
1829
- });
1830
-
1831
- export type PiModelOption = {
1832
- id: string;
1833
- name: string;
1834
- description?: string;
1835
- provider: string;
1836
- modelId: string;
1837
- /** Thinking levels the model supports; omitted for non-reasoning models. */
1838
- efforts?: { id: string; name: string }[];
1839
- };
1840
-
1841
- /** Display names for Pi's thinking levels (the UI-facing half of `efforts`;
1842
- * which levels a model supports comes from `PiModelInfo`). */
1843
- const THINKING_LEVEL_NAMES: Record<PiThinkingLevel, string> = {
1844
- off: "Off",
1845
- minimal: "Minimal",
1846
- low: "Low",
1847
- medium: "Medium",
1848
- high: "High",
1849
- xhigh: "xHigh",
1850
- };
1851
-
1852
- const ALL_THINKING_LEVELS = Object.keys(
1853
- THINKING_LEVEL_NAMES,
1854
- ) as PiThinkingLevel[];
1855
-
1856
- const thinkingEfforts = (
1857
- model: PiModelInfo,
1858
- ): { id: string; name: string }[] | undefined => {
1859
- if (!model.supportsThinking) return undefined;
1860
- // No `availableThinkingLevels` means the model declares no per-level map;
1861
- // every level stays selectable (provider defaults apply).
1862
- return (model.availableThinkingLevels ?? ALL_THINKING_LEVELS).map((id) => ({
1863
- id,
1864
- name: THINKING_LEVEL_NAMES[id],
1865
- }));
1866
- };
1867
-
1868
- export type PiHandshake = {
1869
- workspacePath: string;
1870
- /** Models Pi can run here (auth-configured first, else the full catalog). */
1871
- models: PiModelOption[];
1872
- /** The env-seeded selection, shown as the selector's default. */
1873
- selectedModelId: string | undefined;
1874
- readiness: PiRuntimeReadiness;
1875
- };
1876
-
1877
- const computeReadiness = (): PiRuntimeReadiness => {
1878
- if (!provider || !modelId) {
1879
- return {
1880
- state: "missing-model",
1881
- message:
1882
- "No default model configured. Pick one with `pi`, or set PI_PROVIDER and PI_MODEL_ID.",
1883
- };
1884
- }
1885
- if (!seededModel) {
1886
- return {
1887
- state: "unavailable-model",
1888
- selection: { provider, modelId },
1889
- message: `${provider}/${modelId} is not in Pi's model registry.`,
1890
- };
1891
- }
1892
- if (!modelRegistry.hasConfiguredAuth(seededModel)) {
1893
- return {
1894
- state: "missing-credentials",
1895
- provider,
1896
- message: `No credentials configured for ${provider}. Authenticate with \`pi\`, then restart the server.`,
1897
- };
1898
- }
1899
- return {
1900
- state: "ready",
1901
- selection: { provider, modelId },
1902
- source: modelSource,
1903
- };
1904
- };
1905
-
1906
- export const getHandshake = async (): Promise<PiHandshake> => {
1907
- const catalog = await piClient.getAvailableModels();
1908
- return {
1909
- workspacePath,
1910
- models: catalog.map((model) => {
1911
- const efforts = thinkingEfforts(model);
1912
- return {
1913
- id: modelKey(model.provider, model.modelId),
1914
- name: model.name ?? model.modelId,
1915
- description: model.provider,
1916
- provider: model.provider,
1917
- modelId: model.modelId,
1918
- ...(efforts ? { efforts } : {}),
1919
- };
1920
- }),
1921
- selectedModelId: seededModel
1922
- ? modelKey(String(seededModel.provider), seededModel.id)
1923
- : undefined,
1924
- readiness: computeReadiness(),
1925
- };
1926
- };
1927
-
1928
- ```
1929
-
1930
- ## next.config.ts
1931
-
1932
- ```typescript
1933
- import { withAui } from "@assistant-ui/next";
1934
- import type { NextConfig } from "next";
1935
-
1936
- const nextConfig: NextConfig = {
1937
- // The Pi SDK is a native Node dependency; keep it external to the server
1938
- // bundle so Next doesn't try to trace/bundle it.
1939
- serverExternalPackages: ["@earendil-works/pi-coding-agent"],
1940
- };
1941
-
1942
- export default withAui(nextConfig);
1943
-
1944
- ```
1945
-
1946
- ## package.json
1947
-
1948
- ```json
1949
- {
1950
- "name": "with-pi",
1951
- "version": "0.0.0",
1952
- "private": true,
1953
- "type": "module",
1954
- "scripts": {
1955
- "dev": "next dev",
1956
- "build": "next build",
1957
- "start": "next start"
1958
- },
1959
- "dependencies": {
1960
- "@assistant-ui/react": "workspace:*",
1961
- "@assistant-ui/react-markdown": "workspace:*",
1962
- "@assistant-ui/react-pi": "workspace:*",
1963
- "@assistant-ui/ui": "workspace:*",
1964
- "@earendil-works/pi-coding-agent": "^0.84.2",
1965
- "class-variance-authority": "^0.7.1",
1966
- "clsx": "^2.1.1",
1967
- "lucide-react": "^1.33.0",
1968
- "next": "^16.3.2",
1969
- "radix-ui": "^1.6.7",
1970
- "react": "^19.2.8",
1971
- "react-dom": "^19.2.8",
1972
- "tailwind-merge": "^3.6.0"
1973
- },
1974
- "devDependencies": {
1975
- "@assistant-ui/next": "workspace:*",
1976
- "@assistant-ui/x-buildutils": "workspace:*",
1977
- "@tailwindcss/postcss": "^4.3.3",
1978
- "@types/node": "^26.2.0",
1979
- "@types/react": "^19.2.18",
1980
- "@types/react-dom": "^19.2.5",
1981
- "postcss": "^8.5.26",
1982
- "tailwindcss": "^4.3.3",
1983
- "tw-animate-css": "^1.4.0",
1984
- "typescript": "^7.0.2"
1985
- }
1986
- }
1987
-
1988
- ```
1989
-
1990
- ## README.md
1991
-
1992
- ```markdown
1993
- # assistant-ui × Pi
1994
-
1995
- A minimal local harness for the [`@assistant-ui/react-pi`](../../packages/react-pi)
1996
- adapter, driving the [Pi coding agent](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)
1997
- in-process.
1998
-
1999
- It demonstrates the MVP surface: a thread list, new sessions, a composer with
2000
- steer / follow-up / stop, streaming assistant responses (text + reasoning), tool
2001
- cards with streaming output, the blocking host-UI dialogs (confirm / input /
2002
- select / editor), a model + credential readiness banner, a context-usage
2003
- indicator, and a read-only workspace browser (`components/workspace-browser.tsx`
2004
- backed by `/api/pi/fs`) for inspecting the agent's working directory. It proves
2005
- the adapter surface, not a full desktop app shell.
2006
-
2007
- ## How it fits together
2008
-
2009
- ```
2010
- browser server (Next.js, Node runtime)
2011
- ───────── ──────────────────────────────
2012
- usePiRuntime app/api/pi/** ← route layer
2013
- └ createPiHttpClient ──HTTP──▶ piClient = createPiNodeClient(...)
2014
- (fetch + SSE) └ PiThreadSupervisor → Pi SDK (AgentSession)
2015
- ```
2016
-
2017
- `createPiHttpClient` and the route layer are two halves of one `PiClient`
2018
- contract: the browser talks HTTP/SSE, the server runs the SDK in-process behind
2019
- the process-singleton supervisor.
2020
-
2021
- ## Model resolution
2022
-
2023
- Resolution mirrors Pi's own `createAgentSession`: if you set
2024
- `PI_PROVIDER` + `PI_MODEL_ID` they win; otherwise the server falls back to Pi's
2025
- configured default (`settings.json`'s `defaultProvider`/`defaultModel`). So if
2026
- you're authenticated with `pi` and have a default model picked, **no env is
2027
- required**. The resolved model is handed to every new session and seeds the
2028
- in-app **model selector** (via `@assistant-ui/ui`'s `ModelSelector`) and the
2029
- readiness banner's credential check. The selector is wired to Pi's per-thread
2030
- `setModel`, and the composer exposes Pi's thinking-level control. Set
2031
- `PI_CODING_AGENT_DIR` to point at a non-default agent config dir (defaults to
2032
- `~/.pi/agent`).
2033
-
2034
- ## Run it
2035
-
2036
- 1. Authenticate Pi once and pick a default model (writes `~/.pi/agent/`):
2037
-
2038
- ```bash
2039
- pnpm dlx @earendil-works/pi-coding-agent
2040
- ```
2041
-
2042
- 2. Start it (no env needed if Pi has a default model):
2043
-
2044
- ```bash
2045
- pnpm dev
2046
- ```
2047
-
2048
- To override the model or workspace, copy `.env.example` to `.env.local` and
2049
- set `PI_PROVIDER` / `PI_MODEL_ID` / `PI_WORKSPACE_PATH`.
2050
-
2051
- The agent reads and writes files and runs shell commands in `PI_WORKSPACE_PATH`.
2052
- Point it at a scratch directory.
2053
-
2054
- ```
2055
-
2056
- ## tsconfig.json
2057
-
2058
- ```json
2059
- {
2060
- "extends": "@assistant-ui/x-buildutils/ts/next",
2061
- "compilerOptions": {
2062
- "paths": {
2063
- "@/*": ["./*"],
2064
- "@/components/assistant-ui/*": [
2065
- "../../packages/ui/src/components/assistant-ui/*"
2066
- ],
2067
- "@/hooks/*": ["../../packages/ui/src/hooks/*"],
2068
- "@/components/ui/*": [
2069
- "../../packages/ui/src/components/ui/base/*",
2070
- "../../packages/ui/src/components/ui/radix/*"
2071
- ],
2072
- "@/components/ui/radix/*": [
2073
- "../../packages/ui/src/components/ui/radix/*"
2074
- ],
2075
- "@/lib/utils": ["../../packages/ui/src/lib/utils"],
2076
- "@assistant-ui/ui/*": ["../../packages/ui/src/*"]
2077
- }
2078
- },
2079
- "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
2080
- "exclude": ["node_modules"]
2081
- }
2082
-
2083
- ```
2084
-