- Use timedelta(hours=1) instead of replace(hour=hour+1) in _window()
to avoid ValueError when hour=23
- Add test_should_batch_by_row_count covering the >10K rows batching
path (previously only the 2 MB size-limit path was tested)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes issue where multipart file uploads through passthrough endpoints failed with RequestValidationError. The proxy was consuming the request body stream and FastAPI was trying to parse multipart bodies as JSON dicts.
Changes:
- Try JSON parsing first for multipart content-type (handles misconfigured clients)
- Skip multipart parsing if JSON succeeds to avoid stream consumption
- Remove custom_body parameter from endpoint_func to prevent FastAPI auto-parsing
- Check for parsed body before using multipart handler
- Add regression test for multipart boundary preservation
Handles both actual multipart uploads and JSON bodies with incorrect multipart content-type headers.
Made-with: Cursor
Adds a pluggable Vantage destination to the existing FOCUS export pipeline,
enabling LiteLLM to export spend data in FOCUS format directly to Vantage's
cost-import API. Supports automatic hourly exports via scheduled background job,
with admin API endpoints for manual control and configuration. Includes CSV
serializer, batching for 10K row / 2MB API limits, and enriched Tags JSON with
team/user/key metadata for Vantage Token Allocation feature.
- Add CSV serializer (FocusCsvSerializer) for FOCUS data
- Add Vantage API destination with automatic batching
- Add VantageLogger that wraps FocusLogger with Vantage defaults
- Add proxy endpoints: /vantage/{init,settings,export,dry-run,delete}
- Register "vantage" callback in logger registry and literal type
- Wire up background job in proxy_server.py startup
- Populate Tags column with JSON metadata (team_id, user_id, user_email, etc.)
- Add 14 unit tests covering serializer, destination, and factory
All tests pass (23 focus tests total, no regressions).
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The GET /v1/mcp/server endpoint allowed any authenticated user to pass
an arbitrary team_id and enumerate another team's MCP server config.
Restricted virtual keys could also use the team_id param to bypass
their access limitations. Add team membership check for non-admins
and block restricted keys from using the team_id filter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When creating a key, the MCP server list now filters by the selected team's
allowed servers. Also fixes UnboundLocalError on `is_restricted_virtual_key`
when `team_id` query param was provided to GET /v1/mcp/server.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Perplexity requires type='message' on input items. Add _ensure_message_type
to automatically set it when missing, matching the old _format_input behavior.
Remove the isinstance(cost, dict) guard from Usage.__init__ — ResponseAPIUsage.parse_cost
validator already converts Perplexity's cost dict to float before it reaches Usage.
Add test_streaming_cost_dict_to_float_via_validator to verify the dict→float conversion
works end-to-end through transform_streaming_response (pydantic recursive construction).
* feat(ui): add Chat UI v0 — standalone LiteLLM-branded chat window
Adds a full chat UI accessible from the sidebar Chat link (opens in new tab).
- Standalone route at /chat (outside dashboard layout — no Navbar/Sidebar chrome)
- Claude.ai-style layout: model selector top-left, LiteLLM logo center, settings top-right
- Greeting with time-of-day, centered input card, suggestion chips (Write/Learn/Code/Brainstorm)
- Sliding conversation history sidebar with Cmd+K search, rename, delete, date grouping
- localStorage-backed conversation persistence (litellm_chat_history_v1)
- Streaming completions via makeOpenAIChatCompletionRequest with AbortController stop support
- MCP server picker (toggle servers on/off per conversation)
- LiteLLM aesthetic: white/light-gray background, Ant Design blue (#1677ff) primary, system font
- Sidebar2: Chat menu item opens in new tab via window.open
* feat(chat-ui): responses API + MCP tool execution display
- Switch /chat from chat completions to responses API (previous_response_id session chaining)
- Add MCP server picker with search filter in chat input bar
- Show MCP tool call events (list_tools + call_tool) inline in chat via MCPEventsDisplay
- Add tool chip strip showing available tools when MCP servers are selected
- Non-blocking MCP toggle: server added immediately, verification in background (works for no-auth MCPs like deepwiki)
- Add truncateAfterMessage to useChatHistory for edit/retry
- Sync activeConversationId on URL change (fixes stale conversation on new chat)
- Add "Open Chat" shortcut button to sidebar
* fix(chat-ui): switch to responses API, remove dead code, add tests
- Switch handleSend from makeOpenAIChatCompletionRequest to makeOpenAIResponsesRequest with previous_response_id session chaining
- Add responsesSessionId state; reset to null when starting a new conversation
- Remove unused ChatInputBar.tsx and ModelSelector.tsx (dead code)
- Add tests/test_litellm/test_chat_ui_responses_session.py covering previous_response_id forwarding and signature validation
* fix(chat-ui): address greptile review issues
- Reset responsesSessionId when activeConversationId changes (not just on new conversation)
- Wire onMCPEvent callback into makeOpenAIResponsesRequest; render MCPEventsDisplay below messages
- Clear mcpEvents on each new send
- Explicitly filter history to user/assistant roles only (no tool-role casting)
- Remove duplicate "Chat" menu item from sidebar (pinned button serves same purpose)
- Make Sider a flex column so "Open Chat" button actually pins to bottom
- Fix tests to intercept real HTTP requests and assert previous_response_id in body
* fix(chat-ui): address greptile review feedback (greploop iteration 1)
- Fix duplicate context: when responsesSessionId is set, only send the
new user message as input (prior context is already server-side via
session chaining). Full history is still sent on the first turn.
- Fix ephemeral MCP events: store events per-message in ChatMessage.mcpEvents
instead of ephemeral component state. Events now survive across turns
and render inline below each assistant response via MCPEventsDisplay.
- Remove stale mcpEvents useState and ephemeral panel at bottom of chat.
* fix(chat-ui): address greptile review feedback (greploop iteration 2)
- Fix stale session on edit/retry: derive previousResponseId as null when
historyOverride is set so edit/retry always starts a fresh Responses API
session rather than chaining off a now-invalid prior session
- Fix unsafe MCPEvent cast: import MCPEvent directly from MCPEventsDisplay
into types.ts and type ChatMessage.mcpEvents as MCPEvent[], eliminating
the bare 'as MCPEvent[]' cast in ChatMessages.tsx
* fix(chat-ui): fix MCPEvent layering, batch localStorage writes, module-level test imports
- Move MCPEvent interface definition into chat/types.ts (single source of truth)
- MCPEventsDisplay.tsx now imports MCPEvent from types.ts instead of defining it locally
- Batch MCP event localStorage writes: accumulate during stream, persist once in finally
- Move test imports to module level per PEP 8 convention
* fix(chat-ui): fix MCPEvent import path and rename truncateFromMessage
- responses_api.tsx now imports MCPEvent directly from chat/types (not via MCPEventsDisplay re-export)
- Remove the now-unnecessary MCPEvent re-export from MCPEventsDisplay.tsx
- Rename truncateAfterMessage → truncateFromMessage: the function removes the target message and all subsequent ones (not just what comes after), so the new name accurately describes the behavior
* fix(responses-api): fix whitespace token filter and MCP server URL construction
- Drop the delta.trim() whitespace filter that was silently swallowing spaces
and newlines during streaming, causing words to concatenate and paragraphs
to collapse. Only skip truly empty strings (delta.length > 0).
- Use proxyBaseUrl for MCP server_url construction instead of the hardcoded
relative path "litellm_proxy/mcp", so non-root deployments route correctly.
* fix(responses-api): use unique server_label per MCP server to prevent tool routing collisions
* fix(chat-ui): move MCPEvent to shared mcp_tools/types, skip partial events on abort
- Move MCPEvent interface to mcp_tools/types.tsx (shared with MCPServer/MCPTool),
eliminating the playground→chat cross-module dependency. chat/types.ts and
both playground components now import from mcp_tools/types.
- Only persist accumulated MCP events when the stream completes cleanly; aborted
or errored turns drop partial events to avoid showing incomplete tool calls.
* fix(responses-api): use server_name for MCP URL routing, fix test path
- Use server_name (not alias) as the URL path segment for MCP server_url;
alias is a display name that may differ from the registered proxy route.
URL-encode the path to handle names with spaces/special characters.
- Fix sys.path.insert in tests to use __file__-relative path so tests pass
regardless of which directory pytest is invoked from.
* fix(chat-ui): fix stale session after failed edit, clean MCP event persistence, unique server_label
- Eagerly call setResponsesSessionId(null) when historyOverride is set so a
failed/aborted edit does not leave a stale session contaminating the next turn
- Replace abort-signal check with streamCompletedCleanly flag to correctly skip
MCP event persistence on both abort and non-abort errors (network/API failures)
- Use server_name (unique) as server_label instead of alias to prevent silent
tool-routing failures when two MCP servers share the same display name
- Remove Perplexity-specific `models` field from ResponsesAPIOptionalRequestParams
TypedDict to avoid polluting the shared OpenAI type. The param is still passed
through via get_supported_openai_params + map_openai_params at runtime.
- Add transform_response_api_response override to catch Perplexity's HTTP 200
with status:"failed" and raise BaseLLMException instead of silently succeeding.
- Update test_all_supported_params_declared to match the simplified
8-param list (was 24 params from old 410-line implementation)
- Replace test_cost_transformation tests: use ResponseAPIUsage
validator directly instead of removed _transform_usage method
(cost parsing moved to generic parse_cost validator)
- Add 'models' field to ResponsesAPIOptionalRequestParams TypedDict so
the Perplexity fallback chain param is not silently filtered out by
get_requested_response_api_optional_param(). Verified with curl that
Perplexity's models param works (tries models in order until one
succeeds).
- Fix test_python_class_takes_priority_over_json: save and restore the
original JSONProviderRegistry entry instead of deleting it, preventing
state corruption for subsequent tests.
When a provider's finish_reason is mapped to a different OpenAI-compatible
value (e.g. "MALFORMED_FUNCTION_CALL" → "stop"), the original value is now
preserved in choices[].provider_specific_fields["native_finish_reason"].
This allows agent loops to distinguish between different stop conditions
without breaking the unified OpenAI-compatible finish_reason mapping.
Also returns a defensive copy from get_finish_reason_mapping() to prevent
accidental mutation of the global _FINISH_REASON_MAP.
* feat(mcp): add BYOM (Bring Your Own MCPs) submission + admin review workflow
Non-admins can now submit MCP servers for review via POST /v1/mcp/server/register.
Admins get a Submissions tab in the UI to approve or reject pending servers.
Approved servers enter the active runtime; rejected ones stay out with notes.
- DB: add approval_status, submitted_by, submitted_at, reviewed_at, review_notes
to LiteLLM_MCPServerTable with migration
- Backend: new endpoints register, submissions, approve, reject
- reload_servers_from_database now only loads approval_status=active servers
- UI: Submissions tab with stat cards, card list, confirm dialogs; non-admin
"Submit MCP Server" button wired to /register endpoint
- Fix get_mcp_submissions to filter by submitted_at IS NOT NULL (not submitted_by,
which can be null for team-scoped keys without an associated user)
* feat(mcp): rename nav item to Team MCPs + add New badge
* fix(mcp): revert nav label, rename Submissions tab to Team MCPs + New badge
* feat(mcp): add MCP Standards — required fields config + CI-style checks on submissions
Adds a "Standards" tab (admin-only) to MCP Servers where admins define which
server fields are required for a submission to pass. Each submission card in
Team MCPs then shows a green ✓ or red ✗ for each required field, with a
summary "N/M checks" badge in the header — like GitHub CI status rows.
Also adds a `source_url` field (GitHub / Source URL) to the MCP server schema
so non-admins can link to the source repo when submitting a server.
- schema.prisma: add `source_url String?` to LiteLLM_MCPServerTable
- migration: 20260309000001_add_mcp_source_url
- _types.py: source_url on NewMCPServerRequest, UpdateMCPServerRequest, LiteLLM_MCPServerTable
- types.tsx: source_url on MCPServer interface
- create_mcp_server.tsx: GitHub/Source URL form field
- MCPStandardsSettings.tsx: new — toggle which fields are required (stored in general settings as mcp_required_fields)
- mcp_servers.tsx: Standards tab (admin-only)
- MCPSubmissionsTab.tsx: load required fields + CI-style check pills on each card
* refactor(mcp): move submission rules into Team MCPs tab, grouped free-form UI
Folds the Standards tab into Team MCPs. Submission Rules panel now lives at the
top of the Team MCPs tab — collapsible, shows active rules as chips when closed,
expands to a grouped checkbox editor (Documentation / Source / Connection /
Security). Removes the separate Standards tab from the nav.
MCPStandardsSettings.tsx is now constants-only (FIELD_GROUPS, MCP_REQUIRED_FIELD_DEFS,
SETTINGS_KEY) — the UI lives in MCPSubmissionsTab.
* feat(mcp): add mcp_required_fields to ConfigGeneralSettings + config/list endpoint
Registers mcp_required_fields as a proper general_settings field so the UI
can read/write it via /config/list and /config/field/update without the
"Invalid field" error. Also fixes a pre-existing pyright None-check issue
in _sync_ui_settings_to_general_settings.
* ui(mcp): GitHub-style PR checks panel on submission cards
* ui: rename Team MCPs -> Submitted Tools, Team Guardrails -> Submitted Guardrails
* address greptile review feedback (greploop iteration 1)
* fix: inline import, add approval workflow tests, rename Submitted MCPs
* fix(mcp): allow re-approval of rejected MCP server submissions
* fix(mcp): evict rejected servers from runtime; enforce mcp_required_fields on /register
* fix(mcp): sort submissions newest-first; force active status on admin-created servers
* fix(mcp): add missing mock in test, show Approve for rejected, clear submission metadata, drop spurious Content-Type
* fix(mcp/ui): show Reject for active servers; show submit form to non-admins with team-key note
* fix(mcp): conditional reload on reject; view-only admin for submissions; block admin from /register
* fix(mcp): match auth_type required-field validation to UI compliance check (reject 'none')
* fix(mcp): block view-only admin from /register; log settings failure; warn on active server reject
* fix(mcp): allow view-only admin to use /register; add _validate_mcp_required_fields tests
* fix(mcp): validate field names in mcp_required_fields; surface backend error in submit UI
* fix(mcp): fix falsy field check; add field-name validation; add take limit; document server-managed fields; close dialog on error
Resolve conflict in perplexity/responses/transformation.py by keeping
the simplified ~50 line version (PR's goal) instead of main's ~410 line
version. Added supports_native_websocket() -> False from main.
Keep unified _FINISH_REASON_MAP dict approach, discard upstream's
inconsistent _VALID_OPENAI_FINISH_REASONS frozenset that mapped to
values not in the OpenAIChatCompletionFinishReason Literal.
* fix(mcp): add AWS SigV4 auth for Bedrock AgentCore MCP servers
Add aws_sigv4 auth type to MCP client via httpx.Auth subclass that
signs each request with SigV4 using botocore. Enables mcp_servers
config to connect to AgentCore-hosted MCP servers.
* docs(mcp): add AWS SigV4 auth documentation for Bedrock AgentCore
Add dedicated docs page for configuring MCP servers with AWS SigV4
authentication, update MCP overview with aws_sigv4 auth type and
config example, and link from Bedrock AgentCore provider docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(mcp): address Greptile review — requires_request_body, full header signing, health check
- Add requires_request_body = True to MCPSigV4Auth so httpx buffers the
request body before calling auth_flow (prevents empty body hash for
streaming requests)
- Pass all request headers to AWSRequest for canonical SigV4 signing
instead of only Content-Type
- Exclude aws_sigv4 from health check skip logic since it has its own
credential fields (not authentication_token)
- Fix docs: mark aws_access_key_id/aws_secret_access_key as optional
(falls back to boto3 credential chain)
- Add test for requires_request_body flag
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Krish Dholakia <krrishdholakia@gmail.com>
- Add 'token' to MCPAuth enum for custom token auth format
- Implement token auth in MCP client (_get_auth_headers)
- Add token auth support for OpenAPI-based MCP tools
- Add comprehensive unit tests to existing test_mcp_client.py
- Fixes issue where MCP servers expecting 'Authorization: token <value>' header could not connect
When guardrails return the full data dict (e.g. guardrails_ai), the
guardrail response logged to spend logs and OTEL traces could contain
data["secret_fields"].raw_headers with plaintext Authorization headers.
This adds a pop("secret_fields") in the guardrail logging path,
matching the existing pattern used by Langfuse and Arize integrations.
Tested: Verified fix removes secret_fields/raw_headers/authorization
from both /spend/logs/ui responses and OTEL trace span attributes.
* fix(streaming): map unknown finish_reason values to finish_reason_unspecified
Some LLM providers return non-standard finish_reason values that are not
in the OpenAIChatCompletionFinishReason Literal (e.g. ZhipuAI/GLM returns
'network_error' when a streaming error occurs mid-response).
Previously map_finish_reason() fell through with return finish_reason,
passing the unknown value directly to Choices.__init__() which calls
Pydantic validation. This caused a ValidationError that was caught by
stream_chunk_builder() and re-raised as the misleading:
litellm.APIError: Error building chunks for logging/streaming usage calculation
Fix: after all known provider-specific mappings, check if the value is in
the valid set (stop, length, tool_calls, content_filter, function_call,
guardrail_intervened, eos, finish_reason_unspecified, malformed_function_call).
Any value not in this set is mapped to 'finish_reason_unspecified' instead
of being returned as-is.
This is consistent with how other unknown stop reasons (e.g. Vertex AI's
FINISH_REASON_UNSPECIFIED) are already handled.
* refactor: use get_args(OpenAIChatCompletionFinishReason) for valid set
Per code review feedback: replace the hardcoded _valid_finish_reasons set
with a module-level frozenset derived dynamically from the source-of-truth
Literal type via typing.get_args(). This ensures the valid-reason check
stays in sync automatically when new finish reasons are added to the Literal,
and avoids recreating the set on every streaming chunk call.
* test(map_finish_reason): add unit tests and warning log for unknown finish reasons
- Add TestMapFinishReason class in test_core_helpers.py covering:
- All known OpenAI-native values pass through unchanged (parametrized)
- Provider-specific mappings: Anthropic, Cohere, Vertex AI
- Unknown/provider-specific values map to 'finish_reason_unspecified'
- Regression test for ZhipuAI/GLM-5 'network_error' case
- Add verbose_logger.warning() in map_finish_reason() when an unknown
finish_reason is encountered, so operators can track which providers
return non-standard values
When assistant content is already a list containing thinking blocks
inline (not str/None), SEQUENTIAL MODE was still prepending all
thinking_blocks from provider_specific_fields, causing duplication
and breaking Anthropic's position-dependent signature verification.
Now detects if the content list already has thinking blocks and skips
the extend(thinking_blocks) to preserve the original interleaved order.
Addresses the correctness gap identified by Greptile review where
list-content messages bypass INTERLEAVED MODE.
Fixes: https://github.com/BerriAI/litellm/issues/23047