UNPKG

ai-functions

Version:

A powerful TypeScript library for building AI-powered applications with template literals and structured outputs

105 lines 4.05 kB
import { AIRequestError } from '../types'; import PQueue from 'p-queue'; export class RequestHandler { constructor(options = {}) { this.retryOptions = { maxRetries: options.maxRetries ?? 2, initialDelay: options.retryDelay ?? 100, maxDelay: options.timeout ?? 1000, backoffFactor: 2 }; this.queue = new PQueue({ concurrency: 1 }); this.streamingTimeout = options.streamingTimeout ?? 30000; } async executeWithRetry(operation, retryable = true, isStreaming = false) { let lastError; let attempt = 0; const maxAttempts = retryable ? this.retryOptions.maxRetries : 0; const timeoutMs = isStreaming ? this.streamingTimeout : this.retryOptions.maxDelay; const executeWithTimeout = async (op) => { const abortController = new AbortController(); const timeoutId = setTimeout(() => { abortController.abort(); }, timeoutMs); try { if (op instanceof Promise) { const result = await Promise.race([ op, new Promise((_, reject) => { abortController.signal.addEventListener('abort', () => { reject(new AIRequestError(`Request timed out after ${timeoutMs}ms`)); }); }) ]); return result; } else { const chunks = []; for await (const chunk of op) { if (abortController.signal.aborted) { throw new AIRequestError(`Stream timed out after ${timeoutMs}ms`); } chunks.push(chunk); } if (chunks.length === 0) { throw new AIRequestError('No chunks received from stream'); } return chunks[chunks.length - 1]; } } finally { clearTimeout(timeoutId); } }; while (attempt <= maxAttempts) { try { return await executeWithTimeout(operation()); } catch (error) { lastError = error; if (!retryable || error instanceof AIRequestError && !error.retryable) { throw error; } if (attempt >= maxAttempts) { throw new AIRequestError(`Failed after ${attempt + 1} attempts: ${lastError?.message || 'Unknown error'}`, undefined, false); } const delay = Math.min(this.retryOptions.initialDelay * Math.pow(this.retryOptions.backoffFactor, attempt), this.retryOptions.maxDelay); await new Promise(resolve => setTimeout(resolve, delay)); attempt++; } } throw lastError || new AIRequestError('Operation failed with no error details'); } execute(operation, retryable = true, isStreaming = false) { const executeOperation = async () => { const result = await this.executeWithRetry(operation, retryable, isStreaming); if (result === undefined) { throw new AIRequestError('Operation returned undefined result'); } return result; }; return this.queue.add(executeOperation); } get concurrency() { return this.queue?.concurrency; } get size() { return this.queue?.size ?? 0; } get pending() { return this.queue?.pending ?? 0; } clear() { this.queue?.clear(); } pause() { this.queue?.pause(); } resume() { this.queue?.start(); } } export function createRequestHandler(options = {}) { return new RequestHandler(options); } //# sourceMappingURL=request-handler.js.map