@mayank3238/keymux 1.0.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.
- package/README.md +79 -0
- package/dist/cli.cjs +870 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +840 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +2615 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +859 -0
- package/dist/index.d.ts +859 -0
- package/dist/index.js +2567 -0
- package/dist/index.js.map +1 -0
- package/dist/proxy/index.cjs +2138 -0
- package/dist/proxy/index.cjs.map +1 -0
- package/dist/proxy/index.d.cts +38 -0
- package/dist/proxy/index.d.ts +38 -0
- package/dist/proxy/index.js +2122 -0
- package/dist/proxy/index.js.map +1 -0
- package/package.json +77 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,859 @@
|
|
|
1
|
+
export { NvidiaStreamTranslator, OpenRouterStreamTranslator, startProxyServer, stopProxyServer, translateAnthropicToOpenAI, translateOpenAIToAnthropic } from './proxy/index.js';
|
|
2
|
+
import 'http';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Core type definitions for keymux
|
|
6
|
+
* Public API types exported for consumers
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for a single API key in the pool
|
|
10
|
+
*/
|
|
11
|
+
interface KeyConfig {
|
|
12
|
+
/** Unique identifier - each key has its own ID */
|
|
13
|
+
id: string;
|
|
14
|
+
/** Actual API key string */
|
|
15
|
+
key: string;
|
|
16
|
+
/** RPM limit - how many requests can run in one minute (default: 40) */
|
|
17
|
+
rpmLimit?: number;
|
|
18
|
+
/** Weight - increase weight for more important keys (default: 1) */
|
|
19
|
+
weight?: number;
|
|
20
|
+
/** Base URL - custom URL if you don't want to use the provider's default URL */
|
|
21
|
+
baseURL?: string;
|
|
22
|
+
/** Custom metadata - for storing extra info */
|
|
23
|
+
metadata?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Internal state tracking for a key
|
|
27
|
+
*/
|
|
28
|
+
interface KeyState {
|
|
29
|
+
config: KeyConfig;
|
|
30
|
+
/** Current RPM - how many requests have been made in the current minute */
|
|
31
|
+
rpm: number;
|
|
32
|
+
/** Last request timestamp */
|
|
33
|
+
lastUsed: number;
|
|
34
|
+
/** Circuit breaker state - HEALTHY, DEGRADED or OPEN */
|
|
35
|
+
circuitState: CircuitState;
|
|
36
|
+
/** Continuous failures - circuit opens if this gets too high */
|
|
37
|
+
failures: number;
|
|
38
|
+
/** Cooldown until - how long to wait before retrying */
|
|
39
|
+
cooldownUntil: number;
|
|
40
|
+
/** Average latency - calculated from last 25 requests */
|
|
41
|
+
avgLatencyMs: number;
|
|
42
|
+
/** Raw TTFT history - sliding window of last 25 readings for true average calculation */
|
|
43
|
+
latencyHistory: number[];
|
|
44
|
+
/** Total requests - how many requests processed so far */
|
|
45
|
+
totalRequests: number;
|
|
46
|
+
/** Total errors - how many errors encountered so far */
|
|
47
|
+
totalErrors: number;
|
|
48
|
+
/** Timestamps of requests in the current window */
|
|
49
|
+
requestTimestamps: number[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Circuit breaker states
|
|
53
|
+
* HEALTHY -> DEGRADED -> OPEN (recovers after cooldown)
|
|
54
|
+
*/
|
|
55
|
+
declare enum CircuitState {
|
|
56
|
+
HEALTHY = "healthy",// Everything is fine, can be used
|
|
57
|
+
DEGRADED = "degraded",// Some issues, use carefully
|
|
58
|
+
OPEN = "open"
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Configuration for the key router
|
|
62
|
+
*/
|
|
63
|
+
interface KeyRouterConfig {
|
|
64
|
+
/** All API keys to manage */
|
|
65
|
+
keys: KeyConfig[];
|
|
66
|
+
/** Default RPM limit - used if key-specific limit is not provided (default: 40) */
|
|
67
|
+
defaultRpmLimit?: number;
|
|
68
|
+
/** Default weight - equal importance for all keys? Or some more important? (default: 1) */
|
|
69
|
+
defaultWeight?: number;
|
|
70
|
+
/** Circuit breaker - block key after this many failures (default: 3) */
|
|
71
|
+
failureThreshold?: number;
|
|
72
|
+
/** Circuit breaker - recovery time in milliseconds after being blocked (default: 30000 = 30sec) */
|
|
73
|
+
cooldownMs?: number;
|
|
74
|
+
/** Sliding window - RPM is counted within a 1 minute (60000ms) window */
|
|
75
|
+
windowMs?: number;
|
|
76
|
+
/** Latency tracking - track how fast requests complete? (default: true) */
|
|
77
|
+
trackLatency?: boolean;
|
|
78
|
+
/** Callback - called when a key's state changes (e.g. healthy to degraded) */
|
|
79
|
+
onStateChange?: (keyId: string, state: CircuitState) => void;
|
|
80
|
+
/** Debug callback - for understanding what is happening in development */
|
|
81
|
+
onDebug?: (event: DebugEvent) => void;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Debug events - for logging and monitoring
|
|
85
|
+
* What event occurred (selected, failed, recovered), which key, when, and extra details
|
|
86
|
+
*/
|
|
87
|
+
interface DebugEvent {
|
|
88
|
+
type: 'key_selected' | 'key_failed' | 'key_recovered' | 'circuit_opened' | 'all_exhausted';
|
|
89
|
+
keyId?: string;
|
|
90
|
+
keyIndex?: number;
|
|
91
|
+
timestamp: number;
|
|
92
|
+
details?: Record<string, unknown>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Result of key selection
|
|
96
|
+
*/
|
|
97
|
+
interface KeySelectionResult {
|
|
98
|
+
/** The selected API key */
|
|
99
|
+
key: string;
|
|
100
|
+
/** Key configuration */
|
|
101
|
+
config: KeyConfig;
|
|
102
|
+
/** Internal key ID */
|
|
103
|
+
keyId: string;
|
|
104
|
+
/** Current utilization (0-1) */
|
|
105
|
+
utilization: number;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Statistics for a single key
|
|
109
|
+
*/
|
|
110
|
+
interface KeyStats {
|
|
111
|
+
id: string;
|
|
112
|
+
key: string;
|
|
113
|
+
rpm: number;
|
|
114
|
+
rpmLimit: number;
|
|
115
|
+
utilization: number;
|
|
116
|
+
circuitState: CircuitState;
|
|
117
|
+
failures: number;
|
|
118
|
+
avgLatencyMs: number;
|
|
119
|
+
totalRequests: number;
|
|
120
|
+
totalErrors: number;
|
|
121
|
+
isHealthy: boolean;
|
|
122
|
+
cooldownRemainingMs?: number;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Overall router statistics
|
|
126
|
+
*/
|
|
127
|
+
interface RouterStats {
|
|
128
|
+
totalKeys: number;
|
|
129
|
+
healthyKeys: number;
|
|
130
|
+
totalRpm: number;
|
|
131
|
+
totalRpmLimit: number;
|
|
132
|
+
overallUtilization: number;
|
|
133
|
+
keys: KeyStats[];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Options for key selection
|
|
137
|
+
*/
|
|
138
|
+
interface SelectionOptions {
|
|
139
|
+
/** Preferred key IDs (will try these first if healthy) */
|
|
140
|
+
preferredKeys?: string[];
|
|
141
|
+
/** Minimum utilization threshold (0-1) - skip keys above this */
|
|
142
|
+
maxUtilization?: number;
|
|
143
|
+
/** Custom selection strategy name */
|
|
144
|
+
strategy?: string;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Error types for better error handling
|
|
148
|
+
*/
|
|
149
|
+
declare class KeyRouterError extends Error {
|
|
150
|
+
readonly code: 'ALL_KEYS_EXHAUSTED' | 'NO_KEYS_CONFIGURED' | 'KEY_NOT_FOUND' | 'CIRCUIT_OPEN' | 'INVALID_CONFIG';
|
|
151
|
+
readonly keyId?: string | undefined;
|
|
152
|
+
constructor(message: string, code: 'ALL_KEYS_EXHAUSTED' | 'NO_KEYS_CONFIGURED' | 'KEY_NOT_FOUND' | 'CIRCUIT_OPEN' | 'INVALID_CONFIG', keyId?: string | undefined);
|
|
153
|
+
}
|
|
154
|
+
declare class RateLimitError extends KeyRouterError {
|
|
155
|
+
constructor(keyId: string);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Provider preset configurations
|
|
159
|
+
*/
|
|
160
|
+
interface ProviderPreset {
|
|
161
|
+
name: string;
|
|
162
|
+
baseURL: string;
|
|
163
|
+
defaultRpmLimit: number;
|
|
164
|
+
models: string[];
|
|
165
|
+
}
|
|
166
|
+
declare const PROVIDER_PRESETS: Record<string, ProviderPreset>;
|
|
167
|
+
/**
|
|
168
|
+
* Configuration for one provider in a multi-provider pool
|
|
169
|
+
* Each provider can have multiple API keys
|
|
170
|
+
*/
|
|
171
|
+
interface ProviderPoolEntry {
|
|
172
|
+
/** Provider name - use preset name ('nvidia', 'gemini', etc.) or any custom string */
|
|
173
|
+
provider: string;
|
|
174
|
+
/** API keys for this provider (multiple keys = more RPM capacity) */
|
|
175
|
+
keys: string[];
|
|
176
|
+
/** Base URL - auto-filled from PROVIDER_PRESETS for known providers */
|
|
177
|
+
baseURL?: string;
|
|
178
|
+
/** Models this provider serves - first model is the default */
|
|
179
|
+
models?: string[];
|
|
180
|
+
/** Per-key RPM limit - auto-filled from PROVIDER_PRESETS for known providers */
|
|
181
|
+
rpmLimit?: number;
|
|
182
|
+
/** Provider priority weight - higher weight = more requests routed here (default: 1) */
|
|
183
|
+
weight?: number;
|
|
184
|
+
/** Custom metadata - for storing extra info per provider */
|
|
185
|
+
metadata?: Record<string, unknown>;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Result of getEndpoint() — everything needed to make an API call
|
|
189
|
+
* Unlike getKey() which returns just a string, this returns the full endpoint info
|
|
190
|
+
*/
|
|
191
|
+
interface EndpointResult {
|
|
192
|
+
/** The API key to use */
|
|
193
|
+
key: string;
|
|
194
|
+
/** Provider's base URL for API calls */
|
|
195
|
+
baseURL: string;
|
|
196
|
+
/** Model to use for this request */
|
|
197
|
+
model: string;
|
|
198
|
+
/** Provider name (e.g. 'nvidia', 'gemini', 'openrouter') */
|
|
199
|
+
provider: string;
|
|
200
|
+
/** Unique endpoint ID for reporting success/failure */
|
|
201
|
+
endpointId: string;
|
|
202
|
+
/** Current utilization of this endpoint (0-1) */
|
|
203
|
+
utilization: number;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Options for getEndpoint() - filter and control which endpoint is selected
|
|
207
|
+
*/
|
|
208
|
+
interface EndpointOptions {
|
|
209
|
+
/** Try these providers first if healthy (e.g. ['gemini', 'nvidia']) */
|
|
210
|
+
preferProviders?: string[];
|
|
211
|
+
/** Never use these providers for this request */
|
|
212
|
+
excludeProviders?: string[];
|
|
213
|
+
/** Request a specific model - router picks the provider that has it */
|
|
214
|
+
model?: string;
|
|
215
|
+
/** Skip endpoints above this utilization (0-1) */
|
|
216
|
+
maxUtilization?: number;
|
|
217
|
+
/** Override selection strategy for this request */
|
|
218
|
+
strategy?: string;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Configuration options for MultiProviderRouter
|
|
222
|
+
*/
|
|
223
|
+
interface MultiProviderConfig {
|
|
224
|
+
/** Circuit breaker - block key after this many failures (default: 3) */
|
|
225
|
+
failureThreshold?: number;
|
|
226
|
+
/** Circuit breaker - recovery time in ms after being blocked (default: 30000) */
|
|
227
|
+
cooldownMs?: number;
|
|
228
|
+
/** Sliding window for RPM counting in ms (default: 60000) */
|
|
229
|
+
windowMs?: number;
|
|
230
|
+
/** Track latency per endpoint? (default: true) */
|
|
231
|
+
trackLatency?: boolean;
|
|
232
|
+
/** Default selection strategy (default: 'weighted-least-utilization') */
|
|
233
|
+
strategy?: string;
|
|
234
|
+
/** Callback when an endpoint's state changes */
|
|
235
|
+
onStateChange?: (endpointId: string, state: CircuitState) => void;
|
|
236
|
+
/** Debug callback for monitoring */
|
|
237
|
+
onDebug?: (event: DebugEvent) => void;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Key Selection Strategies
|
|
242
|
+
* Pure functions that select the best key from the available keys
|
|
243
|
+
*/
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Strategy interface
|
|
247
|
+
*/
|
|
248
|
+
interface SelectionStrategy {
|
|
249
|
+
name: string;
|
|
250
|
+
select(keys: KeyState[], options?: SelectionOptions): KeyState | null;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Weighted Least Utilization Strategy (RECOMMENDED)
|
|
254
|
+
* Picks key with lowest (rpm / rpmLimit) ratio, weighted by key weight
|
|
255
|
+
* Handles different RPM limits per key fairly
|
|
256
|
+
*/
|
|
257
|
+
declare class WeightedLeastUtilizationStrategy implements SelectionStrategy {
|
|
258
|
+
name: string;
|
|
259
|
+
select(keys: KeyState[], options?: SelectionOptions): KeyState | null;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Least Requests Strategy
|
|
263
|
+
* Picks key with absolute lowest RPM count
|
|
264
|
+
* Good when all keys have same limits
|
|
265
|
+
*/
|
|
266
|
+
declare class LeastRequestsStrategy implements SelectionStrategy {
|
|
267
|
+
name: string;
|
|
268
|
+
select(keys: KeyState[], options?: SelectionOptions): KeyState | null;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Smart Routing Strategy
|
|
272
|
+
* 1. Filters out keys exceeding max utilization.
|
|
273
|
+
* 2. Forms a "fast pool" of keys within 100ms of the fastest key. Always includes keys with no latency data (e.g., avgLatencyMs === 0) so they don't get starved.
|
|
274
|
+
* 3. Sorts the pool by RPM (ascending) and then totalRequests (ascending) to balance load (Least Used).
|
|
275
|
+
*/
|
|
276
|
+
declare class SmartRoutingStrategy implements SelectionStrategy {
|
|
277
|
+
name: string;
|
|
278
|
+
select(keys: KeyState[], options?: SelectionOptions): KeyState | null;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Least Latency Strategy
|
|
282
|
+
* Picks key with lowest average latency
|
|
283
|
+
* Requires latency tracking to be enabled
|
|
284
|
+
* Falls back to LeastRequestsStrategy if no latency data is available
|
|
285
|
+
*/
|
|
286
|
+
declare class LeastLatencyStrategy implements SelectionStrategy {
|
|
287
|
+
name: string;
|
|
288
|
+
select(keys: KeyState[], options?: SelectionOptions): KeyState | null;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Preferred Keys Strategy
|
|
292
|
+
* Tries preferred keys first, falls back to another strategy
|
|
293
|
+
*/
|
|
294
|
+
declare class PreferredKeysStrategy implements SelectionStrategy {
|
|
295
|
+
private fallbackStrategy;
|
|
296
|
+
name: string;
|
|
297
|
+
constructor(fallbackStrategy: SelectionStrategy);
|
|
298
|
+
select(keys: KeyState[], options?: SelectionOptions): KeyState | null;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Random Strategy
|
|
302
|
+
* Selects a key randomly - good for distributing load evenly over time
|
|
303
|
+
* Applies utilization filter so overloaded keys are not selected
|
|
304
|
+
*/
|
|
305
|
+
declare class RandomStrategy implements SelectionStrategy {
|
|
306
|
+
name: string;
|
|
307
|
+
select(keys: KeyState[], options?: SelectionOptions): KeyState | null;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Strategy Factory - creates strategy by name
|
|
311
|
+
*/
|
|
312
|
+
declare function createStrategy(name: string, fallback?: SelectionStrategy): SelectionStrategy;
|
|
313
|
+
/**
|
|
314
|
+
* Default strategy instance (singleton)
|
|
315
|
+
*/
|
|
316
|
+
declare const defaultStrategy: WeightedLeastUtilizationStrategy;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* KeyRouter - Main entry point of the keymux library
|
|
320
|
+
* Manages keys, tracks state, and distributes requests
|
|
321
|
+
* Uses a selection strategy to pick the best key
|
|
322
|
+
*/
|
|
323
|
+
|
|
324
|
+
declare class KeyRouter {
|
|
325
|
+
private tracker;
|
|
326
|
+
private strategy;
|
|
327
|
+
private config;
|
|
328
|
+
private initialized;
|
|
329
|
+
constructor(config: KeyRouterConfig);
|
|
330
|
+
/**
|
|
331
|
+
* Initialize the router with keys
|
|
332
|
+
* Must be called before using getKey()
|
|
333
|
+
*/
|
|
334
|
+
initialize(): void;
|
|
335
|
+
/**
|
|
336
|
+
* Get the next available API key
|
|
337
|
+
* Returns key string ready to use with OpenAI/LangChain SDKs
|
|
338
|
+
*/
|
|
339
|
+
getKey(options?: SelectionOptions): Promise<string>;
|
|
340
|
+
/**
|
|
341
|
+
* Get key with full metadata (for debugging/logging)
|
|
342
|
+
*/
|
|
343
|
+
getKeyWithMeta(options?: SelectionOptions): Promise<KeySelectionResult>;
|
|
344
|
+
/**
|
|
345
|
+
* Report a failed request for a specific key or key ID
|
|
346
|
+
* Call this when you catch a 429/5xx error from the API
|
|
347
|
+
*/
|
|
348
|
+
reportFailure(keyOrId: string, isRateLimit?: boolean): void;
|
|
349
|
+
/**
|
|
350
|
+
* Report a successful request for a specific key or key ID
|
|
351
|
+
* Optional: call this if you want to track latency manually
|
|
352
|
+
*/
|
|
353
|
+
reportSuccess(keyOrId: string, latencyMs?: number): void;
|
|
354
|
+
/**
|
|
355
|
+
* Manually mark a key as recovered
|
|
356
|
+
*/
|
|
357
|
+
reportRecovery(keyOrId: string): void;
|
|
358
|
+
/**
|
|
359
|
+
* Get statistics for all keys
|
|
360
|
+
*/
|
|
361
|
+
getStats(): KeyStats[];
|
|
362
|
+
/**
|
|
363
|
+
* Get overall router statistics
|
|
364
|
+
*/
|
|
365
|
+
getOverallStats(): {
|
|
366
|
+
totalKeys: number;
|
|
367
|
+
healthyKeys: number;
|
|
368
|
+
totalRpm: number;
|
|
369
|
+
totalRpmLimit: number;
|
|
370
|
+
overallUtilization: number;
|
|
371
|
+
keys: KeyStats[];
|
|
372
|
+
};
|
|
373
|
+
/**
|
|
374
|
+
* Get a specific key's stats by ID
|
|
375
|
+
*/
|
|
376
|
+
getKeyStats(keyId: string): KeyStats | undefined;
|
|
377
|
+
/**
|
|
378
|
+
* Check if a specific key is healthy
|
|
379
|
+
*/
|
|
380
|
+
isKeyHealthy(keyId: string): boolean;
|
|
381
|
+
/**
|
|
382
|
+
* Get all available (healthy + under limit) key IDs
|
|
383
|
+
*/
|
|
384
|
+
getAvailableKeyIds(): string[];
|
|
385
|
+
/**
|
|
386
|
+
* Set custom selection strategy
|
|
387
|
+
*/
|
|
388
|
+
setStrategy(strategy: SelectionStrategy | string): void;
|
|
389
|
+
/**
|
|
390
|
+
* Add a new key at runtime
|
|
391
|
+
*/
|
|
392
|
+
addKey(keyConfig: KeyConfig): void;
|
|
393
|
+
/**
|
|
394
|
+
* Remove a key at runtime
|
|
395
|
+
*/
|
|
396
|
+
removeKey(keyId: string): boolean;
|
|
397
|
+
/**
|
|
398
|
+
* Update key configuration at runtime
|
|
399
|
+
*/
|
|
400
|
+
updateKey(keyId: string, updates: Partial<KeyConfig>): boolean;
|
|
401
|
+
/**
|
|
402
|
+
* Reset all tracking (useful for testing)
|
|
403
|
+
*/
|
|
404
|
+
reset(): void;
|
|
405
|
+
/**
|
|
406
|
+
* Shutdown and cleanup
|
|
407
|
+
*/
|
|
408
|
+
destroy(): void;
|
|
409
|
+
/**
|
|
410
|
+
* Create a router from a provider preset
|
|
411
|
+
*/
|
|
412
|
+
static fromProvider(provider: keyof typeof PROVIDER_PRESETS, keys: string[], options?: Partial<KeyRouterConfig>): KeyRouter;
|
|
413
|
+
/**
|
|
414
|
+
* Create a router for NVIDIA specifically (most common use case)
|
|
415
|
+
*/
|
|
416
|
+
static forNvidia(keys: string[], options?: Partial<KeyRouterConfig>): KeyRouter;
|
|
417
|
+
/**
|
|
418
|
+
* Create router from environment variable (comma-separated keys)
|
|
419
|
+
*/
|
|
420
|
+
static fromEnv(envVar: string, options?: Partial<KeyRouterConfig>): KeyRouter;
|
|
421
|
+
private ensureInitialized;
|
|
422
|
+
private findKeyOrId;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* KeyTracker - Tracks per-key state: RPM, circuit breaker, latency
|
|
427
|
+
* Single responsibility: tracks and updates key metrics
|
|
428
|
+
*/
|
|
429
|
+
|
|
430
|
+
interface KeyTrackerOptions {
|
|
431
|
+
defaultRpmLimit: number;
|
|
432
|
+
defaultWeight: number;
|
|
433
|
+
failureThreshold: number;
|
|
434
|
+
cooldownMs: number;
|
|
435
|
+
windowMs: number;
|
|
436
|
+
trackLatency: boolean;
|
|
437
|
+
onStateChange?: (keyId: string, state: CircuitState) => void;
|
|
438
|
+
onDebug?: (event: DebugEvent) => void;
|
|
439
|
+
}
|
|
440
|
+
declare class KeyTracker {
|
|
441
|
+
private keys;
|
|
442
|
+
private options;
|
|
443
|
+
private cleanupInterval?;
|
|
444
|
+
constructor(options: KeyTrackerOptions);
|
|
445
|
+
initialize(keyConfigs: KeyConfig[]): void;
|
|
446
|
+
/**
|
|
447
|
+
* Get all key states - for debugging or stats
|
|
448
|
+
*/
|
|
449
|
+
getAllStates(): KeyState[];
|
|
450
|
+
/**
|
|
451
|
+
* Get state of a specific key
|
|
452
|
+
*/
|
|
453
|
+
getState(keyId: string): KeyState | undefined;
|
|
454
|
+
/**
|
|
455
|
+
* Get only healthy and available keys
|
|
456
|
+
* Checks circuit breaker state and RPM limits
|
|
457
|
+
*/
|
|
458
|
+
getAvailableKeys(): KeyState[];
|
|
459
|
+
/**
|
|
460
|
+
* Record a request attempt (reservation)
|
|
461
|
+
* Increments RPM count without touching failures or circuit state.
|
|
462
|
+
*/
|
|
463
|
+
recordAttempt(keyId: string): void;
|
|
464
|
+
/**
|
|
465
|
+
* Record a successful request completion
|
|
466
|
+
* Updates latency and resets failures (RPM already incremented by recordAttempt).
|
|
467
|
+
*/
|
|
468
|
+
recordSuccess(keyId: string, latencyMs?: number): void;
|
|
469
|
+
/**
|
|
470
|
+
* Record a failed request (rate limit, server error, etc.)
|
|
471
|
+
* Triggers circuit breaker logic (RPM already incremented by recordAttempt).
|
|
472
|
+
*/
|
|
473
|
+
recordFailure(keyId: string, isRateLimit?: boolean): void;
|
|
474
|
+
/**
|
|
475
|
+
* Manually report a key as recovered (for external health checks)
|
|
476
|
+
*/
|
|
477
|
+
reportRecovery(keyId: string): void;
|
|
478
|
+
/**
|
|
479
|
+
* Get statistics for all keys
|
|
480
|
+
*/
|
|
481
|
+
getStats(): KeyStats[];
|
|
482
|
+
/**
|
|
483
|
+
* Get overall router statistics
|
|
484
|
+
*/
|
|
485
|
+
getOverallStats(): {
|
|
486
|
+
totalKeys: number;
|
|
487
|
+
healthyKeys: number;
|
|
488
|
+
totalRpm: number;
|
|
489
|
+
totalRpmLimit: number;
|
|
490
|
+
overallUtilization: number;
|
|
491
|
+
keys: KeyStats[];
|
|
492
|
+
};
|
|
493
|
+
/**
|
|
494
|
+
* Reset all tracking (useful for testing)
|
|
495
|
+
*/
|
|
496
|
+
reset(): void;
|
|
497
|
+
/**
|
|
498
|
+
* Shutdown cleanup
|
|
499
|
+
*/
|
|
500
|
+
destroy(): void;
|
|
501
|
+
private transitionToHealthy;
|
|
502
|
+
private openCircuit;
|
|
503
|
+
private toStats;
|
|
504
|
+
private maskKey;
|
|
505
|
+
private emitDebug;
|
|
506
|
+
private startCleanupLoop;
|
|
507
|
+
private cleanupExpiredWindows;
|
|
508
|
+
private checkCircuitRecovery;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Utility helpers for keymux
|
|
513
|
+
*/
|
|
514
|
+
/**
|
|
515
|
+
* Check if an error is a rate limit error (429)
|
|
516
|
+
*/
|
|
517
|
+
declare function isRateLimitError(error: unknown, seen?: WeakSet<object>): boolean;
|
|
518
|
+
/**
|
|
519
|
+
* Check if an error is a server error (5xx)
|
|
520
|
+
*/
|
|
521
|
+
declare function isServerError(error: unknown, seen?: WeakSet<object>): boolean;
|
|
522
|
+
/**
|
|
523
|
+
* Check if an error is retryable (rate limit or server error)
|
|
524
|
+
*/
|
|
525
|
+
declare function isRetryableError(error: unknown): boolean;
|
|
526
|
+
/**
|
|
527
|
+
* Calculate exponential backoff delay with jitter
|
|
528
|
+
*/
|
|
529
|
+
declare function calculateBackoff(attempt: number, baseMs?: number, maxMs?: number): number;
|
|
530
|
+
/**
|
|
531
|
+
* Mask API key for logging (show only first 4 and last 4 chars)
|
|
532
|
+
* Example: sk-abcdef1234567890 -> sk-ab****7890
|
|
533
|
+
*/
|
|
534
|
+
declare function maskKey(key: string): string;
|
|
535
|
+
/**
|
|
536
|
+
* Parse comma-separated keys from string
|
|
537
|
+
* Reads keys from environment variables - splits by comma to create an array
|
|
538
|
+
* Filters out empty strings
|
|
539
|
+
*/
|
|
540
|
+
declare function parseKeys(keysString: string): string[];
|
|
541
|
+
/**
|
|
542
|
+
* Create a simple async key getter for OpenAI SDK / LangChain
|
|
543
|
+
* Usage: new ChatOpenAI({ apiKey: createKeyGetter(router) })
|
|
544
|
+
* This function returns an async getter that calls router.getKey()
|
|
545
|
+
*/
|
|
546
|
+
declare function createKeyGetter(router: {
|
|
547
|
+
getKey(): Promise<string>;
|
|
548
|
+
}): () => Promise<string>;
|
|
549
|
+
/**
|
|
550
|
+
* Create a key getter with automatic failover
|
|
551
|
+
* Usage: new ChatOpenAI({ apiKey: createFailoverKeyGetter(router, 3) })
|
|
552
|
+
*/
|
|
553
|
+
declare function createFailoverKeyGetter(router: {
|
|
554
|
+
getKey(): Promise<string>;
|
|
555
|
+
reportFailure(key: string, isRateLimit?: boolean): void;
|
|
556
|
+
reportSuccess(key: string): void;
|
|
557
|
+
}, maxRetries?: number): () => Promise<string>;
|
|
558
|
+
/**
|
|
559
|
+
* Sleep utility
|
|
560
|
+
*/
|
|
561
|
+
declare function sleep(ms: number): Promise<void>;
|
|
562
|
+
/**
|
|
563
|
+
* Format router stats for pretty logging
|
|
564
|
+
*/
|
|
565
|
+
declare function formatStats(stats: ReturnType<KeyRouter['getOverallStats']>): string;
|
|
566
|
+
/**
|
|
567
|
+
* Create a periodic stats logger
|
|
568
|
+
*/
|
|
569
|
+
declare function createStatsLogger(router: {
|
|
570
|
+
getOverallStats(): ReturnType<KeyRouter['getOverallStats']>;
|
|
571
|
+
}, intervalMs?: number): NodeJS.Timeout;
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* MultiProviderRouter - Routes requests across multiple AI providers
|
|
575
|
+
*
|
|
576
|
+
* Internally uses KeyTracker + SelectionStrategy directly (same engine as KeyRouter)
|
|
577
|
+
* but adds provider-awareness: baseURL, model, and provider-level filtering.
|
|
578
|
+
*
|
|
579
|
+
* Architecture:
|
|
580
|
+
* - Flattens all providers' keys into a single pool of KeyConfig entries
|
|
581
|
+
* - Each entry gets a unique ID: `${provider}-${keyIndex}` (e.g. 'nvidia-0', 'gemini-1')
|
|
582
|
+
* - An endpointMap stores the metadata (baseURL, model, provider) for each key ID
|
|
583
|
+
* - A providerGroups map tracks which key IDs belong to which provider
|
|
584
|
+
* - Selection uses existing KeyTracker (circuit breakers, RPM) + SelectionStrategy
|
|
585
|
+
*/
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Per-provider statistics
|
|
589
|
+
*/
|
|
590
|
+
interface ProviderStats {
|
|
591
|
+
provider: string;
|
|
592
|
+
totalKeys: number;
|
|
593
|
+
healthyKeys: number;
|
|
594
|
+
totalRpm: number;
|
|
595
|
+
totalRpmLimit: number;
|
|
596
|
+
utilization: number;
|
|
597
|
+
keys: KeyStats[];
|
|
598
|
+
}
|
|
599
|
+
declare class MultiProviderRouter {
|
|
600
|
+
private tracker;
|
|
601
|
+
private strategy;
|
|
602
|
+
private endpointMap;
|
|
603
|
+
private providerGroups;
|
|
604
|
+
lastRoute: {
|
|
605
|
+
provider: string;
|
|
606
|
+
model: string;
|
|
607
|
+
key: string;
|
|
608
|
+
time: number;
|
|
609
|
+
} | null;
|
|
610
|
+
private keyConfigs;
|
|
611
|
+
private providers;
|
|
612
|
+
private config;
|
|
613
|
+
constructor(providers: ProviderPoolEntry[], config?: MultiProviderConfig);
|
|
614
|
+
/**
|
|
615
|
+
* Get the next best endpoint for making an API call
|
|
616
|
+
* Returns key + baseURL + model + provider — everything needed for a request
|
|
617
|
+
*
|
|
618
|
+
* Supports filtering:
|
|
619
|
+
* - preferProviders: try these first
|
|
620
|
+
* - excludeProviders: skip these entirely
|
|
621
|
+
* - model: pick a provider that has this model
|
|
622
|
+
* - maxUtilization: skip overloaded endpoints
|
|
623
|
+
*/
|
|
624
|
+
getEndpoint(options?: EndpointOptions): Promise<EndpointResult>;
|
|
625
|
+
/**
|
|
626
|
+
* Report a successful request for an endpoint
|
|
627
|
+
* Resets failure count, updates latency (EMA), recovers degraded state
|
|
628
|
+
*/
|
|
629
|
+
reportSuccess(endpointId: string, latencyMs?: number): void;
|
|
630
|
+
/**
|
|
631
|
+
* Report a failed request for an endpoint
|
|
632
|
+
* Increments failure count, may open circuit breaker
|
|
633
|
+
* Next getEndpoint() call will automatically route to a different provider
|
|
634
|
+
*/
|
|
635
|
+
reportFailure(endpointId: string, isRateLimit?: boolean): void;
|
|
636
|
+
/**
|
|
637
|
+
* Manually recover an endpoint (e.g. after external health check)
|
|
638
|
+
*/
|
|
639
|
+
reportRecovery(endpointId: string): void;
|
|
640
|
+
/**
|
|
641
|
+
* Get overall stats across all providers (same familiar format as KeyRouter)
|
|
642
|
+
*/
|
|
643
|
+
getOverallStats(): {
|
|
644
|
+
totalKeys: number;
|
|
645
|
+
healthyKeys: number;
|
|
646
|
+
totalRpm: number;
|
|
647
|
+
totalRpmLimit: number;
|
|
648
|
+
overallUtilization: number;
|
|
649
|
+
keys: KeyStats[];
|
|
650
|
+
};
|
|
651
|
+
/**
|
|
652
|
+
* Get stats for all endpoints
|
|
653
|
+
*/
|
|
654
|
+
getStats(): KeyStats[];
|
|
655
|
+
/**
|
|
656
|
+
* Get stats for a specific provider
|
|
657
|
+
*/
|
|
658
|
+
getProviderStats(provider: string): ProviderStats;
|
|
659
|
+
/**
|
|
660
|
+
* Get list of all configured provider names
|
|
661
|
+
*/
|
|
662
|
+
getProviderNames(): string[];
|
|
663
|
+
/**
|
|
664
|
+
* Get all available (healthy + under limit) endpoint IDs
|
|
665
|
+
*/
|
|
666
|
+
getLastRoute(): {
|
|
667
|
+
provider: string;
|
|
668
|
+
model: string;
|
|
669
|
+
key: string;
|
|
670
|
+
time: number;
|
|
671
|
+
} | null;
|
|
672
|
+
getAvailableEndpointIds(): string[];
|
|
673
|
+
/**
|
|
674
|
+
* Check if a specific endpoint is healthy
|
|
675
|
+
*/
|
|
676
|
+
isEndpointHealthy(endpointId: string): boolean;
|
|
677
|
+
/**
|
|
678
|
+
* Check if a provider has any healthy endpoints
|
|
679
|
+
*/
|
|
680
|
+
isProviderHealthy(provider: string): boolean;
|
|
681
|
+
/**
|
|
682
|
+
* Add a new provider at runtime
|
|
683
|
+
*/
|
|
684
|
+
addProvider(entry: ProviderPoolEntry): void;
|
|
685
|
+
/**
|
|
686
|
+
* Add a key to an existing provider at runtime
|
|
687
|
+
*/
|
|
688
|
+
addKeyToProvider(provider: string, key: string): void;
|
|
689
|
+
/**
|
|
690
|
+
* Remove a provider entirely at runtime
|
|
691
|
+
*/
|
|
692
|
+
removeProvider(provider: string): boolean;
|
|
693
|
+
/**
|
|
694
|
+
* Remove a specific key from a provider at runtime
|
|
695
|
+
*/
|
|
696
|
+
removeKey(provider: string, key: string): boolean;
|
|
697
|
+
/**
|
|
698
|
+
* Set the selection strategy
|
|
699
|
+
*/
|
|
700
|
+
setStrategy(strategy: SelectionStrategy | string): void;
|
|
701
|
+
/**
|
|
702
|
+
* Reset all tracking data (useful for testing)
|
|
703
|
+
*/
|
|
704
|
+
reset(): void;
|
|
705
|
+
/**
|
|
706
|
+
* Shutdown and cleanup (stops background cleanup timer)
|
|
707
|
+
*/
|
|
708
|
+
destroy(): void;
|
|
709
|
+
/**
|
|
710
|
+
* Flatten all providers into a single key pool
|
|
711
|
+
* Each key gets a unique ID: `${provider}-${keyIndex}`
|
|
712
|
+
* Provider metadata (baseURL, model) is stored in endpointMap
|
|
713
|
+
*/
|
|
714
|
+
private buildPool;
|
|
715
|
+
/**
|
|
716
|
+
* Rebuild the pool from current providers (used after dynamic changes)
|
|
717
|
+
*/
|
|
718
|
+
private rebuildPool;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
interface FetchConfig {
|
|
722
|
+
router: MultiProviderRouter;
|
|
723
|
+
maxRetries?: number;
|
|
724
|
+
preferProviders?: string[];
|
|
725
|
+
excludeProviders?: string[];
|
|
726
|
+
model?: string;
|
|
727
|
+
}
|
|
728
|
+
declare function fetchWithFailover(urlBuilder: (ep: EndpointResult) => string | URL, fetchOptionsBuilder: (ep: EndpointResult) => RequestInit, config: FetchConfig): Promise<Response>;
|
|
729
|
+
|
|
730
|
+
interface DailyUsage {
|
|
731
|
+
date: string;
|
|
732
|
+
inputTokens: number;
|
|
733
|
+
outputTokens: number;
|
|
734
|
+
cacheTokens: number;
|
|
735
|
+
requests: number;
|
|
736
|
+
providerUsage?: Record<string, ProviderUsage>;
|
|
737
|
+
}
|
|
738
|
+
interface ProviderUsage {
|
|
739
|
+
requests: number;
|
|
740
|
+
tokens: number;
|
|
741
|
+
}
|
|
742
|
+
interface UsageData {
|
|
743
|
+
totalRequests: number;
|
|
744
|
+
totalInputTokens: number;
|
|
745
|
+
totalOutputTokens: number;
|
|
746
|
+
totalCacheTokens: number;
|
|
747
|
+
providerUsage: Record<string, number | ProviderUsage>;
|
|
748
|
+
daily: Record<string, DailyUsage>;
|
|
749
|
+
sessions: number;
|
|
750
|
+
firstUsed: number;
|
|
751
|
+
}
|
|
752
|
+
declare class UsageTracker {
|
|
753
|
+
private data;
|
|
754
|
+
private filePath;
|
|
755
|
+
private saveTimeout;
|
|
756
|
+
private sessionProviderUsage;
|
|
757
|
+
private initialized;
|
|
758
|
+
constructor();
|
|
759
|
+
private ensureInitialized;
|
|
760
|
+
private loadData;
|
|
761
|
+
private saveData;
|
|
762
|
+
private scheduleSave;
|
|
763
|
+
recordRequest(provider: string, inputTokens: number, outputTokens: number, cacheTokens?: number): void;
|
|
764
|
+
getSessionUsage(): Record<string, ProviderUsage>;
|
|
765
|
+
getTodayUsage(): Record<string, ProviderUsage>;
|
|
766
|
+
getData(): UsageData;
|
|
767
|
+
}
|
|
768
|
+
declare const globalUsageTracker: UsageTracker;
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Create a KeyRouter for NVIDIA keys - the most common use case
|
|
772
|
+
* NVIDIA preset settings will be automatically applied
|
|
773
|
+
*
|
|
774
|
+
* @example
|
|
775
|
+
* ```typescript
|
|
776
|
+
* import { createNvidiaRouter } from 'keymux';
|
|
777
|
+
*
|
|
778
|
+
* const router = createNvidiaRouter([
|
|
779
|
+
* 'nvapi-key-1',
|
|
780
|
+
* 'nvapi-key-2',
|
|
781
|
+
* 'nvapi-key-3'
|
|
782
|
+
* ]);
|
|
783
|
+
*
|
|
784
|
+
* // Use with OpenAI SDK
|
|
785
|
+
* import OpenAI from 'openai';
|
|
786
|
+
* const client = new OpenAI({
|
|
787
|
+
* apiKey: async () => await router.getKey(),
|
|
788
|
+
* baseURL: 'https://integrate.api.nvidia.com/v1'
|
|
789
|
+
* });
|
|
790
|
+
* ```
|
|
791
|
+
*/
|
|
792
|
+
declare function createNvidiaRouter(keys: string[], options?: Partial<KeyRouterConfig>): KeyRouter;
|
|
793
|
+
/**
|
|
794
|
+
* Create a KeyRouter from a provider preset
|
|
795
|
+
* Examples: mistral, openai, nvidia, etc.
|
|
796
|
+
*
|
|
797
|
+
* @example
|
|
798
|
+
* ```typescript
|
|
799
|
+
* import { createRouter } from 'keymux';
|
|
800
|
+
*
|
|
801
|
+
* const router = createRouter('mistral', [
|
|
802
|
+
* 'mistral-key-1',
|
|
803
|
+
* 'mistral-key-2'
|
|
804
|
+
* ]);
|
|
805
|
+
* ```
|
|
806
|
+
*/
|
|
807
|
+
declare function createRouter(provider: keyof typeof PROVIDER_PRESETS, keys: string[], options?: Partial<KeyRouterConfig>): KeyRouter;
|
|
808
|
+
/**
|
|
809
|
+
* Create a KeyRouter from an environment variable
|
|
810
|
+
* Read keys from .env file or system env - comma separated
|
|
811
|
+
*
|
|
812
|
+
* @example
|
|
813
|
+
* ```typescript
|
|
814
|
+
* import { createRouterFromEnv } from 'keymux';
|
|
815
|
+
*
|
|
816
|
+
* // Set NVIDIA_KEYS="key1,key2,key3" in .env
|
|
817
|
+
* const router = createRouterFromEnv('NVIDIA_KEYS');
|
|
818
|
+
* ```
|
|
819
|
+
*/
|
|
820
|
+
declare function createRouterFromEnv(envVar: string, options?: Partial<KeyRouterConfig>): KeyRouter;
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Create a MultiProviderRouter from provider configs
|
|
824
|
+
* Routes requests across multiple AI providers with automatic failover
|
|
825
|
+
*
|
|
826
|
+
* @example
|
|
827
|
+
* ```typescript
|
|
828
|
+
* import { createMultiProviderRouter } from 'keymux';
|
|
829
|
+
*
|
|
830
|
+
* const router = createMultiProviderRouter([
|
|
831
|
+
* { provider: 'gemini', keys: ['AIza-key1'] },
|
|
832
|
+
* { provider: 'nvidia', keys: ['nvapi-key1', 'nvapi-key2'] },
|
|
833
|
+
* { provider: 'openrouter', keys: ['sk-or-key1'] }
|
|
834
|
+
* ]);
|
|
835
|
+
*
|
|
836
|
+
* const ep = await router.getEndpoint();
|
|
837
|
+
* // ep = { key, baseURL, model, provider, endpointId, utilization }
|
|
838
|
+
* ```
|
|
839
|
+
*/
|
|
840
|
+
declare function createMultiProviderRouter(providers: ProviderPoolEntry[], config?: MultiProviderConfig): MultiProviderRouter;
|
|
841
|
+
/**
|
|
842
|
+
* Create a MultiProviderRouter from environment variables
|
|
843
|
+
* Each provider's keys are read from a comma-separated env var
|
|
844
|
+
*
|
|
845
|
+
* @example
|
|
846
|
+
* ```typescript
|
|
847
|
+
* import { createMultiProviderRouterFromEnv } from 'keymux';
|
|
848
|
+
*
|
|
849
|
+
* // process.env.NVIDIA_KEYS = "nvapi-key1,nvapi-key2"
|
|
850
|
+
* // process.env.GEMINI_KEYS = "AIza-key1"
|
|
851
|
+
* const router = createMultiProviderRouterFromEnv({
|
|
852
|
+
* nvidia: 'NVIDIA_KEYS',
|
|
853
|
+
* gemini: 'GEMINI_KEYS',
|
|
854
|
+
* });
|
|
855
|
+
* ```
|
|
856
|
+
*/
|
|
857
|
+
declare function createMultiProviderRouterFromEnv(envMap: Record<string, string>, overrides?: Record<string, Partial<ProviderPoolEntry>>, config?: MultiProviderConfig): MultiProviderRouter;
|
|
858
|
+
|
|
859
|
+
export { CircuitState, type DailyUsage, type DebugEvent, type EndpointOptions, type EndpointResult, type KeyConfig, KeyRouter, type KeyRouterConfig, KeyRouterError, type KeySelectionResult, type KeyState, type KeyStats, KeyTracker, LeastLatencyStrategy, LeastRequestsStrategy, type MultiProviderConfig, MultiProviderRouter, PROVIDER_PRESETS, PreferredKeysStrategy, type ProviderPoolEntry, type ProviderPreset, type ProviderStats, type ProviderUsage, RandomStrategy, RateLimitError, type RouterStats, type SelectionOptions, type SelectionStrategy, SmartRoutingStrategy, type UsageData, UsageTracker, WeightedLeastUtilizationStrategy, calculateBackoff, createFailoverKeyGetter, createKeyGetter, createMultiProviderRouter, createMultiProviderRouterFromEnv, createNvidiaRouter, createRouter, createRouterFromEnv, createStatsLogger, createStrategy, defaultStrategy, fetchWithFailover, formatStats, globalUsageTracker, isRateLimitError, isRetryableError, isServerError, maskKey, parseKeys, sleep };
|