Skip to main content

Documentation Index

Fetch the complete documentation index at: https://developer.watson-orchestrate.ibm.com/llms.txt

Use this file to discover all available pages before exploring further.

Triggered before a conversation thread is loaded from history. Use this event to inspect or modify the messages before they are displayed in the chat UI.

Event properties

type
string
required
Always 'pre:threadLoaded'.
messages
array
required
Array of messages to load into the chat.
messages[].messageId
string
Unique identifier for the message.
messages[].sender
string
Sender type: 'user' or 'response'.
messages[].content
array
Array of content items in the message.
messages[].content[].text
string
Text content of the message item. If you need to modify the displayed text only, update this property.
messages[].content[].response_type
string
Type of the content item, for example, 'text'.
messages[].message_state
object
State information for the message. You can create a new object with the spread operator before adding nested custom properties.
messages[].message_state.user_defined
object
A dedicated namespace for your custom metadata. Use this namespace to:
  • Prevent conflicts with system properties.
  • Keep your custom data organized.
  • Ensure future platform updates do not break your code.
To use this namespace, create a new message_state object and a new user_defined object inside it. Add your custom properties inside user_defined.
If message_state or user_defined was previously sealed or made non-extensible, directly assigning a new nested property can throw an Object is not extensible error in strict mode. To make your code more robust and preserve existing properties, use the spread operator to create a fresh object reference before adding custom fields:
message.message_state = {
  ...message.message_state,
  user_defined: {
    ...(message.message_state?.user_defined || {}),
    my_custom_flag: true
  }
};
messageIdsForUpdate
array
Array of message IDs to persist to the backend database.When you modify historical messages in pre:threadLoaded, those changes only affect what displays in the UI. To save your modifications to the backend, add the messageId of each modified message to this array. This triggers automatic PATCH API calls that persist your changes.
The system does not automatically set tracking properties. If you need to track whether modifications were applied, create a new message_state object and set the flag inside message_state.user_defined before adding the message ID to the array.

Examples

The following example demonstrates how to modify historical messages when loading a conversation thread, including adding feedback controls to agent responses and updating message text:
instance.on('pre:threadLoaded', (event, instance) => {
    // Log all messages being loaded from conversation history
    console.log('Loading thread with messages:', event.messages);
    
    // Add feedback controls to all agent responses in the conversation history
    // This allows users to provide feedback on past agent responses
    event?.messages.forEach((message) => {
        if (message?.sender === 'response') {
            // Get the first content item 
            const [lastItem] = message.content;
            lastItem.message_options = {
                feedback: {
                    is_on: true // Enable feedback controls for this message
                }
            };
            
            // Initialize feedback state with default values
            // This prepares the message to accept user feedback
            message.message_state = {
                content: {
                    1: {
                        feedback: {
                            text: "", // Empty feedback text initially
                            is_positive: true, // Default to positive feedback
                            selected_categories: [] // No categories selected yet
                        }
                    }
                }
            };
        }
    });
    
    // Modify the first message in the conversation
    // This could be used to add a welcome back message or context
    if (event.messages.length > 0) {
        event.messages[0].content[0].text = 'Previous conversation loaded';
    }
});
The following example demonstrates how to translate all historical messages when loading conversation history, with intelligent skipping of already-translated content:
instance.on('pre:threadLoaded', async (event, instance) => {
    // Define the user's preferred language
    const userLanguage = 'es'; // User's preferred language
    const messageIdsToUpdate = []; // Track which messages need to be persisted
    
    // Only process messages if the user's language is not English
    if (userLanguage !== 'en') {
        for (const message of event.messages) {
            // Skip user messages - only translate agent responses
            // User messages are already in the user's language
            if (message.sender === 'user') {
                continue;
            }
            
            // Skip if already translated to current language
            // This prevents unnecessary re-translation and API calls
            if (message.message_state?.user_defined?.translated_language === userLanguage) {
                console.log(`Skipping message ${message.messageId} - already translated`);
                continue;
            }
            
            // Skip English originals when user language is English
            // No translation needed for English content
            if (message.message_state?.user_defined?.translated_language === 'en') {
                console.log(`Skipping message ${message.messageId} - English original`);
                continue;
            }
            
            // Translate each text content item in the message
            // Messages can contain multiple content items (text, images, etc.)
            for (const content of (message.content || [])) {
                if (content.response_type === 'text' && content.text) {
                    // Translate from English to user's language
                    // Note: translateText() is a placeholder - implement your own translation service
                    const translatedText = await translateText(content.text, 'en', userLanguage);
                    content.text = translatedText;
                }
            }
            
            // Store custom metadata in user_defined namespace
            // Use the spread operator to preserve existing properties and avoid
            // issues with sealed or non-extensible objects in strict mode
            message.message_state = {
                ...message.message_state,
                user_defined: {
                    ...(message.message_state?.user_defined || {}),
                    translated_language: userLanguage
                }
            };
            
            // Add this message ID to the update list
            // This will trigger a PATCH API call to persist the translation
            messageIdsToUpdate.push(message.messageId);
        }
    }
    
    // Trigger automatic persistence for all modified messages
    // The system will make PATCH API calls for each message ID in this array
    event.messageIdsForUpdate = messageIdsToUpdate;
});
The following example demonstrates how to optimize translation performance by tracking the current language state of each message and only translating when necessary:
instance.on('pre:threadLoaded', async (event, instance) => {
    // User switches to French - could happen when user changes language preference
    const userLanguage = 'fr'; // User switches to French
    const messageIdsToUpdate = []; // Track messages that need persistence
    
    // Only process if user language is not English
    if (userLanguage !== 'en') {
        for (const message of event.messages) {
            // Skip user messages - they're already in the user's language
            if (message.sender === 'user') continue;
            
            // Get the current translation state of this message from user_defined namespace
            // This tells us what language the message is currently in
            const currentLanguage = message.message_state?.user_defined?.translated_language;
            
            // Skip if already in target language
            // This is the key optimization - avoid re-translating messages
            if (currentLanguage === userLanguage) {
                continue;
            }
            
            // Translate from any language to French
            // This handles cases where messages were previously translated to other languages
            for (const content of (message.content || [])) {
                if (content.response_type === 'text' && content.text) {
                    // Determine source language (default to English if never translated)
                    // This allows translation from Spanish->French, German->French, etc.
                    const sourceLanguage = currentLanguage || 'en';
                    content.text = await translateText(content.text, sourceLanguage, userLanguage);
                }
            }
            
            // Store the new translation language in user_defined namespace
            // This prevents re-translation on next load
            message.message_state = {
                ...message.message_state,
                user_defined: {
                    ...(message.message_state?.user_defined || {}),
                    translated_language: userLanguage
                }
            };
            messageIdsToUpdate.push(message.messageId);
        }
    }
    
    // Trigger automatic persistence for all modified messages
    event.messageIdsForUpdate = messageIdsToUpdate;
    
    // Log the number of messages that were translated for debugging
    console.log(`Translated ${messageIdsToUpdate.length} messages to ${userLanguage}`);
});

Considerations

Complement to pre:receive

Use both events together for consistent modifications and ensure that all messages are processed: To avoid duplicate processing, you can:
  1. Set a tracking flag in message_state.user_defined during pre:receive.
  2. Check for this flag in pre:threadLoaded.
  3. Process only messages without the flag.
Example tracking flag: message_state.user_defined.already_translated = true

Do you need practical examples?

Learn how to apply the features available for embedded chat into your implementation with guidance and examples.