@kitn.ai/ui 0.20.1 → 0.22.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 (510) hide show
  1. package/README.md +44 -45
  2. package/dist/components/artifact-card.d.ts +51 -0
  3. package/dist/components/artifact.d.ts +1 -1
  4. package/dist/components/attachments.d.ts +2 -2
  5. package/dist/components/audio-visualizer/index.d.ts +3 -3
  6. package/dist/components/audio-visualizer/labs/lab-choreography.d.ts +59 -0
  7. package/dist/components/audio-visualizer/labs/lab-shaders.d.ts +22 -0
  8. package/dist/components/audio-visualizer/labs/lab-visualizer.d.ts +18 -0
  9. package/dist/components/audio-visualizer/variant-aurora.d.ts +2 -2
  10. package/dist/components/audio-visualizer/variant-bar.d.ts +2 -2
  11. package/dist/components/audio-visualizer/variant-custom.d.ts +2 -2
  12. package/dist/components/audio-visualizer/variant-grid.d.ts +1 -1
  13. package/dist/components/audio-visualizer/variant-radial.d.ts +1 -1
  14. package/dist/components/audio-visualizer/variant-wave.d.ts +1 -1
  15. package/dist/components/card-fallback.d.ts +12 -2
  16. package/dist/components/card-renderer.d.ts +63 -2
  17. package/dist/components/chat-container.d.ts +3 -0
  18. package/dist/components/chat-scope-picker.d.ts +2 -2
  19. package/dist/components/chat-thread.d.ts +77 -18
  20. package/dist/components/choice-card.d.ts +3 -28
  21. package/dist/components/composer-dom.d.ts +1 -1
  22. package/dist/components/composer-highlight.d.ts +1 -1
  23. package/dist/components/composer-history.d.ts +1 -1
  24. package/dist/components/composer.d.ts +1 -1
  25. package/dist/components/confirm-card.d.ts +3 -18
  26. package/dist/components/context.d.ts +8 -4
  27. package/dist/components/conversation-item.d.ts +1 -1
  28. package/dist/components/conversation-list.d.ts +4 -3
  29. package/dist/components/embed.d.ts +3 -3
  30. package/dist/components/file-tree.d.ts +11 -22
  31. package/dist/components/form-widgets.d.ts +1 -1
  32. package/dist/components/form.d.ts +3 -44
  33. package/dist/components/link-preview.d.ts +3 -3
  34. package/dist/components/loader.d.ts +30 -48
  35. package/dist/components/message-skills.d.ts +4 -0
  36. package/dist/components/message.d.ts +84 -21
  37. package/dist/components/model-switcher.d.ts +2 -2
  38. package/dist/components/prompt-input.d.ts +2 -2
  39. package/dist/components/response-compare-types.d.ts +11 -2
  40. package/dist/components/response-compare.d.ts +3 -3
  41. package/dist/components/source.d.ts +12 -3
  42. package/dist/components/tasks-card.d.ts +3 -31
  43. package/dist/components/thread.d.ts +17 -5
  44. package/dist/components/toast.d.ts +2 -2
  45. package/dist/components/tool-classify.d.ts +8 -0
  46. package/dist/components/tool-types.d.ts +29 -1
  47. package/dist/components/tool.d.ts +3 -3
  48. package/dist/components/use-card-resolution.d.ts +1 -1
  49. package/dist/create-tween-COnPwAQN.js +338 -0
  50. package/dist/create-tween-DmTUaXWE.js +341 -0
  51. package/dist/create-tween-qs-jjvNA.js +7 -0
  52. package/dist/custom-elements.json +1726 -463
  53. package/dist/elements/agent-card.js +1 -1
  54. package/dist/elements/artifact.js +1 -1
  55. package/dist/elements/attachments.js +1 -1
  56. package/dist/elements/audio-visualizer.js +1 -1
  57. package/dist/elements/avatar.js +1 -1
  58. package/dist/elements/badge.js +1 -1
  59. package/dist/elements/button.js +1 -1
  60. package/dist/elements/card.js +1 -1
  61. package/dist/elements/cards.js +1 -1
  62. package/dist/elements/chain-of-thought.js +1 -1
  63. package/dist/elements/chat-scope-picker.js +1 -1
  64. package/dist/elements/chat-types.d.ts +70 -10
  65. package/dist/elements/chat-workspace.js +1 -1
  66. package/dist/elements/chat.js +1 -1
  67. package/dist/elements/checkpoint.js +1 -1
  68. package/dist/elements/choice.js +1 -1
  69. package/dist/elements/chunks/{Icon-Tj58ES_d.js → Icon-CHG5z5L7.js} +1 -1
  70. package/dist/elements/chunks/{action-icons-ns1Fnedj.js → action-icons-BAtCEEgg.js} +1 -1
  71. package/dist/elements/chunks/arrow-left-CFPTeL1Y.js +1 -0
  72. package/dist/elements/chunks/artifact-fBZOE4SO.js +1 -0
  73. package/dist/elements/chunks/{attachments-mCB5F1Fq.js → attachments-DpYMe5VQ.js} +1 -1
  74. package/dist/elements/chunks/audio-visualizer-DFwzAcuL.js +1 -0
  75. package/dist/elements/chunks/{badge-B4Zuajl2.js → badge-D6zodm6E.js} +1 -1
  76. package/dist/elements/chunks/{button-ZWNif3--.js → button-C42vrGvO.js} +1 -1
  77. package/dist/elements/chunks/card-renderer-DMZsSuvU.js +1 -0
  78. package/dist/elements/chunks/card-tags-D8lZ-C_U.js +1 -0
  79. package/dist/elements/chunks/chat-thread-CNNEhol-.js +1 -0
  80. package/dist/elements/chunks/check-7Q3K-iXE.js +1 -0
  81. package/dist/elements/chunks/chevron-down-CpNYLn7E.js +1 -0
  82. package/dist/elements/chunks/chevron-right-DrH85Fha.js +1 -0
  83. package/dist/elements/chunks/choice-card-YoWjpsFV.js +1 -0
  84. package/dist/elements/chunks/circle-C9KRjKYv.js +1 -0
  85. package/dist/elements/chunks/circle-check-Dmi7Y4zw.js +1 -0
  86. package/dist/elements/chunks/{circle-x-B7faJFlY.js → circle-x-QIxXda6k.js} +1 -1
  87. package/dist/elements/chunks/code-block-CqeugReY.js +1 -0
  88. package/dist/elements/chunks/{collapsible-DM1Ckd4e.js → collapsible-sTv-_5lF.js} +1 -1
  89. package/dist/elements/chunks/{composer-BguS0QL_.js → composer-ldQyeVEB.js} +2 -2
  90. package/dist/elements/chunks/confirm-card-C0RNOwfn.js +1 -0
  91. package/dist/elements/chunks/context-5qgypNHC.js +1 -0
  92. package/dist/elements/chunks/conversation-list-DLzOacXz.js +1 -0
  93. package/dist/elements/chunks/create-tween-V4TXZ93X.js +7 -0
  94. package/dist/elements/chunks/default-input-DeeRSDHc.js +1 -0
  95. package/dist/elements/chunks/define-lE-QvQFn.js +1 -0
  96. package/dist/elements/chunks/{disclosure-CIZ5c8nO.js → disclosure-DZSa9jog.js} +1 -1
  97. package/dist/elements/chunks/{download-Dsbg3ste.js → download-B2ZmnGqW.js} +1 -1
  98. package/dist/elements/chunks/{dropdown-BDd1-Zgi.js → dropdown-R3B7cSRE.js} +1 -1
  99. package/dist/elements/chunks/{ellipsis-Dw6qrss-.js → ellipsis-BmxceY4l.js} +1 -1
  100. package/dist/elements/chunks/embed-BVrtTZPo.js +1 -0
  101. package/dist/elements/chunks/{external-link-Vt74zAoL.js → external-link-BbdR7YO7.js} +1 -1
  102. package/dist/elements/chunks/{file-text-CZrZF6_v.js → file-text-D7dijo1A.js} +1 -1
  103. package/dist/elements/chunks/{file-tree-DF4okKyo.js → file-tree-0upJ5jeb.js} +1 -1
  104. package/dist/elements/chunks/{folder-glP5rha4.js → folder-D8z58t7B.js} +1 -1
  105. package/dist/elements/chunks/form-p5BKJNw5.js +1 -0
  106. package/dist/elements/chunks/hover-card-CN9s2md5.js +1 -0
  107. package/dist/elements/chunks/{icon-BLb8beoi.js → icon-CNiKP37E.js} +1 -1
  108. package/dist/elements/chunks/{info-CIsv92JK.js → info-BSnuJiHI.js} +1 -1
  109. package/dist/elements/chunks/{input-Dds1kFyt.js → input-BauDmzxX.js} +1 -1
  110. package/dist/elements/chunks/{kbd-De_xbgi9.js → kbd-BlHJaP9b.js} +1 -1
  111. package/dist/elements/chunks/{link-i4r_k7z4.js → link-DEi5iUWC.js} +1 -1
  112. package/dist/elements/chunks/link-preview-BaHyM6W4.js +1 -0
  113. package/dist/elements/chunks/{loader-BmQH4s3V.js → loader-CmJG_Fkq.js} +1 -1
  114. package/dist/elements/chunks/{markdown-Zi21WhUz.js → markdown-D2jaMpCN.js} +1 -1
  115. package/dist/elements/chunks/message-C5UfXZ2Z.js +1 -0
  116. package/dist/elements/chunks/message-CXWLmmLn.js +1 -0
  117. package/dist/elements/chunks/message-square-hXylwsy0.js +1 -0
  118. package/dist/elements/chunks/{minimize-2--ifeCGrl.js → minimize-2-C9ytZFMU.js} +1 -1
  119. package/dist/elements/chunks/model-switcher-CivGfD7-.js +1 -0
  120. package/dist/elements/chunks/{overlay-kD8dwRv_.js → overlay-DJL_sbOm.js} +1 -1
  121. package/dist/elements/chunks/{paperclip-q21dYOSK.js → paperclip-CFaB8naM.js} +1 -1
  122. package/dist/elements/chunks/{progress-bar-BrAIIaaJ.js → progress-bar-Br5SksfR.js} +1 -1
  123. package/dist/elements/chunks/{prompt-suggestion-Bjn2K-fR.js → prompt-suggestion-CMIvtAhU.js} +1 -1
  124. package/dist/elements/chunks/reasoning-DpZcNzLq.js +1 -0
  125. package/dist/elements/chunks/{resizable-Dp4JExql.js → resizable-Q8i3T9a_.js} +1 -1
  126. package/dist/elements/chunks/{rotate-cw-CFWWkpj2.js → rotate-cw-B5IHCBFV.js} +1 -1
  127. package/dist/elements/chunks/{scroll-area-Bb0kHH1j.js → scroll-area-eLeydBje.js} +1 -1
  128. package/dist/elements/chunks/scroll-button-DWbz0yA1.js +1 -0
  129. package/dist/elements/chunks/{separator-9kRgr1X9.js → separator-Xic78AD_.js} +1 -1
  130. package/dist/elements/chunks/{settings-B8lmec4D.js → settings-B8H51pAx.js} +1 -1
  131. package/dist/elements/chunks/{settings-group-I0hEuXiN.js → settings-group-CsG9eYLe.js} +1 -1
  132. package/dist/elements/chunks/{share-C3lTiYGt.js → share-CNTx0z__.js} +1 -1
  133. package/dist/elements/chunks/skeleton-CLlHTcMI.js +1 -0
  134. package/dist/elements/chunks/slots-CIw9RlAe.js +1 -0
  135. package/dist/elements/chunks/source-BoVcU5ok.js +1 -0
  136. package/dist/elements/chunks/{star-CSJtGxgv.js → star-CxQVTVhI.js} +1 -1
  137. package/dist/elements/chunks/{store-CHyW95TP.js → store-CLtS5CUG.js} +1 -1
  138. package/dist/elements/chunks/tasks-card-CzlkLmUt.js +1 -0
  139. package/dist/elements/chunks/{text-shimmer-Cn9TmNpI.js → text-shimmer-D5NhpIQ1.js} +1 -1
  140. package/dist/elements/chunks/textarea-x5Uxsmtm.js +1 -0
  141. package/dist/elements/chunks/{thumbs-up-PNVVThEF.js → thumbs-up-UobcJnx9.js} +1 -1
  142. package/dist/elements/chunks/{toast-store-DKbQn3Wn.js → toast-store-BOfs9eXq.js} +1 -1
  143. package/dist/elements/chunks/{tool-DjTRpIpl.js → tool-Be6Dh_dt.js} +1 -1
  144. package/dist/elements/chunks/tooltip-RxMxyCLF.js +1 -0
  145. package/dist/elements/chunks/{triangle-alert-DsHTHCPm.js → triangle-alert-C4yxH2XN.js} +1 -1
  146. package/dist/elements/chunks/use-card-resolution-jva9ehH6.js +1 -0
  147. package/dist/elements/chunks/{variant-aurora-Dc4nQUia.js → variant-aurora-Dx4VRcjS.js} +2 -2
  148. package/dist/elements/chunks/{variant-custom-y_3kVQhS.js → variant-custom-Y1ZTWgEa.js} +1 -1
  149. package/dist/elements/chunks/{variant-wave-D5OR1-9Z.js → variant-wave-B9gml9Cg.js} +2 -2
  150. package/dist/elements/chunks/{video-B7vmenoC.js → video-CrPHsytT.js} +1 -1
  151. package/dist/elements/chunks/x-_-OPHept.js +1 -0
  152. package/dist/elements/coachmark.js +1 -1
  153. package/dist/elements/code-block.js +1 -1
  154. package/dist/elements/command.js +1 -1
  155. package/dist/elements/compare.js +1 -1
  156. package/dist/elements/composer.js +1 -1
  157. package/dist/elements/confirm-card.js +1 -1
  158. package/dist/elements/context-meter.js +1 -1
  159. package/dist/elements/conversation-list.js +1 -1
  160. package/dist/elements/default-input.d.ts +34 -7
  161. package/dist/elements/define.d.ts +1 -0
  162. package/dist/elements/dialog.js +1 -1
  163. package/dist/elements/disclosure.d.ts +1 -1
  164. package/dist/elements/editable-label.js +1 -1
  165. package/dist/elements/element-data-types.d.ts +107 -0
  166. package/dist/elements/embed.js +1 -1
  167. package/dist/elements/empty.js +1 -1
  168. package/dist/elements/feedback-bar.js +1 -1
  169. package/dist/elements/file-tree.js +1 -1
  170. package/dist/elements/file-upload.js +1 -1
  171. package/dist/elements/form.js +1 -1
  172. package/dist/elements/hover-card.js +1 -1
  173. package/dist/elements/icon.js +1 -1
  174. package/dist/elements/image.js +1 -1
  175. package/dist/elements/input.js +1 -1
  176. package/dist/elements/kbd.js +1 -1
  177. package/dist/elements/link-preview.js +1 -1
  178. package/dist/elements/loader.js +1 -1
  179. package/dist/elements/markdown.js +1 -1
  180. package/dist/elements/menu.js +1 -1
  181. package/dist/elements/message-skills.js +1 -1
  182. package/dist/elements/message.js +1 -1
  183. package/dist/elements/model-switcher.js +1 -1
  184. package/dist/elements/nav.js +1 -1
  185. package/dist/elements/notice.js +1 -1
  186. package/dist/elements/pane-group.js +1 -1
  187. package/dist/elements/pane.js +1 -1
  188. package/dist/elements/popover.js +1 -1
  189. package/dist/elements/progress-bar.js +1 -1
  190. package/dist/elements/prompt-dock.js +1 -1
  191. package/dist/elements/prompt-input.js +1 -1
  192. package/dist/elements/prompt-suggestions.js +1 -1
  193. package/dist/elements/reasoning.js +1 -1
  194. package/dist/elements/register.d.ts +8 -6
  195. package/dist/elements/remote.js +1 -1
  196. package/dist/elements/resizable.js +1 -1
  197. package/dist/elements/response-stream.js +1 -1
  198. package/dist/elements/screen.js +1 -1
  199. package/dist/elements/scroll-area.js +1 -1
  200. package/dist/elements/scroll-button.js +1 -1
  201. package/dist/elements/search.js +1 -1
  202. package/dist/elements/segmented.js +1 -1
  203. package/dist/elements/separator.js +1 -1
  204. package/dist/elements/setting-item.js +1 -1
  205. package/dist/elements/settings-group.js +1 -1
  206. package/dist/elements/skeleton.js +1 -1
  207. package/dist/elements/slot-text.d.ts +51 -0
  208. package/dist/elements/slots.d.ts +21 -0
  209. package/dist/elements/source.js +1 -1
  210. package/dist/elements/status.js +1 -1
  211. package/dist/elements/switch.js +1 -1
  212. package/dist/elements/tabs.js +1 -1
  213. package/dist/elements/tasks.js +1 -1
  214. package/dist/elements/text-shimmer.js +1 -1
  215. package/dist/elements/thinking-bar.js +1 -1
  216. package/dist/elements/thread.js +1 -1
  217. package/dist/elements/toast.js +1 -1
  218. package/dist/elements/tool.js +1 -1
  219. package/dist/elements/tooltip.js +1 -1
  220. package/dist/elements/validate-messages.d.ts +25 -0
  221. package/dist/elements/voice-input.js +1 -1
  222. package/dist/elements/voice-output.js +1 -1
  223. package/dist/elements.d.ts +2734 -217
  224. package/dist/index.d.ts +163 -125
  225. package/dist/index.js +6572 -5990
  226. package/dist/index.server.d.ts +5 -0
  227. package/dist/index.server.js +5403 -4860
  228. package/dist/kai-provider.es.d.ts +5 -0
  229. package/dist/kai-provider.es.js +79 -77
  230. package/dist/kai.es.d.ts +5 -0
  231. package/dist/kai.es.js +1 -1
  232. package/dist/llms/llms-full.txt +774 -151
  233. package/dist/llms/llms.txt +57 -5
  234. package/dist/mcp.es.js +4756 -782
  235. package/dist/primitives/card-component-types.d.ts +11 -0
  236. package/dist/primitives/card-data-types.d.ts +196 -0
  237. package/dist/primitives/card-host.d.ts +1 -1
  238. package/dist/primitives/card-recovery.d.ts +3 -3
  239. package/dist/primitives/card-registry.d.ts +4 -14
  240. package/dist/primitives/card-resolution.d.ts +1 -1
  241. package/dist/primitives/card-routing.d.ts +1 -1
  242. package/dist/primitives/card-tags.d.ts +11 -0
  243. package/dist/primitives/card-validate-cards.d.ts +57 -0
  244. package/dist/primitives/card-validate-generator.testlib.d.ts +20 -0
  245. package/dist/primitives/card-validate-schemas.d.ts +17 -0
  246. package/dist/primitives/card-validate.d.ts +24 -0
  247. package/dist/primitives/create-kai-chat.d.ts +3 -3
  248. package/dist/primitives/embed-providers.d.ts +1 -1
  249. package/dist/primitives/link-preview.d.ts +1 -1
  250. package/dist/primitives/message-feedback.d.ts +2 -2
  251. package/dist/primitives/toast-store.d.ts +1 -1
  252. package/dist/react/index.d.ts +730 -302
  253. package/dist/react/use-kai-chat.d.ts +8 -4
  254. package/dist/react.d.ts +5 -0
  255. package/dist/react.js +418 -317
  256. package/dist/register-impl-AxIlqjo7.js +145 -0
  257. package/dist/remote/host-embed.d.ts +1 -1
  258. package/dist/remote/index.d.ts +2 -2
  259. package/dist/remote/provider-runtime.d.ts +2 -2
  260. package/dist/remote/provider.d.ts +2 -2
  261. package/dist/remote/validate.d.ts +1 -1
  262. package/dist/remote/wire.d.ts +1 -1
  263. package/dist/schemas/artifact.schema.json +92 -0
  264. package/dist/schemas/embed.schema.json +0 -2
  265. package/dist/schemas/from-tool-call.d.ts +48 -0
  266. package/dist/schemas/index.d.ts +48 -0
  267. package/dist/schemas/link.schema.json +0 -2
  268. package/dist/schemas/provider-subsets.d.ts +136 -0
  269. package/dist/schemas/registry.d.ts +140 -0
  270. package/dist/schemas/tool-defs.d.ts +121 -0
  271. package/dist/schemas.d.ts +5 -0
  272. package/dist/schemas.js +717 -0
  273. package/dist/solid-BGYSJyHG.js +22960 -0
  274. package/dist/solid-Dt2nEwfl.js +20194 -0
  275. package/dist/solid.d.ts +66 -0
  276. package/dist/solid.js +261 -0
  277. package/dist/solid.server.d.ts +5 -0
  278. package/dist/solid.server.js +261 -0
  279. package/dist/state/index.d.ts +14 -4
  280. package/dist/state/messages.d.ts +8 -3
  281. package/dist/state/mock.d.ts +46 -0
  282. package/dist/state/parts.d.ts +85 -0
  283. package/dist/state/stream.d.ts +15 -9
  284. package/dist/state.d.ts +5 -0
  285. package/dist/state.js +253 -84
  286. package/dist/ui/action-icons.d.ts +1 -1
  287. package/dist/ui/collapsible.d.ts +11 -6
  288. package/dist/ui/dropdown.d.ts +47 -42
  289. package/dist/ui/pane-group.d.ts +1 -1
  290. package/dist/{variant-aurora-DVtGqVH6.js → variant-aurora-B44X69AG.js} +1 -1
  291. package/dist/variant-aurora-BRhV8Flk.js +391 -0
  292. package/dist/variant-aurora-Ds5ou9BH.js +381 -0
  293. package/dist/variant-custom-B20FhtYj.js +170 -0
  294. package/dist/{variant-custom-Dif-IV4u.js → variant-custom-BvA1Schl.js} +1 -1
  295. package/dist/variant-custom-D6Rtvneb.js +200 -0
  296. package/dist/{variant-wave-DbfNdvSi.js → variant-wave-BUQtkryq.js} +1 -1
  297. package/dist/variant-wave-BVYnqy9L.js +218 -0
  298. package/dist/variant-wave-DXC6_LcH.js +208 -0
  299. package/dist/wire/chunk.d.ts +171 -0
  300. package/dist/wire/consume.d.ts +26 -0
  301. package/dist/wire/encode.d.ts +221 -0
  302. package/dist/wire/files.d.ts +116 -0
  303. package/dist/wire/formats/anthropic.d.ts +2 -0
  304. package/dist/wire/formats/openai.d.ts +5 -0
  305. package/dist/wire/index.d.ts +16 -0
  306. package/dist/wire/media-types.d.ts +106 -0
  307. package/dist/wire/read.d.ts +26 -0
  308. package/dist/wire/sink-helpers.d.ts +21 -0
  309. package/dist/wire/sse.d.ts +33 -0
  310. package/dist/wire.d.ts +5 -53
  311. package/dist/wire.js +1126 -0
  312. package/frameworks/react/index.tsx +191 -158
  313. package/frameworks/react/runtime.tsx +21 -1
  314. package/frameworks/react/use-kai-chat.tsx +11 -1
  315. package/llms-full.txt +774 -151
  316. package/llms.txt +57 -5
  317. package/package.json +62 -8
  318. package/src/agent-tooling/archetypes.ts +127 -0
  319. package/src/agent-tooling/integrations/anthropic.ts +436 -0
  320. package/src/agent-tooling/integrations/cloudflare.ts +141 -52
  321. package/src/agent-tooling/integrations/langgraph.ts +80 -13
  322. package/src/agent-tooling/integrations/mastra.ts +161 -12
  323. package/src/agent-tooling/integrations/mock.ts +47 -8
  324. package/src/agent-tooling/integrations/ollama.ts +68 -24
  325. package/src/agent-tooling/integrations/openai.ts +89 -0
  326. package/src/agent-tooling/integrations/openrouter.ts +58 -8
  327. package/src/agent-tooling/integrations/pi.ts +104 -23
  328. package/src/agent-tooling/integrations/pydantic-ai.ts +61 -5
  329. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +120 -13
  330. package/src/agent-tooling/mcp/manifest.ts +206 -25
  331. package/src/agent-tooling/mcp/tools/debug.ts +13 -7
  332. package/src/agent-tooling/mcp/tools/reference.ts +285 -6
  333. package/src/agent-tooling/mcp/tools/scaffold.ts +4152 -661
  334. package/src/agent-tooling/registry.ts +74 -0
  335. package/src/agent-tooling/types.ts +361 -1
  336. package/src/components/artifact-card.tsx +120 -0
  337. package/src/components/audio-visualizer/index.tsx +9 -0
  338. package/src/components/audio-visualizer/labs/lab-choreography.ts +187 -0
  339. package/src/components/audio-visualizer/labs/lab-shaders.ts +363 -0
  340. package/src/components/audio-visualizer/labs/lab-visualizer.tsx +102 -0
  341. package/src/components/audio-visualizer/shader-canvas.tsx +31 -1
  342. package/src/components/card-fallback.tsx +42 -10
  343. package/src/components/card-renderer.tsx +146 -9
  344. package/src/components/chat-container.tsx +4 -0
  345. package/src/components/chat-thread.tsx +128 -52
  346. package/src/components/choice-card.tsx +17 -29
  347. package/src/components/composer.tsx +12 -2
  348. package/src/components/confirm-card.tsx +24 -20
  349. package/src/components/context.tsx +30 -16
  350. package/src/components/conversation-item.tsx +6 -0
  351. package/src/components/conversation-list.tsx +18 -2
  352. package/src/components/file-tree.tsx +11 -22
  353. package/src/components/file-upload.tsx +14 -8
  354. package/src/components/form.tsx +14 -47
  355. package/src/components/loader.tsx +39 -12
  356. package/src/components/message-skills.tsx +4 -0
  357. package/src/components/message.tsx +294 -46
  358. package/src/components/model-switcher.tsx +38 -8
  359. package/src/components/reasoning.tsx +92 -5
  360. package/src/components/response-compare-types.ts +25 -0
  361. package/src/components/response-compare.tsx +4 -9
  362. package/src/components/source.tsx +50 -24
  363. package/src/components/tasks-card.tsx +10 -33
  364. package/src/components/thread.tsx +84 -42
  365. package/src/components/toast.tsx +17 -6
  366. package/src/components/tool-classify.ts +26 -0
  367. package/src/components/tool-types.ts +31 -1
  368. package/src/elements/agent-card.tsx +11 -11
  369. package/src/elements/artifact.tsx +15 -7
  370. package/src/elements/attachments.tsx +6 -4
  371. package/src/elements/avatar.tsx +1 -1
  372. package/src/elements/button.tsx +29 -6
  373. package/src/elements/card.tsx +2 -2
  374. package/src/elements/cards.tsx +109 -14
  375. package/src/elements/chain-of-thought.tsx +14 -10
  376. package/src/elements/chat-scope-picker.tsx +9 -7
  377. package/src/elements/chat-types.ts +59 -6
  378. package/src/elements/chat-workspace.tsx +50 -9
  379. package/src/elements/chat.tsx +90 -3
  380. package/src/elements/choice.tsx +8 -8
  381. package/src/elements/coachmark.tsx +1 -1
  382. package/src/elements/command.tsx +8 -23
  383. package/src/elements/compare.tsx +7 -6
  384. package/src/elements/compiled.css +1 -1
  385. package/src/elements/composer.tsx +8 -8
  386. package/src/elements/confirm-card.tsx +9 -9
  387. package/src/elements/context-meter.tsx +4 -18
  388. package/src/elements/conversation-list.tsx +14 -9
  389. package/src/elements/default-input.tsx +131 -8
  390. package/src/elements/define.tsx +178 -43
  391. package/src/elements/editable-label.tsx +17 -2
  392. package/src/elements/element-data-types.ts +112 -0
  393. package/src/elements/element-meta.json +1624 -306
  394. package/src/elements/element-types.d.ts +2645 -214
  395. package/src/elements/feedback-bar.tsx +1 -1
  396. package/src/elements/file-tree.tsx +1 -1
  397. package/src/elements/file-upload.tsx +1 -1
  398. package/src/elements/form.tsx +14 -6
  399. package/src/elements/input.tsx +1 -1
  400. package/src/elements/kbd.tsx +1 -1
  401. package/src/elements/menu.tsx +40 -32
  402. package/src/elements/message-skills.tsx +7 -9
  403. package/src/elements/message.tsx +200 -32
  404. package/src/elements/model-switcher.tsx +5 -3
  405. package/src/elements/pane.tsx +1 -1
  406. package/src/elements/prompt-dock.tsx +8 -8
  407. package/src/elements/prompt-input.tsx +10 -10
  408. package/src/elements/prompt-suggestions.tsx +7 -4
  409. package/src/elements/reasoning.tsx +1 -1
  410. package/src/elements/register.ts +9 -0
  411. package/src/elements/resizable.globals.d.ts +36 -0
  412. package/src/elements/resizable.tsx +24 -13
  413. package/src/elements/response-stream.tsx +1 -1
  414. package/src/elements/scroll-button.tsx +1 -1
  415. package/src/elements/search.tsx +6 -1
  416. package/src/elements/segmented.tsx +5 -11
  417. package/src/elements/separator.tsx +1 -1
  418. package/src/elements/slot-text.ts +73 -0
  419. package/src/elements/slots.ts +67 -24
  420. package/src/elements/source.tsx +21 -22
  421. package/src/elements/switch.tsx +3 -3
  422. package/src/elements/tasks.tsx +5 -4
  423. package/src/elements/thread.tsx +36 -4
  424. package/src/elements/toast.tsx +3 -2
  425. package/src/elements/tool.tsx +1 -1
  426. package/src/elements/validate-messages.ts +76 -0
  427. package/src/elements/voice-input.tsx +7 -7
  428. package/src/elements/voice-output.tsx +4 -4
  429. package/src/index.ts +145 -6
  430. package/src/primitives/card-component-types.ts +63 -0
  431. package/src/primitives/card-data-types.ts +312 -0
  432. package/src/primitives/card-recovery.ts +13 -2
  433. package/src/primitives/card-registry.tsx +34 -23
  434. package/src/primitives/card-schemas/artifact.schema.json +92 -0
  435. package/src/primitives/card-schemas/embed.schema.json +0 -2
  436. package/src/primitives/card-schemas/link.schema.json +0 -2
  437. package/src/primitives/card-tags.ts +61 -0
  438. package/src/primitives/card-validate-cards.ts +255 -0
  439. package/src/primitives/card-validate-generator.testlib.ts +38 -0
  440. package/src/primitives/card-validate-schemas.ts +58 -0
  441. package/src/primitives/card-validate.ts +48 -20
  442. package/src/primitives/create-tween.ts +54 -2
  443. package/src/primitives/message-feedback.ts +3 -2
  444. package/src/primitives/use-audio-analysis.ts +20 -1
  445. package/src/primitives/use-sequencer.ts +20 -1
  446. package/src/schemas/from-tool-call.ts +151 -0
  447. package/src/schemas/index.ts +340 -0
  448. package/src/schemas/provider-subsets.ts +538 -0
  449. package/src/schemas/registry.ts +355 -0
  450. package/src/schemas/tool-defs.ts +492 -0
  451. package/src/solid.ts +150 -0
  452. package/src/state/index.ts +27 -1
  453. package/src/state/messages.ts +21 -4
  454. package/src/state/mock.ts +211 -0
  455. package/src/state/parts.ts +295 -0
  456. package/src/state/stream.ts +56 -58
  457. package/src/ui/collapsible.tsx +17 -5
  458. package/src/ui/dropdown.tsx +46 -12
  459. package/src/ui/overlay.tsx +10 -4
  460. package/src/wire/chunk.ts +201 -0
  461. package/src/wire/consume.ts +440 -0
  462. package/src/wire/encode.ts +689 -0
  463. package/src/wire/files.ts +342 -0
  464. package/src/wire/formats/anthropic.ts +267 -0
  465. package/src/wire/formats/openai.ts +187 -0
  466. package/src/wire/index.ts +73 -0
  467. package/src/wire/media-types.ts +344 -0
  468. package/src/wire/read.ts +152 -0
  469. package/src/wire/sink-helpers.ts +50 -0
  470. package/src/wire/sse.ts +140 -0
  471. package/dist/create-tween-hvpmSuwW.js +0 -7
  472. package/dist/elements/chunks/arrow-left-DQocUFvy.js +0 -1
  473. package/dist/elements/chunks/audio-visualizer-BqNy7dfm.js +0 -1
  474. package/dist/elements/chunks/card-BkHbp4BA.js +0 -1
  475. package/dist/elements/chunks/chat-thread-CkRN6pZ7.js +0 -1
  476. package/dist/elements/chunks/check-B9-gn6SS.js +0 -1
  477. package/dist/elements/chunks/chevron-down-BNsxc2hk.js +0 -1
  478. package/dist/elements/chunks/chevron-right-Hv00StBO.js +0 -1
  479. package/dist/elements/chunks/circle-0e3KJTDr.js +0 -1
  480. package/dist/elements/chunks/circle-check-BNAS0JEy.js +0 -1
  481. package/dist/elements/chunks/code-block-BFUHn6Va.js +0 -1
  482. package/dist/elements/chunks/context-Cb7k4F-v.js +0 -1
  483. package/dist/elements/chunks/conversation-list-CvoAkWhN.js +0 -1
  484. package/dist/elements/chunks/create-tween-oVvAIG8r.js +0 -7
  485. package/dist/elements/chunks/default-input-Cqqilg9t.js +0 -1
  486. package/dist/elements/chunks/define-D1din7Ld.js +0 -1
  487. package/dist/elements/chunks/hover-card-Cmm6Q53m.js +0 -1
  488. package/dist/elements/chunks/message-BqyXDN3-.js +0 -1
  489. package/dist/elements/chunks/message-feedback-Djr3z6Yb.js +0 -1
  490. package/dist/elements/chunks/message-square-vFAgbDaX.js +0 -1
  491. package/dist/elements/chunks/model-switcher-B94YAKsM.js +0 -1
  492. package/dist/elements/chunks/reasoning-DTpCGtnY.js +0 -1
  493. package/dist/elements/chunks/scroll-button-B0cH5fXf.js +0 -1
  494. package/dist/elements/chunks/skeleton-DSXW5Yya.js +0 -1
  495. package/dist/elements/chunks/slots--V19HwUb.js +0 -1
  496. package/dist/elements/chunks/textarea-CUwqAX0G.js +0 -1
  497. package/dist/elements/chunks/tooltip-CgBZCVKp.js +0 -1
  498. package/dist/elements/chunks/use-card-resolution-BSu_XHjH.js +0 -1
  499. package/dist/elements/chunks/x-GRUeeQFT.js +0 -1
  500. package/dist/host-embed.d.ts +0 -33
  501. package/dist/kai-provider.d.ts +0 -2
  502. package/dist/origin.d.ts +0 -5
  503. package/dist/provider-runtime.d.ts +0 -27
  504. package/dist/register-impl-DQyejdfb.js +0 -145
  505. package/dist/validate.d.ts +0 -4
  506. package/dist/version.d.ts +0 -4
  507. package/src/components/component-meta.json +0 -4550
  508. package/src/elements/card-media.jpg +0 -0
  509. package/src/elements/resizable.d.ts +0 -27
  510. package/src/elements/video-poster.jpg +0 -0
