Skip to content

feat(relay): plan-based gating, KV error logging, and request logging#33

Open
iceteaSA wants to merge 3 commits into
cortexkit:mainfrom
iceteaSA:feat/relay-worker-improvements
Open

feat(relay): plan-based gating, KV error logging, and request logging#33
iceteaSA wants to merge 3 commits into
cortexkit:mainfrom
iceteaSA:feat/relay-worker-improvements

Conversation

@iceteaSA
Copy link
Copy Markdown
Contributor

@iceteaSA iceteaSA commented May 21, 2026

Worker-script improvements — all within the WORKER_SCRIPT constant; no client-side relay code modified.

  • Plan-based gating: WebSocket transport requires the paid plan (RELAY_PLAN=paid); free-plan upgrade attempts get a 403 before any handshake. The HTTP relay path is unaffected by plan tier.
  • KV error logging: non-429/403 upstream errors are logged to RELAY_STATE KV with a 7-day TTL (response preview + headers). The HTTP path defers the write via ctx.waitUntil (matching the WebSocket path) so reading the error body never adds latency to forwarding, and the KV key carries a crypto.randomUUID() suffix so two errors for the same id/affinity in the same millisecond can't overwrite each other.
  • Request logging: HTTP and WebSocket requests are logged on the paid plan only.
  • GET health endpoint: returns status, plan (paid/free), and allowed transports (HTTP on free; HTTP + WebSocket on paid).
  • Tests: Miniflare coverage for the free-plan WebSocket 403 gate and the health-endpoint shape on both free and paid plans.

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 opencode CLI now sets RELAY_PLAN on deploy and picks the default transport for your plan.

  • New Features

    • WebSocket transport requires paid plan; free returns 403 before handshake.
    • Non-429/403 upstream errors logged to RELAY_STATE KV with 7-day TTL via ctx.waitUntil, including response preview and headers.
    • Request logging enabled only on the paid plan.
    • GET health endpoint returns status, plan (paid/free), and transports (HTTP on free; HTTP + WebSocket on paid).
  • Bug Fixes

    • opencode CLI sets RELAY_PLAN when deploying the worker and selects the default transport based on plan (websocket for paid, http for free); now covered by CLI tests.

Written for commit b4939fe. Summary will update on new commits.

Review in cubic

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 to RELAY_STATE KV with a 7-day TTL and collision-resistant keys (crypto.randomUUID() suffix).

  • Plan gating: getPlanConfig derives allowWebSocket and logRequests from env.RELAY_PLAN; the WebSocket gate fires before the handshake and the HTTP relay path is unaffected.
  • KV error logging: logUpstreamError is a shared helper that both paths call; the HTTP path wraps it in ctx.waitUntil so no latency is added to response forwarding; the WebSocket path correctly guards the upstream.clone() behind a status check.
  • Tests: Two new Miniflare tests cover the free-plan WebSocket 403 and the health-endpoint shape on both plans; startWorker now accepts a plan parameter.

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

Filename Overview
packages/core/src/relay.ts Adds plan-based WebSocket gating, async KV error logging with collision-resistant keys, and request logging gated on paid plan; the HTTP path unconditionally clones every upstream response body before the status check, leaving an unread tee branch on 2xx SSE streams (previously flagged).
packages/opencode/src/tests/relay-worker-miniflare.test.ts Adds Miniflare tests for free-plan WebSocket 403 gate and health endpoint shape on both plans; startWorker now accepts a plan parameter; test structure is sound.

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]
Loading

Comments Outside Diff (1)

  1. packages/core/src/relay.ts, line 1332-1348 (link)

    P1 Unconditional clone tees every streaming response body

    upstream.clone() is called before checking whether the status is an error, so every successful Anthropic SSE response is teed. On the 2xx path logUpstreamError returns immediately without reading errorClone.body, leaving one half of the tee unread. In Cloudflare Workers, an unread ReadableStream branch 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 behind if (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

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread packages/core/src/relay.ts
@iceteaSA iceteaSA force-pushed the feat/relay-worker-improvements branch from 5e33a83 to 693b3db Compare May 22, 2026 17:08
@iceteaSA iceteaSA force-pushed the feat/relay-worker-improvements branch 5 times, most recently from f381ba8 to 373871d Compare June 3, 2026 18:17
Comment thread packages/core/src/relay.ts Outdated
Comment thread packages/opencode/src/tests/relay-worker-miniflare.test.ts Outdated
Comment thread packages/core/src/relay.ts Outdated
@iceteaSA iceteaSA force-pushed the feat/relay-worker-improvements branch 3 times, most recently from d23b2a8 to e3cc138 Compare June 3, 2026 19:59
…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
@iceteaSA iceteaSA force-pushed the feat/relay-worker-improvements branch from e3cc138 to 2e7467c Compare June 4, 2026 07:56
…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.
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iceteaSA has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iceteaSA has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant