Skip to content

cordis-plugin-credential-rotate

Verified

@argszero/cordis-plugin-credential-rotate · v0.1.1 · MIT

Per-route API-key rotation + retry for dsh. When a provider request ends with a retryable failure (default: QUOTA, AUTH — the codes where waiting cannot help), this plugin (1) repoints the provider's credential reference (e.g. llm-deepseek's apiKeyEnv) to

Install

dsh plugin add @argszero/cordis-plugin-credential-rotate

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Published to npm without a public repository. Inspect the package contents before installing.

Tags

Readme

@argszero/cordis-plugin-credential-rotate

Per-route API-key rotation + retry for dsh — when one credential is exhausted, the next one takes the same request. No proxy, no baseURL surgery.

Grounded in discussion #6344 (“one provider route, several credentials, rotate automatically”).

What it does

A route carries one credential reference (apiKeyEnv for llm-deepseek, profile.apiKeyEnv for llm-pi-ai). When that key hits its quota or is revoked, the whole route goes dark even though the operator holds several keys.

This plugin keeps the route alive:

  1. a request fails with a rotation-eligible code (QUOTA, AUTH by default),
  2. and nothing visible has been rendered yet,
  3. so it repoints the credential reference to the next name in your pool — via settings.update(namespace, patch) — and re-runs the same request through the llm/stream waterfall. The adapter re-resolves its credential once per stream call, so the retry simply carries the next key.

When the pool is spent, the request fails visibly with code CREDENTIAL_ROTATE_EXHAUSTED (the message names every reference tried and repeats the provider's own failure text).

Install

dsh plugin --profile web add @argszero/cordis-plugin-credential-rotate

Then put the pool in the environment (values, not references — the plugin never sees them) and point the plugin at the pod that holds the reference:

DEEPSEEK_KEY_A=sk-... DEEPSEEK_KEY_B=sk-... dsh web
# profile layer
- set:
    - id: credential-rotate
      config:
        pods: ['llm-deepseek:apiKeyEnv']
        refs: [DEEPSEEK_KEY_A, DEEPSEEK_KEY_B]

refs are environment-variable names. A reference that resolves to nothing is skipped with a warning — it is never installed as an empty key.

option default meaning
pods [] <settingsNamespace>:<field> addresses that hold the credential reference
refs (required) the pool, tried in order
rotateCodes [QUOTA, AUTH] failure codes that justify a rotation
maxRotationsPerRequest 3 rotations allowed inside one request
maxPodWritesPerRotation 200 write bound for one rotation move

Why not rotate on RATE_LIMIT too?

RATE_LIMIT is transient — the same key usually works again after the provider's own Retry-After, which the in-tree llm-retry policy already honours. QUOTA and AUTH are the codes where waiting cannot help. Rotating on RATE_LIMIT would burn every key on one throttled minute; if you disagree, add it to rotateCodes explicitly.

What it deliberately does not do

  • No response buffering. Chunks are forwarded the instant they arrive. The only withheld data is the head of an attempt (chunks that arrived before anything visible), which must not be replayed if that attempt is discarded. That buffer closes permanently at the first visible delta.
  • No rotation after visible output. A mid-stream failure is passed through untouched: re-running a partially-rendered answer would duplicate it, and that policy belongs to the harness (assistant/attempt), not to a plugin.
  • No unbounded retry. Rotations are bounded by maxRotationsPerRequest and the pool length; the failure always surfaces.
  • No durability. The pool cursor is per-request, so a restart re-starts the pool from the top while the installed reference survives in settings. Rotation is a latency optimization, not a quota accountant.

Compatibility

dsh 0.1.2-rc.1, 0.1.3-alpha.2, 0.1.5, and 0.1.6 (>=0.1.2-rc.1 <0.1.3 || >=0.1.3-alpha.2 <0.1.4 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-alpha.1 <0.2.0). The wrapped seam (llm/stream) and the per-stream-call credential resolution both predate this range; every line above has had the full suite run against it.

License

MIT