Salesforce Agent API from Lightning Web Component

Salesforce Agent API from Lightning Web Component

We can securely and seamlessly access the Salesforce Agent APIs from Lightning Web Component using Apex.

Salesforce Agent API Setup:

In order to store the Consumer Key, Consumer Secret, API Instance URL and My Domain URL, I have used Custom Metadata Type.

Apex Class:

public with sharing class ChatClientController {

    public static Agentforce_Agent_Configuration__mdt objConfig = Agentforce_Agent_Configuration__mdt.getInstance( 'ServiceAgent' );
    
    /*
    * This method gets the access token from Salesforce if the Access Toke is expired or null.
    * Session creation will be done if the Session Id is null.
    * Message will be sent to the Agentforce Agent and the response will be returned.
    * @param strToken - Access Token
    * @param strLastTokenIssued - Last Token Issued
    * @param strSessionId - Session Id
    * @param strMessage - Message
    * @return Map < String, Object > - Result Map
    */
    @AuraEnabled
    public static Map < String, Object > sendMessage( String strToken, String strLastTokenIssued, String strSessionId, String strMessage ) {
        
        HttpResponse response;
        Map < String, Object > resultMap = new Map < String, Object >(); 
        
        if ( 
            String.isBlank( strToken ) || 
            String.isBlank( strLastTokenIssued ) || 
            ( ( DateTime.now().getTime() - DateTime.newInstance( Long.valueOf( strLastTokenIssued ) ).getTime() )  / 60000 ) > 25 
        ) {

            try {

                response = accessToken();
                
                if ( response.getStatusCode() == 200 && response.getBody() != null) {

                    Map < String, Object > jsonMap = ( Map < String, Object > )JSON.deserializeUntyped( response.getBody() );     
                    
                    String strAccessToken = ( String ) jsonMap.get( 'access_token' ); 

                    if ( strAccessToken != null ) {

                        strToken = strAccessToken;
                        resultMap.put( 'AccessToken', strAccessToken );
                        resultMap.put( 'LastTokenIssued', DateTime.now().getTime() );
                        
                    } else {

                        resultMap.put( 'Error', 'Access Token is null' );

                    }

                } else {

                    System.debug( 'Error: ' + response.getStatusCode() + ' - ' + response.getBody()  );
                    resultMap.put( 'Error', response.getBody() );

                }

            } catch ( System.CalloutException e ) {

                System.debug('HTTP Request failed: ' + e.getMessage());
                resultMap.put( 'Error', e.getMessage() );

            }

            if ( !resultMap.containsKey( 'AccessToken' ) ) {

                resultMap.put( 'LastTokenIssued', null );

            }

        }

        if ( String.isBlank( strSessionId) ) {

            try {

                response = sessionRequest( strToken );
                
                if ( response.getStatusCode() == 200 && response.getBody() != null) {

                    Map < String, Object > jsonMap = ( Map < String, Object > )JSON.deserializeUntyped( response.getBody() );     
                    
                    String strTempSessionId = ( String ) jsonMap.get( 'sessionId' ); 

                    if ( strTempSessionId != null ) {

                        resultMap.put( 'SessionId', strTempSessionId );
                        strSessionId = strTempSessionId;
                        
                    } else {

                        resultMap.put( 'Error', 'Session Id is null' );

                    }

                } else {

                    System.debug( 'Error: ' + response.getStatusCode() + ' - ' + response.getBody() );
                    resultMap.put( 'Error', response.getBody() );

                }

            } catch ( System.CalloutException e ) {

                System.debug('HTTP Request failed: ' + e.getMessage());
                resultMap.put( 'Error', e.getMessage() );

            }

        }

        if ( !resultMap.containsKey( 'Error' ) ) {
            
            try {
                
                response = sendSynchronousMessage( strToken, strSessionId, strMessage );
                
                if ( response.getStatusCode() == 200 && response.getBody() != null) {
					
                    String strReceievedMessage;
                    
                    Map < String, Object > jsonMap = ( Map < String, Object > )JSON.deserializeUntyped( response.getBody() );   
                
                    List < Object > messages = ( List < Object > ) jsonMap.get( 'messages' );
                    
                    for ( Object objMessage : messages ) {
                        
                        Map < String, Object > messageMap = ( Map < String, Object > ) objMessage;
                        System.debug( 'Message: ' + messageMap.get( 'message' ) );
                        strReceievedMessage = ( String ) messageMap.get( 'message' );
                        
                    }

                    resultMap.put( 'Message', strReceievedMessage );

                } else {

                    System.debug( 'Error: ' + response.getStatusCode() + ' - ' + response.getBody() );
                    resultMap.put( 'Error', response.getBody() );

                }
                
            } catch ( System.CalloutException e ) {

                System.debug('HTTP Request failed: ' + e.getMessage());
                resultMap.put( 'Error', e.getMessage() );

            }
                
        }

        return resultMap;

    }

    /*
    * This method gets the access token from Salesforce.
    * @return HTTPResponse - Response
    */
    public static HTTPResponse accessToken() {

        HTTPResponse response;
        HTTP http = new HTTP();
        HTTPRequest request = new HTTPRequest();
        request.setEndpoint( objConfig.My_Domain_URL__c + '/services/oauth2/token');
        request.setMethod( 'POST' );
        request.setHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
        
        String strRequestBody = 'grant_type=client_credentials' + 
            '&client_id=' + EncodingUtil.urlEncode( objConfig.Consumer_Key__c,'UTF-8' ) +
            '&client_secret=' + EncodingUtil.urlEncode( objConfig.Consumer_Secret__c, 'UTF-8' );
        request.setBody( strRequestBody );

        response = http.send( request );

        System.debug( 'Response: ' + response.getBody() );
        return response;

    }

    /*
    * This method creates a session in Salesforce.
    * @param strAccessToken - Access Token
    * @return HTTPResponse - Response
    */
    public static HTTPResponse sessionRequest( String strAccessToken ) {

        HTTP http = new HTTP();
        HTTPResponse response; 
        HTTPRequest request = new HTTPRequest();
        request.setEndpoint( objConfig.API_Instance_URL__c + '/einstein/ai-agent/v1/agents/0XxKj000000cVuaKAE/sessions' );
        request.setMethod( 'POST' );
        request.setHeader( 'Content-Type', 'application/json' );
        request.setHeader( 'Authorization', 'Bearer ' + strAccessToken );

        UUID randomUUID = UUID.randomUUID();
        SessionRequestPayload sessionPayload = new SessionRequestPayload( randomUUID.toString(), objConfig.My_Domain_URL__c );
        String strRequestBody = JSON.serialize( sessionPayload );
        System.debug( 'Request Body: ' + strRequestBody );
        request.setBody( strRequestBody );

        response = http.send( request );

        System.debug( 'Response: ' + response.getBody() );
        return response;

    }
    
    /*
    * This method sends a message to the Agentforce Agent.
    * @param strAccessToken - Access Token
    * @param strSessionId - Session Id
    * @param strMessage - Message
    * @return HTTPResponse - Response
    */
    public static HTTPResponse sendSynchronousMessage( String strAccessToken, String strSessionId, String strMessage ) {

        HTTP http = new HTTP();
        HTTPResponse response; 
        HTTPRequest request = new HTTPRequest();
        request.setMethod( 'POST' );
        request.setTimeout( 60000 );
        request.setHeader( 'Accept', 'application/json' );
        request.setHeader( 'Content-Type', 'application/json' );
        request.setHeader( 'Authorization', 'Bearer ' + strAccessToken );
        request.setEndpoint( objConfig.API_Instance_URL__c + '/einstein/ai-agent/v1/sessions/' + strSessionId + '/messages' );

        UUID randomUUID = UUID.randomUUID();
        SendMessageRequestPayload sendMsgPayload = new SendMessageRequestPayload( strMessage );
        String strRequestBody = JSON.serialize( sendMsgPayload );
        request.setBody( strRequestBody );

        response = http.send( request );

        System.debug( 'Response: ' + response.getBody() );
        return response;

    }

    /*
    * This method ends the Agentforce Agent session in Salesforce.
    * @param strAccessToken - Access Token
    * @param strSessionId - Session Id
    * @return Map < String, Object > - Result Map
    */
    @AuraEnabled
    public static Map < String, Object > endSession( String strAccessToken, String strSessionId ) {

        Map < String, Object > resultMap = new Map < String, Object >(); 

        try {

            HTTP http = new HTTP();
            HTTPResponse response; 
            HTTPRequest request = new HTTPRequest();
            request.setTimeout( 60000 );
            request.setMethod( 'DELETE' );
            request.setHeader( 'x-session-end-reason', 'UserRequest' );
            request.setHeader( 'Authorization', 'Bearer ' + strAccessToken );
            request.setEndpoint( objConfig.API_Instance_URL__c + '/einstein/ai-agent/v1/sessions/' + strSessionId );

            response = http.send( request );
            System.debug( 'Response: ' + response.getBody() );

            if ( response.getStatusCode() == 200 && response.getBody() != null) {

                Map < String, Object > jsonMap = ( Map < String, Object > )JSON.deserializeUntyped( response.getBody() );   
                
                List < Object > messages = ( List < Object > ) jsonMap.get( 'messages' );
                
                for ( Object objMessage : messages ) {
                    
                    Map < String, Object > messageMap = ( Map < String, Object > ) objMessage;
                    System.debug( 'Type: ' + messageMap.get( 'type' ) );
                    System.debug( 'Reason: ' + messageMap.get( 'reason' ) );
                    resultMap.put( 'Message', ( String ) messageMap.get( 'type' ) + ' - ' + ( String ) messageMap.get( 'reason' ));
                    
                }

            } else {

                System.debug( 'Error: ' + response.getStatusCode() + ' - ' + response.getBody() );
                resultMap.put( 'Error', response.getBody() );

            }

        } catch ( System.CalloutException e ) {

            System.debug('HTTP Request failed: ' + e.getMessage());
            resultMap.put( 'Error', e.getMessage() );

        }

        return resultMap;

    }

    public class SessionRequestPayload {

        public String externalSessionKey;
        public InstanceConfig instanceConfig;
        public StreamingCapabilities streamingCapabilities;
        
        public SessionRequestPayload( String sessionKey, String orgURL ) {
            this.externalSessionKey = sessionKey;
            this.instanceConfig = new InstanceConfig(orgURL);
            this.streamingCapabilities = new StreamingCapabilities();
        }
    }
    
    public class InstanceConfig {
        
        public String endpoint;
        
        public InstanceConfig( String orgURL ) {
            
            this.endpoint = orgURL;
            
        }
        
    }
    
    public class StreamingCapabilities {

        public List<String> chunkTypes = new List<String>();
        
        public StreamingCapabilities() {
            this.chunkTypes.add('Text');
        }

    }
    
    public class SendMessageRequestPayload {
        
        public List < String > variables;
        public Message message;
        
        public SendMessageRequestPayload( String msg ) {
            
            this.variables = new List < String >();
            this.message = new Message(msg);
            
        }
        
    }
    
    public class Message {
        
        public String text;
        public String type = 'Text';
        public String sequenceId = DateTime.now().getTime().toString();
        
        public Message( String msg ) {
            
            this.text = msg;
            
        }
        
    }

}

