From 75fbc4b4813974b44c9b58479f6ec450bdb5ce6e Mon Sep 17 00:00:00 2001 From: chelsealong Date: Wed, 19 Aug 2026 02:00:45 +0000 Subject: [PATCH] fix(proxy): stop /{provider}/v1/files from capturing /anthropic passthrough The native files and batches routers mount before the passthrough router, so /anthropic/v1/files matched /{provider}/v1/files with provider="anthropic" and 422'd on the OpenAI-only `purpose` field instead of forwarding to Anthropic's Files API. Give /anthropic its own router mounted ahead of batches and files, mirroring the fix already applied to /openai_passthrough in #36092. --- .../llm_passthrough_endpoints.py | 3 ++- litellm/proxy/proxy_server.py | 2 ++ .../test_llm_pass_through_endpoints.py | 20 +++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py index c5ab7f1fc63..fb389bd829a 100644 --- a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py @@ -63,6 +63,7 @@ from .passthrough_endpoint_router import PassthroughEndpointRouter vertex_llm_base: Final = VertexBase() router: Final = APIRouter() openai_passthrough_router: Final = APIRouter() +anthropic_passthrough_router: Final = APIRouter() default_vertex_config: Final = None passthrough_endpoint_router: Final = PassthroughEndpointRouter() @@ -563,7 +564,7 @@ async def is_streaming_request_fn(request: Request) -> bool: return False -@router.api_route( +@anthropic_passthrough_router.api_route( "/anthropic/{endpoint:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH"], tags=["Anthropic Pass-through", "pass-through"], diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 56036713fa9..a9de794d2cf 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -560,6 +560,7 @@ from litellm.proxy.openai_files_endpoints.files_endpoints import ( set_files_config, ) from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import ( + anthropic_passthrough_router, openai_passthrough_router, passthrough_endpoint_router, vertex_ai_live_websocket_passthrough, @@ -17280,6 +17281,7 @@ app.include_router(image_router) app.include_router(fine_tuning_router) app.include_router(credential_router) app.include_router(openai_passthrough_router) +app.include_router(anthropic_passthrough_router) app.include_router(batches_router) app.include_router(openai_files_router) app.include_router(llm_passthrough_router) 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 b56a8da7c66..d164f31486f 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 @@ -2990,6 +2990,26 @@ def test_native_provider_routes_are_unchanged(method, path, expected_name): assert _resolve_route_name(method, path) == expected_name +@pytest.mark.parametrize( + "method, path", + [ + ("POST", "/anthropic/v1/files"), + ("GET", "/anthropic/v1/files"), + ("GET", "/anthropic/v1/files/file-abc123"), + ("DELETE", "/anthropic/v1/files/file-abc123"), + ("POST", "/anthropic/v1/batches"), + ("POST", "/anthropic/v1/messages"), + ], +) +def test_anthropic_passthrough_prefix_wins_over_native_provider_routes(method, path): + """ + Anthropic's Files API has no `purpose` field and no `files_settings` config, + so the native /{provider}/v1/files and /{provider}/v1/batches routes must never + capture /anthropic/... with provider="anthropic". + """ + assert _resolve_route_name(method, path) == "anthropic_proxy_route" + + class TestCursorProxyRoute: """Tests for the Cursor Cloud Agents pass-through route."""