Event Overview

Core Event Types

Events are emitted for all major operations:
  • Message Events: Chat message lifecycle (start, complete, error)
  • Image Generation Events: Image creation process (start, complete, error)
  • Authentication Events: Token management and auth state changes
  • Tool Calling Events: Function execution lifecycle
  • Stream Events: Real-time streaming updates
import { AnimusClient } from 'animus-client';

const client = new AnimusClient({
  tokenProviderUrl: 'https://your-backend.com/api/get-animus-token',
  chat: {
    model: 'vivian-llama3.1-70b-1.0-fp8',
    systemMessage: 'You are a helpful assistant.',
    autoTurn: true,
    check_image_generation: true
  }
});

// Listen to all events
client.on('messageStart', (data) => console.log('Message started:', data));
client.on('messageComplete', (data) => console.log('Message completed:', data));
client.on('messageError', (data) => console.error('Message error:', data));
client.on('imageGenerationStart', (data) => console.log('Image generation started:', data));
client.on('imageGenerationComplete', (data) => console.log('Image generated:', data));
client.on('imageGenerationError', (data) => console.error('Image generation error:', data));

Message Events

Unified Message Event System

All message types (regular, auto-turn, follow-up) use the same event structure:
// Message Event Data Structure (for chat.send() events)
interface MessageEventData {
  content: string;                    // The message content
  messageType?: 'regular' | 'auto' | 'followup'; // Type of message
  turnIndex?: number;                 // For auto-turn: current turn index (0-based)
  totalTurns?: number;                // For auto-turn: total number of turns
  totalMessages?: number;             // When all messages in sequence are complete
  timestamp: string;                  // ISO timestamp
  error?: string;                     // For error events only
}

// Chat Completions Response Structure (for await chat.completions())
interface ChatCompletionResponse {
  id: string;                         // Unique response ID
  object: 'chat.completion';          // Response type identifier
  created: number;                    // Unix timestamp
  model: string;                      // Model used for completion
  choices: Array<{
    message: {
      role: 'assistant';
      content: string | null;         // Main response text (null if only tool_calls)
      reasoning?: string;             // Model's reasoning process (optional)
      tool_calls?: ToolCall[];        // Function calls requested (optional)
      image_prompt?: string;          // Image generation prompt (optional)
      turns?: string[];               // Conversation turns for autoTurn (optional)
      next?: boolean;                 // Indicates follow-up expected (optional)
    };
    index: number;
    finish_reason: string;
  }>;
  usage?: {                           // Token usage statistics (optional)
    prompt_tokens: number;
    completion_tokens: number;
    total_tokens: number;
  };
  compliance_violations?: string[];   // Any compliance issues (optional)
}

Message Lifecycle Events

// Message starts (typing begins)
client.on('messageStart', (data: MessageEventData) => {
  console.log(`Starting ${data.messageType || 'regular'} message`);
  
  if (data.messageType === 'auto') {
    showTypingIndicator(`Turn ${data.turnIndex! + 1}/${data.totalTurns}`);
  } else if (data.messageType === 'followup') {
    showFollowUpIndicator();
  } else {
    showTypingIndicator();
  }
});

// Message completes (content ready)
client.on('messageComplete', (data: MessageEventData) => {
  console.log(`Completed ${data.messageType || 'regular'} message`);
  
  hideTypingIndicator();
  displayMessage(data.content, data.messageType);
  
  // Check if this is the final message in a sequence
  if (data.totalMessages) {
    console.log(`All ${data.totalMessages} messages completed`);
    onSequenceComplete();
  }
});

// Message error (something went wrong)
client.on('messageError', (data: MessageEventData) => {
  console.error(`${data.messageType || 'regular'} message error:`, data.error);
  
  hideTypingIndicator();
  
  // Handle cancellations gracefully
  if (data.error?.includes('Canceled')) {
    console.log('Message was canceled due to new user input');
  } else {
    showErrorMessage(data.error || 'Unknown error');
  }
});

Auto-Turn Specific Events

// Track auto-turn progress
client.on('messageStart', (data) => {
  if (data.messageType === 'auto') {
    updateTurnProgress(data.turnIndex! + 1, data.totalTurns!);
  }
});

client.on('messageComplete', (data) => {
  if (data.messageType === 'auto') {
    console.log(`Turn ${data.turnIndex! + 1}/${data.totalTurns} completed`);
    
    // Update progress bar
    const progress = ((data.turnIndex! + 1) / data.totalTurns!) * 100;
    updateProgressBar(progress);
  }
});

function updateTurnProgress(current: number, total: number) {
  const progressElement = document.getElementById('turn-progress');
  if (progressElement) {
    progressElement.textContent = `Turn ${current} of ${total}`;
  }
}

Image Generation Events

Image Generation Lifecycle

