Add Cursor Cloud Agent integration - #25
Conversation
Define safe agent-provider boundaries and an executable TDD plan before implementation. Co-authored-by: Cursor <cursoragent@cursor.com>
Require the retry-after test to assert the decoded delay instead of only its classification. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Extends the Task 1 metadata client with CreateAgent, CreateRun, GetAgent, GetRun, and CancelRun, plus a resumable StreamRun built on a standard-library SSE parser. StreamRun preserves Last-Event-ID across bounded reconnect attempts with 250ms/500ms/1s backoff, resets an invalid event id once, falls back to GetRun on 410 or a done-without- result stream, and returns immediately on context cancellation or an emit callback error. The streaming HTTP client clone runs with no timeout so long-lived Cursor heartbeats are not killed by the ordinary 30s metadata client timeout. Co-authored-by: Cursor <cursoragent@cursor.com>
Task 2 review (P1): the SSE stream path was correct but undocumented,
and the report incorrectly called it inferred. Adds a comment citing
Cursor's official Cloud Agents API docs ("Stream A Run") next to the
path construction in streamOnce, and a focused test asserting the
exact GET /v1/agents/{id}/runs/{runId}/stream request so a future
route change fails a test instead of only production traffic.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Wire Cursor into provider management with an injectable metadata-client
boundary (cursorFactory/newCursorMetadataClient) so /api/providers/cursor/key
and the new GET /api/providers/{id}/models verify identity + fetch the model
catalogue via internal/cursor instead of the generic llm.New path.
Cursor is tagged capability:"agent" in the provider catalogue and excluded
from /api/setup/status, rejected in /api/setup/complete, and skipped by
/api/model/list-all — an agent integration can never become, or leak into,
the active chat model. Provider HasKey now resolves through
cfg.ResolveProvider so environment-supplied credentials report correctly.
Cursor auth failures map to 200/ok:false (like other providers) without
echoing the supplied key; transport/invalid-response failures map to 502; the
credential is only persisted after both the identity and catalogue calls
succeed.
Co-authored-by: Cursor <cursoragent@cursor.com>
Review found three Important issues in the Cursor provider integration:
1. /api/model/set could still assign provider "cursor" as the active model.
Now rejects any resulting provider whose capability is "agent" before any
config mutation (memory or disk).
2. Generic model paths still reached llm.New for Cursor instead of failing
fast: /api/setup/test, /api/model/list, and
/api/providers/{id}/model-info now guard on capability before calling the
generic client/model code, with actionable errors pointing at the
dedicated /api/providers/{id}/key and /api/providers/{id}/models
endpoints. The adjacent add/delete provider-model handlers got the same
guard, since Cursor has no manual model whitelist to curate.
3. TestConnectCursorPreservesActiveModel (and three sibling tests) relied on
real DNS resolution of api.cursor.com for base-URL validation, which
proved flaky in this sandbox (a public IPv4 one run, a private ULA IPv6
the next). Tests now supply an IP-literal base_url so
validateProviderBaseURL never leaves net.ParseIP, while the injected
Cursor client continues to handle all metadata — fully network-hermetic.
Six new focused tests prove each generic path fails before reaching its
llm.New/agent.Models call, not merely that Task 3's own cursor-agent guard
happens to catch it.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Expose Cursor's read-only agent model catalog without changing LLM provider management. Co-authored-by: Cursor <cursoragent@cursor.com>
Surface embedded Cursor authentication failures before the empty model catalogue. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep legacy provider fallbacks scoped to configured entries, allow long-running streams to recover without false completion, and reject orphaned starting refs before paid requests. Co-authored-by: Cursor <cursoragent@cursor.com>
Document standards-based NAT64 detection that preserves provider SSRF protections on DNS64 networks. Co-authored-by: Cursor <cursoragent@cursor.com>
Define a TDD implementation path for RFC-compliant NAT64 detection and secure provider URL validation. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
A well-known IPv4 address sitting at several RFC 6052 placements yielded a broader prefix that let an unrelated internal AAAA record pass provider validation. Follow RFC 7050 and keep only placements both 192.0.0.170 and 192.0.0.171 agree on, or that an answer evidences exactly once. Co-authored-by: Cursor <cursoragent@cursor.com>
`antares model <id> cursor` persisted an agent integration as the active chat provider, bypassing the capability rule the API, TUI, and provider commands already enforce and leaving chat unusable. Co-authored-by: Cursor <cursoragent@cursor.com>
Connection resets and truncated reads failed a run outright instead of reconnecting with Last-Event-ID, and a read error arriving after a decoded result discarded that result. Only transport failures retry, reusing the existing bounded no-progress budget. EXPIRED joins the terminal statuses Cursor documents, so recovery stops reconnecting to a run that can never emit again, and in-band SSE errors now pass through the same redaction, UTF-8, and length policy as REST errors. Co-authored-by: Cursor <cursoragent@cursor.com>
Cancelling reported a missing run even when Cursor's typed code said the agent itself was gone. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Merged as 1ec08ca. Reviewed for both architecture fit and credential/security surface. What held up well:
One change on merge, in 51d860f: Worth noting the live |
…provider-impl Main advanced 15 commits past the PR enowdev#25 merge that this branch forked from, including the fix that ships the Cursor provider disabled by default. Merging before the follow-up PR keeps that fix rather than letting a stale `Enabled: true` ride back in, and pins the branch's new Cursor suites against the default a user will actually get. Three files overlapped and auto-merged cleanly: - internal/config/defaults.go: main's cursor `Enabled: false` alongside this branch's Auto (`""`) reasoning defaults. - internal/tools/cursor_agent_test.go: main's explicit `p.Enabled = true` fixture. - web/src/pages/ChatPage.tsx: main's ReasoningBlock expand-freeze fix. Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
Test plan
go test ./... -count=1with live-provider credentials unsetgo vetbun testandbun x tsc -b --noEmit/api/healthHTTP 200/v1/me+/v1/modelssmoke test whenCURSOR_API_KEYis exportedMade with Cursor