fix(proxy): forward every method on the typesafe pass-through route

Backport of #41723 to stable/1.100.x.
Cherry-picked from 34718f0da6 (main).
This commit is contained in:
yuneng-jiang 2026-09-23 00:14:05 +00:00 • committed by mateo
parent 8bf252b397
commit ded7f601d9
4 changed files with 149 additions and 7 deletions

View file

@ -471,7 +471,7 @@ async def mistral_proxy_route(
@router.api_route(
"/typesafe/{endpoint:path}",
methods=["GET", "POST"], # mutable-ok: FastAPI route metadata requires a list
methods=["GET", "POST", "PUT", "DELETE", "PATCH"], # mutable-ok: FastAPI route metadata requires a list
tags=["TypeSafe AI Pass-through", "pass-through"], # mutable-ok: FastAPI route metadata requires a list
)
async def typesafe_proxy_route(

View file

@ -1360,8 +1360,7 @@ def test_models_by_provider():
or v["litellm_provider"] == "bedrock_converse"
):
continue
elif v.get("mode") == "search":
# Skip search providers as they don't have traditional models
elif v.get("mode") in ("search", "evaluation"):
continue
else:
providers.add(v["litellm_provider"])

View file

@ -3,7 +3,7 @@ import contextlib
import json
import os
import traceback
from collections.abc import Mapping
from collections.abc import Iterator, Mapping
from types import MappingProxyType, SimpleNamespace
from typing import Final
from unittest import mock
@ -12,6 +12,7 @@ from urllib.parse import parse_qs
import httpx
import pytest
import respx
from fastapi import HTTPException, Request, Response
from fastapi.testclient import TestClient
from starlette.datastructures import FormData
@ -42,6 +43,7 @@ from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import (
)
from litellm.proxy._types import LitellmUserRoles, SpecialHeaders, UserAPIKeyAuth
from litellm.proxy.auth.handle_jwt import JWTHandler
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
from litellm.types.passthrough_endpoints.vertex_ai import VertexPassThroughCredentials
@ -4640,6 +4642,42 @@ class TestTypeSafePassthroughRoute:
request.json = AsyncMock(return_value=body)
return request
@pytest.fixture
def client(self, monkeypatch: pytest.MonkeyPatch) -> Iterator[TestClient]:
from litellm.proxy.proxy_server import app
monkeypatch.setenv("TYPESAFE_API_KEY", "typesafe-test-key")
monkeypatch.setenv("TYPESAFE_API_BASE", "https://typesafe.example/base")
monkeypatch.delenv("SERVER_ROOT_PATH", raising=False)
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
litellm.in_memory_llm_clients_cache.flush_cache()
monkeypatch.setitem(app.dependency_overrides, user_api_key_auth, lambda: UserAPIKeyAuth(api_key="sk-virtual"))
yield TestClient(app)
@pytest.mark.parametrize(
"method, body",
[
("GET", None),
("POST", {"state": "x"}),
("PUT", {"state": "x"}),
("DELETE", None),
("PATCH", {"state": "x"}),
],
)
def test_forwards_every_method_and_body_upstream(
self, client: TestClient, method: str, body: dict[str, str] | None
) -> None:
with respx.mock(assert_all_called=True) as upstream:
route = upstream.request(method, "https://typesafe.example/base/v1/systemone").mock(
return_value=httpx.Response(200, json={"id": "upstream_123"})
)
response = client.request(method, "/typesafe/v1/systemone", json=body)
assert (response.status_code, response.json()) == (200, {"id": "upstream_123"})
sent: Final = route.calls.last.request
assert sent.headers["authorization"] == "Bearer typesafe-test-key"
assert json.loads(sent.content or b"{}") == (body or {})
@pytest.mark.asyncio
async def test_forwards_target_auth_headers_provider_and_query(self, monkeypatch):
monkeypatch.setenv("TYPESAFE_API_KEY", "typesafe-test-key")

View file

@ -15911,16 +15911,28 @@ export interface paths {
* @description [Docs](https://docs.litellm.ai/docs/pass_through/typesafe)
*/
get: operations["typesafe_proxy_route_typesafe__endpoint__get"];
put?: never;
/**
* Typesafe Proxy Route
* @description [Docs](https://docs.litellm.ai/docs/pass_through/typesafe)
*/
put: operations["typesafe_proxy_route_typesafe__endpoint__put"];
/**
* Typesafe Proxy Route
* @description [Docs](https://docs.litellm.ai/docs/pass_through/typesafe)
*/
post: operations["typesafe_proxy_route_typesafe__endpoint__post"];
delete?: never;
/**
* Typesafe Proxy Route
* @description [Docs](https://docs.litellm.ai/docs/pass_through/typesafe)
*/
delete: operations["typesafe_proxy_route_typesafe__endpoint__delete"];
options?: never;
head?: never;
patch?: never;
/**
* Typesafe Proxy Route
* @description [Docs](https://docs.litellm.ai/docs/pass_through/typesafe)
*/
patch: operations["typesafe_proxy_route_typesafe__endpoint__patch"];
trace?: never;
};
"/update/default_team_settings": {
@ -58373,6 +58385,37 @@ export interface operations {
};
};
};
typesafe_proxy_route_typesafe__endpoint__put: {
parameters: {
query?: never;
header?: never;
path: {
endpoint: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": unknown;
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
typesafe_proxy_route_typesafe__endpoint__post: {
parameters: {
query?: never;
@ -58404,6 +58447,68 @@ export interface operations {
};
};
};
typesafe_proxy_route_typesafe__endpoint__delete: {
parameters: {
query?: never;
header?: never;
path: {
endpoint: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": unknown;
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
typesafe_proxy_route_typesafe__endpoint__patch: {
parameters: {
query?: never;
header?: never;
path: {
endpoint: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": unknown;
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
update_default_team_settings_update_default_team_settings_patch: {
parameters: {
query?: never;