@hallaxius/opencode-keypool
@hallaxius/opencode-keypool
OpenCode plugin for automatic API key rotation with health-aware weighted selection, cooldown, and session pinning.
Keypool lets you configure multiple API keys for the same provider in OpenCode and automatically rotates them across requests. If a key fails (rate limit, server error), it goes into a temporary cooldown with exponential backoff instead of permanently breaking your workflow.
Features
- Multiple API keys per provider — add as many as you want
- Health-aware weighted selection — healthier keys get used more often
- Cooldown with exponential backoff — failed keys rest and retry automatically
- Session pinning — same key used within a session for consistency
- Rate limit prediction — tracks usage to avoid exhausted keys
- Graceful degradation — never interrupts your workflow, even if all keys cool down
- Encrypted storage — AES-256-GCM when configured
- Custom tools —
keypool_status,keypool_add,keypool_resetvia thetoolhook - Provider abstraction — NVIDIA supported now, ready for OpenAI/Anthropic
Installation
bun add -g @hallaxius/opencode-keypoolOr install locally in your project:
bun add @hallaxius/opencode-keypoolThe postinstall script automatically adds the plugin to your OpenCode config.
Quick Start
Connect your first key:
Run
/connectin the OpenCode TUI, select NVIDIA NIM, and enter your API key. Or ask the agent:add key nvapi-YOURKEY... named "Primary"Add more keys (optional):
Simply ask the agent to add more keys — they're validated and added automatically:
add another key nvapi-OTHERKEY... named "Secondary"Or run
/connectagain.Check status anytime:
show key statusStart using OpenCode:
Keypool automatically rotates keys on every request. No further setup needed.
Configuration
Configure via opencode.json:
{
"plugin": [
["@hallaxius/opencode-keypool", {
"strategy": "weighted-random",
"maxFailures": 3,
"cooldownBaseMs": 30000,
"cooldownMaxMs": 480000,
"passphrase": "${KEYPOOL_PASSPHRASE}",
"providers": ["nvidia"]
}]
]
}Options
| Option | Type | Default | Description |
|---|---|---|---|
strategy |
string |
"weighted-random" |
Rotation strategy: round-robin, weighted-random, least-failures |
maxFailures |
number |
3 |
Consecutive failures before cooldown |
cooldownBaseMs |
number |
30000 |
Initial cooldown duration (30s) |
cooldownMaxMs |
number |
480000 |
Maximum cooldown duration (8min) |
passphrase |
string |
— | Passphrase for encrypted key storage |
providers |
string[] |
["nvidia"] |
Providers to enable rotation for |
How It Works
Architecture
opencode.json
|
┌────────────────────┴────────────────────┐
| Plugin: @hallaxius/opencode-keypool |
| |
| ┌──────────┐ ┌──────────┐ ┌─────────┐ |
| │ rotator │ │ storage │ │ tools │ |
| │ - health │ │ - encrypt│ │ - status│ |
| │ - select │ │ - atomic │ │ - add │ |
| │ - cool │ │ - hash │ │ - reset │ |
| └────┬─────┘ └────┬─────┘ └────┬────┘ |
| | | | |
└──────┼─────────────┼─────────────┼──────┘
| | |
chat.headers shell.env event
| | |
injects key rotates env tracks failures
Key Lifecycle
active
|
+-- failure (429/401/5xx) -> cooling (30s, 60s, 2m, 4m, 8m max)
| |
| +-- time expires -> active
|
+-- manual -> disabled
|
+-- cooldown reset after 5 consecutive successes
Health Score (Weighted Random)
health = successRate * 0.50 + latencyScore * 0.20 + freshnessScore * 0.20 + quotaScore * 0.10
successRate = 1 - (consecutiveFailures / max(totalRequests, 1))
latencyScore = 1 - (avgLatency / 10000)
freshnessScore = min((now - lastUsedAt) / 300000, 1)
quotaScore = rateLimitRemaining / 1000
Selection probability is proportional to health score. All keys get tested, but healthier keys are used more often.
Cooldown Backoff
| Attempt | Duration |
|---|---|
| 1st | 30 seconds |
| 2nd | 60 seconds |
| 3rd | 2 minutes |
| 4th | 4 minutes |
| 5th+ | 8 minutes (max) |
Key Management
Via Chat (recommended)
Ask the agent to add keys — they're validated against the provider's API automatically:
add key nvapi-abc123... named "Production"
add key nvapi-def456... named "Backup"
show key status
reset key "Backup"
Via /connect
Run /connect, select NVIDIA NIM, enter your key. Repeat to add more keys.
Via Custom Tools (LLM)
The plugin registers three tools the AI agent can call during conversations. You can also invoke them directly:
keypool_status
Shows a health dashboard for all keys:
Key Rotation Status ---------------------
NVIDIA (3 keys, 2 active):
[OK] "Primary" health: 92% req: 142 fail: 0 avg: 432ms
[OK] "Secondary" health: 85% req: 89 fail: 1 avg: 512ms
[..] "Free-Tier" health: 30% req: 45 fail: 3 cooling: 45s
Strategy: weighted-random | Cooldown base: 30s | Max: 8min
Use keypool_status detailed=true to include latency and cooldown remaining in the output.
keypool_add
Add a new API key to the rotation pool. The key is validated against the provider's API before being stored.
keypool_add key="nvapi-yOURKEY..." name="Production Key" provider="nvidia"
key— the API key value (required)name— optional friendly name (auto-generated if omitted)provider—nvidia(default),openai, oranthropic
Validation checks:
- Key prefix matches the provider (
nvapi-,sk-,sk-ant-) - Key works against the provider's API (HTTP 200)
keypool_reset
Reset failure metrics and remove cooldown for a specific key.
keypool_reset key="Key 2"
keypool_reset key="<key-uuid>"
Accepts either the auto-generated UUID or the key name (case-insensitive partial match).
Session Context Preservation
Key rotation is completely transparent to your session. Here's why:
OpenCode sends the full message history with every request. The only thing we change is the Authorization header. The provider receives the complete conversation context regardless of which key is used.
When a key fails mid-session:
- The failed key goes into cooldown
- Session pinning migrates to the next healthy key
- The next request carries the full history to the new key
- Zero context loss -- the session continues seamlessly
Security
| Measure | Detail |
|---|---|
| File permissions | 0600 (owner read/write only) |
| Atomic writes | Temp file + rename (no corruption on crash) |
| Tamper detection | SHA-256 hash verified on each load |
| Log masking | Keys shown as nvapi-abc...xyz in output |
| Encryption | AES-256-GCM when passphrase is configured |
| Key validation | Format prefix checked on add/import |
Custom Tools for LLM
The plugin registers three tools the AI agent can call. They are defined using Zod schemas and surfaced via the tool hook — the agent discovers them automatically:
| Tool | Args | Description |
|---|---|---|
keypool_status |
{ detailed?: boolean } |
Show key health dashboard with strategy info |
keypool_add |
{ key: string, name?: string, provider?: "nvidia" | "openai" | "anthropic" } |
Add a new API key with validation |
keypool_reset |
{ key: string } |
Reset failures and remove cooldown by ID or name |
Development
# Install dependencies
bun install
# Build
bun run build
# Test
bun test
bun test --coverage
# Lint and format
bun run lint
bun run format
bun run lint:fix
# Full check (lint + typecheck + test)
bun run checkAdding a New Provider
The plugin is designed to be extended. To add a new provider, just add an entry to the PROVIDER_CONFIGS map in src/types.ts:
export const PROVIDER_CONFIGS: Record<string, ProviderConfig> = {
nvidia: { /* ... */ },
openai: { /* ... */ },
anthropic: { /* ... */ },
};Zero changes to rotation logic, storage, or tools.
FAQ
Q: Does key rotation lose session context?
No. The LLM provider is stateless -- every request carries the full conversation history. Changing the auth key only affects authentication, not content.
Q: What happens when all keys fail?
Graceful degradation kicks in. The last used key is reactivated as a fallback to ensure your workflow never interrupts.
Q: Can I use this with OpenAI or Anthropic?
Yes. The provider abstraction layer is already in place. Add the provider config and it works. Initial release ships with NVIDIA support.
Q: Are my keys stored securely?
Keys are stored at ~/.config/opencode/keypool-keys.json with 0600 permissions. Optionally encrypt with AES-256-GCM using the passphrase config option.
License
MIT