ms365-mcp-server 1.1.15 → 1.1.17

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,390 @@
1
+ import { logger } from './api.js';
2
+ export var EmailCategory;
3
+ (function (EmailCategory) {
4
+ EmailCategory["GOVERNMENT"] = "government";
5
+ EmailCategory["TAX"] = "tax";
6
+ EmailCategory["LEGAL"] = "legal";
7
+ EmailCategory["FINANCIAL"] = "financial";
8
+ EmailCategory["HEALTHCARE"] = "healthcare";
9
+ EmailCategory["INSURANCE"] = "insurance";
10
+ EmailCategory["DEADLINE"] = "deadline";
11
+ EmailCategory["INVOICE"] = "invoice";
12
+ EmailCategory["CONTRACT"] = "contract";
13
+ EmailCategory["SECURITY"] = "security";
14
+ EmailCategory["COMPLIANCE"] = "compliance";
15
+ EmailCategory["NOTIFICATION"] = "notification";
16
+ EmailCategory["SPAM"] = "spam";
17
+ EmailCategory["PERSONAL"] = "personal";
18
+ EmailCategory["BUSINESS"] = "business";
19
+ EmailCategory["URGENT"] = "urgent";
20
+ EmailCategory["AUTOMATED"] = "automated";
21
+ })(EmailCategory || (EmailCategory = {}));
22
+ export class ProactiveIntelligence {
23
+ constructor(ms365Operations) {
24
+ this.ms365Operations = ms365Operations;
25
+ }
26
+ /**
27
+ * Classify a single email
28
+ */
29
+ classifyEmail(email) {
30
+ const matchedRules = [];
31
+ const categories = [];
32
+ const reasons = [];
33
+ let maxPriority = 'low';
34
+ let totalConfidence = 0;
35
+ for (const rule of ProactiveIntelligence.CLASSIFICATION_RULES) {
36
+ const match = this.evaluateRule(email, rule);
37
+ if (match.isMatch) {
38
+ matchedRules.push(rule.id);
39
+ categories.push(rule.category);
40
+ reasons.push(match.reason);
41
+ totalConfidence += match.confidence;
42
+ // Update priority
43
+ if (this.getPriorityScore(rule.priority) > this.getPriorityScore(maxPriority)) {
44
+ maxPriority = rule.priority;
45
+ }
46
+ }
47
+ }
48
+ const averageConfidence = matchedRules.length > 0 ? totalConfidence / matchedRules.length : 0;
49
+ const actions = this.generateActions(categories, maxPriority);
50
+ return {
51
+ email,
52
+ categories,
53
+ priority: maxPriority,
54
+ confidence: averageConfidence,
55
+ matchedRules,
56
+ reasons,
57
+ actions
58
+ };
59
+ }
60
+ /**
61
+ * Evaluate a classification rule against an email
62
+ */
63
+ evaluateRule(email, rule) {
64
+ let matches = 0;
65
+ let totalChecks = 0;
66
+ const matchDetails = [];
67
+ const emailText = {
68
+ senderDomain: email.from.address.split('@')[1]?.toLowerCase() || '',
69
+ senderName: email.from.name.toLowerCase(),
70
+ subject: email.subject.toLowerCase(),
71
+ body: email.bodyPreview.toLowerCase(),
72
+ combined: `${email.from.name} ${email.subject} ${email.bodyPreview}`.toLowerCase()
73
+ };
74
+ // Check sender domains
75
+ if (rule.patterns.senderDomains) {
76
+ totalChecks++;
77
+ for (const domain of rule.patterns.senderDomains) {
78
+ if (emailText.senderDomain.includes(domain.toLowerCase())) {
79
+ matches++;
80
+ matchDetails.push(`sender domain: ${domain}`);
81
+ break;
82
+ }
83
+ }
84
+ }
85
+ // Check sender names
86
+ if (rule.patterns.senderNames) {
87
+ totalChecks++;
88
+ for (const name of rule.patterns.senderNames) {
89
+ if (emailText.senderName.includes(name.toLowerCase())) {
90
+ matches++;
91
+ matchDetails.push(`sender name: ${name}`);
92
+ break;
93
+ }
94
+ }
95
+ }
96
+ // Check subject keywords
97
+ if (rule.patterns.subjectKeywords) {
98
+ totalChecks++;
99
+ for (const keyword of rule.patterns.subjectKeywords) {
100
+ if (emailText.subject.includes(keyword.toLowerCase())) {
101
+ matches++;
102
+ matchDetails.push(`subject keyword: ${keyword}`);
103
+ break;
104
+ }
105
+ }
106
+ }
107
+ // Check body keywords
108
+ if (rule.patterns.bodyKeywords) {
109
+ totalChecks++;
110
+ for (const keyword of rule.patterns.bodyKeywords) {
111
+ if (emailText.body.includes(keyword.toLowerCase())) {
112
+ matches++;
113
+ matchDetails.push(`body keyword: ${keyword}`);
114
+ break;
115
+ }
116
+ }
117
+ }
118
+ // Check combined keywords
119
+ if (rule.patterns.combinedKeywords) {
120
+ totalChecks++;
121
+ for (const keyword of rule.patterns.combinedKeywords) {
122
+ if (emailText.combined.includes(keyword.toLowerCase())) {
123
+ matches++;
124
+ matchDetails.push(`combined keyword: ${keyword}`);
125
+ break;
126
+ }
127
+ }
128
+ }
129
+ // Determine if rule matches
130
+ let isMatch = false;
131
+ const confidence = totalChecks > 0 ? matches / totalChecks : 0;
132
+ if (rule.conditions.matchType === 'any') {
133
+ isMatch = matches > 0;
134
+ }
135
+ else if (rule.conditions.matchType === 'all') {
136
+ isMatch = matches === totalChecks;
137
+ }
138
+ if (rule.conditions.minimumMatches) {
139
+ isMatch = matches >= rule.conditions.minimumMatches;
140
+ }
141
+ if (rule.conditions.confidenceThreshold) {
142
+ isMatch = isMatch && confidence >= rule.conditions.confidenceThreshold;
143
+ }
144
+ return {
145
+ isMatch,
146
+ confidence,
147
+ reason: `${rule.name}: ${matchDetails.join(', ')}`
148
+ };
149
+ }
150
+ /**
151
+ * Get priority score for comparison
152
+ */
153
+ getPriorityScore(priority) {
154
+ switch (priority) {
155
+ case 'critical': return 4;
156
+ case 'high': return 3;
157
+ case 'medium': return 2;
158
+ case 'low': return 1;
159
+ default: return 0;
160
+ }
161
+ }
162
+ /**
163
+ * Generate recommended actions based on classification
164
+ */
165
+ generateActions(categories, priority) {
166
+ const actions = [];
167
+ if (priority === 'critical') {
168
+ actions.push('Mark as high priority');
169
+ actions.push('Add to urgent folder');
170
+ }
171
+ if (categories.includes(EmailCategory.GOVERNMENT)) {
172
+ actions.push('File in government folder');
173
+ actions.push('Set reminder for response');
174
+ }
175
+ if (categories.includes(EmailCategory.TAX)) {
176
+ actions.push('File in tax documents');
177
+ actions.push('Check for deadline');
178
+ }
179
+ if (categories.includes(EmailCategory.DEADLINE)) {
180
+ actions.push('Set calendar reminder');
181
+ actions.push('Flag for follow-up');
182
+ }
183
+ if (categories.includes(EmailCategory.LEGAL)) {
184
+ actions.push('File in legal folder');
185
+ actions.push('Consider legal review');
186
+ }
187
+ if (categories.includes(EmailCategory.FINANCIAL)) {
188
+ actions.push('File in financial folder');
189
+ actions.push('Verify sender authenticity');
190
+ }
191
+ return actions;
192
+ }
193
+ /**
194
+ * Batch classify multiple emails
195
+ */
196
+ async batchClassifyEmails(emails) {
197
+ const results = new Map();
198
+ logger.log(`🎯 Classifying ${emails.length} emails`);
199
+ for (const email of emails) {
200
+ try {
201
+ const classification = this.classifyEmail(email);
202
+ results.set(email.id, classification);
203
+ }
204
+ catch (error) {
205
+ logger.error(`Error classifying email ${email.id}:`, error);
206
+ }
207
+ }
208
+ const criticalEmails = Array.from(results.values()).filter(r => r.priority === 'critical').length;
209
+ const highPriorityEmails = Array.from(results.values()).filter(r => r.priority === 'high').length;
210
+ logger.log(`🎯 Classification complete: ${criticalEmails} critical, ${highPriorityEmails} high priority`);
211
+ return results;
212
+ }
213
+ /**
214
+ * Get emails by category
215
+ */
216
+ getEmailsByCategory(classifications, category) {
217
+ return Array.from(classifications.values())
218
+ .filter(result => result.categories.includes(category))
219
+ .sort((a, b) => this.getPriorityScore(b.priority) - this.getPriorityScore(a.priority));
220
+ }
221
+ /**
222
+ * Get high priority emails
223
+ */
224
+ getHighPriorityEmails(classifications) {
225
+ return Array.from(classifications.values())
226
+ .filter(result => result.priority === 'critical' || result.priority === 'high')
227
+ .sort((a, b) => this.getPriorityScore(b.priority) - this.getPriorityScore(a.priority));
228
+ }
229
+ /**
230
+ * Generate classification summary
231
+ */
232
+ generateClassificationSummary(classifications) {
233
+ const results = Array.from(classifications.values());
234
+ const byPriority = {
235
+ critical: results.filter(r => r.priority === 'critical').length,
236
+ high: results.filter(r => r.priority === 'high').length,
237
+ medium: results.filter(r => r.priority === 'medium').length,
238
+ low: results.filter(r => r.priority === 'low').length
239
+ };
240
+ const categoryCount = new Map();
241
+ const reasonCount = new Map();
242
+ const actionCount = new Map();
243
+ for (const result of results) {
244
+ // Count categories
245
+ for (const category of result.categories) {
246
+ categoryCount.set(category, (categoryCount.get(category) || 0) + 1);
247
+ }
248
+ // Count reasons
249
+ for (const reason of result.reasons) {
250
+ reasonCount.set(reason, (reasonCount.get(reason) || 0) + 1);
251
+ }
252
+ // Count actions
253
+ for (const action of result.actions) {
254
+ actionCount.set(action, (actionCount.get(action) || 0) + 1);
255
+ }
256
+ }
257
+ const byCategory = Object.fromEntries(categoryCount);
258
+ const topReasons = Array.from(reasonCount.entries())
259
+ .sort((a, b) => b[1] - a[1])
260
+ .slice(0, 10)
261
+ .map(([reason]) => reason);
262
+ const recommendedActions = Array.from(actionCount.entries())
263
+ .sort((a, b) => b[1] - a[1])
264
+ .slice(0, 10)
265
+ .map(([action]) => action);
266
+ return {
267
+ totalEmails: results.length,
268
+ byPriority,
269
+ byCategory,
270
+ topReasons,
271
+ recommendedActions
272
+ };
273
+ }
274
+ }
275
+ ProactiveIntelligence.CLASSIFICATION_RULES = [
276
+ // Government emails
277
+ {
278
+ id: 'gov_federal',
279
+ name: 'Federal Government',
280
+ category: EmailCategory.GOVERNMENT,
281
+ priority: 'critical',
282
+ patterns: {
283
+ senderDomains: ['.gov', '.mil', 'irs.gov', 'ssa.gov', 'treasury.gov'],
284
+ senderNames: ['Internal Revenue Service', 'Social Security', 'Treasury', 'IRS'],
285
+ combinedKeywords: ['federal', 'government', 'official', 'notice']
286
+ },
287
+ conditions: { matchType: 'any', confidenceThreshold: 0.8 }
288
+ },
289
+ // Tax notices
290
+ {
291
+ id: 'tax_notice',
292
+ name: 'Tax Notice',
293
+ category: EmailCategory.TAX,
294
+ priority: 'critical',
295
+ patterns: {
296
+ subjectKeywords: ['tax', 'irs', 'notice', 'refund', 'audit', 'filing'],
297
+ bodyKeywords: ['tax return', 'refund', 'audit', 'tax due', 'payment'],
298
+ senderDomains: ['irs.gov', 'treasury.gov']
299
+ },
300
+ conditions: { matchType: 'any', minimumMatches: 2 }
301
+ },
302
+ // Legal documents
303
+ {
304
+ id: 'legal_doc',
305
+ name: 'Legal Document',
306
+ category: EmailCategory.LEGAL,
307
+ priority: 'high',
308
+ patterns: {
309
+ subjectKeywords: ['legal', 'court', 'lawsuit', 'summons', 'subpoena'],
310
+ bodyKeywords: ['attorney', 'lawyer', 'court', 'legal action', 'litigation'],
311
+ senderNames: ['Attorney', 'Legal', 'Court', 'Law Firm']
312
+ },
313
+ conditions: { matchType: 'any', minimumMatches: 1 }
314
+ },
315
+ // Financial institutions
316
+ {
317
+ id: 'financial_inst',
318
+ name: 'Financial Institution',
319
+ category: EmailCategory.FINANCIAL,
320
+ priority: 'high',
321
+ patterns: {
322
+ senderDomains: ['bank', 'credit', 'financial', 'investment'],
323
+ subjectKeywords: ['account', 'statement', 'balance', 'transaction'],
324
+ bodyKeywords: ['account number', 'balance', 'transaction', 'payment']
325
+ },
326
+ conditions: { matchType: 'any', minimumMatches: 1 }
327
+ },
328
+ // Healthcare
329
+ {
330
+ id: 'healthcare',
331
+ name: 'Healthcare',
332
+ category: EmailCategory.HEALTHCARE,
333
+ priority: 'high',
334
+ patterns: {
335
+ senderNames: ['Hospital', 'Medical', 'Doctor', 'Clinic', 'Health'],
336
+ subjectKeywords: ['appointment', 'medical', 'health', 'prescription'],
337
+ bodyKeywords: ['patient', 'medical', 'health', 'appointment', 'prescription']
338
+ },
339
+ conditions: { matchType: 'any', minimumMatches: 1 }
340
+ },
341
+ // Insurance
342
+ {
343
+ id: 'insurance',
344
+ name: 'Insurance',
345
+ category: EmailCategory.INSURANCE,
346
+ priority: 'high',
347
+ patterns: {
348
+ senderNames: ['Insurance', 'Policy', 'Claims'],
349
+ subjectKeywords: ['policy', 'claim', 'coverage', 'premium'],
350
+ bodyKeywords: ['policy number', 'claim', 'coverage', 'premium', 'deductible']
351
+ },
352
+ conditions: { matchType: 'any', minimumMatches: 1 }
353
+ },
354
+ // Deadline-sensitive
355
+ {
356
+ id: 'deadline',
357
+ name: 'Deadline/Urgent',
358
+ category: EmailCategory.DEADLINE,
359
+ priority: 'critical',
360
+ patterns: {
361
+ subjectKeywords: ['deadline', 'due', 'expires', 'urgent', 'asap'],
362
+ bodyKeywords: ['deadline', 'due date', 'expires', 'urgent', 'immediately']
363
+ },
364
+ conditions: { matchType: 'any', minimumMatches: 1 }
365
+ },
366
+ // Invoices
367
+ {
368
+ id: 'invoice',
369
+ name: 'Invoice/Bill',
370
+ category: EmailCategory.INVOICE,
371
+ priority: 'medium',
372
+ patterns: {
373
+ subjectKeywords: ['invoice', 'bill', 'payment', 'receipt'],
374
+ bodyKeywords: ['invoice number', 'amount due', 'payment', 'bill']
375
+ },
376
+ conditions: { matchType: 'any', minimumMatches: 1 }
377
+ },
378
+ // Security alerts
379
+ {
380
+ id: 'security',
381
+ name: 'Security Alert',
382
+ category: EmailCategory.SECURITY,
383
+ priority: 'critical',
384
+ patterns: {
385
+ subjectKeywords: ['security', 'alert', 'breach', 'unauthorized', 'suspicious'],
386
+ bodyKeywords: ['security', 'breach', 'unauthorized', 'login', 'password']
387
+ },
388
+ conditions: { matchType: 'any', minimumMatches: 1 }
389
+ }
390
+ ];
@@ -0,0 +1,284 @@
1
+ import { logger } from './api.js';
2
+ export class RateLimiter {
3
+ constructor(config) {
4
+ this.config = config;
5
+ this.requests = [];
6
+ this.throttleUntil = 0;
7
+ this.adaptiveDelay = 0;
8
+ this.consecutiveThrottles = 0;
9
+ }
10
+ /**
11
+ * Check if request is allowed
12
+ */
13
+ checkLimit(endpoint) {
14
+ const now = Date.now();
15
+ const windowStart = now - this.config.windowMs;
16
+ // Remove old requests outside the window
17
+ this.requests = this.requests.filter(req => req.timestamp > windowStart);
18
+ // Check if we're still throttled
19
+ if (this.throttleUntil > now) {
20
+ return {
21
+ allowed: false,
22
+ remainingRequests: 0,
23
+ resetTime: this.throttleUntil,
24
+ retryAfter: this.throttleUntil - now,
25
+ isThrottled: true
26
+ };
27
+ }
28
+ // Check burst allowance
29
+ const burstLimit = this.config.burstAllowance || Math.floor(this.config.maxRequests * 0.5);
30
+ const recentRequests = this.requests.filter(req => req.timestamp > now - 10000); // Last 10 seconds
31
+ if (recentRequests.length >= burstLimit) {
32
+ const retryAfter = 10000 - (now - recentRequests[0].timestamp);
33
+ return {
34
+ allowed: false,
35
+ remainingRequests: this.config.maxRequests - this.requests.length,
36
+ resetTime: windowStart + this.config.windowMs,
37
+ retryAfter,
38
+ isThrottled: false
39
+ };
40
+ }
41
+ // Check window limit
42
+ if (this.requests.length >= this.config.maxRequests) {
43
+ const oldestRequest = this.requests[0];
44
+ const retryAfter = (oldestRequest.timestamp + this.config.windowMs) - now;
45
+ return {
46
+ allowed: false,
47
+ remainingRequests: 0,
48
+ resetTime: oldestRequest.timestamp + this.config.windowMs,
49
+ retryAfter,
50
+ isThrottled: false
51
+ };
52
+ }
53
+ // Request is allowed - add to tracking
54
+ this.requests.push({ timestamp: now, endpoint });
55
+ return {
56
+ allowed: true,
57
+ remainingRequests: this.config.maxRequests - this.requests.length,
58
+ resetTime: windowStart + this.config.windowMs,
59
+ isThrottled: false
60
+ };
61
+ }
62
+ /**
63
+ * Record a throttling event from the API
64
+ */
65
+ recordThrottle(retryAfterMs) {
66
+ const now = Date.now();
67
+ this.throttleUntil = now + retryAfterMs;
68
+ this.consecutiveThrottles++;
69
+ // Adaptive throttling - increase delay with consecutive throttles
70
+ if (this.config.adaptiveThrottling) {
71
+ this.adaptiveDelay = Math.min(this.consecutiveThrottles * 1000, 30000); // Max 30 seconds
72
+ logger.log(`🚨 Throttled by API. Consecutive throttles: ${this.consecutiveThrottles}, adaptive delay: ${this.adaptiveDelay}ms`);
73
+ }
74
+ }
75
+ /**
76
+ * Record a successful request
77
+ */
78
+ recordSuccess() {
79
+ // Reset consecutive throttles on success
80
+ if (this.consecutiveThrottles > 0) {
81
+ this.consecutiveThrottles = 0;
82
+ this.adaptiveDelay = 0;
83
+ logger.log('✅ API throttling recovered');
84
+ }
85
+ }
86
+ /**
87
+ * Get current status
88
+ */
89
+ getStatus() {
90
+ const now = Date.now();
91
+ const windowStart = now - this.config.windowMs;
92
+ const activeRequests = this.requests.filter(req => req.timestamp > windowStart);
93
+ return {
94
+ requestsInWindow: activeRequests.length,
95
+ maxRequests: this.config.maxRequests,
96
+ isThrottled: this.throttleUntil > now,
97
+ throttleUntil: this.throttleUntil,
98
+ consecutiveThrottles: this.consecutiveThrottles,
99
+ adaptiveDelay: this.adaptiveDelay
100
+ };
101
+ }
102
+ }
103
+ export class MS365RateLimitManager {
104
+ constructor() {
105
+ this.rateLimiters = new Map();
106
+ }
107
+ static getInstance() {
108
+ if (!this.instance) {
109
+ this.instance = new MS365RateLimitManager();
110
+ }
111
+ return this.instance;
112
+ }
113
+ /**
114
+ * Get rate limiter for endpoint
115
+ */
116
+ getRateLimiter(endpoint) {
117
+ if (!this.rateLimiters.has(endpoint)) {
118
+ const config = MS365RateLimitManager.ENDPOINT_CONFIGS[endpoint] ||
119
+ MS365RateLimitManager.ENDPOINT_CONFIGS['default'];
120
+ this.rateLimiters.set(endpoint, new RateLimiter(config));
121
+ }
122
+ return this.rateLimiters.get(endpoint);
123
+ }
124
+ /**
125
+ * Check if request is allowed
126
+ */
127
+ checkRequest(endpoint, operation) {
128
+ const limiter = this.getRateLimiter(endpoint);
129
+ const result = limiter.checkLimit(operation);
130
+ if (!result.allowed) {
131
+ logger.log(`🚫 Rate limit exceeded for ${endpoint}:${operation}. Retry after ${result.retryAfter}ms`);
132
+ }
133
+ return result;
134
+ }
135
+ /**
136
+ * Record a throttling event
137
+ */
138
+ recordThrottle(endpoint, retryAfterMs) {
139
+ const limiter = this.getRateLimiter(endpoint);
140
+ limiter.recordThrottle(retryAfterMs);
141
+ }
142
+ /**
143
+ * Record a successful request
144
+ */
145
+ recordSuccess(endpoint) {
146
+ const limiter = this.getRateLimiter(endpoint);
147
+ limiter.recordSuccess();
148
+ }
149
+ /**
150
+ * Execute operation with rate limiting
151
+ */
152
+ async executeWithRateLimit(operation, endpoint, operationName) {
153
+ const rateLimitResult = this.checkRequest(endpoint, operationName);
154
+ if (!rateLimitResult.allowed) {
155
+ const retryAfter = rateLimitResult.retryAfter || 1000;
156
+ logger.log(`⏳ Rate limited, waiting ${retryAfter}ms before ${endpoint}:${operationName}`);
157
+ await new Promise(resolve => setTimeout(resolve, retryAfter));
158
+ // Retry after waiting
159
+ return this.executeWithRateLimit(operation, endpoint, operationName);
160
+ }
161
+ try {
162
+ const result = await operation();
163
+ this.recordSuccess(endpoint);
164
+ return result;
165
+ }
166
+ catch (error) {
167
+ // Check if error is due to rate limiting
168
+ if (error.status === 429 || error.code === 'TooManyRequests') {
169
+ const retryAfter = this.extractRetryAfter(error);
170
+ this.recordThrottle(endpoint, retryAfter);
171
+ logger.log(`🚨 API throttled ${endpoint}:${operationName}, waiting ${retryAfter}ms`);
172
+ await new Promise(resolve => setTimeout(resolve, retryAfter));
173
+ // Retry after throttling
174
+ return this.executeWithRateLimit(operation, endpoint, operationName);
175
+ }
176
+ throw error;
177
+ }
178
+ }
179
+ /**
180
+ * Extract retry-after value from error
181
+ */
182
+ extractRetryAfter(error) {
183
+ // Check for Retry-After header
184
+ if (error.headers?.['retry-after']) {
185
+ return parseInt(error.headers['retry-after']) * 1000;
186
+ }
187
+ // Check for retry-after in error message
188
+ const retryMatch = error.message?.match(/retry after (\d+)/i);
189
+ if (retryMatch) {
190
+ return parseInt(retryMatch[1]) * 1000;
191
+ }
192
+ // Default retry after 60 seconds
193
+ return 60000;
194
+ }
195
+ /**
196
+ * Get status of all rate limiters
197
+ */
198
+ getStatus() {
199
+ const status = {};
200
+ for (const [endpoint, limiter] of this.rateLimiters) {
201
+ status[endpoint] = limiter.getStatus();
202
+ }
203
+ return status;
204
+ }
205
+ /**
206
+ * Reset rate limiters (for testing or recovery)
207
+ */
208
+ reset() {
209
+ this.rateLimiters.clear();
210
+ logger.log('🔄 Rate limiters reset');
211
+ }
212
+ /**
213
+ * Get intelligent delay based on current state
214
+ */
215
+ getIntelligentDelay(endpoint) {
216
+ const limiter = this.getRateLimiter(endpoint);
217
+ const status = limiter.getStatus();
218
+ // Base delay increases with request density
219
+ const requestDensity = status.requestsInWindow / status.maxRequests;
220
+ let baseDelay = 0;
221
+ if (requestDensity > 0.8) {
222
+ baseDelay = 2000; // 2 seconds when near limit
223
+ }
224
+ else if (requestDensity > 0.6) {
225
+ baseDelay = 1000; // 1 second when getting busy
226
+ }
227
+ else if (requestDensity > 0.4) {
228
+ baseDelay = 500; // 0.5 seconds when moderately busy
229
+ }
230
+ // Add adaptive delay if we've been throttled
231
+ const totalDelay = baseDelay + status.adaptiveDelay;
232
+ if (totalDelay > 0) {
233
+ logger.log(`🕐 Intelligent delay: ${totalDelay}ms for ${endpoint} (density: ${(requestDensity * 100).toFixed(1)}%)`);
234
+ }
235
+ return totalDelay;
236
+ }
237
+ }
238
+ // Rate limit configurations for different MS365 endpoints
239
+ MS365RateLimitManager.ENDPOINT_CONFIGS = {
240
+ 'search': {
241
+ maxRequests: 10,
242
+ windowMs: 60000, // 1 minute
243
+ burstAllowance: 5,
244
+ adaptiveThrottling: true
245
+ },
246
+ 'email': {
247
+ maxRequests: 100,
248
+ windowMs: 60000, // 1 minute
249
+ burstAllowance: 20,
250
+ adaptiveThrottling: true
251
+ },
252
+ 'send': {
253
+ maxRequests: 30,
254
+ windowMs: 60000, // 1 minute
255
+ burstAllowance: 10,
256
+ adaptiveThrottling: true
257
+ },
258
+ 'attachment': {
259
+ maxRequests: 50,
260
+ windowMs: 60000, // 1 minute
261
+ burstAllowance: 10,
262
+ adaptiveThrottling: true
263
+ },
264
+ 'calendar': {
265
+ maxRequests: 60,
266
+ windowMs: 60000, // 1 minute
267
+ burstAllowance: 15,
268
+ adaptiveThrottling: true
269
+ },
270
+ 'contacts': {
271
+ maxRequests: 60,
272
+ windowMs: 60000, // 1 minute
273
+ burstAllowance: 15,
274
+ adaptiveThrottling: true
275
+ },
276
+ 'default': {
277
+ maxRequests: 50,
278
+ windowMs: 60000, // 1 minute
279
+ burstAllowance: 10,
280
+ adaptiveThrottling: true
281
+ }
282
+ };
283
+ // Export singleton instance
284
+ export const ms365RateLimit = MS365RateLimitManager.getInstance();