adaptive-memory-multi-model-router 2.6.0 → 2.8.0

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,371 @@
1
+ "use strict";
2
+ /**
3
+ * Provider Health Manager with Circuit Breaker
4
+ *
5
+ * Intelligent failover system for A3M Router providing:
6
+ * - Rolling window metrics tracking (latency, error rate)
7
+ * - Health scoring based on latency percentile + error rate
8
+ * - Circuit breaker: 3 consecutive errors → 60s cooldown
9
+ * - Probe mode after cooldown for recovery
10
+ * - Sorted fallback chain based on health scores
11
+ *
12
+ * Usage:
13
+ * import { ProviderHealthManager, ProviderHealth } from './routing/providerHealth';
14
+ *
15
+ * const healthManager = new ProviderHealthManager();
16
+ *
17
+ * // Record outcomes
18
+ * healthManager.recordSuccess('openai/gpt-4o', 150);
19
+ * healthManager.recordFailure('anthropic/claude-3-5-sonnet', 'rate_limit');
20
+ *
21
+ * // Get health status
22
+ * const health = healthManager.getHealth('openai/gpt-4o');
23
+ *
24
+ * // Get sorted fallback chain
25
+ * const chain = healthManager.getFallbackChain(['openai/gpt-4o', 'anthropic/claude-3-5-sonnet']);
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.ProviderHealthManager = exports.HealthEvent = void 0;
29
+ const events_1 = require("events");
30
+ // ============================================================
31
+ // Events
32
+ // ============================================================
33
+ var HealthEvent;
34
+ (function (HealthEvent) {
35
+ HealthEvent["HEALTH_CHANGED"] = "healthChanged";
36
+ HealthEvent["CIRCUIT_OPENED"] = "circuitOpened";
37
+ HealthEvent["CIRCUIT_CLOSED"] = "circuitClosed";
38
+ HealthEvent["COOLDOWN_STARTED"] = "cooldownStarted";
39
+ HealthEvent["COOLDOWN_ENDED"] = "cooldownEnded";
40
+ HealthEvent["PROVIDER_DISABLED"] = "providerDisabled";
41
+ HealthEvent["PROVIDER_ENABLED"] = "providerEnabled";
42
+ HealthEvent["PROBE_ALLOWED"] = "probeAllowed";
43
+ })(HealthEvent || (exports.HealthEvent = HealthEvent = {}));
44
+ // ============================================================
45
+ // ProviderHealthManager
46
+ // ============================================================
47
+ class ProviderHealthManager extends events_1.EventEmitter {
48
+ // Rolling window metrics per provider
49
+ metrics = new Map();
50
+ // Current health state per provider
51
+ health = new Map();
52
+ // Disabled providers (manual disable)
53
+ disabled = new Map();
54
+ // Config
55
+ config;
56
+ constructor(config = {}) {
57
+ super();
58
+ this.config = {
59
+ windowSize: config.windowSize ?? 100,
60
+ circuitBreakerThreshold: config.circuitBreakerThreshold ?? 3,
61
+ cooldownMs: config.cooldownMs ?? 60000,
62
+ latencyPercentile: config.latencyPercentile ?? 95,
63
+ weights: {
64
+ latency: config.weights?.latency ?? 0.3,
65
+ errorRate: config.weights?.errorRate ?? 0.5,
66
+ consecutiveErrors: config.weights?.consecutiveErrors ?? 0.2,
67
+ },
68
+ };
69
+ }
70
+ /**
71
+ * Record a successful request
72
+ */
73
+ recordSuccess(provider, latencyMs) {
74
+ this.ensureProviderExists(provider);
75
+ const now = Date.now();
76
+ const window = this.getMetricsWindow(provider);
77
+ window.push({
78
+ name: provider,
79
+ totalRequests: 1,
80
+ successfulRequests: 1,
81
+ failedRequests: 0,
82
+ totalLatency: latencyMs,
83
+ lastLatency: latencyMs,
84
+ });
85
+ // Trim to window size
86
+ while (window.length > this.config.windowSize) {
87
+ window.shift();
88
+ }
89
+ // Update health state
90
+ const health = this.health.get(provider);
91
+ health.lastSuccess = now;
92
+ health.consecutiveErrors = 0;
93
+ health.cooldownUntil = 0;
94
+ health.isHealthy = true;
95
+ // Recalculate health score
96
+ this.recalculateHealthScore(provider);
97
+ this.emit(HealthEvent.HEALTH_CHANGED, health);
98
+ }
99
+ /**
100
+ * Record a failed request
101
+ */
102
+ recordFailure(provider, error) {
103
+ this.ensureProviderExists(provider);
104
+ const now = Date.now();
105
+ const window = this.getMetricsWindow(provider);
106
+ window.push({
107
+ name: provider,
108
+ totalRequests: 1,
109
+ successfulRequests: 0,
110
+ failedRequests: 1,
111
+ totalLatency: 0,
112
+ lastLatency: 0,
113
+ });
114
+ // Trim to window size
115
+ while (window.length > this.config.windowSize) {
116
+ window.shift();
117
+ }
118
+ // Update health state
119
+ const health = this.health.get(provider);
120
+ health.lastError = now;
121
+ health.consecutiveErrors++;
122
+ // Check circuit breaker
123
+ if (health.consecutiveErrors >= this.config.circuitBreakerThreshold) {
124
+ health.cooldownUntil = now + this.config.cooldownMs;
125
+ health.isHealthy = false;
126
+ this.emit(HealthEvent.CIRCUIT_OPENED, {
127
+ provider,
128
+ consecutiveErrors: health.consecutiveErrors,
129
+ cooldownUntil: health.cooldownUntil,
130
+ reason: error,
131
+ });
132
+ this.emit(HealthEvent.COOLDOWN_STARTED, {
133
+ provider,
134
+ duration: this.config.cooldownMs,
135
+ reason: error,
136
+ });
137
+ }
138
+ this.recalculateHealthScore(provider);
139
+ this.emit(HealthEvent.HEALTH_CHANGED, health);
140
+ }
141
+ /**
142
+ * Get current health for a provider
143
+ */
144
+ getHealth(provider) {
145
+ this.ensureProviderExists(provider);
146
+ return { ...this.health.get(provider) };
147
+ }
148
+ /**
149
+ * Get all provider health statuses
150
+ */
151
+ getAllHealth() {
152
+ const result = new Map();
153
+ for (const [name, health] of this.health.entries()) {
154
+ result.set(name, { ...health });
155
+ }
156
+ return result;
157
+ }
158
+ /**
159
+ * Check if a provider is available (healthy and not in cooldown/manual disable)
160
+ */
161
+ isAvailable(provider) {
162
+ const health = this.health.get(provider);
163
+ if (!health)
164
+ return false;
165
+ // Check manual disable
166
+ const disabled = this.disabled.get(provider);
167
+ if (disabled && disabled.until > Date.now()) {
168
+ return false;
169
+ }
170
+ // Check cooldown
171
+ if (health.cooldownUntil > Date.now()) {
172
+ return false;
173
+ }
174
+ return health.isHealthy;
175
+ }
176
+ /**
177
+ * Check if cooldown has expired and probe is allowed
178
+ */
179
+ isProbeAllowed(provider) {
180
+ const health = this.health.get(provider);
181
+ if (!health)
182
+ return false;
183
+ // If not in cooldown, no probe needed
184
+ if (health.cooldownUntil === 0)
185
+ return true;
186
+ // If cooldown has expired
187
+ if (health.cooldownUntil <= Date.now()) {
188
+ // Only allow one probe request per cooldown period
189
+ // After probe (marked by consecutiveErrors reset), normal requests allowed
190
+ return true;
191
+ }
192
+ return false;
193
+ }
194
+ /**
195
+ * Get the best provider from a list based on health scores
196
+ */
197
+ getBestProvider(providers) {
198
+ const available = providers.filter(p => this.isAvailable(p));
199
+ if (available.length === 0)
200
+ return null;
201
+ return available.reduce((best, current) => {
202
+ const health = this.health.get(current);
203
+ const bestHealth = this.health.get(best);
204
+ if (!health || !bestHealth)
205
+ return current;
206
+ return health.healthScore >= bestHealth.healthScore ? current : best;
207
+ });
208
+ }
209
+ /**
210
+ * Get sorted fallback chain based on health scores
211
+ * Returns providers sorted by health score (descending)
212
+ */
213
+ getFallbackChain(providers) {
214
+ // Score each provider
215
+ const scored = providers.map(p => ({
216
+ provider: p,
217
+ score: this.isAvailable(p) ? (this.health.get(p)?.healthScore ?? 0) : -1,
218
+ }));
219
+ // Sort by health score (descending), unavailable at end
220
+ scored.sort((a, b) => {
221
+ if (a.score === -1 && b.score === -1)
222
+ return 0;
223
+ if (a.score === -1)
224
+ return 1;
225
+ if (b.score === -1)
226
+ return -1;
227
+ return b.score - a.score;
228
+ });
229
+ return scored.map(s => s.provider);
230
+ }
231
+ /**
232
+ * Mark provider as disabled (manual circuit breaker)
233
+ */
234
+ disableProvider(provider, reason) {
235
+ const until = Number.MAX_SAFE_INTEGER; // Manual disable until explicitly enabled
236
+ this.disabled.set(provider, { reason, until });
237
+ const health = this.health.get(provider);
238
+ if (health) {
239
+ health.isHealthy = false;
240
+ }
241
+ this.emit(HealthEvent.PROVIDER_DISABLED, { provider, reason });
242
+ }
243
+ /**
244
+ * Enable a previously disabled provider
245
+ */
246
+ enableProvider(provider) {
247
+ this.disabled.delete(provider);
248
+ const health = this.health.get(provider);
249
+ if (health) {
250
+ health.isHealthy = true;
251
+ health.consecutiveErrors = 0;
252
+ health.cooldownUntil = 0;
253
+ }
254
+ this.emit(HealthEvent.PROVIDER_ENABLED, { provider });
255
+ }
256
+ /**
257
+ * Clear cooldown and reset circuit breaker for a provider
258
+ */
259
+ resetCircuitBreaker(provider) {
260
+ const health = this.health.get(provider);
261
+ if (health) {
262
+ health.consecutiveErrors = 0;
263
+ health.cooldownUntil = 0;
264
+ health.isHealthy = true;
265
+ this.emit(HealthEvent.CIRCUIT_CLOSED, { provider });
266
+ }
267
+ }
268
+ /**
269
+ * Get health stats for monitoring
270
+ */
271
+ getStats() {
272
+ let healthyCount = 0;
273
+ let cooldownCount = 0;
274
+ let disabledCount = 0;
275
+ let totalScore = 0;
276
+ for (const [name, health] of this.health.entries()) {
277
+ totalScore += health.healthScore;
278
+ if (!health.isHealthy && health.cooldownUntil > Date.now()) {
279
+ cooldownCount++;
280
+ }
281
+ else if (health.isHealthy) {
282
+ healthyCount++;
283
+ }
284
+ if (this.disabled.has(name)) {
285
+ disabledCount++;
286
+ }
287
+ }
288
+ const total = this.health.size;
289
+ return {
290
+ totalProviders: total,
291
+ healthyProviders: healthyCount,
292
+ cooldownProviders: cooldownCount,
293
+ disabledProviders: disabledCount,
294
+ avgHealthScore: total > 0 ? totalScore / total : 0,
295
+ };
296
+ }
297
+ // ============================================================
298
+ // Private Methods
299
+ // ============================================================
300
+ ensureProviderExists(provider) {
301
+ if (!this.health.has(provider)) {
302
+ const now = Date.now();
303
+ this.health.set(provider, {
304
+ name: provider,
305
+ latency: 0,
306
+ errorRate: 0,
307
+ lastSuccess: 0,
308
+ lastError: 0,
309
+ consecutiveErrors: 0,
310
+ isHealthy: true,
311
+ cooldownUntil: 0,
312
+ healthScore: 1.0,
313
+ });
314
+ this.metrics.set(provider, []);
315
+ }
316
+ }
317
+ getMetricsWindow(provider) {
318
+ return this.metrics.get(provider) ?? [];
319
+ }
320
+ recalculateHealthScore(provider) {
321
+ const window = this.getMetricsWindow(provider);
322
+ const health = this.health.get(provider);
323
+ if (!health || window.length === 0)
324
+ return;
325
+ // Calculate error rate
326
+ const totalRequests = window.reduce((sum, m) => sum + m.totalRequests, 0);
327
+ const failedRequests = window.reduce((sum, m) => sum + m.failedRequests, 0);
328
+ const errorRate = totalRequests > 0 ? failedRequests / totalRequests : 0;
329
+ health.errorRate = errorRate;
330
+ // Calculate latency metrics
331
+ const latencies = window.filter(m => m.totalLatency > 0).map(m => m.lastLatency);
332
+ const avgLatency = latencies.length > 0
333
+ ? latencies.reduce((a, b) => a + b, 0) / latencies.length
334
+ : 0;
335
+ health.latency = avgLatency;
336
+ // Percentile latency (simplified: use avg latency as proxy)
337
+ // For true percentile, we'd need raw data points
338
+ const latencyScore = this.calculateLatencyScore(avgLatency);
339
+ // Health score: weighted combination
340
+ // Higher error rate = lower score, higher latency = lower score
341
+ const errorScore = 1 - errorRate;
342
+ const consecutiveScore = Math.max(0, 1 - (health.consecutiveErrors / this.config.circuitBreakerThreshold));
343
+ const score = this.config.weights.latency * latencyScore +
344
+ this.config.weights.errorRate * errorScore +
345
+ this.config.weights.consecutiveErrors * consecutiveScore;
346
+ health.healthScore = Math.max(0, Math.min(1, score));
347
+ }
348
+ calculateLatencyScore(avgLatency) {
349
+ // Latency score: 1 at 0ms, 0 at 10000ms+, with exponential decay
350
+ // Configurable thresholds could be passed in
351
+ const latencyThresholds = {
352
+ excellent: 100, // 100ms - score 1.0
353
+ good: 500, // 500ms - score 0.8
354
+ acceptable: 1000, // 1s - score 0.6
355
+ poor: 3000, // 3s - score 0.3
356
+ terrible: 10000, // 10s+ - score 0.0
357
+ };
358
+ if (avgLatency <= 0)
359
+ return 1.0;
360
+ if (avgLatency <= latencyThresholds.excellent)
361
+ return 1.0;
362
+ if (avgLatency >= latencyThresholds.terrible)
363
+ return 0.0;
364
+ // Exponential interpolation
365
+ const k = 0.003; // decay constant
366
+ return Math.exp(-k * avgLatency);
367
+ }
368
+ }
369
+ exports.ProviderHealthManager = ProviderHealthManager;
370
+ exports.default = ProviderHealthManager;
371
+ //# sourceMappingURL=providerHealth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providerHealth.js","sourceRoot":"","sources":["../../src/routing/providerHealth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * A3M Router - Per-Provider Retry Logic
3
+ *
4
+ * Implements exponential backoff with jitter for transient errors,
5
+ * rate limit (429) handling with Retry-After header support,
6
+ * and context window validation before sending requests.
7
+ */
8
+ export interface RetryConfig {
9
+ maxRetries: number;
10
+ initialDelayMs: number;
11
+ maxDelayMs: number;
12
+ backoffMultiplier: number;
13
+ retryableErrors?: string[];
14
+ }
15
+ export interface ProviderRetryConfig {
16
+ [providerName: string]: {
17
+ timeout: number;
18
+ retry: RetryConfig;
19
+ rateLimitRetries?: number;
20
+ };
21
+ }
22
+ export interface RetryStats {
23
+ totalRequests: number;
24
+ successfulRequests: number;
25
+ failedRequests: number;
26
+ totalRetries: number;
27
+ rateLimitRetries: number;
28
+ averageLatencyMs: number;
29
+ }
30
+ export interface ContextWindowValidation {
31
+ valid: boolean;
32
+ reason?: string;
33
+ suggestedProvider?: string;
34
+ }
35
+ declare const DEFAULT_RETRY_CONFIG: RetryConfig;
36
+ export declare const DEFAULT_PROVIDER_CONFIG: ProviderRetryConfig;
37
+ declare const PROVIDER_CONTEXT_LIMITS: Record<string, number>;
38
+ export declare class ProviderRetryHandler {
39
+ private configs;
40
+ private stats;
41
+ private customProviders;
42
+ constructor(customConfigs?: ProviderRetryConfig);
43
+ private initStats;
44
+ /**
45
+ * Configure or update a provider's retry settings
46
+ */
47
+ configureProvider(provider: string, config: Partial<{
48
+ timeout: number;
49
+ retry: Partial<RetryConfig>;
50
+ rateLimitRetries: number;
51
+ }>): void;
52
+ /**
53
+ * Get current config for a provider
54
+ */
55
+ getConfig(provider: string): {
56
+ timeout: number;
57
+ retry: RetryConfig;
58
+ rateLimitRetries: number;
59
+ };
60
+ /**
61
+ * Execute a function with retry logic
62
+ */
63
+ executeWithRetry<T>(provider: string, fn: () => Promise<T>, options?: {
64
+ timeout?: number;
65
+ onRetry?: (attempt: number, error: any, delayMs: number) => void;
66
+ }): Promise<T>;
67
+ /**
68
+ * Execute with custom timeout wrapper
69
+ */
70
+ private executeWithTimeout;
71
+ /**
72
+ * Check if an error should trigger a retry
73
+ */
74
+ isRetryableError(error: any): boolean;
75
+ /**
76
+ * Check if error is a rate limit (429)
77
+ */
78
+ isRateLimitError(error: any): boolean;
79
+ /**
80
+ * Calculate backoff delay with exponential increase and jitter
81
+ */
82
+ calculateBackoffDelay(attempt: number, config: RetryConfig, error?: any): number;
83
+ /**
84
+ * Validate context window size before sending request
85
+ */
86
+ validateContextWindow(provider: string, prompt: string, expectedTokens?: number): ContextWindowValidation;
87
+ /**
88
+ * Get retry statistics for a provider
89
+ */
90
+ getStats(provider: string): RetryStats;
91
+ /**
92
+ * Get all provider stats
93
+ */
94
+ getAllStats(): Record<string, RetryStats>;
95
+ /**
96
+ * Reset stats for a provider
97
+ */
98
+ resetStats(provider?: string): void;
99
+ private createTimeoutError;
100
+ private sleep;
101
+ private recordSuccess;
102
+ private recordFailure;
103
+ private recordRetry;
104
+ }
105
+ /**
106
+ * Create a retry handler with optional custom configs
107
+ */
108
+ export declare function createRetryHandler(customConfigs?: ProviderRetryConfig): ProviderRetryHandler;
109
+ export declare function getDefaultRetryHandler(): ProviderRetryHandler;
110
+ export { DEFAULT_RETRY_CONFIG, PROVIDER_CONTEXT_LIMITS, };