Skip to content

Add Cursor Cloud Agent integration - #25

Merged
enowdev merged 25 commits into
enowdev:mainfrom
nvdorman:feature/cursor-agent-provider-impl
Aug 12, 2026
Merged

Add Cursor Cloud Agent integration#25
enowdev merged 25 commits into
enowdev:mainfrom
nvdorman:feature/cursor-agent-provider-impl

Conversation

@nvdorman

Copy link
Copy Markdown
Contributor

Summary

  • add a secret-safe Cursor Cloud Agents REST/SSE client with resumable run streaming
  • expose Cursor as an agent-only provider with approval-gated delegation/status tools and dashboard model discovery
  • preserve existing LLM selection, harden credential/model boundaries, and document shared-key operation

Test plan

  • go test ./... -count=1 with live-provider credentials unset
  • focused Cursor/server/tools race tests and go vet
  • bun test and bun x tsc -b --noEmit
  • production build, CLI install, daemon restart, and /api/health HTTP 200
  • optional live /v1/me + /v1/models smoke test when CURSOR_API_KEY is exported

Made with Cursor

nvdorman and others added 17 commits August 12, 2026 19:40
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>
@nvdorman nvdorman closed this Aug 12, 2026
@nvdorman nvdorman reopened this Aug 12, 2026
nvdorman and others added 4 commits August 12, 2026 21:57
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>
@nvdorman nvdorman closed this Aug 12, 2026
@nvdorman nvdorman reopened this Aug 12, 2026
@nvdorman nvdorman closed this Aug 12, 2026
@nvdorman nvdorman reopened this Aug 12, 2026
nvdorman and others added 3 commits August 12, 2026 22:50
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>
@nvdorman nvdorman closed this Aug 12, 2026
@nvdorman nvdorman reopened this Aug 12, 2026
@nvdorman nvdorman closed this Aug 12, 2026
@nvdorman nvdorman reopened this Aug 12, 2026
@nvdorman nvdorman closed this Aug 12, 2026
@nvdorman nvdorman reopened this Aug 12, 2026
@enowdev
enowdev merged commit 1ec08ca into enowdev:main Aug 12, 2026
5 of 8 checks passed
@enowdev

enowdev commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Merged as 1ec08ca. Reviewed for both architecture fit and credential/security surface.

What held up well:

  • The Connect/Activate split in providers/catalog.go is a real abstraction rather than a special case, and the capability boundary is enforced at every entry point (server, commands, TUI, CLI) — not just one.
  • No credential leak: two independent redaction layers, the key never logged and never in a URL, and StreamEvent.Raw is populated but never forwarded to the model.
  • One new route (GET /api/providers/{id}/models), correctly behind requireDashboardPassword and absent from every exemption allowlist.
  • The security.go delta is DNS64/NAT64 support for the existing SSRF guard — IPv4 semantics unchanged, IPv6 literals actually stricter, discovery fails closed, and the RFC 6052 acceptance needs three independent conditions.
  • The model can't influence the destination URL; ids are prefix-validated and path-escaped.
  • No new dependency — internal/cursor is stdlib only.
  • The SSE client is careful work: bounded line buffer, no busy-retry, no-progress budget, and a terminal result that survives a subsequent connection break.

One change on merge, in 51d860f: cursor now defaults to Enabled: false, matching every other bring-your-own-key provider (ollama, lmstudio, custom). With Enabled: true and the default approval_mode: "auto" — which returns before the approval gate and only regex-matches shell commands for its danger check — merely having CURSOR_API_KEY in the environment would let the model spawn billable cloud agents with no human in the loop, reachable via prompt injection from any fetched page or cloned README. The tool's own key check makes this harmless without a key; this is about the case where the key is present. Enabling from the Providers page stays one click. Test fixtures now enable it explicitly instead of inheriting the default.

Worth noting the live /v1/me + /v1/models smoke test is still unchecked in your test plan — the hermetic tests are good but can't catch a wrong endpoint path or a changed response shape. Nice work on this one.

nvdorman added a commit to nvdorman/antares that referenced this pull request Aug 13, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants