# PreProcessor

The `PreProcessor` class allows you to filter, modify, or block user messages before they reach your AI agent. Use preprocessors for content filtering, message enrichment, spam detection, and access control.

## Import

```typescript
import { PreProcessor } from 'lua-cli';
```

## Overview

PreProcessors enable:
- Filter and block inappropriate content
- Modify messages before agent processing
- Add context and enrichment to messages
- Implement access control and permissions
- Detect spam and malicious content
- Route messages based on content
- Execute in sync or async mode
- Control execution order with priorities

## Class Definition

```typescript
class PreProcessor {
  constructor(config: PreProcessorConfig);
  getName(): string;
  getDescription(): string;
  getAsync(): boolean;
  getPriority(): number;
  execute(user: UserDataInstance, messages: ChatMessage[], channel: string): Promise<PreProcessorResult>;
}
```

## Configuration

```typescript
interface PreProcessorConfig {
  /** PreProcessor name (optional, defaults to 'unnamed-preprocessor') */
  name?: string;

  /** Short description of the preprocessor */
  description: string;

  /**
   * Async flag - indicates if processor should run in background:
   * - true: Run asynchronously (non-blocking, for slow operations)
   * - false: Run synchronously (blocking, for fast operations)
   * Default: false (synchronous)
   */
  async?: boolean;

  /**
   * Execution priority (lower runs first).
   * Default: 100
   */
  priority?: number;

  /**
   * Function that processes messages before sending to agent.
   */
  execute: (
    user: UserDataInstance,
    messages: ChatMessage[],
    channel: string
  ) => Promise<PreProcessorResult>;
}
```

## Result Types

### Block Response

Stop processing and send a response to the user:

```typescript
{
  action: 'block',
  response: string,        // Message to show to user
  metadata?: Record<string, any>  // Optional metadata
}
```

### Proceed Response

Continue to the next preprocessor or agent:

```typescript
{
  action: 'proceed',
  modifiedMessage?: ChatMessage[],  // Optional modified messages
  metadata?: Record<string, any>    // Optional metadata
}
```

## Message Types

```typescript
type ChatMessage =
  | { type: 'text'; text: string }
  | { type: 'image'; url: string }
  | { type: 'file'; url: string; mimeType?: string };
```

## Complete Examples

### Content Filter

```typescript
import { PreProcessor } from 'lua-cli';

export const contentFilter = new PreProcessor({
  name: 'content-filter',
  description: 'Filters inappropriate content',
  priority: 10,  // Run early

  execute: async (user, messages, channel) => {
    const blockedWords = ['spam', 'scam', 'phishing'];

    // Check text messages for blocked words
    for (const message of messages) {
      if (message.type === 'text') {
        const lowerText = message.text.toLowerCase();

        for (const word of blockedWords) {
          if (lowerText.includes(word)) {
            return {
              action: 'block',
              response: 'Your message contains inappropriate content and cannot be processed.',
              metadata: {
                reason: 'blocked_word',
                word
              }
            };
          }
        }
      }
    }

    return { action: 'proceed' };
  }
});
```

### User Verification

```typescript
import { PreProcessor } from 'lua-cli';

export const userVerification = new PreProcessor({
  name: 'user-verification',
  description: 'Verifies user has completed onboarding',
  priority: 5,  // Run very early

  execute: async (user, messages, channel) => {
    const userData = await user.get();

    // Check if user has completed onboarding
    if (!userData.data?.onboardingCompleted) {
      return {
        action: 'block',
        response: 'Please complete your profile setup before using the assistant. Type "/start" to begin.',
        metadata: {
          reason: 'onboarding_incomplete'
        }
      };
    }

    // Check if user is suspended
    if (userData.data?.status === 'suspended') {
      return {
        action: 'block',
        response: 'Your account has been suspended. Please contact support.',
        metadata: {
          reason: 'account_suspended'
        }
      };
    }

    return { action: 'proceed' };
  }
});
```

### Message Enrichment

```typescript
import { PreProcessor } from 'lua-cli';

export const messageEnrichment = new PreProcessor({
  name: 'message-enrichment',
  description: 'Adds user context to messages',
  priority: 50,

  execute: async (user, messages, channel) => {
    const userData = await user.get();

    // Add context message with user info
    const contextMessage = {
      type: 'text' as const,
      text: `[User Context: Name: ${userData.data?.name || 'Unknown'}, Plan: ${userData.data?.plan || 'free'}, Location: ${userData.data?.location || 'Unknown'}]`
    };

    // Prepend context to messages
    const enrichedMessages = [contextMessage, ...messages];

    return {
      action: 'proceed',
      modifiedMessage: enrichedMessages,
      metadata: {
        enriched: true,
        userPlan: userData.data?.plan
      }
    };
  }
});
```

