Cursor-opencode-proxy: a local proxy that adds the session header Cursor can't send x-opencode-session

The problem
│
│ Cursor’s “Override OpenAI Base URL” (BYOK) path doesn’t send any conversation or agent identity headers — Cursor staff confirmed this in Grouping requests into conversations
using an API gateway
. That’s fine for plain OpenAI-compatible endpoints, but some
endpoints require a per-conversation session header for routing and prompt caching. OpenCode Go, for instance, asks clients to send a stable x-opencode-session per
conversation.
│
│ Without it, every request lands as a brand-new anonymous session and you lose session affinity and cache reuse.
│
│ What I built
│
│ cursor-opencode-proxy — a single-file, zero-dependency Node 22 proxy that sits locally between Cursor and the upstream:
│
│ - injects the required session header (x-opencode-session by default, configurable)
│ - streams the SSE response through byte-for-byte — no buffering, no parsing, no re-chunking
│ - swaps the client’s Authorization header for your upstream API key, so a locally-issued access token is what you hand to Cursor
│ - optional access-token auth and an auto-started cloudflared named tunnel; config.json hot-reloads on save
│
│ Setup, roughly
│
│ 1. clone, copy config.example.json to config.json, fill in baseUrl and apiKey
│ 2. node cursor-opencode-proxy.js
│ 3. expose the port through a cloudflared named tunnel, then put that HTTPS URL + /v1 into Cursor
│
│ On startup the proxy derives and prints the exact Base URL and API Key to paste into Cursor, read from your cloudflared config.yml ingress hostname. 77+ tests,
node --test tests/*.js.
│
│ Honest limitations
│
│ - The tunnel isn’t optional. Cursor issues BYOK requests server-side, so 127.0.0.1 is rejected by its SSRF protection. A public HTTPS endpoint is required.
│ - Session detection is heuristic. Since Cursor sends no session header, the proxy probes common candidates, then falls back to a device-level Cloudflare header, then to a
per-request UUID. Practical effect: conversations from one device share one upstream session. The README calls this a transitional compromise. The precise fix — fingerprinting
the prompt prefix — isn’t implemented, because it would mean parsing the request body, which conflicts with the pass-through design.
│ - Session state is in-memory, so it resets when the process restarts, and it expires after an idle window (default in the template: 7 days).
│ - It’s a personal-scale tool, not a hardened gateway.
│
│ Feedback and corrections welcome — especially if anyone has found a cleaner way to derive a stable per-conversation key without parsing the request body.

One thing I’m curious about with the device-level fallback: when two Cursor chats share that upstream session, is the header only used for routing and cache affinity, or can it affect conversation state too? I saw the shared-session limitation in your README; the upstream meaning of “session” seems important here.