response-optimizer.ts•12.5 kB
/**
 * Response formatting and parsing optimization utilities
 * Provides high-performance response processing with minimal memory allocation
 */
import type { ChatCompletionResponse } from '../types/openrouter.js';
import type {
  SearchResponse,
  SearchResult,
  SearchSource,
  SearchMetadata,
} from '../types/search.js';
// Performance API declarations for Node.js
declare const performance: {
  now(): number;
};
// Pre-compiled regex patterns for better performance
const URL_REGEX = /https?:\/\/[^\s\]]+/g;
const CITATION_REGEX = /\[(\d+)\]:\s*(https?:\/\/[^\s\]]+)(?:\s+"([^"]*)")?/g;
const DOMAIN_REGEX = /^https?:\/\/([^/]+)/;
// Pre-allocated string patterns for optimization
const SOURCE_TITLE_PREFIX = 'Source ';
const REFERENCE_PREFIX = 'Reference from ';
/**
 * Performance metrics for response processing
 */
export interface ResponseProcessingMetrics {
  /** Time spent parsing response JSON */
  parseTime: number;
  /** Time spent extracting sources */
  sourceExtractionTime: number;
  /** Time spent validating response */
  validationTime: number;
  /** Time spent creating final object */
  formattingTime: number;
  /** Total processing time */
  totalTime: number;
  /** Number of sources extracted */
  sourceCount: number;
  /** Content length processed */
  contentLength: number;
  /** Memory usage estimate (bytes) */
  estimatedMemoryUsage: number;
}
/**
 * Optimized source extraction result
 */
interface SourceExtractionResult {
  sources: SearchSource[];
  processedContent: string;
  extractionTime: number;
}
/**
 * High-performance response formatter with optimization features
 */