### Rate Limiter

```typescript
import { PreProcessor, Data } from 'lua-cli';

export const rateLimiter = new PreProcessor({
  name: 'rate-limiter',
  description: 'Limits message rate per user',
  priority: 15,

  execute: async (user, messages, channel) => {
    const userId = user.id;
    const now = Date.now();
    const windowMs = 60 * 1000;  // 1 minute
    const maxMessages = 10;

    // Get user's recent messages
    const rateLimitKey = `rate_limit_${userId}`;
    const records = await Data.get('rate_limits');
    let userRecord = records.find(r => r.data.key === rateLimitKey);

    if (!userRecord) {
      // Create new record
      await Data.create('rate_limits', {
        key: rateLimitKey,
        messages: [now],
        windowStart: now
      });
      return { action: 'proceed' };
    }

    // Filter messages within window
    const recentMessages = userRecord.data.messages
      .filter((timestamp: number) => now - timestamp < windowMs);

    // Check if limit exceeded
    if (recentMessages.length >= maxMessages) {
      return {
        action: 'block',
        response: 'You are sending messages too quickly. Please wait a moment and try again.',
        metadata: {
          reason: 'rate_limit_exceeded',
          retryAfter: windowMs - (now - recentMessages[0])
        }
      };
    }

    // Update record
    await Data.update('rate_limits', userRecord.id, {
      ...userRecord.data,
      messages: [...recentMessages, now]
    });

    return { action: 'proceed' };
  }
});
```

### Spam Detector

```typescript
import { PreProcessor, AI } from 'lua-cli';

export const spamDetector = new PreProcessor({
  name: 'spam-detector',
  description: 'AI-powered spam detection',
  priority: 20,
  async: true,  // Can run in background

  execute: async (user, messages, channel) => {
    // Only check text messages
    const textMessages = messages.filter(m => m.type === 'text');

    if (textMessages.length === 0) {
      return { action: 'proceed' };
    }

    const messageText = textMessages.map(m => m.text).join(' ');

    // Use AI to detect spam
    const analysis = await AI.generate(
      'You are a spam detector. Analyze the message and respond with JSON: { "isSpam": true/false, "confidence": 0-1, "reason": "explanation" }',
      [{ type: 'text', text: messageText }]
    );

    try {
      const result = JSON.parse(analysis);

      if (result.isSpam && result.confidence > 0.8) {
        return {
          action: 'block',
          response: 'Your message appears to be spam and cannot be processed.',
          metadata: {
            reason: 'spam_detected',
            confidence: result.confidence,
            analysis: result.reason
          }
        };
      }
    } catch (error) {
      // If AI response is not valid JSON, proceed anyway
      console.error('Spam detection parsing error:', error);
    }

    return { action: 'proceed' };
  }
});
```

### Business Hours Checker

```typescript
import { PreProcessor } from 'lua-cli';

export const businessHoursChecker = new PreProcessor({
  name: 'business-hours',
  description: 'Checks if within business hours',
  priority: 30,

  execute: async (user, messages, channel) => {
    const now = new Date();
    const hour = now.getHours();
    const day = now.getDay();

    // Business hours: Monday-Friday, 9 AM - 5 PM
    const isWeekday = day >= 1 && day <= 5;
    const isBusinessHours = hour >= 9 && hour < 17;

    if (!isWeekday || !isBusinessHours) {
      // Add notice to message
      const noticeMessage = {
        type: 'text' as const,
        text: '[Note: You are contacting us outside business hours. Our team will respond when we return (Monday-Friday, 9 AM - 5 PM).]'
      };

      return {
        action: 'proceed',
        modifiedMessage: [noticeMessage, ...messages],
        metadata: {
          outsideBusinessHours: true,
          timestamp: now.toISOString()
        }
      };
    }

    return { action: 'proceed' };
  }
});
```

### Language Translator

```typescript
import { PreProcessor, AI } from 'lua-cli';

export const languageTranslator = new PreProcessor({
  name: 'language-translator',
  description: 'Translates non-English messages to English',
  priority: 40,
  async: true,

  execute: async (user, messages, channel) => {
    const textMessages = messages.filter(m => m.type === 'text');

    if (textMessages.length === 0) {
      return { action: 'proceed' };
    }

    const messageText = textMessages[0].text;

    // Detect language
    const detection = await AI.generate(
      'Detect the language of this text. Respond with just the language code (e.g., "en", "es", "fr") or "en" if already English.',
      [{ type: 'text', text: messageText }]
    );

    const language = detection.trim().toLowerCase();

    // If not English, translate
    if (language !== 'en' && language !== 'english') {
      const translation = await AI.generate(
        'Translate this text to English. Return only the translation, no explanations.',
        [{ type: 'text', text: messageText }]
      );

      // Replace with translated text
      const translatedMessages = messages.map(msg => {
        if (msg.type === 'text') {
          return { type: 'text' as const, text: translation };
        }
        return msg;
      });

      return {
        action: 'proceed',
        modifiedMessage: translatedMessages,
        metadata: {
          originalLanguage: language,
          translated: true
        }
      };
    }

    return { action: 'proceed' };
  }
});
```

