@loxia-labs/loxia-autopilot-one 1.0.1

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 (80) hide show
  1. package/LICENSE +267 -0
  2. package/README.md +509 -0
  3. package/bin/cli.js +117 -0
  4. package/package.json +94 -0
  5. package/scripts/install-scanners.js +236 -0
  6. package/src/analyzers/CSSAnalyzer.js +297 -0
  7. package/src/analyzers/ConfigValidator.js +690 -0
  8. package/src/analyzers/ESLintAnalyzer.js +320 -0
  9. package/src/analyzers/JavaScriptAnalyzer.js +261 -0
  10. package/src/analyzers/PrettierFormatter.js +247 -0
  11. package/src/analyzers/PythonAnalyzer.js +266 -0
  12. package/src/analyzers/SecurityAnalyzer.js +729 -0
  13. package/src/analyzers/TypeScriptAnalyzer.js +247 -0
  14. package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
  15. package/src/analyzers/codeCloneDetector/detector.js +203 -0
  16. package/src/analyzers/codeCloneDetector/index.js +160 -0
  17. package/src/analyzers/codeCloneDetector/parser.js +199 -0
  18. package/src/analyzers/codeCloneDetector/reporter.js +148 -0
  19. package/src/analyzers/codeCloneDetector/scanner.js +59 -0
  20. package/src/core/agentPool.js +1474 -0
  21. package/src/core/agentScheduler.js +2147 -0
  22. package/src/core/contextManager.js +709 -0
  23. package/src/core/messageProcessor.js +732 -0
  24. package/src/core/orchestrator.js +548 -0
  25. package/src/core/stateManager.js +877 -0
  26. package/src/index.js +631 -0
  27. package/src/interfaces/cli.js +549 -0
  28. package/src/interfaces/webServer.js +2162 -0
  29. package/src/modules/fileExplorer/controller.js +280 -0
  30. package/src/modules/fileExplorer/index.js +37 -0
  31. package/src/modules/fileExplorer/middleware.js +92 -0
  32. package/src/modules/fileExplorer/routes.js +125 -0
  33. package/src/modules/fileExplorer/types.js +44 -0
  34. package/src/services/aiService.js +1232 -0
  35. package/src/services/apiKeyManager.js +164 -0
  36. package/src/services/benchmarkService.js +366 -0
  37. package/src/services/budgetService.js +539 -0
  38. package/src/services/contextInjectionService.js +247 -0
  39. package/src/services/conversationCompactionService.js +637 -0
  40. package/src/services/errorHandler.js +810 -0
  41. package/src/services/fileAttachmentService.js +544 -0
  42. package/src/services/modelRouterService.js +366 -0
  43. package/src/services/modelsService.js +322 -0
  44. package/src/services/qualityInspector.js +796 -0
  45. package/src/services/tokenCountingService.js +536 -0
  46. package/src/tools/agentCommunicationTool.js +1344 -0
  47. package/src/tools/agentDelayTool.js +485 -0
  48. package/src/tools/asyncToolManager.js +604 -0
  49. package/src/tools/baseTool.js +800 -0
  50. package/src/tools/browserTool.js +920 -0
  51. package/src/tools/cloneDetectionTool.js +621 -0
  52. package/src/tools/dependencyResolverTool.js +1215 -0
  53. package/src/tools/fileContentReplaceTool.js +875 -0
  54. package/src/tools/fileSystemTool.js +1107 -0
  55. package/src/tools/fileTreeTool.js +853 -0
  56. package/src/tools/imageTool.js +901 -0
  57. package/src/tools/importAnalyzerTool.js +1060 -0
  58. package/src/tools/jobDoneTool.js +248 -0
  59. package/src/tools/seekTool.js +956 -0
  60. package/src/tools/staticAnalysisTool.js +1778 -0
  61. package/src/tools/taskManagerTool.js +2873 -0
  62. package/src/tools/terminalTool.js +2304 -0
  63. package/src/tools/webTool.js +1430 -0
  64. package/src/types/agent.js +519 -0
  65. package/src/types/contextReference.js +972 -0
  66. package/src/types/conversation.js +730 -0
  67. package/src/types/toolCommand.js +747 -0
  68. package/src/utilities/attachmentValidator.js +292 -0
  69. package/src/utilities/configManager.js +582 -0
  70. package/src/utilities/constants.js +722 -0
  71. package/src/utilities/directoryAccessManager.js +535 -0
  72. package/src/utilities/fileProcessor.js +307 -0
  73. package/src/utilities/logger.js +436 -0
  74. package/src/utilities/tagParser.js +1246 -0
  75. package/src/utilities/toolConstants.js +317 -0
  76. package/web-ui/build/index.html +15 -0
  77. package/web-ui/build/logo.png +0 -0
  78. package/web-ui/build/logo2.png +0 -0
  79. package/web-ui/build/static/index-CjkkcnFA.js +344 -0
  80. package/web-ui/build/static/index-Dy2bYbOa.css +1 -0
