Handler System
Handlers are the core building blocks for conversation logic in Stentor. Each handler is responsible for processing a specific intent and generating appropriate responses.
Overviewβ
The handler system provides a flexible, modular approach to managing conversation flow. Handlers:
- Process incoming user requests (intents)
- Generate appropriate responses
- Manage conversation state and context
- Control conversation flow through forwarding and redirects
- Handle edge cases like unknown input and repeats
Handler Typesβ
ConversationHandlerβ
The most basic handler for simple request/response interactions:
import { ConversationHandler } from "stentor-handler";
class MyHandler extends ConversationHandler {
// Most functionality is inherited from AbstractHandler
// Override methods as needed for custom behavior
}
Custom Handlersβ
For specialized logic, extend AbstractHandler:
import { AbstractHandler } from "stentor-handler";
class CustomHandler extends AbstractHandler {
async handleRequest(request: Request, context: Context): Promise<Response> {
// Custom request processing logic
const response = this.responseBuilder
.respond("Hello from custom handler!")
.build();
return response;
}
canHandleRequest(request: Request, context: Context): boolean {
// Custom logic to determine if this handler can process the request
return request.intentId === this.intentId;
}
}
Handler Structureβ
A handler is defined by a configuration object with the following properties:
interface Handler {
intentId: string; // Unique intent identifier
appId: string; // Application ID
organizationId: string; // Organization ID
name?: string; // Human-readable name
type: string; // Handler class type
content?: Content; // Response content for different intents
data?: Data; // Handler data/configuration
forward?: Forward; // Forwarding rules
redirect?: Redirect; // Redirect rules
permissions?: UserDataType[]; // Required user data permissions
}
Handler Contentβ
Handlers define responses in a content object keyed by intentId:
{
content: {
["LaunchRequest"]: [
{
outputSpeech: {
ssml: "<speak>Welcome to my app!</speak>",
displayText: "Welcome to my app!"
},
reprompt: {
ssml: "<speak>What would you like to do?</speak>",
displayText: "What would you like to do?"
}
}
]
}
}
Key Handler Methodsβ
handleRequest()β
Main entry point for processing requests:
async handleRequest(request: Request, context: Context): Promise<Response> {
// 1. Check redirects/gates
const redirectPath = this.redirectingPathForRequest(request, context);
if (redirectPath) {
// Handle redirect
}
// 2. Check forwarding
const forwardPath = this.forwardingPathForRequest(request, context);
if (forwardPath) {
// Forward to another handler
}
// 3. Process request and build response
const response = this.responseBuilder
.respond(this.content[request.intentId])
.build();
return response;
}
canHandleRequest()β
Determines if the handler can process a request:
canHandleRequest(request: Request, context: Context): boolean {
// Check if this handler's intentId matches the request
return request.intentId === this.intentId ||
this.content.hasOwnProperty(request.intentId);
}
forwardingPathForRequest()β
Finds forwarding path for a request:
forwardingPathForRequest(request: Request, context: Context): string | undefined {
// Check forward rules and return intentId to forward to
if (this.forward && this.forward.conditions) {
// Evaluate conditions
// Return target intentId if conditions met
}
return undefined;
}
redirectingPathForRequest()β
Finds redirect path (gate conditions):
redirectingPathForRequest(request: Request, context: Context): string | undefined {
// Check redirect gates
// Return intentId if gate condition fails
return undefined;
}
Handler Registrationβ
Handlers are registered with the Assistant using withHandlers():
import { Assistant } from "stentor";
import { MyHandler } from "./MyHandler";
const assistant = new Assistant()
.withHandlers([
MyHandler // Handler class
]);
// Or with key-value mapping (for obfuscated code)
assistant.withHandlers({
"MyHandler": MyHandler
});
Handler Lifecycleβ
- Request Received - Runtime receives request from platform
- Channel Translation - Channel translates to Stentor format
- Handler Selection - HandlerManager/Factory selects appropriate handler
- Request Processing - Handler's
handleRequest()processes the request - Response Building - Response is constructed using response builder
- Channel Translation - Response translated back to platform format
Advanced Featuresβ
Input Unknown Handlingβ
Handlers can define strategies for when user input can't be matched:
{
content: {
["InputUnknown"]: [
{
name: "InputUnknownGlobal",
outputSpeech: {
ssml: "<speak>I didn't understand that. Could you try again?</speak>"
}
}
]
}
}
Configuration strategies:
REPROMPT- Re-ask the previous questionGLOBAL- Use global input unknown response
Forwardingβ
Route to different handlers based on conditions:
{
forward: {
conditions: [
{
should: "gt",
value: 5,
path: "{{data.counter}}",
intentId: "ThresholdReachedIntent"
}
]
}
}
Redirectsβ
Gate logic that must pass before handling:
{
redirect: {
conditions: [
{
should: "eq",
value: undefined,
path: "{{context.user.email}}",
intentId: "RequestEmailIntent"
}
]
}
}
Session Managementβ
Track state across turns:
async handleRequest(request: Request, context: Context): Promise<Response> {
// Read from session
const counter = context.session.get("counter") || 0;
// Update session
context.session.set("counter", counter + 1);
// Session is automatically persisted between turns
}
Context Managementβ
Access user data and conversation context:
async handleRequest(request: Request, context: Context): Promise<Response> {
// Access user data
const userId = context.user.userId;
const email = context.user.email;
// Access device info
const canPlayAudio = context.device.canPlayAudio;
// Access storage
const userData = await context.storage.read(userId);
}
Repeat Functionalityβ
Re-play previous response on repeat intent:
class MyHandler extends ConversationHandler {
async handleRequest(request: Request, context: Context): Promise<Response> {
if (request.intentId === "AMAZON.RepeatIntent") {
// Return previous response from history
return this.getRepeatResponse(context);
}
// Normal handling
return super.handleRequest(request, context);
}
}
Response Buildingβ
Handlers use a response builder to construct responses:
this.responseBuilder
.respond("Hello!") // Add speech output
.reprompt("What would you like?") // Add reprompt
.withCard({ // Add visual card
title: "Welcome",
content: "Welcome to my app"
})
.withSuggestions(["Option 1", "Option 2"]) // Add quick replies
.build();
Handler Dataβ
Store handler-specific configuration in the data property:
{
data: {
maxAttempts: 3,
defaultGreeting: "Hello!",
customSettings: {
feature: "enabled"
}
}
}
Access in handler code:
const maxAttempts = this.data?.maxAttempts || 3;
Permissionsβ
Request user data permissions:
{
permissions: ["USER_NAME", "EMAIL_ADDRESS"]
}
This triggers permission request flows on supported platforms.
Best Practicesβ
- Single Responsibility: Each handler should manage one logical intent
- Content Configuration: Use
contentfor responses rather than hardcoding - Error Handling: Always handle error cases gracefully
- Context Awareness: Leverage context for personalization
- Testing: Write unit tests for your handlers
- Reusability: Extract common logic to utility functions
- Type Safety: Use TypeScript interfaces from stentor-models
Example Handlerβ
import { ConversationHandler } from "stentor-handler";
import { Request, Response, Context } from "stentor-models";
export class GreetingHandler extends ConversationHandler {
async handleRequest(request: Request, context: Context): Promise<Response> {
// Get user name from context
const userName = context.user?.name || "there";
// Build personalized response
const response = this.responseBuilder
.respond(`Hello ${userName}! Welcome back.`)
.reprompt("What can I help you with today?")
.withCard({
title: "Welcome",
content: `Hello ${userName}!`
})
.build();
// Track usage in session
context.session.set("lastGreeting", new Date().toISOString());
return response;
}
}
Reference Filesβ
View the source code on GitHub:
- AbstractHandler:
packages/stentor-handler/src/AbstractHandler/Handler.ts - ConversationHandler:
packages/stentor-handler/src/ConversationHandler.ts - Handler Interface:
packages/stentor-models/src/Handler/Handler.ts - Handler Tests:
packages/stentor-handler/src/__test__/ConversationHandler.test.ts
Next Stepsβ
- Learn about Custom Channels
- Explore the Getting Started guide