UNPKG

venice-ai-sdk-apl

Version:

A comprehensive SDK for the Venice AI API with CLI support, programmatic CLI usage, CLI-style interface, and interactive demo

350 lines 13 kB
"use strict"; /** * HTTP client for making API requests * * This module provides a wrapper around axios for making HTTP requests to the Venice AI API. * It handles authentication, error handling, and response parsing. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HttpClient = void 0; const axios_1 = __importDefault(require("axios")); const form_data_1 = __importDefault(require("form-data")); const api_error_1 = require("../errors/api-error"); const rate_limit_error_1 = require("../errors/rate-limit-error"); const logger_1 = require("./logger"); /** * HTTP client for making API requests */ class HttpClient { /** * Creates a new HTTP client * * @param config - Client configuration */ constructor(config) { this.config = config; logger_1.Logger.debug('Creating HTTP client', { baseUrl: config.baseUrl, timeout: config.timeout }); // Create axios instance with default configuration this.client = axios_1.default.create({ baseURL: config.baseUrl, timeout: config.timeout, headers: { 'Authorization': `Bearer ${config.apiKey}`, 'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'Venice-AI-SDK-APL/0.1.0', }, }); // Add response interceptor for error handling this.client.interceptors.response.use(this.handleSuccess.bind(this), this.handleError.bind(this)); // Add request interceptor for logging this.client.interceptors.request.use((config) => { logger_1.Logger.debug(`Request: ${config.method?.toUpperCase()} ${config.url}`, { headers: this.sanitizeHeaders(config.headers || {}), params: config.params, data: this.sanitizeData(config.data), }); return config; }); } /** * Gets the base URL for API requests * * @returns The base URL */ getBaseURL() { return this.config.baseUrl; } /** * Gets the API key for authentication * * @returns The API key */ getApiKey() { return this.config.apiKey; } /** * Makes an HTTP request to the API * * @param params - Request parameters * @returns Promise that resolves with the response data */ async request(params) { const config = { url: params.path, method: params.method, params: params.query, data: params.body, headers: params.headers, timeout: params.timeout || this.config.timeout, }; // Handle form data if (params.formData) { const formData = new form_data_1.default(); Object.entries(params.formData).forEach(([key, value]) => { formData.append(key, value); }); config.data = formData; config.headers = { ...config.headers, ...formData.getHeaders(), }; } // Handle streaming and responseType if (params.stream) { config.responseType = 'stream'; } else if (params.responseType) { config.responseType = params.responseType; } try { logger_1.Logger.debug(`Making ${params.method} request to ${params.path}`); const response = await this.client.request(config); return response.data; } catch (error) { // This should be handled by the error interceptor, // but we'll catch any unexpected errors here if (error instanceof Error) { logger_1.Logger.error(`Unexpected error in request: ${error.message}`, error); throw new api_error_1.ApiError({ message: error.message, status: 500, code: 'UNKNOWN_ERROR', }); } throw error; } } /** * Handles successful responses * * @param response - Axios response * @returns Response data */ handleSuccess(response) { logger_1.Logger.debug(`Response: ${response.status} ${response.statusText}`, { headers: response.headers, data: this.sanitizeData(response.data), }); // Extract rate limit information from headers const rateLimitInfo = this.extractRateLimitInfo(response); // Extract balance information from headers const balanceInfo = this.extractBalanceInfo(response); // Check if the response is binary data (Buffer or ArrayBuffer) const isBinaryResponse = response.data instanceof Buffer || response.data instanceof ArrayBuffer || (response.config?.responseType === 'arraybuffer'); if (isBinaryResponse) { // For binary responses, wrap the data in an object with the binary property // and add metadata without modifying the original binary data const binaryData = Buffer.isBuffer(response.data) ? response.data : Buffer.from(response.data); response.data = { binary: binaryData, _metadata: { rateLimit: rateLimitInfo, balance: balanceInfo, }, }; } else { // For JSON responses, add metadata to the existing object response.data = { ...response.data, _metadata: { rateLimit: rateLimitInfo, balance: balanceInfo, }, }; } return response; } /** * Handles error responses * * @param error - Axios error * @throws ApiError or RateLimitError */ handleError(error) { if (axios_1.default.isAxiosError(error) && error.response) { const { response } = error; const status = response.status; const data = response.data || {}; logger_1.Logger.error(`API Error: ${status} ${response.statusText}`, { url: error.config?.url, method: error.config?.method, status, data, }); // Handle rate limit errors if (status === 429) { const rateLimitInfo = this.extractRateLimitInfo(response); throw new rate_limit_error_1.RateLimitError({ message: data.message || 'Rate limit exceeded', status, code: data.code || 'RATE_LIMIT_EXCEEDED', rateLimitInfo, }); } // Handle other API errors throw new api_error_1.ApiError({ message: data.message || 'API request failed', status, code: data.code || 'API_ERROR', data: data, }); } // Handle network errors if (axios_1.default.isAxiosError(error) && error.request) { logger_1.Logger.error(`Network Error: ${error.message}`, { url: error.config?.url, method: error.config?.method, }); throw new api_error_1.ApiError({ message: 'Network error', status: 0, code: 'NETWORK_ERROR', }); } // Handle other errors logger_1.Logger.error(`Unknown Error: ${error.message || 'Unknown error'}`, error); throw error; } /** * Extracts rate limit information from response headers * * @param response - Axios response * @returns Rate limit information */ extractRateLimitInfo(response) { const headers = response.headers; if (headers['x-ratelimit-limit-requests'] && headers['x-ratelimit-remaining-requests'] && headers['x-ratelimit-reset-requests']) { return { limit: parseInt(headers['x-ratelimit-limit-requests'], 10), remaining: parseInt(headers['x-ratelimit-remaining-requests'], 10), reset: parseInt(headers['x-ratelimit-reset-requests'], 10), tokensUsed: headers['x-ratelimit-limit-tokens'] ? parseInt(headers['x-ratelimit-limit-tokens'], 10) : undefined, tokensRemaining: headers['x-ratelimit-remaining-tokens'] ? parseInt(headers['x-ratelimit-remaining-tokens'], 10) : undefined, tokensReset: headers['x-ratelimit-reset-tokens'] ? parseInt(headers['x-ratelimit-reset-tokens'], 10) : undefined, }; } return undefined; } /** * Extracts balance information from response headers * * @param response - Axios response * @returns Balance information */ extractBalanceInfo(response) { const headers = response.headers; if (headers['x-venice-balance-vcu'] || headers['x-venice-balance-usd']) { return { vcu: headers['x-venice-balance-vcu'] ? parseFloat(headers['x-venice-balance-vcu']) : 0, usd: headers['x-venice-balance-usd'] ? parseFloat(headers['x-venice-balance-usd']) : 0, }; } return undefined; } /** * Sanitizes headers for logging * * @param headers - Headers to sanitize * @returns Sanitized headers */ sanitizeHeaders(headers) { const sanitized = { ...headers }; // Remove sensitive information if (sanitized.Authorization) { sanitized.Authorization = 'Bearer [REDACTED]'; } return sanitized; } /** * Sanitizes data for logging * * @param data - Data to sanitize * @returns Sanitized data */ sanitizeData(data) { if (!data) { return data; } // For simple types, check if it's a string that might be image data if (typeof data === 'string') { // Check if it's likely base64 encoded image data if (data.length > 1000 && (data.startsWith('data:image') || data.startsWith('/9j/') || // JPEG data.startsWith('iVBOR') || // PNG /^[A-Za-z0-9+/=]{1000,}$/.test(data))) { return '[IMAGE DATA EXCLUDED]'; } return data; } // For FormData, return a placeholder if (data instanceof form_data_1.default) { return '[FormData]'; } // For streams, return a placeholder if (typeof data.pipe === 'function') { return '[Stream]'; } // For Buffer or ArrayBuffer, check if it might be image data if (data instanceof Buffer || data instanceof ArrayBuffer) { return '[BINARY DATA EXCLUDED]'; } // For objects, create a copy and sanitize sensitive fields try { const sanitized = JSON.parse(JSON.stringify(data)); // Sanitize API keys if (sanitized.apiKey) { sanitized.apiKey = '[REDACTED]'; } // Sanitize authorization headers if (sanitized.headers && sanitized.headers.Authorization) { sanitized.headers.Authorization = 'Bearer [REDACTED]'; } // Sanitize image data for (const key in sanitized) { if (Object.prototype.hasOwnProperty.call(sanitized, key)) { // Check for common image field names if (['image', 'data', 'content', 'file', 'buffer', 'base64'].includes(key.toLowerCase())) { const value = sanitized[key]; // Check if value is likely image data (string longer than 1000 chars) if (typeof value === 'string' && value.length > 1000) { sanitized[key] = '[IMAGE DATA EXCLUDED]'; } } else if (typeof sanitized[key] === 'object' && sanitized[key] !== null) { // Recursively sanitize nested objects sanitized[key] = this.sanitizeData(sanitized[key]); } } } return sanitized; } catch (error) { return '[Object could not be stringified]'; } } } exports.HttpClient = HttpClient; //# sourceMappingURL=http.js.map