refactor(providers): split proxy route adapters (#1934)
## Description
Refactors provider-specific proxy routing into provider-owned helper
modules so `headroom/providers/proxy_routes.py` primarily registers
routes and delegates behavior. This keeps Codex, OpenAI
Responses/images, model metadata, Vertex, Cloud Code, passthrough target
selection, and request path normalization logic testable outside the
route table.
Closes #
## Type of Change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Documentation update
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)
## Changes Made
- Extracted Codex routing helpers for headers, endpoint URLs, image
forwarding, response subpaths, and model metadata.
- Moved provider target selection, route specs, OpenAI Responses/images
helpers, Vertex runtime helpers, Cloud Code path normalization,
passthrough telemetry, and request scope normalization into focused
modules.
- Kept `proxy_routes.py` as route registration/delegation and preserved
current-main `/v1/messages` custom-base behavior.
- Added focused provider/proxy tests for the extracted modules and route
delegation behavior.
## Testing
- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed
### Test Output
```text
python -m pytest tests/test_package_init_lazy.py::test_codex_package_import_stays_runtime_only tests/test_provider_cloudcode_runtime.py tests/test_provider_codex_endpoints.py tests/test_provider_codex_headers.py tests/test_provider_codex_images.py tests/test_provider_codex_model_metadata.py tests/test_provider_codex_responses.py tests/test_provider_model_metadata.py tests/test_provider_openai_images.py tests/test_provider_openai_responses.py tests/test_provider_proxy_targets.py tests/test_provider_route_specs.py tests/test_provider_vertex_runtime.py tests/test_proxy_request_scope.py tests/test_provider_proxy_routes.py::test_provider_passthrough_routes_forward_expected_targets tests/test_provider_proxy_routes.py::test_proxy_route_helpers_prefer_legacy_targets_and_gemini_passthrough tests/test_provider_proxy_routes.py::test_provider_specific_routes_delegate_to_expected_proxy_handlers tests/test_provider_proxy_routes.py::test_openai_response_websocket_aliases_delegate_to_openai_ws_handler tests/test_provider_proxy_routes.py::test_openai_response_subpath_passthrough_returns_502_on_http_failure tests/test_provider_proxy_routes.py::test_openai_response_subpath_passthrough_uses_openai_target tests/test_provider_proxy_routes.py::test_openai_response_subpath_aliases_and_chatgpt_auth_use_expected_targets tests/test_provider_proxy_routes.py::test_openai_image_routes_use_codex_backend_under_chatgpt_auth tests/test_provider_proxy_routes.py::test_openai_image_codex_response_strips_stale_compression_headers tests/test_provider_proxy_routes.py::test_openai_image_edits_api_key_auth_falls_through_to_openai_passthrough tests/test_provider_proxy_routes.py::test_openai_image_edits_preserves_multipart_body_under_chatgpt_auth tests/test_provider_proxy_routes.py::test_gemini_batch_embed_contents_passthrough_uses_gemini_target tests/test_provider_proxy_routes.py::test_v1_models_fetches_codex_registry_under_chatgpt_auth tests/test_provider_proxy_routes.py::test_v1_models_falls_back_to_synthetic_list_under_chatgpt_auth tests/test_provider_proxy_routes.py::test_v1_models_get_single_dynamic_under_chatgpt_auth tests/test_provider_proxy_routes.py::test_v1_models_still_forwards_under_non_chatgpt_auth tests/test_provider_proxy_routes.py::test_v1_models_routes_claude_code_gateway_discovery_to_anthropic tests/test_provider_proxy_routes.py::test_anthropic_model_metadata_strips_ansi_model_ids tests/test_custom_base_passthrough_telemetry.py tests/test_proxy_passthrough.py tests/test_proxy_google_cloudcode_route_aliases.py tests/test_proxy_project_savings.py::test_with_project_prefix_round_trips_through_split tests/test_vertex_claude_compression.py
============================ 102 passed in 34.83s =============================
python -m ruff check headroom/providers/cloudcode headroom/providers/codex headroom/providers/vertex headroom/providers/model_metadata.py headroom/providers/openai_images.py headroom/providers/openai_responses.py headroom/providers/proxy_targets.py headroom/providers/route_specs.py headroom/providers/proxy_routes.py headroom/proxy/handlers/openai.py headroom/proxy/passthrough.py headroom/proxy/request_scope.py headroom/proxy/project_context.py tests/test_package_init_lazy.py tests/test_provider_cloudcode_runtime.py tests/test_provider_codex_endpoints.py tests/test_provider_codex_headers.py tests/test_provider_codex_images.py tests/test_provider_codex_model_metadata.py tests/test_provider_codex_responses.py tests/test_provider_model_metadata.py tests/test_provider_openai_images.py tests/test_provider_openai_responses.py tests/test_provider_proxy_targets.py tests/test_provider_route_specs.py tests/test_provider_vertex_runtime.py tests/test_proxy_request_scope.py tests/test_provider_proxy_routes.py tests/test_custom_base_passthrough_telemetry.py tests/test_proxy_passthrough.py tests/test_proxy_google_cloudcode_route_aliases.py tests/test_proxy_project_savings.py tests/test_vertex_claude_compression.py
All checks passed!
python -m compileall -q headroom\providers\cloudcode headroom\providers\codex headroom\providers\vertex headroom\providers\model_metadata.py headroom\providers\openai_images.py headroom\providers\openai_responses.py headroom\providers\proxy_targets.py headroom\providers\route_specs.py headroom\providers\proxy_routes.py headroom\proxy\handlers\openai.py headroom\proxy\passthrough.py headroom\proxy\request_scope.py headroom\proxy\project_context.py
# no output; exited 0
git commit -m "refactor(providers): split proxy route adapters"
Sync plugin versions.....................................................Passed
check for merge conflicts................................................Passed
ruff.....................................................................Passed
ruff-format..............................................................Passed
mypy.....................................................................Passed
```
## Real Behavior Proof
- Environment: Windows PowerShell, Python 3.13.13, branch
`jd/provider-route-slices` based on `headroomlabs/main`.
- Exact command / steps: Ran the focused provider/proxy pytest suite,
focused ruff command, compileall over changed Python modules, and commit
hooks.
- Observed result: Provider/proxy route behavior tests passed; lint,
formatting, and mypy passed.
- Not tested: Full pytest suite, live upstream provider calls, and
manual end-to-end proxy traffic.
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable
## Screenshots (if applicable)
N/A
## Additional Notes
Documentation and CHANGELOG updates are N/A for this internal refactor.
The full pytest suite was not run; coverage here is focused on
provider/proxy routing behavior touched by this slice.
---------
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-07-12 21:27:45 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from headroom.providers.route_specs import (
|
|
|
|
|
ANTHROPIC_BATCH_ROUTES,
|
|
|
|
|
ANTHROPIC_HANDLER_ROUTES,
|
|
|
|
|
ANTHROPIC_PASSTHROUGH_ROUTES,
|
|
|
|
|
CLOUDCODE_HANDLER_ROUTES,
|
|
|
|
|
GEMINI_BATCH_ROUTES,
|
|
|
|
|
GEMINI_HANDLER_ROUTES,
|
|
|
|
|
GEMINI_PASSTHROUGH_ROUTES,
|
|
|
|
|
OPENAI_BATCH_ROUTES,
|
|
|
|
|
OPENAI_HANDLER_ROUTES,
|
|
|
|
|
OPENAI_PASSTHROUGH_ROUTES,
|
|
|
|
|
PROVIDER_HANDLER_ROUTES,
|
|
|
|
|
PROVIDER_PASSTHROUGH_ROUTES,
|
|
|
|
|
ProviderHandlerRoute,
|
|
|
|
|
ProviderPassthroughRoute,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_provider_passthrough_route_specs_are_unique() -> None:
|
|
|
|
|
route_keys = {(spec.method, spec.path) for spec in PROVIDER_PASSTHROUGH_ROUTES}
|
|
|
|
|
|
|
|
|
|
assert len(route_keys) == len(PROVIDER_PASSTHROUGH_ROUTES)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_provider_handler_route_specs_are_unique() -> None:
|
|
|
|
|
route_keys = {(spec.method, spec.path) for spec in PROVIDER_HANDLER_ROUTES}
|
|
|
|
|
|
|
|
|
|
assert len(route_keys) == len(PROVIDER_HANDLER_ROUTES)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_openai_passthrough_routes_model_endpoint_intent() -> None:
|
|
|
|
|
assert OPENAI_PASSTHROUGH_ROUTES == (
|
|
|
|
|
ProviderPassthroughRoute("POST", "/v1/embeddings", "openai", "embeddings"),
|
|
|
|
|
ProviderPassthroughRoute("POST", "/v1/moderations", "openai", "moderations"),
|
|
|
|
|
ProviderPassthroughRoute(
|
|
|
|
|
"POST",
|
|
|
|
|
"/v1/audio/transcriptions",
|
|
|
|
|
"openai",
|
|
|
|
|
"audio/transcriptions",
|
|
|
|
|
),
|
|
|
|
|
ProviderPassthroughRoute("POST", "/v1/audio/speech", "openai", "audio/speech"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_anthropic_passthrough_routes_model_endpoint_intent() -> None:
|
|
|
|
|
assert ANTHROPIC_PASSTHROUGH_ROUTES == (
|
|
|
|
|
ProviderPassthroughRoute(
|
|
|
|
|
"POST",
|
|
|
|
|
"/v1/messages/count_tokens",
|
|
|
|
|
"anthropic",
|
|
|
|
|
"count_tokens",
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_gemini_passthrough_routes_model_endpoint_intent() -> None:
|
|
|
|
|
assert (
|
|
|
|
|
ProviderPassthroughRoute(
|
|
|
|
|
"POST",
|
|
|
|
|
"/v1beta/models/{model}:batchEmbedContents",
|
|
|
|
|
"gemini",
|
|
|
|
|
"batchEmbedContents",
|
|
|
|
|
)
|
|
|
|
|
in GEMINI_PASSTHROUGH_ROUTES
|
|
|
|
|
)
|
|
|
|
|
assert (
|
|
|
|
|
ProviderPassthroughRoute(
|
|
|
|
|
"DELETE",
|
|
|
|
|
"/v1beta/cachedContents/{cache_id}",
|
|
|
|
|
"gemini",
|
|
|
|
|
"cachedContents",
|
|
|
|
|
)
|
|
|
|
|
in GEMINI_PASSTHROUGH_ROUTES
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_direct_handler_routes_model_endpoint_intent() -> None:
|
|
|
|
|
assert ANTHROPIC_HANDLER_ROUTES == (
|
|
|
|
|
ProviderHandlerRoute("POST", "/v1/messages", "handle_anthropic_messages"),
|
|
|
|
|
)
|
|
|
|
|
assert OPENAI_HANDLER_ROUTES == (
|
|
|
|
|
ProviderHandlerRoute("POST", "/v1/chat/completions", "handle_openai_chat"),
|
2026-08-13 15:06:41 -05:00
|
|
|
ProviderHandlerRoute("POST", "/chat/completions", "handle_openai_chat"),
|
refactor(providers): split proxy route adapters (#1934)
## Description
Refactors provider-specific proxy routing into provider-owned helper
modules so `headroom/providers/proxy_routes.py` primarily registers
routes and delegates behavior. This keeps Codex, OpenAI
Responses/images, model metadata, Vertex, Cloud Code, passthrough target
selection, and request path normalization logic testable outside the
route table.
Closes #
## Type of Change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Documentation update
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)
## Changes Made
- Extracted Codex routing helpers for headers, endpoint URLs, image
forwarding, response subpaths, and model metadata.
- Moved provider target selection, route specs, OpenAI Responses/images
helpers, Vertex runtime helpers, Cloud Code path normalization,
passthrough telemetry, and request scope normalization into focused
modules.
- Kept `proxy_routes.py` as route registration/delegation and preserved
current-main `/v1/messages` custom-base behavior.
- Added focused provider/proxy tests for the extracted modules and route
delegation behavior.
## Testing
- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed
### Test Output
```text
python -m pytest tests/test_package_init_lazy.py::test_codex_package_import_stays_runtime_only tests/test_provider_cloudcode_runtime.py tests/test_provider_codex_endpoints.py tests/test_provider_codex_headers.py tests/test_provider_codex_images.py tests/test_provider_codex_model_metadata.py tests/test_provider_codex_responses.py tests/test_provider_model_metadata.py tests/test_provider_openai_images.py tests/test_provider_openai_responses.py tests/test_provider_proxy_targets.py tests/test_provider_route_specs.py tests/test_provider_vertex_runtime.py tests/test_proxy_request_scope.py tests/test_provider_proxy_routes.py::test_provider_passthrough_routes_forward_expected_targets tests/test_provider_proxy_routes.py::test_proxy_route_helpers_prefer_legacy_targets_and_gemini_passthrough tests/test_provider_proxy_routes.py::test_provider_specific_routes_delegate_to_expected_proxy_handlers tests/test_provider_proxy_routes.py::test_openai_response_websocket_aliases_delegate_to_openai_ws_handler tests/test_provider_proxy_routes.py::test_openai_response_subpath_passthrough_returns_502_on_http_failure tests/test_provider_proxy_routes.py::test_openai_response_subpath_passthrough_uses_openai_target tests/test_provider_proxy_routes.py::test_openai_response_subpath_aliases_and_chatgpt_auth_use_expected_targets tests/test_provider_proxy_routes.py::test_openai_image_routes_use_codex_backend_under_chatgpt_auth tests/test_provider_proxy_routes.py::test_openai_image_codex_response_strips_stale_compression_headers tests/test_provider_proxy_routes.py::test_openai_image_edits_api_key_auth_falls_through_to_openai_passthrough tests/test_provider_proxy_routes.py::test_openai_image_edits_preserves_multipart_body_under_chatgpt_auth tests/test_provider_proxy_routes.py::test_gemini_batch_embed_contents_passthrough_uses_gemini_target tests/test_provider_proxy_routes.py::test_v1_models_fetches_codex_registry_under_chatgpt_auth tests/test_provider_proxy_routes.py::test_v1_models_falls_back_to_synthetic_list_under_chatgpt_auth tests/test_provider_proxy_routes.py::test_v1_models_get_single_dynamic_under_chatgpt_auth tests/test_provider_proxy_routes.py::test_v1_models_still_forwards_under_non_chatgpt_auth tests/test_provider_proxy_routes.py::test_v1_models_routes_claude_code_gateway_discovery_to_anthropic tests/test_provider_proxy_routes.py::test_anthropic_model_metadata_strips_ansi_model_ids tests/test_custom_base_passthrough_telemetry.py tests/test_proxy_passthrough.py tests/test_proxy_google_cloudcode_route_aliases.py tests/test_proxy_project_savings.py::test_with_project_prefix_round_trips_through_split tests/test_vertex_claude_compression.py
============================ 102 passed in 34.83s =============================
python -m ruff check headroom/providers/cloudcode headroom/providers/codex headroom/providers/vertex headroom/providers/model_metadata.py headroom/providers/openai_images.py headroom/providers/openai_responses.py headroom/providers/proxy_targets.py headroom/providers/route_specs.py headroom/providers/proxy_routes.py headroom/proxy/handlers/openai.py headroom/proxy/passthrough.py headroom/proxy/request_scope.py headroom/proxy/project_context.py tests/test_package_init_lazy.py tests/test_provider_cloudcode_runtime.py tests/test_provider_codex_endpoints.py tests/test_provider_codex_headers.py tests/test_provider_codex_images.py tests/test_provider_codex_model_metadata.py tests/test_provider_codex_responses.py tests/test_provider_model_metadata.py tests/test_provider_openai_images.py tests/test_provider_openai_responses.py tests/test_provider_proxy_targets.py tests/test_provider_route_specs.py tests/test_provider_vertex_runtime.py tests/test_proxy_request_scope.py tests/test_provider_proxy_routes.py tests/test_custom_base_passthrough_telemetry.py tests/test_proxy_passthrough.py tests/test_proxy_google_cloudcode_route_aliases.py tests/test_proxy_project_savings.py tests/test_vertex_claude_compression.py
All checks passed!
python -m compileall -q headroom\providers\cloudcode headroom\providers\codex headroom\providers\vertex headroom\providers\model_metadata.py headroom\providers\openai_images.py headroom\providers\openai_responses.py headroom\providers\proxy_targets.py headroom\providers\route_specs.py headroom\providers\proxy_routes.py headroom\proxy\handlers\openai.py headroom\proxy\passthrough.py headroom\proxy\request_scope.py headroom\proxy\project_context.py
# no output; exited 0
git commit -m "refactor(providers): split proxy route adapters"
Sync plugin versions.....................................................Passed
check for merge conflicts................................................Passed
ruff.....................................................................Passed
ruff-format..............................................................Passed
mypy.....................................................................Passed
```
## Real Behavior Proof
- Environment: Windows PowerShell, Python 3.13.13, branch
`jd/provider-route-slices` based on `headroomlabs/main`.
- Exact command / steps: Ran the focused provider/proxy pytest suite,
focused ruff command, compileall over changed Python modules, and commit
hooks.
- Observed result: Provider/proxy route behavior tests passed; lint,
formatting, and mypy passed.
- Not tested: Full pytest suite, live upstream provider calls, and
manual end-to-end proxy traffic.
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable
## Screenshots (if applicable)
N/A
## Additional Notes
Documentation and CHANGELOG updates are N/A for this internal refactor.
The full pytest suite was not run; coverage here is focused on
provider/proxy routing behavior touched by this slice.
---------
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-07-12 21:27:45 +00:00
|
|
|
)
|
|
|
|
|
assert GEMINI_HANDLER_ROUTES == (
|
|
|
|
|
ProviderHandlerRoute(
|
|
|
|
|
"POST",
|
|
|
|
|
"/v1beta/models/{model}:generateContent",
|
|
|
|
|
"handle_gemini_generate_content",
|
|
|
|
|
"model",
|
|
|
|
|
),
|
|
|
|
|
ProviderHandlerRoute(
|
|
|
|
|
"POST",
|
|
|
|
|
"/v1beta/models/{model}:streamGenerateContent",
|
|
|
|
|
"handle_gemini_stream_generate_content",
|
|
|
|
|
"model",
|
|
|
|
|
),
|
|
|
|
|
ProviderHandlerRoute(
|
|
|
|
|
"POST",
|
|
|
|
|
"/v1beta/models/{model}:countTokens",
|
|
|
|
|
"handle_gemini_count_tokens",
|
|
|
|
|
"model",
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
assert CLOUDCODE_HANDLER_ROUTES == (
|
|
|
|
|
ProviderHandlerRoute(
|
|
|
|
|
"POST",
|
|
|
|
|
"/v1internal:streamGenerateContent",
|
|
|
|
|
"handle_google_cloudcode_stream",
|
|
|
|
|
),
|
|
|
|
|
ProviderHandlerRoute(
|
|
|
|
|
"POST",
|
|
|
|
|
"/v1/v1internal:streamGenerateContent",
|
|
|
|
|
"handle_google_cloudcode_stream",
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_batch_handler_routes_model_endpoint_intent() -> None:
|
|
|
|
|
assert ANTHROPIC_BATCH_ROUTES[0] == ProviderHandlerRoute(
|
|
|
|
|
"POST",
|
|
|
|
|
"/v1/messages/batches",
|
|
|
|
|
"handle_anthropic_batch_create",
|
|
|
|
|
)
|
|
|
|
|
assert (
|
|
|
|
|
ProviderHandlerRoute(
|
|
|
|
|
"GET",
|
|
|
|
|
"/v1/messages/batches/{batch_id}/results",
|
|
|
|
|
"handle_anthropic_batch_results",
|
|
|
|
|
"batch_id",
|
|
|
|
|
)
|
|
|
|
|
in ANTHROPIC_BATCH_ROUTES
|
|
|
|
|
)
|
|
|
|
|
assert OPENAI_BATCH_ROUTES == (
|
|
|
|
|
ProviderHandlerRoute("POST", "/v1/batches", "handle_batch_create"),
|
|
|
|
|
ProviderHandlerRoute("GET", "/v1/batches", "handle_batch_list"),
|
|
|
|
|
ProviderHandlerRoute("GET", "/v1/batches/{batch_id}", "handle_batch_get", "batch_id"),
|
|
|
|
|
ProviderHandlerRoute(
|
|
|
|
|
"POST",
|
|
|
|
|
"/v1/batches/{batch_id}/cancel",
|
|
|
|
|
"handle_batch_cancel",
|
|
|
|
|
"batch_id",
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
assert (
|
|
|
|
|
ProviderHandlerRoute(
|
|
|
|
|
"POST",
|
|
|
|
|
"/v1beta/models/{model}:batchGenerateContent",
|
|
|
|
|
"handle_google_batch_create",
|
|
|
|
|
"model",
|
|
|
|
|
)
|
|
|
|
|
in GEMINI_BATCH_ROUTES
|
|
|
|
|
)
|
|
|
|
|
assert (
|
|
|
|
|
ProviderHandlerRoute(
|
|
|
|
|
"DELETE",
|
|
|
|
|
"/v1beta/batches/{batch_name}",
|
|
|
|
|
"handle_google_batch_passthrough",
|
|
|
|
|
"batch_name",
|
|
|
|
|
)
|
|
|
|
|
in GEMINI_BATCH_ROUTES
|
|
|
|
|
)
|