@splashcodex/api-key-manager 4.0.0 → 5.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 +100 -186
- package/dist/index.d.ts +22 -2
- package/dist/index.js +144 -5
- package/dist/index.js.map +1 -1
- package/dist/persistence/file.d.ts +43 -0
- package/dist/persistence/file.js +82 -0
- package/dist/persistence/file.js.map +1 -0
- package/dist/persistence/index.d.ts +7 -0
- package/dist/persistence/index.js +12 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/memory.d.ts +30 -0
- package/dist/persistence/memory.js +43 -0
- package/dist/persistence/memory.js.map +1 -0
- package/dist/presets/base.d.ts +138 -0
- package/dist/presets/base.js +227 -0
- package/dist/presets/base.js.map +1 -0
- package/dist/presets/gemini.d.ts +60 -0
- package/dist/presets/gemini.js +77 -0
- package/dist/presets/gemini.js.map +1 -0
- package/dist/presets/index.d.ts +10 -0
- package/dist/presets/index.js +16 -0
- package/dist/presets/index.js.map +1 -0
- package/dist/presets/multi.d.ts +120 -0
- package/dist/presets/multi.js +210 -0
- package/dist/presets/multi.js.map +1 -0
- package/dist/presets/openai.d.ts +45 -0
- package/dist/presets/openai.js +62 -0
- package/dist/presets/openai.js.map +1 -0
- package/package.json +75 -3
- package/src/index.ts +163 -4
- package/src/persistence/file.ts +89 -0
- package/src/persistence/index.ts +7 -0
- package/src/persistence/memory.ts +43 -0
- package/src/presets/base.ts +323 -0
- package/src/presets/gemini.ts +84 -0
- package/src/presets/index.ts +10 -0
- package/src/presets/multi.ts +280 -0
- package/src/presets/openai.ts +69 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MultiManager Preset — Multi-Provider Key Vault
|
|
4
|
+
*
|
|
5
|
+
* Manages API keys across multiple providers from a single entry point.
|
|
6
|
+
* Aggregates keys from multiple environment variables and routes
|
|
7
|
+
* requests to the correct provider pool.
|
|
8
|
+
*
|
|
9
|
+
* @module presets/multi
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { MultiManager } from '@splashcodex/api-key-manager/presets/multi';
|
|
14
|
+
*
|
|
15
|
+
* const result = MultiManager.getInstance({
|
|
16
|
+
* providers: {
|
|
17
|
+
* gemini: { envKeys: ['GOOGLE_GEMINI_API_KEY'] },
|
|
18
|
+
* openai: { envKeys: ['OPENAI_API_KEY'] },
|
|
19
|
+
* }
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* if (result.success) {
|
|
23
|
+
* const vault = result.data;
|
|
24
|
+
*
|
|
25
|
+
* // Route to specific provider
|
|
26
|
+
* const text = await vault.execute(async (key) => {
|
|
27
|
+
* // key is from the gemini pool
|
|
28
|
+
* return callGemini(key, prompt);
|
|
29
|
+
* }, { provider: 'gemini', maxRetries: 3 });
|
|
30
|
+
*
|
|
31
|
+
* // Get stats across all providers
|
|
32
|
+
* const stats = vault.getMultiStats();
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.MultiManager = void 0;
|
|
38
|
+
const index_1 = require("../index");
|
|
39
|
+
const file_1 = require("../persistence/file");
|
|
40
|
+
const path_1 = require("path");
|
|
41
|
+
const os_1 = require("os");
|
|
42
|
+
// ─── MultiManager ───────────────────────────────────────────────────────────
|
|
43
|
+
class MultiManager {
|
|
44
|
+
static instance = null;
|
|
45
|
+
managers = new Map();
|
|
46
|
+
logger;
|
|
47
|
+
constructor(options) {
|
|
48
|
+
this.logger = options.logger || console;
|
|
49
|
+
for (const [providerName, config] of Object.entries(options.providers)) {
|
|
50
|
+
const keys = MultiManager.parseKeysFromEnv(config.envKeys);
|
|
51
|
+
if (keys.length === 0) {
|
|
52
|
+
this.logger.warn(`[MultiManager:${providerName}] No API keys found in: ${config.envKeys.join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
const storage = new file_1.FileStorage({
|
|
55
|
+
filePath: (0, path_1.join)((0, os_1.tmpdir)(), `codedex_multi_${providerName}_state.json`),
|
|
56
|
+
clearOnInit: true,
|
|
57
|
+
});
|
|
58
|
+
const manager = new index_1.ApiKeyManager(keys, {
|
|
59
|
+
storage,
|
|
60
|
+
strategy: config.strategy || new index_1.LatencyStrategy(),
|
|
61
|
+
concurrency: config.concurrency ?? 20,
|
|
62
|
+
semanticCache: config.semanticCache,
|
|
63
|
+
});
|
|
64
|
+
// Wire events
|
|
65
|
+
manager.on('keyDead', (key) => this.logger.error(`[MultiManager:${providerName}] Key DEAD: ...${key.slice(-4)}`));
|
|
66
|
+
manager.on('circuitOpen', (key) => this.logger.warn(`[MultiManager:${providerName}] Circuit OPEN: ...${key.slice(-4)}`));
|
|
67
|
+
manager.on('keyRecovered', (key) => this.logger.info(`[MultiManager:${providerName}] Key RECOVERED: ...${key.slice(-4)}`));
|
|
68
|
+
manager.on('allKeysExhausted', () => this.logger.error(`[MultiManager:${providerName}] ALL KEYS EXHAUSTED`));
|
|
69
|
+
// Health checks
|
|
70
|
+
const interval = options.healthCheckIntervalMs ?? 300_000;
|
|
71
|
+
if (interval > 0) {
|
|
72
|
+
manager.startHealthChecks(interval);
|
|
73
|
+
}
|
|
74
|
+
this.managers.set(providerName, manager);
|
|
75
|
+
this.logger.info(`[MultiManager:${providerName}] Initialized with ${keys.length} keys`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// ─── Factory ────────────────────────────────────────────────────────────
|
|
79
|
+
/**
|
|
80
|
+
* Get or create the singleton MultiManager instance.
|
|
81
|
+
*/
|
|
82
|
+
static getInstance(options) {
|
|
83
|
+
if (MultiManager.instance) {
|
|
84
|
+
return { success: true, data: MultiManager.instance };
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
MultiManager.instance = new MultiManager(options);
|
|
88
|
+
return { success: true, data: MultiManager.instance };
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Reset the singleton (primarily for testing).
|
|
99
|
+
*/
|
|
100
|
+
static reset() {
|
|
101
|
+
if (MultiManager.instance) {
|
|
102
|
+
MultiManager.instance.destroy();
|
|
103
|
+
MultiManager.instance = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// ─── Public API ─────────────────────────────────────────────────────────
|
|
107
|
+
/**
|
|
108
|
+
* Execute a function with a specific provider's key pool.
|
|
109
|
+
*
|
|
110
|
+
* @param fn - The function to execute with a key
|
|
111
|
+
* @param options - Must include `provider` to select the pool
|
|
112
|
+
*/
|
|
113
|
+
async execute(fn, options) {
|
|
114
|
+
const manager = this.managers.get(options.provider);
|
|
115
|
+
if (!manager) {
|
|
116
|
+
throw new Error(`[MultiManager] Unknown provider "${options.provider}". ` +
|
|
117
|
+
`Available: ${[...this.managers.keys()].join(', ')}`);
|
|
118
|
+
}
|
|
119
|
+
// Strip `provider` before delegating — each manager only has keys for one provider
|
|
120
|
+
const { provider, ...delegateOptions } = options;
|
|
121
|
+
return manager.execute(fn, delegateOptions);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Execute a streaming function with a specific provider's key pool.
|
|
125
|
+
*/
|
|
126
|
+
async *executeStream(fn, options) {
|
|
127
|
+
const manager = this.managers.get(options.provider);
|
|
128
|
+
if (!manager) {
|
|
129
|
+
throw new Error(`[MultiManager] Unknown provider "${options.provider}". ` +
|
|
130
|
+
`Available: ${[...this.managers.keys()].join(', ')}`);
|
|
131
|
+
}
|
|
132
|
+
const { provider, ...delegateOptions } = options;
|
|
133
|
+
yield* manager.executeStream(fn, delegateOptions);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get a raw key from a specific provider pool.
|
|
137
|
+
*/
|
|
138
|
+
getKey(provider) {
|
|
139
|
+
const manager = this.managers.get(provider);
|
|
140
|
+
if (!manager)
|
|
141
|
+
return null;
|
|
142
|
+
return manager.getKey();
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get stats for a specific provider.
|
|
146
|
+
*/
|
|
147
|
+
getStats(provider) {
|
|
148
|
+
const manager = this.managers.get(provider);
|
|
149
|
+
if (!manager)
|
|
150
|
+
return null;
|
|
151
|
+
return manager.getStats();
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get aggregate stats across ALL providers.
|
|
155
|
+
*/
|
|
156
|
+
getMultiStats() {
|
|
157
|
+
const stats = {};
|
|
158
|
+
for (const [name, manager] of this.managers) {
|
|
159
|
+
stats[name] = manager.getStats();
|
|
160
|
+
}
|
|
161
|
+
return stats;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get the list of available provider names.
|
|
165
|
+
*/
|
|
166
|
+
getProviders() {
|
|
167
|
+
return [...this.managers.keys()];
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get the underlying ApiKeyManager for a specific provider.
|
|
171
|
+
*/
|
|
172
|
+
getManager(provider) {
|
|
173
|
+
return this.managers.get(provider);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Stop all health checks and clean up.
|
|
177
|
+
*/
|
|
178
|
+
destroy() {
|
|
179
|
+
for (const [, manager] of this.managers) {
|
|
180
|
+
manager.stopHealthChecks();
|
|
181
|
+
}
|
|
182
|
+
this.managers.clear();
|
|
183
|
+
}
|
|
184
|
+
// ─── Helpers ────────────────────────────────────────────────────────────
|
|
185
|
+
static parseKeysFromEnv(envKeys) {
|
|
186
|
+
const keys = [];
|
|
187
|
+
for (const envName of envKeys) {
|
|
188
|
+
const envValue = process.env[envName];
|
|
189
|
+
if (!envValue)
|
|
190
|
+
continue;
|
|
191
|
+
const trimmed = envValue.trim();
|
|
192
|
+
if (!trimmed)
|
|
193
|
+
continue;
|
|
194
|
+
if (trimmed.startsWith('[')) {
|
|
195
|
+
try {
|
|
196
|
+
const parsed = JSON.parse(trimmed);
|
|
197
|
+
if (Array.isArray(parsed)) {
|
|
198
|
+
keys.push(...parsed.filter((k) => typeof k === 'string' && k.trim()));
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch { /* not JSON */ }
|
|
203
|
+
}
|
|
204
|
+
keys.push(...trimmed.split(',').map(k => k.trim()).filter(k => k.length > 0));
|
|
205
|
+
}
|
|
206
|
+
return [...new Set(keys)];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
exports.MultiManager = MultiManager;
|
|
210
|
+
//# sourceMappingURL=multi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi.js","sourceRoot":"","sources":["../../src/presets/multi.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Preset — One-line OpenAI API key management
|
|
3
|
+
*
|
|
4
|
+
* Pre-configured singleton with:
|
|
5
|
+
* - Reads `OPENAI_API_KEY` from env
|
|
6
|
+
* - LatencyStrategy for optimal key selection
|
|
7
|
+
* - File persistence (survives restarts)
|
|
8
|
+
* - Health checks every 5 minutes
|
|
9
|
+
* - Concurrency limit of 20
|
|
10
|
+
*
|
|
11
|
+
* @module presets/openai
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { OpenAIManager } from '@splashcodex/api-key-manager/presets/openai';
|
|
16
|
+
*
|
|
17
|
+
* const result = OpenAIManager.getInstance();
|
|
18
|
+
* if (!result.success) throw result.error;
|
|
19
|
+
* const openai = result.data;
|
|
20
|
+
*
|
|
21
|
+
* const response = await openai.execute(async (key) => {
|
|
22
|
+
* const client = new OpenAI({ apiKey: key });
|
|
23
|
+
* const completion = await client.chat.completions.create({
|
|
24
|
+
* model: 'gpt-4o',
|
|
25
|
+
* messages: [{ role: 'user', content: 'Hello!' }],
|
|
26
|
+
* });
|
|
27
|
+
* return completion.choices[0].message.content;
|
|
28
|
+
* }, { maxRetries: 3, timeoutMs: 30000 });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
import { BasePreset, PresetOptions, Result } from './base';
|
|
32
|
+
export declare class OpenAIManager extends BasePreset {
|
|
33
|
+
private static readonly PROVIDER;
|
|
34
|
+
private constructor();
|
|
35
|
+
private static getDefaultOptions;
|
|
36
|
+
/**
|
|
37
|
+
* Get or create the singleton OpenAIManager instance.
|
|
38
|
+
*
|
|
39
|
+
* @param overrides - Optional configuration overrides
|
|
40
|
+
* @returns Result<OpenAIManager>
|
|
41
|
+
*/
|
|
42
|
+
static getInstance(overrides?: Partial<PresetOptions>): Result<OpenAIManager>;
|
|
43
|
+
static reset(): void;
|
|
44
|
+
}
|
|
45
|
+
export { Result } from './base';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenAI Preset — One-line OpenAI API key management
|
|
4
|
+
*
|
|
5
|
+
* Pre-configured singleton with:
|
|
6
|
+
* - Reads `OPENAI_API_KEY` from env
|
|
7
|
+
* - LatencyStrategy for optimal key selection
|
|
8
|
+
* - File persistence (survives restarts)
|
|
9
|
+
* - Health checks every 5 minutes
|
|
10
|
+
* - Concurrency limit of 20
|
|
11
|
+
*
|
|
12
|
+
* @module presets/openai
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { OpenAIManager } from '@splashcodex/api-key-manager/presets/openai';
|
|
17
|
+
*
|
|
18
|
+
* const result = OpenAIManager.getInstance();
|
|
19
|
+
* if (!result.success) throw result.error;
|
|
20
|
+
* const openai = result.data;
|
|
21
|
+
*
|
|
22
|
+
* const response = await openai.execute(async (key) => {
|
|
23
|
+
* const client = new OpenAI({ apiKey: key });
|
|
24
|
+
* const completion = await client.chat.completions.create({
|
|
25
|
+
* model: 'gpt-4o',
|
|
26
|
+
* messages: [{ role: 'user', content: 'Hello!' }],
|
|
27
|
+
* });
|
|
28
|
+
* return completion.choices[0].message.content;
|
|
29
|
+
* }, { maxRetries: 3, timeoutMs: 30000 });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.OpenAIManager = void 0;
|
|
34
|
+
const base_1 = require("./base");
|
|
35
|
+
class OpenAIManager extends base_1.BasePreset {
|
|
36
|
+
static PROVIDER = 'openai';
|
|
37
|
+
constructor(keys, options) {
|
|
38
|
+
super(keys, options);
|
|
39
|
+
}
|
|
40
|
+
static getDefaultOptions() {
|
|
41
|
+
return {
|
|
42
|
+
envKeys: ['OPENAI_API_KEY'],
|
|
43
|
+
provider: OpenAIManager.PROVIDER,
|
|
44
|
+
concurrency: 20,
|
|
45
|
+
healthCheckIntervalMs: 300_000,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get or create the singleton OpenAIManager instance.
|
|
50
|
+
*
|
|
51
|
+
* @param overrides - Optional configuration overrides
|
|
52
|
+
* @returns Result<OpenAIManager>
|
|
53
|
+
*/
|
|
54
|
+
static getInstance(overrides) {
|
|
55
|
+
return base_1.BasePreset.createInstance(OpenAIManager, OpenAIManager.getDefaultOptions(), overrides);
|
|
56
|
+
}
|
|
57
|
+
static reset() {
|
|
58
|
+
base_1.BasePreset.resetInstance(OpenAIManager.PROVIDER);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.OpenAIManager = OpenAIManager;
|
|
62
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/presets/openai.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;;;AAEH,iCAA2D;AAE3D,MAAa,aAAc,SAAQ,iBAAU;IACjC,MAAM,CAAU,QAAQ,GAAG,QAAQ,CAAC;IAE5C,YAAoB,IAAc,EAAE,OAAsB;QACtD,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACzB,CAAC;IAEO,MAAM,CAAC,iBAAiB;QAC5B,OAAO;YACH,OAAO,EAAE,CAAC,gBAAgB,CAAC;YAC3B,QAAQ,EAAE,aAAa,CAAC,QAAQ;YAChC,WAAW,EAAE,EAAE;YACf,qBAAqB,EAAE,OAAO;SACjC,CAAC;IACN,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,WAAW,CAAC,SAAkC;QACjD,OAAO,iBAAU,CAAC,cAAc,CAC5B,aAAyF,EACzF,aAAa,CAAC,iBAAiB,EAAE,EACjC,SAAS,CACa,CAAC;IAC/B,CAAC;IAED,MAAM,CAAC,KAAK;QACR,iBAAU,CAAC,aAAa,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACrD,CAAC;;AAhCL,sCAiCC"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,75 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@splashcodex/api-key-manager",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Universal API Key
|
|
3
|
+
"version": "5.0.0",
|
|
4
|
+
"description": "Universal API Key Management Gateway with provider presets, persistence, and multi-provider vault",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"require": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
},
|
|
12
|
+
"./presets": {
|
|
13
|
+
"require": "./dist/presets/index.js",
|
|
14
|
+
"types": "./dist/presets/index.d.ts"
|
|
15
|
+
},
|
|
16
|
+
"./presets/gemini": {
|
|
17
|
+
"require": "./dist/presets/gemini.js",
|
|
18
|
+
"types": "./dist/presets/gemini.d.ts"
|
|
19
|
+
},
|
|
20
|
+
"./presets/openai": {
|
|
21
|
+
"require": "./dist/presets/openai.js",
|
|
22
|
+
"types": "./dist/presets/openai.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./presets/multi": {
|
|
25
|
+
"require": "./dist/presets/multi.js",
|
|
26
|
+
"types": "./dist/presets/multi.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./presets/base": {
|
|
29
|
+
"require": "./dist/presets/base.js",
|
|
30
|
+
"types": "./dist/presets/base.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"./persistence": {
|
|
33
|
+
"require": "./dist/persistence/index.js",
|
|
34
|
+
"types": "./dist/persistence/index.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./persistence/file": {
|
|
37
|
+
"require": "./dist/persistence/file.js",
|
|
38
|
+
"types": "./dist/persistence/file.d.ts"
|
|
39
|
+
},
|
|
40
|
+
"./persistence/memory": {
|
|
41
|
+
"require": "./dist/persistence/memory.js",
|
|
42
|
+
"types": "./dist/persistence/memory.d.ts"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"typesVersions": {
|
|
46
|
+
"*": {
|
|
47
|
+
"presets": [
|
|
48
|
+
"dist/presets/index.d.ts"
|
|
49
|
+
],
|
|
50
|
+
"presets/gemini": [
|
|
51
|
+
"dist/presets/gemini.d.ts"
|
|
52
|
+
],
|
|
53
|
+
"presets/openai": [
|
|
54
|
+
"dist/presets/openai.d.ts"
|
|
55
|
+
],
|
|
56
|
+
"presets/multi": [
|
|
57
|
+
"dist/presets/multi.d.ts"
|
|
58
|
+
],
|
|
59
|
+
"presets/base": [
|
|
60
|
+
"dist/presets/base.d.ts"
|
|
61
|
+
],
|
|
62
|
+
"persistence": [
|
|
63
|
+
"dist/persistence/index.d.ts"
|
|
64
|
+
],
|
|
65
|
+
"persistence/file": [
|
|
66
|
+
"dist/persistence/file.d.ts"
|
|
67
|
+
],
|
|
68
|
+
"persistence/memory": [
|
|
69
|
+
"dist/persistence/memory.d.ts"
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
},
|
|
7
73
|
"files": [
|
|
8
74
|
"dist",
|
|
9
75
|
"src"
|
|
@@ -18,6 +84,7 @@
|
|
|
18
84
|
"key",
|
|
19
85
|
"rotation",
|
|
20
86
|
"gemini",
|
|
87
|
+
"openai",
|
|
21
88
|
"rate-limit",
|
|
22
89
|
"circuit-breaker",
|
|
23
90
|
"resilience",
|
|
@@ -25,7 +92,12 @@
|
|
|
25
92
|
"timeout",
|
|
26
93
|
"retry",
|
|
27
94
|
"gateway",
|
|
28
|
-
"load-balancing"
|
|
95
|
+
"load-balancing",
|
|
96
|
+
"preset",
|
|
97
|
+
"persistence",
|
|
98
|
+
"multi-provider",
|
|
99
|
+
"semantic-cache",
|
|
100
|
+
"streaming"
|
|
29
101
|
],
|
|
30
102
|
"author": "Antigravity",
|
|
31
103
|
"license": "ISC",
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Universal ApiKeyManager
|
|
2
|
+
* Universal ApiKeyManager v5.0 — Ecosystem Edition
|
|
3
3
|
* Implements: Rotation, Circuit Breaker, Persistence, Exponential Backoff, Strategies,
|
|
4
4
|
* Event Emitter, Fallback, execute(), Timeout, Auto-Retry, Provider Tags,
|
|
5
5
|
* Health Checks, Bulkhead/Concurrency
|
|
6
|
+
* NEW in v5.0: Provider Presets (GeminiManager, OpenAIManager, MultiManager),
|
|
7
|
+
* Built-in Persistence (FileStorage, MemoryStorage)
|
|
6
8
|
* Gemini-Specific: finishReason handling, Safety blocks, RECITATION detection
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
import { EventEmitter } from 'events';
|
|
10
12
|
|
|
13
|
+
// ─── Re-exports: Persistence ─────────────────────────────────────────────────
|
|
14
|
+
// Persistence adapters can be imported from root or via subpath
|
|
15
|
+
export { FileStorage } from './persistence/file';
|
|
16
|
+
export type { FileStorageOptions } from './persistence/file';
|
|
17
|
+
export { MemoryStorage } from './persistence/memory';
|
|
18
|
+
|
|
19
|
+
// ─── Presets ─────────────────────────────────────────────────────────────────
|
|
20
|
+
// Presets are available via subpath imports to avoid circular dependencies:
|
|
21
|
+
// import { GeminiManager } from '@splashcodex/api-key-manager/presets/gemini';
|
|
22
|
+
// import { OpenAIManager } from '@splashcodex/api-key-manager/presets/openai';
|
|
23
|
+
// import { MultiManager } from '@splashcodex/api-key-manager/presets/multi';
|
|
24
|
+
// Or import all at once:
|
|
25
|
+
// import { GeminiManager, OpenAIManager, MultiManager } from '@splashcodex/api-key-manager/presets';
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
11
29
|
// ─── Interfaces & Types ──────────────────────────────────────────────────────
|
|
12
30
|
|
|
13
31
|
export interface KeyState {
|
|
@@ -185,12 +203,15 @@ export class WeightedStrategy implements LoadBalancingStrategy {
|
|
|
185
203
|
}
|
|
186
204
|
|
|
187
205
|
/**
|
|
188
|
-
* Latency Strategy: Pick lowest average latency
|
|
206
|
+
* Latency Strategy: Pick lowest average latency with LRU tie-break
|
|
189
207
|
*/
|
|
190
208
|
export class LatencyStrategy implements LoadBalancingStrategy {
|
|
191
209
|
next(candidates: KeyState[]): KeyState | null {
|
|
192
210
|
if (candidates.length === 0) return null;
|
|
193
|
-
candidates.sort((a, b) =>
|
|
211
|
+
candidates.sort((a, b) => {
|
|
212
|
+
if (a.averageLatency !== b.averageLatency) return a.averageLatency - b.averageLatency;
|
|
213
|
+
return a.lastUsed - b.lastUsed; // LRU tie-break
|
|
214
|
+
});
|
|
194
215
|
return candidates[0];
|
|
195
216
|
}
|
|
196
217
|
}
|
|
@@ -664,6 +685,140 @@ export class ApiKeyManager extends EventEmitter {
|
|
|
664
685
|
}
|
|
665
686
|
}
|
|
666
687
|
|
|
688
|
+
/**
|
|
689
|
+
* Executes a streaming function (AsyncGenerator) with retry logic and semantic caching.
|
|
690
|
+
*
|
|
691
|
+
* @example
|
|
692
|
+
* const stream = await manager.executeStream(async (key) => {
|
|
693
|
+
* return await gemini.generateContentStream({ prompt: "..." });
|
|
694
|
+
* }, { prompt: "..." });
|
|
695
|
+
*
|
|
696
|
+
* for await (const chunk of stream) {
|
|
697
|
+
* console.log(chunk.text());
|
|
698
|
+
* }
|
|
699
|
+
*/
|
|
700
|
+
public async *executeStream<T>(
|
|
701
|
+
fn: (key: string, signal?: AbortSignal) => AsyncGenerator<T, any, unknown>,
|
|
702
|
+
options?: ExecuteOptions & { prompt?: string }
|
|
703
|
+
): AsyncGenerator<T, any, unknown> {
|
|
704
|
+
const maxRetries = options?.maxRetries ?? 0;
|
|
705
|
+
const timeoutMs = options?.timeoutMs;
|
|
706
|
+
const finishReason = options?.finishReason;
|
|
707
|
+
const prompt = options?.prompt;
|
|
708
|
+
const provider = options?.provider;
|
|
709
|
+
|
|
710
|
+
// 1. Semantic Cache Check
|
|
711
|
+
let currentPromptVector: number[] | null = null;
|
|
712
|
+
if (this.semanticCache && this.getEmbeddingFn && prompt && !this._isResolvingEmbedding) {
|
|
713
|
+
try {
|
|
714
|
+
this._isResolvingEmbedding = true;
|
|
715
|
+
currentPromptVector = await this.getEmbeddingFn(prompt);
|
|
716
|
+
const cachedResponse = this.semanticCache.get(currentPromptVector);
|
|
717
|
+
if (cachedResponse !== null) {
|
|
718
|
+
console.log(`[Semantic Cache HIT] Streaming cached response for prompt: "${prompt.slice(0, 30)}..."`);
|
|
719
|
+
this.emit('executeSuccess', 'CACHE_HIT_STREAM', 0);
|
|
720
|
+
// Replay full response as a single chunk (or iterate if it's an array)
|
|
721
|
+
if (Array.isArray(cachedResponse)) {
|
|
722
|
+
for (const chunk of cachedResponse) yield chunk as T;
|
|
723
|
+
} else {
|
|
724
|
+
yield cachedResponse as T;
|
|
725
|
+
}
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
} catch (e) {
|
|
729
|
+
console.warn('[Semantic Cache Check Failed] Proceeding to live stream', e);
|
|
730
|
+
} finally {
|
|
731
|
+
this._isResolvingEmbedding = false;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// 2. Bulkhead check
|
|
736
|
+
if (this.activeCalls >= this.maxConcurrency) {
|
|
737
|
+
this.emit('bulkheadRejected');
|
|
738
|
+
throw new BulkheadRejectionError();
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
this.activeCalls++;
|
|
742
|
+
const accumulatedChunks: T[] = [];
|
|
743
|
+
let lastError: any;
|
|
744
|
+
|
|
745
|
+
try {
|
|
746
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
747
|
+
const key = provider ? this.getKeyByProvider(provider) : this.getKey();
|
|
748
|
+
|
|
749
|
+
if (!key) {
|
|
750
|
+
if (this.fallbackFn) {
|
|
751
|
+
this.emit('fallback', 'all keys exhausted (stream)');
|
|
752
|
+
yield this.fallbackFn();
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
throw new AllKeysExhaustedError();
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
let iterator: AsyncGenerator<T, any, unknown>;
|
|
759
|
+
try {
|
|
760
|
+
// Start the generator
|
|
761
|
+
iterator = fn(key);
|
|
762
|
+
|
|
763
|
+
// PRIME THE ITERATOR:
|
|
764
|
+
// Try to get the first chunk. This forces the generator body to
|
|
765
|
+
// execute until the first 'yield' or until it throws.
|
|
766
|
+
const firstResult = await iterator.next();
|
|
767
|
+
|
|
768
|
+
// If we got here, the INITIAL connection/logic succeeded!
|
|
769
|
+
if (firstResult.done) return;
|
|
770
|
+
|
|
771
|
+
// Yield first chunk
|
|
772
|
+
yield firstResult.value;
|
|
773
|
+
if (this.semanticCache && prompt) accumulatedChunks.push(firstResult.value);
|
|
774
|
+
|
|
775
|
+
// Yield rest of chunks
|
|
776
|
+
for await (const chunk of iterator) {
|
|
777
|
+
yield chunk;
|
|
778
|
+
if (this.semanticCache && prompt) accumulatedChunks.push(chunk);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Success! Store in cache
|
|
782
|
+
if (this.semanticCache && prompt && currentPromptVector && accumulatedChunks.length > 0) {
|
|
783
|
+
this.semanticCache.set(prompt, currentPromptVector, accumulatedChunks);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
return; // Full success, exit retry loop
|
|
787
|
+
|
|
788
|
+
} catch (error: any) {
|
|
789
|
+
lastError = error;
|
|
790
|
+
const classification = this.classifyError(error, finishReason);
|
|
791
|
+
|
|
792
|
+
this.markFailed(key, classification);
|
|
793
|
+
this.emit('executeFailed', key, error);
|
|
794
|
+
|
|
795
|
+
// Note: If we already yielded the FIRST chunk, we CANNOT retry the connection
|
|
796
|
+
// because the user has already received data. Mid-stream failures propagate.
|
|
797
|
+
if (accumulatedChunks.length > 0) {
|
|
798
|
+
throw error;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (!classification.retryable || attempt >= maxRetries) {
|
|
802
|
+
if (this.fallbackFn && attempt >= maxRetries) {
|
|
803
|
+
this.emit('fallback', 'max retries exceeded (stream)');
|
|
804
|
+
yield this.fallbackFn();
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
throw error;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const delay = this.calculateBackoff(attempt);
|
|
811
|
+
this.emit('retry', key, attempt + 1, delay);
|
|
812
|
+
await this._sleep(delay);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
} finally {
|
|
816
|
+
this.activeCalls--;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
throw lastError;
|
|
820
|
+
}
|
|
821
|
+
|
|
667
822
|
private async _executeWithRetry<T>(
|
|
668
823
|
fn: (key: string, signal?: AbortSignal) => Promise<T>,
|
|
669
824
|
maxRetries: number,
|
|
@@ -751,7 +906,10 @@ export class ApiKeyManager extends EventEmitter {
|
|
|
751
906
|
}
|
|
752
907
|
|
|
753
908
|
private _sleep(ms: number): Promise<void> {
|
|
754
|
-
return new Promise(resolve =>
|
|
909
|
+
return new Promise(resolve => {
|
|
910
|
+
const timer = setTimeout(resolve, ms);
|
|
911
|
+
if (timer.unref) timer.unref();
|
|
912
|
+
});
|
|
755
913
|
}
|
|
756
914
|
|
|
757
915
|
// ─── Health Checks ───────────────────────────────────────────────────────
|
|
@@ -770,6 +928,7 @@ export class ApiKeyManager extends EventEmitter {
|
|
|
770
928
|
public startHealthChecks(intervalMs: number = 60_000) {
|
|
771
929
|
this.stopHealthChecks(); // Clear any existing interval
|
|
772
930
|
this.healthCheckInterval = setInterval(() => this._runHealthChecks(), intervalMs);
|
|
931
|
+
if (this.healthCheckInterval.unref) this.healthCheckInterval.unref();
|
|
773
932
|
}
|
|
774
933
|
|
|
775
934
|
/**
|