// Image generation event data structure
interface ImageGenerationEventData {
  prompt: string;           // The image prompt
  imageUrl?: string;        // Generated image URL (on complete)
  error?: string;          // Error message (on error)
  timestamp: string;       // ISO timestamp
}

// Image generation starts
client.on('imageGenerationStart', (data: ImageGenerationEventData) => {
  console.log('Starting image generation:', data.prompt);
  
  showImageGenerationUI();
  updateImagePrompt(data.prompt);
  startImageLoadingAnimation();
});

// Image generation completes
client.on('imageGenerationComplete', (data: ImageGenerationEventData) => {
  console.log('Image generated:', data.imageUrl);
  
  stopImageLoadingAnimation();
  displayGeneratedImage(data.imageUrl!);
  addImageToHistory(data.imageUrl!, data.prompt);
});

// Image generation fails
client.on('imageGenerationError', (data: ImageGenerationEventData) => {
  console.error('Image generation failed:', data.error);
  
  stopImageLoadingAnimation();
  showImageGenerationError(data.error!);
  hideImageGenerationUI();
});

Image Generation UI Integration

class ImageGenerationManager {
  private isGenerating = false;
  private currentPrompt = '';

  constructor() {
    this.setupEventListeners();
  }

  setupEventListeners() {
    client.on('imageGenerationStart', (data) => {
      this.isGenerating = true;
      this.currentPrompt = data.prompt;
      this.showGenerationProgress();
    });

    client.on('imageGenerationComplete', (data) => {
      this.isGenerating = false;
      this.showGeneratedImage(data.imageUrl!, this.currentPrompt);
    });

    client.on('imageGenerationError', (data) => {
      this.isGenerating = false;
      this.showGenerationError(data.error!);
    });
  }

  showGenerationProgress() {
    const container = document.getElementById('image-container');
    container!.innerHTML = `
      <div class="image-generating">
        <div class="loading-spinner"></div>
        <p>Generating image...</p>
        <small>${this.currentPrompt.substring(0, 100)}...</small>
      </div>
    `;
  }

  showGeneratedImage(imageUrl: string, prompt: string) {
    const container = document.getElementById('image-container');
    container!.innerHTML = `
      <div class="generated-image-container">
        <img src="${imageUrl}" alt="${prompt}" class="generated-image" />
        <p class="image-prompt">${prompt}</p>
      </div>
    `;
  }

  showGenerationError(error: string) {
    const container = document.getElementById('image-container');
    container!.innerHTML = `
      <div class="image-error">
        <p>Failed to generate image: ${error}</p>
        <button onclick="this.retryGeneration()">Try Again</button>
      </div>
    `;
  }

  isCurrentlyGenerating(): boolean {
    return this.isGenerating;
  }
}

Advanced Event Handling

Event Aggregation

Combine multiple events for complex UI states:
class ConversationStateManager {
  private state = {
    isTyping: false,
    isGeneratingImage: false,
    currentTurn: 0,
    totalTurns: 0,
    pendingOperations: 0
  };

  constructor() {
    this.setupEventListeners();
  }

  setupEventListeners() {
    // Track typing state
    client.on('messageStart', (data) => {
      this.state.isTyping = true;
      this.state.pendingOperations++;
      
      if (data.messageType === 'auto') {
        this.state.currentTurn = data.turnIndex! + 1;
        this.state.totalTurns = data.totalTurns!;
      }
      
      this.updateUI();
    });

    client.on('messageComplete', (data) => {
      this.state.pendingOperations--;
      
      if (data.totalMessages) {
        this.state.isTyping = false;
        this.state.currentTurn = 0;
        this.state.totalTurns = 0;
      }
      
      this.updateUI();
    });

    // Track image generation state
    client.on('imageGenerationStart', () => {
      this.state.isGeneratingImage = true;
      this.state.pendingOperations++;
      this.updateUI();
    });

    client.on('imageGenerationComplete', () => {
      this.state.isGeneratingImage = false;
      this.state.pendingOperations--;
      this.updateUI();
    });

    client.on('imageGenerationError', () => {
      this.state.isGeneratingImage = false;
      this.state.pendingOperations--;
      this.updateUI();
    });
  }

  updateUI() {
    const statusElement = document.getElementById('conversation-status');
    
    if (this.state.isTyping && this.state.isGeneratingImage) {
      statusElement!.textContent = 'AI is typing and generating an image...';
    } else if (this.state.isTyping) {
      if (this.state.totalTurns > 1) {
        statusElement!.textContent = `AI is typing (${this.state.currentTurn}/${this.state.totalTurns})...`;
      } else {
        statusElement!.textContent = 'AI is typing...';
      }
    } else if (this.state.isGeneratingImage) {
      statusElement!.textContent = 'Generating image...';
    } else if (this.state.pendingOperations > 0) {
      statusElement!.textContent = 'Processing...';
    } else {
      statusElement!.textContent = 'Ready';
    }

    // Update input state
    const inputElement = document.getElementById('message-input') as HTMLInputElement;
    inputElement.disabled = this.state.pendingOperations > 0;
  }

