ai-functions
Version:
A powerful TypeScript library for building AI-powered applications with template literals and structured outputs
105 lines • 4.05 kB
JavaScript
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