@velatir/sdk
Version:
Official TypeScript SDK for Velatir - Monitor and approve/reject AI function calls
384 lines (375 loc) • 14.1 kB
JavaScript
;
var axios = require('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;
}
}
exports.LogLevel = void 0;
(function (LogLevel) {
LogLevel[LogLevel["NONE"] = 0] = "NONE";
LogLevel[LogLevel["ERROR"] = 1] = "ERROR";
LogLevel[LogLevel["INFO"] = 2] = "INFO";
LogLevel[LogLevel["DEBUG"] = 3] = "DEBUG";
})(exports.LogLevel || (exports.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 exports.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 = exports.LogLevel[level];
const logMessage = data
? `${timestamp} - velatir - ${levelName} - ${message} - ${JSON.stringify(data)}`
: `${timestamp} - velatir - ${levelName} - ${message}`;
switch (level) {
case exports.LogLevel.ERROR:
console.error(logMessage);
break;
case exports.LogLevel.INFO:
console.info(logMessage);
break;
case exports.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(exports.LogLevel.INFO, `Making ${method} request to ${url}`);
if (data) {
this.log(exports.LogLevel.DEBUG, 'Request payload', data);
}
if (params) {
this.log(exports.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(exports.LogLevel.DEBUG, `Request completed in ${duration}ms`);
this.log(exports.LogLevel.INFO, `Received response from ${url} - ${response.status}`);
this.log(exports.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(exports.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(exports.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(exports.LogLevel.ERROR, 'Request timed out', error.message);
return new VelatirTimeoutError(`Request timed out after ${this.maxRetries} retries: ${error.message}`);
}
else {
this.log(exports.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(exports.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(exports.LogLevel.ERROR, errorMsg);
throw new VelatirTimeoutError(errorMsg);
}
const response = await this.getWatchStatus(requestId);
if (response.state !== 'pending') {
this.log(exports.LogLevel.INFO, `Request ${requestId} ${response.state}`, { state: response.state });
return response;
}
attempts++;
this.log(exports.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'],
});
}
exports.Client = Client;
exports.VERSION = VERSION;
exports.VelatirAPIError = VelatirAPIError;
exports.VelatirError = VelatirError;
exports.VelatirResponse = VelatirResponse;
exports.VelatirTimeoutError = VelatirTimeoutError;
exports.VelatirWatchDeniedError = VelatirWatchDeniedError;
exports.configureLogging = configureLogging;
exports.getClient = getClient;
exports.init = init;
exports.resetGlobalClient = resetGlobalClient;
exports.watch = watch;
//# sourceMappingURL=index.js.map