Build a TypeScript/Node.js proxy plugin that pools multiple OpenCode Go API subscriptions into a single endpoint, with automatic failover, round-robin load balancing, quota tracking, and a local web UI for management.
Important
Secure Key Storage: The PRD specifies using keytar or OS-equivalent for secure credential storage. However, keytar is deprecated and unmaintained. I propose using keytar's successor approach: encrypted JSON file storage with a master password derived via node:crypto (PBKDF2 + AES-256-GCM). This works cross-platform without requiring D-Bus/Keychain dependencies that complicate headless/server environments. Alternative: we could use the secret-service npm package for Linux or skip OS keychain integration entirely in favor of encrypted-at-rest file storage. Please confirm your preference.
Important
Port Selection: The PRD says "chose less common and available port." I'll use port 18904 for the web UI dashboard. The proxy API endpoint will run on a separate port 18905. Let me know if you have a preference.
Note
The PRD references the OpenCode Go API at https://opencode.ai/zen/go/v1. Confirm this is the correct upstream base URL to proxy to.
Note
The PRD mentions a $60 Go limit per key. The earlier section says $10/month accounts. I'll implement quota tracking against the $60 limit as stated in section 3.2.
- Node.js project with TypeScript, Express,
http-proxy-middleware - Scripts:
dev,build,start - Dependencies:
express,http-proxy,winston(logging),ws(WebSocket for real-time log streaming)
- TypeScript strict mode, ES2022 target, Node16 module resolution
- Standard Node.js gitignore +
dist/,*.log,.env
- Template for optional env overrides (ports, upstream URL, log path)
ApiKeyinterface:{ id, key, alias, addedAt, status, cooldownUntil, consecutiveErrors, tokensUsed, costAccumulated }RoutingStrategyenum:EXHAUSTION_FAILOVER,ROUND_ROBINRouterConfiginterface: cooldown duration, quota limit, circuit breaker thresholds
KeyManagerclass: manages the pool of API keys- Methods:
addKey(),removeKey(),getNextKey(strategy),markExhausted(keyId),markError(keyId),resetCooldown(keyId) - Round-robin index tracking
- Exhaustion failover: use primary until limit, then fall through
- Emits events for state changes (for UI/logging)
CircuitBreakerclass: monitors per-key health- Tracks consecutive 5xx errors (threshold: 3)
- States:
CLOSED(healthy),OPEN(tripped),HALF_OPEN(testing) - Auto-recovery after configurable timeout
QuotaTrackerclass: tracks token usage per key against $60 limit- Parses response headers/body for token usage data
- Proactive switching: triggers failover at 95% quota usage
- Persists usage data to local JSON file
- HTTP proxy server on port 18905
- Intercepts all requests to
/v1/*(OpenAI-compatible) and/v1/messages(Anthropic-compatible) - For each request:
- Select key via
KeyManager.getNextKey(currentStrategy) - Preserve all caching headers (
X-Session-Id,prompt_cache_key,cache_control) — passthrough unmodified - Attach selected API key to upstream request
- Forward to upstream (
https://opencode.ai/zen/go/v1) - On response: check for 402/429 → trigger exhaustion failover & retry
- On response: check for 5xx → feed to circuit breaker
- Parse token usage from response → feed to quota tracker
- Log the routing decision
- Select key via
- Utility to identify, preserve, and forward caching headers
- Logs warning on failover: "Cold start cache miss — first request on new key will not benefit from cached context"
- Never injects artificial session IDs
- Encrypted-at-rest key storage using AES-256-GCM
- Master key derived from machine-specific entropy (hostname + username hash) via PBKDF2
- File stored at
~/.opencode/router-keys.enc - Methods:
saveKeys(),loadKeys(),addKey(),removeKey() - Falls back gracefully if crypto unavailable
- Stores non-secret configuration (selected strategy, port settings, cooldown durations)
- JSON file at
~/.opencode/router-config.json
- Winston-based logger
- Outputs to both console and
~/.opencode/router.log - Log levels:
info,warn,error,debug - Structured JSON log entries with timestamps
- Rotation: max 5MB per file, keep 3 rotated files
- WebSocket-based real-time log streaming for Web UI
- Maintains in-memory ring buffer of last 500 log entries
- Broadcasts new entries to connected WebSocket clients
- Express server on port 18904
- Serves static files from
src/dashboard/public/ - REST API endpoints:
GET /api/keys— list keys (masked)POST /api/keys— add keyDELETE /api/keys/:id— remove keyGET /api/strategy— get current strategyPUT /api/strategy— change strategyGET /api/status— full status (keys health, quota, cooldowns)GET /api/logs— recent log entries
- WebSocket endpoint at
/ws/logsfor real-time log streaming
- Single-page dashboard with premium dark-mode design
- Sections:
- Header: App name + connection status indicator
- Key Management: Add/remove keys with masked display, secure input form
- Strategy Selector: Dropdown to switch between Exhaustion Failover and Round-Robin
- Status Ledger: Visual cards per key showing health (green/yellow/red), quota bar, cooldown timer, tokens used
- Log Viewer: Real-time scrolling log feed with color-coded severity
- Premium dark theme with glassmorphism cards
- CSS custom properties for theming
- Smooth animations and transitions
- Responsive layout (works on mobile too)
- Color-coded status indicators (green=healthy, amber=warning, red=error/cooldown)
- Vanilla JS SPA logic
- WebSocket connection for real-time logs
- Fetch-based API calls for key management and strategy changes
- Auto-refreshing status ledger (polling every 5s)
- Toast notifications for actions
- Main entry point
- Boots:
- Load config from
~/.opencode/router-config.json - Load keys from encrypted store
- Initialize
KeyManager,CircuitBreaker,QuotaTracker - Start proxy server on port 18905
- Start dashboard server on port 18904
- Log startup info with port numbers
- Load config from
- Installation instructions
- Configuration guide
- Usage with OpenCode CLI
npm run build— TypeScript compilation succeeds- Manual testing: start the server, verify dashboard loads at
http://localhost:18904 - Manual testing: add a key via UI, verify it appears in status
- Manual testing: verify proxy forwards requests correctly
- Add 2+ test API keys via the Web UI
- Switch between strategies and verify behavior changes
- Verify logs appear in real-time in the log viewer
- Verify encrypted key file is created at
~/.opencode/router-keys.enc - Test failover by simulating a 429 response