From 3cd4ee661b109ce8c255467f7c8df8078db04b8c Mon Sep 17 00:00:00 2001 From: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Date: Sat, 10 Oct 2026 04:42:16 -0300 Subject: [PATCH] fix(providers): backport ChatGPT Web reasoning transport limits (#14194) --- .../14194-chatgpt-web-transport-limits.md | 1 + .../codex-chatgpt-web/chatgpt-web-models.ts | 7 +- ...t-web-codex-transport-limits-14194.test.ts | 120 ++++++++++++++++++ 3 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 changelog.d/fixes/14194-chatgpt-web-transport-limits.md create mode 100644 tests/unit/chatgpt-web-codex-transport-limits-14194.test.ts diff --git a/changelog.d/fixes/14194-chatgpt-web-transport-limits.md b/changelog.d/fixes/14194-chatgpt-web-transport-limits.md new file mode 100644 index 000000000000..f48c38a7ff82 --- /dev/null +++ b/changelog.d/fixes/14194-chatgpt-web-transport-limits.md @@ -0,0 +1 @@ +- Backport the upstream v6.1.7 conservative 500,000-character ChatGPT Web reasoning preflight limit for Medium/High and Pro Extra High, including multipart stages and final parts. Instant, Pro mode, and Luna keep their existing limits. This partial backport retains the v4.0.7 vendor pin; the full refresh and live acceptance remain pending. Refs #14194. diff --git a/open-sse/vendor/codex-chatgpt-web/chatgpt-web-models.ts b/open-sse/vendor/codex-chatgpt-web/chatgpt-web-models.ts index d615fa0abb47..250586b16f20 100644 --- a/open-sse/vendor/codex-chatgpt-web/chatgpt-web-models.ts +++ b/open-sse/vendor/codex-chatgpt-web/chatgpt-web-models.ts @@ -19,7 +19,9 @@ export const CHATGPT_WEB_INSTANT_AUTO_COMPACT_TOKEN_LIMIT = 32_000; export const CHATGPT_WEB_MEDIUM_HIGH_CONTEXT_WINDOW = 90_000; export const CHATGPT_WEB_MEDIUM_HIGH_AUTO_COMPACT_TOKEN_LIMIT = 80_000; export const CHATGPT_WEB_INSTANT_COMPOSER_CHAR_LIMIT = 211_256; -export const CHATGPT_WEB_MEDIUM_HIGH_COMPOSER_CHAR_LIMIT = 1_048_572; +// Partial backport of the conservative reasoning composer ceiling from upstream v6.1.7, +// commit f9ad4ae83a579287105ad822dd0c3e0029b04ef6. The overall vendor remains v4.0.7. +export const CHATGPT_WEB_MEDIUM_HIGH_COMPOSER_CHAR_LIMIT = 500_000; /** Hidden ChatGPT product prompt and Codex Native schema reserve included in usage estimates. */ export const CHATGPT_WEB_PLATFORM_RESERVE_TOKENS = 8_192; /** Pro-account usable browser windows and separately measured one-message boundaries. */ @@ -33,7 +35,8 @@ export const CHATGPT_WEB_PRO_STANDARD_CONTEXT_WINDOW = export const CHATGPT_WEB_PRO_MODEL_CONTEXT_WINDOW = CHATGPT_WEB_PRO_MODEL_MESSAGE_TOKEN_LIMIT + CHATGPT_WEB_PLATFORM_RESERVE_TOKENS + 1; export const CHATGPT_WEB_PRO_INSTANT_COMPOSER_CHAR_LIMIT = 545_000; -export const CHATGPT_WEB_PRO_REASONING_COMPOSER_CHAR_LIMIT = 1_045_000; +export const CHATGPT_WEB_PRO_REASONING_COMPOSER_CHAR_LIMIT = + CHATGPT_WEB_MEDIUM_HIGH_COMPOSER_CHAR_LIMIT; export const CHATGPT_WEB_PRO_MODEL_COMPOSER_CHAR_LIMIT = 1_635_000; /** * The underlying Luna model owns this context window. ChatGPT Free's much smaller browser request diff --git a/tests/unit/chatgpt-web-codex-transport-limits-14194.test.ts b/tests/unit/chatgpt-web-codex-transport-limits-14194.test.ts new file mode 100644 index 000000000000..4ed5f4c97ddd --- /dev/null +++ b/tests/unit/chatgpt-web-codex-transport-limits-14194.test.ts @@ -0,0 +1,120 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { requireChatGptWebCodexRoute } from "../../open-sse/executors/chatgpt-web-codex/models.ts"; +import { + assertChatGptWebInputWithinLimits, + assertChatGptWebMultipartInputWithinLimits, +} from "../../open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/browser-worker.ts"; + +// Conservative transport policy from upstream v6.1.7, commit f9ad4ae83a579287105ad822dd0c3e0029b04ef6. +// These literals describe the preflight contract, not a new measurement of the remote service. +const LIMIT_ERROR = { + name: "ChatGptWebAdapterError", + status: 400, + errorType: "invalid_request_error", + code: "context_length_exceeded", + retryable: false, +}; + +function preflight( + id: string, + proAvailable: boolean, + chars: number, + inputTokens = 1_000, + messageTokens = 1_000 +) { + const route = requireChatGptWebCodexRoute(id); + assertChatGptWebInputWithinLimits( + inputTokens, + messageTokens, + route.backendModel, + route.effort, + { localToolsEnabled: true, solAvailable: route.sol, proAvailable }, + chars + ); +} + +for (const [id, proAvailable] of [ + ["medium", false], + ["high", false], + ["medium", true], + ["high", true], + ["extra-high", true], +] as const) { + test(`${id} on ${proAvailable ? "Pro" : "Plus"} accepts 500k chars and rejects the next char`, () => { + const model = `chatgpt-web-codex/${id}`; + assert.doesNotThrow(() => preflight(model, proAvailable, 500_000)); + assert.throws(() => preflight(model, proAvailable, 500_001), LIMIT_ERROR); + }); +} + +for (const [id, proAvailable, limit] of [ + ["instant", false, 211_256], + ["instant", true, 545_000], + ["pro", true, 1_635_000], +] as const) { + test(`${id} on ${proAvailable ? "Pro" : "Plus"} retains its distinct character boundary`, () => { + assert.doesNotThrow(() => preflight(id, proAvailable, limit)); + assert.throws(() => preflight(id, proAvailable, limit + 1), LIMIT_ERROR); + }); +} + +for (const id of ["luna", "think"]) { + test(`${id} retains its token limit without acquiring a reasoning character limit`, () => { + assert.doesNotThrow(() => preflight(id, false, 500_001, 28_000)); + assert.throws(() => preflight(id, false, 1, 28_001), LIMIT_ERROR); + }); +} + +test("the character ceiling does not replace Plus context-token rejection", () => { + assert.doesNotThrow(() => preflight("medium", false, 1, 89_999)); + assert.throws(() => preflight("medium", false, 1, 90_000), LIMIT_ERROR); +}); + +test("the character ceiling does not replace Pro message-token rejection", () => { + assert.doesNotThrow(() => preflight("high", true, 1, 104_000, 103_000)); + assert.throws(() => preflight("high", true, 1, 104_000, 103_001), LIMIT_ERROR); +}); + +test("unknown backend models remain rejected before transport", () => { + assert.throws( + () => + assertChatGptWebInputWithinLimits( + 1_000, + 1_000, + "unknown-model", + "medium", + { localToolsEnabled: true, solAvailable: true, proAvailable: false }, + 1 + ), + /context limit is not defined for model: unknown-model/ + ); +}); + +for (const proAvailable of [false, true]) { + for (const oversizedPart of ["stage", "final"] as const) { + test(`multipart ${oversizedPart} on ${proAvailable ? "Pro" : "Plus"} respects the 500k boundary`, () => { + const route = requireChatGptWebCodexRoute("high"); + const checkMultipart = (chars: number) => + assertChatGptWebMultipartInputWithinLimits( + 1_000, + 1_000, + route.backendModel, + route.effort, + { localToolsEnabled: true, solAvailable: true, proAvailable }, + chars, + 2, + { + stagingEffort: "medium", + maxStageMessageTokens: 1_000, + maxStageChars: oversizedPart === "stage" ? chars : 1, + finalMessageTokens: 1_000, + finalMessageChars: oversizedPart === "final" ? chars : 1, + } + ); + assert.doesNotThrow(() => checkMultipart(500_000)); + assert.throws(() => checkMultipart(500_001), LIMIT_ERROR); + }); + } +}