Multi-turn conversation system with thread management, conditional logic, and state persistence. Supports complex dialog flows with branching, variable collection, and user input validation.
Creates a new conversation dialog with the specified ID and controller reference.
/**
* Create a new BotkitConversation object
* @param dialogId A unique identifier for this dialog, used to later trigger this dialog
* @param controller A pointer to the main Botkit controller
*/
constructor(dialogId: string, controller: Botkit);Usage Example:
import { BotkitConversation } from "botkit";
const convo = new BotkitConversation("greeting_flow", controller);Add messages to conversation threads that will be sent sequentially.
/**
* Add a non-interactive message to the default thread
* @param message Message template to be sent
*/
say(message: Partial<BotkitMessageTemplate> | string): BotkitConversation;
/**
* Add a message template to a specific thread
* @param message Message template to be sent
* @param thread_name Name of thread to which message will be added
*/
addMessage(message: Partial<BotkitMessageTemplate> | string, thread_name: string): BotkitConversation;
interface BotkitMessageTemplate {
text: ((template: any, vars: any) => string) | string[];
action?: string;
execute?: { script: string; thread?: string; };
quick_replies?: ((template: any, vars: any) => any[]) | any[];
attachments?: ((template: any, vars: any) => any[]) | any[];
blocks?: ((template: any, vars: any) => any[]) | any[];
attachment?: ((template: any, vars: any) => any) | any;
attachmentLayout?: string;
channelData?: any;
collect: { key?: string; options?: BotkitConvoTrigger[]; };
}Usage Examples:
// Simple text messages
const convo = new BotkitConversation("welcome", controller);
convo.say("Hello! Welcome to our service.");
convo.say("Let me show you around...");
// Rich message with attachments
convo.say({
text: "Here's some information:",
attachments: [{
title: "Getting Started",
text: "Follow these steps to begin.",
}]
});
// Message with quick replies
convo.say({
text: "Choose an option:",
quick_replies: [
{ title: "Option 1", payload: "opt1" },
{ title: "Option 2", payload: "opt2" }
]
});
// Add to specific thread
convo.addMessage("This is in a different thread", "help_thread");Ask questions and handle user responses with conditional logic.
/**
* Add a question to the default thread
* @param message a message that will be used as the prompt
* @param handlers one or more handler functions defining conditional actions
* @param key name of variable to store response in
*/
ask(
message: Partial<BotkitMessageTemplate> | string,
handlers: BotkitConvoHandler | BotkitConvoTrigger[],
key: {key: string} | string | null
): BotkitConversation;
/**
* Add a question to a specific thread
* @param message A message that will be used as the prompt
* @param handlers One or more handler functions defining conditional actions
* @param key Name of variable to store response in
* @param thread_name Name of thread to which message will be added
*/
addQuestion(
message: Partial<BotkitMessageTemplate> | string,
handlers: BotkitConvoHandler | BotkitConvoTrigger[],
key: {key: string} | string | null,
thread_name: string
): BotkitConversation;
interface BotkitConvoHandler {
(answer: string, convo: BotkitDialogWrapper, bot: BotWorker, message: BotkitMessage): Promise<any>;
}
interface BotkitConvoTrigger {
type?: string;
pattern?: string | RegExp;
handler: BotkitConvoHandler;
default?: boolean;
}Usage Examples:
// Simple question with single handler
convo.ask("What is your name?", async (response, convo, bot) => {
await bot.say(`Hello, ${response}!`);
}, { key: "name" });
// Question with conditional responses
convo.ask("Do you like pizza?", [
{
pattern: "yes",
type: "string",
handler: async (response, convo, bot) => {
await convo.gotoThread("pizza_lover");
}
},
{
pattern: "no",
type: "string",
handler: async (response, convo, bot) => {
await convo.gotoThread("pizza_hater");
}
},
{
default: true,
handler: async (response, convo, bot) => {
await bot.say("I don't understand. Please answer yes or no.");
await convo.repeat();
}
}
], { key: "likes_pizza" });
// Question with regex pattern
convo.ask("Enter your email:", [
{
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
type: "regex",
handler: async (response, convo, bot) => {
await bot.say("Thanks! Email saved.");
}
},
{
default: true,
handler: async (response, convo, bot) => {
await bot.say("Please enter a valid email address.");
await convo.repeat();
}
}
], { key: "email" });Control conversation flow with threads and actions.
/**
* Add an action to the conversation timeline
* @param action An action or thread name
* @param thread_name The name of the thread to which this action is added
*/
addAction(action: string, thread_name?: string): BotkitConversation;Usage Examples:
// Create multiple threads
convo.say("Welcome!");
convo.addAction("choose_path");
// Choose path thread
convo.addMessage("What would you like to do?", "choose_path");
convo.ask("Type 'help' or 'start':", [
{
pattern: "help",
handler: async (response, convo, bot) => {
await convo.gotoThread("help_thread");
}
},
{
pattern: "start",
handler: async (response, convo, bot) => {
await convo.gotoThread("start_thread");
}
}
], null, "choose_path");
// Help thread
convo.addMessage("Here's how to use this bot...", "help_thread");
convo.addAction("complete", "help_thread");
// Start thread
convo.addMessage("Let's get started!", "start_thread");
convo.addAction("complete", "start_thread");
// Actions available:
// - Thread names: Jump to that thread
// - 'complete': End dialog successfully
// - 'stop': End dialog as cancelled
// - 'repeat': Repeat last message
// - 'timeout': End dialog due to timeoutCombine multiple dialogs for complex conversation flows.
/**
* Call a child dialog, wait for completion, and store results
* @param dialog_id the id of another dialog
* @param key_name the variable name to store results (defaults to dialog_id)
* @param thread_name the name of thread to add this call to
*/
addChildDialog(dialog_id: string, key_name?: string, thread_name?: string): BotkitConversation;
/**
* Handoff to another dialog (parent will not resume)
* @param dialog_id the id of another dialog
* @param thread_name the name of thread to add this call to
*/
addGotoDialog(dialog_id: string, thread_name?: string): BotkitConversation;Usage Examples:
// Create profile collection dialog
const profileDialog = new BotkitConversation("profile_dialog", controller);
profileDialog.ask("What is your name?", async (res, convo, bot) => {}, { key: "name" });
profileDialog.ask("What is your age?", async (res, convo, bot) => {}, { key: "age" });
controller.addDialog(profileDialog);
// Main onboarding dialog that uses profile dialog
const onboardDialog = new BotkitConversation("onboard", controller);
onboardDialog.say("Welcome! Let's collect your profile.");
onboardDialog.addChildDialog("profile_dialog", "profile");
onboardDialog.say("Thanks {{vars.profile.name}}! Onboarding complete.");
// Handoff example
const emergencyDialog = new BotkitConversation("emergency", controller);
emergencyDialog.say("Transferring to emergency procedures...");
emergencyDialog.addGotoDialog("emergency_handler");Bind functions to conversation lifecycle events.
/**
* Register a handler function that will fire before a given thread begins
* @param thread_name A valid thread defined in this conversation
* @param handler A handler function
*/
before(thread_name: string, handler: (convo: BotkitDialogWrapper, bot: BotWorker) => Promise<any>): void;
/**
* Bind a function to run after the dialog has completed
* @param handler handler function with results and bot worker
*/
after(handler: (results: any, bot: BotWorker) => void): void;
/**
* Bind a function to run whenever a user answers a specific question
* @param variable name of the variable to watch for changes
* @param handler a handler function that will fire when variable changes
*/
onChange(variable: string, handler: (response, convo, bot) => Promise<any>): void;Usage Examples:
// Before thread handler
convo.before("payment_thread", async (convo, bot) => {
// Set up payment variables
convo.setVar("payment_method", "credit_card");
convo.setVar("currency", "USD");
});
// After dialog completion
convo.after(async (results, bot) => {
console.log("Dialog completed with results:", results);
// Save to database
await saveUserData({
name: results.name,
email: results.email,
preferences: results.preferences
});
// Send confirmation
await bot.say("Your information has been saved!");
});
// Variable change handler
convo.onChange("email", async (response, convo, bot) => {
// Validate email format
if (!response.includes("@")) {
await bot.say("That doesn't look like a valid email.");
await convo.repeat();
}
});The BotkitDialogWrapper provides conversation control methods within handlers.
class BotkitDialogWrapper {
/** Variables and user responses from this conversation */
vars: { [key: string]: any };
/** Jump immediately to the first message in a different thread */
gotoThread(thread: string): Promise<void>;
/** Repeat the last message sent on the next turn */
repeat(): Promise<void>;
/** Stop the dialog */
stop(): Promise<void>;
/** Set the value of a variable available to messages */
setVar(key: string, val: any): void;
}Usage Examples:
// Use dialog wrapper in handlers
convo.ask("Continue?", async (response, convo, bot) => {
if (response.toLowerCase() === "yes") {
convo.setVar("confirmed", true);
await convo.gotoThread("confirmed_thread");
} else {
await convo.stop();
}
}, { key: "continue" });
// Access variables
convo.say("Hello {{vars.name}}! You are {{vars.age}} years old.");
// Before handler using wrapper
convo.before("summary", async (convo, bot) => {
const userData = {
name: convo.vars.name,
email: convo.vars.email,
score: calculateScore(convo.vars)
};
convo.setVar("summary", userData);
});Add conversations to the bot and handle completion.
// Add dialog to controller (from bot-controller)
controller.addDialog(dialog: Dialog): void;
// Handle dialog completion (from bot-controller)
controller.afterDialog(dialog: Dialog | string, handler: BotkitHandler): void;Usage Examples:
// Create and add dialog
const surveyDialog = new BotkitConversation("customer_survey", controller);
// ... define dialog structure ...
controller.addDialog(surveyDialog);
// Handle completion
controller.afterDialog("customer_survey", async (bot, results) => {
await bot.say("Thanks for completing the survey!");
// Process results
await processSurveyResults(results);
});
// Start dialog from message handler
controller.hears("survey", "message", async (bot, message) => {
await bot.beginDialog("customer_survey");
});Use Mustache templating in messages to insert variables:
// Set variables and use in templates
convo.ask("What's your name?", async (response, convo, bot) => {
convo.setVar("user_name", response);
}, { key: "name" });
convo.say("Nice to meet you, {{vars.user_name}}!");
convo.say("Your name has {{vars.user_name.length}} characters.");Use functions to generate dynamic content:
convo.say({
text: (template, vars) => {
const hour = new Date().getHours();
const greeting = hour < 12 ? "Good morning" : "Good afternoon";
return `${greeting}, ${vars.name}!`;
}
});
convo.say({
quick_replies: (template, vars) => {
return vars.user_type === "admin" ?
[{ title: "Admin Panel", payload: "admin" }] :
[{ title: "User Dashboard", payload: "user" }];
}
});Handle conversation errors and edge cases:
// Timeout handling
convo.addAction("timeout", "main_thread");
// Error recovery
convo.ask("Enter a number:", [
{
pattern: /^\d+$/,
handler: async (response, convo, bot) => {
convo.setVar("number", parseInt(response));
}
},
{
default: true,
handler: async (response, convo, bot) => {
await bot.say("Please enter a valid number.");
await convo.repeat();
}
}
], { key: "number" });