basalt-ui 1.11.0 → 1.13.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 (301) hide show
  1. package/AGENTS.md +21 -21
  2. package/CHANGELOG.md +32 -0
  3. package/README.md +63 -51
  4. package/agent/rules/basalt-agent.md +14 -0
  5. package/agent/rules/basalt-content.md +16 -3
  6. package/configs/oxlint-plugin.js +7 -3
  7. package/configs/oxlint-plugin.test.ts +34 -5
  8. package/configs/oxlint.json +4 -4
  9. package/dist/agent/adapter.d.ts +150 -0
  10. package/dist/agent/adapter.d.ts.map +1 -0
  11. package/dist/agent/adapter.js +482 -0
  12. package/dist/agent/adapter.test.d.ts +2 -0
  13. package/dist/agent/adapter.test.d.ts.map +1 -0
  14. package/dist/agent/ai-sdk-transport.d.ts.map +1 -1
  15. package/dist/agent/ai-sdk-transport.js +6 -2
  16. package/dist/agent/ai-sdk-transport.test.d.ts +2 -0
  17. package/dist/agent/ai-sdk-transport.test.d.ts.map +1 -0
  18. package/dist/agent/coalesce.d.ts +20 -4
  19. package/dist/agent/coalesce.d.ts.map +1 -1
  20. package/dist/agent/coalesce.js +76 -8
  21. package/dist/agent/coalesce.test.d.ts +2 -0
  22. package/dist/agent/coalesce.test.d.ts.map +1 -0
  23. package/dist/agent/foreign.test.d.ts +2 -0
  24. package/dist/agent/foreign.test.d.ts.map +1 -0
  25. package/dist/agent/id.d.ts +78 -0
  26. package/dist/agent/id.d.ts.map +1 -1
  27. package/dist/agent/id.js +30 -0
  28. package/dist/agent/id.test.d.ts +2 -0
  29. package/dist/agent/id.test.d.ts.map +1 -0
  30. package/dist/agent/index.d.ts +2 -0
  31. package/dist/agent/index.d.ts.map +1 -1
  32. package/dist/agent/index.js +3 -0
  33. package/dist/agent/merge.d.ts.map +1 -1
  34. package/dist/agent/merge.js +19 -7
  35. package/dist/agent/merge.test.d.ts +2 -0
  36. package/dist/agent/merge.test.d.ts.map +1 -0
  37. package/dist/agent/part-list.d.ts +49 -34
  38. package/dist/agent/part-list.d.ts.map +1 -1
  39. package/dist/agent/part-list.js +42 -6
  40. package/dist/agent/part-list.test.d.ts +2 -0
  41. package/dist/agent/part-list.test.d.ts.map +1 -0
  42. package/dist/agent/parts.d.ts +46 -10
  43. package/dist/agent/parts.d.ts.map +1 -1
  44. package/dist/agent/parts.js +16 -6
  45. package/dist/agent/parts.test.d.ts +2 -0
  46. package/dist/agent/parts.test.d.ts.map +1 -0
  47. package/dist/agent/thread.d.ts +18 -0
  48. package/dist/agent/thread.d.ts.map +1 -1
  49. package/dist/agent/thread.js +8 -2
  50. package/dist/agent/thread.test.d.ts +2 -0
  51. package/dist/agent/thread.test.d.ts.map +1 -0
  52. package/dist/agent/transport.test.d.ts +2 -0
  53. package/dist/agent/transport.test.d.ts.map +1 -0
  54. package/dist/agent/use-agent-stream.d.ts.map +1 -1
  55. package/dist/agent/use-agent-stream.js +2 -2
  56. package/dist/agent/use-agent-stream.test.d.ts +2 -0
  57. package/dist/agent/use-agent-stream.test.d.ts.map +1 -0
  58. package/dist/agent/use-agent-thread-runs.d.ts +24 -3
  59. package/dist/agent/use-agent-thread-runs.d.ts.map +1 -1
  60. package/dist/agent/use-agent-thread-runs.foreign.test.d.ts +2 -0
  61. package/dist/agent/use-agent-thread-runs.foreign.test.d.ts.map +1 -0
  62. package/dist/agent/use-agent-thread-runs.js +55 -15
  63. package/dist/agent/use-agent-thread-runs.resume.test.d.ts +2 -0
  64. package/dist/agent/use-agent-thread-runs.resume.test.d.ts.map +1 -0
  65. package/dist/agent/use-agent-thread-runs.test.d.ts +2 -0
  66. package/dist/agent/use-agent-thread-runs.test.d.ts.map +1 -0
  67. package/dist/agent/use-agent-thread-runs.wedge.test.d.ts +2 -0
  68. package/dist/agent/use-agent-thread-runs.wedge.test.d.ts.map +1 -0
  69. package/dist/agent-chat/composer.d.ts +130 -8
  70. package/dist/agent-chat/composer.d.ts.map +1 -1
  71. package/dist/agent-chat/composer.js +189 -23
  72. package/dist/agent-chat/composer.test.d.ts +2 -0
  73. package/dist/agent-chat/composer.test.d.ts.map +1 -0
  74. package/dist/agent-chat/index.d.ts +5 -1
  75. package/dist/agent-chat/index.d.ts.map +1 -1
  76. package/dist/agent-chat/index.js +2 -0
  77. package/dist/agent-chat/message-affordances.d.ts +59 -0
  78. package/dist/agent-chat/message-affordances.d.ts.map +1 -0
  79. package/dist/agent-chat/message-affordances.js +7 -0
  80. package/dist/agent-chat/relative-time.d.ts +27 -0
  81. package/dist/agent-chat/relative-time.d.ts.map +1 -0
  82. package/dist/agent-chat/relative-time.js +27 -0
  83. package/dist/agent-chat/relative-time.test.d.ts +2 -0
  84. package/dist/agent-chat/relative-time.test.d.ts.map +1 -0
  85. package/dist/agent-chat/thread-detail-panel.d.ts.map +1 -1
  86. package/dist/agent-chat/thread-detail-panel.js +1 -1
  87. package/dist/agent-chat/thread-feed-row.d.ts +65 -0
  88. package/dist/agent-chat/thread-feed-row.d.ts.map +1 -0
  89. package/dist/agent-chat/thread-feed-row.js +156 -0
  90. package/dist/agent-chat/thread-feed-row.test.d.ts +2 -0
  91. package/dist/agent-chat/thread-feed-row.test.d.ts.map +1 -0
  92. package/dist/agent-chat/thread-feed.d.ts +34 -5
  93. package/dist/agent-chat/thread-feed.d.ts.map +1 -1
  94. package/dist/agent-chat/thread-feed.js +52 -19
  95. package/dist/agent-chat/thread-feed.test.d.ts +2 -0
  96. package/dist/agent-chat/thread-feed.test.d.ts.map +1 -0
  97. package/dist/agent-chat/thread-message.d.ts +183 -2
  98. package/dist/agent-chat/thread-message.d.ts.map +1 -1
  99. package/dist/agent-chat/thread-message.js +329 -37
  100. package/dist/agent-chat/thread-message.test.d.ts +2 -0
  101. package/dist/agent-chat/thread-message.test.d.ts.map +1 -0
  102. package/dist/agent-chat/thread-outcome-card.d.ts.map +1 -1
  103. package/dist/agent-chat/thread-outcome-card.js +1 -16
  104. package/dist/agent-chat/thread-workspace.d.ts +14 -2
  105. package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
  106. package/dist/agent-chat/thread-workspace.js +30 -4
  107. package/dist/agent-chat/thread-workspace.test.d.ts +2 -0
  108. package/dist/agent-chat/thread-workspace.test.d.ts.map +1 -0
  109. package/dist/agent-chat/tool-chip.test.d.ts +2 -0
  110. package/dist/agent-chat/tool-chip.test.d.ts.map +1 -0
  111. package/dist/agent-chat/virtualize.d.ts +123 -0
  112. package/dist/agent-chat/virtualize.d.ts.map +1 -0
  113. package/dist/agent-chat/virtualize.js +9 -0
  114. package/dist/agent-chat/virtualize.type-guard.test.d.ts +2 -0
  115. package/dist/agent-chat/virtualize.type-guard.test.d.ts.map +1 -0
  116. package/dist/charts/primitives/ChartFrame.test.d.ts +2 -0
  117. package/dist/charts/primitives/ChartFrame.test.d.ts.map +1 -0
  118. package/dist/charts/utils/domain.test.d.ts +2 -0
  119. package/dist/charts/utils/domain.test.d.ts.map +1 -0
  120. package/dist/cli/check-theme.test.d.ts +2 -0
  121. package/dist/cli/check-theme.test.d.ts.map +1 -0
  122. package/dist/cli/help.test.d.ts +2 -0
  123. package/dist/cli/help.test.d.ts.map +1 -0
  124. package/dist/cli/index.d.ts.map +1 -1
  125. package/dist/cli/init-sync.test.d.ts +2 -0
  126. package/dist/cli/init-sync.test.d.ts.map +1 -0
  127. package/dist/cli/placement-engine.test.d.ts +2 -0
  128. package/dist/cli/placement-engine.test.d.ts.map +1 -0
  129. package/dist/cli/tarball-parity.test.d.ts +2 -0
  130. package/dist/cli/tarball-parity.test.d.ts.map +1 -0
  131. package/dist/cli/template-format.test.d.ts +2 -0
  132. package/dist/cli/template-format.test.d.ts.map +1 -0
  133. package/dist/cli/template-ledger.test.d.ts +2 -0
  134. package/dist/cli/template-ledger.test.d.ts.map +1 -0
  135. package/dist/cli/tokens-css.test.d.ts +2 -0
  136. package/dist/cli/tokens-css.test.d.ts.map +1 -0
  137. package/dist/content/article-actions.test.d.ts +2 -0
  138. package/dist/content/article-actions.test.d.ts.map +1 -0
  139. package/dist/content/article-model.test.d.ts +2 -0
  140. package/dist/content/article-model.test.d.ts.map +1 -0
  141. package/dist/content/block-split.test.d.ts +2 -0
  142. package/dist/content/block-split.test.d.ts.map +1 -0
  143. package/dist/content/fence-block.d.ts +37 -10
  144. package/dist/content/fence-block.d.ts.map +1 -1
  145. package/dist/content/fence-block.js +80 -14
  146. package/dist/content/heading-components.d.ts.map +1 -1
  147. package/dist/content/heading-components.js +7 -4
  148. package/dist/content/heading-components.test.d.ts +2 -0
  149. package/dist/content/heading-components.test.d.ts.map +1 -0
  150. package/dist/content/index.d.ts +6 -3
  151. package/dist/content/index.d.ts.map +1 -1
  152. package/dist/content/index.js +5 -1
  153. package/dist/content/markdown.d.ts +101 -14
  154. package/dist/content/markdown.d.ts.map +1 -1
  155. package/dist/content/markdown.js +141 -28
  156. package/dist/content/markdown.test.d.ts +2 -0
  157. package/dist/content/markdown.test.d.ts.map +1 -0
  158. package/dist/content/prose.module.css.test.d.ts +2 -0
  159. package/dist/content/prose.module.css.test.d.ts.map +1 -0
  160. package/dist/content/sanitize.d.ts +170 -0
  161. package/dist/content/sanitize.d.ts.map +1 -0
  162. package/dist/content/sanitize.js +178 -0
  163. package/dist/content/sanitize.test.d.ts +2 -0
  164. package/dist/content/sanitize.test.d.ts.map +1 -0
  165. package/dist/content/slug.test.d.ts +2 -0
  166. package/dist/content/slug.test.d.ts.map +1 -0
  167. package/dist/dashboard/stat-card.test.d.ts +2 -0
  168. package/dist/dashboard/stat-card.test.d.ts.map +1 -0
  169. package/dist/data/index.d.ts +5 -1
  170. package/dist/data/index.d.ts.map +1 -1
  171. package/dist/data/virtual-list.d.ts.map +1 -1
  172. package/dist/data/virtual.d.ts +2 -1
  173. package/dist/data/virtual.d.ts.map +1 -1
  174. package/dist/guard/check-source.test.d.ts +2 -0
  175. package/dist/guard/check-source.test.d.ts.map +1 -0
  176. package/dist/guard/guard-hook.test.d.ts +2 -0
  177. package/dist/guard/guard-hook.test.d.ts.map +1 -0
  178. package/dist/index.d.ts +4 -4
  179. package/dist/index.d.ts.map +1 -1
  180. package/dist/index.js +11 -1
  181. package/dist/provider/index.test.d.ts +2 -0
  182. package/dist/provider/index.test.d.ts.map +1 -0
  183. package/dist/provider/lab-theme.test.d.ts +2 -0
  184. package/dist/provider/lab-theme.test.d.ts.map +1 -0
  185. package/dist/router-tanstack/multi-search-param-store.test.d.ts +2 -0
  186. package/dist/router-tanstack/multi-search-param-store.test.d.ts.map +1 -0
  187. package/dist/styles.floor.test.d.ts +2 -0
  188. package/dist/styles.floor.test.d.ts.map +1 -0
  189. package/dist/surfaces.d.ts +5 -5
  190. package/dist/surfaces.d.ts.map +1 -1
  191. package/dist/surfaces.js +14 -4
  192. package/dist/theme/border-coverage.test.d.ts +18 -0
  193. package/dist/theme/border-coverage.test.d.ts.map +1 -0
  194. package/dist/theme/contrast.test.d.ts +2 -0
  195. package/dist/theme/contrast.test.d.ts.map +1 -0
  196. package/dist/theme/density-option.test.d.ts +2 -0
  197. package/dist/theme/density-option.test.d.ts.map +1 -0
  198. package/dist/theme/density-relations.test.d.ts +2 -0
  199. package/dist/theme/density-relations.test.d.ts.map +1 -0
  200. package/dist/theme/derive-theme.test.d.ts +2 -0
  201. package/dist/theme/derive-theme.test.d.ts.map +1 -0
  202. package/dist/theme/fonts-option.test.d.ts +2 -0
  203. package/dist/theme/fonts-option.test.d.ts.map +1 -0
  204. package/dist/theme/radius-option.test.d.ts +2 -0
  205. package/dist/theme/radius-option.test.d.ts.map +1 -0
  206. package/dist/theme/radius.test.d.ts +2 -0
  207. package/dist/theme/radius.test.d.ts.map +1 -0
  208. package/dist/theme/shadow-surfaces.test.d.ts +26 -0
  209. package/dist/theme/shadow-surfaces.test.d.ts.map +1 -0
  210. package/dist/theme/spacing.test.d.ts +2 -0
  211. package/dist/theme/spacing.test.d.ts.map +1 -0
  212. package/dist/theme/use-basalt-spacing.test.d.ts +2 -0
  213. package/dist/theme/use-basalt-spacing.test.d.ts.map +1 -0
  214. package/dist/theme-lab/derive-controls.test.d.ts +2 -0
  215. package/dist/theme-lab/derive-controls.test.d.ts.map +1 -0
  216. package/dist/theme-lab/derive-state.test.d.ts +2 -0
  217. package/dist/theme-lab/derive-state.test.d.ts.map +1 -0
  218. package/dist/theme-lab/theme-lab.test.d.ts +2 -0
  219. package/dist/theme-lab/theme-lab.test.d.ts.map +1 -0
  220. package/dist/tokens/build-fonts-css.test.d.ts +2 -0
  221. package/dist/tokens/build-fonts-css.test.d.ts.map +1 -0
  222. package/dist/tokens/build-radius-css.test.d.ts +2 -0
  223. package/dist/tokens/build-radius-css.test.d.ts.map +1 -0
  224. package/dist/tokens/density.test.d.ts +2 -0
  225. package/dist/tokens/density.test.d.ts.map +1 -0
  226. package/dist/tokens/derive-radius.test.d.ts +2 -0
  227. package/dist/tokens/derive-radius.test.d.ts.map +1 -0
  228. package/dist/tokens/derive.test.d.ts +2 -0
  229. package/dist/tokens/derive.test.d.ts.map +1 -0
  230. package/dist/tokens/palette.test.d.ts +2 -0
  231. package/dist/tokens/palette.test.d.ts.map +1 -0
  232. package/dist/vite-color-mix.test.d.ts +2 -0
  233. package/dist/vite-color-mix.test.d.ts.map +1 -0
  234. package/dist/vite.test.d.ts +2 -0
  235. package/dist/vite.test.d.ts.map +1 -0
  236. package/llms.txt +9 -9
  237. package/package.json +7 -2
  238. package/src/agent/adapter.test.ts +1640 -0
  239. package/src/agent/adapter.ts +945 -0
  240. package/src/agent/ai-sdk-transport.test.ts +117 -26
  241. package/src/agent/ai-sdk-transport.ts +8 -2
  242. package/src/agent/coalesce.test.ts +202 -9
  243. package/src/agent/coalesce.ts +126 -15
  244. package/src/agent/foreign.test.ts +5 -1
  245. package/src/agent/id.test.ts +101 -3
  246. package/src/agent/id.ts +118 -0
  247. package/src/agent/index.ts +8 -0
  248. package/src/agent/merge.test.ts +87 -0
  249. package/src/agent/merge.ts +57 -10
  250. package/src/agent/part-list.test.tsx +31 -1
  251. package/src/agent/part-list.tsx +77 -29
  252. package/src/agent/parts.test.ts +121 -4
  253. package/src/agent/parts.ts +78 -17
  254. package/src/agent/thread.test.ts +75 -2
  255. package/src/agent/thread.ts +25 -1
  256. package/src/agent/use-agent-stream.test.tsx +31 -5
  257. package/src/agent/use-agent-stream.ts +6 -3
  258. package/src/agent/use-agent-thread-runs.foreign.test.tsx +5 -0
  259. package/src/agent/use-agent-thread-runs.resume.test.tsx +16 -11
  260. package/src/agent/use-agent-thread-runs.test.tsx +356 -17
  261. package/src/agent/use-agent-thread-runs.ts +159 -23
  262. package/src/agent/use-agent-thread-runs.wedge.test.tsx +93 -7
  263. package/src/agent-chat/composer.test.tsx +924 -0
  264. package/src/agent-chat/composer.tsx +521 -52
  265. package/src/agent-chat/index.ts +13 -1
  266. package/src/agent-chat/message-affordances.ts +61 -0
  267. package/src/agent-chat/relative-time.test.ts +95 -0
  268. package/src/agent-chat/relative-time.ts +55 -0
  269. package/src/agent-chat/thread-detail-panel.tsx +3 -1
  270. package/src/agent-chat/thread-feed-row.test.tsx +510 -0
  271. package/src/agent-chat/thread-feed-row.tsx +274 -0
  272. package/src/agent-chat/thread-feed.test.tsx +241 -0
  273. package/src/agent-chat/thread-feed.tsx +119 -18
  274. package/src/agent-chat/thread-message.test.tsx +1078 -4
  275. package/src/agent-chat/thread-message.tsx +795 -42
  276. package/src/agent-chat/thread-outcome-card.tsx +1 -25
  277. package/src/agent-chat/thread-workspace.test.tsx +169 -0
  278. package/src/agent-chat/thread-workspace.tsx +70 -6
  279. package/src/agent-chat/virtualize.ts +132 -0
  280. package/src/agent-chat/virtualize.type-guard.test.ts +49 -0
  281. package/src/cli/index.ts +0 -1
  282. package/src/cli/template-format.test.ts +2 -2
  283. package/src/content/fence-block.tsx +242 -15
  284. package/src/content/heading-components.test.tsx +65 -0
  285. package/src/content/heading-components.tsx +26 -5
  286. package/src/content/index.ts +19 -3
  287. package/src/content/markdown.test.tsx +603 -0
  288. package/src/content/markdown.tsx +355 -35
  289. package/src/content/sanitize.test.ts +659 -0
  290. package/src/content/sanitize.ts +510 -0
  291. package/src/dashboard/stat-card.test.tsx +6 -1
  292. package/src/data/index.ts +5 -1
  293. package/src/data/virtual-list.tsx +2 -1
  294. package/src/data/virtual.ts +2 -1
  295. package/src/index.ts +18 -1
  296. package/src/surfaces.ts +13 -3
  297. package/src/theme/contrast.test.ts +1 -4
  298. package/src/theme/density-relations.test.ts +4 -2
  299. package/src/theme/fonts-option.test.ts +7 -1
  300. package/src/theme/shadow-surfaces.test.ts +21 -6
  301. package/src/theme/use-basalt-spacing.test.tsx +2 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-sdk-transport.test.d.ts","sourceRoot":"","sources":["../../src/agent/ai-sdk-transport.test.ts"],"names":[],"mappings":""}
