@flamingo-stack/openframe-frontend-core 0.0.488 → 0.0.490-1506.3944.1

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 (311) hide show
  1. package/dist/chat-protocol/decode.d.ts +58 -0
  2. package/dist/chat-protocol/decode.d.ts.map +1 -0
  3. package/dist/chat-protocol/encode.d.ts +44 -0
  4. package/dist/chat-protocol/encode.d.ts.map +1 -0
  5. package/dist/chat-protocol/env-flag.d.ts +35 -0
  6. package/dist/chat-protocol/env-flag.d.ts.map +1 -0
  7. package/dist/chat-protocol/events.d.ts +196 -0
  8. package/dist/chat-protocol/events.d.ts.map +1 -0
  9. package/dist/chat-protocol/frames.d.ts +213 -0
  10. package/dist/chat-protocol/frames.d.ts.map +1 -0
  11. package/dist/chat-protocol/index.cjs +494 -0
  12. package/dist/chat-protocol/index.cjs.map +1 -0
  13. package/dist/chat-protocol/index.d.ts +18 -0
  14. package/dist/chat-protocol/index.d.ts.map +1 -0
  15. package/dist/chat-protocol/index.js +478 -0
  16. package/dist/chat-protocol/index.js.map +1 -0
  17. package/dist/chat-protocol/ip-normalize.d.ts +44 -0
  18. package/dist/chat-protocol/ip-normalize.d.ts.map +1 -0
  19. package/dist/chat-protocol/nats-decoder.d.ts +26 -0
  20. package/dist/chat-protocol/nats-decoder.d.ts.map +1 -0
  21. package/dist/{chunk-AJNDDXKC.js → chunk-4NWARV5I.js} +3 -3
  22. package/dist/{chunk-GQX6LFFA.cjs → chunk-5CJR2GZ4.cjs} +42 -42
  23. package/dist/{chunk-GQX6LFFA.cjs.map → chunk-5CJR2GZ4.cjs.map} +1 -1
  24. package/dist/{chunk-EUYIFALO.cjs → chunk-5EYRXSY5.cjs} +14 -14
  25. package/dist/{chunk-EUYIFALO.cjs.map → chunk-5EYRXSY5.cjs.map} +1 -1
  26. package/dist/{chunk-5RXKOGH6.cjs → chunk-5LMGFI4J.cjs} +7011 -4777
  27. package/dist/chunk-5LMGFI4J.cjs.map +1 -0
  28. package/dist/{chunk-KFQXO7QB.js → chunk-6GT557KN.js} +1 -1
  29. package/dist/chunk-6GT557KN.js.map +1 -0
  30. package/dist/{chunk-77DFPD35.js → chunk-BE6DK7QY.js} +12267 -10033
  31. package/dist/chunk-BE6DK7QY.js.map +1 -0
  32. package/dist/{chunk-A7IPON2V.cjs → chunk-BIKOJ4FH.cjs} +5 -5
  33. package/dist/{chunk-A7IPON2V.cjs.map → chunk-BIKOJ4FH.cjs.map} +1 -1
  34. package/dist/{chunk-HQYJINFV.js → chunk-CORIBKHG.js} +4 -4
  35. package/dist/{chunk-YEEHB4T6.cjs → chunk-CSYSYJCF.cjs} +40 -40
  36. package/dist/{chunk-YEEHB4T6.cjs.map → chunk-CSYSYJCF.cjs.map} +1 -1
  37. package/dist/{chunk-SIKCXKGB.cjs → chunk-DSDZ5T4P.cjs} +7 -7
  38. package/dist/{chunk-SIKCXKGB.cjs.map → chunk-DSDZ5T4P.cjs.map} +1 -1
  39. package/dist/{chunk-PNXNWGBY.js → chunk-GTWO5NAY.js} +2 -2
  40. package/dist/{chunk-SCCTJ2UI.js → chunk-HSACATCD.js} +4 -4
  41. package/dist/{chunk-PECGZWQR.cjs → chunk-KP5KRRD6.cjs} +3 -3
  42. package/dist/{chunk-PECGZWQR.cjs.map → chunk-KP5KRRD6.cjs.map} +1 -1
  43. package/dist/{chunk-2B6773QM.cjs → chunk-LILQJ3UZ.cjs} +1 -1
  44. package/dist/chunk-LILQJ3UZ.cjs.map +1 -0
  45. package/dist/{chunk-RZSA6MZL.cjs → chunk-NWX7MMF5.cjs} +15 -15
  46. package/dist/{chunk-RZSA6MZL.cjs.map → chunk-NWX7MMF5.cjs.map} +1 -1
  47. package/dist/{chunk-V3CNNMY3.cjs → chunk-P4WTZU6L.cjs} +18 -18
  48. package/dist/{chunk-V3CNNMY3.cjs.map → chunk-P4WTZU6L.cjs.map} +1 -1
  49. package/dist/{chunk-KLYHZRAD.js → chunk-PB3BTZDU.js} +2 -2
  50. package/dist/{chunk-FZOBQG6M.cjs → chunk-Q6E7VJ4W.cjs} +37 -36
  51. package/dist/chunk-Q6E7VJ4W.cjs.map +1 -0
  52. package/dist/{chunk-EYEIVYYF.cjs → chunk-QSCHCZL3.cjs} +7 -7
  53. package/dist/{chunk-EYEIVYYF.cjs.map → chunk-QSCHCZL3.cjs.map} +1 -1
  54. package/dist/{chunk-YJSKQ2ZV.js → chunk-RQLBHKXN.js} +3 -3
  55. package/dist/{chunk-CWQACI4Z.js → chunk-RW2LPL72.js} +2 -2
  56. package/dist/{chunk-DX6AOHNU.cjs → chunk-S2NHJDKQ.cjs} +77 -77
  57. package/dist/{chunk-DX6AOHNU.cjs.map → chunk-S2NHJDKQ.cjs.map} +1 -1
  58. package/dist/{chunk-AHDJMEEN.js → chunk-T2EGSZFN.js} +9 -8
  59. package/dist/chunk-T2EGSZFN.js.map +1 -0
  60. package/dist/{chunk-S52LJNMU.js → chunk-TKN5667N.js} +13 -13
  61. package/dist/chunk-TKN5667N.js.map +1 -0
  62. package/dist/{chunk-3BFX6HJM.js → chunk-UUYRJPGJ.js} +6 -6
  63. package/dist/{chunk-F6BYUO5U.js → chunk-VHG6AX5V.js} +3 -3
  64. package/dist/{chunk-FOQV6RCO.cjs → chunk-VQ523ORK.cjs} +29 -29
  65. package/dist/{chunk-FOQV6RCO.cjs.map → chunk-VQ523ORK.cjs.map} +1 -1
  66. package/dist/{chunk-PXF5J24T.js → chunk-XQ2B74SI.js} +2 -2
  67. package/dist/{chunk-H7JX2BG7.cjs → chunk-YHOIAA2E.cjs} +76 -76
  68. package/dist/chunk-YHOIAA2E.cjs.map +1 -0
  69. package/dist/{chunk-LLWBPPZ4.js → chunk-YZ2OIYQ3.js} +6 -6
  70. package/dist/components/case-studies/index.cjs +10 -10
  71. package/dist/components/case-studies/index.js +4 -4
  72. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  73. package/dist/components/chat/chat-message-list.d.ts.map +1 -1
  74. package/dist/components/chat/hooks/index.d.ts +0 -1
  75. package/dist/components/chat/hooks/index.d.ts.map +1 -1
  76. package/dist/components/chat/hooks/use-chat-history-hydration.d.ts +47 -0
  77. package/dist/components/chat/hooks/use-chat-history-hydration.d.ts.map +1 -0
  78. package/dist/components/chat/hooks/use-chat.d.ts +1 -0
  79. package/dist/components/chat/hooks/use-chat.d.ts.map +1 -1
  80. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +15 -48
  81. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  82. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +10 -53
  83. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  84. package/dist/components/chat/index.cjs +12 -4
  85. package/dist/components/chat/index.cjs.map +1 -1
  86. package/dist/components/chat/index.d.ts +1 -0
  87. package/dist/components/chat/index.d.ts.map +1 -1
  88. package/dist/components/chat/index.js +23 -15
  89. package/dist/components/chat/stream/chat-dialog-store.d.ts +184 -0
  90. package/dist/components/chat/stream/chat-dialog-store.d.ts.map +1 -0
  91. package/dist/components/chat/stream/chat-stream-reducer.d.ts +367 -0
  92. package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -0
  93. package/dist/components/chat/stream/delta-batcher.d.ts +51 -0
  94. package/dist/components/chat/stream/delta-batcher.d.ts.map +1 -0
  95. package/dist/components/chat/stream/index.d.ts +17 -0
  96. package/dist/components/chat/stream/index.d.ts.map +1 -0
  97. package/dist/components/chat/stream/message-mutations.d.ts +77 -0
  98. package/dist/components/chat/stream/message-mutations.d.ts.map +1 -0
  99. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts +22 -0
  100. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts.map +1 -0
  101. package/dist/components/chat/types/api.types.d.ts +1 -81
  102. package/dist/components/chat/types/api.types.d.ts.map +1 -1
  103. package/dist/components/chat/types/processing.types.d.ts +2 -90
  104. package/dist/components/chat/types/processing.types.d.ts.map +1 -1
  105. package/dist/components/chat/types/unified-chat-state.types.d.ts +5 -0
  106. package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
  107. package/dist/components/chat/utils/chat-conversation-storage.d.ts +34 -0
  108. package/dist/components/chat/utils/chat-conversation-storage.d.ts.map +1 -0
  109. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts +34 -6
  110. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
  111. package/dist/components/chat/utils/index.d.ts +2 -3
  112. package/dist/components/chat/utils/index.d.ts.map +1 -1
  113. package/dist/components/chat/utils/process-historical-messages.d.ts +59 -11
  114. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  115. package/dist/components/contact/index.cjs +5 -5
  116. package/dist/components/contact/index.js +4 -4
  117. package/dist/components/docs/index.cjs +7 -7
  118. package/dist/components/docs/index.js +6 -6
  119. package/dist/components/embeds/index.cjs +5 -5
  120. package/dist/components/embeds/index.js +4 -4
  121. package/dist/components/faq/index.cjs +6 -6
  122. package/dist/components/faq/index.js +5 -5
  123. package/dist/components/features/index.cjs +4 -4
  124. package/dist/components/features/index.js +3 -3
  125. package/dist/components/help-center-pages/index.cjs +23 -23
  126. package/dist/components/help-center-pages/index.js +14 -14
  127. package/dist/components/index.cjs +182 -140
  128. package/dist/components/index.cjs.map +1 -1
  129. package/dist/components/index.js +66 -24
  130. package/dist/components/index.js.map +1 -1
  131. package/dist/components/navigation/index.cjs +4 -4
  132. package/dist/components/navigation/index.js +3 -3
  133. package/dist/components/onboarding-guides/index.cjs +8 -8
  134. package/dist/components/onboarding-guides/index.js +7 -7
  135. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
  136. package/dist/components/related-content/index.cjs +6 -6
  137. package/dist/components/related-content/index.js +5 -5
  138. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  139. package/dist/components/tickets/index.cjs +7 -7
  140. package/dist/components/tickets/index.js +6 -6
  141. package/dist/components/ui/index.cjs +46 -4
  142. package/dist/components/ui/index.cjs.map +1 -1
  143. package/dist/components/ui/index.d.ts +1 -2
  144. package/dist/components/ui/index.d.ts.map +1 -1
  145. package/dist/components/ui/index.js +57 -15
  146. package/dist/components/ui/markdown/base-components.d.ts +51 -0
  147. package/dist/components/ui/markdown/base-components.d.ts.map +1 -0
  148. package/dist/components/ui/markdown/engine.d.ts +99 -0
  149. package/dist/components/ui/markdown/engine.d.ts.map +1 -0
  150. package/dist/components/ui/markdown/heading-ids.d.ts +65 -0
  151. package/dist/components/ui/markdown/heading-ids.d.ts.map +1 -0
  152. package/dist/components/ui/markdown/index.d.ts +18 -0
  153. package/dist/components/ui/markdown/index.d.ts.map +1 -0
  154. package/dist/components/ui/markdown/mermaid-diagram.d.ts +79 -0
  155. package/dist/components/ui/markdown/mermaid-diagram.d.ts.map +1 -0
  156. package/dist/components/ui/markdown/rich/embed-overrides.d.ts +14 -0
  157. package/dist/components/ui/markdown/rich/embed-overrides.d.ts.map +1 -0
  158. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts +40 -0
  159. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts.map +1 -0
  160. package/dist/components/ui/markdown/rich/shortcodes.d.ts +8 -0
  161. package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +1 -0
  162. package/dist/components/ui/markdown/sanitize.d.ts +153 -0
  163. package/dist/components/ui/markdown/sanitize.d.ts.map +1 -0
  164. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts +23 -0
  165. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts.map +1 -0
  166. package/dist/components/ui/markdown/streaming.d.ts +78 -0
  167. package/dist/components/ui/markdown/streaming.d.ts.map +1 -0
  168. package/dist/components/ui/markdown/text-size.d.ts +34 -0
  169. package/dist/components/ui/markdown/text-size.d.ts.map +1 -0
  170. package/dist/contexts/chat-runtime-context.d.ts +8 -0
  171. package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
  172. package/dist/contexts/index.cjs +2 -2
  173. package/dist/contexts/index.js +1 -1
  174. package/dist/hooks/index.cjs +3 -3
  175. package/dist/hooks/index.js +2 -2
  176. package/dist/index.cjs +62 -4
  177. package/dist/index.cjs.map +1 -1
  178. package/dist/index.js +73 -15
  179. package/dist/utils/index.cjs +169 -40
  180. package/dist/utils/index.cjs.map +1 -1
  181. package/dist/utils/index.d.ts +1 -0
  182. package/dist/utils/index.d.ts.map +1 -1
  183. package/dist/utils/index.js +162 -41
  184. package/dist/utils/index.js.map +1 -1
  185. package/dist/utils/markdown-fences.d.ts +42 -0
  186. package/dist/utils/markdown-fences.d.ts.map +1 -0
  187. package/dist/utils/markdown-heading-id.d.ts +85 -0
  188. package/dist/utils/markdown-heading-id.d.ts.map +1 -0
  189. package/dist/utils/markdown-section-extractor.d.ts.map +1 -1
  190. package/package.json +7 -1
  191. package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +307 -0
  192. package/src/chat-protocol/__tests__/chat-protocol.test.ts +516 -0
  193. package/src/chat-protocol/__tests__/env-flag.test.ts +52 -0
  194. package/src/chat-protocol/__tests__/ip-normalize.test.ts +136 -0
  195. package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +228 -0
  196. package/src/chat-protocol/decode.ts +278 -0
  197. package/src/chat-protocol/encode.ts +71 -0
  198. package/src/chat-protocol/env-flag.ts +39 -0
  199. package/src/chat-protocol/events.ts +232 -0
  200. package/src/chat-protocol/frames.ts +245 -0
  201. package/src/chat-protocol/index.ts +21 -0
  202. package/src/chat-protocol/ip-normalize.ts +146 -0
  203. package/src/chat-protocol/nats-decoder.ts +252 -0
  204. package/src/components/chat/__tests__/chat-message-list.test.tsx +293 -5
  205. package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +207 -0
  206. package/src/components/chat/__tests__/chat-pending-turn.test.tsx +209 -0
  207. package/src/components/chat/chat-message-enhanced.tsx +111 -17
  208. package/src/components/chat/chat-message-list.tsx +358 -109
  209. package/src/components/chat/embeddable-chat.tsx +6 -0
  210. package/src/components/chat/hooks/.index.md +30 -33
  211. package/src/components/chat/hooks/.use-nats-chat-adapter.md +36 -55
  212. package/src/components/chat/hooks/__tests__/__snapshots__/conversation-id-persistence-golden.test.ts.snap +37 -0
  213. package/src/components/chat/hooks/__tests__/__snapshots__/sse-stream-golden.test.ts.snap +0 -0
  214. package/src/components/chat/hooks/__tests__/conversation-id-persistence-golden.test.ts +438 -0
  215. package/src/components/chat/hooks/__tests__/sse-stream-golden.test.ts +318 -0
  216. package/src/components/chat/hooks/index.ts +0 -1
  217. package/src/components/chat/hooks/use-chat-history-hydration.ts +142 -0
  218. package/src/components/chat/hooks/use-chat.ts +20 -0
  219. package/src/components/chat/hooks/use-nats-chat-adapter.ts +243 -823
  220. package/src/components/chat/hooks/use-sse-chat-adapter.ts +417 -774
  221. package/src/components/chat/index.ts +5 -0
  222. package/src/components/chat/stream/__tests__/__snapshots__/chat-stream-reducer-golden.test.ts.snap +940 -0
  223. package/src/components/chat/stream/__tests__/chat-dialog-store.test.ts +756 -0
  224. package/src/components/chat/stream/__tests__/chat-stream-reducer-golden.test.ts +460 -0
  225. package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +894 -0
  226. package/src/components/chat/stream/__tests__/delta-batcher.test.ts +255 -0
  227. package/src/components/chat/stream/__tests__/use-chat-stream-reducer.test.ts +98 -0
  228. package/src/components/chat/stream/chat-dialog-store.ts +536 -0
  229. package/src/components/chat/stream/chat-stream-reducer.ts +1796 -0
  230. package/src/components/chat/stream/delta-batcher.ts +159 -0
  231. package/src/components/chat/stream/index.ts +55 -0
  232. package/src/components/chat/stream/message-mutations.ts +338 -0
  233. package/src/components/chat/stream/use-chat-stream-reducer.ts +126 -0
  234. package/src/components/chat/thinking-display.tsx +1 -1
  235. package/src/components/chat/types/.api.types.md +39 -49
  236. package/src/components/chat/types/.processing.types.md +29 -58
  237. package/src/components/chat/types/api.types.ts +6 -73
  238. package/src/components/chat/types/processing.types.ts +11 -52
  239. package/src/components/chat/types/unified-chat-state.types.ts +6 -0
  240. package/src/components/chat/utils/.index.md +31 -40
  241. package/src/components/chat/utils/__tests__/__snapshots__/process-historical-messages-golden.test.ts.snap +420 -0
  242. package/src/components/chat/utils/__tests__/__snapshots__/segment-accumulator-golden.test.ts.snap +605 -0
  243. package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +317 -0
  244. package/src/components/chat/utils/__tests__/segment-accumulator-golden.test.ts +270 -0
  245. package/src/components/chat/utils/chat-conversation-storage.ts +83 -0
  246. package/src/components/chat/utils/extract-incomplete-message-state.ts +63 -5
  247. package/src/components/chat/utils/index.ts +5 -8
  248. package/src/components/chat/utils/process-historical-messages.ts +352 -384
  249. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +22 -4
  250. package/src/components/shared/legal-document/legal-document-page.tsx +1 -1
  251. package/src/components/shared/product-release/release-detail-page.tsx +19 -4
  252. package/src/components/ui/__tests__/__snapshots__/markdown-parity.test.tsx.snap +2420 -0
  253. package/src/components/ui/__tests__/markdown-parity.test.tsx +2528 -0
  254. package/src/components/ui/index.ts +1 -2
  255. package/src/components/ui/markdown/__tests__/mermaid-security.test.ts +115 -0
  256. package/src/components/ui/markdown/__tests__/mermaid-stale-render.test.tsx +95 -0
  257. package/src/components/ui/markdown/__tests__/sanitize-invariant.test.ts +309 -0
  258. package/src/components/ui/markdown/__tests__/streaming.test.tsx +388 -0
  259. package/src/components/ui/markdown/base-components.tsx +360 -0
  260. package/src/components/ui/markdown/engine.tsx +315 -0
  261. package/src/components/ui/markdown/heading-ids.ts +239 -0
  262. package/src/components/ui/markdown/index.ts +49 -0
  263. package/src/components/ui/markdown/mermaid-diagram.tsx +291 -0
  264. package/src/components/ui/markdown/rich/embed-overrides.tsx +199 -0
  265. package/src/components/ui/markdown/rich/rich-markdown-renderer.tsx +184 -0
  266. package/src/components/ui/markdown/rich/shortcodes.ts +170 -0
  267. package/src/components/ui/markdown/sanitize.ts +3499 -0
  268. package/src/components/ui/markdown/simple-markdown-renderer.tsx +28 -0
  269. package/src/components/ui/markdown/streaming.ts +362 -0
  270. package/src/components/ui/markdown/text-size.ts +106 -0
  271. package/src/components/ui/release-changelog-section.tsx +1 -1
  272. package/src/components/ui/ticket-info-section.tsx +1 -1
  273. package/src/contexts/chat-runtime-context.tsx +8 -0
  274. package/src/utils/index.ts +1 -0
  275. package/src/utils/markdown-fences.ts +127 -0
  276. package/src/utils/markdown-heading-id.ts +348 -0
  277. package/src/utils/markdown-section-extractor.ts +41 -56
  278. package/dist/chunk-2B6773QM.cjs.map +0 -1
  279. package/dist/chunk-5RXKOGH6.cjs.map +0 -1
  280. package/dist/chunk-77DFPD35.js.map +0 -1
  281. package/dist/chunk-AHDJMEEN.js.map +0 -1
  282. package/dist/chunk-FZOBQG6M.cjs.map +0 -1
  283. package/dist/chunk-H7JX2BG7.cjs.map +0 -1
  284. package/dist/chunk-KFQXO7QB.js.map +0 -1
  285. package/dist/chunk-S52LJNMU.js.map +0 -1
  286. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts +0 -6
  287. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +0 -1
  288. package/dist/components/chat/utils/chunk-parser.d.ts +0 -25
  289. package/dist/components/chat/utils/chunk-parser.d.ts.map +0 -1
  290. package/dist/components/ui/rich-markdown-renderer.d.ts +0 -34
  291. package/dist/components/ui/rich-markdown-renderer.d.ts.map +0 -1
  292. package/dist/components/ui/simple-markdown-renderer.d.ts +0 -73
  293. package/dist/components/ui/simple-markdown-renderer.d.ts.map +0 -1
  294. package/src/components/chat/hooks/.use-realtime-chunk-processor.md +0 -77
  295. package/src/components/chat/hooks/use-realtime-chunk-processor.ts +0 -479
  296. package/src/components/chat/utils/.chunk-parser.md +0 -62
  297. package/src/components/chat/utils/chunk-parser.ts +0 -256
  298. package/src/components/ui/.simple-markdown-renderer.md +0 -52
  299. package/src/components/ui/rich-markdown-renderer.tsx +0 -1223
  300. package/src/components/ui/simple-markdown-renderer.tsx +0 -964
  301. /package/dist/{chunk-AJNDDXKC.js.map → chunk-4NWARV5I.js.map} +0 -0
  302. /package/dist/{chunk-HQYJINFV.js.map → chunk-CORIBKHG.js.map} +0 -0
  303. /package/dist/{chunk-PNXNWGBY.js.map → chunk-GTWO5NAY.js.map} +0 -0
  304. /package/dist/{chunk-SCCTJ2UI.js.map → chunk-HSACATCD.js.map} +0 -0
  305. /package/dist/{chunk-KLYHZRAD.js.map → chunk-PB3BTZDU.js.map} +0 -0
  306. /package/dist/{chunk-YJSKQ2ZV.js.map → chunk-RQLBHKXN.js.map} +0 -0
  307. /package/dist/{chunk-CWQACI4Z.js.map → chunk-RW2LPL72.js.map} +0 -0
  308. /package/dist/{chunk-3BFX6HJM.js.map → chunk-UUYRJPGJ.js.map} +0 -0
  309. /package/dist/{chunk-F6BYUO5U.js.map → chunk-VHG6AX5V.js.map} +0 -0
  310. /package/dist/{chunk-PXF5J24T.js.map → chunk-XQ2B74SI.js.map} +0 -0
  311. /package/dist/{chunk-LLWBPPZ4.js.map → chunk-YZ2OIYQ3.js.map} +0 -0