Sample Lightning Web Component:

HTML:

<template>
    <div class="slds-align_absolute-center">
        <div style="width: 450px;">
            <lightning-card title="Chat with Agentforce Agent" icon-name="standard:live_chat" variant="narrow">
                <lightning-button-icon
                    slot="actions"
                    disabled={downloadDisabled} 
                    icon-name="utility:download"
                    alternative-text="Download Conversation"
                    onclick={downloadConversation}>
                </lightning-button-icon>
                <!-- Displaying spinner when sending and receiving messages -->
                <template lwc:if={showSpinner}>
                    <lightning-spinner 
                        alternative-text="Loading" 
                        size="large">
                    </lightning-spinner>
                </template>
                <div class="slds-p-around_medium">
                    <div
                        class="scrollableArea" 
                        style="height: 300px; overflow-x: hidden; overflow-y: auto;">
                        <lightning-formatted-text 
                            value={consolidatedMessage}>
                        </lightning-formatted-text>
                    </div>
                    <lightning-textarea 
                        type="text" 
                        label="Message" 
                        value={strMessage}
                        onchange={handleMessageChange}>
                    </lightning-textarea>
                </div>
                <!-- Displaying Buttons in the Footer -->
                <div slot="footer">
                    <lightning-button 
                        title="Chat"
                        label="Chat" 
                        variant="brand"
                        onclick={handleChat}
                        disabled={chatDisabled}
                        icon-name="utility:chat"
                        class="slds-m-left_x-small">
                    </lightning-button>
                    <lightning-button 
                        label="End Session" 
                        title="End Session" 
                        variant="destructive" 
                        icon-name="utility:close"
                        onclick={handleEndSession} 
                        disabled={endSessionDisabled}
                        class="slds-m-left_x-small">
                    </lightning-button>
                </div>
            </lightning-card>
        </div>
    </div>