  getState() {
    return { ...this.state };
  }
}

Event Filtering and Routing

class EventRouter {
  private handlers = new Map<string, Function[]>();

  constructor() {
    this.setupRouting();
  }

  setupRouting() {
    // Route all events through the router
    client.on('messageStart', (data) => this.route('messageStart', data));
    client.on('messageComplete', (data) => this.route('messageComplete', data));
    client.on('messageError', (data) => this.route('messageError', data));
    client.on('imageGenerationStart', (data) => this.route('imageGenerationStart', data));
    client.on('imageGenerationComplete', (data) => this.route('imageGenerationComplete', data));
    client.on('imageGenerationError', (data) => this.route('imageGenerationError', data));
  }

  // Register handlers for specific event types
  on(eventType: string, handler: Function) {
    if (!this.handlers.has(eventType)) {
      this.handlers.set(eventType, []);
    }
    this.handlers.get(eventType)!.push(handler);
  }

  // Register handlers for specific message types
  onMessageType(messageType: 'regular' | 'auto' | 'followup', eventType: string, handler: Function) {
    this.on(eventType, (data: any) => {
      if (data.messageType === messageType || (!data.messageType && messageType === 'regular')) {
        handler(data);
      }
    });
  }

  private route(eventType: string, data: any) {
    const handlers = this.handlers.get(eventType) || [];
    handlers.forEach(handler => {
      try {
        handler(data);
      } catch (error) {
        console.error(`Error in event handler for ${eventType}:`, error);
      }
    });
  }
}

// Usage
const eventRouter = new EventRouter();

// Handle only auto-turn messages
eventRouter.onMessageType('auto', 'messageStart', (data) => {
  console.log('Auto-turn message started:', data);
});

// Handle only regular messages
eventRouter.onMessageType('regular', 'messageComplete', (data) => {
  console.log('Regular message completed:', data);
});

// Handle all image generation events
eventRouter.on('imageGenerationStart', (data) => {
  console.log('Any image generation started:', data);
});

Error Event Handling

Comprehensive Error Management

class ErrorManager {
  private errorHistory: Array<{type: string, error: string, timestamp: string}> = [];

  constructor() {
    this.setupErrorHandling();
  }

  setupErrorHandling() {
    // Handle message errors
    client.on('messageError', (data) => {
      this.logError('message', data.error!, data.timestamp);
      
      if (data.error?.includes('Canceled')) {
        this.handleCancellation(data);
      } else if (data.error?.includes('Authentication')) {
        this.handleAuthError(data);
      } else if (data.error?.includes('Rate limit')) {
        this.handleRateLimit(data);
      } else {
        this.handleGenericError(data);
      }
    });

    // Handle image generation errors
    client.on('imageGenerationError', (data) => {
      this.logError('imageGeneration', data.error!, data.timestamp);
      
      if (data.error?.includes('content policy')) {
        this.handleContentPolicyError(data);
      } else if (data.error?.includes('timeout')) {
        this.handleTimeoutError(data);
      } else {
        this.handleImageGenerationError(data);
      }
    });
  }

  private logError(type: string, error: string, timestamp: string) {
    this.errorHistory.push({ type, error, timestamp });
    
    // Keep only last 50 errors
    if (this.errorHistory.length > 50) {
      this.errorHistory.shift();
    }
  }

  private handleCancellation(data: any) {
    console.log('Operation was canceled:', data);
    // Don't show error UI for cancellations
  }

  private handleAuthError(data: any) {
    console.error('Authentication error:', data);
    this.showAuthErrorDialog();
  }

  private handleRateLimit(data: any) {
    console.warn('Rate limit exceeded:', data);
    this.showRateLimitWarning();
  }

  private handleContentPolicyError(data: any) {
    console.warn('Content policy violation:', data);
    this.showContentPolicyWarning();
  }

  private handleTimeoutError(data: any) {
    console.warn('Operation timed out:', data);
    this.showTimeoutWarning();
  }

  private handleGenericError(data: any) {
    console.error('Generic error:', data);
    this.showGenericErrorDialog(data.error);
  }

  private handleImageGenerationError(data: any) {
    console.error('Image generation error:', data);
    this.showImageErrorDialog(data.error);
  }

  // UI methods
  private showAuthErrorDialog() {
    // Implement auth error UI
  }

  private showRateLimitWarning() {
    // Implement rate limit UI
  }

  private showContentPolicyWarning() {
    // Implement content policy UI
  }

  private showTimeoutWarning() {
    // Implement timeout UI
  }

