@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,318 @@
1
+ /**
2
+ * Phase-0 GOLDEN CHARACTERIZATION TESTS — the SSE wire decoder inside
3
+ * `useSseChatAdapter` (`createDocStreamFn`), driven through the full
4
+ * public hook (`useSseChatAdapter` → `useChat` → `useSSE`).
5
+ *
6
+ * Wire format (recorded from the hub's /api/docs/chat):
7
+ * [JSON frame]\0 [JSON frame]\0 … \x1E <raw UTF-8 answer deltas> \x1F [JSON usage trailer]
8
+ *
9
+ * Each fixture feeds a mocked global fetch whose Response body is a
10
+ * ReadableStream of Uint8Array chunks, then snapshots the resulting
11
+ * message state. Behaviors captured here — INCLUDING the sentinel-byte
12
+ * mis-framing in fixture (d) — are the recorded baseline for the
13
+ * unification reducer. Do NOT "fix" them here.
14
+ */
15
+
16
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
17
+ import { renderHook, act } from '@testing-library/react'
18
+ import { createElement, type ReactNode } from 'react'
19
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
20
+ import { useSseChatAdapter } from '../use-sse-chat-adapter'
21
+ import { ChatRuntimeContext, type ChatRuntime } from '../../../../contexts/chat-runtime-context'
22
+
23
+ // =============================================================================
24
+ // Harness
25
+ // =============================================================================
26
+
27
+ const SOURCE = 'goldensrc'
28
+
29
+ const runtime: ChatRuntime = {
30
+ endpoints: {
31
+ chatStreamUrl: '/api/docs/chat',
32
+ approvalToolUrl: '/api/chat/agent/confirm-tool',
33
+ commandsUrl: '/api/docs/commands',
34
+ buildListUrl: () => null,
35
+ attachmentUploadUrl: '/api/storage/generate-upload-url',
36
+ attachmentViewUrlPrefix: '/api/storage/view/chat-attachments/',
37
+ identityUrl: '/api/chat/identity',
38
+ },
39
+ navigation: { mode: 'host' },
40
+ source: SOURCE,
41
+ }
42
+
43
+ function wrapper({ children }: { children: ReactNode }) {
44
+ const queryClient = new QueryClient({
45
+ defaultOptions: { queries: { retry: false } },
46
+ })
47
+ return createElement(
48
+ QueryClientProvider,
49
+ { client: queryClient },
50
+ createElement(ChatRuntimeContext.Provider, { value: runtime }, children),
51
+ )
52
+ }
53
+
54
+ /** renderHook with the required runtime + react-query providers.
55
+ * `active: false` skips the slash-command registry fetch so the mocked
56
+ * fetch only ever serves the chat stream. */
57
+ function renderAdapter() {
58
+ return renderHook(() => useSseChatAdapter(undefined, { active: false }), { wrapper })
59
+ }
60
+
61
+ const enc = new TextEncoder()
62
+
63
+ /** Build a ReadableStream that enqueues each chunk (string → UTF-8) then closes. */
64
+ function streamOf(chunks: Array<string | Uint8Array>): ReadableStream<Uint8Array> {
65
+ return new ReadableStream<Uint8Array>({
66
+ start(controller) {
67
+ for (const c of chunks) {
68
+ controller.enqueue(typeof c === 'string' ? enc.encode(c) : c)
69
+ }
70
+ controller.close()
71
+ },
72
+ })
73
+ }
74
+
75
+ /** Mock global fetch to serve ONE streamed chat response. */
76
+ function mockFetchStream(chunks: Array<string | Uint8Array>) {
77
+ const fetchMock = vi.fn(async (_url: string, init?: RequestInit) => {
78
+ const body = streamOf(chunks)
79
+ // Honor the caller's AbortSignal like real fetch: error the stream so
80
+ // pending reader.read() calls reject with AbortError.
81
+ return { ok: true, status: 200, body } as unknown as Response
82
+ })
83
+ vi.stubGlobal('fetch', fetchMock)
84
+ return fetchMock
85
+ }
86
+
87
+ const tick = () => new Promise<void>((r) => setTimeout(r, 0))
88
+
89
+ beforeEach(() => {
90
+ window.localStorage.clear()
91
+ // Deterministic Date.now: message ids (`user-<now>`) and the 50ms
92
+ // thinking-throttle both read it. With a frozen clock the FIRST
93
+ // thinking delta yields immediately (now - 0 >= 50) and every later
94
+ // delta within the same stream is throttled (now - last = 0 < 50) and
95
+ // flushed at the \x1E boundary — fully deterministic.
96
+ vi.spyOn(Date, 'now').mockReturnValue(1_700_000_000_000)
97
+ })
98
+
99
+ afterEach(() => {
100
+ vi.unstubAllGlobals()
101
+ vi.restoreAllMocks()
102
+ })
103
+
104
+ /** Compact projection of the adapter's public state for snapshots. */
105
+ function snapshotState(result: { current: ReturnType<typeof useSseChatAdapter> }) {
106
+ const s = result.current
107
+ return {
108
+ messages: s.messages,
109
+ streamingPhase: s.streamingPhase,
110
+ provider: s.currentProvider,
111
+ modelLabel: s.currentModelLabel,
112
+ contextWindowMaxTokens: s.currentContextWindowMaxTokens,
113
+ inputTokens: s.currentInputTokens,
114
+ outputTokens: s.currentOutputTokens,
115
+ cacheHitRatePct: s.currentCacheHitRatePct,
116
+ usageBreakdown: s.currentUsageBreakdown,
117
+ }
118
+ }
119
+
120
+ // Shared leading-frame fixtures (recorded wire shapes).
121
+ const THINKING_STATUS = '{"status":"thinking"}\0'
122
+ const THINKING_DELTA_1 = '{"kind":"thinking-delta","text":"Let me check the docs… "}\0'
123
+ const THINKING_DELTA_2 = '{"kind":"thinking-delta","text":"found it."}\0'
124
+ const METADATA_FRAME =
125
+ '{"sources":[{"index":1,"name":"Pricing overview","path":"docs/pricing.md","documentType":"markdown","sourceRepo":"markdown"}],"model":"claude-sonnet-x","modelLabel":"Claude Sonnet","provider":"anthropic","contextWindowMaxTokens":200000}\0'
126
+ const ROUTING_FRAME = '{"kind":"routing","routedComplexity":"simple","routedThinkingBudget":0}\0'
127
+ const USAGE_START_FRAME = '{"kind":"usage","stage":"start","input_tokens":1234}\0'
128
+ const USAGE_END_TRAILER =
129
+ '{"kind":"usage","stage":"end","input_tokens":1234,"output_tokens":56,"hit_rate_pct":81.5,"breakdown":{"haikuRewriter":{"input":10,"output":4},"haikuClassifier":{"input":8,"output":1},"routedAnswer":{"model":"claude-sonnet-x","complexity":"simple","thinkingBudget":0}}}'
130
+
131
+ describe('SSE wire decode — golden fixtures (full hook path)', () => {
132
+ it('(a) normal turn: thinking frames → metadata → usage:start → \\x1E → text ×3 → \\x1F trailer', async () => {
133
+ mockFetchStream([
134
+ THINKING_STATUS,
135
+ THINKING_DELTA_1,
136
+ THINKING_DELTA_2,
137
+ METADATA_FRAME,
138
+ ROUTING_FRAME,
139
+ USAGE_START_FRAME,
140
+ '\x1E',
141
+ 'Answer part one, ',
142
+ 'part two, ',
143
+ 'part three.',
144
+ '\x1F' + USAGE_END_TRAILER,
145
+ ])
146
+ const { result } = renderAdapter()
147
+ await act(async () => {
148
+ await result.current.sendMessage('what is the pricing?')
149
+ })
150
+ expect(snapshotState(result)).toMatchSnapshot()
151
+ })
152
+
153
+ it('(b) EVERYTHING in one single TCP chunk', async () => {
154
+ mockFetchStream([
155
+ THINKING_STATUS +
156
+ THINKING_DELTA_1 +
157
+ METADATA_FRAME +
158
+ USAGE_START_FRAME +
159
+ '\x1E' +
160
+ 'One-chunk answer.' +
161
+ '\x1F' +
162
+ USAGE_END_TRAILER,
163
+ ])
164
+ const { result } = renderAdapter()
165
+ await act(async () => {
166
+ await result.current.sendMessage('one chunk please')
167
+ })
168
+ expect(snapshotState(result)).toMatchSnapshot()
169
+ })
170
+
171
+ it('(c) trailer split across two chunks arriving with stream done', async () => {
172
+ mockFetchStream([
173
+ METADATA_FRAME,
174
+ '\x1E',
175
+ 'Split-trailer answer.',
176
+ '\x1F{"kind":"usage","stage":"end","input_to',
177
+ 'kens":42,"output_tokens":9,"hit_rate_pct":12.5}',
178
+ ])
179
+ const { result } = renderAdapter()
180
+ await act(async () => {
181
+ await result.current.sendMessage('split the trailer')
182
+ })
183
+ expect(snapshotState(result)).toMatchSnapshot()
184
+ })
185
+
186
+ it('(d) answer text containing literal \\x1E/\\x1F/\\0 bytes', async () => {
187
+ // CHARACTERIZATION: current behavior, see unification plan.
188
+ // - In text mode only \x1F is scanned: literal \0 and \x1E bytes in
189
+ // the answer pass through into the rendered text unchanged.
190
+ // - The FIRST literal \x1F flips the decoder into trailer mode:
191
+ // everything after it (including later real text) is captured as
192
+ // the trailer buffer, fails JSON.parse, and is SILENTLY DROPPED —
193
+ // the real \x1F usage trailer is lost too (mis-framing/truncation).
194
+ mockFetchStream([
195
+ METADATA_FRAME,
196
+ '\x1E',
197
+ 'has-nul[\0]and-rs[\x1E]still-text. ',
198
+ 'now-us[\x1F]everything-after-is-swallowed',
199
+ ' including this and the real trailer:',
200
+ '\x1F' + USAGE_END_TRAILER,
201
+ ])
202
+ const { result } = renderAdapter()
203
+ await act(async () => {
204
+ await result.current.sendMessage('sentinel bytes in answer')
205
+ })
206
+ expect(snapshotState(result)).toMatchSnapshot()
207
+ })
208
+
209
+ it('(e) [card://blog:x] marker in the answer with refs metadata frame', async () => {
210
+ const refsFrame =
211
+ '{"refs":{"blog:x":{"type":"blog","id":"x","title":"Post X","url":"/blog/x","metadata":{"slug":"post-x"}}}}\0'
212
+ mockFetchStream([
213
+ METADATA_FRAME,
214
+ refsFrame,
215
+ '\x1E',
216
+ 'See [card://blog:x] for the full story.',
217
+ '\x1F' + USAGE_END_TRAILER,
218
+ ])
219
+ const { result } = renderAdapter()
220
+ await act(async () => {
221
+ await result.current.sendMessage('show me the post')
222
+ })
223
+ expect(snapshotState(result)).toMatchSnapshot()
224
+ })
225
+
226
+ it('(f) abort mid-stream via AbortController leaves the partial message in place', async () => {
227
+ // Manually-controlled stream: enqueue the leading frames + first text
228
+ // delta, keep the stream OPEN, then Stop. The mocked fetch honors the
229
+ // AbortSignal by erroring the stream with AbortError — same observable
230
+ // behavior as real fetch cancelling the body.
231
+ let ctrl!: ReadableStreamDefaultController<Uint8Array>
232
+ const body = new ReadableStream<Uint8Array>({
233
+ start(c) {
234
+ ctrl = c
235
+ c.enqueue(enc.encode(METADATA_FRAME))
236
+ c.enqueue(enc.encode('\x1E'))
237
+ c.enqueue(enc.encode('Partial ans'))
238
+ },
239
+ })
240
+ const fetchMock = vi.fn(async (_url: string, init?: RequestInit) => {
241
+ init?.signal?.addEventListener('abort', () => {
242
+ try {
243
+ ctrl.error(new DOMException('The operation was aborted.', 'AbortError'))
244
+ } catch {
245
+ /* already closed */
246
+ }
247
+ })
248
+ return { ok: true, status: 200, body } as unknown as Response
249
+ })
250
+ vi.stubGlobal('fetch', fetchMock)
251
+
252
+ const { result } = renderAdapter()
253
+ let sendPromise!: Promise<void>
254
+ act(() => {
255
+ sendPromise = result.current.sendMessage('will be aborted')
256
+ })
257
+ // Let the decoder consume the enqueued chunks.
258
+ await act(async () => {
259
+ await tick()
260
+ await tick()
261
+ await tick()
262
+ })
263
+ act(() => {
264
+ result.current.stopMessage()
265
+ })
266
+ await act(async () => {
267
+ await sendPromise
268
+ })
269
+ expect(snapshotState(result)).toMatchSnapshot()
270
+ expect(fetchMock).toHaveBeenCalledTimes(1)
271
+ })
272
+
273
+ it('(g) multi-byte UTF-8 character split across two chunks decodes intact', async () => {
274
+ // '🦩' = F0 9F A6 A9 — split 2+2 across chunk boundary. The decoder is
275
+ // created once per stream with {stream:true} reads, so the glyph
276
+ // survives the split. CHARACTERIZATION: current (correct) behavior.
277
+ const flamingo = enc.encode('🦩')
278
+ mockFetchStream([
279
+ METADATA_FRAME,
280
+ '\x1E',
281
+ 'A wild ',
282
+ flamingo.slice(0, 2),
283
+ flamingo.slice(2),
284
+ ' appears',
285
+ '\x1F' + USAGE_END_TRAILER,
286
+ ])
287
+ const { result } = renderAdapter()
288
+ await act(async () => {
289
+ await result.current.sendMessage('emoji split')
290
+ })
291
+ expect(snapshotState(result)).toMatchSnapshot()
292
+ })
293
+
294
+ it('leading approval_request + text-leading frames yield a pending approval card', async () => {
295
+ mockFetchStream([
296
+ '{"kind":"text-leading","text":"I\'ll open a ticket for you."}\0',
297
+ '{"kind":"approval_request","proposalId":"prop-1","toolName":"create_ticket","title":"Create ticket","fields":[{"label":"Subject","value":"Printer down"},{"label":"Priority","value":"High"},{"bad":"missing label/value"}]}\0',
298
+ // Stream closes with NO \x1E answer body — the approval turn ends here.
299
+ ])
300
+ const { result } = renderAdapter()
301
+ await act(async () => {
302
+ await result.current.sendMessage('open a ticket')
303
+ })
304
+ expect(snapshotState(result)).toMatchSnapshot()
305
+ })
306
+
307
+ it('non-JSON leading buffer falls through to answer text (legacy no-frame stream)', async () => {
308
+ // CHARACTERIZATION: a frame-less response whose first \0-terminated
309
+ // block is not JSON flips straight into text mode and the buffer is
310
+ // emitted as answer text.
311
+ mockFetchStream(['plain text answer with no frames\0and more after the nul'])
312
+ const { result } = renderAdapter()
313
+ await act(async () => {
314
+ await result.current.sendMessage('legacy stream')
315
+ })
316
+ expect(snapshotState(result)).toMatchSnapshot()
317
+ })
318
+ })
@@ -5,7 +5,6 @@ export * from './use-collapsible'
5
5
  export * from './use-scripted-stream'
