@flamingo-stack/openframe-frontend-core 0.0.508 → 0.0.509
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.
- package/dist/chat-protocol/decode.d.ts +58 -0
- package/dist/chat-protocol/decode.d.ts.map +1 -0
- package/dist/chat-protocol/encode.d.ts +44 -0
- package/dist/chat-protocol/encode.d.ts.map +1 -0
- package/dist/chat-protocol/env-flag.d.ts +35 -0
- package/dist/chat-protocol/env-flag.d.ts.map +1 -0
- package/dist/chat-protocol/events.d.ts +216 -0
- package/dist/chat-protocol/events.d.ts.map +1 -0
- package/dist/chat-protocol/frames.d.ts +213 -0
- package/dist/chat-protocol/frames.d.ts.map +1 -0
- package/dist/chat-protocol/index.cjs +506 -0
- package/dist/chat-protocol/index.cjs.map +1 -0
- package/dist/chat-protocol/index.d.ts +18 -0
- package/dist/chat-protocol/index.d.ts.map +1 -0
- package/dist/chat-protocol/index.js +490 -0
- package/dist/chat-protocol/index.js.map +1 -0
- package/dist/chat-protocol/ip-normalize.d.ts +44 -0
- package/dist/chat-protocol/ip-normalize.d.ts.map +1 -0
- package/dist/chat-protocol/nats-decoder.d.ts +26 -0
- package/dist/chat-protocol/nats-decoder.d.ts.map +1 -0
- package/dist/{chunk-3SQ5KXHQ.cjs → chunk-2T4GTV25.cjs} +9 -9
- package/dist/{chunk-3SQ5KXHQ.cjs.map → chunk-2T4GTV25.cjs.map} +1 -1
- package/dist/{chunk-CWAOV2FK.cjs → chunk-3CGZPAGR.cjs} +3 -3
- package/dist/{chunk-CWAOV2FK.cjs.map → chunk-3CGZPAGR.cjs.map} +1 -1
- package/dist/{chunk-TLMJHMXJ.js → chunk-3QSR22IC.js} +12 -12
- package/dist/chunk-3QSR22IC.js.map +1 -0
- package/dist/{chunk-TWYNR4TZ.cjs → chunk-4G5TLHJD.cjs} +7141 -4799
- package/dist/chunk-4G5TLHJD.cjs.map +1 -0
- package/dist/{chunk-ADPMHWOE.js → chunk-72XAID7Y.js} +4 -4
- package/dist/{chunk-PAGKRNWK.js → chunk-7WZHBQ4J.js} +2 -2
- package/dist/{chunk-OCEKO5CW.js → chunk-AEELJC4N.js} +12106 -9764
- package/dist/chunk-AEELJC4N.js.map +1 -0
- package/dist/{chunk-Z42BGM6Q.cjs → chunk-BT2WWDFP.cjs} +5 -5
- package/dist/{chunk-Z42BGM6Q.cjs.map → chunk-BT2WWDFP.cjs.map} +1 -1
- package/dist/{chunk-7LEL3RIX.cjs → chunk-C7OY3PQP.cjs} +11 -11
- package/dist/{chunk-7LEL3RIX.cjs.map → chunk-C7OY3PQP.cjs.map} +1 -1
- package/dist/{chunk-RHGGEKPQ.cjs → chunk-CIOLOQKQ.cjs} +4 -4
- package/dist/{chunk-RHGGEKPQ.cjs.map → chunk-CIOLOQKQ.cjs.map} +1 -1
- package/dist/{chunk-TSZHM74B.js → chunk-DC2TKS7C.js} +2 -2
- package/dist/{chunk-M2GOR3XQ.cjs → chunk-DYPYR7FR.cjs} +87 -87
- package/dist/{chunk-M2GOR3XQ.cjs.map → chunk-DYPYR7FR.cjs.map} +1 -1
- package/dist/{chunk-I64ABCDX.js → chunk-I2B6X77L.js} +2 -2
- package/dist/{chunk-NIZDKTGL.cjs → chunk-K5MSCMDK.cjs} +37 -37
- package/dist/{chunk-NIZDKTGL.cjs.map → chunk-K5MSCMDK.cjs.map} +1 -1
- package/dist/{chunk-DTYRYB2N.js → chunk-K6QTZ7CZ.js} +2 -2
- package/dist/{chunk-4LTMXDUS.js → chunk-KBN7PMFT.js} +2 -2
- package/dist/{chunk-TBMV7I5N.js → chunk-KXZO2WZX.js} +2 -2
- package/dist/{chunk-QTYZMP6D.cjs → chunk-LBAKLT6T.cjs} +62 -62
- package/dist/chunk-LBAKLT6T.cjs.map +1 -0
- package/dist/{chunk-KEBYLU3U.js → chunk-LFUPI7VX.js} +2 -2
- package/dist/{chunk-LDTR4IVZ.cjs → chunk-M37PBG4N.cjs} +31 -31
- package/dist/{chunk-LDTR4IVZ.cjs.map → chunk-M37PBG4N.cjs.map} +1 -1
- package/dist/{chunk-WA7RR64F.js → chunk-PO5O654H.js} +6 -5
- package/dist/chunk-PO5O654H.js.map +1 -0
- package/dist/{chunk-WK4N5VBX.cjs → chunk-QJJISLPA.cjs} +28 -27
- package/dist/chunk-QJJISLPA.cjs.map +1 -0
- package/dist/{chunk-2F44PLTV.cjs → chunk-T7MCSA55.cjs} +26 -26
- package/dist/{chunk-2F44PLTV.cjs.map → chunk-T7MCSA55.cjs.map} +1 -1
- package/dist/{chunk-WJQHJD7J.js → chunk-TWSKTJHW.js} +2 -2
- package/dist/{chunk-ZSQHZYCO.cjs → chunk-UQDMJ6DH.cjs} +13 -13
- package/dist/{chunk-ZSQHZYCO.cjs.map → chunk-UQDMJ6DH.cjs.map} +1 -1
- package/dist/{chunk-TZRUCD56.js → chunk-V3ZAAFE3.js} +5 -5
- package/dist/{chunk-LRGHJPET.js → chunk-XGIFOHKE.js} +2 -2
- package/dist/{chunk-POOMO3PA.cjs → chunk-YQEPRYT5.cjs} +7 -7
- package/dist/{chunk-POOMO3PA.cjs.map → chunk-YQEPRYT5.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/chat-message-list.d.ts.map +1 -1
- package/dist/components/chat/hooks/index.d.ts +0 -1
- package/dist/components/chat/hooks/index.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-chat.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-chunk-catchup.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +15 -48
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +10 -57
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +10 -2
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.d.ts +1 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +21 -13
- package/dist/components/chat/stream/chat-dialog-store.d.ts +184 -0
- package/dist/components/chat/stream/chat-dialog-store.d.ts.map +1 -0
- package/dist/components/chat/stream/chat-stream-reducer.d.ts +380 -0
- package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -0
- package/dist/components/chat/stream/delta-batcher.d.ts +51 -0
- package/dist/components/chat/stream/delta-batcher.d.ts.map +1 -0
- package/dist/components/chat/stream/index.d.ts +17 -0
- package/dist/components/chat/stream/index.d.ts.map +1 -0
- package/dist/components/chat/stream/message-mutations.d.ts +77 -0
- package/dist/components/chat/stream/message-mutations.d.ts.map +1 -0
- package/dist/components/chat/stream/use-chat-stream-reducer.d.ts +22 -0
- package/dist/components/chat/stream/use-chat-stream-reducer.d.ts.map +1 -0
- package/dist/components/chat/types/api.types.d.ts +1 -81
- package/dist/components/chat/types/api.types.d.ts.map +1 -1
- package/dist/components/chat/types/processing.types.d.ts +2 -93
- package/dist/components/chat/types/processing.types.d.ts.map +1 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts +15 -0
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
- package/dist/components/chat/utils/extract-incomplete-message-state.d.ts +34 -6
- package/dist/components/chat/utils/extract-incomplete-message-state.d.ts.map +1 -1
- package/dist/components/chat/utils/history-merge.d.ts.map +1 -1
- package/dist/components/chat/utils/index.d.ts +2 -3
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts +59 -11
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/help-center-pages/index.cjs +22 -22
- package/dist/components/help-center-pages/index.js +13 -13
- package/dist/components/index.cjs +174 -132
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +65 -23
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +34 -34
- package/dist/components/meeting-scheduler/index.js +3 -3
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +6 -6
- package/dist/components/tickets/index.js +5 -5
- package/dist/components/ui/index.cjs +44 -2
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +1 -2
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +55 -13
- package/dist/components/ui/markdown/base-components.d.ts +51 -0
- package/dist/components/ui/markdown/base-components.d.ts.map +1 -0
- package/dist/components/ui/markdown/engine.d.ts +99 -0
- package/dist/components/ui/markdown/engine.d.ts.map +1 -0
- package/dist/components/ui/markdown/heading-ids.d.ts +65 -0
- package/dist/components/ui/markdown/heading-ids.d.ts.map +1 -0
- package/dist/components/ui/markdown/index.d.ts +18 -0
- package/dist/components/ui/markdown/index.d.ts.map +1 -0
- package/dist/components/ui/markdown/mermaid-diagram.d.ts +79 -0
- package/dist/components/ui/markdown/mermaid-diagram.d.ts.map +1 -0
- package/dist/components/ui/markdown/rich/embed-overrides.d.ts +14 -0
- package/dist/components/ui/markdown/rich/embed-overrides.d.ts.map +1 -0
- package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts +40 -0
- package/dist/components/ui/markdown/rich/rich-markdown-renderer.d.ts.map +1 -0
- package/dist/components/ui/markdown/rich/shortcodes.d.ts +8 -0
- package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +1 -0
- package/dist/components/ui/markdown/sanitize.d.ts +153 -0
- package/dist/components/ui/markdown/sanitize.d.ts.map +1 -0
- package/dist/components/ui/markdown/simple-markdown-renderer.d.ts +23 -0
- package/dist/components/ui/markdown/simple-markdown-renderer.d.ts.map +1 -0
- package/dist/components/ui/markdown/streaming.d.ts +78 -0
- package/dist/components/ui/markdown/streaming.d.ts.map +1 -0
- package/dist/components/ui/markdown/text-size.d.ts +34 -0
- package/dist/components/ui/markdown/text-size.d.ts.map +1 -0
- package/dist/index.cjs +60 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +71 -13
- package/dist/utils/index.cjs +169 -40
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +162 -41
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/markdown-fences.d.ts +42 -0
- package/dist/utils/markdown-fences.d.ts.map +1 -0
- package/dist/utils/markdown-heading-id.d.ts +85 -0
- package/dist/utils/markdown-heading-id.d.ts.map +1 -0
- package/dist/utils/markdown-section-extractor.d.ts.map +1 -1
- package/package.json +7 -1
- package/src/chat-protocol/__tests__/__snapshots__/nats-decoder-golden.test.ts.snap +319 -0
- package/src/chat-protocol/__tests__/chat-protocol.test.ts +516 -0
- package/src/chat-protocol/__tests__/env-flag.test.ts +52 -0
- package/src/chat-protocol/__tests__/ip-normalize.test.ts +136 -0
- package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +237 -0
- package/src/chat-protocol/decode.ts +278 -0
- package/src/chat-protocol/encode.ts +71 -0
- package/src/chat-protocol/env-flag.ts +39 -0
- package/src/chat-protocol/events.ts +254 -0
- package/src/chat-protocol/frames.ts +245 -0
- package/src/chat-protocol/index.ts +21 -0
- package/src/chat-protocol/ip-normalize.ts +146 -0
- package/src/chat-protocol/nats-decoder.ts +264 -0
- package/src/components/chat/.chat-message-list.md +10 -6
- package/src/components/chat/__tests__/chat-message-list.test.tsx +450 -9
- package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +207 -0
- package/src/components/chat/__tests__/chat-pending-turn.test.tsx +250 -0
- package/src/components/chat/chat-message-enhanced.tsx +122 -20
- package/src/components/chat/chat-message-list.tsx +515 -133
- package/src/components/chat/embeddable-chat.tsx +6 -0
- package/src/components/chat/hooks/.index.md +30 -33
- package/src/components/chat/hooks/.use-nats-chat-adapter.md +36 -55
- package/src/components/chat/hooks/__tests__/__snapshots__/conversation-id-persistence-golden.test.ts.snap +37 -0
- package/src/components/chat/hooks/__tests__/__snapshots__/sse-stream-golden.test.ts.snap +0 -0
- package/src/components/chat/hooks/__tests__/chunk-catchup-dialog-staleness.test.ts +92 -0
- package/src/components/chat/hooks/__tests__/conversation-id-persistence-golden.test.ts +438 -0
- package/src/components/chat/hooks/__tests__/sse-stream-golden.test.ts +318 -0
- package/src/components/chat/hooks/index.ts +0 -1
- package/src/components/chat/hooks/use-chat.ts +8 -0
- package/src/components/chat/hooks/use-chunk-catchup.ts +26 -1
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +254 -825
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +380 -684
- package/src/components/chat/index.ts +5 -0
- package/src/components/chat/stream/__tests__/__snapshots__/chat-stream-reducer-golden.test.ts.snap +944 -0
- package/src/components/chat/stream/__tests__/chat-dialog-store.test.ts +806 -0
- package/src/components/chat/stream/__tests__/chat-stream-reducer-golden.test.ts +467 -0
- package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +1330 -0
- package/src/components/chat/stream/__tests__/delta-batcher.test.ts +255 -0
- package/src/components/chat/stream/__tests__/use-chat-stream-reducer.test.ts +105 -0
- package/src/components/chat/stream/chat-dialog-store.ts +536 -0
- package/src/components/chat/stream/chat-stream-reducer.ts +1928 -0
- package/src/components/chat/stream/delta-batcher.ts +168 -0
- package/src/components/chat/stream/index.ts +55 -0
- package/src/components/chat/stream/message-mutations.ts +340 -0
- package/src/components/chat/stream/use-chat-stream-reducer.ts +126 -0
- package/src/components/chat/thinking-display.tsx +1 -1
- package/src/components/chat/types/.api.types.md +39 -49
- package/src/components/chat/types/.processing.types.md +29 -58
- package/src/components/chat/types/.unified-chat-state.types.md +2 -2
- package/src/components/chat/types/api.types.ts +6 -73
- package/src/components/chat/types/processing.types.ts +11 -53
- package/src/components/chat/types/unified-chat-state.types.ts +16 -0
- package/src/components/chat/utils/.extract-incomplete-message-state.md +21 -3
- package/src/components/chat/utils/.history-merge.md +44 -3
- package/src/components/chat/utils/.index.md +31 -40
- package/src/components/chat/utils/__tests__/__snapshots__/process-historical-messages-golden.test.ts.snap +420 -0
- package/src/components/chat/utils/__tests__/__snapshots__/segment-accumulator-golden.test.ts.snap +605 -0
- package/src/components/chat/utils/__tests__/extract-incomplete-message-state.test.ts +106 -0
- package/src/components/chat/utils/__tests__/history-merge.test.ts +160 -0
- package/src/components/chat/utils/__tests__/process-historical-messages-approvals.test.ts +61 -0
- package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +394 -0
- package/src/components/chat/utils/__tests__/segment-accumulator-golden.test.ts +270 -0
- package/src/components/chat/utils/extract-incomplete-message-state.ts +93 -6
- package/src/components/chat/utils/history-merge.ts +85 -5
- package/src/components/chat/utils/index.ts +5 -8
- package/src/components/chat/utils/process-historical-messages.ts +361 -385
- package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +22 -4
- package/src/components/shared/legal-document/legal-document-page.tsx +1 -1
- package/src/components/shared/product-release/release-detail-page.tsx +19 -4
- package/src/components/ui/__tests__/__snapshots__/markdown-parity.test.tsx.snap +2420 -0
- package/src/components/ui/__tests__/markdown-parity.test.tsx +2540 -0
- package/src/components/ui/index.ts +1 -2
- package/src/components/ui/markdown/__tests__/mermaid-security.test.ts +176 -0
- package/src/components/ui/markdown/__tests__/mermaid-stale-render.test.tsx +151 -0
- package/src/components/ui/markdown/__tests__/sanitize-invariant.test.ts +354 -0
- package/src/components/ui/markdown/__tests__/sanitize-render.test.tsx +118 -0
- package/src/components/ui/markdown/__tests__/streaming.test.tsx +436 -0
- package/src/components/ui/markdown/base-components.tsx +360 -0
- package/src/components/ui/markdown/engine.tsx +315 -0
- package/src/components/ui/markdown/heading-ids.ts +239 -0
- package/src/components/ui/markdown/index.ts +49 -0
- package/src/components/ui/markdown/mermaid-diagram.tsx +323 -0
- package/src/components/ui/markdown/rich/embed-overrides.tsx +199 -0
- package/src/components/ui/markdown/rich/rich-markdown-renderer.tsx +184 -0
- package/src/components/ui/markdown/rich/shortcodes.ts +170 -0
- package/src/components/ui/markdown/sanitize.ts +3584 -0
- package/src/components/ui/markdown/simple-markdown-renderer.tsx +28 -0
- package/src/components/ui/markdown/streaming.ts +390 -0
- package/src/components/ui/markdown/text-size.ts +106 -0
- package/src/components/ui/release-changelog-section.tsx +1 -1
- package/src/components/ui/ticket-info-section.tsx +1 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/markdown-fences.ts +127 -0
- package/src/utils/markdown-heading-id.ts +348 -0
- package/src/utils/markdown-section-extractor.ts +41 -56
- package/dist/chunk-OCEKO5CW.js.map +0 -1
- package/dist/chunk-QTYZMP6D.cjs.map +0 -1
- package/dist/chunk-TLMJHMXJ.js.map +0 -1
- package/dist/chunk-TWYNR4TZ.cjs.map +0 -1
- package/dist/chunk-WA7RR64F.js.map +0 -1
- package/dist/chunk-WK4N5VBX.cjs.map +0 -1
- package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts +0 -6
- package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +0 -1
- package/dist/components/chat/utils/chunk-parser.d.ts +0 -25
- package/dist/components/chat/utils/chunk-parser.d.ts.map +0 -1
- package/dist/components/ui/rich-markdown-renderer.d.ts +0 -34
- package/dist/components/ui/rich-markdown-renderer.d.ts.map +0 -1
- package/dist/components/ui/simple-markdown-renderer.d.ts +0 -73
- package/dist/components/ui/simple-markdown-renderer.d.ts.map +0 -1
- package/src/components/chat/hooks/.use-realtime-chunk-processor.md +0 -77
- package/src/components/chat/hooks/use-realtime-chunk-processor.ts +0 -489
- package/src/components/chat/utils/.chunk-parser.md +0 -62
- package/src/components/chat/utils/chunk-parser.ts +0 -262
- package/src/components/ui/.simple-markdown-renderer.md +0 -52
- package/src/components/ui/rich-markdown-renderer.tsx +0 -1223
- package/src/components/ui/simple-markdown-renderer.tsx +0 -964
- /package/dist/{chunk-ADPMHWOE.js.map → chunk-72XAID7Y.js.map} +0 -0
- /package/dist/{chunk-PAGKRNWK.js.map → chunk-7WZHBQ4J.js.map} +0 -0
- /package/dist/{chunk-TSZHM74B.js.map → chunk-DC2TKS7C.js.map} +0 -0
- /package/dist/{chunk-I64ABCDX.js.map → chunk-I2B6X77L.js.map} +0 -0
- /package/dist/{chunk-DTYRYB2N.js.map → chunk-K6QTZ7CZ.js.map} +0 -0
- /package/dist/{chunk-4LTMXDUS.js.map → chunk-KBN7PMFT.js.map} +0 -0
- /package/dist/{chunk-TBMV7I5N.js.map → chunk-KXZO2WZX.js.map} +0 -0
- /package/dist/{chunk-KEBYLU3U.js.map → chunk-LFUPI7VX.js.map} +0 -0
- /package/dist/{chunk-WJQHJD7J.js.map → chunk-TWSKTJHW.js.map} +0 -0
- /package/dist/{chunk-TZRUCD56.js.map → chunk-V3ZAAFE3.js.map} +0 -0
- /package/dist/{chunk-LRGHJPET.js.map → chunk-XGIFOHKE.js.map} +0 -0
|
@@ -1,489 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Hook for processing real-time NATS chunks
|
|
5
|
-
* Provides a consistent interface for handling streaming messages
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useCallback, useRef, useEffect } from 'react'
|
|
9
|
-
import { parseChunkToAction } from '../utils/chunk-parser'
|
|
10
|
-
import {
|
|
11
|
-
MessageSegmentAccumulator,
|
|
12
|
-
createMessageSegmentAccumulator,
|
|
13
|
-
} from '../utils/message-segment-accumulator'
|
|
14
|
-
import type { UseRealtimeChunkProcessorReturn, UseRealtimeChunkProcessorOptions, ChatApprovalStatus, PendingToolCallData, MessageSegment, SegmentsUpdateMetadata } from '../types'
|
|
15
|
-
import { getCommandText } from '../utils/tool-call-helpers'
|
|
16
|
-
|
|
17
|
-
// Actions allowed through once the dialog is in direct mode. Everything else is
|
|
18
|
-
// an AI-assistant chunk and gets dropped — an allowlist so any future assistant
|
|
19
|
-
// action is blocked by default.
|
|
20
|
-
const DIRECT_MODE_ALLOWED = new Set(['direct_message', 'message_request', 'system', 'dialog_closed'])
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Hook for processing real-time NATS chunks into message segments
|
|
24
|
-
*/
|
|
25
|
-
export function useRealtimeChunkProcessor(
|
|
26
|
-
options: UseRealtimeChunkProcessorOptions
|
|
27
|
-
): UseRealtimeChunkProcessorReturn {
|
|
28
|
-
const {
|
|
29
|
-
callbacks,
|
|
30
|
-
displayApprovalTypes = ['CLIENT'],
|
|
31
|
-
approvalStatuses = {},
|
|
32
|
-
initialState,
|
|
33
|
-
// Owned by the consumer (e.g. oss-tenant chat client / openframe-frontend
|
|
34
|
-
// tickets view). Default ON so consumers that haven't wired the flag yet
|
|
35
|
-
// get the new batch UI; pass `false` explicitly to fall back to legacy.
|
|
36
|
-
batchApprovalsEnabled = true,
|
|
37
|
-
isDirectMode = false,
|
|
38
|
-
} = options
|
|
39
|
-
|
|
40
|
-
const accumulatorRef = useRef<MessageSegmentAccumulator>(
|
|
41
|
-
createMessageSegmentAccumulator({
|
|
42
|
-
onApprove: callbacks.onApprove,
|
|
43
|
-
onReject: callbacks.onReject,
|
|
44
|
-
})
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
const hasInitializedWithData = useRef(false)
|
|
48
|
-
|
|
49
|
-
// Initialize accumulator when we get initial state data
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
if (initialState && !hasInitializedWithData.current) {
|
|
52
|
-
accumulatorRef.current.initializeWithState(initialState)
|
|
53
|
-
|
|
54
|
-
if (initialState.escalatedApprovals) {
|
|
55
|
-
pendingEscalatedRef.current = new Map(initialState.escalatedApprovals)
|
|
56
|
-
|
|
57
|
-
initialState.escalatedApprovals.forEach((data, requestId) => {
|
|
58
|
-
callbacks.onEscalatedApproval?.(requestId, data)
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Resumed dialog: a MESSAGE_START already fired server-side. Treat
|
|
63
|
-
// subsequent continuation chunks (after the next MESSAGE_END) as
|
|
64
|
-
// post-stream so they append into the existing bubble instead of
|
|
65
|
-
// replacing its content via the cold-start cumulative path.
|
|
66
|
-
hasEverStreamedRef.current = true
|
|
67
|
-
hasInitializedWithData.current = true
|
|
68
|
-
}
|
|
69
|
-
}, [initialState, callbacks])
|
|
70
|
-
|
|
71
|
-
const isInStreamRef = useRef(false)
|
|
72
|
-
// Distinguishes post-MESSAGE_END continuation (append into prior bubble)
|
|
73
|
-
// from cold-start before any MESSAGE_START (cumulative; otherwise
|
|
74
|
-
// appendSegmentsToLastAssistant silently drops the chunk when no
|
|
75
|
-
// assistant bubble exists yet). Flipped true on MESSAGE_START and on
|
|
76
|
-
// resumed-dialog initializeWithState.
|
|
77
|
-
const hasEverStreamedRef = useRef(false)
|
|
78
|
-
|
|
79
|
-
// Direct-mode barrier: once engaged, AI-assistant chunks are dropped. Engaged
|
|
80
|
-
// by the host flag (optimistic) or by an in-order DIRECT_MESSAGE chunk.
|
|
81
|
-
const directModeFlagRef = useRef(isDirectMode)
|
|
82
|
-
const sawDirectMessageRef = useRef(false)
|
|
83
|
-
// One-shot teardown guard for the barrier. Busy state can be asserted
|
|
84
|
-
// OUTSIDE an open stream (onAgentBusy on tool/approval chunks), so the
|
|
85
|
-
// teardown must fire once even when isInStreamRef is false — otherwise a
|
|
86
|
-
// human takeover right after an approval leaves the consumer's composer
|
|
87
|
-
// locked forever (all releasing AI chunks get dropped by the barrier).
|
|
88
|
-
const directTeardownFiredRef = useRef(false)
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
directModeFlagRef.current = isDirectMode
|
|
91
|
-
}, [isDirectMode])
|
|
92
|
-
|
|
93
|
-
// Track pending escalated approvals (single or batch)
|
|
94
|
-
const pendingEscalatedRef = useRef<
|
|
95
|
-
Map<string, { command: string; explanation?: string; approvalType: string; toolCalls?: PendingToolCallData[] }>
|
|
96
|
-
>(new Map())
|
|
97
|
-
|
|
98
|
-
const processChunk = useCallback(
|
|
99
|
-
(chunk: unknown) => {
|
|
100
|
-
const action = parseChunkToAction(chunk)
|
|
101
|
-
if (!action) return
|
|
102
|
-
|
|
103
|
-
const accumulator = accumulatorRef.current
|
|
104
|
-
|
|
105
|
-
// streamSeq of the chunk currently being processed (JetStream only).
|
|
106
|
-
// Only content emissions below carry it through to the host, so the
|
|
107
|
-
// streaming bubble is stamped with the highest CONTENT seq it saw — the
|
|
108
|
-
// non-persisted MESSAGE_END / TOKEN_USAGE chunks never reach emitSegments.
|
|
109
|
-
const streamSeq =
|
|
110
|
-
chunk && typeof chunk === 'object' && typeof (chunk as { streamSeq?: unknown }).streamSeq === 'number'
|
|
111
|
-
? (chunk as { streamSeq: number }).streamSeq
|
|
112
|
-
: undefined
|
|
113
|
-
const emitSegments = (s: MessageSegment[], meta?: SegmentsUpdateMetadata) =>
|
|
114
|
-
callbacks.onSegmentsUpdate?.(s, streamSeq != null ? { ...meta, streamSeq } : meta)
|
|
115
|
-
|
|
116
|
-
if (action.action === 'direct_message') sawDirectMessageRef.current = true
|
|
117
|
-
|
|
118
|
-
if ((directModeFlagRef.current || sawDirectMessageRef.current) && !DIRECT_MODE_ALLOWED.has(action.action)) {
|
|
119
|
-
// Hard stop: tear down AI activity exactly once so the typing
|
|
120
|
-
// indicator, composer lock, and half-rendered bubble clear, then drop
|
|
121
|
-
// the chunk. Fires even when no stream is open — an onAgentBusy lock
|
|
122
|
-
// (approved commands executing post-MESSAGE_END) has no other release
|
|
123
|
-
// once the barrier starts dropping the continuation chunks.
|
|
124
|
-
if (isInStreamRef.current || !directTeardownFiredRef.current) {
|
|
125
|
-
isInStreamRef.current = false
|
|
126
|
-
directTeardownFiredRef.current = true
|
|
127
|
-
callbacks.onStreamEnd?.()
|
|
128
|
-
accumulator.resetSegments()
|
|
129
|
-
}
|
|
130
|
-
return
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
switch (action.action) {
|
|
134
|
-
case 'message_start':
|
|
135
|
-
isInStreamRef.current = true
|
|
136
|
-
hasEverStreamedRef.current = true
|
|
137
|
-
callbacks.onStreamStart?.()
|
|
138
|
-
accumulator.resetSegments()
|
|
139
|
-
break
|
|
140
|
-
|
|
141
|
-
case 'message_end':
|
|
142
|
-
isInStreamRef.current = false
|
|
143
|
-
callbacks.onStreamEnd?.()
|
|
144
|
-
accumulator.resetSegments()
|
|
145
|
-
break
|
|
146
|
-
|
|
147
|
-
case 'metadata':
|
|
148
|
-
callbacks.onMetadata?.(action)
|
|
149
|
-
break
|
|
150
|
-
|
|
151
|
-
case 'text': {
|
|
152
|
-
const segments = accumulator.appendText(action.text)
|
|
153
|
-
// Append-mode only for *true* post-stream continuation (after a
|
|
154
|
-
// MESSAGE_END we actually saw). Cold-start chunks (no prior
|
|
155
|
-
// MESSAGE_START) emit cumulative segments so the consumer can
|
|
156
|
-
// spawn the first assistant bubble — otherwise appendSegmentsToLastAssistant
|
|
157
|
-
// silently drops the chunk when no last assistant exists.
|
|
158
|
-
if (isInStreamRef.current || !hasEverStreamedRef.current) {
|
|
159
|
-
emitSegments(segments)
|
|
160
|
-
} else {
|
|
161
|
-
emitSegments([{ type: 'text', text: action.text }], { append: true })
|
|
162
|
-
}
|
|
163
|
-
break
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
case 'thinking': {
|
|
167
|
-
const segments = accumulator.appendThinking(action.text)
|
|
168
|
-
if (isInStreamRef.current || !hasEverStreamedRef.current) {
|
|
169
|
-
emitSegments(segments)
|
|
170
|
-
} else {
|
|
171
|
-
emitSegments([{ type: 'thinking', text: action.text }], { append: true })
|
|
172
|
-
}
|
|
173
|
-
break
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
case 'guide': {
|
|
177
|
-
const segments = accumulator.appendGuide(action.text)
|
|
178
|
-
if (isInStreamRef.current || !hasEverStreamedRef.current) {
|
|
179
|
-
emitSegments(segments)
|
|
180
|
-
} else {
|
|
181
|
-
emitSegments([{ type: 'guide', text: action.text }], { append: true })
|
|
182
|
-
}
|
|
183
|
-
break
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
case 'tool_execution': {
|
|
187
|
-
// A starting tool run means the agent's turn is in progress even
|
|
188
|
-
// when this lands after MESSAGE_END (approved commands execute
|
|
189
|
-
// between the approval bubble and the continuation stream).
|
|
190
|
-
if (action.segment.data.type === 'EXECUTING_TOOL') {
|
|
191
|
-
callbacks.onAgentBusy?.()
|
|
192
|
-
}
|
|
193
|
-
// Post-MESSAGE_END tool chunks (cancellations / async batch
|
|
194
|
-
// results for a batch in a prior bubble) flow only through the
|
|
195
|
-
// cross-message updater. Skipping the accumulator avoids
|
|
196
|
-
// pushing a standalone segment that the next text chunk would
|
|
197
|
-
// replay into a new bubble.
|
|
198
|
-
if (!isInStreamRef.current && callbacks.onToolExecuted) {
|
|
199
|
-
callbacks.onToolExecuted(action.segment)
|
|
200
|
-
break
|
|
201
|
-
}
|
|
202
|
-
// In-stream: accumulator-driven update of the streaming bubble
|
|
203
|
-
// is the source of truth. Don't fire onToolExecuted here — its
|
|
204
|
-
// cross-message scan is first-match-wins and could touch a
|
|
205
|
-
// same-execId segment in a prior bubble (agent retry case).
|
|
206
|
-
//
|
|
207
|
-
// KNOWN EDGE (accepted): a slow async batch tool whose EXECUTED
|
|
208
|
-
// lands only AFTER the continuation stream's MESSAGE_START takes
|
|
209
|
-
// this path too — the freshly reset accumulator has no batch
|
|
210
|
-
// segment to merge into, so the chunk renders as a standalone card
|
|
211
|
-
// in the new bubble while the prior batch card's executions slot
|
|
212
|
-
// stays 'executing' until the next history refetch. Routing such
|
|
213
|
-
// chunks cross-message would reintroduce the retry hazard above;
|
|
214
|
-
// revisit only if the backend confirms batch executions can
|
|
215
|
-
// overlap the continuation stream.
|
|
216
|
-
const segments = accumulator.addToolExecution(action.segment)
|
|
217
|
-
emitSegments(segments)
|
|
218
|
-
break
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
case 'approval_request': {
|
|
222
|
-
const { requestId, command, explanation, approvalType } = action
|
|
223
|
-
|
|
224
|
-
if (displayApprovalTypes.includes(approvalType)) {
|
|
225
|
-
// Display directly
|
|
226
|
-
const status = approvalStatuses[requestId] || 'pending'
|
|
227
|
-
const segments = accumulator.addApprovalRequest(
|
|
228
|
-
requestId,
|
|
229
|
-
command,
|
|
230
|
-
explanation,
|
|
231
|
-
approvalType,
|
|
232
|
-
status as ChatApprovalStatus
|
|
233
|
-
)
|
|
234
|
-
emitSegments(segments)
|
|
235
|
-
} else {
|
|
236
|
-
// Track as escalated
|
|
237
|
-
pendingEscalatedRef.current.set(requestId, { command, explanation, approvalType })
|
|
238
|
-
callbacks.onEscalatedApproval?.(requestId, { command, explanation, approvalType })
|
|
239
|
-
}
|
|
240
|
-
break
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
case 'approval_batch': {
|
|
244
|
-
const { requestId, approvalType, toolCalls } = action
|
|
245
|
-
const status = (approvalStatuses[requestId] || 'pending') as ChatApprovalStatus
|
|
246
|
-
|
|
247
|
-
if (!displayApprovalTypes.includes(approvalType)) {
|
|
248
|
-
// Escalated: keep batch context locally for replay on result; surface a
|
|
249
|
-
// summary command via the legacy escalation callback.
|
|
250
|
-
const required = toolCalls.find((c) => c.requiresApproval) ?? toolCalls[0]
|
|
251
|
-
const summary = required ? getCommandText(required) : `Batch of ${toolCalls.length} tool calls`
|
|
252
|
-
pendingEscalatedRef.current.set(requestId, {
|
|
253
|
-
command: summary,
|
|
254
|
-
explanation: required?.toolExplanation,
|
|
255
|
-
approvalType,
|
|
256
|
-
toolCalls,
|
|
257
|
-
})
|
|
258
|
-
callbacks.onEscalatedApproval?.(requestId, {
|
|
259
|
-
command: summary,
|
|
260
|
-
explanation: required?.toolExplanation,
|
|
261
|
-
approvalType,
|
|
262
|
-
})
|
|
263
|
-
break
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (batchApprovalsEnabled) {
|
|
267
|
-
const segments = accumulator.addApprovalBatch(requestId, approvalType, toolCalls, status)
|
|
268
|
-
emitSegments(segments)
|
|
269
|
-
break
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Flag OFF — unfold batch into N legacy approval cards. They share
|
|
273
|
-
// `requestId`, so a click on any will approve the whole batch via a
|
|
274
|
-
// single backend call, and the resulting APPROVAL_RESULT chunk will
|
|
275
|
-
// flip status on every matching segment (see updateApprovalStatus).
|
|
276
|
-
let segments = accumulator.getSegments()
|
|
277
|
-
for (const call of toolCalls) {
|
|
278
|
-
if (!call.requiresApproval) continue
|
|
279
|
-
segments = accumulator.addApprovalRequest(
|
|
280
|
-
requestId,
|
|
281
|
-
getCommandText(call),
|
|
282
|
-
call.toolExplanation,
|
|
283
|
-
approvalType,
|
|
284
|
-
status,
|
|
285
|
-
)
|
|
286
|
-
}
|
|
287
|
-
emitSegments(segments)
|
|
288
|
-
break
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
case 'approval_result': {
|
|
292
|
-
const { requestId, approved, approvalType, resolvedByName } = action
|
|
293
|
-
// Approved → the agent resumes to execute the command(s); surface
|
|
294
|
-
// busy immediately so the composer locks before EXECUTING_TOOL
|
|
295
|
-
// lands. Rejection keeps the input free — the user may want to
|
|
296
|
-
// type a correction right away.
|
|
297
|
-
if (approved) callbacks.onAgentBusy?.()
|
|
298
|
-
const escalatedData = pendingEscalatedRef.current.get(requestId)
|
|
299
|
-
const status: ChatApprovalStatus = approved ? 'approved' : 'rejected'
|
|
300
|
-
|
|
301
|
-
if (escalatedData) {
|
|
302
|
-
pendingEscalatedRef.current.delete(requestId)
|
|
303
|
-
callbacks.onEscalatedApprovalResult?.(requestId, approved, {
|
|
304
|
-
command: escalatedData.command,
|
|
305
|
-
explanation: escalatedData.explanation,
|
|
306
|
-
approvalType: escalatedData.approvalType,
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
// The escalated card was never displayed inline, so this emit is
|
|
310
|
-
// what surfaces it after resolution. In-stream the cumulative
|
|
311
|
-
// array is correct; post-MESSAGE_END the accumulator was RESET,
|
|
312
|
-
// so a cumulative (replace-mode) emit would wipe the trailing
|
|
313
|
-
// bubble down to the lone card — emit only the resolved card(s)
|
|
314
|
-
// as an append instead. Store hosts upsert batches by request id,
|
|
315
|
-
// so a replayed append stays idempotent.
|
|
316
|
-
const emitResolved = (segments: MessageSegment[]) => {
|
|
317
|
-
if (isInStreamRef.current) {
|
|
318
|
-
emitSegments(segments)
|
|
319
|
-
return
|
|
320
|
-
}
|
|
321
|
-
const delta = segments.filter(
|
|
322
|
-
(s) =>
|
|
323
|
-
(s.type === 'approval_request' && s.data.requestId === requestId) ||
|
|
324
|
-
(s.type === 'approval_batch' && s.data.approvalRequestId === requestId),
|
|
325
|
-
)
|
|
326
|
-
emitSegments(delta, { append: true })
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (escalatedData.toolCalls && escalatedData.toolCalls.length > 0) {
|
|
330
|
-
if (batchApprovalsEnabled) {
|
|
331
|
-
const segments = accumulator.addApprovalBatch(
|
|
332
|
-
requestId,
|
|
333
|
-
escalatedData.approvalType,
|
|
334
|
-
escalatedData.toolCalls,
|
|
335
|
-
status,
|
|
336
|
-
undefined,
|
|
337
|
-
resolvedByName,
|
|
338
|
-
)
|
|
339
|
-
emitResolved(segments)
|
|
340
|
-
} else {
|
|
341
|
-
let segments = accumulator.getSegments()
|
|
342
|
-
for (const call of escalatedData.toolCalls) {
|
|
343
|
-
if (!call.requiresApproval) continue
|
|
344
|
-
segments = accumulator.addApprovalRequest(
|
|
345
|
-
requestId,
|
|
346
|
-
getCommandText(call),
|
|
347
|
-
call.toolExplanation,
|
|
348
|
-
escalatedData.approvalType,
|
|
349
|
-
status,
|
|
350
|
-
)
|
|
351
|
-
}
|
|
352
|
-
emitResolved(segments)
|
|
353
|
-
}
|
|
354
|
-
} else {
|
|
355
|
-
const segments = accumulator.addApprovalRequest(
|
|
356
|
-
requestId,
|
|
357
|
-
escalatedData.command,
|
|
358
|
-
escalatedData.explanation,
|
|
359
|
-
escalatedData.approvalType,
|
|
360
|
-
status,
|
|
361
|
-
)
|
|
362
|
-
emitResolved(segments)
|
|
363
|
-
}
|
|
364
|
-
} else {
|
|
365
|
-
// Always keep the in-memory accumulator in sync so a following
|
|
366
|
-
// text/tool chunk replays the resolved status into the message.
|
|
367
|
-
accumulator.updateApprovalStatus(requestId, status, resolvedByName)
|
|
368
|
-
// When the consumer wires cross-message resolution via
|
|
369
|
-
// `onApprovalResolved`, skip `onSegmentsUpdate` here: this path
|
|
370
|
-
// routes through `ensureAssistantMessage` + `updateStreamingMessageSegments`,
|
|
371
|
-
// which adopts/creates an assistant bubble and replays the
|
|
372
|
-
// accumulator's segments into it — turning a status flip into a
|
|
373
|
-
// bubble overwrite that wipes the original card.
|
|
374
|
-
if (!callbacks.onApprovalResolved) {
|
|
375
|
-
emitSegments(accumulator.getSegments())
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
callbacks.onApprovalResolved?.(requestId, status, approvalType, resolvedByName)
|
|
379
|
-
break
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
case 'error': {
|
|
383
|
-
let message: string | undefined
|
|
384
|
-
if ('details' in action && action?.details) {
|
|
385
|
-
try {
|
|
386
|
-
message = JSON.parse(action.details)?.error?.message
|
|
387
|
-
} catch {
|
|
388
|
-
message = action.details
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
const segments = accumulator.addError(action.error, message)
|
|
392
|
-
emitSegments(segments)
|
|
393
|
-
callbacks.onError?.(action.error, message)
|
|
394
|
-
break
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
case 'system': {
|
|
398
|
-
callbacks.onSystemMessage?.(action.text, { streamSeq })
|
|
399
|
-
break
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
case 'direct_message': {
|
|
403
|
-
callbacks.onDirectMessage?.(action.text, {
|
|
404
|
-
ownerType: action.ownerType,
|
|
405
|
-
displayName: action.displayName,
|
|
406
|
-
userId: action.userId,
|
|
407
|
-
streamSeq,
|
|
408
|
-
})
|
|
409
|
-
break
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
case 'message_request':
|
|
413
|
-
callbacks.onUserMessage?.(action.text, {
|
|
414
|
-
ownerType: action.ownerType,
|
|
415
|
-
displayName: action.displayName,
|
|
416
|
-
userId: action.userId,
|
|
417
|
-
streamSeq,
|
|
418
|
-
contextItems: action.contextItems,
|
|
419
|
-
})
|
|
420
|
-
break
|
|
421
|
-
|
|
422
|
-
case 'token_usage':
|
|
423
|
-
callbacks.onTokenUsage?.(action.data)
|
|
424
|
-
break
|
|
425
|
-
|
|
426
|
-
case 'context_compaction_start': {
|
|
427
|
-
const standalone = !isInStreamRef.current
|
|
428
|
-
const segments = accumulator.addContextCompaction()
|
|
429
|
-
emitSegments(segments, standalone ? { append: true, isCompacting: true } : undefined)
|
|
430
|
-
break
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
case 'context_compaction_end': {
|
|
434
|
-
const standalone = !isInStreamRef.current
|
|
435
|
-
const segments = accumulator.completeContextCompaction(action.summary)
|
|
436
|
-
emitSegments(segments, standalone ? { append: true, isCompacting: true } : undefined)
|
|
437
|
-
break
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
case 'dialog_closed':
|
|
441
|
-
callbacks.onDialogClosed?.()
|
|
442
|
-
break
|
|
443
|
-
|
|
444
|
-
default:
|
|
445
|
-
// Unknown action - ignore
|
|
446
|
-
break
|
|
447
|
-
}
|
|
448
|
-
},
|
|
449
|
-
[callbacks, displayApprovalTypes, approvalStatuses, initialState]
|
|
450
|
-
)
|
|
451
|
-
|
|
452
|
-
const getSegments = useCallback(() => {
|
|
453
|
-
return accumulatorRef.current.getSegments()
|
|
454
|
-
}, [])
|
|
455
|
-
|
|
456
|
-
const reset = useCallback(() => {
|
|
457
|
-
accumulatorRef.current.reset()
|
|
458
|
-
pendingEscalatedRef.current.clear()
|
|
459
|
-
hasInitializedWithData.current = false
|
|
460
|
-
sawDirectMessageRef.current = false
|
|
461
|
-
directTeardownFiredRef.current = false
|
|
462
|
-
// Stream-state flags are per-dialog too. Leaking them across a reset
|
|
463
|
-
// made the next dialog's cold-start chunks take the post-stream append
|
|
464
|
-
// path (hasEverStreamed stuck true) — which consumers may silently drop
|
|
465
|
-
// when no assistant bubble exists yet — and left tool/compaction routing
|
|
466
|
-
// thinking a stream was still open (isInStream stuck true).
|
|
467
|
-
isInStreamRef.current = false
|
|
468
|
-
hasEverStreamedRef.current = false
|
|
469
|
-
}, [])
|
|
470
|
-
|
|
471
|
-
const updateApprovalStatus = useCallback(
|
|
472
|
-
(requestId: string, status: ChatApprovalStatus, resolvedByName?: string | null) => {
|
|
473
|
-
return accumulatorRef.current.updateApprovalStatus(requestId, status, resolvedByName)
|
|
474
|
-
},
|
|
475
|
-
[]
|
|
476
|
-
)
|
|
477
|
-
|
|
478
|
-
const getPendingApprovals = useCallback(() => {
|
|
479
|
-
return new Map(pendingEscalatedRef.current)
|
|
480
|
-
}, [])
|
|
481
|
-
|
|
482
|
-
return {
|
|
483
|
-
processChunk,
|
|
484
|
-
getSegments,
|
|
485
|
-
reset,
|
|
486
|
-
updateApprovalStatus,
|
|
487
|
-
getPendingApprovals,
|
|
488
|
-
}
|
|
489
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
<!-- source-hash: c8c1cef3b3966f8791e7003c4f74429f -->
|
|
2
|
-
Utilities for parsing raw NATS message chunks into typed, structured action objects consumed by the OpenFrame chat pipeline.
|
|
3
|
-
|
|
4
|
-
## Key Components
|
|
5
|
-
|
|
6
|
-
### `parseChunkToAction(chunk: unknown): ParsedChunkAction | null`
|
|
7
|
-
The primary export. Accepts an untyped NATS chunk, inspects its `type` field against `MESSAGE_TYPE` constants, and returns a discriminated `ParsedChunkAction` union or `null` for unrecognized types. Handles the full message lifecycle including:
|
|
8
|
-
|
|
9
|
-
- **Lifecycle signals:** `message_start`, `message_end`, `dialog_closed`
|
|
10
|
-
- **Content:** `text`, `thinking`, `system`, `direct_message`
|
|
11
|
-
- **Tool execution:** `tool_execution` (both `EXECUTING_TOOL` and `EXECUTED_TOOL` states)
|
|
12
|
-
- **Approval flow:** `approval_request`, `approval_batch`, `approval_result`
|
|
13
|
-
- **Metadata & telemetry:** `metadata`, `token_usage`, `context_compaction_start/end`
|
|
14
|
-
- **Error handling:** `error`
|
|
15
|
-
|
|
16
|
-
### `normalizeToolCalls(raw: unknown): PendingToolCallData[]` *(internal)*
|
|
17
|
-
Safely coerces an untyped array into `PendingToolCallData[]`, filtering non-objects and providing safe defaults for all fields.
|
|
18
|
-
|
|
19
|
-
### Predicate helpers
|
|
20
|
-
Lightweight type guards for fast chunk classification without full parsing:
|
|
21
|
-
|
|
22
|
-
| Function | Returns `true` when |
|
|
23
|
-
|---|---|
|
|
24
|
-
| `isControlChunk` | `MESSAGE_START` or `MESSAGE_END` |
|
|
25
|
-
| `isErrorChunk` | `ERROR` type |
|
|
26
|
-
| `isMetadataChunk` | `AI_METADATA` type |
|
|
27
|
-
| `extractTextFromChunk` | `TEXT` type — returns the string or `null` |
|
|
28
|
-
|
|
29
|
-
## Usage Example
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
import {
|
|
33
|
-
parseChunkToAction,
|
|
34
|
-
isControlChunk,
|
|
35
|
-
isErrorChunk,
|
|
36
|
-
} from './chunk-parser'
|
|
37
|
-
|
|
38
|
-
// Parse an incoming NATS chunk
|
|
39
|
-
const action = parseChunkToAction(rawChunk)
|
|
40
|
-
|
|
41
|
-
if (action?.action === 'text') {
|
|
42
|
-
appendToTranscript(action.text)
|
|
43
|
-
} else if (action?.action === 'approval_batch') {
|
|
44
|
-
promptUserApproval(action.toolCalls)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Fast-path guards before full parse
|
|
48
|
-
if (isErrorChunk(rawChunk)) {
|
|
49
|
-
handleError()
|
|
50
|
-
}
|
|
51
|
-
if (isControlChunk(rawChunk)) {
|
|
52
|
-
resetStreamState()
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Notes
|
|
57
|
-
|
|
58
|
-
- All field access is defensive — missing or wrong-typed properties fall back to safe defaults rather than throwing.
|
|
59
|
-
- `approval_result` accepts both `resolvedByName` and `displayName` so realtime NATS chunks and GraphQL history replays produce identical output shapes.
|
|
60
|
-
- `contextItems` on `message_request` back-fills `label` from `id` to satisfy the required `ChatContextItem` contract when no label is present on the wire.
|
|
61
|
-
|
|
62
|
-
**Source:** [`https://github.com/flamingo-stack/openframe-oss-lib/blob/main/chunk-parser.ts`](https://github.com/flamingo-stack/openframe-oss-lib/blob/main/chunk-parser.ts)
|