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
JavaScript
// 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
};