  private showGenericErrorDialog(error: string) {
    // Implement generic error UI
  }

  private showImageErrorDialog(error: string) {
    // Implement image error UI
  }

  getErrorHistory() {
    return [...this.errorHistory];
  }
}

Performance and Debugging

Event Performance Monitoring

class EventPerformanceMonitor {
  private eventTimes = new Map<string, number>();
  private eventCounts = new Map<string, number>();

  constructor() {
    this.setupMonitoring();
  }

  setupMonitoring() {
    const events = [
      'messageStart', 'messageComplete', 'messageError',
      'imageGenerationStart', 'imageGenerationComplete', 'imageGenerationError'
    ];

    events.forEach(eventType => {
      client.on(eventType, (data) => {
        this.recordEvent(eventType, data.timestamp);
      });
    });
  }

  private recordEvent(eventType: string, timestamp: string) {
    const now = Date.now();
    const eventTime = new Date(timestamp).getTime();
    const latency = now - eventTime;

    // Record latency
    this.eventTimes.set(eventType, latency);

    // Count events
    const count = this.eventCounts.get(eventType) || 0;
    this.eventCounts.set(eventType, count + 1);

    // Log slow events
    if (latency > 1000) {
      console.warn(`Slow event detected: ${eventType} took ${latency}ms`);
    }
  }

  getStats() {
    return {
      eventTimes: Object.fromEntries(this.eventTimes),
      eventCounts: Object.fromEntries(this.eventCounts)
    };
  }

  logStats() {
    console.table(this.getStats());
  }
}

Event Debugging

class EventDebugger {
  private isEnabled = false;
  private eventLog: Array<{type: string, data: any, timestamp: number}> = [];

  enable() {
    this.isEnabled = true;
    this.setupLogging();
  }

  disable() {
    this.isEnabled = false;
  }

  private setupLogging() {
    if (!this.isEnabled) return;

    const events = [
      'messageStart', 'messageComplete', 'messageError',
      'imageGenerationStart', 'imageGenerationComplete', 'imageGenerationError'
    ];

    events.forEach(eventType => {
      client.on(eventType, (data) => {
        this.logEvent(eventType, data);
      });
    });
  }

  private logEvent(type: string, data: any) {
    if (!this.isEnabled) return;

    const logEntry = {
      type,
      data: { ...data },
      timestamp: Date.now()
    };

    this.eventLog.push(logEntry);
    console.log(`[EVENT] ${type}:`, data);

    // Keep only last 100 events
    if (this.eventLog.length > 100) {
      this.eventLog.shift();
    }
  }

  getEventLog() {
    return [...this.eventLog];
  }

  exportLog() {
    const logData = JSON.stringify(this.eventLog, null, 2);
    const blob = new Blob([logData], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    
    const a = document.createElement('a');
    a.href = url;
    a.download = `animus-event-log-${Date.now()}.json`;
    a.click();
    
    URL.revokeObjectURL(url);
  }
}

// Usage
const debugger = new EventDebugger();
debugger.enable(); // Start logging events

Best Practices

Event Handler Organization

// Organize event handlers by feature
class ChatEventHandlers {
  static setupMessageHandlers() {
    client.on('messageStart', this.onMessageStart);
    client.on('messageComplete', this.onMessageComplete);
    client.on('messageError', this.onMessageError);
  }

  static onMessageStart(data: any) {
    // Handle message start
  }

  static onMessageComplete(data: any) {
    // Handle message complete
  }

  static onMessageError(data: any) {
    // Handle message error
  }
}

class ImageEventHandlers {
  static setupImageHandlers() {
    client.on('imageGenerationStart', this.onImageStart);
    client.on('imageGenerationComplete', this.onImageComplete);
    client.on('imageGenerationError', this.onImageError);
  }

  static onImageStart(data: any) {
    // Handle image generation start
  }

  static onImageComplete(data: any) {
    // Handle image generation complete
  }

  static onImageError(data: any) {
    // Handle image generation error
  }
}

// Initialize all handlers
ChatEventHandlers.setupMessageHandlers();
ImageEventHandlers.setupImageHandlers();

Memory Management

// Clean up event listeners when needed
class EventManager {
  private listeners: Array<{event: string, handler: Function}> = [];

  addListener(event: string, handler: Function) {
    client.on(event, handler);
    this.listeners.push({ event, handler });
  }

  removeAllListeners() {
    this.listeners.forEach(({ event, handler }) => {
      client.off(event, handler);
    });
    this.listeners = [];
  }

  cleanup() {
    this.removeAllListeners();
  }
}

// Use in components that may be destroyed
const eventManager = new EventManager();
eventManager.addListener('messageComplete', handleMessageComplete);

// Clean up when component unmounts
window.addEventListener('beforeunload', () => {
  eventManager.cleanup();
});

Next Steps