@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,689 @@
1
+ // ChatMessage[] back onto the wire, so a host can run a multi-round tool loop in
2
+ // about fifteen lines of its own code. The kit never calls a consumer's function
3
+ // and never drives the loop; these two functions are the whole contribution.
4
+ //
5
+ // No provider SDK, no fetch, no DOM. Pure functions over the content model.
6
+ import type { ChatMessage, MessagePart } from '../elements/chat-types';
7
+ import type { ToolPart } from '../components/tool-types';
8
+ import { classifyAttachment, textFileContent, type ClassifiedFile } from './files';
9
+ import { resolveMediaPolicy, type MediaPolicy, type MediaTypeFilter } from './media-types';
10
+
11
+ export interface OpenAIToolCall {
12
+ id: string;
13
+ type: 'function';
14
+ function: { name: string; arguments: string };
15
+ }
16
+
17
+ /** One `reasoning_details` entry. Provider-owned open shape, kept as a record
18
+ * for the same reason `AnthropicContentBlock` is: an opaque entry has to pass
19
+ * through UNTOUCHED, and a closed type would be a list of the fields we happen
20
+ * to have seen. */
21
+ export type OpenAIReasoningDetail = Record<string, unknown>;
22
+
23
+ /** A multimodal user message's content entries. `image_url` takes an https URL
24
+ * or a `data:` URI in the same field; `file` takes `file_data`, which is a DATA
25
+ * URI on this wire (`data:application/pdf;base64,...`) and not bare base64. */
26
+ export type OpenAIContentPart =
27
+ | { type: 'text'; text: string }
28
+ | { type: 'image_url'; image_url: { url: string } }
29
+ | { type: 'file'; file: { filename?: string; file_data: string } };
30
+
31
+ export interface OpenAIWireMessage {
32
+ role: 'system' | 'user' | 'assistant' | 'tool';
33
+ /** An ARRAY only when the turn carries an encodable `file` part. A text-only
34
+ * turn stays a plain string, so adding attachment support changed nothing
35
+ * about what an existing thread puts on the wire. */
36
+ content: string | OpenAIContentPart[] | null;
37
+ tool_calls?: OpenAIToolCall[];
38
+ tool_call_id?: string;
39
+ name?: string;
40
+ /** Only ever present when `toOpenAIMessages` was asked for it. See
41
+ * `OpenAIEncodeOptions.reasoning`. */
42
+ reasoning_details?: OpenAIReasoningDetail[];
43
+ }
44
+
45
+ /** EXTENDS rather than restates the file options: `onUnencodableFile` and
46
+ * `accept` mean the same thing on both wires, and a second declaration of them
47
+ * here is a second place to forget to update. */
48
+ export interface OpenAIEncodeOptions extends FileEncodeOptions {
49
+ /**
50
+ * Whether to send the assistant's own reasoning back with the thread.
51
+ *
52
+ * DEFAULT `'omit'`, and that default is a measurement, not caution. Omitting
53
+ * reasoning is accepted by every configuration tested -- five live omission
54
+ * trials plus 28 recorded live requests per configuration across the spike's
55
+ * conformance sweep, zero 400s -- so the path that ships today demonstrably
56
+ * works, while including reasoning cost about 25% more prompt tokens per round
57
+ * when measured (665 -> 834 on a two-round loop). A library does not get to
58
+ * raise every consumer's bill and add a new provider-validation surface as a
59
+ * side effect of a bug fix.
60
+ *
61
+ * `'include'` is for a multi-round TOOL loop, which is where OpenRouter says it
62
+ * pays: "when you post tool results, including the original reasoning ensures
63
+ * the model can continue its reasoning from where it left off". Measured
64
+ * accepted (HTTP 200) for a signed Anthropic block and for an OpenAI encrypted
65
+ * block, over the OpenAI-compatible wire.
66
+ *
67
+ * The Anthropic wire has no such knob because it has no such choice: a filtered
68
+ * or rebuilt thinking block there is a hard 400.
69
+ */
70
+ reasoning?: 'omit' | 'include';
71
+ }
72
+
73
+ /**
74
+ * What to do with a `file` part this wire cannot carry.
75
+ *
76
+ * DEFAULT `'throw'`, and the default is the whole point. Skipping is how
77
+ * attachments came to render perfectly in the thread and reach the model as
78
+ * nothing: the developer wires up upload, watches it work, and ships a model
79
+ * that cannot see the file. A throw here names the message, the part and the
80
+ * reason, which is strictly more than a 400 at request time would tell you.
81
+ *
82
+ * `'skip'` restores the lenient behaviour for a host that would rather send a
83
+ * degraded turn than fail one. It is silent, but it is silence the developer
84
+ * asked for by name, which is the difference that matters.
85
+ */
86
+ export type UnencodableFilePolicy = 'throw' | 'skip';
87
+
88
+ export interface FileEncodeOptions {
89
+ onUnencodableFile?: UnencodableFilePolicy;
90
+ /**
91
+ * Narrow which attachment media types reach the wire, as HTML `accept` syntax
92
+ * (`'image/*,application/pdf'`) or an array of the same.
93
+ *
94
+ * THE SAME STRING the composer takes as `<kai-chat accept="...">`, resolved by
95
+ * the same function against the same declaration -- so a developer writes the
96
+ * set once as a constant and hands it to both ends. Omitted means the kit's
97
+ * full capability set, which is `encodableMediaTypes()`.
98
+ *
99
+ * It can only NARROW. Naming a type the encoders cannot represent does not
100
+ * enable it; that would just move the failure to a provider 400.
101
+ */
102
+ accept?: MediaTypeFilter;
103
+ }
104
+
105
+ export type AnthropicEncodeOptions = FileEncodeOptions;
106
+
107
+ /** Anthropic content blocks are an open, provider-owned union. Keeping them as
108
+ * records is what lets a verbatim `thinking` payload pass through UNTOUCHED,
109
+ * which is the entire point of this encoder. */
110
+ export type AnthropicContentBlock = Record<string, unknown>;
111
+
112
+ export interface AnthropicWireMessage {
113
+ role: 'user' | 'assistant';
114
+ content: AnthropicContentBlock[];
115
+ }
116
+
117
+ /** A message cannot be encoded without losing something the provider will reject.
118
+ * Thrown at encode time, on purpose: a throw here beats a 400 at request time,
119
+ * because here you still know which message and which part caused it. */
120
+ export class WireEncodeError extends Error {
121
+ readonly messageId: string;
122
+ readonly partIndex: number;
123
+
124
+ constructor(message: string, messageId: string, partIndex: number) {
125
+ super(message);
126
+ // Restores the prototype chain when this class is DOWNLEVELLED to ES5 by a
127
+ // consumer's build, exactly as WireError does. Without it
128
+ // `err instanceof WireEncodeError` is false there and the documented way to
129
+ // catch an unencodable history stops working.
130
+ Object.setPrototypeOf(this, WireEncodeError.prototype);
131
+ this.name = 'WireEncodeError';
132
+ this.messageId = messageId;
133
+ this.partIndex = partIndex;
134
+ }
135
+ }
136
+
137
+ type TextPart = Extract<MessagePart, { type: 'text' }>;
138
+
139
+ /** A tool that can be echoed back: it has the provider's own call id AND a
140
+ * result. Narrowing `toolCallId` to `string` here is what removes every
141
+ * non-null assertion downstream. */
142
+ type SettledTool = ToolPart & { toolCallId: string };
143
+
144
+ const isTextPart = (p: MessagePart): p is TextPart => p.type === 'text';
145
+
146
+ const textOf = (parts: MessagePart[]): string =>
147
+ parts
148
+ .filter(isTextPart)
149
+ .map((p) => p.text)
150
+ .join('');
151
+
152
+ type FilePart = Extract<MessagePart, { type: 'file' }>;
153
+
154
+ /** A wire either shapes a classified file into a block, or explains why it
155
+ * cannot. `ok: false` is NOT the same as an unencodable attachment: the
156
+ * attachment can be perfectly sound and still have no form on this particular
157
+ * wire, which is exactly the remote-PDF case on OpenAI. */
158
+ type WireFileResult<T> = { ok: true; block: T } | { ok: false; reason: string };
159
+
160
+ /**
161
+ * One `file` part to one wire block, or `null` when it is skipped.
162
+ *
163
+ * Skipped means one of two things and never a third: the attachment is kit-side
164
+ * (a citation chip), or the host explicitly opted into `onUnencodableFile:
165
+ * 'skip'`. Everything else throws, because a dropped attachment is invisible in
166
+ * exactly the way the developer needs it not to be.
167
+ */
168
+ function encodeFilePart<T>(
169
+ part: FilePart,
170
+ messageId: string,
171
+ partIndex: number,
172
+ policy: UnencodableFilePolicy,
173
+ media: MediaPolicy,
174
+ toBlock: (file: ClassifiedFile) => WireFileResult<T>,
175
+ ): T | null {
176
+ const classified = classifyAttachment(part.attachment, media);
177
+ if (classified.status === 'kit-side') return null;
178
+
179
+ const refuse = (reason: string): null => {
180
+ if (policy === 'skip') return null;
181
+ throw new WireEncodeError(
182
+ `Cannot encode file part ${partIndex} of message "${messageId}": ${reason} Pass { onUnencodableFile: 'skip' } to drop it instead of failing.`,
183
+ messageId,
184
+ partIndex,
185
+ );
186
+ };
187
+
188
+ if (classified.status === 'unencodable') return refuse(classified.reason);
189
+
190
+ const shaped = toBlock(classified.file);
191
+ return shaped.ok ? shaped.block : refuse(shaped.reason);
192
+ }
193
+
194
+ /** `image_url` takes an https URL and a `data:` URI in the same field, so an
195
+ * image is one shape here. A PDF is not: `file_data` is base64-only. A text
196
+ * file is neither: it becomes ordinary text content, because that is the only
197
+ * thing either API's block set can express it as. */
198
+ const openAIFileBlock = (file: ClassifiedFile): WireFileResult<OpenAIContentPart> => {
199
+ if (file.kind === 'text') {
200
+ return { ok: true, block: { type: 'text', text: textFileContent(file) } };
201
+ }
202
+
203
+ if (file.kind === 'image') {
204
+ const url = file.source.type === 'base64' ? file.source.dataUri : file.source.url;
205
+ return { ok: true, block: { type: 'image_url', image_url: { url } } };
206
+ }
207
+
208
+ if (file.source.type === 'remote') {
209
+ return {
210
+ ok: false,
211
+ reason: `it is a PDF at a remote URL ("${file.source.url}"), and a chat-completions \`file\` content part carries only \`file_id\` or \`file_data\` -- this wire has no URL form for a document. Fetch it and stage it as a \`data:\` URI, or upload it through the Files API and send \`file_id\` yourself; fetching it HERE would put I/O in the encoder. The Anthropic wire does take a remote PDF, so toAnthropicMessages accepts this same message.`,
212
+ };
213
+ }
214
+
215
+ return {
216
+ ok: true,
217
+ block: {
218
+ type: 'file',
219
+ file: {
220
+ ...(file.filename !== undefined ? { filename: file.filename } : {}),
221
+ file_data: file.source.dataUri,
222
+ },
223
+ },
224
+ };
225
+ };
226
+
227
+ /** The image and document blocks take the same two source shapes, which is why
228
+ * this wire has no gap where OpenAI has one. Text is the third case and is not
229
+ * a `document` block: this API's content-block set is text / image / document
230
+ * with no arbitrary-file member, so an inlined text file IS a text block.
231
+ * (A `document` block CAN carry text via the Files API, which would buy
232
+ * citations -- see the design note; it needs an upload step this layer cannot
233
+ * make, because it does no I/O.) */
234
+ const anthropicFileBlock = (file: ClassifiedFile): WireFileResult<AnthropicContentBlock> => {
235
+ if (file.kind === 'text') {
236
+ return { ok: true, block: { type: 'text', text: textFileContent(file) } };
237
+ }
238
+
239
+ return {
240
+ ok: true,
241
+ block: {
242
+ type: file.kind === 'image' ? 'image' : 'document',
243
+ source:
244
+ file.source.type === 'base64'
245
+ ? { type: 'base64', media_type: file.source.mediaType, data: file.source.data }
246
+ : { type: 'url', url: file.source.url },
247
+ },
248
+ };
249
+ };
250
+
251
+ /** A tool is encodable only once it has a RESULT. Both APIs require every echoed
252
+ * tool call to have exactly one matching result, so a call with no answer yet is
253
+ * skipped entirely rather than sent half-formed. In a real loop the host
254
+ * executes tools before re-encoding, so this only drops genuinely pending work.
255
+ *
256
+ * A tool with no `toolCallId` is skipped for the same reason: there is no id to
257
+ * correlate a result with, and synthesising one (`call_0`) produces a request
258
+ * the provider rejects. */
259
+ const isSettled = (tool: ToolPart): tool is SettledTool =>
260
+ typeof tool.toolCallId === 'string' &&
261
+ tool.toolCallId !== '' &&
262
+ (tool.output !== undefined || tool.errorText !== undefined);
263
+
264
+ /** The argument text to echo. `rawInput` is the raw accumulated fragments and is
265
+ * preferred: providers validate an echoed tool block against what they emitted,
266
+ * and re-stringifying a parse changes key order and whitespace. An EMPTY
267
+ * `rawInput` is not valid JSON, so it falls through to the parsed snapshot the
268
+ * same way an absent one does. */
269
+ function argumentsOf(tool: ToolPart): string {
270
+ if (tool.rawInput !== undefined && tool.rawInput !== '') return tool.rawInput;
271
+ return tool.input !== undefined ? JSON.stringify(tool.input) : '{}';
272
+ }
273
+
274
+ const toolCallOf = (tool: SettledTool): OpenAIToolCall => ({
275
+ id: tool.toolCallId,
276
+ type: 'function',
277
+ function: { name: tool.type, arguments: argumentsOf(tool) },
278
+ });
279
+
280
+ /** `errorText` WINS over `output` when a part somehow carries both, on both
281
+ * wires. A host that reported a failure after a partial result meant the
282
+ * failure, and echoing a half-written success back to the model is the version
283
+ * of this that produces confidently wrong follow-up work. */
284
+ const toolResultOf = (tool: SettledTool): OpenAIWireMessage => ({
285
+ role: 'tool',
286
+ tool_call_id: tool.toolCallId,
287
+ name: tool.type,
288
+ content: tool.errorText ?? JSON.stringify(tool.output),
289
+ });
290
+
291
+ type ReasoningPart = Extract<MessagePart, { type: 'reasoning' }>;
292
+
293
+ const isRecord = (v: unknown): v is Record<string, unknown> =>
294
+ typeof v === 'object' && v !== null && !Array.isArray(v);
295
+
296
+ /**
297
+ * The provider's own `reasoning_details` entry for this block, if the part came
298
+ * from the OpenAI wire at all.
299
+ *
300
+ * WHAT THIS IS NOT: the block. `part.raw` holds the LAST delta that touched this
301
+ * part, because `appendReasoningPart` resolves `raw` last-write-wins, and every
302
+ * reasoning-carrying provider streams its blocks in fragments. So this entry is
303
+ * read for its SHAPE -- type, format, id, index, and an opaque payload if it has
304
+ * one -- and never for its text.
305
+ *
306
+ * A delta carrying several entries at once is rare but legal, so the entry is
307
+ * matched on the part's own block index first; that is the only correlator there
308
+ * is. The last one wins otherwise, which is the entry `raw` was captured from.
309
+ */
310
+ function detailOf(part: ReasoningPart): Record<string, unknown> | undefined {
311
+ if (part.raw?.source !== 'openai.reasoning_details') return undefined;
312
+ const payload = part.raw.payload;
313
+ if (!Array.isArray(payload)) return undefined;
314
+ const records = payload.filter(isRecord);
315
+ const matched = records.filter((d) => d.index === part.index);
316
+ const pool = matched.length > 0 ? matched : records;
317
+ return pool[pool.length - 1];
318
+ }
319
+
320
+ /**
321
+ * ONE reasoning part to ONE `reasoning_details` entry, or nothing.
322
+ *
323
+ * Three cases, and every one of them is in a recorded fixture:
324
+ *
325
+ * 1. OPAQUE (`reasoning.encrypted`, `openai/gpt-5.4-mini`). The `data` blob is
326
+ * the block; there is no text and no signature to rebuild it from, and it
327
+ * arrives whole in a single frame, so `raw` really is the block here. It goes
328
+ * back BY REFERENCE, untouched.
329
+ *
330
+ * 2. SIGNED (`reasoning.text` with a signature, an Anthropic model over this
331
+ * wire). REASSEMBLED from `part.text` and `part.signature`, never echoed.
332
+ * Echoing `raw` sends the final frame, which carries the signature and NO
333
+ * text -- measured: of 85 reasoning frames in one turn, only the last has the
334
+ * signature and it has no text. The signature is the load-bearing field:
335
+ * stripping it is a hard 400 (`Invalid signature in thinking block`) from all
336
+ * four providers OpenRouter tried, while mutating the text was accepted.
337
+ *
338
+ * 3. NEITHER (`deepseek` `format: "unknown"`, and the `reasoning.summary`
339
+ * entries that ride alongside gpt-5.4-mini's encrypted block). SKIPPED.
340
+ * Nothing measured supports sending these: the two accepted round-trips were
341
+ * a signed block and an encrypted one, and every measured 400 was a provider
342
+ * failing to verify a block it was handed. Omission, by contrast, is proven
343
+ * accepted everywhere. `format: "unknown"` is OpenRouter saying it could not
344
+ * attribute the block to a round-trippable format, and re-sending a summary
345
+ * OpenAI itself treats as a display artifact spends tokens on nothing.
346
+ *
347
+ * On the sequence rule -- "the entire sequence of consecutive reasoning blocks
348
+ * must match the outputs generated by the model" -- case 3 is the one place this
349
+ * is READ rather than proven. It holds up because verifiability is a
350
+ * per-configuration property in all 40 recorded reasoning turns, never a
351
+ * per-block one: Haiku signs every block, deepseek signs none, gpt-5.4-mini
352
+ * signs none but carries `data`. So the skip never filters one block out of a
353
+ * run of comparable ones. For gpt-5.4-mini it drops the summary at block index 0
354
+ * and keeps the encrypted carrier at index 1, which is exactly the single-entry
355
+ * payload the live probe sent and got a 200 for.
356
+ */
357
+ function reasoningDetailOf(part: ReasoningPart): OpenAIReasoningDetail | undefined {
358
+ const detail = detailOf(part);
359
+ if (!detail) return undefined;
360
+
361
+ if (detail.type === 'reasoning.encrypted' || typeof detail.data === 'string') return detail;
362
+
363
+ if (typeof part.signature !== 'string' || part.signature === '') return undefined;
364
+
365
+ const type = typeof detail.type === 'string' ? detail.type : 'reasoning.text';
366
+ // The readable field is named for the entry type: `summary` entries carry
367
+ // `summary`, everything else carries `text`.
368
+ const textKey = type === 'reasoning.summary' ? 'summary' : 'text';
369
+ const out: OpenAIReasoningDetail = { type, [textKey]: part.text, signature: part.signature };
370
+ if (typeof detail.format === 'string') out.format = detail.format;
371
+ if (typeof detail.id === 'string') out.id = detail.id;
372
+ if (detail.index !== undefined) out.index = detail.index;
373
+ return out;
374
+ }
375
+
376
+ /**
377
+ * ChatMessage[] to an OpenAI chat-completions `messages` array.
378
+ *
379
+ * ONE ChatMessage CAN BECOME SEVERAL WIRE MESSAGES. The kit streams a whole
380
+ * assistant turn into a single message, so text, a tool call and the model's
381
+ * answer to that call all live in one `parts` array. The OpenAI wire has no such
382
+ * shape: a `role:'tool'` result must sit between the assistant message that
383
+ * announced the call and whatever the model said afterwards. So the turn is
384
+ * SPLIT at each tool boundary, into
385
+ *
386
+ * assistant(pre-tool text + tool_calls) -> tool(result)... -> assistant(answer)
387
+ *
388
+ * Flattening instead would put the model's answer BEFORE the result it was based
389
+ * on. No endpoint rejects that, which is exactly why it is worth spelling out:
390
+ * it quietly degrades every later round of a tool loop.
391
+ *
392
+ * Consecutive tool parts stay in ONE assistant message, because parallel calls
393
+ * are announced together and their results follow together.
394
+ *
395
+ * A turn that encodes to nothing is SKIPPED, never sent as `{ content: null }`
396
+ * with no `tool_calls`: OpenAI treats `content` as required unless `tool_calls`
397
+ * is present, and strict-compatible endpoints reject it.
398
+ *
399
+ * REASONING IS OPT-IN, and off by default. OpenRouter's OpenAI-compatible
400
+ * endpoint does have a channel on the way back in -- `reasoning_details` on the
401
+ * assistant message -- and `{ reasoning: 'include' }` uses it, one entry per
402
+ * reasoning part, in part order, reassembled by `reasoningDetailOf` rather than
403
+ * echoed out of `part.raw`. Read that function for which blocks make it and why.
404
+ * The default omits, because omitting is measured-accepted everywhere and costs
405
+ * about 25% fewer prompt tokens per round; see `OpenAIEncodeOptions.reasoning`.
406
+ *
407
+ * Reasoning alone still encodes to NOTHING. A block is content the model already
408
+ * produced, not a reason to send a turn, so a message carrying reasoning and no
409
+ * text and no settled tool is skipped exactly as before, rather than becoming
410
+ * `{ content: null }` with no `tool_calls`.
411
+ *
412
+ * `card` and `source` parts are never encoded; they are kit-side.
413
+ *
414
+ * `file` parts ARE encoded, on a USER turn, and a turn carrying nothing but an
415
+ * attachment is now a real message rather than nothing. Images become
416
+ * `image_url` (https URL or `data:` URI alike); a base64 PDF becomes a `file`
417
+ * part whose `file_data` is the data URI. Two cases have no form here and THROW
418
+ * by default: a remote PDF, because this wire's `file` part has no URL variant,
419
+ * and anything that is neither -- see `UnencodableFilePolicy` for why the
420
+ * default is a throw and not a skip.
421
+ *
422
+ * A `file` part on an ASSISTANT turn is still dropped. Neither API accepts image
423
+ * or document content in an assistant message, so there is nothing to encode it
424
+ * to; attachments belong to the user turn that sent them.
425
+ */
426
+ export function toOpenAIMessages(
427
+ messages: ChatMessage[],
428
+ options: OpenAIEncodeOptions = {},
429
+ ): OpenAIWireMessage[] {
430
+ const includeReasoning = options.reasoning === 'include';
431
+ const filePolicy = options.onUnencodableFile ?? 'throw';
432
+ const media = resolveMediaPolicy({ accept: options.accept });
433
+ const out: OpenAIWireMessage[] = [];
434
+
435
+ for (const message of messages) {
436
+ if (message.role === 'user') {
437
+ // Built in PART ORDER, with runs of adjacent text merged into one entry.
438
+ // A file authored after the text stays after it: `parts` is an ordered
439
+ // content model and reordering it here would be the encoder second-guessing
440
+ // the author. Anthropic's docs do suggest putting images before text for
441
+ // best results, which is guidance for whoever builds the parts array.
442
+ const content: OpenAIContentPart[] = [];
443
+ let buffered = '';
444
+ let carriesFile = false;
445
+
446
+ const flushText = (): void => {
447
+ if (buffered === '') return;
448
+ content.push({ type: 'text', text: buffered });
449
+ buffered = '';
450
+ };
451
+
452
+ message.parts.forEach((part, partIndex) => {
453
+ if (part.type === 'text') {
454
+ buffered += part.text;
455
+ return;
456
+ }
457
+ if (part.type !== 'file') return;
458
+ const block = encodeFilePart(part, message.id, partIndex, filePolicy, media, openAIFileBlock);
459
+ if (!block) return;
460
+ flushText();
461
+ content.push(block);
462
+ carriesFile = true;
463
+ });
464
+ flushText();
465
+
466
+ // No file survived, so this is an ordinary turn and stays a plain string.
467
+ if (!carriesFile) {
468
+ const text = textOf(message.parts);
469
+ if (text !== '') out.push({ role: 'user', content: text });
470
+ continue;
471
+ }
472
+ out.push({ role: 'user', content });
473
+ continue;
474
+ }
475
+
476
+ let text = '';
477
+ let pending: SettledTool[] = [];
478
+ let reasoning: OpenAIReasoningDetail[] = [];
479
+
480
+ /** Emit the assistant message for everything buffered so far, then the
481
+ * result message for each call it announced, in the same order.
482
+ *
483
+ * The early return leaves any buffered reasoning where it is on purpose:
484
+ * nothing was emitted, so those blocks still belong to a message that has
485
+ * not been written yet. In practice it is unreachable with reasoning
486
+ * buffered, because the only mid-loop `flush()` runs with `pending`
487
+ * non-empty. */
488
+ const flush = (): void => {
489
+ if (text === '' && pending.length === 0) return;
490
+ out.push({
491
+ role: 'assistant',
492
+ content: text === '' ? null : text,
493
+ ...(reasoning.length > 0 ? { reasoning_details: reasoning } : {}),
494
+ ...(pending.length > 0 ? { tool_calls: pending.map(toolCallOf) } : {}),
495
+ });
496
+ for (const tool of pending) out.push(toolResultOf(tool));
497
+ text = '';
498
+ pending = [];
499
+ reasoning = [];
500
+ };
501
+
502
+ for (const part of message.parts) {
503
+ if (part.type === 'text') {
504
+ // Text that arrives AFTER a call is the model's answer to it, so the
505
+ // call and its result have to be on the wire before this text is.
506
+ if (pending.length > 0) flush();
507
+ text += part.text;
508
+ continue;
509
+ }
510
+ if (part.type === 'reasoning') {
511
+ if (!includeReasoning) continue;
512
+ // A block after a call belongs to the round that READ the result, for the
513
+ // same reason text does. Same rule as `toAnthropicMessages`.
514
+ if (pending.length > 0) flush();
515
+ const detail = reasoningDetailOf(part);
516
+ if (detail) reasoning.push(detail);
517
+ continue;
518
+ }
519
+ if (part.type === 'tool' && isSettled(part.tool)) pending.push(part.tool);
520
+ }
521
+ flush();
522
+ }
523
+
524
+ return out;
525
+ }
526
+
527
+ /**
528
+ * ChatMessage[] to an Anthropic Messages `messages` array. THE ROUND-TRIP
529
+ * ENCODER.
530
+ *
531
+ * A reasoning block is emitted as `part.raw.payload` verbatim and is NEVER
532
+ * rebuilt from `text` plus `signature`: Anthropic returns 400 if a thinking
533
+ * block in the most recent assistant message is modified, reordered, filtered or
534
+ * reconstructed. A reasoning part with no `raw`, or with a `raw` captured from
535
+ * some other format, therefore THROWS rather than silently producing a request
536
+ * that will fail.
537
+ *
538
+ * Block order follows part order, which follows stream order, with no filtering,
539
+ * because the API validates order too. An empty-text reasoning part (an omitted
540
+ * or redacted block) is still emitted: the docs require sending back every block
541
+ * "including any blocks with empty thinking fields".
542
+ *
543
+ * ONE ChatMessage CAN BECOME SEVERAL WIRE MESSAGES, for the same reason as
544
+ * `toOpenAIMessages`: the kit streams a whole assistant turn into one message, so
545
+ * the tool call and the model's answer to it share a `parts` array, but Anthropic
546
+ * carries the result in a SEPARATE user message that has to sit between them. So
547
+ * the turn is SPLIT at each tool boundary, into
548
+ *
549
+ * assistant(pre-tool blocks + tool_use) -> user(tool_result)... -> assistant(answer)
550
+ *
551
+ * Flattening instead puts the model's answer BEFORE the result it was based on,
552
+ * and strands every later round's thinking block in the first assistant message.
553
+ * Consecutive tool parts stay in ONE assistant message, because parallel calls are
554
+ * announced together and their results come back together.
555
+ *
556
+ * Adjacent user messages are MERGED. The API combines consecutive same-role turns
557
+ * itself rather than rejecting them, so this is not what stands between you and a
558
+ * 400; it is emitted anyway because the tool-result turn and a following user turn
559
+ * are one turn, several OpenAI-compatible Anthropic proxies do enforce strict
560
+ * alternation, and the merged form is what the models are trained on. Ordering is
561
+ * safe by construction: `results` is only non-empty when `blocks` is, so a
562
+ * tool_result message always follows its assistant message and can never be
563
+ * appended after a plain user turn.
564
+ *
565
+ * `file` parts on a USER turn become `image` and `document` blocks, in part
566
+ * order. Both take `source: {type:'base64'}` and `source: {type:'url'}`, so this
567
+ * wire can carry a remote PDF that `toOpenAIMessages` has to refuse. Anything
568
+ * neither API accepts as message content THROWS by default; see
569
+ * `UnencodableFilePolicy`. A `file` part on an ASSISTANT turn is dropped, because
570
+ * an assistant message here carries only text, thinking and tool_use.
571
+ *
572
+ * Asymmetry worth knowing: `tool_use.input` is a parsed OBJECT on this wire, not
573
+ * a string, so it uses `input` and not `rawInput`. Only thinking blocks carry a
574
+ * verbatim requirement.
575
+ */
576
+ export function toAnthropicMessages(
577
+ messages: ChatMessage[],
578
+ options: AnthropicEncodeOptions = {},
579
+ ): AnthropicWireMessage[] {
580
+ const filePolicy = options.onUnencodableFile ?? 'throw';
581
+ const media = resolveMediaPolicy({ accept: options.accept });
582
+ const out: AnthropicWireMessage[] = [];
583
+
584
+ /** Append to the trailing user message when there is one, so the wire never
585
+ * carries two user turns in a row. */
586
+ const pushUser = (content: AnthropicContentBlock[]): void => {
587
+ const last = out[out.length - 1];
588
+ if (last?.role === 'user') last.content = [...last.content, ...content];
589
+ else out.push({ role: 'user', content });
590
+ };
591
+
592
+ for (const message of messages) {
593
+ if (message.role === 'user') {
594
+ // Part order, for the same reason as `toOpenAIMessages`.
595
+ const userBlocks: AnthropicContentBlock[] = [];
596
+ message.parts.forEach((part, partIndex) => {
597
+ if (part.type === 'text') {
598
+ if (part.text !== '') userBlocks.push({ type: 'text', text: part.text });
599
+ return;
600
+ }
601
+ if (part.type !== 'file') return;
602
+ const block = encodeFilePart(part, message.id, partIndex, filePolicy, media, anthropicFileBlock);
603
+ if (block) userBlocks.push(block);
604
+ });
605
+ if (userBlocks.length > 0) pushUser(userBlocks);
606
+ continue;
607
+ }
608
+
609
+ let blocks: AnthropicContentBlock[] = [];
610
+ let results: AnthropicContentBlock[] = [];
611
+
612
+ /** Emit the assistant message built so far, then the user message carrying the
613
+ * results of the calls it announced. */
614
+ const flush = (): void => {
615
+ if (blocks.length > 0) out.push({ role: 'assistant', content: blocks });
616
+ // Anthropic carries tool results in the FOLLOWING user message.
617
+ if (results.length > 0) pushUser(results);
618
+ blocks = [];
619
+ results = [];
620
+ };
621
+
622
+ message.parts.forEach((part, partIndex) => {
623
+ switch (part.type) {
624
+ case 'reasoning': {
625
+ if (!part.raw) {
626
+ throw new WireEncodeError(
627
+ `Cannot encode reasoning part ${partIndex} of message "${message.id}": it has no \`raw\` payload, and Anthropic requires a thinking block to be echoed back verbatim. Rebuilding one from text plus signature is the documented 400. Produce reasoning parts with readAnthropicStream, which attaches the provider's own block, and keep \`raw\` when you persist a message.`,
628
+ message.id,
629
+ partIndex,
630
+ );
631
+ }
632
+ if (!part.raw.source.startsWith('anthropic.')) {
633
+ throw new WireEncodeError(
634
+ `Cannot encode reasoning part ${partIndex} of message "${message.id}": its \`raw\` came from "${part.raw.source}", not from the Anthropic Messages format. Only a payload tagged \`anthropic.\` can be echoed back verbatim. If you are talking to an Anthropic model through an OpenAI-compatible endpoint, use toOpenAIMessages.`,
635
+ message.id,
636
+ partIndex,
637
+ );
638
+ }
639
+ // A thinking block after a call belongs to the round that READ the
640
+ // result, so the result has to be on the wire before it is.
641
+ if (results.length > 0) flush();
642
+ blocks.push(part.raw.payload as AnthropicContentBlock);
643
+ break;
644
+ }
645
+ case 'text': {
646
+ if (part.text === '') break;
647
+ // Text after a call is the model's answer to it. Same reason.
648
+ if (results.length > 0) flush();
649
+ blocks.push({ type: 'text', text: part.text });
650
+ break;
651
+ }
652
+ case 'tool': {
653
+ const tool = part.tool;
654
+ if (!isSettled(tool)) break;
655
+ blocks.push({
656
+ type: 'tool_use',
657
+ id: tool.toolCallId,
658
+ name: tool.type,
659
+ input: tool.input ?? {},
660
+ });
661
+ results.push(
662
+ tool.errorText !== undefined
663
+ ? {
664
+ type: 'tool_result',
665
+ tool_use_id: tool.toolCallId,
666
+ is_error: true,
667
+ content: tool.errorText,
668
+ }
669
+ : {
670
+ type: 'tool_result',
671
+ tool_use_id: tool.toolCallId,
672
+ content: JSON.stringify(tool.output),
673
+ },
674
+ );
675
+ break;
676
+ }
677
+ default:
678
+ // card and source are kit-side. A `file` part reaches this arm only on
679
+ // an ASSISTANT turn, where the API accepts no image or document
680
+ // content at all -- user attachments are handled in the user branch.
681
+ break;
682
+ }
683
+ });
684
+
685
+ flush();
686
+ }
687
+
688
+ return out;
689
+ }