Skip to content

feat(clinepass): add ClinePass (Cline API) as a provider - #44379

Open
djbclark wants to merge 24 commits into
BerriAI:mainfrom
djbclark:clinepass-upstream
Open

djbclark wants to merge 24 commits into
BerriAI:mainfrom
djbclark:clinepass-upstream

Conversation

@djbclark

@djbclark djbclark commented Oct 3, 2026 •

Copy link
Copy Markdown

Adds ClinePass (the Cline API) as a LiteLLM provider, routed as clinepass/<model>.

ClinePass is an OpenAI-compatible endpoint with two wrinkles that need real code rather than a declarative entry: it nests non-streaming completions under a data envelope, and it expects a cline-pass/ qualifier on outbound model ids. The JSON provider system can't express the first, because its OpenAI-SDK dispatch path never calls transform_response, so this is a provider module with an explicit route to the generic HTTP dispatcher. Provider-specific credentials, URLs and transforms live in llms/clinepass/

What's here

  • litellm/llms/clinepass/ — ClinePassConfig(OpenAIGPTConfig), envelope unwrap, model-prefix handling
  • an explicit dispatch branch routing to base_llm_http_handler.completion
  • credential metadata, Admin UI Providers/provider_map entries, README provider row
  • model_prices_and_context_window.json registration
  • 105 tests under tests/unit/llms/clinepass/ (the CI-selected tree)

Notes a reviewer may want up front

clinepass is deliberately not in openai_compatible_providers. An earlier revision added it there to reach _map_openai_exception, assuming membership was inert for routing. It isn't. That list is also read by the speech branch in main.py — which pairs the provider's own api_base with a key chain that falls back to OPENAI_API_KEY — so litellm.speech(model="clinepass/...") sent the caller's OpenAI credential to a third-party host, for an endpoint ClinePass doesn't implement. OPENAI_AUDIO_TRANSCRIPTION_PROVIDERS is derived from the same list, and litellm/images/main.py reads it too. Exception mapping is instead registered explicitly beside mistral and runwayml. tests/unit/llms/clinepass/test_clinepass_endpoint_guard.py asserts speech/transcription/image make zero outbound requests and transmit no credential.

Chat authentication uses only the request's api_key or CLINEPASS_API_KEY, never global or other-provider keys. The provider rejects moderation, realtime client secrets, transcription sessions and SDP calls before HTTP dispatch, including when a ClinePass key is supplied. The managed Responses WebSocket-to-chat bridge no longer borrows global or OpenAI credentials, and provider-switching frames do not inherit another provider's connection key or URL. Authenticated managed sockets are pinned to the connection-authorized model or alias; changing models requires a new authorized connection. Sync, async, streaming and real managed-WebSocket tests inspect outbound authorization; unsupported-endpoint tests require a specific error and zero outbound requests. Mutation checks detect restored credential fallbacks, removed endpoint rejection and removed model pinning

Unknown kwargs remain flat at the optional-params level. This is distinct from the wire body: BaseLLMHTTPHandler also merges extra_body before serialization

No pricing is claimed. ClinePass is a flat-rate subscription, so the catalogue entry carries litellm_provider and mode only. Inventing per-token rates would report fabricated spend, and null prices aren't schema-valid. This follows the existing github_copilot/* precedent of metadata without price fields.

Docs: the README row and provider_endpoints_support.json point at docs.litellm.ai/docs/providers/clinepass, the final URL for the page in BerriAI/litellm-docs#2049. The documentation and code-quality jobs fail only because upstream docs do not yet document CLINEPASS_API_KEY and CLINEPASS_API_BASE. All documentation validators pass locally with the companion page present. Merge the companion PR before this one

Verifying

uv run --no-sync pytest tests/unit/llms/clinepass/ -q   # 105 passed

The commits are fine to squash-merge

The change was reviewed by two independent tools before submission; their findings are all addressed, including dropping a finish_reason heuristic that inferred truncation from usage reaching the cap — unsound, since a natural completion can land exactly on the cap, and it didn't apply to streaming.

@CLAassistant

CLAassistant commented Oct 3, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@greptile-apps

greptile-apps Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Medium risk] Adds a new LLM provider integration for ClinePass.