export class ResponseOptimizer {
  private static instance: ResponseOptimizer | null = null;
  // Reusable objects for memory efficiency
  private readonly urlMatchArray: RegExpMatchArray[] = [];
  private readonly citationMatchArray: RegExpMatchArray[] = [];
  private constructor() {
    // Private constructor for singleton pattern
  }
  /**
   * Get singleton instance for memory efficiency
   */
  static getInstance(): ResponseOptimizer {
    if (!ResponseOptimizer.instance) {
      ResponseOptimizer.instance = new ResponseOptimizer();
    }
    return ResponseOptimizer.instance;
  }
  /**
   * Optimized response formatting with performance metrics
   */
  formatSearchResponseOptimized(
    apiResponse: ChatCompletionResponse,
    query: string,
    startTime: number,
    temperature?: number,
    maxTokens?: number
  ): { response: SearchResponse; metrics: ResponseProcessingMetrics } {
    const processingStartTime = performance.now();
    const endTime = Date.now();
    const responseTime = endTime - startTime;
    // Parse content with optimization
    const parseStartTime = performance.now();
    const mainContent = this.extractContentOptimized(apiResponse);
    const parseTime = performance.now() - parseStartTime;
    // Extract sources with optimization
    const sourceExtractionResult = this.extractSourcesOptimized(mainContent);
    // Validate with optimization
    const validationStartTime = performance.now();
    const isValidResponse = this.validateApiResponseOptimized(apiResponse);
    const validationTime = performance.now() - validationStartTime;
    // Format final response
    const formattingStartTime = performance.now();
    const searchResult = this.createSearchResultOptimized(
      mainContent,
      sourceExtractionResult.sources,
      apiResponse,
      query,
      endTime,
      temperature,
      maxTokens,
      responseTime
    );
    const searchResponse: SearchResponse = {
      success: isValidResponse,
      result: isValidResponse ? searchResult : undefined,
      requestId: apiResponse.id,
    };
    const formattingTime = performance.now() - formattingStartTime;
    const totalTime = performance.now() - processingStartTime;
    // Calculate metrics
    const metrics: ResponseProcessingMetrics = {
      parseTime,
      sourceExtractionTime: sourceExtractionResult.extractionTime,
      validationTime,
      formattingTime,
      totalTime,
      sourceCount: sourceExtractionResult.sources.length,
      contentLength: mainContent.length,
      estimatedMemoryUsage: this.estimateMemoryUsage(searchResponse),
    };
    return { response: searchResponse, metrics };
  }
  /**
   * Optimized content extraction with minimal allocations
   */
  private extractContentOptimized(apiResponse: ChatCompletionResponse): string {
    // Direct property access without optional chaining for performance
    const choices = apiResponse.choices;
    if (!choices || choices.length === 0) return '';
    const firstChoice = choices[0];
    if (!firstChoice || !firstChoice.message) return '';
    return firstChoice.message.content || '';
  }
  /**
   * High-performance source extraction with multiple strategies
   */
  private extractSourcesOptimized(content: string): SourceExtractionResult {
    const extractionStartTime = performance.now();
    const sources: SearchSource[] = [];
    const foundUrls = new Set<string>();
    // Clear previous matches for reuse
    this.urlMatchArray.length = 0;
    this.citationMatchArray.length = 0;
    // Strategy 1: Extract citation-style references (highest priority)
    // Format: [1]: https://example.com "Title"
    let match: RegExpExecArray | null;
    // Reset regex
    CITATION_REGEX.lastIndex = 0;
    while ((match = CITATION_REGEX.exec(content)) !== null) {
      const url = match[2];
      const title =
        match[3] || this.generateOptimizedTitle(url, parseInt(match[1], 10));
      const description = this.generateOptimizedDescription(url);
      sources.push({
        url,
        title,
        description,
      });
      foundUrls.add(url);
      // Prevent infinite loop
      if (CITATION_REGEX.lastIndex === 0) break;
    }
    // Strategy 2: Extract remaining plain URLs (always run to catch all URLs)
    // Reset regex
    URL_REGEX.lastIndex = 0;
    let urlIndex = sources.length + 1;
    while ((match = URL_REGEX.exec(content)) !== null) {
      const url = match[0];
      // Avoid duplicates by checking if URL was already found
      if (!foundUrls.has(url)) {
        sources.push({
          url,
          title: SOURCE_TITLE_PREFIX + urlIndex,
          description: this.generateOptimizedDescription(url),
        });
        foundUrls.add(url);
        urlIndex++;
      }
      // Prevent infinite loop
      if (URL_REGEX.lastIndex === 0) break;
    }
    const extractionTime = performance.now() - extractionStartTime;
    return {
      sources,
      processedContent: content,
      extractionTime,
    };
  }
  /**
   * Optimized title generation with minimal string operations
   */
  private generateOptimizedTitle(url: string, index: number): string {
    // Try to extract domain for a better title
    const domainMatch = DOMAIN_REGEX.exec(url);
    if (domainMatch && domainMatch[1]) {
      const domain = domainMatch[1];
      // Remove www. prefix if present
      const cleanDomain = domain.startsWith('www.') ? domain.slice(4) : domain;
      return `${cleanDomain} (${index})`;
    }
    return SOURCE_TITLE_PREFIX + index;
  }
  /**
   * Optimized description generation
   */
  private generateOptimizedDescription(url: string): string {
    const domainMatch = DOMAIN_REGEX.exec(url);
    if (domainMatch && domainMatch[1]) {
      return REFERENCE_PREFIX + domainMatch[1];
    }
    return REFERENCE_PREFIX + 'unknown source';
  }
  /**
   * Fast API response validation
   */
  private validateApiResponseOptimized(
    response: ChatCompletionResponse
  ): boolean {
    // Quick property existence checks
    return !!(
      response &&
      response.id &&
      response.choices &&
      response.choices.length > 0 &&
      response.choices[0] &&
      response.choices[0].message
    );
  }
  /**
   * Optimized search result creation with minimal object allocation
   */
  private createSearchResultOptimized(
    content: string,
    sources: SearchSource[],
    apiResponse: ChatCompletionResponse,
    query: string,
    timestamp: number,
    temperature?: number,
    maxTokens?: number,
    responseTime?: number
  ): SearchResult {
    // Create metadata object with direct property assignment
    const metadata: SearchMetadata = {
      model: apiResponse.model,
      timestamp,
      query,
    };
    // Add optional properties only if defined (avoid undefined assignments)
    if (temperature !== undefined) metadata.temperature = temperature;
    if (maxTokens !== undefined) metadata.maxTokens = maxTokens;
    if (apiResponse.usage) metadata.usage = apiResponse.usage;
    if (responseTime !== undefined) metadata.responseTime = responseTime;
    return {
      content,
      sources,
      metadata,
    };
  }
  /**
   * Estimate memory usage for performance monitoring
   */
  private estimateMemoryUsage(response: SearchResponse): number {
    let size = 0;
    // Rough estimation based on string lengths and object overhead
    if (response.result) {
      size += response.result.content.length * 2; // UTF-16 encoding
      size += response.result.sources.reduce((acc, source) => {
        return (
          acc +
          source.url.length * 2 +
          source.title.length * 2 +
          (source.description?.length || 0) * 2
        );
      }, 0);
      size += 200; // Object overhead estimation
    }
    if (response.error) {
      size += response.error.length * 2;
    }
    if (response.requestId) {
      size += response.requestId.length * 2;
    }
    size += 100; // Base object overhead
    return size;
  }
  /**
   * Batch process multiple responses for improved performance
   */
  batchFormatResponses(
    responses: Array<{
      apiResponse: ChatCompletionResponse;
      query: string;
      startTime: number;
      temperature?: number;
      maxTokens?: number;
    }>
  ): Array<{ response: SearchResponse; metrics: ResponseProcessingMetrics }> {
    return responses.map(item =>
      this.formatSearchResponseOptimized(
        item.apiResponse,
        item.query,
        item.startTime,
        item.temperature,
        item.maxTokens
      )
    );
  }
  /**
   * Clear internal caches for memory management
   */
  clearCache(): void {
    this.urlMatchArray.length = 0;
    this.citationMatchArray.length = 0;
  }
}
/**
 * Streaming response processor for handling large responses efficiently
 */
