Commit graph

328 commits

Author SHA1 Message Date
Kerry Lu
b662f3f7a4
feat(decisions): add EmpirioLabs AI as a decisions provider (#49977)
* feat(decisions): add EmpirioLabs AI as a decisions provider

empiriolabs/aplomb-1 now works with litellm.decisions and litellm.systemone, the proxy /v1/decisions and /v1/systemone routes, the decision model guardrail and the Admin UI. Requests go to POST /v1/decisions on the EmpirioLabs AI API with the System One body, keyed by EMPIRIOLABS_API_KEY with an optional EMPIRIOLABS_API_BASE

Chat models keep routing through the existing OpenAI-compatible JSON provider, and a test pins that

* ci: run the EmpirioLabs AI decisions tests in the llms unit shard

* fix: align EmpirioLabs registration with CI gates

---------

Co-authored-by: Adam Dalloul <47503782+Adam-Dalloul@users.noreply.github.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-04 10:41:16 -08:00
devin-ai-integration[bot]
527d882508
test(responses): isolate cancel wire checks from background polling (#50002)
Some checks are pending
unit / router (push) Blocked by required conditions
unit / rust-bridge-harness (push) Blocked by required conditions
unit / auth-checks (push) Blocked by required conditions
unit / budgets (push) Blocked by required conditions
unit / custom-logging (push) Blocked by required conditions
unit / db-and-spend (push) Blocked by required conditions
unit / endpoints-and-responses (push) Blocked by required conditions
unit / enterprise-routing (push) Blocked by required conditions
unit / guardrails-hooks (push) Blocked by required conditions
unit / jwt-and-keys (push) Blocked by required conditions
unit / key-generation (push) Blocked by required conditions
unit / logging-misc (push) Blocked by required conditions
unit / mcp-oauth (push) Blocked by required conditions
unit / proxy-runtime (push) Blocked by required conditions
unit / proxy-server-core (push) Blocked by required conditions
unit / root (push) Blocked by required conditions
unit / proxy-utils (push) Blocked by required conditions
unit / ui-unit (push) Waiting to run
unit / docs (push) Waiting to run
unit / helm (push) Waiting to run
unit / OpenAI 2.20.0 compatibility (push) Blocked by required conditions
unit / OpenAI 3.0.0 compatibility (push) Blocked by required conditions
unit / OpenAI 3.25.0 compatibility (push) Blocked by required conditions
unit / redis-py 5.3.1 compatibility (push) Blocked by required conditions
unit / redis-py 6.4.0 compatibility (push) Blocked by required conditions
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
GitHub Actions Security Analysis / zizmor (push) Waiting to run
Co-authored-by: mateo <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-04 05:12:01 +00:00
Mateo Wang
d5ca88900c
fix(anthropic): answer an upstream Anthropic 529 as 529 overloaded_error on /v1/messages (#46063)
* fix(anthropic): answer a 503 as 529 overloaded_error on /v1/messages

Anthropic's error contract has no 503, so a client built against it (Claude Code) treats a 503 from /v1/messages as a plain 5xx and retries it ten times instead of taking its overload path. A Bedrock 503 now leaves /v1/messages and /v1/messages/count_tokens as 529 overloaded_error, for both a JSON answer and a stream that fails before its first byte.

An Anthropic 529 overloaded_error now maps to litellm.ServiceUnavailableError (503) instead of InternalServerError (500), so OpenAI-shaped routes answer it as 503, the status OpenAI documents for an overloaded engine, and router retry and allowed-fails policies key it as ServiceUnavailableError.

* test(proxy): expect overloaded_error on a 503 stream error after a keepalive ping

* fix(proxy): record the 529 the Anthropic routes answer and type the chat stream error frame

The messages route stamps the answered status on the exception before post_call_failure_hook runs, so the spend log row, the OTel server span, the failed-request metric, and alerting record the 529 the client saw instead of the deployment's 503, while the deployment failure metric and cooldown keep the deployment's own status. A chat completions stream error frame names the type its status stands for instead of the null .type ServiceUnavailableError carries. An Anthropic-shaped 503 envelope passes through as 529 overloaded_error through a validated TypedDict, and count_tokens answers every ProxyException as the Anthropic envelope. Five integration tests that pinned 503 on the messages route now expect 529

* test(proxy): expect the top-level Anthropic envelope from count_tokens errors

count_tokens now answers a ProxyException as a JSONResponse carrying the
Anthropic error envelope instead of raising HTTPException, which FastAPI
wrapped in {"detail": ...}. The three legacy error-format tests now read
the response body and check that no detail wrap is left.

* test(proxy): cover the 529 overloaded_error answer across routes, cooldown, and retry budgets

* fix(anthropic): keep Anthropic overloads as InternalServerError and answer 529 from the provider status

An upstream Anthropic 529 stays litellm.InternalServerError (status 500) so
InternalServerErrorRetries, InternalServerErrorAllowedFails, SDK handlers,
and the OpenAI-shaped routes behave as on main. The error now carries
provider_status_code, and the /v1/messages routes answer 529
overloaded_error from it.

* refactor(proxy): unwrap a mid-stream fallback error once instead of recursing for the provider status

* fix(anthropic): keep count_tokens' detail body, end keepalive streams with an Anthropic error event, and log any 529 as an overload

* test(integration): cover the audit round 3 cells and expect 529 on the count_tokens 503 cases

* fix(proxy): re-raise an unchanged stream HTTPException as itself and walk nested mid-stream fallbacks for the provider status

* fix(proxy): walk nested mid-stream fallbacks without recursion

* fix(anthropic): preserve 503s and answer upstream 529s on messages routes

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

* fix(anthropic): answer an upstream Anthropic 529 as 529 on count_tokens

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

* test(anthropic): use integration coverage for keepalive errors

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

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-03 17:37:09 -08:00
Kerry Lu
ce73cf8b9d
chore(cost-map): remove the 52 rows whose provider retired them before 2026 (#49974)
* chore(cost-map): remove the 52 rows whose provider retired them before 2026

* test(utils): pin the proxy function-calling parity case to azure/gpt-4.1

* test(batches): price the Azure batch-logging regression case against gpt-4.1

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-03 16:23:06 -08:00
zoveki
ff182c194e
fix(logging): keep the caller's call_type when chat completions are bridged to the Responses API (#49710)
* fix(logging): keep the caller's call_type when chat completions are bridged to the Responses API

* fix(logging): log bridged chat completions with the chat response they returned

* fix(logging): log bridged text completions and /v1/messages as served, with the request's options

Bridged rows were translated without the request's options, so structured
output and replay fields could differ from what the client got. Text
completions and non-streamed /v1/messages through the chat bridge also kept
a Responses API object or the encoded id.

* fix(logging): redact reasoning summaries on bridged rows and log bridged text completions as text completions

Redaction now drops reasoning_items on chat messages and deltas, so a bridged row keeps no reasoning summary when message logging is off. A non-streamed bridged text completion logs the text completion the client got, priced like the chat response it came from. The bridge handler's input TypedDict fields are ReadOnly.

* fix(logging): redact bridged text completions for opted-out callbacks and pin the sync logged shape and usage ack

* test(integration): look up bridged chat spend rows by the id the client got

Bridged chat spend rows are keyed by the client-facing id and logged with the caller's call type, so the helpers that decoded the old resp_<base64> key or pinned call_type responses now assert the new contract

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-03 10:02:54 +00:00
tessellark
5b546259d5
fix(cache_control)!: keep one deployment's prompt-cache opt-in from reaching siblings on the same model (#49685)
* fix(cache_control): read the routed deployment's own prompt-cache flag, not a sibling's shared cost-map entry

Two deployments that share one model string share one key in the live
litellm.model_cost map, and router registration writes each deployment's
supports_prompt_cache_breakpoint into that key. The cache control hook read
the live map, so a flagless deployment picked up a sibling's opt-in and sent
prompt_cache_options and prompt_cache_breakpoint upstream, which the provider
rejects with a 400

When a call carries the routed deployment's model_info, the prompt cache
checks now use its declared supports_prompt_cache_breakpoint, and fall back
to the loaded catalog instead of the live map. This covers the hook on
completion, acompletion, Responses and the /v1/messages route, the azure_ai
chat transform, the OpenAI Responses transform under drop_params, and the
/v1/messages to Responses adapter

* fix(prompt caching): read each deployment's own breakpoint flag in the openai and bedrock readers

A deployment's own supports_prompt_cache_breakpoint now picks OpenAI's
prompt_cache_breakpoint only where the request goes out OpenAI-shaped on a
provider other than openai, such as azure_ai OpenAI models and bedrock_mantle.
Bedrock on every route and providers that build Anthropic messages keep
cache_control, which Bedrock Converse turns into cachePoint

The openai chat reader now passes the routed model_info and checks base_model.
The Bedrock cache point reader takes the deployment's flag first and, for a
routed call with no flag, reads the catalog as loaded instead of the live map
that sibling deployments write into. model_info is threaded from the converse
transform, the factory message and tool builders, and the responses bridge

* fix(prompt caching): keep gateways on cache_control and read register_model opt-ins on routed calls

A declared supports_prompt_cache_breakpoint picks OpenAI's breakpoint only on providers hosting OpenAI's API or for a model the cost map lists under the provider, so OpenRouter, LiteLLM proxy, and vLLM deployments keep cache_control as before. Routed calls read the loaded catalog plus the user's own register_model entries, never router registrations.

* fix(prompt caching): honor a deployment's own supports_prompt_caching on Bedrock and its declared litellm_provider on gateways

* fix(prompt caching): keep wildcard cache flags off matched models, read deployment flags for auto caching and base_model aliases

* test(anthropic): cover auto caching on an unmapped Claude deployment

* fix(cache_control): keep the merge base's reads where the per-deployment flag changed them

A register_model entry under a provider-prefixed name reaches routed calls again, since the
overlay now resolves the bare and prefixed names the way runtime registrations are stored.

The hook and the openai chat reader no longer judge base_model, as at the merge base, so a
client base_model sent under allow_client_pricing_override cannot pick OpenAI's breakpoint
and get a 400.

A Bedrock deployment flag that is not a real true reads as false again, as at the merge base.

An azure_ai wildcard's supports_prompt_cache_breakpoint speaks for each model it matches again,
as at the merge base. Other wildcards still leave both cache flags to the catalog.

The s10/s11 Bedrock wire test asserted that a flagless deployment inherits its sibling's flag
on a shared ARN, which is the leak this PR removes. It now asserts that each deployment keeps
its own flag and that a flagless unmapped ARN keeps its cache points after the delete.

* fix(cache_control): let the later register_model entry win between a bare and a provider-prefixed name

Routed calls read register_model entries through an overlay that merged the bare
name after the provider-prefixed one whatever order they were registered in, so a
later opt-out under one name lost to an earlier opt-in under the other. Each
registration now moves its key to the end of the kept registrations, and the
overlay merges both names oldest first, which matches what SDK calls read from
the live model map.

Also lists the wildcard cache flag helper in the router coverage script, since
the hook's wildcard tests drive it rather than calling it by name.

* fix(cache_control): keep register_model entries visible while a cost map reload replays them

The previous commit moved a re-registered name to the end of the saved
registrations by removing and re-adding it, and the overlay walked that dict to
order a bare and a provider-prefixed name. A reload replays every saved entry,
so a request on another thread could briefly miss an entry or fail on a dict
that changed size mid-walk. Entries are written in place again, a per-name
sequence records which registration came last, and both the overlay and the
reload replay order by it, so the later registration still wins after a reload.

* fix(cache_control): read a string or numeric supports_prompt_caching as the bool it spells and find register_model entries under any route prefix

A deployment flag stored as "true", 1, "false" or 0 (POST /model/new and quoted YAML keep strings) now means that bool on the Bedrock cachePoint reader and Anthropic auto caching, and any other value falls to the catalog. register_model records which names wrote to each catalog row, so an opt-out under bedrock/ or bedrock/converse/ reaches routed calls like one under anthropic/.

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-03 07:55:51 +00:00
devin-ai-integration[bot]
3da6029977
test(bedrock): assert native structured output when the model rejects forced tools (#49799)
Co-authored-by: mateo <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-03 06:10:00 +00:00
Kerry Lu
fcef9cd0a3
feat(videos): serve custom_provider_map BaseVideoConfig providers on /v1/videos (#49785)
* feat(videos): serve custom_provider_map BaseVideoConfig providers on /v1/videos

A provider registered in custom_provider_map with a BaseVideoConfig handler now resolves on every
video call type (create, status, content, list, remix, edit, extension, characters), the same way
CustomLLM handlers already resolve on /v1/images/generations. The video entry points and the HTTP
handler stop casting the provider name to LlmProviders, which raised before any lookup ran, and a
provider without a video config gets a 400 instead of a 500.

* fix(videos): type the custom provider lookups so the type gate holds

Pick the CustomLLM for a custom provider through ProviderConfigManager.get_custom_llm_handler,
which narrows the now wider custom_provider_map handler union, read custom_provider_map instead
of the private _custom_providers list, and annotate the untyped custom_llm_provider parameters
on the video entry points

* test(videos): cover a CustomLLM shadowing a built-in video provider name and type the new tests

Mark custom_handler ReadOnly on CustomLLMItem, type the new tests, and assert that a CustomLLM
registered under openai still leaves openai video on the built-in config

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-02 21:28:46 -08:00
ryan-crabbe-berri
be50cbe9d8
fix(responses)!: authorize and decode comparison response IDs (#49402)
* test(responses): reproduce comparison response ID ownership and wire failures

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

* test(responses): guard stale retries and malformed comparison envelopes

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

* fix(responses): authorize and unwrap comparison response ids

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

* test(responses): require comparison IDs to follow response security policy

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

* test(responses): deny raw comparison response ids by default

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

* fix(responses): reject raw comparison response ids by default

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

* test(responses): enforce managed ownership with unmanaged ID opt-in

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

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-03 03:40:46 +00:00
tavren27
94b5b6386a
fix(bedrock): send native structured output for json_schema on Claude models that cannot force a tool (#49703)
* fix(bedrock): send native structured output for json_schema on Claude models that cannot force a tool

Opus 5.5 and Sonnet 5.5 on Bedrock cannot force the json tool, so a json_schema request without thinking
sent an unforced json tool the model ignored. The opt-in native output key now also applies when the model
map says the model cannot force a tool, and the Opus 5.5 commercial Bedrock rows carry the opt-in key. The
GovCloud Opus 5.5 rows carry it as false, like the Sonnet 5.5 GovCloud rows

* fix(bedrock): keep json tool with thinking on Opus 5.5 and Fable 5.1, and GovCloud on the tool

Native output without thinking now applies to every Bedrock Claude model that cannot
force the json tool unless supports_bedrock_native_structured_output is false, so Opus 5.5
needs no opt-in row and keeps the json tool when thinking is requested, as #46870 chose.
The invoke chat path keeps the native output_format it built instead of inlining it, and
GovCloud region-path ids read their own false rows.

* fix(bedrock): clean caller output_format schemas before sending them natively on invoke

* fix(bedrock): keep Mantle Claude on the json tool and send a caller's output_config.format natively on invoke

* fix(bedrock): keep the faked Converse stream for native output that replaces the json tool

A json_schema stream that goes native only because the model can't force the json tool now keeps the json tool's faked stream, so it still needs only bedrock:InvokeModel. /v1/messages builds a native-output retry only where it sends native output, and the Mantle test drops its mutable capture.

* test(bedrock): cover native output and the opt-out in the invoke json-mode refusal tests

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-03 01:56:52 +00:00
tavren27
640a87f70d
fix(gemini): resend once with the placeholder signature when the other backend rejects a replayed thought signature (#49697)
* fix(gemini): resend once with the placeholder signature when the other backend rejects a replayed thought signature

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

* test(integration): gemini tool-call signature rejected by the backend is resent once with the placeholder

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

* fix(gemini): keep the placeholder resend inside the cache rebuild so either rejection order ends in an accepted body

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

* fix(gemini): resend a cached-prefix signature inline and cover native generateContent

When the rejected signature sits inside a cachedContents prefix, the
placeholder resend now carries the history inline, since replacing the
signature in the request body cannot reach the cache. The native
generateContent handlers get the same one-time resend.

* fix(gemini): send the history inline when Vertex rejects a signature while creating the cache

* fix(gemini): keep the context cache on a signature resend and go inline only when that is rejected too

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-03 01:24:55 +00:00
devin-ai-integration[bot]
0bd677afef
test(integration): look up streamed Responses spend rows by the client-facing id (#49752)
Co-authored-by: mateo <277851410+mateo-berri@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-02 17:13:41 -08:00
Mateo Wang
4e268c2c55
test(gemini): assert vertex lists on content chunks (#49734)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-02 15:40:32 -08:00
simonp9
250af8f027
fix(spend)!: save streamed /v1/responses spend rows under the encrypted resp_ id the client received (#49493)
* fix(responses): log the encrypted response id on streamed /v1/responses spend rows

With response id security on, the stream hook encrypts the resp_ id on every
frame, but logging had already received the SDK's own copy of the terminal
response, so the spend row, the standard logging payload and callbacks got the
managed id (or the chatcmpl id on a bridged stream) the client never saw. The
hook now records the exact id it advertises on the request's logging object,
and the logging layer stamps that id on the logged streamed response

* fix(responses): return the id the client sent on GET and cancel, and keep read rows off the create row's id

Every GET /v1/responses/{id} re-encrypted the response id, so a client that continued a bridged conversation with the
id a GET returned referenced a ciphertext no spend row was filed under. The follow-up lost turn 1 and opened a new
session. GET and cancel now answer with the exact id the client sent when it is an id this proxy issued and it decodes
to the response the provider returned. A create with previous_response_id still gets a fresh id for its own response,
and a raw provider id still gets a managed id

Because a read now carries the create's id, a GET or cancel spend row is filed as <id>_retrieve_<call id> or
<id>_cancel_<call id>. That keeps one row per read, as before, and a read row can never take the create row's
request_id, so the create row survives whichever row is written first and a follow-up replays turn 1 once. The
background settlement row keeps <id>_background_cost, and the cost poll's own status checks keep the response id

* fix(responses): log a fresh id for each read and cancel, echo the held id only to its owner

A GET or cancel of a stored response now logs a new encrypted id of its own,
so callbacks and spend rows never reuse the create's id, while the owner of
the id still gets back the exact id they sent. An admin reading someone
else's response gets an id bound to the admin, as before. The spend row id
suffix for reads and cancels is gone, so with response id security off the
rows and session history match the default branch. A streamed GET follows
the same rules.

* fix(responses): echo the sent id to every reader that passes the response access check

A GET, streamed GET or cancel of a response now answers with the id the caller sent whenever the caller passed
the access check, not only when the caller is the user or team the id was issued to. An admin, the master key
or a teammate who read someone else's streamed response got a fresh id back, and the session lookup could not
reach the first turn from it, so a bridged follow-up through that id lost the conversation history

Every read and cancel still logs a fresh id bound to the reader, never the create's id

* fix(responses): keep a read or cancel row in session history only when it brings new output

A GET, streamed GET or cancel sent with a session header joins the
create's session, and its stored response repeats the create's output.
The replay then sent the same assistant turn once per read.

Session history now keeps an aget_responses or acancel_responses row
only when its stored Responses output has an item id that no earlier
kept row carried. A polled background response keeps the first poll
that returns the finished output, since its queued create row has none,
and later polls of the same output are dropped. Every other row keeps
today's rule

* fix(responses): replay each history item once, echo the sent id on delete, keep background cost files apart

A create row now owns its output items in session history, and a read or cancel row adds only items no
create and no earlier read carried, in one linear pass, so a background response polled while it grows,
or a read that ends before its streamed create, replays the answer once and after its prompt

DELETE /v1/responses/{id} answers with the id the caller sent, on a copy, and logs a fresh encrypted id
like a read does

Azure Data Lake Storage per-event files and unbatched GCS objects are named after the event's spend row
id, so a streamed background create and its cost event, which now share the client's id, keep separate
files

The integration tests that decoded the managed id now match the exact id the client received

* fix(responses): keep main's answer for a raw-id delete and main's storage names outside background cost events

A DELETE by a raw provider id returns the provider's id as main did, and logging still records a fresh
encrypted id. A streamed GET matches the held id by upstream response id, like a plain GET

Only a background response's cost event gets the _background_cost suffix on its Azure Data Lake Storage
file and unbatched GCS object, so batch retrieves and events with no id keep main's names

An item a session read replays takes the newest read's copy at the first read's place, so an item a
poll caught half written replays finished

Tests pin the client's id over the router's continued response id, and the storage test no longer reads
the clock after the upload

* fix(spend): give background cost events their own OpenMeter and DynamoDB ids, replay id-less read items

A streamed background create and its later cost event share the client's
id, so OpenMeter dropped the second event and DynamoDB overwrote the
create's item. Both now key the cost event as <id>_background_cost, like
its spend row and storage file.

Session history again replays a read's output items that carry no id,
as main did.

* fix(integrations): keep OpenMeter and DynamoDB logging working without the proxy extras

* fix(integrations): give background cost events their own ClickHouse request id and DeepEval span id

A streamed background create and its later cost event now share the id the
client received, so ClickHouse and DeepEval saw both events under one id.
Route both through logged_event_key, the way the Azure storage, GCS,
OpenMeter and DynamoDB loggers already do, so the cost event's ClickHouse
request_id matches its spend row's `<id>_background_cost` and its DeepEval
span gets its own uuid. ClickHouse response_id stays the raw provider id.

* test(deepeval): annotate the background cost span test's locals with Final

* test(responses): run the DELETE wire test that pins the echoed client id

DELETE /v1/responses/{id} now answers with the id the caller sent, so the
skip that marked it as a known bug no longer applies. The test passes on
the /v1, /openai/v1 and bare routes.

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-02 14:13:17 -08:00
yuneng-jiang
044bd824c0
test(integration): align remaining release qualification checks (#49714)
* test(integration): align remaining release qualification checks

* test(integration): scope offline resolution to the restricted runner
2026-11-02 12:43:15 -08:00
tavren27
a4de720144
fix(count_tokens): count grok-4-1, grok-4.3 and DeepSeek V4.1 tool requests the way their hosts bill them (#49074)
* fix(count_tokens): count grok-4-1, grok-4.3 and DeepSeek V4.1 tool requests like their chat templates

Render grok-4-1, grok-4.3 and DeepSeek V4.1 tool schemas the way each chat
template does, and add a per-deployment token_count_tool_framing next to
token_count_framing for the hidden tool preamble a host bills. The Azure AI
Foundry grok-4-1, xAI grok-4.3 and Fireworks DeepSeek V4.1 cost map rows carry
the measured framing, overridable in model_info

* fix(count_tokens): count Azure grok-4-1 Responses requests with their own framing and use the tool framing in place of the base framing

* fix(count_tokens): keep operator framing on tool and Responses counts, frame bridged Responses like chat, follow base_model for tool rendering, count grok tool_choice

* fix(token_counter): count Azure Responses strict default, skip false strict and built-in-only tools, per-kind operator framing

* fix(token_counter): count an explicit null strict like false on Azure Responses

* fix(count_tokens): follow the chat completions bridge flag and Azure's strict default on azure/ deployments

Keep Azure grok-4-1 chat tool counts at or over the bill by raising its tool framing to 302

* fix(token_counter): keep strict false for non-grok templates, coerce the bridge flag, frame DeepSeek API counts

* fix(proxy): frame Responses counts for the api_base router defaults send inference to

* fix(proxy): keep the api_base helper's name so main's /v1/messages count caller resolves

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-02 12:37:35 -08:00
zoveki
6be98d8c75
fix(bedrock)!: give Invoke and Mantle Claude the model's own default max_tokens (#49692)
* fix(bedrock): resolve Invoke Claude default max_tokens from the base model id

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

* refactor(bedrock): build Invoke Claude request params without mutating

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

* fix(bedrock): type tool-search normalizer return to clear unknown-type gate

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

* fix(bedrock): resolve Invoke and Mantle Claude default max_tokens from the deployment id, ARN base_model, and Mantle row

* fix(bedrock): default Invoke Claude max_tokens from the deployment's own cap and honor AnthropicConfig

Read the called deployment's model_info.max_output_tokens before the base model's
cost row, skip the shared regional key the router fills from every deployment, and
leave the default to litellm.AnthropicConfig(max_tokens) when it is set.

* fix(bedrock): send the mapped default max_tokens on bedrock_mantle Claude chat

* fix(bedrock): bound a global AnthropicConfig max_tokens by the deployment's own cap

* test(bedrock): annotate the new Mantle default max_tokens tests with Final

* refactor(bedrock): inject the AnthropicConfig max_tokens reader into the Invoke Claude config

---------

Co-authored-by: mateo <mateo@berri.ai>
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-11-02 11:42:45 -08:00
yuneng-jiang
f8f12a270a
test(integration): repair stale contract fixtures (#49700) 2026-11-02 11:23:11 -08:00
zoveki
508a98586e
fix(databricks): join plain text blocks for non-Claude json_object requests (#49385)
* fix(databricks): join plain text blocks for non-Claude json_object requests

* fix(databricks): join only the last message for json_object and drop cache_control under drop_params

Databricks checks for "json" only in the last message, so earlier turns now go out as sent and only the
last message, plus any system messages merged into it, is joined into a string. Under drop_params a
text block's cache_control is dropped so that message can be joined too, and without it the request
still goes out as a list.

* fix(databricks): skip dropped assistant turns and blank parts when joining for json_object

The json_object join picked the last message before the request transform drops bare assistant turns,
so a trailing thinking-only or reasoning-only turn (a Responses input ending in a reasoning item included)
left the real last user message as a list and Databricks still returned its 400. The pick now skips the
turns that transform drops.

The join also ran when only one part had text, which sent whitespace-only parts the existing empty block
filter used to drop on requests that already worked. It now joins only two or more non-blank parts, so a
request with one non-blank part goes out exactly as before. Without drop_params, a message with a
message-level cache_control now goes out as sent instead of being rewritten into one cached block.

* fix(databricks): join json_object text parts only on models Databricks hosts

Databricks checks for the word "json" only on the models it hosts
(databricks-* pay per token names and system.ai.* AI Gateway names). An
external model endpoint skips that check and takes a list of text parts
today, so its request now goes out as sent instead of joined.

The drop_params read now goes through drops_unsupported_params, and a
test pins that a text block with a truthy non-string text is kept.

* fix(databricks): reject a text part holding a non-string locally with a clear 400

* refactor(databricks): reject a non-string text part at the transform_request boundary

* fix(databricks): read pydantic message objects in the non-string text check

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-02 11:17:40 -08:00
tavren27
a0b1edc0a0
fix(providers): trim prompt_cache_breakpoint to what each API takes under drop_params (#49064)
* fix(openai): trim a malformed prompt_cache_breakpoint under drop_params on chat completions

* fix(openai): leave a non-object content part for OpenAI to reject under drop_params

* fix(openai): trim an unsupported prompt_cache_breakpoint ttl on every path and on azure_ai chat under drop_params

* fix: trim prompt_cache_breakpoint to what Azure and Mantle accept under drop_params

Azure OpenAI chat now gets the same content part trim as OpenAI chat, and
the Azure and Foundry gates also read a deployment's declared base_model.
Each Responses config keeps only the ttl its API takes: Azure keeps 30m,
and Mantle keeps no ttl. A trimmed ttl is logged at debug.

* fix(responses): trim prompt_cache_breakpoint only on APIs known to read it

Other Responses providers get the marker whole or not at all, as on main.
Azure chat now trims for a deployment with no base_model unless the cost
map marks the model as not reading breakpoints, and Azure AI Foundry
Responses keeps Azure's 30m-only shape.

* fix(azure): trim prompt cache breakpoints only for deployments known to read them

Azure and Azure AI Responses keep main's whole-or-nothing rule for unmapped deployment names, an opted-in custom OpenAI host trims the ttl and keeps the mode, and Azure chat strips every marker for a known non-reader under drop_params

* fix(providers): keep main's marker handling where the target won't trim it

A /v1/responses model with unknown breakpoint support on OpenAI's host gets a marker whole or not at all, as on main. The chat to Responses bridge keeps main's whole-marker check when a non-bool global drop_params leaves the target's trim off. A hyphenated name like gpt-5-6-sol now reads as gpt-5.6.

* fix(providers): read a non-version hyphen suffix like -32k as part of the model name

* fix(providers): keep main's reading of a dotted minor version like gpt-5.6o

* fix(providers): keep main's breakpoint shape for unmapped Mantle models and skip the Azure lookup without drop_params

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-02 10:00:00 -08:00
tavren27
1046716b6d
fix(anthropic)!: sync the beta header allowlist with what anthropic, vertex_ai, and azure_ai accept (#49409)
* fix(anthropic): sync the beta header allowlist with what anthropic, vertex_ai, and azure_ai accept

* test(e2e): send the azure_ai beta check without the code execution tool

* test(anthropic): require a vertex_ai and azure_ai decision for every anthropic beta

* fix(anthropic): forward the betas Claude Code sends that anthropic, vertex_ai, and azure_ai accept

* test(e2e): send Vertex's header-only betas in the beta acceptance case

* fix(anthropic): keep forwarding Vertex computer-use betas and point beta tests at platforms that still lack them

A live probe found each Claude 4.5 and 4.6 model on Vertex needs one of the two
computer-use betas for the computer tool, so dropping both broke it. The e2e
acceptance test leaves those two out of its all-at-once Vertex call, and the
integration and unit tests that relied on a beta being dropped now use one
the platform still lacks.

* test(anthropic): point unsupported-beta tests at betas the platforms still drop

The Azure AI handler test used claude-code-20250219 as the beta Azure drops, and the
Rust parse test used it as an unknown value. This PR forwards it on Azure AI and maps it,
so both now use values that stay dropped or unknown.

* fix(anthropic): add computer-use-2025-11-24 for the computer_20251124 tool

The computer_20251124 tool fell through to the default computer-use-2024-10-22 beta, which the
provider rejects for that tool version. Map it to computer-use-2025-11-24, which the beta map now
forwards on anthropic and azure_ai

* fix(anthropic): send no computer use beta for an unknown computer tool version

An unknown computer tool version fell back to computer-use-2024-10-22, which the anthropic and azure_ai columns now forward. It now gets no beta, as every platform effectively sent before. Every computer tool version also skips defer_loading and allowed_callers, computer_20251124 included

* test(azure_ai): read the branch's beta map in the auto-injected beta wire test

* fix(anthropic): keep defer_loading and allowed_callers on the computer_20251124 chat tool

* test(anthropic): read the branch's beta map in the azure_ai safeguards beta wire test

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-02 05:57:51 -08:00
Mateo Wang
3155aacda4
fix(caching): stop counting an unrun server tool call mark, and follow #49192 and #35602 in their integration tests (#49468)
* fix(caching): stop counting an unrun server tool call mark, and follow #49192 and #35602 in their integration tests

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

* refactor(caching): return the server tool call id instead of a TypeGuard

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

* test(integration): assert the Gemini cache write carries the system prompt

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

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-31 12:27:17 -07:00
Kerry Lu
948445c828
fix(watsonx): read pass-through credentials from use_in_pass_through deployments (#49462)
* fix(watsonx): read passthrough credentials from use_in_pass_through deployments

/watsonx/{endpoint} always called the provider config with api_key=None and api_base=None, so it only ever
worked from WATSONX_API_KEY / WATSONX_API_BASE. It now asks PassthroughEndpointRouter for the proxy-wide
watsonx deployment flagged use_in_pass_through, like the other passthrough routes, and keeps the env
fallback when none is flagged.

Co-authored-by: T K Chandra Hasan <t.k.chandra.hasan@ibm.com>

* fix(watsonx): keep env key precedence when no deployment is flagged

Resolve only use_in_pass_through deployments in the route. get_proxy_wide_credentials also falls back to
WATSONX_API_KEY, which put it ahead of WX_API_KEY in generate_iam_token

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: T K Chandra Hasan <t.k.chandra.hasan@ibm.com>
2026-10-31 11:53:25 -07:00
paveln83
27fbcd9895
fix(anthropic): map pause_turn and return refusal explanations on chat and responses (#49192)
* fix(anthropic): map pause_turn and return refusal explanations on chat and responses

pause_turn now maps to finish_reason length (Python and Rust) and keeps the raw
stop_reason in provider_specific_fields. Responses reports a paused turn as
incomplete with max_output_tokens and shows the unrun server search as an
in_progress web_search_call instead of a client function_call.

A refusal's stop_details.explanation now fills message.refusal and the final
stream delta.refusal on chat, and becomes a refusal content part on Responses,
streamed or not.

* fix(anthropic): let a chat client resend a paused turn so Claude continues it

* fix(anthropic): replay a paused chat turn without its thinking blocks, and drop its unrun search before a new message

Chat cannot say where Claude wrote each thinking block in a paused web search turn, and
Anthropic answers 400 when one moves but accepts the turn without them. A new message after
a paused turn also 400s on a server_tool_use with no result, so that unrun search is dropped.

* fix(responses): send the streamed refusal part's type and text in content_part.added

* fix(anthropic): keep pause_turn and Claude refusal explanations out of the Messages adapter's text

A paused Claude turn now ends on stop_reason pause_turn instead of max_tokens, streamed or not. A Claude refusal keeps its explanation in stop_details with Claude's category and no longer repeats it as a text block, so a guardrail mask on a streamed refusal is delivered. A paused turn sent back with list content drops its thinking blocks like string content does

* fix(anthropic): replay a paused turn with one unrun search and no thinking on chat and Responses

* fix(responses): report a paused Claude turn as completed so Codex and the Agents SDK keep going

* fix(responses): mark a paused turn's message item completed

* fix(responses): mark every item of a paused turn completed

* fix(responses): keep a refusal's explanation when a refused turn is sent back

A refusal part in a Responses input message now goes to the model as text, so the assistant turn is no longer dropped from the history. The integration test now expects a paused Claude turn on Responses to report completed, and the paused-turn test helper is typed

* fix(responses): scan refusal parts in Responses guardrails

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-31 02:53:59 -07:00
mika-lt
c4b01b2ac8
fix(decisions)!: honor a deployment's own supports_audio_input and supports_video_input flags (#49207)
* fix(decisions): honor a deployment's own supports_audio_input and supports_video_input flags

A Decisions deployment's model_info flags for audio and video now win over
the cost map, per media kind, the same way supports_box_questions already
does. An unlisted model flagged true gets its clips sent upstream, and a
false flag refuses them (or drops them under drop_params). OpenAI-format
providers still refuse clips since their request shape has no field for
them, and the SystemOne request builder no longer re-reads the cost map.

* fix(decisions): ignore media flags on a deployment whose own mode is not decisions

A chat-mode deployment's supports_audio_input describes chat input, so it no longer
turns on audio or video clips for Decisions. The cost map decides for it, as before.
Adds a proxy integration test for flagged, partly flagged, unflagged, and drop_params
deployments on both /v1/decisions and /v1/systemone, and for caller-sent model_info.

* fix(decisions): follow the cost map's mode for a flagged deployment without its own mode

A chat deployment flagged for audio or video usually leaves mode unset, so
its flags described chat input yet still let clips through on /v1/decisions.
When the deployment sets no mode, the cost map's mode for the model now
decides whether its media flags apply, and an unlisted model still takes
them. The audio and video lookups also run only when the request carries a
clip.

* fix(decisions): read the mode a flagged deployment falls back to from the shipped cost map

The router copies each deployment's model_info into the model's shared cost map row, so another deployment's mode there could switch off a flagged deployment's media flags. The fallback now reads the cost map as loaded, untouched by router registrations

* fix(decisions): route clips in a load-balanced group to a deployment that takes them

A Decisions or SystemOne request carrying audio or video now skips group deployments whose own flags or the cost map refuse that media, and keeps the whole group when none takes it, so the 400 still names the media.

* fix(decisions): keep deployments that drop or cannot resolve clips in the media filter

* fix(router): skip clip-refusing Decisions deployments after cooldown and tag filtering

Running the media filter before cooldown turned the 400 a group answered
when its only clip-taking deployment was cooling down into a 429. It now
runs next to the image filter, just before order filtering, in both the
async and sync selection paths.

* fix(router): honor router default drop_params when skipping clip-refusing Decisions deployments

* test(router): list the router default params helper in the router coverage check

* test(bedrock): stop reloading the cost map module in the GovCloud fixture

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-31 01:09:46 -07:00
mika-lt
e5aa3d32cd
fix(proxy): forward a valid metadata.user_id to Anthropic Messages on pass-through routes (#49333)
* fix(proxy): forward the client's own metadata on Anthropic and custom pass-through routes

* fix(proxy): forward only a valid metadata.user_id to Anthropic Messages on pass-through routes

The whole-object forward sent keys Anthropic refuses and reached other
providers, so a request that used to pass could start failing with a 400.
Read the client's user_id after hooks, drop it unless it is a string with no
email or phone number, and send only that key when the upstream URL path ends
in /v1/messages. Share the validator with the translated route, whose email
check now matches any embedded address as Anthropic's does.

* fix(proxy): forward metadata.user_id only to Anthropic upstreams no guardrail scans

Limit the pass-through user_id to Messages create on api.anthropic.com or the
configured Anthropic base, mirror the checks Anthropic answers 400 for, and keep
the field off any request a pre-call guardrail inspects. The translated route
goes back to its own user_id filter.

* fix(proxy): judge user_id forwarding on the whole request and cap it for configured Anthropic bases

* refactor(proxy): pass the user_id check a plain mapping instead of an optional body

* fix(proxy): drop an unprintable metadata.user_id and warn when a refused id is dropped

Anthropic's whitespace is not Python's, so a control character before the at sign hid an email from the filter and turned a 200 into a 400. Fireworks and Moonshot also fail on newline ids. Requiring a printable id covers both, and a refused id now logs a warning that never carries the value.

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-31 00:16:42 -07:00
ryan-crabbe-berri
5950af8fb5
fix(responses): preserve text verbosity when bridging to chat completions (#49049)
* test(responses): cover text verbosity on the chat completions bridge

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

* fix(responses): send text.verbosity as chat verbosity on the chat completions bridge

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

* refactor(responses): type the bridged text verbosity lookup

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

* test(responses): cover text verbosity on the client tool search follow-up bridge turn

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

* test(responses): cover text verbosity validation on the codex tool search follow-up turn

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

* fix(responses): send text.verbosity on the client tool search follow-up bridge turn

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

* test(responses): cover text.verbosity on previous_response_id client tool_search follow-up

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

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-30 21:41:20 -07:00
yuneng-jiang
fbd126e518
test: fix integration tests that fail on test order, races and intended changes (#49310)
Seven integration tests fail on main for reasons that are not product bugs. Two are order or race bugs in the tests, one reads rows other tests wrote, one passed on an error body, and three still expect behavior that #48644, #39864 and #39540 changed on purpose
2026-10-30 13:25:47 -07:00
tin-berri
632f71b850
fix(router): recover streamed context window errors before output (#49293) 2026-10-30 13:19:58 -07:00
Maxime Boudier
b20acb4869
fix(proxy)!: persist sent model_info fields on POST /model/update (#46902)
* fix(proxy): persist sent model_info fields on POST /model/update

POST /model/update rebuilt litellm_params from the request but dropped the
request's model_info, so changing base_model (or mode, tier, etc.) on an
existing DB model was silently ignored. Clients that only use this endpoint,
like the Terraform provider, could never fix a deployment's base_model
without deleting and recreating it.

Persist the model_info fields the caller actually sent, on top of the stored
ones. ModelInfo defaults never overwrite stored values, server-owned, team,
PTU and pricing fields stay on their dedicated write paths, and values echoed
from a /model/info response are not pinned.

* fix(proxy): keep echoed and blank model_info values out of POST /model/update writes

Blank strings from typed clients no longer overwrite stored values. Values echoed from
/model/info (the stored base model's cost map entry and discovered limits) are not
pinned, and an echoed catalog value resets a differing stored override like PATCH does.
direct_access and access_via_team_ids stay display-only. The tests stop patching the
auth check class and give the team lookup a real row instead.

* fix(proxy): lock the model row and skip unchanged model_info on POST /model/update

* fix(proxy): treat only keyed /model/update bodies as echoes and write the stored model_info as saved

A typed model_info value that matched the cost map, or the previous base model's entry,
was dropped and could erase a stored override. Echo detection now follows PATCH, only a
body carrying the /model/info key counts, compared against the base model the row has
after the write. The locked write patches the stored JSON instead of dumping the hydrated
Deployment, so litellm_params pricing is no longer copied into model_info. Tests run the
real Router with discovery over a mock upstream

* docs(terraform): note that model_info updates now take effect and overwrite values set outside Terraform

* test: expect POST /model/update to save the cache point flag, and wrap long test lines

test_x06 pinned the old contract, where POST /model/update answered 200 and ignored model_info.
This PR makes POST save model_info the way PATCH does, so x06 now asserts what its PATCH sibling
x01 asserts, the stored flag flips to true and cache points come back on.

* fix(proxy): answer 400 when POST /model/update gets an access_groups that is not a list of strings

* fix(proxy): read discovered model info through the router registry entry point

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-30 12:10:13 -07:00
Siddhant Badola
b71491c6c9
fix(bedrock): route a Bedrock model id with no Invoke provider to Converse (#43290)
* fix(bedrock): route unmapped converse-only family model ids to converse

#42713 stopped Bedrock-native OpenAI ids that are not in the cost map yet from
falling to the invoke route. The same gap exists for the other families that
have no invoke transformation at all: minimax, nvidia, writer, zai, google and
xai. An id from one of them that is newer than the cost map hits
get_bedrock_route -> "invoke" -> get_bedrock_invoke_provider() -> None ->
BedrockError, when Converse would have served it

Add a second family regex for those six and default them to converse, the same
way the openai family is handled. _OPENAI_FAMILY_MODEL_RE is left alone because
the prompt-caching rule is openai-specific

Every shipped id of these families already resolves to a converse entry via its
base model (checked against the cost map), so this only changes behaviour for
ids that are not in the map yet

Tests: six unmapped ids route to converse, and an unmapped meta.* id still
routes to invoke since meta has an invoke handler

* fix(bedrock): route a model id with no Invoke provider to Converse

A bedrock/<model> id missing from the cost map fell to the Invoke route,
where an id that names no Invoke provider (a new vendor's model, a bare
provisioned-model, imported-model, or custom-model-deployment ARN) fails
locally with "Unknown provider=None" before any request is sent. Such ids
now go to Converse, which replaces the per-family regex with the general
rule. Ids that name an Invoke provider and explicit route prefixes are
unchanged.

* test(bedrock): expect Converse for ids with no Invoke provider in the nova ARN and not-found tests

* fix(bedrock): accept tools on Converse for a model with no cost-map row or Invoke provider, and keep a 404 for an invalid id

* fix(bedrock): answer 404 for an invalid model id instead of AWS's 400

AWS answers a mistyped Bedrock model id with a 400 ValidationException. The mapping already raised NotFoundError, but passing AWS's 400 response through made the openai base class report 400, so the proxy answered 400 and the router skipped its cooldown

* fix(bedrock): drop lookaround regexes from tool schemas for a model the cost map does not list

* fix(bedrock): apply the unknown-model defaults only to bare ids LiteLLM knows nothing about

A bare Bedrock id with no cost-map row and no Invoke vendor gets Converse's open params, its reasoning_effort as sent, lookaround-free tool schemas, and a 404 for AWS's invalid-identifier reply, which keeps AWS's response headers. An explicit route like converse/, an ARN, or an id LiteLLM recognizes keeps main's params, regexes and error status, so drop_params still drops tools there

* fix(bedrock): judge lookaround support by model id on every route, ignore price-only rows

The regex check now strips route and region prefixes from the model and its
base_model before asking whether LiteLLM knows the model, so
bedrock/converse/<id> gets the same answer in a direct transform call as on the
completion path. A cost-map row counts only when it names a provider, so a
price-only register_model row leaves an unlisted id unknown, as on the proxy.
AmazonInvokeConfig now inherits the Invoke provider lookup instead of keeping
an identical copy.

* fix(bedrock): treat a region path like the bare id when the cost map does not list it

A deployment like bedrock/us-east-1/<unlisted id> now gets the same tools,
reasoning_effort and typo 404 as bedrock/<unlisted id>, so Claude Code works
on it and a mistyped one still answers 404.

* fix(bedrock): keep today's 404 for ids that reach Converse only by default, and keep an empty id local

* fix(bedrock): answer 404 for an invalid id only when Converse rejected it, and keep a region path with no id local

* fix(bedrock): keep a path-shaped id local, and map /v1/completions errors with the model's provider

An id with a slash that is not an ARN, like bedrock/../../converse or an unknown region path, keeps Invoke and its local 404 instead of reaching AWS Converse.

atext_completion mapped every error as OpenAI, so a Bedrock typo on /v1/completions answered 400 while chat answered 404. It now resolves the provider from the request's model, as the other async entry points do.

* fix(text completion): keep /v1/completions error mapping as it is on main

Mapping async /v1/completions errors with each provider's rules is wider than this Bedrock routing change. It moves classes, statuses, cooldown and fallbacks for most providers, and some lose context-window fallbacks. So this restores main's mapping here and leaves that change to its own PR. A Bedrock typo on /v1/completions answers AWS's 400, while chat keeps the 404.

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-30 06:27:59 -07:00
simonp9
d1758cda91
fix(router)!: keep a deployment's own model_id over the request body's (#49045)
* fix(router): keep a deployment's own model_id over the request body's

A request body model_id overrode the deployment's pinned model_id, so a client could move SageMaker chat and embedding calls to another inference component. The router now drops the request's model_id when the deployment sets one.

* fix(router): let deployments opt back in to a client model_id and stop client component headers on SageMaker chat

Drop the request's model_id after the router defaults are applied, so a default_litellm_params model_id cannot win
over the deployment either, and apply the same precedence on the sync text_completion path. A deployment that lists
model_id in configurable_clientside_auth_params keeps the client's value. SageMaker chat drops any case variant of a
client's X-Amzn-SageMaker-Inference-Component header when the deployment sets a component

* fix(router): keep a SageMaker deployment's component when it pins it through extra_headers

* fix(router): read the SageMaker component header as a pin only on SageMaker deployments

* fix(sagemaker): send the inference component only as a header, never as a body model_id

SageMaker picks the component from X-Amzn-SageMaker-Inference-Component, so the chat
container body and legacy parameters no longer carry model_id. Nova endpoints accept a
fixed body field list, so a pinned Nova deployment could reject the extra field.

* fix(health): probe header-pinned SageMaker deployments at their component

Health checks bypass the router, so a legacy sagemaker/ deployment pinned only
through its inference component header sent no component on /health while routed
requests now send it. Also update the two SageMaker integration tests that still
asserted the old precedence and the dropped body field.

* fix(health): apply the router's model_id pin rule to health probes

Health probes now share the router's pin function, so a deployment that lets
clients set model_id gets no header pin and its own model_id wins without
reading the header. Header keys that are not strings are skipped when
reading the inference component header instead of failing the call.

* fix(sagemaker): keep header keys that aren't strings and report a string opt-in in health checks

The chat transformation now keeps extra_headers keys that aren't strings, so such a deployment fails the way it did before instead of answering 500, and the model_id pin checks configurable_clientside_auth_params with isinstance, so a string there reaches the health probe's own error capture again.

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-30 02:40:01 -07:00
Mateo Wang
4c8b90945f
fix(realtime)!: send the Gemini Live setup at the first content so session.update settings apply (#39864)
* fix(realtime): send Gemini Live setup on the client's first message so session.update modalities apply

Gemini and Vertex AI Live accept exactly one setup per session, and the proxy sent its
default audio setup at connect, so a client's session.update (GA output_modalities or
beta modalities) was dropped and every answer came back as audio. The setup now goes
out on the client's first message: a session.update becomes that setup, anything else
gets the model default. Bedrock Nova Sonic keeps its connect-time setup through a
provider hook. Text setups label deltas response.output_text.delta, and downgrading
TEXT to AUDIO on an audio-only model logs a warning

* fix(realtime): keep one Gemini Live response modality and skip the "inf" token limit in the setup

* fix(realtime): merge every session.update sent before the first content frame into the Gemini Live setup

Gemini and Vertex Live accept one setup per connection, so the bridge used to
turn only the first session.update into that setup and drop the rest without
telling the client. Now every session.update that arrives before the first
content frame is held, deep-merged (later values win per field), acknowledged
with a session.updated built from the setup it would produce, and the merged
session becomes the one setup sent with the first conversation item, audio
frame, or tool result. The session events also surface the systemInstruction
text as instructions, which the old string check never matched because the
setup carries it as a Content object.

* fix(realtime): cap the session.update merge depth so the recursive detector accepts it

_merge_session_fields recurses once per nested mapping level, so CI's
recursive_detector flagged it. Bound the descent at eight levels (the OpenAI
session object nests four deep at most), replace anything deeper wholesale
with the later session.update's value, and list the helper with the other
depth-capped functions the detector allows.

* fix(gemini-live): accept a string output modality and drop values Live cannot produce

A bare string output_modalities is read as a one-element list and modality values Gemini Live cannot produce are dropped with a warning instead of reaching the backend, so a malformed session.update no longer costs the session. Transcription guardrails now fold their VAD disable into the first held session.update only, so the merged setup carries it exactly once.

* fix(gemini realtime): answer an error for session updates Gemini Live cannot apply

An output modality the model cannot produce, or a session.update sent after the
first content, now gets an invalid_request_error event instead of a silent swap
or drop, unless drop_params is set. The synthetic session.created reports the
model's default modality, setupComplete no longer adds an extra session.updated,
and merging nested session updates is linear in their size

* fix(gemini realtime): send the setup before guardrail blocks and idle deadlines, mark push-to-talk turns

A pre_call guardrail blocking the first typed message now goes out after the
setup, so Gemini and Vertex no longer close the session with 1007.

Push-to-talk sessions (turn_detection null or create_response false) wrap each
audio turn in empty activityStart and activityEnd messages, which Gemini needs
when automatic activity detection is off.

A session with no content after 60 seconds sends its setup, since Vertex
refuses a setup 300 seconds after connect. Held session.update frames past
10 MB answer session_update_too_large, and deferred mode now errors on a
second session.update unless drop_params is set.

* fix(realtime): ack unchanged Gemini Live session.update resends and honor null turn_detection

An update that leaves the applied setup unchanged now gets session.updated
instead of session_update_not_applied, so clients that resend their session
with the first response keep working. Deferred setup mode merges held updates
like the default mode, turn_detection null turns Gemini's activity detection
off, drop_params drops a voice outside the documented Live voices, and the
lazy setup deadline moves to 240 seconds

* fix(realtime): keep Vertex activity detection off for null turn_detection and set up deferred sessions before text content

* fix(realtime): refuse unscreenable Gemini audio, send held setup after buffered audio, reject unknown Vertex voices

A realtime_input_transcription guardrail cannot screen Gemini Live audio before the answer starts, so audio input under one is refused with an audio_input_not_screenable error instead of reaching the model. In deferred setup mode a session.update that follows buffered audio is sent as setup at once. Only conversation content triggers the setup, so control events no longer lock in a default one. Vertex rejects an undocumented voice on the session.update, or drops it with drop_params, while Google AI Studio forwards any name.

* fix(realtime): hold first content so a following session.update joins the Gemini setup

Convert flat realtime tool schemas through Gemini's schema builder, and collapse a text and audio pair to what the model can produce on text-only models too

* fix(realtime): check AI Studio Live voices like Vertex

* fix(realtime): forward any voice to AI Studio Live models that accept it

* fix(realtime): map OpenAI TTS voices before the Live voice check, keep outage spend rows, share the playground session config

The Live voice gate now judges the Gemini voice an OpenAI TTS name maps to, so fable and onyx reach Vertex as Umbriel and Orus again. An upstream close before the held setup was sent is logged as a session, not a refusal. The playground sends one server_vad session config for typed and voice turns, and the e2e, integration, and dashboard tests follow lazy setup.

* fix(realtime): take the lazy setup clock as a dependency so its tests run on a stepped clock

The deadline and grace tests waited on real 50 ms timers, and the merge cost
check compared wall-clock runs. The session now reads an injected clock,
time.monotonic by default, the two tests step it, and the merge cost check
moves to the CodSpeed benchmarks. The Nova Sonic style handler test passes
the backend through wrap_backend, which main added after the test was written
2026-10-30 01:31:39 -07:00
Filip Sinitsin
e81dd348a4
fix(proxy)!: log and bill Responses-style bodies sent to /cursor/chat/completions (#30277)
* fix(proxy): restore spend logging for /cursor/chat/completions (#30126)

The /cursor/chat/completions route processes requests through the Responses API pipeline, but _get_metadata_variable_name did not treat it as a litellm_metadata route. Proxy auth metadata therefore landed in the user-facing metadata param, so the spend tracking callback saw user_api_key=None and silently skipped the SpendLogs write. Add the route to LITELLM_METADATA_ROUTES and cover it with regression tests.

* test(proxy): pin provider metadata and key billing on /cursor/chat/completions

A Responses-format body on the Cursor route now sends OpenAI only the metadata the client sent and writes a spend row that bills the key. The chat-body branch, which this fix also moves onto litellm_metadata, still bills the key with its team and request tags. The scripted chat reply gets a unique id per test, since a shared id let only the first spend row per database land

* fix(proxy): pick the Cursor route's metadata field from the body shape

A Responses-style body on /cursor/chat/completions keeps proxy metadata in
litellm_metadata, the same as /v1/responses, while a Chat Completions body
keeps metadata, the same as /v1/chat/completions. Adding the whole route to
the internal-metadata list let a client's chat-body tags replace the key's
tags in spend logs and skip tag budgets.

* fix(proxy): keep get_metadata_variable_name callable with only the request

* fix(proxy): check client tags against tag budgets and read agent limits from litellm_metadata on Responses bodies

A Responses-style body on /cursor/chat/completions now keeps its proxy fields in litellm_metadata, so the pre-call tag budget check and the v3 agent rate limiter stopped seeing the client's metadata.tags and the x-litellm-agent-id value. The pre-auth tag check now merges the caller's metadata.tags into the selected metadata field, sharing one helper with request setup, and the limiter reads agent_id from either field

* fix(proxy): check client tags sent in a JSON-string metadata against tag budgets on Responses bodies

* fix(proxy): answer 422, not 500, when a budget rejects a Responses body whose metadata is a JSON string

* fix(proxy): keep key guardrails, metadata type errors and failure-row client details on Responses-style bodies

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-30 00:18:15 -07:00
devin-ai-integration[bot]
4a5c5665fa
fix(azure_ai)!: keep OpenAI prompt cache breakpoints on the Foundry chat completions path (#45299)
* fix(azure_ai): keep OpenAI prompt cache breakpoints on the Foundry chat completions path

* fix(azure_ai): keep only OpenAI content part types when preserving prompt cache breakpoints

* fix(azure_ai): share the breakpoint check with the cache-mark step and keep only text parts

A deployment's model_info opt-in is registered under its provider-prefixed cost-map key without a litellm_provider, so that prefix now names the provider the flag speaks for.

* refactor(azure_ai): read text parts through a walrus and mark new test values Final

* fix(azure_ai): read a router-registered breakpoint opt-in only for azure_ai and keep a message mark off dropped file parts

* fix(azure_ai): read the breakpoint opt-in from each deployment's own model_info and base_model

A router deployment without its own supports_prompt_cache_breakpoint flag now
reads the cost map as loaded, so a sibling's opt-in on the same model string no
longer turns its requests into 400s. The shared hook no longer reads router
registered keys for azure_ai.

* fix(azure_ai): send breakpoints in Foundry's shape under drop_params and fall back to the model name

* test(integration): expect the injected breakpoint on the Foundry GPT-6 chat wire

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-29 23:49:55 -07:00
yuneng-jiang
d76db470f9
test: update integration tests left stale by recent product changes (#49143)
* test: update integration tests left stale by recent product changes

* test: expect proxy Messages calls to stay on Python with Rust enabled
2026-10-29 18:06:28 -07:00
zoveki
bbc68e21f9
feat(dashscope): add image edits and text-to-speech (#48998)
* feat(dashscope): add image edits and text-to-speech

* fix(dashscope): bill reported speech characters and input images, follow the chat region for edits, keep the call timeout on the audio download

* fix(dashscope): bill deployment image prices as main does and route edits to the host speech uses

A deployment that sets its own input_cost_per_image, output_cost_per_image or input_cost_per_pixel bills through the default image calculator again, so an override is never read as a per-input-image price and pixel pricing on qwen-image-3.0-pro no longer raises.

Edits now resolve the caller's api_base or DASHSCOPE_API_BASE through the same API-root helper speech uses, on any host, with DASHSCOPE_API_BASE_IMAGE still winning. An image response whose usage is null or not an object keeps its images instead of raising.

* fix(images): bill a deployment's own prices on image edits through the proxy

The proxy builds the logging object before the router picks a deployment, so image_edit never handed the deployment's price fields to its cost calculation and billed the cost-map price instead. Pass the custom pricing params with the pre-call update, as the Responses API does.

* fix(images): bill per-pixel image prices at a DashScope W*H size

DashScope's mapped size reads 1328*1328, which the cost size reader rejected, so a deployment's input_cost_per_pixel billed every DashScope generation or edit at the 1024x1024 default.

* fix(dashscope): bill image prices by their meaning, report the rendered size, prefer the deployment edit base, fetch audio over https

* fix(dashscope): read the rendered image size from output_width and output_height

* test(images): bill a fal edit at its deployment's own image price

* fix(dashscope): keep the qwen-image-3.0 input-image fee out of the shared cost map row

Released proxies read the cost map from main and bill input_cost_per_image as the
flat price of each DashScope image, so the row's $0.003 input fee billed every
qwen-image-3.0 image there at a tenth of its $0.03 price. A deployment can still
set input_cost_per_image to bill DashScope's input images

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-29 17:48:38 -07:00
ryan-crabbe-berri
d884167bf2
fix(responses): only merge repeated Codex tools when drop_params is on (#49112)
Without drop_params, every copy of a tool from additional_tools input items
is forwarded as sent, so a provider that rejects duplicate tools returns its
own 400. Codex keeps the merge because the proxy turns drop_params on for it.
2026-10-29 12:55:29 -07:00
Mateo Wang
be6d082bdc
fix(anthropic)!: send /v1/messages PDF documents to OpenAI-shaped providers as file parts (#47166)
* fix(anthropic): send /v1/messages PDF documents to OpenAI-shaped providers as file parts

* refactor(anthropic): hold the chat completions document change for a later commit

* fix(anthropic): send /v1/messages PDFs as file parts and keep file parts on azure_ai

* fix(snowflake): turn file parts into document blocks for Cortex Claude

* fix(prompt_security): sanitize chat file parts that carry file_data

* fix: answer 400 or drop PDFs for models that cannot read them

Models whose model_info or cost map entry sets supports_pdf_input to
false, and Ollama, now get the PDF dropped under drop_params and a 400
from litellm without it. GigaChat uploads file parts, SAP keeps sending
them as image_url data URLs, azure_ai passes dict content through, and
annotations other than url_citation move to provider_specific_fields
instead of failing the response.

* fix(router): scope supports_pdf_input to its deployment and keep provider annotation keys

Mark the azure_ai chat models in the cost map that cannot read PDFs, so drop_params drops the PDF and
litellm answers its own 400 without it. Keep url_citation annotations whole, move only typed provider
annotations to provider_specific_fields, and split them the same way when a stream is rebuilt.

* fix(vertex_ai): send PDF file parts to Vertex partner models as image_url

Vertex AI's OpenAI-compatible partner endpoint rejects the chat `file` part, so PDFs from /v1/messages
reach those models as the image_url data URL they took before. Mark native DeepSeek chat models as
unable to read PDFs, and treat a file part as a PDF by its format or filename when it has no data URL.

* fix(cost-map): leave native DeepSeek PDF support unset

/v1/messages reaches native DeepSeek through its own Anthropic endpoint, so the marks never applied there
and would only change chat completions, which this PR does not otherwise touch.

* fix(main): drop a user turn that held only a PDF under drop_params

Dropping the PDF from a PDF-only user turn left an empty content list, which
Foundry and OpenAI-shaped providers reject with a 400, so drop_params still
failed for that request. The emptied user turn is now removed.

* fix: read OCI PDFs, gate deployment base models, and stop empty drops

OCI generic chat sends file parts as image data URLs again, so OCI Gemini
keeps reading PDFs from /v1/messages and chat file parts.

The PDF gate now reads model_info.base_model, drops PDFs under drop_params
for azure_ai deployments that never declared PDF support (the old flattening
behavior), and answers 400 when dropping the PDF leaves no user message.

The cost map marks the Vertex partner text models that a live probe showed
answering without reading the PDF.

* test(integration): cover the PDF input gate, messages PDF file parts and provider annotations

Wire cells for the supports_pdf_input check on chat, chat streaming, /v1/messages and the Responses bridge, its model_info, base_model, cost map and Ollama sources, drop_params at deployment and request level, azure_ai file parts, sibling deployment isolation, Anthropic SDK PDFs reaching azure as titled file parts, Gemini and Bedrock document controls, Prompt Security sanitizing a /v1/messages PDF, and OpenRouter file annotations live, cached and streamed

* test(integration): cover Snowflake Claude and SAP in the messages PDF wire cells

* fix(azure_ai): flatten non-PDF file parts to text as before and cover the GigaChat PDF upload

* fix(main): PDF check reads the deployment's top-level base_model

* fix(main): keep roles alternating after drop_params removes a PDF-only turn

When ensure_alternating_roles is set, re-run role normalization on the messages left after the PDF check drops a turn, so two assistant turns never end up next to each other

* test: type the PDF and annotation tests the PR added

* test: type the GigaChat, SAP and Snowflake PDF tests
2026-10-29 08:06:12 -07:00
devin-ai-integration[bot]
bee9f679dc
fix(github_copilot): treat a malformed api-key.json like a missing one (#44723)
* fix(github_copilot): treat a malformed api-key.json like a missing one

* fix(github_copilot): read the key and the endpoints of api-key.json as separate strict views

* fix(github_copilot): ignore a non-UTF-8 api-key.json like any other malformed file

Read the cached key file as bytes so a UTF-16 file, which Windows
PowerShell 5.1 writes with >, fails JSON validation instead of raising
the decoder's error, and the request gets the refresh or the login hint

* fix(github_copilot): decode UTF-16 key files and refresh over a malformed endpoints field

* fix(github_copilot): decode a UTF-16 access-token file and refuse an undecodable one like a missing one

* fix(github_copilot): use the refreshed key when api-key.json cannot be written

* fix(github_copilot): keep a refreshed key that cannot be saved in memory

* fix(github_copilot): serve a kept key and its endpoint together after the key file is repaired

A key held in memory is now checked before api-key.json, so the token and the endpoint come from the same source, and the map holds one entry per key file with the access token beside it

* test(github_copilot): type the authenticator test fixture and the tests this branch adds

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-29 03:46:44 -07:00
devin-ai-integration[bot]
184ec7b894
fix(vertex_ai): build the cachedContents collection URL under a custom api_base (#44783)
* fix(gemini): use collection endpoint for custom context cache

* fix(vertex_ai): build the cachedContents collection URL under a custom api_base

* fix(vertex_ai): keep a gateway prefix that contains /publishers/

* test(integration): expect the cachedContents collection URL on a custom Gemini api_base

* fix(vertex_ai): cut cache collection URLs at the base's location, graft a mount prefix

* fix(vertex_ai): cache under a publisher model api_base in that model's location

A Vertex AI api_base that names an endpoint or no model keeps its old cache URL, so
no cache is created that the chat call cannot use. The cache body takes its project and
location from the api_base model, matching the collection URL.

* fix(vertex_ai): cache for the api_base's model and skip caching on a Gemini v1 base

A Vertex api_base naming a publisher model now gives the cache its model as well as its project and location, so the cache matches the model generateContent calls. A Gemini api_base ending in /v1 skips context caching, since the v1 API has no cachedContents and rejects the cachedContent field.

* fix(vertex_ai): check the cache token minimum against the api_base's model

The cache is created for the model a publisher-model api_base names, so the minimum prompt size now comes from that model too. A prompt below its minimum is sent uncached instead of failing at cache create.

* fix(vertex_ai): use the stricter cache minimum and send a Gemini base's 404 uncached

---------

Co-authored-by: paulo037 <paulo.h.carneiro.18@gmail.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-29 03:46:35 -07:00
devin-ai-integration[bot]
b93d26bc85
fix(responses): drop a malformed prompt_cache_breakpoint under drop_params on the direct /v1/responses path (#45330)
* fix(responses): drop a malformed prompt_cache_breakpoint under drop_params on the direct /v1/responses path

* refactor(responses): reuse the shared prompt_cache_breakpoint_for_wire helper in the chat bridge

* fix(responses): read a non-flag deployment drop_params as unset in the marker walk, like the param mapper

* fix(responses): drop every prompt_cache_breakpoint under drop_params when the model has no breakpoints

Also drop a marker set on the message item itself, sanitize before the
cache_control conversion so a client typo no longer erases that breakpoint,
and skip items that carry no marker so unmarked history costs no extra walk

* fix(responses): keep a valid prompt_cache_breakpoint unless the model is known to reject it

A deployment alias the cost map does not know (an Azure deployment name, a fine-tune, a
litellm_proxy alias) kept its valid markers before this PR and must keep them under
drop_params. base_model is read from litellm_params first, then model_info, as chat
completions does.

* fix(responses): drop a valid marker on known non-breakpoint models and keep the tool schema gate unchanged

* fix(responses): judge breakpoint support by model name so cost-map aliases keep valid markers

* fix(responses): keep the caller's request stripped of cache_control as before when a marker is copied

* fix(responses): keep valid prompt cache markers in place and let cache_control fill a dropped one

Under drop_params the marker step now hands back the caller's own objects when every marker is
already wire-safe, so a request with valid markers is no longer copied and walked a second time.

A deployment that turns cache_control into markers keeps the caller's own markers too, whatever
its base_model says, so one request never gets two answers about breakpoint support.

On /v1/messages a malformed raw marker next to cache_control is dropped before the conversion,
so the block gets the cache hint cache_control alone would give, as on /v1/responses.

* test(responses): keep a null marker and the original key order on a model that reads breakpoints

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-29 03:37:54 -07:00
paveln83
032342d2b7
fix(responses): resume background response streams with stream and starting_after (#48819)
* fix(responses): resume background response streams with stream and starting_after

Retrieving a response with stream=true and starting_after=N now returns the
replayed event stream from OpenAI and Azure instead of one JSON object, on the
SDK (get_responses / aget_responses) and on the proxy's GET /v1/responses/{id}

Providers that cannot replay a stored stream drop the params under drop_params
and raise UnsupportedParamsError without it. Malformed values answer 400 with
the param named. A replay that reaches its terminal event settles the
background response's bill once and passes the provider's usage through
without estimating one. An empty replay (cursor at the end) ends normally

* fix(responses): encrypt replayed stream ids and resume Azure AI Foundry streams

A replayed background stream now gets the same response id encryption as a
created one, so the key that made the response can reuse the id. Azure AI
Foundry deployments resume streams like Azure ones, and the chat-bridged
Responses iterator closes its wrapped stream instead of raising.

* fix(responses): drop malformed resume params under drop_params and keep JSON answers to a streamed retrieve

A bad stream flag drops to a plain retrieve and a bad cursor to a full replay when drop_params is on, and a cursor without stream is ignored even when malformed. A streamed retrieve that the upstream answers with JSON returns the response object instead of an empty stream. get_responses forwards only the include keys from extra_query.

* fix(responses): give proxy stream resumes the deployment stream_timeout and document retrieve query params

* fix(responses): keep deployment stream off retrieves and reject oversized cursors

* fix(responses): keep router default stream off plain retrieves

* test(responses): black box coverage for resume refusals, drop_params, stream timeout and billing

* test(responses): replay a real stream in the billing test and type the fake wrapper chunks

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>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-29 02:31:22 -07:00
simonp9
dff474e450
fix(responses): replay the original Responses item order, phase, and boundaries on the next chat turn (#48912)
* fix(responses): replay the original Responses item order, phase, and boundaries on the next chat turn

* fix(responses): keep tool-call-only stream content empty and drop unreachable replay guards

* fix(responses): reject an assistant phase OpenAI does not define unless drop_params is set

* fix(responses): keep other providers' reasoning and stream output unchanged and fall back on records that do not fit

* fix(responses): label a structured output turn with its returned message's phase and keep lone surrogates from failing the checksum

* fix(responses): leave a turn without a phase when its content message cannot be read

* test(responses): add black box tests for chat turn history replay

* test(responses): expect the replay record on bridged chat replies

* test(responses): expect the replay record in bridged chat translation cases

* fix(streaming): keep the served service tier on the finish chunk the stream adds

* test(responses): read the expected chat message without JSON validation so the replay matcher stays

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-29 00:56:02 -07:00
ryan-crabbe-berri
9a68573e9b
fix(responses): keep each Codex tool once when its tool list repeats across turns (#49006)
* test(responses): cover Codex tool lists repeated across turns on the Bedrock chat bridge and Mantle

* fix(responses): keep each Codex tool once when its tool list repeats across turns

* test(responses): type the new tool list dedupe unit tests
2026-10-29 00:33:40 -07:00
tessellark
dbaae521f9
fix(responses): drop the null created_by OpenAI rejects when output items are replayed (#48783)
* fix(responses): drop the null created_by OpenAI rejects when output items are replayed

* fix(responses): drop a null created_by from replayed input items too

A conversation saved from an older proxy still carries created_by: null on its compaction, apply_patch and shell items, so OpenAI keeps rejecting the replay. Drop the null from input items on the HTTP request transform and the WebSocket response.create rewrite, keeping a created_by that is not null.

* fix(responses): drop a null created_by from counted input items too

* test(responses): cover saved reasoning nulls and re-compaction of a saved conversation

* fix(responses): drop a set created_by from input under drop_params

* fix(types): type the Responses output serializer's parameters

* fix(responses): drop a set created_by on reasoning items and Perplexity presets under drop_params

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 21:11:49 -07:00
paveln83
49ccce7813
test(extraction): audit #47473 (#48898)
* test(extraction): audit #47473

* test(extraction): check over-budget extraction never reaches Cloudflare

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 17:12:07 -07:00
moe-berri
9e878cfae2
test(gemini): cover every web source for a grounded passage (#48861)
* fix(gemini): preserve every grounding web source

* test(gemini): cover all grounding sources per passage

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

---------

Co-authored-by: Zabir Raihan <zabirraihan@icloud.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-28 16:26:28 -07:00
moe-berri
cf7c35a117
fix(anthropic): map model_context_window_exceeded stop reason to length (#48874)
* test(anthropic): cover model_context_window_exceeded finish reason on chat and responses

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

* fix(anthropic): map model_context_window_exceeded stop reason to length

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

* fix(rust): mirror model_context_window_exceeded finish reason mapping

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

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-28 13:48:37 -07:00
zoveki
41fcce62ff
fix(gemini)!: accept Google's current Managed Agents fields on create and interactions (#48750)
* fix(gemini): send Google's current Managed Agents fields on create and forward agent_config on interactions

* fix(gemini): accept id on managed agent get, delete, and list_versions

* fix(gemini): drop unsupported agent fields under drop_params and keep agent_config keyword-only

A model interaction drops agent_config under drop_params, agent create drops wrongly typed optional fields under drop_params and otherwise answers 400 naming the field the caller sent, and agent_config on interactions create is keyword-only so positional response settings stay in place

* fix(gemini): drop a non-object agent_config under drop_params and name the id alias the caller sent

* fix(interactions): reject agent_config on bridged providers and drop a typeless one under drop_params

Bridged providers answer 400 for agent_config without drop_params and drop it with drop_params. A Gemini agent run drops an agent_config without a string type under drop_params. The Gemini agents wire test expects Google's current upstream field names.

* fix(interactions): honor additional_drop_params for agent_config on bridged providers

* fix(agents): drop a clashing instructions alias under drop_params

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-28 12:37:59 -07:00