The PR is not safe to merge until the cross-provider credential fallback is removed, the provider-specific dispatch setup satisfies the repository requirement, and the linked documentation is available.

Findings

  1. P1 Security Global credential sent to ClinePass ▶
  2. P2 Provider policy in shared dispatcher ▶
  3. P2 Provider link lacks a page ▶

Summary

Adds ClinePass chat-completion routing, response-envelope and model-ID transformations, provider metadata, and tests. The dispatch introduces a cross-provider credential fallback that needs correction. The provider-specific setup should be moved out of the shared dispatcher, and the linked documentation should be published before merge.

Reviews (1) · Last reviewed commit: "test(clinepass): cover streaming, usage,..."

Comment thread litellm/main.py Outdated
stream: Final = ctx.stream
timeout: Final = ctx.timeout

api_key = api_key or get_secret_str("CLINEPASS_API_KEY") or litellm.api_key

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Global credential sent to ClinePass

If litellm.api_key contains a credential for another provider and no ClinePass key is supplied, this fallback uses that credential for a clinepass/... chat completion. The inherited header builder then sends it as a Bearer token to the ClinePass host, disclosing an unrelated credential.

How this was verified: The dispatch passes the global-key fallback to the ClinePass HTTP handler, whose inherited configuration puts it in the outbound Authorization header.

Suggested change
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")

Comment thread litellm/main.py Outdated
Comment on lines +2469 to +2473
api_key = api_key or get_secret_str("CLINEPASS_API_KEY") or litellm.api_key

