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.

Use the pre:send event to intercept and modify user messages before they are sent to the agent. This capability enables powerful use cases such as message translation, input sanitization, content transformation, and adding contextual metadata.

Understanding the message content system

When working with user input in the pre:send event, two properties control different aspects of the message:

message.content

The message.content property contains the text that is sent to the agent for LLM processing. When you modify this property:
  • The agent receives the modified text for generating responses.
  • The modification affects what the LLM processes and how it responds.
  • The original user input is preserved in the chat UI by default.

message_state.input.display_text

The message_state.input.display_text property contains the text that is displayed in the chat UI. When present:
  • This property takes priority over message.content for display purposes.
  • The user sees this text in their chat.
  • This allows you to show different text than what the agent receives.

How the system works

Default behavior

By default, when a user types a message, the system uses the same text for both LLM processing and UI display:
// User types: "Hello, how are you?"
// System automatically sets:
{
  message: {
    content: "Hello, how are you?",
    message_state: {
      input: {
        display_text: undefined  // Not set, so content is used for display
      }
    }
  }
}

Modified behavior

When you modify message.content in the pre:send event handler, the system maintains separation between processing and display:
instance.on('pre:send', (event, instance) => {
    // Modify content for agent
    event.message.message.content = "HELLO, HOW ARE YOU?";
    
    // display_text isn't set, so UI shows original: "Hello, how are you?"
});

Synchronized behavior

To synchronize what the agent receives with what the user sees, update both properties:
instance.on('pre:send', (event, instance) => {
    const modified = "HELLO, HOW ARE YOU?";
    
    // Update for agent processing
    event.message.message.content = modified;
    
    // Update for UI display
    event.message.message.message_state = {
        input: { display_text: modified }
    };
    
    // Now both agent and user see: "HELLO, HOW ARE YOU?"
});

Common use cases

Use case 1: Message translation

In multilingual applications, you might need to translate user messages to a common language that your agent understands while preserving the original language in the chat interface. For example, if a Spanish-speaking user types “Hola, ¿cómo estás?” but your agent only processes English, you can translate the message for the agent while showing the user their original Spanish text. This pattern works by updating only message.content with the translated text for agent processing, while setting message_state.input.display_text to preserve the original language in the UI.
instance.on('pre:send', async (event, instance) => {
    const userLanguage = 'es';
    
    if (userLanguage !== 'en' && event?.message?.message?.content) {
        // Store the original text
        const originalText = event.message.message.content;
        
        // Translate to English for agent processing
        const translatedText = await translateToEnglish(originalText);
        
        // Agent receives English translation
        event.message.message.content = translatedText;
        
        // User sees their original Spanish input
        // Note: display_text is set to preserve the original language in UI
        event.message.message.message_state = {
            input: { display_text: originalText }
        };
    }
});
In this example, the agent receives “Hello, how are you?” for processing, while the user sees their original message “Hola, ¿cómo estás?” in the chat interface. This approach enhances agent understanding without changing the user experience.

Use case 2: Input sanitization

Protecting sensitive information is critical in chat applications. You can automatically detect and remove sensitive data like credit card numbers, social security numbers, or passwords before sending messages to the agent, while still showing users what they typed. This maintains transparency with users while protecting their data. For instance, if a user accidentally includes a credit card number in their message, you can mask it before the agent processes it. Update only message.content with the sanitized version while preserving the original input in message_state.input.display_text.
instance.on('pre:send', (event, instance) => {
    const userInput = event.message.message.content;
    
    // Detect and mask credit card numbers
    const creditCardPattern = /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g;
    const sanitizedContent = userInput.replace(creditCardPattern, '[CARD NUMBER REDACTED]');
    
    // Agent receives sanitized version
    event.message.message.content = sanitizedContent;
    
    // User sees their original input
    // Note: Preserving original input maintains transparency with the user
    event.message.message.message_state = {
        input: { display_text: userInput }
    };
});
The agent receives “I want to pay with [CARD NUMBER REDACTED]” while the user sees their original message “I want to pay with 1234 5678 9012 3456” in the chat. Always preserve the original user input in the UI when sanitizing data to maintain transparency and allow users to see exactly what they typed.

