{"version":3,"sources":["src/common.speech/DialogServiceAdapter.ts"],"names":[],"mappings":"AAOA,OAAO,EAUH,YAAY,EAKf,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAEH,qBAAqB,EACrB,kBAAkB,EAElB,sBAAsB,EAQtB,uBAAuB,EAE1B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAKH,qBAAqB,EAKxB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAIzD,OAAO,EAAE,eAAe,EAAE,MAAM,6DAA6D,CAAC;AAC9F,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAEhF,qBAAa,oBAAqB,SAAQ,qBAAqB;IAC3D,OAAO,CAAC,0BAA0B,CAAyB;IAE3D,OAAO,CAAC,qBAAqB,CAAe;IAE5C,OAAO,CAAC,kBAAkB,CAAgB;IAC1C,OAAO,CAAC,oBAAoB,CAAU;IACtC,OAAO,CAAC,eAAe,CAAU;IACjC,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,UAAU,CAA2B;IAK7C,OAAO,CAAC,oBAAoB,CAAgC;gBAGxD,cAAc,EAAE,eAAe,EAC/B,iBAAiB,EAAE,kBAAkB,EACrC,WAAW,EAAE,YAAY,EACzB,gBAAgB,EAAE,gBAAgB,EAClC,sBAAsB,EAAE,sBAAsB;IAyBrC,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;cAwBxC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAY/C,SAAS,CAAC,2BAA2B,CAAC,iBAAiB,EAAE,uBAAuB,GAAG,OAAO,CAAC,OAAO,CAAC;cAmInF,iBAAiB,CAC7B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,kBAAkB,EAAE,kBAAkB,EACtC,SAAS,EAAE,qBAAqB,EAChC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;cA8CjB,UAAU,CACtB,QAAQ,EAAE,eAAe,EACzB,eAAe,EAAE,CAAC,CAAC,EAAE,uBAAuB,KAAK,IAAI,EACrD,aAAa,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GACnC,OAAO,CAAC,IAAI,CAAC;IAgDhB,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,4BAA4B;YA6ItB,gBAAgB;YAchB,gBAAgB;YAWhB,oBAAoB;IAQlC,OAAO,CAAC,eAAe;IA4BvB,OAAO,CAAC,gBAAgB;IAwBxB,OAAO,CAAC,kBAAkB;IAoB1B,OAAO,CAAC,qBAAqB;IAqD7B,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,qBAAqB;CAoChC","file":"DialogServiceAdapter.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT license.\n\nimport {\n    ReplayableAudioNode\n} from \"../common.browser/Exports.js\";\nimport { SendingAgentContextMessageEvent } from \"../common/DialogEvents.js\";\nimport {\n    BackgroundEvent,\n    ConnectionEvent,\n    ConnectionMessage,\n    createGuid,\n    createNoDashGuid,\n    Deferred,\n    DialogEvent,\n    Events,\n    EventSource,\n    IAudioSource,\n    IAudioStreamNode,\n    IConnection,\n    MessageType,\n    ServiceEvent,\n} from \"../common/Exports.js\";\nimport { AudioOutputFormatImpl } from \"../sdk/Audio/AudioOutputFormat.js\";\nimport { PullAudioOutputStreamImpl } from \"../sdk/Audio/AudioOutputStream.js\";\nimport { AudioStreamFormatImpl } from \"../sdk/Audio/AudioStreamFormat.js\";\nimport {\n    ActivityReceivedEventArgs,\n    CancellationErrorCode,\n    CancellationReason,\n    DialogServiceConfig,\n    DialogServiceConnector,\n    PropertyCollection,\n    PropertyId,\n    RecognitionEventArgs,\n    ResultReason,\n    SessionEventArgs,\n    SpeechRecognitionCanceledEventArgs,\n    SpeechRecognitionEventArgs,\n    SpeechRecognitionResult,\n    TurnStatusReceivedEventArgs,\n} from \"../sdk/Exports.js\";\nimport { DialogServiceTurnStateManager } from \"./DialogServiceTurnStateManager.js\";\nimport {\n    CancellationErrorCodePropertyName,\n    EnumTranslation,\n    ISpeechConfigAudioDevice,\n    RecognitionStatus,\n    ServiceRecognizerBase,\n    SimpleSpeechPhrase,\n    SpeechDetected,\n    SpeechHypothesis,\n    SpeechKeyword,\n} from \"./Exports.js\";\nimport { IAuthentication } from \"./IAuthentication.js\";\nimport { IConnectionFactory } from \"./IConnectionFactory.js\";\nimport { RecognizerConfig } from \"./RecognizerConfig.js\";\nimport { ActivityPayloadResponse } from \"./ServiceMessages/ActivityResponsePayload.js\";\nimport { InvocationSource } from \"./ServiceMessages/InvocationSource.js\";\nimport { ClientDetectedKeyword, KeywordDetectionType, OnRejectAction } from \"./ServiceMessages/KeywordDetection/KeywordDetection.js\";\nimport { RecognitionMode } from \"./ServiceMessages/PhraseDetection/PhraseDetectionContext.js\";\nimport { SpeechConnectionMessage } from \"./SpeechConnectionMessage.Internal.js\";\n\nexport class DialogServiceAdapter extends ServiceRecognizerBase {\n    private privDialogServiceConnector: DialogServiceConnector;\n\n    private privDialogAudioSource: IAudioSource;\n\n    private privConnectionLoop: Promise<void>;\n    private terminateMessageLoop: boolean;\n    private agentConfigSent: boolean;\n    private privLastResult: SpeechRecognitionResult;\n    private privEvents: EventSource<DialogEvent>;\n\n    // Turns are of two kinds:\n    // 1: SR turns, end when the SR result is returned and then turn end.\n    // 2: Service turns where an activity is sent by the service along with the audio.\n    private privTurnStateManager: DialogServiceTurnStateManager;\n\n    public constructor(\n        authentication: IAuthentication,\n        connectionFactory: IConnectionFactory,\n        audioSource: IAudioSource,\n        recognizerConfig: RecognizerConfig,\n        dialogServiceConnector: DialogServiceConnector) {\n\n        super(authentication, connectionFactory, audioSource, recognizerConfig, dialogServiceConnector);\n\n        this.privEvents = new EventSource<DialogEvent>();\n        this.privDialogServiceConnector = dialogServiceConnector;\n        this.receiveMessageOverride = (): Promise<void> => this.receiveDialogMessageOverride();\n        this.privTurnStateManager = new DialogServiceTurnStateManager();\n        this.recognizeOverride =\n            (recoMode: RecognitionMode, successCallback: (e: SpeechRecognitionResult) => void, errorCallback: (e: string) => void): Promise<void> =>\n                this.listenOnce(recoMode, successCallback, errorCallback);\n        this.postConnectImplOverride = (connection: Promise<IConnection>): Promise<IConnection> => this.dialogConnectImpl(connection);\n        this.configConnectionOverride = (connection: IConnection): Promise<IConnection> => this.configConnection(connection);\n        this.disconnectOverride = (): Promise<void> => this.privDisconnect();\n        this.privDialogAudioSource = audioSource;\n\n        this.agentConfigSent = false;\n        this.privLastResult = null;\n        this.connectionEvents.attach((connectionEvent: ConnectionEvent): void => {\n            if (connectionEvent.name === \"ConnectionClosedEvent\") {\n                this.terminateMessageLoop = true;\n            }\n        });\n    }\n\n    public async sendMessage(message: string): Promise<void> {\n        const interactionGuid: string = createGuid();\n        const requestId: string = createNoDashGuid();\n\n        const agentMessage: any = {\n            context: {\n                interactionId: interactionGuid\n            },\n            // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n            messagePayload: JSON.parse(message),\n            version: 0.5\n        };\n\n        const agentMessageJson = JSON.stringify(agentMessage);\n        const connection: IConnection = await this.fetchConnection();\n        await connection.send(new SpeechConnectionMessage(\n            MessageType.Text,\n            \"agent\",\n            requestId,\n            \"application/json\",\n            agentMessageJson));\n\n    }\n\n    protected async privDisconnect(): Promise<void> {\n        await this.cancelRecognition(this.privRequestSession.sessionId,\n            this.privRequestSession.requestId,\n            CancellationReason.Error,\n            CancellationErrorCode.NoError,\n            \"Disconnecting\");\n\n        this.terminateMessageLoop = true;\n        this.agentConfigSent = false;\n        return;\n    }\n\n    protected processTypeSpecificMessages(connectionMessage: SpeechConnectionMessage): Promise<boolean> {\n\n        const resultProps: PropertyCollection = new PropertyCollection();\n        if (connectionMessage.messageType === MessageType.Text) {\n            resultProps.setProperty(PropertyId.SpeechServiceResponse_JsonResult, connectionMessage.textBody);\n        }\n\n        let result: SpeechRecognitionResult;\n        let processed: boolean;\n\n        switch (connectionMessage.path.toLowerCase()) {\n            case \"speech.phrase\":\n                const speechPhrase: SimpleSpeechPhrase = SimpleSpeechPhrase.fromJSON(connectionMessage.textBody, this.privRequestSession.currentTurnAudioOffset);\n\n                this.privRequestSession.onPhraseRecognized(speechPhrase.Offset + speechPhrase.Duration);\n\n                if (speechPhrase.RecognitionStatus !== RecognitionStatus.TooManyRequests && speechPhrase.RecognitionStatus !== RecognitionStatus.Error) {\n                    const args: SpeechRecognitionEventArgs = this.fireEventForResult(speechPhrase, resultProps);\n                    this.privLastResult = args.result;\n\n                    if (!!this.privDialogServiceConnector.recognized) {\n                        try {\n                            this.privDialogServiceConnector.recognized(this.privDialogServiceConnector, args);\n                            /* eslint-disable no-empty */\n                        } catch (error) {\n                            // Not going to let errors in the event handler\n                            // trip things up.\n                        }\n                    }\n                }\n                processed = true;\n                break;\n            case \"speech.hypothesis\":\n                const hypothesis: SpeechHypothesis = SpeechHypothesis.fromJSON(connectionMessage.textBody, this.privRequestSession.currentTurnAudioOffset);\n\n                result = new SpeechRecognitionResult(\n                    this.privRequestSession.requestId,\n                    ResultReason.RecognizingSpeech,\n                    hypothesis.Text,\n                    hypothesis.Duration,\n                    hypothesis.Offset,\n                    hypothesis.Language,\n                    hypothesis.LanguageDetectionConfidence,\n                    undefined,\n                    undefined,\n                    hypothesis.asJson(),\n                    resultProps);\n\n                this.privRequestSession.onHypothesis(hypothesis.Offset);\n\n                const ev = new SpeechRecognitionEventArgs(result, hypothesis.Offset, this.privRequestSession.sessionId);\n\n                if (!!this.privDialogServiceConnector.recognizing) {\n                    try {\n                        this.privDialogServiceConnector.recognizing(this.privDialogServiceConnector, ev);\n                        /* eslint-disable no-empty */\n                    } catch (error) {\n                        // Not going to let errors in the event handler\n                        // trip things up.\n                    }\n                }\n                processed = true;\n                break;\n            case \"speech.keyword\":\n                const keyword: SpeechKeyword = SpeechKeyword.fromJSON(connectionMessage.textBody, this.privRequestSession.currentTurnAudioOffset);\n\n                result = new SpeechRecognitionResult(\n                    this.privRequestSession.requestId,\n                    keyword.Status === \"Accepted\" ? ResultReason.RecognizedKeyword : ResultReason.NoMatch,\n                    keyword.Text,\n                    keyword.Duration,\n                    keyword.Offset,\n                    undefined,\n                    undefined,\n                    undefined,\n                    undefined,\n                    keyword.asJson(),\n                    resultProps);\n\n                if (keyword.Status !== \"Accepted\") {\n                    this.privLastResult = result;\n                }\n\n                const event = new SpeechRecognitionEventArgs(result, result.duration, result.resultId);\n\n                if (!!this.privDialogServiceConnector.recognized) {\n                    try {\n                        this.privDialogServiceConnector.recognized(this.privDialogServiceConnector, event);\n                        /* eslint-disable no-empty */\n                    } catch (error) {\n                        // Not going to let errors in the event handler\n                        // trip things up.\n                    }\n                }\n                processed = true;\n                break;\n            case \"audio\":\n                {\n                    const audioRequestId = connectionMessage.requestId.toUpperCase();\n                    const turn = this.privTurnStateManager.GetTurn(audioRequestId);\n                    try {\n                        // Empty binary message signals end of stream.\n                        if (!connectionMessage.binaryBody) {\n                            turn.endAudioStream();\n                        } else {\n                            turn.audioStream.write(connectionMessage.binaryBody);\n                        }\n                    } catch (error) {\n                        // Not going to let errors in the event handler\n                        // trip things up.\n                    }\n                }\n                processed = true;\n                break;\n\n            case \"response\":\n                {\n                    this.handleResponseMessage(connectionMessage);\n                }\n                processed = true;\n                break;\n\n            default:\n                break;\n        }\n        const defferal = new Deferred<boolean>();\n        defferal.resolve(processed);\n        return defferal.promise;\n    }\n\n    // Cancels recognition.\n    protected async cancelRecognition(\n        sessionId: string,\n        requestId: string,\n        cancellationReason: CancellationReason,\n        errorCode: CancellationErrorCode,\n        error: string): Promise<void> {\n\n        this.terminateMessageLoop = true;\n\n        if (!!this.privRequestSession.isRecognizing) {\n            await this.privRequestSession.onStopRecognizing();\n        }\n\n        if (!!this.privDialogServiceConnector.canceled) {\n            const properties: PropertyCollection = new PropertyCollection();\n            properties.setProperty(CancellationErrorCodePropertyName, CancellationErrorCode[errorCode]);\n\n            const cancelEvent: SpeechRecognitionCanceledEventArgs = new SpeechRecognitionCanceledEventArgs(\n                cancellationReason,\n                error,\n                errorCode,\n                undefined,\n                sessionId);\n\n            try {\n                this.privDialogServiceConnector.canceled(this.privDialogServiceConnector, cancelEvent);\n                /* eslint-disable no-empty */\n            } catch { }\n\n            if (!!this.privSuccessCallback) {\n                const result: SpeechRecognitionResult = new SpeechRecognitionResult(\n                    undefined, // ResultId\n                    ResultReason.Canceled,\n                    undefined, // Text\n                    undefined, // Duration\n                    undefined, // Offset\n                    undefined, // Language\n                    undefined, // Language Detection Confidence\n                    undefined, // Speaker Id\n                    error,\n                    undefined, // Json\n                    properties);\n                try {\n                    this.privSuccessCallback(result);\n                    this.privSuccessCallback = undefined;\n                    /* eslint-disable no-empty */\n                } catch { }\n            }\n        }\n    }\n\n    protected async listenOnce(\n        recoMode: RecognitionMode,\n        successCallback: (e: SpeechRecognitionResult) => void,\n        errorCallback: (e: string) => void\n    ): Promise<void> {\n        this.privRecognizerConfig.recognitionMode = recoMode;\n\n        this.privSuccessCallback = successCallback;\n        this.privErrorCallback = errorCallback;\n\n        this.privRequestSession.startNewRecognition();\n        this.privRequestSession.listenForServiceTelemetry(this.privDialogAudioSource.events);\n\n        this.privRecognizerConfig.parameters.setProperty(PropertyId.Speech_SessionId, this.privRequestSession.sessionId);\n\n        // Start the connection to the service. The promise this will create is stored and will be used by configureConnection().\n        const conPromise: Promise<IConnection> = this.connectImpl();\n\n        const preAudioPromise: Promise<void> = this.sendPreAudioMessages();\n\n        const node: IAudioStreamNode = await this.privDialogAudioSource.attach(this.privRequestSession.audioNodeId);\n        const format: AudioStreamFormatImpl = await this.privDialogAudioSource.format;\n        const deviceInfo: ISpeechConfigAudioDevice = await this.privDialogAudioSource.deviceInfo;\n\n        const audioNode = new ReplayableAudioNode(node, format.avgBytesPerSec);\n        await this.privRequestSession.onAudioSourceAttachCompleted(audioNode, false);\n\n        this.privRecognizerConfig.SpeechServiceConfig.Context.audio = { source: deviceInfo };\n\n        try {\n            await conPromise;\n            await preAudioPromise;\n        } catch (error) {\n            await this.cancelRecognition(this.privRequestSession.sessionId, this.privRequestSession.requestId, CancellationReason.Error, CancellationErrorCode.ConnectionFailure, error as string);\n            return Promise.resolve();\n        }\n\n        const sessionStartEventArgs: SessionEventArgs = new SessionEventArgs(this.privRequestSession.sessionId);\n\n        if (!!this.privRecognizer.sessionStarted) {\n            this.privRecognizer.sessionStarted(this.privRecognizer, sessionStartEventArgs);\n        }\n\n        const audioSendPromise = this.sendAudio(audioNode);\n\n        // /* eslint-disable no-empty */\n        audioSendPromise.then((): void => { /* add? return true;*/ }, async (error: string): Promise<void> => {\n            await this.cancelRecognition(this.privRequestSession.sessionId, this.privRequestSession.requestId, CancellationReason.Error, CancellationErrorCode.RuntimeError, error);\n        });\n    }\n\n    // Establishes a websocket connection to the end point.\n    private dialogConnectImpl(connection: Promise<IConnection>): Promise<IConnection> {\n        this.privConnectionLoop = this.startMessageLoop();\n        return connection;\n    }\n\n    private receiveDialogMessageOverride(): Promise<void> {\n\n        // we won't rely on the cascading promises of the connection since we want to continually be available to receive messages\n        const communicationCustodian: Deferred<void> = new Deferred<void>();\n\n        const loop = async (): Promise<void> => {\n            try {\n                const isDisposed: boolean = this.isDisposed();\n                const terminateMessageLoop = (!this.isDisposed() && this.terminateMessageLoop);\n                if (isDisposed || terminateMessageLoop) {\n                    // We're done.\n                    communicationCustodian.resolve(undefined);\n                    return;\n                }\n\n                const connection: IConnection = await this.fetchConnection();\n                const message: ConnectionMessage = await connection.read();\n\n                if (!message) {\n                    return loop();\n                }\n\n                const connectionMessage = SpeechConnectionMessage.fromConnectionMessage(message);\n\n                switch (connectionMessage.path.toLowerCase()) {\n                    case \"turn.start\":\n                        {\n                            const turnRequestId = connectionMessage.requestId.toUpperCase();\n                            const audioSessionReqId = this.privRequestSession.requestId.toUpperCase();\n\n                            // turn started by the service\n                            if (turnRequestId !== audioSessionReqId) {\n                                this.privTurnStateManager.StartTurn(turnRequestId);\n                            } else {\n                                this.privRequestSession.onServiceTurnStartResponse();\n                            }\n                        }\n                        break;\n\n                    case \"speech.startdetected\":\n                        const speechStartDetected: SpeechDetected = SpeechDetected.fromJSON(connectionMessage.textBody, this.privRequestSession.currentTurnAudioOffset);\n\n                        const speechStartEventArgs = new RecognitionEventArgs(speechStartDetected.Offset, this.privRequestSession.sessionId);\n\n                        if (!!this.privRecognizer.speechStartDetected) {\n                            this.privRecognizer.speechStartDetected(this.privRecognizer, speechStartEventArgs);\n                        }\n\n                        break;\n\n                    case \"speech.enddetected\":\n\n                        let json: string;\n\n                        if (connectionMessage.textBody.length > 0) {\n                            json = connectionMessage.textBody;\n                        } else {\n                            // If the request was empty, the JSON returned is empty.\n                            json = \"{ Offset: 0 }\";\n                        }\n\n                        const speechStopDetected: SpeechDetected = SpeechDetected.fromJSON(json, this.privRequestSession.currentTurnAudioOffset);\n\n                        this.privRequestSession.onServiceRecognized(speechStopDetected.Offset);\n\n                        const speechStopEventArgs = new RecognitionEventArgs(speechStopDetected.Offset, this.privRequestSession.sessionId);\n\n                        if (!!this.privRecognizer.speechEndDetected) {\n                            this.privRecognizer.speechEndDetected(this.privRecognizer, speechStopEventArgs);\n                        }\n                        break;\n\n                    case \"turn.end\":\n                        {\n                            const turnEndRequestId = connectionMessage.requestId.toUpperCase();\n\n                            const audioSessionReqId = this.privRequestSession.requestId.toUpperCase();\n\n                            // turn started by the service\n                            if (turnEndRequestId !== audioSessionReqId) {\n                                this.privTurnStateManager.CompleteTurn(turnEndRequestId);\n                            } else {\n                                // Audio session turn\n\n                                const sessionStopEventArgs: SessionEventArgs = new SessionEventArgs(this.privRequestSession.sessionId);\n                                await this.privRequestSession.onServiceTurnEndResponse(false);\n\n                                if (!this.privRecognizerConfig.isContinuousRecognition || this.privRequestSession.isSpeechEnded || !this.privRequestSession.isRecognizing) {\n                                    if (!!this.privRecognizer.sessionStopped) {\n                                        this.privRecognizer.sessionStopped(this.privRecognizer, sessionStopEventArgs);\n                                    }\n                                }\n\n                                // report result to promise.\n                                if (!!this.privSuccessCallback && this.privLastResult) {\n                                    try {\n                                        this.privSuccessCallback(this.privLastResult);\n                                        this.privLastResult = null;\n                                    } catch (e) {\n                                        if (!!this.privErrorCallback) {\n                                            this.privErrorCallback(e as string);\n                                        }\n                                    }\n                                    // Only invoke the call back once.\n                                    // and if it's successful don't invoke the\n                                    // error after that.\n                                    this.privSuccessCallback = undefined;\n                                    this.privErrorCallback = undefined;\n                                }\n                            }\n                        }\n                        break;\n\n                    default:\n                        try {\n                            const processed = await this.processTypeSpecificMessages(connectionMessage);\n                            if (!processed) {\n                                if (!!this.serviceEvents) {\n                                    this.serviceEvents.onEvent(new ServiceEvent(connectionMessage.path.toLowerCase(), connectionMessage.textBody));\n                                }\n                            }\n                        } catch (e) {\n                            //\n                        }\n                }\n                const ret: Promise<void> = loop();\n\n                return ret;\n            } catch (error) {\n                this.terminateMessageLoop = true;\n                communicationCustodian.resolve();\n            }\n        };\n\n        loop().catch((reason: string): void => {\n            Events.instance.onEvent(new BackgroundEvent(reason));\n        });\n\n        return communicationCustodian.promise;\n    }\n\n    private async startMessageLoop(): Promise<void> {\n\n        this.terminateMessageLoop = false;\n\n        try {\n            await this.receiveDialogMessageOverride();\n        } catch (error) {\n            await this.cancelRecognition(this.privRequestSession.sessionId, this.privRequestSession.requestId, CancellationReason.Error, CancellationErrorCode.RuntimeError, error as string);\n        }\n\n        return Promise.resolve();\n    }\n\n    // Takes an established websocket connection to the endpoint and sends speech configuration information.\n    private async configConnection(connection: IConnection): Promise<IConnection> {\n        if (this.terminateMessageLoop) {\n            this.terminateMessageLoop = false;\n            return Promise.reject(\"Connection to service terminated.\");\n        }\n\n        await this.sendSpeechServiceConfig(connection, this.privRequestSession, this.privRecognizerConfig.SpeechServiceConfig.serialize());\n        await this.sendAgentConfig(connection);\n        return connection;\n    }\n\n    private async sendPreAudioMessages(): Promise<void> {\n        const connection: IConnection = await this.fetchConnection();\n        this.addKeywordContextData();\n        await this.sendSpeechContext(connection, true);\n        await this.sendAgentContext(connection);\n        await this.sendWaveHeader(connection);\n    }\n\n    private sendAgentConfig(connection: IConnection): Promise<void> {\n        if (this.agentConfig && !this.agentConfigSent) {\n\n            if (this.privRecognizerConfig\n                .parameters\n                .getProperty(PropertyId.Conversation_DialogType) === DialogServiceConfig.DialogTypes.CustomCommands) {\n                const config = this.agentConfig.get();\n                config.botInfo.commandsCulture = this.privRecognizerConfig.parameters.getProperty(PropertyId.SpeechServiceConnection_RecoLanguage, \"en-us\");\n                this.agentConfig.set(config);\n            }\n            this.onEvent(new SendingAgentContextMessageEvent(this.agentConfig));\n\n            const agentConfigJson = this.agentConfig.toJsonString();\n\n            // guard against sending this multiple times on one connection\n            this.agentConfigSent = true;\n\n            return connection.send(new SpeechConnectionMessage(\n                MessageType.Text,\n                \"agent.config\",\n                this.privRequestSession.requestId,\n                \"application/json\",\n                agentConfigJson));\n        }\n\n        return;\n    }\n\n    private sendAgentContext(connection: IConnection): Promise<void> {\n        const guid: string = createGuid();\n\n        const speechActivityTemplate = this.privDialogServiceConnector.properties.getProperty(PropertyId.Conversation_Speech_Activity_Template);\n\n        const agentContext: any = {\n            channelData: \"\",\n            context: {\n                interactionId: guid\n            },\n            messagePayload: typeof speechActivityTemplate === undefined ? undefined : speechActivityTemplate,\n            version: 0.5\n        };\n\n        const agentContextJson = JSON.stringify(agentContext);\n\n        return connection.send(new SpeechConnectionMessage(\n            MessageType.Text,\n            \"speech.agent.context\",\n            this.privRequestSession.requestId,\n            \"application/json\",\n            agentContextJson));\n    }\n\n    private fireEventForResult(serviceResult: SimpleSpeechPhrase, properties: PropertyCollection): SpeechRecognitionEventArgs {\n        const resultReason: ResultReason = EnumTranslation.implTranslateRecognitionResult(serviceResult.RecognitionStatus);\n\n        const result = new SpeechRecognitionResult(\n            this.privRequestSession.requestId,\n            resultReason,\n            serviceResult.DisplayText,\n            serviceResult.Duration,\n            serviceResult.Offset,\n            serviceResult.Language,\n            serviceResult.LanguageDetectionConfidence,\n            undefined,\n            undefined,\n            serviceResult.asJson(),\n            properties);\n\n        const ev = new SpeechRecognitionEventArgs(result, serviceResult.Offset, this.privRequestSession.sessionId);\n        return ev;\n    }\n\n    private handleResponseMessage(responseMessage: SpeechConnectionMessage): void {\n        // \"response\" messages can contain either \"message\" (activity) or \"MessageStatus\" data. Fire the appropriate\n        // event according to the message type that's specified.\n        const responsePayload: { messageType: string } = JSON.parse(responseMessage.textBody) as { messageType: string };\n        switch (responsePayload.messageType.toLowerCase()) {\n            case \"message\":\n                const responseRequestId = responseMessage.requestId.toUpperCase();\n                const activityPayload: ActivityPayloadResponse = ActivityPayloadResponse.fromJSON(responseMessage.textBody);\n                const turn = this.privTurnStateManager.GetTurn(responseRequestId);\n\n                // update the conversation Id\n                if (activityPayload.conversationId) {\n                    const updateAgentConfig = this.agentConfig.get();\n                    updateAgentConfig.botInfo.conversationId = activityPayload.conversationId;\n                    this.agentConfig.set(updateAgentConfig);\n                }\n\n                const pullAudioOutputStream: PullAudioOutputStreamImpl = turn.processActivityPayload(\n                    activityPayload,\n                    AudioOutputFormatImpl.fromSpeechSynthesisOutputFormatString(this.privDialogServiceConnector.properties.getProperty(PropertyId.SpeechServiceConnection_SynthOutputFormat, undefined)));\n                const activity = new ActivityReceivedEventArgs(activityPayload.messagePayload, pullAudioOutputStream);\n                if (!!this.privDialogServiceConnector.activityReceived) {\n                    try {\n                        this.privDialogServiceConnector.activityReceived(this.privDialogServiceConnector, activity);\n                        /* eslint-disable-next-line no-empty */\n                    } catch (error) {\n                        // Not going to let errors in the event handler\n                        // trip things up.\n                    }\n                }\n                break;\n\n            case \"messagestatus\":\n                if (!!this.privDialogServiceConnector.turnStatusReceived) {\n                    try {\n                        this.privDialogServiceConnector.turnStatusReceived(\n                            this.privDialogServiceConnector,\n                            new TurnStatusReceivedEventArgs(responseMessage.textBody));\n                        /* eslint-disable-next-line no-empty */\n                    } catch (error) {\n                        // Not going to let errors in the event handler\n                        // trip things up.\n                    }\n                }\n                break;\n\n            default:\n                Events.instance.onEvent(\n                    new BackgroundEvent(`Unexpected response of type ${responsePayload.messageType}. Ignoring.`));\n                break;\n        }\n    }\n\n    private onEvent(event: DialogEvent): void {\n        this.privEvents.onEvent(event);\n        Events.instance.onEvent(event);\n    }\n\n    private addKeywordContextData(): void {\n        const keywordPropertyValue: string = this.privRecognizerConfig.parameters.getProperty(\"SPEECH-KeywordsToDetect\");\n        if (keywordPropertyValue === undefined) {\n            return;\n        }\n\n        const keywordOffsetPropertyValue: string = this.privRecognizerConfig.parameters\n            .getProperty(\"SPEECH-KeywordsToDetect-Offsets\");\n        const keywordDurationPropertyValue: string = this.privRecognizerConfig.parameters\n            .getProperty(\"SPEECH-KeywordsToDetect-Durations\");\n\n        const keywords = keywordPropertyValue.split(\";\");\n        const keywordOffsets = keywordOffsetPropertyValue === undefined ? [] : keywordOffsetPropertyValue.split(\";\");\n        const keywordDurations = keywordDurationPropertyValue === undefined ? [] : keywordDurationPropertyValue.split(\";\");\n\n        const keywordDefinitionArray: ClientDetectedKeyword[] = [];\n        for (let i = 0; i < keywords.length; i++) {\n            const definition: ClientDetectedKeyword = {\n                text: keywords[i]\n            };\n            if (i < keywordOffsets.length) {\n                definition.startOffset = Number(keywordOffsets[i]);\n            }\n            if (i < keywordDurations.length) {\n                definition.duration = Number(keywordDurations[i]);\n            }\n            keywordDefinitionArray.push(definition);\n        }\n\n        this.speechContext.getContext().invocationSource = InvocationSource.VoiceActivationWithKeyword;\n        this.speechContext.getContext().keywordDetection = [{\n            clientDetectedKeywords: keywordDefinitionArray,\n            onReject: { action: OnRejectAction.EndOfTurn },\n            type: KeywordDetectionType.StartTrigger\n        }];\n    }\n}\n"]}