@@ -0,0 +1,159 @@
1
+ /**
2
+ * createDeltaBatcher — framework-free, append-only batching of `text-delta`
3
+ * and `thinking-delta` events.
4
+ *
5
+ * WHY THIS IS SHARED (and not a hook-private detail): every host that drives a
6
+ * `ChatStreamReducer` from a raw transport needs the SAME batching, and a
7
+ * copy-paste of it drifts. The React wrapper (`useChatStreamReducer`) and any
8
+ * non-React mirror in a product app both consume this one implementation.
9
+ *
10
+ * Behaviour:
11
+ * - `push(event, key)` queues delta events and returns true; anything else
12
+ * returns false so the caller can flush + apply it synchronously (ordering:
13
+ * completion state must always land on fully-applied deltas).
14
+ * - CONSECUTIVE same-type deltas are COALESCED into one event (the reducer's
15
+ * append semantics distribute over concatenation), so a 60-delta/sec turn
16
+ * costs ONE `applyOne` per frame instead of 60. `text-delta` additionally
17
+ * requires a matching `leading` flag before coalescing.
18
+ * - A flush is scheduled on `requestAnimationFrame` when available, with a
19
+ * timer fallback that is ALWAYS armed (rAF pauses in background tabs, and a
20
+ * hidden chat panel must still keep its thread current).
21
+ * - `key` is an opaque routing token handed back to `applyOne`. When it
22
+ * changes, the pending batch is flushed against the PREVIOUS key first, so
23
+ * deltas never land on the wrong dialog/side.
24
+ */
25
+
26
+ import type { ChatStreamEvent, TextDeltaEvent, ThinkingDeltaEvent } from '../../../chat-protocol/events'
27
+
28
+ export const DELTA_FLUSH_FALLBACK_MS = 50
29
+
30
+ export type DeltaEvent = TextDeltaEvent | ThinkingDeltaEvent
31
+
32
+ export function isDeltaEvent(event: ChatStreamEvent): event is DeltaEvent {
33
+ return event.type === 'text-delta' || event.type === 'thinking-delta'
34
+ }
35
+
36
+ export interface CreateDeltaBatcherOptions<K> {
37
+ /** Apply one (possibly coalesced) delta against `key`'s reducer. */
38
+ applyOne: (event: DeltaEvent, key: K | undefined) => void
39
+ /** Called after a flush that applied at least one delta. */
40
+ onFlushed?: (appliedCount: number) => void
41
+ /** Timer fallback in ms (default `DELTA_FLUSH_FALLBACK_MS`). */
42
+ fallbackMs?: number
43
+ }
44
+
45
+ export interface DeltaBatcher<K = unknown> {
46
+ /**
47
+ * Queue `event` when it is a delta (returns true). Non-delta events are NOT
48
+ * queued and return false — the caller flushes and applies them itself.
49
+ */
50
+ push(event: ChatStreamEvent, key?: K): boolean
51
+ /** Synchronously apply the pending batch and cancel any scheduled flush. */
52
+ flush(): void
53
+ /** Flush and release timers — call on teardown so tail deltas aren't lost. */
54
+ dispose(): void
55
+ /** Queued (post-coalescing) delta count. Test/diagnostic surface. */
56
+ readonly pendingCount: number
57
+ }
58
+
59
+ export function createDeltaBatcher<K = unknown>({
60
+ applyOne,
61
+ onFlushed,
62
+ fallbackMs = DELTA_FLUSH_FALLBACK_MS,
63
+ }: CreateDeltaBatcherOptions<K>): DeltaBatcher<K> {
64
+ let pending: DeltaEvent[] = []
65
+ let pendingKey: K | undefined
66
+ let hasKey = false
67
+ let raf: number | null = null
68
+ let timer: ReturnType<typeof setTimeout> | null = null
69
+
70
+ function cancelScheduled(): void {
71
+ if (raf !== null) {
72
+ if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(raf)
73
+ raf = null
74
+ }
75
+ if (timer !== null) {
76
+ clearTimeout(timer)
77
+ timer = null
78
+ }
79
+ }
80
+
81
+ function flush(): void {
82
+ cancelScheduled()
83
+ if (pending.length === 0) return
84
+ const batch = pending
85
+ pending = []
86
+ const key = pendingKey
87
+ for (const delta of batch) applyOne(delta, key)
88
+ onFlushed?.(batch.length)
89
+ }
90
+
91
+ function schedule(): void {
92
+ if (raf !== null || timer !== null) return
93
+ if (typeof requestAnimationFrame === 'function') {
94
+ raf = requestAnimationFrame(() => {
95
+ raf = null
96
+ flush()
97
+ })
98
+ }
99
+ // Timer fallback ALWAYS armed: rAF pauses in background tabs.
100
+ timer = setTimeout(() => {
101
+ timer = null
102
+ flush()
103
+ }, fallbackMs)
104
+ }
105
+
106
+ return {
107
+ push(event, key) {
108
+ // A pending batch belonging to a previous key must land on ITS reducer
109
+ // before we start queueing for the new one.
110
+ if (!hasKey || !Object.is(pendingKey, key)) {
111
+ flush()
112
+ pendingKey = key
113
+ hasKey = true
114
+ }
115
+ if (!isDeltaEvent(event)) return false
116
+ const tail = pending[pending.length - 1]
117
+ // NEVER coalesce a delta whose seq does not ADVANCE past the tail's.
118
+ // Coalescing stamps the merged entry with the LAST pushed seq, so a
119
+ // redelivered / out-of-order delta arriving inside one frame would both
120
+ // concatenate its text (the reducer never sees it as a duplicate) and
121
+ // REWIND the batch's seq below an already-applied one. Pushing it as its
122
+ // own entry hands it to the reducer intact, where the `seq <=
123
+ // lastAppliedSeq` gate drops it — the batcher must not depend on hosts
124
+ // gating at dispatch.
125
+ // A seq-LESS tail is not "always advancing": merging a seq-BEARING delta
126
+ // onto it stamps the merged entry with that seq, which the reducer's gate
127
+ // then treats as the batch's position — a redelivered delta could rewind
128
+ // it. Coalesce onto a seq-less tail only when the incoming delta is
129
+ // likewise seq-less (a uniformly seq-less stream, e.g. SSE).
130
+ const seqAdvances =
131
+ tail?.seq == null ? event.seq == null : event.seq != null && event.seq > tail.seq
132
+ if (
133
+ tail &&
134
+ seqAdvances &&
135
+ tail.type === event.type &&
136
+ (tail.type !== 'text-delta' ||
137
+ (tail as TextDeltaEvent).leading === (event as TextDeltaEvent).leading)
138
+ ) {
139
+ pending[pending.length - 1] = {
140
+ ...tail,
141
+ text: tail.text + event.text,
142
+ ...(event.seq != null ? { seq: event.seq } : {}),
143
+ } as DeltaEvent
144
+ } else {
145
+ pending.push({ ...event })
146
+ }
147
+ schedule()
148
+ return true
149
+ },
150
+ flush,
151
+ dispose() {
152
+ flush()
153
+ cancelScheduled()
154
+ },
155
+ get pendingCount() {
156
+ return pending.length
157
+ },
158
+ }
159
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Chat stream module — ONE master reader for every chat event source
3
+ * (SSE decoder, NATS chunk decoder, historical replay). See
4
+ * `chat-stream-reducer.ts` for the architecture note.
5
+ *
6
+ * NOTE: the pure message-mutation helpers (`appendToTrailingAssistant`,
7
+ * `applyToolExecutionToMessages`, …) are intentionally NOT star-exported
8
+ * here — their legacy public home is `use-nats-chat-adapter`, which
9
+ * re-exports them from `./message-mutations`; exporting the same bindings
10
+ * from two star-exported barrels would make them ambiguous and drop them
11
+ * from the chat barrel entirely.
12
+ */
13
+
14
+ export {
15
+ createChatStreamReducer,
16
+ createEmptyTurnMeta,
17
+ type BeginSseSendOptions,
18
+ type ChatReducerEffect,
19
+ type ChatReducerState,
20
+ type ChatStreamReducer,
21
+ type ChatStreamReducerOptions,
22
+ type ChatTurnMetaState,
23
+ type EscalatedApprovalData,
24
+ type InitializeExtras,
25
+ type PendingEcho,
26
+ } from './chat-stream-reducer'
27
+
28
+ export {
29
+ createChatDialogStore,
30
+ DEFAULT_DIALOG_SIDE,
31
+ DEFAULT_MAX_REDUCERS,
32
+ MAX_PENDING_RETAIN_KEYS,
33
+ type ChatDialogSide,
34
+ type ChatDialogStore,
35
+ type CreateChatDialogStoreOptions,
36
+ type EvictedReducerState,
37
+ } from './chat-dialog-store'
38
+
39
+ // Framework-free delta batching — shared with NON-React hosts that drive a
40
+ // reducer straight from a transport (they must NOT re-implement it).
41
+ // `isDeltaEvent` + `DELTA_FLUSH_FALLBACK_MS` are deliberately NOT re-exported:
42
+ // they are internals of the batcher (its own tests import them from the
43
+ // module), and no consumer needs to classify or re-time deltas by hand.
44
+ export {
45
+ createDeltaBatcher,
46
+ type CreateDeltaBatcherOptions,
47
+ type DeltaBatcher,
48
+ type DeltaEvent,
49
+ } from './delta-batcher'
50
+
51
+ export {
52
+ useChatStreamReducer,
53
+ type UseChatStreamReducerOptions,
54
+ type UseChatStreamReducerReturn,
55
+ } from './use-chat-stream-reducer'
@@ -0,0 +1,338 @@
1
+ /**
2
+ * Pure message-mutation kernel for the master chat-stream reducer.
3
+ *
4
+ * These helpers were absorbed from `use-nats-chat-adapter.ts` in Phase 3 of
5
+ * the chat unification — they are the ONE implementation of "apply a stream
6
+ * fragment to a message thread". The reducer (`chat-stream-reducer.ts`) and
7
+ * the cross-side projections (`chat-dialog-store.ts`) both build on them.
8
+ *
9
+ * REFERENTIAL-STABILITY CONTRACT: every helper clones ONLY the touched
10
+ * message object (and the touched segment slot inside it). A value-level
11
+ * no-op — e.g. a replayed duplicate EXECUTED_TOOL whose merge would write
12
+ * identical values — returns the PRIOR references untouched, so React
13
+ * memoization over untouched messages survives redeliveries.
14
+ *
15
+ * Framework-free: no React, no timers.
16
+ */
17
+
18
+ import type {
19
+ ApprovalBatchExecutionState,
20
+ ChatApprovalStatus,
21
+ MessageSegment,
22
+ ToolExecutionSegment,
23
+ ToolExecutionData,
24
+ } from '../types'
25
+ import type { UnifiedChatMessage } from '../types/unified-chat-state.types'
26
+
27
+ export function nextId(role: 'user' | 'assistant'): string {
28
+ // Date.now() + counter sliver keeps ids monotonic even when two
29
+ // messages are produced inside the same ms tick (user + assistant
30
+ // placeholder fire back-to-back from a single sendMessage call).
31
+ return `${role}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`
32
+ }
33
+
34
+ /**
35
+ * Realtime user/direct/system chunks can be replays of rows already on
36
+ * screen. Content-window dedup is the LAST layer (seq-less transports only)
37
+ * — see the layer list in `chat-stream-reducer.ts`.
38
+ */
39
+ export const CONTENT_DEDUP_WINDOW = 4
40
+ export const SYSTEM_DEDUP_WINDOW = 10
41
+
42
+ export function hasRecentMessage(
43
+ prev: UnifiedChatMessage[],
44
+ predicate: (message: UnifiedChatMessage) => boolean,
45
+ window: number,
46
+ ): boolean {
47
+ const start = Math.max(0, prev.length - window)
48
+ for (let i = prev.length - 1; i >= start; i--) {
49
+ if (predicate(prev[i])) return true
50
+ }
51
+ return false
52
+ }
53
+
54
+ /**
55
+ * Replace (or append) the trailing assistant message with the latest
56
+ * accumulated segments.
57
+ */
58
+ export function updateTrailingAssistant(
59
+ prev: UnifiedChatMessage[],
60
+ segments: MessageSegment[],
61
+ ): UnifiedChatMessage[] {
62
+ const last = prev[prev.length - 1]
63
+ if (!last || last.role !== 'assistant') {
64
+ // No placeholder exists — append a fresh assistant message.
65
+ return [
66
+ ...prev,
67
+ {
68
+ id: nextId('assistant'),
69
+ role: 'assistant',
70
+ content: '',
71
+ segments,
72
+ },
73
+ ]
74
+ }
75
+ return [...prev.slice(0, -1), { ...last, segments }]
76
+ }
77
+
78
+ /**
79
+ * Append-mode counterpart of `updateTrailingAssistant` for post-MESSAGE_END
80
+ * continuation fragments (`SegmentsUpdateMetadata.append`). Coalesces
81
+ * trailing fragments of the same type, mirroring the accumulator; approval
82
+ * deltas upsert by request id so a replayed emit stays idempotent.
83
+ */
84
+ export function appendToTrailingAssistant(
85
+ prev: UnifiedChatMessage[],
86
+ segments: MessageSegment[],
87
+ ): UnifiedChatMessage[] {
88
+ if (segments.length === 0) return prev
89
+ const last = prev[prev.length - 1]
90
+ if (!last || last.role !== 'assistant') {
91
+ return [
92
+ ...prev,
93
+ { id: nextId('assistant'), role: 'assistant', content: '', segments },
94
+ ]
95
+ }
96
+ const merged = [...(last.segments ?? [])]
97
+ for (const seg of segments) {
98
+ const tail = merged[merged.length - 1]
99
+ if (seg.type === 'text' && tail?.type === 'text') {
100
+ merged[merged.length - 1] = { type: 'text', text: tail.text + seg.text }
101
+ } else if (seg.type === 'thinking' && tail?.type === 'thinking') {
102
+ merged[merged.length - 1] = { type: 'thinking', text: tail.text + seg.text }
103
+ } else if (seg.type === 'approval_batch') {
104
+ // Approval deltas must be IDEMPOTENT: the escalated-result emit can be
105
+ // seen twice (live + catch-up replay over hydrated history), so upsert
106
+ // by request id instead of raw-appending a duplicate card.
107
+ const idx = merged.findIndex(
108
+ (m) => m.type === 'approval_batch' && m.data.approvalRequestId === seg.data.approvalRequestId,
109
+ )
110
+ if (idx !== -1) merged[idx] = seg
111
+ else merged.push(seg)
112
+ } else if (seg.type === 'approval_request') {
113
+ // Legacy cards share one requestId across an unfolded batch — key on
114
+ // (requestId, command) so each tool's card upserts its own twin.
115
+ const idx = merged.findIndex(
116
+ (m) =>
117
+ m.type === 'approval_request' &&
118
+ m.data.requestId === seg.data.requestId &&
119
+ m.data.command === seg.data.command,
120
+ )
121
+ if (idx !== -1) merged[idx] = seg
122
+ else merged.push(seg)
123
+ } else {
124
+ // Other non-text segments are pushed RAW on purpose: EXECUTING↔EXECUTED
125
+ // pairing and batch merging are `applyToolExecutionToMessages`'s job
126
+ // (post-END tool chunks never reach this helper — the reducer routes
127
+ // them cross-message). Running the accumulator here would double-apply
128
+ // those rules.
129
+ merged.push(seg)
130
+ }
131
+ }
132
+ return [...prev.slice(0, -1), { ...last, segments: merged }]
133
+ }
134
+
135
+ /**
136
+ * Upsert a standalone context-compaction segment into the trailing assistant
137
+ * bubble. Compaction emissions arrive as the accumulator's CUMULATIVE array —
138
+ * only the compaction segment itself may be applied, or interleaved
139
+ * continuation text would duplicate. A `completed` segment replaces the last
140
+ * `started` one in place.
141
+ */
142
+ export function upsertTrailingCompaction(
143
+ prev: UnifiedChatMessage[],
144
+ segments: MessageSegment[],
145
+ ): UnifiedChatMessage[] {
146
+ const compaction = [...segments].reverse().find((s) => s.type === 'context_compaction')
147
+ if (!compaction) return prev
148
+ const last = prev[prev.length - 1]
149
+ if (!last || last.role !== 'assistant') {
150
+ return [
151
+ ...prev,
152
+ { id: nextId('assistant'), role: 'assistant', content: '', segments: [compaction] },
153
+ ]
154
+ }
155
+ const existing = last.segments ?? []
156
+ // LAST 'started' segment (not first): with repeated compactions in one
157
+ // bubble the earlier ones are already completed-in-place, so the newest
158
+ // 'started' is the only one this completion can belong to.
159
+ const startedIdx = existing
160
+ .map((s) => s.type === 'context_compaction' && s.status === 'started')
161
+ .lastIndexOf(true)
162
+ const merged =
163
+ startedIdx !== -1
164
+ ? existing.map((s, i) => (i === startedIdx ? compaction : s))
165
+ : [...existing, compaction]
166
+ return [...prev.slice(0, -1), { ...last, segments: merged }]
167
+ }
168
+
169
+ /** Scoped value-compare for the fields a tool merge writes (status/type,
170
+ * result, success, execId, restored title, parameters). Small objects —
171
+ * JSON compare for `parameters` keeps a replayed duplicate a no-op even
172
+ * when the transport rebuilt the object. */
173
+ function sameToolData(a: ToolExecutionData, b: ToolExecutionData): boolean {
174
+ return (
175
+ a.type === b.type &&
176
+ a.integratedToolType === b.integratedToolType &&
177
+ a.toolFunction === b.toolFunction &&
178
+ a.toolTitle === b.toolTitle &&
179
+ a.result === b.result &&
180
+ a.success === b.success &&
181
+ a.toolExecutionRequestId === b.toolExecutionRequestId &&
182
+ (a.parameters === b.parameters ||
183
+ JSON.stringify(a.parameters ?? null) === JSON.stringify(b.parameters ?? null))
184
+ )
185
+ }
186
+
187
+ function sameExecutionState(
188
+ a: ApprovalBatchExecutionState | undefined,
189
+ b: ApprovalBatchExecutionState,
190
+ ): boolean {
191
+ return !!a && a.status === b.status && a.result === b.result && a.success === b.success
192
+ }
193
+
194
+ /**
195
+ * Cross-message tool-execution updater for post-MESSAGE_END tool chunks
196
+ * (approved commands executing after the approval bubble, async batch
197
+ * results). Scans messages from the end:
198
+ * 1) an `approval_batch` whose `toolCalls` contains the execution id →
199
+ * merge into its `executions` map;
200
+ * 2) a matching `tool_execution` segment (same id, or EXECUTING with the
201
+ * same tool for legacy id-less backends) → update in place;
202
+ * 3) no match → append the segment to the trailing assistant bubble.
203
+ *
204
+ * A value-level no-op (replayed duplicate) returns `prev` untouched.
205
+ */
206
+ export function applyToolExecutionToMessages(
207
+ prev: UnifiedChatMessage[],
208
+ segment: ToolExecutionSegment,
209
+ ): UnifiedChatMessage[] {
210
+ const merged = mergeToolExecutionIfPresent(prev, segment)
211
+ if (merged !== null) return merged
212
+ return appendToTrailingAssistant(prev, [segment])
213
+ }
214
+
215
+ /**
216
+ * Merge-only variant of `applyToolExecutionToMessages` — the pure projection
217
+ * used for the cross-SIDE fan-out in `chat-dialog-store.ts`. Updates a
218
+ * matching batch slot / tool segment when present; returns `null` when
219
+ * nothing matches (the caller decides whether to append — projections never
220
+ * do, so a tool that belongs to one side only never grows a card on the
221
+ * other).
222
+ */
223
+ export function mergeToolExecutionIfPresent(
224
+ prev: UnifiedChatMessage[],
225
+ segment: ToolExecutionSegment,
226
+ ): UnifiedChatMessage[] | null {
227
+ const toolData = segment.data
228
+ const execId = toolData.toolExecutionRequestId
229
+
230
+ for (let i = prev.length - 1; i >= 0; i--) {
231
+ const message = prev[i]
232
+ if (message.role !== 'assistant' || !message.segments) continue
233
+
234
+ for (let j = message.segments.length - 1; j >= 0; j--) {
235
+ const seg = message.segments[j]
236
+
237
+ if (
238
+ execId &&
239
+ seg.type === 'approval_batch' &&
240
+ seg.data.toolCalls.some((c) => c.toolExecutionRequestId === execId)
241
+ ) {
242
+ const prevExec: ApprovalBatchExecutionState | undefined = seg.data.executions?.[execId]
243
+ // Never downgrade a done batch slot back to executing (JetStream
244
+ // redelivery of the EXECUTING chunk after EXECUTED landed). Returning
245
+ // prev = matched-with-no-change, so the caller doesn't append either.
246
+ if (toolData.type === 'EXECUTING_TOOL' && prevExec?.status === 'done') {
247
+ return prev
248
+ }
249
+ const nextExec: ApprovalBatchExecutionState =
250
+ toolData.type === 'EXECUTED_TOOL'
251
+ ? { status: 'done', result: toolData.result, success: toolData.success }
252
+ : { status: 'executing', result: prevExec?.result, success: prevExec?.success }
253
+ // Value-level no-op (replayed duplicate) → prior references.
254
+ if (sameExecutionState(prevExec, nextExec)) return prev
255
+ const nextSegments = [...message.segments]
256
+ nextSegments[j] = {
257
+ ...seg,
258
+ data: { ...seg.data, executions: { ...(seg.data.executions ?? {}), [execId]: nextExec } },
259
+ }
260
+ const next = [...prev]
261
+ next[i] = { ...message, segments: nextSegments }
262
+ return next
263
+ }
264
+
265
+ if (seg.type === 'tool_execution') {
266
+ // KNOWN LIMIT (id-less chunks only — current backends always send
267
+ // toolExecutionRequestId): the fuzzy fallback pairs only with a
268
+ // segment still EXECUTING, so a replayed id-less EXECUTING/EXECUTED
269
+ // arriving after the pair completed matches nothing and falls to the
270
+ // caller's append (duplicate card). Widening the predicate to
271
+ // EXECUTED twins would instead swallow a LEGITIMATE second run of the
272
+ // same tool — without ids the two are indistinguishable, and losing a
273
+ // real run is worse than a duplicate card on a legacy transport.
274
+ const matches = execId
275
+ ? seg.data.toolExecutionRequestId === execId
276
+ : seg.data.type === 'EXECUTING_TOOL' &&
277
+ seg.data.integratedToolType === toolData.integratedToolType &&
278
+ seg.data.toolFunction === toolData.toolFunction
279
+ if (!matches) continue
280
+ // Never downgrade a completed segment back to EXECUTING (replayed
281
+ // EXECUTING chunk after its EXECUTED already landed).
282
+ if (toolData.type === 'EXECUTING_TOOL' && seg.data.type === 'EXECUTED_TOOL') {
283
+ return prev
284
+ }
285
+ const mergedData: ToolExecutionData = {
286
+ ...toolData,
287
+ toolTitle: toolData.toolTitle ?? seg.data.toolTitle,
288
+ parameters: toolData.parameters || seg.data.parameters,
289
+ }
290
+ // Value-level no-op (replayed duplicate) → prior references.
291
+ if (sameToolData(seg.data, mergedData)) return prev
292
+ const nextSegments = [...message.segments]
293
+ nextSegments[j] = { type: 'tool_execution', data: mergedData }
294
+ const next = [...prev]
295
+ next[i] = { ...message, segments: nextSegments }
296
+ return next
297
+ }
298
+ }
299
+ }
300
+
301
+ return null
302
+ }
303
+
304
+ /**
305
+ * Cross-message approval status flip (absorbed from the NATS adapter's
306
+ * `onApprovalResolved` handler). Idempotent + reference-preserving: only the
307
+ * messages/segments whose status (or resolvedByName) actually changes are
308
+ * cloned; a no-op returns `prev`.
309
+ */
310
+ export function projectApprovalResolutionToMessages(
311
+ prev: UnifiedChatMessage[],
312
+ requestId: string,
313
+ status: ChatApprovalStatus,
314
+ resolvedByName?: string | null,
315
+ ): UnifiedChatMessage[] {
316
+ let changed = false
317
+ const next = prev.map((m) => {
318
+ if (m.role !== 'assistant' || !m.segments) return m
319
+ let msgChanged = false
320
+ const segs = m.segments.map((s) => {
321
+ if (s.type === 'approval_request' && s.data.requestId === requestId && s.status !== status) {
322
+ msgChanged = true
323
+ return { ...s, status }
324
+ }
325
+ if (s.type === 'approval_batch' && s.data.approvalRequestId === requestId) {
326
+ const nextResolvedBy = resolvedByName ?? s.resolvedByName
327
+ if (s.status === status && nextResolvedBy === s.resolvedByName) return s
328
+ msgChanged = true
329
+ return { ...s, status, resolvedByName: nextResolvedBy }
330
+ }
331
+ return s
332
+ })
333
+ if (!msgChanged) return m
334
+ changed = true
335
+ return { ...m, segments: segs }
336
+ })
337
+ return changed ? next : prev
338
+ }
@@ -0,0 +1,126 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * useChatStreamReducer — thin React wrapper over a `ChatDialogStore` side.
5
+ *
6
+ * - `useSyncExternalStore` over the store (snapshot identity is stable
7
+ * between mutations, so untouched renders bail out);
8
+ * - APPEND-ONLY DELTA BATCHING: delegated wholesale to the framework-free
9
+ * `createDeltaBatcher` (same module) so a non-React host can share the
10
+ * exact same coalescing/flush policy instead of re-implementing it.
11
+ * - Any NON-delta event force-flushes the pending batch first, so ordering
12
+ * is preserved and stream completion (`turn-end`, errors, approval
13
+ * frames) always lands on fully-applied delta state. Adapters whose
14
+ * transport has no terminal event (SSE) call `flushDeltas()` explicitly
15
+ * before their end-of-turn command.
16
+ */
17
+
18
+ import { useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from 'react'
19
+ import type { ChatStreamEvent } from '../../../chat-protocol/events'
20
+ import {
21
+ DEFAULT_DIALOG_SIDE,
22
+ type ChatDialogSide,
23
+ type ChatDialogStore,
24
+ } from './chat-dialog-store'
25
+ import { createDeltaBatcher, type DeltaBatcher } from './delta-batcher'
26
+ import type {
27
+ ChatReducerState,
28
+ ChatStreamReducer,
29
+ ChatStreamReducerOptions,
30
+ } from './chat-stream-reducer'
31
+
32
+ interface DialogKey {
33
+ dialogId: string
34
+ side: ChatDialogSide
35
+ }
36
+
37
+ export interface UseChatStreamReducerOptions {
38
+ store: ChatDialogStore
39
+ dialogId: string
40
+ side?: ChatDialogSide
41
+ /** Consulted once, when this (dialogId, side) reducer is first created. */
42
+ createReducerOptions?: () => ChatStreamReducerOptions
43
+ }
44
+
45
+ export interface UseChatStreamReducerReturn {
46
+ state: ChatReducerState
47
+ /** Apply a decoded event (deltas are batched; everything else immediate). */
48
+ applyEvent: (event: ChatStreamEvent) => void
49
+ /** Synchronously apply any pending delta batch. */
50
+ flushDeltas: () => void
51
+ /** Run reducer commands (adapter-driven, non-wire mutations). */
52
+ mutate: <T>(fn: (reducer: ChatStreamReducer) => T) => T
53
+ reducer: ChatStreamReducer
54
+ }
55
+
56
+ export function useChatStreamReducer({
57
+ store,
58
+ dialogId,
59
+ side = DEFAULT_DIALOG_SIDE,
60
+ createReducerOptions,
61
+ }: UseChatStreamReducerOptions): UseChatStreamReducerReturn {
62
+ const optionsRef = useRef(createReducerOptions)
63
+ optionsRef.current = createReducerOptions
64
+
65
+ // Ensure the reducer exists before the first snapshot read.
66
+ const reducer = store.getReducer(dialogId, side, () => optionsRef.current?.() ?? {})
67
+
68
+ const getSnapshot = useCallback(() => store.getSnapshot(dialogId, side), [store, dialogId, side])
69
+ // Distinct server getter: `getSnapshot` is a pure read of a CLIENT-lived
70
+ // map, so reusing it for SSR would render a per-request-meaningless value
71
+ // (and, before it was made pure, silently accumulate reducers per request).
72
+ const getServerSnapshot = useCallback(() => store.getServerSnapshot(), [store])
73
+ const subscribe = useCallback((listener: () => void) => store.subscribe(listener), [store])
74
+ const state = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
75
+
76
+ // Pin this key for as long as the hook is mounted, so the store's LRU can
77
+ // never evict a reducer a live panel is rendering.
78
+ useEffect(() => store.retain(dialogId, side), [store, dialogId, side])
79
+
80
+ // ── Delta batch ──────────────────────────────────────────────────────────
81
+ // Identity-stable key object: the batcher compares keys with `Object.is`,
82
+ // so it must only change when the (dialogId, side) VALUES change.
83
+ const keyRef = useRef<DialogKey>({ dialogId, side })
84
+ if (keyRef.current.dialogId !== dialogId || keyRef.current.side !== side) {
85
+ keyRef.current = { dialogId, side }
86
+ }
87
+ const storeRef = useRef(store)
88
+ storeRef.current = store
89
+
90
+ const batcherRef = useRef<DeltaBatcher<DialogKey> | null>(null)
91
+ if (batcherRef.current === null) {
92
+ batcherRef.current = createDeltaBatcher<DialogKey>({
93
+ applyOne: (event, key) => {
94
+ if (key) storeRef.current.apply(key.dialogId, key.side, event)
95
+ },
96
+ })
97
+ }
98
+ const batcher = batcherRef.current
99
+
100
+ const flushDeltas = useCallback(() => batcher.flush(), [batcher])
101
+
102
+ const applyEvent = useCallback(
103
+ (event: ChatStreamEvent) => {
104
+ // `push` flushes on key change and returns false for non-delta events;
105
+ // those flush the batch first (ordering) and apply synchronously, so
106
+ // completion state always lands on fully-applied deltas.
107
+ if (batcher.push(event, keyRef.current)) return
108
+ batcher.flush()
109
+ store.apply(dialogId, side, event)
110
+ },
111
+ [batcher, store, dialogId, side],
112
+ )
113
+
114
+ // Flush on unmount so a torn-down panel doesn't drop its tail deltas.
115
+ useEffect(() => () => batcher.dispose(), [batcher])
116
+
117
+ const mutate = useCallback(
118
+ <T,>(fn: (reducer: ChatStreamReducer) => T): T => store.mutate(dialogId, side, fn),
119
+ [store, dialogId, side],
120
+ )
121
+
122
+ return useMemo(
123
+ () => ({ state, applyEvent, flushDeltas, mutate, reducer }),
124
+ [state, applyEvent, flushDeltas, mutate, reducer],
125
+ )
126
+ }
@@ -4,7 +4,7 @@ import { forwardRef, useState } from "react"
4
4
 
5
5
  import { cn } from "../../utils/cn"
6
6
  import { DotsLoaderIcon } from "../icons-v2-generated"
7
- import { SimpleMarkdownRenderer } from "../ui/simple-markdown-renderer"
7
+ import { SimpleMarkdownRenderer } from "../ui/markdown/simple-markdown-renderer"
8
8
  import { ExpandChevron } from "./expand-chevron"
9
9
  import { useCollapsible } from "./hooks/use-collapsible"
10
10
  import type { ThinkingDisplayProps } from "./types"