From a011e81beda27a43c076bc8e4bafc00ea64c9a08 Mon Sep 17 00:00:00 2001 From: ndycode <405533+ndycode@users.noreply.github.com> Date: Sun, 22 Mar 2026 09:58:14 +0800 Subject: [PATCH 1/3] refactor: add package subpath exports --- lib/auth/index.ts | 1 + lib/codex-cli/index.ts | 4 + lib/request/index.ts | 6 ++ package.json | 33 ++++++++ test/public-api-contract.test.ts | 137 +++++++++++++++++++++++++------ 5 files changed, 154 insertions(+), 27 deletions(-) create mode 100644 lib/auth/index.ts create mode 100644 lib/codex-cli/index.ts create mode 100644 lib/request/index.ts diff --git a/lib/auth/index.ts b/lib/auth/index.ts new file mode 100644 index 00000000..d12b110c --- /dev/null +++ b/lib/auth/index.ts @@ -0,0 +1 @@ +export * from "./auth.js"; diff --git a/lib/codex-cli/index.ts b/lib/codex-cli/index.ts new file mode 100644 index 00000000..a9850ef1 --- /dev/null +++ b/lib/codex-cli/index.ts @@ -0,0 +1,4 @@ +export * from "./observability.js"; +export * from "./state.js"; +export * from "./sync.js"; +export * from "./writer.js"; diff --git a/lib/request/index.ts b/lib/request/index.ts new file mode 100644 index 00000000..076c0643 --- /dev/null +++ b/lib/request/index.ts @@ -0,0 +1,6 @@ +export * from "./failure-policy.js"; +export * from "./fetch-helpers.js"; +export * from "./rate-limit-backoff.js"; +export * from "./request-transformer.js"; +export * from "./response-handler.js"; +export * from "./stream-failover.js"; diff --git a/package.json b/package.json index e4bae7b8..bff68ee8 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,39 @@ "description": "Multi-account OAuth manager and codex auth wrapper for the official @openai/codex CLI, with switching, health checks, and recovery tools", "main": "./dist/index.js", "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + }, + "./auth": { + "types": "./dist/lib/auth/index.d.ts", + "import": "./dist/lib/auth/index.js", + "default": "./dist/lib/auth/index.js" + }, + "./storage": { + "types": "./dist/lib/storage.d.ts", + "import": "./dist/lib/storage.js", + "default": "./dist/lib/storage.js" + }, + "./config": { + "types": "./dist/lib/config.d.ts", + "import": "./dist/lib/config.js", + "default": "./dist/lib/config.js" + }, + "./request": { + "types": "./dist/lib/request/index.d.ts", + "import": "./dist/lib/request/index.js", + "default": "./dist/lib/request/index.js" + }, + "./cli": { + "types": "./dist/lib/codex-cli/index.d.ts", + "import": "./dist/lib/codex-cli/index.js", + "default": "./dist/lib/codex-cli/index.js" + }, + "./package.json": "./package.json" + }, "type": "module", "license": "MIT", "author": "ndycode", diff --git a/test/public-api-contract.test.ts b/test/public-api-contract.test.ts index 307093f3..f81e60ae 100644 --- a/test/public-api-contract.test.ts +++ b/test/public-api-contract.test.ts @@ -1,10 +1,4 @@ import { describe, expect, it, vi } from "vitest"; -import { - HealthScoreTracker, - TokenBucketTracker, - exponentialBackoff, - selectHybridAccount, -} from "../lib/rotation.js"; import { getTopCandidates } from "../lib/parallel-probe.js"; import { createCodexHeaders } from "../lib/request/fetch-helpers.js"; import { @@ -12,7 +6,14 @@ import { getRateLimitBackoffWithReason, } from "../lib/request/rate-limit-backoff.js"; import { transformRequestBody } from "../lib/request/request-transformer.js"; +import { + exponentialBackoff, + HealthScoreTracker, + selectHybridAccount, + TokenBucketTracker, +} from "../lib/rotation.js"; import type { RequestBody } from "../lib/types.js"; +import pkg from "../package.json" with { type: "json" }; describe("public api contract", () => { it("keeps root plugin exports aligned", async () => { @@ -26,37 +27,106 @@ describe("public api contract", () => { const rotation = await import("../lib/rotation.js"); const parallelProbe = await import("../lib/parallel-probe.js"); const fetchHelpers = await import("../lib/request/fetch-helpers.js"); - const rateLimitBackoff = await import("../lib/request/rate-limit-backoff.js"); - const requestTransformer = await import("../lib/request/request-transformer.js"); - const required: ReadonlyArray< - readonly [string, Record] - > = [ - ["selectHybridAccount", rotation], - ["exponentialBackoff", rotation], - ["getTopCandidates", parallelProbe], - ["createCodexHeaders", fetchHelpers], - ["getRateLimitBackoffWithReason", rateLimitBackoff], - ["transformRequestBody", requestTransformer], - ]; + const rateLimitBackoff = await import( + "../lib/request/rate-limit-backoff.js" + ); + const requestTransformer = await import( + "../lib/request/request-transformer.js" + ); + const required: ReadonlyArray]> = + [ + ["selectHybridAccount", rotation], + ["exponentialBackoff", rotation], + ["getTopCandidates", parallelProbe], + ["createCodexHeaders", fetchHelpers], + ["getRateLimitBackoffWithReason", rateLimitBackoff], + ["transformRequestBody", requestTransformer], + ]; for (const [name, mod] of required) { expect(name in mod, `missing export: ${name}`).toBe(true); expect(typeof mod[name], `${name} should be a function`).toBe("function"); } }); + it("declares the supported package subpath exports", async () => { + expect(pkg.exports).toEqual({ + ".": { + types: "./dist/index.d.ts", + import: "./dist/index.js", + default: "./dist/index.js", + }, + "./auth": { + types: "./dist/lib/auth/index.d.ts", + import: "./dist/lib/auth/index.js", + default: "./dist/lib/auth/index.js", + }, + "./storage": { + types: "./dist/lib/storage.d.ts", + import: "./dist/lib/storage.js", + default: "./dist/lib/storage.js", + }, + "./config": { + types: "./dist/lib/config.d.ts", + import: "./dist/lib/config.js", + default: "./dist/lib/config.js", + }, + "./request": { + types: "./dist/lib/request/index.d.ts", + import: "./dist/lib/request/index.js", + default: "./dist/lib/request/index.js", + }, + "./cli": { + types: "./dist/lib/codex-cli/index.d.ts", + import: "./dist/lib/codex-cli/index.js", + default: "./dist/lib/codex-cli/index.js", + }, + "./package.json": "./package.json", + }); + }); + + it("keeps the supported subpath entry barrels aligned", async () => { + const auth = await import("../lib/auth/index.js"); + const storage = await import("../lib/storage.js"); + const config = await import("../lib/config.js"); + const request = await import("../lib/request/index.js"); + const cli = await import("../lib/codex-cli/index.js"); + + expect(typeof auth.exchangeAuthorizationCode).toBe("function"); + expect(typeof storage.loadAccounts).toBe("function"); + expect(typeof config.loadPluginConfig).toBe("function"); + expect(typeof request.createCodexHeaders).toBe("function"); + expect(typeof request.transformRequestBody).toBe("function"); + expect(typeof cli.loadCodexCliState).toBe("function"); + }); + it("keeps positional and options-object overload behavior aligned", async () => { const healthTracker = new HealthScoreTracker(); const tokenTracker = new TokenBucketTracker(); - const accounts = [{ index: 0, isAvailable: true, lastUsed: 1_709_280_000_000 }]; + const accounts = [ + { index: 0, isAvailable: true, lastUsed: 1_709_280_000_000 }, + ]; - const selectedPositional = selectHybridAccount(accounts, healthTracker, tokenTracker); - const selectedNamed = selectHybridAccount({ accounts, healthTracker, tokenTracker }); + const selectedPositional = selectHybridAccount( + accounts, + healthTracker, + tokenTracker, + ); + const selectedNamed = selectHybridAccount({ + accounts, + healthTracker, + tokenTracker, + }); expect(selectedNamed?.index).toBe(selectedPositional?.index); const randomSpy = vi.spyOn(Math, "random").mockReturnValue(0.5); try { const backoffPositional = exponentialBackoff(3, 1000, 60000, 0); - const backoffNamed = exponentialBackoff({ attempt: 3, baseMs: 1000, maxMs: 60000, jitterFactor: 0 }); + const backoffNamed = exponentialBackoff({ + attempt: 3, + baseMs: 1000, + maxMs: 60000, + jitterFactor: 0, + }); expect(backoffNamed).toBe(backoffPositional); } finally { randomSpy.mockRestore(); @@ -82,7 +152,9 @@ describe("public api contract", () => { 1, ); const topNamed = getTopCandidates({ - accountManager: manager as unknown as Parameters[0], + accountManager: manager as unknown as Parameters< + typeof getTopCandidates + >[0], modelFamily: "codex", model: null, maxCandidates: 1, @@ -99,11 +171,22 @@ describe("public api contract", () => { accessToken: "token", opts: { model: "gpt-5", promptCacheKey: "session-compat" }, }); - expect(headersNamed.get("Authorization")).toBe(headersPositional.get("Authorization")); - expect(headersNamed.get("conversation_id")).toBe(headersPositional.get("conversation_id")); - expect(headersNamed.get("session_id")).toBe(headersPositional.get("session_id")); + expect(headersNamed.get("Authorization")).toBe( + headersPositional.get("Authorization"), + ); + expect(headersNamed.get("conversation_id")).toBe( + headersPositional.get("conversation_id"), + ); + expect(headersNamed.get("session_id")).toBe( + headersPositional.get("session_id"), + ); - const ratePositional = getRateLimitBackoffWithReason(1, "compat", 1000, "tokens"); + const ratePositional = getRateLimitBackoffWithReason( + 1, + "compat", + 1000, + "tokens", + ); clearRateLimitBackoffState(); const rateNamed = getRateLimitBackoffWithReason({ accountIndex: 1, From 9095badc187fb69d91dc9222d249e11842a550e9 Mon Sep 17 00:00:00 2001 From: ndycode <405533+ndycode@users.noreply.github.com> Date: Sun, 22 Mar 2026 13:42:01 +0800 Subject: [PATCH 2/3] docs: document supported package subpaths --- docs/reference/public-api.md | 6 ++++++ test/documentation.test.ts | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/docs/reference/public-api.md b/docs/reference/public-api.md index 865189ff..989b0104 100644 --- a/docs/reference/public-api.md +++ b/docs/reference/public-api.md @@ -16,6 +16,12 @@ Stable APIs are covered by semver compatibility guarantees and must remain backw - `OpenAIOAuthPlugin` - `OpenAIAuthPlugin` - default export (alias of `OpenAIOAuthPlugin`) +- Supported package subpath entrypoints: + - `codex-multi-auth/auth` + - `codex-multi-auth/storage` + - `codex-multi-auth/config` + - `codex-multi-auth/request` + - `codex-multi-auth/cli` - CLI surface: - `codex auth ...` command family - documented flags and aliases in `reference/commands.md` diff --git a/test/documentation.test.ts b/test/documentation.test.ts index 1c696d36..7241c40c 100644 --- a/test/documentation.test.ts +++ b/test/documentation.test.ts @@ -224,6 +224,11 @@ describe("Documentation Integrity", () => { expect(publicApi).toContain("tier c"); expect(publicApi).toContain("options-object"); expect(publicApi).toContain("semver"); + expect(publicApi).toContain("codex-multi-auth/auth"); + expect(publicApi).toContain("codex-multi-auth/storage"); + expect(publicApi).toContain("codex-multi-auth/config"); + expect(publicApi).toContain("codex-multi-auth/request"); + expect(publicApi).toContain("codex-multi-auth/cli"); expect(errorContracts).toContain("exit codes"); expect(errorContracts).toContain("json mode contract"); From f335beb98e0a239b86283146e53439a228d1a485 Mon Sep 17 00:00:00 2001 From: ndycode <405533+ndycode@users.noreply.github.com> Date: Sun, 22 Mar 2026 13:48:05 +0800 Subject: [PATCH 3/3] Hide internal request SSE helpers --- lib/request/index.ts | 2 -- test/public-api-contract.test.ts | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/request/index.ts b/lib/request/index.ts index 076c0643..869bad70 100644 --- a/lib/request/index.ts +++ b/lib/request/index.ts @@ -2,5 +2,3 @@ export * from "./failure-policy.js"; export * from "./fetch-helpers.js"; export * from "./rate-limit-backoff.js"; export * from "./request-transformer.js"; -export * from "./response-handler.js"; -export * from "./stream-failover.js"; diff --git a/test/public-api-contract.test.ts b/test/public-api-contract.test.ts index f81e60ae..3e6ceaf7 100644 --- a/test/public-api-contract.test.ts +++ b/test/public-api-contract.test.ts @@ -96,6 +96,8 @@ describe("public api contract", () => { expect(typeof config.loadPluginConfig).toBe("function"); expect(typeof request.createCodexHeaders).toBe("function"); expect(typeof request.transformRequestBody).toBe("function"); + expect("handleResponse" in request).toBe(false); + expect("withStreamFailover" in request).toBe(false); expect(typeof cli.loadCodexCliState).toBe("function"); });