Commit graph

45043 commits

Author SHA1 Message Date
mateo
6b8d6406e3 Merge origin/litellm_internal_staging, resolve grok-4.6 conflict
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-13 21:52:03 +00:00
Mateo Wang
a4ab511d0b
Merge pull request #36805 from BerriAI/litellm_grok_4_6
feat(xai): day-0 pricing for grok-4.6
2026-08-13 14:49:41 -07:00
Yuneng Jiang
726292720c
fix(proxy): guard every view creation, not just the first and last
Against a real Postgres the previous commit still died on MonthlyGlobalSpend:
only 2 of the 8 creation sites went through the tolerant helper, so the losing
replica re-raised on the first unguarded one and skipped the rest.

The regression test now makes every CREATE lose the race and asserts all 8 are
still attempted, which fails on the partial fix.
2026-08-13 14:41:59 -07:00
Yuneng Jiang
28032eb4c9
test(ui): decouple usage-route tests from antd and Tremor markup
Rewrites the usage route's library-coupled assertions to role and text
queries so they characterise behaviour rather than the widget library that
happens to render it. Every test here is green against the current antd and
Tremor components and is meant to survive a migration unedited.

Drops the wholesale vi.mock of antd, @ant-design/icons and @tremor/react in
UsagePageView, UsageViewSelect and activity_metrics, and drives the real
controls instead. Two of those mocks were hiding behaviour: activity_metrics
asserted panel order through a heading role that only existed because the
mock faked an h2, and the Tremor tab stubs flattened panel selection away
entirely.

Replaces DOM-shape lookups with anchors that do not move: a chart is found
from its own heading rather than a fixed wrapper depth, an active panel is
identified by the inactive markers both tab libraries set, and select options
are matched by text since antd's real options carry no option role.

Adds the missing characterisation test for team_multi_select, and covers the
mount contracts the route depends on: Tremor keeps every tab panel mounted,
and antd Collapse keeps a section mounted once it has been expanded, which is
what preserves the view-mode state a model section owns.
2026-08-13 14:23:26 -07:00
yuneng-jiang
fa498391a4
Merge pull request #36129 from BerriAI/litellm_playground_shadcn
feat(ui): migrate playground chat controls to shadcn
2026-08-13 14:21:38 -07:00
mubashir1osmani
2a7f5f270c Merge remote-tracking branch 'berri/litellm_internal_staging' into litellm_playground_shadcn
# Conflicts:
#	ui/litellm-dashboard/eslint-suppressions.json
2026-08-13 13:54:11 -07:00
Yuneng Jiang
8ada34fe2c
test(ui): characterise guardrails route behaviour before shadcn migration
Rewrite the PatternModal test off antd class selectors onto role and text
queries, and add characterisation tests for the guardrails components that
had none: the keyword modal, the content filter display and configuration,
the guardrail garden and the custom code modal.

Also covers two behaviours the migration must preserve: the action and
severity dropdowns in the content filter tables, and test playground state
surviving a tab switch away and back.

Every assertion here passes against the current antd and Tremor components
so the same file can prove the shadcn versions unedited.
2026-08-13 13:49:46 -07:00
Yuneng Jiang
77e64c5d40
fix(proxy): tolerate a concurrent creator when creating spend views
Every replica booting against the same fresh database sees each view as
absent and issues the CREATE. Postgres fails all but one with a
duplicate-object error, and that exception propagated out of
create_missing_views, so every view after the first was never created and
/global/spend* 500'd for the life of the deployment.

Losing that race reaches the desired end state, so treat it as success.
Genuine DDL errors still propagate.
2026-08-13 13:42:57 -07:00
yuneng-jiang
69b0296ca3
Merge pull request #36793 from BerriAI/litellm_shadcn_logs_drawer_header_0813
refactor(ui): migrate SectionHeader and ToolsSection to shadcn
2026-08-13 13:41:36 -07:00
Yuneng Jiang
ed01f7316b
test(e2e): assert the model allow-list permits, not only denies
Every case in TestAccessControl asserted that something was refused. A gateway
that denied the allow-listed model too would have passed all of them, so the
suite could not tell "denied correctly" from "broken outright".

