The proxy-endpoints shard failed with KeyError: 'model' because the new Anthropic post-call context translation reached translate_anthropic_to_openai with request data that only carried messages and guardrail metadata.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Response-side guardrail scans on OpenAI Chat Completions, Anthropic Messages, and OpenAI Responses now carry structured_messages (the request turns scoped exactly like the pre-call scan, closed by the model's reply as an assistant turn) and tools (the request's function definitions), in addition to texts, images, and tool_calls.
Guardrails that used structured_messages or tools as a response-side signal (akto, crowdstrike_aidr, hiddenlayer, openai moderations, promptguard, qualifire, straiker) keep their previous response payloads. Logging-only scans whose output translation differs from the input translation get a chat-shaped request so the context survives.
Resolves LIT-6628
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The session usage collapsed duplicate query strings across turns while the
price was per turn, so two turns asking the same question paid two fees yet
reported web_search_requests 1. Sum each turn's grounding requests so the
counter matches the bill; duplicates within one turn still collapse.
A converted-stream request whose cache entry is a plain (non-stream) object is
replayed as that plain object, so nothing later fires the success callbacks.
Decide deferral from the replayed result's type instead of the request kwargs.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
A deployment hook (Headroom, code interpreter, web search) can downgrade
kwargs["stream"] to False while the caller still expects to iterate the
result. The cache handler keyed stream replay and callback deferral off
the raw flag, so a cache hit returned a plain object to a caller that
iterates, and the Responses iterator never persisted the converted
stream in the first place. Key both off the conversion marker as well
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Deployment hooks such as Headroom downgrade stream=True to a non-streaming provider call and the agentic loop then hands back a CustomStreamWrapper (or MockResponsesAPIStreamingIterator for Responses). wrapper_async still saw kwargs["stream"] is False, so it took the non-streaming success path with a lazy stream object: no standard_logging_object was built, the proxy cost callback raised failed_tracking_spend, and the wrapper's own end-of-stream dispatch was deduped away. Treat a lazy stream result as streaming for logging regardless of the downgraded kwarg. Regression in v1.99.0 via #35017
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Anthropic's preserved-thinking controls (`thinking.block_binding`, Claude
Fable 5.1) are only accepted alongside the beta header
`thinking-binding-controls-2026-08-01`. The proxy forwards the body field
untouched but `filter_and_transform_beta_headers` drops the header because
it has no entry in `anthropic_beta_headers_config.json`, so Bedrock and
Vertex reject the request with
"thinking.adaptive.block_binding: Extra inputs are not permitted".
Map the header for anthropic, bedrock, bedrock_converse, vertex_ai and
databricks (same beta name on all of them per Anthropic's docs). azure_ai is
left null pending verification on Foundry.
A guardrail that rewrites text and hands back tool_use arguments that are
not a JSON object used to leave the text rewrite applied when the request
was rejected, so failure logging saw a half-rewritten request. Every
rejection now happens before any write to system or messages.
The router clamps a negative request_retry_count found in request metadata before counting a failure, and the proxy strips a client-supplied request_retry_count with the other router-reserved metadata fields. The rust OCR lifecycle test that trips the per-request cap now plants request_retry_count instead of attempted_retries, which the cap no longer reads since the previous commit