With every kept get_configured_mode test using mode "chat", a Router that
answered "chat" for any non-blank configured mode passed all four of them
(the deleted #39630 pair's audio_speech case was the only test catching it).
Read the mode back as audio_speech on an unmapped model so the configured
value itself is what the test checks. Six hand-applied mutations of
Router.get_configured_mode, including that hardcoded-chat one, are now all
killed by the four surviving tests.
PRs #39630 and #39634 both added test_get_configured_mode_reads_deployment_model_info
to tests/test_litellm/test_router.py, so the staging tip defines it twice and the
required lint check fails with F811 on every PR synced past 321636ef5d.
Keep the four tests from #39634 (mode read, None for unset or unknown, no wildcard
pattern routing, malformed values treated as absent), which subsume the #39630 pair,
and delete that pair. Five hand-applied mutations of Router.get_configured_mode are
all still killed by the surviving tests.
* fix(docker): match USE_DDTRACE case-insensitively and route build_from_pip through prod_entrypoint.sh
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(docker): run build_from_pip ENTRYPOINT and CMD through the shipped prod_entrypoint.sh
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>
* fix(mcp): resolve OAuth broker endpoints by server_id with IP access checks
Resolve named OAuth lookups through server IDs while retaining client IP checks\n\nCo-authored-by: KK291860 <krishnakumar.kocherykumaran@sephora.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* ci: retrigger e2e pipeline
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>
Listing agents (GET /v1/agents and MCP agent_search) treated the absence of any
agent grant on the key or team as permission to see every agent. Non-admin keys
now list only the union of explicit grants, and dashboard sessions resolve that
union through the user's real teams and user row instead of the shared
dashboard team. Proxy admins still see everything and direct access to a named
agent is unchanged.
Resolves LIT-6862
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(auto_router): bill the routing embedding to the caller's key and team
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* refactor(auto_router): validate the forwarded caller metadata with a pydantic model
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>
Always serialize object_permission.agents and agent_access_groups in
the team update payload so removing the last agent in the dashboard
sends an explicit empty array instead of omitting the key, which the
backend merge treats as no change
Resolves LIT-6861
Co-authored-by: yassin <yassin@berri.ai>
The cancel test accumulated chunk_count and reassigned response_id on every
iteration, and the lock watcher rebound its query result on every poll. Both are
the mutable-local pattern the repo avoids.
The stream now drains through a generator that stops at the first chunk carrying
a response id, so the caller binds streamed_ids once and reads the id off the
tail. Empty stream, no-id stream and first-chunk-id all behave exactly as the
loop did. The watcher inlines its poll result.
Without the auto_router feature in the signed enterprise license a proxy may hold
one complexity router with classifier_type heuristic_v2 across config.yaml and the
DB; with it the limit is lifted. The ceiling is derived once from LicenseCheck and
handed to the Router, which refuses the extra router at registration. config.yaml
over the limit refuses to start, and /model/new, /model/update and
PATCH /model/{id}/update refuse the write with a 403 before touching the DB.
Expiry follows the existing max_users/max_teams pattern: judged when the
license is verified, not on every call, and a verify that rejects the license
(expired or unreadable) leaves no signed payload behind. The rollback after a
failed upsert re-admits state that was already serving, so it is exempt from the
ceiling: an edit that fails, including one refused by a ceiling that has since
tightened, leaves the router serving its previous configuration.
A write that leaves a row on heuristic_v2 under a limited license runs in one
transaction that takes a Postgres advisory lock before counting the DB rows plus
this proxy's config.yaml routers, so concurrent writes on any pod cannot both
claim the sole slot and no surplus row is ever persisted.
Only the row insert runs under that lock: the team model bookkeeping, which
needs a second pool connection, runs after the transaction has committed.
PATCH /model/{id}/update follows the same order as create: the row is written
through the slot first and the team's model list is updated only afterwards, so
a refused write leaves the team as it was.
The slot transaction bypasses the repository's publish-on-write, so it
publishes the config change once after commit, as delete_team_models does.