feat(relay): plan-based gating, KV error logging, and request logging#33
Open
iceteaSA wants to merge 3 commits into
Open
feat(relay): plan-based gating, KV error logging, and request logging#33iceteaSA wants to merge 3 commits into
iceteaSA wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
1 issue found across 2 files
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
5e33a83 to
693b3db
Compare
f381ba8 to
373871d
Compare
d23b2a8 to
e3cc138
Compare
…ging Worker script improvements: - Plan-based gating: WebSocket transport requires paid plan (RELAY_PLAN=paid) - KV error logging: non-429/403 upstream errors logged to KV with 7-day TTL - Request logging: HTTP and WebSocket requests logged on paid plan - GET health endpoint returns plan info and available transports
e3cc138 to
2e7467c
Compare
…ort to plan The worker gates websocket + logging on RELAY_PLAN, but the CLI never set it, so a worker PUT silently reverted to free-plan behaviour. relaySetup now selects the plan (env or prompt, default free) and passes it through uploadRelayWorker as the RELAY_PLAN binding; transport defaults to websocket for paid, http for free.
There was a problem hiding this comment.
iceteaSA has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
iceteaSA has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Worker-script improvements — all within the
WORKER_SCRIPTconstant; no client-side relay code modified.RELAY_PLAN=paid); free-plan upgrade attempts get a403before any handshake. The HTTP relay path is unaffected by plan tier.RELAY_STATEKV with a 7-day TTL (response preview + headers). The HTTP path defers the write viactx.waitUntil(matching the WebSocket path) so reading the error body never adds latency to forwarding, and the KV key carries acrypto.randomUUID()suffix so two errors for the same id/affinity in the same millisecond can't overwrite each other.status,plan(paid/free), and allowedtransports(HTTP on free; HTTP + WebSocket on paid).Summary by cubic
Adds plan-based gating to the relay worker and improves observability with KV-backed error logging, paid-only request logs, and a health endpoint. The
opencodeCLI now setsRELAY_PLANon deploy and picks the default transport for your plan.New Features
RELAY_STATEKV with 7-day TTL viactx.waitUntil, including response preview and headers.paid/free), and transports (HTTP on free; HTTP + WebSocket on paid).Bug Fixes
opencodeCLI setsRELAY_PLANwhen deploying the worker and selects the default transport based on plan (websocketfor paid,httpfor free); now covered by CLI tests.Written for commit b4939fe. Summary will update on new commits.
Greptile Summary
This PR adds plan-based gating to the relay worker: WebSocket transport is restricted to the paid plan (
RELAY_PLAN=paid), returning a 403 before any handshake on free; the GET health endpoint now reflects the current plan and allowed transports. Non-429/403 upstream errors on both HTTP and WebSocket paths are logged asynchronously toRELAY_STATEKV with a 7-day TTL and collision-resistant keys (crypto.randomUUID()suffix).getPlanConfigderivesallowWebSocketandlogRequestsfromenv.RELAY_PLAN; the WebSocket gate fires before the handshake and the HTTP relay path is unaffected.logUpstreamErroris a shared helper that both paths call; the HTTP path wraps it inctx.waitUntilso no latency is added to response forwarding; the WebSocket path correctly guards theupstream.clone()behind a status check.startWorkernow accepts aplanparameter.Confidence Score: 4/5
The plan gating, health endpoint, and KV logging logic are all correct. One known issue — the HTTP path unconditionally clones every upstream response before checking the status, leaving an unread tee branch on successful SSE streams — was previously flagged and remains unaddressed.
The plan gating and KV logging implementations are sound, and the WebSocket path correctly guards the clone behind a status check. However, the HTTP handler still calls upstream.clone() before logUpstreamError's early-return check, creating an unread ReadableStream branch on every 2xx SSE response. On Cloudflare Workers this causes the tee buffer to fill and backpressures the upstream read, which can stall token delivery to the client mid-stream on the primary relay path.
packages/core/src/relay.ts — specifically the HTTP handler block where upstream.clone() is called unconditionally before the status guard inside logUpstreamError.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Incoming Request] --> B{Upgrade: websocket?} B -- Yes --> C{config.allowWebSocket?} C -- No free plan --> D[403 Forbidden] C -- Yes paid plan --> E[WebSocket Handshake] E --> F[handleWebSocket] F --> G[fetch Anthropic upstream] G --> H{status >= 400 and not 429/403?} H -- Yes --> I[upstream.clone to logUpstreamError via ctx.waitUntil] I --> J[KV: RELAY_STATE.put 7-day TTL] H -- No --> K[Stream response body via WebSocket] B -- No --> L{GET?} L -- Yes --> M[Health JSON: status + plan + transports] L -- No --> N{POST?} N -- No --> O[405 Method Not Allowed] N -- Yes --> P{Valid relay token?} P -- No --> Q[401 Unauthorized] P -- Yes --> R[handleRelayPayload] R --> S[prepareUpstream + fetch Anthropic] S --> T[upstream.clone ALWAYS] T --> U[logUpstreamError via ctx.waitUntil] U --> V{status >= 400 and not 429/403?} V -- Yes --> W[KV: RELAY_STATE.put 7-day TTL] V -- No --> X[early return - clone unread] T --> Y[return Response upstream.body to client]Comments Outside Diff (1)
packages/core/src/relay.ts, line 1332-1348 (link)upstream.clone()is called before checking whether the status is an error, so every successful Anthropic SSE response is teed. On the 2xx pathlogUpstreamErrorreturns immediately without readingerrorClone.body, leaving one half of the tee unread. In Cloudflare Workers, an unreadReadableStreambranch causes backpressure: once the internal tee buffer fills up, the upstream pull is paused and tokens stop flowing to the client mid-stream. The WebSocket path (line 1216) correctly guards the clone behindif (upstream.status >= 400 && !SKIP_ERROR_LOG_STATUSES.has(upstream.status))— the HTTP path should do the same.Reviews (5): Last reviewed commit: "feat(relay): add plan-based gating, KV e..." | Re-trigger Greptile