@adea-ai/ui 0.140.0 → 0.142.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 (455) hide show
  1. package/dist/components/composites/appearance-editor/appearance-editor.js +2 -2
  2. package/dist/components/composites/appearance-editor/appearance-editor.js.map +1 -1
  3. package/dist/components/composites/catalog-browser/catalog-browser-group.js +1 -1
  4. package/dist/components/composites/chain-of-thought/chain-of-thought.d.ts +56 -0
  5. package/dist/components/composites/chain-of-thought/chain-of-thought.d.ts.map +1 -0
  6. package/dist/components/composites/chain-of-thought/chain-of-thought.js +117 -0
  7. package/dist/components/composites/chain-of-thought/chain-of-thought.js.map +1 -0
  8. package/dist/components/composites/chain-of-thought/index.d.ts +2 -0
  9. package/dist/components/composites/chain-of-thought/index.d.ts.map +1 -0
  10. package/dist/components/composites/chain-of-thought/index.js +2 -0
  11. package/dist/components/composites/chat/chat.d.ts +67 -0
  12. package/dist/components/composites/chat/chat.d.ts.map +1 -0
  13. package/dist/components/composites/chat/chat.js +179 -0
  14. package/dist/components/composites/chat/chat.js.map +1 -0
  15. package/dist/components/composites/chat/index.d.ts +2 -0
  16. package/dist/components/composites/chat/index.d.ts.map +1 -0
  17. package/dist/components/composites/chat/index.js +2 -0
  18. package/dist/components/composites/checkpoint/checkpoint-model.d.ts +34 -0
  19. package/dist/components/composites/checkpoint/checkpoint-model.d.ts.map +1 -0
  20. package/dist/components/composites/checkpoint/checkpoint-model.js +48 -0
  21. package/dist/components/composites/checkpoint/checkpoint-model.js.map +1 -0
  22. package/dist/components/composites/checkpoint/checkpoint.d.ts +48 -0
  23. package/dist/components/composites/checkpoint/checkpoint.d.ts.map +1 -0
  24. package/dist/components/composites/checkpoint/checkpoint.js +160 -0
  25. package/dist/components/composites/checkpoint/checkpoint.js.map +1 -0
  26. package/dist/components/composites/checkpoint/index.d.ts +3 -0
  27. package/dist/components/composites/checkpoint/index.d.ts.map +1 -0
  28. package/dist/components/composites/checkpoint/index.js +3 -0
  29. package/dist/components/composites/confirmation/confirmation-model.d.ts +45 -0
  30. package/dist/components/composites/confirmation/confirmation-model.d.ts.map +1 -0
  31. package/dist/components/composites/confirmation/confirmation-model.js +51 -0
  32. package/dist/components/composites/confirmation/confirmation-model.js.map +1 -0
  33. package/dist/components/composites/confirmation/confirmation.d.ts +72 -0
  34. package/dist/components/composites/confirmation/confirmation.d.ts.map +1 -0
  35. package/dist/components/composites/confirmation/confirmation.js +300 -0
  36. package/dist/components/composites/confirmation/confirmation.js.map +1 -0
  37. package/dist/components/composites/confirmation/index.d.ts +3 -0
  38. package/dist/components/composites/confirmation/index.d.ts.map +1 -0
  39. package/dist/components/composites/confirmation/index.js +3 -0
  40. package/dist/components/composites/context/context-model.d.ts +69 -0
  41. package/dist/components/composites/context/context-model.d.ts.map +1 -0
  42. package/dist/components/composites/context/context-model.js +122 -0
  43. package/dist/components/composites/context/context-model.js.map +1 -0
  44. package/dist/components/composites/context/context.d.ts +38 -0
  45. package/dist/components/composites/context/context.d.ts.map +1 -0
  46. package/dist/components/composites/context/context.js +234 -0
  47. package/dist/components/composites/context/context.js.map +1 -0
  48. package/dist/components/composites/context/index.d.ts +3 -0
  49. package/dist/components/composites/context/index.d.ts.map +1 -0
  50. package/dist/components/composites/context/index.js +3 -0
  51. package/dist/components/composites/file-tree/build-file-tree.d.ts +138 -0
  52. package/dist/components/composites/file-tree/build-file-tree.d.ts.map +1 -0
  53. package/dist/components/composites/file-tree/build-file-tree.js +325 -0
  54. package/dist/components/composites/file-tree/build-file-tree.js.map +1 -0
  55. package/dist/components/composites/file-tree/file-tree.d.ts +74 -0
  56. package/dist/components/composites/file-tree/file-tree.d.ts.map +1 -0
  57. package/dist/components/composites/file-tree/file-tree.js +389 -0
  58. package/dist/components/composites/file-tree/file-tree.js.map +1 -0
  59. package/dist/components/composites/file-tree/index.d.ts +3 -0
  60. package/dist/components/composites/file-tree/index.d.ts.map +1 -0
  61. package/dist/components/composites/file-tree/index.js +3 -0
  62. package/dist/components/composites/inline-citation/citation.d.ts +37 -0
  63. package/dist/components/composites/inline-citation/citation.d.ts.map +1 -0
  64. package/dist/components/composites/inline-citation/citation.js +52 -0
  65. package/dist/components/composites/inline-citation/citation.js.map +1 -0
  66. package/dist/components/composites/inline-citation/index.d.ts +3 -0
  67. package/dist/components/composites/inline-citation/index.d.ts.map +1 -0
  68. package/dist/components/composites/inline-citation/index.js +3 -0
  69. package/dist/components/composites/inline-citation/inline-citation.d.ts +64 -0
  70. package/dist/components/composites/inline-citation/inline-citation.d.ts.map +1 -0
  71. package/dist/components/composites/inline-citation/inline-citation.js +272 -0
  72. package/dist/components/composites/inline-citation/inline-citation.js.map +1 -0
  73. package/dist/components/composites/message-scroller/index.d.ts +3 -0
  74. package/dist/components/composites/message-scroller/index.d.ts.map +1 -0
  75. package/dist/components/composites/message-scroller/index.js +3 -0
  76. package/dist/components/composites/message-scroller/message-scroller.d.ts +55 -0
  77. package/dist/components/composites/message-scroller/message-scroller.d.ts.map +1 -0
  78. package/dist/components/composites/message-scroller/message-scroller.js +201 -0
  79. package/dist/components/composites/message-scroller/message-scroller.js.map +1 -0
  80. package/dist/components/composites/message-scroller/scroll-anchor.d.ts +66 -0
  81. package/dist/components/composites/message-scroller/scroll-anchor.d.ts.map +1 -0
  82. package/dist/components/composites/message-scroller/scroll-anchor.js +122 -0
  83. package/dist/components/composites/message-scroller/scroll-anchor.js.map +1 -0
  84. package/dist/components/composites/model-selector/index.d.ts +3 -0
  85. package/dist/components/composites/model-selector/index.d.ts.map +1 -0
  86. package/dist/components/composites/model-selector/index.js +3 -0
  87. package/dist/components/composites/model-selector/model-selector.d.ts +55 -0
  88. package/dist/components/composites/model-selector/model-selector.d.ts.map +1 -0
  89. package/dist/components/composites/model-selector/model-selector.js +490 -0
  90. package/dist/components/composites/model-selector/model-selector.js.map +1 -0
  91. package/dist/components/composites/model-selector/models.d.ts +51 -0
  92. package/dist/components/composites/model-selector/models.d.ts.map +1 -0
  93. package/dist/components/composites/model-selector/models.js +71 -0
  94. package/dist/components/composites/model-selector/models.js.map +1 -0
  95. package/dist/components/composites/plan/index.d.ts +2 -0
  96. package/dist/components/composites/plan/index.d.ts.map +1 -0
  97. package/dist/components/composites/plan/index.js +2 -0
  98. package/dist/components/composites/plan/plan.d.ts +61 -0
  99. package/dist/components/composites/plan/plan.d.ts.map +1 -0
  100. package/dist/components/composites/plan/plan.js +130 -0
  101. package/dist/components/composites/plan/plan.js.map +1 -0
  102. package/dist/components/composites/queue/index.d.ts +2 -0
  103. package/dist/components/composites/queue/index.d.ts.map +1 -0
  104. package/dist/components/composites/queue/index.js +2 -0
  105. package/dist/components/composites/queue/queue.d.ts +77 -0
  106. package/dist/components/composites/queue/queue.d.ts.map +1 -0
  107. package/dist/components/composites/queue/queue.js +271 -0
  108. package/dist/components/composites/queue/queue.js.map +1 -0
  109. package/dist/components/composites/reasoning/index.d.ts +2 -0
  110. package/dist/components/composites/reasoning/index.d.ts.map +1 -0
  111. package/dist/components/composites/reasoning/index.js +2 -0
  112. package/dist/components/composites/reasoning/reasoning.d.ts +62 -0
  113. package/dist/components/composites/reasoning/reasoning.d.ts.map +1 -0
  114. package/dist/components/composites/reasoning/reasoning.js +156 -0
  115. package/dist/components/composites/reasoning/reasoning.js.map +1 -0
  116. package/dist/components/composites/sources/index.d.ts +3 -0
  117. package/dist/components/composites/sources/index.d.ts.map +1 -0
  118. package/dist/components/composites/sources/index.js +3 -0
  119. package/dist/components/composites/sources/source.d.ts +46 -0
  120. package/dist/components/composites/sources/source.d.ts.map +1 -0
  121. package/dist/components/composites/sources/source.js +74 -0
  122. package/dist/components/composites/sources/source.js.map +1 -0
  123. package/dist/components/composites/sources/sources.d.ts +45 -0
  124. package/dist/components/composites/sources/sources.d.ts.map +1 -0
  125. package/dist/components/composites/sources/sources.js +175 -0
  126. package/dist/components/composites/sources/sources.js.map +1 -0
  127. package/dist/components/composites/stack-trace/index.d.ts +3 -0
  128. package/dist/components/composites/stack-trace/index.d.ts.map +1 -0
  129. package/dist/components/composites/stack-trace/index.js +3 -0
  130. package/dist/components/composites/stack-trace/parse-stack-trace.d.ts +118 -0
  131. package/dist/components/composites/stack-trace/parse-stack-trace.d.ts.map +1 -0
  132. package/dist/components/composites/stack-trace/parse-stack-trace.js +376 -0
  133. package/dist/components/composites/stack-trace/parse-stack-trace.js.map +1 -0
  134. package/dist/components/composites/stack-trace/stack-trace.d.ts +63 -0
  135. package/dist/components/composites/stack-trace/stack-trace.d.ts.map +1 -0
  136. package/dist/components/composites/stack-trace/stack-trace.js +491 -0
  137. package/dist/components/composites/stack-trace/stack-trace.js.map +1 -0
  138. package/dist/components/composites/subagent/index.d.ts +2 -0
  139. package/dist/components/composites/subagent/index.d.ts.map +1 -0
  140. package/dist/components/composites/subagent/index.js +2 -0
  141. package/dist/components/composites/subagent/subagent.d.ts +55 -0
  142. package/dist/components/composites/subagent/subagent.d.ts.map +1 -0
  143. package/dist/components/composites/subagent/subagent.js +138 -0
  144. package/dist/components/composites/subagent/subagent.js.map +1 -0
  145. package/dist/components/composites/suggestion/index.d.ts +2 -0
  146. package/dist/components/composites/suggestion/index.d.ts.map +1 -0
  147. package/dist/components/composites/suggestion/index.js +2 -0
  148. package/dist/components/composites/suggestion/suggestion.d.ts +37 -0
  149. package/dist/components/composites/suggestion/suggestion.d.ts.map +1 -0
  150. package/dist/components/composites/suggestion/suggestion.js +100 -0
  151. package/dist/components/composites/suggestion/suggestion.js.map +1 -0
  152. package/dist/components/composites/task/index.d.ts +2 -0
  153. package/dist/components/composites/task/index.d.ts.map +1 -0
  154. package/dist/components/composites/task/index.js +2 -0
  155. package/dist/components/composites/task/task.d.ts +96 -0
  156. package/dist/components/composites/task/task.d.ts.map +1 -0
  157. package/dist/components/composites/task/task.js +311 -0
  158. package/dist/components/composites/task/task.js.map +1 -0
  159. package/dist/components/composites/terminal/ansi.d.ts +101 -0
  160. package/dist/components/composites/terminal/ansi.d.ts.map +1 -0
  161. package/dist/components/composites/terminal/ansi.js +544 -0
  162. package/dist/components/composites/terminal/ansi.js.map +1 -0
  163. package/dist/components/composites/terminal/index.d.ts +4 -0
  164. package/dist/components/composites/terminal/index.d.ts.map +1 -0
  165. package/dist/components/composites/terminal/index.js +4 -0
  166. package/dist/components/composites/terminal/terminal-model.d.ts +63 -0
  167. package/dist/components/composites/terminal/terminal-model.d.ts.map +1 -0
  168. package/dist/components/composites/terminal/terminal-model.js +69 -0
  169. package/dist/components/composites/terminal/terminal-model.js.map +1 -0
  170. package/dist/components/composites/terminal/terminal.d.ts +88 -0
  171. package/dist/components/composites/terminal/terminal.d.ts.map +1 -0
  172. package/dist/components/composites/terminal/terminal.js +500 -0
  173. package/dist/components/composites/terminal/terminal.js.map +1 -0
  174. package/dist/components/composites/test-results/index.d.ts +3 -0
  175. package/dist/components/composites/test-results/index.d.ts.map +1 -0
  176. package/dist/components/composites/test-results/index.js +3 -0
  177. package/dist/components/composites/test-results/test-results-model.d.ts +154 -0
  178. package/dist/components/composites/test-results/test-results-model.d.ts.map +1 -0
  179. package/dist/components/composites/test-results/test-results-model.js +252 -0
  180. package/dist/components/composites/test-results/test-results-model.js.map +1 -0
  181. package/dist/components/composites/test-results/test-results.d.ts +60 -0
  182. package/dist/components/composites/test-results/test-results.d.ts.map +1 -0
  183. package/dist/components/composites/test-results/test-results.js +631 -0
  184. package/dist/components/composites/test-results/test-results.js.map +1 -0
  185. package/dist/components/composites/tool/format-tool-value.d.ts +37 -0
  186. package/dist/components/composites/tool/format-tool-value.d.ts.map +1 -0
  187. package/dist/components/composites/tool/format-tool-value.js +135 -0
  188. package/dist/components/composites/tool/format-tool-value.js.map +1 -0
  189. package/dist/components/composites/tool/index.d.ts +3 -0
  190. package/dist/components/composites/tool/index.d.ts.map +1 -0
  191. package/dist/components/composites/tool/index.js +3 -0
  192. package/dist/components/composites/tool/tool.d.ts +69 -0
  193. package/dist/components/composites/tool/tool.d.ts.map +1 -0
  194. package/dist/components/composites/tool/tool.js +214 -0
  195. package/dist/components/composites/tool/tool.js.map +1 -0
  196. package/dist/components/composites/update-dialog/update-dialog.js +1 -1
  197. package/dist/components/conversation/conversation-surface.d.ts +7 -0
  198. package/dist/components/conversation/conversation-surface.d.ts.map +1 -1
  199. package/dist/components/conversation/conversation-surface.js +2 -0
  200. package/dist/components/conversation/conversation-surface.js.map +1 -1
  201. package/dist/components/conversation/scroll-follow-core.d.ts +24 -0
  202. package/dist/components/conversation/scroll-follow-core.d.ts.map +1 -1
  203. package/dist/components/conversation/scroll-follow-core.js +23 -1
  204. package/dist/components/conversation/scroll-follow-core.js.map +1 -1
  205. package/dist/components/conversation/scroll-follow.d.ts +3 -0
  206. package/dist/components/conversation/scroll-follow.d.ts.map +1 -1
  207. package/dist/components/conversation/scroll-follow.js +80 -1
  208. package/dist/components/conversation/scroll-follow.js.map +1 -1
  209. package/dist/components/ui/attachment/attachment.d.ts +58 -0
  210. package/dist/components/ui/attachment/attachment.d.ts.map +1 -0
  211. package/dist/components/ui/attachment/attachment.js +279 -0
  212. package/dist/components/ui/attachment/attachment.js.map +1 -0
  213. package/dist/components/ui/attachment/index.d.ts +2 -0
  214. package/dist/components/ui/attachment/index.d.ts.map +1 -0
  215. package/dist/components/ui/attachment/index.js +2 -0
  216. package/dist/components/ui/badge/badge.d.ts +1 -1
  217. package/dist/components/ui/bubble/bubble.d.ts +33 -0
  218. package/dist/components/ui/bubble/bubble.d.ts.map +1 -0
  219. package/dist/components/ui/bubble/bubble.js +95 -0
  220. package/dist/components/ui/bubble/bubble.js.map +1 -0
  221. package/dist/components/ui/bubble/index.d.ts +2 -0
  222. package/dist/components/ui/bubble/index.d.ts.map +1 -0
  223. package/dist/components/ui/bubble/index.js +2 -0
  224. package/dist/components/ui/button/button.d.ts +1 -1
  225. package/dist/components/ui/code-block/code-block.d.ts +56 -3
  226. package/dist/components/ui/code-block/code-block.d.ts.map +1 -1
  227. package/dist/components/ui/code-block/code-block.js +231 -50
  228. package/dist/components/ui/code-block/code-block.js.map +1 -1
  229. package/dist/components/ui/code-block/index.d.ts +1 -1
  230. package/dist/components/ui/code-block/index.d.ts.map +1 -1
  231. package/dist/components/ui/code-block/index.js +2 -2
  232. package/dist/components/ui/field/number-field.d.ts +21 -4
  233. package/dist/components/ui/field/number-field.d.ts.map +1 -1
  234. package/dist/components/ui/field/number-field.js +22 -11
  235. package/dist/components/ui/field/number-field.js.map +1 -1
  236. package/dist/components/ui/marker/index.d.ts +2 -0
  237. package/dist/components/ui/marker/index.d.ts.map +1 -0
  238. package/dist/components/ui/marker/index.js +2 -0
  239. package/dist/components/ui/marker/marker.d.ts +35 -0
  240. package/dist/components/ui/marker/marker.d.ts.map +1 -0
  241. package/dist/components/ui/marker/marker.js +123 -0
  242. package/dist/components/ui/marker/marker.js.map +1 -0
  243. package/dist/components/ui/message/cite-ranges.d.ts +42 -0
  244. package/dist/components/ui/message/cite-ranges.d.ts.map +1 -0
  245. package/dist/components/ui/message/cite-ranges.js +105 -0
  246. package/dist/components/ui/message/cite-ranges.js.map +1 -0
  247. package/dist/components/ui/message/index.d.ts +4 -0
  248. package/dist/components/ui/message/index.d.ts.map +1 -0
  249. package/dist/components/ui/message/index.js +4 -0
  250. package/dist/components/ui/message/message.d.ts +95 -0
  251. package/dist/components/ui/message/message.d.ts.map +1 -0
  252. package/dist/components/ui/message/message.js +443 -0
  253. package/dist/components/ui/message/message.js.map +1 -0
  254. package/dist/components/ui/message/parse-message-text.d.ts +93 -0
  255. package/dist/components/ui/message/parse-message-text.d.ts.map +1 -0
  256. package/dist/components/ui/message/parse-message-text.js +365 -0
  257. package/dist/components/ui/message/parse-message-text.js.map +1 -0
  258. package/dist/components/ui/questionnaire/index.d.ts +2 -0
  259. package/dist/components/ui/questionnaire/index.d.ts.map +1 -0
  260. package/dist/components/ui/questionnaire/index.js +2 -0
  261. package/dist/components/ui/questionnaire/questionnaire.d.ts +83 -0
  262. package/dist/components/ui/questionnaire/questionnaire.d.ts.map +1 -0
  263. package/dist/components/ui/questionnaire/questionnaire.js +470 -0
  264. package/dist/components/ui/questionnaire/questionnaire.js.map +1 -0
  265. package/dist/components/ui/status-chip/status-chip.d.ts +1 -1
  266. package/dist/components/ui/switch/switch.js +11 -11
  267. package/dist/components/ui/switch/switch.js.map +1 -1
  268. package/dist/components/ui/trace/index.d.ts +2 -0
  269. package/dist/components/ui/trace/index.d.ts.map +1 -0
  270. package/dist/components/ui/trace/index.js +2 -0
  271. package/dist/components/ui/trace/trace.d.ts +72 -0
  272. package/dist/components/ui/trace/trace.d.ts.map +1 -0
  273. package/dist/components/ui/trace/trace.js +340 -0
  274. package/dist/components/ui/trace/trace.js.map +1 -0
  275. package/dist/index.d.ts +26 -0
  276. package/dist/index.d.ts.map +1 -1
  277. package/dist/index.js +46 -5
  278. package/dist/r/attachment.json +25 -0
  279. package/dist/r/bubble.json +22 -0
  280. package/dist/r/chain-of-thought.json +26 -0
  281. package/dist/r/chat.json +24 -0
  282. package/dist/r/checkpoint.json +35 -0
  283. package/dist/r/confirmation.json +34 -0
  284. package/dist/r/context.json +33 -0
  285. package/dist/r/file-tree.json +32 -0
  286. package/dist/r/inline-citation.json +35 -0
  287. package/dist/r/marker.json +22 -0
  288. package/dist/r/message-scroller.json +31 -0
  289. package/dist/r/message.json +33 -0
  290. package/dist/r/model-selector.json +34 -0
  291. package/dist/r/plan.json +26 -0
  292. package/dist/r/questionnaire.json +28 -0
  293. package/dist/r/queue.json +26 -0
  294. package/dist/r/reasoning.json +26 -0
  295. package/dist/r/registry.json +811 -52
  296. package/dist/r/sources.json +32 -0
  297. package/dist/r/src/components/composites/chain-of-thought/chain-of-thought.tsx +141 -0
  298. package/dist/r/src/components/composites/chain-of-thought/index.ts +11 -0
  299. package/dist/r/src/components/composites/chat/chat.tsx +190 -0
  300. package/dist/r/src/components/composites/chat/index.ts +11 -0
  301. package/dist/r/src/components/composites/checkpoint/checkpoint-model.ts +71 -0
  302. package/dist/r/src/components/composites/checkpoint/checkpoint.tsx +214 -0
  303. package/dist/r/src/components/composites/checkpoint/index.ts +8 -0
  304. package/dist/r/src/components/composites/confirmation/confirmation-model.ts +83 -0
  305. package/dist/r/src/components/composites/confirmation/confirmation.tsx +311 -0
  306. package/dist/r/src/components/composites/confirmation/index.ts +9 -0
  307. package/dist/r/src/components/composites/context/context-model.ts +168 -0
  308. package/dist/r/src/components/composites/context/context.tsx +187 -0
  309. package/dist/r/src/components/composites/context/index.ts +18 -0
  310. package/dist/r/src/components/composites/file-tree/build-file-tree.ts +456 -0
  311. package/dist/r/src/components/composites/file-tree/file-tree.tsx +405 -0
  312. package/dist/r/src/components/composites/file-tree/index.ts +25 -0
  313. package/dist/r/src/components/composites/inline-citation/citation.ts +78 -0
  314. package/dist/r/src/components/composites/inline-citation/index.ts +9 -0
  315. package/dist/r/src/components/composites/inline-citation/inline-citation.tsx +285 -0
  316. package/dist/r/src/components/composites/message-scroller/index.ts +2 -0
  317. package/dist/r/src/components/composites/message-scroller/message-scroller.tsx +301 -0
  318. package/dist/r/src/components/composites/message-scroller/scroll-anchor.ts +170 -0
  319. package/dist/r/src/components/composites/model-selector/index.ts +14 -0
  320. package/dist/r/src/components/composites/model-selector/model-selector.tsx +420 -0
  321. package/dist/r/src/components/composites/model-selector/models.ts +103 -0
  322. package/dist/r/src/components/composites/plan/index.ts +1 -0
  323. package/dist/r/src/components/composites/plan/plan.tsx +155 -0
  324. package/dist/r/src/components/composites/queue/index.ts +10 -0
  325. package/dist/r/src/components/composites/queue/queue.tsx +264 -0
  326. package/dist/r/src/components/composites/reasoning/index.ts +1 -0
  327. package/dist/r/src/components/composites/reasoning/reasoning.tsx +176 -0
  328. package/dist/r/src/components/composites/sources/index.ts +11 -0
  329. package/dist/r/src/components/composites/sources/source.ts +116 -0
  330. package/dist/r/src/components/composites/sources/sources.tsx +163 -0
  331. package/dist/r/src/components/composites/stack-trace/index.ts +19 -0
  332. package/dist/r/src/components/composites/stack-trace/parse-stack-trace.ts +507 -0
  333. package/dist/r/src/components/composites/stack-trace/stack-trace.tsx +395 -0
  334. package/dist/r/src/components/composites/subagent/index.ts +1 -0
  335. package/dist/r/src/components/composites/subagent/subagent.tsx +159 -0
  336. package/dist/r/src/components/composites/suggestion/index.ts +1 -0
  337. package/dist/r/src/components/composites/suggestion/suggestion.tsx +140 -0
  338. package/dist/r/src/components/composites/task/index.ts +14 -0
  339. package/dist/r/src/components/composites/task/task.tsx +317 -0
  340. package/dist/r/src/components/composites/terminal/ansi.ts +674 -0
  341. package/dist/r/src/components/composites/terminal/index.ts +20 -0
  342. package/dist/r/src/components/composites/terminal/terminal-model.ts +111 -0
  343. package/dist/r/src/components/composites/terminal/terminal.tsx +574 -0
  344. package/dist/r/src/components/composites/test-results/index.ts +20 -0
  345. package/dist/r/src/components/composites/test-results/test-results-model.ts +368 -0
  346. package/dist/r/src/components/composites/test-results/test-results.tsx +586 -0
  347. package/dist/r/src/components/composites/tool/format-tool-value.ts +221 -0
  348. package/dist/r/src/components/composites/tool/index.ts +2 -0
  349. package/dist/r/src/components/composites/tool/tool.tsx +228 -0
  350. package/dist/r/src/components/conversation/conversation-surface.tsx +9 -0
  351. package/dist/r/src/components/conversation/scroll-follow-core.ts +31 -0
  352. package/dist/r/src/components/conversation/scroll-follow.ts +92 -0
  353. package/dist/r/src/components/ui/attachment/attachment.tsx +282 -0
  354. package/dist/r/src/components/ui/attachment/index.ts +10 -0
  355. package/dist/r/src/components/ui/bubble/bubble.tsx +86 -0
  356. package/dist/r/src/components/ui/bubble/index.ts +1 -0
  357. package/dist/r/src/components/ui/code-block/code-block.tsx +256 -26
  358. package/dist/r/src/components/ui/code-block/index.ts +9 -1
  359. package/dist/r/src/components/ui/field/number-field.tsx +51 -14
  360. package/dist/r/src/components/ui/marker/index.ts +1 -0
  361. package/dist/r/src/components/ui/marker/marker.tsx +90 -0
  362. package/dist/r/src/components/ui/message/cite-ranges.ts +144 -0
  363. package/dist/r/src/components/ui/message/index.ts +23 -0
  364. package/dist/r/src/components/ui/message/message.tsx +434 -0
  365. package/dist/r/src/components/ui/message/parse-message-text.ts +450 -0
  366. package/dist/r/src/components/ui/questionnaire/index.ts +10 -0
  367. package/dist/r/src/components/ui/questionnaire/questionnaire.tsx +446 -0
  368. package/dist/r/src/components/ui/trace/index.ts +9 -0
  369. package/dist/r/src/components/ui/trace/trace.tsx +334 -0
  370. package/dist/r/src/styles/base.css +34 -0
  371. package/dist/r/stack-trace.json +32 -0
  372. package/dist/r/subagent.json +27 -0
  373. package/dist/r/suggestion.json +24 -0
  374. package/dist/r/task.json +28 -0
  375. package/dist/r/terminal.json +35 -0
  376. package/dist/r/test-results.json +33 -0
  377. package/dist/r/tool.json +31 -0
  378. package/dist/r/trace.json +25 -0
  379. package/package.json +6 -3
  380. package/registry.json +811 -52
  381. package/src/components/composites/chain-of-thought/chain-of-thought.tsx +141 -0
  382. package/src/components/composites/chain-of-thought/index.ts +11 -0
  383. package/src/components/composites/chat/chat.tsx +190 -0
  384. package/src/components/composites/chat/index.ts +11 -0
  385. package/src/components/composites/checkpoint/checkpoint-model.ts +71 -0
  386. package/src/components/composites/checkpoint/checkpoint.tsx +214 -0
  387. package/src/components/composites/checkpoint/index.ts +8 -0
  388. package/src/components/composites/confirmation/confirmation-model.ts +83 -0
  389. package/src/components/composites/confirmation/confirmation.tsx +311 -0
  390. package/src/components/composites/confirmation/index.ts +9 -0
  391. package/src/components/composites/context/context-model.ts +168 -0
  392. package/src/components/composites/context/context.tsx +187 -0
  393. package/src/components/composites/context/index.ts +18 -0
  394. package/src/components/composites/file-tree/build-file-tree.ts +456 -0
  395. package/src/components/composites/file-tree/file-tree.tsx +405 -0
  396. package/src/components/composites/file-tree/index.ts +25 -0
  397. package/src/components/composites/inline-citation/citation.ts +78 -0
  398. package/src/components/composites/inline-citation/index.ts +9 -0
  399. package/src/components/composites/inline-citation/inline-citation.tsx +285 -0
  400. package/src/components/composites/message-scroller/index.ts +2 -0
  401. package/src/components/composites/message-scroller/message-scroller.tsx +301 -0
  402. package/src/components/composites/message-scroller/scroll-anchor.ts +170 -0
  403. package/src/components/composites/model-selector/index.ts +14 -0
  404. package/src/components/composites/model-selector/model-selector.tsx +420 -0
  405. package/src/components/composites/model-selector/models.ts +103 -0
  406. package/src/components/composites/plan/index.ts +1 -0
  407. package/src/components/composites/plan/plan.tsx +155 -0
  408. package/src/components/composites/queue/index.ts +10 -0
  409. package/src/components/composites/queue/queue.tsx +264 -0
  410. package/src/components/composites/reasoning/index.ts +1 -0
  411. package/src/components/composites/reasoning/reasoning.tsx +176 -0
  412. package/src/components/composites/sources/index.ts +11 -0
  413. package/src/components/composites/sources/source.ts +116 -0
  414. package/src/components/composites/sources/sources.tsx +163 -0
  415. package/src/components/composites/stack-trace/index.ts +19 -0
  416. package/src/components/composites/stack-trace/parse-stack-trace.ts +507 -0
  417. package/src/components/composites/stack-trace/stack-trace.tsx +395 -0
  418. package/src/components/composites/subagent/index.ts +1 -0
  419. package/src/components/composites/subagent/subagent.tsx +159 -0
  420. package/src/components/composites/suggestion/index.ts +1 -0
  421. package/src/components/composites/suggestion/suggestion.tsx +140 -0
  422. package/src/components/composites/task/index.ts +14 -0
  423. package/src/components/composites/task/task.tsx +317 -0
  424. package/src/components/composites/terminal/ansi.ts +674 -0
  425. package/src/components/composites/terminal/index.ts +20 -0
  426. package/src/components/composites/terminal/terminal-model.ts +111 -0
  427. package/src/components/composites/terminal/terminal.tsx +574 -0
  428. package/src/components/composites/test-results/index.ts +20 -0
  429. package/src/components/composites/test-results/test-results-model.ts +368 -0
  430. package/src/components/composites/test-results/test-results.tsx +586 -0
  431. package/src/components/composites/tool/format-tool-value.ts +221 -0
  432. package/src/components/composites/tool/index.ts +2 -0
  433. package/src/components/composites/tool/tool.tsx +228 -0
  434. package/src/components/conversation/conversation-surface.tsx +9 -0
  435. package/src/components/conversation/scroll-follow-core.ts +31 -0
  436. package/src/components/conversation/scroll-follow.ts +92 -0
  437. package/src/components/ui/attachment/attachment.tsx +282 -0
  438. package/src/components/ui/attachment/index.ts +10 -0
  439. package/src/components/ui/bubble/bubble.tsx +86 -0
  440. package/src/components/ui/bubble/index.ts +1 -0
  441. package/src/components/ui/code-block/code-block.tsx +256 -26
  442. package/src/components/ui/code-block/index.ts +9 -1
  443. package/src/components/ui/field/number-field.tsx +51 -14
  444. package/src/components/ui/marker/index.ts +1 -0
  445. package/src/components/ui/marker/marker.tsx +90 -0
  446. package/src/components/ui/message/cite-ranges.ts +144 -0
  447. package/src/components/ui/message/index.ts +23 -0
  448. package/src/components/ui/message/message.tsx +434 -0
  449. package/src/components/ui/message/parse-message-text.ts +450 -0
  450. package/src/components/ui/questionnaire/index.ts +10 -0
  451. package/src/components/ui/questionnaire/questionnaire.tsx +446 -0
  452. package/src/components/ui/trace/index.ts +9 -0
  453. package/src/components/ui/trace/trace.tsx +334 -0
  454. package/src/index.ts +26 -0
  455. package/src/styles/base.css +34 -0
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Turning a tool's input or output into text, safely.
3
+ *
4
+ * A tool call's payload is whatever the model sent and whatever the tool
5
+ * returned: it can be a megabyte of log, an object that refers to itself, a
6
+ * `BigInt` (which `JSON.stringify` throws on), or a getter that throws. A
7
+ * transcript component that calls `JSON.stringify` on that and renders the result
8
+ * is one hostile payload away from a frozen tab or a blank page, so this is a
9
+ * small serializer of our own rather than a call to the built-in one:
10
+ *
11
+ * - **It never throws.** Anything it cannot read becomes a marker string.
12
+ * - **It stops early.** Output is written into a budget of `maxChars`; the moment
13
+ * the budget is spent it stops walking, so the cost is bounded by the budget and
14
+ * not by the size of the value.
15
+ * - **It says when it stopped.** `truncated` is true whenever text was cut, so the
16
+ * caller can tell the reader the view is partial instead of passing off a cut
17
+ * JSON document as the whole thing.
18
+ * - **It does not lie about types.** Values JSON cannot carry are written as what
19
+ * they are (`12n`, `undefined`, `NaN`) rather than coerced to `null`, which
20
+ * would read as the tool having returned null.
21
+ *
22
+ * Strings are returned as given, not quoted and not re-parsed: re-serializing a
23
+ * JSON string would silently round numbers beyond double precision.
24
+ */
25
+ export type ToolValueText = Readonly<{
26
+ text: string
27
+ /** `json` for structured values, `text` for strings and anything that is not JSON. */
28
+ language: 'json' | 'text'
29
+ /** True when `text` is only the start of the value. */
30
+ truncated: boolean
31
+ }>
32
+
33
+ /** Characters shown per section unless the caller says otherwise. */
34
+ export const TOOL_VALUE_MAX_CHARS = 20_000
35
+
36
+ /** Nesting deeper than this is written as a marker. */
37
+ const MAX_DEPTH = 12
38
+
39
+ class BudgetSpent extends Error {}
40
+
41
+ type Writer = {
42
+ out: string[]
43
+ length: number
44
+ max: number
45
+ }
46
+
47
+ function write(writer: Writer, chunk: string) {
48
+ writer.out.push(chunk)
49
+ writer.length += chunk.length
50
+ if (writer.length > writer.max) throw new BudgetSpent()
51
+ }
52
+
53
+ /** A JSON string literal, without stringifying more than the budget could hold. */
54
+ function quote(writer: Writer, value: string): string {
55
+ const room = Math.max(0, writer.max - writer.length)
56
+ // Past the budget the result is thrown away, so the tail need not be well formed.
57
+ return value.length > room + 1
58
+ ? JSON.stringify(value.slice(0, room + 1)).slice(0, -1)
59
+ : JSON.stringify(value)
60
+ }
61
+
62
+ function describeNonJson(value: unknown): string {
63
+ switch (typeof value) {
64
+ case 'undefined':
65
+ return 'undefined'
66
+ case 'bigint':
67
+ return `${value}n`
68
+ case 'symbol':
69
+ return value.toString()
70
+ case 'function':
71
+ return `[Function${value.name ? ` ${value.name}` : ''}]`
72
+ default:
73
+ return String(value)
74
+ }
75
+ }
76
+
77
+ /** At most `limit` items: each costs at least a character, so more could never be shown. */
78
+ function take<T>(source: Iterable<T>, limit: number): T[] {
79
+ const taken: T[] = []
80
+ for (const item of source) {
81
+ if (taken.length > limit) break
82
+ taken.push(item)
83
+ }
84
+ return taken
85
+ }
86
+
87
+ function readEntries(value: object, limit: number): [string, unknown][] {
88
+ const entries: [string, unknown][] = []
89
+ for (const key of Object.keys(value).slice(0, limit + 1)) {
90
+ try {
91
+ entries.push([key, (value as Record<string, unknown>)[key]])
92
+ } catch {
93
+ entries.push([key, '[Unreadable]'])
94
+ }
95
+ }
96
+ return entries
97
+ }
98
+
99
+ function writeValue(writer: Writer, value: unknown, depth: number, ancestors: object[]) {
100
+ if (value === null) return write(writer, 'null')
101
+ if (typeof value === 'string') return write(writer, quote(writer, value))
102
+ if (typeof value === 'number') {
103
+ return write(writer, Number.isFinite(value) ? JSON.stringify(value) : String(value))
104
+ }
105
+ if (typeof value === 'boolean') return write(writer, String(value))
106
+ if (typeof value !== 'object') {
107
+ // bigint, undefined, symbol, function: written as what they are, in quotes
108
+ // only where a bare token would be mistaken for the surrounding structure.
109
+ const text = describeNonJson(value)
110
+ return write(
111
+ writer,
112
+ typeof value === 'bigint' || typeof value === 'undefined' ? text : quote(writer, text)
113
+ )
114
+ }
115
+
116
+ if (ancestors.includes(value)) return write(writer, '"[Circular]"')
117
+
118
+ let subject: unknown = value
119
+ if (!Array.isArray(value) && typeof (value as { toJSON?: unknown }).toJSON === 'function') {
120
+ try {
121
+ subject = (value as { toJSON: () => unknown }).toJSON()
122
+ } catch {
123
+ return write(writer, '"[Unreadable]"')
124
+ }
125
+ if (typeof subject !== 'object' || subject === null) {
126
+ return writeValue(writer, subject, depth, ancestors)
127
+ }
128
+ }
129
+ const node = subject as object
130
+
131
+ if (ArrayBuffer.isView(node) || node instanceof ArrayBuffer) {
132
+ return write(
133
+ writer,
134
+ quote(writer, `[${node.constructor.name}: ${node.byteLength.toLocaleString('en')} bytes]`)
135
+ )
136
+ }
137
+
138
+ let items: unknown[] | undefined
139
+ let fields: [string, unknown][] | undefined
140
+ if (Array.isArray(node)) items = node
141
+ else if (node instanceof Set) items = take(node, writer.max)
142
+ else if (node instanceof Map) items = take(node, writer.max).map(([key, entry]) => [key, entry])
143
+ else if (node instanceof Error) {
144
+ fields = [
145
+ ['name', node.name],
146
+ ['message', node.message],
147
+ ]
148
+ } else fields = readEntries(node, writer.max)
149
+
150
+ if (depth >= MAX_DEPTH) {
151
+ return write(writer, quote(writer, items ? `[Array(${items.length})]` : '[Object]'))
152
+ }
153
+
154
+ const nested = [...ancestors, value]
155
+ const indent = ' '.repeat(depth + 1)
156
+ const closing = ' '.repeat(depth)
157
+
158
+ if (items) {
159
+ if (items.length === 0) return write(writer, '[]')
160
+ write(writer, '[\n')
161
+ // Indexed rather than `forEach`, which skips the holes of a sparse array: a
162
+ // million-slot hole would loop a million times without ever spending the budget.
163
+ for (let index = 0; index < items.length; index += 1) {
164
+ write(writer, indent)
165
+ writeValue(writer, items[index], depth + 1, nested)
166
+ write(writer, index < items.length - 1 ? ',\n' : '\n')
167
+ }
168
+ return write(writer, `${closing}]`)
169
+ }
170
+
171
+ const present = fields ?? []
172
+ if (present.length === 0) return write(writer, '{}')
173
+ write(writer, '{\n')
174
+ present.forEach(([key, entry], index) => {
175
+ write(writer, `${indent}${quote(writer, key)}: `)
176
+ writeValue(writer, entry, depth + 1, nested)
177
+ write(writer, index < present.length - 1 ? ',\n' : '\n')
178
+ })
179
+ write(writer, `${closing}}`)
180
+ }
181
+
182
+ export function formatToolValue(
183
+ value: unknown,
184
+ options: { maxChars?: number } = {}
185
+ ): ToolValueText {
186
+ const requested = options.maxChars
187
+ const max =
188
+ requested === undefined || Number.isNaN(requested)
189
+ ? TOOL_VALUE_MAX_CHARS
190
+ : Math.max(0, Math.floor(requested))
191
+
192
+ if (typeof value === 'string') {
193
+ return {
194
+ text: value.slice(0, max),
195
+ language: 'text',
196
+ truncated: value.length > max,
197
+ }
198
+ }
199
+ if (value === undefined || typeof value === 'symbol' || typeof value === 'function') {
200
+ return { text: describeNonJson(value).slice(0, max), language: 'text', truncated: false }
201
+ }
202
+
203
+ const writer: Writer = { out: [], length: 0, max }
204
+ let truncated = false
205
+ try {
206
+ writeValue(writer, value, 0, [])
207
+ } catch (error) {
208
+ // Anything other than the budget running out is a bug in a getter or a proxy
209
+ // we could not foresee; the reader still gets what was written so far.
210
+ truncated = true
211
+ if (!(error instanceof BudgetSpent) && writer.out.length === 0) {
212
+ return { text: '[Unreadable]', language: 'text', truncated: false }
213
+ }
214
+ }
215
+ const joined = writer.out.join('')
216
+ return {
217
+ text: joined.length > max ? joined.slice(0, max) : joined,
218
+ language: 'json',
219
+ truncated: truncated || joined.length > max,
220
+ }
221
+ }
@@ -0,0 +1,2 @@
1
+ export { Tool, TOOL_STATUS_LABELS, type ToolProps, type ToolStatus } from './tool'
2
+ export { formatToolValue, TOOL_VALUE_MAX_CHARS, type ToolValueText } from './format-tool-value'
@@ -0,0 +1,228 @@
1
+ import { Wrench } from 'lucide-solid'
2
+ import { children as resolveChildren, createMemo, Show, splitProps, type JSX } from 'solid-js'
3
+ import { Badge } from '../../ui/badge'
4
+ import { CodeBlock, type CodeLineMark } from '../../ui/code-block'
5
+ import { TraceDetail, TraceLine, type TraceLineProps, type TraceStatus } from '../../ui/trace'
6
+ import { formatToolValue, TOOL_VALUE_MAX_CHARS } from './format-tool-value'
7
+
8
+ /**
9
+ * `TraceLine` reads its children twice: once to decide whether the line can
10
+ * expand, and once to draw them when it is open. A function child is returned
11
+ * unevaluated by the first read and run once, when the content mounts, by the
12
+ * second — so a folded line builds none of its body. Solid's `insert` unwraps
13
+ * functions; the JSX types simply do not admit them.
14
+ */
15
+ const deferred = (render: () => JSX.Element) => render as unknown as JSX.Element
16
+
17
+ /**
18
+ * Tool.
19
+ *
20
+ * One tool call, from what was asked to what came back, as a `TraceLine`: the
21
+ * tool's name and a short description on the line, and — when there is anything
22
+ * to show — an input section, an output section and an error underneath. Reach
23
+ * for `Subagent` when the "tool" is another agent with steps of its own, for a
24
+ * bare `CodeBlock` when the point is the output itself and not the call that made
25
+ * it, and for a confirmation (passed as `children`) when the reader has to decide
26
+ * something about the call.
27
+ *
28
+ * ## Everything is optional, and absence is not a claim
29
+ *
30
+ * A call with no `status` shows no mark at all — an event that merely arrived is
31
+ * not "done". `input`, `output` and `errorText` each render a section only when
32
+ * the host passed them: no empty "Output" box under a call that is still running,
33
+ * and no invented "No output" under one that failed. An error with no output is
34
+ * just the error. A call with nothing to expand is a plain line, not a
35
+ * disclosure that opens onto nothing.
36
+ *
37
+ * Two statuses are words, not only marks, because the glyphs for them are easy to
38
+ * misread: `awaiting-approval` and `denied` also show a badge. Both are spoken
39
+ * once (the badge is hidden from assistive technology and the status word is
40
+ * read in its place). A call awaiting approval opens by default, since it is the
41
+ * one state in which the reader has something to do. The component never decides
42
+ * whether an approval is allowed: the controls live in `children`, supplied by
43
+ * the host, or are absent.
44
+ *
45
+ * ## Payloads are untrusted and unbounded
46
+ *
47
+ * `input` and `output` are `unknown` — whatever the model sent and the tool
48
+ * returned. They go through `formatToolValue`, which cannot throw (circular
49
+ * references, `BigInt`, throwing getters) and stops after `maxChars`; a cut view
50
+ * says so rather than presenting half a document as the whole. Sections render in
51
+ * a small `CodeBlock` with a capped height. A string is shown as given; anything
52
+ * structured is pretty-printed JSON. The sections mount only when the line is
53
+ * opened, so a transcript of hundreds of calls formats none of them until asked.
54
+ *
55
+ * A `Tool` is a line: place it inside a `Trace`.
56
+ */
57
+ export type ToolStatus =
58
+ | 'pending'
59
+ | 'running'
60
+ | 'success'
61
+ | 'error'
62
+ | 'awaiting-approval'
63
+ | 'denied'
64
+ | 'cancelled'
65
+
66
+ /**
67
+ * The words spoken (and, for the last three, shown) for each status. `denied` is
68
+ * a decision about the call; `cancelled` is the run being stopped while the call
69
+ * was in flight. Give a call `cancelled` only when the host knows that call was
70
+ * stopped: a run ending does not by itself say what became of each call.
71
+ */
72
+ export const TOOL_STATUS_LABELS: Readonly<Record<ToolStatus, string>> = {
73
+ pending: 'Pending',
74
+ running: 'Running',
75
+ success: 'Completed',
76
+ error: 'Failed',
77
+ 'awaiting-approval': 'Awaiting approval',
78
+ denied: 'Denied',
79
+ cancelled: 'Cancelled',
80
+ }
81
+
82
+ const TRACE_STATUS: Readonly<Record<ToolStatus, TraceStatus>> = {
83
+ pending: 'pending',
84
+ running: 'running',
85
+ success: 'success',
86
+ error: 'error',
87
+ 'awaiting-approval': 'pending',
88
+ denied: 'cancelled',
89
+ cancelled: 'cancelled',
90
+ }
91
+
92
+ export type ToolProps = Omit<
93
+ TraceLineProps,
94
+ 'title' | 'status' | 'statusLabel' | 'href' | 'onSelect' | 'selectHaspopup'
95
+ > & {
96
+ /** The tool's name, as the host knows it. Rendered as text. */
97
+ name: string
98
+ /** What this call is for: a path, a query, a command. */
99
+ description?: JSX.Element
100
+ status?: ToolStatus
101
+ /** What the model asked the tool to do. Any value; omit it to show no section. */
102
+ input?: unknown
103
+ /** What the tool returned. Any value; omit it to show no section. */
104
+ output?: unknown
105
+ /** The failure, as text. */
106
+ errorText?: string
107
+ /** Characters of each section shown before it is cut. Defaults to 20,000. */
108
+ maxChars?: number
109
+ /** Extra detail below the sections: an approval, a link to the full output. */
110
+ children?: JSX.Element
111
+ }
112
+
113
+ const SECTION_MAX_HEIGHT = '16rem'
114
+
115
+ function Section(props: {
116
+ title: string
117
+ value: unknown
118
+ maxChars: number
119
+ complete?: boolean
120
+ failure?: boolean
121
+ }) {
122
+ const formatted = createMemo(() => formatToolValue(props.value, { maxChars: props.maxChars }))
123
+ // Every line is marked as an error rather than recolouring the block, which
124
+ // keeps the mark in the gutter (a glyph, not colour alone) and the copy exact.
125
+ const marks = createMemo((): Readonly<Record<number, CodeLineMark>> | undefined => {
126
+ if (!props.failure) return undefined
127
+ const lines = formatted().text.split('\n').length
128
+ return Object.fromEntries(Array.from({ length: lines }, (_, index) => [index + 1, 'error']))
129
+ })
130
+ return (
131
+ <div class="flex min-w-0 flex-col gap-1">
132
+ <CodeBlock
133
+ size="sm"
134
+ title={props.title}
135
+ language={props.failure ? 'error' : formatted().language}
136
+ code={formatted().text}
137
+ lineMarks={marks()}
138
+ complete={props.complete ?? true}
139
+ maxHeight={SECTION_MAX_HEIGHT}
140
+ wrap={formatted().language === 'text'}
141
+ />
142
+ <Show when={formatted().truncated}>
143
+ <p class="m-0 px-1 text-xs text-muted-foreground">
144
+ Showing the first {props.maxChars.toLocaleString('en')} characters.
145
+ </p>
146
+ </Show>
147
+ </div>
148
+ )
149
+ }
150
+
151
+ export function Tool(props: ToolProps) {
152
+ const [local, rest] = splitProps(props, [
153
+ 'name',
154
+ 'description',
155
+ 'status',
156
+ 'input',
157
+ 'output',
158
+ 'errorText',
159
+ 'maxChars',
160
+ 'icon',
161
+ 'meta',
162
+ 'defaultOpen',
163
+ 'children',
164
+ ])
165
+ const maxChars = () => local.maxChars ?? TOOL_VALUE_MAX_CHARS
166
+ const hasInput = () => local.input !== undefined
167
+ const hasOutput = () => local.output !== undefined
168
+ const hasError = () => local.errorText !== undefined && local.errorText !== ''
169
+ // Resolved once: a host's approval controls are created once and keep their
170
+ // state and focus however often the line is folded.
171
+ const extra = resolveChildren(() => local.children)
172
+ const hasDetail = () => hasInput() || hasOutput() || hasError() || extra.toArray().length > 0
173
+
174
+ const detail = () => (
175
+ <TraceDetail class="flex flex-col gap-2">
176
+ <Show when={hasInput()}>
177
+ <Section title="Input" value={local.input} maxChars={maxChars()} />
178
+ </Show>
179
+ <Show when={hasOutput()}>
180
+ <Section
181
+ title="Output"
182
+ value={local.output}
183
+ maxChars={maxChars()}
184
+ complete={local.status !== 'running'}
185
+ />
186
+ </Show>
187
+ <Show when={hasError()}>
188
+ <Section title="Error" value={local.errorText} maxChars={maxChars()} failure />
189
+ </Show>
190
+ {extra()}
191
+ </TraceDetail>
192
+ )
193
+
194
+ // Words as well as marks for the states whose glyph is easy to misread. The
195
+ // badge is hidden from assistive technology: the status word is already spoken.
196
+ const wordBadge = () =>
197
+ local.status === 'awaiting-approval' ||
198
+ local.status === 'denied' ||
199
+ local.status === 'cancelled' ? (
200
+ <span class="flex items-center gap-2">
201
+ <Badge
202
+ aria-hidden="true"
203
+ size="sm"
204
+ variant={local.status === 'awaiting-approval' ? 'warning' : 'outline'}
205
+ >
206
+ {TOOL_STATUS_LABELS[local.status]}
207
+ </Badge>
208
+ {local.meta}
209
+ </span>
210
+ ) : (
211
+ local.meta
212
+ )
213
+
214
+ return (
215
+ <TraceLine
216
+ icon={local.icon ?? <Wrench />}
217
+ title={local.name}
218
+ description={local.description}
219
+ status={local.status ? TRACE_STATUS[local.status] : undefined}
220
+ statusLabel={local.status ? TOOL_STATUS_LABELS[local.status] : undefined}
221
+ meta={wordBadge()}
222
+ defaultOpen={local.defaultOpen ?? local.status === 'awaiting-approval'}
223
+ {...rest}
224
+ >
225
+ {hasDetail() ? deferred(detail) : undefined}
226
+ </TraceLine>
227
+ )
228
+ }
@@ -4,6 +4,7 @@ import { Show, splitProps } from 'solid-js'
4
4
  import { cn } from '#lib/utils'
