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

Packages

Package details

extension

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.8.1
Published
Sep 4, 2026
Downloads
1,604/mo · 436/wk
Author
kslamph
License
MIT
Types
extension
Size
156 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:

  1. 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.
  2. 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.
  3. 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 4 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
glm-5.3-flash 1M / 131K text+image low · high · max (always thinks, no off)

Why null must be explicit: pi's getSupportedThinkingLevels treats mapped === null as unsupported and hides that level, but omitting it is treated as supported and the level name is sent to the API verbatim; xhigh / max additionally 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: /multikeyAdd 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:

  1. It fetches <baseUrl>/models (and <baseUrl>/v1/models as a fallback) with Authorization: Bearer; on 401/403 it retries with x-api-key.
  2. /models is 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.
  3. 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).
  4. 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.json and 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 custom streamSimple.
  • Each request leases one key from the pool (options.apiKey overrides), 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.
  • 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