feat: Cursor dashboard API-key dual-mode - #22
Merged
Conversation
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
marked this pull request as ready for review
August 27, 2026 19:21
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Cursor stays OAuth-default (
ccx login cursor, PKCE) on the existing experimental unofficialapi2.cursor.shAgentService/Run adapter. This PR adds a first-class dashboard user API key path on the samecursorprovider id (xAI-style dual-mode), not a new adapter and not a public OpenAI chat-completions API.allowKeyAuthOverride: trueanddashboardUrl: https://cursor.com/dashboard/apiso Cursor appears in key-login lists. OAuth remains the defaultauthKind.allowKeyAuthOverridewhenauthModeiskeyand a usable key exists. Pasting a key now actually routes.upsertOAuthProvideralready preservesapiKey/apiKeyPoolwhen that flag is set; tests now cover Cursor.authMode: "key"and is now honored by routing. Elevated ToS warning ingui/src/oauth-tos-risk.tsis unchanged.fetchCursorUsableModels/validateCursorApiKey. It never calls OpenAI-styleGET /modelsonapi2.cursor.sh.Exchange vs raw Bearer (verified in-repo)
Cursor OAuth refresh is already
POST https://api2.cursor.sh/auth/exchange_user_api_keywith the refresh token as Bearer (src/oauth/cursor.ts).Honest materialization (
src/adapters/cursor/run-bearer.ts):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.key_…secrets fromapi.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.cursorrow:ccx login cursor/ dashboard LoginauthMode: "oauth"authMode: "key"+apiKeyauthMode: "key"ccx login cursorstill 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/completionscredential, and that Cloud Agents keys fromapi.cursor.comare a different product.Tests
tests/cursor-apikey-dual-mode.test.ts— key-login list, routing, pool flip to key billing, JWT vscrsr_exchange vs Cloud Agents passthrough, GetUsableModels validation, live-transport keeps the original secrettests/oauth-upsert-preserves-api-key.test.ts— OAuth login does not wipe a pasted Cursor keytests/router.test.ts,tests/provider-registry-parity.test.ts,tests/cursor-oauth-shell.test.ts,tests/provider-workspace-auth.test.tsgui/tests/cursor-apikey-dual-mode.test.tsx— Add Provider honesty hint + Settings dual surfacetests/cursor-live-transport.test.ts— pin-release and context-estimate stubs still matchopen()positionalsLocal verification
bun run typecheckbun run privacy:scancd 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 buildbun run test:parallelre-run after theopen()positional fix