@flamingo-stack/openframe-frontend-core 0.0.492-1506.3956.1 → 0.0.492

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 (296) hide show
  1. package/dist/{chunk-KX3K2EH4.cjs → chunk-2XAAFAQT.cjs} +11 -11
  2. package/dist/{chunk-KX3K2EH4.cjs.map → chunk-2XAAFAQT.cjs.map} +1 -1
  3. package/dist/{chunk-JHEIOXXZ.js → chunk-4AWRPTKE.js} +5 -6
  4. package/dist/chunk-4AWRPTKE.js.map +1 -0
  5. package/dist/{chunk-PTXZB2KW.js → chunk-4J3CB4KO.js} +2 -2
  6. package/dist/{chunk-7N7YZCHR.cjs → chunk-4MLOMMDX.cjs} +5519 -7555
  7. package/dist/chunk-4MLOMMDX.cjs.map +1 -0
  8. package/dist/{chunk-MEMRQTOI.js → chunk-56J7RLMM.js} +10434 -12470
  9. package/dist/chunk-56J7RLMM.js.map +1 -0
  10. package/dist/{chunk-MEIGDK7M.cjs → chunk-5ICPQAUI.cjs} +60 -60
  11. package/dist/chunk-5ICPQAUI.cjs.map +1 -0
  12. package/dist/{chunk-BEEPU44B.js → chunk-5RHJVPL7.js} +10 -10
  13. package/dist/chunk-5RHJVPL7.js.map +1 -0
  14. package/dist/{chunk-XOFE2RT4.js → chunk-BTIRYGDT.js} +4 -4
  15. package/dist/{chunk-JSEJ5MH5.cjs → chunk-DY6AJNVK.cjs} +5 -5
  16. package/dist/{chunk-JSEJ5MH5.cjs.map → chunk-DY6AJNVK.cjs.map} +1 -1
  17. package/dist/{chunk-PLB6CQSO.js → chunk-FG5ALORQ.js} +2 -2
  18. package/dist/{chunk-QG2EBIH5.cjs → chunk-J2J36PEN.cjs} +31 -31
  19. package/dist/{chunk-QG2EBIH5.cjs.map → chunk-J2J36PEN.cjs.map} +1 -1
  20. package/dist/{chunk-A6FPYAUD.cjs → chunk-LIRXKEOE.cjs} +15 -15
  21. package/dist/{chunk-A6FPYAUD.cjs.map → chunk-LIRXKEOE.cjs.map} +1 -1
  22. package/dist/{chunk-MSL5NE3N.cjs → chunk-LVDI7R7T.cjs} +37 -37
  23. package/dist/{chunk-MSL5NE3N.cjs.map → chunk-LVDI7R7T.cjs.map} +1 -1
  24. package/dist/{chunk-MPUQFRJG.cjs → chunk-M4SCYPCR.cjs} +7 -7
  25. package/dist/{chunk-MPUQFRJG.cjs.map → chunk-M4SCYPCR.cjs.map} +1 -1
  26. package/dist/{chunk-QDGB2DKY.cjs → chunk-O6S6Q52W.cjs} +26 -26
  27. package/dist/{chunk-QDGB2DKY.cjs.map → chunk-O6S6Q52W.cjs.map} +1 -1
  28. package/dist/{chunk-W56JYDSC.cjs → chunk-OFFYASUY.cjs} +27 -28
  29. package/dist/chunk-OFFYASUY.cjs.map +1 -0
  30. package/dist/{chunk-NCAN6VUF.js → chunk-P3ITKTUE.js} +2 -2
  31. package/dist/{chunk-M6FEGPQJ.cjs → chunk-PI5BMKV3.cjs} +62 -62
  32. package/dist/{chunk-M6FEGPQJ.cjs.map → chunk-PI5BMKV3.cjs.map} +1 -1
  33. package/dist/{chunk-34TDA22K.js → chunk-QHRNQTW5.js} +2 -2
  34. package/dist/{chunk-OL3MJKUI.js → chunk-SZWMJIH3.js} +2 -2
  35. package/dist/{chunk-MZRIRQYV.js → chunk-U5LU56JE.js} +2 -2
  36. package/dist/{chunk-C65NCIMV.cjs → chunk-VML66FJQ.cjs} +9 -9
  37. package/dist/{chunk-C65NCIMV.cjs.map → chunk-VML66FJQ.cjs.map} +1 -1
  38. package/dist/{chunk-3LKB722U.js → chunk-WKWZ7LVC.js} +2 -2
  39. package/dist/{chunk-J3VBVR4W.js → chunk-WPFLV4QJ.js} +4 -4
  40. package/dist/components/case-studies/index.cjs +8 -8
  41. package/dist/components/case-studies/index.js +2 -2
  42. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  43. package/dist/components/chat/chat-message-list.d.ts.map +1 -1
  44. package/dist/components/chat/hooks/index.d.ts +1 -0
  45. package/dist/components/chat/hooks/index.d.ts.map +1 -1
  46. package/dist/components/chat/hooks/use-chat.d.ts.map +1 -1
  47. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +48 -15
  48. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  49. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts +6 -0
  50. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +1 -0
  51. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +57 -10
  52. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  53. package/dist/components/chat/index.cjs +2 -10
  54. package/dist/components/chat/index.cjs.map +1 -1
  55. package/dist/components/chat/index.d.ts +0 -1
  56. package/dist/components/chat/index.d.ts.map +1 -1
  57. package/dist/components/chat/index.js +13 -21
  58. package/dist/components/chat/types/api.types.d.ts +81 -1
  59. package/dist/components/chat/types/api.types.d.ts.map +1 -1
  60. package/dist/components/chat/types/processing.types.d.ts +93 -2
  61. package/dist/components/chat/types/processing.types.d.ts.map +1 -1
  62. package/dist/components/chat/utils/chunk-parser.d.ts +25 -0
  63. package/dist/components/chat/utils/chunk-parser.d.ts.map +1 -0
  64. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts +6 -34
  65. package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
  66. package/dist/components/chat/utils/index.d.ts +3 -2
  67. package/dist/components/chat/utils/index.d.ts.map +1 -1
  68. package/dist/components/chat/utils/process-historical-messages.d.ts +11 -59
  69. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  70. package/dist/components/contact/index.cjs +3 -3
  71. package/dist/components/contact/index.js +2 -2
  72. package/dist/components/docs/index.cjs +5 -5
  73. package/dist/components/docs/index.js +4 -4
  74. package/dist/components/embeds/index.cjs +3 -3
  75. package/dist/components/embeds/index.js +2 -2
  76. package/dist/components/faq/index.cjs +3 -3
  77. package/dist/components/faq/index.js +2 -2
  78. package/dist/components/features/floating-walkthrough-video.d.ts.map +1 -1
  79. package/dist/components/features/index.cjs +2 -2
  80. package/dist/components/features/index.js +1 -1
  81. package/dist/components/help-center-pages/index.cjs +20 -20
  82. package/dist/components/help-center-pages/index.js +11 -11
  83. package/dist/components/index.cjs +130 -172
  84. package/dist/components/index.cjs.map +1 -1
  85. package/dist/components/index.js +21 -63
  86. package/dist/components/index.js.map +1 -1
  87. package/dist/components/navigation/index.cjs +2 -2
  88. package/dist/components/navigation/index.js +1 -1
  89. package/dist/components/navigation/navigation-sidebar-item.d.ts +23 -1
  90. package/dist/components/navigation/navigation-sidebar-item.d.ts.map +1 -1
  91. package/dist/components/navigation/navigation-sidebar.d.ts.map +1 -1
  92. package/dist/components/onboarding-guides/index.cjs +5 -5
  93. package/dist/components/onboarding-guides/index.js +4 -4
  94. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
  95. package/dist/components/related-content/index.cjs +3 -3
  96. package/dist/components/related-content/index.js +2 -2
  97. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  98. package/dist/components/tickets/index.cjs +5 -5
  99. package/dist/components/tickets/index.js +4 -4
  100. package/dist/components/ui/data-table/data-table-column-filter.d.ts.map +1 -1
  101. package/dist/components/ui/data-table/data-table-header.d.ts.map +1 -1
  102. package/dist/components/ui/data-table/data-table-skeleton.d.ts.map +1 -1
  103. package/dist/components/ui/index.cjs +2 -44
  104. package/dist/components/ui/index.cjs.map +1 -1
  105. package/dist/components/ui/index.d.ts +2 -1
  106. package/dist/components/ui/index.d.ts.map +1 -1
  107. package/dist/components/ui/index.js +13 -55
  108. package/dist/components/ui/rich-markdown-renderer.d.ts +34 -0
  109. package/dist/components/ui/rich-markdown-renderer.d.ts.map +1 -0
  110. package/dist/components/ui/simple-markdown-renderer.d.ts +73 -0
  111. package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -0
  112. package/dist/components/ui/tab-navigation.d.ts.map +1 -1
  113. package/dist/index.cjs +2 -60
  114. package/dist/index.cjs.map +1 -1
  115. package/dist/index.js +13 -71
  116. package/dist/utils/index.cjs +40 -169
  117. package/dist/utils/index.cjs.map +1 -1
  118. package/dist/utils/index.d.ts +0 -1
  119. package/dist/utils/index.d.ts.map +1 -1
  120. package/dist/utils/index.js +41 -162
  121. package/dist/utils/index.js.map +1 -1
  122. package/dist/utils/markdown-section-extractor.d.ts.map +1 -1
  123. package/package.json +1 -7
  124. package/src/components/chat/__tests__/chat-message-list.test.tsx +5 -293
  125. package/src/components/chat/chat-message-enhanced.tsx +20 -122
  126. package/src/components/chat/chat-message-list.tsx +109 -358
  127. package/src/components/chat/embeddable-chat.tsx +0 -6
  128. package/src/components/chat/hooks/.index.md +33 -30
  129. package/src/components/chat/hooks/.use-nats-chat-adapter.md +55 -36
  130. package/src/components/chat/hooks/.use-realtime-chunk-processor.md +77 -0
  131. package/src/components/chat/hooks/index.ts +1 -0
  132. package/src/components/chat/hooks/use-chat.ts +0 -8
  133. package/src/components/chat/hooks/use-nats-chat-adapter.ts +825 -243
  134. package/src/components/chat/hooks/use-realtime-chunk-processor.ts +489 -0
  135. package/src/components/chat/hooks/use-sse-chat-adapter.ts +684 -380
  136. package/src/components/chat/index.ts +0 -5
  137. package/src/components/chat/thinking-display.tsx +1 -1
  138. package/src/components/chat/types/.api.types.md +49 -39
  139. package/src/components/chat/types/.processing.types.md +58 -29
  140. package/src/components/chat/types/api.types.ts +73 -6
  141. package/src/components/chat/types/processing.types.ts +53 -11
  142. package/src/components/chat/utils/.chunk-parser.md +62 -0
  143. package/src/components/chat/utils/.index.md +40 -31
  144. package/src/components/chat/utils/chunk-parser.ts +262 -0
  145. package/src/components/chat/utils/extract-incomplete-message-state.ts +5 -63
  146. package/src/components/chat/utils/index.ts +8 -5
  147. package/src/components/chat/utils/process-historical-messages.ts +386 -358
  148. package/src/components/features/floating-walkthrough-video.tsx +3 -1
  149. package/src/components/navigation/navigation-sidebar-item.tsx +80 -30
  150. package/src/components/navigation/navigation-sidebar.tsx +106 -46
  151. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +4 -22
  152. package/src/components/shared/legal-document/legal-document-page.tsx +1 -1
  153. package/src/components/shared/product-release/release-detail-page.tsx +4 -19
  154. package/src/components/ui/.simple-markdown-renderer.md +52 -0
  155. package/src/components/ui/__tests__/tab-navigation.test.tsx +159 -0
  156. package/src/components/ui/data-table/data-table-column-filter.tsx +7 -1
  157. package/src/components/ui/data-table/data-table-header.tsx +4 -1
  158. package/src/components/ui/data-table/data-table-skeleton.tsx +7 -0
  159. package/src/components/ui/index.ts +2 -1
  160. package/src/components/ui/release-changelog-section.tsx +1 -1
  161. package/src/components/ui/rich-markdown-renderer.tsx +1223 -0
  162. package/src/components/ui/simple-markdown-renderer.tsx +964 -0
  163. package/src/components/ui/tab-navigation.tsx +370 -171
  164. package/src/components/ui/ticket-info-section.tsx +1 -1
  165. package/src/utils/index.ts +0 -1
  166. package/src/utils/markdown-section-extractor.ts +56 -41
  167. package/dist/chat-protocol/decode.d.ts +0 -58
  168. package/dist/chat-protocol/decode.d.ts.map +0 -1
  169. package/dist/chat-protocol/encode.d.ts +0 -44
  170. package/dist/chat-protocol/encode.d.ts.map +0 -1
  171. package/dist/chat-protocol/env-flag.d.ts +0 -35
  172. package/dist/chat-protocol/env-flag.d.ts.map +0 -1
  173. package/dist/chat-protocol/events.d.ts +0 -207
  174. package/dist/chat-protocol/events.d.ts.map +0 -1
  175. package/dist/chat-protocol/frames.d.ts +0 -213
  176. package/dist/chat-protocol/frames.d.ts.map +0 -1
  177. package/dist/chat-protocol/index.cjs +0 -500
  178. package/dist/chat-protocol/index.cjs.map +0 -1
  179. package/dist/chat-protocol/index.d.ts +0 -18
  180. package/dist/chat-protocol/index.d.ts.map +0 -1
  181. package/dist/chat-protocol/index.js +0 -484
  182. package/dist/chat-protocol/index.js.map +0 -1
  183. package/dist/chat-protocol/ip-normalize.d.ts +0 -44
  184. package/dist/chat-protocol/ip-normalize.d.ts.map +0 -1
  185. package/dist/chat-protocol/nats-decoder.d.ts +0 -26
  186. package/dist/chat-protocol/nats-decoder.d.ts.map +0 -1
  187. package/dist/chunk-7N7YZCHR.cjs.map +0 -1
  188. package/dist/chunk-BEEPU44B.js.map +0 -1
  189. package/dist/chunk-JHEIOXXZ.js.map +0 -1
  190. package/dist/chunk-MEIGDK7M.cjs.map +0 -1
  191. package/dist/chunk-MEMRQTOI.js.map +0 -1
  192. package/dist/chunk-W56JYDSC.cjs.map +0 -1
  193. package/dist/components/chat/stream/chat-dialog-store.d.ts +0 -184
  194. package/dist/components/chat/stream/chat-dialog-store.d.ts.map +0 -1
  195. package/dist/components/chat/stream/chat-stream-reducer.d.ts +0 -367
  196. package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +0 -1
  197. package/dist/components/chat/stream/delta-batcher.d.ts +0 -51
  198. package/dist/components/chat/stream/delta-batcher.d.ts.map +0 -1
  199. package/dist/components/chat/stream/index.d.ts +0 -17
  200. package/dist/components/chat/stream/index.d.ts.map +0 -1
  201. package/dist/components/chat/stream/message-mutations.d.ts +0 -77
  202. package/dist/components/chat/stream/message-mutations.d.ts.map +0 -1
  203. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts +0 -22
  204. package/dist/components/chat/stream/use-chat-stream-reducer.d.ts.map +0 -1
  205. package/dist/components/ui/markdown/base-components.d.ts +0 -51
  206. package/dist/components/ui/markdown/base-components.d.ts.map +0 -1
  207. package/dist/components/ui/markdown/engine.d.ts +0 -99
  208. package/dist/components/ui/markdown/engine.d.ts.map +0 -1
  209. package/dist/components/ui/markdown/heading-ids.d.ts +0 -65
  210. package/dist/components/ui/markdown/heading-ids.d.ts.map +0 -1
  211. package/dist/components/ui/markdown/index.d.ts +0 -18
  212. package/dist/components/ui/markdown/index.d.ts.map +0 -1
  213. package/dist/components/ui/markdown/mermaid-diagram.d.ts +0 -79
  214. package/dist/components/ui/markdown/mermaid-diagram.d.ts.map +0 -1
  215. package/dist/components/ui/markdown/rich/embed-overrides.d.ts +0 -14
  216. package/dist/components/ui/markdown/rich/embed-overrides.d.ts.map +0 -1
  217. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts +0 -40
  218. package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts.map +0 -1
  219. package/dist/components/ui/markdown/rich/shortcodes.d.ts +0 -8
  220. package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +0 -1
  221. package/dist/components/ui/markdown/sanitize.d.ts +0 -153
  222. package/dist/components/ui/markdown/sanitize.d.ts.map +0 -1
  223. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts +0 -23
  224. package/dist/components/ui/markdown/simple-markdown-renderer.d.ts.map +0 -1
  225. package/dist/components/ui/markdown/streaming.d.ts +0 -78
  226. package/dist/components/ui/markdown/streaming.d.ts.map +0 -1
  227. package/dist/components/ui/markdown/text-size.d.ts +0 -34
  228. package/dist/components/ui/markdown/text-size.d.ts.map +0 -1
  229. package/dist/utils/markdown-fences.d.ts +0 -42
  230. package/dist/utils/markdown-fences.d.ts.map +0 -1
  231. package/dist/utils/markdown-heading-id.d.ts +0 -85
  232. package/dist/utils/markdown-heading-id.d.ts.map +0 -1
  233. package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +0 -317
  234. package/src/chat-protocol/__tests__/chat-protocol.test.ts +0 -516
  235. package/src/chat-protocol/__tests__/env-flag.test.ts +0 -52
  236. package/src/chat-protocol/__tests__/ip-normalize.test.ts +0 -136
  237. package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +0 -232
  238. package/src/chat-protocol/decode.ts +0 -278
  239. package/src/chat-protocol/encode.ts +0 -71
  240. package/src/chat-protocol/env-flag.ts +0 -39
  241. package/src/chat-protocol/events.ts +0 -245
  242. package/src/chat-protocol/frames.ts +0 -245
  243. package/src/chat-protocol/index.ts +0 -21
  244. package/src/chat-protocol/ip-normalize.ts +0 -146
  245. package/src/chat-protocol/nats-decoder.ts +0 -258
  246. package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +0 -207
  247. package/src/components/chat/__tests__/chat-pending-turn.test.tsx +0 -230
  248. package/src/components/chat/hooks/__tests__/__snapshots__/conversation-id-persistence-golden.test.ts.snap +0 -37
  249. package/src/components/chat/hooks/__tests__/__snapshots__/sse-stream-golden.test.ts.snap +0 -0
  250. package/src/components/chat/hooks/__tests__/conversation-id-persistence-golden.test.ts +0 -438
  251. package/src/components/chat/hooks/__tests__/sse-stream-golden.test.ts +0 -318
  252. package/src/components/chat/stream/__tests__/__snapshots__/chat-stream-reducer-golden.test.ts.snap +0 -940
  253. package/src/components/chat/stream/__tests__/chat-dialog-store.test.ts +0 -756
  254. package/src/components/chat/stream/__tests__/chat-stream-reducer-golden.test.ts +0 -460
  255. package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +0 -973
  256. package/src/components/chat/stream/__tests__/delta-batcher.test.ts +0 -255
  257. package/src/components/chat/stream/__tests__/use-chat-stream-reducer.test.ts +0 -98
  258. package/src/components/chat/stream/chat-dialog-store.ts +0 -536
  259. package/src/components/chat/stream/chat-stream-reducer.ts +0 -1808
  260. package/src/components/chat/stream/delta-batcher.ts +0 -168
  261. package/src/components/chat/stream/index.ts +0 -55
  262. package/src/components/chat/stream/message-mutations.ts +0 -340
  263. package/src/components/chat/stream/use-chat-stream-reducer.ts +0 -126
  264. package/src/components/chat/utils/__tests__/__snapshots__/process-historical-messages-golden.test.ts.snap +0 -420
  265. package/src/components/chat/utils/__tests__/__snapshots__/segment-accumulator-golden.test.ts.snap +0 -605
  266. package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +0 -394
  267. package/src/components/chat/utils/__tests__/segment-accumulator-golden.test.ts +0 -270
  268. package/src/components/ui/__tests__/__snapshots__/markdown-parity.test.tsx.snap +0 -2420
  269. package/src/components/ui/__tests__/markdown-parity.test.tsx +0 -2528
  270. package/src/components/ui/markdown/__tests__/mermaid-security.test.ts +0 -115
  271. package/src/components/ui/markdown/__tests__/mermaid-stale-render.test.tsx +0 -95
  272. package/src/components/ui/markdown/__tests__/sanitize-invariant.test.ts +0 -309
  273. package/src/components/ui/markdown/__tests__/streaming.test.tsx +0 -388
  274. package/src/components/ui/markdown/base-components.tsx +0 -360
  275. package/src/components/ui/markdown/engine.tsx +0 -315
  276. package/src/components/ui/markdown/heading-ids.ts +0 -239
  277. package/src/components/ui/markdown/index.ts +0 -49
  278. package/src/components/ui/markdown/mermaid-diagram.tsx +0 -291
  279. package/src/components/ui/markdown/rich/embed-overrides.tsx +0 -199
  280. package/src/components/ui/markdown/rich/rich-markdown-renderer.tsx +0 -184
  281. package/src/components/ui/markdown/rich/shortcodes.ts +0 -170
  282. package/src/components/ui/markdown/sanitize.ts +0 -3499
  283. package/src/components/ui/markdown/simple-markdown-renderer.tsx +0 -28
  284. package/src/components/ui/markdown/streaming.ts +0 -362
  285. package/src/components/ui/markdown/text-size.ts +0 -106
  286. package/src/utils/markdown-fences.ts +0 -127
  287. package/src/utils/markdown-heading-id.ts +0 -348
  288. /package/dist/{chunk-PTXZB2KW.js.map → chunk-4J3CB4KO.js.map} +0 -0
  289. /package/dist/{chunk-XOFE2RT4.js.map → chunk-BTIRYGDT.js.map} +0 -0
  290. /package/dist/{chunk-PLB6CQSO.js.map → chunk-FG5ALORQ.js.map} +0 -0
  291. /package/dist/{chunk-NCAN6VUF.js.map → chunk-P3ITKTUE.js.map} +0 -0
  292. /package/dist/{chunk-34TDA22K.js.map → chunk-QHRNQTW5.js.map} +0 -0
  293. /package/dist/{chunk-OL3MJKUI.js.map → chunk-SZWMJIH3.js.map} +0 -0
  294. /package/dist/{chunk-MZRIRQYV.js.map → chunk-U5LU56JE.js.map} +0 -0
  295. /package/dist/{chunk-3LKB722U.js.map → chunk-WKWZ7LVC.js.map} +0 -0
  296. /package/dist/{chunk-J3VBVR4W.js.map → chunk-WPFLV4QJ.js.map} +0 -0
