Local adapter that exposes Cursor Agent CLI as an OpenAI-style chat completion endpoint so OpenCode can call it as if it were a model provider.
- OpenAI-compatible subset endpoints:
GET /healthGET /modelsGET /v1/modelsPOST /v1/chat/completions
- Configurable model aliases, with optional dynamic model discovery from
cursor-agent - Prompt flattening from
messages[]into a Cursor-ready text block - Safe subprocess execution with argument arrays (no shell interpolation)
- Timeout handling with process termination
- Stable error codes for common failure modes
- Integration-style tests with mocked subprocess behavior
- Node.js
>=20 - Cursor Agent CLI installed locally (
cursor-agent) - Local Cursor Agent binary path configured in
CURSOR_BIN_PATH
Verify Cursor Agent is installed:
cursor-agent --version- Install dependencies:
npm install- Copy and edit env:
cp .env.example .env- Start dev server:
npm run devcurl http://127.0.0.1:8787/healthcurl http://127.0.0.1:8787/modelscurl http://127.0.0.1:8787/v1/modelscurl -X POST http://127.0.0.1:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "cursor-agent/default",
"messages": [
{ "role": "system", "content": "Be concise." },
{ "role": "user", "content": "Say hello." }
],
"stream": false
}'Run a single prompt directly through the runner:
npx tsx scripts/feasibility.ts "Say hello in one sentence"Configure OpenCode to use this local endpoint as an OpenAI-compatible provider:
- Base URL:
http://127.0.0.1:8787/v1 - Model:
cursor-agent/default - API key: any dummy value if OpenCode requires one
Create or update ~/.config/opencode/opencode.json with a cursor provider entry like:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"cursor": {
"name": "Cursor",
"npm": "@ai-sdk/openai-compatible",
"models": {
"claude-4-6-sonnet": {
"name": "Claude Sonnet 4.6"
},
"gpt-5.3-codex": {
"name": "GPT-5.3 Codex"
}
},
"options": {
"baseURL": "http://127.0.0.1:8787/v1"
}
}
}
}On startup, the adapter automatically calls cursor-agent models, parses the full model list, and writes it into the provider.cursor.models block of ~/.config/opencode/opencode.json. This means OpenCode's model picker will show every model your Cursor account has access to without any manual config.
- Controlled by
CURSOR_DISCOVER_MODELS=true(default). Set tofalseto disable. - If the sync fails for any reason (cursor-agent not found, non-zero exit, etc.), the adapter logs a warning and leaves
opencode.jsonuntouched — models already configured there remain as the fallback. - The sync runs once per adapter boot (not on every request).
- Cursor CLI is callable as a local binary and can run headlessly.
- For
stream=true, the adapter requests Cursor stream output (--output-format stream-json --print) and forwards incremental SSE chunks as they arrive. - Prompt delivery mode may differ by Cursor version; use:
CURSOR_PROMPT_MODE=stdin(default), orCURSOR_PROMPT_MODE=argwithCURSOR_PROMPT_ARG.
- Model compatibility can be widened with:
CURSOR_ACCEPT_ANY_MODEL=true(default)CURSOR_MODEL_ALIASES=cursor-agent/default,claude-4-6-sonnet
- Model forwarding and discovery:
CURSOR_MODEL_ARG=--modelCURSOR_DISCOVER_MODELS=true(default)CURSOR_MODELS_ARGS=modelsCURSOR_MODELS_CACHE_TTL_MS=300000
- For low-latency greetings:
ENABLE_GREETING_FAST_PATH=true(default)GREETING_FAST_PATH_RESPONSE=Hi! What can I help you build or debug today?
- For faster non-trivial prompts:
PROMPT_MAX_CONVERSATION_MESSAGES=10PROMPT_MAX_MESSAGE_CHARS=2000PROMPT_MAX_CHARS=12000RESPONSE_CACHE_TTL_MS=60000RESPONSE_CACHE_MAX_ENTRIES=200
- For Cursor session reuse:
ENABLE_CURSOR_SESSIONS=trueCURSOR_SESSION_FALLBACK_TO_CWD=true(single warm session per cwd when no conversation id)CURSOR_SESSION_TTL_MS=1800000CURSOR_SESSION_MAX_ENTRIES=500- Include
metadata.conversationId(orthreadId/sessionId) in request payload
- Working directory resolution for each request:
- First tries request metadata keys like
cwd,workspacePath,projectPath,repoPath - Then checks equivalent top-level request fields and
x-opencode-cwd/x-workspace-pathheaders - Finally attempts to extract
Workspace Path: ...from message text before falling back toDEFAULT_CWD
- First tries request metadata keys like
- If Cursor cannot emit structured output, adapter falls back to trimmed stdout.
Run:
npm test