Adds the positive half: a key allow-listed for gemini-2.5-flash can call it and
gets back a real completion rather than a 200-wrapped error.

Also tightens the unknown-model case. It accepted any valid JSON, so a bare
"{}" or even "null" satisfied it. It now requires the OpenAI-shaped error
envelope with a message a client can actually surface, parsed through a typed
model instead of json.loads.
2026-08-13 13:37:31 -07:00
Yuneng Jiang
1dc0ea3d11
Merge branch 'litellm_internal_staging' into litellm_shadcn_logs_drawer_header_0813 2026-08-13 13:33:00 -07:00
yuneng-jiang
160548d40b
Merge pull request #36739 from BerriAI/litellm_/quirky-mcnulty-e432c4
refactor(ui): migrate TruncatedValue and OutputCard to shadcn
2026-08-13 13:31:55 -07:00
Mateo Wang
c1310de342
Merge pull request #36763 from BerriAI/litellm_decrease_anys_fable7
refactor: replace Any with precise types across responses, proxy, and llms modules
2026-08-13 13:24:47 -07:00
Yuneng Jiang
9f947a7406
Merge branch 'litellm_internal_staging' into litellm_/quirky-mcnulty-e432c4 2026-08-13 13:23:51 -07:00
yuneng-jiang
faea98ee7e
Merge pull request #36738 from BerriAI/litellm_/eloquent-wu-3ab1d5
refactor(ui): migrate HistoryTree and CollapsibleMessage to shadcn
2026-08-13 13:23:27 -07:00
MUSE
f91e698adb fix(batch): avoid reading a nonexistent output artifact for completed batches
Completed batches that contain only failed requests do not generate an
output file, leaving output_file_id unset while the failures are recorded
through error_file_id instead.

The completion handler attempted to read the output payload regardless of
whether an output file actually existed. During retrieve polling this caused
the logging pipeline to fail with "Output file id is None cannot retrieve
file content", preventing normal completion bookkeeping from running.

Skip output retrieval when no output file is available and return an empty
batch summary (zero usage, zero cost, no model entries). The lower-level
file retrieval helper still reports an error if it is called directly with
an invalid or missing file identifier.