@@ -0,0 +1,637 @@
1
+ /**
2
+ * ConversationCompactionService - Intelligent conversation compactization
3
+ *
4
+ * Purpose:
5
+ * - Compress long conversations while preserving critical information
6
+ * - Support multiple compaction strategies
7
+ * - Maintain conversation continuity and context
8
+ * - Enable agents to perform long-running tasks
9
+ *
10
+ * Strategies:
11
+ * 1. Summarization - AI-based intelligent summary (sandwich approach)
12
+ * 2. Truncation - Fast extraction for model switching
13
+ * 3. Aggressive - More aggressive summarization for retry scenarios
14
+ *
15
+ * Key Features:
16
+ * - Token-aware segment identification
17
+ * - Preservation guidelines (high/medium/low priority)
18
+ * - Quality validation
19
+ * - Model switching support
20
+ * - Comprehensive metadata tracking
21
+ */
22
+
23
+ import {
24
+ COMPACTION_CONFIG,
25
+ COMPACTION_STRATEGIES,
26
+ TOKEN_COUNTING_MODES,
27
+ } from '../utilities/constants.js';
28
+
29
+ class ConversationCompactionService {
30
+ constructor(tokenCountingService, aiService, logger) {
31
+ this.tokenCountingService = tokenCountingService;
32
+ this.aiService = aiService;
33
+ this.logger = logger;
34
+
35
+ // Summary generation prompt template
36
+ this.summaryPromptTemplate = this._createSummaryPromptTemplate();
37
+ }
38
+
39
+ /**
40
+ * Main compaction entry point - determines strategy and executes
41
+ * @param {Array} messages - Original messages array
42
+ * @param {string} currentModel - Current model being used
43
+ * @param {string} targetModel - Target model (may differ if switching)
44
+ * @param {Object} options - Compaction options
45
+ * @returns {Promise<Object>} Compaction result with messages and metadata
46
+ */
47
+ async compactConversation(messages, currentModel, targetModel, options = {}) {
48
+ const startTime = Date.now();
49
+
50
+ try {
51
+ // Validate inputs
52
+ if (!Array.isArray(messages) || messages.length === 0) {
53
+ throw new Error('Messages array is required and cannot be empty');
54
+ }
55
+
56
+ if (messages.length < COMPACTION_CONFIG.MIN_MESSAGES_FOR_COMPACTION) {
57
+ this.logger.warn('Too few messages for compaction', {
58
+ messageCount: messages.length,
59
+ minimum: COMPACTION_CONFIG.MIN_MESSAGES_FOR_COMPACTION
60
+ });
61
+
62
+ return {
63
+ compactedMessages: messages,
64
+ strategy: 'none',
65
+ originalTokenCount: 0,
66
+ compactedTokenCount: 0,
67
+ reductionPercent: 0,
68
+ skipped: true,
69
+ reason: 'Too few messages'
70
+ };
71
+ }
72
+
73
+ // Determine strategy
74
+ const isModelSwitch = currentModel !== targetModel;
75
+ const isRetry = options.isRetry || false;
76
+
77
+ let strategy;
78
+ if (isModelSwitch) {
79
+ strategy = COMPACTION_STRATEGIES.TRUNCATION;
80
+ } else if (isRetry) {
81
+ strategy = COMPACTION_STRATEGIES.AGGRESSIVE;
82
+ } else {
83
+ strategy = options.strategy || COMPACTION_STRATEGIES.SUMMARIZATION;
84
+ }
85
+
86
+ this.logger.info('Starting conversation compaction', {
87
+ messageCount: messages.length,
88
+ currentModel,
89
+ targetModel,
90
+ strategy,
91
+ isModelSwitch,
92
+ isRetry
93
+ });
94
+
95
+ // Execute strategy
96
+ let result;
97
+ switch (strategy) {
98
+ case COMPACTION_STRATEGIES.SUMMARIZATION:
99
+ result = await this._compactWithSummarization(messages, currentModel, options);
100
+ break;
101
+
102
+ case COMPACTION_STRATEGIES.TRUNCATION:
103
+ result = await this._compactWithTruncation(messages, targetModel, options);
104
+ break;
105
+
106
+ case COMPACTION_STRATEGIES.AGGRESSIVE:
107
+ result = await this._compactWithAggressiveSummarization(messages, currentModel, options);
108
+ break;
109
+
110
+ default:
111
+ throw new Error(`Unknown compaction strategy: ${strategy}`);
112
+ }
113
+
114
+ // Add execution metadata
115
+ const executionTime = Date.now() - startTime;
116
+ result.executionTime = executionTime;
117
+ result.timestamp = new Date().toISOString();
118
+
119
+ this.logger.info('Compaction completed successfully', {
120
+ strategy: result.strategy,
121
+ originalMessages: messages.length,
122
+ compactedMessages: result.compactedMessages.length,
123
+ originalTokens: result.originalTokenCount,
124
+ compactedTokens: result.compactedTokenCount,
125
+ reductionPercent: result.reductionPercent.toFixed(2),
126
+ executionTime: `${executionTime}ms`
127
+ });
128
+
129
+ return result;
130
+
131
+ } catch (error) {
132
+ const executionTime = Date.now() - startTime;
133
+
134
+ this.logger.error('Compaction failed', {
135
+ error: error.message,
136
+ messageCount: messages.length,
137
+ currentModel,
138
+ targetModel,
139
+ executionTime: `${executionTime}ms`
140
+ });
141
+
142
+ throw error;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Strategy 1: Intelligent summarization with sandwich approach
148
+ * Preserves beginning (15%) + AI summary of middle + end (35%)
149
+ * @private
150
+ */
151
+ async _compactWithSummarization(messages, model, options) {
152
+ const strategy = COMPACTION_STRATEGIES.SUMMARIZATION;
153
+
154
+ // Count tokens in original conversation
155
+ const originalTokenCount = await this.tokenCountingService.estimateConversationTokens(
156
+ messages,
157
+ model,
158
+ TOKEN_COUNTING_MODES.ACCURATE
159
+ );
160
+
161
+ // Identify segments using token-based boundaries
162
+ const segments = await this._identifySegments(
163
+ messages,
164
+ model,
165
+ COMPACTION_CONFIG.BEGINNING_SEGMENT_PERCENTAGE,
166
+ COMPACTION_CONFIG.END_SEGMENT_PERCENTAGE
167
+ );
168
+
169
+ this.logger.debug('Segments identified for summarization', {
170
+ beginningMessages: segments.beginning.length,
171
+ middleMessages: segments.middle.length,
172
+ endMessages: segments.end.length,
173
+ middleStartIndex: segments.middleStartIndex,
174
+ middleEndIndex: segments.middleEndIndex,
175
+ totalMessages: messages.length
176
+ });
177
+
178
+ // Generate summary of middle segment
179
+ const summary = await this._generateSummary(
180
+ segments.middle,
181
+ model,
182
+ {
183
+ ...options,
184
+ middleStartIndex: segments.middleStartIndex,
185
+ middleEndIndex: segments.middleEndIndex
186
+ }
187
+ );
188
+
189
+ // Construct compacted conversation
190
+ const compactedMessages = [
191
+ ...segments.beginning,
192
+ summary,
193
+ ...segments.end
194
+ ];
195
+
196
+ // Count tokens in compacted conversation
197
+ const compactedTokenCount = await this.tokenCountingService.estimateConversationTokens(
198
+ compactedMessages,
199
+ model,
200
+ TOKEN_COUNTING_MODES.ACCURATE
201
+ );
202
+
203
+ // Calculate reduction
204
+ const reductionPercent = ((originalTokenCount - compactedTokenCount) / originalTokenCount) * 100;
205
+
206
+ return {
207
+ compactedMessages,
208
+ strategy,
209
+ originalTokenCount,
210
+ compactedTokenCount,
211
+ reductionPercent,
212
+ segments: {
213
+ beginningCount: segments.beginning.length,
214
+ middleCount: segments.middle.length,
215
+ endCount: segments.end.length,
216
+ summaryInserted: true
217
+ }
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Strategy 2: Fast truncation for model switching
223
+ * Preserves beginning (30%) + separator + end (50%)
224
+ * No AI call required - fast extraction
225
+ * @private
226
+ */
227
+ async _compactWithTruncation(messages, targetModel, options) {
228
+ const strategy = COMPACTION_STRATEGIES.TRUNCATION;
229
+
230
+ // Get target context window
231
+ const targetContextWindow = this.tokenCountingService.getModelContextWindow(targetModel);
232
+ const targetTokenCount = Math.floor(targetContextWindow * 0.8); // Use 80% of context
233
+
234
+ // Count tokens in original conversation
235
+ const originalTokenCount = await this.tokenCountingService.estimateConversationTokens(
236
+ messages,
237
+ targetModel,
238
+ TOKEN_COUNTING_MODES.ACCURATE
239
+ );
240
+
241
+ // Identify segments using token-based boundaries (30% + 50%)
242
+ const segments = await this._identifySegments(
243
+ messages,
244
+ targetModel,
245
+ COMPACTION_CONFIG.TRUNCATION_BEGINNING_PERCENTAGE,
246
+ COMPACTION_CONFIG.TRUNCATION_END_PERCENTAGE
247
+ );
248
+
249
+ this.logger.debug('Segments identified for truncation', {
250
+ beginningMessages: segments.beginning.length,
251
+ middleMessages: segments.middle.length,
252
+ endMessages: segments.end.length,
253
+ targetTokenCount,
254
+ originalTokenCount
255
+ });
256
+
257
+ // Create separator message
258
+ const separatorMessage = {
259
+ role: 'system',
260
+ content: COMPACTION_CONFIG.COMPACTION_SEPARATOR_MESSAGE,
261
+ type: 'separator',
262
+ timestamp: new Date().toISOString(),
263
+ metadata: {
264
+ truncatedMessages: segments.middle.length,
265
+ reason: 'Model switch - middle section removed to fit target context window'
266
+ }
267
+ };
268
+
269
+ // Construct compacted conversation
270
+ const compactedMessages = [
271
+ ...segments.beginning,
272
+ separatorMessage,
273
+ ...segments.end
274
+ ];
275
+
276
+ // Count tokens in compacted conversation
277
+ const compactedTokenCount = await this.tokenCountingService.estimateConversationTokens(
278
+ compactedMessages,
279
+ targetModel,
280
+ TOKEN_COUNTING_MODES.ACCURATE
281
+ );
282
+
283
+ // Calculate reduction
284
+ const reductionPercent = ((originalTokenCount - compactedTokenCount) / originalTokenCount) * 100;
285
+
286
+ return {
287
+ compactedMessages,
288
+ strategy,
289
+ originalTokenCount,
290
+ compactedTokenCount,
291
+ reductionPercent,
292
+ segments: {
293
+ beginningCount: segments.beginning.length,
294
+ middleCount: segments.middle.length,
295
+ endCount: segments.end.length,
296
+ summaryInserted: false
297
+ }
298
+ };
299
+ }
300
+
301
+ /**
302
+ * Strategy 3: Aggressive summarization for retry scenarios
303
+ * More aggressive compression: 10% beginning + summary + 30% end
304
+ * @private
305
+ */
306
+ async _compactWithAggressiveSummarization(messages, model, options) {
307
+ const strategy = COMPACTION_STRATEGIES.AGGRESSIVE;
308
+
309
+ // Count tokens in original conversation
310
+ const originalTokenCount = await this.tokenCountingService.estimateConversationTokens(
311
+ messages,
312
+ model,
313
+ TOKEN_COUNTING_MODES.ACCURATE
314
+ );
315
+
316
+ // Identify segments with aggressive percentages
317
+ const segments = await this._identifySegments(
318
+ messages,
319
+ model,
320
+ COMPACTION_CONFIG.AGGRESSIVE_BEGINNING_PERCENTAGE,
321
+ COMPACTION_CONFIG.AGGRESSIVE_END_PERCENTAGE
322
+ );
323
+
324
+ this.logger.debug('Segments identified for aggressive compaction', {
325
+ beginningMessages: segments.beginning.length,
326
+ middleMessages: segments.middle.length,
327
+ endMessages: segments.end.length,
328
+ middleStartIndex: segments.middleStartIndex,
329
+ middleEndIndex: segments.middleEndIndex
330
+ });
331
+
332
+ // Generate summary with more aggressive compression instruction
333
+ const summary = await this._generateSummary(segments.middle, model, {
334
+ ...options,
335
+ aggressive: true,
336
+ middleStartIndex: segments.middleStartIndex,
337
+ middleEndIndex: segments.middleEndIndex
338
+ });
339
+
340
+ // Construct compacted conversation
341
+ const compactedMessages = [
342
+ ...segments.beginning,
343
+ summary,
344
+ ...segments.end
345
+ ];
346
+
347
+ // Count tokens in compacted conversation
348
+ const compactedTokenCount = await this.tokenCountingService.estimateConversationTokens(
349
+ compactedMessages,
350
+ model,
351
+ TOKEN_COUNTING_MODES.ACCURATE
352
+ );
353
+
354
+ // Calculate reduction
355
+ const reductionPercent = ((originalTokenCount - compactedTokenCount) / originalTokenCount) * 100;
356
+
357
+ return {
358
+ compactedMessages,
359
+ strategy,
360
+ originalTokenCount,
361
+ compactedTokenCount,
362
+ reductionPercent,
363
+ segments: {
364
+ beginningCount: segments.beginning.length,
365
+ middleCount: segments.middle.length,
366
+ endCount: segments.end.length,
367
+ summaryInserted: true
368
+ }
369
+ };
370
+ }
371
+
372
+ /**
373
+ * Identify conversation segments based on token counts
374
+ * @private
375
+ */
376
+ async _identifySegments(messages, model, beginningPercent, endPercent) {
377
+ // Calculate total tokens
378
+ const totalTokens = await this.tokenCountingService.estimateConversationTokens(
379
+ messages,
380
+ model,
381
+ TOKEN_COUNTING_MODES.ACCURATE
382
+ );
383
+
384
+ // Calculate target tokens for each segment
385
+ const beginningTargetTokens = Math.floor(totalTokens * beginningPercent);
386
+ const endTargetTokens = Math.floor(totalTokens * endPercent);
387
+
388
+ // Identify beginning segment (from start)
389
+ let beginningTokens = 0;
390
+ let beginningEndIndex = 0;
391
+
392
+ for (let i = 0; i < messages.length; i++) {
393
+ const messageTokens = await this.tokenCountingService.countTokens(
394
+ messages[i].content,
395
+ model,
396
+ TOKEN_COUNTING_MODES.CACHED
397
+ );
398
+
399
+ if (beginningTokens + messageTokens > beginningTargetTokens && i >= 2) {
400
+ // Ensure at least 2 messages in beginning (as per design doc)
401
+ break;
402
+ }
403
+
404
+ beginningTokens += messageTokens;
405
+ beginningEndIndex = i + 1;
406
+ }
407
+
408
+ // Identify end segment (from end, working backwards)
409
+ let endTokens = 0;
410
+ let endStartIndex = messages.length;
411
+
412
+ for (let i = messages.length - 1; i >= 0; i--) {
413
+ const messageTokens = await this.tokenCountingService.countTokens(
414
+ messages[i].content,
415
+ model,
416
+ TOKEN_COUNTING_MODES.CACHED
417
+ );
418
+
419
+ if (endTokens + messageTokens > endTargetTokens) {
420
+ break;
421
+ }
422
+
423
+ endTokens += messageTokens;
424
+ endStartIndex = i;
425
+ }
426
+
427
+ // Ensure no overlap
428
+ if (endStartIndex < beginningEndIndex) {
429
+ endStartIndex = beginningEndIndex;
430
+ }
431
+
432
+ // Extract segments
433
+ const beginning = messages.slice(0, beginningEndIndex);
434
+ const middle = messages.slice(beginningEndIndex, endStartIndex);
435
+ const end = messages.slice(endStartIndex);
436
+
437
+ return {
438
+ beginning,
439
+ middle,
440
+ end,
441
+ middleStartIndex: beginningEndIndex,
442
+ middleEndIndex: endStartIndex - 1 // Inclusive end index
443
+ };
444
+ }
445
+
446
+ /**
447
+ * Generate AI summary of middle segment using GPT-4o-mini
448
+ * @private
449
+ */
450
+ async _generateSummary(middleMessages, model, options = {}) {
451
+ if (middleMessages.length === 0) {
452
+ return {
453
+ role: 'system',
454
+ content: `${COMPACTION_CONFIG.COMPACTION_SUMMARY_PREFIX} No messages to summarize.`,
455
+ type: 'summary',
456
+ timestamp: new Date().toISOString()
457
+ };
458
+ }
459
+
460
+ // Format middle messages for summarization
461
+ const middleContent = middleMessages
462
+ .map(msg => `${msg.role}: ${msg.content}`)
463
+ .join('\n\n');
464
+
465
+ // Build summary prompt
466
+ const summaryPrompt = this.summaryPromptTemplate
467
+ .replace('{middle_segment}', middleContent)
468
+ .replace('{aggressive_instruction}', options.aggressive
469
+ ? '\n\nIMPORTANT: Be VERY aggressive in compression. Prioritize only the most critical information. Aim for maximum brevity while maintaining essential context.'
470
+ : '');
471
+
472
+ // Try primary model and fallbacks
473
+ const modelsToTry = [
474
+ COMPACTION_CONFIG.COMPACTION_MODEL,
475
+ ...COMPACTION_CONFIG.COMPACTION_MODEL_FALLBACKS
476
+ ];
477
+
478
+ let lastError = null;
479
+
480
+ for (const compactionModel of modelsToTry) {
481
+ try {
482
+ this.logger.debug('Generating summary', {
483
+ compactionModel,
484
+ middleMessageCount: middleMessages.length,
485
+ middleContentLength: middleContent.length,
486
+ aggressive: options.aggressive
487
+ });
488
+
489
+ // Call AI service
490
+ const response = await this.aiService.sendMessage(
491
+ compactionModel,
492
+ summaryPrompt,
493
+ {
494
+ systemPrompt: 'You are a conversation summarization expert. Your goal is to compress conversations while preserving critical information for continued interaction.',
495
+ maxTokens: COMPACTION_CONFIG.MAX_SUMMARY_TOKENS,
496
+ temperature: 0.3, // Lower temperature for consistency
497
+ sessionId: options.sessionId,
498
+ platformProvided: true // Use Loxia API key
499
+ }
500
+ );
501
+
502
+ const summaryContent = response.content.trim();
503
+
504
+ // Build index range string
505
+ const indexRange = (options.middleStartIndex !== undefined && options.middleEndIndex !== undefined)
506
+ ? `original messages ${options.middleStartIndex}-${options.middleEndIndex}`
507
+ : `${middleMessages.length} messages`;
508
+
509
+ this.logger.info('Summary generated successfully', {
510
+ compactionModel,
511
+ originalLength: middleContent.length,
512
+ summaryLength: summaryContent.length,
513
+ compressionRatio: (summaryContent.length / middleContent.length * 100).toFixed(2) + '%',
514
+ indexRange
515
+ });
516
+
517
+ // Return formatted summary message
518
+ return {
519
+ role: 'system',
520
+ content: `${COMPACTION_CONFIG.COMPACTION_SUMMARY_PREFIX} - ${indexRange}]\n\n${summaryContent}\n\n${COMPACTION_CONFIG.COMPACTION_SUMMARY_SUFFIX}`,
521
+ type: 'summary',
522
+ timestamp: new Date().toISOString(),
523
+ metadata: {
524
+ originalMessageCount: middleMessages.length,
525
+ originalStartIndex: options.middleStartIndex,
526
+ originalEndIndex: options.middleEndIndex,
527
+ compactionModel: compactionModel,
528
+ aggressive: options.aggressive || false
529
+ }
530
+ };
531
+
532
+ } catch (error) {
533
+ lastError = error;
534
+ this.logger.warn('Summary generation failed with model, trying next fallback', {
535
+ compactionModel,
536
+ error: error.message,
537
+ middleMessageCount: middleMessages.length
538
+ });
539
+ // Continue to next model
540
+ }
541
+ }
542
+
543
+ // All models failed, use fallback summary
544
+ this.logger.error('All summary generation attempts failed', {
545
+ modelsAttempted: modelsToTry.length,
546
+ lastError: lastError?.message,
547
+ middleMessageCount: middleMessages.length
548
+ });
549
+
550
+ // Fallback to simple truncated summary
551
+ const fallbackSummary = this._createFallbackSummary(middleMessages);
552
+
553
+ // Build index range string for fallback
554
+ const indexRange = (options.middleStartIndex !== undefined && options.middleEndIndex !== undefined)
555
+ ? `original messages ${options.middleStartIndex}-${options.middleEndIndex}`
556
+ : `${middleMessages.length} messages`;
557
+
558
+ return {
559
+ role: 'system',
560
+ content: `${COMPACTION_CONFIG.COMPACTION_SUMMARY_PREFIX} - ${indexRange}]\n\n${fallbackSummary}\n\n${COMPACTION_CONFIG.COMPACTION_SUMMARY_SUFFIX}`,
561
+ type: 'summary',
562
+ timestamp: new Date().toISOString(),
563
+ metadata: {
564
+ originalMessageCount: middleMessages.length,
565
+ originalStartIndex: options.middleStartIndex,
566
+ originalEndIndex: options.middleEndIndex,
567
+ fallback: true,
568
+ error: lastError?.message,
569
+ modelsAttempted: modelsToTry
570
+ }
571
+ };
572
+ }
573
+
574
+ /**
575
+ * Create fallback summary when AI generation fails
576
+ * @private
577
+ */
578
+ _createFallbackSummary(messages) {
579
+ const messageCount = messages.length;
580
+ const roles = messages.map(m => m.role);
581
+ const userCount = roles.filter(r => r === 'user').length;
582
+ const assistantCount = roles.filter(r => r === 'assistant').length;
583
+
584
+ return `[${messageCount} messages summarized - ${userCount} user messages, ${assistantCount} assistant responses. ` +
585
+ `Summary generation unavailable - middle section compressed for context management.]`;
586
+ }
587
+
588
+ /**
589
+ * Create summary prompt template with preservation guidelines
590
+ * @private
591
+ */
592
+ _createSummaryPromptTemplate() {
593
+ return `You are compacting a conversation to preserve critical information while reducing token count.
594
+
595
+ CONTEXT: This is part of a longer conversation that needs compactization for continued processing.
596
+
597
+ PRESERVATION GUIDELINES:
598
+
599
+ HIGH PRIORITY (Always Preserve):
600
+ - Future reference value: Information likely to be referenced again
601
+ - Decisions and reasoning: WHY things were decided, not just what
602
+ - API signatures and interfaces: Function definitions, method calls
603
+ - Active dependencies: Information that ongoing work relies on
604
+ - Error patterns and solutions: What failed and how it was fixed
605
+ - Key facts and data: Specific numbers, names, configurations
606
+
607
+ MEDIUM PRIORITY (Compress Intelligently):
608
+ - Code blocks: Keep function signatures + brief description, compress implementation details
609
+ - Working solutions: Essence and outcome, not every implementation step
610
+ - Failed attempts: Brief mention of what didn't work and why, skip detailed troubleshooting
611
+ - Repetitive content: Consolidate similar examples or explanations
612
+
613
+ LOW PRIORITY (Heavily Compress/Remove):
614
+ - Completed calculations: Keep results, skip intermediate steps
615
+ - Verbose explanations: Summarize well-known concepts
616
+ - Debug output: Skip terminal logs and error messages that served their purpose
617
+ - Trial-and-error sequences: Skip multiple failed attempts with no lasting value
618
+ - Acknowledgments and pleasantries: Skip "thank you", "sure", "okay" type exchanges
619
+
620
+ CONVERSATION SEGMENT TO SUMMARIZE:
621
+ {middle_segment}
622
+
623
+ TASK: Create a concise summary that preserves logical flow and critical information. Focus on:
624
+ 1. Key decisions and their reasoning
625
+ 2. Important facts, data, and configurations
626
+ 3. Active context needed for continuation
627
+ 4. Problem-solving outcomes (skip the debugging process)
628
+ 5. Dependencies and interfaces that code/work relies on
629
+
630
+ Someone reading this should understand the conversation progression and have all information needed for effective continuation.
631
+ {aggressive_instruction}
632
+
633
+ OUTPUT: Provide ONLY the summary text without preamble, explanation, or meta-commentary.`;
634
+ }
635
+ }
636
+
637
+ export default ConversationCompactionService;