mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
fix(management-v1): keep the user PATCH within the lint budgets and regenerate the specs
The patch request and item models now annotate their collections read-only, and the merge-patch resolvers hand back MappingProxyType rather than plain dicts, so the only mutable dict left is the one the shared write path stamps into before Prisma takes it. The resolver seam is a Protocol instead of a Callable alias. Also regenerates _lazy_openapi_snapshot.json, which had drifted on the base branch, and schema.d.ts, which the new route belongs in.
This commit is contained in:
parent
0ace6e7ed5
commit
72679076fd
5 changed files with 350 additions and 57 deletions
|
|
@ -15038,6 +15038,17 @@
|
|||
}
|
||||
],
|
||||
"title": "Upstream Resource"
|
||||
},
|
||||
"upstream_token_header": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Upstream Token Header"
|
||||
}
|
||||
},
|
||||
"title": "MCPCredentials",
|
||||
|
|
@ -17518,6 +17529,17 @@
|
|||
}
|
||||
],
|
||||
"title": "Upstream Resource"
|
||||
},
|
||||
"upstream_token_header": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Upstream Token Header"
|
||||
}
|
||||
},
|
||||
"title": "MCPCredentials",
|
||||
|
|
@ -20352,6 +20374,17 @@
|
|||
}
|
||||
],
|
||||
"title": "Upstream Resource"
|
||||
},
|
||||
"upstream_token_header": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Upstream Token Header"
|
||||
}
|
||||
},
|
||||
"title": "MCPCredentials",
|
||||
|
|
@ -23699,6 +23732,17 @@
|
|||
}
|
||||
],
|
||||
"title": "Upstream Resource"
|
||||
},
|
||||
"upstream_token_header": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Upstream Token Header"
|
||||
}
|
||||
},
|
||||
"title": "MCPCredentials",
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ These are members of a Team on LiteLLM
|
|||
import asyncio
|
||||
import json
|
||||
import traceback
|
||||
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
||||
from collections.abc import Awaitable, Mapping, Sequence
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Final, Literal, TypeAlias, cast
|
||||
from typing import Any, Final, Literal, Protocol, cast
|
||||
|
||||
import fastapi
|
||||
from fastapi import APIRouter, Depends, Header, HTTPException, Request, status
|
||||
|
|
@ -1181,13 +1181,20 @@ def _process_keys_for_user_info(
|
|||
return returned_keys
|
||||
|
||||
|
||||
UserUpdateFieldResolver: TypeAlias = Callable[
|
||||
[dict, "UpdateUserRequest | UpdateUserRequestNoUserIDorEmail"],
|
||||
dict,
|
||||
]
|
||||
class UserUpdateFieldResolver(Protocol):
|
||||
"""Decides which fields a user-update request writes; see `update_single_user`."""
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
data_json: Mapping[str, object],
|
||||
data: "UpdateUserRequest | UpdateUserRequestNoUserIDorEmail",
|
||||
) -> Mapping[str, object]: ...
|
||||
|
||||
|
||||
def _update_internal_user_params(data_json: dict, data: UpdateUserRequest | UpdateUserRequestNoUserIDorEmail) -> dict:
|
||||
def _update_internal_user_params(
|
||||
data_json: Mapping[str, object],
|
||||
data: UpdateUserRequest | UpdateUserRequestNoUserIDorEmail,
|
||||
) -> dict: # mutable-ok: `/user/bulk_update` pops identity fields off this result before writing it
|
||||
"""Legacy `/user/update` field resolution: a null (or `[]`/`{}`) means "not sent", so it is dropped.
|
||||
|
||||
Retained verbatim for backwards compatibility. It dates to when `data_json` came from
|
||||
|
|
@ -1284,7 +1291,7 @@ def _check_user_update_authz(
|
|||
# Presence, not truthiness: a merge-patch caller clearing their own role to null would otherwise
|
||||
# slip past a `is not None` check and demote themselves out of whatever an admin assigned.
|
||||
sends_role: Final = (
|
||||
"user_role" in (user_request.fields_set() if hasattr(user_request, "fields_set") else set())
|
||||
"user_role" in (user_request.fields_set() if hasattr(user_request, "fields_set") else frozenset())
|
||||
or user_request.user_role is not None
|
||||
)
|
||||
if sends_role and user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN.value:
|
||||
|
|
@ -1400,7 +1407,8 @@ async def update_single_user(
|
|||
)
|
||||
|
||||
data_json: Final[dict] = user_request.model_dump(exclude_unset=True)
|
||||
non_default_values = resolve_fields(data_json, user_request)
|
||||
resolved_fields: Final = resolve_fields(data_json, user_request)
|
||||
non_default_values = dict(resolved_fields) # mutable-ok: the write path stamps into this before prisma takes it
|
||||
_hash_password_in_dict(non_default_values)
|
||||
|
||||
existing_user_row: BaseModel | None = None
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from collections.abc import Mapping, MutableMapping
|
||||
from types import MappingProxyType
|
||||
from typing import Final
|
||||
from typing import Final, TypeAlias
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from fastapi import Request
|
||||
|
|
@ -54,34 +54,41 @@ def problem_response(problem: ProblemDetail) -> JSONResponse:
|
|||
)
|
||||
|
||||
|
||||
def add_problem_detail_component(openapi_schema: MutableMapping[str, object]) -> MutableMapping[str, object]:
|
||||
def add_problem_detail_component(
|
||||
openapi_schema: MutableMapping[str, object], # mutable-ok: fastapi's generated schema is a plain nested dict
|
||||
) -> None:
|
||||
"""Register `ProblemDetail` as an OpenAPI component so `problem_responses()` can `$ref` it.
|
||||
|
||||
FastAPI only emits components for models reachable from a route's `response_model`, and a problem
|
||||
document never is one: it is the failure shape, declared out of band.
|
||||
"""
|
||||
components: Final = openapi_schema.setdefault("components", {})
|
||||
components: Final = openapi_schema.setdefault("components", {}) # mutable-ok: seeds a branch of fastapi's own dict
|
||||
if not isinstance(components, MutableMapping):
|
||||
return openapi_schema
|
||||
schemas: Final = components.setdefault("schemas", {})
|
||||
return
|
||||
schemas: Final = components.setdefault("schemas", {}) # mutable-ok: seeds a branch of fastapi's own dict
|
||||
if isinstance(schemas, MutableMapping):
|
||||
schemas.setdefault(PROBLEM_DETAIL_SCHEMA_NAME, ProblemDetail.model_json_schema())
|
||||
return openapi_schema
|
||||
|
||||
|
||||
def problem_responses(*statuses: int) -> dict[int | str, dict[str, object]]:
|
||||
# FastAPI declares `responses=` as a dict of dicts and rewrites copies of the entries as it renders
|
||||
# the schema, so every layer below has to be a plain mutable dict.
|
||||
ProblemResponses: TypeAlias = dict[int | str, dict[str, object]] # mutable-ok: fastapi's `responses=` contract
|
||||
|
||||
|
||||
def problem_responses(*statuses: int) -> ProblemResponses:
|
||||
"""OpenAPI `responses=` entries declaring each status as an RFC 9457 problem document.
|
||||
|
||||
Spelled as a raw `$ref` rather than `model=`, because FastAPI renders a `model=` entry under the
|
||||
route's own media type and would document these as `application/json`. A plain `dict` because
|
||||
that is the shape FastAPI's `responses=` parameter is annotated to take.
|
||||
route's own media type and would document these as `application/json`.
|
||||
"""
|
||||
return {
|
||||
status: {
|
||||
"description": _PROBLEM_TITLES.get(status, "Error"),
|
||||
"content": {PROBLEM_CONTENT_TYPE: {"schema": {"$ref": PROBLEM_DETAIL_REF}}},
|
||||
}
|
||||
for status in statuses
|
||||
return {status: _problem_entry(status) for status in statuses} # mutable-ok: fastapi's `responses=` contract
|
||||
|
||||
|
||||
def _problem_entry(status: int) -> dict[str, object]: # mutable-ok: fastapi's `responses=` contract
|
||||
media_type: Final = {"schema": {"$ref": PROBLEM_DETAIL_REF}} # mutable-ok: fastapi's `responses=` contract
|
||||
return { # mutable-ok: fastapi's `responses=` contract
|
||||
"description": _PROBLEM_TITLES.get(status, "Error"),
|
||||
"content": {PROBLEM_CONTENT_TYPE: media_type}, # mutable-ok: fastapi's `responses=` contract
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""`PATCH /management/v1/users/{user_id}`."""
|
||||
|
||||
from collections.abc import Callable, Mapping
|
||||
from collections.abc import Callable, Mapping, Sequence
|
||||
from datetime import datetime
|
||||
from types import MappingProxyType
|
||||
from typing import TYPE_CHECKING, Annotated, Final, Literal
|
||||
|
|
@ -49,6 +49,8 @@ _CLEARS_TO_EMPTY: Final[Mapping[str, Callable[[], object]]] = MappingProxyType(
|
|||
{"models": list, "metadata": dict, "model_max_budget": dict}
|
||||
)
|
||||
|
||||
_NO_FIELDS: Final[Mapping[str, object]] = MappingProxyType({})
|
||||
|
||||
|
||||
class UserPatchRequest(BaseModel):
|
||||
"""Body of `PATCH /management/v1/users/{user_id}`, read as an RFC 7396 JSON merge patch.
|
||||
|
|
@ -71,14 +73,14 @@ class UserPatchRequest(BaseModel):
|
|||
]
|
||||
| None
|
||||
) = None
|
||||
models: list[str] | None = None
|
||||
models: Sequence[str] | None = None
|
||||
max_budget: float | None = None
|
||||
budget_duration: str | None = None
|
||||
tpm_limit: int | None = None
|
||||
rpm_limit: int | None = None
|
||||
max_parallel_requests: int | None = None
|
||||
metadata: dict[str, JsonValue] | None = None
|
||||
model_max_budget: dict[str, JsonValue] | None = None
|
||||
metadata: Mapping[str, JsonValue] | None = None
|
||||
model_max_budget: Mapping[str, JsonValue] | None = None
|
||||
object_permission: LiteLLM_ObjectPermissionBase | None = None
|
||||
|
||||
|
||||
|
|
@ -95,7 +97,7 @@ class UserItem(BaseModel):
|
|||
user_email: str | None = None
|
||||
user_alias: str | None = None
|
||||
user_role: str | None = None
|
||||
models: list[str] = Field(default_factory=list)
|
||||
models: Sequence[str] = Field(default_factory=list)
|
||||
spend: float = 0.0
|
||||
max_budget: float | None = None
|
||||
budget_duration: str | None = None
|
||||
|
|
@ -103,18 +105,18 @@ class UserItem(BaseModel):
|
|||
tpm_limit: int | None = None
|
||||
rpm_limit: int | None = None
|
||||
max_parallel_requests: int | None = None
|
||||
metadata: dict[str, JsonValue] = Field(default_factory=dict)
|
||||
model_max_budget: dict[str, JsonValue] = Field(default_factory=dict)
|
||||
metadata: Mapping[str, JsonValue] = Field(default_factory=dict)
|
||||
model_max_budget: Mapping[str, JsonValue] = Field(default_factory=dict)
|
||||
object_permission_id: str | None = None
|
||||
teams: list[str] = Field(default_factory=list)
|
||||
teams: Sequence[str] = Field(default_factory=list)
|
||||
created_at: datetime | None = None
|
||||
updated_at: datetime | None = None
|
||||
|
||||
|
||||
def resolve_user_patch_fields(
|
||||
data_json: dict,
|
||||
data_json: Mapping[str, object],
|
||||
data: UpdateUserRequest | UpdateUserRequestNoUserIDorEmail,
|
||||
) -> dict:
|
||||
) -> Mapping[str, object]:
|
||||
"""Merge-patch field resolution: whatever the caller sent is written, nulls included.
|
||||
|
||||
The inverse of `_update_internal_user_params`, which drops nulls and so cannot express a clear.
|
||||
|
|
@ -124,29 +126,36 @@ def resolve_user_patch_fields(
|
|||
`object_permission` clear is left for the caller of this resolver, which drops the entitlement
|
||||
link rather than writing a column.
|
||||
"""
|
||||
resolved: Final[dict] = {
|
||||
key: (_CLEARS_TO_EMPTY[key]() if value is None and key in _CLEARS_TO_EMPTY else value)
|
||||
for key, value in data_json.items()
|
||||
if not (key == "object_permission" and value is None)
|
||||
}
|
||||
derived: Final = _budget_reset_fields(resolved["budget_duration"]) if "budget_duration" in resolved else {}
|
||||
return {**resolved, **derived, **_internal_user_role_defaults(data, resolved)}
|
||||
resolved: Final[Mapping[str, object]] = MappingProxyType(
|
||||
{
|
||||
key: (_CLEARS_TO_EMPTY[key]() if value is None and key in _CLEARS_TO_EMPTY else value)
|
||||
for key, value in data_json.items()
|
||||
if not (key == "object_permission" and value is None)
|
||||
}
|
||||
)
|
||||
budget_duration: Final = resolved.get("budget_duration")
|
||||
derived: Final = (
|
||||
_budget_reset_fields(budget_duration if isinstance(budget_duration, str) else None)
|
||||
if "budget_duration" in resolved
|
||||
else _NO_FIELDS
|
||||
)
|
||||
return MappingProxyType({**resolved, **derived, **_internal_user_role_defaults(data, resolved)})
|
||||
|
||||
|
||||
def _budget_reset_fields(budget_duration: str | None) -> dict:
|
||||
def _budget_reset_fields(budget_duration: str | None) -> Mapping[str, object]:
|
||||
"""`budget_reset_at` is derived from `budget_duration`, so the two only ever move together."""
|
||||
from litellm.proxy.common_utils.timezone_utils import get_budget_reset_time
|
||||
|
||||
if budget_duration is None:
|
||||
return {"budget_reset_at": None}
|
||||
return MappingProxyType({"budget_reset_at": None})
|
||||
validate_budget_duration(budget_duration)
|
||||
return {"budget_reset_at": get_budget_reset_time(budget_duration=budget_duration)}
|
||||
return MappingProxyType({"budget_reset_at": get_budget_reset_time(budget_duration=budget_duration)})
|
||||
|
||||
|
||||
def _internal_user_role_defaults(
|
||||
data: UpdateUserRequest | UpdateUserRequestNoUserIDorEmail,
|
||||
resolved: dict,
|
||||
) -> dict:
|
||||
resolved: Mapping[str, object],
|
||||
) -> Mapping[str, object]:
|
||||
"""Apply the proxy-wide internal-user budget caps, but only to fields the caller left alone.
|
||||
|
||||
Mirrors `/user/update`, so promoting someone to internal user still lands them under
|
||||
|
|
@ -154,19 +163,21 @@ def _internal_user_role_defaults(
|
|||
never overwritten by a global default the caller was trying to get out from under.
|
||||
"""
|
||||
if data.user_role != LitellmUserRoles.INTERNAL_USER:
|
||||
return {}
|
||||
budget: Final = (
|
||||
{"max_budget": litellm.max_internal_user_budget}
|
||||
return _NO_FIELDS
|
||||
budget: Final[Mapping[str, object]] = (
|
||||
MappingProxyType({"max_budget": litellm.max_internal_user_budget})
|
||||
if "max_budget" not in resolved and litellm.max_internal_user_budget is not None
|
||||
else {}
|
||||
else _NO_FIELDS
|
||||
)
|
||||
if "budget_duration" in resolved or litellm.internal_user_budget_duration is None:
|
||||
return budget
|
||||
return {
|
||||
**budget,
|
||||
"budget_duration": litellm.internal_user_budget_duration,
|
||||
**_budget_reset_fields(litellm.internal_user_budget_duration),
|
||||
}
|
||||
return MappingProxyType(
|
||||
{
|
||||
**budget,
|
||||
"budget_duration": litellm.internal_user_budget_duration,
|
||||
**_budget_reset_fields(litellm.internal_user_budget_duration),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _problem(status: int, slug: str, title: str, detail: str) -> ManagementProblem:
|
||||
|
|
@ -200,7 +211,7 @@ def _problem_from_http_exception(exc: HTTPException) -> ManagementProblem:
|
|||
|
||||
@router.patch(
|
||||
"/users/{user_id}",
|
||||
tags=["Internal User management"],
|
||||
tags=("Internal User management",),
|
||||
dependencies=(Depends(user_api_key_auth),),
|
||||
response_model=ItemResponse[UserItem],
|
||||
responses=problem_responses(403, 404, 422, 500, 503),
|
||||
|
|
@ -278,4 +289,4 @@ async def patch_user(
|
|||
|
||||
async def _find_user(prisma_client: PrismaClient, user_id: str) -> "prisma_models.LiteLLM_UserTable | None":
|
||||
table: Final[TableActions[prisma_models.LiteLLM_UserTable]] = UserRepository(prisma_client).table
|
||||
return await table.find_first(where={"user_id": user_id})
|
||||
return await table.find_first(where={"user_id": user_id}) # mutable-ok: prisma query filters are dict-shaped
|
||||
|
|
|
|||
223
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
223
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -8111,6 +8111,42 @@ export interface paths {
|
|||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/management/v1/users/{user_id}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
/**
|
||||
* Patch User
|
||||
* @description Partially update one internal user, as an RFC 7396 JSON merge patch.
|
||||
*
|
||||
* An omitted field is left alone and an explicit `null` clears the setting, which is the whole
|
||||
* reason this route exists: `POST /user/update` drops nulls, so it answers `200` to a clear it
|
||||
* silently discarded, and only `max_budget` was ever made clearable. Unknown body keys are
|
||||
* refused with a `422` rather than ignored. `null` on `models`, `metadata` or `model_max_budget`
|
||||
* resets the column to empty, since the schema declares those NOT NULL.
|
||||
*
|
||||
* Requires a proxy admin: the route is in no non-admin allowlist, so everyone else is refused at
|
||||
* the route gate, and the shared write path's self-service guards stand behind that as defense in
|
||||
* depth. Unlike `/user/update`, a user id that does not exist is a `404` rather than a silent
|
||||
* create, since the underlying write is an upsert.
|
||||
*
|
||||
* Example curl, clearing a rate limit and setting another:
|
||||
* ```
|
||||
* curl --location --request PATCH 'http://0.0.0.0:4000/management/v1/users/user123' --header 'Authorization: Bearer sk-1234' --header 'Content-Type: application/json' --data '{"tpm_limit": null, "rpm_limit": 60}'
|
||||
* ```
|
||||
*/
|
||||
patch: operations["patch_user_management_v1_users__user_id__patch"];
|
||||
trace?: never;
|
||||
};
|
||||
"/mcp": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
|
@ -27151,6 +27187,10 @@ export interface components {
|
|||
/** Is Accepted */
|
||||
is_accepted: boolean;
|
||||
};
|
||||
/** ItemResponse[UserItem] */
|
||||
ItemResponse_UserItem_: {
|
||||
data: components["schemas"]["UserItem"];
|
||||
};
|
||||
/** JWTKeyMappingResponse */
|
||||
JWTKeyMappingResponse: {
|
||||
/**
|
||||
|
|
@ -27178,6 +27218,7 @@ export interface components {
|
|||
/** Updated By */
|
||||
updated_by?: string | null;
|
||||
};
|
||||
JsonValue: unknown;
|
||||
/** KeyHealthResponse */
|
||||
KeyHealthResponse: {
|
||||
/**
|
||||
|
|
@ -30208,6 +30249,8 @@ export interface components {
|
|||
token_exchange_profile?: string | null;
|
||||
/** Upstream Resource */
|
||||
upstream_resource?: string | null;
|
||||
/** Upstream Token Header */
|
||||
upstream_token_header?: string | null;
|
||||
};
|
||||
/**
|
||||
* MCPEnvVar
|
||||
|
|
@ -32960,6 +33003,25 @@ export interface components {
|
|||
*/
|
||||
version_status: string;
|
||||
};
|
||||
/**
|
||||
* ProblemDetail
|
||||
* @description RFC 9457 problem details, served as `application/problem+json`.
|
||||
*/
|
||||
ProblemDetail: {
|
||||
/**
|
||||
* Allowed
|
||||
* @default null
|
||||
*/
|
||||
allowed: string[] | null;
|
||||
/** Detail */
|
||||
detail: string;
|
||||
/** Status */
|
||||
status: number;
|
||||
/** Title */
|
||||
title: string;
|
||||
/** Type */
|
||||
type: string;
|
||||
};
|
||||
/** Prompt */
|
||||
Prompt: {
|
||||
litellm_params: components["schemas"]["PromptLiteLLMParams"];
|
||||
|
|
@ -37540,6 +37602,58 @@ export interface components {
|
|||
/** User Role */
|
||||
user_role?: string | null;
|
||||
};
|
||||
/**
|
||||
* UserItem
|
||||
* @description One internal user as the control plane returns it, read back off the row the write produced.
|
||||
*
|
||||
* Re-reading rather than echoing the request is the point of the endpoint: a caller can tell a
|
||||
* clear that landed from one that was dropped by looking at the response.
|
||||
*/
|
||||
UserItem: {
|
||||
/** Budget Duration */
|
||||
budget_duration?: string | null;
|
||||
/** Budget Reset At */
|
||||
budget_reset_at?: string | null;
|
||||
/** Created At */
|
||||
created_at?: string | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
max_parallel_requests?: number | null;
|
||||
/** Metadata */
|
||||
metadata?: {
|
||||
[key: string]: components["schemas"]["JsonValue"];
|
||||
};
|
||||
/** Model Max Budget */
|
||||
model_max_budget?: {
|
||||
[key: string]: components["schemas"]["JsonValue"];
|
||||
};
|
||||
/** Models */
|
||||
models?: string[];
|
||||
/** Object Permission Id */
|
||||
object_permission_id?: string | null;
|
||||
/** Rpm Limit */
|
||||
rpm_limit?: number | null;
|
||||
/**
|
||||
* Spend
|
||||
* @default 0
|
||||
*/
|
||||
spend: number;
|
||||
/** Teams */
|
||||
teams?: string[];
|
||||
/** Tpm Limit */
|
||||
tpm_limit?: number | null;
|
||||
/** Updated At */
|
||||
updated_at?: string | null;
|
||||
/** User Alias */
|
||||
user_alias?: string | null;
|
||||
/** User Email */
|
||||
user_email?: string | null;
|
||||
/** User Id */
|
||||
user_id: string;
|
||||
/** User Role */
|
||||
user_role?: string | null;
|
||||
};
|
||||
/**
|
||||
* UserListResponse
|
||||
* @description Response model for the user list endpoint
|
||||
|
|
@ -37556,6 +37670,43 @@ export interface components {
|
|||
/** Users */
|
||||
users: components["schemas"]["LiteLLM_UserTableWithKeyCount"][];
|
||||
};
|
||||
/**
|
||||
* UserPatchRequest
|
||||
* @description Body of `PATCH /management/v1/users/{user_id}`, read as an RFC 7396 JSON merge patch.
|
||||
*
|
||||
* Every field is optional and nullable, and the two are not the same thing: an omitted field is
|
||||
* left alone, an explicit `null` clears the setting. `extra="forbid"` is what makes that promise
|
||||
* keepable, since a misspelled key would otherwise read as "omitted" and silently do nothing.
|
||||
*/
|
||||
UserPatchRequest: {
|
||||
/** Budget Duration */
|
||||
budget_duration?: string | null;
|
||||
/** Max Budget */
|
||||
max_budget?: number | null;
|
||||
/** Max Parallel Requests */
|
||||
max_parallel_requests?: number | null;
|
||||
/** Metadata */
|
||||
metadata?: {
|
||||
[key: string]: components["schemas"]["JsonValue"];
|
||||
} | null;
|
||||
/** Model Max Budget */
|
||||
model_max_budget?: {
|
||||
[key: string]: components["schemas"]["JsonValue"];
|
||||
} | null;
|
||||
/** Models */
|
||||
models?: string[] | null;
|
||||
object_permission?: components["schemas"]["LiteLLM_ObjectPermissionBase"] | null;
|
||||
/** Rpm Limit */
|
||||
rpm_limit?: number | null;
|
||||
/** Tpm Limit */
|
||||
tpm_limit?: number | null;
|
||||
/** User Alias */
|
||||
user_alias?: string | null;
|
||||
/** User Email */
|
||||
user_email?: string | null;
|
||||
/** User Role */
|
||||
user_role?: ("proxy_admin" | "proxy_admin_viewer" | "internal_user" | "internal_user_viewer") | null;
|
||||
};
|
||||
/**
|
||||
* UserUpdateResult
|
||||
* @description Result of a single user update operation
|
||||
|
|
@ -48588,6 +48739,78 @@ export interface operations {
|
|||
};
|
||||
};
|
||||
};
|
||||
patch_user_management_v1_users__user_id__patch: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description The id of the user to update. */
|
||||
user_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["UserPatchRequest"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ItemResponse_UserItem_"];
|
||||
};
|
||||
};
|
||||
/** @description Forbidden */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/problem+json": components["schemas"]["ProblemDetail"];
|
||||
};
|
||||
};
|
||||
/** @description Not found */
|
||||
404: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/problem+json": components["schemas"]["ProblemDetail"];
|
||||
};
|
||||
};
|
||||
/** @description Invalid request body */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/problem+json": components["schemas"]["ProblemDetail"];
|
||||
};
|
||||
};
|
||||
/** @description Internal server error */
|
||||
500: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/problem+json": components["schemas"]["ProblemDetail"];
|
||||
};
|
||||
};
|
||||
/** @description Database not connected */
|
||||
503: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/problem+json": components["schemas"]["ProblemDetail"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
aggregate_mcp_route_mcp_get: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue