mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
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:
parent
70e9791728
commit
9f5b31deee
2 changed files with 73 additions and 8 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue