@goodandready/dsh-key-rotation 0.7.22 → 0.7.23
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/lib/index.js +42 -11
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -92,8 +92,18 @@ const latencyHistogram = new LatencyHistogram();
|
|
|
92
92
|
const quotaStore = new QuotaStore();
|
|
93
93
|
const agentBudget = new AgentBudget();
|
|
94
94
|
const regionMap = new RegionMap();
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
// IncidentReporter: lazily built when Config provides incidentGitHubToken + incidentGitHubBaseUrl.
|
|
96
|
+
// ponytail: never bake the token into source; repo is hardcoded (this plugin's home repo) but token is per-deploy.
|
|
97
|
+
let incidentReporter = null;
|
|
98
|
+
function ensureIncidentReporter() {
|
|
99
|
+
if (incidentReporter) return incidentReporter;
|
|
100
|
+
const cfg = getConfig();
|
|
101
|
+
const token = cfg ? cfg.incidentGitHubToken : '';
|
|
102
|
+
const baseUrl = cfg ? cfg.incidentGitHubBaseUrl : '';
|
|
103
|
+
if (!token || !baseUrl) return null;
|
|
104
|
+
incidentReporter = new IncidentReporter({ token, baseUrl, repo: 'goodandready/dsh-key-rotation', fetchImpl: globalThis.fetch });
|
|
105
|
+
return incidentReporter;
|
|
106
|
+
}
|
|
97
107
|
const shadowRouter = new ShadowRouter({ primary: '', secondary: '', percent: 0 });let sandboxRunner = null;
|
|
98
108
|
const webhookSender = new WebhookSender({ fetchImpl: globalThis.fetch });
|
|
99
109
|
function ensureSandboxRunner(ctx) {
|
|
@@ -145,6 +155,9 @@ export const Config = Schema.object({
|
|
|
145
155
|
selfHealIdleMs: Schema.number().default(3600000),
|
|
146
156
|
latencyEnabled: Schema.boolean().default(true),
|
|
147
157
|
latencyWindow: Schema.number().default(200),
|
|
158
|
+
incidentGitHubToken: Schema.string().default(''),
|
|
159
|
+
incidentGitHubBaseUrl: Schema.string().default(''),
|
|
160
|
+
incidentThreshold: Schema.number().default(5),
|
|
148
161
|
rateLimitThreshold: Schema.number().default(0.1),
|
|
149
162
|
providers: Schema.array(Schema.object({
|
|
150
163
|
provider: Schema.string().required(),
|
|
@@ -548,7 +561,7 @@ export function apply(ctx, config = {}) {
|
|
|
548
561
|
for (const key of [...poolState.keys()]) {
|
|
549
562
|
if (![...poolByRef.values()].some((p) => p.base === key)) poolState.delete(key);
|
|
550
563
|
}
|
|
551
|
-
return { switchCodes, cooldownMs, maxCooldownMs, notifyWebhook, notifyThreshold, backupDir, backupIntervalMs, backupKeep, rotationScheduleDays, rateLimitThreshold, poolByRef, providerToPool, modelPoolByProvider, cloneIds };
|
|
564
|
+
return { switchCodes, cooldownMs, maxCooldownMs, notifyWebhook, notifyThreshold, incidentThreshold, backupDir, backupIntervalMs, backupKeep, rotationScheduleDays, rateLimitThreshold, poolByRef, providerToPool, modelPoolByProvider, cloneIds };
|
|
552
565
|
}
|
|
553
566
|
|
|
554
567
|
// ── patch credentials.resolve: pool refs resolve to the next healthy key ──
|
|
@@ -756,14 +769,8 @@ export function apply(ctx, config = {}) {
|
|
|
756
769
|
pool.state.lastExhaustionAt = Date.now();
|
|
757
770
|
pool.state.exhaustionCount = (pool.state.exhaustionCount ?? 0) + 1;
|
|
758
771
|
console.warn(`[dsh-key-rotation] ${options.provider}: pool exhausted — all ${pool.refs.length} keys cooling`);
|
|
759
|
-
// notify
|
|
760
|
-
|
|
761
|
-
const { notifyWebhook, notifyThreshold } = buildRuntime();
|
|
762
|
-
if (notifyWebhook && pool.state.exhaustionCount >= notifyThreshold) {
|
|
763
|
-
const payload = JSON.stringify({ provider: options.provider, exhaustionCount: pool.state.exhaustionCount, at: pool.state.lastExhaustionAt, keys: pool.refs });
|
|
764
|
-
fetch(notifyWebhook, { method: 'POST', headers: { 'content-type': 'application/json' }, body: payload }).catch(()=>{});
|
|
765
|
-
}
|
|
766
|
-
} catch {}
|
|
772
|
+
// notify via extracted helper (see notifyExhaustion above)
|
|
773
|
+
notifyExhaustion(buildRuntime(), pool, { provider: options.provider });
|
|
767
774
|
|
|
768
775
|
yield lastFailure ?? finishError('TRANSPORT', 'dsh-key-rotation: all keys failed');
|
|
769
776
|
})();
|
|
@@ -1200,3 +1207,27 @@ export function apply(ctx, config = {}) {
|
|
|
1200
1207
|
});
|
|
1201
1208
|
});
|
|
1202
1209
|
}
|
|
1210
|
+
|
|
1211
|
+
// Notify on exhaustion: webhook + (optional) GitHub incident.
|
|
1212
|
+
// Extracted at module scope for testability. No I/O outside the injected hooks.
|
|
1213
|
+
// ponytail: thresholds and URLs are runtime-resolved per call, so changing Config is reflected immediately.
|
|
1214
|
+
export function notifyExhaustion(runtime, pool, options, hooks = { webhookSender, ensureIncidentReporter }) {
|
|
1215
|
+
if (!runtime || !pool) return;
|
|
1216
|
+
const count = pool.state ? (pool.state.exhaustionCount ?? 0) : 0;
|
|
1217
|
+
if (count <= 0) return;
|
|
1218
|
+
try {
|
|
1219
|
+
if (runtime.notifyWebhook && count >= (runtime.notifyThreshold ?? 0)) {
|
|
1220
|
+
hooks.webhookSender.send(runtime.notifyWebhook, {
|
|
1221
|
+
provider: options.provider,
|
|
1222
|
+
exhaustionCount: count,
|
|
1223
|
+
at: pool.state.lastExhaustionAt,
|
|
1224
|
+
keys: pool.refs,
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
if (runtime.incidentThreshold && count >= runtime.incidentThreshold) {
|
|
1228
|
+
const reporter = hooks.ensureIncidentReporter();
|
|
1229
|
+
if (reporter) reporter.open(options.provider, pool.state.lastExhaustionAt);
|
|
1230
|
+
}
|
|
1231
|
+
} catch (_) { /* ponytail: never crash rotate() */ }
|
|
1232
|
+
}
|
|
1233
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-key-rotation",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.23",
|
|
4
4
|
"description": "Per-provider API key rotation for DeepSeek Harness: a key pool per provider, auto-created clone routes, and switching to the next key on quota/rate-limit errors. Includes a Settings section (Key Rotation) to edit the key pools, cooldown and switch codes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|