llmforge
Version:
One API, every AI model, instant switching. Change from GPT-4 to Gemini to local models with a single config update. LLMForge is the lightweight, TypeScript-first solution for multi-provider AI applications with zero vendor lock-in.
112 lines • 4.24 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpClient = void 0;
// http-client.ts
const types_1 = require("../types");
const expo_1 = require("../strategies/retry/expo");
const undici_1 = require("undici");
class HttpClient {
constructor(config, baseUrl, retryHandler) {
this.config = {
baseUrl: baseUrl, // 'https://generativelanguage.googleapis.com',
timeout: 30000,
maxRetries: 1,
retryDelay: 1000,
...config,
};
this.retryHandler = retryHandler || new expo_1.RetryHandler({
maxRetries: this.config.maxRetries,
retryDelay: this.config.retryDelay,
});
}
async request(endpoint, options = {}, isStream = false) {
const url = `${this.config.baseUrl}${endpoint}?key=${this.config.apiKey}`;
// Convert RequestInit to undici's RequestOptions
const undiciOptions = {
path: `${endpoint}?key=${this.config.apiKey}`,
method: options.method || 'POST',
headers: Object.fromEntries(Object.entries({
'Content-Type': 'application/json',
...options.headers,
}).map(([k, v]) => [k, v === null || v === void 0 ? void 0 : v.toString()])),
body: options.body,
signal: this.createTimeoutSignal(this.config.timeout),
};
const operation = async () => {
const { statusCode, body } = await (0, undici_1.request)(url, undiciOptions);
if (statusCode >= 400) {
await this.handleErrorResponse(statusCode, body);
}
if (isStream) {
// For streaming, return the readable stream
return body;
}
// For non-streaming, parse JSON
const chunks = [];
for await (const chunk of body) {
chunks.push(chunk);
}
const responseText = Buffer.concat(chunks).toString();
return JSON.parse(responseText);
};
return this.retryHandler.executeWithRetry(operation, `${options.method || 'POST'} ${endpoint}`);
}
async streamRequest(endpoint, options = {}) {
return this.request(endpoint, options, true);
}
async handleErrorResponse(statusCode, body) {
let errorData;
try {
const chunks = [];
for await (const chunk of body) {
chunks.push(chunk);
}
const responseText = Buffer.concat(chunks).toString();
errorData = JSON.parse(responseText);
}
catch (_a) {
errorData = {
error: {
code: statusCode,
message: this.getStatusText(statusCode) || 'Unknown error',
status: this.getStatusText(statusCode),
},
};
}
const { code, message, status, details } = errorData.error;
const isRetryable = this.isRetryableStatusCode(code);
const ErrorClass = isRetryable ? types_1.RetryableError : types_1.NonRetryableError;
throw new ErrorClass(message, code, status, details);
}
isRetryableStatusCode(statusCode) {
const retryableCodes = [429, 500, 502, 503, 504];
return retryableCodes.includes(statusCode);
}
getStatusText(statusCode) {
const statusTexts = {
400: 'BAD_REQUEST',
401: 'UNAUTHORIZED',
403: 'FORBIDDEN',
404: 'NOT_FOUND',
429: 'RATE_LIMITED',
500: 'INTERNAL_SERVER_ERROR',
502: 'BAD_GATEWAY',
503: 'SERVICE_UNAVAILABLE',
504: 'GATEWAY_TIMEOUT',
};
return statusTexts[statusCode] || 'UNKNOWN_ERROR';
}
createTimeoutSignal(timeout) {
const controller = new AbortController();
setTimeout(() => controller.abort(), timeout);
return controller.signal;
}
updateConfig(newConfig) {
this.config = { ...this.config, ...newConfig };
}
getConfig() {
return { ...this.config };
}
}
exports.HttpClient = HttpClient;
//# sourceMappingURL=http-client.js.map