ms365-mcp-server 1.1.16 → 1.1.18

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,599 @@
1
+ import { logger } from './api.js';
2
+ export class LargeMailboxSearch {
3
+ constructor(ms365Operations, contextAwareSearch, intelligenceEngine, proactiveIntelligence) {
4
+ this.ms365Operations = ms365Operations;
5
+ this.contextAwareSearch = contextAwareSearch;
6
+ this.intelligenceEngine = intelligenceEngine;
7
+ this.proactiveIntelligence = proactiveIntelligence;
8
+ }
9
+ /**
10
+ * Perform scalable search for large mailboxes (20k+ emails)
11
+ */
12
+ async search(query, options = {}) {
13
+ const startTime = Date.now();
14
+ const opts = { ...LargeMailboxSearch.DEFAULT_OPTIONS, ...options };
15
+ logger.log(`🔍 Large mailbox search initiated for: "${query}"`);
16
+ logger.log(`📊 Search options: ${JSON.stringify(opts)}`);
17
+ // Dynamic timeout based on mailbox size estimation
18
+ const estimatedSize = await this.quickMailboxSizeEstimate();
19
+ const baseTimeout = 60000; // 60 seconds base
20
+ const searchTimeout = estimatedSize > 50000 ?
21
+ 180000 : // 3 minutes for very large mailboxes (50k+)
22
+ estimatedSize > 20000 ?
23
+ 120000 : // 2 minutes for large mailboxes (20k+)
24
+ baseTimeout; // 1 minute for smaller mailboxes
25
+ logger.log(`⏱️ Dynamic timeout set to ${searchTimeout / 1000} seconds based on estimated mailbox size: ${estimatedSize}`);
26
+ // Progressive timeout with partial results
27
+ const searchPromise = this.performProgressiveSearch(query, opts, searchTimeout);
28
+ const timeoutPromise = new Promise((_, reject) => {
29
+ setTimeout(() => {
30
+ reject(new Error(`Large mailbox search timed out after ${searchTimeout / 1000} seconds. Try using more specific search terms, reducing the time window (timeWindowDays), or using smaller maxTotalResults.`));
31
+ }, searchTimeout);
32
+ });
33
+ return Promise.race([searchPromise, timeoutPromise]);
34
+ }
35
+ async performActualSearch(query, opts) {
36
+ const startTime = Date.now();
37
+ const result = {
38
+ query,
39
+ totalEmailsInMailbox: 0,
40
+ searchStrategy: '',
41
+ tierResults: [],
42
+ finalResults: [],
43
+ confidence: 0,
44
+ searchTime: 0,
45
+ recommendations: []
46
+ };
47
+ // Get total mailbox size estimate
48
+ result.totalEmailsInMailbox = await this.estimateMailboxSize();
49
+ if (result.totalEmailsInMailbox < 1000) {
50
+ // Small mailbox - use direct approach
51
+ return await this.performDirectSearch(query, opts, result);
52
+ }
53
+ else if (result.totalEmailsInMailbox < 10000) {
54
+ // Medium mailbox - use filtered approach
55
+ return await this.performFilteredSearch(query, opts, result);
56
+ }
57
+ else {
58
+ // Large mailbox (20k+) - use multi-tier approach
59
+ return await this.performTieredSearch(query, opts, result);
60
+ }
61
+ }
62
+ /**
63
+ * Multi-tier search strategy for 20k+ emails
64
+ */
65
+ async performTieredSearch(query, options, result) {
66
+ result.searchStrategy = 'multi-tier-large-mailbox';
67
+ // Define search tiers in order of priority
68
+ const tiers = this.buildSearchTiers(query, options);
69
+ let allResults = [];
70
+ let totalSearched = 0;
71
+ for (const tier of tiers) {
72
+ if (allResults.length >= options.maxTotalResults) {
73
+ logger.log(`🎯 Reached target results (${options.maxTotalResults}), stopping search`);
74
+ break;
75
+ }
76
+ const tierStartTime = Date.now();
77
+ logger.log(`🔍 Executing tier: ${tier.name}`);
78
+ try {
79
+ // Use native MS365 search for initial filtering
80
+ const nativeResults = await this.ms365Operations.searchEmails({
81
+ ...tier.searchCriteria,
82
+ maxResults: Math.min(options.batchSize, 500)
83
+ });
84
+ const emailsSearched = nativeResults.messages.length;
85
+ totalSearched += emailsSearched;
86
+ if (emailsSearched > 0) {
87
+ // Apply AI intelligence to filtered results
88
+ let intelligentResults = [];
89
+ if (tier.name.includes('context')) {
90
+ // Use context-aware search
91
+ const contextResults = await this.contextAwareSearch.search(query, nativeResults.messages);
92
+ intelligentResults = contextResults.emails;
93
+ }
94
+ else {
95
+ // Use fuzzy/intelligent search
96
+ const fuzzyResults = await this.intelligenceEngine.intelligentSearch(query, nativeResults.messages, { maxResults: 50 });
97
+ intelligentResults = fuzzyResults.results;
98
+ }
99
+ // Add to results (avoid duplicates)
100
+ const newResults = intelligentResults.filter(email => !allResults.some(existing => existing.id === email.id));
101
+ allResults.push(...newResults);
102
+ const tierTime = Date.now() - tierStartTime;
103
+ result.tierResults.push({
104
+ tier: tier.name,
105
+ emailsSearched,
106
+ resultsFound: newResults.length,
107
+ processingTime: tierTime
108
+ });
109
+ logger.log(`✅ Tier "${tier.name}": ${emailsSearched} searched, ${newResults.length} found (${tierTime}ms)`);
110
+ }
111
+ else {
112
+ logger.log(`⚠️ Tier "${tier.name}": No emails found matching criteria`);
113
+ }
114
+ }
115
+ catch (error) {
116
+ logger.error(`❌ Error in tier "${tier.name}":`, error);
117
+ }
118
+ }
119
+ // Limit final results and calculate confidence
120
+ result.finalResults = allResults.slice(0, options.maxTotalResults);
121
+ result.confidence = this.calculateSearchConfidence(result, totalSearched);
122
+ result.searchTime = Date.now() - Date.now();
123
+ result.recommendations = this.generateLargeMailboxRecommendations(result, options);
124
+ logger.log(`🎯 Large mailbox search completed: ${result.finalResults.length} results from ${totalSearched} emails searched`);
125
+ return result;
126
+ }
127
+ /**
128
+ * Build prioritized search tiers based on query analysis
129
+ */
130
+ buildSearchTiers(query, options) {
131
+ const tiers = [];
132
+ const currentDate = new Date();
133
+ // Tier 1: Recent emails with query terms (highest priority)
134
+ const recentDays = Math.min(options.timeWindowDays / 4, 30); // Last 30 days max
135
+ const recentDate = new Date(currentDate.getTime() - recentDays * 24 * 60 * 60 * 1000);
136
+ tiers.push({
137
+ name: 'recent-context-search',
138
+ searchCriteria: {
139
+ query: query,
140
+ after: recentDate.toISOString().split('T')[0],
141
+ folder: 'inbox'
142
+ },
143
+ priority: 1,
144
+ estimatedResults: 100
145
+ });
146
+ // Tier 2: Subject line matches (medium-high priority)
147
+ tiers.push({
148
+ name: 'subject-fuzzy-search',
149
+ searchCriteria: {
150
+ subject: query,
151
+ maxResults: 300
152
+ },
153
+ priority: 2,
154
+ estimatedResults: 150
155
+ });
156
+ // Tier 3: Sender-based search if query contains person/email
157
+ if (this.queryContainsPerson(query)) {
158
+ const personName = this.extractPersonFromQuery(query);
159
+ tiers.push({
160
+ name: 'sender-intelligent-search',
161
+ searchCriteria: {
162
+ from: personName,
163
+ maxResults: 200
164
+ },
165
+ priority: 3,
166
+ estimatedResults: 100
167
+ });
168
+ }
169
+ // Tier 4: Extended time range for important keywords
170
+ if (this.queryContainsImportantKeywords(query)) {
171
+ const extendedDate = new Date(currentDate.getTime() - options.timeWindowDays * 24 * 60 * 60 * 1000);
172
+ tiers.push({
173
+ name: 'extended-context-search',
174
+ searchCriteria: {
175
+ query: query,
176
+ after: extendedDate.toISOString().split('T')[0],
177
+ maxResults: 400
178
+ },
179
+ priority: 4,
180
+ estimatedResults: 200
181
+ });
182
+ }
183
+ // Tier 5: Folder-specific search for document-related queries
184
+ if (this.queryContainsDocumentKeywords(query)) {
185
+ tiers.push({
186
+ name: 'attachment-search',
187
+ searchCriteria: {
188
+ query: query,
189
+ hasAttachment: true,
190
+ maxResults: 200
191
+ },
192
+ priority: 5,
193
+ estimatedResults: 80
194
+ });
195
+ }
196
+ // Sort by priority and return
197
+ return tiers.sort((a, b) => a.priority - b.priority);
198
+ }
199
+ /**
200
+ * Estimate total mailbox size
201
+ */
202
+ async estimateMailboxSize() {
203
+ try {
204
+ // Get sample from different folders to estimate total
205
+ const folders = ['inbox', 'sent', 'archive'];
206
+ let totalEstimate = 0;
207
+ for (const folder of folders) {
208
+ try {
209
+ const sample = await this.ms365Operations.searchEmails({
210
+ query: '*',
211
+ folder: folder,
212
+ maxResults: 50
213
+ });
214
+ // Rough estimation: if we get 50, assume there are many more
215
+ if (sample.messages.length === 50) {
216
+ totalEstimate += 1000; // Conservative estimate per active folder
217
+ }
218
+ else {
219
+ totalEstimate += sample.messages.length;
220
+ }
221
+ }
222
+ catch (error) {
223
+ // Folder might not exist, continue
224
+ }
225
+ }
226
+ return Math.max(totalEstimate, 1000);
227
+ }
228
+ catch (error) {
229
+ logger.error('Error estimating mailbox size:', error);
230
+ return 5000; // Default assumption for large mailbox
231
+ }
232
+ }
233
+ /**
234
+ * Direct search for small mailboxes
235
+ */
236
+ async performDirectSearch(query, options, result) {
237
+ result.searchStrategy = 'direct-small-mailbox';
238
+ const emails = await this.ms365Operations.searchEmails({
239
+ query: '*',
240
+ maxResults: 1000
241
+ });
242
+ const contextResults = await this.contextAwareSearch.search(query, emails.messages);
243
+ result.finalResults = contextResults.emails.slice(0, options.maxTotalResults);
244
+ result.confidence = contextResults.confidence;
245
+ result.tierResults = [{
246
+ tier: 'direct-search',
247
+ emailsSearched: emails.messages.length,
248
+ resultsFound: result.finalResults.length,
249
+ processingTime: 0
250
+ }];
251
+ return result;
252
+ }
253
+ /**
254
+ * Filtered search for medium mailboxes
255
+ */
256
+ async performFilteredSearch(query, options, result) {
257
+ result.searchStrategy = 'filtered-medium-mailbox';
258
+ // Use native search to filter first
259
+ const filtered = await this.ms365Operations.searchEmails({
260
+ query: query,
261
+ maxResults: 500
262
+ });
263
+ const intelligentResults = await this.intelligenceEngine.intelligentSearch(query, filtered.messages, { maxResults: options.maxTotalResults });
264
+ result.finalResults = intelligentResults.results;
265
+ result.confidence = intelligentResults.insights.averageConfidence;
266
+ result.tierResults = [{
267
+ tier: 'filtered-search',
268
+ emailsSearched: filtered.messages.length,
269
+ resultsFound: result.finalResults.length,
270
+ processingTime: intelligentResults.insights.processingTime
271
+ }];
272
+ return result;
273
+ }
274
+ // Helper methods
275
+ queryContainsPerson(query) {
276
+ return /\b(from|by|sent by)\s+\w+/.test(query.toLowerCase()) ||
277
+ /\b[A-Z][a-z]+\s+[A-Z][a-z]+\b/.test(query);
278
+ }
279
+ extractPersonFromQuery(query) {
280
+ const match = query.match(/(?:from|by|sent by)\s+([a-zA-Z\s]+)/i);
281
+ return match ? match[1].trim() : '';
282
+ }
283
+ queryContainsImportantKeywords(query) {
284
+ const important = ['tax', 'legal', 'contract', 'government', 'urgent', 'important'];
285
+ return important.some(keyword => query.toLowerCase().includes(keyword));
286
+ }
287
+ queryContainsDocumentKeywords(query) {
288
+ const docKeywords = ['document', 'file', 'pdf', 'attachment', 'report'];
289
+ return docKeywords.some(keyword => query.toLowerCase().includes(keyword));
290
+ }
291
+ calculateSearchConfidence(result, totalSearched) {
292
+ const searchCoverage = Math.min(totalSearched / result.totalEmailsInMailbox, 1.0);
293
+ const resultQuality = result.finalResults.length > 0 ? 0.8 : 0.3;
294
+ return (searchCoverage * 0.4) + (resultQuality * 0.6);
295
+ }
296
+ generateLargeMailboxRecommendations(result, options) {
297
+ const recommendations = [];
298
+ if (result.finalResults.length === 0) {
299
+ recommendations.push("Try broader search terms or extend time range");
300
+ recommendations.push("Check if emails might be in Archive or Sent folders");
301
+ }
302
+ if (result.totalEmailsInMailbox > 20000) {
303
+ recommendations.push("Consider organizing emails into folders for faster searches");
304
+ recommendations.push("Use specific date ranges when possible (e.g., 'last month', '2023')");
305
+ }
306
+ const totalSearched = result.tierResults.reduce((sum, tier) => sum + tier.emailsSearched, 0);
307
+ const searchPercentage = (totalSearched / result.totalEmailsInMailbox) * 100;
308
+ if (searchPercentage < 10) {
309
+ recommendations.push(`Only searched ${searchPercentage.toFixed(1)}% of emails. Use more specific terms for better coverage.`);
310
+ }
311
+ return recommendations;
312
+ }
313
+ /**
314
+ * Quick mailbox size estimation for dynamic timeout calculation
315
+ */
316
+ async quickMailboxSizeEstimate() {
317
+ try {
318
+ // Try to get a quick count from recent emails to estimate total size
319
+ const graphClient = await this.ms365Operations.getGraphClient();
320
+ // Sample recent emails to estimate total mailbox size
321
+ const recentSample = await graphClient
322
+ .api('/me/messages')
323
+ .select('id')
324
+ .top(100)
325
+ .get();
326
+ if (recentSample.value && recentSample.value.length > 0) {
327
+ // If we get a full page of 100, estimate larger mailbox
328
+ if (recentSample.value.length === 100) {
329
+ // Try a folder count approach for better estimation
330
+ try {
331
+ const folders = await graphClient.api('/me/mailFolders').get();
332
+ const folderCount = folders.value ? folders.value.length : 1;
333
+ // Rough estimation: assume 1000+ emails per active folder
334
+ const estimatedSize = folderCount * 1000;
335
+ logger.log(`📊 Quick mailbox size estimate: ${estimatedSize} (based on ${folderCount} folders)`);
336
+ return Math.min(estimatedSize, 100000); // Cap at 100k
337
+ }
338
+ catch {
339
+ // Fallback to conservative estimate
340
+ return 10000;
341
+ }
342
+ }
343
+ else {
344
+ // Small mailbox
345
+ return recentSample.value.length * 10; // Multiply by 10 for rough total
346
+ }
347
+ }
348
+ // Default fallback
349
+ return 5000;
350
+ }
351
+ catch (error) {
352
+ logger.error('❌ Error in quick mailbox size estimation:', error);
353
+ return 10000; // Conservative default
354
+ }
355
+ }
356
+ /**
357
+ * Progressive search with partial results and early termination
358
+ */
359
+ async performProgressiveSearch(query, opts, totalTimeout) {
360
+ const startTime = Date.now();
361
+ const result = {
362
+ query,
363
+ totalEmailsInMailbox: 0,
364
+ searchStrategy: 'progressive-search',
365
+ tierResults: [],
366
+ finalResults: [],
367
+ confidence: 0,
368
+ searchTime: 0,
369
+ recommendations: []
370
+ };
371
+ // Quick mailbox size for strategy selection
372
+ result.totalEmailsInMailbox = await this.quickMailboxSizeEstimate();
373
+ try {
374
+ if (result.totalEmailsInMailbox < 1000) {
375
+ // Small mailbox - use direct approach
376
+ return await this.performDirectSearch(query, opts, result);
377
+ }
378
+ else if (result.totalEmailsInMailbox < 10000) {
379
+ // Medium mailbox - use filtered approach with timeout check
380
+ return await this.performFilteredSearchWithTimeout(query, opts, result, totalTimeout, startTime);
381
+ }
382
+ else {
383
+ // Large mailbox - use progressive tiered approach
384
+ return await this.performProgressiveTieredSearch(query, opts, result, totalTimeout, startTime);
385
+ }
386
+ }
387
+ catch (error) {
388
+ // If search fails, return partial results if any
389
+ if (result.finalResults.length > 0) {
390
+ result.searchTime = Date.now() - startTime;
391
+ result.confidence = 0.3; // Low confidence for partial results
392
+ result.recommendations.push(`Search interrupted but found ${result.finalResults.length} partial results. Consider using more specific search terms.`);
393
+ logger.log(`⚠️ Returning ${result.finalResults.length} partial results due to error: ${error}`);
394
+ return result;
395
+ }
396
+ else {
397
+ throw error;
398
+ }
399
+ }
400
+ }
401
+ /**
402
+ * Progressive tiered search with timeout monitoring
403
+ */
404
+ async performProgressiveTieredSearch(query, options, result, totalTimeout, startTime) {
405
+ result.searchStrategy = 'progressive-tiered-large-mailbox';
406
+ // Build optimized search tiers for large mailboxes
407
+ const tiers = this.buildOptimizedSearchTiers(query, options);
408
+ let allResults = [];
409
+ let totalSearched = 0;
410
+ const timePerTier = Math.floor(totalTimeout / tiers.length * 0.8); // Reserve 20% for processing
411
+ for (let i = 0; i < tiers.length; i++) {
412
+ const tier = tiers[i];
413
+ const tierStartTime = Date.now();
414
+ // Check overall timeout
415
+ if (Date.now() - startTime > totalTimeout * 0.9) {
416
+ logger.log(`⏱️ Approaching timeout, stopping at tier ${i + 1}/${tiers.length}`);
417
+ break;
418
+ }
419
+ // Check if we have enough results
420
+ if (allResults.length >= options.maxTotalResults) {
421
+ logger.log(`🎯 Reached target results (${options.maxTotalResults}), stopping search`);
422
+ break;
423
+ }
424
+ logger.log(`🔍 Executing tier ${i + 1}/${tiers.length}: ${tier.name} (${timePerTier / 1000}s limit)`);
425
+ try {
426
+ // Set timeout for this tier
427
+ const tierPromise = this.executeTierSearch(tier, query, options);
428
+ const tierTimeoutPromise = new Promise((_, reject) => {
429
+ setTimeout(() => reject(new Error('Tier timeout')), timePerTier);
430
+ });
431
+ const tierResults = await Promise.race([tierPromise, tierTimeoutPromise]);
432
+ if (tierResults && tierResults.length > 0) {
433
+ // Add to results (avoid duplicates)
434
+ const newResults = tierResults.filter((email) => !allResults.some(existing => existing.id === email.id));
435
+ allResults.push(...newResults);
436
+ totalSearched += tierResults.length;
437
+ const tierTime = Date.now() - tierStartTime;
438
+ result.tierResults.push({
439
+ tier: tier.name,
440
+ emailsSearched: tierResults.length,
441
+ resultsFound: newResults.length,
442
+ processingTime: tierTime
443
+ });
444
+ logger.log(`✅ Tier "${tier.name}": ${tierResults.length} searched, ${newResults.length} new results (${tierTime}ms)`);
445
+ }
446
+ else {
447
+ logger.log(`⚠️ Tier "${tier.name}": No results found`);
448
+ }
449
+ }
450
+ catch (error) {
451
+ logger.log(`⚠️ Tier "${tier.name}" timed out or failed: ${error.message}`);
452
+ // Continue with next tier
453
+ }
454
+ }
455
+ // Finalize results
456
+ result.finalResults = allResults.slice(0, options.maxTotalResults);
457
+ result.confidence = this.calculateSearchConfidence(result, totalSearched);
458
+ result.searchTime = Date.now() - startTime;
459
+ result.recommendations = this.generateOptimizedRecommendations(result, options);
460
+ logger.log(`🎯 Progressive search completed: ${result.finalResults.length} results from ${totalSearched} emails searched in ${result.searchTime}ms`);
461
+ return result;
462
+ }
463
+ /**
464
+ * Execute individual tier search with optimizations
465
+ */
466
+ async executeTierSearch(tier, query, options) {
467
+ // Use faster native search for initial filtering
468
+ const nativeResults = await this.ms365Operations.searchEmails({
469
+ ...tier.searchCriteria,
470
+ maxResults: Math.min(options.batchSize, 300) // Reduced batch size for faster execution
471
+ });
472
+ const emailsFound = nativeResults.messages || [];
473
+ if (emailsFound.length === 0) {
474
+ return [];
475
+ }
476
+ // Apply lighter AI processing for speed
477
+ if (tier.name.includes('recent') || tier.name.includes('urgent')) {
478
+ // Skip heavy AI processing for time-sensitive tiers
479
+ return emailsFound.slice(0, 30); // Return top 30 for speed
480
+ }
481
+ else {
482
+ // Use intelligent search for better relevance
483
+ const fuzzyResults = await this.intelligenceEngine.intelligentSearch(query, emailsFound, { maxResults: 25 } // Reduced for speed
484
+ );
485
+ return fuzzyResults.results || [];
486
+ }
487
+ }
488
+ /**
489
+ * Filtered search with timeout monitoring for medium mailboxes
490
+ */
491
+ async performFilteredSearchWithTimeout(query, options, result, totalTimeout, startTime) {
492
+ result.searchStrategy = 'filtered-search-with-timeout';
493
+ // Use existing filtered search but with timeout monitoring
494
+ try {
495
+ const filteredResult = await this.performFilteredSearch(query, options, result);
496
+ return filteredResult;
497
+ }
498
+ catch (error) {
499
+ // If timeout, return partial results if available
500
+ if (result.finalResults.length > 0) {
501
+ result.searchTime = Date.now() - startTime;
502
+ result.confidence = 0.5;
503
+ result.recommendations.push('Search timed out but returned partial results. Try using more specific search terms.');
504
+ return result;
505
+ }
506
+ throw error;
507
+ }
508
+ }
509
+ /**
510
+ * Build optimized search tiers for faster large mailbox searches
511
+ */
512
+ buildOptimizedSearchTiers(query, options) {
513
+ const tiers = [];
514
+ const currentDate = new Date();
515
+ // Tier 1: Very recent emails (last 7 days) - highest priority, fastest
516
+ const veryRecentDate = new Date(currentDate.getTime() - 7 * 24 * 60 * 60 * 1000);
517
+ tiers.push({
518
+ name: 'very-recent-urgent-search',
519
+ searchCriteria: {
520
+ query: query,
521
+ after: veryRecentDate.toISOString().split('T')[0],
522
+ folder: 'inbox'
523
+ },
524
+ priority: 1,
525
+ estimatedResults: 50
526
+ });
527
+ // Tier 2: Recent emails (last 30 days) with subject focus
528
+ const recentDate = new Date(currentDate.getTime() - 30 * 24 * 60 * 60 * 1000);
529
+ tiers.push({
530
+ name: 'recent-subject-search',
531
+ searchCriteria: {
532
+ subject: query,
533
+ after: recentDate.toISOString().split('T')[0],
534
+ maxResults: 200
535
+ },
536
+ priority: 2,
537
+ estimatedResults: 100
538
+ });
539
+ // Tier 3: Sender-based search if query contains person/email (faster than content search)
540
+ if (this.queryContainsPerson(query)) {
541
+ const personName = this.extractPersonFromQuery(query);
542
+ tiers.push({
543
+ name: 'sender-optimized-search',
544
+ searchCriteria: {
545
+ from: personName,
546
+ after: recentDate.toISOString().split('T')[0],
547
+ maxResults: 150
548
+ },
549
+ priority: 3,
550
+ estimatedResults: 75
551
+ });
552
+ }
553
+ // Tier 4: Extended time range but limited scope (only if still needed)
554
+ const extendedDate = new Date(currentDate.getTime() - Math.min(options.timeWindowDays, 90) * 24 * 60 * 60 * 1000);
555
+ tiers.push({
556
+ name: 'extended-limited-search',
557
+ searchCriteria: {
558
+ query: query,
559
+ after: extendedDate.toISOString().split('T')[0],
560
+ maxResults: 250
561
+ },
562
+ priority: 4,
563
+ estimatedResults: 125
564
+ });
565
+ return tiers.slice(0, 4); // Limit to 4 tiers for speed
566
+ }
567
+ /**
568
+ * Generate optimized recommendations for performance
569
+ */
570
+ generateOptimizedRecommendations(result, options) {
571
+ const recommendations = [];
572
+ if (result.finalResults.length === 0) {
573
+ recommendations.push('No results found. Try using broader search terms or increasing timeWindowDays.');
574
+ recommendations.push('Consider searching for sender names, subject keywords, or specific date ranges.');
575
+ }
576
+ else if (result.finalResults.length < 10) {
577
+ recommendations.push('Few results found. Consider expanding search terms or increasing timeWindowDays.');
578
+ }
579
+ if (result.searchTime > 30000) { // > 30 seconds
580
+ recommendations.push('Search took longer than expected. Try using more specific terms, reduce timeWindowDays, or lower maxTotalResults.');
581
+ }
582
+ if (result.confidence < 0.5) {
583
+ recommendations.push('Low confidence results. Use more specific search terms for better accuracy.');
584
+ }
585
+ if (result.totalEmailsInMailbox > 50000) {
586
+ recommendations.push('Very large mailbox detected. For best performance, use specific date ranges (timeWindowDays: 30) and targeted search terms.');
587
+ }
588
+ return recommendations;
589
+ }
590
+ }
591
+ LargeMailboxSearch.DEFAULT_OPTIONS = {
592
+ maxTotalResults: 50,
593
+ enableTieredSearch: true,
594
+ enableCaching: false,
595
+ prioritizeRecent: true,
596
+ timeWindowDays: 90,
597
+ batchSize: 150,
598
+ maxBatches: 8
599
+ };