@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
@@ -0,0 +1,436 @@
1
+ import type { Integration } from '../types';
2
+
3
+ /**
4
+ * Anthropic Messages, and NOT a copy of the OpenRouter route.
5
+ *
6
+ * Four constraints established by live testing and recorded in
7
+ * docs/superpowers/HANDOFF-model-driven-components.md §4. Every one of them is a
8
+ * 400 (or worse, a silent 200) if you port an OpenAI-shaped body over verbatim:
9
+ *
10
+ * 1. `system` is a TOP-LEVEL field. A `{ role: 'system' }` entry in `messages`
11
+ * is rejected.
12
+ * 2. `tool_choice` is an OBJECT, never the bare string OpenAI accepts.
13
+ * 3. `max_tokens` is REQUIRED. OpenAI defaults it; this API does not.
14
+ * 4. Content blocks stream strictly sequentially and are indexed by CONTENT
15
+ * BLOCK, so a thinking block pushes a tool call from block 0 to block 1.
16
+ * OpenAI's `tool_calls[].index` counts TOOL CALLS, so the two indices are
17
+ * not the same number and passing one through as the other is the original
18
+ * Critical.
19
+ *
20
+ * The thinking constraint in that document has since MOVED, and the route says
21
+ * so rather than repeating it: `budget_tokens` is not a tunable with a floor of
22
+ * 1024 any more, it is REMOVED from the current models and returns a 400. See
23
+ * the block comment beside `thinking` in the emitted body.
24
+ */
25
+ const anthropic: Integration = {
26
+ id: 'anthropic',
27
+ title: 'Anthropic',
28
+ category: 'provider',
29
+ language: 'ts',
30
+ // 'native': the Messages API streams its OWN SSE dialect (message_start,
31
+ // content_block_delta, message_stop), not OpenAI's. The route re-frames it —
32
+ // see `reframeToOpenAISse` and the note in streamMapping for why.
33
+ streamFormat: 'native',
34
+ envVars: ['ANTHROPIC_API_KEY'],
35
+ // No per-framework templates: the handler below is web-standard, so the
36
+ // scaffolder wraps it in whatever the target framework routes with.
37
+ routeTemplates: {},
38
+ webRoute: `/** Where an image or document's bytes come from. Anthropic takes both forms
39
+ * for both block types, which is why this route has no gap where the
40
+ * OpenAI-shaped one does: a chat-completions \`file\` part is base64-only. */
41
+ type AnthropicSource =
42
+ | { type: 'base64'; media_type: string; data: string }
43
+ | { type: 'url'; url: string };
44
+
45
+ /** One Anthropic content block. Open union on the wire; these are the ones the
46
+ * scaffold's own thread can produce. */
47
+ type AnthropicBlock =
48
+ | { type: 'text'; text: string }
49
+ | { type: 'image'; source: AnthropicSource }
50
+ | { type: 'document'; source: AnthropicSource }
51
+ | { type: 'tool_use'; id: string; name: string; input: unknown }
52
+ | { type: 'tool_result'; tool_use_id: string; content: string };
53
+
54
+ /** Anthropic messages carry ONLY 'user' and 'assistant'. There is no 'system'
55
+ * role here and no 'tool' role: a system prompt is a top-level field, and a
56
+ * tool RESULT rides on a user message. Both are handled in toAnthropicBody. */
57
+ type AnthropicMessage = { role: 'user' | 'assistant'; content: AnthropicBlock[] };
58
+
59
+ /** Anthropic calls the JSON Schema \`input_schema\`; OpenAI calls it
60
+ * \`parameters\`, and nests everything under \`function\`. Handing the OpenAI
61
+ * shape over unconverted is a 400. */
62
+ type AnthropicTool = { name: string; description?: string; input_schema: unknown };
63
+
64
+ /** The OpenAI tool shape the front end sends, narrowed from \`unknown[]\`. */
65
+ type OpenAIFunctionTool = {
66
+ function?: { name?: string; description?: string; parameters?: unknown };
67
+ };
68
+
69
+ /** One decoded Messages-API SSE frame. Every field is optional because this is
70
+ * a provider-owned union and an unrecognised frame must be skipped, not throw. */
71
+ type AnthropicEvent = {
72
+ type?: string;
73
+ index?: number;
74
+ content_block?: { type?: string; id?: string; name?: string };
75
+ delta?: {
76
+ type?: string;
77
+ text?: string;
78
+ thinking?: string;
79
+ partial_json?: string;
80
+ stop_reason?: string;
81
+ };
82
+ error?: { message?: string };
83
+ };
84
+
85
+ /** Anthropic stop reasons -> OpenAI finish_reason. 'refusal' has no OpenAI
86
+ * equivalent and is reported in band instead (see the message_delta case). */
87
+ const FINISH_REASONS: Record<string, string> = {
88
+ end_turn: 'stop',
89
+ stop_sequence: 'stop',
90
+ max_tokens: 'length',
91
+ tool_use: 'tool_calls',
92
+ };
93
+
94
+ function toAnthropicTools(tools: unknown[] | undefined): AnthropicTool[] | undefined {
95
+ if (!tools || tools.length === 0) return undefined;
96
+ return tools.map((raw) => {
97
+ const fn = (raw as OpenAIFunctionTool).function ?? {};
98
+ return {
99
+ name: fn.name ?? '',
100
+ description: fn.description,
101
+ input_schema: fn.parameters ?? { type: 'object', properties: {} },
102
+ };
103
+ });
104
+ }
105
+
106
+ /**
107
+ * The OpenAI-shaped thread the front end posts -> an Anthropic Messages body.
108
+ *
109
+ * \`system\` comes back SEPARATELY because it is a top-level request field here.
110
+ * A \`{ role: 'system' }\` entry left in \`messages\` is a 400, and it is the most
111
+ * common thing to get wrong when porting a route across.
112
+ */
113
+ function toAnthropicBody(messages: ChatRequestBody['messages']): {
114
+ system?: string;
115
+ messages: AnthropicMessage[];
116
+ } {
117
+ let system: string | undefined;
118
+ const out: AnthropicMessage[] = [];
119
+
120
+ // Append to the trailing user message when there is one. A tool result and the
121
+ // user's next turn are one turn on this wire, and several Anthropic-compatible
122
+ // proxies enforce strict alternation.
123
+ const pushUser = (content: AnthropicBlock[]): void => {
124
+ const last = out[out.length - 1];
125
+ if (last?.role === 'user') last.content.push(...content);
126
+ else out.push({ role: 'user', content });
127
+ };
128
+
129
+ for (const message of messages) {
130
+ switch (message.role) {
131
+ case 'system': {
132
+ // TOP-LEVEL, not a message. Several system turns concatenate.
133
+ const text = wireText(message.content);
134
+ system = system === undefined ? text : \`\${system}\\n\\n\${text}\`;
135
+ break;
136
+ }
137
+ case 'user': {
138
+ // Attachments ride HERE, as image and document blocks beside the text,
139
+ // in the order the thread authored them.
140
+ const blocks = wireParts(message.content).map((part): AnthropicBlock => {
141
+ if (part.kind === 'text') return { type: 'text', text: part.text };
142
+ const source: AnthropicSource =
143
+ part.source.type === 'data'
144
+ ? { type: 'base64', media_type: part.mediaType, data: part.source.data }
145
+ : { type: 'url', url: part.source.url };
146
+ // \`image/png\` and the bare top-level \`image\` both mean an image; a URL
147
+ // source reports only the segment because the wire carries no more.
148
+ return part.mediaType.startsWith('image') ? { type: 'image', source } : { type: 'document', source };
149
+ });
150
+ if (blocks.length > 0) pushUser(blocks);
151
+ break;
152
+ }
153
+ case 'tool': {
154
+ // A tool RESULT is a block on a USER message here, not a role of its own.
155
+ pushUser([
156
+ {
157
+ type: 'tool_result',
158
+ tool_use_id: message.tool_call_id ?? '',
159
+ content: wireText(message.content),
160
+ },
161
+ ]);
162
+ break;
163
+ }
164
+ case 'assistant': {
165
+ const content: AnthropicBlock[] = [];
166
+ const text = wireText(message.content);
167
+ if (text) content.push({ type: 'text', text });
168
+ for (const call of message.tool_calls ?? []) {
169
+ content.push({
170
+ type: 'tool_use',
171
+ id: call.id,
172
+ name: call.function.name,
173
+ // \`input\` is a parsed OBJECT here. OpenAI carries the same value as a
174
+ // JSON STRING in \`function.arguments\`; forwarding the string is a 400.
175
+ input: call.function.arguments ? JSON.parse(call.function.arguments) : {},
176
+ });
177
+ }
178
+ // An assistant turn with nothing in it is not a legal message.
179
+ if (content.length > 0) out.push({ role: 'assistant', content });
180
+ break;
181
+ }
182
+ }
183
+ }
184
+
185
+ return { system, messages: out };
186
+ }
187
+
188
+ /**
189
+ * Anthropic Messages SSE -> OpenAI-format SSE.
190
+ *
191
+ * WHY THIS EXISTS AT ALL: the front end reads with readOpenAIStream. Handing it
192
+ * Anthropic frames does not throw — it parses to NOTHING, so the turn ends empty
193
+ * with no error anywhere, which is the worst failure this kit has. Re-framing
194
+ * here keeps the browser half of an anthropic scaffold identical to every other
195
+ * integration's, the same way the cloudflare Worker route re-frames env.AI.
196
+ *
197
+ * The alternative, if you would rather keep the native frames: return
198
+ * \`upstream.body\` untouched and read it with readAnthropicStream from
199
+ * '@kitn.ai/ui/wire'. That is also the ONLY way to round-trip thinking blocks
200
+ * verbatim (toAnthropicMessages echoes \`part.raw\` — a rebuilt block is a hard
201
+ * 400), so it is the path to take the moment you enable thinking below.
202
+ */
203
+ function reframeToOpenAISse(body: ReadableStream<Uint8Array>): ReadableStream<Uint8Array> {
204
+ const encoder = new TextEncoder();
205
+ const decoder = new TextDecoder();
206
+ // THE INDEX TRAP. Anthropic's \`index\` counts CONTENT BLOCKS; OpenAI's
207
+ // \`tool_calls[].index\` counts TOOL CALLS. A thinking or text block ahead of a
208
+ // tool call shifts the block index, but the call is still tool call 0 — so the
209
+ // block index is translated through this map rather than passed through.
210
+ const toolIndexOfBlock = new Map<number, number>();
211
+ let toolCount = 0;
212
+
213
+ return new ReadableStream<Uint8Array>({
214
+ async start(controller) {
215
+ const send = (chunk: unknown): void => {
216
+ controller.enqueue(encoder.encode(\`data: \${JSON.stringify(chunk)}\\n\\n\`));
217
+ };
218
+ const reader = body.getReader();
219
+ let buffer = '';
220
+ try {
221
+ for (;;) {
222
+ const { value, done } = await reader.read();
223
+ if (done) break;
224
+ buffer += decoder.decode(value, { stream: true });
225
+ // Split on \\n rather than readline: readline also breaks on the
226
+ // Unicode separators U+2028/U+2029, which appear inside model output.
227
+ const lines = buffer.split('\\n');
228
+ buffer = lines.pop() ?? ''; // hold the partial line for the next chunk
229
+ for (const line of lines) {
230
+ const trimmed = line.trim();
231
+ // \`event:\` lines are redundant — every frame's data carries \`type\`.
232
+ if (!trimmed.startsWith('data:')) continue;
233
+ let event: AnthropicEvent;
234
+ try {
235
+ event = JSON.parse(trimmed.slice(5)) as AnthropicEvent;
236
+ } catch {
237
+ continue; // skip a malformed frame rather than kill the turn
238
+ }
239
+
240
+ if (event.type === 'content_block_start') {
241
+ const block = event.content_block;
242
+ if (block?.type !== 'tool_use') continue;
243
+ const index = toolCount;
244
+ toolCount += 1;
245
+ toolIndexOfBlock.set(event.index ?? 0, index);
246
+ send({
247
+ choices: [{
248
+ delta: {
249
+ tool_calls: [{
250
+ index,
251
+ id: block.id,
252
+ type: 'function',
253
+ function: { name: block.name, arguments: '' },
254
+ }],
255
+ },
256
+ }],
257
+ });
258
+ continue;
259
+ }
260
+
261
+ if (event.type === 'content_block_delta') {
262
+ const delta = event.delta;
263
+ if (delta?.type === 'text_delta') {
264
+ send({ choices: [{ delta: { content: delta.text } }] });
265
+ } else if (delta?.type === 'thinking_delta') {
266
+ send({ choices: [{ delta: { reasoning: delta.thinking } }] });
267
+ } else if (delta?.type === 'input_json_delta') {
268
+ const index = toolIndexOfBlock.get(event.index ?? 0);
269
+ // No entry means these fragments belong to a block that is not a
270
+ // tool_use. Dropping them beats inventing a tool call.
271
+ if (index !== undefined) {
272
+ send({
273
+ choices: [{
274
+ delta: {
275
+ tool_calls: [{ index, function: { arguments: delta.partial_json } }],
276
+ },
277
+ }],
278
+ });
279
+ }
280
+ }
281
+ continue;
282
+ }
283
+
284
+ if (event.type === 'message_delta') {
285
+ const reason = event.delta?.stop_reason;
286
+ if (!reason) continue;
287
+ if (reason === 'refusal') {
288
+ // A safety classifier declined. HTTP 200, no content, so without
289
+ // this the turn just ends blank. Report it IN BAND:
290
+ // readOpenAIStream lands it on turn.error.
291
+ send({ error: { message: 'The model declined this request (stop_reason: refusal).' } });
292
+ continue;
293
+ }
294
+ send({ choices: [{ delta: {}, finish_reason: FINISH_REASONS[reason] ?? 'stop' }] });
295
+ continue;
296
+ }
297
+
298
+ if (event.type === 'error') {
299
+ send({ error: { message: event.error?.message ?? 'Anthropic returned an error frame.' } });
300
+ }
301
+ }
302
+ }
303
+ } catch (err) {
304
+ // The headers went out with the first byte, so the status is spent:
305
+ // report IN BAND. readOpenAIStream lands this on turn.error and keeps
306
+ // whatever already streamed.
307
+ const message = err instanceof Error ? err.message : 'Anthropic stream failed';
308
+ send({ error: { message } });
309
+ }
310
+ // Always: an unclosed stream leaves the browser waiting forever.
311
+ controller.enqueue(encoder.encode('data: [DONE]\\n\\n'));
312
+ controller.close();
313
+ },
314
+ });
315
+ }
316
+
317
+ async function chatHandler(request: Request): Promise<Response> {
318
+ // Both model and tools come from the browser. \`tools\` arrives in OpenAI
319
+ // function form and is converted to this API's shape below.
320
+ const { model, messages, tools } = await readChatRequest(request);
321
+ const { system, messages: anthropicMessages } = toAnthropicBody(messages);
322
+ const anthropicTools = toAnthropicTools(tools);
323
+
324
+ const upstream = await fetch('https://api.anthropic.com/v1/messages', {
325
+ method: 'POST',
326
+ headers: {
327
+ 'x-api-key': \`\${process.env.ANTHROPIC_API_KEY}\`,
328
+ 'anthropic-version': '2023-06-01',
329
+ 'Content-Type': 'application/json',
330
+ },
331
+ body: JSON.stringify({
332
+ // Model ids here are Anthropic's own — 'claude-opus-5', 'claude-sonnet-5',
333
+ // 'claude-haiku-4-5'. An OpenAI id or an OpenRouter 'vendor/model' slug is
334
+ // a 404. The front end's \`model\` const is the one to edit; it is seeded
335
+ // from CLIENT_MODEL_IDS.anthropic.
336
+ model,
337
+ // REQUIRED. OpenAI defaults it; this API 400s without it. It caps thinking
338
+ // AND response text together, so raise it if you turn thinking on.
339
+ max_tokens: 4096,
340
+ // TOP-LEVEL, never a { role: 'system' } entry in \`messages\`.
341
+ ...(system ? { system } : {}),
342
+ messages: anthropicMessages,
343
+ ...(anthropicTools ? { tools: anthropicTools } : {}),
344
+ // tool_choice, if you set one, is an OBJECT: { type: 'auto' } |
345
+ // { type: 'any' } | { type: 'tool', name: 'search' }. OpenAI's bare string
346
+ // ('auto') is a 400 here.
347
+ // tool_choice: { type: 'auto' },
348
+ //
349
+ // THINKING. The rules changed with the model generation, so check yours:
350
+ // · claude-opus-5 / sonnet-5 / opus-4.8 / opus-4.7 / fable-5 —
351
+ // \`budget_tokens\` is REMOVED and returns a 400. Use
352
+ // \`thinking: { type: 'adaptive', display: 'summarized' }\` plus
353
+ // \`output_config: { effort: 'low' | 'medium' | 'high' | 'xhigh' | 'max' }\`.
354
+ // On claude-opus-5 thinking is already ON by default; what is NOT on is
355
+ // \`display\`, which defaults to 'omitted' and streams thinking blocks
356
+ // with EMPTY text. That reads exactly like "this model has no reasoning
357
+ // mode" — a silent 200 with nothing in the panel — and it is the same
358
+ // shape of mistake as the budget bug below. Set display explicitly.
359
+ // · older models (claude-haiku-4-5, claude-sonnet-4-5) — still
360
+ // \`thinking: { type: 'enabled', budget_tokens: N }\`, with N >= 1024 and
361
+ // max_tokens STRICTLY GREATER than N. Deriving N as a fraction of a
362
+ // small max_tokens returns HTTP 200 with no thinking and no error.
363
+ // Either way, once thinking is on a tool loop must echo every thinking
364
+ // block back VERBATIM, which this route cannot do: it re-frames to OpenAI
365
+ // SSE and the thread is re-encoded with toOpenAIMessages each round. Switch
366
+ // to readAnthropicStream + toAnthropicMessages (both from '@kitn.ai/ui/wire')
367
+ // and pass upstream.body through untouched before you enable it.
368
+ stream: true,
369
+ }),
370
+ });
371
+
372
+ // FORWARD THE STATUS. Returning 200 here is how a missing key turns into
373
+ // silence: the 401 body is JSON, it goes out labelled text/event-stream, the
374
+ // SSE reader finds no frame, and the turn resolves empty with nothing logged
375
+ // and no bubble. With the status intact readOpenAIStream throws a WireError
376
+ // carrying Anthropic's own message — which for this API is usually the exact
377
+ // field you got wrong.
378
+ if (!upstream.ok) {
379
+ return new Response(await upstream.text(), {
380
+ status: upstream.status,
381
+ headers: {
382
+ 'Content-Type': upstream.headers.get('content-type') ?? 'application/json',
383
+ },
384
+ });
385
+ }
386
+ if (!upstream.body) {
387
+ return new Response(JSON.stringify({ error: { message: 'Anthropic returned no body to stream.' } }), {
388
+ status: 502,
389
+ headers: { 'Content-Type': 'application/json' },
390
+ });
391
+ }
392
+
393
+ return new Response(reframeToOpenAISse(upstream.body), {
394
+ status: 200,
395
+ headers: {
396
+ 'Content-Type': 'text/event-stream; charset=utf-8',
397
+ // no-transform stops a proxy buffering the stream into one blob.
398
+ 'Cache-Control': 'no-cache, no-transform',
399
+ Connection: 'keep-alive',
400
+ },
401
+ });
402
+ }`,
403
+ streamMapping:
404
+ "The Anthropic Messages API streams its OWN SSE dialect: message_start, content_block_start / content_block_delta / content_block_stop, message_delta, message_stop. It is NOT OpenAI-format, and feeding it to readOpenAIStream does not throw — it parses to nothing and the turn ends silently empty. The route therefore re-frames each frame to OpenAI form before it reaches the browser: text_delta -> delta.content, thinking_delta -> delta.reasoning, a tool_use content_block_start plus its input_json_delta fragments -> delta.tool_calls, message_delta.stop_reason -> finish_reason, an error frame -> an in-band {error:{message}}. readOpenAIStream from @kitn.ai/ui/wire then parses it exactly as it does every other integration. Anthropic indexes by CONTENT BLOCK while OpenAI's tool_calls[].index counts TOOL CALLS, so the route keeps a block-index -> tool-index map: a thinking block ahead of a tool call shifts the block index but not the tool index. Usage is the one thing not re-framed (input_tokens arrives on message_start and output_tokens on message_delta, so joining them needs state) — map both onto a {usage:{prompt_tokens,completion_tokens}} frame if you want token counts. To keep the native frames instead, return upstream.body untouched and read it with readAnthropicStream; that is also the only way to round-trip thinking blocks verbatim.",
405
+ runNote:
406
+ "Set ANTHROPIC_API_KEY (console.anthropic.com). The route pins claude-opus-5; claude-sonnet-5 and claude-haiku-4-5 are the cheaper swaps. Four things this API does NOT share with the OpenAI one, all of them 400s: system is a top-level field and not a message, max_tokens is required, tool_choice is an object ({ type: 'auto' }) and not a string, and tool schemas use input_schema rather than function.parameters. Extended thinking: on claude-opus-5 and the other current models budget_tokens is REMOVED (400) — use thinking: { type: 'adaptive', display: 'summarized' } with output_config.effort, and note that display defaults to 'omitted', which streams empty thinking blocks that look exactly like no reasoning at all. On older models (claude-haiku-4-5, claude-sonnet-4-5) budget_tokens still applies and must be >= 1024 with max_tokens strictly greater.",
407
+ docsSlug: 'integrations/connect-any-model',
408
+ // 'model' was withheld until scaffold.ts could seed it correctly: the shared
409
+ // fallback is 'openai/gpt-4o-mini', which api.anthropic.com rejects outright,
410
+ // and shipping an editable const with a broken default is the dead-const defect
411
+ // forwardsFromClient exists to prevent. CLIENT_MODEL_IDS now carries a real
412
+ // Anthropic id, so the const is live and the route reads it.
413
+ forwardsFromClient: ['model', 'tools'],
414
+ // 'openai', NOT 'anthropic' — and this is the counter-intuitive one, so read
415
+ // `toAnthropicTools` above before changing it. This route CONVERTS the array
416
+ // server-side: it reads `raw.function.name` and `raw.function.parameters` off
417
+ // each entry, i.e. the OpenAI envelope, and rebuilds it as
418
+ // `{ name, description, input_schema }`. Sending Anthropic's own shape from the
419
+ // client would leave `.function` undefined, so every tool would go upstream
420
+ // with a blank name and an empty schema. The envelope belongs to the ROUTE's
421
+ // request contract, not to the provider it happens to POST to — which is
422
+ // exactly why this is declared here rather than derived from `streamFormat`.
423
+ clientToolFormat: 'openai',
424
+ // Nothing to install. The route hand-rolls the Messages API over global
425
+ // `fetch` and its own re-framer, so it imports no module — not even
426
+ // @anthropic-ai/sdk.
427
+ deps: { npm: [], pip: [] },
428
+ // The route reads ANTHROPIC_API_KEY into an `x-api-key` header. Note the
429
+ // header is NOT `Authorization`, which is why the schema's detector matches
430
+ // both spellings.
431
+ keyExposure: 'needs-proxy',
432
+ // A remote HTTPS endpoint and a key. Nothing to install, nothing to start.
433
+ outOfBand: 'none',
434
+ };
435
+
436
+ export default anthropic;
@@ -8,41 +8,44 @@ const cloudflare: Integration = {
8
8
  streamFormat: 'openai-sse',
9
9
  envVars: ['CF_ACCOUNT_ID', 'CF_API_TOKEN'],
10
10
  routeTemplates: {
11
- next: `// app/api/chat/route.ts proxy Workers AI, keep the token server-side
12
- export async function POST(req: Request) {
13
- const { messages } = await req.json();
14
-
15
- const upstream = await fetch(
16
- \`https://api.cloudflare.com/client/v4/accounts/\${process.env.CF_ACCOUNT_ID}/ai/v1/chat/completions\`,
17
- {
18
- method: 'POST',
19
- headers: {
20
- Authorization: \`Bearer \${process.env.CF_API_TOKEN}\`,
21
- 'Content-Type': 'application/json',
22
- },
23
- body: JSON.stringify({
24
- model: '@cf/meta/llama-3.1-8b-instruct',
25
- messages,
26
- stream: true,
27
- }),
28
- },
29
- );
30
-
31
- // Workers AI returns OpenAI-format SSE — pass it straight through.
32
- return new Response(upstream.body, { headers: { 'Content-Type': 'text/event-stream' } });
33
- }`,
34
- worker: `// Worker handler — env.AI is bound in wrangler.toml
11
+ // Kept as a framework-specific template because it cannot be expressed as a
12
+ // portable handler: `env.AI` is a Worker binding, and only a Worker has one.
13
+ // Everything else uses `webRoute` below.
14
+ worker: `// Worker handler: env.AI is bound in wrangler.toml
35
15
  // env.AI.run emits Cloudflare-native SSE (data: {"response":"<token>"}).
36
16
  // The TransformStream below re-frames each chunk to OpenAI-format SSE so
37
- // kai-chat's reader works without any client-side changes.
17
+ // readOpenAIStream reads it without any client-side changes.
18
+ import type { OpenAIWireMessage } from '@kitn.ai/ui/wire';
19
+
20
+ /**
21
+ * What the front end POSTs. \`req.json()\` is \`unknown\` (it is whatever the
22
+ * client sent), so the body is narrowed once here instead of at every use —
23
+ * without it this Worker does not compile. Widen it as you add fields.
24
+ */
25
+ type ChatRequestBody = { messages: OpenAIWireMessage[] };
26
+
38
27
  export default {
39
28
  async fetch(req: Request, env: Env): Promise<Response> {
40
- const { messages } = await req.json();
29
+ const { messages } = (await req.json()) as ChatRequestBody;
41
30
 
42
- const nativeStream = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
43
- messages,
44
- stream: true,
45
- });
31
+ let nativeStream: ReadableStream<Uint8Array>;
32
+ try {
33
+ // env.AI.run is typed to return the non-streaming shape; \`stream: true\`
34
+ // makes it a ReadableStream at runtime, which the types cannot express, so
35
+ // the hop through \`unknown\` is required rather than cosmetic.
36
+ nativeStream = (await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
37
+ messages,
38
+ stream: true,
39
+ })) as unknown as ReadableStream<Uint8Array>;
40
+ } catch (err) {
41
+ // A REAL status, before a byte is streamed. Returning 200 here would send
42
+ // a JSON error labelled text/event-stream: the reader finds no frame and
43
+ // the turn resolves empty, with nothing logged and no bubble.
44
+ return new Response(
45
+ JSON.stringify({ error: { message: err instanceof Error ? err.message : 'Workers AI call failed' } }),
46
+ { status: 502, headers: { 'Content-Type': 'application/json' } },
47
+ );
48
+ }
46
49
 
