Files
OmniRoute/open-sse/executors/chatgpt-web.ts
backryun 7ed8ada432 feat(providers): restore ChatGPT Web via clean-room browser transport (#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 #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 #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 #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.
2026-09-02 10:31:41 -03:00

52 lines
1.9 KiB
TypeScript

import { chatgpt_webProvider } from "../config/providers/registry/chatgpt-web/index.ts";
import {
executeChatGptWebCleanRoom,
type ChatGptWebExecutorAdapterDeps,
} from "../utils/chatgptWebExecutorAdapter.ts";
import { makeExecutorErrorResult, sanitizeErrorMessage } from "../utils/error.ts";
import { BaseExecutor, type ExecuteInput } from "./base.ts";
const CHATGPT_WEB_URL = "https://chatgpt.com";
function statusForAdapterError(message: string): number {
if (/storage state|credentials|connection ID/i.test(message)) return 401;
// Preserve upstream quota semantics so the shared account-fallback loop can exclude a
// depleted Free session and immediately try the next configured ChatGPT Web account.
if (
/(?:\bHTTP[_\s-]*429\b|\bstatus\s+429\b|\brate[-_\s]?limit(?:ed)?\b|\bquota\s+(?:exhausted|reached|exceeded)\b|\b(?:image(?:\s+upload)?|upload|usage)\s+limit\s+(?:reached|exceeded)\b|\breached\s+(?:your\s+)?(?:image(?:\s+upload)?|upload|usage)\s+limit\b)/i.test(
message
)
) {
return 429;
}
if (/request|messages|prompt|model|tools|text content|reasoning effort/i.test(message))
return 400;
return 502;
}
/** Common ChatGPT Web executor rebuilt solely from first-party UI/network observations. */
export class ChatGptWebExecutor extends BaseExecutor {
constructor(private readonly deps: ChatGptWebExecutorAdapterDeps = {}) {
super("chatgpt-web", {
id: chatgpt_webProvider.id,
baseUrl: chatgpt_webProvider.baseUrl,
});
}
async execute(input: ExecuteInput) {
try {
return await executeChatGptWebCleanRoom(input, this.deps);
} catch (error) {
const message = sanitizeErrorMessage(error);
return makeExecutorErrorResult(
statusForAdapterError(message),
message || "ChatGPT Web browser execution failed",
input.body,
CHATGPT_WEB_URL
);
}
}
}
export default ChatGptWebExecutor;