@kitn.ai/ui 0.25.2 → 0.26.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 (409) hide show
  1. package/README.md +2 -2
  2. package/dist/components/attachment-types.d.ts +46 -1
  3. package/dist/components/attachments.d.ts +31 -0
  4. package/dist/components/audio-visualizer/fit-scale.d.ts +50 -0
  5. package/dist/components/audio-visualizer/index.d.ts +15 -1
  6. package/dist/components/audio-visualizer/variant-bar.d.ts +24 -1
  7. package/dist/components/chat-thread.d.ts +3 -41
  8. package/dist/components/code-block.d.ts +22 -0
  9. package/dist/components/conversation-item.d.ts +54 -1
  10. package/dist/components/conversation-list.d.ts +65 -0
  11. package/dist/components/form.d.ts +42 -0
  12. package/dist/components/message.d.ts +7 -0
  13. package/dist/components/toast.d.ts +2 -1
  14. package/dist/components/voice-input.d.ts +8 -0
  15. package/dist/components/voice-output.d.ts +12 -1
  16. package/dist/components/workspace-shell.d.ts +87 -0
  17. package/dist/{create-tween-B5Y4Im8G.js → create-tween-BFT6c-Y8.js} +1 -1
  18. package/dist/{create-tween-4z1XYLkR.js → create-tween-CJNlzoaO.js} +1 -1
  19. package/dist/{create-tween-CUzOlaMS.js → create-tween-CQED4T0g.js} +1 -1
  20. package/dist/custom-elements.json +2931 -2572
  21. package/dist/diagnostics/hook.d.ts +81 -0
  22. package/dist/diagnostics/index.d.ts +7 -0
  23. package/dist/diagnostics/report-request.d.ts +61 -0
  24. package/dist/diagnostics.d.ts +5 -0
  25. package/dist/diagnostics.js +209 -0
  26. package/dist/elements/agent-card.js +1 -1
  27. package/dist/elements/artifact.js +1 -1
  28. package/dist/elements/attachments.js +1 -1
  29. package/dist/elements/audio-visualizer.js +1 -1
  30. package/dist/elements/autoloader.js +1 -1
  31. package/dist/elements/avatar.js +1 -1
  32. package/dist/elements/badge.js +1 -1
  33. package/dist/elements/button.js +1 -1
  34. package/dist/elements/card.js +1 -1
  35. package/dist/elements/cards.js +1 -1
  36. package/dist/elements/chain-of-thought.js +1 -1
  37. package/dist/elements/chat-scope-picker.js +1 -1
  38. package/dist/elements/chat-workspace.js +1 -1
  39. package/dist/elements/chat.js +1 -1
  40. package/dist/elements/checkpoint.js +1 -1
  41. package/dist/elements/choice.js +1 -1
  42. package/dist/elements/chunks/{Icon-b5A8hlHs.js → Icon--Y8vsFal.js} +1 -1
  43. package/dist/elements/chunks/action-icons-D9u3OzYW.js +1 -0
  44. package/dist/elements/chunks/arrow-left-CVh1TeN7.js +1 -0
  45. package/dist/elements/chunks/{artifact-BSuPYFHY.js → artifact-BkYcfIlZ.js} +1 -1
  46. package/dist/elements/chunks/attachments-DwfNlubq.js +1 -0
  47. package/dist/elements/chunks/audio-visualizer-D7lB6mvR.js +1 -0
  48. package/dist/elements/chunks/{badge-TIMiJFKc.js → badge-Q0-Cmwzz.js} +1 -1
  49. package/dist/elements/chunks/{button-B9okqG73.js → button-flUbeufF.js} +1 -1
  50. package/dist/elements/chunks/{card-renderer-CSoTL_e1.js → card-renderer-3Dyl-FpN.js} +1 -1
  51. package/dist/elements/chunks/check-B5kBPKeo.js +1 -0
  52. package/dist/elements/chunks/chevron-down-DZ1GS39h.js +1 -0
  53. package/dist/elements/chunks/chevron-right-CvP7OUd2.js +1 -0
  54. package/dist/elements/chunks/{choice-card-CGMc3kek.js → choice-card-D4QUIOWZ.js} +1 -1
  55. package/dist/elements/chunks/circle-CDf9g6MQ.js +1 -0
  56. package/dist/elements/chunks/circle-check-CA47Nqbz.js +1 -0
  57. package/dist/elements/chunks/{circle-x-mhH7pOK2.js → circle-x-CJ_HVbC8.js} +1 -1
  58. package/dist/elements/chunks/code-block-BdLiTitd.js +1 -0
  59. package/dist/elements/chunks/collapsible-BzbXpPzr.js +1 -0
  60. package/dist/elements/chunks/{composer-CZZmtYOT.js → composer-Byve-n8K.js} +2 -2
  61. package/dist/elements/chunks/{confirm-card-BWjf2-xo.js → confirm-card-B0flE3MN.js} +1 -1
  62. package/dist/elements/chunks/context-VLIL3sJ-.js +1 -0
  63. package/dist/elements/chunks/controllable-DTwTIlwp.js +1 -0
  64. package/dist/elements/chunks/conversation-item-BDztezOG.js +1 -0
  65. package/dist/elements/chunks/copy-Nf-kqxXH.js +1 -0
  66. package/dist/elements/chunks/{create-tween-CUrM6D_o.js → create-tween-c4Z8TDYz.js} +2 -2
  67. package/dist/elements/chunks/default-input-C2iLFq0b.js +1 -0
  68. package/dist/elements/chunks/define-wjHR1MkU.js +1 -0
  69. package/dist/elements/chunks/disclosure-CcNbyLy0.js +1 -0
  70. package/dist/elements/chunks/{download-DnCyWFJE.js → download-C4V7tBdb.js} +1 -1
  71. package/dist/elements/chunks/dropdown-BgWTthUx.js +1 -0
  72. package/dist/elements/chunks/{ellipsis-CVxqfsDZ.js → ellipsis-7gU33mL2.js} +1 -1
  73. package/dist/elements/chunks/embed-D3KewsIJ.js +1 -0
  74. package/dist/elements/chunks/{external-link-DG2sRdQL.js → external-link-CSOEsNlx.js} +1 -1
  75. package/dist/elements/chunks/{file-text-D0J9F6M7.js → file-text-D1G8khle.js} +1 -1
  76. package/dist/elements/chunks/{file-tree-yGWNK0Js.js → file-tree-zvB2i7P9.js} +1 -1
  77. package/dist/elements/chunks/{folder-NV1pi_ud.js → folder-WW2LGLid.js} +1 -1
  78. package/dist/elements/chunks/form-CQirdC4W.js +1 -0
  79. package/dist/elements/chunks/hover-card-CJJkfHTK.js +1 -0
  80. package/dist/elements/chunks/icon-Cfgmf4fa.js +1 -0
  81. package/dist/elements/chunks/{info-C8r5Q6cc.js → info-DX3YMOt6.js} +1 -1
  82. package/dist/elements/chunks/input-BtW9U6Sq.js +1 -0
  83. package/dist/elements/chunks/{kbd-OGJDUT-O.js → kbd-LGXwuT_V.js} +1 -1
  84. package/dist/elements/chunks/{link-BdsfLB7b.js → link-2My69BqT.js} +1 -1
  85. package/dist/elements/chunks/{link-preview-Cf5KOP-i.js → link-preview-OQ9IEq0f.js} +1 -1
  86. package/dist/elements/chunks/{loader-B5QzK_8_.js → loader-iOxc_Tu-.js} +1 -1
  87. package/dist/elements/chunks/{markdown-DbozyYqa.js → markdown-DgBFaGAz.js} +1 -1
  88. package/dist/elements/chunks/{message-DtXKPqiR.js → message-VMZohrXx.js} +1 -1
  89. package/dist/elements/chunks/message-circle-CpXMM-_n.js +1 -0
  90. package/dist/elements/chunks/message-square-DxcWtETQ.js +1 -0
  91. package/dist/elements/chunks/message-tZmV2A3p.js +1 -0
  92. package/dist/elements/chunks/{minimize-2-SegJ_oku.js → minimize-2-DPIUKTrT.js} +1 -1
  93. package/dist/elements/chunks/{model-switcher-D7JrFFHp.js → model-switcher-DsT8tFB0.js} +1 -1
  94. package/dist/elements/chunks/{overlay-CV7z2YuX.js → overlay-DuiwdXjm.js} +1 -1
  95. package/dist/elements/chunks/{paperclip-Bg0ITU3b.js → paperclip-CRZVp4z_.js} +1 -1
  96. package/dist/elements/chunks/{progress-bar-D1_rzt6N.js → progress-bar-DNy66e_E.js} +1 -1
  97. package/dist/elements/chunks/{prompt-suggestion-D5Sm8xF1.js → prompt-suggestion-Bn64Nl7L.js} +1 -1
  98. package/dist/elements/chunks/reasoning-Emn9N4-T.js +1 -0
  99. package/dist/elements/chunks/{resizable-Dpzjlvp4.js → resizable-D181b4fM.js} +1 -1
  100. package/dist/elements/chunks/{rotate-cw-DzVDJOA1.js → rotate-cw-CPWe37D9.js} +1 -1
  101. package/dist/elements/chunks/{scroll-area-93A1bbeG.js → scroll-area-ClZkZxIk.js} +1 -1
  102. package/dist/elements/chunks/scroll-button-DPE-t3x_.js +1 -0
  103. package/dist/elements/chunks/{separator-DH2r_ZSZ.js → separator-CeJHQqLW.js} +1 -1
  104. package/dist/elements/chunks/{settings-BFMOd6yB.js → settings-CqaHgJF9.js} +1 -1
  105. package/dist/elements/chunks/{settings-group-B3VZKIcz.js → settings-group-CjqjLCOn.js} +1 -1
  106. package/dist/elements/chunks/share-CyfnNEqp.js +1 -0
  107. package/dist/elements/chunks/{skeleton-N8BuWYmf.js → skeleton-D8akBnKS.js} +1 -1
  108. package/dist/elements/chunks/slots-DaNjX0rc.js +1 -0
  109. package/dist/elements/chunks/{source-w9V0NnBn.js → source-UWCvmVak.js} +1 -1
  110. package/dist/elements/chunks/{star-DHvACxtm.js → star-DoHFAikQ.js} +1 -1
  111. package/dist/elements/chunks/store-Cjq0qwYN.js +1 -0
  112. package/dist/elements/chunks/{tasks-card-lPxoodlA.js → tasks-card-CmZ5xh6v.js} +1 -1
  113. package/dist/elements/chunks/{text-shimmer-BY_rI3s5.js → text-shimmer-DYn09zrz.js} +1 -1
  114. package/dist/elements/chunks/textarea-skiGHURM.js +1 -0
  115. package/dist/elements/chunks/{thumbs-up-DQe0hvkZ.js → thumbs-up-DX2Ijk3H.js} +1 -1
  116. package/dist/elements/chunks/{toast-store-VUZZBhzH.js → toast-store-CFnQpThX.js} +1 -1
  117. package/dist/elements/chunks/{tool-C1YtxwPu.js → tool-DKQ2D1DP.js} +1 -1
  118. package/dist/elements/chunks/{tooltip-BqwnFWhn.js → tooltip-B0G99y8w.js} +1 -1
  119. package/dist/elements/chunks/{triangle-alert-B6or7F5u.js → triangle-alert-F9cyUEyy.js} +1 -1
  120. package/dist/elements/chunks/{use-card-resolution-DJg32jGH.js → use-card-resolution-DoA2f_EU.js} +1 -1
  121. package/dist/elements/chunks/{variant-aurora-QXTZPyST.js → variant-aurora-qt6Zct8M.js} +2 -2
  122. package/dist/elements/chunks/variant-custom-tJp22_BQ.js +1 -0
  123. package/dist/{variant-wave-BwzrHD1s.js → elements/chunks/variant-wave-CaQ5ti39.js} +2 -2
  124. package/dist/elements/chunks/x-B6UnOiLw.js +1 -0
  125. package/dist/elements/coachmark.js +1 -1
  126. package/dist/elements/code-block.js +1 -1
  127. package/dist/elements/command.js +1 -1
  128. package/dist/elements/compare.js +1 -1
  129. package/dist/elements/composer.js +1 -1
  130. package/dist/elements/confirm-card.js +1 -1
  131. package/dist/elements/context-meter.js +1 -1
  132. package/dist/elements/conversation-item.d.ts +1 -0
  133. package/dist/elements/conversation-item.js +1 -0
  134. package/dist/elements/conversation-list.js +1 -1
  135. package/dist/elements/default-input.d.ts +3 -3
  136. package/dist/elements/define.d.ts +27 -0
  137. package/dist/elements/diagnostic-events.d.ts +74 -0
  138. package/dist/elements/dialog.js +1 -1
  139. package/dist/elements/dock.d.ts +1 -0
  140. package/dist/elements/dock.js +149 -0
  141. package/dist/elements/dropdown.d.ts +1 -0
  142. package/dist/elements/dropdown.js +1 -0
  143. package/dist/elements/editable-label.js +1 -1
  144. package/dist/elements/element-diagnostics.d.ts +81 -0
  145. package/dist/elements/embed.js +1 -1
  146. package/dist/elements/empty.js +1 -1
  147. package/dist/elements/feedback-bar.js +1 -1
  148. package/dist/elements/file-tree.js +1 -1
  149. package/dist/elements/file-upload.js +1 -1
  150. package/dist/elements/form.js +1 -1
  151. package/dist/elements/hover-card.js +1 -1
  152. package/dist/elements/icon.js +1 -1
  153. package/dist/elements/image.js +1 -1
  154. package/dist/elements/input.js +1 -1
  155. package/dist/elements/kbd.js +1 -1
  156. package/dist/elements/link-preview.js +1 -1
  157. package/dist/elements/loader.js +1 -1
  158. package/dist/elements/markdown.js +1 -1
  159. package/dist/elements/menu.js +1 -1
  160. package/dist/elements/message-skills.js +1 -1
  161. package/dist/elements/message.js +1 -1
  162. package/dist/elements/model-switcher.js +1 -1
  163. package/dist/elements/nav.js +1 -1
  164. package/dist/elements/notice.js +1 -1
  165. package/dist/elements/pane-group.js +1 -1
  166. package/dist/elements/pane.js +1 -1
  167. package/dist/elements/popover.js +1 -1
  168. package/dist/elements/progress-bar.js +1 -1
  169. package/dist/elements/prompt-dock.js +1 -1
  170. package/dist/elements/prompt-input.js +1 -1
  171. package/dist/elements/prompt-suggestions.js +1 -1
  172. package/dist/elements/reasoning.js +1 -1
  173. package/dist/elements/register.d.ts +2 -0
  174. package/dist/elements/remote.js +1 -1
  175. package/dist/elements/resizable.js +1 -1
  176. package/dist/elements/response-stream.js +3 -3
  177. package/dist/elements/screen.js +1 -1
  178. package/dist/elements/scroll-area.js +1 -1
  179. package/dist/elements/scroll-button.js +1 -1
  180. package/dist/elements/search.js +1 -1
  181. package/dist/elements/segmented.js +1 -1
  182. package/dist/elements/separator.js +1 -1
  183. package/dist/elements/setting-item.js +1 -1
  184. package/dist/elements/settings-group.js +1 -1
  185. package/dist/elements/skeleton.js +1 -1
  186. package/dist/elements/slots.d.ts +35 -3
  187. package/dist/elements/source.js +1 -1
  188. package/dist/elements/status.js +1 -1
  189. package/dist/elements/switch.js +1 -1
  190. package/dist/elements/tabs.js +1 -1
  191. package/dist/elements/tasks.js +1 -1
  192. package/dist/elements/text-shimmer.js +1 -1
  193. package/dist/elements/thinking-bar.js +1 -1
  194. package/dist/elements/thread.js +1 -1
  195. package/dist/elements/toast.js +1 -1
  196. package/dist/elements/tool.js +1 -1
  197. package/dist/elements/tooltip.js +1 -1
  198. package/dist/elements/voice-input.js +1 -1
  199. package/dist/elements/voice-output.js +1 -1
  200. package/dist/elements.d.ts +232 -148
  201. package/dist/index.d.ts +2 -0
  202. package/dist/index.js +5456 -4931
  203. package/dist/index.server.js +4142 -3669
  204. package/dist/kai-provider.es.js +40 -40
  205. package/dist/kai.es.js +1 -1
  206. package/dist/llms/llms-full.txt +184 -69
  207. package/dist/llms/llms.txt +3 -3
  208. package/dist/mcp.es.js +2669 -546
  209. package/dist/primitives/use-resize-observer.d.ts +23 -0
  210. package/dist/primitives/use-speech-recognition.d.ts +8 -0
  211. package/dist/primitives/use-text-stream.d.ts +5 -0
  212. package/dist/react/index.d.ts +115 -266
  213. package/dist/react.js +247 -221
  214. package/dist/register-impl-DpE7icIb.js +293 -0
  215. package/dist/schemas/index.d.ts +1 -1
  216. package/dist/schemas/tool-defs.d.ts +35 -0
  217. package/dist/schemas.js +403 -310
  218. package/dist/{solid-BMuDmFo_.js → solid-BtdqNZI7.js} +8435 -7394
  219. package/dist/{solid-CGYNdLRR.js → solid-CnavwBye.js} +5987 -5035
  220. package/dist/solid.d.ts +5 -0
  221. package/dist/solid.js +210 -203
  222. package/dist/solid.server.js +210 -203
  223. package/dist/state/index.d.ts +4 -0
  224. package/dist/state/persistence.d.ts +51 -0
  225. package/dist/state/stream.d.ts +50 -0
  226. package/dist/state/threads.d.ts +71 -0
  227. package/dist/state.js +269 -169
  228. package/dist/types.d.ts +4 -2
  229. package/dist/ui/dock.d.ts +109 -0
  230. package/dist/ui/hover-card.d.ts +44 -0
  231. package/dist/{variant-aurora-DwSOfraQ.js → variant-aurora-B8II3-I1.js} +27 -27
  232. package/dist/{variant-aurora-wKvAQ5wp.js → variant-aurora-CMNWOSJR.js} +28 -28
  233. package/dist/{variant-aurora-XEkPT0_z.js → variant-aurora-CcuDMWwQ.js} +2 -2
  234. package/dist/{variant-custom-eFIg2XWQ.js → variant-custom-BdvYC7af.js} +40 -40
  235. package/dist/{variant-custom-BhS9wIpU.js → variant-custom-C714mqKl.js} +65 -65
  236. package/dist/variant-custom-L16JLLZy.js +1 -0
  237. package/dist/{elements/chunks/variant-wave-DDCwhLas.js → variant-wave-2S4La2Ka.js} +2 -2
  238. package/dist/{variant-wave-CbVsmgwT.js → variant-wave-D7ruiA1W.js} +25 -25
  239. package/dist/{variant-wave-CUWGx9Bn.js → variant-wave-ecwykeyL.js} +21 -21
  240. package/dist/wire/chunk.d.ts +47 -0
  241. package/dist/wire/diagnostics.d.ts +575 -0
  242. package/dist/wire/encode-probe.d.ts +70 -0
  243. package/dist/wire/encode.d.ts +24 -0
  244. package/dist/wire/index.d.ts +2 -0
  245. package/dist/wire/sse.d.ts +10 -2
  246. package/dist/wire.js +917 -480
  247. package/frameworks/react/index.tsx +132 -93
  248. package/llms-full.txt +184 -69
  249. package/llms.txt +3 -3
  250. package/package.json +26 -6
  251. package/src/agent-tooling/archetypes.ts +41 -9
  252. package/src/agent-tooling/catalog/README.md +676 -0
  253. package/src/agent-tooling/catalog/catalog-types.ts +231 -0
  254. package/src/agent-tooling/catalog/fabrications.ts +96 -0
  255. package/src/agent-tooling/catalog/invariants.ts +284 -0
  256. package/src/agent-tooling/catalog/labs-titles.ts +114 -0
  257. package/src/agent-tooling/catalog/scenarios.ts +87 -0
  258. package/src/agent-tooling/catalog/surfaces.ts +275 -0
  259. package/src/agent-tooling/integrations/anthropic.ts +7 -1
  260. package/src/agent-tooling/integrations/cloudflare.ts +43 -2
  261. package/src/agent-tooling/integrations/langgraph.ts +7 -1
  262. package/src/agent-tooling/integrations/mastra.ts +7 -1
  263. package/src/agent-tooling/integrations/mock.ts +92 -17
  264. package/src/agent-tooling/integrations/ollama.ts +7 -1
  265. package/src/agent-tooling/integrations/openai.ts +7 -1
  266. package/src/agent-tooling/integrations/openrouter.ts +7 -1
  267. package/src/agent-tooling/integrations/pi.ts +56 -1
  268. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +7 -1
  269. package/src/agent-tooling/mcp/css-raw.d.ts +12 -0
  270. package/src/agent-tooling/mcp/manifest.ts +100 -1
  271. package/src/agent-tooling/mcp/server.ts +61 -5
  272. package/src/agent-tooling/mcp/tools/reference.ts +440 -4
  273. package/src/agent-tooling/mcp/tools/scaffold.ts +1834 -307
  274. package/src/agent-tooling/mcp/tools/theme.ts +146 -30
  275. package/src/agent-tooling/mcp/validate-args.ts +141 -0
  276. package/src/agent-tooling/registry.ts +19 -7
  277. package/src/agent-tooling/route-emit.ts +26 -2
  278. package/src/components/attachment-types.ts +47 -1
  279. package/src/components/attachments.tsx +169 -22
  280. package/src/components/audio-visualizer/fit-scale.ts +112 -0
  281. package/src/components/audio-visualizer/index.tsx +80 -3
  282. package/src/components/audio-visualizer/variant-aurora.tsx +10 -4
  283. package/src/components/audio-visualizer/variant-bar.tsx +38 -6
  284. package/src/components/audio-visualizer/variant-custom.tsx +8 -2
  285. package/src/components/audio-visualizer/variant-grid.tsx +16 -11
  286. package/src/components/audio-visualizer/variant-radial.tsx +15 -10
  287. package/src/components/audio-visualizer/variant-wave.tsx +7 -2
  288. package/src/components/chat-container.tsx +4 -1
  289. package/src/components/chat-thread.tsx +36 -7
  290. package/src/components/code-block.tsx +79 -3
  291. package/src/components/composer.tsx +8 -1
  292. package/src/components/conversation-item.tsx +114 -4
  293. package/src/components/conversation-list.tsx +212 -12
  294. package/src/components/embed.tsx +4 -0
  295. package/src/components/form.tsx +41 -14
  296. package/src/components/message.tsx +75 -5
  297. package/src/components/response-stream.tsx +13 -7
  298. package/src/components/screen.tsx +5 -0
  299. package/src/components/scroll-button.tsx +7 -0
  300. package/src/components/toast.tsx +11 -5
  301. package/src/components/voice-input.tsx +35 -3
  302. package/src/components/voice-output.tsx +46 -6
  303. package/src/components/workspace-shell.tsx +285 -0
  304. package/src/diagnostics/hook.ts +352 -0
  305. package/src/diagnostics/index.ts +82 -0
  306. package/src/diagnostics/report-request.ts +296 -0
  307. package/src/elements/attachments.tsx +6 -7
  308. package/src/elements/audio-visualizer.tsx +48 -4
  309. package/src/elements/chat-workspace.tsx +159 -287
  310. package/src/elements/chat.tsx +12 -9
  311. package/src/elements/code-block.tsx +19 -1
  312. package/src/elements/command.tsx +4 -1
  313. package/src/elements/compiled.css +1 -1
  314. package/src/elements/conversation-item.tsx +80 -0
  315. package/src/elements/conversation-list.tsx +60 -11
  316. package/src/elements/default-input.tsx +5 -5
  317. package/src/elements/define.tsx +168 -8
  318. package/src/elements/diagnostic-events.ts +114 -0
  319. package/src/elements/dialog.tsx +47 -0
  320. package/src/elements/disclosure.ts +34 -12
  321. package/src/elements/dock.tsx +204 -0
  322. package/src/elements/dropdown.tsx +123 -0
  323. package/src/elements/element-diagnostics.ts +392 -0
  324. package/src/elements/element-manifest.json +12 -0
  325. package/src/elements/element-meta.json +672 -439
  326. package/src/elements/element-nonscalar.json +170 -0
  327. package/src/elements/element-types.d.ts +232 -148
  328. package/src/elements/menu.tsx +13 -0
  329. package/src/elements/prompt-dock.tsx +2 -1
  330. package/src/elements/prompt-input.tsx +8 -8
  331. package/src/elements/register-impl.ts +46 -0
  332. package/src/elements/register.ts +28 -0
  333. package/src/elements/resizable.tsx +52 -25
  334. package/src/elements/scroll-button.tsx +5 -0
  335. package/src/elements/slots.ts +107 -12
  336. package/src/elements/styles.css +30 -6
  337. package/src/elements/thinking-bar.tsx +16 -7
  338. package/src/elements/toast.tsx +3 -0
  339. package/src/elements/tool.tsx +26 -5
  340. package/src/elements/voice-input.tsx +11 -2
  341. package/src/elements/voice-output.tsx +16 -3
  342. package/src/index.ts +2 -0
  343. package/src/primitives/use-resize-observer.ts +33 -0
  344. package/src/primitives/use-speech-recognition.ts +12 -1
  345. package/src/primitives/use-stick-to-bottom.ts +34 -2
  346. package/src/primitives/use-text-stream.ts +33 -2
  347. package/src/remote/provider-runtime.ts +6 -3
  348. package/src/schemas/index.ts +1 -0
  349. package/src/schemas/tool-defs.ts +297 -6
  350. package/src/solid.ts +5 -0
  351. package/src/state/index.ts +12 -0
  352. package/src/state/persistence.ts +180 -0
  353. package/src/state/stream.ts +71 -4
  354. package/src/state/threads.ts +142 -0
  355. package/src/types.ts +4 -2
  356. package/src/ui/dialog.tsx +6 -0
  357. package/src/ui/dock.tsx +445 -0
  358. package/src/ui/dropdown.tsx +67 -23
  359. package/src/ui/hover-card.tsx +156 -6
  360. package/src/ui/input.tsx +29 -10
  361. package/src/ui/resizable.tsx +11 -0
  362. package/src/wire/chunk.ts +47 -0
  363. package/src/wire/consume.ts +185 -26
  364. package/src/wire/diagnostics.ts +727 -0
  365. package/src/wire/encode-probe.ts +214 -0
  366. package/src/wire/encode.ts +267 -14
  367. package/src/wire/formats/anthropic.ts +8 -1
  368. package/src/wire/formats/openai.ts +5 -0
  369. package/src/wire/index.ts +22 -0
  370. package/src/wire/read.ts +250 -6
  371. package/src/wire/sse.ts +16 -3
  372. package/dist/elements/chunks/action-icons-DW9muWrY.js +0 -1
  373. package/dist/elements/chunks/arrow-left-iJVnQmxA.js +0 -1
  374. package/dist/elements/chunks/attachments-CL6TaqIb.js +0 -1
  375. package/dist/elements/chunks/audio-visualizer-BuUqQmaO.js +0 -1
  376. package/dist/elements/chunks/chat-thread-DxoKaStk.js +0 -1
  377. package/dist/elements/chunks/check-MdLzbxrm.js +0 -1
  378. package/dist/elements/chunks/chevron-down-MK7IzDqe.js +0 -1
  379. package/dist/elements/chunks/chevron-right-C5jXhmtV.js +0 -1
  380. package/dist/elements/chunks/circle-BG7VkPep.js +0 -1
  381. package/dist/elements/chunks/circle-check-GqvO2UBs.js +0 -1
  382. package/dist/elements/chunks/code-block-ClhQO9RW.js +0 -1
  383. package/dist/elements/chunks/collapsible-DBcBkFPh.js +0 -1
  384. package/dist/elements/chunks/context-hKjNqiyQ.js +0 -1
  385. package/dist/elements/chunks/conversation-list-CsGJjSKG.js +0 -1
  386. package/dist/elements/chunks/default-input-B1Mem5wu.js +0 -1
  387. package/dist/elements/chunks/define-B_tXMYRm.js +0 -1
  388. package/dist/elements/chunks/disclosure-DAoYOKew.js +0 -1
  389. package/dist/elements/chunks/dropdown-CmNna5lT.js +0 -1
  390. package/dist/elements/chunks/embed-CjAD2jSi.js +0 -1
  391. package/dist/elements/chunks/form-Cp8Y00Wn.js +0 -1
  392. package/dist/elements/chunks/hover-card-6KtXpLwT.js +0 -1
  393. package/dist/elements/chunks/icon-97R8SR2p.js +0 -1
  394. package/dist/elements/chunks/input-DrseEjhp.js +0 -1
  395. package/dist/elements/chunks/message-CE4k-qaj.js +0 -1
  396. package/dist/elements/chunks/message-square-C9ovmtOo.js +0 -1
  397. package/dist/elements/chunks/reasoning-BrxQfpfI.js +0 -1
  398. package/dist/elements/chunks/scroll-button-CrTo7wm4.js +0 -1
  399. package/dist/elements/chunks/share-DgNbkJKY.js +0 -1
  400. package/dist/elements/chunks/slots-CIw9RlAe.js +0 -1
  401. package/dist/elements/chunks/store-HIPGhNz9.js +0 -1
  402. package/dist/elements/chunks/textarea-DPfuhswM.js +0 -1
  403. package/dist/elements/chunks/variant-custom-D5ZlmziF.js +0 -1
  404. package/dist/elements/chunks/video-Bb86fVYI.js +0 -1
  405. package/dist/elements/chunks/x-BOhjBeOd.js +0 -1
  406. package/dist/primitives/card-validate-generator.testlib.d.ts +0 -20
  407. package/dist/register-impl-DGmGzrEM.js +0 -145
  408. package/dist/variant-custom-MkTNTycd.js +0 -1
  409. package/src/primitives/card-validate-generator.testlib.ts +0 -38