5
5
  import { Button } from '../ui/button/button'
6
6
  import { createScrollFollow } from './scroll-follow'
7
+ import type { ScrollFollowHold } from './scroll-follow'
7
8
 
8
9
  /**
9
10
  * ConversationSurface.
@@ -44,6 +45,12 @@ export type ConversationSurfaceProps = ComponentProps<'div'> & {
44
45
  * frame around the surface is `ConversationPane`'s job.
45
46
  */
46
47
  gutter?: boolean
48
+ /**
49
+ * Receives the surface's `hold`, once, when it is created. A host that moves content
50
+ * under a reader who is following (a history load that lands after the conversation
51
+ * changed) opens a hold around it, so the move is not read as the reader scrolling away.
52
+ */
53
+ onFollowHold?: (hold: ScrollFollowHold) => void
47
54
  }
48
55
 
49
56
  export function ConversationSurface(props: ConversationSurfaceProps) {
@@ -57,6 +64,7 @@ export function ConversationSurface(props: ConversationSurfaceProps) {
57
64
  'empty',
58
65
  'header',
59
66
  'gutter',
67
+ 'onFollowHold',
60
68
  'children',
61
69
  'onScroll',
62
70
  'ref',
@@ -72,6 +80,7 @@ export function ConversationSurface(props: ConversationSurfaceProps) {
72
80
  initialPosition: () => local.initialReadingPosition,
73
81
  onPositionChange: (position) => local.onReadingPositionChange?.(position),
74
82
  })
83
+ local.onFollowHold?.(follow.hold)
75
84
  const onScroll: JSX.EventHandlerUnion<HTMLDivElement, Event> = (event) => {
76
85
  follow.onScroll()
77
86
  const handler = local.onScroll
@@ -169,6 +169,37 @@ export function resolveUserScrollStick(args: {
169
169
  return stick
170
170
  }
171
171
 
172
+ /** What the reader did to the scroller, as far as telling a scroll-up from anything else. */
173
+ export type ReaderInput =
174
+ | { kind: 'wheel'; deltaY: number }
175
+ | { kind: 'key'; key: string; shiftKey: boolean }
176
+ | { kind: 'touchmove' }
177
+ | { kind: 'pointerdown'; onScroller: boolean }
178
+
179
+ const SCROLL_UP_KEYS = new Set(['ArrowUp', 'PageUp', 'Home'])
180
+
181
+ /**
182
+ * Is this the reader moving the view up, right now?
183
+ *
184
+ * A held position (see `createScrollFollow().hold`) is the caller's until the reader takes it,
185
+ * and only an input that can scroll the view up takes it: a wheel notch up, an up key, a
186
+ * touch drag, or a press on the scroller itself (its scrollbar or gutter). A click or a
187
+ * selection inside the content, a letter, a wheel notch down or a down key do not move the
188
+ * view away from the end, so they leave it where it is.
189
+ */
190
+ export function isScrollUpIntent(input: ReaderInput): boolean {
191
+ switch (input.kind) {
192
+ case 'wheel':
193
+ return input.deltaY < 0
194
+ case 'key':
195
+ return SCROLL_UP_KEYS.has(input.key) || (input.key === ' ' && input.shiftKey)
196
+ case 'touchmove':
197
+ return true
198
+ case 'pointerdown':
199
+ return input.onScroller
200
+ }
201
+ }
202
+
172
203
  /** Result of an automatic (RO / append) pin evaluation. */
173
204
  export interface AutoPinResult {
174
205
  /** Whether to write `el.scrollTop = target` now. */
@@ -16,11 +16,16 @@
16
16
  import { createEffect, createSignal, onCleanup, untrack } from 'solid-js'
17
17
  import type { ConversationReadingPosition } from './conversation-surface'
18
18
  import {
19
+ atBottomEpsilon,
19
20
  bottomTarget,
20
21
  computeAtBottom,
22
+ distanceFromBottom,
21
23
  evaluateAutoPin,
24
+ isScrollUpIntent,
22
25
  isSelfScroll,
23
26
  resolveUserScrollStick,
27
+ SELF_SCROLL_EPSILON,
28
+ type ReaderInput,
24
29
  } from './scroll-follow-core'
25
30
 
26
31
  const geometry = (element: HTMLDivElement) => ({
@@ -30,6 +35,9 @@ const geometry = (element: HTMLDivElement) => ({
30
35
  })
31
36
  const noop = () => {}
32
37
 
38
+ /** Opens a hold on the follow position (see `createScrollFollow`); the function it returns closes it. */
39
+ export type ScrollFollowHold = () => () => void
40
+
33
41
  /** Internal plain-scroller binding; host-owned restoration can disable follow. */
34
42
  export function createScrollFollow(options: {
35
43
  enabled: () => boolean
@@ -49,6 +57,26 @@ export function createScrollFollow(options: {
49
57
  let pendingRestore: { frame: number; fromTop: number; target: number } | undefined
50
58
  let pendingRestoreUserIntent = false
51
59
  let removeRestoreIntentListeners = noop
60
+ // A hold is the caller saying it is positioning the view (see `hold`): while one is
61
+ // open and the reader was following, the content's moves are not read as the reader's.
62
+ let holds = 0
63
+ let holdFollowing = false
64
+ let holdIntent = false
65
+ let holdTop = -1
66
+ // Bumped on every identity change, so a hold opened for the previous conversation can
67
+ // neither pin the next one nor close over its state.
68
+ let holdGeneration = 0
69
+ const holding = () => holds > 0 && holdFollowing && !holdIntent
70
+ // Is the view still where the hold left it? The content can move the offset only by
71
+ // clamping it to the end it has now; anything else moved it from outside (a reveal, a
72
+ // search jump), and that is the caller's to keep, so the hold gives way.
73
+ const holdsPosition = (element: HTMLDivElement) => {
74
+ if (!holding()) return false
75
+ const maxTop = Math.max(0, element.scrollHeight - element.clientHeight)
76
+ if (Math.abs(element.scrollTop - Math.min(holdTop, maxTop)) <= SELF_SCROLL_EPSILON) return true
77
+ holdIntent = true
78
+ return false
79
+ }
52
80
  const reportPosition = () => {
53
81
  if (scroller)
54
82
  untrack(() =>
@@ -68,8 +96,24 @@ export function createScrollFollow(options: {
68
96
  lastWriteClientHeight = element.clientHeight
69
97
  previousTop = element.scrollTop
70
98
  }
99
+ const pinHeld = (element: HTMLDivElement) => {
100
+ const geom = geometry(element)
101
+ stick = true
102
+ if (distanceFromBottom(geom) > atBottomEpsilon()) writePin(element, bottomTarget(geom))
103
+ // An end the engine already clamped to is accepted as it stands, and is the reference
104
+ // from here on: the write before it is further down than the content now reaches.
105
+ else {
106
+ lastWriteTop = element.scrollTop
107
+ lastWriteClientHeight = element.clientHeight
108
+ previousTop = element.scrollTop
109
+ }
110
+ holdTop = element.scrollTop
111
+ setAtBottom(computeAtBottom(geometry(element), options.threshold()))
112
+ reportPosition()
113
+ }
71
114
  const pinAuto = () => {
72
115
  if (!scroller || !options.enabled() || pendingRestore) return
116
+ if (holdsPosition(scroller)) return pinHeld(scroller)
73
117
  const geom = geometry(scroller)
74
118
  const result = evaluateAutoPin({
75
119
  stick,
@@ -112,6 +156,7 @@ export function createScrollFollow(options: {
112
156
  reportPosition()
113
157
  return
114
158
  }
159
+ if (holdsPosition(scroller)) return pinHeld(scroller)
115
160
  const geom = geometry(scroller)
116
161
  setAtBottom(computeAtBottom(geom, options.threshold()))
117
162
  if (!isSelfScroll(geom.scrollTop, lastWriteTop)) {
@@ -145,6 +190,46 @@ export function createScrollFollow(options: {
145
190
  scroller.focus({ preventScroll: true })
146
191
  }
147
192
 
193
+ // Open while the caller moves content under a reader who is following, so a clamp or an
194
+ // insert it causes is not mistaken for the reader leaving the end. It gives way to the
195
+ // reader's own scroll-up (`isScrollUpIntent`) and to anything else that moves the view
196
+ // from outside (a reveal, a search jump); closing it puts a reader who is still following
197
+ // back at the end. A change of conversation invalidates it, so the next conversation's
198
+ // saved position is its own. A reveal or search jump outside a hold is never affected.
199
+ const hold = () => {
200
+ if (holds === 0) {
201
+ holdFollowing = options.enabled() && stick && scroller !== undefined
202
+ holdIntent = false
203
+ holdTop = scroller ? scroller.scrollTop : -1
204
+ }
205
+ holds += 1
206
+ const generation = holdGeneration
207
+ let open = true
208
+ return () => {
209
+ if (!open || generation !== holdGeneration) return
210
+ open = false
211
+ // Asked while this hold still counts: `holdsPosition` is false once none is open.
212
+ const last = holds === 1
213
+ const resume = last && scroller !== undefined && holdsPosition(scroller)
214
+ holds -= 1
215
+ if (!last) return
216
+ holdFollowing = false
217
+ holdIntent = false
218
+ if (resume && scroller && options.enabled() && !pendingRestore) pinHeld(scroller)
219
+ }
220
+ }
221
+ const noteHoldInput = (event: Event) => {
222
+ if (holds === 0 || holdIntent) return
223
+ let input: ReaderInput | undefined
224
+ if (event instanceof WheelEvent) input = { kind: 'wheel', deltaY: event.deltaY }
225
+ else if (event instanceof KeyboardEvent)
226
+ input = { kind: 'key', key: event.key, shiftKey: event.shiftKey }
227
+ else if (event.type === 'touchmove') input = { kind: 'touchmove' }
228
+ else if (event.type === 'pointerdown')
229
+ input = { kind: 'pointerdown', onScroller: event.target === scroller }
230
+ if (input && isScrollUpIntent(input)) holdIntent = true
231
+ }
232
+
148
233
  createEffect(() => {
149
234
  options.resetKey()
150
235
  const enabled = options.enabled()
@@ -165,6 +250,10 @@ export function createScrollFollow(options: {
165
250
  observer?.disconnect()
166
251
  })
167
252
  stick = true
253
+ holdGeneration += 1
254
+ holds = 0
255
+ holdFollowing = false
256
+ holdIntent = false
168
257
  lastWriteTop = -1
169
258
  lastWriteClientHeight = -1
170
259
  previousTop = -1
@@ -248,8 +337,11 @@ export function createScrollFollow(options: {
248
337
  atBottom,
249
338
  onScroll,
250
339
  jump,
340
+ hold,
251
341
  bindScroller: (element: HTMLDivElement) => {
252
342
  scroller = element
343
+ for (const type of ['wheel', 'touchmove', 'pointerdown', 'keydown'])
344
+ element.addEventListener(type, noteHoldInput, { passive: true })
253
345
  },
254
346
  bindContent: (element: HTMLDivElement) => {
255
347
  content = element