dsh-keys-manager
btspoony/dsh-keys-manager
A DSH plugin that turns one provider API key into a managed pool + more built-in LLM Providers
설치
dsh plugin --profile web add github:btspoony/dsh-keys-managerREADME
DSH Keys Manager
English | 简体中文
A DeepSeek Harness plugin that turns one provider API key into a managed pool. It extends the Models page rather than replacing it: providers, models, and the model picker stay where they are; only the API-key source moves behind the manager.
What it does
Pool and routing
- Several keys per provider, each with an alias and a health state.
- One key pinned to a session, round-robin for new sessions; a failing request is retried on its key, then moved to another key.
- Exponential cooldown recovery, capped at seven days.
Accounts and sign-in
- Seven pi-ai OAuth providers can join the pool as an account instead of a pasted key.
- Two more sign in through the plugin's own bundled clients: Kilo and Z.AI Coding Plan.
- Seven companion transport routes for providers the vendored pi-ai cannot speak: Cursor, Devin, the two Google routes, Factory Droid, and the two GitLab Duo routes.
Interface
- A search-driven Keys Manager page on the plugin's own detail page.
- A "managed by Keys Manager" badge inside the existing Models provider cards.
- Extra built-in providers installed as ordinary
llm-pi-airoutes — 35 of them, listed under Provider coverage.
Quick start
Install
In the Desktop or Web app, open Plugins → Add plugin, enter dsh-keys-manager,
click Install, then Enable now. This installs the same published package
as the CLI path, already carrying its built bundle.
dsh plugin --profile web add dsh-keys-manager
One install yields both bundles, dsh-keys-manager and llm-omp-transports;
enable both — the manager owns credentials and routing, the transport the seven
routes' wire protocols. Refresh the Web page afterwards for the browser half.
Install from a local checkout
git clone https://github.com/btspoony/dsh-keys-manager
cd dsh-keys-manager
pnpm i
dsh plugin --profile web link .
add installs the published tarball with its built lib/ and client bundle;
link . builds them via prepare during pnpm i, as does prepack when packing.
Use it
Take over a provider and add keys
- Search for a provider. Open Plugins → Keys Manager → View (
Plugins → 密钥管理 → 查看in the Chinese locale); the manager renders on the plugin's detail page. Existing DSH providers appear first; the plugin also ships the curated extra catalog described under Provider coverage. - Adopt a provider. For an existing provider the manager imports the currently resolved credential, writes a broker reference into the provider profile, and leaves the original key in the credential store so release can restore it.
- Add more keys with an alias. Every key is stored in the Harness credential store under its own reference; the manager state document never contains a key literal.
- Manage a key from the three-dot menu: rename, pause/resume, delete. A key row shows status, last use, and the recovery time while cooling.
- Tune retry count (1–3), base cooldown, and recovery grace in the settings card.
Accounts and sign-in
Providers that ship a native OAuth sign-in offer two ways to join the pool:
Add key stores a static API key, while Add account runs the provider's
OAuth sign-in flow and stores the result as an account row in the same pool.
Nine routes join as an account: seven through pi-ai's own flows, kilo and
zai through the plugin's bundled clients (device code, pasted zcode://
redirect), which mint a static credential renewed only by Sign in again.
| Route group | How it joins | Refresh / renew |
|---|---|---|
| 9 OAuth accounts | Add account — seven pi-ai flows, two plugin clients | provider-managed for the seven; Sign in again for the two static credentials |
| 7 transport routes | Transport accounts — one grant per route | see Transport routes |
API keys form the selection ring — sticky sessions, round-robin, retry failover and cooldowns operate across the provider's keys — while one projected OAuth grant serves its provider, and an account row's counters reflect only that account's own successes and failures. Per-account ranking is future work. When the projected grant fails, the account cools down and the pool falls back to its keys, or stops serving on an OAuth-only route until a manual re-login.
muse-code needs no transport or account row of its own: the vendored pi-ai
meta route (openai-responses on https://api.meta.ai/v1) serves it, the
agreement check was static, and the first live call is a manual smoke check per
Known limits.
Transport routes
Providers the vendored pi-ai cannot speak are served by the companion
dsh-llm-omp-transports component:
- One grant per route, at
llm-omp-transports/<route>— no key pool and no per-account rotation. Sign-in happens in the Transport accounts section of the Keys Manager page. - Models are generated from the pinned
@oh-my-pi/pi-catalog@18.12.3table when the transport is built, and the clients are unofficial: read the transports package docs and its known limits before relying on them.
| Route | Sign-in | Refresh | Model rows |
|---|---|---|---|
cursor | browser page + poll | on the credential seam | 123 |
devin | paste the landed callback URL | none — sign in again | 2 |
google-gemini-cli | loopback 8085 + paste fallback | on the credential seam | 7 |
google-antigravity | loopback 51121 + paste fallback | on the credential seam | 22 |
factory-droid | WorkOS device code (URL + user code) | one refresh in a 60 s window, keeps orgId/region | 62 |
gitlab-duo | PKCE loopback localhost:8080/callback, no port fallback, paste fallback | one refresh in a 60 s window | 16 |
gitlab-duo-agent | paste the vscode:// URL | one refresh, echoes the vscode redirect | 1 |
Per-route notes
- Cursor — streams text and reasoning and hands tool calls to DSH's tool loop; images are refused with a typed error. The client presents Cursor's vendor-internal OAuth identity.
- Devin — no refresh: a static bearer whose expiry is derived from the token
(JWT
expminus five minutes, else one year), and without a readable expiry the row showsactivewith a blank countdown column. - Google routes — both share one generated
lib/omp/google.mjsartifact yet keep separate grants, account rows and catalogs. The grant is{access, refresh, expires, projectId, email?}; sign-in resolves the Cloud project the route bills, and alegacy-tieror paid account whose project cannot be discovered failsAUTH_FAILEDuntilGOOGLE_CLOUD_PROJECT(orGOOGLE_CLOUD_PROJECT_ID) is set. - Factory Droid — four wires behind one proxy, chosen by model id inside the
artifact; rows carry no base URL and the account's residency
(
orgId/region) survives refresh. - GitLab Duo pair —
gitlab-duo's direct-access token cache is keyed on the GitLab token, so a rotated token misses it;gitlab-duo-agentre-seeds a workflow per request and replays the transcript rather than resuming a socket.
Selection and failure policy
| Mechanism | Behaviour |
|---|---|
| Sticky session | The first request of a session selects a key and binds it for that session/provider pair. Later requests reuse it. |
| Round robin | A new session advances the provider cursor and takes the next healthy key. |
| Retry | A failing request is retried on the same key retryCount times. Request-shaped failures such as CONTEXT_WINDOW_EXCEEDED and cancellation never cool a key. |
| Switch | After the retry budget is spent, the failing key is marked cooling, and the session is rebound to another healthy key. When no alternate key exists, the manager delegates to the composition's normal retry/fallback policy. |
| Cooldown | The first failure cools a key for baseCooldownMs. A failure while cooling, or within recoveryGraceMs after recovery, doubles the next cooldown; a long healthy gap resets the escalation. The hard cap is seven days. |
| Recovery | The key is automatically selectable again when its cooldown expires; no manual reset is required. |
Configuration & data
Manager metadata lives at:
$DSH_HOME/keys-manager/state.json
with owner-only permissions. It contains aliases, credential references,
health counters, session bindings, and provider adoption facts. API key
literals live only in the Harness credential store; an account row follows the
same rule behind its grantRef, with only non-sensitive metadata (email,
account id, expiry) in the state. The file is written atomically through a
temporary file and a rename.
Each managed provider profile points its apiKeyEnv at a broker reference
such as KEYSMGR_ZAI_CODING_CN_3FA2C1. The Host credential layer answers that
reference per request with the session's selected key. The Models page sees
the profile as configured; writing directly to the broker reference is
refused with a pointer to the manager.
OAuth2 extension point
The state shape already distinguishes kind: "api-key" from kind: "oauth"
and keeps a nullable oauth object beside the credential reference. A future
account flow can add records under keys-manager/<provider>/<key> without
rewriting existing rows. Providers whose only authentication is OAuth are
reported as unsupported in this version rather than being treated as API-key
providers.
Provider coverage
Four groups, one pool:
- DSH built-in — routes from the shipped DSH pi-ai catalog. The set follows your installed DSH version, so the Models page is the authoritative list; four of them are outside single-key pooling (below).
- Plugin extra built-in — 35 routes this plugin adds as ordinary
llm-pi-airoutes. - OAuth accounts — 9 routes that join as an account instead of a pasted key:
seven use pi-ai's own flows,
kiloandzaiare signed in by the plugin's bundled clients. - Transport routes — 7 routes the vendored pi-ai cannot speak, served by
dsh-llm-omp-transports.
Supported — every plugin extra, OAuth account and transport route
Plugin extra built-in — 35 — from oh-my-pi's provider catalog (MIT):
| Provider | Route id | API key env |
|---|---|---|
| DeepInfra | deepinfra | DEEPINFRA_API_KEY |
| Novita | novita | NOVITA_API_KEY |
| StepFun | stepfun | STEPFUN_API_KEY |
| SambaNova | sambanova | SAMBANOVA_API_KEY |
| Qianfan | qianfan | QIANFAN_API_KEY |
| GMI Cloud | gmi-cloud | GMI_API_KEY |
| Synthetic | synthetic | SYNTHETIC_API_KEY |
| ZenMux | zenmux | ZENMUX_API_KEY |
| Kilo Gateway | kilo | KILO_API_KEY |
| NanoGPT | nanogpt | NANO_GPT_API_KEY |
| Venice AI | venice | VENICE_API_KEY |
| AIML API | aimlapi | AIMLAPI_API_KEY |
| AI& | aiand | AIAND_API_KEY |
| Helmcode | helmcode | HELMCODE_API_KEY |
| Alibaba Coding Plan | alibaba-coding-plan | ALIBABA_CODING_PLAN_API_KEY |
| MiniMax Coding (Global) | minimax-code | MINIMAX_CODE_API_KEY |
| MiniMax Coding (CN) | minimax-code-cn | MINIMAX_CODE_CN_API_KEY |
| Umans AI Coding Plan | umans | UMANS_AI_CODING_PLAN_API_KEY |
| Sakana AI | sakana | SAKANA_API_KEY |
| CoralBricks | coralbricks | CORAL_API_KEY |
| Wafer Serverless | wafer-serverless | WAFER_SERVERLESS_API_KEY |
| Alibaba Token Plan | alibaba-token-plan | ALIBABA_TOKEN_PLAN_API_KEY |
| Alibaba Token Plan (CN) | alibaba-token-plan-cn | BAILIAN_TOKEN_PLAN_API_KEY |
| Charm Hyper | charm-hyper | CHARM_HYPER_API_KEY, HYPER_API_KEY |
| LiteLLM Proxy | litellm | LITELLM_API_KEY |
| SiliconFlow | siliconflow | SILICONFLOW_API_KEY |
| SiliconFlow (CN) | siliconflow-cn | SILICONFLOW_CN_API_KEY |
| Zhipu Coding Plan | zhipu-coding-plan | ZHIPU_API_KEY |
| Abliteration | abliteration | ABLITERATION_API_KEY, ABLIT_KEY |
| ClinePass | cline-pass | CLINE_API_KEY |
| Command Code | commandcode | COMMAND_CODE_API_KEY, COMMANDCODE_API_KEY |
| CoreWeave Serverless Inference | coreweave | COREWEAVE_API_KEY |
| Fire Pass | firepass | FIREPASS_API_KEY |
| Ollama Cloud | ollama-cloud | OLLAMA_CLOUD_API_KEY |
| Yolo Auto | yolo-auto | YOLO_AUTO_API_KEY |
Where two variables are listed, either is accepted; the first is the primary.
CoreWeave Serverless Inference also needs COREWEAVE_PROJECT=<team>/<project> in
the DSH process environment before it is added.
OAuth accounts — 9
| Provider | Route | Sign-in client |
|---|---|---|
| Anthropic | anthropic | pi-ai |
| GitHub Copilot | github-copilot | pi-ai |
| Kimi Coding | kimi-coding | pi-ai |
| OpenAI Codex | openai-codex | pi-ai |
| OpenRouter | openrouter | pi-ai |
| Radius | radius | pi-ai |
| xAI | xai | pi-ai |
| Kilo | kilo | plugin bundled client — device code + poll |
| Z.AI Coding Plan | zai | plugin bundled client — pasted zcode:// redirect |
The two plugin-signed credentials are static, tagged signin:kilo /
signin:zai-coding-plan, and nothing is written to llm-pi-ai/<provider>; the
seven pi-ai rows are the provider's own grants. kilo is listed twice on
purpose: the same route takes an API key as an extra built-in and an account
through its bundled client.
Transport routes — 7 — cursor, devin, google-gemini-cli,
google-antigravity, factory-droid, gitlab-duo, gitlab-duo-agent; sign-in,
refresh and model detail live in Transport routes.
Not pooled — 4 DSH built-ins
| Provider | Why |
|---|---|
amazon-bedrock | AWS credential chain (profile, IAM keys, ECS/IRSA/EC2), not one API key |
cloudflare-ai-gateway | API key plus a Cloudflare account ID and gateway ID |
cloudflare-workers-ai | API key plus a Cloudflare account ID |
openai-codex | ChatGPT OAuth sign-in only; no API-key credential — usable as an OAuth account instead of a key |
Every other DSH catalog route exposes an API-key credential; a route you declare
yourself in the profile (自定义 / Custom) is pooled like any built-in.
Documentation
| Doc | Content |
|---|---|
| Development | build and dev workflow, repository layout |
| Release | CI checks and the npm release flow |
| Known limits | current design boundaries, including the seven transport routes |
| Transports package | Transport routes: support status, risks, build/test, release |
Chinese: README.zh-CN.md · 开发 · 发布 · 已知限制.
License
Released under the MIT License.