npm.io
0.2.0 • Published 18h ago

@hallaxius/opencode-keypool

Licence
MIT
Version
0.2.0
Deps
2
Size
103 kB
Vulns
0
Weekly
0
Install scriptsThis package runs scripts during installation (preinstall/install/postinstall)

@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 toolskeypool_status, keypool_add, keypool_reset via the tool hook
  • Provider abstraction — NVIDIA supported now, ready for OpenAI/Anthropic

Installation

bun add -g @hallaxius/opencode-keypool

Or install locally in your project:

bun add @hallaxius/opencode-keypool

The postinstall script automatically adds the plugin to your OpenCode config.

Quick Start

  1. Connect your first key:

    Run /connect in the OpenCode TUI, select NVIDIA NIM, and enter your API key. Or ask the agent:

    add key nvapi-YOURKEY... named "Primary"
  2. 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 /connect again.

  3. Check status anytime:

    show key status
  4. Start 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

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)
  • providernvidia (default), openai, or anthropic

Validation checks:

  1. Key prefix matches the provider (nvapi-, sk-, sk-ant-)
  2. 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:

  1. The failed key goes into cooldown
  2. Session pinning migrates to the next healthy key
  3. The next request carries the full history to the new key
  4. 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 check

Adding 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

Keywords