UNPKG

@probelabs/probe-chat

Version:

CLI and web interface for Probe code search (formerly @probelabs/probe-web and @probelabs/probe-chat)

276 lines (248 loc) 7.26 kB
/** * Abstract base class for all implementation backends * @module BaseBackend */ import { BackendError, ErrorTypes } from '../core/utils.js'; /** * Base class that all implementation backends must extend * @class */ class BaseBackend { /** * @param {string} name - Backend name * @param {string} version - Backend version */ constructor(name, version) { if (new.target === BaseBackend) { throw new Error('BaseBackend is an abstract class and cannot be instantiated directly'); } this.name = name; this.version = version; this.initialized = false; this.activeSessions = new Map(); } /** * Initialize the backend with configuration * @param {import('../types/BackendTypes').BackendConfig} config - Backend-specific configuration * @returns {Promise<void>} * @abstract */ async initialize(config) { throw new Error('initialize() must be implemented by subclass'); } /** * Check if backend is available and properly configured * @returns {Promise<boolean>} * @abstract */ async isAvailable() { throw new Error('isAvailable() must be implemented by subclass'); } /** * Get required dependencies for this backend * @returns {import('../types/BackendTypes').Dependency[]} * @abstract */ getRequiredDependencies() { throw new Error('getRequiredDependencies() must be implemented by subclass'); } /** * Execute implementation task * @param {import('../types/BackendTypes').ImplementRequest} request - Implementation request * @returns {Promise<import('../types/BackendTypes').ImplementResult>} * @abstract */ async execute(request) { throw new Error('execute() must be implemented by subclass'); } /** * Cancel an active implementation session * @param {string} sessionId - Session to cancel * @returns {Promise<void>} */ async cancel(sessionId) { const session = this.activeSessions.get(sessionId); if (session && session.cancel) { await session.cancel(); } this.activeSessions.delete(sessionId); } /** * Get status of an implementation session * @param {string} sessionId - Session ID * @returns {Promise<import('../types/BackendTypes').BackendStatus>} */ async getStatus(sessionId) { const session = this.activeSessions.get(sessionId); if (!session) { return { status: 'unknown', message: 'Session not found' }; } return { status: session.status || 'running', progress: session.progress, message: session.message, details: session.details }; } /** * Clean up backend resources * @returns {Promise<void>} */ async cleanup() { // Cancel all active sessions const sessionIds = Array.from(this.activeSessions.keys()); await Promise.all(sessionIds.map(id => this.cancel(id))); this.activeSessions.clear(); this.initialized = false; } /** * Get backend capabilities * @returns {import('../types/BackendTypes').BackendCapabilities} */ getCapabilities() { return { supportsLanguages: [], supportsStreaming: false, supportsRollback: false, supportsDirectFileEdit: false, supportsPlanGeneration: false, supportsTestGeneration: false, maxConcurrentSessions: 1 }; } /** * Get backend information * @returns {import('../types/BackendTypes').BackendInfo} */ getInfo() { return { name: this.name, version: this.version, description: this.getDescription(), available: false, capabilities: this.getCapabilities(), dependencies: this.getRequiredDependencies() }; } /** * Get backend description * @returns {string} */ getDescription() { return 'Implementation backend'; } /** * Validate implementation request * @param {import('../types/BackendTypes').ImplementRequest} request - Request to validate * @returns {import('../types/BackendTypes').ValidationResult} */ validateRequest(request) { const errors = []; const warnings = []; // Required fields if (!request.sessionId) { errors.push('sessionId is required'); } if (!request.task || request.task.trim().length === 0) { errors.push('task description is required'); } // Check for active sessions limit if (this.activeSessions.size >= this.getCapabilities().maxConcurrentSessions) { errors.push(`Maximum concurrent sessions (${this.getCapabilities().maxConcurrentSessions}) reached`); } // Language support check if (request.context?.language) { const supportedLanguages = this.getCapabilities().supportsLanguages; if (supportedLanguages.length > 0 && !supportedLanguages.includes(request.context.language)) { warnings.push(`Language '${request.context.language}' may not be fully supported`); } } // Option validation if (request.options?.generateTests && !this.getCapabilities().supportsTestGeneration) { warnings.push('Test generation requested but not supported by this backend'); } return { valid: errors.length === 0, errors, warnings }; } /** * Create a session info object * @param {string} sessionId - Session ID * @returns {Object} * @protected */ createSessionInfo(sessionId) { return { sessionId, startTime: Date.now(), status: 'pending', progress: 0, message: 'Initializing', cancel: null, details: {} }; } /** * Update session status * @param {string} sessionId - Session ID * @param {Partial<import('../types/BackendTypes').BackendStatus>} update - Status update * @protected */ updateSessionStatus(sessionId, update) { const session = this.activeSessions.get(sessionId); if (session) { Object.assign(session, update); } } /** * Check if backend is initialized * @throws {Error} If backend is not initialized * @protected */ checkInitialized() { if (!this.initialized) { throw new BackendError( `Backend '${this.name}' is not initialized`, ErrorTypes.INITIALIZATION_FAILED, 'BACKEND_NOT_INITIALIZED' ); } } /** * Log message with backend context * @param {string} level - Log level * @param {string} message - Log message * @param {Object} [data] - Additional data * @protected */ log(level, message, data = {}) { const logMessage = `[${this.name}] ${message}`; const logData = { backend: this.name, ...data }; switch (level) { case 'debug': // Only output debug logs if DEBUG environment variable is set if (process.env.DEBUG) { console.debug(logMessage, logData); } break; case 'info': // Send info logs to stderr to avoid mixing with stdout output console.error(logMessage, logData); break; case 'warn': console.warn(logMessage, logData); break; case 'error': console.error(logMessage, logData); break; default: console.error(logMessage, logData); } } } export default BaseBackend;