From acc375a2a9d2110847597406968c2e6cb7697b4a Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Thu, 17 Sep 2026 18:00:51 -0700 Subject: [PATCH] fix(proxy): forward every method on the typesafe pass-through route #41607 registered the typesafe pass-through with a route that only accepted GET and POST, so a PUT, DELETE or PATCH to /typesafe/... came back 405 before reaching the upstream. CircleCI's pass-through method test caught it, but that lane does not run on the PR gate, so the mapped unit test now covers the same invariant for typesafe The same CircleCI run also failed test_models_by_provider because typesafe is not a key of models_by_provider. Registering it there would satisfy the assertion without changing behaviour: typesafe has no LlmProviders member, so a typesafe/* deployment never loads and get_valid_models returns nothing, and its spend is priced straight from model_cost. The test already skips search-mode providers for that reason, so it now skips evaluation mode too --- .../proxy/pass_through_endpoints/llm_passthrough_endpoints.py | 2 +- tests/litellm_utils_tests/test_utils.py | 3 +-- .../pass_through_endpoints/test_llm_pass_through_endpoints.py | 4 ++++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py index f251b3b052c..1c763db2146 100644 --- a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py @@ -527,7 +527,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( diff --git a/tests/litellm_utils_tests/test_utils.py b/tests/litellm_utils_tests/test_utils.py index 0ccfae55290..b68c2cb3d65 100644 --- a/tests/litellm_utils_tests/test_utils.py +++ b/tests/litellm_utils_tests/test_utils.py @@ -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"]) diff --git a/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py b/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py index 901c5318442..07936e7a9e9 100644 --- a/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py +++ b/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py @@ -6149,6 +6149,10 @@ class TestTypeSafePassthroughRoute: request.json = AsyncMock(return_value=body) return request + @pytest.mark.parametrize("method", ["GET", "POST", "PUT", "DELETE", "PATCH"]) + def test_route_serves_every_method(self, method: str): + assert _resolve_route_name(method, "/typesafe/v1/systemone") == "typesafe_proxy_route" + @pytest.mark.asyncio async def test_forwards_target_auth_headers_provider_and_query(self, monkeypatch): monkeypatch.setenv("TYPESAFE_API_KEY", "typesafe-test-key")