Add managed browser and tunnel deployment

This commit is contained in:
Jan Leon
2026-07-30 06:56:00 +02:00
parent 8941eafcb9
commit a0f9ad852d
10 changed files with 266 additions and 7 deletions

View File

@@ -156,6 +156,11 @@ const EXTRA_MODULE_ENTRIES = [
src: ["scripts", "dev", "responses-ws-proxy.mjs"],
dest: ["responses-ws-proxy.mjs"],
},
{
label: "ChatGPT Web Codex MCP tunnel entrypoint",
src: ["bin", "chatgpt-web-codex-mcp.mjs"],
dest: ["bin", "chatgpt-web-codex-mcp.mjs"],
},
{
label: "webdav-handler (server-ws.mjs dependency)",
src: ["scripts", "dev", "webdav-handler.mjs"],

View File

@@ -41,6 +41,7 @@ export const APP_STAGING_ALLOWED_EXACT_PATHS: string[] = [
"head-response-guard.cjs",
"http-method-guard.cjs",
"open-sse/mcp-server/server.js",
"open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/mcp-server.js",
// LLMLingua ONNX worker — esbuild'd standalone .js spawned via worker_threads
// (the Next.js bundler can't trace the computed Worker path). Kept like the MCP server.
"open-sse/services/compression/engines/llmlingua/onnxWorker.js",
@@ -48,6 +49,7 @@ export const APP_STAGING_ALLOWED_EXACT_PATHS: string[] = [
"peer-stamp.mjs",
"main-server-timeouts.mjs",
"responses-ws-proxy.mjs",
"bin/chatgpt-web-codex-mcp.mjs",
"scripts/dev/sync-env.mjs",
"scripts/dev/tls-options.mjs",
"server.js",
@@ -86,7 +88,9 @@ export const PACK_ARTIFACT_ROOT_ALLOWED_EXACT_PATHS: string[] = [
".env.example",
"LICENSE",
"README.md",
"THIRD_PARTY_NOTICES.md",
"bin/aliasResolver.mjs",
"bin/chatgpt-web-codex-mcp.mjs",
// #7808: ESM loader hook split out of bin/aliasResolver.mjs to silence CodeQL
// js/incomplete-url-substring-sanitization (the old code built a
// `data:text/javascript,...` URL dynamically). Loaded via pathToFileURL() at
@@ -157,6 +161,7 @@ export const PACK_ARTIFACT_ROOT_ALLOWED_PATH_PREFIXES: string[] = [
export const PACK_ARTIFACT_REQUIRED_PATHS: string[] = [
"dist/open-sse/services/compression/engines/rtk/filters/generic-output.json",
"dist/open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/mcp-server.js",
"dist/open-sse/services/compression/rules/en/filler.json",
"dist/server.js",
"dist/server-ws.mjs",

View File

@@ -254,6 +254,42 @@ if (existsSync(mcpSrcFile)) {
}
}
const chatGptWebCodexMcpSrcFile = join(
ROOT,
"open-sse",
"vendor",
"codex-chatgpt-web",
"adapters",
"chatgpt-web",
"mcp-server.ts"
);
const chatGptWebCodexMcpDestFile = join(
DIST_DIR,
"open-sse",
"vendor",
"codex-chatgpt-web",
"adapters",
"chatgpt-web",
"mcp-server.js"
);
if (existsSync(chatGptWebCodexMcpSrcFile)) {
console.log(" 🔨 Bundling ChatGPT Web (Codex) MCP bridge...");
mkdirSync(dirname(chatGptWebCodexMcpDestFile), { recursive: true });
execFileSync(
NPX_BIN,
[
"esbuild",
"open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/mcp-server.ts",
"--bundle",
"--platform=node",
"--packages=external",
"--format=esm",
"--outfile=dist/open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/mcp-server.js",
],
{ cwd: ROOT, stdio: "inherit" }
);
}
// ── Step 8.6: Bundle LLMLingua ONNX worker ────────────────────────────
// The worker is spawned via worker_threads at a path the Next.js bundler cannot
// statically trace, so it must ship as a standalone .js (mirrors the MCP-server

View File

@@ -585,12 +585,18 @@ class ResponsesWsSession {
// preparedContext, but never touches this.upstream/this.upstreamReady; the caller decides
// whether a new upstream socket is needed.
async runPrepare(message, responseBody) {
const prepared = await callInternal(this.fetchImpl, this.baseUrl, this.bridgeSecret, "prepare", {
requestUrl: this.requestUrl,
headers: getAuthHeaders(this.requestUrl, this.requestHeaders),
message,
response: responseBody,
});
const prepared = await callInternal(
this.fetchImpl,
this.baseUrl,
this.bridgeSecret,
"prepare",
{
requestUrl: this.requestUrl,
headers: getAuthHeaders(this.requestUrl, this.requestHeaders),
message,
response: responseBody,
}
);
if (!prepared.ok) {
const message2 =
@@ -602,6 +608,7 @@ class ResponsesWsSession {
const error = new Error(message2);
error.code = code;
error.status = prepared.status;
if (code === "responses_websocket_http_fallback") error.httpFallback = true;
throw error;
}
@@ -716,11 +723,28 @@ class ResponsesWsSession {
// otherwise every turn after the first bypasses the whole pipeline. This reuses
// the already-established upstream transport; it must NOT recreate the socket.
const prepared = await this.runPrepare(message, nextTurnBody);
this.upstream.send(jsonStringifySafe(withPreparedResponseCreate(message, prepared.json.response)));
this.upstream.send(
jsonStringifySafe(withPreparedResponseCreate(message, prepared.json.response))
);
return;
}
this.upstream.send(jsonStringifySafe(message));
} catch (error) {
if (error?.httpFallback) {
const failurePayload = this.sendFailure(
"responses_websocket_http_fallback",
"Retry this request over HTTP/SSE Responses"
);
void this.persistHistory({
status: 426,
success: false,
errorCode: "responses_websocket_http_fallback",
errorMessage: "HTTP/SSE Responses transport required",
terminalMessage: failurePayload,
});
this.close(1013, "http_fallback_required");
return;
}
const code = error?.code || "upstream_websocket_connect_failed";
const messageText = error instanceof Error ? error.message : String(error);
const failurePayload = this.sendFailure(code, messageText);