pi-multikey
One pi provider backed by many API keys: automatic 429 rotation, per-request key leases for concurrent subagents, and a /multikey management TUI
Package details
Install pi-multikey from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-multikey- Package
pi-multikey- Version
1.9.0- Published
- Sep 12, 2026
- Downloads
- 1,604/mo · 436/wk
- Author
- kslamph
- License
- MIT
- Types
- extension
- Size
- 155.5 KB
- Dependencies
- 0 dependencies · 3 peers
Pi manifest JSON
{
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-multikey
A pi extension that bundles multiple API keys into a single key pool, exposing only one provider to pi.
It solves three pain points:
- No more copying your provider config per key — models (contextWindow / modalities / thinkingLevelMap / compat) are configured once; swapping or adding keys never touches the model definitions.
- Automatic 429 key rotation — on a failed request it immediately retries with the next key, and the failed key goes into cooldown (honoring
retry-after). No manual provider switching. - Concurrent subagents share keys automatically — every in-flight request holds a key lease, picked by "fewest in use + least recently used", so when the main agent spawns multiple subagents they naturally land on different keys.
Installation
# Option 1: git (recommended, no npm account needed)
pi install git:github.com/kslamph/multikey@v1.2.0
# Option 2: npm
pi install npm:pi-multikey
# Option 3: local directory
pi install /path/to/multikey
Quick start (B.AI preset)
/multikey → Add pool… → Preset: B.AI → paste keys one per line (blank line to finish)
After picking the preset, the endpoint, compat, and all 3 model definitions are wired up automatically. Models are available directly as bai/<model-id>, e.g. bai/hy3.
Presets
Built-in presets decouple "model settings" from "keys". The data comes from b.ai model cards and DeepSeek / Tencent / Xiaomi official docs, with each thinking level probed empirically; unsupported levels are written as null so the UI hides them.
| Model | ctx / max-out | Modalities | Supported thinking levels |
|---|---|---|---|
| hy3 | 256K / 128K | text | off · low · high |
| mimo-v2.5 | 1M / 128K | text+image | off · high (official: low/medium/high behave identically) |
| qwen3.8-flash | 1M / 131K | text+image | off · low · medium · xhigh |
Why
nullmust be explicit: pi'sgetSupportedThinkingLevelstreatsmapped === nullas unsupported and hides that level, but omitting it is treated as supported and the level name is sent to the API verbatim;xhigh/maxadditionally require an explicit non-null value to be usable.
Configuration
~/.pi/agent/multikey.json. On first run it auto-discovers mergeable pools from ~/.pi/agent/models.json (≥2 providers sharing a baseUrl = you copying the provider per key), and also picks up providers pointing at api.b.ai; if nothing is found it generates an empty config.
{
"pools": [
{
"id": "bai", // provider id in pi → bai/hy3
"name": "B.AI (Key Pool)",
"baseUrl": "https://api.b.ai/v1",
"api": "openai-completions",
"auth": "bearer", // optional: "bearer" (default) or "api-key" (x-api-key header)
"compat": { ... }, // provider-level defaults, merged into every model
"cooldownMs": 20000, // 429 cooldown
"invalidKeyCooldownMs": 600000, // 401/403 cooldown
"keys": [
{ "key": "sk-...", "label": "key-1", "enabled": true },
{ "key": "sk-...", "label": "key-2", "enabled": true },
{ "key": "<access token>", "label": "cline-account", "enabled": true,
"credential": { "kind": "cline-oauth", "refreshToken": "...", "accessToken": "...", "expiresAt": 1735689600000 } }
],
"models": [ "…preset or hand-configured model definitions…" ]
}
]
}
To add nvidia / other providers later: /multikey → Add pool… (Custom), or edit the JSON directly and Reload config from disk.
Adding a custom pool (no questions about API types)
The custom wizard only asks for the essentials — provider id, base URL, key(s). It then probes the endpoint:
- It fetches
<baseUrl>/models(and<baseUrl>/v1/modelsas a fallback) withAuthorization: Bearer; on 401/403 it retries withx-api-key. /modelsis public on some gateways, so it also sends a tiny 1-token chat request to verify the key. If both header styles are rejected there but a dummy key passes, the endpoint simply doesn't check keys (open endpoint) and the pool is saved with the default Bearer auth.- You multi-select the models to add straight from the server's list. Context window / input modes / max output found in the model metadata are adopted; everything else gets safe defaults (128k context, text input, 16k max output, zero cost).
- Optionally tune the common params (context size, input modes, max output) per model — or skip and edit them later via the Models menu. Anything advanced (thinking maps, compat, cost) you edit in
multikey.jsonand hit Reload config from disk.
The detected header style is stored as "auth": "api-key" only when the endpoint proved to want x-api-key; the default is Bearer. The pool is saved only after this completes, so a cancelled wizard never leaves a half-configured provider behind.
Management UI
/multikey
├─ Status live status: in-flight / cooldown / 429 count per key
├─ Manage pools… pools with an unknown api type are marked ⚠ broken; incomplete pools (incomplete)
│ ├─ Keys… add keys one per line; delete / edit / disable
│ ├─ Models… fetch from /models (multi-select) or add manually; edit contextWindow,
│ │ maxTokens, modalities, reasoning, thinkingLevelMap, compat, cost
│ ├─ Endpoint & settings… baseUrl, api type, auth style, cooldown durations, headers
│ └─ Delete pool
├─ Add pool…
│ ├─ Preset: B.AI all model settings preloaded; paste keys (verified by a probe) and you're done
│ ├─ Preset: OpenCode Zen free-tier models preloaded (8 models); paste keys and you're done
│ └─ Custom… id + base URL + keys, then auto-probe, model multi-select, safe defaults
└─ Reload config from disk
Changes take effect immediately (the provider is re-registered) — no restart needed.
How it works
- The extension registers a provider via
pi.registerProvider()with a customstreamSimple. - Each request leases one key from the pool (
options.apiKeyoverrides), and once the HTTP response headers arrive:- 429 → that key is cooled down (default 20s, honoring
retry-after) and the request immediately retries with the next key (no duplicated output); - Cline daily free limit (429 +
"free limit reached on model"in the body) → that key cools down until the server-reported reset time (hours, not seconds) and the request retries with the next key; - 401/403 → that key gets a long cooldown (default 10 minutes) and the request retries with the next key; for OAuth-backed keys (Cline), a 401 first forces one token refresh + same-key retry before any cooldown;
- other errors → handed back to pi's own retry mechanism.
- 429 → that key is cooled down (default 20s, honoring
- OAuth-backed keys (Cline accounts) resolve a fresh access token from their stored refresh token before every request (single-flight per account, so concurrent subagents share one refresh), and every rotation of the refresh token is persisted back to
multikey.json. - Only when every key is exhausted does it surface the 429 upward, letting pi's own backoff retry as a safety net (by then the earliest cooldown has usually expired).
Security note
Keys are stored in plaintext at ~/.pi/agent/multikey.json; recommended:
chmod 600 ~/.pi/agent/multikey.json