Skip to content

Remove the direct router and API-key-selection import cycle #3894

Closed

Description

@parkjs101

Area

Proxy and routing

What are you trying to accomplish?

Maintain provider routing and API-key failover without coupling the router's selection-snapshot helper to the module that performs persisted selection changes and route resolution. The scope is a small dependency cleanup with unchanged routing and key-selection behavior.

What prevents this today?

At dev commit 76436a3ee221d577cc94d6c10544c03bb1f9d9db, the following direct runtime cycle exists:

src/router.ts
  -> src/providers/api-key-selection.ts
  -> src/router.ts

The helper only derives entryId, reference, and revision from its provider argument. It does not need route resolution or persisted mutation. Keeping it in the larger module creates an unnecessary direct cycle.

This is a maintainability issue. No current startup crash or guaranteed module-order failure is claimed.

What should OpenCodex do?

Remove this specific direct cycle while preserving current routing, selection snapshots, and failover behavior:

  • Put the snapshot helper in a small module whose dependencies are type-only, or use an equivalent existing dependency-neutral location.
  • Have the router and API-key-selection module consume that helper.
  • Preserve the existing helper export if it is moved, so existing imports remain compatible.
  • Keep routedProviderConfig imports in the selection and failover modules within this scope.
  • Add focused coverage for the helper's behavior and its runtime dependency boundary, including that the router no longer imports the stateful API-key-selection module directly.

This issue does not require making the entire router dependency graph acyclic or banning every provider-to-router dependency.

Example usage or interface

Before:

// src/router.ts
import { captureProviderApiKeySelection } from "./providers/api-key-selection";

Possible after:

// src/router.ts
import { captureProviderApiKeySelection } from "./providers/api-key-selection-capture";

// src/providers/api-key-selection.ts
import { captureProviderApiKeySelection } from "./api-key-selection-capture";
export { captureProviderApiKeySelection } from "./api-key-selection-capture";
import { routedProviderConfig } from "../router";

For a provider with a selected pool key and a selection revision, the captured entry ID, key reference, and revision must remain identical. Missing pool entries and optional fields must retain their existing behavior.

Alternatives or workarounds

Extracting routedProviderConfig itself would move a broader set of routing dependencies and is not necessary to remove this direct edge.

A global router cycle test would exceed this issue's scope. An in-memory graph check applying only the proposed router import change still found this other static cycle:

router -> routing/evaluator -> routing/quota -> providers/quota
       -> codex/auth-api -> lib/state-store-registrations -> router

The return edge is state-store-registrations.ts:42. That broader dependency work should be evaluated separately.

Additional context

The current direct cycle was verified from source and a static runtime-import graph using Bun's TypeScript transpiler. The hypothetical extraction was evaluated in memory; no source change is included.

On this checkout, all 17 tests in tests/lab/core-lab-boundary.test.ts passed. The focused key-failover and provider-key-store test files could not load because zod/v4 was missing, so their success is not claimed. They should be run alongside the new focused coverage when implementing this change:

bun test tests/adapters/key-failover.test.ts tests/providers/provider-key-store.test.ts tests/lab/core-lab-boundary.test.ts

Checks

  • I searched existing issues and documentation.
  • This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
  • I removed secrets and personal data.

Activity

  1. added
    enhancementNew feature or request
    proxyHTTP proxy, routing, reverse-proxy / management auth
    on Sep 7, 2026
  2. lidge-jun commented on Sep 7, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 38 / 80

    이 이슈는 런타임 동작 버그가 아니라 직접 순환 import 정리입니다. 지금 dev에서 src/router.ts가 captureProviderApiKeySelection을 src/providers/api-key-selection.ts에서 가져오고, 그 파일이 다시 routedProviderConfig를 ../router에서 가져옵니다. 스냅샷 헬퍼는 entryId/reference/revision만 뽑아서 route resolve나 persist가 필요 없습니다. 큰 모듈에 두면 순환이 생깁니다.

    제안은 타입 위주 작은 모듈(예: api-key-selection-capture.ts)로 헬퍼를 옮기고, router와 api-key-selection이 그걸 쓰며, 기존 export는 re-export로 호환을 유지하는 것입니다. routedProviderConfig를 뽑아내는 큰 리팩터는 범위 밖이라고 잘 그었습니다. 다른 정적 순환(router → … → state-store-registrations → router)도 따로 적혀 있어 이 이슈가 전체를 고치겠다고 과대광고하지 않습니다.

    src/router.ts:13 - api-key-selection 대신 capture 전용 모듈을 import하면 이 직접 간선이 끊깁니다.

    src/providers/api-key-selection.ts:6 - routedProviderConfig import는 selection/failover 본래 일에 필요하면 남겨도 됩니다. 헬퍼만 분리하면 됩니다.

    메인테이너의 판단이 필요한 지점

    • 지금 주기적으로 손볼 우선순위인지, 다음 router 정리 묶음에 태울지
    • 더 큰 state-store 순환까지 같은 마일스톤에 넣을지(이 이슈 범위 밖 권고)

    너의 추천
    유지보수 이슈로 열고, 제안한 작은 추출 + 헬퍼/의존 경계 테스트 PR을 받으면 됩니다. 동작 변경이 없어야 하고, 전체를 acyclic으로 만들 필요는 없습니다.

    이 댓글은 grok-bot이 작성했습니다

  3. added a commit that references this issue on Sep 8, 2026
    164bd2b
  4. lidge-jun commented on Sep 8, 2026

    @lidge-jun
    Owner

    Fixed on dev in 164bd2b (via #4053, carrying #3897 by @parkjs101).

    The API-key selection capture moved into its own module, src/providers/api-key-selection-capture.ts, and api-key-selection.ts re-exports it, so src/router.ts now reaches the helper without importing the stateful module that imported the router back. Existing importers are unaffected — the re-export resolves to the same function object, which the shipped test asserts directly.

    The extraction is byte-identical: same first-match order over apiKeyPool, same captured shape, same undefined handling. An independent review confirmed the cycle is gone with a runtime-import scan that distinguishes erased type imports from real dependencies, and 442 tests across fifteen downstream consumers pass.

    Closing manually because pull requests here target dev, and GitHub only auto-closes on merge into main.

  5. lidge-jun commented on Sep 8, 2026

    @lidge-jun
    Owner

    Landed via #4053 at 164bd2b

  6. added a commit that references this issue on Sep 17, 2026
    6be108b
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

    enhancementNew feature or requestlanded-via-maintainerOriginal PR closed after landing via a maintainer merge trainproxyHTTP proxy, routing, reverse-proxy / management auth

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions