Commit graph

54644 commits

Author SHA1 Message Date
debanshd
1b1ddef1b2
fix: preserve non-null anyOf branches when stripping null types for Vertex (#41683)
* fix: preserve non-null anyOf branches when stripping null types for Vertex

* refactor: build anyOf branches immutably and drop explanatory comments

* style: apply ruff format

* fix: annotate anyOf list construction for the LIT002 budget

* fix(vertex_ai): send a null-only anyOf as one nullable null instead of raising

* test(vertex_ai): pin empty anyOf passthrough

* fix(vertex_ai): forward a non-list anyOf unchanged and add wire cells for the null fold

* fix(vertex_ai): forward a non-object anyOf branch for Google to judge instead of crashing

* fix(vertex_ai): fold null out of type arrays and union branches before sending to Gemini

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-16 08:00:48 +00:00
devin-ai-integration[bot]
4e49394ade
fix(anthropic): keep trailing user messages with None or empty list content (#41397)
* fix(anthropic): keep user messages with None or empty list content so requests do not end on an assistant turn

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(anthropic): avoid mutable empty-list construction in sanitizer

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(anthropic): avoid rebinding sanitizer input

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(anthropic): keep a trailing user turn whose content key was dropped

litellm.completion strips None fields from every message before the provider
transform, so a user message sent with content null reaches the Anthropic
converter with no content key and raised KeyError. Read the content with get
so that turn gets the same placeholder as an empty list.

* fix(anthropic): only fill user turns with empty content and keep their cache_control

* fix(anthropic): fill each empty user message in place like an empty string

* fix(anthropic): add the placeholder only for an empty turn or a cache breakpoint, in linear time

* test(anthropic): cover the tool-result guard with cache_control and widen the linear-time bound

* test(anthropic): cover empty user turns through the proxy on chat, responses and messages

* test(anthropic): type the empty user turn tests and move the linear-time check to a benchmark

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-16 07:41:05 +00:00
yucheng-berri
f281f77bc0
fix(guardrails): record every content filter match in logging_only and offer the mode in the UI (#46673)
* test(guardrails): pin content filter logging_only matches and UI modes in an integration test

* fix(guardrails): record every content filter match in logging_only and offer the mode in the UI

* style(tests): format the content filter logging_only integration test

* refactor(guardrails): keep the logging_only predicate and scan override within the type gates

* fix(guardrails): flag tool arguments a content filter would reject in logging_only and keep scanning

* test(integration): expect guardrail_flagged for an observe-only content filter block in the scope config test

* test(integration): pin logging_only flags for tool arguments an enforcing content filter rejects

* refactor(guardrails): drop the content filter flagged branch no logging_only scan can reach

* test(integration): pin OTel spans, Guardrails Monitor and a provider outage burst for a logging_only content filter

* test(integration): pin MCP tool call verdicts and fail the Monitor cell when usage never rolls up

* test(integration): pin category and competitor intent verdicts and the provider failure row in logging_only

---------

Co-authored-by: yucheng-berri <293926326+yucheng-berri@users.noreply.github.com>
2026-10-16 00:37:03 -07:00
moyai-devin-berriai[bot]
a3251a3bfd
fix(responses): preserve provider request IDs in streaming failure logs (#45453)
* fix(responses): preserve provider request IDs in streaming failure logs

* fix(bedrock): log the provider request id for in-stream errors on bridged Bedrock streams

Bedrock models that run through the chat-completions bridge (Claude,
converse, invoke) raise in-stream failures from the event-stream decoder
as a BedrockError without headers, so their failure logs kept
error_provider_request_id null. The decoder now passes the stream's
request-id headers to those errors.

Both the native Responses iterator and the decoder pass only the
PROVIDER_REQUEST_ID_HEADERS subset, so error responses and failure logs
pick up the request id and none of the 200 response's other headers.
MidStreamFallbackError gets its response at construction instead of
mutating it afterwards.

---------

Co-authored-by: moyai-devin-berriai[bot] <336287033+moyai-devin-berriai[bot]@users.noreply.github.com>
Co-authored-by: yucheng-berri <293926326+yucheng-berri@users.noreply.github.com>
2026-10-16 00:29:11 -07:00
Mateo Wang
b21e659c24
fix(proxy)!: count endpoints return errors in their source API shape instead of silent local counts (#46815)
* fix(count_tokens): convert Anthropic-format messages for the OpenAI counter the way inference does

* test(count_tokens): check /v1/messages/count_tokens sends OpenAI the input /v1/messages inference sends

* fix(count_tokens): build the Anthropic count request without a cast and sync dashboard API types

* fix(count_tokens): count Anthropic-format requests for Gemini the way /v1/messages inference converts them

* fix(count_tokens): return a 400 naming Anthropic blocks the OpenAI and Gemini count can't convert

* test(count_tokens): compare tools by value in the public api test

* fix(count_tokens): reject unconvertible blocks nested inside a tool_result

* fix(count_tokens): count Anthropic bodies the way /v1/messages inference sends them to OpenAI

Unwrap affinity-encoded reasoning, count the structured-output schema, flatten replayed web search results, join a list system prompt, and answer a message without a role with a 400 before calling OpenAI. messages_format is now internal to the Anthropic count route instead of a public /utils/token_counter field.

* test(count_tokens): expect the image detail the adapter now sends after merging main

* fix(count_tokens): fetch Gemini remote media without blocking the event loop

* fix(count_tokens): type normalize_native_messages without the unimported Any

On Python 3.10 to 3.13 the annotation is evaluated at import, so the proxy failed to start with NameError

* chore(count_tokens): give the Gemini count helpers' list returns a reason

* fix(count_tokens): normalize Anthropic messages for Gemini counts the way /v1/messages does

* fix(count_tokens): count Gemini bodies after inference's history rewrites and answer converter errors with 400

* test(count_tokens): cover the Gemini count error paths and compaction with a text-block system

* fix(proxy): count endpoints return missing-key and request errors in their source API shape

* fix(proxy): drop imports the count error change left unused

* fix(proxy): let Entra-authenticated Azure AI Claude count tokens and keep Google count errors in the Google envelope

* fix(proxy): send Azure environment keys as x-api-key on Azure AI Claude counts and log unexpected Google count failures

* fix(proxy): count with the global OpenAI and Anthropic keys chat already uses

* fix(proxy): resolve the OpenAI count base with the key so a global key stays on its gateway

* fix(proxy): keep unexpected Google count failures generic so internal details stay out of the response

* fix(proxy): resolve the Azure AI Claude count base from the same settings chat reads

* fix(proxy): count OpenAI tokens with the workload identity bearer chat already uses

* fix(proxy): mint the OpenAI workload identity bearer inside the count error handling

* fix(proxy): report a failed OpenAI workload identity exchange on the count endpoints as a 401

---------

Co-authored-by: Mrinal Chanshetty <mrinal@berri.ai>
2026-10-16 00:11:39 -07:00
David Bochenski
f072498304
fix(bedrock): grant sts:GetWebIdentityToken and sts:TagGetWebIdentityToken in web identity session policy for claude_platform (#46670)
* fix(bedrock): grant sts:GetWebIdentityToken in web identity session policy for claude_platform

Claude Platform on AWS calls STS GetWebIdentityToken (outbound identity
federation) with the caller's session before serving inference. The
session policy passed to AssumeRoleWithWebIdentity is a permission
ceiling and never granted it, so every bedrock/claude_platform request
using OIDC web identity auth failed with 403 "no session policy allows
the sts:GetWebIdentityToken action", even when the role's identity
policy allowed it.

Add a ClaudePlatformOutboundIdentityLiteLLM statement granting only
sts:GetWebIdentityToken, plus tests that it is granted, scoped to its
own statement, and that no sts:* wildcard is introduced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(bedrock): also grant sts:TagGetWebIdentityToken for claude_platform web identity

Claude Platform on AWS tags the outbound web identity token it requests, so
the caller also needs sts:TagGetWebIdentityToken. Found end-to-end on a
deployment: with only GetWebIdentityToken granted, calls failed with
"not authorized to perform: sts:TagGetWebIdentityToken on resource:
arn:aws:sts::<account>:self"; granting both made /v1/chat/completions and
/v1/messages (streamed and not) succeed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(bedrock): only let Claude Platform use the outbound identity STS grant

Scope the ClaudePlatformOutboundIdentityLiteLLM session policy statement with aws:CalledViaLast = aws-external-anthropic.amazonaws.com, so a web identity session can no longer call sts:GetWebIdentityToken or sts:TagGetWebIdentityToken directly. Drop the code comment and fix the stale test messages.

* test(bedrock): fail when the Claude Platform caller condition reaches other statements

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-16 06:37:49 +00:00
Michelangelo Setaro
c288dc7da0
fix(router): let an exact base_model widen a deployment's context window (#46197)
* fix(router): honor explicit bedrock base model capacity

* fix(router): honor an exact base_model's capacity for every non-Azure provider

Extends the Bedrock-only check to every non-Azure provider and only uses the
base_model when it has its own exact cost-map entry, so an unmapped or wildcard
base_model falls back to the route id instead of raising in the router.

* test(router): call _maps_to_exact_model_info directly in its cost-map tests

* fix(router): let an exact base_model only widen a deployment's context window

A base_model used as a pricing stand-in with a smaller window no longer narrows the route's limit, and a route with no window never takes one from base_model

* test(router): count the base_model window helpers as covered through get_router_model_info

* fix(router): widen only from a model_info base_model naming the same model

A stand-in base_model for a different, larger model widened a smaller
deployment's window, so pre-call checks sent it prompts the provider
rejects with 400. The window now widens only when model_info.base_model
names the same provider model id as the route, which is also the field
/model/info reads.

* fix(router): match a base_model's model id across Bedrock region prefixes and @ qualifiers

* fix(router): read litellm_params.base_model too and look the base row up as written

* test(router): cover a same-model base_model window on chat, messages, responses, and complexity escalation

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-16 06:37:40 +00:00
Dan Lemon
c8b73cde84
feat(soniox): decode proxy form fields for context, translation and flags (#40605)
* feat(soniox): decode proxy form fields for context, translation and flags

Multipart form fields reach the proxy as strings, so Soniox received
context and translation as JSON strings and booleans as "true".
Decode them at the handler entry point and add typed shapes for the
context and translation request fields.

* fix(soniox): keep bracket-prefixed free-form context and move types under llms

Free-form context such as "[inaudible] consultation" is valid Soniox
input, so a string that fails JSON parsing is now forwarded unchanged
instead of returning a 400. The Soniox request types live next to the
provider code.

* test(soniox): exercise SDK-typed context and translation shapes through the decoder

* refactor(soniox): derive handler options and provider params without mutating a local dict

* chore(soniox): drop explanatory comment on the non-forwarded param set

* refactor(soniox): return invalid form bool as a value and raise once in the handler

* fix(soniox): validate decoded form fields and keep language with hints

Validate structured context before sending it as an object, answer 400 for a
bad polling interval or flag unless drop_params is on, nest SDK bracketed
extra_body fields, keep the OpenAI language at the head of language_hints, and
return the translated text for one-way translation without tags.

* fix(soniox): keep the source transcript, rebuild SDK list fields, and reject malformed JSON values

* fix(soniox): keep cleanup strings whole and drop the unused translation type

* fix(soniox): let explicit language_hints win over language and test the proxy form path

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-16 06:37:34 +00:00
yucheng-berri
cda19d6a29
test(integration): wait for the gen-AI span before reading the excluded-services operator trace (#46846)
The operator trace wait stopped at the root and redis spans, but the gen-AI
span is closed later by the async success callback. When it had not landed
yet, the tenant mirror was built without it and the gen-AI assertion failed.

Co-authored-by: yucheng-berri <293926326+yucheng-berri@users.noreply.github.com>
2026-10-15 22:35:23 -07:00
yucheng-berri
b5870df967
fix(bedrock-passthrough)!: return AWS-shaped errors so AWS SDKs read the code and message (#46522)
* fix(bedrock-passthrough): return AWS-shaped errors so AWS SDKs read the code and message

A guardrail block on a /bedrock route now answers with x-amzn-errortype ValidationException and a top-level message, and an error Bedrock returned (such as a 429 ThrottlingException) is passed through with its status, x-amzn-errortype and body

* fix(bedrock-passthrough): keep 5xx guardrail failures and failure-hook replacements unchanged

* test(bedrock-passthrough): guardrail vendor failures and unknown models keep the litellm error

* fix(bedrock-passthrough): only answer guardrail exceptions that blocked content as ValidationException

* fix(bedrock-passthrough): pass Bedrock's content-type through with its error body

* refactor(bedrock-passthrough): wrap the guardrail ProxyException instead of copying its fields

Unit tests drive the error response with the real request processor and ProxyLogging

* test(bedrock-passthrough): cover the direct model route, invoke, and auth, budget and rate-limit errors

* fix(bedrock-passthrough): pass through only errors a Bedrock model operation returned

A guardrail service's HTTP error that carries x-amzn-errortype keeps the litellm error

* refactor(bedrock-passthrough): mark Bedrock's errors where the passthrough raises them instead of matching the URL

A guardrail service's HTTP error never goes through the passthrough call, so it keeps the litellm error whatever its URL

---------

Co-authored-by: yucheng-berri <293926326+yucheng-berri@users.noreply.github.com>
2026-10-15 22:05:46 -07:00
yuneng-jiang
0620f1a515
fix(ui): validate the Add Model form when Test Connect is clicked (#46834)
* fix(ui): gate Add Model's Test Connect on the form's own validation

Test Connect stays disabled until the Add Model form would pass validation,
with a tooltip saying why. Provider password, text and textarea inputs now
render empty after a successful add resets the form, and an empty prepared
request shows a plain message instead of a destructuring crash

* fix(ui): validate the Add Model form when Test Connect is clicked

Test Connect stays enabled and, on click, runs the same validation Add Model
runs. An incomplete form now shows the inline required errors instead of
opening a crashing connection test, and a valid form tests as before. This
replaces the disabled-button gate, which read a validity flag that went stale
after programmatic form updates

* test(ui): cover a blank required Azure API Base on Test Connect
2026-10-16 04:44:58 +00:00
Kerry Lu
cb7e6a7f4c
test(e2e): mixed_size_requests_2rps perf case (coding-agent bodies 5k to 1m tokens with tools, JWT and key callers, two guardrails) (#46717)
Some checks failed
unit / caching-local (push) Blocked by required conditions
unit / core-utils (push) Blocked by required conditions
unit / assert-shard-coverage (push) Waiting to run
unit / enterprise-managed-files (push) Blocked by required conditions
unit / enterprise-repositories-secrets (push) Blocked by required conditions
unit / proxy-server (push) Blocked by required conditions
unit / lens-python-310 (push) Blocked by required conditions
unit / endpoints (push) Blocked by required conditions
unit / enterprise-package (push) Blocked by required conditions
unit / integrations (push) Blocked by required conditions
unit / ui-unit (push) Waiting to run
unit / llms-anthropic (push) Blocked by required conditions
unit / llms-bedrock (push) Blocked by required conditions
unit / llms-openai-meta (push) Blocked by required conditions
unit / llms-providers (push) Blocked by required conditions
unit / llms-vertex-ai (push) Blocked by required conditions
unit / docs (push) Waiting to run
unit / helm (push) Waiting to run
unit / OpenAI 2.20.0 compatibility (push) Waiting to run
unit / OpenAI 3.0.0 compatibility (push) Waiting to run
unit / OpenAI 3.25.0 compatibility (push) Waiting to run
unit / redis-py 5.3.1 compatibility (push) Blocked by required conditions
unit / redis-py 6.4.0 compatibility (push) Blocked by required conditions
GitHub Actions Security Analysis / zizmor (push) Waiting to run
unit / redis-py 7.4.1 compatibility (push) Blocked by required conditions
unit / redis-py 8.0.1 compatibility (push) Blocked by required conditions
unit / coverage (push) Blocked by required conditions
unit / unit passed (push) Blocked by required conditions
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
* test(e2e): mixed_size_requests_2rps perf case (coding-agent bodies, JWT and key callers, two guardrails)

* test(e2e_harness): test_observe builds DbCounters with statements and expects the background Postgres line

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-16 03:35:35 +00:00
devin-ai-integration[bot]
5570d4268c
fix(proxy): only release the legacy limiter's slot from an admitted Responses WebSocket (#46822)
The shared disconnect release dispatched to the legacy limiter, which decrements unconditionally, so a rejected realtime attempt freed a slot it never took. The generic release is v3-only again and the Responses WebSocket close releases through its own method

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-15 20:34:48 -07:00
berriai-litellm-provider-info-sync[bot]
e74e4e5693
fix(vertex-ai): drop cached flex price from gemini 3 preview rows (#46826)
Price-Sync: litellm-providers

Co-authored-by: berriai-litellm-provider-info-sync[bot] <328147090+berriai-litellm-provider-info-sync[bot]@users.noreply.github.com>
2026-10-15 20:31:10 -07:00
Mateo Wang
79ca820933
fix(count_tokens): return a 400 naming Anthropic blocks the OpenAI and Gemini count can't convert (#46772)
* fix(count_tokens): convert Anthropic-format messages for the OpenAI counter the way inference does

* test(count_tokens): check /v1/messages/count_tokens sends OpenAI the input /v1/messages inference sends

* fix(count_tokens): build the Anthropic count request without a cast and sync dashboard API types

* fix(count_tokens): count Anthropic-format requests for Gemini the way /v1/messages inference converts them

* fix(count_tokens): return a 400 naming Anthropic blocks the OpenAI and Gemini count can't convert

* test(count_tokens): compare tools by value in the public api test

* fix(count_tokens): reject unconvertible blocks nested inside a tool_result

* fix(count_tokens): count Anthropic bodies the way /v1/messages inference sends them to OpenAI

Unwrap affinity-encoded reasoning, count the structured-output schema, flatten replayed web search results, join a list system prompt, and answer a message without a role with a 400 before calling OpenAI. messages_format is now internal to the Anthropic count route instead of a public /utils/token_counter field.

* test(count_tokens): expect the image detail the adapter now sends after merging main

* fix(count_tokens): fetch Gemini remote media without blocking the event loop

* fix(count_tokens): type normalize_native_messages without the unimported Any

On Python 3.10 to 3.13 the annotation is evaluated at import, so the proxy failed to start with NameError

* chore(count_tokens): give the Gemini count helpers' list returns a reason

* fix(count_tokens): normalize Anthropic messages for Gemini counts the way /v1/messages does

* fix(count_tokens): count Gemini bodies after inference's history rewrites and answer converter errors with 400

* test(count_tokens): cover the Gemini count error paths and compaction with a text-block system

---------

Co-authored-by: Mrinal Chanshetty <mrinal@berri.ai>
2026-10-16 03:19:55 +00:00
devin-ai-integration[bot]
91ae7d1b7d
fix(cost): bill Responses API image generation and usage reported web search as tool usage cost (#41877)
* fix(cost): bill Responses API image_generation_call tool usage

Price completed image_generation_call output items from tool_usage.image_gen token counts when present, otherwise per image from the quality and size on the item, with the image model taken from tools[].model (default gpt-image-1). Add the result into tool_usage_cost alongside web search and file search, and read tool_usage from the top level or usage.tool_usage. Squashed and rebased onto main

* style(cost): annotate read-only views and mark casts for the type discipline gate

* fix(cost): price Responses image tool from tools[].model only, no default model or provider

* refactor(cost): drop redundant status check, unread usage fields and unused output_type literal

* test(cost): integration case for Responses image_generation tool token billing

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-15 20:18:17 -07:00
Kerry Lu
34d724c609
test(e2e): perf datastore limits count the Postgres statements and Redis commands a request waits on, background work reported on its own (#46798)
* test(e2e): perf datastore limits count what a request waits on, background Postgres and Redis work reported on its own

postgres_calls_per_request and redis_calls_per_request were whole-server totals (pg_stat_database commits, Redis INFO
commands), so spend flushes and config reloads moved them and a new per-request lookup could hide in the noise. They
now count the statements and commands a request waits on before it is forwarded (auth and routing reads, the
rate-limiter script), from pg_stat_statements read before and after the run and a Redis MONITOR connection held
during it. Spend writes, daily aggregates, config reloads and cache invalidations are postgres_background_calls_per_request
and redis_background_calls_per_request. The run output lists the top statements and commands per bucket, and the
counters are read right after the run so the test's own spend sample is not counted

* test(e2e): perf datastore reads only the runtime user's pg_stat_statements rows, other users' rows have a NULL queryid

* test(e2e): perf Redis classifier strips the litellm.caching: namespace the deployed gateway puts on every key

* test(e2e): small_bodies_5rps limits from ten split-metric runs (postgres awaited 1.8, redis awaited 18, postgres background 12.5), spend buffers and cron locks classified as background

* test(e2e): small_bodies_5rps asserts only the awaited Postgres and Redis counts, background lines stay printed

* test(e2e): perf Redis classifier puts the cron lock script's GET and DEL in background, not awaited

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-15 20:10:17 -07:00
Kerry Lu
081cfa2a62
fix(proxy): reset end-user spend in committed pages so the budget cascade survives a large LiteLLM_EndUserTable (#46502)
* fix(proxy): reset end-user spend in committed pages so the budget cascade survives large LiteLLM_EndUserTable

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* style(proxy): collapse _enduser_ids_by_tier return to one line for ruff format

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy): mark the end-user page IN list as bounded for the unbounded-IN check

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-15 19:47:12 -07:00
Mateo Wang
dba86bec28
fix(count_tokens): count Anthropic-format requests for Gemini the way /v1/messages inference converts them (#46767)
* fix(count_tokens): convert Anthropic-format messages for the OpenAI counter the way inference does

* test(count_tokens): check /v1/messages/count_tokens sends OpenAI the input /v1/messages inference sends

* fix(count_tokens): build the Anthropic count request without a cast and sync dashboard API types

* fix(count_tokens): count Anthropic-format requests for Gemini the way /v1/messages inference converts them

* test(count_tokens): compare tools by value in the public api test

* fix(count_tokens): count Anthropic bodies the way /v1/messages inference sends them to OpenAI

Unwrap affinity-encoded reasoning, count the structured-output schema, flatten replayed web search results, join a list system prompt, and answer a message without a role with a 400 before calling OpenAI. messages_format is now internal to the Anthropic count route instead of a public /utils/token_counter field.

* test(count_tokens): expect the image detail the adapter now sends after merging main

* fix(count_tokens): fetch Gemini remote media without blocking the event loop

* fix(count_tokens): type normalize_native_messages without the unimported Any

On Python 3.10 to 3.13 the annotation is evaluated at import, so the proxy failed to start with NameError

* chore(count_tokens): give the Gemini count helpers' list returns a reason

* fix(count_tokens): normalize Anthropic messages for Gemini counts the way /v1/messages does

* fix(count_tokens): count Gemini bodies after inference's history rewrites and answer converter errors with 400

* test(count_tokens): cover the Gemini count error paths and compaction with a text-block system

---------

Co-authored-by: Mrinal Chanshetty <mrinal@berri.ai>
2026-10-16 02:32:24 +00:00
Mateo Wang
dfb949d437
fix(terraform)!: send additional_drop_params to the proxy instead of deleting the keys it names (#46782)
* fix(terraform): send additional_drop_params to the proxy instead of deleting the keys it names

litellm_model treated additional_drop_params in additional_litellm_params as a
provider-side delete list: it never sent the list and removed every key it named
from litellm_params, so vertex_location vanished and requests still carried
store. The list now goes to the proxy like any other JSON value, so the proxy
drops those params per request. Approach credited to #38345.

Fixes #38349

* fix(terraform)!: validate additional_drop_params and read it back from the proxy on refresh

* docs(terraform): note that a non-list additional_drop_params now fails the apply

* fix(terraform)!: check additional_drop_params at plan time and refresh listed keys and outside lists

* fix(terraform)!: show every listed key an older provider deleted in the upgrade plan and leave outside drop lists alone

Refresh no longer reads listed keys back from the proxy, which left a
plan diff that never cleared when a listed key was removed from config.
When the proxy has no stored list, the plan now shows the list and each
listed key the config sets as changes. A drop list set outside Terraform
on a model whose config has none stays out of the plan, and an empty
value now fails the plan

* fix(terraform): show a listed merge_reasoning_content_in_choices in the upgrade plan

The refresh now clears merge_reasoning_content_in_choices when it is listed and the proxy has no stored list, since an older provider deleted it, and no longer clears litellm_credential_name, which the older provider always sent
2026-10-16 02:24:47 +00:00
Nate Armstrong
f13d0430e8
test(e2e): attach perf case metrics and limits as JUnit properties (#46774)
* test(e2e): attach perf case metrics and limits as JUnit properties

The perf case only printed its metrics table, so a green run left no numbers
behind. Each observed metric and its limit now land as <property> entries on
the test's JUnit testcase, named perf_<metric>[_p<percentile>] and
perf_<metric>[_p<percentile>]_limit, so project-releaser can collect them

* test(e2e): compare perf properties exactly instead of through pytest.approx

basedpyright flags pytest.approx as partially unknown in the e2e lint step,
so the harness test now uses metric values that are exact in binary floating
point
2026-10-15 19:12:07 -07:00
Emmanuel Acheampong
de4d28653d
fix(crusoe): update base URL to current endpoint (#33121)
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-15 19:10:58 -07:00
Kerry Lu
5ed3629242
fix(responses): write one spend-log row per native WebSocket turn (#46768)
* fix(responses): write one spend-log row per native WebSocket turn

Each response.create turn on a native Responses WebSocket connection logs and bills as its own call when its terminal event arrives, keyed by the resp_ id the client saw, instead of one row at socket close that sums every turn

* test(responses): mock the websocket slot release and mark the per-turn start time naive

* style(responses): keep the per-turn start time line within ruff format

* refactor(responses): type websocket turn events as read-only mappings

* fix(responses): settle the websocket budget reservation on the first turn only

Every per-turn logging copy carried the connection's admission reservation, so two turns whose cost callbacks overlapped could both reconcile it. The first turn keeps the reservation and later turns charge their cost directly

* fix(proxy): keep the legacy limiter's websocket slot until disconnect

A websocket turn callback no longer decrements current_requests in the legacy limiter. The socket's slot is released once from arelease_max_parallel_requests_on_disconnect, which now dispatches to the legacy handler as well

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-15 19:05:22 -07:00
yuneng-jiang
cf1e6b19ab
fix(sso): share SAML sign-in state over Redis without enable_redis_auth_cache (#46807)
* fix(sso): share SAML sign-in state over Redis without enable_redis_auth_cache

SAML login tickets, the single-use assertion mark and cached IdP metadata
lived in user_api_key_cache, which only gets Redis when
enable_redis_auth_cache is on. With several replicas or workers the IdP
callback often landed on a process that never saw the ticket and got a
401, and IdP-initiated sign-in was refused even with Redis configured.

SAML state now has its own DualCache that _attach_redis_usage_cache
always wires to Redis, like the CLI SSO session cache, and the SAML cache
calls are async.

* test(sso): drop docstring from the SAML proxy pair fixture

* test(sso): cover the SAML login and metadata endpoints
2026-10-16 02:01:04 +00:00
Mateo Wang
d25b58f744
fix(count_tokens): count Anthropic-format requests for OpenAI the way /v1/messages inference converts them (#46759)
* fix(count_tokens): convert Anthropic-format messages for the OpenAI counter the way inference does

* test(count_tokens): check /v1/messages/count_tokens sends OpenAI the input /v1/messages inference sends

* fix(count_tokens): build the Anthropic count request without a cast and sync dashboard API types

* test(count_tokens): compare tools by value in the public api test

* fix(count_tokens): count Anthropic bodies the way /v1/messages inference sends them to OpenAI

Unwrap affinity-encoded reasoning, count the structured-output schema, flatten replayed web search results, join a list system prompt, and answer a message without a role with a 400 before calling OpenAI. messages_format is now internal to the Anthropic count route instead of a public /utils/token_counter field.

* test(count_tokens): expect the image detail the adapter now sends after merging main

* fix(count_tokens): type normalize_native_messages without the unimported Any

On Python 3.10 to 3.13 the annotation is evaluated at import, so the proxy failed to start with NameError

* test(count_tokens): expect the converted Responses input in the budgeted-key count integration test

---------

Co-authored-by: Mrinal Chanshetty <mrinal@berri.ai>
2026-10-16 01:58:46 +00:00
yucheng-berri
627bcab42b
fix(guardrails): honor streaming settings on the Noma v2 guardrail (#46606)
* fix(guardrails): honor streaming settings on the Noma v2 guardrail

The Noma v2 initializer now passes streaming_buffer_until_moderated,
streaming_end_of_stream_only, streaming_sampling_rate and
streaming_buffer_release_on_scan from the guardrail config to
NomaV2Guardrail, so the unified streaming hook applies them. The
constructor validates them, so a custom guardrail class built on
NomaV2Guardrail gets the same checks. Unset keys keep the hook defaults,
and an invalid value is ignored with a warning naming the guardrail, so
the guardrail still loads.

Resolves LIT-9633

* test(guardrails): cover Noma v2 streaming settings across clients, failures and workers

Adds integration cells for the custom-subclass loader, string and invalid
values, unset and false keys, a Noma outage with and without block_failures,
an upstream rejection, n=2 choices, pre_call and non-streaming guardrails,
the OpenAI and Anthropic SDKs (sync and async), stored-guardrail info, a
PATCH while traffic flows, a burst while Noma is down, a slow Noma, and
replaced workers.

Resolves LIT-9633

* test(guardrails): pin the PATCH probes to one worker at a time

Each probe suspends the other worker and opens a fresh connection, so the
test checks that every worker applies the patched setting.

Resolves LIT-9633

* test(guardrails): check sampled scans by count, not window position

/v1/messages hands the streaming hook raw network reads, so where the
default sampling windows fall depends on timing; the base leg caught one
shifted window. Check the scan count and the final full-text scan instead.

Resolves LIT-9633

---------

Co-authored-by: yucheng-berri <293926326+yucheng-berri@users.noreply.github.com>
2026-10-15 18:29:05 -07:00
yucheng-berri
250e6bd24b
test(integration): build OpenAI and Anthropic SDK models before tests start threads (#46797)
K2 in test_logging_only_scope_chaos.py flaked on CircleCI with
AttributeError: type object 'BaseModel' has no attribute '__pydantic_core_schema__'
raised inside the OpenAI client while its 30-thread control burst parsed responses

The SDKs define their response models with deferred schemas, so the first parse of each
model rebuilds it, and pydantic's model_rebuild is not safe when several threads hit it
at once. Every integration worker now builds those schemas in pytest_configure, before
any test can start a thread pool

Co-authored-by: yucheng-berri <293926326+yucheng-berri@users.noreply.github.com>
2026-10-15 18:28:32 -07:00
Mateo Wang
b47f7a4712
fix(responses): preserve explicit websocket API keys (internal copy of #40853) (#46724)
* fix(responses): preserve explicit websocket API keys

Refresh the one-line fix from BerriAI/litellm#34253 against the current default branch

Co-authored-by: puffer <389053104@qq.com>

* test(responses): verify websocket authorization

Cover explicit deployment keys, precedence over global keys, and the existing global fallback at the upstream WebSocket transport boundary

* test(responses): type the authorization capture

Replace the mutable header list with a typed, final Future carrying the upstream authorization value, addressing the review on #40853

* Refactor test_openai_websocket_authorization method

* fix(responses): resolve websocket keys with the deployment's provider

Pass the deployment custom_llm_provider to get_llm_provider so an api_base
host never swaps in another provider's env key, and drop the OpenAI-only
global key fallbacks so non-OpenAI websocket deployments resolve their own
env keys the way the HTTP route does

* fix(responses): let a deployment's own websocket key beat env keys

A deployment with an api_base on a host litellm recognizes, and no declared
provider, got that host's env key ahead of its own api_key. The explicit key
now always wins, matching the HTTP Responses route. Drop the litellm_params
api_key fallback, which could never be set because api_key is a named argument

* fix(responses): keep openai deployments on other providers' known hosts on the HTTP stream

An openai deployment whose api_base is a known OpenAI-compatible provider
host (DeepSeek, Groq, ...) opened a native Responses WebSocket there and got
a 405, because those hosts serve /responses over HTTP only. Route it through
the managed HTTP stream with the deployment's own key instead.

* fix(responses): resolve openai deployments on known provider hosts as that provider

An openai deployment whose api_base is a known provider host (DeepSeek,
Mistral, ...) now resolves to that host's provider on the Responses
WebSocket again, as before this branch, so it keeps that provider's
chat completions bridge, pricing and env key fallback. Its own api_key
still wins over the env key. The known-host test now mocks the upstream
HTTP route and checks the route, the bearer and response.completed.

* test(responses): prove websocket deployment keys and declared providers reach the provider socket

* test(responses): use the reported bare model shape for the websocket deployment key cell

---------

Co-authored-by: Ysh <734645956@qq.com>
Co-authored-by: puffer <389053104@qq.com>
2026-10-16 01:28:12 +00:00
yujonglee
d66d8ac127
refactor(rust-bridge): let each route host pick the body params (#46787)
* refactor(rust-bridge): let each route host pick the body params

The native routes projected request body params from a name list, hard-coded in
Rust for Messages and read from `PARAMETERS` for Chat Completions and Responses.
Each route host now answers `body_params(request)` over the keywords the call would
send, so the Python side owns which params reach the provider. No behavior change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(rust-host): use read-only body parameter types

Co-Authored-By: yujonglee.dev@gmail.com <yujong@berri.ai>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-15 18:27:06 -07:00
yujonglee
55c8ec4647
refactor(rust-bridge): move the route host modules under host/routes (#46786)
* refactor(rust-bridge): move the route host modules under host/routes

Each native route's Python half lived in its own `<route>/route_host.py`, apart
from the shared host modules Rust also imports by name. They now sit together in
`litellm/rust_bridge/host/routes/<route>.py`, with their unit tests moved to the
mirrored path. No behavior change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(rust-host): reference messages route alias

Co-Authored-By: yujonglee.dev@gmail.com <yujong@berri.ai>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-15 18:27:06 -07:00
Mateo Wang
238597ed49
test(ui): send Connection: close on every integration browser request (#46805)
* test(ui): close each federation spec API connection so the proxy cannot drop an idle keep-alive socket

* test(ui): send Connection: close on every integration browser spec API call

* test(ui): send Connection: close on every integration browser request from the Playwright config
2026-10-16 01:11:57 +00:00
mrinal-berri
bd60cc8608
fix(bedrock): forward Nova Sonic realtime tool calls with their arguments (#46214) 2026-10-16 01:10:53 +00:00
berriai-litellm-provider-info-sync[bot]
123e2071bf
feat(wandb): add NVIDIA Nemotron 3.5 Super VL to the cost map (#46795)
Price-Sync: litellm-providers

Co-authored-by: berriai-litellm-provider-info-sync[bot] <328147090+berriai-litellm-provider-info-sync[bot]@users.noreply.github.com>
2026-10-15 17:53:56 -07:00
ryan-crabbe-berri
38f3f8a1a9
fix(proxy): cooldown errors carry 429, Sentry starts at boot without request bodies, tracing failures log at ERROR, exporter counters exported (#46344)
* fix(router): carry a 429 status on cooldown and no-deployment errors

RouterRateLimitError and RouterRateLimitErrorBasic had no status_code, so the
proxy answered an all-deployments-cooled-down request as a generic 500 class
error and logged the bug report notice for every cooldown. Both now carry
status_code 429, which the proxy maps to a rate_limit_error and which the
retry policy already treats as retryable

* fix(proxy): start Sentry at config load and never attach request bodies

Sentry only came up inside set_callbacks on the first LLM call, so boot and
background errors were never reported, and general_settings.alerting: [sentry]
always raised Missing SENTRY_DSN because the alerting path read a dead
litellm.utils pin instead of the live SDK. The proxy now initializes Sentry
once while loading the config whenever sentry is a configured callback or
alerting target, the alerting and failure handlers read the live instance, and
max_request_body_size is pinned to never so prompts and completions stay out
of Sentry events

* fix(tracing): log Lens failures at ERROR and export row counters to Prometheus

A tracing store that fails to start, or a Lens service that rejects a batch,
was only logged at WARNING, so operators running at the default ERROR level
never saw tracing silently go dark. Both now log at ERROR and name the lens
store. The exporter's rows_written and rows_dropped tallies were never
exported, so the two counts are now Prometheus counters
litellm_lens_export_rows_written_total and
litellm_lens_export_rows_dropped_total, registered lazily on the default
registry like the other proxy-level counters and charted on the all-metrics
Grafana dashboard

* test(proxy): drop the litellm import the alerting tests no longer use

* fix(proxy): start Sentry once a DSN is set, from any config source

Boot-time init used to lock Sentry in even with no SENTRY_DSN, so a DSN loaded later from DB environment_variables was never picked up. initialize_sentry now skips when no DSN is set and re-inits when the DSN changes. alerting_handler calls it too, so alerting: [sentry] set through the DB works. Drops the litellm_settings.callbacks check since that config fails to load with sentry in it

* fix(sentry): keep stack frame locals out of events unless PII is opted in

Frame locals carried prompts (messages, request bodies, provider options) into every error event. include_local_variables now follows SENTRY_SEND_DEFAULT_PII

* test(router): drop the test that only pinned the 429 class attribute

* fix(proxy): start Sentry at boot when it is only an input callback

* fix(proxy): keep Sentry init and alerting within the strict ruff and basedpyright ceilings

---------

Co-authored-by: yuneng-jiang <yuneng@berri.ai>
2026-10-16 00:53:52 +00:00
Yassin Kortam
b06f4345e4
fix(terraform): accept 2xx from the proxy in mcp server, model, key and org requests (#46550)
The proxy answers POST /v1/mcp/server with 201 and DELETE /v1/mcp/server/{id} with 202, but handleMCPAPIResponse, handleAPIResponse, Client.sendRequest and resourceLiteLLMMCPServerDelete treated every status other than 200 as a failure. terraform apply created the server on the proxy, then reported an error and never wrote the resource to state, so the next apply created a duplicate, and destroy failed on the 202.

Accept any 2xx in the four status checks, treat an empty or null 2xx body in handleAPIResponse as an empty ModelResponse, and cover each path with httptest tables that fail on the 200-only comparison.

Re-lands the diff from #40723 on current main.

Co-authored-by: matthew-hull-bright <matthew.hull@brightsg.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-16 00:40:24 +00:00
Mateo Wang
c6a4437e98
ci(cost map): classify token_count_framing and token_count_fallback_model in the schema generator (#46784)
* ci(cost map): classify token_count_framing and token_count_fallback_model in the schema generator

* ci(cost map): bound token_count_framing to non-negative and token_count_fallback_model to non-empty

* test(cost map): reject a fractional token_count_framing in the generated schema

* test(cost map): pin that a token_count_framing of 0 passes the generated schema
2026-10-16 00:30:12 +00:00
berriai-litellm-provider-info-sync[bot]
464bb5a1ca
chore(pricing): add github_copilot deprecation dates for claude-opus-5 and gpt-5.6-terra (#46792)
Price-Sync: litellm-providers

Co-authored-by: berriai-litellm-provider-info-sync[bot] <328147090+berriai-litellm-provider-info-sync[bot]@users.noreply.github.com>
2026-10-15 17:27:12 -07:00
tin-berri
9c62573005
fix(proxy): limit gzip to dashboard analytics and offload compression (#46712)
* fix(proxy): limit gzip to dashboard analytics and offload compression

* fix(proxy): preserve gzip for customer analytics aliases
2026-10-15 17:27:03 -07:00
berriai-litellm-provider-info-sync[bot]
6c9c3a9098
feat(cost-map): add baseten Muse Spark Open and Nemotron 3.5 Super VL (#46794)
Price-Sync: litellm-providers

Co-authored-by: berriai-litellm-provider-info-sync[bot] <328147090+berriai-litellm-provider-info-sync[bot]@users.noreply.github.com>
2026-10-15 17:25:54 -07:00
berriai-litellm-provider-info-sync[bot]
d387cdc816
fix(vertex-ai): end free period for deepseek-v4.1-flash-maas (#46793)
Price-Sync: litellm-providers

Co-authored-by: berriai-litellm-provider-info-sync[bot] <328147090+berriai-litellm-provider-info-sync[bot]@users.noreply.github.com>
2026-10-15 17:23:57 -07:00
devin-ai-integration[bot]
67bfb7a148
fix(ollama): prevent reused response ids from dropping spend logs (#39227)
* fix(spend_tracking): key completion spend logs off the per-call id so duplicate provider response ids cannot drop rows

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(spend_tracking): narrow the provider response id with isinstance instead of cast

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(gcs_bucket): store request payloads under the spend log request id so the logs viewer can find them

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(spend_tracking): validate callback fields for current type gates

* test(gcs_pubsub): assert provider response id in spend metadata

* fix(ollama): normalize reused response ids automatically

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-15 17:17:51 -07:00
Kerry Lu
5fd6379a78
test(e2e): perf users start staggered with jitter, tool-call streams get a first token, small_bodies_5rps sends from the mixed case's callers (#46776)
* test(e2e): perf streams take a reasoning or tool-call delta as the first token, tool-call replies had no time_to_first_token

* test(e2e): perf users start at a random offset and jitter each interval by up to 25%, no more lockstep bursts at the planned rate

* test(e2e): small_bodies_5rps sends from the mixed case's callers (two teams, JWT users, keys in and out of teams) through the pre and post guardrail

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-15 17:14:20 -07:00
Yassin Kortam
9d442b1300
fix(terraform): stop the perpetual plan diff on litellm_mcp_server (#46781)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-15 16:57:05 -07:00
yuneng-jiang
b602209ca6
test(integration): hand every harness server a port from one per-worker block (#46761)
* test(integration): hand every harness server a port from one per-worker block

About 45 places in the integration harness picked a port by binding port 0,
reading the number back, closing the socket and starting a server on that
number later. Another process could take the port in between, which surfaced
as "Address already in use" flakes. Earlier fixes retried around the race in
single call sites without removing it.

tests/integration/_support/ports.py now owns every port the harness picks
ahead of a server. Each pytest process claims a 200-port block between 10000
and 32767 by taking an exclusive flock on a per-block lock file in the system
temp directory, so parallel runs on one machine never share a block and the
lock goes away with the process. The range is above the fixed service ports
and below the Linux and macOS ephemeral ranges. Ports go out in order, are
never reused in a run, skip anything an outside process already holds, and
a process that exhausts its block claims the next free one.

The three _free_port copies, worker_endpoints' port-0 binds,
free_loopback_port, the owned proxy's lost-port-race retry and the owned
Redis retry loop are gone. Tests that restart a server on the same port now
allocate that port up front instead of reading it back from the first
server. TQ012 in scripts/check_test_quality.py rejects a port-0 bind read
back with getsockname anywhere under tests/integration.

* ci(integration): record listening sockets and the ephemeral port range

* Revert "ci(integration): record listening sockets and the ephemeral port range"

This reverts commit d3fafc3da2.
2026-10-15 16:56:40 -07:00
Yassin Kortam
1e5d83f6ce
ci: run mcp interactions and redis cluster iam integration tests only in circleci (#46780)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-15 16:53:36 -07:00
yujonglee
ebac947907
fix(rust-bridge): honor the configured request_timeout on native Messages (#46749)
The route host now reports get_configured_request_timeout() in MessagesSettings, and prepare uses the call timeout first, then that setting, then the 600s default. Before this, litellm_settings.request_timeout and REQUEST_TIMEOUT were ignored on the native path, so a long turn that works on Python timed out after 10 minutes

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:50:02 -07:00
devin-ai-integration[bot]
77c22a4c01
feat(proxy): add agent_access_default_deny for least-privilege agent access (#43979)
* feat(proxy): add require_key_agent_access_defined for least-privilege agent access

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(proxy): align A2A model visibility assertions with supported routes

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy): keep A2A model entries on their existing routes

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(proxy): point the listing model at the harness upstream

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy): coerce require_key_agent_access_defined like the settings validator

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(proxy): inject agent access lookups instead of patching the handler

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor(proxy): rename require_key_agent_access_defined to agent_access_default_deny

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: ryan <ryan@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-15 16:49:26 -07:00
Mateo Wang
b9079755bb
fix(ci): restore loadscope on the mcp-interactions shard so its MCP subscription cases do not race across workers (#46783)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-15 23:35:43 +00:00
joshua-berri
440744f967
feat(mcp): enable verified stateless protocol capabilities (#46264)
* feat(mcp): advertise verified stateless capabilities

* fix(mcp): honor rate limits during capability discovery

* test(mcp): cover protocol interactions and scoped enablement rollback

* fix(mcp): preserve legacy capabilities and bound discovery probes

* fix(mcp): keep discovery results single-assignment

* ci(mcp): distribute acceptance cases across existing workers

* ci: balance MCP integration cases across idle workers

* ci: isolate MCP interaction acceptance workload

* test(mcp): acknowledge fixture notifications after dispatch

* test(mcp): drain session dispatch before fixture deletion

* test(mcp): preserve deletion of streaming fixture requests

---------

Co-authored-by: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com>
2026-10-15 23:18:18 +00:00
Mateo Wang
d489c04554
fix(anthropic): drop unknown legacy thinking keys under drop_params when rebuilding adaptive thinking (#46741)
* fix(anthropic): drop unknown legacy thinking keys under drop_params when rebuilding adaptive thinking

* fix(bedrock): drop unknown thinking keys under drop_params on display updates rebuilds

Bedrock Invoke copied every caller thinking key when adding display updates,
so an unknown key still reached the provider with drop_params on. Filter that
copy the same way, and cover each chat call site plus the global
litellm.drop_params path in tests

* fix(anthropic): drop budget_tokens from rebuilt adaptive thinking under drop_params

* test(anthropic): cover drop_params sources for rebuilt adaptive thinking on /v1/messages and Bedrock Invoke
2026-10-15 23:09:30 +00:00