@@ -1,516 +0,0 @@
1
- /**
2
- * Unit tests for the chat wire-protocol SSOT (`src/chat-protocol/`) —
3
- * the decoder state machine, the encoders, and their round-trip. The
4
- * adapter-level golden fixtures
5
- * (`src/components/chat/hooks/__tests__/sse-stream-golden.test.ts`) pin
6
- * the full hook path; these pin the protocol module in isolation.
7
- */
8
-
9
- import { describe, it, expect } from 'vitest'
10
- import {
11
- createSseFrameDecoder,
12
- escapeThinkingTags,
13
- encodeLeadingFrame,
14
- encodeEndOfLeading,
15
- encodeTextDelta,
16
- encodeTrailingUsageFrame,
17
- stripSentinelBytes,
18
- decodeNatsChunk,
19
- END_OF_LEADING,
20
- TRAILER_SENTINEL,
21
- FRAME_TERMINATOR,
22
- type ChatStreamEvent,
23
- type SseTrailingUsageFrame,
24
- } from '../index'
25
-
26
- const enc = new TextEncoder()
27
-
28
- /** Run a full stream through a fresh decoder: push each chunk, then end(). */
29
- function decodeAll(chunks: Array<string | Uint8Array>): ChatStreamEvent[] {
30
- const decoder = createSseFrameDecoder()
31
- const out: ChatStreamEvent[] = []
32
- for (const c of chunks) {
33
- out.push(...decoder.push(typeof c === 'string' ? enc.encode(c) : c))
34
- }
35
- out.push(...decoder.end())
36
- return out
37
- }
38
-
39
- const TRAILER: SseTrailingUsageFrame = {
40
- kind: 'usage',
41
- stage: 'end',
42
- input_tokens: 1234,
43
- output_tokens: 56,
44
- hit_rate_pct: 81.5,
45
- breakdown: {
46
- haikuRewriter: { input: 10, output: 4 },
47
- routedAnswer: { model: 'claude-sonnet-x', complexity: 'default', thinkingBudget: 0 },
48
- },
49
- }
50
-
51
- describe('createSseFrameDecoder', () => {
52
- it('decodes everything arriving in ONE chunk (frames + sentinel + text + trailer)', () => {
53
- const wire =
54
- '{"status":"thinking"}\0' +
55
- '{"kind":"thinking-delta","text":"hmm "}\0' +
56
- '{"sources":[{"index":1}],"model":"m","modelLabel":"M","provider":"anthropic","contextWindowMaxTokens":200000,"conversationId":"conv-1"}\0' +
57
- '{"kind":"usage","stage":"start","input_tokens":11}\0' +
58
- END_OF_LEADING +
59
- 'One-chunk answer.' +
60
- TRAILER_SENTINEL +
61
- JSON.stringify(TRAILER)
62
- const events = decodeAll([wire])
63
- expect(events).toEqual([
64
- { type: 'status', phase: 'thinking' },
65
- { type: 'thinking-delta', text: 'hmm ' },
66
- {
67
- type: 'metadata',
68
- sources: [{ index: 1 }],
69
- refs: undefined,
70
- provider: 'anthropic',
71
- modelLabel: 'M',
72
- modelName: 'm',
73
- contextWindowMaxTokens: 200000,
74
- scrollAnchor: undefined,
75
- conversationId: 'conv-1',
76
- },
77
- {
78
- type: 'usage',
79
- stage: 'start',
80
- input_tokens: 11,
81
- cache_read_input_tokens: undefined,
82
- cache_creation_input_tokens: undefined,
83
- },
84
- { type: 'turn-start' },
85
- { type: 'text-delta', text: 'One-chunk answer.' },
86
- {
87
- type: 'usage',
88
- stage: 'end',
89
- input_tokens: 1234,
90
- output_tokens: 56,
91
- cache_read_input_tokens: undefined,
92
- cache_creation_input_tokens: undefined,
93
- hit_rate_pct: 81.5,
94
- telemetry: undefined,
95
- breakdown: TRAILER.breakdown,
96
- debug: undefined,
97
- },
98
- ])
99
- })
100
-
101
- it('parses a trailer split across pushes only at end()', () => {
102
- const decoder = createSseFrameDecoder()
103
- const events: ChatStreamEvent[] = []
104
- events.push(...decoder.push(enc.encode(END_OF_LEADING + 'answer')))
105
- events.push(
106
- ...decoder.push(enc.encode(TRAILER_SENTINEL + '{"kind":"usage","stage":"end","input_to')),
107
- )
108
- events.push(...decoder.push(enc.encode('kens":42,"output_tokens":9}')))
109
- // No usage event until end() — trailer runs to stream end.
110
- expect(events.map((e) => e.type)).toEqual(['turn-start', 'text-delta'])
111
- const endEvents = decoder.end()
112
- expect(endEvents).toHaveLength(1)
113
- expect(endEvents[0]).toMatchObject({
114
- type: 'usage',
115
- stage: 'end',
116
- input_tokens: 42,
117
- output_tokens: 9,
118
- })
119
- })
120
-
121
- it('end() is IDEMPOTENT: a second call emits the usage frame zero more times', () => {
122
- // Adapters routinely call end() from BOTH their completion path and a
123
- // `finally`; a re-emitted usage event would double the displayed token
124
- // cost.
125
- const decoder = createSseFrameDecoder()
126
- decoder.push(
127
- enc.encode(
128
- END_OF_LEADING +
129
- 'answer' +
130
- TRAILER_SENTINEL +
131
- '{"kind":"usage","stage":"end","input_tokens":42,"output_tokens":9}',
132
- ),
133
- )
134
- const first = decoder.end()
135
- expect(first).toHaveLength(1)
136
- expect(first[0]).toMatchObject({ type: 'usage', stage: 'end', input_tokens: 42 })
137
- expect(decoder.end()).toEqual([])
138
- expect(decoder.end()).toEqual([])
139
- })
140
-
141
- it('silently ignores a malformed trailer at end()', () => {
142
- const decoder = createSseFrameDecoder()
143
- decoder.push(enc.encode(END_OF_LEADING + 'x' + TRAILER_SENTINEL + 'not json'))
144
- expect(decoder.end()).toEqual([])
145
- })
146
-
147
- it('decodes a multi-byte UTF-8 character split across pushes intact', () => {
148
- const flamingo = enc.encode('🦩') // F0 9F A6 A9
149
- const events = decodeAll([
150
- END_OF_LEADING + 'A wild ',
151
- flamingo.slice(0, 2),
152
- flamingo.slice(2),
153
- ' appears',
154
- ])
155
- const text = events
156
- .filter((e): e is Extract<ChatStreamEvent, { type: 'text-delta' }> => e.type === 'text-delta')
157
- .map((e) => e.text)
158
- .join('')
159
- expect(text).toBe('A wild 🦩 appears')
160
- // The partial code point decodes to an EMPTY delta first (legacy
161
- // parity: text-mode chunks are emitted unconditionally).
162
- expect(events.map((e) => e.type)).toEqual([
163
- 'turn-start',
164
- 'text-delta',
165
- 'text-delta',
166
- 'text-delta',
167
- 'text-delta',
168
- ])
169
- })
170
-
171
- it('emits thinking deltas VERBATIM (append-only) — escape(concat) === concat(escape), even with a tag split across deltas', () => {
172
- const events = decodeAll([
173
- '{"kind":"thinking-delta","text":"use <scr"}\0',
174
- '{"kind":"thinking-delta","text":"ipt> carefully"}\0',
175
- ])
176
- const deltas = events.filter(
177
- (e): e is Extract<ChatStreamEvent, { type: 'thinking-delta' }> =>
178
- e.type === 'thinking-delta',
179
- )
180
- expect(deltas.map((d) => d.text)).toEqual(['use <scr', 'ipt> carefully'])
181
- const raw = deltas.map((d) => d.text).join('')
182
- expect(raw).toBe('use <script> carefully')
183
- // escapeThinkingTags is per-character, so it distributes over
184
- // concatenation — accumulate-then-escape and escape-per-delta agree.
185
- expect(escapeThinkingTags(raw)).toBe('use &lt;script> carefully')
186
- expect(deltas.map((d) => escapeThinkingTags(d.text)).join('')).toBe(
187
- escapeThinkingTags(raw),
188
- )
189
- })
190
-
191
- it('falls back to answer-text mode when a leading block is not JSON (implicit turn-start, whole buffer emitted)', () => {
192
- const events = decodeAll(['plain text answer with no frames\0and more after the nul'])
193
- expect(events).toEqual([
194
- { type: 'turn-start', implicit: true },
195
- { type: 'text-delta', text: 'plain text answer with no frames\0and more after the nul' },
196
- ])
197
- })
198
-
199
- it('handles sentinel + text + trailer inside the post-sentinel slice of one chunk', () => {
200
- const events = decodeAll([
201
- '{"status":"thinking"}\0' +
202
- END_OF_LEADING +
203
- 'short' +
204
- TRAILER_SENTINEL +
205
- '{"kind":"usage","stage":"end","input_tokens":1}',
206
- ])
207
- expect(events.map((e) => e.type)).toEqual(['status', 'turn-start', 'text-delta', 'usage'])
208
- })
209
-
210
- it('drops an un-terminated leading buffer at end() (legacy parity)', () => {
211
- const decoder = createSseFrameDecoder()
212
- expect(decoder.push(enc.encode('{"kind":"thinking-de'))).toEqual([])
213
- expect(decoder.end()).toEqual([])
214
- })
215
-
216
- it('maps tool frames: text-leading, tool_error, approval_request, decision_resolved', () => {
217
- const events = decodeAll([
218
- '{"kind":"text-leading","text":"I\'ll open a ticket."}\0',
219
- '{"kind":"tool_error","toolName":"create_ticket","message":""}\0',
220
- '{"kind":"approval_request","proposalId":"prop-1","toolName":"create_ticket","title":"Create ticket","fields":[{"label":"Subject","value":"Printer down"},{"bad":"row"}]}\0',
221
- '{"kind":"decision_resolved","proposalId":"prop-1","ok":true,"action":"approved","willAutoContinue":true,"tool_name":"create_ticket","result":{"ticket_id":"T-1"},"card":{"type":"ticket","marker":"[card://ticket:T-1]","ref":{"type":"ticket","id":"T-1","title":"t","url":null}},"receiptText":"Done."}\0',
222
- ])
223
- expect(events).toEqual([
224
- { type: 'text-delta', text: "I'll open a ticket.", leading: true },
225
- // Empty message falls back to the canned copy (legacy parity).
226
- { type: 'error', title: 'Could not complete the requested action right now.' },
227
- {
228
- type: 'approval-request',
229
- requestId: 'prop-1',
230
- approvalType: 'create_ticket',
231
- command: 'Create ticket',
232
- fields: [{ label: 'Subject', value: 'Printer down' }],
233
- status: 'pending',
234
- },
235
- {
236
- type: 'approval-resolved',
237
- status: 'approved',
238
- ok: true,
239
- willAutoContinue: true,
240
- toolName: 'create_ticket',
241
- result: { ticket_id: 'T-1' },
242
- marker: '[card://ticket:T-1]',
243
- cardRef: { type: 'ticket', id: 'T-1', title: 't', url: null },
244
- cardType: 'ticket',
245
- receiptText: 'Done.',
246
- requestId: 'prop-1',
247
- },
248
- ])
249
- })
250
-
251
- it('routing frame → metadata.routing; non-string routedComplexity emits nothing', () => {
252
- const events = decodeAll([
253
- '{"kind":"routing","routedComplexity":"simple","routedThinkingBudget":0}\0',
254
- '{"kind":"routing","routedComplexity":42}\0',
255
- ])
256
- expect(events).toEqual([
257
- {
258
- type: 'metadata',
259
- routing: { routedComplexity: 'simple', routedThinkingBudget: 0 },
260
- },
261
- ])
262
- })
263
- })
264
-
265
- describe('stripSentinelBytes', () => {
266
- it('removes every framing sentinel and nothing else', () => {
267
- expect(
268
- stripSentinelBytes(`a${FRAME_TERMINATOR}b${END_OF_LEADING}c${TRAILER_SENTINEL}d`),
269
- ).toBe('abcd')
270
- expect(stripSentinelBytes('plain 🦩 text\nwith\tws')).toBe('plain 🦩 text\nwith\tws')
271
- })
272
-
273
- it('is idempotent and distributes over concatenation (safe per-delta OR on an accumulator)', () => {
274
- const a = `head${TRAILER_SENTINEL}`
275
- const b = `${FRAME_TERMINATOR}tail`
276
- expect(stripSentinelBytes(stripSentinelBytes(a))).toBe(stripSentinelBytes(a))
277
- expect(stripSentinelBytes(a + b)).toBe(stripSentinelBytes(a) + stripSentinelBytes(b))
278
- })
279
-
280
- it('is the SAME strip `encodeTextDelta` applies (one regex, two call sites)', () => {
281
- const hostile = `x${FRAME_TERMINATOR}y${END_OF_LEADING}z${TRAILER_SENTINEL}!`
282
- expect(new TextDecoder().decode(encodeTextDelta(hostile))).toBe(stripSentinelBytes(hostile))
283
- })
284
- })
285
-
286
- describe('encoders', () => {
287
- it('encodeTextDelta strips ALL sentinel bytes so hostile answer text round-trips clean', () => {
288
- const hostile = `has-nul[${FRAME_TERMINATOR}]rs[${END_OF_LEADING}]us[${TRAILER_SENTINEL}]end`
289
- const events = decodeAll([
290
- encodeLeadingFrame({ status: 'thinking' }),
291
- encodeEndOfLeading(),
292
- encodeTextDelta(hostile),
293
- encodeTrailingUsageFrame(TRAILER),
294
- ])
295
- const text = events
296
- .filter((e): e is Extract<ChatStreamEvent, { type: 'text-delta' }> => e.type === 'text-delta')
297
- .map((e) => e.text)
298
- .join('')
299
- // Sentinels removed — no mis-framing, trailer still parsed.
300
- expect(text).toBe('has-nul[]rs[]us[]end')
301
- expect(events.map((e) => e.type)).toEqual(['status', 'turn-start', 'text-delta', 'usage'])
302
- })
303
-
304
- it('round-trips every leading frame type through encode → decode', () => {
305
- const cases: Array<{ frame: Parameters<typeof encodeLeadingFrame>[0]; expected: ChatStreamEvent }> = [
306
- { frame: { status: 'thinking' }, expected: { type: 'status', phase: 'thinking' } },
307
- {
308
- frame: { kind: 'thinking-delta', text: 'slice' },
309
- expected: { type: 'thinking-delta', text: 'slice' },
310
- },
311
- {
312
- frame: {
313
- kind: 'routing',
314
- routedComplexity: 'complex',
315
- routedModel: 'claude-sonnet-x',
316
- routedThinkingBudget: 1024,
317
- },
318
- expected: {
319
- type: 'metadata',
320
- routing: {
321
- routedComplexity: 'complex',
322
- routedModel: 'claude-sonnet-x',
323
- routedThinkingBudget: 1024,
324
- },
325
- },
326
- },
327
- {
328
- frame: { kind: 'usage', stage: 'start', input_tokens: 7, cache_read_input_tokens: 3 },
329
- expected: {
330
- type: 'usage',
331
- stage: 'start',
332
- input_tokens: 7,
333
- cache_read_input_tokens: 3,
334
- cache_creation_input_tokens: undefined,
335
- },
336
- },
337
- {
338
- frame: { kind: 'text-leading', text: 'preamble' },
339
- expected: { type: 'text-delta', text: 'preamble', leading: true },
340
- },
341
- {
342
- frame: { kind: 'tool_error', toolName: 't', message: 'boom' },
343
- expected: { type: 'error', title: 'boom' },
344
- },
345
- {
346
- frame: {
347
- kind: 'approval_request',
348
- proposalId: 'p1',
349
- toolName: 'create_ticket',
350
- fields: [{ label: 'A', value: 'B' }],
351
- },
352
- expected: {
353
- type: 'approval-request',
354
- requestId: 'p1',
355
- approvalType: 'create_ticket',
356
- // No title → headline falls back to the tool name.
357
- command: 'create_ticket',
358
- fields: [{ label: 'A', value: 'B' }],
359
- status: 'pending',
360
- },
361
- },
362
- {
363
- frame: {
364
- kind: 'decision_resolved',
365
- proposalId: 'p1',
366
- ok: false,
367
- action: 'rejected',
368
- willAutoContinue: false,
369
- },
370
- expected: {
371
- type: 'approval-resolved',
372
- status: 'rejected',
373
- ok: false,
374
- willAutoContinue: false,
375
- requestId: 'p1',
376
- },
377
- },
378
- {
379
- frame: { modelLabel: 'Claude Sonnet', provider: 'anthropic', contextWindowMaxTokens: 200000 },
380
- expected: {
381
- type: 'metadata',
382
- sources: undefined,
383
- refs: undefined,
384
- provider: 'anthropic',
385
- modelLabel: 'Claude Sonnet',
386
- modelName: undefined,
387
- contextWindowMaxTokens: 200000,
388
- scrollAnchor: undefined,
389
- },
390
- },
391
- ]
392
- for (const { frame, expected } of cases) {
393
- const events = decodeAll([encodeLeadingFrame(frame)])
394
- expect(events, JSON.stringify(frame)).toEqual([expected])
395
- }
396
- })
397
-
398
- it('round-trips the trailing usage frame', () => {
399
- const events = decodeAll([
400
- encodeEndOfLeading(),
401
- encodeTextDelta('body'),
402
- encodeTrailingUsageFrame(TRAILER),
403
- ])
404
- expect(events[events.length - 1]).toMatchObject({
405
- type: 'usage',
406
- stage: 'end',
407
- input_tokens: 1234,
408
- output_tokens: 56,
409
- hit_rate_pct: 81.5,
410
- })
411
- })
412
- })
413
-
414
- describe('decodeNatsChunk', () => {
415
- it('lifts streamSeq into the seq envelope', () => {
416
- expect(decodeNatsChunk({ type: 'TEXT', text: 'hi', streamSeq: 42 })).toEqual({
417
- type: 'text-delta',
418
- text: 'hi',
419
- seq: 42,
420
- })
421
- expect(decodeNatsChunk({ type: 'MESSAGE_START' })).toEqual({ type: 'turn-start' })
422
- expect(decodeNatsChunk({ type: 'MESSAGE_END', streamSeq: 9 })).toEqual({
423
- type: 'turn-end',
424
- seq: 9,
425
- })
426
- })
427
-
428
- it('maps metadata, approval batch, approval result, token usage, compaction', () => {
429
- expect(
430
- decodeNatsChunk({
431
- type: 'AI_METADATA',
432
- modelName: 'gpt-x',
433
- modelDisplayName: 'GPT X',
434
- providerName: 'openai',
435
- contextWindow: 128000,
436
- }),
437
- ).toEqual({
438
- type: 'metadata',
439
- modelLabel: 'GPT X',
440
- modelName: 'gpt-x',
441
- provider: 'openai',
442
- contextWindowMaxTokens: 128000,
443
- })
444
-
445
- expect(
446
- decodeNatsChunk({
447
- type: 'APPROVAL_REQUEST',
448
- approvalRequestId: 'req-1',
449
- approvalType: 'ADMIN',
450
- toolCalls: [
451
- { toolExecutionRequestId: 'e1', toolName: 'run', requiresApproval: true },
452
- 'garbage-row',
453
- ],
454
- }),
455
- ).toEqual({
456
- type: 'approval-request',
457
- requestId: 'req-1',
458
- approvalType: 'ADMIN',
459
- toolCalls: [
460
- {
461
- toolExecutionRequestId: 'e1',
462
- toolName: 'run',
463
- toolTitle: undefined,
464
- toolExplanation: undefined,
465
- toolType: undefined,
466
- requiresApproval: true,
467
- approvalType: null,
468
- toolCallArguments: null,
469
- },
470
- ],
471
- })
472
-
473
- expect(
474
- decodeNatsChunk({
475
- type: 'APPROVAL_RESULT',
476
- approvalRequestId: 'req-1',
477
- approved: true,
478
- displayName: 'Michael',
479
- }),
480
- ).toEqual({
481
- type: 'approval-resolved',
482
- requestId: 'req-1',
483
- status: 'approved',
484
- approvalType: 'CLIENT',
485
- resolvedByName: 'Michael',
486
- })
487
-
488
- expect(
489
- decodeNatsChunk({ type: 'TOKEN_USAGE', inputTokensSize: 1, outputTokensSize: 2 }),
490
- ).toEqual({
491
- type: 'token-usage',
492
- inputTokensSize: 1,
493
- outputTokensSize: 2,
494
- totalTokensSize: 0,
495
- contextSize: 0,
496
- })
497
-
498
- expect(decodeNatsChunk({ type: 'CONTEXT_COMPACTION_START' })).toEqual({
499
- type: 'compaction',
500
- phase: 'start',
501
- })
502
- expect(decodeNatsChunk({ type: 'CONTEXT_COMPACTION_END', text: 'sum' })).toEqual({
503
- type: 'compaction',
504
- phase: 'end',
505
- summary: 'sum',
506
- })
507
- })
508
-
509
- it('returns null for unknown or malformed chunks', () => {
510
- expect(decodeNatsChunk(null)).toBeNull()
511
- expect(decodeNatsChunk('nope')).toBeNull()
512
- expect(decodeNatsChunk({ type: 'SOMETHING_ELSE' })).toBeNull()
513
- expect(decodeNatsChunk({ type: 'TEXT' })).toBeNull()
514
- expect(decodeNatsChunk({ type: 'AI_METADATA', modelName: 'x' })).toBeNull()
515
- })
516
- })
@@ -1,52 +0,0 @@
1
- import { describe, it, expect, afterEach } from 'vitest'
2
- import { envFlagEnabled } from '../env-flag'
3
-
4
- const NAME = 'ENV_FLAG_TEST_TRUSTED_INGRESS'
5
-
6
- afterEach(() => {
7
- delete process.env[NAME]
8
- })
9
-
10
- describe('envFlagEnabled', () => {
11
- it('is false when unset', () => {
12
- expect(envFlagEnabled(NAME)).toBe(false)
13
- })
14
-
15
- it('is false for empty / whitespace-only values', () => {
16
- for (const raw of ['', ' ', '\t\n']) {
17
- process.env[NAME] = raw
18
- expect(envFlagEnabled(NAME)).toBe(false)
19
- }
20
- })
21
-
22
- // The whole reason this predicate is shared: a producer using bare
23
- // `Boolean(process.env.X)` read every one of these as ENABLED, so an
24
- // operator typing `=0` to mean "off" got attacker-writable IP headers
25
- // trusted anyway.
26
- it('is false for explicit off values, case- and whitespace-insensitively', () => {
27
- for (const raw of ['0', 'false', 'off', 'FALSE', 'Off', ' 0 ', ' OFF ']) {
28
- process.env[NAME] = raw
29
- expect(envFlagEnabled(NAME)).toBe(false)
30
- }
31
- })
32
-
33
- it('is true for any other non-empty value', () => {
34
- for (const raw of ['1', 'true', 'TRUE', 'on', 'yes', ' 1 ']) {
35
- process.env[NAME] = raw
36
- expect(envFlagEnabled(NAME)).toBe(true)
37
- }
38
- })
39
-
40
- it('reads process.env per call, never module-hoisted', () => {
41
- expect(envFlagEnabled(NAME)).toBe(false)
42
- process.env[NAME] = '1'
43
- expect(envFlagEnabled(NAME)).toBe(true)
44
- process.env[NAME] = '0'
45
- expect(envFlagEnabled(NAME)).toBe(false)
46
- })
47
-
48
- it('is reachable from the ./chat-protocol barrel', async () => {
49
- const barrel = await import('../index')
50
- expect(barrel.envFlagEnabled).toBe(envFlagEnabled)
51
- })
52
- })