Skip to main content
Glama
backend-adapter.js8.72 kB
/** * @fileoverview BackendAdapter - Abstract base class for AI backends * @module backends/backend-adapter * * Provides unified interface for all AI backend implementations. * New backends extend this class and implement required methods. * * Ported from MKG V2 for Smart AI Bridge v1.3.0 * @source mecha-king-ghidorah-v2/src/backends/backend-adapter.js */ /** * @typedef {Object} BackendConfig * @property {string} name - Backend identifier * @property {string} type - Backend type (local, cloud, nvidia, etc.) * @property {string} url - Backend API URL * @property {string} [apiKey] - API key if required * @property {number} [maxTokens] - Maximum token limit * @property {number} [timeout] - Request timeout in ms * @property {boolean} [streaming] - Whether streaming is supported * @property {Object} [headers] - Additional headers */ /** * @typedef {Object} BackendHealth * @property {boolean} healthy - Whether backend is healthy * @property {number} latency - Response latency in ms * @property {string} [error] - Error message if unhealthy * @property {Date} checkedAt - When health was checked */ /** * @typedef {Object} RequestOptions * @property {number} [maxTokens] - Maximum tokens for response * @property {number} [temperature] - Temperature for generation * @property {boolean} [thinking] - Enable thinking mode * @property {boolean} [stream] - Enable streaming * @property {string} [model] - Model override */ /** * @typedef {Object} BackendResponse * @property {string} content - Response content * @property {string} backend - Backend that processed request * @property {number} tokens - Token count * @property {number} latency - Response latency in ms * @property {boolean} success - Whether request succeeded * @property {Object} [metadata] - Additional metadata */ /** * Abstract base class for AI backend adapters * @abstract */ class BackendAdapter { /** * Create a new BackendAdapter * @param {BackendConfig} config - Backend configuration */ constructor(config) { if (new.target === BackendAdapter) { throw new Error('BackendAdapter is abstract and cannot be instantiated directly'); } /** @type {BackendConfig} */ this.config = { timeout: 30000, maxTokens: 4096, streaming: false, ...config }; /** @type {BackendHealth|null} */ this.lastHealth = null; /** @type {Object} */ this.metrics = { totalRequests: 0, successfulRequests: 0, failedRequests: 0, totalLatency: 0, averageLatency: 0 }; /** @type {boolean} Circuit breaker state */ this.circuitOpen = false; /** @type {number} Consecutive failure count */ this.consecutiveFailures = 0; /** @type {number} Failures before opening circuit */ this.circuitOpenThreshold = 5; /** @type {number} Time before attempting reset (ms) */ this.circuitResetTimeout = 30000; /** @type {number|null} When circuit was opened */ this.circuitOpenedAt = null; } /** * Get backend name * @returns {string} */ get name() { return this.config.name; } /** * Get backend type * @returns {string} */ get type() { return this.config.type; } /** * Make a request to the backend (must be implemented by subclass) * @abstract * @param {string} prompt - The prompt to send * @param {RequestOptions} [options] - Request options * @returns {Promise<BackendResponse>} */ async makeRequest(prompt, options = {}) { throw new Error('makeRequest must be implemented by subclass'); } /** * Check backend health (must be implemented by subclass) * @abstract * @returns {Promise<BackendHealth>} */ async checkHealth() { throw new Error('checkHealth must be implemented by subclass'); } /** * Get backend configuration * @returns {BackendConfig} */ getConfig() { return { ...this.config }; } /** * Check if backend is available (healthy and circuit closed) * @returns {Promise<boolean>} */ async isAvailable() { // Check circuit breaker if (this.circuitOpen) { // Check if we should try to reset if (Date.now() - this.circuitOpenedAt > this.circuitResetTimeout) { this.circuitOpen = false; this.consecutiveFailures = 0; console.error(`[${this.name}] Circuit breaker reset, attempting recovery`); } else { return false; } } try { const health = await this.checkHealth(); return health.healthy; } catch (error) { return false; } } /** * Execute request with circuit breaker protection * @param {string} prompt - The prompt to send * @param {RequestOptions} [options] - Request options * @returns {Promise<BackendResponse>} */ async execute(prompt, options = {}) { // Check circuit breaker if (this.circuitOpen) { if (Date.now() - this.circuitOpenedAt > this.circuitResetTimeout) { this.circuitOpen = false; this.consecutiveFailures = 0; console.error(`[${this.name}] Circuit breaker reset, attempting request`); } else { throw new Error(`Circuit breaker open for ${this.name}`); } } const startTime = Date.now(); this.metrics.totalRequests++; try { const response = await this.makeRequest(prompt, options); const latency = Date.now() - startTime; // Update metrics on success this.metrics.successfulRequests++; this.metrics.totalLatency += latency; this.metrics.averageLatency = this.metrics.totalLatency / this.metrics.successfulRequests; this.consecutiveFailures = 0; return { ...response, latency, backend: this.name, success: true }; } catch (error) { this.metrics.failedRequests++; this.consecutiveFailures++; // Open circuit breaker if threshold exceeded if (this.consecutiveFailures >= this.circuitOpenThreshold) { this.circuitOpen = true; this.circuitOpenedAt = Date.now(); console.error(`[${this.name}] Circuit breaker opened after ${this.consecutiveFailures} failures`); } throw error; } } /** * Get backend metrics * @returns {Object} */ getMetrics() { return { ...this.metrics, circuitOpen: this.circuitOpen, consecutiveFailures: this.consecutiveFailures, lastHealth: this.lastHealth }; } /** * Reset metrics */ resetMetrics() { this.metrics = { totalRequests: 0, successfulRequests: 0, failedRequests: 0, totalLatency: 0, averageLatency: 0 }; } /** * Force close circuit breaker (for testing/recovery) */ closeCircuit() { this.circuitOpen = false; this.consecutiveFailures = 0; this.circuitOpenedAt = null; } /** * Force open circuit breaker (for maintenance) */ openCircuit() { this.circuitOpen = true; this.circuitOpenedAt = Date.now(); } /** * Build request headers * @protected * @returns {Object} */ buildHeaders() { const headers = { 'Content-Type': 'application/json', ...this.config.headers }; if (this.config.apiKey) { headers['Authorization'] = `Bearer ${this.config.apiKey}`; } return headers; } /** * Build request body * @protected * @param {string} prompt - The prompt * @param {RequestOptions} options - Request options * @returns {Object} */ buildRequestBody(prompt, options) { return { messages: [{ role: 'user', content: prompt }], max_tokens: options.maxTokens || this.config.maxTokens, temperature: options.temperature || 0.7, stream: options.stream || this.config.streaming }; } /** * Parse response from backend * @protected * @param {Object} response - Raw response * @returns {BackendResponse} */ parseResponse(response) { // Default implementation - subclasses may override // Handle reasoning models that return reasoning_content instead of content const message = response.choices?.[0]?.message; const content = message?.content || message?.reasoning_content || response.content || response.text || ''; const tokens = response.usage?.total_tokens || response.usage?.completion_tokens || 0; return { content, tokens, backend: this.name, success: true, metadata: { model: response.model, finishReason: response.choices?.[0]?.finish_reason } }; } } export { BackendAdapter };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Platano78/Smart-AI-Bridge'

If you have feedback or need assistance with the MCP directory API, please join our Discord server