diff --git a/litellm/proxy/_lazy_openapi_snapshot.json b/litellm/proxy/_lazy_openapi_snapshot.json index 473b21186e8..b244678e201 100644 --- a/litellm/proxy/_lazy_openapi_snapshot.json +++ b/litellm/proxy/_lazy_openapi_snapshot.json @@ -20374,6 +20374,50 @@ } }, "/typesafe/{endpoint}": { + "delete": { + "description": "[Docs](https://docs.litellm.ai/docs/pass_through/typesafe)", + "operationId": "typesafe_proxy_route_typesafe__endpoint__delete", + "parameters": [ + { + "in": "path", + "name": "endpoint", + "required": true, + "schema": { + "title": "Endpoint", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ], + "summary": "Typesafe Proxy Route", + "tags": [ + "llm_passthrough" + ] + }, "get": { "description": "[Docs](https://docs.litellm.ai/docs/pass_through/typesafe)", "operationId": "typesafe_proxy_route_typesafe__endpoint__get", @@ -20418,6 +20462,50 @@ "llm_passthrough" ] }, + "patch": { + "description": "[Docs](https://docs.litellm.ai/docs/pass_through/typesafe)", + "operationId": "typesafe_proxy_route_typesafe__endpoint__patch", + "parameters": [ + { + "in": "path", + "name": "endpoint", + "required": true, + "schema": { + "title": "Endpoint", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ], + "summary": "Typesafe Proxy Route", + "tags": [ + "llm_passthrough" + ] + }, "post": { "description": "[Docs](https://docs.litellm.ai/docs/pass_through/typesafe)", "operationId": "typesafe_proxy_route_typesafe__endpoint__post", @@ -20461,6 +20549,50 @@ "tags": [ "llm_passthrough" ] + }, + "put": { + "description": "[Docs](https://docs.litellm.ai/docs/pass_through/typesafe)", + "operationId": "typesafe_proxy_route_typesafe__endpoint__put", + "parameters": [ + { + "in": "path", + "name": "endpoint", + "required": true, + "schema": { + "title": "Endpoint", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ], + "summary": "Typesafe Proxy Route", + "tags": [ + "llm_passthrough" + ] } }, "/vertex_ai/discovery/{endpoint}": { 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..9394a13fee4 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,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") diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index d9bc9827d47..fd882937e79 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -16488,16 +16488,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": { @@ -61695,6 +61707,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; @@ -61726,6 +61769,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;