/**
 * Timeout utilities for NeuroLink
 *
 * Provides flexible timeout parsing and error handling for AI operations.
 * Supports multiple time formats: milliseconds, seconds, minutes, hours.
 */
/**
 * Custom error class for timeout operations
 */
export class TimeoutError extends Error {
    timeout;
    provider;
    operation;
    constructor(message, timeout, provider, operation) {
        super(message);
        this.timeout = timeout;
        this.provider = provider;
        this.operation = operation;
        this.name = "TimeoutError";
        // Maintains proper stack trace for where error was thrown
        if (typeof Error.captureStackTrace === "function") {
            Error.captureStackTrace(this, TimeoutError);
        }
    }
}
/**
 * Parse timeout value from various formats
 * @param timeout - Can be number (ms), string with unit, or undefined
 * @returns Parsed timeout in milliseconds or undefined
 * @throws Error if format is invalid
 *
 * Examples:
 * - parseTimeout(5000) => 5000
 * - parseTimeout('30s') => 30000
 * - parseTimeout('2m') => 120000
 * - parseTimeout('1.5h') => 5400000
 * - parseTimeout(undefined) => undefined
 */
export function parseTimeout(timeout) {
    if (timeout === undefined) {
        return undefined;
    }
    if (typeof timeout === "number") {
        if (timeout <= 0) {
            throw new Error(`Timeout must be positive, got: ${timeout}`);
        }
        return timeout; // Assume milliseconds
    }
    if (typeof timeout === "string") {
        // Match number (including decimals) followed by optional unit
        const match = timeout.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/);
        if (!match) {
            throw new Error(`Invalid timeout format: ${timeout}. Use formats like '30s', '2m', '500ms', or '1.5h'`);
        }
        const value = parseFloat(match[1]);
        if (value <= 0) {
            throw new Error(`Timeout must be positive, got: ${value}`);
        }
        const unit = match[2] || "ms";
        switch (unit) {
            case "ms":
                return value;
            case "s":
                return value * 1000;
            case "m":
                return value * 60 * 1000;
            case "h":
                return value * 60 * 60 * 1000;
            default:
                return value; // Should never reach here due to regex
        }
    }
    throw new Error(`Invalid timeout type: ${typeof timeout}`);
}
/**
 * Default timeout configurations for different providers and operations
 */
export const DEFAULT_TIMEOUTS = {
    global: "30s", // Default for all providers
    streaming: "2m", // Longer timeout for streaming operations
    providers: {
        openai: "30s", // OpenAI typically responds quickly
        bedrock: "45s", // AWS can be slower, especially for cold starts
        vertex: "60s", // Google Cloud can be slower
        anthropic: "60s", // Increased timeout for Anthropic API stability
        azure: "30s", // Azure OpenAI similar to OpenAI
        "google-ai": "30s", // Google AI Studio is fast
        huggingface: "2m", // Open source models vary significantly
        ollama: "5m", // Local models need more time, especially large ones
        mistral: "45s", // Mistral AI moderate speed
    },
    tools: {
        default: "10s", // Default timeout for MCP tool execution
        filesystem: "5s", // File operations should be quick
        network: "30s", // Network requests might take longer
        computation: "2m", // Heavy computation tools need more time
    },
};
/**
 * Get default timeout for a specific provider
 * @param provider - Provider name
 * @param operation - Operation type (generate or stream)
 * @returns Default timeout string
 */
export function getDefaultTimeout(provider, operation = "generate") {
    if (operation === "stream") {
        return DEFAULT_TIMEOUTS.streaming;
    }
    const providerKey = provider.toLowerCase().replace("_", "-");
    return (DEFAULT_TIMEOUTS.providers[providerKey] || DEFAULT_TIMEOUTS.global);
}
/**
 * Create a timeout promise that rejects after specified duration
 * @param timeout - Timeout duration
 * @param provider - Provider name for error message
 * @param operation - Operation type for error message
 * @returns Promise that rejects with TimeoutError
 */