47
50
  // Re-frame Cloudflare-native SSE → OpenAI-format SSE
48
51
  const { readable, writable } = new TransformStream();
@@ -51,38 +54,124 @@ export default {
51
54
  const decoder = new TextDecoder();
52
55
 
53
56
  (async () => {
54
- const reader = (nativeStream as ReadableStream<Uint8Array>).getReader();
57
+ const reader = nativeStream.getReader();
55
58
  let buffer = '';
56
- while (true) {
57
- const { value, done } = await reader.read();
58
- if (done) break;
59
- buffer += decoder.decode(value, { stream: true });
60
- const lines = buffer.split('\\n');
61
- buffer = lines.pop() ?? '';
62
- for (const line of lines) {
63
- const s = line.trim();
64
- if (!s.startsWith('data:')) continue;
65
- const payload = s.slice(5).trim();
66
- if (payload === '[DONE]') continue;
67
- try {
68
- const { response } = JSON.parse(payload) as { response?: string };
69
- if (response == null) continue;
70
- const openaiChunk = JSON.stringify({ choices: [{ delta: { content: response } }] });
71
- await writer.write(encoder.encode(\`data: \${openaiChunk}\\n\\n\`));
72
- } catch { /* skip malformed lines */ }
59
+ try {
60
+ while (true) {
61
+ const { value, done } = await reader.read();
62
+ if (done) break;
63
+ buffer += decoder.decode(value, { stream: true });
64
+ const lines = buffer.split('\\n');
65
+ buffer = lines.pop() ?? '';
66
+ for (const line of lines) {
67
+ const s = line.trim();
68
+ if (!s.startsWith('data:')) continue;
69
+ const payload = s.slice(5).trim();
70
+ if (payload === '[DONE]') continue;
71
+ try {
72
+ const { response } = JSON.parse(payload) as { response?: string };
73
+ if (response == null) continue;
74
+ const openaiChunk = JSON.stringify({ choices: [{ delta: { content: response } }] });
75
+ await writer.write(encoder.encode(\`data: \${openaiChunk}\\n\\n\`));
76
+ } catch { /* skip malformed lines */ }
77
+ }
73
78
  }
79
+ } catch (err) {
80
+ // The response is already streaming, so the status is spent: report the
81
+ // failure IN BAND. readOpenAIStream lands this on turn.error and keeps
82
+ // whatever already streamed.
83
+ const message = err instanceof Error ? err.message : 'Workers AI stream failed';
84
+ await writer.write(encoder.encode(\`data: \${JSON.stringify({ error: { message } })}\\n\\n\`));
85
+ } finally {
86
+ // Always: an unclosed writer leaves the browser waiting forever.
87
+ await writer.write(encoder.encode('data: [DONE]\\n\\n'));
88
+ await writer.close();
74
89
  }
75
- await writer.write(encoder.encode('data: [DONE]\\n\\n'));
76
- await writer.close();
77
90
  })();
78
91
 
79
- return new Response(readable, { headers: { 'Content-Type': 'text/event-stream' } });
92
+ return new Response(readable, {
93
+ status: 200,
94
+ headers: {
95
+ 'Content-Type': 'text/event-stream; charset=utf-8',
96
+ 'Cache-Control': 'no-cache, no-transform',
97
+ },
98
+ });
80
99
  },
81
100
  };`,
82
101
  },
83
- streamMapping: "Workers AI via the OpenAI-compatible HTTP endpoint returns OpenAI-format SSE — pipe upstream.body straight to the browser; kai-chat's reader handles it. The native env.AI binding streams Cloudflare's own format (data: {\"response\":\"...token...\"}); the worker route template re-frames these chunks to OpenAI-format SSE via a TransformStream before returning.",
102
+ webRoute: `async function chatHandler(request: Request): Promise<Response> {
103
+ // Proxy Workers AI over its OpenAI-compatible HTTP endpoint, token server-side.
104
+ const { messages } = await readChatRequest(request);
105
+
106
+ const upstream = await fetch(
107
+ \`https://api.cloudflare.com/client/v4/accounts/\${process.env.CF_ACCOUNT_ID}/ai/v1/chat/completions\`,
108
+ {
109
+ method: 'POST',
110
+ headers: {
111
+ Authorization: \`Bearer \${process.env.CF_API_TOKEN}\`,
112
+ 'Content-Type': 'application/json',
113
+ },
114
+ body: JSON.stringify({
115
+ model: '@cf/meta/llama-3.1-8b-instruct',
116
+ messages,
117
+ stream: true,
118
+ }),
119
+ },
120
+ );
121
+
122
+ // FORWARD THE STATUS. A wrong CF_API_TOKEN is a 401/403: returning 200 sends
123
+ // its JSON body out labelled text/event-stream, the SSE reader finds no frame,
124
+ // and the turn resolves empty with nothing logged and no bubble.
125
+ if (!upstream.ok) {
126
+ return new Response(await upstream.text(), {
127
+ status: upstream.status,
128
+ headers: {
129
+ 'Content-Type': upstream.headers.get('content-type') ?? 'application/json',
130
+ },
131
+ });
132
+ }
133
+ if (!upstream.body) {
134
+ return new Response(JSON.stringify({ error: { message: 'Workers AI returned no body to stream.' } }), {
135
+ status: 502,
136
+ headers: { 'Content-Type': 'application/json' },
137
+ });
138
+ }
139
+
140
+ // Workers AI returns OpenAI-format SSE. Pass it straight through.
141
+ return new Response(upstream.body, {
142
+ status: 200,
143
+ headers: {
144
+ 'Content-Type': 'text/event-stream; charset=utf-8',
145
+ 'Cache-Control': 'no-cache, no-transform',
146
+ Connection: 'keep-alive',
147
+ },
148
+ });
149
+ }`,
150
+ streamMapping:
151
+ "Workers AI via the OpenAI-compatible HTTP endpoint returns OpenAI-format SSE. Pipe upstream.body straight to the browser; readOpenAIStream from @kitn.ai/ui/wire parses it, including tool calls and reasoning. The native env.AI binding streams Cloudflare's own format (data: {\"response\":\"...token...\"}); the worker route template re-frames these chunks to OpenAI-format SSE via a TransformStream before returning.",
84
152
  runNote: 'Set CF_ACCOUNT_ID and CF_API_TOKEN. Model ids are prefixed with @cf/, e.g. @cf/meta/llama-3.1-8b-instruct. For the AI binding (worker key), add an [ai] block with binding = "AI" in wrangler.toml.',
85
153
  docsSlug: 'integrations/cloudflare-ai',
154
+ // Nothing. Both routes pin '@cf/meta/llama-3.1-8b-instruct' (the generic
155
+ // 'openai/gpt-4o-mini' a scaffold used to default to is not even a valid
156
+ // Workers AI id), and the worker route's re-framing reader forwards only the
157
+ // text `response` field, so a tool call could not reach the browser anyway.
158
+ forwardsFromClient: [],
159
+ // Nothing. The webRoute is global `fetch`, and the worker template's only
160
+ // import is a type from '@kitn.ai/ui/wire' — the kit itself, which every
161
+ // scaffold already depends on. Its `Env` is an ambient wrangler type, not an
162
+ // import, and the Worker skeleton owns wrangler/@cloudflare/workers-types.
163
+ deps: { npm: [], pip: [] },
164
+ // The webRoute reads CF_API_TOKEN into an `Authorization: Bearer` header.
165
+ //
166
+ // The two routes differ and the stricter one governs: the worker template uses
167
+ // the `env.AI` binding and holds no token in code at all, but a binding is a
168
+ // Worker capability, not a browser one. Either way a server is required.
169
+ keyExposure: 'needs-proxy',
170
+ // Workers AI is a remote HTTPS endpoint (the REST route) or a platform binding
171
+ // (the worker route) — either way there is nothing on the developer's machine
172
+ // to install or run. `wrangler` is a devDependency of a Worker app, not an
173
+ // out-of-band prerequisite of this integration.
174
+ outOfBand: 'none',
86
175
  };
87
176
 
88
177
  export default cloudflare;