Middleware System
The middleware system provides a plugin architecture for processing messages before and after the flow engine handles them.
Overview
Middlewares are functions that can:
- Modify incoming messages
- Add metadata or context
- Block or validate messages
- Log or track activity
- Transform responses
Creating Middleware
Basic Middleware
typescript
import type { MiddlewareFn, MiddlewareContext, NextFunction } from "@andresaya/flowkit";
const myMiddleware: MiddlewareFn = async (
context: MiddlewareContext,
next: NextFunction
) => {
// Before processing
console.log("Incoming:", context.message);
// Call next middleware or engine
await next();
// After processing
console.log("Response:", context.response);
};Middleware Context
typescript
interface MiddlewareContext {
conversationId: string; // Session ID
message: string; // User message
slots: Slots; // Current slot values
metadata: Record<string, JsonValue>; // Custom metadata
response?: string; // Engine response (after processing)
blocked?: boolean; // Set to true to block message
blockReason?: string; // Reason for blocking
}Using the MiddlewareManager
typescript
import { MiddlewareManager, FlowEngine } from "@andresaya/flowkit";
// Create manager and add middlewares
const middleware = new MiddlewareManager();
middleware.use(loggingMiddleware);
middleware.use(rateLimitMiddleware({ maxRequests: 10, windowMs: 60000 }));
middleware.use(validationMiddleware({ minLength: 1, maxLength: 1000 }));
// Wrap engine.handle with middleware
const engine = new FlowEngine(flow, { llm: adapter, storage });
async function handleMessage(sessionId: string, message: string) {
return middleware.run(
{ conversationId: sessionId, message, slots: {}, metadata: {} },
async (ctx) => {
const result = await engine.handle(sessionId, ctx.message);
ctx.response = result.message;
}
);
}Built-in Middlewares
Logging Middleware
Logs all incoming messages and responses.
typescript
import { loggingMiddleware } from "@andresaya/flowkit";
middleware.use(loggingMiddleware);Output:
[FlowKit] session-123 | Incoming: Hello there!
[FlowKit] session-123 | Response: Hi! How can I help you?Rate Limit Middleware
Prevent abuse by limiting requests per time window.
typescript
import { rateLimitMiddleware } from "@andresaya/flowkit";
middleware.use(rateLimitMiddleware({
maxRequests: 10, // Max requests per window
windowMs: 60000, // Window in ms (1 minute)
}));When limit exceeded:
context.blocked = truecontext.blockReason = "Rate limit exceeded"
Validation Middleware
Validate message length and content.
typescript
import { validationMiddleware } from "@andresaya/flowkit";
middleware.use(validationMiddleware({
minLength: 1, // Minimum message length
maxLength: 2000, // Maximum message length
}));When validation fails:
context.blocked = truecontext.blockReason = "Message too short"or"Message too long"
Profanity Filter Middleware
Block messages containing inappropriate content.
typescript
import { profanityFilterMiddleware } from "@andresaya/flowkit";
middleware.use(profanityFilterMiddleware({
words: ["badword1", "badword2"], // Words to block
replacement: "***", // Optional replacement
}));When profanity detected:
context.blocked = truecontext.blockReason = "Message contains inappropriate content"
Sentiment Middleware
Analyze message sentiment and add to metadata.
typescript
import { sentimentMiddleware } from "@andresaya/flowkit";
middleware.use(sentimentMiddleware());Adds to metadata:
typescript
context.metadata.sentiment = "positive" | "negative" | "neutral";You can then use this in your flow or for analytics.
Custom Middleware Examples
Authentication Middleware
typescript
const authMiddleware: MiddlewareFn = async (context, next) => {
const userId = context.metadata.userId as string;
if (!userId) {
context.blocked = true;
context.blockReason = "Authentication required";
return;
}
// Add user info to context
const user = await getUserById(userId);
context.metadata.user = user;
await next();
};Timing Middleware
typescript
const timingMiddleware: MiddlewareFn = async (context, next) => {
const start = Date.now();
await next();
const duration = Date.now() - start;
console.log(`Request took ${duration}ms`);
context.metadata.processingTime = duration;
};Language Detection Middleware
typescript
const languageMiddleware: MiddlewareFn = async (context, next) => {
// Simple detection (use a proper library in production)
const hasSpanish = /[¿¡ñáéíóú]/i.test(context.message);
context.metadata.language = hasSpanish ? "es" : "en";
await next();
};Conversation Context Middleware
typescript
const contextMiddleware: MiddlewareFn = async (context, next) => {
// Load previous context
const history = await loadConversationHistory(context.conversationId);
context.metadata.messageCount = history.length;
context.metadata.isReturningUser = history.length > 0;
await next();
// Save updated context
await saveMessage(context.conversationId, {
message: context.message,
response: context.response,
timestamp: Date.now(),
});
};Middleware Order
Middlewares execute in the order they're added:
typescript
middleware.use(timingMiddleware); // 1st - Start timer
middleware.use(loggingMiddleware); // 2nd - Log incoming
middleware.use(rateLimitMiddleware); // 3rd - Check rate limit
middleware.use(validationMiddleware); // 4th - Validate
// ... engine processes message ...
// Then unwinding:
// 4th - After validation
// 3rd - After rate limit
// 2nd - Log response
// 1st - End timerError Handling
Errors in middleware are propagated up the chain:
typescript
const safeMiddleware: MiddlewareFn = async (context, next) => {
try {
await next();
} catch (error) {
console.error("Middleware error:", error);
context.response = "Sorry, something went wrong.";
}
};
// Add at the beginning to catch all errors
middleware.use(safeMiddleware);Tips
- Keep middlewares focused - Each should do one thing well
- Order matters - Put validation/blocking before processing
- Don't block unnecessarily - Only block for real issues
- Use metadata - Pass data between middlewares via metadata
- Handle errors - Add error handling middleware at the start