Repository navigation
feat(providers): Cursor API provider (crsr_ keys) and Cursor CLI passthrough - #10729
Merged
diegosouzapw merged 3 commits intoAug 20, 2026
Conversation
Cursor user API keys (crsr_...) are not accepted as a Bearer by api2.cursor.sh; cursor-agent exchanges them at /auth/exchange_user_api_key for a 1h session JWT whose refreshToken carries the same exp. This adds: - open-sse/services/cursorApiKeyAuth.ts: the exchange plus a per-key cache (refresh 5 min before exp, in-flight dedupe, invalidate on upstream 401). - CursorExecutor resolves apiKey connections to the exchanged token before opening the h2 stream; OAuth/IDE-session connections are untouched. - cursor joins DUAL_AUTH_PROVIDER_IDS so POST /api/providers admits an API-key connection and the card shows Manual API key. Registry stays authType oauth (resilience profile unchanged). refresh-cursor rejects non-OAuth rows. - /api/cursor-cli/[...path]: Cursor CLI passthrough. With CURSOR_API_ENDPOINT=http://<omniroute>/api/cursor-cli and CURSOR_API_KEY=<OmniRoute key> (plus network.useHttp1ForAgent), the CLI exchanges its key for an OmniRoute-minted session JWT and every RPC (unary Connect, RunSSE, BidiAppend, traces) is forwarded to Cursor with the connection credential swapped in and a call_logs row written.
…-auth on the IDE provider Review feedback on diegosouzapw#10729: API-key access to Cursor is now a separate provider, cursor-api (card Cursor API, alias cua), registered next to cursor in the registry, the API-key catalog and the executor map (CursorExecutor bound to its own provider id). The IDE cursor provider goes back to OAuth-only: DUAL_AUTH_PROVIDER_IDS, refresh-cursor and the dual-auth dashboard test are restored to base. The Cursor CLI passthrough resolves cursor-api connections. cursor-api gets a real connection test (the key exchange), so the credential-health sweep no longer marks fresh connections unavailable; CursorExecutor reads the synced catalog for its own provider id; the provider icon aliases to the Cursor SVG; provider count bumps to 342 across the generated reference, diagrams, README, llm.txt mirrors and package.json.
diegosouzapw
added a commit
that referenced
this pull request
Aug 20, 2026
…llision #10722 (Token Kiosk) and #10729 (Cursor) each independently bumped the gateways/specialty-media APIKEY_PROVIDERS count by one in the same merge-train batch; neither PR could see the other's bump, so the merged tree landed at 230 while the surviving hardcoded assertion still said 229. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
diegosouzapw
added a commit
that referenced
this pull request
Aug 20, 2026
Today's merge-train batch1 (#10722 Token Kiosk, #10729 Cursor) each added one new APIKEY_PROVIDERS entry, bringing the live provider count to 343 — the hardcoded '342' in README.md, AGENTS.md, llm.txt, package.json's description, PROVIDER_REFERENCE.md, and 4 hero/comparison SVGs went stale as a result. check:docs-counts (STRICT) now passes; regenerated PROVIDER_REFERENCE.md via npm run gen:provider-reference. The v3.8.50 growth-log table row in README.md (line 66) is left as-is — it's a historical point-in-time snapshot, not a live claim. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Contributor
Author
|
@diegosouzapw confirmed its working on my teammates' hosts |
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…through (diegosouzapw#10729) Merged via merge-train (release/v3.8.50, batch1 2026-08-20) — static gates (typecheck/file-size/complexity/cognitive/changelog) green on the combined tree; test:unit reds observed in the boarded run were verified pre-existing on the pure release tip (unrelated flake), not caused by this PR. Thanks for the contribution!
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…llision diegosouzapw#10722 (Token Kiosk) and diegosouzapw#10729 (Cursor) each independently bumped the gateways/specialty-media APIKEY_PROVIDERS count by one in the same merge-train batch; neither PR could see the other's bump, so the merged tree landed at 230 while the surviving hardcoded assertion still said 229. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
Today's merge-train batch1 (diegosouzapw#10722 Token Kiosk, diegosouzapw#10729 Cursor) each added one new APIKEY_PROVIDERS entry, bringing the live provider count to 343 — the hardcoded '342' in README.md, AGENTS.md, llm.txt, package.json's description, PROVIDER_REFERENCE.md, and 4 hero/comparison SVGs went stale as a result. check:docs-counts (STRICT) now passes; regenerated PROVIDER_REFERENCE.md via npm run gen:provider-reference. The v3.8.50 growth-log table row in README.md (line 66) is left as-is — it's a historical point-in-time snapshot, not a live claim. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
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.
Summary
Two things, both about using Cursor without an IDE session:
A new provider,
cursor-api(dashboard card "Cursor API", aliascua). It is an API-key provider that holds a Cursor user API key (crsr_..., from cursor.com/dashboard/api) and reuses the existing Cursor agent executor, wire format and model catalog. The IDE provider (cursor, OAuth/IDE session) is untouched: separate registry entry, separate catalog card, separate executor binding (new CursorExecutor("cursor-api")), so API-key and IDE-session connections never share renewal, quota or dashboard semantics. Acrsr_key is not usable as a Bearer on api2.cursor.sh (it answers 401); cursor-agent POSTs it to/auth/exchange_user_api_keyand gets a 1h session JWT whoserefreshTokencarries the sameexp.open-sse/services/cursorApiKeyAuth.tsdoes that exchange, caches one session token per key, re-exchanges 5 minutes before expiry and drops the cache when Cursor answers 401.CursorExecutorresolvescursor-apiconnections to the exchanged token right before opening the h2 stream. The connection test forcursor-apiis the exchange itself, so the credential-health sweep gets a real verdict instead of "not supported".A Cursor CLI passthrough at
/api/cursor-cli/[...path]. The CLI honoursCURSOR_API_ENDPOINT, and withnetwork.useHttp1ForAgent: truein~/.cursor/cli-config.jsonevery call it makes is plain HTTP/1.1: unary Connect RPCs,agent.v1.AgentService/RunSSE(SSE),BidiService/BidiAppend, OTLP traces and the key exchange. The handler (open-sse/handlers/cursorCliProxy.ts) authenticates/auth/exchange_user_api_keywith an OmniRoute API key and answers with an OmniRoute-minted HS256 JWT (1h,JWT_SECRET); every other path verifies that JWT (issuer, audience, expiry, key still active), picks an activecursor-apiconnection, swaps in the Cursor credential and streams the upstream reply back. Each hop becomes acall_logsrow (providercursor-api, request typecursor-cli, the OmniRoute key and the connection that served it). The prefix is inPUBLIC_API_ROUTE_PREFIXESwith handler-enforced auth, same as/api/codex/connect/.The minted token has to be a real JWT: the CLI reads
expfrom whatever it gets back and re-exchanges before almost every request when the token is opaque (measured: 37 exchanges for 40 RPCs with an opaque token, 1 exchange with a JWT).Provider count moves 341 to 342 (generated reference, diagrams, README, llm.txt mirrors, package.json description). Docs:
docs/providers/CURSOR-API-KEY-AND-CLI.md.History note: the first revision of this PR added API-key support as dual-auth on the
cursorIDE provider; it was reworked into the separatecursor-apiprovider after review.Related Issues
Validation
node --import tsx/esm --test tests/unit/cursor-api-key-auth.test.ts tests/unit/cursor-cli-proxy.test.ts tests/unit/cursor-apikey-provider.test.ts tests/unit/public-api-routes.test.ts tests/unit/providers-constants-split.test.ts tests/unit/clinepass-provider.test.ts tests/unit/cursor-executor-split.test.ts tests/unit/cursor-streaming.test.ts tests/unit/cursor-registry-claude-families.test.ts tests/unit/deepai-provider.test.ts;npx bun scripts/check/check-provider-consistency.tsnpx eslintandnpx prettier --checkon the changed files;npm run check:docs-all;npm run check:changelog-integrity;npm run gen:provider-referencerelease/v3.8.50head (b754e44)Live run (Docker image built from this branch,
docker run omniroute:basewithREQUIRE_API_KEY=true):/api/usage/call-logsafterwards: 37cursor-clirows for the CLI run (1auth/exchange_user_api_key, 1agent.v1.AgentService/RunSSE4.9 s,BidiService/BidiAppendturns, the rest dashboard/config RPCs), all with providercursor-api, attributed to the OmniRoute key and the connectioncursor-api-docker, plus the chat row withrequestedModel cursor-api/auto./v1/modelslists the catalog ascursor-api/<id>andcua/<id>. Direct probes that drove the design:GetMewith the raw key gives 401, with the exchanged JWT gives 200.Tests Added Or Updated
tests/unit/cursor-api-key-auth.test.ts(new): exchange request shape, 401/5xx/malformed mapping, cache refresh window, in-flight dedupe, invalidation, bearer resolver for both connection shapes.tests/unit/cursor-cli-proxy.test.ts(new): exchange mint and 401/503/400/405 paths, token verification (missing, expired, foreign audience, tampered, revoked key), raw key rejected on RPC paths, header stripping and Authorization swap, connection cooldown/fallthrough, upstream 401 cache invalidation, SSE streaming with one log row, sanitized 502.tests/unit/cursor-apikey-provider.test.ts(new):cursor-apiregistry/catalog/executor wiring,cursorstays OAuth-only, executor credential resolution, connection test via exchange.tests/unit/providers-constants-split.test.ts: APIKEY_PROVIDERS count 228 to 229.tests/snapshots/executors/executor-map.json:cursor-apiandcuaentries.Coverage Notes
cursorApiKeyAuth.ts,cursorCliProxy.tsandvalidateCursorApiProviderare covered end to end by the new unit tests through injected dependencies or a stubbedfetch(no DB or network). The executor change is three small paths (resolveExecutionCredentials,buildHeaders, the 401 invalidation) and the first two are asserted directly.Reviewer Notes
/api/cursor-cli/does not spawn anything; it only callsfetchagainstapi2.cursor.sh, so it is not in the LOCAL_ONLY set./v1/*policy whenREQUIRE_API_KEY=false(anonymous session). Every other path still needs the minted JWT, so a stale Cursor token from a previous real login gets a 401 and the CLI re-exchanges, which is the behaviour the CLI already has.cursor-apimodel listing comes from the static Cursor registry (providerUsesExclusiveSyncedListingstays cursor-only), so nocursor-agentis needed on the host. Usage/quota fetch is not wired forcursor-api(the IDE path uses a session cookie); can follow up if wanted.