Repository navigation
openrot v0.2.11
What's New
Reliable bridge rate-limit handling
The bridge now tells real rate limits apart from free-tier quota exhaustion and treats each differently:
- A
FreeUsageLimitError(the free tier's "no more requests" signal) rotates the cascade and retries the same request. - Any other retryable status (plain
429, server load, ...) is honored withRetry-After(or a 5s fallback) before the retry — no rotation is wasted on a limit that a backoff can clear.
Retries stay configurable via bridge_retry_statuses / bridge_retry_attempts (and their OPENROT_BRIDGE_* env overrides).
Upstream pacing and concurrency control
Requests leave the bridge through a single shared httpx client instead of one per request, so connections are reused and the daemon stops leaking sockets.
bridge_max_concurrent(default3) — how many parallel upstream requests may be in flight; the rest queue on a global semaphore.bridge_min_interval(default0.5s) — a global minimum gap between request starts, so a burst of client calls is smoothed out before it reaches the upstream.
Both are settable in config or via OPENROT_BRIDGE_MAX_CONCURRENT / OPENROT_BRIDGE_MIN_INTERVAL.
Free-tier compatibility
opencode's free tier accepts a request only when it looks like it comes from a real opencode client. The bridge now makes bare curl/script calls just work:
X-Opencode-Session(andX-Opencode-Request) are injected when missing, matching the format the gateway validates.- A request body with fewer than two
toolsgets stub tools (bash,read) injected, since the gateway rejects tool-less bodies — requests from real opencode clients (30+ tools) pass through untouched. - A non-opencode
User-Agent(e.g.curl/8.0) is replaced with an opencode one. - The unused
x-opencode-clientmarker is dropped rather than forwarded.
Client-supplied session/request ids and an existing opencode User-Agent are always respected, never overwritten. Disable injection with bridge_inject_session: false (OPENROT_BRIDGE_INJECT_SESSION=0).
Verbatim streaming responses
Responses are re-framed exactly as the client expects: content is streamed through chunked encoding when content-length is absent, and a server that ignores Accept-Encoding: identity is decoded transparently instead of forwarding mismatched headers. The request-summary log now reflects the real output size even for streamed responses.
Daemon cascade keeps the bridge healthy
The bridge daemon now runs the cascade in the background instead of sitting passive next to a foreground loop:
- node health and WARP health loops run as daemon threads, so a dead node or a dropped WARP connection triggers a fallback instead of leaving the bridge serving
502s forever. - the node refresh scheduler starts too (when
update_interval > 0).
Smarter WARP rotation
WARP rotate verifies the IP actually changed and stops only when it did:
- disconnects, waits for the session to fully tear down, reconnects, and compares the new egress IP.
- if Cloudflare re-assigns the same address, it retries (up to
OPENROT_WARP_ROTATE_ATTEMPTS, default 5), waitingOPENROT_WARP_ROTATE_DELAYseconds between attempts. - returns success only after a real IP change (or connection, after exhausting retries).
Port guards
openrot bridge serve and cascade startup now refuse to run when the port is already in use, failing fast with a clear message instead of silently colliding with another instance.
Request debugging
OPENROT_DEBUG_REQ=1 logs every in/out request to the bridge — method, headers (secrets masked), and a summary of the body (model, stream, tools, message sizes) — making upstream differences trivial to diagnose.
Internal
openrot probeis gone: probing is fully covered byverify.openrot.providers.freerenamed toopenrot.providers.proxy.- Shared
httpxclient factory (openrot.core.http), egress-IP helper, and atomic PID-file handling (openrot.core.pid) now used consistently across proxy, daemon, and bridge.
Full Changelog: v0.2.10...v0.2.11