@@ -0,0 +1,214 @@
1
+ // The encoders' diagnostic ledger: what went in, what came out, what did not.
2
+ //
3
+ // SEPARATE FROM encode.ts on purpose, and the reason is a guard rather than
4
+ // tidiness. `lint:silent-drops` holds every function in `src/wire/` that
5
+ // discriminates a `MessagePart` responsible for all six variants, and it is
6
+ // right to. Nothing in this file discriminates one: every entry point takes the
7
+ // variant as a STRING, read at the call site out of a branch that had already
8
+ // discriminated it. So instrumenting the drops cannot introduce a new switch to
9
+ // go stale when a seventh variant lands, and the parts that reach `dropped()`
10
+ // are typed `unknown` so they cannot be inspected here even by accident.
11
+ //
12
+ // ZERO COST WITH NO SUBSCRIBER. The encoders build a probe only inside a
13
+ // `wireDiagnosticsActive()` branch and reach it through `probe?.`, so with
14
+ // nobody listening the whole ledger is one symbol read per encode and a handful
15
+ // of skipped optional calls.
16
+ //
17
+ // SSR: no window, no globals at module scope. The TextEncoder is built on first
18
+ // use, the same rule `read.ts` follows.
19
+ import {
20
+ emitWireDiagnostic,
21
+ wirePayloadActive,
22
+ withPayload,
23
+ type EncodeAttachmentReport,
24
+ } from './diagnostics';
25
+
26
+ /** Built on first use and cached -- never at module scope, so this module keeps
27
+ * importing cleanly in a runtime that lacks the global. Only ever reached from
28
+ * inside an active-diagnostics branch. */
29
+ let encoder: TextEncoder | undefined;
30
+
31
+ /**
32
+ * UTF-8 byte length of the encoded body, or undefined when it cannot be one.
33
+ *
34
+ * ★ ONLY CALLED WHEN PAYLOAD CAPTURE IS ON, i.e. when the body is being
35
+ * materialized for a subscriber anyway. `base64Bytes` below refuses to decode a
36
+ * 40 MB attachment to count it, on the grounds that the diagnostic would cost
37
+ * more than the encode it is watching -- and stringifying the whole body to
38
+ * produce one number is that same mistake one level up. With payload off the
39
+ * field is OMITTED, which reads as "not reported" under the absence rule.
40
+ *
41
+ * Measured against the built bundle, subscribed with payload OFF, on a thread
42
+ * of ten 1 MB attachments whose bare encode is 4.2 ms: ungated the
43
+ * instrumentation cost +36.6 ms and +0.9 MB of heap; gated it costs +5.8 ms and
44
+ * no measurable heap. (Upper bounds -- the box was not idle. `--expose-gc`, 15
45
+ * runs, `scripts/`-adjacent harness in the PR.)
46
+ *
47
+ * NOT REPLACED BY AN ESTIMATE. A number that looks like a measurement and is
48
+ * not is worse than an absent field, and a size threshold would make the
49
+ * field's presence depend on how big the body happened to be -- which is the
50
+ * one thing a reader would then be unable to conclude anything from.
51
+ *
52
+ * `JSON.stringify` is not total either: a circular `tool.output` or a BigInt
53
+ * anywhere in a host's own data throws, and a diagnostic must never be the thing
54
+ * that breaks an encode. That case goes absent for the same reason.
55
+ */
56
+ function bodyBytes(body: unknown): number | undefined {
57
+ try {
58
+ encoder ??= new TextEncoder();
59
+ return encoder.encode(JSON.stringify(body)).length;
60
+ } catch {
61
+ return undefined;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Bytes a base64 payload stands for, WITHOUT decoding it.
67
+ *
68
+ * Four base64 characters carry three bytes, less the padding. Decoding a 40 MB
69
+ * attachment to count it would make the diagnostic more expensive than the
70
+ * encode it is watching.
71
+ *
72
+ * ★ THIS IS AN O(n) SCAN AND THERE IS NO EXACT WAY AROUND IT -- about 0.43 ms
73
+ * per MB of attachment. THAT IS WHY ITS CALLER IS PAYLOAD-GATED (see `bytesOf`
74
+ * in encode.ts): the thread is re-encoded every turn, so the scan is a
75
+ * recurring per-turn cost rather than a one-off, and unbounded-per-turn is what
76
+ * fails "cheap enough to leave on". With payload off this is never called; with
77
+ * payload on it runs and the number is exact.
78
+ *
79
+ * THE ALTERNATIVE WAS NOT TO GUESS, and three candidates were measured before
80
+ * settling on gating instead:
81
+ *
82
+ * · A charCodeAt counting loop -- 7x SLOWER on the shape that actually occurs
83
+ * (a `data:` URI, no whitespace): 2.77 ms vs 0.61 ms per 1.4 MB. It avoids
84
+ * a copy that a whitespace-free `.replace` never makes, because V8 returns
85
+ * the original string when the pattern does not match. Strictly worse.
86
+ * · Native whitespace `.test()` then O(1) arithmetic on `.length` -- exact,
87
+ * and identical in time (0.60 ms), because the test is the same full scan.
88
+ * No win worth the extra branch.
89
+ * · Probing only a prefix for whitespace, then O(1) arithmetic -- 170x faster
90
+ * (0.0036 ms) and REJECTED anyway: it assumes whitespace is periodic line
91
+ * wrapping, so a payload whose only whitespace sits past the probe window
92
+ * is silently OVER-counted. That is an estimate wearing a measurement's
93
+ * clothes, which is the one thing a size field must never be.
94
+ *
95
+ * So the resolution is exact-but-gated rather than always-on-but-approximate. A
96
+ * cheaper counter is welcome; a less truthful one is not, and `base64-bytes.
97
+ * test.ts` pins the arithmetic against a real decode so any replacement has to
98
+ * prove it is the same function rather than merely a plausible one.
99
+ */
100
+ export function base64Bytes(data: string): number {
101
+ const clean = data.replace(/\s/g, '');
102
+ if (clean === '') return 0;
103
+ const pad = clean.endsWith('==') ? 2 : clean.endsWith('=') ? 1 : 0;
104
+ return Math.max(0, Math.floor((clean.length * 3) / 4) - pad);
105
+ }
106
+
107
+ export interface EncodeProbe {
108
+ /** One part seen in the thread. `variant` is `part.type`, read at a site that
109
+ * has the part in hand -- this function never looks. */
110
+ seen(variant: string): void;
111
+ /** One part that reached the wire. */
112
+ encoded(variant: string): void;
113
+ /** One part that did NOT. `part` is carried only to be published under the
114
+ * payload key, and is `unknown` so it cannot be inspected here. */
115
+ dropped(
116
+ variant: string,
117
+ reason: string,
118
+ messageIndex: number,
119
+ partIndex: number,
120
+ part: unknown,
121
+ ): void;
122
+ /** One `file` part's outcome, plus the filename held back for the payload
123
+ * key. Called exactly once per file part, whatever became of it. */
124
+ attachment(report: EncodeAttachmentReport, filename?: string): void;
125
+ /** Emit `encode.request`. Called once, after the body is built. */
126
+ finish(
127
+ format: 'openai' | 'anthropic',
128
+ threadMessages: number,
129
+ body: ReadonlyArray<{ role: string }>,
130
+ ): void;
131
+ }
132
+
133
+ /**
134
+ * A ledger for one encode call.
135
+ *
136
+ * Built ONLY inside an active-diagnostics branch. It holds counts and, for the
137
+ * payload key, filenames; it never holds message text.
138
+ *
139
+ * NO `streamId`, deliberately. An encode is not a read: it happens before there
140
+ * is a response to correlate to, and minting an id here would invent a
141
+ * relationship the kit cannot vouch for. `traceId`/`label` DO carry, because
142
+ * those are the app's own declaration and the app is the one layer that knows
143
+ * the encoded request and the read that answered it are the same turn.
144
+ */
145
+ export function createEncodeProbe(opts: { traceId?: string; label?: string }): EncodeProbe {
146
+ const correlation = {
147
+ ...(opts.traceId !== undefined ? { traceId: opts.traceId } : {}),
148
+ ...(opts.label !== undefined ? { label: opts.label } : {}),
149
+ };
150
+ const partsIn: Record<string, number> = {};
151
+ const partsEncoded: Record<string, number> = {};
152
+ const attachments: EncodeAttachmentReport[] = [];
153
+ const filenames: Array<{ filename?: string }> = [];
154
+
155
+ const bump = (into: Record<string, number>, key: string) => {
156
+ into[key] = (into[key] ?? 0) + 1;
157
+ };
158
+
159
+ return {
160
+ seen: (variant) => bump(partsIn, variant),
161
+ encoded: (variant) => bump(partsEncoded, variant),
162
+ dropped(variant, reason, messageIndex, partIndex, part) {
163
+ emitWireDiagnostic({
164
+ type: 'encode.dropped',
165
+ t: Date.now(),
166
+ ...correlation,
167
+ variant,
168
+ count: 1,
169
+ messageIndex,
170
+ partIndex,
171
+ reason,
172
+ // The part itself, by reference and unread. This function does not know
173
+ // what a part is and must not: knowing would mean discriminating one.
174
+ ...withPayload(() => ({ part })),
175
+ });
176
+ },
177
+ attachment(report, filename) {
178
+ attachments.push(report);
179
+ // Positionally aligned with `attachments`, so a panel with the payload key
180
+ // turned on can name the file in row N without the name ever sitting
181
+ // beside the metadata.
182
+ filenames.push(filename !== undefined ? { filename } : {});
183
+ },
184
+ finish(format, threadMessages, body) {
185
+ const byRole: Record<string, number> = {};
186
+ for (const message of body) bump(byRole, message.role);
187
+ // Gated: measuring costs a full serialization of every inlined
188
+ // attachment, so it happens only when the body is already being
189
+ // materialized for the payload key. See `bodyBytes`.
190
+ const bytes = wirePayloadActive() ? bodyBytes(body) : undefined;
191
+ emitWireDiagnostic({
192
+ type: 'encode.request',
193
+ t: Date.now(),
194
+ ...correlation,
195
+ format,
196
+ threadMessages,
197
+ wireMessages: body.length,
198
+ // Stated, not omitted: zero is the answer that tells a developer the
199
+ // system prompt lives somewhere the kit cannot see.
200
+ systemMessages: byRole.system ?? 0,
201
+ byRole,
202
+ partsIn,
203
+ partsEncoded,
204
+ attachments,
205
+ ...(bytes !== undefined ? { bytes } : {}),
206
+ // The encoded body, and the filenames held back from the ledger above.
207
+ ...withPayload(() => ({
208
+ body,
209
+ ...(filenames.length > 0 ? { attachments: filenames } : {}),
210
+ })),
211
+ });
212
+ },
213
+ };
214
+ }
@@ -7,6 +7,8 @@ import type { ChatMessage, MessagePart } from '../elements/chat-types';
7
7
  import type { ToolPart } from '../components/tool-types';
8
8
  import { classifyAttachment, textFileContent, type ClassifiedFile } from './files';
9
9
  import { resolveMediaPolicy, type MediaPolicy, type MediaTypeFilter } from './media-types';
10
+ import { wireDiagnosticsActive, wirePayloadActive } from './diagnostics';
11
+ import { base64Bytes, createEncodeProbe, type EncodeProbe } from './encode-probe';
10
12
 
11
13
  export interface OpenAIToolCall {
12
14
  id: string;
@@ -100,6 +102,30 @@ export interface FileEncodeOptions {
100
102
  * enable it; that would just move the failure to a provider 400.
101
103
  */
102
104
  accept?: MediaTypeFilter;
105
+ /**
106
+ * The app's own id for the logical turn this encode belongs to, carried onto
107
+ * every diagnostic event the encode emits. Purely diagnostic: nothing here
108
+ * branches on it and it never reaches a provider.
109
+ *
110
+ * THE SAME FIELD, THE SAME MEANING, as `ConsumeOptions.traceId` -- and that
111
+ * symmetry is the whole payoff. Encoding happens BEFORE a read opens, so
112
+ * there is no stream to attach an encode to and the kit will not invent one.
113
+ * Pass the same id to both halves:
114
+ *
115
+ * const body = toOpenAIMessages(messages, { traceId: 'turn-42' });
116
+ * readOpenAIStream(res, sink, { traceId: 'turn-42' });
117
+ *
118
+ * and the request and the response it produced sit together, with a tool loop
119
+ * or a sub-agent fan-out grouping into one trace. Without it you still see
120
+ * both halves; they are simply unlinked, which is the honest rendering --
121
+ * pinning an encode to "the next stream that opens" would be a guess, and an
122
+ * encode may be followed by no stream at all.
123
+ */
124
+ traceId?: string;
125
+ /** The app's name for this call inside its trace (`'planner'`, `'retry-2'`).
126
+ * Same field and same meaning as `ConsumeOptions.label`. Absent when not
127
+ * supplied. */
128
+ label?: string;
103
129
  }
104
130
 
105
131
  export type AnthropicEncodeOptions = FileEncodeOptions;
@@ -173,12 +199,52 @@ function encodeFilePart<T>(
173
199
  policy: UnencodableFilePolicy,
174
200
  media: MediaPolicy,
175
201
  toBlock: (file: ClassifiedFile) => WireFileResult<T>,
202
+ probe: EncodeProbe | undefined,
203
+ messageIndex: number,
176
204
  ): T | null {
177
205
  const classified = classifyAttachment(part.attachment, media);
178
- if (classified.status === 'kit-side') return null;
206
+ // What the HOST declared, which is the only label available on a path where
207
+ // classification never got as far as settling one.
208
+ const declared = part.attachment.mediaType;
209
+ const filename = part.attachment.filename;
210
+
211
+ if (classified.status === 'kit-side') {
212
+ // Reported in the attachment ledger, and NOT as a drop: a `source-document`
213
+ // is the citation chip beside a RAG answer, and its content is already in
214
+ // the prompt that produced that answer. Nothing was lost.
215
+ probe?.attachment(
216
+ {
217
+ ...(declared !== undefined ? { mediaType: declared } : {}),
218
+ encoded: false,
219
+ disposition: 'skipped',
220
+ reason:
221
+ 'it is a kit-side attachment (a source-document citation chip), not something the user staged to send.',
222
+ },
223
+ filename,
224
+ );
225
+ return null;
226
+ }
179
227
 
180
- const refuse = (reason: string): null => {
181
- if (policy === 'skip') return null;
228
+ const refuse = (reason: string, file?: ClassifiedFile): null => {
229
+ const mediaType = file?.mediaType ?? declared;
230
+ if (policy === 'skip') {
231
+ // THE SKIP THE DEVELOPER ASKED FOR BY NAME, now visible. It is still
232
+ // silent on the wire, exactly as documented; what changed is that the
233
+ // panel can say "this attachment rendered in the thread and was never
234
+ // sent", which is the sentence nobody could get out of the kit before.
235
+ probe?.attachment(
236
+ {
237
+ ...(mediaType !== undefined ? { mediaType } : {}),
238
+ ...bytesOf(file),
239
+ encoded: false,
240
+ disposition: 'skipped',
241
+ reason,
242
+ },
243
+ filename,
244
+ );
245
+ probe?.dropped('file', reason, messageIndex, partIndex, part);
246
+ return null;
247
+ }
182
248
  throw new WireEncodeError(
183
249
  `Cannot encode file part ${partIndex} of message "${messageId}": ${reason} Pass { onUnencodableFile: 'skip' } to drop it instead of failing.`,
184
250
  messageId,
@@ -189,7 +255,49 @@ function encodeFilePart<T>(
189
255
  if (classified.status === 'unencodable') return refuse(classified.reason);
190
256
 
191
257
  const shaped = toBlock(classified.file);
192
- return shaped.ok ? shaped.block : refuse(shaped.reason);
258
+ if (!shaped.ok) return refuse(shaped.reason, classified.file);
259
+
260
+ probe?.attachment(
261
+ {
262
+ mediaType: classified.file.mediaType,
263
+ ...bytesOf(classified.file),
264
+ encoded: true,
265
+ // A text file rides as text CONTENT on both wires -- worth naming
266
+ // separately, because it is how an attachment can be "sent" and still not
267
+ // be a file as far as the model is concerned.
268
+ disposition: classified.file.kind === 'text' ? 'as-text' : 'encoded',
269
+ },
270
+ filename,
271
+ );
272
+ return shaped.block;
273
+ }
274
+
275
+ /**
276
+ * The size of what went on the wire, when it went on the wire at all.
277
+ *
278
+ * ★ GATED BEHIND PAYLOAD CAPTURE, for the same reason `encode.request.bytes`
279
+ * is, and the deciding argument is that THE COST RECURS. The whole thread is
280
+ * re-encoded on every turn, so a 10 MB attachment sitting in history is not a
281
+ * one-time 4.3 ms -- it is 4.3 ms per turn for as long as a subscriber is
282
+ * attached. Unbounded-per-turn is what fails "cheap enough to leave on", not
283
+ * the absolute number.
284
+ *
285
+ * The diagnosis survives the omission: "your image/png attachment was skipped,
286
+ * so the model never saw it" is the finding, and the size is a refinement of
287
+ * it. `mediaType`, `encoded`, `disposition` and `reason` cost nothing and are
288
+ * reported unconditionally. Arming payload gives exact sizes, which is
289
+ * precisely the moment a developer has already accepted paying for content.
290
+ *
291
+ * ABSENT rather than estimated, always. For a remote attachment the provider
292
+ * dereferences that URL itself and the bytes never enter this process; with
293
+ * payload off the scan is simply not run. Both read as "not reported" under the
294
+ * absence rule, and neither is ever backfilled with a guess -- see the rejected
295
+ * candidates at `base64Bytes`.
296
+ */
297
+ function bytesOf(file?: ClassifiedFile): { bytes?: number } {
298
+ if (!wirePayloadActive()) return {};
299
+ if (file?.source.type !== 'base64') return {};
300
+ return { bytes: base64Bytes(file.source.data) };
193
301
  }
194
302
 
195
303
  /** `image_url` takes an https URL and a `data:` URI in the same field, so an
@@ -434,7 +542,16 @@ export function toOpenAIMessages(
434
542
  const media = resolveMediaPolicy({ accept: options.accept });
435
543
  const out: OpenAIWireMessage[] = [];
436
544
 
545
+ // Gated BEFORE anything is allocated, so with nobody listening this whole
546
+ // ledger costs one symbol read for the entire encode.
547
+ const probe = wireDiagnosticsActive() ? createEncodeProbe(options) : undefined;
548
+ if (probe) {
549
+ for (const message of messages) for (const part of message.parts) probe.seen(part.type);
550
+ }
551
+
552
+ let messageIndex = -1;
437
553
  for (const message of messages) {
554
+ messageIndex++;
438
555
  if (message.role === 'user') {
439
556
  // Built in PART ORDER, with runs of adjacent text merged into one entry.
440
557
  // A file authored after the text stays after it: `parts` is an ordered
@@ -455,19 +572,47 @@ export function toOpenAIMessages(
455
572
  message.parts.forEach((part, partIndex) => {
456
573
  if (part.type === 'text') {
457
574
  buffered += part.text;
575
+ if (part.text !== '') probe?.encoded('text');
576
+ return;
577
+ }
578
+ if (part.type !== 'file') {
579
+ // The drop the waiver above declares, now also reported. `part.type`
580
+ // is read off a part this branch has ALREADY discriminated -- no new
581
+ // switch, which is what keeps `lint:silent-drops` looking at exactly
582
+ // the sites it looked at before.
583
+ probe?.dropped(
584
+ part.type,
585
+ 'a USER turn carries authored content only: reasoning and tool parts are assistant-side, and cards and sources are kit-side UI with no OpenAI wire form.',
586
+ messageIndex,
587
+ partIndex,
588
+ part,
589
+ );
458
590
  return;
459
591
  }
460
- if (part.type !== 'file') return;
461
- const block = encodeFilePart(part, message.id, partIndex, filePolicy, media, openAIFileBlock);
592
+ const block = encodeFilePart(
593
+ part,
594
+ message.id,
595
+ partIndex,
596
+ filePolicy,
597
+ media,
598
+ openAIFileBlock,
599
+ probe,
600
+ messageIndex,
601
+ );
462
602
  if (!block) return;
463
603
  flushText();
464
604
  content.push(block);
605
+ probe?.encoded('file');
465
606
  carriesFile = true;
466
607
  });
467
608
  flushText();
468
609
 
469
610
  // No file survived, so this is an ordinary turn and stays a plain string.
470
611
  if (!carriesFile) {
612
+ // `textOf`'s own waiver names the same drops the callback above just
613
+ // reported, over the same parts array in the same branch. Reporting them
614
+ // again HERE would double-count every card and source in a user turn, so
615
+ // the callback is the single site and this one deliberately stays quiet.
471
616
  const text = textOf(message.parts);
472
617
  if (text !== '') out.push({ role: 'user', content: text });
473
618
  continue;
@@ -502,28 +647,82 @@ export function toOpenAIMessages(
502
647
  reasoning = [];
503
648
  };
504
649
 
650
+ let partIndex = -1;
505
651
  for (const part of message.parts) {
652
+ partIndex++;
506
653
  if (part.type === 'text') {
507
654
  // Text that arrives AFTER a call is the model's answer to it, so the
508
655
  // call and its result have to be on the wire before this text is.
509
656
  if (pending.length > 0) flush();
510
657
  text += part.text;
658
+ if (part.text !== '') probe?.encoded('text');
511
659
  continue;
512
660
  }
513
661
  if (part.type === 'reasoning') {
514
- if (!includeReasoning) continue;
662
+ if (!includeReasoning) {
663
+ probe?.dropped(
664
+ 'reasoning',
665
+ "reasoning is omitted by default, because omitting is measured-accepted everywhere and costs about 25% fewer prompt tokens per round. Pass { reasoning: 'include' } to send the model its own prior thinking back.",
666
+ messageIndex,
667
+ partIndex,
668
+ part,
669
+ );
670
+ continue;
671
+ }
515
672
  // A block after a call belongs to the round that READ the result, for the
516
673
  // same reason text does. Same rule as `toAnthropicMessages`.
517
674
  if (pending.length > 0) flush();
518
675
  const detail = reasoningDetailOf(part);
519
- if (detail) reasoning.push(detail);
676
+ if (detail) {
677
+ reasoning.push(detail);
678
+ probe?.encoded('reasoning');
679
+ continue;
680
+ }
681
+ // ★ THE REASONING ROUND TRIP, reported rather than changed. `detailOf`
682
+ // only produces an entry for a part whose `raw` came from
683
+ // `openai.reasoning_details`, so a part read off `reasoning_content`
684
+ // (DeepSeek-direct) has no round-trippable carrier and is not echoed --
685
+ // and the model loses its own prior thinking on the next turn. The
686
+ // ruling stands and is deliberate; what changes here is that it is
687
+ // visible. See `reasoningDetailOf` for which blocks make it and why.
688
+ probe?.dropped(
689
+ 'reasoning',
690
+ 'it has no round-trippable `reasoning_details` carrier -- an opaque or signed block can be echoed back, but a part read from `reasoning_content` has neither, and re-sending an unverifiable block is the documented 400. The model does not see its own prior thinking on the next turn.',
691
+ messageIndex,
692
+ partIndex,
693
+ part,
694
+ );
520
695
  continue;
521
696
  }
522
- if (part.type === 'tool' && isSettled(part.tool)) pending.push(part.tool);
697
+ if (part.type === 'tool') {
698
+ if (isSettled(part.tool)) {
699
+ pending.push(part.tool);
700
+ probe?.encoded('tool');
701
+ } else {
702
+ probe?.dropped(
703
+ 'tool',
704
+ 'the call has no result yet (or no provider tool call id). Both APIs require every echoed tool call to have exactly one matching result, so a half-formed one is left off rather than sent.',
705
+ messageIndex,
706
+ partIndex,
707
+ part,
708
+ );
709
+ }
710
+ continue;
711
+ }
712
+ // card, source, and a `file` part on an ASSISTANT turn. Same parts the
713
+ // function's waiver names, read off a part already discriminated above.
714
+ probe?.dropped(
715
+ part.type,
716
+ 'cards and sources are kit-side UI with no OpenAI wire form, and a `file` part on an ASSISTANT turn has none either -- the API takes image and document content on user turns only.',
717
+ messageIndex,
718
+ partIndex,
719
+ part,
720
+ );
523
721
  }
524
722
  flush();
525
723
  }
526
724
 
725
+ probe?.finish('openai', messages.length, out);
527
726
  return out;
528
727
  }
529
728
 
@@ -584,6 +783,12 @@ export function toAnthropicMessages(
584
783
  const media = resolveMediaPolicy({ accept: options.accept });
585
784
  const out: AnthropicWireMessage[] = [];
586
785
 
786
+ // Gated before anything is allocated, exactly as in `toOpenAIMessages`.
787
+ const probe = wireDiagnosticsActive() ? createEncodeProbe(options) : undefined;
788
+ if (probe) {
789
+ for (const message of messages) for (const part of message.parts) probe.seen(part.type);
790
+ }
791
+
587
792
  /** Append to the trailing user message when there is one, so the wire never
588
793
  * carries two user turns in a row. */
589
794
  const pushUser = (content: AnthropicContentBlock[]): void => {
@@ -592,19 +797,47 @@ export function toAnthropicMessages(
592
797
  else out.push({ role: 'user', content });
593
798
  };
594
799
 
800
+ let messageIndex = -1;
595
801
  for (const message of messages) {
802
+ messageIndex++;
596
803
  if (message.role === 'user') {
597
804
  // Part order, for the same reason as `toOpenAIMessages`.
598
805
  const userBlocks: AnthropicContentBlock[] = [];
599
806
  // lint-silent-drops: drops reasoning,tool,card,source -- a USER turn carries authored content only; reasoning and tool parts are assistant-side, cards and sources are kit-side UI with no Anthropic wire form.
600
807
  message.parts.forEach((part, partIndex) => {
601
808
  if (part.type === 'text') {
602
- if (part.text !== '') userBlocks.push({ type: 'text', text: part.text });
809
+ if (part.text !== '') {
810
+ userBlocks.push({ type: 'text', text: part.text });
811
+ probe?.encoded('text');
812
+ }
813
+ return;
814
+ }
815
+ if (part.type !== 'file') {
816
+ // The drop this callback's waiver declares, reported. `part.type` is
817
+ // read off a part the branch already discriminated.
818
+ probe?.dropped(
819
+ part.type,
820
+ 'a USER turn carries authored content only: reasoning and tool parts are assistant-side, and cards and sources are kit-side UI with no Anthropic wire form.',
821
+ messageIndex,
822
+ partIndex,
823
+ part,
824
+ );
603
825
  return;
604
826
  }
605
- if (part.type !== 'file') return;
606
- const block = encodeFilePart(part, message.id, partIndex, filePolicy, media, anthropicFileBlock);
607
- if (block) userBlocks.push(block);
827
+ const block = encodeFilePart(
828
+ part,
829
+ message.id,
830
+ partIndex,
831
+ filePolicy,
832
+ media,
833
+ anthropicFileBlock,
834
+ probe,
835
+ messageIndex,
836
+ );
837
+ if (block) {
838
+ userBlocks.push(block);
839
+ probe?.encoded('file');
840
+ }
608
841
  });
609
842
  if (userBlocks.length > 0) pushUser(userBlocks);
610
843
  continue;
@@ -645,6 +878,7 @@ export function toAnthropicMessages(
645
878
  // result, so the result has to be on the wire before it is.
646
879
  if (results.length > 0) flush();
647
880
  blocks.push(part.raw.payload as AnthropicContentBlock);
881
+ probe?.encoded('reasoning');
648
882
  break;
649
883
  }
650
884
  case 'text': {
@@ -652,11 +886,22 @@ export function toAnthropicMessages(
652
886
  // Text after a call is the model's answer to it. Same reason.
653
887
  if (results.length > 0) flush();
654
888
  blocks.push({ type: 'text', text: part.text });
889
+ probe?.encoded('text');
655
890
  break;
656
891
  }
657
892
  case 'tool': {
658
893
  const tool = part.tool;
659
- if (!isSettled(tool)) break;
894
+ if (!isSettled(tool)) {
895
+ probe?.dropped(
896
+ 'tool',
897
+ 'the call has no result yet (or no provider tool call id). Both APIs require every echoed tool call to have exactly one matching result, so a half-formed one is left off rather than sent.',
898
+ messageIndex,
899
+ partIndex,
900
+ part,
901
+ );
902
+ break;
903
+ }
904
+ probe?.encoded('tool');
660
905
  blocks.push({
661
906
  type: 'tool_use',
662
907
  id: tool.toolCallId,
@@ -683,6 +928,13 @@ export function toAnthropicMessages(
683
928
  // card and source are kit-side. A `file` part reaches this arm only on
684
929
  // an ASSISTANT turn, where the API accepts no image or document
685
930
  // content at all -- user attachments are handled in the user branch.
931
+ probe?.dropped(
932
+ part.type,
933
+ 'cards and sources are kit-side UI, and a `file` part on an ASSISTANT turn has no Anthropic representation -- the API takes image and document content on user turns only.',
934
+ messageIndex,
935
+ partIndex,
936
+ part,
937
+ );
686
938
  break;
687
939
  }
688
940
  });
@@ -690,5 +942,6 @@ export function toAnthropicMessages(
690
942
  flush();
691
943
  }
692
944
 
945
+ probe?.finish('anthropic', messages.length, out);
693
946
  return out;
694
947
  }
@@ -244,8 +244,15 @@ export const anthropicMessagesFormat: WireFormat = {
244
244
  case 'message_start': {
245
245
  const message = frame.message;
246
246
  if (!isRecord(message)) return [];
247
+ // `message.model` sits right beside the `message.usage` this handler
248
+ // already destructures. Anthropic states it once, here, and nowhere
249
+ // else in the stream.
250
+ const out: ModelStreamChunk = {};
247
251
  const usage = usageOf(message.usage);
248
- return usage ? [{ usage }] : [];
252
+ if (usage) out.usage = usage;
253
+ const model = str(message.model);
254
+ if (model) out.model = model;
255
+ return Object.keys(out).length > 0 ? [out] : [];
249
256
  }
250
257
  case 'content_block_start':
251
258
  return blockStart(frame, blocks, toolIndexById);
@@ -182,6 +182,11 @@ function pushOpenAI(frame: unknown): ModelStreamChunk[] {
182
182
  }
183
183
  }
184
184
 
185
+ // Stated per frame and reported per frame, no dedupe: a stream that changes
186
+ // its mind mid-turn is a finding, and collapsing it would hide one.
187
+ const model = str(frame.model);
188
+ if (model) out.model = model;
189
+
185
190
  const usage = usageOf(frame.usage);
186
191
  if (usage) out.usage = usage;
187
192