</template>

JavaScript:

import { LightningElement } from 'lwc';
import endSession from '@salesforce/apex/ChatClientController.endSession';
import sendMessage from '@salesforce/apex/ChatClientController.sendMessage';

export default class ChatClient extends LightningElement {

    strToken;
    strMessage;
    strSessionId;
    strLastTokenIssued;
    consolidatedMessage;
    showSpinner = false;
    chatDisabled = true;
    downloadDisabled = true;
    endSessionDisabled = true;

    /*
    * This method is called when Chat button is clicked
    */
    handleChat() {

        this.showSpinner = true;
        this.chatDisabled = true;
        console.log( 'Inside  handleChat' );
        console.log( 
            'strMessage: ' + this.strMessage 
        );

        if ( this.consolidatedMessage ) {

            this.consolidatedMessage += '\nYou: ' + this.strMessage;

        } else {

            this.consolidatedMessage = 'You: ' + this.strMessage;    

        }

        this.consolidatedMessage += '\n' + new Date( Date.now() ).toUTCString() + '\n';
    
        // Invoking the Apex method to send the message to the Agentforce Agent
        sendMessage( { 
            strToken : this.strToken, 
            strLastTokenIssued : this.strLastTokenIssued, 
            strSessionId : this.strSessionId, 
            strMessage : this.strMessage 

        } )    
        .then( result => {  
            
            console.log( 'result is', JSON.stringify( result ) );
            this.consolidatedMessage += '\nAgent: ';

            if ( result.Message ) {
                
                this.consolidatedMessage += result.Message;
                
            } else if ( result.Error ) {
                
                this.consolidatedMessage += result.Error;
                
            }

            this.consolidatedMessage += '\n' + new Date( Date.now() ).toUTCString() + '\n';

            if ( result.LastTokenIssued ) {

                this.strLastTokenIssued = result.LastTokenIssued;

            }
            
            if ( result.AccessToken ) {

                this.strToken = result.AccessToken;

            }

            if ( result.SessionId ) {

                this.strSessionId = result.SessionId;
                this.endSessionDisabled = false;

            }

            this.template.querySelector( '.scrollableArea' ).scrollTop = this.template.querySelector( '.scrollableArea' ).scrollHeight;

            this.showSpinner = false;

        } )  
        .catch( error => {  
            
            this.showSpinner = false;
            console.log( 'Error Occured', JSON.stringify( error ) );

        } );  

        this.strMessage = '';

    }