### Priority Routing

```typescript
import { PreProcessor } from 'lua-cli';

export const priorityRouter = new PreProcessor({
  name: 'priority-router',
  description: 'Routes urgent messages to priority queue',
  priority: 25,

  execute: async (user, messages, channel) => {
    const urgentKeywords = ['urgent', 'emergency', 'critical', 'asap', 'help'];

    // Check for urgent keywords
    for (const message of messages) {
      if (message.type === 'text') {
        const lowerText = message.text.toLowerCase();

        for (const keyword of urgentKeywords) {
          if (lowerText.includes(keyword)) {
            // Mark as high priority
            const priorityMessage = {
              type: 'text' as const,
              text: `[HIGH PRIORITY] ${message.text}`
            };

            return {
              action: 'proceed',
              modifiedMessage: [priorityMessage, ...messages.slice(1)],
              metadata: {
                priority: 'high',
                keyword,
                flagged: true
              }
            };
          }
        }
      }
    }

    return {
      action: 'proceed',
      metadata: {
        priority: 'normal'
      }
    };
  }
});
```

## Execution Order

PreProcessors execute in priority order (lowest first):

```typescript
// Priority 5 - User verification (runs first)
new PreProcessor({ priority: 5, ... });

// Priority 10 - Content filter
new PreProcessor({ priority: 10, ... });

// Priority 50 - Message enrichment (runs last)
new PreProcessor({ priority: 50, ... });
```

## Async vs Sync

### Synchronous (default)
- Blocks message processing until complete
- Best for fast operations (< 100ms)
- Use for filtering, validation, simple transforms

### Asynchronous
- Runs in background, doesn't block
- Best for slow operations (API calls, AI)
- Set `async: true` in config

```typescript
// Fast operation - synchronous
new PreProcessor({
  name: 'quick-filter',
  async: false,  // Default
  execute: async (user, messages, channel) => {
    // Fast check
    return { action: 'proceed' };
  }
});

// Slow operation - asynchronous
new PreProcessor({
  name: 'ai-analyzer',
  async: true,  // Non-blocking
  execute: async (user, messages, channel) => {
    // Slow AI call
    await AI.generate(...);
    return { action: 'proceed' };
  }
});
```

## Best Practices

### ✅ DO

- Use low priorities for critical checks (run early)
- Set `async: true` for slow operations
- Return clear block messages to users
- Log blocked messages for review
- Use metadata to pass data between preprocessors
- Handle errors gracefully
- Keep preprocessing logic fast
- Test with edge cases

### ❌ DON'T

- Don't block valid messages unnecessarily
- Don't perform heavy computation synchronously
- Don't skip error handling
- Don't forget to return a result
- Don't modify messages unless necessary
- Don't create circular logic
- Don't use very high priorities (use low numbers instead)

## Error Handling

```typescript
export const safePreProcessor = new PreProcessor({
  name: 'safe-preprocessor',
  description: 'Preprocessor with error handling',

  execute: async (user, messages, channel) => {
    try {
      // Your preprocessing logic
      const result = await processMessages(messages);

      return { action: 'proceed' };
    } catch (error) {
      console.error('PreProcessor error:', error);

      // Log error but don't block message
      await Data.create('preprocessor_errors', {
        error: error.message,
        userId: user.id,
        timestamp: new Date().toISOString()
      });

      // Proceed anyway (fail open)
      return { action: 'proceed' };
    }
  }
});
```

## Common Patterns

### Conditional Blocking

```typescript
if (shouldBlock) {
  return {
    action: 'block',
    response: 'Message blocked'
  };
}
return { action: 'proceed' };
```

### Message Modification

```typescript
const modifiedMessages = messages.map(msg => {
  if (msg.type === 'text') {
    return { type: 'text', text: msg.text.toUpperCase() };
  }
  return msg;
});

return {
  action: 'proceed',
  modifiedMessage: modifiedMessages
};
```

### Adding Context

```typescript
const contextMsg = { type: 'text' as const, text: '[Context info]' };
return {
  action: 'proceed',
  modifiedMessage: [contextMsg, ...messages]
};
```

## Related APIs

- [PostProcessor](./PostProcessor.md) - Process agent responses
- [User API](./User.md) - Access user data
- [Data API](./Data.md) - Store filtering logs

## See Also

- [Message Filtering Guide](../guides/MessageFiltering.md)
- [Content Moderation](../guides/ContentModeration.md)
