# cursor-adapter SSE Diagnostic Results > **Status: RESOLVED 2026-04-18.** This file is kept for history. The bugs > listed below have been fixed. Current behavior is captured by regression > tests in `internal/server/messages_test.go` and `internal/converter/convert_test.go`. --- ## Originally reported (2026-04-15) When used as an OpenAI-compatible endpoint for SDKs like `@ai-sdk/openai-compatible` (OpenCode), cursor-adapter had five issues: 1. **Non-streaming is completely broken** — server hung for 30s and never wrote a response body. 2. **SSE content was double-JSON-encoded** — each `delta.content` field held the *entire* Cursor CLI JSON line (including `type:"system"`, `type:"user"`, `type:"result"`) serialized as a string, instead of plain assistant text. 3. **Missing `role` in first delta.** 4. **Missing `finish_reason` in final chunk.** 5. **Usage not at the top level** — embedded inside a stringified JSON payload instead of `chunk.usage`. --- ## Root cause Two separate bugs plus one latent one, all landed together: - **Non-stream hang / `exit status 1`:** the chat-only isolation ported from `cursor-api-proxy` was overriding `HOME` → temp dir. On macOS with keychain login, the `agent` CLI resolves its session token via `~/.cursor/` + the real keychain, so a fake `HOME` made `agent` exit immediately with "Authentication required. Please run 'agent login'". The adapter surfaced this as either a hang (when timeouts swallowed the exit) or as `exit status 1` once the error bubbled up. - **Content wrapping / leaked system-user chunks:** older pre-parser code forwarded raw Cursor JSON lines as `delta.content`. The parser had already been rewritten by the time this diagnostic was taken, but the report caught an earlier build. - **Duplicate final delta (discovered during this pass):** the stream parser's accumulator was *reassigned* (`p.accumulated = content`) even when the new fragment did not start with the accumulated prefix. With Cursor CLI's incremental output mode (one fragment per message), that meant the "you said the full text" final message looked different from accumulated and was emitted as a second copy of the whole response. --- ## Fix summary - `internal/workspace/workspace.go` — only override `CURSOR_CONFIG_DIR` by default. `HOME`/`XDG_CONFIG_HOME`/`APPDATA` are only isolated when `CURSOR_API_KEY` is set (which bypasses keychain auth anyway). - `internal/converter/convert.go` — stream parser now handles both cumulative and incremental Cursor output modes. In the non-prefix branch it appends to accumulated instead of replacing it, so the final duplicate is correctly detected via `content == accumulated` and skipped. - `internal/server/handlers.go` + `anthropic_handlers.go` — already emit `role:"assistant"` in the first delta, `finish_reason:"stop"` in the final chunk, and `usage` at the top level. Regression tests added to `messages_test.go` lock this in. ## Verified manually ``` $ curl -sN http://localhost:8765/v1/chat/completions \ -H 'Content-Type: application/json' \ -d '{"model":"auto","stream":true,"messages":[{"role":"user","content":"count 1 to 5"}]}' data: {"id":"chatcmpl-…","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]} data: {"id":"chatcmpl-…","choices":[{"index":0,"delta":{"content":"\n1、2、3、4、5。"},"finish_reason":null}]} data: {"id":"chatcmpl-…","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":6,"completion_tokens":5,"total_tokens":11}} data: [DONE] ``` Non-streaming returns `chat.completion` JSON with `stop_reason:"stop"` and `usage` populated. Anthropic `/v1/messages` emits `message_start` → `content_block_delta*` → `message_stop` without duplicating the final cumulative fragment.