6
6
  export * from './use-jetstream-dialog-subscription'
7
7
  export * from './use-nats-dialog-subscription'
8
- export * from './use-realtime-chunk-processor'
9
8
 
10
9
  // Chat hooks migrated from hub `hooks/*` (Task #7).
11
10
  export * from './use-slash-commands'
@@ -0,0 +1,142 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * useChatHistoryHydration — mount-time rebuild of the chat message list from
5
+ * the SERVER-side conversation store (the `chat_conversations` /
6
+ * `chat_messages` SSOT behind `GET <chatStreamUrl>/history`).
7
+ *
8
+ * The client persists ONLY the server-issued conversation id (see
9
+ * `chat-conversation-storage.ts`); this hook turns that id back into rendered
10
+ * history: message bubbles, inline entity-card refs (per send index, same
11
+ * `sendIdx` scheme as live turns), and the send counter.
12
+ *
13
+ * Failure semantics: a fetch/parse failure simply leaves the UI empty — the
14
+ * SERVER still resolves full history for the next turn (it re-reads
15
+ * `chat_messages` by conversation id on every send), so conversation context
16
+ * is never lost when hydration misses.
17
+ */
18
+
19
+ import { useEffect, useRef, useState, type MutableRefObject } from 'react'
20
+ import type { ChatRef } from '../chat-ref.types'
21
+ import type { Message } from './use-chat'
22
+ import { chatAuthedFetch } from '../utils/chat-authed-fetch'
23
+ import { AUTO_CONTINUATION_DIRECTIVE_PREFIX } from '../utils/auto-continuation-directive'
24
+
25
+ export interface UseChatHistoryHydrationArgs {
26
+ /** Mirrors the adapter's `active` gate — an idle (Mingo-mode) mount never fetches. */
27
+ active: boolean
28
+ /** Chat source (= platform); part of the once-per-conversation guard key. */
29
+ source: string
30
+ /** Resolved history endpoint (`<chatStreamUrl>/history` by default). */
31
+ historyUrl: string
32
+ /** The server-issued conversation id (null = nothing to hydrate). */
33
+ conversationIdRef: MutableRefObject<string | null>
34
+ /** Per-send inline entity-card refs — repopulated from `chat_refs`. */
35
+ refsMapRef: MutableRefObject<Map<number, Record<string, ChatRef>>>
36
+ /** User-send counter — set to the hydrated user-turn count so the next
37
+ * live send lands on the following `sendIdx`. */
38
+ sendCountRef: MutableRefObject<number>
39
+ /** `useChat`'s injection primitive (replace-or-prepend). */
40
+ hydrateMessages: (history: Message[]) => void
41
+ /** Invalidates the adapter's `latestMeta` memo after refs repopulate. */
42
+ bumpMetaTick: () => void
43
+ }
44
+
45
+ export interface UseChatHistoryHydrationResult {
46
+ /** True while the rebuild request is in flight. */
47
+ isHydratingHistory: boolean
48
+ /** Once-per-`source:conversationId` guard. The adapter's `clearMessages`
49
+ * resets it to null alongside the stored id (a fresh conversation has no
50
+ * server history to fetch). */
51
+ hydratedKeyRef: MutableRefObject<string | null>
52
+ }
53
+
54
+ export function useChatHistoryHydration({
55
+ active,
56
+ source,
57
+ historyUrl,
58
+ conversationIdRef,
59
+ refsMapRef,
60
+ sendCountRef,
61
+ hydrateMessages,
62
+ bumpMetaTick,
63
+ }: UseChatHistoryHydrationArgs): UseChatHistoryHydrationResult {
64
+ const [isHydratingHistory, setIsHydratingHistory] = useState(false)
65
+ const hydratedKeyRef = useRef<string | null>(null)
66
+
67
+ useEffect(() => {
68
+ if (!active) return
69
+ // No stored conversation id → nothing to hydrate (fresh visitor / after
70
+ // "new chat"); the first send establishes the conversation server-side.
71
+ const conversationId = conversationIdRef.current
72
+ if (!conversationId) return
73
+ const key = `${source}:${conversationId}`
74
+ if (hydratedKeyRef.current === key) return
75
+ hydratedKeyRef.current = key
76
+ let cancelled = false
77
+ setIsHydratingHistory(true)
78
+ ;(async () => {
79
+ try {
80
+ const res = await chatAuthedFetch(
81
+ `${historyUrl}?conversationId=${encodeURIComponent(conversationId)}`,
82
+ { method: 'GET' },
83
+ )
84
+ if (!res.ok) return
85
+ const payload = await res.json().catch(() => null)
86
+ // route-base successResponse envelope ({ data }) with a raw-body fallback.
87
+ const body = (payload?.data ?? payload) as
88
+ | { messages?: Array<Record<string, unknown>> }
89
+ | null
90
+ const rows = Array.isArray(body?.messages) ? body!.messages! : []
91
+ if (cancelled || rows.length === 0) return
92
+ const hydrated: Message[] = []
93
+ let userTurns = 0
94
+ for (const row of rows) {
95
+ const role =
96
+ row.role === 'assistant' ? 'assistant' : row.role === 'user' ? 'user' : null
97
+ if (!role) continue
98
+ const content = typeof row.content === 'string' ? row.content : ''
99
+ // The approval placeholder ('') and the server-built auto-continuation
100
+ // directive are part of the LLM history but never rendered.
101
+ const hidden =
102
+ role === 'user' &&
103
+ (content === '' || content.startsWith(AUTO_CONTINUATION_DIRECTIVE_PREFIX))
104
+ if (role === 'user') {
105
+ userTurns += 1
106
+ } else if (row.chat_refs && typeof row.chat_refs === 'object') {
107
+ // Re-attach inline entity-card refs at the send index this
108
+ // assistant row belongs to (same sendIdx scheme as live turns).
109
+ refsMapRef.current.set(
110
+ Math.max(0, userTurns - 1),
111
+ row.chat_refs as Record<string, ChatRef>,
112
+ )
113
+ }
114
+ hydrated.push({
115
+ id: `hydrated-${String(row.seq ?? hydrated.length)}`,
116
+ role,
117
+ content,
118
+ ...(typeof row.created_at === 'string'
119
+ ? { timestamp: new Date(row.created_at) }
120
+ : {}),
121
+ ...(hidden ? { hidden: true } : {}),
122
+ } as Message)
123
+ }
124
+ if (cancelled || hydrated.length === 0) return
125
+ sendCountRef.current = userTurns
126
+ hydrateMessages(hydrated)
127
+ bumpMetaTick()
128
+ } catch {
129
+ // Fetch failed — start empty; the server still owns history (above).
130
+ } finally {
131
+ if (!cancelled) setIsHydratingHistory(false)
132
+ }
133
+ })()
134
+ return () => {
135
+ cancelled = true
136
+ }
137
+ // Refs are stable across renders — the effect keys on the identity-ish deps only.
138
+ // eslint-disable-next-line react-hooks/exhaustive-deps
139
+ }, [active, source, historyUrl, hydrateMessages, bumpMetaTick])
140
+
141
+ return { isHydratingHistory, hydratedKeyRef }
142
+ }
@@ -1,5 +1,13 @@
1
1
  'use client'