    /*
    * This method is called when the message is changed in the textarea
    */
    handleMessageChange( event ) {

        this.strMessage = event.detail.value;

        if ( this.strMessage && this.strMessage.length > 0 ) {

            this.chatDisabled = false;

        } else {

            this.chatDisabled = true;

        }

    }

    /*
    * This method is called when End Session button is clicked
    */
    handleEndSession() {

        this.showSpinner = true;
        console.log( 'In End Session' );
        this.downloadDisabled = false;

        // Invoking the Apex method to end the Agentforce Agent session
        endSession( { strAccessToken : this.strToken, strSessionId : this.strSessionId } )
        .then( result => {  
            
            console.log( 'result is', JSON.stringify( result ) );
            console.log( 'result.Message is', result.Message );
            this.consolidatedMessage += '\nAgent: ';

            if ( result.Message ) {
                
                this.consolidatedMessage += result.Message;
                
            } else if ( result.Error ) {
                
                this.consolidatedMessage += result.Error;
                
            }

            this.consolidatedMessage += '\n' + new Date( Date.now() ).toUTCString() + '\n';
            this.strSessionId = '';
            this.downloadDisabled = false;
            this.endSessionDisabled = true;
            this.showSpinner = false;

        } )
        .catch( error => {  
            
            this.showSpinner = false;
            console.log( 'Error Occured', JSON.stringify( error ) );

        } );

    }

    /*
    * This method is called when Download Conversation button is clicked
    */
    downloadConversation() {

        console.log( 'Download Start' );
        this.downloadString( this.consolidatedMessage, "text/plain", "Conversation.txt" );
        console.log( 'Download End' );

    }

    /*
    * This method is called to download the conversation
    */
    downloadString( text, fileType, fileName ) {
        
        let objBlob = new Blob( [ text ], { type: fileType } );
        let objAnchor = document.createElement( 'a' );
        objAnchor.download = fileName;
        objAnchor.href = URL.createObjectURL( objBlob );
        objAnchor.dataset.downloadurl = [ 
            fileType, 
            objAnchor.download, 
            objAnchor.href 
        ].join( ':' );
        objAnchor.style.display = "none";
        objAnchor.click();
        setTimeout( 
            function() { 
                URL.revokeObjectURL( objAnchor.href ); 
            }, 1000 
        );

    }

}

js-meta.xml:

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>62.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__Tab</target>
    </targets>
</LightningComponentBundle>

Output:

When the Session is ended using the “End Session” button, the download icon will be enabled. You can download the conversation transcript.

Leave a Reply