Creating Custom Channels
Channels in Stentor are platform abstractions that translate external platform requests and responses into Stentor's internal format. This guide explains how to create custom channels to integrate new platforms.
Overviewβ
Each channel bridges communication between external platforms (like Alexa, Google Assistant, or custom platforms) and Stentor's core runtime. A channel is responsible for:
- Detecting if an incoming request belongs to its platform
- Translating platform-specific requests to Stentor's Request format
- Converting Stentor's Response back to the platform's format
- Reporting device capabilities
Channel Interfaceβ
A channel implements the Channel interface with the following required properties:
Required Propertiesβ
name(string) - Alphanumeric identifier for the channel (e.g., "actions-on-google", "alexa")test(body: object): boolean- Function to detect if incoming request is for this channelrequest- A Translator instance that converts incoming requests to Stentor's Request formatresponse- A Translator instance that converts Stentor's Response to the platform's formatcapabilities(body: object): Device- Function returning device capabilities from the request
Optional Propertiesβ
nlu?: NLUService- Optional NLU service for the channelhooks?: ChannelHooks- Optional runtime hooks (preExecution, postRequestTranslation, preResponseTranslation)handlerHook?- Optional Lambda event interceptor for special handlingbuilder?(deprecated) - Deprecated response builder pattern
Implementation Stepsβ
1. Create Request Translatorβ
Create a request translator that extends the Translator pattern:
import { Translator } from "@xapp/patterns";
import { Request } from "stentor-models";
class MyRequestTranslator extends Translator<object, Request> {
translate(body: object): Request {
// Convert platform request to Stentor Request format
return {
type: "INTENT_REQUEST",
intentId: extractIntentId(body),
sessionId: extractSessionId(body),
userId: extractUserId(body),
// ... other Request properties
};
}
}
2. Create Response Translatorβ
Create a response translator for converting Stentor responses:
import { Translator } from "@xapp/patterns";
import { Response } from "stentor-models";
class MyResponseTranslator extends Translator<Response, object> {
translate(response: Response): object {
// Convert Stentor Response to platform format
return {
// Platform-specific response structure
speech: response.outputSpeech?.ssml,
displayText: response.outputSpeech?.displayText,
// ... other platform properties
};
}
}
3. Implement Capabilities Functionβ
The capabilities function extracts device information from the request:
function getCapabilities(body: object): Device {
return {
canSpeak: true,
canPlayAudio: checkAudioSupport(body),
hasScreen: checkScreenSupport(body),
hasWebBrowser: false,
channel: "my-platform",
// ... other device capabilities
};
}
4. Create Channel Objectβ
Combine all components into a Channel object:
import { Channel } from "stentor-models";
export function MyCustomChannel(): Channel {
return {
name: "my-platform",
test(body): boolean {
// Detect if request is for this channel
// Check for platform-specific markers
return body?.platform === "my-platform" ||
body?.version?.startsWith("my-platform-");
},
request: new MyRequestTranslator(),
response: new MyResponseTranslator(),
capabilities(body): Device {
return {
canSpeak: true,
canPlayAudio: true,
hasScreen: body?.device?.screenAvailable || false,
channel: "my-platform"
};
}
};
}
5. Register Channelβ
Register the channel with your Assistant instance:
import { Assistant } from "stentor";
import { MyCustomChannel } from "./MyCustomChannel";
const assistant = new Assistant()
.withChannels([
MyCustomChannel()
])
.withHandlers([
// Your handlers
]);
Example: Test Channelβ
Here's a simplified example from the Stentor test suite:
export function TestChannel(): Channel {
return {
name: "test-channel",
test(body): boolean {
return body?.channel === "test-channel";
},
request: new TestRequestTranslator(),
response: new TestResponseTranslator(),
capabilities(body): Device {
return {
canSpeak: true,
canPlayAudio: false,
hasScreen: false,
channel: "test-channel"
};
}
};
}
Channel Selectionβ
At runtime, Stentor:
- Receives an incoming request
- Tests each registered channel using its
test()function - Selects the first channel that returns
true - Uses that channel's translators for the entire request/response cycle
Advanced Featuresβ
Channel Hooksβ
Channels can define hooks to intercept and modify behavior:
{
hooks: {
preExecution: async (request, context) => {
// Run before handler execution
console.log("Pre-execution hook");
},
postRequestTranslation: async (request, body, context) => {
// Run after request translation
return request; // Return modified request
},
preResponseTranslation: async (response, context) => {
// Run before response translation
return response; // Return modified response
}
}
}
NLU Integrationβ
Channels can provide their own NLU service:
{
nlu: {
query: async (text: string) => {
// Call your NLU service
const result = await myNLU.query(text);
return {
intentId: result.intent,
slots: result.entities
};
}
}
}
Best Practicesβ
- Unique Identifiers: Use descriptive, unique channel names
- Robust Detection: Make
test()specific enough to avoid false positives - Complete Translation: Ensure all Request/Response properties are mapped
- Error Handling: Handle malformed requests gracefully
- Device Capabilities: Accurately report what the device can do
- Testing: Write comprehensive tests for your translators
Reference Filesβ
View the source code on GitHub:
- Channel Interface:
packages/stentor-models/src/Channel/Channel.ts - Test Channel Example:
packages/stentor/src/__test__/TestChannel.ts - Base Channel Implementation:
packages/stentor-channel/src/Channel.ts
Next Stepsβ
- Learn about Handlers to process requests
- Explore the Getting Started guide