@@ -7,20 +7,70 @@ const openrouter: Integration = {
7
7
  language: 'ts',
8
8
  streamFormat: 'openai-sse',
9
9
  envVars: ['OPENROUTER_API_KEY'],
10
- routeTemplates: {
11
- next: `export async function POST(req: Request) {
12
- const { model, messages } = await req.json();
10
+ // No per-framework templates: the handler below is web-standard, so the
11
+ // scaffolder wraps it in whatever the target framework routes with. This used
12
+ // to be a `next`-only entry that every other framework inherited verbatim.
13
+ routeTemplates: {},
14
+ webRoute: `async function chatHandler(request: Request): Promise<Response> {
15
+ // tools is undefined unless the front end sent one; JSON.stringify drops it,
16
+ // so the same handler serves a tool archetype and a plain chat.
17
+ const { model, messages, tools } = await readChatRequest(request);
18
+
13
19
  const upstream = await fetch('https://openrouter.ai/api/v1/chat/completions', {
14
20
  method: 'POST',
15
- headers: { Authorization: \`Bearer \${process.env.OPENROUTER_API_KEY}\`, 'Content-Type': 'application/json' },
16
- body: JSON.stringify({ model, messages, stream: true }),
21
+ headers: {
22
+ Authorization: \`Bearer \${process.env.OPENROUTER_API_KEY}\`,
23
+ 'Content-Type': 'application/json',
24
+ },
25
+ body: JSON.stringify({ model, messages, tools, stream: true }),
26
+ });
27
+
28
+ // FORWARD THE STATUS. Returning 200 here is how a missing key turns into
29
+ // silence: the 401 body is JSON, it goes out labelled text/event-stream, the
30
+ // SSE reader finds no frame, and the turn resolves empty with nothing logged
31
+ // and no bubble. With the status intact readOpenAIStream throws a WireError
32
+ // carrying the provider's own message.
33
+ if (!upstream.ok) {
34
+ return new Response(await upstream.text(), {
35
+ status: upstream.status,
36
+ headers: {
37
+ 'Content-Type': upstream.headers.get('content-type') ?? 'application/json',
38
+ },
39
+ });
40
+ }
41
+ if (!upstream.body) {
42
+ return new Response(JSON.stringify({ error: { message: 'The provider returned no body to stream.' } }), {
43
+ status: 502,
44
+ headers: { 'Content-Type': 'application/json' },
45
+ });
46
+ }
47
+
48
+ return new Response(upstream.body, {
49
+ status: 200,
50
+ headers: {
51
+ 'Content-Type': 'text/event-stream; charset=utf-8',
52
+ // no-transform stops a proxy buffering the stream into one blob.
53
+ 'Cache-Control': 'no-cache, no-transform',
54
+ Connection: 'keep-alive',
55
+ },
17
56
  });
18
- return new Response(upstream.body, { headers: { 'Content-Type': 'text/event-stream' } });
19
57
  }`,
20
- },
21
- streamMapping: 'OpenRouter returns OpenAI-format SSE pipe upstream.body straight to the browser; the Streaming-recipe reader handles it.',
58
+ streamMapping:
59
+ 'OpenRouter returns OpenAI-format SSE. Pipe upstream.body straight to the browser; readOpenAIStream from @kitn.ai/ui/wire parses it, including tool calls and reasoning.',
22
60
  runNote: 'Set OPENROUTER_API_KEY. Model ids are vendor/model, e.g. openai/gpt-4o.',
23
61
  docsSlug: 'integrations/connect-any-model',
62
+ // The route forwards both, so a scaffold's editable consts reach the gateway.
63
+ forwardsFromClient: ['model', 'tools'],
64
+ // The handler puts the client's `tools` straight into an OpenAI-compatible
65
+ // /chat/completions body, unconverted — so the client must send OpenAI's own
66
+ // `{ type: 'function', function: { parameters } }` envelope.
67
+ clientToolFormat: 'openai',
68
+ // Nothing to install. The route is global `fetch` and imports no module.
69
+ deps: { npm: [], pip: [] },
70
+ // The route reads OPENROUTER_API_KEY into an `Authorization: Bearer` header.
71
+ keyExposure: 'needs-proxy',
72
+ // A remote HTTPS endpoint and a key. Nothing to install, nothing to start.
73
+ outOfBand: 'none',
24
74
  };
25
75
 
26
76
  export default openrouter;
@@ -8,33 +8,114 @@ const pi: Integration = {
8
8
  streamFormat: 'native',
9
9
  envVars: [],
10
10
  routeTemplates: {
11
- express: `import { spawn } from 'node:child_process';
12
-
13
- // POST /api/chat bridge a Pi RPC session to the browser as SSE
14
- const pi = spawn('pi', ['--mode', 'rpc', '--no-session']);
15
-
16
- // Send the user's turn. Pi commands are { type, message }.
17
- pi.stdin.write(JSON.stringify({ type: 'prompt', message: prompt }) + '\\n');
18
-
19
- let buffer = '';
20
- pi.stdout.on('data', (chunk) => {
21
- buffer += chunk.toString();
22
- const lines = buffer.split('\\n');
23
- buffer = lines.pop(); // hold the partial line for the next chunk
24
- for (const line of lines) {
25
- if (!line) continue;
26
- const event = JSON.parse(line);
27
- const part = event.assistantMessageEvent;
28
- if (event.type === 'message_update' && part?.type === 'text_delta') {
29
- res.write(\`data: \${JSON.stringify({ choices: [{ delta: { content: part.delta } }] })}\\n\\n\`);
30
- }
11
+ // Express only, and genuinely so: the bridge spawns a local process, which a
12
+ // Worker or an edge runtime cannot do. Every other framework gets this under
13
+ // the scaffolder's "cannot host" warning, the same way pydantic-ai emits a
14
+ // whole FastAPI service.
15
+ express: `// server.ts
16
+ import express from 'express';
17
+ import { spawn } from 'node:child_process';
18
+ import type { OpenAIWireMessage } from '@kitn.ai/ui/wire';
19
+
20
+ const app = express();
21
+ app.use(express.json());
22
+
23
+ // POST /api/chat: bridge a Pi RPC session to the browser as SSE.
24
+ app.post('/api/chat', (req, res) => {
25
+ const { messages } = req.body as { messages: OpenAIWireMessage[] };
26
+ const last = messages.at(-1)?.content;
27
+ // \`content\` is a plain string until the turn carries an attachment, at which
28
+ // point it is an ARRAY of content parts. Pi's RPC mode takes a TEXT prompt and
29
+ // has no channel for a file, so an attachment is REFUSED here. Passing the
30
+ // array straight through would JSON.stringify an object graph into the prompt
31
+ // — no type error, no crash, just a model reading serialised noise.
32
+ if (Array.isArray(last) && last.some((part) => part.type !== 'text')) {
33
+ res.status(400).json({
34
+ error: {
35
+ message:
36
+ 'This Pi bridge forwards a text prompt only and has no channel for an attachment. Extract the file content into the message text, or send it through a tool.',
37
+ },
38
+ });
39
+ return;
31
40
  }
41
+ const prompt =
42
+ typeof last === 'string'
43
+ ? last
44
+ : (last ?? []).map((part) => (part.type === 'text' ? part.text : '')).join('');
45
+
46
+ // Headers before the first frame: without text/event-stream the browser
47
+ // buffers the body and readOpenAIStream never sees a frame.
48
+ res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
49
+ // no-transform stops a proxy buffering the stream into one blob.
50
+ res.setHeader('Cache-Control', 'no-cache, no-transform');
51
+ res.setHeader('Connection', 'keep-alive');
52
+
53
+ // ONE PROCESS PER REQUEST. Pi in RPC mode holds conversation state, so a
54
+ // module-level process would leak one caller's turns into the next caller's
55
+ // reply and serialise every request behind a single stdin.
56
+ const pi = spawn('pi', ['--mode', 'rpc', '--no-session']);
57
+
58
+ // A spawn failure (pi not on PATH) arrives asynchronously, by which point the
59
+ // SSE headers are out and the status is spent, so report IN BAND:
60
+ // readOpenAIStream lands this on turn.error instead of leaving a blank bubble.
61
+ pi.on('error', (err) => {
62
+ res.write(\`data: \${JSON.stringify({ error: { message: err.message } })}\\n\\n\`);
63
+ res.write('data: [DONE]\\n\\n');
64
+ res.end();
65
+ });
66
+
67
+ // Send the user's turn. Pi commands are { type, message }.
68
+ pi.stdin.write(JSON.stringify({ type: 'prompt', message: prompt }) + '\\n');
69
+
70
+ let buffer = '';
71
+ pi.stdout.on('data', (chunk: Buffer) => {
72
+ buffer += chunk.toString();
73
+ // Split on \\n rather than readline: readline also breaks on the Unicode
74
+ // separators U+2028/U+2029, which appear inside model output.
75
+ const lines = buffer.split('\\n');
76
+ // pop() is \`string | undefined\`, and the buffer has to stay a string.
77
+ buffer = lines.pop() ?? ''; // hold the partial line for the next chunk
78
+ for (const line of lines) {
79
+ if (!line) continue;
80
+ const event = JSON.parse(line);
81
+ const part = event.assistantMessageEvent;
82
+ if (event.type === 'message_update' && part?.type === 'text_delta') {
83
+ res.write(\`data: \${JSON.stringify({ choices: [{ delta: { content: part.delta } }] })}\\n\\n\`);
84
+ }
85
+ }
86
+ });
87
+ pi.on('close', () => { res.write('data: [DONE]\\n\\n'); res.end(); });
32
88
  });
33
- pi.on('close', () => { res.write('data: [DONE]\\n\\n'); res.end(); });`,
89
+
90
+ app.listen(3001, () => console.log('chat api: http://localhost:3001/api/chat'));
91
+ // The front end fetches a RELATIVE /api/chat, so proxy that path to this port
92
+ // from your dev server, or serve both from one origin.`,
34
93
  },
35
- streamMapping: "Pi runs as a local stdio process in RPC mode (pi --mode rpc --no-session). It emits newline-delimited JSON events on stdout. Map message_update events where assistantMessageEvent.type === 'text_delta' to data: {choices:[{delta:{content:part.delta}}]} SSE frames; send data: [DONE] on close. Split stdout on \\n (not readline, which breaks on Unicode separators U+2028/U+2029). Pi also emits thinking_delta (map to reasoning) and toolcall_* events (map to tool calls).",
36
- runNote: "Pi must be installed locally and available on PATH as 'pi'. No API key is required by the bridge itself; Pi uses its own credentials. Pi runs with full user permissions sandbox before exposing to a public endpoint. See the RPC reference: https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/rpc.md",
94
+ streamMapping: "Pi runs as a local stdio process in RPC mode (pi --mode rpc --no-session). It emits newline-delimited JSON events on stdout. Map message_update events where assistantMessageEvent.type === 'text_delta' to data: {choices:[{delta:{content:part.delta}}]} SSE frames; send data: [DONE] on close. Split stdout on \\n (not readline, which breaks on Unicode separators U+2028/U+2029). Pi also emits thinking_delta and toolcall_* events; re-frame them onto delta.reasoning and delta.tool_calls in the same frames. readOpenAIStream from @kitn.ai/ui/wire parses it, including tool calls and reasoning.",
95
+ runNote: "Pi must be installed locally and available on PATH as 'pi'. No API key is required by the bridge itself; Pi uses its own credentials. Pi runs with full user permissions, so sandbox it before exposing to a public endpoint. See the RPC reference: https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/rpc.md",
37
96
  docsSlug: 'integrations/harnesses',
97
+ // Nothing. Pi runs with its own credentials, model and tools; the bridge
98
+ // forwards a prompt, not a model id.
99
+ forwardsFromClient: [],
100
+ // `express` only. The bridge's other two imports are excluded by the rule:
101
+ // `node:child_process` is a builtin and '@kitn.ai/ui/wire' is the kit. Pi
102
+ // itself is NOT an npm dep of the app — runNote requires it on PATH, and the
103
+ // route reaches it with spawn('pi'), not an import.
104
+ deps: { npm: ['express'], pip: [] },
105
+ // No key anywhere: envVars is empty and Pi uses its own credentials on disk.
106
+ // This is 'needs-proxy' for the OTHER reason the flag covers — a server-only
107
+ // capability. The bridge calls spawn(), which no browser can do, and runNote
108
+ // warns that Pi runs with full user permissions and wants sandboxing before it
109
+ // is exposed. "No key" is not the same fact as "safe in the browser", and
110
+ // collapsing the two is exactly how this flag would be got wrong.
111
+ keyExposure: 'needs-proxy',
112
+ // Not 'local-server': nothing is listening in advance. The bridge spawns
113
+ // `pi --mode rpc` per request, so what the developer supplies is an EXECUTABLE
114
+ // on PATH ("Pi must be installed locally and available on PATH as 'pi'"), and
115
+ // "start the server first" would be the wrong instruction to print. Pi is not
116
+ // in `deps` for the same reason — the route reaches it with spawn(), not an
117
+ // import. The schema's SPAWNS_PROCESS net catches this one independently.
118
+ outOfBand: 'local-binary',
38
119
  };
39
120
 
40
121
  export default pi;
@@ -10,7 +10,7 @@ const pydanticAi: Integration = {
10
10
  routeTemplates: {
11
11
  fastapi: `# main.py
12
12
  import json
13
- from fastapi import FastAPI
13
+ from fastapi import FastAPI, HTTPException
14
14
  from fastapi.middleware.cors import CORSMiddleware
15
15
  from fastapi.responses import StreamingResponse
16
16
  from pydantic import BaseModel
@@ -23,15 +23,41 @@ app.add_middleware(
23
23
  CORSMiddleware, allow_origins=['*'], allow_methods=['*'], allow_headers=['*']
24
24
  )
25
25
 
26
+ class ContentPart(BaseModel):
27
+ type: str
28
+ text: str | None = None
29
+
26
30
  class Message(BaseModel):
27
31
  role: str
28
- content: str
32
+ # A plain string until the turn carries an attachment, at which point the
33
+ # wire sends an ARRAY of content parts. Declaring this \`str\` alone made a
34
+ # message with a file a 422 that named pydantic rather than the cause.
35
+ content: str | list[ContentPart] | None = None
29
36
 
30
37
  class ChatRequest(BaseModel):
31
38
  messages: list[Message]
32
39
 
40
+ def prompt_text(message: Message) -> str:
41
+ """The text of a turn, refusing what this route cannot carry.
42
+
43
+ agent.run_stream() takes a TEXT prompt, so an image or a document has no
44
+ channel here. Dropping it would send the model a turn that silently lost the
45
+ user's file; a 400 that names the reason is the honest failure.
46
+ """
47
+ if message.content is None:
48
+ return ''
49
+ if isinstance(message.content, str):
50
+ return message.content
51
+ if any(part.type != 'text' for part in message.content):
52
+ raise HTTPException(
53
+ status_code=400,
54
+ detail='This route forwards a text prompt only and has no channel for an attachment. '
55
+ 'Extract the file content into the message text, or send it through a tool.',
56
+ )
57
+ return ''.join(part.text or '' for part in message.content)
58
+
33
59
  async def openai_sse(messages: list[Message]):
34
- prompt = messages[-1].content if messages else ''
60
+ prompt = prompt_text(messages[-1]) if messages else ''
35
61
  async with agent.run_stream(prompt) as result:
36
62
  async for delta in result.stream_text(delta=True):
37
63
  chunk = {'choices': [{'delta': {'content': delta}}]}
@@ -43,10 +69,40 @@ async def chat(req: ChatRequest):
43
69
  return StreamingResponse(openai_sse(req.messages), media_type='text/event-stream')`,
44
70
  },
45
71
  streamMapping:
46
- "Pydantic AI's agent.run_stream() yields text deltas via result.stream_text(delta=True). Each delta is re-framed as a data: {choices:[{delta:{content}}]} SSE line and the stream closes with data: [DONE]. The kai-chat reader is unchanged it sees standard OpenAI-format SSE.",
72
+ "Pydantic AI's agent.run_stream() yields text deltas via result.stream_text(delta=True). Each delta is re-framed as a data: {choices:[{delta:{content}}]} SSE line and the stream closes with data: [DONE]. readOpenAIStream from @kitn.ai/ui/wire parses tool calls and reasoning too, but stream_text() yields text only: iterate the run's events instead and re-frame its tool-call events onto delta.tool_calls to fill kai-tool.",
73
+ // No install list here: `deps` below is the one, and the scaffolder emits it.
74
+ // This sentence used to open "Install: pip install pydantic-ai fastapi
75
+ // uvicorn." — three of the four packages, missing the `pydantic` its own route
76
+ // imports on the next line.
47
77
  runNote:
48
- 'Install: pip install pydantic-ai fastapi uvicorn. Set OPENAI_API_KEY. Run: uvicorn main:app --reload (default port 8000). Point kai-chat at http://localhost:8000/api/chat.',
78
+ 'Set OPENAI_API_KEY. Run: uvicorn main:app --reload (default port 8000). Point kai-chat at http://localhost:8000/api/chat.',
49
79
  docsSlug: 'integrations/pydantic-ai',
80
+ // Nothing. Agent('openai:gpt-4o') pins the model and the agent registers its
81
+ // own tools, both server-side.
82
+ forwardsFromClient: [],
83
+ // The only python integration, so the only non-empty `pip`. Three of the four
84
+ // are the route's own imports (`pydantic_ai` is imported under its module
85
+ // name and installed under its hyphenated one); `uvicorn` is the ASGI server
86
+ // the run note starts, which nothing imports and without which the app cannot
87
+ // run. That asymmetry is why the pip guard checks imports ⊆ declared and not
88
+ // the reverse.
89
+ deps: { npm: [], pip: ['pydantic-ai', 'fastapi', 'pydantic', 'uvicorn'] },
90
+ // Agent('openai:gpt-4o') reads OPENAI_API_KEY inside the python process.
91
+ //
92
+ // Worth being precise about, because this one looks like an exception: the
93
+ // FastAPI app sets CORS to allow_origins=['*'] and the browser fetches it
94
+ // DIRECTLY on :8000, with no JS proxy in front. That still is not
95
+ // 'frontend-safe' — the flag asks where the SECRET lives, and it lives in the
96
+ // python process. The FastAPI service IS the server hop.
97
+ keyExposure: 'needs-proxy',
98
+ // The emitted backend is a FastAPI service: a python interpreter, the four
99
+ // `deps.pip` packages, and `uvicorn main:app` on its own port — none of which a
100
+ // node toolchain provides or starts. It also needs OPENAI_API_KEY, so this is
101
+ // the entry that proves the groups are not mutually exclusive: a runtime
102
+ // prerequisite and a key at once. The prompt should lead with the runtime,
103
+ // because a key is useless until the service runs. The schema's `language ===
104
+ // 'python'` net catches this one independently.
105
+ outOfBand: 'language-runtime',
50
106
  };
51
107
 
52
108
  export default pydanticAi;
@@ -7,38 +7,145 @@ const vercelAiSdk: Integration = {
7
7
  language: 'ts',
8
8
  streamFormat: 'ai-sdk',
9
9
  envVars: ['AI_GATEWAY_API_KEY'],
10
- routeTemplates: {
11
- next: `// app/api/chat/route.ts
12
- import { streamText } from 'ai';
10
+ // No per-framework templates: the handler below is web-standard, so the
11
+ // scaffolder wraps it in the target framework's own route declaration.
12
+ routeTemplates: {},
13
+ webRoute: `import { streamText } from 'ai';
14
+ import type { ModelMessage, AssistantContent, UserContent, FilePart, ToolResultPart } from 'ai';
13
15
 
14
- export const maxDuration = 30; // allow long streaming responses
16
+ // Next.js only: add \`export const maxDuration = 30\` to the route file to allow
17
+ // long streaming responses. It is a Next route-segment config, not part of the
18
+ // handler, so it lives in the file rather than in here.
15
19
 
16
- export async function POST(req: Request) {
17
- const { messages } = await req.json();
20
+ /**
21
+ * One attachment to an AI SDK FilePart.
22
+ *
23
+ * \`data\` takes the BARE shorthand — a base64 string, or a URL object for a
24
+ * remote file — rather than the newer tagged \`{ type: 'data' | 'url' }\` form.
25
+ * Both are accepted by the installed SDK; the shorthand is also what AI SDK v5
26
+ * and v6 accept, so this route keeps compiling if the app pins an older \`ai\`.
27
+ *
28
+ * Images arrive as \`image_url\` and become FileParts too. \`ImagePart\` still
29
+ * exists but is deprecated in favour of exactly this.
30
+ */
31
+ function toFilePart(part: Extract<WirePart, { kind: 'file' }>): FilePart {
32
+ return {
33
+ type: 'file',
34
+ mediaType: part.mediaType,
35
+ ...(part.filename === undefined ? {} : { filename: part.filename }),
36
+ data: part.source.type === 'data' ? part.source.data : new URL(part.source.url),
37
+ };
38
+ }
39
+
40
+ // The kit's wire format is OpenAI-shaped (tool_calls on the assistant message,
41
+ // a flat content string on the tool message). The AI SDK's ModelMessage is not:
42
+ // a tool call is a CONTENT PART on the assistant message, and a tool result is a
43
+ // tagged output union, not a bare string. Converting rather than casting is the
44
+ // point — a cast would compile but hand the SDK the wrong shape at runtime.
45
+ function toModelMessages(messages: ChatRequestBody['messages']): ModelMessage[] {
46
+ return messages.map((message): ModelMessage => {
47
+ switch (message.role) {
48
+ // A system prompt is text-only on every wire, so the array form collapses.
49
+ case 'system':
50
+ return { role: 'system', content: wireText(message.content) };
51
+ case 'user': {
52
+ const parts = wireParts(message.content);
53
+ // Plain string for an ordinary turn: identical to what this route sent
54
+ // before attachments existed, and the shape the SDK docs lead with.
55
+ if (parts.every((p) => p.kind === 'text')) {
56
+ return { role: 'user', content: wireText(message.content) };
57
+ }
58
+ const content: UserContent = parts.map((p) =>
59
+ p.kind === 'text' ? { type: 'text' as const, text: p.text } : toFilePart(p),
60
+ );
61
+ return { role: 'user', content };
62
+ }
63
+ case 'tool': {
64
+ const result: ToolResultPart = {
65
+ type: 'tool-result',
66
+ toolCallId: message.tool_call_id ?? '',
67
+ toolName: message.name ?? '',
68
+ output: { type: 'text', value: wireText(message.content) },
69
+ };
70
+ return { role: 'tool', content: [result] };
71
+ }
72
+ case 'assistant': {
73
+ const text = wireText(message.content);
74
+ if (!message.tool_calls?.length) return { role: 'assistant', content: text };
75
+ const content: AssistantContent = [];
76
+ if (text) content.push({ type: 'text', text });
77
+ for (const call of message.tool_calls) {
78
+ content.push({
79
+ type: 'tool-call',
80
+ toolCallId: call.id,
81
+ toolName: call.function.name,
82
+ input: JSON.parse(call.function.arguments),
83
+ });
84
+ }
85
+ return { role: 'assistant', content };
86
+ }
87
+ }
88
+ });
89
+ }
90
+
91
+ async function chatHandler(request: Request): Promise<Response> {
92
+ const { messages } = await readChatRequest(request);
18
93
 
19
94
  const result = streamText({
20
95
  model: 'openai/gpt-4o', // AI Gateway id; needs AI_GATEWAY_API_KEY
21
- messages,
96
+ messages: toModelMessages(messages),
22
97
  });
23
98
 
24
99
  const encoder = new TextEncoder();
25
100
  const sse = new ReadableStream({
26
101
  async start(controller) {
27
- for await (const delta of result.textStream) {
28
- const chunk = { choices: [{ delta: { content: delta } }] };
29
- controller.enqueue(encoder.encode(\`data: \${JSON.stringify(chunk)}\\n\\n\`));
102
+ try {
103
+ for await (const delta of result.textStream) {
104
+ const chunk = { choices: [{ delta: { content: delta } }] };
105
+ controller.enqueue(encoder.encode(\`data: \${JSON.stringify(chunk)}\\n\\n\`));
106
+ }
107
+ } catch (err) {
108
+ // The status is spent by the time the SDK fails — the headers went out
109
+ // with the first byte — so report it IN BAND. readOpenAIStream lands
110
+ // this on turn.error and keeps whatever streamed before it. Without it a
111
+ // failed key is an empty bubble and nothing in the console.
112
+ const message = err instanceof Error ? err.message : 'Model stream failed';
113
+ controller.enqueue(encoder.encode(\`data: \${JSON.stringify({ error: { message } })}\\n\\n\`));
30
114
  }
31
115
  controller.enqueue(encoder.encode('data: [DONE]\\n\\n'));
32
116
  controller.close();
33
117
  },
34
118
  });
35
119
 
36
- return new Response(sse, { headers: { 'Content-Type': 'text/event-stream' } });
120
+ return new Response(sse, {
121
+ status: 200,
122
+ headers: {
123
+ 'Content-Type': 'text/event-stream; charset=utf-8',
124
+ // no-transform stops a proxy buffering the stream into one blob.
125
+ 'Cache-Control': 'no-cache, no-transform',
126
+ Connection: 'keep-alive',
127
+ },
128
+ });
37
129
  }`,
38
- },
39
- streamMapping: "The Vercel AI SDK's toUIMessageStreamResponse() and toTextStreamResponse() don't emit OpenAI-format SSE. Wrap result.textStream manually: iterate text deltas and emit data: {choices:[{delta:{content}}]} frames, closing with data: [DONE]. The kai-chat SSE reader handles it.",
130
+ streamMapping: "The Vercel AI SDK's toUIMessageStreamResponse() and toTextStreamResponse() don't emit OpenAI-format SSE. Wrap result.textStream manually: iterate text deltas and emit data: {choices:[{delta:{content}}]} frames, closing with data: [DONE]. readOpenAIStream from @kitn.ai/ui/wire parses tool calls and reasoning too, but textStream carries neither: it is text deltas only. Switch to result.fullStream, which yields typed parts, and re-frame its tool-call and reasoning parts onto delta.tool_calls and delta.reasoning to get them.",
40
131
  runNote: 'Set AI_GATEWAY_API_KEY for the AI Gateway (string model id form: creator/model-name). For direct provider access, import its provider package (e.g. @ai-sdk/openai) and set the corresponding key (e.g. OPENAI_API_KEY).',
41
132
  docsSlug: 'integrations/vercel-ai-sdk',
133
+ // Nothing. The route pins model: 'openai/gpt-4o' in the streamText() call and
134
+ // defines any tools there too, so neither belongs in the front end.
135
+ forwardsFromClient: [],
136
+ // `ai` only. A direct provider (e.g. @ai-sdk/openai) is the alternative path
137
+ // described in runNote, not what this route imports, so it is not listed: the
138
+ // rule is what the emitted code actually imports.
139
+ deps: { npm: ['ai'], pip: [] },
140
+ // Grep the route for a key and you find NOTHING — no header, no process.env.
141
+ // The AI SDK reads AI_GATEWAY_API_KEY out of the environment itself, inside
142
+ // streamText(). This is the case that makes inference from route source
143
+ // unsafe and the declaration necessary; `envVars` is what the schema's safety
144
+ // net can still see.
145
+ keyExposure: 'needs-proxy',
146
+ // The AI Gateway is a remote HTTPS endpoint reached through the `ai` package,
147
+ // which is an ordinary npm dependency (see `deps`). Nothing to start.
148
+ outOfBand: 'none',
42
149
  };
43
150
 
44
151
  export default vercelAiSdk;