export function createTimeoutPromise(timeout, provider, operation) {
    const timeoutMs = parseTimeout(timeout);
    if (!timeoutMs) {
        return null; // No timeout
    }
    return new Promise((_, reject) => {
        const timer = setTimeout(() => {
            reject(new TimeoutError(`${provider} ${operation} operation timed out after ${timeout}`, timeoutMs, provider, operation));
        }, timeoutMs);
        // Unref the timer so it doesn't keep the process alive (Node.js only)
        if (typeof timer === "object" &&
            timer &&
            "unref" in timer &&
            typeof timer.unref === "function") {
            timer.unref();
        }
    });
}
/**
 * Enhanced timeout manager with proper cleanup and abort controller integration
 * Consolidated from timeout-manager.ts
 */
export class TimeoutManager {
    activeTimeouts = new Map();
    /**
     * Execute operation with timeout and proper cleanup
     */
    async executeWithTimeout(operation, config) {
        const startTime = Date.now();
        const operationId = this.generateOperationId(config.operation);
        let retriesUsed = 0;
        const maxRetries = config.retryOnTimeout ? (config.maxRetries ?? 1) : 0;
        while (retriesUsed <= maxRetries) {
            try {
                const result = await this.performSingleOperation(operation, config, operationId);
                return {
                    success: true,
                    data: result,
                    timedOut: false,
                    executionTime: Date.now() - startTime,
                    retriesUsed,
                };
            }
            catch (error) {
                this.cleanup(operationId);
                if (error instanceof TimeoutError && retriesUsed < maxRetries) {
                    retriesUsed++;
                    continue;
                }
                return {
                    success: false,
                    error: error instanceof Error ? error : new Error(String(error)),
                    timedOut: error instanceof TimeoutError,
                    executionTime: Date.now() - startTime,
                    retriesUsed,
                };
            }
        }
        return {
            success: false,
            error: new Error("Maximum retries exceeded"),
            timedOut: true,
            executionTime: Date.now() - startTime,
            retriesUsed,
        };
    }
    async performSingleOperation(operation, config, operationId) {
        const timeoutMs = this.getTimeoutMs(config);
        if (!timeoutMs) {
            return await operation();
        }
        const controller = new AbortController();
        const existingSignal = config.abortSignal;
        if (existingSignal) {
            existingSignal.addEventListener("abort", () => {
                controller.abort(existingSignal.reason);
            });
            if (existingSignal.aborted) {
                throw new Error("Operation aborted before execution");
            }
        }
        const timeoutPromise = this.createTimeoutPromise(timeoutMs, operationId);
        this.registerTimeout(operationId, timeoutPromise.timer, controller, () => {
            clearTimeout(timeoutPromise.timer);
        });
        try {
            return await Promise.race([operation(), timeoutPromise.promise]);
        }
        finally {
            this.cleanup(operationId);
        }
    }
    getTimeoutMs(config) {
        return parseTimeout(config.timeout);
    }
    generateOperationId(operation) {
        return `${operation}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    }
    createTimeoutPromise(timeoutMs, _operationId) {
        let timer;
        const promise = new Promise((_, reject) => {
            timer = setTimeout(() => {
                reject(new TimeoutError(`Operation timeout after ${timeoutMs}ms`, timeoutMs));
            }, timeoutMs);
        });
        if (!timer) {
            throw new Error("Failed to create timeout timer");
        }
        return { promise, timer };
    }
    registerTimeout(operationId, timer, controller, cleanup) {
        this.activeTimeouts.set(operationId, { timer, controller, cleanup });
    }
    cleanup(operationId) {
        const timeoutInfo = this.activeTimeouts.get(operationId);
        if (timeoutInfo) {
            timeoutInfo.cleanup();
            this.activeTimeouts.delete(operationId);
        }
    }
    gracefulShutdown() {
        for (const [operationId] of this.activeTimeouts) {
            this.cleanup(operationId);
        }
    }
}
/**
 * Wrapper functions consolidated from timeout-wrapper.ts
 */
/**
 * Wrap a promise with timeout
 * @param promise - The promise to wrap
 * @param timeout - Timeout duration (number in ms or string with unit)
 * @param provider - Provider name for error messages
 * @param operation - Operation type (generate or stream)
 * @returns The result of the promise or throws TimeoutError
 */
export async function withTimeout(promise, timeout, provider, operation) {
    const timeoutMs = parseTimeout(timeout);
    if (!timeoutMs) {
        return promise;
    }
    const timeoutPromise = new Promise((_, reject) => {
        setTimeout(() => {
            reject(new TimeoutError(`${provider} ${operation} operation timed out after ${timeoutMs}ms`, timeoutMs, provider, operation));
        }, timeoutMs);
    });
    return Promise.race([promise, timeoutPromise]);
}
/**
 * Wrap a streaming async generator with timeout
 * @param generator - The async generator to wrap
 * @param timeout - Timeout duration for the entire stream
 * @param provider - Provider name for error messages
 * @returns Wrapped async generator that respects timeout
 */
export async function* withStreamingTimeout(generator, timeout, provider) {
    const timeoutMs = parseTimeout(timeout);
    if (!timeoutMs) {
        yield* generator;
        return;
    }
    let timeoutId;
    const timeoutPromise = new Promise((_, reject) => {
        timeoutId = setTimeout(() => {
            reject(new TimeoutError(`${provider} streaming operation timed out after ${timeoutMs}ms`, timeoutMs, provider, "stream"));
        }, timeoutMs);
    });
    try {
        for await (const item of generator) {
            const raceResult = await Promise.race([
                Promise.resolve(item),
                timeoutPromise,
            ]);
            yield raceResult;
        }
    }
    finally {
        if (timeoutId) {
            clearTimeout(timeoutId);
        }
    }
}
/**
 * Create an abort controller with timeout
 * @param timeout - Timeout duration
 * @param provider - Provider name for error messages
 * @param operation - Operation type
 * @returns AbortController and cleanup function
 */
export function createTimeoutController(timeout, provider, operation) {
    const timeoutMs = parseTimeout(timeout);
    if (!timeoutMs) {
        return null;
    }
    const controller = new AbortController();
    const timer = setTimeout(() => {
        controller.abort(new TimeoutError(`${provider} ${operation} operation timed out after ${timeout}`, timeoutMs, provider, operation));
    }, timeoutMs);
    const cleanup = () => {
        clearTimeout(timer);
    };
    return { controller, cleanup, timeoutMs };
}
/**
 * Compose an external abort signal with a timeout controller's signal.
 * Returns a single AbortSignal that fires when either signal aborts.
 * If only one signal is present, returns it directly without wrapping.
 *
 * @param externalSignal - User-provided AbortSignal (e.g., from options.abortSignal)
 * @param timeoutSignal - Timeout controller's signal
 * @returns Combined AbortSignal, or undefined if neither is present
 */
export function composeAbortSignals(externalSignal, timeoutSignal) {
    if (externalSignal && timeoutSignal) {
        return AbortSignal.any([externalSignal, timeoutSignal]);
    }
    return externalSignal ?? timeoutSignal;
}
/**
 * Merge abort signals (for combining user abort with timeout)
 * @param signals - Array of abort signals to merge
 * @returns Combined abort controller
 */
export function mergeAbortSignals(signals) {
    const controller = new AbortController();
    for (const signal of signals) {
        if (signal && !signal.aborted) {
            signal.addEventListener("abort", () => {
                if (!controller.signal.aborted) {
                    controller.abort(signal.reason);
                }
            });
        }
        if (signal?.aborted) {
            controller.abort(signal.reason);
            break;
        }
    }
    return controller;
}
//# sourceMappingURL=timeout.js.map