feat(relay): plan-based gating, KV error logging, and request logging#33
Open
iceteaSA wants to merge 1 commit into
Open
feat(relay): plan-based gating, KV error logging, and request logging#33iceteaSA wants to merge 1 commit into
iceteaSA wants to merge 1 commit 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
1a57f74 to
d23b2a8
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
d23b2a8 to
e3cc138
Compare
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 upstream error logging, paid-only request logs, and a health endpoint. All runtime changes are scoped to
WORKER_SCRIPT; no client relay code touched.RELAY_PLAN=paid); returns 403 on free before upgrade.RELAY_STATEKV with a 7-day TTL (includes response preview and headers); logging runs async viactx.waitUntil.paid/free), and allowed transports (HTTP on free; HTTP + WebSocket on paid).Written for commit e3cc138. Summary will update on new commits.
Greptile Summary
This PR adds plan-based gating (WebSocket requires
RELAY_PLAN=paid), async KV error logging with collision-resistant keys, paid-only request logging, and a richer GET health endpoint to the relay worker script. Tests cover the new free-plan 403 gate and health-endpoint shape on both plans.getPlanConfigdrivesallowWebSocketandlogRequests; free-plan WebSocket upgrades get a 403 before the handshake and before token validation (intentional fast rejection).logUpstreamErrorreads the upstream error body asynchronously, writes toRELAY_STATEKV with a 7-day TTL and acrypto.randomUUID()suffix to prevent key collisions; the WebSocket path correctly guardsupstream.clone()behind astatus >= 400check, but the HTTP path still callsupstream.clone()unconditionally (flagged in a prior review), leaving one tee branch unread on every 2xx SSE response.startWorkernow accepts aplanparameter; two new tests cover the free-plan gate and both plan shapes on the health endpoint.Confidence Score: 4/5
The change is safe to merge for all error-response paths, but the unconditional upstream.clone() on the HTTP path (first flagged in a prior review, still unresolved) creates an unread tee branch on every successful Anthropic SSE response and risks stalling token delivery to the client once the internal buffer fills.
All new code paths (plan gating, KV error writes, health endpoint, tests) are implemented correctly. The WebSocket error-logging path guards clone() behind a status check. The HTTP path still clones the response unconditionally before checking the status, so every 2xx SSE stream has a tee whose second branch is never consumed — backpressure from that unread branch can pause upstream reads and stall streaming responses mid-flight.
packages/core/src/relay.ts — specifically the unconditional upstream.clone() at the HTTP relay path (lines 1337–1345); the WebSocket path at lines 1218–1228 handles this correctly and is the reference pattern.
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 WebSocket requires paid plan] C -- Yes / paid plan --> E[WebSocket Handshake + handleWebSocket] E --> F[prepareWebSocketUpstream] F --> G[fetch upstream] G --> H{status >= 400 and not 429/403?} H -- Yes --> I[upstream.clone → logUpstreamError via ctx.waitUntil] H -- No --> J[stream response chunks to client] I --> J B -- No --> K{GET?} K -- Yes --> L[Return status / plan / transports] K -- No --> M{POST + valid token?} M -- No --> N[401 / 405] M -- Yes --> O[handleRelayPayload → fetch upstream] O --> P[upstream.clone unconditionally] P --> Q[logUpstreamError via ctx.waitUntil] Q --> R[Return new Response upstream.body]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 (4): Last reviewed commit: "feat(relay): add plan-based gating, KV e..." | Re-trigger Greptile