Interaction, video, container, code, MCP tool and A2A calls are charged per call and keyed on
the id the provider minted for that call, the same shape as a chat completion, so a reused id
would drop their rows as well. List them with the inference calls.
The fake table, the wiring helpers and the new tests took Any where the fixtures have concrete
types. The collapse test now walks the file, fine-tuning, video, container and Responses reads
and an unknown call type, and a sibling test walks the inference call types that are re-keyed.
The flush exempted an enumerated set of object-keyed call types from the re-key, and file
retrieves were not in it: two reads of one file used to share a row and were re-keyed into
three. Every call type outside the inference family is keyed on the id of the object it
addressed, so list the inference calls instead and let everything else keep collapsing.
get_provider_response_id compared candidates against the top-level kwargs call id only, while the
row resolves its litellm_call_id with a fallback to litellm_params. Pass the resolved call id in so a
response id equal to the nested call id is not published as a provider id.
A response without an id of its own is keyed on the id the standard logging payload
resolved, so the provider id kept in metadata follows that source too, stopping short of
the proxy's own call id, which is not a provider identity.
LiteLLM_SpendLogs.request_id is the provider's response id and the flush inserts with
skip_duplicates, so a self-hosted OpenAI-compatible server that answers every request
with the same completion id had every request after the first served and charged but
never logged. The flush now reads back, from the writer, the identity of the rows
create_many skipped and re-inserts the ones that belong to a different request keyed on
their own litellm_call_id, with the provider id kept in metadata.response_id. Rows the
flush already stored (a replay after a transport retry, under either key) and rows keyed
on a stored object's id (batch polls, file uploads, the zero-priced object reads) stay
skipped, and a read-back the database rejects leaves the rows skipped instead of
requeueing the flush.
request_id keeps meaning the id the caller was shown, so GET /spend/logs?request_id=
by response id, the Logs page search, and Responses API previous_response_id session
lookups behave as before.
_client_async_logging_helper re-submitted logging_obj.success_handler to the
executor after _dispatch_success_logging had already done so, running the same
success pipeline twice per async request and racing on shared logging state.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
FastAPI walked the multi-megabyte /model/info payload through jsonable_encoder
before json.dumps on every request. Return a prebuilt orjson Response instead,
keeping jsonable_encoder as the fallback for datetimes and other non-native values
Resolves LIT-5724
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
feat(auth): breached password detection and forced change
BREAKING CHANGE: users can no longer change their password by issuing a request with a password parameter to /user/update; this has been replaced with /user/password/change dedicated to secure password change.
Annotate screen_login_password_for_breach's update/where dicts with
prisma input TypedDicts and replace authenticate_user's conditional
dict splat with plain keyword arguments, clearing the LIT002 lines
this branch added in login_utils.py. No behavior change: an unflagged
login now passes allowed_routes=None and metadata={} explicitly, which
are the parameter defaults
A breach found during a login previously only flagged the account for the
NEXT login, handing out one free unrestricted 24h session. The HIBP screen
is now awaited before the session key is minted (worst case one 5s window
per user per 24h, fail-open unchanged), so a fresh hit restricts the
current session and the dashboard routes straight to change-password.
Also repairs two casualties of merge f5e47974db that the layout tests
caught: the lost usePathname import and a call to migratedHref, which
staging renamed to uiHref.
The Terraform endpoint audit wanted POST /user/password/change covered
or allowlisted; it is a caller-scoped one-shot action, so allowlist it
next to /user/bulk_update. leftnav.test.tsx mocked next/navigation
without useRouter, which SidebarAccountMenu now calls, so every render
in that file threw. The two unannotated audit-log patches in
test_password_endpoints.py get their test-quality-ok reasons.
Also removes the LIT002 violations the PR added: prisma input TypedDicts
annotate the where/data dicts, a shared HTTPExceptionErrorDetail
TypedDict covers the HTTPException detail dicts, and the route decorator
takes a tags tuple.
Admin password sets on /user/update and per-user /user/bulk_update stay
supported and policy-enforced. The request model hides the password from
repr so management alerts never format the plaintext, and the all_users
bulk path rejects passwords instead of writing one plaintext value to
every row.
The staging merge brought BLE001 into the strict ruff set and lowered the
LIT002 ceiling, so the HIBP fail-open except and the params/headers dicts
in password_policy.py now need their noqa and mutable-ok reasons. The
headers dict moves to an annotated Final so the suppression fits the line
limit.
/user/bulk_update awaited a separate HIBP lookup for each user in the
batch, so a degraded-slow HIBP (5s timeout per lookup) could stretch a
500-user batch to ~2500s and time out the request after some updates
had already persisted.
validate_passwords_bulk dedupes the batch's passwords, strength-checks
first, then fires every needed HIBP lookup concurrently, bounding the
worst case at one 5s timeout window. bulk_update_processed_users now
screens the whole batch before the serial update loop, so a rejected
password fails only its own entry and validation failures precede any
persistence.
CredentialLiteLLMParams omitted tenant_id, client_id, client_secret,
azure_scope, azure_username and azure_password, so the strict dump used
by credential reuse and Azure client init dropped them and the reused
credential ended with no auth at all
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The e2e harness exists to prove product features end to end against a live
proxy. The prior Hard Rule carved out an exception for "tests that cover the
harness itself" and pointed at coverage_registry/test_collector.py, which in
practice invited unit tests of harness helpers to be staged alongside e2e
work. That is the wrong tool: harness logic that is worth locking down does
not need a mock-driven unit test living under tests/e2e.
Drop the carve-out. The Hard Rule now reads that no unit tests of any kind
belong under tests/e2e, and the passing mention of unmarked harness coverage
in the transport section is removed so the doc no longer contradicts itself.
coverage_registry/test_collector.py still exists on disk and is left in place
for now; whether to relocate or remove it is a separate decision.
Keeps the base's rule that a non-admin id lookup matching no spend-log row answers 403, so the detail route never consults cold storage without an owner row