@172ai/containers-mcp-server 1.12.2 → 1.12.4

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.
Files changed (75) hide show
  1. package/dist/auth.d.ts +1 -1
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/auth.js +26 -33
  4. package/dist/auth.js.map +1 -1
  5. package/dist/cli-tool.js +67 -105
  6. package/dist/cli-tool.js.map +1 -1
  7. package/dist/cli.js +4 -37
  8. package/dist/cli.js.map +1 -1
  9. package/dist/config.d.ts +1 -1
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +5 -42
  12. package/dist/config.js.map +1 -1
  13. package/dist/scripts/test-mcp-tools.js +9 -12
  14. package/dist/scripts/test-mcp-tools.js.map +1 -1
  15. package/dist/server.d.ts +13 -0
  16. package/dist/server.d.ts.map +1 -1
  17. package/dist/server.js +727 -87
  18. package/dist/server.js.map +1 -1
  19. package/dist/services/buildService.d.ts +1 -1
  20. package/dist/services/buildService.d.ts.map +1 -1
  21. package/dist/services/buildService.js +37 -41
  22. package/dist/services/buildService.js.map +1 -1
  23. package/dist/services/capabilityService.d.ts +1 -1
  24. package/dist/services/capabilityService.d.ts.map +1 -1
  25. package/dist/services/capabilityService.js +32 -36
  26. package/dist/services/capabilityService.js.map +1 -1
  27. package/dist/services/containerService.d.ts +1 -1
  28. package/dist/services/containerService.d.ts.map +1 -1
  29. package/dist/services/containerService.js +47 -51
  30. package/dist/services/containerService.js.map +1 -1
  31. package/dist/services/executionService.d.ts +1 -1
  32. package/dist/services/executionService.d.ts.map +1 -1
  33. package/dist/services/executionService.js +48 -52
  34. package/dist/services/executionService.js.map +1 -1
  35. package/dist/services/exportService.d.ts +108 -0
  36. package/dist/services/exportService.d.ts.map +1 -0
  37. package/dist/services/exportService.js +659 -0
  38. package/dist/services/exportService.js.map +1 -0
  39. package/dist/services/fileService.d.ts +1 -1
  40. package/dist/services/fileService.d.ts.map +1 -1
  41. package/dist/services/fileService.js +73 -77
  42. package/dist/services/fileService.js.map +1 -1
  43. package/dist/services/importService.d.ts +90 -0
  44. package/dist/services/importService.d.ts.map +1 -0
  45. package/dist/services/importService.js +570 -0
  46. package/dist/services/importService.js.map +1 -0
  47. package/dist/services/streamingService.js +16 -23
  48. package/dist/services/streamingService.js.map +1 -1
  49. package/dist/services/userNotificationManager.d.ts +22 -4
  50. package/dist/services/userNotificationManager.d.ts.map +1 -1
  51. package/dist/services/userNotificationManager.js +59 -19
  52. package/dist/services/userNotificationManager.js.map +1 -1
  53. package/dist/services/userService.d.ts +1 -5
  54. package/dist/services/userService.d.ts.map +1 -1
  55. package/dist/services/userService.js +19 -43
  56. package/dist/services/userService.js.map +1 -1
  57. package/dist/setup.js +28 -67
  58. package/dist/setup.js.map +1 -1
  59. package/dist/types.d.ts +121 -0
  60. package/dist/types.d.ts.map +1 -1
  61. package/dist/types.js +1 -2
  62. package/dist/types.js.map +1 -1
  63. package/dist/utils/enhancedErrorHandler.d.ts +1 -1
  64. package/dist/utils/enhancedErrorHandler.d.ts.map +1 -1
  65. package/dist/utils/enhancedErrorHandler.js +47 -52
  66. package/dist/utils/enhancedErrorHandler.js.map +1 -1
  67. package/dist/utils/errorHandler.d.ts +1 -1
  68. package/dist/utils/errorHandler.d.ts.map +1 -1
  69. package/dist/utils/errorHandler.js +15 -20
  70. package/dist/utils/errorHandler.js.map +1 -1
  71. package/dist/utils/logBuffer.js +2 -6
  72. package/dist/utils/logBuffer.js.map +1 -1
  73. package/dist/utils/retryHandler.js +7 -13
  74. package/dist/utils/retryHandler.js.map +1 -1
  75. package/package.json +2 -1