api_base = (
api_base or litellm.api_base or get_secret_str("CLINEPASS_API_BASE") or "https://api.cline.bot/api/v1"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Provider policy in shared dispatcher

This wrapper selects ClinePass-specific credentials and URLs in main.py, alongside a dedicated ClinePass dispatch branch. The repository requires provider-specific code to stay under llms/. Move this setup into the provider configuration before merging.

Rule Used: What: Avoid writing provider-specific code outside of the llms/ directory. Why: This practice ensures better maintainability and reduces complexity over time. Good: ```python # Handle provider-specific logic within llms/vertex_ai/transformation.py ... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread README.md
| [Bytez (`bytez`)](https://docs.litellm.ai/docs/providers/bytez) | ✅ | ✅ | ✅ | | | | | | | |
| [Cerebras (`cerebras`)](https://docs.litellm.ai/docs/providers/cerebras) | ✅ | ✅ | ✅ | | | | | | | |
| [Clarifai (`clarifai`)](https://docs.litellm.ai/docs/providers/clarifai) | ✅ | ✅ | ✅ | | | | | | | |
| [ClinePass (`clinepass`)](https://docs.litellm.ai/docs/providers/clinepass) | ✅ | ✅ | ✅ | | | | | | | |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Provider link lacks a page

The PR description says the linked ClinePass documentation page is only drafted for a separate repository. If this change merges before that page is published, readers following the new provider link will reach a missing page. Publish the companion page before exposing the link.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread litellm/utils.py Fixed
Comment thread litellm/litellm_core_utils/get_llm_provider_logic.py Outdated
@veria-ai

veria-ai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 3 · PR risk: 0/10

@codecov

codecov Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 48.73950% with 61 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/llms/clinepass/chat/transformation.py 48.68% 39 Missing ⚠️
litellm/main.py 20.83% 19 Missing ⚠️
litellm/responses/streaming_iterator.py 75.00% 2 Missing ⚠️
litellm/utils.py 50.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing djbclark:clinepass-upstream (838f7bd) with main (e0e3523)

Open in CodSpeed

@djbclark

djbclark commented Oct 3, 2026

Copy link
Copy Markdown
Author

The documentation and code-quality failures are a cross-repo dependency rather than something this branch can fix: tests/documentation_tests/test_env_keys.py resolves docs by checking out BerriAI/litellm-docs into docs/my-website, so CLINEPASS_API_BASE and CLINEPASS_API_KEY read as undocumented until a page exists there.

Companion PR opened: BerriAI/litellm-docs#2049 (adds docs/providers/clinepass.md + its sidebars.js entry). Once that merges, both checks should go green here — happy to rerun them.

The other three failures from the first run are fixed in 1561abd:

  • cost-map-guard — litellm/model_prices_and_context_window_backup.json needed the root cost map copied over it.
  • ui-unit-tests — ClinePass ships no logo on purpose (the <Logo> component falls back to a letter circle rather than an invented asset), so it belongs in provider_info_helpers.test.tsx's known-logoless set.
  • lint — ruff format.

Comment thread litellm/utils.py Fixed
djbclark and others added 15 commits October 3, 2026 13:55
ClinePass (the Cline API) is OpenAI-compatible apart from two quirks:

1. Non-streaming completions are nested under a `data` envelope --
   `{"data": {"choices": [...]}, "success": true}` -- rather than returning
   `choices` at the top level. Against the openai SDK this surfaces as
   `r.choices` being None, not as an error. Streaming responses are *not*
   enveloped, so SSE needs no special handling.
2. A bare model id is rejected with HTTP 400 "invalid model format. Expected
   format: modelType/model", but LiteLLM strips its own `clinepass/` routing
   prefix before the request is built, so it has to be restored.

Both are handled in ClinePassConfig, which inherits OpenAIGPTConfig and
overrides only transform_request/async_transform_request (prefix) and
transform_response (unwrap). Unwrapping rebuilds the httpx.Response around the
inner object, so the inherited OpenAI response transform -- including its
`reasoning` -> `reasoning_content` mapping, which Cline populates -- is reused
rather than duplicated.

A response transform is the reason this cannot be a declarative entry in
litellm/llms/openai_like/providers.json: JSON-configured providers are
dispatched to `_complete_custom_openai`, which builds the request via
provider_config but parses the response with convert_to_model_response_object
and never calls provider_config.transform_response. Under that dispatch the
unwrap is unreachable, so ClinePass gets an explicit branch in main.py routing
it to base_llm_http_handler.completion.

`clinepass` is still listed in openai_compatible_providers, which is what
routes an upstream 401 through _map_openai_exception to AuthenticationError;
the explicit dispatch branch precedes that list's catch-all, so membership does
not send it back to the SDK path.

Tests drive litellm.completion()/acompletion() against a mocked transport
rather than calling the transforms directly, so dispatch itself is covered --
a unit test that calls transform_response() proves the function is correct but
not that anything invokes it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit 86b3486)
Addresses findings from three independent code reviews (Claude Opus 5,
GPT-5.6-sol xhigh via codex, Grok 4.6 xhigh via cursor) ahead of proposing
this branch upstream.

Correctness:

* The restored model qualifier was `clinepass/`, a namespace that does not
  exist. The catalog namespace is `cline-pass/` (hyphenated); `clinepass/` is
  LiteLLM's own routing prefix, which is stripped before the request is built.
  This failed silently because the API validates only the *shape* of a model
  id -- `totallybogus/deepseek-v4-flash` also returns HTTP 200. It is not
  inert, though: verified against the live API, `cline-pass/deepseek-v4-flash`
  resolves to `deepseek/deepseek-v4-flash` while any unrecognised namespace
  falls back to the date-pinned `deepseek/deepseek-v4-flash-0731`.

* `_correct_truncated_finish_reason` compared an aggregate
  `usage.completion_tokens` against a per-choice `max_tokens`. With `n > 1`
  that relabels naturally-finished choices as truncated: two 60-token choices
  under a cap of 100 aggregate to 120 and both became `length`. Restricted to
  single-choice responses, where the inference is sound.

* Zero, negative and `bool` caps are now rejected. `bool` subclasses `int`, so
  `max_tokens=True` was read as a cap of 1 and would relabel everything.
  Float usage/caps are now accepted rather than silently skipped.

* `_unwrap_response_envelope` reached for the private `_request` attribute
  because `httpx.Response.request` raises `RuntimeError` instead of returning
  `None`. Ask for the public attribute defensively instead.

* `get_models()` is overridden to return an empty catalog. ClinePass has no
  `/models` endpoint (404), and the inherited OpenAI implementation asked for
  it at the wrong path.

* Dropped the `async_transform_request` override. `BaseLLMHTTPHandler` builds
  the body with the synchronous `transform_request` on both the sync and async
  paths, so it was dead code -- the same shape of bug this provider shipped
  once already. Pinned with a test.

Packaging / CI:

* Added the `clinepass` entry to `provider_endpoints_support.json` and its
  backup. Without it `check_provider_folders_documented.py` fails, which is a
  required Code Quality job -- verified failing before, passing after.

* `transformation.py` did not satisfy `ruff format` under the repo's pinned
  ruff 0.15.3, which the changed-file CI gate runs. Reformatted.

* This commit replaces the previous branch tip, which had accidentally swept
  in ~435 regenerated Next.js artifacts under `litellm/proxy/_experimental/`.
  The branch is now +883/-0 across 12 files.

Honesty note on the truncation fix: re-probing the live API (streaming and
non-streaming, caps of 2000 and 4000, both namespaces) could NOT reproduce the
`stop`-instead-of-`length` misreport that motivated it. The correction is kept
as a conservative safety net and documented as such rather than as a workaround
for a currently-observable defect. Streaming is deliberately not covered; see
the docstring.

Tests: 41 pass (was 32). openai_like + cometapi regressions: 126 passed,
8 skipped.

(cherry picked from commit c2dda4f)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 890301f)
The tests were under tests/test_litellm/llms/clinepass/chat/, but
.circleci/scripts/unit_selection.sh:62 builds the llm-other-providers shard
with `find tests/unit/llms -name 'test_*.py'`, and GitHub Actions consumes the
same script. Nothing errored -- the 482 lines of tests simply never ran in CI,
so the PR would have looked green on tests that never executed.

Moved to tests/unit/llms/clinepass/chat/, matching the layout cometapi and
deepseek already use for an OpenAI-compatible provider, including the
__init__.py files those directories carry, and renamed to
test_clinepass_chat_transformation.py to match that same convention.

41 passed at the new location, where tests/unit/conftest.py applies rather
than tests/test_litellm/conftest.py. The CI selector now lists the file.
… the OpenAI key

ClinePass implements chat completions only, but it was listed in
`openai_compatible_providers` to reach `_map_openai_exception`. That list is not
inert for routing:

* the speech branch in `main.py` matched it, and sends the request to the
  provider's own `api_base` while reading the credential from `OPENAI_API_KEY` --
  so `litellm.speech(model="clinepass/...")` POSTed the caller's OpenAI key to
  the Cline host for an endpoint that does not exist there;
* `OPENAI_AUDIO_TRANSCRIPTION_PROVIDERS` is derived from the same list, opening
  the identical hole for transcription;
* `litellm/images/main.py` consults it for image generation;
* `_add_provider_specific_params` packs unknown kwargs into `extra_body`, an
  OpenAI *SDK* concept the SDK unwraps client-side. ClinePass dispatches through
  `BaseLLMHTTPHandler`, which serialises optional params straight into the JSON
  body -- so membership put a literal `"extra_body": {}` on the wire on every
  chat request and buried genuine vendor kwargs one level deep.

Drop the membership and register ClinePass explicitly for `_map_openai_exception`
alongside `mistral` and `runwayml`, which is the established shape for a provider
with its own module. Exception mapping is unchanged and still covered by
`test_upstream_401_maps_to_authentication_error`.

Verified: speech, transcription and image generation now make zero outbound
requests and transmit no credential; unknown chat kwargs are flattened instead of
wrapped. Image generation returns an empty `ImageResponse` rather than raising,
which is pre-existing upstream behaviour for a provider with no image support --
`mistral` behaves identically.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e, UI, README

Fixes from two independent reviews of the provider addition:

* `main.py`: drop the provider's own `logging.post_call`. The shared
  `BaseLLMHTTPHandler` already logs post-call, so this double-fired callbacks and
  overwrote raw-response metadata; on the async path `response` is still a
  coroutine there, so callbacks ran before the request completed. Also adopt the
  `Final` annotations and `_dispatch_client_http(ctx)` used by the sibling
  `_complete_*` blocks.
* `transformation.py`: `json.dumps(..., ensure_ascii=False)` when rebuilding the
  unwrapped body -- the default escapes non-ASCII to `\uXXXX`, and
  `OpenAIGPTConfig.transform_response` hands `raw_response.text` straight to
  `logging.post_call`, so observability backends received mangled text.
* `transformation.py`: remove two unreachable branches. `httpx.StreamError` cannot
  arrive, because `BaseLLMHTTPHandler` returns `get_model_response_iterator()`
  without calling `transform_response` when streaming (the only paths that do call
  it set `stream=False`, so the body is fully buffered). The
  `max_completion_tokens` fallback cannot fire either, because `request_data` is
  the post-`map_openai_params` payload and the mapping has already rewritten that
  key to `max_tokens`.
* `transformation.py`: modernise typing to builtin generics and PEP 604 unions,
  matching the repo's UP006/UP007/UP035/UP045 rules and the CometAPI house style.
* UI: add `CLINEPASS` to the `Providers` enum and `provider_map`.
  `CredentialModal` builds its options from `Object.entries(Providers)`, so
  backend credential metadata alone left the provider unselectable.
* README: add the provider table row, ticking only the endpoints
  `provider_endpoints_support.json` declares.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`model_prices_and_context_window.json` had no ClinePass entry, so
provider-specific model info could not resolve: a DeepSeek row declares
`"litellm_provider": "deepseek"`, which `_check_provider_match` rejects when the
requested provider is `clinepass`.

Prices and limits are deliberately omitted rather than guessed. ClinePass is a
flat-rate monthly subscription, so inventing per-token rates would report an
invented allocation as provider spend; `null` prices are not schema-valid, and
the schema requires only `litellm_provider`. Omitting them follows the existing
subscription precedent -- several `github_copilot/*` entries carry metadata with
no price fields. Context and output limits are left unset because nothing in
this repo establishes them, and DeepSeek's limits are not evidence for
ClinePass's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…g truncation

`_correct_truncated_finish_reason` rewrote a single-choice response's
`finish_reason` from `stop` to `length` whenever completion usage reached the
requested cap. Two independent reviewers flagged it, and it is unsound:

* usage reaching the cap is a token count, not a reason for stopping. A natural
  completion, or a stop-sequence match, can land exactly on the cap, and the
  heuristic then mislabels a successful response as truncated -- which can
  provoke spurious continuation requests in callers;
* it read no explicit provider truncation signal, so it was pure inference;
* it was inconsistent. Real streaming never reaches `transform_response` --
  `BaseLLMHTTPHandler` delegates to `get_model_response_iterator()` -- so a
  streamed response kept the provider's `stop` while the identical non-streamed
  response was rewritten to `length`.

The original incident is recorded in a comment on `transform_response` so it is
not lost: ClinePass was once observed returning `stop` on a completion cut off
at 4000 tokens, and follow-up probes on 2026-08-22 did not reproduce it.

Tests: the three end-to-end rewrite tests become regression tests asserting an
explicit `stop` survives at and above the cap, including through the
`max_completion_tokens` -> `max_tokens` mapping path. The streaming test now
appends a terminal finish-reason chunk and asserts it survives, which closes the
streaming/non-streaming asymmetry that motivated the change. Ten tests that only
exercised the removed helper's internals (non-positive cap filtering, missing
usage, multi-choice guard, float coercion) are deleted with it: 46 -> 36 passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Closes the last external-review finding (codex P2 BerriAI#9), which noted the only
streaming test checked concatenated text and that there was no async-stream,
tool-call-fragment, usage-preservation or outgoing-Authorization assertion, and
asked for the registry/`__dict__` introspection to become behaviour checks.

Added: an async streaming test; tool-call fragments split across SSE chunks
reassembling intact; usage (prompt/completion/total) surviving the envelope
unwrap into ModelResponse.usage; the request actually carrying
`Authorization: Bearer <clinepass key>`; an explicit `api_key=` argument taking
precedence over CLINEPASS_API_KEY in the environment; and an upstream 429
mapping to RateLimitError, mirroring the existing 401 test.

Replaced three introspection assertions with observable behaviour: the JSON
provider-registry check now proves the transform layer is actually reached, and
the async-transform check proves `acompletion` evaluates the synchronous
`transform_request` rather than reading `__dict__`.

Kept the structural membership assertion rather than replacing it. The
behavioural test proves the consequence; the one-line structural test proves the
cause, with no mocking that could itself drift into passing for the wrong
reason. Both are wanted, and its docstring carries why the membership was
dangerous: the list is read by the speech branch (which pairs the provider's
api_base with OPENAI_API_KEY), by the transcription set derived from it, by
image generation, and by the extra_body wrapping that BaseLLMHTTPHandler never
unwraps.

36 -> 43 passing; ruff check and format green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three failures from the first CI run on the PR, all genuine and all local:

- `cost-map-guard`: litellm/model_prices_and_context_window_backup.json is a
  mirror of the root cost map and must be copied over whenever the root changes.
  I did not know that second copy existed.
- `ui-unit-tests`: provider_info_helpers.test.tsx asserts every provider maps to
  a bundled logo *except* a known-logoless set. ClinePass intentionally ships no
  logo -- the `<Logo>` component falls back to a first-letter circle rather than
  an invented asset -- so it belongs in that set, not in the logo map.
- `lint`: `ruff format` on litellm/main.py and the endpoint-guard test.

Not fixed here, because it cannot be: `documentation` and `code-quality` both
fail with "Environment variables read under ./litellm but mentioned nowhere in
the docs: ['CLINEPASS_API_BASE', 'CLINEPASS_API_KEY']". That check resolves docs
by checking out BerriAI/litellm-docs into docs/my-website (the test raises
"check out BerriAI/litellm-docs into docs/my-website" when the directory is
absent), so those two keys stay undocumented until a companion PR lands there.
It is a genuine cross-repo ordering dependency, not something this branch can
satisfy alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two lint failures, both mine, found by CI rather than locally because I had been
running ruff from the repo root while the job runs it from `litellm/` and with
`ruff-tests.toml` for the test tree.

- PIE790: `ClinePassException` had a `pass` after its docstring.
- PT011 x2: `pytest.raises(Exception)` is too broad. Replaced with an explicit
  try/except that is also more precise about the contract: which exception
  litellm raises for an unsupported endpoint is its business and may change,
  whereas "nothing was transmitted" is what the test exists to prove. The
  fixture's own AssertionError (meaning the network WAS reached) is re-raised
  rather than swallowed as "some exception happened", which the previous form
  only caught via an isinstance check afterwards.

43 tests still pass; `ruff check` clean under both of the configurations CI uses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `lint` job's failing step was not plain ruff but
`scripts/ruff_strict_gate.py`, a strict-rule budget gate that compares totals
against the base commit: "ANN401: total 361 over limit 119 (this change added
2)", pointing at transformation.py:192 and :197.

Those two were `logging_obj: Any` and `encoding: Any`. The base class already
types them (`litellm/llms/base_llm/chat/transformation.py:350,355`), so they now
use `LiteLLMLoggingObj` and `"Tokenizer | None"`, imported under TYPE_CHECKING
with an `Any` fallback exactly as litellm/llms/openai/chat/gpt_transformation.py
does -- importing litellm_logging at runtime from a provider module risks a
circular import, which is presumably why that pattern exists.

This was also codex review finding BerriAI#6, which said to "use the existing
logging/tokenizer types as current CometAPI and Perplexity transformations do".
I had read that as satisfied by the builtin-generics work and it was not.

`scripts/ruff_strict_gate.py --base upstream/main` now reports "OK: every strict
rule is within its codebase ceiling"; ruff clean under both CI configurations;
43 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two upstream guards did not know about ClinePass, and both caught something real.

`test_every_cost_map_provider_is_registered[main|backup]`: its failure message
says exactly what a new provider owes — "a `<provider>_models` set in
litellm/__init__.py, filled in _populate_provider_model_sets and listed in
_build_models_by_provider". Adding the cost-map entry without that leaves the
provider's models unreachable through `models_by_provider`. Wired at all four
sites, mirroring `deepseek`. Verified with LITELLM_LOCAL_MODEL_COST_MAP=True,
since `model_cost` is fetched remotely by default and the remote map naturally
has no clinepass yet: `clinepass_models` and `models_by_provider["clinepass"]`
both resolve to `clinepass/deepseek-v4-flash`.

`test_a_provider_without_a_handler_maps_by_the_upstream_status[clinepass-403]`:
expected PermissionDeniedError, got APIError. The test derives its
no-handler list as `LlmProviders - PROVIDERS_WITH_A_HANDLER - aliases -
openai_compatible_providers`. Removing clinepass from
`openai_compatible_providers` (the P1 fix) dropped it into the no-handler bucket
while the explicit `_map_openai_exception` registration gave it a handler, so the
test's model of reality went stale rather than the code being wrong. `clinepass`
now sits in `PROVIDERS_WITH_A_HANDLER` beside `mistral` and `runwayml` — the two
providers whose registration shape this change copied.

Not addressed, and not mine: `proxy-infra`'s
test_every_model_in_the_prisma_schema_is_a_renderable_span_table. This branch
touches no prisma schema, span, or proxy-db file — the full changed-file list is
the clinepass provider, its tests, the two cost maps, the provider-support JSONs,
the UI helpers and README.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
clinepass is mapped by _map_openai_exception, the same path as openai,
mistral and runwayml, so a full context window and a content-policy block
reach the caller as ContextWindowExceededError / ContentPolicyViolationError.
The per-provider tests added upstream pin that set, and clinepass was
missing from it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
_complete_clinepass fell back to litellm.api_key when no ClinePass credential
was configured, so a caller's general-purpose (usually OpenAI) key was sent to
the Cline host as a Bearer token. get_llm_provider already resolves the key
without that fallback; use what it resolved. Adds a regression test that
asserts the sentinel key never leaves the process, and checks the request
actually reached the transport so it cannot pass vacuously.

Also suppress three basedpyright reportUnknownArgumentType diagnostics this
change introduced (the untyped model cost map key and the dispatch context's
messages/headers), which tipped the per-rule budget by +3.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The docstrings said BaseLLMHTTPHandler never unwraps extra_body and that it
would reach the API verbatim. It pops extra_body and merges it back into the
request body, so the wire body is identical whether or not clinepass is in
openai_compatible_providers, and the body-level assertions cannot detect that
drift. Say so, and point at the checks that do: the structural membership test,
the transcription half, and the params-level test (which fails when the
provider is listed, because the kwarg lands nested under extra_body).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@djbclark
djbclark force-pushed the clinepass-upstream branch from 5068b9a to 08408fe Compare October 3, 2026 19:33
djbclark and others added 2 commits October 3, 2026 15:45
…ine budget

The mutable-collection annotations in the ClinePass chat config are all dictated
by the OpenAIGPTConfig signatures they override (and the request body handed to
its transform_request). Mark each with the repo's own '# mutable-ok: matches the
dict-typed base-class signature' so the change adds none to the LIT001 budget.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…der policy

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread litellm/llms/clinepass/chat/transformation.py Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread litellm/responses/streaming_iterator.py
djbclark and others added 2 commits October 4, 2026 06:08
…n routing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolve conflicts with upstream's public-name refactor:
- get_llm_provider_logic: call ClinePassConfig().get_openai_compatible_provider_info,
  matching the renamed Perplexity call beside it.
- main: _complete_http_provider takes CompletionDispatchContext.
- ClinePassConfig gains the public get_openai_compatible_provider_info forwarder
  that upstream added to every other config defining the private one.
- test_exception_mapping_utils: clinepass maps 402 through _map_openai_exception
  like openai/mistral, so it joins their APIError deviation for 402.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@djbclark
djbclark requested a review from kerry-berri as a code owner October 8, 2026 15:22
Upstream's strict ruff gate bans typing.Any (TID251) and fails when the total
grows past the merge base. The alias is only meaningful to type checkers, so
the runtime fallback can be object.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The merge of upstream/main brought the LIT010/LIT011 gates and a per-rule
basedpyright ceiling. Declare ClinePass locals Final, stop rebinding
parameters, replace bare dict annotations with dict[str, object], and parse
the response envelope through a pydantic TypeAdapter instead of Any.
@djbclark

Copy link
Copy Markdown
Author

The only red check here is code-quality, and its single failing step is documentation_test_env_keys. That test reads its docs from a checkout of BerriAI/litellm-docs, not from this repository, so no change here can make it pass. It needs the companion docs PR, djbclark/litellm-docs#2049, which documents CLINEPASS_API_KEY and CLINEPASS_API_BASE, to merge first. Once it has merged, re-running this job is enough; the checkout re-clones litellm-docs main at run time. The latest push also merges current upstream/main, which clears the separate rust-test failure

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants