* feat: add Laya gateway and classifier backend
* test: cover the pass-through model_group pin and repair the shard fakes
MockRequest in tests/pass_through_unit_tests gains an httpx.URL and an ASGI
scope, which get_request_route now reads inside
_init_kwargs_for_pass_through_endpoint, and the POST-only /laya/v1/systemone
route joins the protocol-constrained exemptions. A built-in pass-through pins
metadata.model_group to the resolved model so a client cannot choose its own
per-model budget key; test_pass_through_endpoints now proves that on a
non-Laya route and drops a duplicated assertion.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
The migration job runs DatabaseURLSettings.apply_to_env(), which rewrites
DATABASE_SSLMODE=verify-full plus DATABASE_SSLROOTCERT into Prisma's TLS
dialect: sslmode=require&sslcert=<CA>&sslaccept=strict. The request-log
index build then hands that same URL to psycopg, and libpq reads sslcert
as a client certificate, failing with "certificate present, but not
private key file" on every verify-full deployment since #43948.
_strip_prisma_query_params now undoes the Prisma dialect before psycopg
sees the URL. sslaccept=strict (or any value Prisma treats as strict)
becomes sslrootcert=<CA> plus sslmode=verify-full whatever sslmode said,
since strict verifies chain and hostname and libpq only does that in
verify-full; sslmode=disable stays off. Without strict, Prisma verifies
nothing, so the CA is dropped and sslmode is kept as is. A URL that also
carries sslkey is libpq's own client-certificate form and is left alone.
Resolves LIT-9169
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(chatgpt): preserve requested service tier in Responses calls
* fix(chatgpt): avoid extra mutable collections in tier filtering
* test(chatgpt): refresh service-tier cases after main rebase
Keep the tier-preservation regression on the current subscription model and match the moved unit-test suite's formatting. The adapter still forwards preferences without claiming backend priority entitlement.
* refactor(chatgpt): map service tier through a lookup table after the allowlist filter
---------
Co-authored-by: ryan-crabbe-berri <ryan@berri.ai>
* fix(oci): resolve the GenAI endpoint realm from the region instead of hardcoding oraclecloud.com
Government (OC2/OC3/OC4) and other non-commercial realms live under a
different second-level domain, so a request for us-luke-1 was sent to
inference.generativeai.us-luke-1.oci.oraclecloud.com and failed DNS
Delegate the lookup to the OCI SDK's region registry when it is installed,
honour OCI_DEFAULT_REALM otherwise, and keep api_base as the explicit override
* fix(oci): read per-region realm metadata without the SDK and cover the registry path
Replace the global OCI_DEFAULT_REALM fallback, which would have redirected
commercial regions too in a mixed deployment, with the SDK's own per-region
sources: OCI_REGION_METADATA and ~/.oci/regions-config.json. Regions not
described anywhere keep their commercial endpoint
Exercise the SDK registry path with a fake oci.regions module so CI, which
has no SDK, still covers it, and skip the real-SDK test on oci.regions so a
namespace package named oci in the tests tree cannot masquerade as the SDK
* fix(oci): validate regions-config.json entries individually and tolerate undecodable files
One malformed entry no longer discards the valid ones, and the file is parsed
from bytes so an undecodable file is logged and ignored instead of failing
every OCI request built without the SDK
* fix(oci): resolve the realm from the compartment OCID so Government regions work without the SDK
The Docker image ships without the oci package and Government deployments rarely carry OCI_REGION_METADATA, so the reviewed fallback still sent us-luke-1 to oraclecloud.com. Every compartment OCID already names its realm (ocid1.compartment.oc2..), so map that key through the SDK's twenty realm domains first, then the metadata sources, then the SDK registry, then the commercial default.
* fix(oci): consult the SDK registry before hand-parsed region metadata and harden the fallback
Review follow-ups on the realm resolver. Read the compartment realm first, then the SDK registry when it is installed, and only then the hand-parsed metadata sources, so the same file is never parsed twice with different rules. Lowercase metadata values like the SDK does, accept single-label realm domains, and expand ~ with os.path so a container without a home directory cannot raise out of URL building. Type the compartment as str | None at the caller, drop populate_by_name, isolate the legacy region tests from the developer's ~/.oci, and keep the new tests on the immutable style.
With DATABASE_URL set and no way to run the Prisma CLI (not on PATH, not importable), run_server used to print a plain notice and keep booting. The server then crashed later inside the DB exception handler with an unrelated ModuleNotFoundError traceback, and the migration-only entrypoint (--skip_server_startup) exited 0 without migrating. It now exits 1 with a red one-line message naming the missing toolchain and how to install it. The no-DATABASE_URL path is unchanged.
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* feat(mcp)!: disable stdio MCP servers by default
stdio MCP servers now only run when the proxy is started with
LITELLM_ENABLE_MCP_STDIO=true. While it is off, existing stdio servers stay
registered but never start: tool listings skip them quietly, direct tool
calls and health checks return a 403 naming the env var, and creating or
updating a stdio server is rejected. The flag is read from the process
environment only, so DB-stored environment_variables cannot turn it on.
The UI reads mcp_stdio_enabled from /.well-known/litellm-ui-config to grey
out the stdio transport, show a banner on stdio forms, and badge stdio
server cards.
BREAKING CHANGE: stdio MCP servers are off by default. Set
LITELLM_ENABLE_MCP_STDIO=true in the proxy environment and restart to keep
using them.
* fix(mcp): ignore stdio flag from config file and read UI flag from the selected worker
LITELLM_ENABLE_MCP_STDIO set under environment_variables in config.yaml is now skipped like the DB-stored value, so only the process environment can enable stdio. The dashboard reads mcp_stdio_enabled from the proxy it is managing, so a control plane shows each worker's own setting.
* test(mcp): cover non-mapping payloads in the shared transport validator
* fix(mcp): skip blocked stdio servers quietly in every listing and keep the UI unchanged until the flag loads
Prompt, resource and resource-template listings now skip a blocked stdio server at debug level like tool listing does, instead of logging a warning per server on every call. The dashboard only treats stdio as disabled once the proxy explicitly reports mcp_stdio_enabled false, so a proxy with the flag on, or an older one without the field, renders exactly as before with no flicker while loading.
* fix(mcp): route blocked stdio tool calls to the flag error and warn once per server
A gateway tools/call naming a blocked stdio server's tool now returns the
LITELLM_ENABLE_MCP_STDIO message instead of "Tool not found".
The "will not start" warning moves out of build_mcp_server_from_table, which
DB reload re-runs on every cycle for rows with a NULL updated_at and which
drafts and test-connection also call. It now fires when a row first enters
the registry or changes transport.
* fix(ui): explain on the server detail page why a stdio server is inert
The Overview and MCP Tools tabs showed "No tools available" with no reason
while stdio is disabled. The detail page now shows the same warning banner
as the edit form, and hands off to the form's banner once editing starts.
* refactor(ui): name the stdio banner conditions on the server detail page
Keeps local/no-long-condition-chain within its budget
* fix(proxy): log the ignored DB-stored LITELLM_ENABLE_MCP_STDIO warning once
The DB config sync re-reads environment_variables on every cycle, so a stored
flag logged the warning on each sync per worker
* test: remove 130 legacy tests owned by stronger unit proofs
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test: list router _embedding and _aembedding as covered via public embedding calls
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: yuneng <yuneng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(ui): register tencent in the Add Model provider dropdown
The Add Model provider dropdown is driven by the proxy's
/public/providers/fields endpoint, which serves
provider_create_fields.json. Tencent was frozen in the test's
ADD_MODEL_UNLISTED_PROVIDERS set, so it never appeared in the dropdown.
Add a Tencent entry (optional api_base + required api_key, matching
TENCENT_API_BASE/TENCENT_API_KEY) and unfreeze it in the backend test.
Register Tencent in the UI Providers enum, provider_map, and placeholder
map so the dropdown resolves the display name and model placeholder.
* fix(tencent): drop test docstring to satisfy comment policy
* fix(ui): bundle the Tencent Cloud logo for the provider dropdown
`migrationJob.retries` and `migrationJob.disableSchemaUpdate` are declared
in values.yaml but referenced by no template, no test and no README row.
Setting either changes nothing about the rendered Job.
`disableSchemaUpdate` is the misleading one: its comment promises "the job
will exit with code 0", but the Job hardcodes DISABLE_SCHEMA_UPDATE=false
and renders it after envVars/extraEnvVars precisely so nothing can turn the
migration off — that ordering is what #12809 fixed. An operator who reads
values.yaml, sets the flag and watches migrations run anyway has no way to
tell the knob is inert. `migrationJob.enabled: false` is the supported way
to skip the Job, and the componentized chart in helm/litellm already ships
a migrationJob block with neither key.
`retries` is simply dead: Jobs retry through `backoffLimit`, which the
chart does render.
Removing values keys is backward compatible — Helm ignores user values that
no template consumes, so existing releases setting either key keep working.
Adds a test pinning the override: with envVars.DISABLE_SCHEMA_UPDATE="true"
the Job's last env entry is still DISABLE_SCHEMA_UPDATE=false, so the
last-wins ordering cannot regress and the key cannot quietly come back as a
chart value. Verified by mutation: flipping the hardcoded value and moving
the entry above the envVars loop each fail the suite.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: ryan-crabbe-berri <ryan@berri.ai>
* feat(docker): one-command quickstart that starts the gateway, Postgres, and the admin UI
scripts/quickstart.sh downloads the quickstart compose file into ~/litellm-gateway, generates the master key, salt key, and a random Postgres password into .env, picks a free port, starts the stack, waits for it to be ready, and prints where to log in. It asks at most two questions (install folder, open the browser) and asks nothing without a terminal, under CI or Claude Code, or with --yes
The compose file reads POSTGRES_PASSWORD and LITELLM_PORT from .env and falls back to the current values, so existing installs keep working unchanged
* fix(quickstart): address review findings on reinstall, ports, gitignore, and binding
Stop with instructions instead of generating a new password when a database volume from an earlier install is still there, since Postgres keeps the original password
Keep port 4000 for an existing .env that has no saved port, and only search for a free port on fresh installs
Only write the catch-all .gitignore into a folder the script created, and warn instead of writing into a folder that already existed
Add LITELLM_BIND to the compose port mapping. It is empty by default, so existing installs keep "4000:4000", and the script sets it to 127.0.0.1: so new installs listen on this machine only
* fix(quickstart): keep generated .env out of git in an existing repository folder
When LITELLM_DIR is inside a git repository that does not ignore .env, add /<path>/.env to the clone's local exclude list (.git/info/exclude) instead of only warning. Tracked files, including .gitignore, are not touched
* fix(quickstart): ignore an inherited LITELLM_BIND and keep .env ignored outside git
Clear LITELLM_BIND from the environment before starting Compose, like the keys and project name, so .env decides the bind address and new installs stay on 127.0.0.1
In an existing folder that is not inside a git repository, add a single .env line to its .gitignore (creating it if needed, without a duplicate), so the keys stay out of commits if the folder later becomes a repository
* fix(quickstart): keep an exported LITELLM_BIND for an .env without one, and show a read-first install
The bind address now follows .env only when .env sets it, which every install this script creates does. For an older .env without a bind line, a LITELLM_BIND exported in the shell is kept, so an intentional 127.0.0.1: is not dropped
The header shows how to download and read the script before running it
* fix(tracing): use ClickHouse URL for reads by default
* fix(tracing): unify ClickHouse storage configuration
* fix(tracing): update dashboard setup copy for one URL
* test(tracing): make tests/unit/tracing a package
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* refactor(config): drop legacy string tracing store variant
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(tracing): own ClickHouse defaults in constants and reject unset env references
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(tracing): use raw regex patterns in config tests
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(tracing): read ClickHouse env defaults when tracing config resolves
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(ui): split audit log query guard to fit condition-chain budget
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>
* test(mcp): fire MCP client test deadlines on conditions instead of wall-clock time
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(mcp): bound MCP client test failure paths independently of the triggered deadline
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(mcp): measure outer-deadline cleanup budget from cancellation, not setup
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>
* refactor(types): replace Any with proven types in 9 files
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(integration): pin xecguard-adjacent guardrail retry, mcp mixed tools, jwt routing and complexity router paths
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* refactor(types): keep only live-provable Any removals
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* refactor(types): type cache_hit as bool | None on the sync success path
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>
* test(straiker): assert a saved api_version v1 with an sk_agt_ key routes to v3 (#44106)
Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(straiker): ignore zombie workers when counting uvicorn children after a kill
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(straiker): describe the saved v1 cell by the v3 route it asserts
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: yucheng <yucheng@berri.ai>
* test(e2e): move live-provider legacy tests into tests/e2e
Port legacy tests that exercise real providers into the tests/e2e suites that own them, using the harness (/model/new plus deferred cleanup) and asserting on what the caller receives. Delete legacy tests already covered at equal or stronger strength by e2e, integration or unit tests, and drop the now empty ocr_testing CircleCI job
* test(e2e): address review on the live-provider test move
Assert the SSE error frame a client actually receives when a post_call guardrail blocks a stream, and require a tool call for every requested city before checking the answer. Restore the OCR matrix and its CircleCI job, the Claude Agent SDK streaming test, and test_async_create_batch, since their SDK-level and callback assertions have no equivalent in tests/e2e
* test(e2e): accept both guardrail block shapes on a blocked stream
A post_call block before the first chunk reaches the client as HTTP 400 with either a JSON error body or a single SSE error frame, depending on whether the block surfaced as an exception or an error chunk. Assert the policy message is present and the blocked output is absent in both
* test(realtime): restore direct SDK realtime tests against OpenAI
The e2e realtime tests go through the proxy and the remaining SDK tests either mock the upstream or assert less, so keep the direct litellm._arealtime tests with and without intent, and TestOpenAIRealtime::test_realtime_connection, in place
* test: make realtime and Nova stream checks deterministic
The direct SDK realtime tests now fail on a refused connection instead of skipping. The with-intent test asserts OpenAI rejects the exact intent value sent, which only happens when the intent is forwarded. The Nova /v1/messages stream test asserts stream structure, stop reason and usage instead of model wording
* test(realtime): own intent forwarding with a unit test instead of a live rejection
Assert litellm._arealtime passes the intent query param into the OpenAI realtime websocket URL, which is the behavior LiteLLM owns, and drop the live test that depended on OpenAI's rejection wording
* fix(azure_storage): keep client call ids from sharing one Data Lake file
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* docs(azure_storage): tighten adls_safe_file_name docstring
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(azure_storage): give ids with dot or empty path segments their own Data Lake file
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(azure_storage): cover failure, cache-hit and edge call ids in Data Lake file names
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(azure_storage): run the Data Lake file name cells on two proxy workers
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(integration): move legacy proxy, router and Redis tests into tests/integration
Port 39 legacy tests to the integration tier that owns them, running against
the scripted upstream, local Postgres and Redis, test-owned wire peers and
owned proxies. Delete 5 legacy tests whose contract is already owned by an
existing integration test, and remove the legacy functions, files and helpers
left unused.
* test(integration): cover recovery of a spent key after its budget is raised
* fix(guardrails): straiker v3 routes sk_agt_ keys to v3 and stops reading a missing verdict as allow
An sk_agt_ key always calls /api/v3/detect, even when the guardrail was saved with
api_version 'v1' by the old shared default: the v1 webhook rejects that key with 401.
A 200 with no decision, or permissionDecision 'ask', now takes the failure policy
instead of allowing the request. A detect-mode action is still not a block.
A response-phase block is no longer remembered under the request, so asking the same
question again is scored instead of refused from memory. The replay memory is scoped
by principal and session together, so two principals on one session id never share a
block. A text-only /guardrails/apply_guardrail call relays the text as a user turn.
The agent_ref description now matches the code: the configured value wins.
* fix(guardrails): straiker v3 relays text beside an empty messages list and keys the memory on the key
/guardrails/apply_guardrail sends `messages: []` beside `text`; an empty list is no
conversation, so the text is relayed as the user turn. A verdict whose blocked_by is not
a list states no decision and takes the failure policy, the same way a missing decision
does. A key that names no user is still the caller, so the replay memory is keyed on the
key when no user is known.
* test(guardrails): build the straiker replay-scope request data without mutating it
---------
Co-authored-by: PhimmStraiker <PhimmStraiker@users.noreply.github.com>
* feat(tracing): add SQL queries and schema-aware query help
* test(tracing): verify help requests and sync API types
* refactor(tracing): render query help with Askama
* refactor(tracing): use jinja extension for query guide
* fix(tracing): preserve query help when discovery fails
* feat(tracing): enforce team SQL scope with managed ClickHouse readers
* test(tracing): verify reads with one ClickHouse URL
* fix(tracing): revoke rotated trace reader credentials
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* refactor(tracing): streamline query help catalog assembly
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(tracing): run query help discovery sequentially
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(tracing): update reader setup request expectations
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>
Remove the ENFORCE_PRISMA_MIGRATION_CHECK opt-in. When PrismaManager.setup_database returns False (database unreachable, connection retries exhausted, or prisma migrate deploy failing after retries) the proxy now always prints the red failure message and exits 1 instead of serving requests against a database whose schema may be behind the code. The --enforce_prisma_migration_check flag stays as a hidden no-op that prints a one-line deprecation warning so existing container args keep parsing; the env var is no longer read anywhere. The standalone migration entrypoint always runs run_server(("--skip_server_startup",)), and the integration launchers drop the flag.
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The daily spend tables store date as text, so a request date that strptime accepts but that is not spelled YYYY-MM-DD (2026-9-24, 2026-09-4, full-width digits) was compared as raw text against canonical rows and matched nothing, and the export route copied it into Content-Disposition, which fails latin-1 encoding and returned 500. A shared parse_canonical_date_range now rejects any spelling whose round trip differs from the input, so every bounded daily activity route (user, team, tag, organization, customer, agent: aggregated, aggregated/keys, search, model top keys, export, cache leakage) and the paginated get_daily_activity path answer 400 before touching the repository, and the export filename is built from the validated dates.
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The shared exclusion predicate negated a PostgreSQL ANY comparison without handling NULL, so NULL entity ids evaluated to UNKNOWN and dropped out of the Unassigned bucket whenever exclude_*_ids was set. The paginated daily rows Prisma filter had the same NOT IN shape and gets the same IS NULL OR NOT IN treatment
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* feat(mcp): add Microsoft 365 (Graph) server to the MCP catalog
* fix(mcp): signpost the self-hosted Microsoft 365 URL and pin the catalog entry in tests
* test(mcp): pin both shipped copies of a catalog icon to the same bytes
---------
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>
#43409 landed three four-condition boolean chains in KeyActivityPanel.tsx, putting local/no-long-condition-chain at 197 against a max of 196, so frontend-lint fails on every PR that touches the dashboard. Name the shared isFiltering && !searching and localOnly && pageRows.length === 0 sub-expressions so each remaining chain has three conditions, and ratchet the max down to the new count of 194.
Co-authored-by: yassin <yassin@berri.ai>
* fix(ui): label the lens trace service filter as agent
* fix(ui): rename the lens traces service column to agent
* refactor(ui): name the lens trace filter state after agents
* test(ui): cover the agent column and filter on lens traces
* style(ui): format lens runs toolbar with prettier
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(ui): name the lens selection footer condition
Keeps local/no-long-condition-chain within its eslint budget on main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* build(deps): bump pyjwt, urllib3 and next to clear osv-scan
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* build(deps): defer pyjwt bump until 2.15.1 clears the uv cooldown
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* build(deps): bump oauthlib to 4.0.0 to clear osv-scan
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* build(deps): drop pyjwt osv ignore now that main locks pyjwt 2.15.0
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>
* feat(rust): embed migration folders with a shared migrate! macro
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(rust): enable syn proc-macro feature for litellm-migrate-macros
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(rust): reject signed versions and symlinks in migrate!
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: Yujong Lee <yujong@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(proxy): persist SSO display name as user_alias on login
Generic/Microsoft SSO already parsed the IdP display_name, first_name and last_name into the SSO result, but the user upsert only wrote user_email and user_role, so the Users table never showed a name. Store the display name (first + last as fallback) as user_alias on first login and on later logins of users whose alias is still empty; never overwrite an alias already set. Whitespace-only names are treated as missing.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(proxy): keep stored user_email when SSO login carries no email claim
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* revert: keep stored user_email change, login writes the IdP email as before
Reverts 47c65cecff. A stored email staying eligible for email-based account linking after the IdP stops sending it is not wanted; the PR goes back to the user_alias fix only
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
uv sync --frozen ignores --no-sources-package, so the "published" branch
already installed litellm-proxy-extras from the workspace (the shipped
main-stable non-root image records file:///app/litellm-proxy-extras in its
direct_url.json). Both branches ran the same install. Keep the single
uv sync that the main and database images use.
Resolves LIT-8899
Usage, cost optimization, user and team pages read the aggregate, paginated key, search, model top key, cache leakage and export routes added by the lower layers instead of downloading every key's daily rows into the browser. Key detail, model top key and search failures render explicit errors with retry controls, Overall Usage shows a loader while the aggregate is in flight, Retry on a failed first key page shows the loading state while it refetches, a short query keeps the loader or first page error visible instead of No keys match, key paging advances by the server offset so a page of already loaded keys moves on and only an empty page ends paging, and search results are stored as rows so a new teams array from the parent does not restart an in-flight search, and the global Cost tab shows a loader instead of zero totals while the aggregate reloads.
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>