@splashcodex/api-key-manager 2.0.0 → 4.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 CHANGED
@@ -1,19 +1,26 @@
1
- # @splashcodex/api-key-manager
1
+ # @splashcodex/ApiKeyManager v4.0 — Mastermind Edition
2
2
 
3
- > Universal API Key Rotation System with Load Balancing Strategies
3
+ > Universal API Key Rotation System with Resilience, Load Balancing, Semantic Caching & AI Gateway Features
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/@splashcodex/api-key-manager)](https://www.npmjs.com/package/@splashcodex/api-key-manager)
5
+ [![npm version](https://img.shields.io/npm/v/@splashcodex/ApiKeyManager)](https://www.npmjs.com/package/@splashcodex/ApiKeyManager)
6
6
 
7
7
  ## Features
8
8
 
9
9
  - **Circuit Breaker** — Keys transition through `CLOSED → OPEN → HALF_OPEN → DEAD`
10
- - **Error Classification** — Automatic detection of 429 (Quota), 403 (Auth), 5xx (Transient), Safety blocks
10
+ - **Error Classification** — Automatic detection of 429 (Quota), 403 (Auth), 5xx (Transient), Timeout, Safety blocks
11
11
  - **Pluggable Strategies** — `StandardStrategy`, `WeightedStrategy`, `LatencyStrategy`
12
- - **Retry-After Parsing** — Respects server-provided cooldown headers
13
- - **Exponential Backoff with Jitter** — Prevents thundering herd
12
+ - **`execute()` Wrapper** — Single method: get key → call → latency → retry → fallback
13
+ - **Event Emitter** — Typed lifecycle hooks for monitoring & alerting
14
+ - **Auto-Retry with Backoff** — Built-in retry loop with exponential backoff + jitter
15
+ - **Request Timeout** — `AbortController`-based timeout per attempt
16
+ - **Fallback Function** — Graceful degradation when all keys fail
17
+ - **Provider Tagging** — Multi-provider routing (`openai`, `gemini`, etc.)
18
+ - **Health Checks** — Periodic key validation and auto-recovery
19
+ - **Bulkhead / Concurrency** — Limits concurrent `execute()` calls
20
+ - **Semantic Cache** *(v4 NEW)* — Cosine-similarity cache with pluggable embeddings
21
+ - **Recursion Guard** *(v4 NEW)* — Prevents infinite loops when `getEmbedding` calls `execute()`
14
22
  - **State Persistence** — Survives restarts via pluggable storage
15
- - **Latency Tracking** — Track average response times per key
16
- - **100% Backward Compatible** — v1.x code works without changes
23
+ - **100% Backward Compatible** — v1.x, v2.x, and v3.x code works without changes
17
24
 
18
25
  ## Installation
19
26
 
@@ -21,61 +28,151 @@
21
28
  npm install @splashcodex/api-key-manager
22
29
  ```
23
30
 
24
- ## Quick Start (v1.x Compatible)
31
+ ## Quick Start
25
32
 
26
33
  ```typescript
27
34
  import { ApiKeyManager } from '@splashcodex/api-key-manager';
28
35
 
36
+ // Simple (v1/v2 compatible)
29
37
  const manager = new ApiKeyManager(['key1', 'key2', 'key3']);
38
+ const key = manager.getKey();
39
+ manager.markSuccess(key!);
30
40
 
31
- const key = manager.getKey(); // Returns best available key
32
- manager.markSuccess(key!); // Report success
41
+ // v3+ Full power
42
+ const result = await manager.execute(
43
+ (key) => fetch(`https://api.example.com?key=${key}`),
44
+ { maxRetries: 3, timeoutMs: 5000 }
45
+ );
46
+ ```
47
+
48
+ ## v4.0 — Semantic Cache (Mastermind Edition)
49
+
50
+ Automatically cache API responses by semantic similarity. Identical or near-identical prompts return cached results without consuming API quota.
51
+
52
+ ```typescript
53
+ import { ApiKeyManager } from '@splashcodex/api-key-manager';
54
+
55
+ const manager = new ApiKeyManager(['key1', 'key2'], {
56
+ semanticCache: {
57
+ threshold: 0.92, // 92% cosine similarity to match
58
+ ttlMs: 86400000, // 24-hour TTL (default)
59
+ getEmbedding: async (text) => {
60
+ // Your embedding function (e.g. OpenAI, Gemini, local model)
61
+ return await myEmbeddingModel.embed(text);
62
+ }
63
+ }
64
+ });
65
+
66
+ // First call → API hit, cached
67
+ const r1 = await manager.execute(apiFn, { prompt: 'What is the weather?' });
68
+
69
+ // Second call → Semantic Cache HIT (no API call)
70
+ const r2 = await manager.execute(apiFn, { prompt: 'How is the weather today?' });
71
+ ```
72
+
73
+ > **Recursion Guard**: If your `getEmbedding` callback internally calls `execute()`,
74
+ > the cache automatically skips on nested calls to prevent infinite recursion.
75
+
76
+ ## execute() Wrapper
77
+
78
+ Wraps the entire lifecycle into one method:
79
+
80
+ ```typescript
81
+ const manager = new ApiKeyManager(keys, {
82
+ storage: localStorage,
83
+ strategy: new WeightedStrategy(),
84
+ fallbackFn: () => cachedResponse,
85
+ concurrency: 10
86
+ });
87
+
88
+ const result = await manager.execute(
89
+ async (key, signal) => {
90
+ const res = await fetch(url, { headers: { 'x-api-key': key }, signal });
91
+ return res.json();
92
+ },
93
+ { maxRetries: 3, timeoutMs: 10000 }
94
+ );
95
+ // Handles: key selection → cache → timeout → retry → fallback → latency tracking
33
96
  ```
34
97
 
35
- ## v2.0 — Strategies
98
+ ## Event Emitter
99
+
100
+ Monitor every state change:
101
+
102
+ ```typescript
103
+ manager.on('keyDead', (key) => alertTeam(`Key ${key} permanently dead`));
104
+ manager.on('circuitOpen', (key) => metrics.increment('circuit_opens'));
105
+ manager.on('keyRecovered', (key) => log(`Key ${key} recovered`));
106
+ manager.on('retry', (key, attempt, delay) => log(`Retry #${attempt} in ${delay}ms`));
107
+ manager.on('fallback', (reason) => log(`Fallback triggered: ${reason}`));
108
+ manager.on('allKeysExhausted', () => alert('No healthy keys!'));
109
+ manager.on('bulkheadRejected', () => metrics.increment('rejected'));
110
+ manager.on('healthCheckPassed', (key) => log(`${key} healthy`));
111
+ manager.on('healthCheckFailed', (key, err) => log(`${key} unhealthy`));
112
+ ```
36
113
 
37
- ### Weighted Strategy (Cost Optimization)
114
+ ## Load Balancing Strategies
38
115
 
39
- Prioritize cheap/free-tier keys. Expensive keys are only used as fallback.
116
+ ### Weighted (Cost Optimization)
40
117
 
41
118
  ```typescript
42
119
  import { ApiKeyManager, WeightedStrategy } from '@splashcodex/api-key-manager';
43
120
 
44
121
  const manager = new ApiKeyManager(
45
122
  [
46
- { key: 'free-tier-key-1', weight: 1.0 }, // High priority
47
- { key: 'free-tier-key-2', weight: 1.0 }, // High priority
48
- { key: 'paid-key-backup', weight: 0.1 }, // Emergency only
123
+ { key: 'free-key-1', weight: 1.0 },
124
+ { key: 'free-key-2', weight: 1.0 },
125
+ { key: 'paid-backup', weight: 0.1 },
49
126
  ],
50
- undefined, // storage (null = in-memory)
51
- new WeightedStrategy()
127
+ { strategy: new WeightedStrategy() }
52
128
  );
129
+ ```
130
+
131
+ ### Latency (Performance)
53
132
 
54
- const key = manager.getKey(); // Heavily favors free-tier keys
133
+ ```typescript
134
+ import { ApiKeyManager, LatencyStrategy } from '@splashcodex/api-key-manager';
135
+
136
+ const manager = new ApiKeyManager(keys, { strategy: new LatencyStrategy() });
137
+ // After execute(), latency is tracked automatically
55
138
  ```
56
139
 
57
- ### Latency Strategy (Performance Optimization)
140
+ ## Provider Tagging
58
141
 
59
- Automatically picks the key with the lowest average response time.
142
+ Route requests to specific providers:
60
143
 
61
144
  ```typescript
62
- import { ApiKeyManager, LatencyStrategy } from '@splashcodex/api-key-manager';
145
+ const manager = new ApiKeyManager([
146
+ { key: 'sk-openai-1', weight: 1.0, provider: 'openai' },
147
+ { key: 'sk-openai-2', weight: 1.0, provider: 'openai' },
148
+ { key: 'AIza-gemini', weight: 0.5, provider: 'gemini' },
149
+ ]);
150
+
151
+ const openaiKey = manager.getKeyByProvider('openai');
152
+ const geminiKey = manager.getKeyByProvider('gemini');
153
+ ```
63
154
 
64
- const manager = new ApiKeyManager(['key1', 'key2'], undefined, new LatencyStrategy());
155
+ ## Health Checks
65
156
 
66
- // After each request, report duration:
67
- const start = Date.now();
68
- await callApi(key);
69
- manager.markSuccess(key, Date.now() - start); // Records latency
157
+ Proactively detect recovered keys:
70
158
 
71
- // Next call automatically picks the fastest key
159
+ ```typescript
160
+ manager.setHealthCheck(async (key) => {
161
+ const res = await fetch(`https://api.openai.com/v1/models`, {
162
+ headers: { Authorization: `Bearer ${key}` }
163
+ });
164
+ return res.ok;
165
+ });
166
+
167
+ manager.startHealthChecks(60_000); // Check every 60 seconds
168
+ // manager.stopHealthChecks(); // Stop when done
72
169
  ```
73
170
 
74
- ### Error Handling
171
+ ## Error Handling
75
172
 
76
173
  ```typescript
77
174
  try {
78
- const result = await callGeminiApi(key);
175
+ const result = await callApi(key);
79
176
  manager.markSuccess(key, duration);
80
177
  } catch (error) {
81
178
  const classification = manager.classifyError(error);
@@ -84,31 +181,71 @@ try {
84
181
  if (classification.retryable) {
85
182
  const delay = manager.calculateBackoff(attempt);
86
183
  await sleep(delay);
87
- // retry with manager.getKey()
88
184
  }
89
185
  }
90
186
  ```
91
187
 
92
- ### Health Monitoring
188
+ ## API Reference
189
+
190
+ ### Constructor
93
191
 
94
192
  ```typescript
95
- const stats = manager.getStats();
96
- // { total: 5, healthy: 3, cooling: 1, dead: 1 }
193
+ // Legacy (v1/v2)
194
+ new ApiKeyManager(keys, storage?, strategy?)
195
+
196
+ // v3+ Options
197
+ new ApiKeyManager(keys, {
198
+ storage?, // Pluggable storage { getItem, setItem }
199
+ strategy?, // LoadBalancingStrategy instance
200
+ fallbackFn?, // () => any — called when all keys exhausted
201
+ concurrency?, // Max concurrent execute() calls
202
+ semanticCache?, // v4: { threshold, ttlMs, getEmbedding }
203
+ })
97
204
  ```
98
205
 
99
- ## API Reference
206
+ ### Methods
100
207
 
101
208
  | Method | Description |
102
209
  |--------|-------------|
103
210
  | `getKey()` | Returns best available key via strategy |
211
+ | `getKeyByProvider(provider)` | Get key filtered by provider tag |
104
212
  | `markSuccess(key, durationMs?)` | Report success + optional latency |
105
213
  | `markFailed(key, classification)` | Report failure with error type |
106
214
  | `classifyError(error, finishReason?)` | Classify an error automatically |
215
+ | `execute(fn, options?)` | Full lifecycle wrapper with retry/timeout |
107
216
  | `calculateBackoff(attempt)` | Get backoff delay with jitter |
108
217
  | `getStats()` | Get pool health statistics |
109
218
  | `getKeyCount()` | Count of non-DEAD keys |
110
-
111
- ## Strategies
219
+ | `setHealthCheck(fn)` | Set health check function |
220
+ | `startHealthChecks(ms)` | Start periodic health checks |
221
+ | `stopHealthChecks()` | Stop health checks |
222
+
223
+ ### Events
224
+
225
+ | Event | Payload | Trigger |
226
+ |-------|---------|---------|
227
+ | `keyDead` | `key: string` | Key marked as permanently dead |
228
+ | `circuitOpen` | `key: string` | Key circuit opened (cooldown) |
229
+ | `circuitHalfOpen` | `key: string` | Key entering test phase |
230
+ | `keyRecovered` | `key: string` | Key recovered from failure |
231
+ | `fallback` | `reason: string` | Fallback function invoked |
232
+ | `allKeysExhausted` | — | All keys dead, no fallback |
233
+ | `retry` | `key, attempt, delayMs` | Retry attempt starting |
234
+ | `executeSuccess` | `key, durationMs` | execute() completed successfully |
235
+ | `executeFailed` | `key, error` | execute() attempt failed |
236
+ | `bulkheadRejected` | — | Concurrency limit reached |
237
+ | `healthCheckPassed` | `key: string` | Health check succeeded |
238
+ | `healthCheckFailed` | `key, error` | Health check failed |
239
+
240
+ ### Custom Errors
241
+
242
+ | Error | When |
243
+ |-------|------|
244
+ | `TimeoutError` | Request exceeded `timeoutMs` |
245
+ | `BulkheadRejectionError` | Concurrency limit exceeded |
246
+ | `AllKeysExhaustedError` | All keys dead, no fallback |
247
+
248
+ ### Strategies
112
249
 
113
250
  | Strategy | Algorithm | Best For |
114
251
  |----------|-----------|----------|
package/dist/index.d.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  /**
2
- * Universal ApiKeyManager v2.0
3
- * Implements: Rotation, Circuit Breaker, Persistence, Exponential Backoff, Strategies
2
+ * Universal ApiKeyManager v4.0 — Mastermind Edition
3
+ * Implements: Rotation, Circuit Breaker, Persistence, Exponential Backoff, Strategies,
4
+ * Event Emitter, Fallback, execute(), Timeout, Auto-Retry, Provider Tags,
5
+ * Health Checks, Bulkhead/Concurrency
4
6
  * Gemini-Specific: finishReason handling, Safety blocks, RECITATION detection
5
7
  */
8
+ import { EventEmitter } from 'events';
6
9
  export interface KeyState {
7
10
  key: string;
8
11
  failCount: number;
@@ -18,8 +21,9 @@ export interface KeyState {
18
21
  averageLatency: number;
19
22
  totalLatency: number;
20
23
  latencySamples: number;
24
+ provider: string;
21
25
  }
22
- export type ErrorType = 'QUOTA' | 'TRANSIENT' | 'AUTH' | 'BAD_REQUEST' | 'SAFETY' | 'RECITATION' | 'UNKNOWN';
26
+ export type ErrorType = 'QUOTA' | 'TRANSIENT' | 'AUTH' | 'BAD_REQUEST' | 'SAFETY' | 'RECITATION' | 'TIMEOUT' | 'UNKNOWN';
23
27
  export interface ErrorClassification {
24
28
  type: ErrorType;
25
29
  retryable: boolean;
@@ -33,6 +37,52 @@ export interface ApiKeyManagerStats {
33
37
  cooling: number;
34
38
  dead: number;
35
39
  }
40
+ export interface ExecuteOptions {
41
+ timeoutMs?: number;
42
+ maxRetries?: number;
43
+ finishReason?: string;
44
+ provider?: string;
45
+ }
46
+ export interface ApiKeyManagerOptions {
47
+ storage?: any;
48
+ strategy?: LoadBalancingStrategy;
49
+ fallbackFn?: () => any;
50
+ concurrency?: number;
51
+ semanticCache?: {
52
+ threshold?: number;
53
+ ttlMs?: number;
54
+ getEmbedding: (text: string) => Promise<number[]>;
55
+ };
56
+ }
57
+ export interface CacheEntry {
58
+ vector: number[];
59
+ prompt: string;
60
+ response: any;
61
+ timestamp: number;
62
+ }
63
+ export interface ApiKeyManagerEventMap {
64
+ keyDead: (key: string) => void;
65
+ circuitOpen: (key: string) => void;
66
+ circuitHalfOpen: (key: string) => void;
67
+ keyRecovered: (key: string) => void;
68
+ fallback: (reason: string) => void;
69
+ allKeysExhausted: () => void;
70
+ retry: (key: string, attempt: number, delayMs: number) => void;
71
+ healthCheckFailed: (key: string, error: any) => void;
72
+ healthCheckPassed: (key: string) => void;
73
+ executeSuccess: (key: string, durationMs: number) => void;
74
+ executeFailed: (key: string, error: any) => void;
75
+ bulkheadRejected: () => void;
76
+ }
77
+ export declare class TimeoutError extends Error {
78
+ constructor(ms: number);
79
+ }
80
+ export declare class BulkheadRejectionError extends Error {
81
+ constructor();
82
+ }
83
+ export declare class AllKeysExhaustedError extends Error {
84
+ constructor();
85
+ }
36
86
  /**
37
87
  * Strategy Interface for selecting the next key
38
88
  */
@@ -58,15 +108,49 @@ export declare class WeightedStrategy implements LoadBalancingStrategy {
58
108
  export declare class LatencyStrategy implements LoadBalancingStrategy {
59
109
  next(candidates: KeyState[]): KeyState | null;
60
110
  }
61
- export declare class ApiKeyManager {
111
+ /**
112
+ * High-performance Vanilla Semantic Cache
113
+ * Implements Cosine Similarity math from scratch.
114
+ */
115
+ export declare class SemanticCache {
116
+ private entries;
117
+ private threshold;
118
+ private ttlMs;
119
+ constructor(threshold?: number, ttlMs?: number);
120
+ set(prompt: string, vector: number[], response: any): void;
121
+ get(vector: number[]): any | null;
122
+ /**
123
+ * Vanilla Cosine Similarity: (A·B) / (||A|| * ||B||)
124
+ */
125
+ private calculateCosineSimilarity;
126
+ }
127
+ export declare class ApiKeyManager extends EventEmitter {
62
128
  private keys;
63
129
  private storageKey;
64
130
  private storage;
65
131
  private strategy;
132
+ private fallbackFn?;
133
+ private maxConcurrency;
134
+ private activeCalls;
135
+ private healthCheckFn?;
136
+ private healthCheckInterval?;
137
+ private semanticCache?;
138
+ private getEmbeddingFn?;
139
+ private _isResolvingEmbedding;
140
+ /**
141
+ * Constructor supports both legacy positional args and new options object.
142
+ *
143
+ * @example Legacy (v1/v2 — still works):
144
+ * new ApiKeyManager(['key1', 'key2'], storage, strategy)
145
+ *
146
+ * @example New (v3):
147
+ * new ApiKeyManager(keys, { storage, strategy, fallbackFn, concurrency })
148
+ */
66
149
  constructor(initialKeys: string[] | {
67
150
  key: string;
68
151
  weight?: number;
69
- }[], storage?: any, strategy?: LoadBalancingStrategy);
152
+ provider?: string;
153
+ }[], storageOrOptions?: any | ApiKeyManagerOptions, strategy?: LoadBalancingStrategy);
70
154
  /**
71
155
  * CLASSIFIES an error to determine handling strategy
72
156
  */
@@ -74,6 +158,10 @@ export declare class ApiKeyManager {
74
158
  private parseRetryAfter;
75
159
  private isOnCooldown;
76
160
  getKey(): string | null;
161
+ /**
162
+ * Get a key filtered by provider tag
163
+ */
164
+ getKeyByProvider(provider: string): string | null;
77
165
  getKeyCount(): number;
78
166
  /**
79
167
  * Mark success AND update latency stats
@@ -85,6 +173,35 @@ export declare class ApiKeyManager {
85
173
  calculateBackoff(attempt: number): number;
86
174
  getStats(): ApiKeyManagerStats;
87
175
  _getKeys(): KeyState[];
176
+ /**
177
+ * Wraps the entire API call lifecycle into a single method.
178
+ *
179
+ * @example
180
+ * const result = await manager.execute(
181
+ * (key) => fetch(`https://api.example.com?key=${key}`),
182
+ * { maxRetries: 3, timeoutMs: 5000 }
183
+ * );
184
+ */
185
+ execute<T>(fn: (key: string, signal?: AbortSignal) => Promise<T>, options?: ExecuteOptions & {
186
+ prompt?: string;
187
+ }): Promise<T>;
188
+ private _executeWithRetry;
189
+ private _executeWithTimeout;
190
+ private _sleep;
191
+ /**
192
+ * Set a health check function that tests if a key is operational
193
+ */
194
+ setHealthCheck(fn: (key: string) => Promise<boolean>): void;
195
+ /**
196
+ * Start periodic health checks
197
+ * @param intervalMs How often to run health checks (default: 60s)
198
+ */
199
+ startHealthChecks(intervalMs?: number): void;
200
+ /**
201
+ * Stop periodic health checks
202
+ */
203
+ stopHealthChecks(): void;
204
+ private _runHealthChecks;
88
205
  private saveState;
89
206
  private loadState;
90
207
  }