Closes #33987
2026-08-14 05:20:16 +09:00
Yuneng Jiang
2f7602b028
Merge branch 'litellm_internal_staging' into litellm_/eloquent-wu-3ab1d5 2026-08-13 13:16:48 -07:00
yuneng-jiang
c9b543dbfe
Merge pull request #36737 from BerriAI/litellm_/gifted-colden-355244
refactor(ui): migrate SimpleMessageBlock and SimpleToolCallBlock to shadcn
2026-08-13 13:16:22 -07:00
tin-berri
d8fda675cc
feat: pre-adoption shadow eval for the auto-router (blind pairwise judge, derived state) (#36587) 2026-08-13 13:15:45 -07:00
Yuneng Jiang
b71e7a3213
Merge branch 'litellm_internal_staging' into litellm_/gifted-colden-355244 2026-08-13 13:07:51 -07:00
yuneng-jiang
0f5fa38ef2
Merge pull request #36735 from BerriAI/litellm_/interesting-meitner-e88370
refactor(ui): migrate TokenFlow and JsonViewer to shadcn
2026-08-13 13:04:50 -07:00
mubashir1osmani
f3f8c48b1d style(ui): format new playground tests and drop suppressions this stack fixed
Prettier flagged the two test files added while fixing review findings.
Migrating these components off Ant Design also retired the lint suppressions
they carried, so prune those 15 entries and leave the unrelated ones for the
PRs that made them stale.
2026-08-13 12:55:43 -07:00
yuneng-jiang
b8577516d4
Merge branch 'litellm_internal_staging' into litellm_/quirky-mcnulty-e432c4 2026-08-13 12:51:53 -07:00
yuneng-jiang
21dbd3381e
Merge branch 'litellm_internal_staging' into litellm_/eloquent-wu-3ab1d5 2026-08-13 12:51:51 -07:00
yuneng-jiang
229d258664
Merge branch 'litellm_internal_staging' into litellm_/gifted-colden-355244 2026-08-13 12:51:50 -07:00
yuneng-jiang
6f9ee6e28f
Merge branch 'litellm_internal_staging' into litellm_/interesting-meitner-e88370 2026-08-13 12:51:48 -07:00
Yuneng Jiang
81f324dc15
Merge branch 'litellm_internal_staging' of github.com:BerriAI/litellm into litellm_shadcn_logs_drawer_header_0813 2026-08-13 12:51:37 -07:00
Yuneng Jiang
dfdafbf89b
fix(ui): keep the tools panel mounted so a tool's expanded detail survives
antd's Collapse kept the panel mounted once opened, so a tool a user had
expanded stayed expanded after closing and reopening Tools. Base UI renders
only the open branch, so the migration silently reset every ToolItem.

The regression test passes against the antd original, fails against the
migration without keepMounted, and passes with it.
2026-08-13 12:50:56 -07:00
Anas Khan
7fcca523aa
fix(proxy/batches): stop forwarding custom_llm_provider twice in list and cancel (#32813)
* fix(proxy/batches): stop forwarding custom_llm_provider twice in list and cancel

The model-routing branches of list_batches and cancel_batch passed
custom_llm_provider as an explicit kwarg while also leaving it inside the dict
they splat, so every such call raised "got multiple values for keyword argument
'custom_llm_provider'" and returned a 500.

list_batches SCENARIO 2 called data.update(credentials) but never removed
custom_llm_provider before litellm.alist_batches(custom_llm_provider=..., **data);
it now uses prepare_data_with_credentials, the same helper the create and
retrieve branches already use, which pops it out.

cancel_batch SCENARIO 3 resolved the provider with
`provider or data.pop("custom_llm_provider", None) or ...`, so when the path
param provider was set the pop short-circuited and a body custom_llm_provider
stayed in data and collided with the explicit kwarg. The body value is now
popped unconditionally before the fallback chain, so the path param wins cleanly
and data no longer carries a duplicate.

Both paths already had strict-xfail regression tests documented "remove when
fixed"; those markers are dropped so the tests now guard the fix.

Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>

* fix(proxy/files): avoid duplicate custom_llm_provider in list

Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>

---------

Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
2026-08-13 12:50:32 -07:00
mateo
3b09484344 refactor(batches): decode unified ids through the public helper
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-13 19:48:16 +00:00
Yuneng Jiang
b4092f476f
test(ui): find the section copy button by role instead of the antd icon
InputCard and OutputCard located SectionHeader's copy button by querying for a
descendant with aria-label="copy", which is the antd CopyOutlined icon. That
selector reaches into SectionHeader's internals, so migrating it off antd left
copyButton undefined and failed four tests.

getByRole("button", { name: /copy/i }) is green against both the antd and the
shadcn SectionHeader, verified by running these two files against each.
2026-08-13 12:47:45 -07:00
mateo
3db1759d04 fix(bedrock): stop emitting an empty assistant delta after the finish_reason chunk
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-13 19:45:32 +00:00
mubashir1osmani
8fd97803ce Merge remote-tracking branch 'berri/litellm_playground_shadcn' into litellm_playground_shadcn 2026-08-13 12:44:01 -07:00
mubashir1osmani
f4fb4e0bea fix(ui): label the virtual key source and unstick a cancelled model load
The key source trigger rendered the stored value, so the playground showed
session and custom instead of Current UI Session and Virtual Key. Name the
selected option on the trigger.

Clearing the key while models were loading left the selector disabled for
good: the in-flight load skips its reset once cancelled, and the branch that
handles an empty key returned without clearing the loading flag, so nothing
put it back. Clear it on that path too.
2026-08-13 12:43:25 -07:00
mubashir1osmani
3fdacfa6f9
fix(ui): restore playground model filtering by endpoint (#36130)
* fix(ui): restore playground model filtering by endpoint

Bring back the prior Chat model dropdown filter (including chat models
on responses/anthropic/interactions and image models on image_edits), and
map mode realtime so the realtime endpoint only lists compatible models

* fix(ui): exclude unknown model modes from playground endpoint filters

Modes outside ModelMode (batch, rerank, ocr, etc.) must not collapse to
chat-compatible, or conversational endpoints surface unusable models

* feat(ui): add shared vercel-style playground chat composer (#36131)

* feat(ui): adopt vercel-style chat composer for playground

Replace the compact single-line input with a PromptInput-style composer:
taller auto-growing textarea, rounded card shell, footer tools, and
stop button while a request is in flight

* style(ui): strengthen playground chat composer border and shadow

Make the shared chat input stand out with a fuller border, layered
shadow, and a slightly stronger focus ring

* fix(ui): size chat composer textarea with CSS field-sizing

Drop direct el.style.height mutation in favor of field-sizing:content

* fix(ui): keep the chat composer out of a nested form and focus its textarea

The composer wrapped everything in a native form, so MCP mode nested Ant
Design's tool-arguments form inside it, which is invalid HTML and let Enter
hit either form. The footer also relied on InputGroupAddon focusing the first
input in the group, which is the hidden file input from the attach controls
rather than the message textarea.

Drop the outer form and submit from the send button directly, and have the
addon focus the element marked as the group's control.

* refactor(ui): reuse the endpoint compatibility check when a model is picked

The endpoint guard added upstream duplicated the compatibility families this
PR introduces, so point it at isModelCompatibleWithEndpoint instead. Filtering
also means an incompatible model is no longer offered for an endpoint, so the
test that picked one now asserts it is absent.

* fix(ui): match the image-edit model mode the backend actually sends

model_prices_and_context_window.json labels these models image_edit, but the
mode enum spelled it image_edits, so once unknown modes started being filtered
out every image-edit model vanished from the playground, /v1/images/edits
included. The endpoint key keeps its own spelling.

The compatibility tests stubbed getEndpointType with a hand-written map that
repeated the same wrong spelling, which is how this stayed hidden, so they now
run against the real mapping.
2026-08-13 12:40:42 -07:00
Mateo Wang
50ae806784
Merge pull request #36733 from BerriAI/litellm_fix_interactions_responses_bridge
fix(interactions): map step and turn history to Responses API roles and content types
2026-08-13 12:35:21 -07:00
mateo-berri
928dfab65c feat(xai): day-0 pricing for grok-4.6 2026-08-13 12:32:09 -07:00
mubashir1osmani
26194b85a4 fix(ui): keep the playground endpoint, editable number fields, and drag-to-upload
Picking a model reset the endpoint from its mode unconditionally, so choosing
a chat model while on /v1/responses, /v1/messages or interactions bounced the
playground to /v1/chat/completions. Only switch when the current endpoint
cannot serve the picked model.

The temperature and max-token boxes parsed and clamped on every keystroke, so
a decimal lost its point and clearing the field snapped to a bound. They are
now text fields with a numeric input mode that hold what was typed and clamp
on blur; the sliders beside them still give the stepped control.

The image-edit and transcription areas invited a drag but had no drop
handlers after the Ant Design Dragger came out, so drops did nothing. Wire
drop through the same validation the file picker uses.
2026-08-13 12:26:50 -07:00
mateo
b8c1103c55 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_check_batch_cost_poll_starvation 2026-08-13 19:26:17 +00:00
tin-berri
5f2986a1f3
feat(complexity_router): calibrate the classifier rubric with worked examples, selectable per router (#36578)
* feat(complexity_router): calibrate the classifier rubric with worked examples

The built-in rubric stated its tier boundaries as prose alone, and prose
calibrated to consumer chat puts "non-trivial code, multi-step technical work"
at the top of the scale. That is the median request in developer and agent
traffic, so ordinary engineering read as top-tier and the router paid for the
most expensive model on it.

Adds calibration examples to the rubric, selected by a new
classifier_llm_config.rubric preset. The agentic preset (now the default)
anchors routine installs, builds, multi-file edits, and standard debugging at
MEDIUM; the chat preset omits those anchors for deployments serving only
conversational traffic. Both share the same tier criteria, the trust-boundary
paragraph, and the context-window closing line, so this moves where the
boundary sits without changing the taxonomy.

Both presets render byte-identical to the strings a prompt sweep scored, and a
test pins that, so the measured accuracy describes what a router sends.

* feat(ui): pick the classifier rubric preset on an auto-router

Adds a Rubric dropdown to the auto-router's classification panel, so the
agentic and chat presets are selectable rather than config-file only. The
prompt editor prefills from the selected preset, since prefilling agentic text
for a router on chat would show examples its classifier never receives.

The picker is disabled while a custom prompt is set, and the payload builder
drops the preset in that case: a custom prompt is the classifier's whole system
role, so the backend rejects the two together. The builder records the default
preset explicitly, so a later change to which preset is default cannot silently
move an existing router.

* fix(complexity_router): mark an unchosen rubric preset with None, not model_fields_set

The mutual-exclusion check read model_fields_set to tell an explicit preset
from the default. That flag does not survive serialization, and this config is
dumped and handed straight back to ComplexityRouter by /auto_router/test_routing,
where a dump re-states every field. So a custom-prompt classifier saved fine and
then failed validation on preview, rejecting on the second pass what it accepted
on the first.

The preset is now optional, with None meaning the default, matching how None
already means the built-in rubric for system_prompt on the same model. The
default lives in one place, DEFAULT_RUBRIC_PRESET, resolved where the prompt is
assembled. The dashboard stops sending a copy of the default it displays, so a
router nobody configured follows the default rather than pinning today's value,
and UI-built routers behave the same as hand-written config.

Regenerates schema.d.ts, which was left stale by an earlier description edit.

* feat(complexity_router): grandfather existing routers onto the uncalibrated rubric

An unset preset now means LEGACY, the rubric exactly as it shipped before
calibration examples existed, so upgrading cannot move the tier decisions or the
bill of a router that is already running. Config-file routers get this for free
since they name no preset, and a stored config that never had one reads the same
way.

New routers still get the calibrated rubric: switching a classifier to LLM
stamps the agentic preset, because a classifier being configured for the first
time has no prior tier behaviour to preserve. The picker offers legacy so an
existing router's state is representable and opening the form cannot silently
upgrade it.

Each preset is pinned byte-identical to the text the prompt sweep scored,
legacy included, which is what proves an existing router's prompt did not move.

Also collapses the preset data from a NamedTuple with group wrappers and
per-preset frozensets into plain text blocks in a MappingProxyType, matching how
the tier criteria next to it are already stored: 21 lines of prompt text no
longer cost 190 lines of constructors. Tiers are format placeholders so
tier_labels still reach the examples.

* refactor(complexity_router): name the field classification_rubric

`rubric` alone did not say what it selects, and the field sits beside
`system_prompt`, which genuinely is the whole classification prompt. The name
now says which of the two an operator is reaching for: the rubric the built-in
prompt is assembled from, not the prompt itself.

Renames the config field, the query param, the enum, and the dashboard label to
match, and moves the preset text to classification_rubrics.py.

* test(ui): set the preset the mutual-exclusion case is meant to drop

The rename left classification_classification_rubric in the custom-prompt case,
so its input never carried a preset and the assertion held for the wrong reason:
it proved an absent preset stays absent, not that a set one is dropped. A
normalizer that forwards the preset whenever one is set passed with the typo and
fails without it.

tsc reports the typo as TS2353; the earlier sweep grepped for the source file
and not the test, so it went unseen.

* test(ui): scope the role-gate assertions to each page's own endpoint

The memory, workflows, and guardrails-monitor page tests asserted that a denied
role fires no request at all. Their names, and the assertion on the very next
line, say the intent is narrower: the page must not fetch its own data.

Resolving whether a caller is an org admin goes through /organization/list for
every role, since deciding org-admin-for-any-org needs the list, and the route
scopes rows per caller. That legitimate request fails a blanket no-fetch
assertion, so all three files went red on staging for a reason unrelated to
what they test.

Drops the blanket assertion and keeps the scoped one. Bypassing the gate in
memory/page.tsx still fails five tests, so the narrower assertion continues to
catch a genuinely broken gate.

* fix(complexity_router): document that an unset rubric keeps the legacy prompt

The field said 'Leave unset for agentic' while an omitted rubric resolves to
LEGACY, so the OpenAPI schema an operator reads promised calibrated routing
where they got the uncalibrated one.

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>
2026-08-13 12:22:20 -07:00
mubashir1osmani
136ef345da Merge remote-tracking branch 'berri/litellm_internal_staging' into litellm_playground_shadcn
# Conflicts:
#	ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.test.tsx
#	ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx
#	ui/litellm-dashboard/src/components/policies/PolicySelector.tsx
2026-08-13 11:28:42 -07:00
tin-berri
add095b494
Fix: ComplexityRouter should not score system prompt text for code/technical complexity (#36721)
System prompts (harnesses, tools, framework boilerplate) are session-wide
constants identical across all requests. Scoring them saturates keyword-match
signals and produces false-positive high-complexity classifications on
trivial utterances like 'hi', routing them to expensive models (sonnet/opus)
instead of tier-1 haiku. A real ~1.6KB CLI-agent harness alone supplied
5 codePresence + 2 technicalTerms matches, overshadowing user signal.

Rescope four scoring dimensions (codePresence, technicalTerms, simpleIndicators,
multiStepPatterns) from full_text (system + user) to user_text (user only).
reasoningMarkers was already scoped this way. This returns 0.63 of the weight
budget to text that actually varies per-request.

Now that every dimension scores user_text only, _score_keyword_match's
disclosable_text param is redundant -- it existed solely to let the signal
name terms matched in the caller's own message while withholding terms
matched only in the (invisible-to-the-caller) system prompt. With no more
system-prompt text in scope, text and disclosable_text were identical at
every call site, so the param is dropped and the function collapses to a
single text argument.

Add mutation-proven regression test: trivial 'hi' message with realistic
Claude Code agent system prompt now routes to haiku tier-1 (not sonnet).

- Unfixed: haiku -> sonnet (bug)
- Fixed: haiku -> haiku (correct)

Invert three pre-existing assertions in TestSignalsNeverQuoteTheSystemPrompt
to capture the corrected behavior: system-prompt-only terms produce no signal.

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-13 11:15:14 -07:00
yuneng-jiang
9f3b1dfec5
Merge pull request #36787 from BerriAI/litellm_osv_nanoid_3_3_18
build(deps): bump nanoid to 3.3.18 to clear osv-scan
2026-08-13 11:09:35 -07:00
Noah Nistler
f8fccec108 fix(azure_ai): enforce admin-only index create on the passthrough route
POST /azure_ai/indexes carries no index name, so
get_azure_ai_search_index_from_endpoint returns None,
is_vector_store_index never matches any segment, and the request falls
through to the generic Azure passthrough on the proxy's own
AZURE_API_BASE and AZURE_API_KEY without ever reaching
is_allowed_to_call_vector_store_endpoint. A non-admin could therefore
create a Search index whenever AZURE_API_BASE points at the Search
service.

The earlier lifecycle commit made this look covered. Its test asserts
that POST /indexes?api-version=... is refused with "Only proxy admins can
create", but it calls the permission gate directly, and that gate is
exactly what the route skips for a path with no index name, so the guard
was verified in isolation while the route stayed open.

Gate the service-level create on the route itself, before the segment
loop, with assert_proxy_admin_for_vector_store_index_management. Scope it
to POST on a path whose last segment is indexes, mirroring the
endswith("/indexes") branch the lifecycle helper already uses, so the
managed-index paths and ordinary Azure OpenAI passthrough traffic are
untouched.

Add route-level tests: a non-admin is refused with the admin-only message
and never reaches the passthrough handler, an admin still creates, and the
new predicate is parametrized over the service-level, per-index, and
non-Search paths.
2026-08-13 17:56:57 +00:00
Noah Nistler
c1125f0abb fix(azure_ai): classify Search suggest, autocomplete, and analyze as reads
The endpoint map covered document reads through the ("GET", "/indexes/")
entry plus POST /docs/search, which left Azure's remaining POST query
endpoints unclassified. POST /docs/suggest, POST /docs/autocomplete, and
POST /analyze matched neither list, so the permission gate resolved
permission_type to None and raised 403 before the caller's
allowed_vector_store_indexes grant was consulted; a non-admin team with a
read grant on the index still could not call them.

Add the three as reads. They are query endpoints that never mutate the
index, so a read grant is the right gate, and each needs its own literal
entry because the write entry also matches on POST.

Keep every pattern literal rather than a {placeholder} template: the
matcher falls back to the substring before a {, which for these routes is
always /indexes/, and reads are matched before writes, so a templated
read would shadow the /docs/index write and let a read-only team upload.

Extend the regression tests to the full non-lifecycle read surface
(stats, GET-form search, $count, point lookup, and both forms of suggest
and autocomplete, plus analyze), asserting a read grant reaches all of
them and a write-only grant reaches none.
2026-08-13 17:56:57 +00:00
Noah Nistler
bdc80b11ac fix(azure_ai): authorize the targeted Search index, not any matching path segment
The Azure passthrough scanned every URL segment for one matching a registered
index, authorized against that, then forwarded the original path. A caller with
a grant on a managed index named e.g. "index" or "docs" could send
POST /azure_ai/indexes/{victim}/docs/index: the scan matched the trailing
segment and authorized on the caller's own index while Azure applied the batch
write to {victim} on the same Search service, enabling cross-index document
uploads or deletions.

Resolve the index positionally from the /indexes/{name} segment and require
that exact name to be the one authorized and credentialed, so the authorized
index and the physical target can never diverge. Add a pure helper plus
regression tests covering positional extraction and the route-level cross-index
attack.
2026-08-13 17:56:57 +00:00
Noah Nistler
23f50e1f34 fix(vector_stores): classify POST /indexes create as admin-only lifecycle with query string
The service-level index-create guard checked normalized.endswith("/indexes")
without stripping the query string, so Azure's real create request
POST /indexes?api-version=... was never classified as a lifecycle request and
fell through to the generic permission check instead of the explicit admin-only
guard. Strip the query string before the suffix check, mirroring how the
PUT/DELETE index paths already tolerate a trailing ?.

Add the POST create path to the lifecycle regression parametrize so a non-admin
team with a write grant is denied with the clear admin-only message.
2026-08-13 17:56:57 +00:00
Noah Nistler
83efa9f630 fix(azure_ai): recognize real Search doc endpoints so teams can read/write via passthrough
The Azure AI Search vector store config declared its write endpoint as
`PUT /docs` and its read endpoints as only `/docs/search`. The passthrough
permission gate (`is_allowed_to_call_vector_store_endpoint`) derives a
read/write permission type by matching the request route against those
lists, and a route matching neither resolves to `None` and raises a 403
before the caller's `allowed_vector_store_indexes` grant is ever checked.

Two real Azure routes fell through that gap for non-admins: document
upload/merge/delete is `POST /docs/index` (not `PUT /docs`), and get
index details is `GET /indexes/{name}` (no `/docs/search` suffix). So a
team with a valid write or read grant still got 403 on upload and on
reading index details, while admins slipped through because they skip the
gate entirely.

Correct the map: read is any GET under `/indexes/` (get details, stats,
count, and the GET form of search) plus `POST /docs/search`; write is
`POST /docs/index`. Index lifecycle (create/update/delete the index
itself) stays proxy-admin only because it is handled first by the
separate lifecycle check on POST/PUT/DELETE/PATCH, so this does not let a
team create or delete indexes.

Add regression tests that exercise the real AzureAIVectorStoreConfig map:
a write-granted team may upload, a read-granted team may search and get
index details, a team missing the matching grant is still denied, and a
team cannot manage index lifecycle even with a write grant.
2026-08-13 17:56:57 +00:00
mateo
7b28476bfc retrigger ci
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-13 17:33:32 +00:00
Mateo Wang
0a73e0f8d9
Merge pull request #36792 from BerriAI/litellm_gemini_3_7_flash
feat(gemini): day-0 pricing for gemini-3.7-flash
2026-08-13 10:28:16 -07:00