/**
 * Base Provider - Abstract LLM Provider Implementation
 *
 * Provides common functionality for LLM providers:
 * - Retry logic with exponential backoff
 * - Rate limit handling
 * - Transient error recovery
 * - Message conversion utilities
 * - Usage tracking
 *
 * Usage:
 * ```typescript
 * class MyProvider extends BaseProvider {
 *   readonly id = 'my-provider';
 *
 *   protected async doGenerate(messages, tools): Promise<ProviderResponse> {
 *     // Provider-specific implementation
 *   }
 *
 *   protected async doGenerateStream(messages, tools): AsyncIterable<StreamChunk> {
 *     // Provider-specific implementation
 *   }
 * }
 * ```
 */
// ═══════════════════════════════════════════════════════════════════════════════
// Constants
// ═══════════════════════════════════════════════════════════════════════════════
/** Patterns that indicate transient/retryable errors */
export const TRANSIENT_ERROR_PATTERNS = [
    'premature close',
    'premature end',
    'unexpected end',
    'aborted',
    'fetcherror',
    'invalid response body',
    'gunzip',
    'decompress',
    'econnreset',
    'econnrefused',
    'epipe',
    'socket hang up',
    'network',
    'timeout',
    '500',
    '502',
    '503',
    '504',
    'overloaded',
];
/** Default retry configuration */
export const DEFAULT_RETRY_CONFIG = {
    maxRetries: 3,
    initialDelayMs: 1000,
    maxDelayMs: 30000,
    backoffMultiplier: 2,
};
/** Default rate limit configuration */
export const DEFAULT_RATE_LIMIT_CONFIG = {
    maxRetries: 4,
    initialDelayMs: 1500,
    minDelayMs: 750,
    maxDelayMs: 40000,
};
// ═══════════════════════════════════════════════════════════════════════════════
// Error Detection Utilities
// ═══════════════════════════════════════════════════════════════════════════════
/**
 * Check if an error is transient and can be retried
 */
export function isTransientError(error) {
    if (!(error instanceof Error))
        return false;
    const message = error.message.toLowerCase();
    const errorName = error.name?.toLowerCase() ?? '';
    const errorCode = error.code?.toLowerCase() ?? '';
    const allText = `${message} ${errorName} ${errorCode}`;
    return TRANSIENT_ERROR_PATTERNS.some((pattern) => allText.includes(pattern));
}
/**
 * Check if an error is a rate limit error
 */
export function isRateLimitError(error) {
    if (!(error instanceof Error))
        return false;
    const message = error.message.toLowerCase();
    // Check for common rate limit indicators
    if (message.includes('rate limit') ||
        message.includes('rate_limit') ||
        message.includes('too many requests') ||
        message.includes('429')) {
        return true;
    }
    // Check for status code
    const status = error.status;
    return status === 429;
}
/**
 * Check if an error is an authentication error
 */
export function isAuthError(error) {
    if (!(error instanceof Error))
        return false;
    const message = error.message.toLowerCase();
    const status = error.status;
    return (status === 401 ||
        status === 403 ||
        message.includes('unauthorized') ||
        message.includes('invalid api key') ||
        message.includes('authentication'));
}
// ═══════════════════════════════════════════════════════════════════════════════
// Retry Utilities
// ═══════════════════════════════════════════════════════════════════════════════
/**
 * Sleep for a given duration
 */
export function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
 * Calculate exponential backoff delay
 */
export function calculateBackoffDelay(attempt, config) {
    const delay = config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt);
    // Add jitter (±10%)
    const jitter = delay * 0.1 * (Math.random() * 2 - 1);
    return Math.min(delay + jitter, config.maxDelayMs);
}
/**
 * Execute an operation with retry logic
 */
