@goodandready/dsh-key-rotation 0.5.4 → 0.6.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.
Files changed (2) hide show
  1. package/lib/index.js +27 -1
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -80,7 +80,7 @@ export const Config = Schema.object({
80
80
  providers: Schema.array(Schema.object({
81
81
  provider: Schema.string().required(),
82
82
  keys: Schema.array(Schema.string()).default([]),
83
- cooldownMs: Schema.number().optional(),
83
+ cooldownMs: Schema.number(),
84
84
  })).default([...DEFAULT_PROVIDERS]),
85
85
  });
86
86
 
@@ -617,6 +617,32 @@ export function apply(ctx, config = {}) {
617
617
  return rotate(options, pool);
618
618
  });
619
619
 
620
+ // Safety net for non-stream requests (agent/request-error waterfall).
621
+ // llm/stream covers streaming calls; sync calls (embeddings, batch) go
622
+ // through agent/request and surface errors here. If the error is
623
+ // switchable, mark the key and ask the agent loop to retry.
624
+ ctx.on('agent/request-error', async (payload, next) => {
625
+ const provider = payload?.provider ?? payload?.failure?.provider ?? '';
626
+ if (!provider) return next();
627
+ const { providerToPool, switchCodes } = buildRuntime();
628
+ const pool = providerToPool.get(provider);
629
+ if (!pool) return next();
630
+ const code = String(payload?.failure?.code ?? payload?.code ?? '');
631
+ const message = String(payload?.failure?.message ?? payload?.message ?? '');
632
+ const switchable = switchCodes.has(code) || SWITCHABLE_MESSAGE_PATTERN.test(message);
633
+ if (!switchable) return next();
634
+ const ref = pool.state.lastUsed;
635
+ if (ref) {
636
+ const backoff = recordFailure(pool, ref, Date.now(), pool.cooldownMs ?? 60000);
637
+ pushEvent(pool, ref, code || 'UNKNOWN', backoff);
638
+ pool.state.switches = (pool.state.switches ?? 0) + 1;
639
+ pool.state.lastReason = code || 'UNKNOWN';
640
+ pool.state.lastSwitchAt = Date.now();
641
+ console.warn(`[dsh-key-rotation] ${provider}: key ${String(ref)} failed via agent/request-error (${String(code)} ${String(message).slice(0, 80)}) — retry`);
642
+ }
643
+ return { kind: 'retry' };
644
+ });
645
+
620
646
  ctx.inject(['settings'], (sctx) => {
621
647
  const scope = sctx.settings.register(NS, Config, { base: config });
622
648
  getConfig = () => scope.get() ?? config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-key-rotation",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
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",