2
2
 
3
+ /**
4
+ * @deprecated Phase 3 of the chat unification moved the SSE adapter off this
5
+ * hook — its trailing-assistant merge + `decision_resolved` receipt path now
6
+ * live in `../stream/chat-stream-reducer` (transport: 'sse'). No lib-internal
7
+ * code consumes `useChat`/`useSSE` anymore; they remain exported for external
8
+ * consumers until Phase 4 migrates them, then get deleted (Phase 6).
9
+ */
10
+
3
11
  import { useState, useCallback, useEffect, useRef } from 'react'
4
12
  import { useSSE, type StreamFn, type StreamFnExtraOptions } from './use-sse'
5
13
  import type {
@@ -476,6 +484,17 @@ export function useChat({
476
484
  reset()
477
485
  }, [reset])
478
486
 
487
+ /**
488
+ * Replace-or-prepend server-hydrated history. Called once by the SSE
489
+ * adapter after `GET /api/docs/chat/history` resolves. If the user already
490
+ * sent a message before hydration landed, the fetched history is PREPENDED
491
+ * so nothing typed is lost — correctness is unaffected either way because
492
+ * the server resolves LLM history from its own store, not from this state.
493
+ */
494
+ const hydrateMessages = useCallback((history: Message[]) => {
495
+ setMessages((prev) => (prev.length === 0 ? history : [...history, ...prev]))
496
+ }, [])
497
+
479
498
  /**
480
499
  * Abort the in-flight streamed message. The fetch's AbortSignal terminates
481
500
  * the upstream Anthropic request (so billing stops); the `for await` loop
@@ -496,6 +515,7 @@ export function useChat({
496
515
  stopMessage,
497
516
  handleQuickAction,
498
517
  clearMessages,
518
+ hydrateMessages,
499
519
  hasMessages: messages.length > 0,
500
520
  }
501
521
  }