11import {
22 any ,
33 array ,
4+ empty ,
45 integer ,
6+ length ,
57 literal ,
68 minValue ,
79 nonEmpty ,
@@ -11,77 +13,74 @@ import {
1113 pipe ,
1214 safeParse ,
1315 string ,
16+ transform ,
1417 undefinedable ,
15- union
18+ union ,
19+ unknown
1620} from 'valibot' ;
1721
1822import { type WebChatActivity } from '../types/WebChatActivity' ;
1923import getOrgSchemaMessage from './getOrgSchemaMessage' ;
2024
2125const EMPTY_ARRAY = Object . freeze ( [ ] ) ;
2226
27+ type StreamData = {
28+ streamId ?: string ;
29+ streamSequence ?: number ;
30+ streamType ?: string ;
31+ } ;
32+
2333const streamSequenceSchema = pipe ( number ( ) , integer ( ) , minValue ( 1 ) ) ;
2434
25- const livestreamingActivitySchema = union ( [
26- // Interim.
35+ const streamInfoSchema = union ( [
2736 object ( {
28- attachments : optional ( array ( any ( ) ) , EMPTY_ARRAY ) ,
29- channelData : object ( {
30- // "streamId" is optional for the very first activity in the session.
31- streamId : optional ( undefinedable ( string ( ) ) ) ,
32- streamSequence : streamSequenceSchema ,
33- streamType : literal ( 'streaming' )
34- } ) ,
35- id : string ( ) ,
36- // "text" is optional. If not set or empty, it presents a contentless activity.
37- text : optional ( undefinedable ( string ( ) ) ) ,
38- type : literal ( 'typing' )
39- } ) ,
40- // Informative message.
41- object ( {
42- attachments : optional ( array ( any ( ) ) , EMPTY_ARRAY ) ,
43- channelData : object ( {
44- // "streamId" is optional for the very first activity in the session.
45- streamId : optional ( undefinedable ( string ( ) ) ) ,
46- streamSequence : streamSequenceSchema ,
47- streamType : literal ( 'informative' )
48- } ) ,
49- id : string ( ) ,
50- // Informative may not have "text", but should have abstract instead (checked later)
51- text : optional ( undefinedable ( string ( ) ) ) ,
52- type : literal ( 'typing' ) ,
53- entities : optional ( array ( any ( ) ) , EMPTY_ARRAY )
37+ // "streamId" is optional for the very first activity in the session.
38+ streamId : optional ( undefinedable ( string ( ) ) ) ,
39+ streamSequence : streamSequenceSchema ,
40+ streamType : union ( [ literal ( 'streaming' ) , literal ( 'informative' ) ] )
5441 } ) ,
55- // Conclude with a message.
5642 object ( {
57- attachments : optional ( array ( any ( ) ) , EMPTY_ARRAY ) ,
58- channelData : object ( {
59- // "streamId" is required for the final activity in the session.
60- // The final activity must not be the sole activity in the session.
61- streamId : pipe ( string ( ) , nonEmpty ( ) ) ,
62- streamType : literal ( 'final' )
63- } ) ,
64- id : string ( ) ,
65- // If "text" is empty, it represents "regretting" the livestream.
66- text : optional ( undefinedable ( string ( ) ) ) ,
67- type : literal ( 'message' )
68- } ) ,
69- // Conclude without a message.
70- object ( {
71- attachments : optional ( array ( any ( ) ) , EMPTY_ARRAY ) ,
72- channelData : object ( {
73- // "streamId" is required for the final activity in the session.
74- // The final activity must not be the sole activity in the session.
75- streamId : pipe ( string ( ) , nonEmpty ( ) ) ,
76- streamType : literal ( 'final' )
77- } ) ,
78- id : string ( ) ,
79- // If "text" is not set or empty, it represents "regretting" the livestream.
80- text : optional ( undefinedable ( literal ( '' ) ) ) ,
81- type : literal ( 'typing' )
43+ // "streamId" is required for the final activity in the session.
44+ // The final activity must not be the sole activity in the session.
45+ streamId : pipe ( string ( ) , nonEmpty ( ) ) ,
46+ streamType : literal ( 'final' )
8247 } )
8348] ) ;
8449
50+ const validEntitiesSchema = union ( [
51+ pipe (
52+ array ( streamInfoSchema ) ,
53+ length ( 1 ) ,
54+ transform ( data => data [ 0 ] )
55+ ) ,
56+ pipe (
57+ array ( unknown ( ) ) ,
58+ empty ( ) ,
59+ transform ( ( ) => undefined )
60+ )
61+ ] ) ;
62+
63+ const validChannelDataSchema = union ( [
64+ streamInfoSchema ,
65+ pipe (
66+ object ( {
67+ webChat : object ( {
68+ receivedAt : number ( )
69+ } )
70+ } ) ,
71+ transform ( ( ) => undefined )
72+ )
73+ ] ) ;
74+
75+ const livestreamingActivitySchema = object ( {
76+ entities : validEntitiesSchema ,
77+ channelData : validChannelDataSchema ,
78+ attachments : optional ( array ( any ( ) ) , EMPTY_ARRAY ) ,
79+ id : string ( ) ,
80+ text : optional ( undefinedable ( string ( ) ) ) ,
81+ type : union ( [ literal ( 'typing' ) , literal ( 'message' ) ] )
82+ } ) ;
83+
8584/**
8685 * Gets the livestreaming metadata of the activity, or `undefined` if the activity is not participating in a livestreaming session.
8786 *
@@ -111,26 +110,36 @@ export default function getActivityLivestreamingMetadata(activity: WebChatActivi
111110 if ( result . success ) {
112111 const { output } = result ;
113112
113+ let streamData : StreamData ;
114+
115+ if ( output . entities !== undefined ) {
116+ streamData = output . entities ;
117+ } else if ( output . channelData !== undefined ) {
118+ streamData = output . channelData ;
119+ } else {
120+ return undefined ;
121+ }
122+
114123 // If the activity is the first in the session, session ID should be the activity ID.
115- const sessionId = output . channelData . streamId || output . id ;
124+ const sessionId = streamData . streamId || output . id ;
116125
117126 return Object . freeze (
118- output . channelData . streamType === 'final'
127+ streamData . streamType === 'final'
119128 ? {
120129 sequenceNumber : Infinity ,
121130 sessionId,
122131 type : 'final activity'
123132 }
124133 : {
125- sequenceNumber : output . channelData . streamSequence ,
134+ sequenceNumber : streamData . streamSequence ,
126135 sessionId,
127136 type : ! (
128137 output . text ||
129138 output . attachments ?. length ||
130139 ( 'entities' in output && getOrgSchemaMessage ( output . entities ) ?. abstract )
131140 )
132141 ? 'contentless'
133- : output . channelData . streamType === 'informative'
142+ : streamData . streamType === 'informative'
134143 ? 'informative message'
135144 : 'interim activity'
136145 }
0 commit comments