reserve() stored fork_only_pid before noticing the runtime already ran under that pid, so a refused reservation still reserved the process: the next enter() cleared the runtime claim and children forked afterwards inherited a dead runtime and hung. Undo the reservation on the error path so the gate is exactly as it was.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Add an aws_textract OCR provider on the Rust route, with no Python path. The
detect-document-text model returns plain lines and analyze-document renders
layout and tables as markdown. Both use Textract's synchronous API, so a
multi-page PDF or TIFF is rejected with an error that names the single-page
limit. A call with no region fails instead of falling back to Bedrock's default
SigV4 covers the request body, and host hooks can rewrite that body before it
is sent. litellm-http now has OutboundRequest, which serializes the body once,
shows those bytes to a RequestSigner and is the only thing a route can send.
Chat, audio transcription and OCR build it after their hooks ran, so a callback
that redacts the body still produces a valid Bedrock or Textract signature
ChatCompletionsAuth and AudioTranscriptionAuth are replaced by
litellm_auth::RequestAuth, and one helper in core turns it into a signed or
unsigned request. Audio transcription now signs only the AWS header set and
rejects a forwarded header that SigV4 computes, the same as chat
The OCR catalog routes aws_textract as Rust required, and the dispatch context
reads the provider from the model prefix so a provider scoped rule can match
The OCR route called back into Python's get_secret_str for every env
fallback. With no secret manager configured that is os.environ behind a GIL
hop, and with one configured it blocked a tokio worker on vault I/O and also
sent the Azure and GCP identity variables, which Python reads with os.getenv,
to the vault. The other Rust routes already read the process environment.
Read the process environment here too. When litellm would read secrets from
a secret manager, decline the Rust route so the Python route serves the call
with the vault-backed keys
EnvironmentProxies holds raw proxy URLs, which can carry user:password, and
it sits inside HttpSettings and HttpClientConfig, so any {:?} of those would
print the password. Derive veil's Redact like the auth crate does. NO_PROXY
stays readable because it holds no credentials.
The media fetcher also rebuilt the hyper-util matcher for every URL and
redirect hop. Build it once when the fetcher is created
Python resolves proxies through urllib.request.getproxies_environment: the
lowercase variable wins, an empty value is unset, an empty lowercase value
clears the uppercase one, and under CGI only the uppercase HTTP_PROXY is
forgotten because a client can set it with a Proxy header. The Rust route
took the uppercase variable even when empty and dropped every proxy under
CGI, so provider calls could skip a required egress proxy
Python resolves the Mistral key as api_key, MISTRAL_AZURE_API_KEY, then
MISTRAL_API_KEY, and the base as api_base, MISTRAL_AZURE_API_BASE, then
the public endpoint, never reading MISTRAL_API_BASE. Native OCR read
MISTRAL_API_KEY and MISTRAL_API_BASE instead, so with the Azure pair set
it sent the call to a different endpoint with a different key. Empty env
values now fall through like Python's `or` chain.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Python resolves the Vertex project and location as call params, then the
litellm.vertex_project / litellm.vertex_location globals, then env, and
Azure AD token refresh from litellm.enable_azure_ad_token_refresh alone.
Native OCR skipped the globals, so a config.yaml litellm_settings value
silently fell through to the credential's project and us-central1, and a
managed identity setup without an API key failed. The bridge now reads
them through a provider_defaults settings group into OcrSettings, and
VertexConfig / AzureAuthInputs slot them in at Python's precedence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Python reads every provider credential fallback (MISTRAL_API_KEY,
AZURE_AI_API_KEY, AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT, Azure AD and
Vertex env, ...) through get_secret_str, which consults the configured
key_management_system before os.environ. Native OCR read std::env
directly, so a key held only in the vault went missing and a stale env
copy silently won. OcrClient now carries an injected secret Lookup that
the connection exposes to providers and auth crates; the bridge backs it
with settings.secret -> get_secret_str, pure Rust keeps the process env.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Native OCR hardcoded the default of five Python constants that come from
env vars, so an operator setting them saw no effect:
REQUEST_TIMEOUT (Rust used 600s, Python 6000s), MAX_IMAGE_URL_DOWNLOAD_SIZE_MB
(0 disables document downloads), AZURE_OPERATION_POLLING_TIMEOUT,
AZURE_DOCUMENT_INTELLIGENCE_API_VERSION and
AZURE_DOCUMENT_INTELLIGENCE_DEFAULT_DPI. OcrSettings reads them through
Lookup with Python's parsing, the bridge builds it per call and OcrClient
carries it into the connection. A zero per-call timeout now falls back to
REQUEST_TIMEOUT, matching `timeout or request_timeout`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
custom_httpx mirrored a Python module that mixes transport plumbing with
OCR orchestration. The transport half (media fetcher, transport errors,
request and header helpers) now lives in litellm-http next to the pool,
TLS, proxies and settings, and the OCR request handler moves to
base_llm/ocr/handler.rs. Drops the unused deserialize_optional_param and
stale dead_code allows.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>