svector-sdk
Version:
Official JavaScript and TypeScript SDK for accessing SVECTOR APIs.
331 lines (330 loc) • 13.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SVECTOR = void 0;
const chat_1 = require("./api/chat");
const conversations_1 = require("./api/conversations");
const files_1 = require("./api/files");
const knowledge_1 = require("./api/knowledge");
const models_1 = require("./api/models");
const vision_1 = require("./api/vision");
const errors_1 = require("./errors");
function isNode() {
return typeof process !== 'undefined' &&
process.versions != null &&
process.versions.node != null;
}
function isBrowser() {
return typeof window !== 'undefined' &&
typeof window.document !== 'undefined';
}
function isWebWorker() {
return typeof globalThis.importScripts === 'function' &&
typeof self !== 'undefined' &&
typeof window === 'undefined';
}
function isDeno() {
return typeof globalThis.Deno !== 'undefined';
}
function isEdgeRuntime() {
return typeof globalThis.EdgeRuntime !== 'undefined';
}
function isSSR() {
return typeof window === 'undefined' &&
typeof process !== 'undefined' &&
process.env !== undefined;
}
class SVECTOR {
constructor(options = {}) {
this.apiKey = options.apiKey || this.getApiKeyFromEnv();
this.baseURL = options.baseURL?.replace(/\/+$/, '') || 'https://spec-chat.tech';
this.maxRetries = options.maxRetries ?? 2;
this.timeout = options.timeout ?? 10 * 60 * 1000;
this.fetch = options.fetch || this.getDefaultFetch();
this.dangerouslyAllowBrowser = options.dangerouslyAllowBrowser ?? false;
if (!this.apiKey) {
throw new errors_1.AuthenticationError('SVECTOR API key is required. Set it via the apiKey option or SVECTOR_API_KEY environment variable.');
}
this.checkBrowserEnvironment();
this.chat = new chat_1.ChatCompletions(this);
this.conversations = new conversations_1.Conversations(this);
this.models = new models_1.Models(this);
this.files = new files_1.Files(this);
this.knowledge = new knowledge_1.Knowledge(this);
this.vision = new vision_1.Vision(this);
}
getApiKeyFromEnv() {
if (typeof process !== 'undefined' && process.env) {
return process.env['SVECTOR_API_KEY'] || '';
}
return '';
}
getDefaultFetch() {
if (typeof globalThis !== 'undefined' && globalThis.fetch) {
return globalThis.fetch.bind(globalThis);
}
if (typeof window !== 'undefined' && window.fetch) {
return window.fetch.bind(window);
}
if (typeof global !== 'undefined' && global.fetch) {
return global.fetch;
}
if (isDeno() && globalThis.fetch) {
return globalThis.fetch;
}
if (isWebWorker() && typeof self !== 'undefined' && self.fetch) {
return self.fetch;
}
if (isNode()) {
try {
const nodeFetch = require('node-fetch');
return nodeFetch.default || nodeFetch;
}
catch (error) {
console.warn('SVECTOR SDK: node-fetch not available, falling back to native fetch');
}
}
throw new Error('No fetch implementation found. Please provide a fetch function via the fetch option, ' +
'or install node-fetch in Node.js environments.');
}
checkBrowserEnvironment() {
if (isSSR()) {
return;
}
if (isEdgeRuntime()) {
return;
}
if (isWebWorker()) {
return;
}
if (isDeno()) {
return;
}
if (isBrowser() && !this.dangerouslyAllowBrowser) {
throw new Error('SVECTOR SDK is being used in a browser environment without dangerouslyAllowBrowser set to true. ' +
'This is strongly discouraged as it exposes your API key to client-side code. ' +
'If you understand the risks, set dangerouslyAllowBrowser: true in the constructor options.');
}
}
async request(method, path, body, options = {}) {
const url = new URL(`${this.baseURL}${path}`);
if (options.query) {
Object.entries(options.query).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
}
const headers = this.buildHeaders(body, options.headers);
const maxRetries = options.maxRetries ?? this.maxRetries;
const timeout = options.timeout ?? this.timeout;
let retries = 0;
while (retries <= maxRetries) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const requestBody = this.prepareRequestBody(body);
const response = await this.fetch(url.toString(), {
method,
headers,
body: requestBody,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorData = await this.parseErrorResponse(response);
throw this.createErrorFromResponse(response, errorData);
}
const responseData = await response.json();
const requestId = response.headers.get('x-request-id');
if (requestId && typeof responseData === 'object' && responseData !== null) {
responseData._request_id = requestId;
}
return responseData;
}
catch (err) {
if (err instanceof Error && err.name === 'AbortError') {
throw new errors_1.APIConnectionTimeoutError('Request timed out');
}
if (err instanceof errors_1.APIError && this.shouldRetry(err.status, retries, maxRetries)) {
retries++;
await this.sleep(this.calculateBackoffDelay(retries));
continue;
}
if (retries === maxRetries) {
throw new errors_1.APIConnectionError('Max retries exceeded');
}
throw err;
}
}
throw new errors_1.APIConnectionError('Unexpected error');
}
async requestStream(method, path, body, options = {}) {
const url = new URL(`${this.baseURL}${path}`);
if (options.query) {
Object.entries(options.query).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
}
const headers = {
...this.buildHeaders(body, options.headers),
'Accept': 'text/event-stream',
};
const controller = new AbortController();
const timeout = options.timeout ?? this.timeout;
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await this.fetch(url.toString(), {
method,
headers,
body: this.prepareRequestBody(body),
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorData = await this.parseErrorResponse(response);
throw this.createErrorFromResponse(response, errorData);
}
return response;
}
catch (err) {
clearTimeout(timeoutId);
if (err instanceof Error && err.name === 'AbortError') {
throw new errors_1.APIConnectionTimeoutError('Request timed out');
}
throw err;
}
}
async withResponse(method, path, body, options = {}) {
const url = new URL(`${this.baseURL}${path}`);
if (options.query) {
Object.entries(options.query).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
}
const headers = this.buildHeaders(body, options.headers);
const maxRetries = options.maxRetries ?? this.maxRetries;
const timeout = options.timeout ?? this.timeout;
let retries = 0;
while (retries <= maxRetries) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const requestBody = this.prepareRequestBody(body);
const response = await this.fetch(url.toString(), {
method,
headers,
body: requestBody,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorData = await this.parseErrorResponse(response);
throw this.createErrorFromResponse(response, errorData);
}
const responseData = await response.json();
const requestId = response.headers.get('x-request-id');
if (requestId && typeof responseData === 'object' && responseData !== null) {
responseData._request_id = requestId;
}
return { data: responseData, response };
}
catch (err) {
if (err instanceof Error && err.name === 'AbortError') {
throw new errors_1.APIConnectionTimeoutError('Request timed out');
}
if (err instanceof errors_1.APIError && this.shouldRetry(err.status, retries, maxRetries)) {
retries++;
await this.sleep(this.calculateBackoffDelay(retries));
continue;
}
if (retries === maxRetries) {
throw new errors_1.APIConnectionError('Max retries exceeded');
}
throw err;
}
}
throw new errors_1.APIConnectionError('Unexpected error');
}
buildHeaders(body, customHeaders = {}) {
const headers = {
'Authorization': `Bearer ${this.apiKey}`,
'User-Agent': 'svector-sdk/1.0.0',
...customHeaders,
};
if (body && !(body instanceof FormData) && !headers['Content-Type'] && !headers['content-type']) {
headers['Content-Type'] = 'application/json';
}
return headers;
}
prepareRequestBody(body) {
if (!body)
return undefined;
if (body instanceof FormData)
return body;
return JSON.stringify(body);
}
async parseErrorResponse(response) {
try {
return await response.json();
}
catch {
return { message: response.statusText };
}
}
createErrorFromResponse(response, data) {
const message = data.message || data.error || data.detail || `HTTP ${response.status}`;
const requestId = response.headers.get('x-request-id') || undefined;
const headers = Object.fromEntries(response.headers.entries());
switch (response.status) {
case 401:
return new errors_1.AuthenticationError(message, requestId, headers);
case 403:
return new errors_1.PermissionDeniedError(message, requestId, headers);
case 404:
return new errors_1.NotFoundError(message, requestId, headers);
case 405:
return new errors_1.APIError('Method Not Allowed. Please check the API endpoint and HTTP method.', response.status, requestId, headers);
case 422:
return new errors_1.UnprocessableEntityError(message, requestId, headers);
case 429:
return new errors_1.RateLimitError(message, requestId, headers);
case 502:
return new errors_1.InternalServerError('Bad Gateway - API server temporarily unavailable', response.status, requestId, headers);
case 503:
return new errors_1.InternalServerError('Service Unavailable - API server temporarily overloaded', response.status, requestId, headers);
case 504:
return new errors_1.InternalServerError('Gateway Timeout - API request timed out', response.status, requestId, headers);
case 524:
return new errors_1.InternalServerError('Cloudflare Timeout - Request took too long to process', response.status, requestId, headers);
default:
if (response.status >= 500) {
return new errors_1.InternalServerError(message, response.status, requestId, headers);
}
return new errors_1.APIError(message, response.status, requestId, headers);
}
}
shouldRetry(status, retries, maxRetries) {
if (retries >= maxRetries)
return false;
if (!status)
return false;
return [408, 409, 429, 502, 503, 504, 524].includes(status) || status >= 500;
}
calculateBackoffDelay(retryCount) {
return Math.min(Math.pow(2, retryCount) * 1000, 8000);
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async get(path, options) {
return this.request('GET', path, undefined, options);
}
async post(path, body, options) {
return this.request('POST', path, body, options);
}
async put(path, body, options) {
return this.request('PUT', path, body, options);
}
async delete(path, options) {
return this.request('DELETE', path, undefined, options);
}
}
exports.SVECTOR = SVECTOR;