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:
- a request fails with a rotation-eligible code (
QUOTA,AUTHby default), - and nothing visible has been rendered yet,
- 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 thellm/streamwaterfall. 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
maxRotationsPerRequestand 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