Use case 3: Content transformation

Sometimes you need to transform user input into a standardized format that your agent can process more effectively. This is useful when you want to normalize casual language into formal system messages or convert user queries into structured commands. Unlike the previous patterns, this approach updates both message.content and message_state.input.display_text so that users see the transformed version of their input. For example, you might convert casual greetings like “hello” or “hi” into formal system messages such as “User initiated greeting protocol.” This provides clear feedback to users about how their input is being interpreted.
instance.on('pre:send', (event, instance) => {
    const userInput = event.message.message.content;
    let processedContent;
    
    // Transform casual greetings
    if (userInput.toLowerCase().includes('hello') ||
        userInput.toLowerCase().includes('hi')) {
        processedContent = "User initiated greeting protocol";
    } else if (userInput.toLowerCase().includes('bye')) {
        processedContent = "User initiated farewell protocol";
    } else {
        processedContent = `User query: ${userInput}`;
    }
    
    // Both agent and user see the transformed version
    event.message.message.content = processedContent;
    event.message.message.message_state = {
        input: { display_text: processedContent }
    };
});
Both the agent and user see “User initiated greeting protocol” in this case. Use this pattern when you want to provide feedback about input transformation or replace user input with a standardized message format.

Use case 4: Adding context

Agents often provide better responses when they have additional context about the user and their session. You can enrich messages with metadata like user IDs, session duration, authentication status, or previous interaction history without cluttering the user interface. This keeps the chat clean and focused for users while giving the agent the information it needs to provide personalized, context-aware responses. For example, you might include user metadata and session information in the message sent to the agent. Update only message.content with the enriched information while keeping message_state.input.display_text set to the original user message.
instance.on('pre:send', (event, instance) => {
    const userInput = event.message.message.content;
    const userId = getCurrentUserId();
    const sessionTime = getSessionDuration();
    
    // Add context for agent
    const enrichedContent = `[User: ${userId}, Session: ${sessionTime}min] ${userInput}`;
    event.message.message.content = enrichedContent;
    
    // User sees only their original message
    // Note: Not setting display_text keeps the UI clean and focused
    event.message.message.message_state = {
        input: { display_text: userInput }
    };
    
    // Add metadata for additional tracking
    event.message.metadata = {
        userId: userId,
        sessionDuration: sessionTime,
        timestamp: Date.now()
    };
});
The agent receives “[User: user123, Session: 5min] What are your hours?” with all the contextual information, while the user simply sees “What are your hours?” in their chat interface. This approach keeps the user interface clean by hiding technical metadata from users while providing rich context to the agent for better responses.

Message flow diagram

Understanding the complete message flow helps you determine where to make modifications: Legend:
  • Blue nodes: User actions, event triggers, and modifiable properties
  • Gray nodes: Decision points in your code
  • Purple nodes: Agent processing and UI display logic

Preserving existing message state

When modifying message_state, use the spread operator to preserve existing properties and avoid errors. You can implement this practice to have the following implementation:
  • The message_state object might already contain properties set by the chat framework or other event handlers. If you directly assign a new object without spreading the existing properties, you lose these values, which can break functionality that depends on them. The spread operator (...) preserves all existing properties while adding or updating only the ones you specify.
  • JavaScript objects can be sealed or frozen to prevent modifications. When you try to directly assign a new object to a sealed property, JavaScript throws an error. By using the spread operator to merge properties instead of replacing the entire object, you work within the constraints of the object’s extensibility settings and avoid runtime errors.
  • The chat platform evolves over time, and future updates might introduce new properties to message_state that your code doesn’t know about yet. By preserving existing properties with the spread operator, your code remains compatible with both current and future versions of the platform, reducing the risk of breaking changes when you upgrade.
The following example shows how to implement this pattern:
instance.on('pre:send', (event, instance) => {
    // Safely merge new properties with existing state
    event.message.message.message_state = {
        ...event.message.message.message_state,
        input: {
            ...(event.message.message.message_state?.input || {}),
            display_text: "Your text here"
        }
    };
});
Apply this pattern in all use cases where you modify message_state to ensure robust and future-proof code.