UNPKG

@future-agi/sdk

Version:

We help GenAI teams maintain high-accuracy for their Models in production.

246 lines 9.45 kB
import axios, { AxiosError } from 'axios'; import { BoundedExecutor } from '../utils/executor'; import { MissingAuthError, DatasetNotFoundError, InvalidAuthError, RateLimitError, ServerError, ServiceUnavailableError } from '../utils/errors'; import { AUTH_ENVVAR_NAME, DEFAULT_SETTINGS, get_base_url } from '../utils/constants'; /** * Generic response handler for parsing and validating HTTP responses */ export class ResponseHandler { /** * Parse the response into the expected type */ static parse(response, handlerClass) { if (!response || response.status !== 200) { handlerClass._handleError(response); } return handlerClass._parseSuccess(response); } /** * Parse successful response - to be implemented by subclasses */ static _parseSuccess(response) { throw new Error("Method '_parseSuccess' must be implemented by subclass."); } /** * Handle error responses - to be implemented by subclasses */ static _handleError(response) { var _a; const status = (response === null || response === void 0 ? void 0 : response.status) || 500; let message; if (response === null || response === void 0 ? void 0 : response.data) { const d = response.data; message = d.message || d.detail || d.result; // If nothing explicit, stringify the whole payload so callers see something meaningful if (!message) { try { message = JSON.stringify(d); } catch (_b) { /* ignore */ } } } if (!message) { const url = ((_a = response === null || response === void 0 ? void 0 : response.config) === null || _a === void 0 ? void 0 : _a.url) ? ` – ${response.config.url}` : ''; message = ((response === null || response === void 0 ? void 0 : response.statusText) && response.statusText.trim().length > 0) ? response.statusText : `HTTP ${status}${url}`; } switch (status) { case 401: case 403: throw new InvalidAuthError(message); case 404: throw new DatasetNotFoundError(message); case 429: throw new RateLimitError(message); case 503: throw new ServiceUnavailableError(message); case 500: case 502: case 504: throw new ServerError(message); default: throw new Error(`HTTP ${status}: ${message}`); } } } /** * Base HTTP client with improved request handling, connection pooling, and async execution */ export class HttpClient { constructor(config = {}) { this._baseUrl = (config.baseUrl || get_base_url()).replace(/\/$/, ''); this._defaultTimeout = config.timeout || DEFAULT_SETTINGS.TIMEOUT; this._defaultRetryAttempts = config.retryAttempts || 3; this._defaultRetryDelay = config.retryDelay || 1000; // Create axios instance with default configuration this._axiosInstance = axios.create({ baseURL: this._baseUrl, timeout: this._defaultTimeout, headers: Object.assign({ 'Content-Type': 'application/json', 'User-Agent': '@future-agi/sdk' }, config.defaultHeaders), // Enable connection pooling maxRedirects: 5, validateStatus: () => true, // Handle all status codes manually }); // Create bounded executor for managing concurrent requests this._executor = new BoundedExecutor(config.maxQueue || DEFAULT_SETTINGS.MAX_QUEUE, config.maxWorkers || DEFAULT_SETTINGS.MAX_WORKERS); this._setupInterceptors(); } /** * Setup request and response interceptors */ _setupInterceptors() { // Request interceptor for logging and debugging this._axiosInstance.interceptors.request.use((config) => { // Add request timestamp for performance monitoring config.startTime = Date.now(); return config; }, (error) => Promise.reject(error)); // Response interceptor for logging and performance monitoring this._axiosInstance.interceptors.response.use((response) => { const duration = Date.now() - (response.config.startTime || 0); // Could add logging here for production monitoring return response; }, (error) => Promise.reject(error)); } /** * Make an HTTP request with retries and response handling */ async request(config, responseHandler) { const requestConfig = { method: config.method, url: config.url, headers: config.headers, params: config.params, data: config.json || config.data, timeout: config.timeout || this._defaultTimeout, }; // Handle file uploads if (config.files && Object.keys(config.files).length > 0) { const formData = new FormData(); Object.entries(config.files).forEach(([key, file]) => { formData.append(key, file); }); if (config.data) { Object.entries(config.data).forEach(([key, value]) => { formData.append(key, value); }); } requestConfig.data = formData; requestConfig.headers = Object.assign(Object.assign({}, requestConfig.headers), { 'Content-Type': 'multipart/form-data' }); } const retryAttempts = config.retry_attempts || this._defaultRetryAttempts; const retryDelay = config.retry_delay || this._defaultRetryDelay; // Execute request with bounded concurrency return this._executor.submit(async () => { var _a; for (let attempt = 0; attempt < retryAttempts; attempt++) { try { const response = await this._axiosInstance.request(requestConfig); if (responseHandler) { return ResponseHandler.parse(response, responseHandler); } // Handle errors if no custom handler if (response.status >= 400) { ResponseHandler._handleError(response); } return response; } catch (error) { // Don't retry certain errors if (error instanceof DatasetNotFoundError || error instanceof InvalidAuthError || (error instanceof AxiosError && ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401)) { throw error; } // Last attempt - throw the error if (attempt === retryAttempts - 1) { if (error instanceof AxiosError) { ResponseHandler._handleError(error.response); } throw error; } // Wait before retry await this._sleep(retryDelay * Math.pow(2, attempt)); // Exponential backoff } } throw new Error('Unexpected end of retry loop'); }); } /** * Helper method for sleep/delay */ async _sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Close the client and cleanup resources */ async close() { await this._executor.shutdown(true); } /** * Get the base URL */ get baseUrl() { return this._baseUrl; } /** * Get the default timeout */ get defaultTimeout() { return this._defaultTimeout; } } /** * HTTP client with API key authentication */ export class APIKeyAuth extends HttpClient { constructor(config = {}) { const fiApiKey = config.fiApiKey || process.env[AUTH_ENVVAR_NAME.API_KEY]; const fiSecretKey = config.fiSecretKey || process.env[AUTH_ENVVAR_NAME.SECRET_KEY]; if (!fiApiKey || !fiSecretKey) { throw new MissingAuthError(fiApiKey, fiSecretKey); } super(Object.assign(Object.assign({}, config), { baseUrl: config.fiBaseUrl || config.baseUrl, defaultHeaders: Object.assign({ 'X-Api-Key': fiApiKey, 'X-Secret-Key': fiSecretKey }, config.defaultHeaders) })); // Set class-level credentials this._fiApiKey = fiApiKey; this._fiSecretKey = fiSecretKey; } /** * Get the current API key */ get fiApiKey() { return this._fiApiKey; } /** * Get the current secret key */ get fiSecretKey() { return this._fiSecretKey; } /** * Get authentication headers */ get headers() { return { 'X-Api-Key': this._fiApiKey, 'X-Secret-Key': this._fiSecretKey, }; } } /** * Factory function to create authenticated HTTP client */ export function createAuthenticatedClient(config) { return new APIKeyAuth(config); } export default { ResponseHandler, HttpClient, APIKeyAuth, createAuthenticatedClient, }; //# sourceMappingURL=auth.js.map