@@ -0,0 +1,659 @@
1
+ import { authManager } from '../auth.js';
2
+ import { ErrorHandler, ApiError } from '../utils/errorHandler.js';
3
+ import { EnhancedMCPErrorHandler, MCP_ERROR_CODES, MCPError } from '../utils/enhancedErrorHandler.js';
4
+ import { streamingService } from './streamingService.js';
5
+ /**
6
+ * Generate a unique request ID
7
+ */
8
+ function generateRequestId() {
9
+ return `mcp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
10
+ }
11
+ export class ExportService {
12
+ constructor() {
13
+ this.mcpServer = null;
14
+ // Real-time streaming management
15
+ this.activeStreams = new Map();
16
+ this.progressUpdates = new Map();
17
+ this.streamAnalytics = {
18
+ totalExports: 0,
19
+ activeExports: 0,
20
+ completedExports: 0,
21
+ failedExports: 0,
22
+ averageExportTime: 0,
23
+ notificationsSent: 0,
24
+ errorsEncountered: 0,
25
+ lastActivity: new Date().toISOString()
26
+ };
27
+ this.httpClient = authManager.getHttpClient();
28
+ }
29
+ /**
30
+ * Set MCP server reference for progress notifications
31
+ */
32
+ setMCPServer(server) {
33
+ this.mcpServer = server;
34
+ }
35
+ /**
36
+ * Get stream analytics and monitoring information
37
+ */
38
+ getStreamAnalytics() {
39
+ return {
40
+ ...this.streamAnalytics,
41
+ activeStreams: Array.from(this.activeStreams.values())
42
+ };
43
+ }
44
+ /**
45
+ * Get progress update for a specific token
46
+ */
47
+ getProgressUpdate(progressToken) {
48
+ const update = this.progressUpdates.get(progressToken);
49
+ if (!update)
50
+ return null;
51
+ // Check if expired (clean up stale data)
52
+ if (update.expiresAt && Date.now() > update.expiresAt) {
53
+ this.progressUpdates.delete(progressToken);
54
+ return null;
55
+ }
56
+ return update;
57
+ }
58
+ /**
59
+ * Cancel export monitoring for a specific token
60
+ */
61
+ async cancelExportMonitoring(progressToken, reason = 'User requested cancellation') {
62
+ const stream = this.activeStreams.get(progressToken);
63
+ if (!stream) {
64
+ return false;
65
+ }
66
+ console.log(`[Export] Cancelling monitoring for export job ${stream.jobId} with token ${progressToken}`);
67
+ try {
68
+ // Update progress with cancellation notice
69
+ await this.storeProgressUpdate(progressToken, stream.lastProgress, 100, `Export monitoring cancelled: ${reason}`);
70
+ // Unsubscribe from stream
71
+ await streamingService.unsubscribeFromStream(stream.streamId);
72
+ // Cleanup
73
+ this.completeStream(progressToken, 'cancelled');
74
+ return true;
75
+ }
76
+ catch (error) {
77
+ console.error(`[Export] Error cancelling monitoring for token ${progressToken}:`, error);
78
+ return false;
79
+ }
80
+ }
81
+ /**
82
+ * Start a container export with real-time streaming progress
83
+ */
84
+ async exportContainer(params, progressToken) {
85
+ try {
86
+ const { containerId, destinationType, destination, options } = params;
87
+ if (!containerId || !destinationType || !destination) {
88
+ throw ErrorHandler.createValidationError('Container ID, destination type, and destination are required');
89
+ }
90
+ // Initialize progress tracking with streaming notifications
91
+ if (progressToken) {
92
+ console.log(`[Export] Starting export with streaming progress token: ${progressToken}`);
93
+ await this.storeProgressUpdate(progressToken, 0, 100, 'Export initialization started');
94
+ }
95
+ const exportRequest = {
96
+ destinationType,
97
+ destination,
98
+ options: options || {}
99
+ };
100
+ // Start the export via API
101
+ const response = await this.httpClient.post(`/v1/containers/${containerId}/export`, exportRequest);
102
+ if (!response.data) {
103
+ throw ErrorHandler.createServerError('Invalid response from export API');
104
+ }
105
+ const data = response.data;
106
+ const exportResult = {
107
+ jobId: data.jobId,
108
+ containerId: data.containerId || containerId,
109
+ containerName: data.containerName || '',
110
+ destinationType: data.destinationType,
111
+ destination: data.destination || destination,
112
+ status: data.status || 'pending',
113
+ progress: data.progress || {
114
+ stage: 'pending',
115
+ stageNumber: 0,
116
+ totalStages: 7,
117
+ percent: 0,
118
+ message: 'Export queued'
119
+ },
120
+ result: data.result,
121
+ errors: data.errors || [],
122
+ tokenCost: data.tokenCost || 0,
123
+ createdAt: data.createdAt || new Date().toISOString(),
124
+ updatedAt: data.updatedAt || new Date().toISOString(),
125
+ completedAt: data.completedAt,
126
+ estimatedDuration: data.estimatedDuration,
127
+ metadata: data.metadata
128
+ };
129
+ // Set up real-time streaming for progress updates
130
+ if (progressToken) {
131
+ await this.storeProgressUpdate(progressToken, 5, 100, `Export job created - ID: ${exportResult.jobId}`);
132
+ // Create streaming subscription
133
+ await this.setupStreamingMonitoring(exportResult.jobId, containerId, destinationType, exportResult.containerName, progressToken);
134
+ }
135
+ return exportResult;
136
+ }
137
+ catch (error) {
138
+ // Use enhanced error handling for export operations
139
+ const context = {
140
+ operation: 'export_container',
141
+ containerId: params.containerId,
142
+ destinationType: params.destinationType,
143
+ exportStage: 'initialization',
144
+ progressToken: progressToken,
145
+ requestId: generateRequestId()
146
+ };
147
+ if (error instanceof ApiError && error.status === 402) {
148
+ throw new MCPError(MCP_ERROR_CODES.API_QUOTA_EXCEEDED, error.message || 'Insufficient tokens for export', context);
149
+ }
150
+ if (error instanceof ApiError && error.status === 400) {
151
+ throw new MCPError(MCP_ERROR_CODES.CONTAINER_STATE_INVALID, error.message || 'Invalid export request', context);
152
+ }
153
+ const enhancedError = EnhancedMCPErrorHandler.processError(error, context);
154
+ throw new ApiError(enhancedError.message, enhancedError.status, enhancedError.code, enhancedError, context.requestId, context.operation);
155
+ }
156
+ }
157
+ /**
158
+ * Set up real-time streaming monitoring for export progress
159
+ */
160
+ async setupStreamingMonitoring(jobId, containerId, destinationType, containerName, progressToken) {
161
+ try {
162
+ console.log(`[Export] Setting up real-time streaming for export job ${jobId} with token ${progressToken}`);
163
+ // Create export stream
164
+ const streamSession = await streamingService.createStream({
165
+ streamType: 'user-notifications',
166
+ resourceId: jobId,
167
+ eventFilters: [
168
+ 'export-started',
169
+ 'export-validating',
170
+ 'export-building',
171
+ 'export-preparing',
172
+ 'export-authenticating',
173
+ 'export-uploading',
174
+ 'export-pushing',
175
+ 'export-verifying',
176
+ 'export-completed',
177
+ 'export-failed'
178
+ ],
179
+ ttlMinutes: 120, // 2 hours
180
+ metadata: {
181
+ jobId,
182
+ containerId,
183
+ progressToken,
184
+ destinationType,
185
+ containerName
186
+ }
187
+ });
188
+ // Track active stream
189
+ const activeStream = {
190
+ jobId,
191
+ containerId,
192
+ containerName,
193
+ destinationType,
194
+ progressToken,
195
+ streamId: streamSession.streamId,
196
+ startTime: Date.now(),
197
+ lastProgress: 5,
198
+ status: 'active'
199
+ };
200
+ this.activeStreams.set(progressToken, activeStream);
201
+ this.streamAnalytics.totalExports++;
202
+ this.streamAnalytics.activeExports++;
203
+ // Subscribe to stream events
204
+ await streamingService.subscribeToStream(streamSession.streamId, (event) => {
205
+ this.handleExportStreamEvent(event, progressToken);
206
+ });
207
+ console.log(`[Export] Real-time streaming active for export job ${jobId} (stream: ${streamSession.streamId})`);
208
+ }
209
+ catch (error) {
210
+ console.error(`[Export] Failed to setup streaming monitoring for job ${jobId}:`, {
211
+ error: error?.message || error,
212
+ stack: error?.stack,
213
+ jobId,
214
+ progressToken
215
+ });
216
+ // Fallback to basic progress tracking if streaming fails
217
+ await this.storeProgressUpdate(progressToken, 5, 100, `Export started - streaming unavailable: ${error?.message || 'Unknown error'}`);
218
+ // Start fallback polling mechanism
219
+ await this.startFallbackProgressPolling(jobId, progressToken);
220
+ }
221
+ }
222
+ /**
223
+ * Start fallback polling mechanism when streaming fails
224
+ */
225
+ async startFallbackProgressPolling(jobId, progressToken) {
226
+ console.log(`[Export] Starting fallback polling for export job ${jobId} with token ${progressToken}`);
227
+ const stream = this.activeStreams.get(progressToken);
228
+ if (!stream) {
229
+ console.error(`[Export] No stream found for token ${progressToken}`);
230
+ return;
231
+ }
232
+ // Start polling every 10 seconds
233
+ const pollInterval = setInterval(async () => {
234
+ try {
235
+ const exportStatus = await this.getExportStatus(jobId);
236
+ if (!this.activeStreams.has(progressToken)) {
237
+ clearInterval(pollInterval);
238
+ return;
239
+ }
240
+ const currentStream = this.activeStreams.get(progressToken);
241
+ let progress = currentStream.lastProgress;
242
+ let message = `Export ${exportStatus.status}`;
243
+ // Map export status to progress
244
+ switch (exportStatus.status) {
245
+ case 'pending':
246
+ progress = 5;
247
+ message = 'Export queued and waiting to start';
248
+ break;
249
+ case 'validating':
250
+ progress = 10;
251
+ message = exportStatus.progress?.message || 'Validating export configuration';
252
+ break;
253
+ case 'building':
254
+ progress = 30;
255
+ message = exportStatus.progress?.message || 'Building container image';
256
+ break;
257
+ case 'preparing':
258
+ progress = 40;
259
+ message = exportStatus.progress?.message || 'Preparing export';
260
+ break;
261
+ case 'authenticating':
262
+ progress = 50;
263
+ message = exportStatus.progress?.message || 'Authenticating with destination';
264
+ break;
265
+ case 'uploading':
266
+ progress = 70;
267
+ message = exportStatus.progress?.message || 'Uploading files';
268
+ break;
269
+ case 'pushing':
270
+ progress = 80;
271
+ message = exportStatus.progress?.message || 'Pushing to destination';
272
+ break;
273
+ case 'verifying':
274
+ progress = 95;
275
+ message = exportStatus.progress?.message || 'Verifying export';
276
+ break;
277
+ case 'completed':
278
+ progress = 100;
279
+ message = this.getCompletionMessage(exportStatus);
280
+ break;
281
+ case 'failed':
282
+ message = `Export failed: ${exportStatus.errors?.[0] || 'Unknown error'}`;
283
+ break;
284
+ }
285
+ // Update progress
286
+ currentStream.lastProgress = progress;
287
+ await this.storeProgressUpdate(progressToken, progress, 100, message, {
288
+ jobId,
289
+ status: exportStatus.status,
290
+ fallbackMode: true
291
+ });
292
+ // Complete if finished
293
+ if (['completed', 'failed', 'cancelled'].includes(exportStatus.status)) {
294
+ const completionTime = Date.now() - currentStream.startTime;
295
+ this.completeStream(progressToken, exportStatus.status === 'completed' ? 'completed' : 'failed', completionTime);
296
+ clearInterval(pollInterval);
297
+ }
298
+ }
299
+ catch (error) {
300
+ console.error(`[Export] Error in fallback polling for ${jobId}:`, error);
301
+ }
302
+ }, 10000); // Poll every 10 seconds
303
+ // Auto-cleanup after 30 minutes
304
+ setTimeout(() => {
305
+ if (this.activeStreams.has(progressToken)) {
306
+ console.log(`[Export] Auto-cleaning up fallback polling for ${jobId} after 30 minutes`);
307
+ clearInterval(pollInterval);
308
+ this.completeStream(progressToken, 'timeout');
309
+ }
310
+ }, 30 * 60 * 1000);
311
+ }
312
+ /**
313
+ * Get completion message based on export result
314
+ */
315
+ getCompletionMessage(exportStatus) {
316
+ if (exportStatus.result?.imageUrl) {
317
+ return `Export completed! Image pushed to ${exportStatus.result.imageUrl}`;
318
+ }
319
+ if (exportStatus.result?.repositoryUrl) {
320
+ return `Export completed! Repository: ${exportStatus.result.repositoryUrl}`;
321
+ }
322
+ if (exportStatus.result?.downloadUrl) {
323
+ return `Export completed! Download ready (expires ${exportStatus.result.expiresAt})`;
324
+ }
325
+ return 'Export completed successfully!';
326
+ }
327
+ /**
328
+ * Handle real-time export stream events
329
+ */
330
+ async handleExportStreamEvent(event, progressToken) {
331
+ try {
332
+ const stream = this.activeStreams.get(progressToken);
333
+ if (!stream) {
334
+ console.warn(`[Export] Received event for unknown progress token: ${progressToken}`);
335
+ return;
336
+ }
337
+ console.log(`[Export] Processing ${event.eventType} for export job ${stream.jobId}: ${event.data.message || event.data.data?.message || ''}`);
338
+ // Convert stream event to progress update
339
+ const progressUpdate = this.convertStreamEventToProgress(event, progressToken, stream);
340
+ // Store progress update
341
+ await this.storeProgressUpdate(progressToken, progressUpdate.progress, progressUpdate.total, progressUpdate.message, progressUpdate.metadata);
342
+ // Update stream tracking
343
+ stream.lastProgress = progressUpdate.progress;
344
+ // Handle completion events
345
+ if (['export-completed', 'export-failed'].includes(event.eventType)) {
346
+ const completionTime = Date.now() - stream.startTime;
347
+ this.completeStream(progressToken, event.eventType.replace('export-', ''), completionTime);
348
+ }
349
+ }
350
+ catch (error) {
351
+ console.error(`[Export] Error handling stream event:`, error);
352
+ this.streamAnalytics.errorsEncountered++;
353
+ }
354
+ }
355
+ /**
356
+ * Convert stream event to progress update format
357
+ */
358
+ convertStreamEventToProgress(event, progressToken, stream) {
359
+ const baseProgress = {
360
+ progressToken,
361
+ total: 100,
362
+ timestamp: event.timestamp,
363
+ metadata: {
364
+ eventType: event.eventType,
365
+ jobId: stream.jobId,
366
+ containerId: stream.containerId,
367
+ destinationType: stream.destinationType,
368
+ streamId: stream.streamId,
369
+ ...event.data.data
370
+ }
371
+ };
372
+ const eventData = event.data.data || event.data;
373
+ switch (event.eventType) {
374
+ case 'export-started':
375
+ return {
376
+ ...baseProgress,
377
+ progress: 5,
378
+ message: eventData.message || 'Export started'
379
+ };
380
+ case 'export-validating':
381
+ return {
382
+ ...baseProgress,
383
+ progress: 10,
384
+ message: eventData.message || 'Validating export configuration'
385
+ };
386
+ case 'export-building':
387
+ return {
388
+ ...baseProgress,
389
+ progress: 30,
390
+ message: eventData.message || 'Building container image'
391
+ };
392
+ case 'export-preparing':
393
+ return {
394
+ ...baseProgress,
395
+ progress: 40,
396
+ message: eventData.message || 'Preparing export files'
397
+ };
398
+ case 'export-authenticating':
399
+ return {
400
+ ...baseProgress,
401
+ progress: 50,
402
+ message: eventData.message || 'Authenticating with destination'
403
+ };
404
+ case 'export-uploading':
405
+ return {
406
+ ...baseProgress,
407
+ progress: 70,
408
+ message: eventData.message || 'Uploading files to destination'
409
+ };
410
+ case 'export-pushing':
411
+ return {
412
+ ...baseProgress,
413
+ progress: 80,
414
+ message: eventData.message || 'Pushing to registry/repository'
415
+ };
416
+ case 'export-verifying':
417
+ return {
418
+ ...baseProgress,
419
+ progress: 95,
420
+ message: eventData.message || 'Verifying export completion'
421
+ };
422
+ case 'export-completed':
423
+ return {
424
+ ...baseProgress,
425
+ progress: 100,
426
+ message: eventData.message || `Export completed successfully!`
427
+ };
428
+ case 'export-failed':
429
+ return {
430
+ ...baseProgress,
431
+ progress: Math.max(stream.lastProgress, 0),
432
+ message: eventData.message || eventData.error || 'Export failed'
433
+ };
434
+ default:
435
+ return {
436
+ ...baseProgress,
437
+ progress: Math.max(stream.lastProgress, 0),
438
+ message: eventData.message || `Unknown event: ${event.eventType}`
439
+ };
440
+ }
441
+ }
442
+ /**
443
+ * Store progress update with expiration
444
+ */
445
+ async storeProgressUpdate(progressToken, progress, total = 100, message, metadata) {
446
+ if (!progressToken)
447
+ return;
448
+ try {
449
+ const notificationData = {
450
+ progressToken,
451
+ progress,
452
+ total,
453
+ message,
454
+ timestamp: new Date().toISOString(),
455
+ expiresAt: Date.now() + (30 * 60 * 1000), // 30-minute expiration
456
+ ...(metadata && { metadata })
457
+ };
458
+ this.progressUpdates.set(progressToken, notificationData);
459
+ this.streamAnalytics.notificationsSent++;
460
+ this.streamAnalytics.lastActivity = new Date().toISOString();
461
+ console.log(`[Export] Progress stored: ${progress}/${total} - ${message} (token: ${progressToken})`);
462
+ }
463
+ catch (error) {
464
+ console.error(`[Export] Failed to store progress update:`, error);
465
+ this.streamAnalytics.errorsEncountered++;
466
+ }
467
+ }
468
+ /**
469
+ * Complete stream monitoring and update analytics
470
+ */
471
+ completeStream(progressToken, reason, completionTime) {
472
+ const stream = this.activeStreams.get(progressToken);
473
+ if (!stream)
474
+ return;
475
+ // Unsubscribe from stream
476
+ streamingService.unsubscribeFromStream(stream.streamId).catch(error => {
477
+ console.error(`[Export] Error unsubscribing from stream ${stream.streamId}:`, error);
478
+ });
479
+ // Update analytics
480
+ this.streamAnalytics.activeExports = Math.max(0, this.streamAnalytics.activeExports - 1);
481
+ if (reason === 'completed') {
482
+ this.streamAnalytics.completedExports++;
483
+ if (completionTime) {
484
+ const currentAvg = this.streamAnalytics.averageExportTime;
485
+ const totalCompleted = this.streamAnalytics.completedExports;
486
+ this.streamAnalytics.averageExportTime =
487
+ ((currentAvg * (totalCompleted - 1)) + completionTime) / totalCompleted;
488
+ }
489
+ }
490
+ else if (reason === 'failed') {
491
+ this.streamAnalytics.failedExports++;
492
+ }
493
+ // Clean up
494
+ this.activeStreams.delete(progressToken);
495
+ console.log(`[Export] Stream monitoring completed for ${stream.jobId} (${reason})`);
496
+ }
497
+ /**
498
+ * Get export job status
499
+ */
500
+ async getExportStatus(jobId) {
501
+ try {
502
+ if (!jobId) {
503
+ throw ErrorHandler.createValidationError('Job ID is required');
504
+ }
505
+ const response = await this.httpClient.get(`/v1/containers/exports/${jobId}`);
506
+ if (!response.data || !response.data.export) {
507
+ throw ErrorHandler.createNotFoundError(`Export job ${jobId} not found`);
508
+ }
509
+ const job = response.data.export;
510
+ return {
511
+ jobId: job.jobId,
512
+ containerId: job.containerId,
513
+ containerName: job.containerName,
514
+ destinationType: job.destinationType,
515
+ destination: job.destination,
516
+ status: job.status,
517
+ progress: job.progress,
518
+ result: job.result,
519
+ errors: job.errors || [],
520
+ tokenCost: job.tokenCost,
521
+ createdAt: job.createdAt,
522
+ updatedAt: job.updatedAt,
523
+ completedAt: job.completedAt,
524
+ estimatedDuration: job.estimatedDuration,
525
+ metadata: job.metadata
526
+ };
527
+ }
528
+ catch (error) {
529
+ if (error instanceof ApiError && error.status === 404) {
530
+ throw ErrorHandler.createNotFoundError(`Export job ${jobId} not found`);
531
+ }
532
+ throw ErrorHandler.processError(error, 'ExportService.getExportStatus');
533
+ }
534
+ }
535
+ /**
536
+ * List export jobs
537
+ */
538
+ async listExportJobs(params = {}) {
539
+ try {
540
+ const queryParams = new URLSearchParams();
541
+ if (params.containerId)
542
+ queryParams.append('containerId', params.containerId);
543
+ if (params.status)
544
+ queryParams.append('status', params.status);
545
+ if (params.limit)
546
+ queryParams.append('limit', params.limit.toString());
547
+ if (params.offset)
548
+ queryParams.append('offset', params.offset.toString());
549
+ let endpoint = '/v1/users/me/exports';
550
+ if (params.containerId) {
551
+ endpoint = `/v1/containers/${params.containerId}/exports`;
552
+ }
553
+ const response = await this.httpClient.get(`${endpoint}?${queryParams.toString()}`);
554
+ if (!response.data) {
555
+ throw ErrorHandler.createServerError('Invalid response format from exports list API');
556
+ }
557
+ const data = response.data;
558
+ return {
559
+ exports: (data.exports || []).map((job) => ({
560
+ jobId: job.jobId,
561
+ containerId: job.containerId,
562
+ containerName: job.containerName,
563
+ destinationType: job.destinationType,
564
+ destination: job.destination,
565
+ status: job.status,
566
+ progress: job.progress,
567
+ result: job.result,
568
+ errors: job.errors || [],
569
+ tokenCost: job.tokenCost,
570
+ createdAt: job.createdAt,
571
+ updatedAt: job.updatedAt,
572
+ completedAt: job.completedAt,
573
+ estimatedDuration: job.estimatedDuration,
574
+ metadata: job.metadata
575
+ })),
576
+ pagination: data.pagination || {
577
+ limit: params.limit || 50,
578
+ offset: params.offset || 0,
579
+ total: data.exports?.length || 0,
580
+ hasMore: false
581
+ }
582
+ };
583
+ }
584
+ catch (error) {
585
+ throw ErrorHandler.processError(error, 'ExportService.listExportJobs');
586
+ }
587
+ }
588
+ /**
589
+ * Get download URL for archive exports
590
+ */
591
+ async getExportDownloadUrl(jobId) {
592
+ try {
593
+ if (!jobId) {
594
+ throw ErrorHandler.createValidationError('Job ID is required');
595
+ }
596
+ const response = await this.httpClient.get(`/v1/containers/exports/${jobId}/download`);
597
+ if (!response.data) {
598
+ throw ErrorHandler.createServerError('Invalid response from download URL API');
599
+ }
600
+ return {
601
+ downloadUrl: response.data.downloadUrl,
602
+ expiresAt: response.data.expiresAt,
603
+ format: response.data.format,
604
+ size: response.data.size
605
+ };
606
+ }
607
+ catch (error) {
608
+ if (error instanceof ApiError && error.status === 404) {
609
+ throw ErrorHandler.createNotFoundError(`Export job ${jobId} not found or download not available`);
610
+ }
611
+ if (error instanceof ApiError && error.status === 410) {
612
+ throw ErrorHandler.createServerError('Download URL has expired');
613
+ }
614
+ throw ErrorHandler.processError(error, 'ExportService.getExportDownloadUrl');
615
+ }
616
+ }
617
+ /**
618
+ * Cancel an export job
619
+ */
620
+ async cancelExportJob(jobId) {
621
+ try {
622
+ if (!jobId) {
623
+ throw ErrorHandler.createValidationError('Job ID is required');
624
+ }
625
+ const response = await this.httpClient.delete(`/v1/containers/exports/${jobId}`);
626
+ if (!response.data || !response.data.export) {
627
+ throw ErrorHandler.createServerError('Invalid response from cancel export API');
628
+ }
629
+ const job = response.data.export;
630
+ return {
631
+ jobId: job.jobId,
632
+ containerId: job.containerId,
633
+ containerName: job.containerName,
634
+ destinationType: job.destinationType,
635
+ destination: job.destination,
636
+ status: job.status,
637
+ progress: job.progress,
638
+ result: job.result,
639
+ errors: job.errors || [],
640
+ tokenCost: job.tokenCost,
641
+ createdAt: job.createdAt,
642
+ updatedAt: job.updatedAt,
643
+ completedAt: job.completedAt
644
+ };
645
+ }
646
+ catch (error) {
647
+ if (error instanceof ApiError && error.status === 404) {
648
+ throw ErrorHandler.createNotFoundError(`Export job ${jobId} not found`);
649
+ }
650
+ if (error instanceof ApiError && error.status === 400) {
651
+ throw ErrorHandler.createValidationError('Cannot cancel export in current state');
652
+ }
653
+ throw ErrorHandler.processError(error, 'ExportService.cancelExportJob');
654
+ }
655
+ }
656
+ }
657
+ // Export singleton instance
658
+ export const exportService = new ExportService();
659
+ //# sourceMappingURL=exportService.js.map