Skip to content

Commit dedaaec

Browse files
committed
rebase(pr/chatgpt-web): onto upstream/release/v3.8.32 for PR #4402
1 parent 26f03df commit dedaaec

13 files changed

Lines changed: 3120 additions & 2863 deletions

File tree

open-sse/executors/chatgpt-web.ts

Lines changed: 1 addition & 2863 deletions
Large diffs are not rendered by default.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { BaseExecutor, type ExecuteInput, type ProviderCredentials } from "../base.ts";
2+
import { OMNIROUTE_VERSION } from "@/shared/constants/version.ts";
3+
import { getProxyForAccount } from "../../utils/proxyFallback.ts";
4+
import { HttpsProxyAgent } from "https-proxy-agent";
5+
import crypto from "node:crypto";
6+
import { createHash } from "node:crypto";
7+
import { saveCallLog } from "@/lib/usage/callLogArtifacts.ts";
8+
import { streamWithTimeout } from "../../utils/stream.ts";
9+
import { ANTIGRAVITY_CONFIG } from "../../config/errorConfig.ts";
10+
import {
11+
storeChatGptImage,
12+
getChatGptImageConversationContext,
13+
__resetChatGptImageCacheForTesting,
14+
type ChatGptImageConversationContext,
15+
} from "../../services/chatgptImageCache.ts";
16+
17+
import { thinkingEffortCache } from "./thinking.ts";
18+
import { tokenCache, cookieKey } from "./session.ts";
19+
import { warmupCache } from "./warmup.ts";
20+
import { dplCache } from "./sentinel.ts";
21+
22+
// ─── Constants ──────────────────────────────────────────────────────────────
23+
24+
export const CHATGPT_BASE = "https://chatgpt.com";
25+
26+
export const SESSION_URL = `${CHATGPT_BASE}/api/auth/session`;
27+
28+
export const CONV_URL = `${CHATGPT_BASE}/backend-api/f/conversation`;
29+
30+
export const CHATGPT_USER_AGENT =
31+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:148.0) Gecko/20100101 Firefox/148.0";
32+
33+
// Captured from a real chatgpt.com browser session (April 2026).
34+
export const OAI_CLIENT_VERSION = "prod-81e0c5cdf6140e8c5db714d613337f4aeab94029";
35+
36+
export const OAI_CLIENT_BUILD_NUMBER = "6128297";
37+
38+
// Per-cookie device ID. The browser stores a persistent `oai-did` cookie that
39+
// uniquely identifies the device for OpenAI's risk model — we derive a stable
40+
// UUID from a hash of the session cookie so that each account/connection gets
41+
// its own device id, but it doesn't change between requests.
42+
export const deviceIdCache = new Map<string, string>();
43+
44+
export function deviceIdFor(cookie: string): string {
45+
const key = cookieKey(cookie);
46+
let id = deviceIdCache.get(key);
47+
if (!id) {
48+
// Synthesize a UUID v4-shaped string from a SHA-256 of the cookie. Stable,
49+
// deterministic per cookie, no PII (the cookie's already secret).
50+
// Not a password hash — SHA-256 is used to derive a stable UUID from the
51+
// session cookie for device-id fingerprinting. The output is a cache key.
52+
const h = createHash("sha256").update(cookie).digest("hex"); // lgtm[js/insufficient-password-hash]
53+
id =
54+
`${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-` +
55+
`${((parseInt(h.slice(16, 17), 16) & 0x3) | 0x8).toString(16)}${h.slice(17, 20)}-` +
56+
h.slice(20, 32);
57+
if (deviceIdCache.size >= 200) {
58+
const first = deviceIdCache.keys().next().value;
59+
if (first) deviceIdCache.delete(first);
60+
}
61+
deviceIdCache.set(key, id);
62+
}
63+
return id;
64+
}
65+
66+
// OmniRoute model ID → ChatGPT internal slug. OmniRoute uses dot-form IDs
67+
// (e.g. "gpt-5.3-instant"), ChatGPT's web routes use dash-form
68+
// (e.g. "gpt-5-3-instant"). The slug catalog comes from
69+
// /backend-api/models on a logged-in account; "gpt-5-4-t-mini" is ChatGPT's
70+
// abbreviated slug for "GPT-5.4 Thinking Mini".
71+
export const MODEL_MAP: Record<string, string> = {
72+
"gpt-5.3-instant": "gpt-5-3-instant",
73+
"gpt-5.3": "gpt-5-3",
74+
"gpt-5.3-mini": "gpt-5-3-mini",
75+
"gpt-5.5-thinking": "gpt-5-5-thinking",
76+
"gpt-5.4-thinking": "gpt-5-4-thinking",
77+
"gpt-5.4-thinking-mini": "gpt-5-4-t-mini",
78+
"gpt-5.2-instant": "gpt-5-2-instant",
79+
"gpt-5.2": "gpt-5-2",
80+
"gpt-5.2-thinking": "gpt-5-2-thinking",
81+
"gpt-5.1": "gpt-5-1",
82+
"gpt-5": "gpt-5",
83+
"gpt-5-mini": "gpt-5-mini",
84+
o3: "o3",
85+
};
86+
87+
/** Set of chatgpt.com slugs that the user_last_used_model_config endpoint
88+
* accepts a `thinking_effort` value for, derived from MODEL_MAP so adding a
89+
* new thinking entry there automatically extends this set. Includes the
90+
* abbreviated slug `gpt-5-4-t-mini` (no literal "thinking" substring) — the
91+
* reason this set exists at all rather than a substring match.
92+
*
93+
* Derived from MODEL_MAP keys (always dot-form) that contain "thinking" or
94+
* are the `o3` reasoning model; the values are the chatgpt.com-side slugs. */
95+
export const THINKING_CAPABLE_SLUGS: ReadonlySet<string> = new Set(
96+
Object.entries(MODEL_MAP)
97+
.filter(([k]) => k.includes("thinking") || k === "o3")
98+
.map(([, v]) => v)
99+
);
100+
101+
// ─── Browser-like default headers ──────────────────────────────────────────
102+
103+
export function browserHeaders(): Record<string, string> {
104+
return {
105+
Accept: "*/*",
106+
"Accept-Language": "en-US,en;q=0.9",
107+
"Cache-Control": "no-cache",
108+
Origin: CHATGPT_BASE,
109+
Pragma: "no-cache",
110+
Referer: `${CHATGPT_BASE}/`,
111+
"Sec-Fetch-Dest": "empty",
112+
"Sec-Fetch-Mode": "cors",
113+
"Sec-Fetch-Site": "same-origin",
114+
"User-Agent": CHATGPT_USER_AGENT,
115+
};
116+
}
117+
118+
/** Headers ChatGPT's web client sends on backend-api requests. */
119+
export function oaiHeaders(sessionId: string, deviceId: string): Record<string, string> {
120+
return {
121+
"OAI-Language": "en-US",
122+
"OAI-Device-Id": deviceId,
123+
"OAI-Client-Version": OAI_CLIENT_VERSION,
124+
"OAI-Client-Build-Number": OAI_CLIENT_BUILD_NUMBER,
125+
"OAI-Session-Id": sessionId,
126+
};
127+
}
128+
129+
// Strip ChatGPT's internal entity markup. The browser renders these as proper
130+
// inline citations / chips via JS; for a plain text completion we just want
131+
// the human-readable form.
132+
// entity["city","Paris","capital of France"] → Paris
133+
// entity["…","value", …] → value
134+
export const ENTITY_RE = /entity\["[^"]*","([^"]*)"[^\]]*\]/g;
135+
136+
// Test-only: clear caches between tests
137+
export function __resetChatGptWebCachesForTesting(): void {
138+
tokenCache.clear();
139+
warmupCache.clear();
140+
thinkingEffortCache.clear();
141+
deviceIdCache.clear();
142+
__resetChatGptImageCacheForTesting();
143+
dplCache = null;
144+
}

0 commit comments

Comments
 (0)