export class StreamingResponseProcessor {
  private buffer = '';
  private foundUrls = new Set<string>();
  private contentLength = 0;
  /**
   * Process response chunks as they arrive
   */
  processChunk(chunk: string): void {
    this.buffer += chunk;
    this.contentLength += chunk.length;
  }
  /**
   * Finalize the streaming response and extract all sources
   */
  finalize(): {
    content: string;
    sources: SearchSource[];
    contentLength: number;
  } {
    const sources: SearchSource[] = [];
    // Extract all URLs from the complete buffer
    URL_REGEX.lastIndex = 0;
    let match: RegExpExecArray | null;
    let urlIndex = 1;
    while ((match = URL_REGEX.exec(this.buffer)) !== null) {
      const url = match[0];
      // Avoid duplicates
      if (!this.foundUrls.has(url)) {
        this.foundUrls.add(url);
        try {
          const urlObj = new globalThis.URL(url);
          sources.push({
            url,
            title: `Source ${urlIndex}`,
            description: `Reference from ${urlObj.hostname}`,
          });
          urlIndex++;
        } catch {
          // Invalid URL, skip it
        }
      }
      if (URL_REGEX.lastIndex === 0) break;
    }
    return {
      content: this.buffer,
      sources,
      contentLength: this.contentLength,
    };
  }
  /**
   * Reset the processor for reuse
   */
  reset(): void {
    this.buffer = '';
    this.foundUrls.clear();
    this.contentLength = 0;
  }
}
/**
 * Factory function for creating optimized formatters
 */
export function createOptimizedFormatter(): ResponseOptimizer {
  return ResponseOptimizer.getInstance();
}
/**
 * Utility function for quick response formatting without metrics
 */
export function formatResponseQuick(
  apiResponse: ChatCompletionResponse,
  query: string,
  startTime: number,
  temperature?: number,
  maxTokens?: number
): SearchResponse {
  const optimizer = ResponseOptimizer.getInstance();
  const result = optimizer.formatSearchResponseOptimized(
    apiResponse,
    query,
    startTime,
    temperature,
    maxTokens
  );
  return result.response;
}