@probelabs/probe-chat 0.6.0-rc100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Abstract base class for all implementation backends
3
+ * @module BaseBackend
4
+ */
5
+
6
+ import { BackendError, ErrorTypes } from '../core/utils.js';
7
+
8
+ /**
9
+ * Base class that all implementation backends must extend
10
+ * @class
11
+ */
12
+ class BaseBackend {
13
+ /**
14
+ * @param {string} name - Backend name
15
+ * @param {string} version - Backend version
16
+ */
17
+ constructor(name, version) {
18
+ if (new.target === BaseBackend) {
19
+ throw new Error('BaseBackend is an abstract class and cannot be instantiated directly');
20
+ }
21
+
22
+ this.name = name;
23
+ this.version = version;
24
+ this.initialized = false;
25
+ this.activeSessions = new Map();
26
+ }
27
+
28
+ /**
29
+ * Initialize the backend with configuration
30
+ * @param {import('../types/BackendTypes').BackendConfig} config - Backend-specific configuration
31
+ * @returns {Promise<void>}
32
+ * @abstract
33
+ */
34
+ async initialize(config) {
35
+ throw new Error('initialize() must be implemented by subclass');
36
+ }
37
+
38
+ /**
39
+ * Check if backend is available and properly configured
40
+ * @returns {Promise<boolean>}
41
+ * @abstract
42
+ */
43
+ async isAvailable() {
44
+ throw new Error('isAvailable() must be implemented by subclass');
45
+ }
46
+
47
+ /**
48
+ * Get required dependencies for this backend
49
+ * @returns {import('../types/BackendTypes').Dependency[]}
50
+ * @abstract
51
+ */
52
+ getRequiredDependencies() {
53
+ throw new Error('getRequiredDependencies() must be implemented by subclass');
54
+ }
55
+
56
+ /**
57
+ * Execute implementation task
58
+ * @param {import('../types/BackendTypes').ImplementRequest} request - Implementation request
59
+ * @returns {Promise<import('../types/BackendTypes').ImplementResult>}
60
+ * @abstract
61
+ */
62
+ async execute(request) {
63
+ throw new Error('execute() must be implemented by subclass');
64
+ }
65
+
66
+ /**
67
+ * Cancel an active implementation session
68
+ * @param {string} sessionId - Session to cancel
69
+ * @returns {Promise<void>}
70
+ */
71
+ async cancel(sessionId) {
72
+ const session = this.activeSessions.get(sessionId);
73
+ if (session && session.cancel) {
74
+ await session.cancel();
75
+ }
76
+ this.activeSessions.delete(sessionId);
77
+ }
78
+
79
+ /**
80
+ * Get status of an implementation session
81
+ * @param {string} sessionId - Session ID
82
+ * @returns {Promise<import('../types/BackendTypes').BackendStatus>}
83
+ */
84
+ async getStatus(sessionId) {
85
+ const session = this.activeSessions.get(sessionId);
86
+ if (!session) {
87
+ return {
88
+ status: 'unknown',
89
+ message: 'Session not found'
90
+ };
91
+ }
92
+
93
+ return {
94
+ status: session.status || 'running',
95
+ progress: session.progress,
96
+ message: session.message,
97
+ details: session.details
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Clean up backend resources
103
+ * @returns {Promise<void>}
104
+ */
105
+ async cleanup() {
106
+ // Cancel all active sessions
107
+ const sessionIds = Array.from(this.activeSessions.keys());
108
+ await Promise.all(sessionIds.map(id => this.cancel(id)));
109
+
110
+ this.activeSessions.clear();
111
+ this.initialized = false;
112
+ }
113
+
114
+ /**
115
+ * Get backend capabilities
116
+ * @returns {import('../types/BackendTypes').BackendCapabilities}
117
+ */
118
+ getCapabilities() {
119
+ return {
120
+ supportsLanguages: [],
121
+ supportsStreaming: false,
122
+ supportsRollback: false,
123
+ supportsDirectFileEdit: false,
124
+ supportsPlanGeneration: false,
125
+ supportsTestGeneration: false,
126
+ maxConcurrentSessions: 1
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Get backend information
132
+ * @returns {import('../types/BackendTypes').BackendInfo}
133
+ */
134
+ getInfo() {
135
+ return {
136
+ name: this.name,
137
+ version: this.version,
138
+ description: this.getDescription(),
139
+ available: false,
140
+ capabilities: this.getCapabilities(),
141
+ dependencies: this.getRequiredDependencies()
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Get backend description
147
+ * @returns {string}
148
+ */
149
+ getDescription() {
150
+ return 'Implementation backend';
151
+ }
152
+
153
+ /**
154
+ * Validate implementation request
155
+ * @param {import('../types/BackendTypes').ImplementRequest} request - Request to validate
156
+ * @returns {import('../types/BackendTypes').ValidationResult}
157
+ */
158
+ validateRequest(request) {
159
+ const errors = [];
160
+ const warnings = [];
161
+
162
+ // Required fields
163
+ if (!request.sessionId) {
164
+ errors.push('sessionId is required');
165
+ }
166
+
167
+ if (!request.task || request.task.trim().length === 0) {
168
+ errors.push('task description is required');
169
+ }
170
+
171
+ // Check for active sessions limit
172
+ if (this.activeSessions.size >= this.getCapabilities().maxConcurrentSessions) {
173
+ errors.push(`Maximum concurrent sessions (${this.getCapabilities().maxConcurrentSessions}) reached`);
174
+ }
175
+
176
+ // Language support check
177
+ if (request.context?.language) {
178
+ const supportedLanguages = this.getCapabilities().supportsLanguages;
179
+ if (supportedLanguages.length > 0 && !supportedLanguages.includes(request.context.language)) {
180
+ warnings.push(`Language '${request.context.language}' may not be fully supported`);
181
+ }
182
+ }
183
+
184
+ // Option validation
185
+ if (request.options?.generateTests && !this.getCapabilities().supportsTestGeneration) {
186
+ warnings.push('Test generation requested but not supported by this backend');
187
+ }
188
+
189
+ return {
190
+ valid: errors.length === 0,
191
+ errors,
192
+ warnings
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Create a session info object
198
+ * @param {string} sessionId - Session ID
199
+ * @returns {Object}
200
+ * @protected
201
+ */
202
+ createSessionInfo(sessionId) {
203
+ return {
204
+ sessionId,
205
+ startTime: Date.now(),
206
+ status: 'pending',
207
+ progress: 0,
208
+ message: 'Initializing',
209
+ cancel: null,
210
+ details: {}
211
+ };
212
+ }
213
+
214
+ /**
215
+ * Update session status
216
+ * @param {string} sessionId - Session ID
217
+ * @param {Partial<import('../types/BackendTypes').BackendStatus>} update - Status update
218
+ * @protected
219
+ */
220
+ updateSessionStatus(sessionId, update) {
221
+ const session = this.activeSessions.get(sessionId);
222
+ if (session) {
223
+ Object.assign(session, update);
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Check if backend is initialized
229
+ * @throws {Error} If backend is not initialized
230
+ * @protected
231
+ */
232
+ checkInitialized() {
233
+ if (!this.initialized) {
234
+ throw new BackendError(
235
+ `Backend '${this.name}' is not initialized`,
236
+ ErrorTypes.INITIALIZATION_FAILED,
237
+ 'BACKEND_NOT_INITIALIZED'
238
+ );
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Log message with backend context
244
+ * @param {string} level - Log level
245
+ * @param {string} message - Log message
246
+ * @param {Object} [data] - Additional data
247
+ * @protected
248
+ */
249
+ log(level, message, data = {}) {
250
+ const logMessage = `[${this.name}] ${message}`;
251
+ const logData = { backend: this.name, ...data };
252
+
253
+ switch (level) {
254
+ case 'debug':
255
+ // Only output debug logs if DEBUG environment variable is set
256
+ if (process.env.DEBUG) {
257
+ console.debug(logMessage, logData);
258
+ }
259
+ break;
260
+ case 'info':
261
+ // Send info logs to stderr to avoid mixing with stdout output
262
+ console.error(logMessage, logData);
263
+ break;
264
+ case 'warn':
265
+ console.warn(logMessage, logData);
266
+ break;
267
+ case 'error':
268
+ console.error(logMessage, logData);
269
+ break;
270
+ default:
271
+ console.error(logMessage, logData);
272
+ }
273
+ }
274
+ }
275
+
276
+ export default BaseBackend;