From 95abee266691497503327346d9ff1bbc008e3946 Mon Sep 17 00:00:00 2001 From: Markus Hartung Date: Tue, 8 Sep 2026 22:06:58 +0200 Subject: [PATCH 1/2] fix(providers): lazily load chatgpt-web-codex admin helpers in PUT /api/providers/[id] #12355 fixed a static top-level import of chatgptWebCodexAdmin.ts in src/app/api/providers/route.ts (the collection route): that module's transitive chain (the vendor ChatGPT-Web browser adapter -> token-estimate.ts -> tiktoken's WASM tokenizer) fails to bundle under Turbopack dev mode even with tiktoken listed in serverExternalPackages (the standalone Node require works fine; only Turbopack's bundling of this specific import graph doesn't). A static import evaluated that whole chain on every request regardless of provider, turning an unrelated provider's bundling bug into a route-wide 500 for everyone. #12355 only touched the collection route and missed the identical pattern in the by-id route (PUT /api/providers/[id]) -- observed live: renaming a plain openai-compatible connection's name failed with "Missing tiktoken_bg.wasm" after 17-50s, having never touched chatgpt-web-codex at all. #12355 also shipped with no regression test, which is exactly how this sibling instance went unnoticed for a week. Fix: same pattern as #12355 -- move the import into a lazy `await import(...)` inside the one `provider === "chatgpt-web-codex"` branch that actually needs it. New regression test (tests/unit/providers-chatgpt-web-codex-lazy-import-12355.test.ts) source-inspects both general provider routes (route.ts and [id]/route.ts) to assert neither statically imports chatgptWebCodexAdmin.ts, and confirms the one legitimately-static import (the chatgpt-web-codex-only "doctor" diagnostic route, where every request already is that provider) is left alone. Matches the existing tests/unit/instrumentation-import-graph-12074.test.ts pattern for import-graph invariants that can't be exercised by actually bundling under Turbopack from this harness. Confirmed it fails against the pre-fix route.ts (static import present) and passes after the fix. Evidence: - node --test tests/unit/providers-chatgpt-web-codex-lazy-import-12355.test.ts: 3/3 pass - node --test tests/unit/providers-route-patch-method.test.ts tests/unit/codex-connection-edit-6562.test.ts tests/unit/chatgpt-web-management-retirement.test.ts tests/unit/provider-patch-ratelimit-protection-11278.test.ts: 11/11 pass (unaffected) - eslint on both touched files: clean - Live-verified against the exact failing request: PUT /api/providers/{id} renaming a llama-cpp connection, previously 500 after 17-50s with "Missing tiktoken_bg.wasm", now 200 in 0.83s. Co-Authored-By: Claude Sonnet 5 --- src/app/api/providers/[id]/route.ts | 20 ++++-- ...hatgpt-web-codex-lazy-import-12355.test.ts | 67 +++++++++++++++++++ 2 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 tests/unit/providers-chatgpt-web-codex-lazy-import-12355.test.ts diff --git a/src/app/api/providers/[id]/route.ts b/src/app/api/providers/[id]/route.ts index 3bbe456efc88..957ef17f3c04 100644 --- a/src/app/api/providers/[id]/route.ts +++ b/src/app/api/providers/[id]/route.ts @@ -29,11 +29,16 @@ import { enableRateLimitProtection, disableRateLimitProtection, } from "@/../open-sse/services/rateLimitManager"; -import { - finalizeValidatedChatGptWebCodexSecrets, - decodeChatGptWebCodexSecrets, - encodeChatGptWebCodexSecrets, -} from "@omniroute/open-sse/services/chatgptWebCodexAdmin.ts"; +// Dynamically imported below, inside the one `provider === "chatgpt-web-codex"` +// branch that needs it -- same fix as #12355 (src/app/api/providers/route.ts), +// which missed this identical pattern in the by-id route. This module's +// transitive chain pulls in tiktoken's WASM tokenizer, which Turbopack dev +// mode fails to resolve for this graph even with `tiktoken` listed in +// serverExternalPackages. A static top-level import evaluates that whole +// chain on EVERY PUT to this route regardless of provider, turning an +// unrelated-provider bundling bug into a route-wide 500 (observed live: PUT +// on a plain openai-compatible connection's rename failed with "Missing +// tiktoken_bg.wasm" after 17-50s, never touching chatgpt-web-codex at all). import { rejectRetiredCommonChatGptWebProvider } from "@/lib/providers/chatgptWebRetirementResponse"; function normalizeCodexLimitPolicy( @@ -178,6 +183,11 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id: ? incomingPsd.validationId : ""; try { + const { + finalizeValidatedChatGptWebCodexSecrets, + decodeChatGptWebCodexSecrets, + encodeChatGptWebCodexSecrets, + } = await import("@omniroute/open-sse/services/chatgptWebCodexAdmin.ts"); const incomingSecrets = decodeChatGptWebCodexSecrets(apiKey); const existingSecrets = decodeChatGptWebCodexSecrets(existing.apiKey || ""); const encoded = encodeChatGptWebCodexSecrets({ diff --git a/tests/unit/providers-chatgpt-web-codex-lazy-import-12355.test.ts b/tests/unit/providers-chatgpt-web-codex-lazy-import-12355.test.ts new file mode 100644 index 000000000000..3ef46324921e --- /dev/null +++ b/tests/unit/providers-chatgpt-web-codex-lazy-import-12355.test.ts @@ -0,0 +1,67 @@ +// #12355 fixed a static top-level import of chatgptWebCodexAdmin.ts in +// src/app/api/providers/route.ts: that module's transitive chain (the vendor +// ChatGPT-Web browser adapter -> token-estimate.ts -> tiktoken's WASM +// tokenizer) fails to bundle under Turbopack dev mode even with tiktoken +// listed in serverExternalPackages, so a static import evaluated that whole +// chain on EVERY request to the route regardless of provider, turning one +// unrelated provider's bundling bug into a route-wide 500 for everyone. +// +// #12355 only touched the collection route (POST /api/providers) and missed +// the identical pattern in the by-id route (PUT /api/providers/[id]) -- +// observed live: renaming a plain openai-compatible connection failed with +// "Missing tiktoken_bg.wasm" after 17-50s, having never touched +// chatgpt-web-codex at all. #12355 also shipped with no regression test, +// which is exactly how the sibling instance went unnoticed. +// +// This pins BOTH general provider routes to stay lazy, and confirms the one +// legitimately-static import (the chatgpt-web-codex-only "doctor" diagnostic +// route, where every request already is for that provider) is left alone -- +// this is a source-inspection test, matching +// tests/unit/instrumentation-import-graph-12074.test.ts's own pattern for +// import-graph invariants that can't be exercised by actually bundling under +// Turbopack from this harness. + +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import test from "node:test"; + +const STATIC_IMPORT_RE = /^import\s+[\s\S]*?from\s+["']@omniroute\/open-sse\/services\/chatgptWebCodexAdmin\.ts["'];?\s*$/m; +const DYNAMIC_IMPORT_RE = /await import\(["']@omniroute\/open-sse\/services\/chatgptWebCodexAdmin\.ts["']\)/; + +function readSource(relativePath: string): string { + return fs.readFileSync(path.join(process.cwd(), relativePath), "utf8"); +} + +for (const routePath of [ + "src/app/api/providers/route.ts", + "src/app/api/providers/[id]/route.ts", +]) { + test(`${routePath} does not statically import chatgptWebCodexAdmin.ts`, () => { + const source = readSource(routePath); + assert.doesNotMatch( + source, + STATIC_IMPORT_RE, + `${routePath} handles every provider, not just chatgpt-web-codex -- a static import here ` + + "evaluates tiktoken's bundling-fragile chain on every request regardless of provider" + ); + assert.match( + source, + DYNAMIC_IMPORT_RE, + `${routePath} must still load chatgptWebCodexAdmin.ts lazily where it's actually needed` + ); + }); +} + +test("the chatgpt-web-codex-doctor route is intentionally exempt (every request there already is that provider)", () => { + const source = readSource( + "src/app/api/providers/[id]/chatgpt-web-codex-doctor/route.ts" + ); + assert.match( + source, + STATIC_IMPORT_RE, + "this route only ever serves chatgpt-web-codex connections -- a static import here is correct, " + + "not the #12355 bug; if this assertion starts failing because the import was lazily moved, that's " + + "fine too, just update this test to match" + ); +}); From ff542215428ec9732788ab549a6d935048190c80 Mon Sep 17 00:00:00 2001 From: Markus Hartung Date: Tue, 15 Sep 2026 16:06:23 +0200 Subject: [PATCH 2/2] docs(changelog): add fragment for #13071 --- .../fixes/13071-providers-put-lazy-chatgpt-web-codex-import.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/fixes/13071-providers-put-lazy-chatgpt-web-codex-import.md diff --git a/changelog.d/fixes/13071-providers-put-lazy-chatgpt-web-codex-import.md b/changelog.d/fixes/13071-providers-put-lazy-chatgpt-web-codex-import.md new file mode 100644 index 000000000000..db636022716f --- /dev/null +++ b/changelog.d/fixes/13071-providers-put-lazy-chatgpt-web-codex-import.md @@ -0,0 +1 @@ +- **fix(providers):** lazily load `chatgpt-web-codex` admin helpers in `PUT /api/providers/[id]`, mirroring #12355's exact pattern for the sibling `POST` route — a source-inspection test pins the import-graph invariant, since Turbopack's client-bundle boundary can't be exercised from the test harness directly ([#13071](https://github.com/diegosouzapw/OmniRoute/pull/13071)).