Repository navigation
feat(providers): restore ChatGPT Web via clean-room browser transport - #12239
Merged
diegosouzapw merged 6 commits intoSep 2, 2026
Conversation
backryun
force-pushed
the
codex/restore-chatgpt-web-cleanroom
branch
17 times, most recently
from
September 2, 2026 08:33
a21f125 to
bb24dda
Compare
added 5 commits
September 2, 2026 20:41
backryun
force-pushed
the
codex/restore-chatgpt-web-cleanroom
branch
from
September 2, 2026 11:49
bb24dda to
d15912e
Compare
…ore-chatgpt-web-cleanroom Clean merge against the tip. The two chat chokepoints this PR grows (chat.ts +40, chatCore.ts +30) are recorded in the file-size baseline with an annotation; no other cap moves. The rebaseline is carried here rather than left in a validation worktree — the mistake that put the 2026-09-02 waves base-red (cheaper-inference#12434).
diegosouzapw
merged commit Sep 2, 2026
7ed8ada
into
cheaper-inference:release/v3.8.51
4 of 7 checks passed
Raudbjorn
added a commit
to Raudbjorn/OmniRoute
that referenced
this pull request
Sep 8, 2026
- Lazily import detectChromeExecutable in the validator, matching the
chatgpt-web-codex sibling: a static import pulls in the vendor
adapter's tiktoken WASM chain and crashes Turbopack dev bundling for
every provider validation request.
- Require a positive integer within setTimeout's int32 delay range for
the stream-open timeout override, superseding the earlier floor-based
fix: a fractional value no longer silently disables the deadline, and
a value beyond Node's timer range no longer fires almost immediately.
- Classify AbortError as 499 client_disconnected before status/message
matching, so a cancelled turn or client disconnect never counts
toward the provider breaker or an account cooldown.
- Classify the "Unsupported ChatGPT Session model" message (an
obsolete/invalid model slug) as a terminal 400 route_unavailable
instead of falling through to a retryable 502.
- Add the three ChatGPT Session input-error codes (no_user_message,
unsupported_content_part, vision_unsupported) to the public error
identifier allowlist so they survive buildErrorBody instead of being
replaced with generic bad_request.
- Reject chatgpt-session and chatgpt-web-codex from the generic
bulk-web-session import route: both read their encoded, browser-
verified credential from apiKey, which that route leaves null,
storing only an unusable raw cookie in providerSpecificData.
- Strengthen the dashboard-modal regression guard to pin the exact
identifier chain (usesChatGptBrowserSessionCredentials → ternary
condition → { version: 1, cookie } envelope) instead of only
checking the predicate's name appears in the file.
- Correct the changelog fragment and provider doc: chatgpt-web itself
was restored (cheaper-inference#12239); only its old cgpt-web alias remains retired.
Also fix "MIT-noticed" -> "MIT-licensed" in the provider doc.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JhWg8Hn1HBhmoHM4a2JmJ5
Raudbjorn
added a commit
to Raudbjorn/OmniRoute
that referenced
this pull request
Sep 8, 2026
* feat(providers): add chatgpt-session model route table * feat(providers): translate OpenAI messages for chatgpt-session * feat(providers): classify chatgpt-session failures onto the router contract * fix(providers): give TimeoutError name precedence over message matching in chatgpt-session error classification * feat(providers): bridge chatgpt-session adapter events to OpenAI payloads * fix(providers): surface mid-stream chatgpt-session errors as structured SSE error chunks - Route mid-stream error events through formatTranslatedStreamError instead of silently closing with finish_reason:stop, so a truncated turn is distinguishable from a completed one (AGENTS.md Hard Rule #6). - Sanitize the message on the pre-stream error verdict as defense in depth. - Cover the previously-untested gating paths: terminal event as the first meaningful event, source ending without a terminal event, empty source, heartbeat-only source, and incomplete with endTurn:true. * feat(providers): add the chatgpt-session executor host * fix(providers): preserve chatgpt-session stream error classification and guarantee queue close Keep the bridge's status/code authoritative on the streaming error branch instead of re-deriving them from the message alone, which downgraded a name-classified 400 into a breaker-tripping 502. Also guarantee events.close() runs even when the storage-state persist path or a caller-supplied logger throws, so neither consumer can hang. * feat(providers): register the chatgpt-session provider and executor * feat(providers): wire chatgpt-session into the dashboard and validation * fix(providers): route chatgpt-session through the shared browser-session credential lifecycle Both /api/providers routes gated the raw-cookie -> verified-storage-state finalize step on a hardcoded chatgpt-web-codex check, so chatgpt-session connections never had their pasted cookie replaced, never released the temporary validation directory, and leaked validationId into persisted providerSpecificData. Introduce usesChatGptBrowserSessionCredentials() as the single source of truth for both provider ids and use it in place of the hardcoded equality checks. Also replace a brittle source-regex test with one that proves the validation dispatch actually resolves. * docs(providers): document the chatgpt-session provider * docs(changelog): add chatgpt-session provider entry * test: bump RESERVED_PREFIX_COUNT ratchet to 402 for chatgpt-session provider The chatgpt-session provider adds two distinct reserved prefixes (id chatgpt-session, alias cgpt-session), moving the shared reserved-prefix set from 400 to 402 members. Adds explicit membership assertions for both new prefixes alongside the count. * fix(sse): drop the Codex read-only banner and align chatgpt-session failover The vendored adapter emits its "local Codex computer is unavailable" warning as a commentary-phase text_delta on every fresh turn, because this provider pins localToolsEnabled: false. The bridge treated every text_delta alike and let assistant_boundary fall through to `default`, so 100% of answers were prefixed with the banner. Commentary-phase deltas are now dropped on both the streaming and the buffered path (not rerouted to reasoning_content — it is transport chatter, not model reasoning), and assistant_boundary gets an explicit ignoring case. Same commit aligns the streaming gate with the buffered failover: the stream no longer commits a 200 on a heartbeat, an assistant boundary, a commentary delta, an empty text delta or a thinking delta, so `thinking_delta` followed by `error{429}` now returns HTTP 429 on both paths instead of a 200 stream carrying an in-band error. Reasoning that arrives while the gate is closed is buffered and replayed once the gate opens. * fix(sse): trust the adapter's explicit status over message patterns CONTRACT CHANGE. classifyChatGptSessionError put message-pattern matching ahead of an explicit numeric status, so an adapter error carrying status 503 whose prose mentioned signing in was classified 401 session_expired — marking a healthy account's credentials expired and pulling it out of rotation. The vendor documents the field as "Authoritative upstream/proxy status when known; avoids message-based classification" (vendor/codex-chatgpt-web/types.ts). Explicit status now runs third (after ChatGptSessionInputError and TimeoutError, both of which are stronger signals), uses the event's own code when present, and attaches fallbackHint: "connection_cooldown" for 503/429 so a genuine outage cools one connection instead of tripping the whole-provider breaker. Message matching is unchanged and still essential — errors thrown by the executor itself carry no status. ChatGptSessionStreamOpen's error arm now carries fallbackHint, so the executor uses the bridge's classification directly instead of re-classifying the sanitized message. The test "a matching message wins over an explicit upstream status" is deliberately inverted and renamed; its comment records why. * fix(dashboard): encode chatgpt-session credentials with the shared predicate Both connection modals wrapped the pasted Cookie header in the {version, cookie, runtimeKey} envelope only when provider === "chatgpt-web-codex", but the create and update routes send every provider accepted by usesChatGptBrowserSessionCredentials() through finalizeValidatedChatGptWebCodexSecrets, whose first statement is JSON.parse. A chatgpt-session save therefore always failed with 400 "Unexpected token '_', "__Secure-n"... is not valid JSON" — the dashboard could never create or update a connection. Both modals now key the envelope off the same predicate the routes use, so client and server cannot drift again, and omit runtimeKey when empty (this provider never has one). The two failure paths returned error.message raw, echoing the first characters of the pasted credential; they now go through sanitizeErrorMessage, and their untranslated German fallback is replaced with provider-neutral English. * fix(sse): reject unsupported chatgpt-session content parts instead of dropping them textFromContent threw for image parts but silently skipped everything else, so a `file` or `input_audio` part vanished and the model answered about content it never received. Any part that is neither a text part nor an already-handled image part now throws ChatGptSessionInputError with code "unsupported_content_part" (a terminal 400). * docs(providers): record the adapter task-framing limitation for chatgpt-session Turns are relayed through the vendored adapter's task-framing prompt, which API clients never see; note that a tool turn may be answered with a local-tool refusal instead of a tool block, explicitly flagged as unconfirmed pending live validation. The markdown tables are reflowed by the lint-staged Prettier hook. * fix(sse): accept assistant refusal content parts in chatgpt-session A `{ type: "refusal", refusal: "…" }` part is legal inside an assistant message in the chat-completions spec, so a client replaying its own conversation history sends it back. The unsupported-part guard rejected it with a 400 and failed the whole request. Fold a refusal part into the flattened content the way a text part is folded, reading its `refusal` field. A refusal part whose payload is missing or is not a string still falls through to the existing rejection — it is never dropped in silence — and every other unrecognised part keeps throwing as before. * fix(sse): bound the chatgpt-session stream-open gate with a deadline The gate withholds the HTTP response until the first event that also counts as committed output on the buffered path, so both paths agree on failover status. While it is closed nothing reaches the client — not even a keepalive, since the first byte commits the 200 — and a turn here runs in a real browser that can think for a long time, so a client with an idle timeout could hang up on a perfectly healthy turn. Race the gate against CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS (30s, overridable per call through the new options argument). A committing event or an error still wins exactly as before; only when the deadline elapses with neither does the stream open anyway and keep consuming the same iterator, replaying the buffered reasoning after the role chunk so nothing is lost or reordered. The in-flight next() the deadline outran is carried into the stream body instead of being abandoned, otherwise its event would vanish behind a second next(). The timer is unref'd and cleared on every exit path. Every failure that needs a real HTTP status — no browser, missing or expired credentials, rate limiting, an incompatible route — surfaces within seconds, far inside the window. * feat(executors): make chatgpt-session stream-open timeout tunable Add OMNIROUTE_CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS to override the 30s CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS default, mirroring resolveDirectHeadersTimeoutMs's validation. Resolved per request in the executor so a changed env var takes effect without a restart. * fix(sse): synthesize the native Codex turn envelope for chatgpt-session The executor passed the OpenAI chat-completions body through as `_rawBody`, but the vendored browser adapter reads `_rawBody` as a native Codex Responses body and demands turn identity from it. Every request therefore failed with "ChatGPT web requires native Codex turn_id metadata for browser-session replay" before any browser work started; no unit test caught it because they all mock the adapter call. `buildParsedRequest` now builds the envelope itself: a fresh thread/turn id pair per request (this provider serves stateless chat completions, so each request genuinely is its own turn), the turn metadata as the JSON string the real client sends, an `input` array mirroring the parsed messages in Responses item shape, and the current-turn passthrough marker on the last user item only. The `rawBody` parameter is gone so no caller can reintroduce the passthrough. Verified against the real adapter through the production code path: the turn now reaches stage=browser_page and fails only on the missing login state. * fix(chatgpt-session): address PR review threads on combo routing and cleanup - Phrase Sol/Pro route-mismatch errors as account-scoped model access denials so combo routing rotates to another connection instead of stopping (account-fallback's MODEL_ACCESS_DENIED_PATTERNS). - Carry the classified fallbackHint through the buffered-completion path and set X-Omni-Fallback-Hint on non-streaming responses. - Keep a trailing adapter error authoritative on the buffered path even when partial text was already produced — the HTTP status hasn't committed yet, unlike streaming. - Reject a stream-open timeout override that floors to zero instead of silently disabling the deadline. - Honor the connection_cooldown fallback hint on 429, not just 503, so a ChatGPT usage-limit response cools the one connection instead of tripping the whole-provider circuit breaker. - Add bounded cleanup for abandoned browser-verification state: a validation that succeeds but is never finalized no longer retains live session cookies on disk indefinitely. - Fix a stale README TOC anchor. Also resolves two provider-registry duplicates (chatgpt-web-codex import/map entry, WEB_COOKIE_PROVIDERS entry) left behind by a prior merge of origin/dev that was textually clean but semantically wrong. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JhWg8Hn1HBhmoHM4a2JmJ5 * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix(chatgpt-session): address second wave of PR review threads - Lazily import detectChromeExecutable in the validator, matching the chatgpt-web-codex sibling: a static import pulls in the vendor adapter's tiktoken WASM chain and crashes Turbopack dev bundling for every provider validation request. - Require a positive integer within setTimeout's int32 delay range for the stream-open timeout override, superseding the earlier floor-based fix: a fractional value no longer silently disables the deadline, and a value beyond Node's timer range no longer fires almost immediately. - Classify AbortError as 499 client_disconnected before status/message matching, so a cancelled turn or client disconnect never counts toward the provider breaker or an account cooldown. - Classify the "Unsupported ChatGPT Session model" message (an obsolete/invalid model slug) as a terminal 400 route_unavailable instead of falling through to a retryable 502. - Add the three ChatGPT Session input-error codes (no_user_message, unsupported_content_part, vision_unsupported) to the public error identifier allowlist so they survive buildErrorBody instead of being replaced with generic bad_request. - Reject chatgpt-session and chatgpt-web-codex from the generic bulk-web-session import route: both read their encoded, browser- verified credential from apiKey, which that route leaves null, storing only an unusable raw cookie in providerSpecificData. - Strengthen the dashboard-modal regression guard to pin the exact identifier chain (usesChatGptBrowserSessionCredentials → ternary condition → { version: 1, cookie } envelope) instead of only checking the predicate's name appears in the file. - Correct the changelog fragment and provider doc: chatgpt-web itself was restored (cheaper-inference#12239); only its old cgpt-web alias remains retired. Also fix "MIT-noticed" -> "MIT-licensed" in the provider doc. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JhWg8Hn1HBhmoHM4a2JmJ5 --------- Co-authored-by: Markus Hartung <diegosouzapw@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…cheaper-inference#12239) Restores ChatGPT Web on a clean-room browser transport, merged on the operator's explicit decision. Worth stating precisely, because this touches a provenance decision: the PR does not revert cheaper-inference#11754. It narrows RETIRED_COMMON_CHATGPT_WEB_PROVIDER_IDS to the single GPL-derived alias cgpt-web and registers chatgpt-web as a separate clean-room id. The old implementation stays retired and blocked; the retirement machinery, its error code and its 410 contract are untouched. All four retirement suites agree with that distinction and pass unchanged. The 44 protected agent-instruction surfaces this PR touches (AGENTS.md, llm.txt and its 42 mirrors, README) were verified rather than trusted: masking digits and comparing the removed and added line sets gives 264 lines on each side, identical — every change is a provider-count substitution, with no sentence added, removed or reworded. Reconciled on merge: clean against the tip, with the two chat chokepoints this PR grows (src/sse/handlers/chat.ts +40, open-sse/handlers/chatCore.ts +30) recorded in the file-size baseline under an annotation. Verified that exactly those two caps move and nothing else, so the cheaper-inference#12411 ratchet holds. The rebaseline is carried on this branch rather than left in a validation worktree — the propagation mistake that put the 2026-09-02 merge waves base-red in cheaper-inference#12434. Verified: 504/504 across the PR's 44 test files plus all four chatgpt-web retirement suites, check:provider-consistency OK (272 REGISTRY entries, 355 canonical providers), check-file-size OK, and every changed TypeScript file parses. Thanks @backryun — separating the clean-room id from the retired alias, instead of reopening the old one, is what made this reviewable.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
chatgpt-webprovider with an independently implemented, first-party browser transport.chatgpt-web-codextool bridge.Related Issues
Validation
Choose the change type and focused loop from the
Contribution Golden Path. The full unit suite,
Vitest, the 60% coverage gate, and the production build all run in CI on this PR (#8329):
npm run lint— left to CI; focused lint/type contracts are covered by the checks belowFocused results:
npm run typecheck:core: passed on the combined tree.npm run check:open-sse-typecheck: passed with zero live errors on the combined tree.OmniRoute Codex v2tool round-trip also passed through the separate Codex connector.release/v3.8.51: 0 commits behind, 4 commits ahead;git merge-tree --write-treesucceeds without conflicts.Tests Added Or Updated
Dedicated clean-room coverage:
tests/unit/chatgpt-web-browser-session-cleanroom.test.tstests/unit/chatgpt-web-cleanroom-provider.test.tstests/unit/chatgpt-web-delta-v1-cleanroom.test.tstests/unit/chatgpt-web-executor-adapter-cleanroom.test.tstests/unit/chatgpt-web-first-party-cleanroom.test.tstests/unit/chatgpt-web-handshake-handoff-cleanroom.test.tstests/unit/migration-171-restore-chatgpt-web-cleanroom.test.tsUpdated provider, executor, routing, image, session, and retirement contracts:
tests/snapshots/executors/executor-map.jsontests/unit/browserPool-proxy.test.tstests/unit/chatcore-executor-proxy.test.tstests/unit/chatgpt-web-image-handler-retirement.test.tstests/unit/chatgpt-web-management-retirement.test.tstests/unit/chatgpt-web-retirement.test.tstests/unit/chatgpt-web-runtime-block.test.tstests/unit/chatgpt-web-source-retirement.test.tstests/unit/combo-auto-candidate-expansion.test.tstests/unit/flat-rate-cost-5552.test.tstests/unit/guardrails/visionBridgeRouter.test.tstests/unit/hard-session-lease-bypass-inventory.test.tstests/unit/image-generation-route.test.tstests/unit/image-registry-gpt56.test.tstests/unit/image-routes-combo-edits-3214-3215.test.tstests/unit/keepalive-threshold.test.tstests/unit/messages-count-tokens-route.test.tstests/unit/model-listing-capability-5420.test.tstests/unit/provider-node-reserved-prefix.test.tstests/unit/resolve-web-provider-host.test.tstests/unit/session-leases-route.test.tstests/unit/token-health-check-webcookie.test.tstests/unit/tokenExtractionConfig.test.tstests/unit/virtual-auto-combo.test.tsCoverage Notes
Reviewer Notes
chatgpt-webintentionally advertisestoolCalling: false. Native PC/MCP tool access remains isolated inchatgpt-web-codex, where the outer Codex runtime owns sandbox and approval enforcement.