Repository navigation
Refactor: extract route-plan and SSE helper clusters from codex_proxy.py monolith #413
Description
Problem Statement
codex_proxy.py has grown into a ~24,000-line (~990 KB) orchestration monolith. Reusable domains (protocol translation, tool compatibility, subagent state, catalog, auth, telemetry) have already been extracted into sibling modules, but five large cohesive clusters still live inline: the main HTTP request handler (~5.4k lines), subagent/multi-agent decision glue (~5.3k lines), route-plan construction (~2.1k lines), SSE parsing/conversion helpers (~1.9k lines), and request/response body compatibility adapters (~1.8k lines). The file's size makes review, navigation, and parallel work painful, and ADR-0001 already prohibits adding more protocol adaptation logic to it — yet there is no incremental path to shrink it.
Solution
Pure code-motion refactor: extract the two least-coupled clusters — route-plan construction and the SSE helper layer — into dedicated modules, preceded by extracting the shared primitives they depend on (enums/config primitives, runtime settings accessors, exceptions) so the new modules do not import back into codex_proxy (no circular imports). Every moved name stays importable from codex_proxy via re-export so the existing test-suite import surface and any external consumers keep working. No behavior changes, no renames, no logic edits inside moved functions.
The main handler (CodexProxyHandler) and the subagent decision glue are deliberately not part of this issue; they are the most coupled clusters and deserve their own follow-up issues once the seams created here exist.
Commits
Each commit leaves the codebase in a working state: run the targeted tests listed after each phase before committing.
Phase 0 — Lock the contract
- Add an import-surface smoke test that imports every public name the test suite currently imports from
codex_proxy(enumerate with a one-time scan oftests/imports) and asserts they remain available. This test is the regression tripwire for every later commit.
Phase 1 — Extract shared primitives (seam prep)
- Move the exception classes into a new errors module; re-export them from
codex_proxy. Run the import-surface smoke test plustests/test_routing.py -k error. - Move the configuration enums and primitives (capability/route-protocol/authentication enums, sensitive-value and frozen-header wrappers, routing strategy constants) into a new primitives module; re-export from
codex_proxy. Run the smoke test andtests/test_routing.py -k route. - Move the runtime settings / feature-flag accessor functions (upstream timeout, retry toggle, image-proxy toggle, subagent assist mode, and siblings) into a new settings module; re-export from
codex_proxy. Run the smoke test andtests/test_routing.py -k settings.
Phase 2 — Extract route-plan construction
- Move the route-plan data types (tool-exposure policy, vision plan, runtime facts, retry execution plan, attempt plan, route plan) into a new route-plan module, importing primitives/settings from the modules created in Phase 1. Re-export from
codex_proxy. - Move the small route-plan helper functions (model/effort validation, capability binding, vision plan derivation) bottom-up into the route-plan module.
- Move the top-level route-plan builder function last. Re-export everything.
- Run the full
tests/test_routing.pyplustests/test_chat_completions_gateway.py; fix only import fallout, never logic.
Phase 3 — Extract SSE helper layer
- Move the pure SSE parsing primitives (payload byte extraction, event field parsing) into a new gateway-SSE module (or extend the existing
sse_eventsmodule where the shapes already match). Re-export fromcodex_proxy. - Move SSE validation and terminal-detection helpers (verified-payload validation, converted-payload helpers, semantic error type) into the new module.
- Move the reasoning-event handling and response-to-chat conversion wrappers that operate on SSE payloads into the new module.
- Run
tests/test_sse_events.py,tests/test_routing.py, and the streaming-related gateway tests.
Phase 4 — Candidate verification
- Final commit: remove any now-unneeded indirection left in
codex_proxy(dead imports only — no renames). Run the full local Python core suite:python -m pytest -q --ignore=tests/test_real_client_e2e.py. If the moved code touched the real-client E2E contract surface, also runpython tests/fixtures/real_client_e2e/run-with-windows-watchdog.py --timeout-seconds 3600 -- python -m pytest -q tests/test_real_client_e2e.py. Runpython scripts/report_quality_gates.py(report-only).
If any commit cannot be made green by import fixes alone, stop and reassess — that indicates a hidden behavior coupling, not something to patch over.
Decision Document
- New modules: one errors module, one primitives module (enums/config), one settings module (runtime accessors), one route-plan module, one gateway-SSE module. Exact file names are left to the implementer, following the existing flat
snake_case.pyconvention in the Python source directory. - No circular imports: extracted modules may import from the primitives/settings/errors modules and from already-extracted siblings (e.g.
sse_events,protocol_translation), but never fromcodex_proxy. Dependencies that currently flow fromcodex_proxyglobals must either move down into primitives/settings or be passed as parameters. - Import-surface stability:
codex_proxyre-exports every moved name it previously defined, sofrom codex_proxy import Xkeeps working across the whole refactor. Tests are not rewritten as part of this issue. - Behavior preservation: no logic edits, no renames, no signature changes inside moved code. Diff review should be able to verify each commit as pure cut-and-paste plus import lines.
- Verification class:
strict(routing/protocol boundary) — targeted checks per commit plus one full Python core suite run at the candidate commit, per the repo verification policy. - Sequencing: route-plan extraction before SSE-layer extraction, because the SSE helpers interact with the handler's stream lifecycle and benefit from the primitives seam already being in place.
Testing Decisions
- This refactor adds no new behavior, so it adds almost no new tests: the existing suite is the safety net.
- The one new test is the Phase 0 import-surface smoke test, which tests external behavior only (public names remain importable), not implementation details.
- Prior art:
tests/test_routing.py(~29k lines) already exercises the route-plan and SSE code paths throughcodex_proxy's public surface;tests/test_sse_events.pyshows the expected style for tests of an extracted SSE module. Follow-up issues that extract further clusters should add per-module unit tests at that point, mirroringtest_sse_events.py. - A good test here = one that fails if a name disappears from the import surface or if routing/streaming behavior changes; tests must not assert which module a name is defined in.
Out of Scope
- Splitting the
CodexProxyHandlerrequest-handler cluster. - Splitting the subagent/multi-agent decision-glue cluster.
- Splitting the request/response body compatibility and Apply Patch adapter cluster.
- Any behavior change, bug fix, performance work, dead-code removal, renaming, or type-annotation cleanup.
- Splitting other large sibling modules (
protocol_translation.py,runtime_tool_compatibility.py,catalog_sync.py). - Rewriting tests to import from the new modules (may happen opportunistically later; not required).
Further Notes
- Follow-up issues (handler split, subagent glue split) should be filed after this lands; the primitives/settings seam created here is the prerequisite.
- ADR-0001 (docs/adr/0001-claude-messages-intermediate-representation.md) and the 2026-08-04 tool-bridge handoff review both anticipate this direction.
Planning Addendum (2026-08-14)
Added after an architecture review plus AST dependency-closure analysis on the
pre-4.1 tree (23,992 lines; the five clusters and sizes above were verified).
Execution is deferred until after the v0.1.8-beta.4.2 release — see
"Execution trigger" below.
Amended phase plan (supersedes the original sequencing)
One arc of independently landable phases:
- Phase 0 — import-surface smoke test (unchanged).
- Phase 1 — extract shared primitives into three new modules (scope
unchanged; names fixed below). - Phase 2 — extract the route-plan cluster, including housing for its 9
non-primitive helper dependencies (see "Dependency housing"). - Phase 2.5 (new) — extract a tool-call normalization module:
_normalize_third_party_tool_calland
_downgrade_invalid_third_party_tool_callstogether with their transitive
glue closure (~18 helper functions) and related constants (TOOL_NAME_RE,
MULTI_AGENT_*,THIRD_PARTY_TOOL_NAME_ALIASES, ...). This lifts one
self-contained normalization subtree out of the glue region; it does not
otherwise restructure the subagent glue cluster. Must land before Phase 3
so the SSE module never imports back intocodex_proxy. - Phase 3 — extract the SSE helper layer (scope unchanged; mechanical
once Phase 2.5 exists). - Phase 4 — candidate verification (unchanged).
Abort rule
Each phase is an independent commit and lands green on its own. If any phase
cannot be made green by import fixes alone, abandon that phase's branch only;
already-landed phases stay, and the remaining phases convert into a follow-up
issue. Worst case degrades to "primitives + route-plan only" with zero loss.
Dependency housing
Closure analysis of the 16 cross-cluster dependencies (pre-4.1 line numbers):
- 14 are CLEAN (closures touch only stdlib, already-extracted siblings, or
the Phase-1 primitives) — pure cut-and-paste. _normalize_third_party_tool_call(106 lines) and
_downgrade_invalid_third_party_tool_calls(29 lines) drag ~18 glue
helpers; no closure reaches handler state or mutable globals, so the whole
set moves together in Phase 2.5 (still pure cut-and-paste).- Route-plan's 9 non-primitive helpers get two homes:
_identity_failure/_catalog_failuremove to the errors module (they
are exception factories);_get_header,_header_items,_responses_url,
_chat_completions_url,_upstream_retry_attempts,
_retry_after_delay_seconds,_responses_request_to_chat_completion_body
move to a new low-levelupstream_plumbingmodule with their small
closures (endpoint-suffix helpers, retry helpers, constants).
New module names (fixed)
| Module | Contents |
|---|---|
gateway_errors.py |
exception classes + the two failure constructors |
route_primitives.py |
config enums, frozen wrappers, routing-strategy constants |
gateway_settings.py |
runtime settings / feature-flag accessors |
upstream_plumbing.py |
header/URL/retry helpers shared by route-plan and others |
route_plan.py |
route-plan data types + builders |
tool_call_normalization.py |
third-party tool-call normalization + invalid-call downgrade |
gateway_sse.py |
SSE parsing/validation/conversion helpers |
Dependency direction: codex_proxy → (route_plan, gateway_sse,
tool_call_normalization) → (upstream_plumbing, route_primitives,
gateway_settings, gateway_errors) → stdlib/siblings. No module imports
codex_proxy.
Mutable globals stay put
OFFICIAL_HTTP_POOLS, GATEWAY_EVENT_WRITER, GATEWAY_SHUTDOWN_CONTROLLER,
_GATEWAY_REQUEST_ADMISSION, the usage queues, and PROXY_EVENT_LOG_PATH
remain in codex_proxy; no extracted module copies them.
Execution trigger
After v0.1.8-beta.4.2 ships: re-run the dependency-closure scan on the
release base (line numbers above are pre-4.1 and will have drifted), confirm
the CLEAN/DRAGS lists still hold, refresh line ranges, then move the issue to
ready-for-agent and start at Phase 0.
Activity
NOirBRight commentedon Aug 19, 2026
Campaign amendment from #441.
The 0.1.8-beta.4.2 execution trigger already shipped. Do not wait on that tag.
Amendments that supersede the 2026-08-14 plumbing/SSE housing notes:
upstream_plumbing.py.gateway_sse.pyowns framing, payload validation, commit/terminal helpers only. Chat codec wrappers such as_responses_request_to_chat_completion_bodystay on theprotocol_translationside (existing thin wrapper incodex_proxyis fine until Deepen protocol_translation behind prepare_exchange for the standard Chat floor #442).main(codex_proxy.pyis now 25,119 lines) after Cleanup: remove unused Level-3 dynamic-DAG subagent machinery from production path #414 lands, then move this issue toready-for-agent.NOirBRight commentedon Aug 19, 2026
#414 is closed. Phase 0–1 of this issue landed on
e98c23d:tests/test_codex_proxy_import_surface.pyfreezes 248 test-suite names and asserts re-export object identity.src-python/route_primitives.pyowns routing enums, immutable auth wrappers, and retry-class constants.src-python/gateway_errors.pyowns the independent Gateway exception types.codex_proxy.pyre-exports every moved name. New modules do not importcodex_proxy.Not in this commit:
gateway_settings.py(still coupled toRUNTIME_PROXY_DIR/ retry-after helpers), RoutePlan, SSE.Next: extract
route_plan.pybehind the frozen import surface.NOirBRight commentedon Aug 19, 2026
Phase 2–3 landed on
065baad(after primitives/errors ine98c23d).Extracted:
gateway_settings.py— timeout/retry/image-proxy/subagent assist accessorsroute_plan.py— immutable RoutePlan cluster +route_plan_for_requestgateway_sse.py— SSE line/payload parsing only (no Chat codec)Facade identity is covered by
tests/test_codex_proxy_import_surface.py. Settings and RoutePlan late-bind a few remaining proxy helpers (_responses_url, retry-after,RUNTIME_PROXY_DIR) so existing patches still hit the live objects.Not moved: handler, compatibility cluster, Vision Proxy, transport/relay.
NOirBRight commentedon Aug 19, 2026
Extraction complete on
065baad(e98c23d+4763f29for the #414 prerequisite).Shipped modules:
route_primitives,gateway_errors,gateway_settings,route_plan,gateway_sse.codex_proxy.pyis the facade (now ~22.3k lines). Import-surface freeze + object-identity tests are intests/test_codex_proxy_import_surface.py.Verification on this Linux host: Gateway-targeted routing/chat/identity/shutdown/SSE green except the pre-existing
OLLAMA_API_KEY400 ontest_provider_scoped_v2_handler_adapts_namespace_before_upstream. Python core: 2385 passed; remaining failures are host gaps (powershelllauncher/release tests), not this refactor.Deferred by design (campaign amendment): Chat codec stays out of
gateway_sse; noupstream_plumbing.py; tool-call normalization is a follow-up; handler/compatibility/transport/Vision remain in the composition root.refactor(gateway): extract route primitives and errors from codex_pro…
refactor(gateway): extract settings, RoutePlan, and SSE framing (#413)