project reborn
This commit is contained in:
parent
72d610f42c
commit
0241da4926
|
|
@ -1,3 +1,3 @@
|
||||||
.idea/
|
.idea/
|
||||||
cursor-api-proxy*
|
bin/
|
||||||
.env
|
.env
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
# 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.
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue