UNPKG

@velatir/sdk

Version:

Official TypeScript SDK for Velatir - Monitor and approve/reject AI function calls

371 lines (363 loc) 13.7 kB
import axios from 'axios'; class VelatirResponse { constructor(data) { this.requestId = data.requestId; this.state = data.state; } get isApproved() { return this.state === 'approved'; } get isDenied() { return this.state === 'denied'; } get isPending() { return this.state === 'pending'; } } class VelatirError extends Error { constructor(message) { super(message); this.name = 'VelatirError'; } } class VelatirAPIError extends VelatirError { constructor(message, code, httpStatus, httpBody) { super(message); this.name = 'VelatirAPIError'; this.code = code; this.httpStatus = httpStatus; this.httpBody = httpBody; } } class VelatirTimeoutError extends VelatirError { constructor(message) { super(message); this.name = 'VelatirTimeoutError'; } } class VelatirWatchDeniedError extends VelatirError { constructor(requestId) { const message = `Function execution denied by Velatir (request_id: ${requestId})`; super(message); this.name = 'VelatirWatchDeniedError'; this.requestId = requestId; } } var LogLevel; (function (LogLevel) { LogLevel[LogLevel["NONE"] = 0] = "NONE"; LogLevel[LogLevel["ERROR"] = 1] = "ERROR"; LogLevel[LogLevel["INFO"] = 2] = "INFO"; LogLevel[LogLevel["DEBUG"] = 3] = "DEBUG"; })(LogLevel || (LogLevel = {})); class Client { constructor(config = {}) { this.apiKey = config.apiKey; this.baseUrl = config.baseUrl || Client.DEFAULT_BASE_URL; this.timeout = config.timeout || Client.DEFAULT_TIMEOUT; this.logLevel = this.parseLogLevel(config.logLevel); this.maxRetries = config.maxRetries || 3; this.retryBackoff = config.retryBackoff || 0.5; this.httpClient = this.createHttpClient(); } parseLogLevel(level) { if (level === undefined || level === null) { return LogLevel.ERROR; } if (typeof level === 'number') { return level; } return level; } createHttpClient() { const version = '0.1.0'; // This should match package.json version return axios.create({ baseURL: this.baseUrl, timeout: this.timeout, headers: { 'X-API-Key': this.apiKey || '', 'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': `Velatir-TypeScript/${version}`, }, }); } log(level, message, data) { if (this.logLevel >= level) { const timestamp = new Date().toISOString(); const levelName = LogLevel[level]; const logMessage = data ? `${timestamp} - velatir - ${levelName} - ${message} - ${JSON.stringify(data)}` : `${timestamp} - velatir - ${levelName} - ${message}`; switch (level) { case LogLevel.ERROR: console.error(logMessage); break; case LogLevel.INFO: console.info(logMessage); break; case LogLevel.DEBUG: console.debug(logMessage); break; default: console.log(logMessage); } } } async request(method, path, params, data) { const url = `${this.baseUrl}${path}`; let lastError = null; for (let attempt = 0; attempt <= this.maxRetries; attempt++) { try { this.log(LogLevel.INFO, `Making ${method} request to ${url}`); if (data) { this.log(LogLevel.DEBUG, 'Request payload', data); } if (params) { this.log(LogLevel.DEBUG, 'Request params', params); } const startTime = Date.now(); const response = await this.httpClient.request({ method, url: path, params, data, }); const duration = Date.now() - startTime; this.log(LogLevel.DEBUG, `Request completed in ${duration}ms`); this.log(LogLevel.INFO, `Received response from ${url} - ${response.status}`); this.log(LogLevel.DEBUG, 'Response data', response.data); return response.data; } catch (error) { lastError = error; const shouldRetry = this.shouldRetry(error, attempt); if (!shouldRetry || attempt >= this.maxRetries) { break; } const backoff = this.retryBackoff * Math.pow(2, attempt) * 1000; this.log(LogLevel.INFO, `Request failed, retrying in ${backoff}ms (${attempt + 1}/${this.maxRetries})`, error.message); await this.sleep(backoff); } } throw this.handleError(lastError); } shouldRetry(error, attempt) { if (attempt >= this.maxRetries) { return false; } // Retry on network errors, timeout errors, or 5xx status codes return !!(error.code === 'ECONNABORTED' || error.code === 'ENOTFOUND' || error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT' || (error.response && error.response.status >= 500)); } handleError(error) { if (error.response) { let message = error.message; let code = error.response.status; try { const errorData = error.response.data; if (errorData.message) { message = errorData.message; } if (errorData.code) { code = errorData.code; } } catch { // Ignore JSON parsing errors } this.log(LogLevel.ERROR, `API error: ${message}`, error.response.data); return new VelatirAPIError(message, code, error.response.status, JSON.stringify(error.response.data)); } else if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) { this.log(LogLevel.ERROR, 'Request timed out', error.message); return new VelatirTimeoutError(`Request timed out after ${this.maxRetries} retries: ${error.message}`); } else { this.log(LogLevel.ERROR, 'Communication error', error.message); return new VelatirError(`Error communicating with Velatir API: ${error.message}`); } } sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async createWatchRequest(functionName, args, doc, metadata) { const payload = { functionname: functionName, args, doc, metadata: metadata || {}, }; const response = await this.request('POST', '/watches', undefined, payload); return new VelatirResponse(response); } async getWatchStatus(requestId) { const response = await this.request('GET', `/watches/${requestId}`); return new VelatirResponse(response); } async waitForApproval(requestId, pollingInterval = 5000, maxAttempts) { let attempts = 0; this.log(LogLevel.INFO, `Waiting for approval of request ${requestId}`); // eslint-disable-next-line no-constant-condition while (true) { if (maxAttempts && attempts >= maxAttempts) { const errorMsg = `Max polling attempts (${maxAttempts}) reached waiting for request ${requestId}`; this.log(LogLevel.ERROR, errorMsg); throw new VelatirTimeoutError(errorMsg); } const response = await this.getWatchStatus(requestId); if (response.state !== 'pending') { this.log(LogLevel.INFO, `Request ${requestId} ${response.state}`, { state: response.state }); return response; } attempts++; this.log(LogLevel.DEBUG, `Request ${requestId} still pending, attempt ${attempts}${maxAttempts ? `/${maxAttempts}` : ''}`, { attempt: attempts, maxAttempts }); await this.sleep(pollingInterval); } } } Client.DEFAULT_BASE_URL = 'https://api.velatir.com/api/v1'; Client.DEFAULT_TIMEOUT = 10000; // 10 seconds let globalClient$1 = null; function getClient$1() { if (!globalClient$1) { throw new Error('Velatir client is not initialized. Call velatir.init() first.'); } return globalClient$1; } function setClient(client) { globalClient$1 = client; } function watch(options = {}) { return function (target, propertyKey, descriptor) { const originalMethod = descriptor.value; if (typeof originalMethod !== 'function') { throw new Error('@watch can only be applied to methods'); } const isAsync = originalMethod.constructor.name === 'AsyncFunction'; const wrappedMethod = async function (...args) { const client = getClient$1(); const functionName = propertyKey.toString(); const doc = getFunctionDocstring(originalMethod); const argsDict = convertArgsToDict(originalMethod, args); const response = await client.createWatchRequest(functionName, argsDict, doc, options.metadata); if (response.isApproved) { if (isAsync) { return await originalMethod.apply(this, args); } else { return originalMethod.apply(this, args); } } else if (response.isPending) { const approval = await client.waitForApproval(response.requestId, (options.pollingInterval || 5) * 1000, // Convert to milliseconds options.maxAttempts); if (approval.isApproved) { if (isAsync) { return await originalMethod.apply(this, args); } else { return originalMethod.apply(this, args); } } else { throw new VelatirWatchDeniedError(approval.requestId); } } else { throw new VelatirWatchDeniedError(response.requestId); } }; if (isAsync) { descriptor.value = wrappedMethod; } else { descriptor.value = (function (...args) { // For sync functions, we still return a Promise since the wrapper is async return wrappedMethod.apply(this, args); }); } return descriptor; }; } function getFunctionDocstring(func) { const funcString = func.toString(); const docMatch = funcString.match(/\/\*\*([\s\S]*?)\*\//); return docMatch ? docMatch[1].trim() : undefined; } function convertArgsToDict(func, args) { const funcString = func.toString(); const paramMatch = funcString.match(/\(([^)]*)\)/); if (!paramMatch) { return {}; } const paramStr = paramMatch[1]; const paramNames = paramStr .split(',') .map(param => param.trim().split(/[\s:=]/)[0]) .filter(name => name.length > 0); const argsDict = {}; for (let i = 0; i < Math.min(paramNames.length, args.length); i++) { const paramName = paramNames[i]; const value = args[i]; if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value === null || value === undefined || Array.isArray(value) || (typeof value === 'object' && value !== null && value.constructor === Object)) { argsDict[paramName] = value; } else { try { if (value && typeof value === 'object' && 'toJSON' in value && typeof value.toJSON === 'function') { argsDict[paramName] = value.toJSON(); } else if (value && typeof value === 'object') { argsDict[paramName] = { ...value }; } else { argsDict[paramName] = String(value); } } catch { argsDict[paramName] = String(value); } } } return argsDict; } const VERSION = '0.1.0'; let globalClient = null; function init(config = {}) { globalClient = new Client(config); setClient(globalClient); return globalClient; } function resetGlobalClient() { globalClient = null; setClient(null); } function getClient() { if (!globalClient) { throw new Error('Velatir client is not initialized. Call velatir.init() first.'); } return globalClient; } function configureLogging(level) { const client = getClient(); init({ apiKey: client['apiKey'], baseUrl: client['baseUrl'], timeout: client['timeout'], logLevel: level, maxRetries: client['maxRetries'], retryBackoff: client['retryBackoff'], }); } export { Client, LogLevel, VERSION, VelatirAPIError, VelatirError, VelatirResponse, VelatirTimeoutError, VelatirWatchDeniedError, configureLogging, getClient, init, resetGlobalClient, watch }; //# sourceMappingURL=index.esm.js.map