diff --git a/litellm/__init__.py b/litellm/__init__.py index 83a97c6fd0e..cb13e3df825 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -868,7 +868,7 @@ def _populate_provider_model_sets(model_cost_map: Dict) -> None: elif value.get("litellm_provider") == "deepseek": deepseek_models.add(key) elif value.get("litellm_provider") == "clinepass": - clinepass_models.add(key) + clinepass_models.add(key) # pyright: ignore[reportUnknownArgumentType] # key comes from the untyped model cost map elif value.get("litellm_provider") == "tencent": tencent_models.add(key) elif value.get("litellm_provider") == "runwayml": diff --git a/litellm/main.py b/litellm/main.py index 979808143d7..e8eb20f2060 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -2466,15 +2466,15 @@ def _complete_clinepass(ctx: _CompletionDispatchContext) -> _CompletionDispatchR stream: Final = ctx.stream timeout: Final = ctx.timeout - api_key = api_key or get_secret_str("CLINEPASS_API_KEY") or litellm.api_key + api_key = api_key or get_secret_str("CLINEPASS_API_KEY") api_base = api_base or litellm.api_base or get_secret_str("CLINEPASS_API_BASE") or "https://api.cline.bot/api/v1" ## COMPLETION CALL response: Final = base_llm_http_handler.completion( model=model, - messages=messages, - headers=headers, + messages=messages, # pyright: ignore[reportUnknownArgumentType] # ctx.messages is list[Unknown] + headers=headers, # pyright: ignore[reportUnknownArgumentType] # ctx.headers is dict[Unknown, Unknown] model_response=model_response, api_key=api_key, api_base=api_base, diff --git a/tests/unit/llms/clinepass/test_clinepass_endpoint_guard.py b/tests/unit/llms/clinepass/test_clinepass_endpoint_guard.py index 1ddb517d99b..9897b58897f 100644 --- a/tests/unit/llms/clinepass/test_clinepass_endpoint_guard.py +++ b/tests/unit/llms/clinepass/test_clinepass_endpoint_guard.py @@ -125,3 +125,30 @@ def test_unknown_kwargs_are_flattened_not_wrapped_in_extra_body(): assert "extra_body" not in params assert params["some_vendor_knob"] == 7 + + +def test_chat_does_not_fall_back_to_the_global_litellm_api_key(monkeypatch): + """`litellm.api_key` is the caller's general-purpose (usually OpenAI) key. + + With no ClinePass credential configured, chat must not borrow it: doing so + sends that key to the Cline host as a Bearer token. + """ + sent: list[tuple[str, str]] = [] + + def record_and_stop(self, *args, **kwargs): + sent.append((str(kwargs.get("url")), str((kwargs.get("headers") or {}).get("Authorization", "")))) + raise RuntimeError("stop before any network I/O") + + monkeypatch.setattr(HTTPHandler, "post", record_and_stop, raising=True) + monkeypatch.delenv("CLINEPASS_API_KEY", raising=False) + monkeypatch.setattr(litellm, "api_key", SENTINEL_OPENAI_KEY) + + with contextlib.suppress(Exception): + litellm.completion( + model="clinepass/deepseek-v4-flash", + messages=[{"role": "user", "content": "hi"}], + num_retries=0, + ) + + assert sent, "the chat request never reached the transport, so nothing was checked" + assert all(SENTINEL_OPENAI_KEY not in authorization for _, authorization in sent)