fix: cursor pass-through reads credentials from UI (litellm.credential_list)

The pass-through route now checks litellm.credential_list as a fallback
when CURSOR_API_KEY env var is not set. This means adding credentials
via the UI (Models + Endpoints → LLM Credentials) works without any
config.yaml or environment variable setup.

Credential lookup order:
1. passthrough_endpoint_router (config.yaml with use_in_pass_through)
2. litellm.credential_list (credentials added via UI)
3. CURSOR_API_KEY environment variable

Also respects api_base from UI credentials if set.

Co-authored-by: Ishaan Jaff <ishaan-jaff@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-02-28 20:48:05 +00:00
parent 70e9791728
commit 9f5b31deee
2 changed files with 73 additions and 8 deletions

View file

@ -2113,29 +2113,47 @@ async def cursor_proxy_route(
- GET /v0/repositories — List GitHub repositories
Uses Basic Authentication (base64-encoded `API_KEY:`).
Credential lookup order:
1. passthrough_endpoint_router (config.yaml deployments with use_in_pass_through)
2. litellm.credential_list (credentials added via UI)
3. CURSOR_API_KEY environment variable
"""
import base64
base_target_url = os.getenv("CURSOR_API_BASE") or "https://api.cursor.com"
encoded_endpoint = httpx.URL(endpoint).path
if not encoded_endpoint.startswith("/"):
encoded_endpoint = "/" + encoded_endpoint
base_url = httpx.URL(base_target_url)
updated_url = base_url.copy_with(path=encoded_endpoint)
cursor_api_key = passthrough_endpoint_router.get_credentials(
custom_llm_provider="cursor",
region_name=None,
)
if cursor_api_key is None:
for credential in litellm.credential_list:
if (
credential.credential_info
and credential.credential_info.get("custom_llm_provider") == "cursor"
):
cursor_api_key = credential.credential_values.get("api_key")
credential_api_base = credential.credential_values.get("api_base")
if credential_api_base:
base_target_url = credential_api_base
break
if cursor_api_key is None:
raise HTTPException(
status_code=401,
detail="Required 'CURSOR_API_KEY' in environment or credentials to make pass-through calls to Cursor.",
detail="Cursor API key not found. Add Cursor credentials via the UI (Models + Endpoints → LLM Credentials) or set CURSOR_API_KEY environment variable.",
)
encoded_endpoint = httpx.URL(endpoint).path
if not encoded_endpoint.startswith("/"):
encoded_endpoint = "/" + encoded_endpoint
base_url = httpx.URL(base_target_url)
updated_url = base_url.copy_with(path=encoded_endpoint)
auth_value = base64.b64encode(
f"{cursor_api_key}:".encode("utf-8")
).decode("ascii")

View file

@ -2439,6 +2439,9 @@ class TestCursorProxyRoute:
with patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router.get_credentials",
return_value=None,
), patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.litellm.credential_list",
[],
):
with pytest.raises(Exception) as exc_info:
await cursor_proxy_route(
@ -2449,6 +2452,50 @@ class TestCursorProxyRoute:
)
assert exc_info.value.status_code == 401
@pytest.mark.asyncio
async def test_cursor_proxy_route_uses_ui_credential(self):
"""should use credentials added via UI (litellm.credential_list) when env var is not set"""
from litellm.types.utils import CredentialItem
mock_request = MagicMock(spec=Request)
mock_request.method = "GET"
mock_request.query_params = {}
mock_request.headers = {}
mock_response = MagicMock(spec=Response)
mock_user_api_key_dict = MagicMock()
ui_credential = CredentialItem(
credential_name="my-cursor-key",
credential_values={"api_key": "crsr_ui_test_key", "api_base": "https://api.cursor.com"},
credential_info={"custom_llm_provider": "cursor"},
)
with patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router.get_credentials",
return_value=None,
), patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.litellm.credential_list",
[ui_credential],
), patch(
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route"
) as mock_create_route:
mock_endpoint_func = AsyncMock(return_value={"models": []})
mock_create_route.return_value = mock_endpoint_func
result = await cursor_proxy_route(
endpoint="v0/models",
request=mock_request,
fastapi_response=mock_response,
user_api_key_dict=mock_user_api_key_dict,
)
call_args = mock_create_route.call_args[1]
assert call_args["target"] == "https://api.cursor.com/v0/models"
import base64
expected_auth = base64.b64encode(b"crsr_ui_test_key:").decode("ascii")
assert call_args["custom_headers"]["Authorization"] == f"Basic {expected_auth}"
@pytest.mark.asyncio
async def test_cursor_proxy_route_custom_api_base(self):
"""should use CURSOR_API_BASE env var when set"""