Skip to content

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 = true
  • context.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 = true
  • context.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 = true
  • context.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 timer

Error 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

  1. Keep middlewares focused - Each should do one thing well
  2. Order matters - Put validation/blocking before processing
  3. Don't block unnecessarily - Only block for real issues
  4. Use metadata - Pass data between middlewares via metadata
  5. Handle errors - Add error handling middleware at the start

Released under the MIT License.