export async function withRetry(operation, options = {}) {
    const config = {
        ...DEFAULT_RETRY_CONFIG,
        ...options.config,
    };
    const shouldRetry = options.shouldRetry ?? isTransientError;
    let lastError;
    for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
        try {
            return await operation();
        }
        catch (error) {
            lastError = error;
            // Don't retry auth errors
            if (isAuthError(error)) {
                throw error;
            }
            // Check if we should retry
            if (attempt === config.maxRetries || !shouldRetry(error)) {
                throw error;
            }
            // Calculate delay and wait
            const delay = calculateBackoffDelay(attempt, config);
            options.onRetry?.(error, attempt, delay);
            await sleep(delay);
        }
    }
    throw lastError;
}
// ═══════════════════════════════════════════════════════════════════════════════
// Message Utilities
// ═══════════════════════════════════════════════════════════════════════════════
/**
 * Extract system message from conversation
 */
export function extractSystemMessage(messages) {
    const systemMessages = messages.filter((m) => m.role === 'system');
    const rest = messages.filter((m) => m.role !== 'system');
    if (systemMessages.length === 0) {
        return { system: null, rest };
    }
    // Combine all system messages
    const system = systemMessages
        .map((m) => (typeof m.content === 'string' ? m.content : JSON.stringify(m.content)))
        .join('\n\n');
    return { system, rest };
}
/**
 * Merge consecutive messages from the same role
 */
export function mergeConsecutiveMessages(messages) {
    if (messages.length === 0)
        return [];
    const merged = [];
    let current = null;
    for (const message of messages) {
        if (!current) {
            current = { ...message };
            continue;
        }
        if (current.role === message.role && typeof current.content === 'string' && typeof message.content === 'string') {
            // Merge text content
            current.content = `${current.content}\n\n${message.content}`;
        }
        else {
            merged.push(current);
            current = { ...message };
        }
    }
    if (current) {
        merged.push(current);
    }
    return merged;
}
// ═══════════════════════════════════════════════════════════════════════════════
// Usage Tracking
// ═══════════════════════════════════════════════════════════════════════════════
/**
 * Create an empty usage object
 */
export function emptyUsage() {
    return {
        inputTokens: 0,
        outputTokens: 0,
        totalTokens: 0,
    };
}
/**
 * Merge usage objects
 */
export function mergeUsage(a, b) {
    return {
        inputTokens: (a.inputTokens ?? 0) + (b.inputTokens ?? 0),
        outputTokens: (a.outputTokens ?? 0) + (b.outputTokens ?? 0),
        totalTokens: (a.totalTokens ?? 0) + (b.totalTokens ?? 0),
    };
}
// ═══════════════════════════════════════════════════════════════════════════════
// Abstract Base Provider
// ═══════════════════════════════════════════════════════════════════════════════
/**
 * Abstract base class for LLM providers
 *
 * Implements common functionality like retry logic and error handling.
 * Subclasses must implement the abstract methods for provider-specific behavior.
 */
export class BaseProvider {
    model;
    maxTokens;
    temperature;
    retryConfig;
    rateLimitConfig;
    constructor(options) {
        this.model = options.model;
        this.maxTokens = options.maxTokens ?? 4096;
        this.temperature = options.temperature ?? 0;
        this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...options.retryConfig };
        this.rateLimitConfig = { ...DEFAULT_RATE_LIMIT_CONFIG, ...options.rateLimitConfig };
    }
    /**
     * Generate a response (implemented by subclass)
     */
    async generate(messages, tools) {
        return withRetry(() => this.doGenerate(messages, tools), {
            config: this.retryConfig,
            shouldRetry: (error) => isTransientError(error) || isRateLimitError(error),
            onRetry: (error, attempt, delay) => {
                this.onRetry(error, attempt, delay);
            },
        });
    }
    /**
     * Generate a streaming response (implemented by subclass)
     */
    async *generateStream(messages, tools) {
        // Stream operations are harder to retry, so we do best-effort
        yield* this.doGenerateStream(messages, tools);
    }
    /**
     * Get available models (optional, implemented by subclass)
     */
    async getModels() {
        return [];
    }
    /**
     * Called when a retry is about to happen
     */
    onRetry(_error, _attempt, _delay) {
        // Subclasses can override for logging
    }
}
//# sourceMappingURL=baseProvider.js.map