UNPKG

morpheus-node

Version:

Official Node.js SDK for the Morpheus API Gateway - Connect to the Morpheus-Lumerin AI Marketplace

553 lines (547 loc) 15.7 kB
// src/client.ts import { EventEmitter } from "eventemitter3"; // src/http-client.ts import fetch from "cross-fetch"; // src/errors.ts var MorpheusError = class _MorpheusError extends Error { constructor(message, status, code, type, param, details) { super(message); this.name = "MorpheusError"; this.status = status; this.code = code; this.type = type; this.param = param; this.details = details; if (Error.captureStackTrace) { Error.captureStackTrace(this, _MorpheusError); } } static fromResponse(response, status) { if (response?.error) { return new _MorpheusError( response.error.message || "Unknown error", status, response.error.code, response.error.type, response.error.param, response ); } return new _MorpheusError( typeof response === "string" ? response : "Unknown error", status, void 0, void 0, void 0, response ); } }; var MorpheusAPIError = class extends MorpheusError { constructor(message, status, details) { super(message, status, "API_ERROR", void 0, void 0, details); this.name = "MorpheusAPIError"; } }; var MorpheusAuthenticationError = class extends MorpheusError { constructor(message = "Authentication failed") { super(message, 401, "AUTHENTICATION_ERROR"); this.name = "MorpheusAuthenticationError"; } }; var MorpheusPermissionError = class extends MorpheusError { constructor(message = "Permission denied") { super(message, 403, "PERMISSION_ERROR"); this.name = "MorpheusPermissionError"; } }; var MorpheusNotFoundError = class extends MorpheusError { constructor(resource) { super(`${resource} not found`, 404, "NOT_FOUND"); this.name = "MorpheusNotFoundError"; } }; var MorpheusRateLimitError = class extends MorpheusError { constructor(message = "Rate limit exceeded", retryAfter) { super(message, 429, "RATE_LIMIT_ERROR"); this.name = "MorpheusRateLimitError"; this.retryAfter = retryAfter; } }; var MorpheusValidationError = class extends MorpheusError { constructor(message, param) { super(message, 400, "VALIDATION_ERROR", void 0, param); this.name = "MorpheusValidationError"; } }; var MorpheusNetworkError = class extends MorpheusError { constructor(message = "Network error", originalError) { super(message, void 0, "NETWORK_ERROR", void 0, void 0, originalError); this.name = "MorpheusNetworkError"; } }; var MorpheusTimeoutError = class extends MorpheusError { constructor(message = "Request timeout") { super(message, void 0, "TIMEOUT_ERROR"); this.name = "MorpheusTimeoutError"; } }; var MorpheusStreamError = class extends MorpheusError { constructor(message, details) { super(message, void 0, "STREAM_ERROR", void 0, void 0, details); this.name = "MorpheusStreamError"; } }; function isMorpheusError(error) { return error instanceof MorpheusError; } function createErrorFromStatus(status, message, details) { switch (status) { case 401: return new MorpheusAuthenticationError(message); case 403: return new MorpheusPermissionError(message); case 404: return new MorpheusNotFoundError(message); case 429: return new MorpheusRateLimitError(message); case 400: return new MorpheusValidationError(message); default: if (status >= 500) { return new MorpheusAPIError(`Server error: ${message}`, status, details); } return new MorpheusAPIError(message, status, details); } } // src/http-client.ts var HttpClient = class { constructor(config) { this.config = config; } async delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } shouldRetry(error, attempt) { if (attempt >= this.config.maxRetries) return false; if (error instanceof TypeError || error instanceof MorpheusNetworkError) { return true; } if (error instanceof MorpheusTimeoutError) { return true; } if (error instanceof MorpheusError && error.status) { return [429, 502, 503, 504].includes(error.status); } return false; } getRetryDelay(attempt, error) { if (error?.status === 429 && error.details?.retryAfter) { return error.details.retryAfter * 1e3; } return Math.min(this.config.retryDelay * Math.pow(2, attempt), 3e4); } async request(method, path, data, options) { const url = `${this.config.baseURL}${path}`; let lastError; for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) { try { const response = await this.makeRequest(method, url, data, options); return await this.handleResponse(response); } catch (error) { lastError = error; if (!this.shouldRetry(error, attempt)) { throw error; } const delay = this.getRetryDelay(attempt, error); console.warn(`Request failed, retrying in ${delay}ms...`, { attempt: attempt + 1, error: error instanceof Error ? error.message : "Unknown error" }); await this.delay(delay); } } throw lastError || new MorpheusError("Max retries exceeded"); } async makeRequest(method, url, data, options) { const controller = new AbortController(); const timeout = options?.timeout || this.config.timeout; const timeoutId = setTimeout(() => { controller.abort(); }, timeout); try { const headers = { "Authorization": `Bearer ${this.config.apiKey}`, "Content-Type": "application/json", "User-Agent": "morpheus-node/1.0.0", ...this.config.headers, ...options?.headers }; const fetchOptions = { method, headers, signal: options?.signal || controller.signal }; if (data && ["POST", "PUT", "PATCH"].includes(method)) { fetchOptions.body = JSON.stringify(data); } const response = await fetch(url, fetchOptions); clearTimeout(timeoutId); return response; } catch (error) { clearTimeout(timeoutId); if (error instanceof Error) { if (error.name === "AbortError") { throw new MorpheusTimeoutError(`Request timeout after ${timeout}ms`); } throw new MorpheusNetworkError(error.message, error); } throw new MorpheusNetworkError("Unknown network error"); } } async handleResponse(response) { let responseData; try { const contentType = response.headers.get("content-type"); if (contentType?.includes("application/json")) { responseData = await response.json(); } else { responseData = await response.text(); } } catch (error) { responseData = response.statusText; } if (!response.ok) { if (responseData?.error) { throw MorpheusError.fromResponse(responseData, response.status); } throw createErrorFromStatus( response.status, typeof responseData === "string" ? responseData : "Request failed", responseData ); } return responseData; } async *stream(method, path, data, options) { const url = `${this.config.baseURL}${path}`; const response = await this.makeRequest(method, url, data, options); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw createErrorFromStatus(response.status, errorData.message || response.statusText, errorData); } const reader = response.body?.getReader(); if (!reader) { throw new MorpheusError("Failed to get response reader"); } const decoder = new TextDecoder(); let buffer = ""; try { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() || ""; for (const line of lines) { if (line.trim() === "") continue; if (line.startsWith("data: ")) { const data2 = line.slice(6).trim(); if (data2 === "[DONE]") { return; } yield data2; } } } } finally { reader.releaseLock(); } } }; // src/client.ts var MorpheusClient = class { constructor(config) { if (!config.apiKey) { throw new MorpheusValidationError("API key is required"); } this.config = { apiKey: config.apiKey, baseURL: config.baseURL || "https://api.mor.org/v1", timeout: config.timeout || 3e4, maxRetries: config.maxRetries || 3, retryDelay: config.retryDelay || 1e3, headers: config.headers || {} }; this.httpClient = new HttpClient({ apiKey: this.config.apiKey, baseURL: this.config.baseURL, timeout: this.config.timeout, maxRetries: this.config.maxRetries, retryDelay: this.config.retryDelay, headers: this.config.headers }); } /** * Chat Completions API */ async createChatCompletion(params, options) { this.validateChatCompletionParams(params); return this.httpClient.request( "POST", "/chat/completions", params, options ); } async *streamChatCompletion(params, options) { this.validateChatCompletionParams(params); const streamParams = { ...params, stream: true }; const stream = this.httpClient.stream( "POST", "/chat/completions", streamParams, options ); for await (const data of stream) { try { const chunk = JSON.parse(data); yield chunk; } catch (error) { console.warn("Failed to parse streaming chunk:", data); continue; } } } /** * Stream chat completion with event emitter */ streamChatCompletionEvents(params, options) { const emitter = new EventEmitter(); (async () => { try { const stream = this.streamChatCompletion(params, options); for await (const chunk of stream) { emitter.emit("message", { type: "message", data: chunk }); } emitter.emit("done", { type: "done" }); } catch (error) { emitter.emit("error", { type: "error", error }); } })(); return emitter; } /** * Models API */ async listModels(options) { const response = await this.httpClient.request( "GET", "/models", void 0, options ); return response.data; } async getModel(modelId, options) { if (!modelId) { throw new MorpheusValidationError("Model ID is required"); } return this.httpClient.request( "GET", `/models/${modelId}`, void 0, options ); } /** * Agents API */ async createAgent(params, options) { this.validateAgentParams(params); return this.httpClient.request( "POST", "/agents", params, options ); } async getAgent(agentId, options) { if (!agentId) { throw new MorpheusValidationError("Agent ID is required"); } return this.httpClient.request( "GET", `/agents/${agentId}`, void 0, options ); } async listAgents(options) { const response = await this.httpClient.request( "GET", "/agents", void 0, options ); return response.data; } async updateAgent(agentId, params, options) { if (!agentId) { throw new MorpheusValidationError("Agent ID is required"); } return this.httpClient.request( "PATCH", `/agents/${agentId}`, params, options ); } async deleteAgent(agentId, options) { if (!agentId) { throw new MorpheusValidationError("Agent ID is required"); } await this.httpClient.request( "DELETE", `/agents/${agentId}`, void 0, options ); } /** * Wallets API */ async createWallet(params, options) { return this.httpClient.request( "POST", "/wallets", params || {}, options ); } async getWallet(walletId, options) { if (!walletId) { throw new MorpheusValidationError("Wallet ID is required"); } return this.httpClient.request( "GET", `/wallets/${walletId}`, void 0, options ); } async getWalletBalance(address, options) { if (!address) { throw new MorpheusValidationError("Wallet address is required"); } return this.httpClient.request( "GET", `/wallets/${address}/balance`, void 0, options ); } /** * Transactions API */ async sendTransaction(params, options) { this.validateTransactionParams(params); return this.httpClient.request( "POST", "/transactions", params, options ); } async getTransaction(transactionId, options) { if (!transactionId) { throw new MorpheusValidationError("Transaction ID is required"); } return this.httpClient.request( "GET", `/transactions/${transactionId}`, void 0, options ); } /** * Health Check */ async healthCheck(options) { return this.httpClient.request( "GET", "/health", void 0, options ); } /** * Validation methods */ validateChatCompletionParams(params) { if (!params.model) { throw new MorpheusValidationError("Model is required"); } if (!params.messages || !Array.isArray(params.messages) || params.messages.length === 0) { throw new MorpheusValidationError("Messages array is required and cannot be empty"); } for (const [index, message] of params.messages.entries()) { if (!message.role || !["system", "user", "assistant", "function"].includes(message.role)) { throw new MorpheusValidationError(`Invalid role for message at index ${index}`); } if (message.content === void 0 || message.content === null) { throw new MorpheusValidationError(`Content is required for message at index ${index}`); } } if (params.temperature !== void 0 && (params.temperature < 0 || params.temperature > 2)) { throw new MorpheusValidationError("Temperature must be between 0 and 2"); } if (params.top_p !== void 0 && (params.top_p < 0 || params.top_p > 1)) { throw new MorpheusValidationError("top_p must be between 0 and 1"); } if (params.n !== void 0 && params.n < 1) { throw new MorpheusValidationError("n must be at least 1"); } if (params.max_tokens !== void 0 && params.max_tokens < 1) { throw new MorpheusValidationError("max_tokens must be at least 1"); } } validateAgentParams(params) { if (!params.name) { throw new MorpheusValidationError("Agent name is required"); } if (!params.model) { throw new MorpheusValidationError("Agent model is required"); } } validateTransactionParams(params) { if (!params.from) { throw new MorpheusValidationError("From address is required"); } if (!params.to) { throw new MorpheusValidationError("To address is required"); } if (!params.value) { throw new MorpheusValidationError("Transaction value is required"); } } }; // src/index.ts var VERSION = "1.0.0"; export { MorpheusAPIError, MorpheusAuthenticationError, MorpheusClient, MorpheusError, MorpheusNetworkError, MorpheusNotFoundError, MorpheusPermissionError, MorpheusRateLimitError, MorpheusStreamError, MorpheusTimeoutError, MorpheusValidationError, VERSION, createErrorFromStatus, MorpheusClient as default, isMorpheusError };