Skip to content

Refactor: extract route-plan and SSE helper clusters from codex_proxy.py monolith #413

Closed

Description

@NOirBRight

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

  1. 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 of tests/ imports) and asserts they remain available. This test is the regression tripwire for every later commit.

Phase 1 — Extract shared primitives (seam prep)

  1. Move the exception classes into a new errors module; re-export them from codex_proxy. Run the import-surface smoke test plus tests/test_routing.py -k error.
  2. 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 and tests/test_routing.py -k route.
  3. 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 and tests/test_routing.py -k settings.

Phase 2 — Extract route-plan construction

  1. 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.
  2. Move the small route-plan helper functions (model/effort validation, capability binding, vision plan derivation) bottom-up into the route-plan module.
  3. Move the top-level route-plan builder function last. Re-export everything.
  4. Run the full tests/test_routing.py plus tests/test_chat_completions_gateway.py; fix only import fallout, never logic.

Phase 3 — Extract SSE helper layer

  1. Move the pure SSE parsing primitives (payload byte extraction, event field parsing) into a new gateway-SSE module (or extend the existing sse_events module where the shapes already match). Re-export from codex_proxy.
  2. Move SSE validation and terminal-detection helpers (verified-payload validation, converted-payload helpers, semantic error type) into the new module.
  3. Move the reasoning-event handling and response-to-chat conversion wrappers that operate on SSE payloads into the new module.
  4. Run tests/test_sse_events.py, tests/test_routing.py, and the streaming-related gateway tests.

Phase 4 — Candidate verification

  1. 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 run python tests/fixtures/real_client_e2e/run-with-windows-watchdog.py --timeout-seconds 3600 -- python -m pytest -q tests/test_real_client_e2e.py. Run python 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.py convention 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 from codex_proxy. Dependencies that currently flow from codex_proxy globals must either move down into primitives/settings or be passed as parameters.
  • Import-surface stability: codex_proxy re-exports every moved name it previously defined, so from codex_proxy import X keeps 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 through codex_proxy's public surface; tests/test_sse_events.py shows 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, mirroring test_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 CodexProxyHandler request-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_call and
    _downgrade_invalid_third_party_tool_calls together 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 into codex_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_failure move 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-level upstream_plumbing module 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

  1. added
    campaign:gateway-chatGateway deep modules and Codex↔Chat bridge
    and removed
    needs-triageMaintainer needs to evaluate this issue
    on Aug 19, 2026
  2. NOirBRight commented on Aug 19, 2026

    @NOirBRight
    OwnerAuthor

    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:

  3. NOirBRight commented on Aug 19, 2026

    @NOirBRight
    OwnerAuthor

    #414 is closed. Phase 0–1 of this issue landed on e98c23d:

    • tests/test_codex_proxy_import_surface.py freezes 248 test-suite names and asserts re-export object identity.
    • src-python/route_primitives.py owns routing enums, immutable auth wrappers, and retry-class constants.
    • src-python/gateway_errors.py owns the independent Gateway exception types.
    • codex_proxy.py re-exports every moved name. New modules do not import codex_proxy.

    Not in this commit: gateway_settings.py (still coupled to RUNTIME_PROXY_DIR / retry-after helpers), RoutePlan, SSE.

    Next: extract route_plan.py behind the frozen import surface.

  4. NOirBRight commented on Aug 19, 2026

    @NOirBRight
    OwnerAuthor

    Phase 2–3 landed on 065baad (after primitives/errors in e98c23d).

    Extracted:

    • gateway_settings.py — timeout/retry/image-proxy/subagent assist accessors
    • route_plan.py — immutable RoutePlan cluster + route_plan_for_request
    • gateway_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.

  5. NOirBRight commented on Aug 19, 2026

    @NOirBRight
    OwnerAuthor

    Extraction complete on 065baad (e98c23d + 4763f29 for the #414 prerequisite).

    Shipped modules: route_primitives, gateway_errors, gateway_settings, route_plan, gateway_sse. codex_proxy.py is the facade (now ~22.3k lines). Import-surface freeze + object-identity tests are in tests/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_KEY 400 on test_provider_scoped_v2_handler_adapts_namespace_before_upstream. Python core: 2385 passed; remaining failures are host gaps (powershell launcher/release tests), not this refactor.

    Deferred by design (campaign amendment): Chat codec stays out of gateway_sse; no upstream_plumbing.py; tool-call normalization is a follow-up; handler/compatibility/transport/Vision remain in the composition root.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    campaign:gateway-chatGateway deep modules and Codex↔Chat bridgeready-for-agentFully specified, ready for an AFK agent

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions