Skip to content

feat: Cursor dashboard API-key dual-mode - #22

Merged
cursor[bot] merged 2 commits into
developmentfrom
cursor/cursor-apikey-dual-mode-32c5
Aug 27, 2026
Merged

feat: Cursor dashboard API-key dual-mode#22
cursor[bot] merged 2 commits into
developmentfrom
cursor/cursor-apikey-dual-mode-32c5

Conversation

@pavelhov

@pavelhov pavelhov commented Aug 27, 2026

Copy link
Copy Markdown
Owner

What changed

Cursor stays OAuth-default (ccx login cursor, PKCE) on the existing experimental unofficial api2.cursor.sh AgentService/Run adapter. This PR adds a first-class dashboard user API key path on the same cursor provider id (xAI-style dual-mode), not a new adapter and not a public OpenAI chat-completions API.

  • Registry: allowKeyAuthOverride: true and dashboardUrl: https://cursor.com/dashboard/api so Cursor appears in key-login lists. OAuth remains the default authKind.
  • Router already honors allowKeyAuthOverride when authMode is key and a usable key exists. Pasting a key now actually routes.
  • upsertOAuthProvider already preserves apiKey/apiKeyPool when that flag is set; tests now cover Cursor.
  • GUI Settings shows both OAuth accounts and an API-key pool for Cursor. Add Provider → Use an API key instead sets authMode: "key" and is now honored by routing. Elevated ToS warning in gui/src/oauth-tos-risk.ts is unchanged.
  • Key validation uses fetchCursorUsableModels / validateCursorApiKey. It never calls OpenAI-style GET /models on api2.cursor.sh.

Exchange vs raw Bearer (verified in-repo)

Cursor OAuth refresh is already POST https://api2.cursor.sh/auth/exchange_user_api_key with the refresh token as Bearer (src/oauth/cursor.ts).

Honest materialization (src/adapters/cursor/run-bearer.ts):

  • Unexpired JWT (OAuth access / already-exchanged session): used as the Run Bearer. No exchange call.
  • Dashboard crsr_ user API keys: exchanged through that same endpoint, then the access token is cached until JWT expiry. The original secret stays on the transport so a later turn can re-exchange; only the Run request uses the materialized Bearer.
  • Anything else, including Cloud Agents-style key_… secrets from api.cursor.com: used as-is so GetUsableModels/Run fail honestly. This PR does not claim those keys are Codex-compatible chat-completions credentials.

Validation probes GetUsableModels with the raw secret first; on an auth failure it exchanges a crsr_ key and retries. A secret that already works as Bearer is accepted without exchange.

Dual-mode contract

Same providers.cursor row:

Path How Routing
OAuth (default) ccx login cursor / dashboard Login authMode: "oauth"
Dashboard user API key GUI pool, Add Provider key pane, or authMode: "key" + apiKey authMode: "key"

ccx login cursor still starts PKCE (OAuth wins over key-login for that name). The key path is GUI / config / key-login list. GitHub Copilot stays out of key-login lists (its dashboard URL is subscription settings, not a key-minting page).

Honesty

This is still experimental, unofficial, and elevated ToS risk. Docs no longer say Cursor is absent from key-login lists. They state that a dashboard key uses the same unofficial AgentService/Run protocol, is not a documented OpenAI /v1/chat/completions credential, and that Cloud Agents keys from api.cursor.com are a different product.

Tests

  • tests/cursor-apikey-dual-mode.test.ts — key-login list, routing, pool flip to key billing, JWT vs crsr_ exchange vs Cloud Agents passthrough, GetUsableModels validation, live-transport keeps the original secret
  • tests/oauth-upsert-preserves-api-key.test.ts — OAuth login does not wipe a pasted Cursor key
  • tests/router.test.ts, tests/provider-registry-parity.test.ts, tests/cursor-oauth-shell.test.ts, tests/provider-workspace-auth.test.ts
  • gui/tests/cursor-apikey-dual-mode.test.tsx — Add Provider honesty hint + Settings dual surface
  • tests/cursor-live-transport.test.ts — pin-release and context-estimate stubs still match open() positionals

Local verification

  • bun run typecheck
  • bun run privacy:scan
  • cd gui && bun run test && bun run lint && bun run lint:i18n && bun run build (871 GUI tests)
  • cd docs-site && bun install --frozen-lockfile && bun run build
  • Focused Cursor dual-mode + live-transport files green; full bun run test:parallel re-run after the open() positional fix
Open in Web Open in Cursor 

cursoragent and others added 2 commits August 27, 2026 19:15
Keep Cursor OAuth as the default and allow a pasted dashboard user API
key (authMode: key) on the same unofficial AgentService/Run path.
Exchange crsr_ keys via /auth/exchange_user_api_key; use a working JWT
Bearer as-is. Do not treat Cloud Agents api.cursor.com keys as OpenAI
chat-completions credentials.

Co-authored-by: pavelhov <pavelhov@users.noreply.github.com>
Tests stub open() by argument position. Passing the materialized Bearer
as the second parameter shifted signal/state/fail and broke pin-release
and context-estimate wiring. Keep the original secret on the instance
and pass the Run Bearer as the last argument.

Co-authored-by: pavelhov <pavelhov@users.noreply.github.com>
@pavelhov
pavelhov marked this pull request as ready for review August 27, 2026 19:21
@cursor
cursor Bot merged commit 160b574 into development Aug 27, 2026
1 check passed
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