@@ -12,10 +12,26 @@
12
12
  * wire, the SDK re-supplies `input` by reading it back off the stored invocation, and a merge that
13
13
  * replaces wholesale rather than accumulating would lose it outright.
14
14
  *
15
- * `approval`-bearing responses can arrive without a usable `toolCallId` (the SDK resolves them by
16
- * reverse lookup on `approval.id` instead) — this maintains an `approvalId -> toolCallId` side
17
- * index while folding and routes such a part through it. A part resolvable by neither key is kept
18
- * as-is rather than dropped; never silently discard a part.
15
+ * A settled tool part can arrive without a `toolCallId` at all — `approval-responded` plus the
16
+ * three terminal states all carry it as optional (see `MaybeIdentified` in `parts.ts`).
17
+ * `approval-responded` is where the WIRE itself omits it: the SDK's `tool-approval-response` chunk
18
+ * carries no `toolCallId`, only `approval.id`, so it is resolved by reverse lookup on `approval.id`
19
+ * instead — this maintains an `approvalId -> toolCallId` side index while folding and routes such a
20
+ * part through it. A part resolvable by neither key is kept as-is rather than dropped; never
21
+ * silently discard a part.
22
+ *
23
+ * The fold is strictly LEFT-TO-RIGHT with no backtracking: the side index only holds ids seen so
24
+ * far, so an unidentified `approval-responded` arriving BEFORE its `approval-requested` stays its
25
+ * own block and the later request opens a second one. That is deliberate, not an oversight —
26
+ * reconciling backwards would let the request (the later arrival) win under the "later state wins"
27
+ * rule and render an already-answered call as still pending, which is worse than two blocks in an
28
+ * ordering the wire never produces.
29
+ *
30
+ * **This function never throws.** It is public, it runs on the render path, and it folds data that
31
+ * came off a wire — so every anomaly (an unrecognized tool state, an unresolvable id) degrades to
32
+ * "keep the part as it arrived", announced in dev, rather than aborting the render. A throw here
33
+ * would turn one malformed chunk into a permanently dead transcript. Exhaustiveness over the tool
34
+ * states is still enforced, but at COMPILE time only (see `mergeToolPart`'s default branch).
19
35
  *
20
36
  * @example
21
37
  * import { coalesceParts } from 'basalt-ui/agent'
@@ -1 +1 @@
1
- {"version":3,"file":"coalesce.d.ts","sourceRoot":"","sources":["../../src/agent/coalesce.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAyC,MAAM,SAAS,CAAA;AA+C/E,wBAAgB,aAAa,CAAC,KAAK,SAAS,SAAS,EAAE,KAAK,EAAE,SAAS,KAAK,EAAE,GAAG,KAAK,EAAE,CA+CvF"}
1
+ {"version":3,"file":"coalesce.d.ts","sourceRoot":"","sources":["../../src/agent/coalesce.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,KAAK,EAAE,SAAS,EAAyC,MAAM,SAAS,CAAA;AA8I/E,wBAAgB,aAAa,CAAC,KAAK,SAAS,SAAS,EAAE,KAAK,EAAE,SAAS,KAAK,EAAE,GAAG,KAAK,EAAE,CA+CvF"}
@@ -2,27 +2,95 @@ function isTextLikeType(type) {
2
2
  return type === "text" || type === "reasoning";
3
3
  }
4
4
  function resolveToolCallId(part, toolCallIdByApprovalId) {
5
- if (typeof part.toolCallId === "string" && part.toolCallId.length > 0) return part.toolCallId;
5
+ if (part.toolCallId !== void 0) return part.toolCallId;
6
6
  const approval = "approval" in part ? part.approval : void 0;
7
7
  return approval !== void 0 ? toolCallIdByApprovalId.get(approval.id) : void 0;
8
8
  }
9
9
  function registerApprovalIndex(part, toolCallIdByApprovalId) {
10
10
  const approval = "approval" in part ? part.approval : void 0;
11
11
  if (approval === void 0) return;
12
- if (typeof part.toolCallId !== "string" || part.toolCallId.length === 0) return;
12
+ if (part.toolCallId === void 0) return;
13
13
  toolCallIdByApprovalId.set(approval.id, part.toolCallId);
14
14
  }
15
15
  function mergeToolPart(existing, next) {
16
16
  const existingInput = "input" in existing ? existing.input : void 0;
17
17
  const nextInput = "input" in next ? next.input : void 0;
18
18
  const input = nextInput !== void 0 ? nextInput : existingInput;
19
- const toolName = next.toolName.length > 0 ? next.toolName : existing.toolName;
20
- return {
21
- ...existing,
22
- ...next,
23
- toolName,
24
- ...input !== void 0 ? { input } : {}
19
+ const durationMs = next.durationMs ?? existing.durationMs;
20
+ const providerExecuted = next.providerExecuted ?? existing.providerExecuted;
21
+ const settledToolCallId = next.toolCallId ?? existing.toolCallId;
22
+ const common = {
23
+ id: next.id,
24
+ type: "tool",
25
+ toolName: next.toolName.length > 0 ? next.toolName : existing.toolName,
26
+ ...durationMs !== void 0 ? { durationMs } : {},
27
+ ...providerExecuted !== void 0 ? { providerExecuted } : {}
25
28
  };
29
+ switch (next.state) {
30
+ case "input-streaming":
31
+ return {
32
+ ...common,
33
+ toolCallId: next.toolCallId,
34
+ state: "input-streaming",
35
+ ...input !== void 0 ? { input } : {}
36
+ };
37
+ case "input-available":
38
+ return { ...common, toolCallId: next.toolCallId, state: "input-available", input };
39
+ case "approval-requested":
40
+ return {
41
+ ...common,
42
+ toolCallId: next.toolCallId,
43
+ state: "approval-requested",
44
+ input,
45
+ approval: next.approval
46
+ };
47
+ case "approval-responded":
48
+ return {
49
+ ...common,
50
+ ...settledToolCallId !== void 0 ? { toolCallId: settledToolCallId } : {},
51
+ state: "approval-responded",
52
+ input,
53
+ approval: next.approval
54
+ };
55
+ case "output-available":
56
+ return {
57
+ ...common,
58
+ ...settledToolCallId !== void 0 ? { toolCallId: settledToolCallId } : {},
59
+ state: "output-available",
60
+ input,
61
+ output: next.output,
62
+ ...next.preliminary !== void 0 ? { preliminary: next.preliminary } : {},
63
+ ...next.approval !== void 0 ? { approval: next.approval } : {}
64
+ };
65
+ case "output-error":
66
+ return {
67
+ ...common,
68
+ ...settledToolCallId !== void 0 ? { toolCallId: settledToolCallId } : {},
69
+ state: "output-error",
70
+ errorText: next.errorText,
71
+ ...input !== void 0 ? { input } : {},
72
+ ...next.rawInput !== void 0 ? { rawInput: next.rawInput } : {},
73
+ ...next.approval !== void 0 ? { approval: next.approval } : {}
74
+ };
75
+ case "output-denied":
76
+ return {
77
+ ...common,
78
+ ...settledToolCallId !== void 0 ? { toolCallId: settledToolCallId } : {},
79
+ state: "output-denied",
80
+ input,
81
+ approval: next.approval
82
+ };
83
+ default: {
84
+ const unhandled = next;
85
+ const fallback = unhandled;
86
+ if (process.env["NODE_ENV"] !== "production") {
87
+ console.warn(
88
+ `[basalt] coalesceParts: unrecognized tool state ${JSON.stringify(fallback.state)} \u2014 kept, merged best-effort`
89
+ );
90
+ }
91
+ return { ...fallback, ...common };
92
+ }
93
+ }
26
94
  }
27
95
  function coalesceParts(parts) {
28
96
  const out = [];
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=coalesce.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coalesce.test.d.ts","sourceRoot":"","sources":["../../src/agent/coalesce.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=foreign.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foreign.test.d.ts","sourceRoot":"","sources":["../../src/agent/foreign.test.ts"],"names":[],"mappings":""}
@@ -1,3 +1,81 @@
1
+ /**
2
+ * Id-minting helpers shared across the agent layer: `mintThreadId` (a low-stakes client-side id —
3
+ * threads, run/chat namespaces — used by `./thread`'s `createThreadsStore`, `./adapter`'s
4
+ * `createAdapterThreadsStore`/`threadsStoreAdapterContract`, `./use-agent-stream`'s per-send run
5
+ * id, and `aiSdkTransport`'s fixed chat id), `mintMessageId` (the higher-stakes id `appendMessage`
6
+ * treats as its only idempotency key), and `withPartIds` (stamps sequence ids onto streamed
7
+ * parts). Grouped here because they're the id-minting leaves every store/hook/transport in this
8
+ * layer sits above — not because they share a mechanism (`mintMessageId` deliberately diverges
9
+ * from `mintThreadId` on rung 3; see its own doc).
10
+ */
11
+ /**
12
+ * Mints a client-side id for a LOW-COLLISION-COST use: a thread id, a per-send run-id namespace, a
13
+ * fixed chat id — anything minted client-side that is NOT the idempotency key some write depends
14
+ * on. `crypto.randomUUID()` exists only in SECURE CONTEXTS (HTTPS or localhost) — a consumer served
15
+ * over plain HTTP on a LAN hostname (a homelab dashboard on a bare IP, a staging box) would
16
+ * otherwise have `create()`, about the most basic user action a thread store has, throw a plain
17
+ * TypeError. A store used from a browser must not throw on a user gesture — the same principle
18
+ * already applied twice this release (`spliceText` clamps rather than throws, `coalesceParts`
19
+ * degrades rather than throws).
20
+ *
21
+ * Fallback chain, each rung reached only when the one above it is unavailable:
22
+ * 1. `crypto.randomUUID()` — cryptographically random, RFC 4122 UUID. The normal path.
23
+ * 2. `crypto.getRandomValues()` — still cryptographically random; hand-assembled into a UUIDv4
24
+ * via `assembleUuidV4`.
25
+ * 3. No usable `crypto` at all (missing entirely, or missing both methods above — an old WebView,
26
+ * an SSR shim). NOT cryptographically random and NOT collision-resistant in general. This rung
27
+ * exists solely so a caller never throws on such a host. It is acceptable ONLY because an id
28
+ * minted here is minted a handful of times per client session and is not the idempotency key
29
+ * any write depends on — `ThreadsStoreAdapter.appendMessage`'s idempotency key is the MESSAGE
30
+ * id (see `mintMessageId`), not this one, so a collision on this rung costs a locally
31
+ * merged/duplicated thread (or two runs sharing a part-id namespace), not silent data loss on
32
+ * the append path. Do not reuse this rung for anything with a higher collision cost than that
33
+ * — see `mintMessageId` for the id that needs one.
34
+ */
35
+ export declare function mintThreadId(): string;
36
+ /**
37
+ * Mints a message id — the value `ThreadsStoreAdapter.appendMessage` treats as its ONLY
38
+ * idempotency key (see that method's contract doc in `./adapter`). Shares `mintThreadId`'s rungs
39
+ * 1-2 (`crypto.randomUUID()`, then `crypto.getRandomValues()` assembled into a UUIDv4) — both are
40
+ * cryptographically random and collision-resistant, and rung 2 is the fix that actually matters in
41
+ * practice (a plain-HTTP/LAN host still has `getRandomValues`; only `randomUUID` is secure-context-
42
+ * gated).
43
+ *
44
+ * Rung 3 deliberately DIVERGES from `mintThreadId`: it THROWS instead of degrading to a
45
+ * non-random fallback. A thread id's rung-3 collision costs a locally merged/duplicated thread —
46
+ * annoying, recoverable, visible. A message id's collision cost is different IN KIND, not degree:
47
+ * `appendMessage` is idempotent on this id, so two messages that collide are not two rows, they
48
+ * are ONE — the second write silently no-ops and its content is gone, with nothing downstream able
49
+ * to tell. Degrading here would trade a loud, debuggable failure for silent, permanent data loss.
50
+ *
51
+ * This deliberately diverges from this layer's other standing rule — render-path code degrades,
52
+ * never throws (`spliceText` clamps, `coalesceParts` degrades, a consumer fence renderer must not
53
+ * take the message down) — because minting a message id is NOT render-path code. It runs once, in
54
+ * the write path that constructs a message before handing it to a store (`start()`,
55
+ * `consumeAndFinalize`'s finalize step, `finalizeStop`, `aiSdkTransport`'s per-turn user message),
56
+ * never inside the per-chunk accumulation/render loop those functions guard. A host with no usable
57
+ * `crypto` at all (missing both `randomUUID` and `getRandomValues`) is vanishingly rare — no
58
+ * runtime with a DOM/fetch surface worth targeting ships without a Crypto object — and an uncaught
59
+ * throw here is a loud, debuggable failure rather than a message silently vanishing with no trace,
60
+ * but WHERE it surfaces differs by call site (audited, not assumed — this paragraph previously
61
+ * claimed a single shared catch handled all of them, which was false):
62
+ * - `consumeAndFinalize`'s own assistantMessage mint runs inside that function's try/catch, so a
63
+ * throw there is correctly turned into `onFailureStatus` ('error' from `start()`, 'interrupted'
64
+ * from the mount-time resume) — a visibly stuck-then-settled run, as originally claimed.
65
+ * - `useAgentThreadRuns.start()`'s userMessage mint runs BEFORE any state is touched
66
+ * (`appendMessage`/`setStatus`/the controller registration all come after it), so a throw there
67
+ * is a true no-op that propagates synchronously out of `start()` itself — not a stuck run at
68
+ * all, since nothing ever started. See `UseAgentThreadRunsReturn.start`'s `@throws` doc.
69
+ * - `finalizeStop`'s stoppedMessage mint is wrapped in its own try/catch precisely so a throw
70
+ * here cannot wedge the thread at 'streaming' forever (stop() has already torn down this run's
71
+ * bookkeeping by the time finalizeStop runs, so a second stop() is always a no-op) — see
72
+ * `finalizeStop`'s doc for the guarded behavior and the resulting 'error' status.
73
+ * A stuck spinner is debuggable; silently discarded chat history is not — that half of the
74
+ * original claim stands regardless of which of the three paths above catches it.
75
+ *
76
+ * @throws {Error} when neither `crypto.randomUUID` nor `crypto.getRandomValues` is available.
77
+ */
78
+ export declare function mintMessageId(): string;
1
79
  /**
2
80
  * withPartIds — stamps a stable id onto every draft part arriving without one.
3
81
  *
@@ -1 +1 @@
1
- {"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/agent/id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAuB,WAAW,CAAC,KAAK,SAAS;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,EAC9D,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,GAC5B,cAAc,CAAC,KAAK,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAUxC"}
1
+ {"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/agent/id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAmBH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAQrC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAYtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAuB,WAAW,CAAC,KAAK,SAAS;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,EAC9D,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,GAC5B,cAAc,CAAC,KAAK,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAUxC"}
package/dist/agent/id.js CHANGED
@@ -1,3 +1,31 @@
1
+ function assembleUuidV4(bytes) {
2
+ const hex = Array.from({ length: 16 }, (_, i) => {
3
+ const raw = bytes.at(i) ?? 0;
4
+ const byte = i === 6 ? raw & 15 | 64 : i === 8 ? raw & 63 | 128 : raw;
5
+ return byte.toString(16).padStart(2, "0");
6
+ }).join("");
7
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
8
+ }
9
+ function mintThreadId() {
10
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
11
+ return crypto.randomUUID();
12
+ }
13
+ if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
14
+ return assembleUuidV4(crypto.getRandomValues(new Uint8Array(16)));
15
+ }
16
+ return `id-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
17
+ }
18
+ function mintMessageId() {
19
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
20
+ return crypto.randomUUID();
21
+ }
22
+ if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
23
+ return assembleUuidV4(crypto.getRandomValues(new Uint8Array(16)));
24
+ }
25
+ throw new Error(
26
+ "mintMessageId: no usable crypto (both randomUUID and getRandomValues are unavailable) \u2014 refusing to mint a non-random message id, since ThreadsStoreAdapter.appendMessage treats this id as its only idempotency key and a collision would silently drop a message."
27
+ );
28
+ }
1
29
  async function* withPartIds(runId, source) {
2
30
  let n = 0;
3
31
  for await (const part of source) {
@@ -10,5 +38,7 @@ async function* withPartIds(runId, source) {
10
38
  }
11
39
  }
12
40
  export {
41
+ mintMessageId,
42
+ mintThreadId,
13
43
  withPartIds
14
44
  };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=id.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id.test.d.ts","sourceRoot":"","sources":["../../src/agent/id.test.ts"],"names":[],"mappings":""}
@@ -55,4 +55,6 @@ export { createThreadsStore } from './thread.js';
55
55
  export type { AgentThread, ThreadStatus, ThreadsStore, ThreadsStoreOptions } from './thread.js';
56
56
  export { heuristicOutcome } from './outcome.js';
57
57
  export type { AgentOutcome, OutcomeResolver } from './outcome.js';
58
+ export { createAdapterThreadsStore, threadsStoreAdapterContract } from './adapter.js';
59
+ export type { AdapterThreadsStoreOptions, ThreadsStoreAdapter, ThreadsStoreAdapterContractCase, } from './adapter.js';
58
60
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAGH,YAAY,EACV,SAAS,EACT,cAAc,EACd,OAAO,EACP,SAAS,EACT,QAAQ,EACR,aAAa,EACb,YAAY,EACZ,aAAa,EACb,UAAU,EACV,SAAS,GACV,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAG9F,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAGnC,OAAO,EAAE,WAAW,EAAE,MAAM,MAAM,CAAA;AAGlC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAG1C,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChE,YAAY,EACV,WAAW,EACX,cAAc,EACd,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,YAAY,GACb,MAAM,WAAW,CAAA;AAGlB,YAAY,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAC1E,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAGxD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAG/E,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAG5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,YAAY,EACV,cAAc,EACd,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,yBAAyB,CAAA;AAGhC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,YAAY,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AAGjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAA;AAClD,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA;AAGvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAC7C,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAG5F,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAC5C,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAGH,YAAY,EACV,SAAS,EACT,cAAc,EACd,OAAO,EACP,SAAS,EACT,QAAQ,EACR,aAAa,EACb,YAAY,EACZ,aAAa,EACb,UAAU,EACV,SAAS,GACV,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAG9F,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAGnC,OAAO,EAAE,WAAW,EAAE,MAAM,MAAM,CAAA;AAGlC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAG1C,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChE,YAAY,EACV,WAAW,EACX,cAAc,EACd,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,YAAY,GACb,MAAM,WAAW,CAAA;AAGlB,YAAY,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAC1E,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAGxD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAG/E,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAG5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,YAAY,EACV,cAAc,EACd,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,yBAAyB,CAAA;AAGhC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACvD,YAAY,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AAGjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAA;AAClD,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA;AAGvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAC7C,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAG5F,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAC5C,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAG9D,OAAO,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAA;AAClF,YAAY,EACV,0BAA0B,EAC1B,mBAAmB,EACnB,+BAA+B,GAChC,MAAM,WAAW,CAAA"}
@@ -12,12 +12,14 @@ import { BasaltStickToBottom } from "./stick-to-bottom.js";
12
12
  import { createChatHistoryStore } from "./history.js";
13
13
  import { createThreadsStore } from "./thread.js";
14
14
  import { heuristicOutcome } from "./outcome.js";
15
+ import { createAdapterThreadsStore, threadsStoreAdapterContract } from "./adapter.js";
15
16
  export {
16
17
  BasaltStickToBottom,
17
18
  PartList,
18
19
  TERMINAL_TOOL_STATES,
19
20
  aiSdkTransport,
20
21
  coalesceParts,
22
+ createAdapterThreadsStore,
21
23
  createChatHistoryStore,
22
24
  createThreadsStore,
23
25
  definePartRenderers,
@@ -29,6 +31,7 @@ export {
29
31
  mergePart,
30
32
  narrowAgentPart,
31
33
  parseAgentPart,
34
+ threadsStoreAdapterContract,
32
35
  useAgentStream,
33
36
  useAgentThreadRuns,
34
37
  withPartIds
@@ -1 +1 @@
1
- {"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../src/agent/merge.ts"],"names":[],"mappings":"AA+BA;iGACiG;AACjG,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAgBrE,wBAAgB,SAAS,CAAC,KAAK,SAAS,QAAQ,EAAE,KAAK,EAAE,SAAS,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,GAAG,KAAK,EAAE,CAiB/F"}
1
+ {"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../src/agent/merge.ts"],"names":[],"mappings":"AAkCA;iGACiG;AACjG,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAoDrE,wBAAgB,SAAS,CAAC,KAAK,SAAS,QAAQ,EAAE,KAAK,EAAE,SAAS,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,GAAG,KAAK,EAAE,CAyB/F"}
@@ -1,19 +1,31 @@
1
1
  function isTextLikeType(type) {
2
2
  return type === "text" || type === "reasoning";
3
3
  }
4
- function spliceText(existing, next) {
5
- const offset = next.offset ?? existing.text.length;
6
- return existing.text.slice(0, offset) + next.text + existing.text.slice(offset + next.text.length);
4
+ function spliceText(existingText, next) {
5
+ const offset = clampOffset(next.offset ?? existingText.length, existingText.length);
6
+ return existingText.slice(0, offset) + next.text + existingText.slice(offset + next.text.length);
7
+ }
8
+ function clampOffset(offset, length) {
9
+ const clamped = Number.isFinite(offset) ? Math.min(Math.max(offset, 0), length) : length;
10
+ if (clamped !== offset && process.env["NODE_ENV"] !== "production") {
11
+ console.warn(
12
+ `[basalt] mergePart: text offset ${offset} out of range [0, ${length}] \u2014 clamped to ${clamped}`
13
+ );
14
+ }
15
+ return clamped;
7
16
  }
8
17
  function mergePart(parts, next) {
9
18
  const index = parts.findIndex((part) => part.id === next.id);
10
- if (index === -1) return [...parts, next];
11
- const existing = parts[index];
12
- const spliceable = existing.type === next.type && isTextLikeType(next.type);
19
+ const existing = index === -1 ? void 0 : parts[index];
20
+ const spliceable = isTextLikeType(next.type) && (existing === void 0 || existing.type === next.type);
13
21
  const merged = spliceable ? {
14
22
  ...next,
15
- text: spliceText(existing, next)
23
+ text: spliceText(
24
+ existing === void 0 ? "" : existing.text,
25
+ next
26
+ )
16
27
  } : next;
28
+ if (existing === void 0) return [...parts, merged];
17
29
  const out = parts.slice();
18
30
  out[index] = merged;
19
31
  return out;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=merge.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.test.d.ts","sourceRoot":"","sources":["../../src/agent/merge.test.ts"],"names":[],"mappings":""}
@@ -1,41 +1,41 @@
1
1
  import type { JSX } from 'react';
2
2
  import type { AgentPart } from './parts.js';
3
- export type TextPartRenderer<TPart extends AgentPart = AgentPart> = (props: {
4
- part: Extract<TPart, {
5
- type: 'text';
6
- }>;
7
- index: number;
8
- }) => JSX.Element | null;
9
- export type ReasoningPartRenderer<TPart extends AgentPart = AgentPart> = (props: {
10
- part: Extract<TPart, {
11
- type: 'reasoning';
12
- }>;
13
- index: number;
14
- }) => JSX.Element | null;
15
- export type ToolCallPartRenderer<TPart extends AgentPart = AgentPart> = (props: {
16
- part: Extract<TPart, {
17
- type: 'tool';
18
- }>;
19
- index: number;
20
- }) => JSX.Element | null;
21
- export type SourcePartRenderer<TPart extends AgentPart = AgentPart> = (props: {
22
- part: Extract<TPart, {
23
- type: 'source';
24
- }>;
25
- index: number;
26
- }) => JSX.Element | null;
27
- export type ErrorPartRenderer<TPart extends AgentPart = AgentPart> = (props: {
28
- part: Extract<TPart, {
29
- type: 'error';
30
- }>;
3
+ /**
4
+ * The per-renderer argument. `settled` is `false` only while the message these parts belong to is
5
+ * still streaming — it converges this map toward the `PartRenderContext` a foreign renderer
6
+ * already receives (`./foreign`), rather than adding a third shape. Additive and
7
+ * source-compatible: an existing `({ part }) => …` renderer simply ignores it.
8
+ *
9
+ * Settlement is MONOTONE — a message that has settled never un-settles.
10
+ */
11
+ type PartRenderArgs<TPart> = {
12
+ part: TPart;
31
13
  index: number;
32
- }) => JSX.Element | null;
14
+ settled: boolean;
15
+ };
16
+ export type TextPartRenderer<TPart extends AgentPart = AgentPart> = (props: PartRenderArgs<Extract<TPart, {
17
+ type: 'text';
18
+ }>>) => JSX.Element | null;
19
+ export type ReasoningPartRenderer<TPart extends AgentPart = AgentPart> = (props: PartRenderArgs<Extract<TPart, {
20
+ type: 'reasoning';
21
+ }>>) => JSX.Element | null;
22
+ export type ToolCallPartRenderer<TPart extends AgentPart = AgentPart> = (props: PartRenderArgs<Extract<TPart, {
23
+ type: 'tool';
24
+ }>>) => JSX.Element | null;
25
+ export type SourcePartRenderer<TPart extends AgentPart = AgentPart> = (props: PartRenderArgs<Extract<TPart, {
26
+ type: 'source';
27
+ }>>) => JSX.Element | null;
28
+ export type ErrorPartRenderer<TPart extends AgentPart = AgentPart> = (props: PartRenderArgs<Extract<TPart, {
29
+ type: 'error';
30
+ }>>) => JSX.Element | null;
33
31
  /**
34
32
  * Partial map of per-type renderers. Any omitted key falls back to the headless default.
35
33
  *
36
34
  * @example
37
35
  * const renderers: Partial<AgentPartRenderers> = {
38
- * text: ({ part }) => <Markdown streaming density="chat">{part.text}</Markdown>,
36
+ * text: ({ part, settled }) => (
37
+ * <Markdown streaming={!settled} contentTrust="untrusted" density="chat">{part.text}</Markdown>
38
+ * ),
39
39
  * }
40
40
  */
41
41
  export type AgentPartRenderers<TPart extends AgentPart = AgentPart> = {
@@ -50,10 +50,17 @@ export type PartListProps<TPart extends AgentPart = AgentPart> = {
50
50
  readonly parts: TPart[];
51
51
  /**
52
52
  * Override individual part renderers. Omitted keys fall back to the headless defaults.
53
- * Pass `{ text: ({ part }) => <Markdown streaming density="chat">{part.text}</Markdown> }` to
54
- * enable rich markdown rendering.
53
+ * Pass `{ text: ({ part, settled }) => <Markdown streaming={!settled} contentTrust="untrusted" density="chat">{part.text}</Markdown> }`
54
+ * to enable rich markdown rendering.
55
55
  */
56
56
  readonly components?: Partial<AgentPartRenderers<TPart>>;
57
+ /**
58
+ * `false` while the message these parts belong to is still streaming — forwarded to every
59
+ * renderer so a text renderer can drop out of streaming mode (and a fenced mermaid/diagram can
60
+ * upgrade) once the turn finishes. Defaults to `true`: a caller that knows nothing about a run
61
+ * is rendering a finished list, and un-settled must be opted into by the thing that does know.
62
+ */
63
+ readonly settled?: boolean;
57
64
  };
58
65
  /**
59
66
  * Renders a list of AgentPart values via an exhaustive switch over part.type.
@@ -69,7 +76,15 @@ export type PartListProps<TPart extends AgentPart = AgentPart> = {
69
76
  * // With basalt's own Markdown for the text renderer:
70
77
  * import { PartList } from 'basalt-ui/agent'
71
78
  * import { Markdown } from 'basalt-ui/content'
72
- * <PartList parts={parts} components={{ text: ({ part }) => <Markdown streaming density="chat">{part.text}</Markdown> }} />
79
+ * <PartList
80
+ * parts={parts}
81
+ * components={{
82
+ * text: ({ part, settled }) => (
83
+ * <Markdown streaming={!settled} contentTrust="untrusted" density="chat">{part.text}</Markdown>
84
+ * ),
85
+ * }}
86
+ * />
73
87
  */
74
- export declare function PartList<TPart extends AgentPart = AgentPart>({ parts, components, }: PartListProps<TPart>): JSX.Element;
88
+ export declare function PartList<TPart extends AgentPart = AgentPart>({ parts, components, settled, }: PartListProps<TPart>): JSX.Element;
89
+ export {};
75
90
  //# sourceMappingURL=part-list.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"part-list.d.ts","sourceRoot":"","sources":["../../src/agent/part-list.tsx"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAGhC,OAAO,KAAK,EACV,SAAS,EAMV,MAAM,SAAS,CAAA;AAIhB,MAAM,MAAM,gBAAgB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAAC,KAAK,EAAE;IAC1E,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtC,KAAK,EAAE,MAAM,CAAA;CACd,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAExB,MAAM,MAAM,qBAAqB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAAC,KAAK,EAAE;IAC/E,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,WAAW,CAAA;KAAE,CAAC,CAAA;IAC3C,KAAK,EAAE,MAAM,CAAA;CACd,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAExB,MAAM,MAAM,oBAAoB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAAC,KAAK,EAAE;IAC9E,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtC,KAAK,EAAE,MAAM,CAAA;CACd,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAExB,MAAM,MAAM,kBAAkB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAAC,KAAK,EAAE;IAC5E,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAA;IACxC,KAAK,EAAE,MAAM,CAAA;CACd,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAExB,MAAM,MAAM,iBAAiB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAAC,KAAK,EAAE;IAC3E,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IACvC,KAAK,EAAE,MAAM,CAAA;CACd,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAExB;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI;IACpE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC,KAAK,CAAC,CAAA;IACtC,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAA;IAChD,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAA;IAC1C,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAA;IAC1C,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC,KAAK,CAAC,CAAA;CACzC,CAAA;AAqJD,MAAM,MAAM,aAAa,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI;IAC/D,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,CAAA;IACvB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAA;CACzD,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,EAAE,EAC5D,KAAK,EACL,UAAU,GACX,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,OAAO,CAgEpC"}
1
+ {"version":3,"file":"part-list.d.ts","sourceRoot":"","sources":["../../src/agent/part-list.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAGhC,OAAO,KAAK,EACV,SAAS,EAMV,MAAM,SAAS,CAAA;AAIhB;;;;;;;GAOG;AACH,KAAK,cAAc,CAAC,KAAK,IAAI;IAC3B,IAAI,EAAE,KAAK,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,gBAAgB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CAClE,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,KACpD,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAEvB,MAAM,MAAM,qBAAqB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CACvE,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC,KACzD,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAEvB,MAAM,MAAM,oBAAoB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CACtE,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,KACpD,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAEvB,MAAM,MAAM,kBAAkB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CACpE,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAC,KACtD,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAEvB,MAAM,MAAM,iBAAiB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI,CACnE,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC,KACrD,GAAG,CAAC,OAAO,GAAG,IAAI,CAAA;AAEvB;;;;;;;;;GASG;AACH,MAAM,MAAM,kBAAkB,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI;IACpE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC,KAAK,CAAC,CAAA;IACtC,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAA;IAChD,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAA;IAC1C,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAA;IAC1C,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC,KAAK,CAAC,CAAA;CACzC,CAAA;AAqJD,MAAM,MAAM,aAAa,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,IAAI;IAC/D,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,CAAA;IACvB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAA;IACxD;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAC3B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,QAAQ,CAAC,KAAK,SAAS,SAAS,GAAG,SAAS,EAAE,EAC5D,KAAK,EACL,UAAU,EACV,OAAc,GACf,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,OAAO,CAoFpC"}
@@ -107,7 +107,8 @@ const DEFAULT_RENDERERS = {
107
107
  };
108
108
  function PartList({
109
109
  parts,
110
- components
110
+ components,
111
+ settled = true
111
112
  }) {
112
113
  const renderers = useMemo(
113
114
  () => ({ ...DEFAULT_RENDERERS, ...components }),
@@ -119,23 +120,58 @@ function PartList({
119
120
  return null;
120
121
  case "text": {
121
122
  const Render = renderers.text;
122
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Render, { part, index }) }, part.id);
123
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
124
+ Render,
125
+ {
126
+ part,
127
+ index,
128
+ settled
129
+ }
130
+ ) }, part.id);
123
131
  }
124
132
  case "reasoning": {
125
133
  const Render = renderers.reasoning;
126
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Render, { part, index }) }, part.id);
134
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
135
+ Render,
136
+ {
137
+ part,
138
+ index,
139
+ settled
140
+ }
141
+ ) }, part.id);
127
142
  }
128
143
  case "tool": {
129
144
  const Render = renderers.tool;
130
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Render, { part, index }) }, part.id);
145
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
146
+ Render,
147
+ {
148
+ part,
149
+ index,
150
+ settled
151
+ }
152
+ ) }, part.id);
131
153
  }
132
154
  case "source": {
133
155
  const Render = renderers.source;
134
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Render, { part, index }) }, part.id);
156
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
157
+ Render,
158
+ {
159
+ part,
160
+ index,
161
+ settled
162
+ }
163
+ ) }, part.id);
135
164
  }
136
165
  case "error": {
137
166
  const Render = renderers.error;
138
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Render, { part, index }) }, part.id);
167
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
168
+ Render,
169
+ {
170
+ part,
171
+ index,
172
+ settled
173
+ }
174
+ ) }, part.id);
139
175
  }
140
176
  default:
141
177
  return assertNever(part);
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=part-list.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"part-list.test.d.ts","sourceRoot":"","sources":["../../src/agent/part-list.test.tsx"],"names":[],"mappings":""}