Experimental — Proof of concept. No further development planned.
MIT licensed — feel free to fork, adapt, or use as reference.
A plugin for OpenCode that adds rate limiting and a concurrency semaphore to LLM requests.
- Rate limiting — per-provider sliding window (e.g. max 1 request every 2s per provider)
- Concurrency semaphore — global cap on simultaneous LLM requests across all workspaces
- Shared state — survives module reloads via
globalThis, consistent across all open workspaces
chat.params hook
┌─────────────────────┐
Request ──→ │ 1. Rate check │
│ 2. Acquire slot │
│ 3. Mark window │
└─────────┬───────────┘
│ (waits if rate-limited
│ or concurrency full)
▼
┌─────────────┐
│ LLM Call │
└─────────────┘
│
(slot auto-releases
after timeout)
- Each provider (nvidia, opencode, etc.) has its own rate window
- Maximum
maxRatePerWindowrequests perrateWindowMsmilliseconds - Excessive requests wait (sleep) until the window expires
- Uses a sliding window: old timestamps are pruned automatically
- Global
Setof active request IDs, shared across all workspaces - If
activeRequests.size >= maxConcurrent, new requests wait with polling - Each acquired slot has a safety timeout (
requestTimeoutMs) that auto-releases if the request never completes - Polling interval (
checkIntervalMs) controls responsiveness
mkdir -p ~/.config/opencode/plugins
cp index.js ~/.config/opencode/plugins/rate-limiter.jsOr clone directly:
git clone https://github.com/tmogeid/opencode-rate-limiter-plugin.git
cp opencode-rate-limiter-plugin/index.js ~/.config/opencode/plugins/rate-limiter.jsecho '{"type":"module"}' > ~/.config/opencode/plugins/package.jsonThe plugin auto-loads on next start. Check the log file to confirm.
All settings are at the top of index.js inside the CONFIG object:
| Variable | Default | Description |
|---|---|---|
maxConcurrent |
15 |
Max simultaneous requests across all providers/workspaces |
maxRatePerWindow |
1 |
Max requests per provider within the time window |
rateWindowMs |
2000 |
Time window for rate limiting (milliseconds) |
requestTimeoutMs |
15000 |
Safety timeout: auto-release concurrency slot after this time |
checkIntervalMs |
200 |
Polling interval for concurrency semaphore (milliseconds) |
Conservative:
maxConcurrent: 5
maxRatePerWindow: 1
rateWindowMs: 3000Balanced (default):
maxConcurrent: 15
maxRatePerWindow: 1
rateWindowMs: 2000Aggressive:
maxConcurrent: 25
maxRatePerWindow: 2
rateWindowMs: 3000Logs are written to:
- Windows:
%TEMP%\opencode-rate-limiter\rate-limiter.log - Linux/macOS:
/tmp/opencode-rate-limiter/rate-limiter.log
| Event | Meaning |
|---|---|
CONCURRENCY_ACQUIRED |
A concurrency slot was granted |
CONCURRENCY_BLOCKED |
Request is waiting for a free slot |
CONCURRENCY_TIMEOUT_RELEASE |
Slot auto-released by safety timeout |
CONCURRENCY_RELEASE |
Slot explicitly released |
RATE_LIMIT |
Request is rate-limited (sleeping) |
PASS |
Request passed through |
Get-Content "$env:TEMP\opencode-rate-limiter\rate-limiter.log" -Wait- Tested with OpenCode v1.17.11 on Windows 11 Pro 64-bit only
- Works with any provider (nvidia, opencode, etc.)
- Shared state across all open workspaces (via
globalThis) - Module ESM format (
type: module)
MIT — see LICENSE.
This is an experimental proof of concept with no active development planned. Feel free to fork or submit PRs if you find it useful.