@maintainer-pro/ai-bridge 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maintainer-pro/ai-bridge",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Local bridge daemon that pairs a machine to Maintainer Pro and configures multiple client sandboxes.",
5
5
  "keywords": [
6
6
  "maintainer-pro",
package/src/daemon.mjs CHANGED
@@ -18,6 +18,13 @@ import readline from "node:readline";
18
18
  import { fileURLToPath, pathToFileURL } from "node:url";
19
19
  import { createLogger, ensureProjectDataDir } from "@maintainer-pro/ai-cli";
20
20
  import { findIife, startAiServer } from "@maintainer-pro/ai-server";
21
+ import {
22
+ applyShareCacheHeaders,
23
+ lookupShareResponse,
24
+ prepareShareHttpRequest,
25
+ processShareHttpResponse,
26
+ shouldRewriteBody,
27
+ } from "./share-rewrite.mjs";
21
28
 
22
29
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
30
  const PACKAGE_VERSION = readPackageVersion();
@@ -2205,9 +2212,16 @@ const embeddedChat = new Map();
2205
2212
  /** @type {Map<string, Promise<object | null>>} */
2206
2213
  const embeddedChatStarting = new Map();
2207
2214
 
2208
- const PROXY_CHUNK_BYTES = 32 * 1024;
2215
+ const PROXY_CHUNK_BYTES = 256 * 1024;
2209
2216
  /** @type {Map<string, import("node:http").ClientRequest>} */
2210
2217
  const proxyHttpReqs = new Map();
2218
+ /** Reuse sockets to the local app — Next/Vite fetch many files per page. */
2219
+ const proxyKeepAliveAgent = new http.Agent({
2220
+ keepAlive: true,
2221
+ maxSockets: 64,
2222
+ maxFreeSockets: 16,
2223
+ timeout: 30_000,
2224
+ });
2211
2225
  /** @type {Map<string, WebSocket>} */
2212
2226
  const proxyLocalSockets = new Map();
2213
2227
  /** @type {Record<string, unknown> | null} */
@@ -2299,6 +2313,50 @@ function replyProxyHttp(id, status, headers, body) {
2299
2313
  bridgeSend({ type: "proxy.http.chunk", id, stream, data: "", eof: true });
2300
2314
  }
2301
2315
 
2316
+ function shareProxyContext(msg, ws, appId) {
2317
+ const apps = Array.isArray(ws?.hostApps) ? ws.hostApps : [];
2318
+ const app = apps.find((row) => row.id === appId);
2319
+ const slug =
2320
+ (typeof msg.slug === "string" && msg.slug.trim()) ||
2321
+ (isAiServerAppId(appId, ws) ? "ai" : proxySlugForApp(app));
2322
+ const publicBase = String(
2323
+ (typeof msg.publicBase === "string" && msg.publicBase) ||
2324
+ proxyUrlForApp(
2325
+ ws,
2326
+ app || {
2327
+ id: appId,
2328
+ role: slug === "ai" ? "ai-server" : "ui",
2329
+ }
2330
+ ) ||
2331
+ ""
2332
+ ).replace(/\/$/, "");
2333
+ const aiPublicBase = String(
2334
+ (typeof msg.aiPublicBase === "string" && msg.aiPublicBase) ||
2335
+ proxyUrlForApp(ws, { id: "ai-server", role: "ai-server" }) ||
2336
+ ""
2337
+ ).replace(/\/$/, "");
2338
+ const acceptEncoding =
2339
+ typeof msg.acceptEncoding === "string" ? msg.acceptEncoding : "";
2340
+ return {
2341
+ path: safeProxyPath(msg.path),
2342
+ slug,
2343
+ publicBase,
2344
+ aiPublicBase,
2345
+ acceptEncoding,
2346
+ port: localPortForProxy(ws, appId),
2347
+ };
2348
+ }
2349
+
2350
+ function sendProcessedProxyHttp(id, ctx, status, headers, body) {
2351
+ const processed = processShareHttpResponse({
2352
+ ...ctx,
2353
+ status,
2354
+ headers,
2355
+ body,
2356
+ });
2357
+ replyProxyHttp(id, processed.status, processed.headers, processed.body);
2358
+ }
2359
+
2302
2360
  function bridgeEmbedConfigJs(ws) {
2303
2361
  const store = ws?.store && typeof ws.store === "object" ? ws.store : {};
2304
2362
  const aiApp = { id: "ai-server", role: "ai-server" };
@@ -2324,7 +2382,16 @@ async function handleAiProxyHttpFromAdmin(msg, ws) {
2324
2382
  const method = String(msg.method || "GET").toUpperCase();
2325
2383
  const reqPath = safeProxyPath(msg.path);
2326
2384
  const pathname = reqPath.split("?")[0] || "/";
2327
- const headers = proxyReqHeaders(msg.headers);
2385
+ const ctx = shareProxyContext(msg, ws, "ai-server");
2386
+ const prepared = prepareShareHttpRequest(
2387
+ proxyReqHeaders(msg.headers),
2388
+ ctx.publicBase,
2389
+ ctx.acceptEncoding,
2390
+ reqPath
2391
+ );
2392
+ ctx.acceptEncoding = prepared.acceptEncoding;
2393
+ ctx.ifNoneMatch = prepared.ifNoneMatch;
2394
+ const headers = prepared.headers;
2328
2395
  const body =
2329
2396
  typeof msg.body === "string" && msg.body
2330
2397
  ? Buffer.from(msg.body, "base64")
@@ -2333,20 +2400,21 @@ async function handleAiProxyHttpFromAdmin(msg, ws) {
2333
2400
  if (pathname === "/ai-ui.iife.js") {
2334
2401
  const file = findIife();
2335
2402
  if (!file || !fs.existsSync(file)) {
2336
- replyProxyHttp(
2403
+ sendProcessedProxyHttp(
2337
2404
  id,
2405
+ ctx,
2338
2406
  404,
2339
2407
  { "content-type": "text/plain; charset=utf-8" },
2340
2408
  "Not found"
2341
2409
  );
2342
2410
  return;
2343
2411
  }
2344
- replyProxyHttp(
2412
+ sendProcessedProxyHttp(
2345
2413
  id,
2414
+ ctx,
2346
2415
  200,
2347
2416
  {
2348
2417
  "content-type": "text/javascript; charset=utf-8",
2349
- "cache-control": "no-store",
2350
2418
  },
2351
2419
  fs.readFileSync(file)
2352
2420
  );
@@ -2356,8 +2424,9 @@ async function handleAiProxyHttpFromAdmin(msg, ws) {
2356
2424
  if (pathname === "/embed-config.js") {
2357
2425
  const cfg = bridgeCfg || loadConfig();
2358
2426
  await loadSandboxStoreEnv(ws, cfg);
2359
- replyProxyHttp(
2427
+ sendProcessedProxyHttp(
2360
2428
  id,
2429
+ ctx,
2361
2430
  200,
2362
2431
  {
2363
2432
  "content-type": "text/javascript; charset=utf-8",
@@ -2380,7 +2449,7 @@ async function handleAiProxyHttpFromAdmin(msg, ws) {
2380
2449
  headers,
2381
2450
  body,
2382
2451
  });
2383
- replyProxyHttp(id, result.status, result.headers, result.body);
2452
+ sendProcessedProxyHttp(id, ctx, result.status, result.headers, result.body);
2384
2453
  } catch (err) {
2385
2454
  bridgeSend({
2386
2455
  type: "proxy.http.error",
@@ -2436,7 +2505,29 @@ function handleProxyHttpFromAdmin(msg) {
2436
2505
  }
2437
2506
  const method = String(msg.method || "GET").toUpperCase();
2438
2507
  const path = safeProxyPath(msg.path);
2439
- const headers = proxyReqHeaders(msg.headers);
2508
+ const ctx = shareProxyContext(msg, ws, appId);
2509
+ ctx.port = port;
2510
+ const prepared = prepareShareHttpRequest(
2511
+ proxyReqHeaders(msg.headers),
2512
+ ctx.publicBase,
2513
+ ctx.acceptEncoding,
2514
+ path
2515
+ );
2516
+ ctx.acceptEncoding = prepared.acceptEncoding;
2517
+ ctx.ifNoneMatch = prepared.ifNoneMatch;
2518
+ if (method === "GET" || method === "HEAD") {
2519
+ const cached = lookupShareResponse({
2520
+ publicBase: ctx.publicBase,
2521
+ path,
2522
+ ifNoneMatch: ctx.ifNoneMatch,
2523
+ acceptEncoding: ctx.acceptEncoding,
2524
+ });
2525
+ if (cached) {
2526
+ replyProxyHttp(id, cached.status, cached.headers, cached.body);
2527
+ return;
2528
+ }
2529
+ }
2530
+ const headers = prepared.headers;
2440
2531
  headers.host = `127.0.0.1:${port}`;
2441
2532
  // Next.js dev 403s `/_next` when Origin/sec-fetch look cross-site.
2442
2533
  // This hop is server-to-server; drop those so chunks always load.
@@ -2463,7 +2554,7 @@ function handleProxyHttpFromAdmin(msg) {
2463
2554
  path,
2464
2555
  method,
2465
2556
  headers,
2466
- agent: false,
2557
+ agent: proxyKeepAliveAgent,
2467
2558
  },
2468
2559
  (res) => {
2469
2560
  /** @type {Record<string, string>} */
@@ -2472,11 +2563,31 @@ function handleProxyHttpFromAdmin(msg) {
2472
2563
  if (value == null) continue;
2473
2564
  outHeaders[key] = Array.isArray(value) ? value.join(", ") : String(value);
2474
2565
  }
2566
+ const status = res.statusCode || 502;
2567
+ applyShareCacheHeaders(outHeaders, path);
2568
+ if (shouldRewriteBody(outHeaders)) {
2569
+ /** @type {Buffer[]} */
2570
+ const chunks = [];
2571
+ res.on("data", (chunk) => {
2572
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
2573
+ });
2574
+ res.on("end", () => {
2575
+ proxyHttpReqs.delete(id);
2576
+ sendProcessedProxyHttp(
2577
+ id,
2578
+ ctx,
2579
+ status,
2580
+ outHeaders,
2581
+ Buffer.concat(chunks)
2582
+ );
2583
+ });
2584
+ return;
2585
+ }
2475
2586
  bridgeSend({
2476
2587
  type: "proxy.http.start",
2477
2588
  id,
2478
2589
  stream,
2479
- status: res.statusCode || 502,
2590
+ status,
2480
2591
  headers: outHeaders,
2481
2592
  });
2482
2593
  res.on("data", (chunk) => {
@@ -2589,10 +2700,27 @@ function attachProxyLocalWs(id, socket) {
2589
2700
  });
2590
2701
  }
2591
2702
 
2592
- function openProxyLocalWs(id, port, path) {
2703
+ function wsProtocolsFromMsg(msg) {
2704
+ if (Array.isArray(msg?.protocols)) {
2705
+ return msg.protocols.map((p) => String(p || "").trim()).filter(Boolean);
2706
+ }
2707
+ const headers = msg?.headers && typeof msg.headers === "object" ? msg.headers : {};
2708
+ const raw = headers["sec-websocket-protocol"] || headers["Sec-WebSocket-Protocol"] || "";
2709
+ return String(raw)
2710
+ .split(",")
2711
+ .map((part) => part.trim())
2712
+ .filter(Boolean);
2713
+ }
2714
+
2715
+ function openProxyLocalWs(id, port, path, protocols) {
2593
2716
  let socket;
2594
2717
  try {
2595
- socket = new WebSocket(`ws://127.0.0.1:${port}${path}`);
2718
+ const url = `ws://127.0.0.1:${port}${path}`;
2719
+ const proto = Array.isArray(protocols)
2720
+ ? protocols.map((p) => String(p || "").trim()).filter(Boolean)
2721
+ : [];
2722
+ // Vite HMR only accepts upgrades with subprotocol `vite-hmr` / `vite-ping`.
2723
+ socket = proto.length ? new WebSocket(url, proto) : new WebSocket(url);
2596
2724
  } catch (err) {
2597
2725
  bridgeSend({
2598
2726
  type: "proxy.ws.error",
@@ -2615,6 +2743,7 @@ function handleProxyWsOpenFromAdmin(msg) {
2615
2743
  return;
2616
2744
  }
2617
2745
  const path = safeProxyPath(msg.path);
2746
+ const protocols = wsProtocolsFromMsg(msg);
2618
2747
  if (isAiServerAppId(appId, ws)) {
2619
2748
  void (async () => {
2620
2749
  const embedded = await ensureEmbeddedChat(ws);
@@ -2623,7 +2752,7 @@ function handleProxyWsOpenFromAdmin(msg) {
2623
2752
  bridgeSend({ type: "proxy.ws.error", id, error: "AI chat is not running" });
2624
2753
  return;
2625
2754
  }
2626
- openProxyLocalWs(id, port, path);
2755
+ openProxyLocalWs(id, port, path, protocols);
2627
2756
  })();
2628
2757
  return;
2629
2758
  }
@@ -2632,7 +2761,7 @@ function handleProxyWsOpenFromAdmin(msg) {
2632
2761
  bridgeSend({ type: "proxy.ws.error", id, error: "No local port mapped" });
2633
2762
  return;
2634
2763
  }
2635
- openProxyLocalWs(id, port, path);
2764
+ openProxyLocalWs(id, port, path, protocols);
2636
2765
  }
2637
2766
 
2638
2767
  function handleProxyWsFrameFromAdmin(msg) {
@@ -5240,6 +5369,7 @@ async function main() {
5240
5369
  senderType: msg.senderType === "client" ? "client" : undefined,
5241
5370
  senderName:
5242
5371
  typeof msg.senderName === "string" ? msg.senderName : undefined,
5372
+ context: msg.context && typeof msg.context === "object" ? msg.context : undefined,
5243
5373
  };
5244
5374
  const embedded = embeddedChat.get(sandboxId);
5245
5375
  if (embedded?.runChat) {
@@ -0,0 +1,729 @@
1
+ /**
2
+ * Share-URL processing lives on the bridge: path rewrite, widget injection,
3
+ * cache-control, and gzip. Maintainer Pro only forwards the result.
4
+ */
5
+ import { gzipSync } from "node:zlib";
6
+ import { createHash } from "node:crypto";
7
+
8
+ const REWRITE_CACHE_MS = 10 * 60 * 1000;
9
+ const REWRITE_CACHE_MAX = 128;
10
+
11
+ /** @type {Map<string, { at: number, payload: Buffer, status: number, headers: Record<string, string>, etag: string }>} */
12
+ const rewriteCache = new Map();
13
+
14
+ /**
15
+ * @param {Record<string, string>} headers
16
+ * @returns {Record<string, string>}
17
+ */
18
+ function lowerHeaders(headers) {
19
+ /** @type {Record<string, string>} */
20
+ const out = {};
21
+ for (const [key, value] of Object.entries(headers || {})) {
22
+ if (value == null) continue;
23
+ out[String(key).toLowerCase()] = String(value);
24
+ }
25
+ return out;
26
+ }
27
+
28
+ function headerGet(headers, name) {
29
+ const lower = String(name).toLowerCase();
30
+ for (const [key, value] of Object.entries(headers || {})) {
31
+ if (String(key).toLowerCase() === lower) return String(value);
32
+ }
33
+ return "";
34
+ }
35
+
36
+ function stripProxyPrefix(urlOrPath, pathPrefix) {
37
+ if (!pathPrefix || !urlOrPath) return urlOrPath;
38
+ try {
39
+ const u = new URL(urlOrPath, "http://mp.invalid");
40
+ let path = u.pathname;
41
+ if (path === pathPrefix) path = "/";
42
+ else if (path.startsWith(`${pathPrefix}/`)) {
43
+ path = path.slice(pathPrefix.length) || "/";
44
+ }
45
+ return `${path}${u.search}${u.hash}`;
46
+ } catch {
47
+ if (urlOrPath === pathPrefix) return "/";
48
+ if (urlOrPath.startsWith(`${pathPrefix}/`)) {
49
+ return urlOrPath.slice(pathPrefix.length) || "/";
50
+ }
51
+ return urlOrPath;
52
+ }
53
+ }
54
+
55
+ function proxyPathPrefix(publicBase) {
56
+ const trimmed = String(publicBase || "").replace(/\/$/, "");
57
+ if (!trimmed) return "";
58
+ try {
59
+ return new URL(trimmed).pathname.replace(/\/$/, "") || "";
60
+ } catch {
61
+ const start = trimmed.indexOf("://");
62
+ if (start < 0) return trimmed;
63
+ const pathStart = trimmed.indexOf("/", start + 3);
64
+ return pathStart >= 0 ? trimmed.slice(pathStart).replace(/\/$/, "") : "";
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Force identity to the local app so HTML/JS can be rewritten, and strip the
70
+ * share prefix from Next's `next-url` header.
71
+ *
72
+ * @param {Record<string, string>} headers
73
+ * @param {string} [publicBase]
74
+ * @param {string} [acceptEncodingHint]
75
+ * @param {string} [path]
76
+ * @returns {{ headers: Record<string, string>, acceptEncoding: string, ifNoneMatch: string }}
77
+ */
78
+ export function prepareShareHttpRequest(
79
+ headers,
80
+ publicBase,
81
+ acceptEncodingHint,
82
+ path
83
+ ) {
84
+ const out = { ...headers };
85
+ const acceptEncoding = String(
86
+ acceptEncodingHint || headerGet(out, "accept-encoding") || ""
87
+ );
88
+ const ifNoneMatch = headerGet(out, "if-none-match");
89
+ const pathPrefix = proxyPathPrefix(publicBase || "");
90
+ if (pathPrefix) {
91
+ for (const key of Object.keys(out)) {
92
+ if (key.toLowerCase() === "next-url") {
93
+ out[key] = stripProxyPrefix(out[key], pathPrefix);
94
+ }
95
+ }
96
+ }
97
+ for (const key of Object.keys(out)) {
98
+ if (key.toLowerCase() === "accept-encoding") delete out[key];
99
+ }
100
+ out["accept-encoding"] = "identity";
101
+ // Rewritten bodies get our ETag. Do not let Next 304 on its own validator
102
+ // or we would have no HTML/JS to rewrite.
103
+ if (willRewritePath(path)) {
104
+ for (const key of Object.keys(out)) {
105
+ const lower = key.toLowerCase();
106
+ if (lower === "if-none-match" || lower === "if-modified-since") {
107
+ delete out[key];
108
+ }
109
+ }
110
+ }
111
+ if (publicBase && !headerGet(out, "x-forwarded-prefix")) {
112
+ try {
113
+ const prefix = new URL(publicBase).pathname.replace(/\/$/, "") || "/";
114
+ out["x-forwarded-prefix"] = prefix;
115
+ } catch {
116
+ /* ignore */
117
+ }
118
+ }
119
+ return { headers: out, acceptEncoding, ifNoneMatch };
120
+ }
121
+
122
+ export function shouldRewriteBody(headers) {
123
+ const enc = headerGet(headers, "content-encoding").toLowerCase();
124
+ if (enc && enc !== "identity") return false;
125
+ const ct = headerGet(headers, "content-type").toLowerCase();
126
+ return /html|javascript|ecmascript|css|json|svg|xml|text\/plain|text\/x-component|text\/x-ref|\brsc\b/.test(
127
+ ct
128
+ );
129
+ }
130
+
131
+ function isImmutableAssetPath(path) {
132
+ const p = String(path || "").split("?")[0] || "";
133
+ return (
134
+ /\/_next\/static\//.test(p) ||
135
+ /\/__nextjs_font\//.test(p) ||
136
+ /\/node_modules\/\.vite\//.test(p) ||
137
+ /\.[0-9a-f]{8,}\.(?:js|css|woff2?)$/i.test(p)
138
+ );
139
+ }
140
+
141
+ function isNoStorePath(path) {
142
+ const p = String(path || "").split("?")[0] || "";
143
+ return (
144
+ /\/embed-config\.js$/i.test(p) ||
145
+ p === "/api" ||
146
+ p.startsWith("/api/") ||
147
+ /\/_next\/webpack-hmr/i.test(p) ||
148
+ /\/_next\/webpack\/hmr/i.test(p) ||
149
+ /\/__nextjs_original-stack-frames/i.test(p) ||
150
+ /\/@vite(?:\/|$)/.test(p) ||
151
+ /\/@react-refresh/.test(p) ||
152
+ /\/@fs\//.test(p) ||
153
+ /\/@id\//.test(p)
154
+ );
155
+ }
156
+
157
+ function isCacheableMediaPath(path) {
158
+ const p = String(path || "").split("?")[0] || "";
159
+ return /\.(?:png|jpe?g|gif|webp|avif|ico|woff2?|ttf|otf|mp4|webm|mp3|wav|wasm)$/i.test(
160
+ p
161
+ );
162
+ }
163
+
164
+ function isLongCacheScriptPath(path) {
165
+ const p = String(path || "").split("?")[0] || "";
166
+ return /\/ai-ui\.iife\.js$/i.test(p);
167
+ }
168
+
169
+ function willRewritePath(path) {
170
+ const p = String(path || "").split("?")[0]?.toLowerCase() || "";
171
+ if (!p || p === "/") return true;
172
+ if (isCacheableMediaPath(p)) return false;
173
+ if (/\.(?:woff2?|ttf|otf|png|jpe?g|gif|webp|avif|ico|mp4|webm|wasm)$/i.test(p)) {
174
+ return false;
175
+ }
176
+ return true;
177
+ }
178
+
179
+ function etagFor(payload) {
180
+ const hash = createHash("sha1").update(payload).digest("base64url").slice(0, 27);
181
+ return `"${hash}"`;
182
+ }
183
+
184
+ function etagMatches(ifNoneMatch, etag) {
185
+ if (!ifNoneMatch || !etag) return false;
186
+ const want = String(etag).trim();
187
+ return String(ifNoneMatch)
188
+ .split(",")
189
+ .some((part) => {
190
+ const token = part.trim();
191
+ return token === "*" || token === want || token === `W/${want}`;
192
+ });
193
+ }
194
+
195
+ function dropHopCacheNoise(headers) {
196
+ delete headers.pragma;
197
+ delete headers.expires;
198
+ }
199
+
200
+ /**
201
+ * Cache-Control for the share URL. Hashed Next/Vite assets can sit in the
202
+ * browser for a year; HTML/RSC revalidate with ETag; secrets and HMR do not
203
+ * cache.
204
+ *
205
+ * @param {Record<string, string>} headers
206
+ * @param {string} path
207
+ * @returns {"no-store" | "immutable" | "store" | "revalidate"}
208
+ */
209
+ export function applyShareCacheHeaders(headers, path) {
210
+ dropHopCacheNoise(headers);
211
+ if (isNoStorePath(path)) {
212
+ headers["cache-control"] = "private, no-store";
213
+ return "no-store";
214
+ }
215
+ if (isImmutableAssetPath(path)) {
216
+ delete headers["set-cookie"];
217
+ headers["cache-control"] = "public, max-age=31536000, immutable";
218
+ return "immutable";
219
+ }
220
+ if (isCacheableMediaPath(path) || isLongCacheScriptPath(path)) {
221
+ delete headers["set-cookie"];
222
+ headers["cache-control"] = "public, max-age=86400";
223
+ return "store";
224
+ }
225
+ headers["cache-control"] = "no-cache";
226
+ return "revalidate";
227
+ }
228
+
229
+ function notModifiedResult(headers, etag) {
230
+ const out = {
231
+ "cache-control": headers["cache-control"] || "no-cache",
232
+ etag,
233
+ };
234
+ if (headers.vary) out.vary = headers.vary;
235
+ return { status: 304, headers: out, body: Buffer.alloc(0) };
236
+ }
237
+
238
+ function rewriteLocation(value, publicBase, port) {
239
+ if (!publicBase) return value;
240
+ try {
241
+ const u = new URL(value, `http://127.0.0.1:${Number(port) || 0}`);
242
+ if (u.hostname === "127.0.0.1" || u.hostname === "localhost") {
243
+ return `${String(publicBase).replace(/\/$/, "")}${u.pathname}${u.search}${u.hash}`;
244
+ }
245
+ } catch {
246
+ /* keep */
247
+ }
248
+ return value;
249
+ }
250
+
251
+ function gzipIfRequested(payload, headers, acceptEncoding) {
252
+ if (payload.length < 1024) return payload;
253
+ if (!/\bgzip\b/i.test(acceptEncoding || "")) return payload;
254
+ const enc = headerGet(headers, "content-encoding");
255
+ if (enc && enc !== "identity") return payload;
256
+ try {
257
+ const gzipped = gzipSync(payload, { level: 6 });
258
+ if (gzipped.length >= payload.length) return payload;
259
+ for (const key of Object.keys(headers)) {
260
+ if (key.toLowerCase() === "content-encoding") delete headers[key];
261
+ if (key.toLowerCase() === "content-length") delete headers[key];
262
+ }
263
+ headers["content-encoding"] = "gzip";
264
+ const vary = headerGet(headers, "vary");
265
+ if (!/\baccept-encoding\b/i.test(vary)) {
266
+ for (const key of Object.keys(headers)) {
267
+ if (key.toLowerCase() === "vary") delete headers[key];
268
+ }
269
+ headers.vary = vary ? `${vary}, Accept-Encoding` : "Accept-Encoding";
270
+ }
271
+ return gzipped;
272
+ } catch {
273
+ return payload;
274
+ }
275
+ }
276
+
277
+ function rewriteCacheKey(publicBase, path) {
278
+ if (!isImmutableAssetPath(path) && !isLongCacheScriptPath(path)) return null;
279
+ return `${publicBase || ""}\0${String(path || "").split("?")[0]}`;
280
+ }
281
+
282
+ function rememberRewrite(key, payload, status, headers, etag) {
283
+ if (rewriteCache.size >= REWRITE_CACHE_MAX) {
284
+ const first = rewriteCache.keys().next().value;
285
+ if (first) rewriteCache.delete(first);
286
+ }
287
+ rewriteCache.set(key, {
288
+ at: Date.now(),
289
+ payload,
290
+ status,
291
+ headers: { ...headers },
292
+ etag,
293
+ });
294
+ }
295
+
296
+ /**
297
+ * Serve a previously rewritten immutable asset without hitting Next.
298
+ * Returns a 304 when the browser already has this ETag.
299
+ *
300
+ * @param {{ publicBase?: string, path?: string, ifNoneMatch?: string, acceptEncoding?: string }} opts
301
+ * @returns {{ status: number, headers: Record<string, string>, body: Buffer } | null}
302
+ */
303
+ export function lookupShareResponse(opts) {
304
+ const path = String(opts?.path || "/");
305
+ const key = rewriteCacheKey(opts?.publicBase || "", path);
306
+ if (!key) return null;
307
+ const hit = rewriteCache.get(key);
308
+ if (!hit || Date.now() - hit.at >= REWRITE_CACHE_MS) return null;
309
+ return finishCachedShareResponse(hit, path, opts?.ifNoneMatch, opts?.acceptEncoding);
310
+ }
311
+
312
+ function finishCachedShareResponse(hit, path, ifNoneMatch, acceptEncoding) {
313
+ const outHeaders = { ...hit.headers };
314
+ applyShareCacheHeaders(outHeaders, path);
315
+ const etag = hit.etag || etagFor(hit.payload);
316
+ outHeaders.etag = etag;
317
+ if (etagMatches(ifNoneMatch, etag)) {
318
+ return notModifiedResult(outHeaders, etag);
319
+ }
320
+ const gzipped = gzipIfRequested(hit.payload, outHeaders, acceptEncoding || "");
321
+ outHeaders["content-length"] = String(gzipped.length);
322
+ return { status: hit.status, headers: outHeaders, body: gzipped };
323
+ }
324
+
325
+ function isNextDocument(body) {
326
+ return /\/_next\//.test(body) || /__NEXT_DATA__|self\.__next_f/.test(body);
327
+ }
328
+
329
+ function isViteDocument(body) {
330
+ return (
331
+ /\/@vite(?:\/client)?(?:["'?]|$)/.test(body) ||
332
+ /\/@react-refresh/.test(body) ||
333
+ /\/node_modules\/\.vite\//.test(body) ||
334
+ /\/node_modules\/\.pnpm\/vite@/.test(body)
335
+ );
336
+ }
337
+
338
+ function viteDevBases(body) {
339
+ const bases = [];
340
+ const re =
341
+ /\bconst base(?:\$\d+)?\s*=\s*["'](\/[^"']*)["']\s*\|\|\s*["']\/["']/g;
342
+ let match;
343
+ while ((match = re.exec(body))) {
344
+ const base = match[1];
345
+ if (!base || base === "/") continue;
346
+ bases.push(base.endsWith("/") ? base : `${base}/`);
347
+ }
348
+ return bases;
349
+ }
350
+
351
+ function viteAssetRoots(body) {
352
+ const roots = new Set([
353
+ "/@vite",
354
+ "/@react-refresh",
355
+ "/@fs",
356
+ "/@id",
357
+ "/node_modules/",
358
+ "/src/",
359
+ "/config.json",
360
+ ]);
361
+ const re =
362
+ /["'`](\/[^"'`]*?)(?=\/(?:@vite|@react-refresh|@fs|@id|node_modules\/|src\/))/g;
363
+ let match;
364
+ while ((match = re.exec(body))) {
365
+ const base = match[1];
366
+ if (!base || base === "/") continue;
367
+ roots.add(base.endsWith("/") ? base : `${base}/`);
368
+ }
369
+ for (const base of viteDevBases(body)) roots.add(base);
370
+ return [...roots];
371
+ }
372
+
373
+ function joinProxyAndViteBase(pathPrefix, viteBase) {
374
+ const prefix = pathPrefix.replace(/\/$/, "");
375
+ if (!viteBase || viteBase === "/") return `${prefix}/`;
376
+ const base = viteBase.startsWith("/") ? viteBase : `/${viteBase}`;
377
+ return `${prefix}${base.endsWith("/") ? base : `${base}/`}`;
378
+ }
379
+
380
+ function rewriteViteHmrClient(body, pathPrefix) {
381
+ if (!pathPrefix || !body.includes("vite-hmr")) return body;
382
+ const viteBase = viteDevBases(body)[0] || "/";
383
+ const prefixed = joinProxyAndViteBase(pathPrefix, viteBase);
384
+ const hostPath = JSON.stringify(prefixed).slice(1, -1);
385
+ let out = body;
386
+ out = out.replace(
387
+ /\bconst socketHost = `[\s\S]*?`;/,
388
+ `const socketHost = \`\${importMetaUrl.host}${hostPath}\`;`
389
+ );
390
+ out = out.replace(
391
+ /\bconst directSocketHost = (?:"[^"]*"|'[^']*');/,
392
+ `const directSocketHost = importMetaUrl.host + ${JSON.stringify(prefixed)};`
393
+ );
394
+ return out;
395
+ }
396
+
397
+ function prefixQuotedRoots(body, pathPrefix, roots) {
398
+ if (!pathPrefix) return body;
399
+ let out = body;
400
+ for (const root of roots) {
401
+ const escaped = root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
402
+ out = out.replace(
403
+ new RegExp(`(["'\`])(${escaped})`, "g"),
404
+ `$1${pathPrefix}$2`
405
+ );
406
+ }
407
+ return out;
408
+ }
409
+
410
+ function alreadyPrefixed(path, pathPrefix) {
411
+ return path === pathPrefix || path.startsWith(`${pathPrefix}/`);
412
+ }
413
+
414
+ function rewriteNextTurbopackBasePath(body, pathPrefix) {
415
+ if (!pathPrefix) return body;
416
+ let out = body;
417
+ if (out.includes("TURBOPACK compile-time value")) {
418
+ const next = JSON.stringify(pathPrefix);
419
+ out = out
420
+ .split(`("TURBOPACK compile-time value", "") || ''`)
421
+ .join(`("TURBOPACK compile-time value", ${next}) || ''`)
422
+ .split(`("TURBOPACK compile-time value", "") || ""`)
423
+ .join(`("TURBOPACK compile-time value", ${next}) || ""`);
424
+ }
425
+ const fromLocation =
426
+ "location ? (0, _createhreffromurl.createHrefFromUrl)(location) : initialCanonicalUrl";
427
+ if (out.includes(fromLocation)) {
428
+ out = out.split(fromLocation).join("initialCanonicalUrl");
429
+ }
430
+ const closeOnDomReady =
431
+ "if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', DOMContentLoaded, false);\n} else {\n // Delayed in marco task to ensure it's executed later than hydration\n setTimeout(DOMContentLoaded);\n}";
432
+ const closeOnLoad =
433
+ "if (document.readyState === 'complete') {\n setTimeout(DOMContentLoaded);\n} else {\n window.addEventListener('load', DOMContentLoaded, false);\n}";
434
+ if (out.includes(closeOnDomReady)) {
435
+ out = out.split(closeOnDomReady).join(closeOnLoad);
436
+ }
437
+ return out;
438
+ }
439
+
440
+ function contentMime(contentType) {
441
+ return contentType.toLowerCase().split(";")[0]?.trim() || "";
442
+ }
443
+
444
+ function prefixRootPaths(body, publicBase, contentType = "") {
445
+ const pathPrefix = proxyPathPrefix(publicBase);
446
+ if (!pathPrefix) return body;
447
+ const mime = contentMime(contentType);
448
+ const isCode =
449
+ /javascript|ecmascript|json|x-component|x-ref|\brsc\b/.test(mime) ||
450
+ mime === "text/plain";
451
+ const html =
452
+ !isCode &&
453
+ (mime === "text/html" ||
454
+ mime === "application/xhtml+xml" ||
455
+ (!mime && /<!doctype html|<html[\s>]/i.test(body)));
456
+ const css = mime === "text/css" || (!isCode && !html && /css/.test(mime));
457
+ const viteAndNext = ["/_next/", "/__nextjs_", ...viteAssetRoots(body)];
458
+ if (!html && !css) {
459
+ let code = rewriteViteHmrClient(body, pathPrefix);
460
+ code = prefixQuotedRoots(code, pathPrefix, viteAndNext);
461
+ code = code.replace(
462
+ /(?<![A-Za-z0-9])\/__nextjs_/g,
463
+ `${pathPrefix}/__nextjs_`
464
+ );
465
+ return rewriteNextTurbopackBasePath(code, pathPrefix);
466
+ }
467
+
468
+ let out = body;
469
+ if (
470
+ html &&
471
+ /<head[\s>]/i.test(out) &&
472
+ !/<base\s/i.test(out) &&
473
+ !isNextDocument(out)
474
+ ) {
475
+ out = out.replace(
476
+ /<head([^>]*)>/i,
477
+ `<head$1><base href="${pathPrefix}/">`
478
+ );
479
+ }
480
+ if (html) {
481
+ out = out.replace(
482
+ /(\s(?:src|href|action|poster)=["'])(\/(?!\/)[^"']*)/gi,
483
+ (full, start, path) =>
484
+ alreadyPrefixed(path, pathPrefix) ? full : `${start}${pathPrefix}${path}`
485
+ );
486
+ out = out.replace(/\s+crossorigin(?:\s*=\s*(["']).*?\1)?/gi, "");
487
+ }
488
+ if (isNextDocument(out)) {
489
+ out = prefixQuotedRoots(out, pathPrefix, ["/_next/", "/__nextjs_"]);
490
+ out = out.replace(
491
+ /(url\(\s*['"]?)(\/(?!\/)[^)"']*)/gi,
492
+ (full, start, path) => {
493
+ if (alreadyPrefixed(path, pathPrefix)) return full;
494
+ if (!path.startsWith("/_next/") && !path.startsWith("/__nextjs_")) {
495
+ return full;
496
+ }
497
+ return `${start}${pathPrefix}${path}`;
498
+ }
499
+ );
500
+ } else {
501
+ out = out.replace(
502
+ /(url\(\s*['"]?)(\/(?!\/)[^)"']*)/gi,
503
+ (full, start, path) =>
504
+ alreadyPrefixed(path, pathPrefix) ? full : `${start}${pathPrefix}${path}`
505
+ );
506
+ for (const root of viteAndNext) {
507
+ const escaped = root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
508
+ out = out.replace(
509
+ new RegExp(`(?<![A-Za-z0-9])${escaped}`, "g"),
510
+ `${pathPrefix}${root}`
511
+ );
512
+ }
513
+ }
514
+ return out;
515
+ }
516
+
517
+ function httpToWsOrigin(httpUrl) {
518
+ const trimmed = httpUrl.replace(/\/$/, "");
519
+ if (trimmed.startsWith("https://")) {
520
+ return `wss://${trimmed.slice("https://".length)}`;
521
+ }
522
+ if (trimmed.startsWith("http://")) {
523
+ return `ws://${trimmed.slice("http://".length)}`;
524
+ }
525
+ return trimmed;
526
+ }
527
+
528
+ function isHtmlDocument(headers, body) {
529
+ const ct = headerGet(headers, "content-type").toLowerCase();
530
+ if (ct && !/html/.test(ct) && !/text\/plain/.test(ct)) return false;
531
+ return /<!doctype html|<html[\s>]|<body[\s>]|<\/body>|<\/html>/i.test(body);
532
+ }
533
+
534
+ function isScriptRequestPath(path) {
535
+ const p = String(path || "").split("?")[0]?.toLowerCase() || "";
536
+ return /\.(?:m?js|cjs)$/.test(p);
537
+ }
538
+
539
+ function rewriteLocalSidecarUrls(html, aiPublicBase, uiPublicBase) {
540
+ const ai = String(aiPublicBase || "").replace(/\/$/, "");
541
+ const ui = String(uiPublicBase || "").replace(/\/$/, "");
542
+ let out = html.split("__AI_SERVER_URL__").join(ai);
543
+ if (ui && ui !== ai) {
544
+ out = out
545
+ .split(`${ui}/embed-config.js`)
546
+ .join(`${ai}/embed-config.js`)
547
+ .split(`${ui}/ai-ui.iife.js`)
548
+ .join(`${ai}/ai-ui.iife.js`);
549
+ }
550
+ out = out.replace(
551
+ /https?:\/\/(?:localhost|127\.0\.0\.1):\d+(?=\/(?:embed-config\.js|ai-ui\.iife\.js|api\/(?:chat|ws)))/gi,
552
+ ai
553
+ );
554
+ return out;
555
+ }
556
+
557
+ function rewriteEmbedConfigJs(body, publicBase) {
558
+ if (!/^\s*window\.__MAINTAINER_PRO__\s*=/.test(body)) return body;
559
+ const base = String(publicBase || "").replace(/\/$/, "");
560
+ const start = body.indexOf("{");
561
+ const end = body.lastIndexOf("}");
562
+ if (start < 0 || end <= start) return body;
563
+ try {
564
+ const payload = JSON.parse(body.slice(start, end + 1));
565
+ payload.aiServerUrl = base;
566
+ payload.apiUrl = `${base}/api/chat`;
567
+ payload.aiServerWsUrl = `${httpToWsOrigin(base)}/api/ws`;
568
+ try {
569
+ payload.maintainerProUrl = new URL(base).origin;
570
+ } catch {
571
+ /* keep */
572
+ }
573
+ return `window.__MAINTAINER_PRO__=${JSON.stringify(payload)};`;
574
+ } catch {
575
+ return body;
576
+ }
577
+ }
578
+
579
+ function htmlAlreadyHasWidget(html) {
580
+ return (
581
+ html.includes("data-mp-proxy-embed") ||
582
+ html.includes("AiUi.init") ||
583
+ html.includes("ai-ui.iife.js")
584
+ );
585
+ }
586
+
587
+ function injectMaintainerProEmbed(html, aiPublicBase) {
588
+ if (htmlAlreadyHasWidget(html)) return html;
589
+ const ai = String(aiPublicBase || "").replace(/\/$/, "");
590
+ const loader = `<script data-mp-proxy-embed>(function(ai,ws){if(!ai||window.__MP_EMBED_LOADING)return;window.__MP_EMBED_LOADING=1;function load(src){return new Promise(function(resolve,reject){var s=document.createElement("script");s.src=src;s.onload=function(){resolve()};s.onerror=function(){reject(new Error(src))};(document.head||document.documentElement).appendChild(s)})}function boot(){var cfg=window.__MAINTAINER_PRO__||{};if(!window.AiUi||!window.AiUi.init)return false;window.AiUi.init({apiUrl:ai+"/api/chat",aiServerWsUrl:ws,title:"AI Assistant",maintainerProUrl:cfg.maintainerProUrl||undefined,maintainerProApiKey:cfg.maintainerProApiKey||undefined});return true}load(ai+"/embed-config.js").then(function(){return load(ai+"/ai-ui.iife.js")}).then(function(){if(boot())return;var n=0,t=setInterval(function(){n+=1;if(boot()||n>50)clearInterval(t)},100)}).catch(function(){})})(${JSON.stringify(ai)},${JSON.stringify(`${httpToWsOrigin(ai)}/api/ws`)})<\/script>`;
591
+ if (/<head[^>]*>/i.test(html)) {
592
+ return html.replace(/<head([^>]*)>/i, `<head$1>${loader}`);
593
+ }
594
+ if (/<\/html>/i.test(html)) {
595
+ return html.replace(/<\/html>/i, `</html>${loader}`);
596
+ }
597
+ if (/<\/body>/i.test(html)) {
598
+ return html.replace(/<\/body>/i, `${loader}</body>`);
599
+ }
600
+ return html + loader;
601
+ }
602
+
603
+ function injectNextProxyShim(html, publicBase) {
604
+ const prefix = proxyPathPrefix(publicBase);
605
+ if (
606
+ !prefix ||
607
+ html.includes("data-mp-proxy-next") ||
608
+ (!isNextDocument(html) && !isViteDocument(html))
609
+ ) {
610
+ return html;
611
+ }
612
+ const p = JSON.stringify(prefix);
613
+ const script = `<script data-mp-proxy-next>(function(p){if(!p)return;function add(v){if(typeof v!=="string"||!v)return v;if(v.charAt(0)==="/"&&v.charAt(1)!=="/"){if(v===p||v.indexOf(p+"/")===0)return v;var root=p.slice(0,p.lastIndexOf("/"));if(root&&(v===root||v.indexOf(root+"/")===0)return v;if(v==="/api/v1"||v.indexOf("/api/v1/")===0)return v;return p+v}try{var u=new URL(v,location.href);var host=String(u.hostname||"").toLowerCase();if(host.charAt(0)==="["&&host.charAt(host.length-1)==="]")host=host.slice(1,-1);var local=host==="localhost"||host==="127.0.0.1"||host==="::1"||host==="0.0.0.0";if(!local){if(u.pathname===p){u.pathname="/";return u.toString()}if(u.pathname.indexOf(p+"/")===0){u.pathname=u.pathname.slice(p.length)||"/";return u.toString()}return v}var samePort=String(u.port||"")===String(location.port||"");if(samePort){u.pathname=add(u.pathname);return u.toString()}if(u.pathname===p||u.pathname.indexOf(p+"/")===0){u.protocol=location.protocol;u.host=location.host;return u.toString()}}catch(e){}return v}function mapSel(sel){if(typeof sel!=="string")return sel;var pairs=[["src=\\"/_next/","src=\\""+p+"/_next/"],["href=\\"/_next/","href=\\""+p+"/_next/"]];for(var i=0;i<pairs.length;i++){if(sel.indexOf(pairs[i][0])!==-1)return sel.split(pairs[i][0]).join(pairs[i][1])}return sel}function patchQS(proto,name){var orig=proto[name];proto[name]=function(sel){var r=orig.call(this,sel);if(name==="querySelector"){if(r)return r}else if(r.length)return r;var s2=mapSel(sel);return s2===sel?r:orig.call(this,s2)}}patchQS(Document.prototype,"querySelector");patchQS(Document.prototype,"querySelectorAll");patchQS(Element.prototype,"querySelector");patchQS(Element.prototype,"querySelectorAll");var sa=Element.prototype.setAttribute;Element.prototype.setAttribute=function(n,v){if((n==="src"||n==="href")&&typeof v==="string")v=add(v);return sa.call(this,n,v)};function patchUrlProp(ctor,prop){try{var d=Object.getOwnPropertyDescriptor(ctor.prototype,prop);if(!d||!d.set)return;Object.defineProperty(ctor.prototype,prop,{configurable:true,enumerable:true,get:d.get,set:function(v){d.set.call(this,add(v))}})}catch(e){}}patchUrlProp(HTMLScriptElement,"src");patchUrlProp(HTMLLinkElement,"href");var f=window.fetch;window.fetch=function(input,init){if(typeof input==="string")input=add(input);else if(typeof Request!=="undefined"&&input instanceof Request)input=new Request(add(input.url),input);else if(typeof URL!=="undefined"&&input instanceof URL)input=new URL(add(input.href));return f.call(this,input,init)};var xo=XMLHttpRequest.prototype.open;XMLHttpRequest.prototype.open=function(m,u){if(typeof u==="string")arguments[1]=add(u);return xo.apply(this,arguments)};var ps=history.pushState.bind(history);history.pushState=function(s,t,u){if(typeof u==="string")u=add(u);return ps(s,t,u)};var rs=history.replaceState.bind(history);history.replaceState=function(s,t,u){if(typeof u==="string")u=add(u);return rs(s,t,u)};var WS=window.WebSocket;function WrappedWS(url,protocols){if(typeof url==="string")url=add(url);return protocols!==undefined?new WS(url,protocols):new WS(url)}WrappedWS.prototype=WS.prototype;WrappedWS.CONNECTING=WS.CONNECTING;WrappedWS.OPEN=WS.OPEN;WrappedWS.CLOSING=WS.CLOSING;WrappedWS.CLOSED=WS.CLOSED;window.WebSocket=WrappedWS})(${p})</script>`;
614
+ return html.replace(/<head([^>]*)>/i, `<head$1>${script}`);
615
+ }
616
+
617
+ /**
618
+ * Rewrite + gzip a local app response before it goes over the bridge WS.
619
+ *
620
+ * @param {{
621
+ * path?: string,
622
+ * slug?: string,
623
+ * publicBase?: string,
624
+ * aiPublicBase?: string,
625
+ * status?: number,
626
+ * headers?: Record<string, string>,
627
+ * body?: Buffer | string,
628
+ * acceptEncoding?: string,
629
+ * ifNoneMatch?: string,
630
+ * port?: number,
631
+ * }} opts
632
+ * @returns {{ status: number, headers: Record<string, string>, body: Buffer }}
633
+ */
634
+ export function processShareHttpResponse(opts) {
635
+ const path = String(opts?.path || "/");
636
+ const slug = String(opts?.slug || "");
637
+ const publicBase = String(opts?.publicBase || "");
638
+ const aiPublicBase = String(opts?.aiPublicBase || "");
639
+ const acceptEncoding = String(opts?.acceptEncoding || "");
640
+ const ifNoneMatch = String(opts?.ifNoneMatch || "");
641
+ const port = Number(opts?.port) || 0;
642
+ let status =
643
+ typeof opts?.status === "number" && opts.status >= 100 ? opts.status : 200;
644
+ const headers = lowerHeaders(opts?.headers || {});
645
+ let payload = Buffer.isBuffer(opts?.body)
646
+ ? opts.body
647
+ : Buffer.from(opts?.body || "");
648
+
649
+ if (headers.location) {
650
+ headers.location = rewriteLocation(headers.location, publicBase, port);
651
+ }
652
+
653
+ const cachedKey = rewriteCacheKey(publicBase, path);
654
+ const hit =
655
+ cachedKey && Date.now() - (rewriteCache.get(cachedKey)?.at || 0) < REWRITE_CACHE_MS
656
+ ? rewriteCache.get(cachedKey)
657
+ : null;
658
+ if (hit) {
659
+ return finishCachedShareResponse(hit, path, ifNoneMatch, acceptEncoding);
660
+ }
661
+
662
+ if (isScriptRequestPath(path) && isHtmlDocument(headers, payload.toString("utf8"))) {
663
+ const notFound = {
664
+ "content-type": "text/plain; charset=utf-8",
665
+ "cache-control": "private, no-store",
666
+ };
667
+ return {
668
+ status: 404,
669
+ headers: notFound,
670
+ body: Buffer.from("Not found"),
671
+ };
672
+ }
673
+
674
+ let text = payload.toString("utf8");
675
+ let mutated = false;
676
+ if (publicBase && shouldRewriteBody(headers)) {
677
+ text = prefixRootPaths(text, publicBase, headers["content-type"] || "");
678
+ mutated = true;
679
+ }
680
+ if (aiPublicBase) {
681
+ const withShareUrls = rewriteLocalSidecarUrls(text, aiPublicBase, publicBase);
682
+ if (withShareUrls !== text) {
683
+ text = withShareUrls;
684
+ mutated = true;
685
+ }
686
+ }
687
+ if (aiPublicBase && /^\s*window\.__MAINTAINER_PRO__\s*=/.test(text)) {
688
+ const next = rewriteEmbedConfigJs(text, aiPublicBase);
689
+ if (next !== text) {
690
+ text = next;
691
+ mutated = true;
692
+ }
693
+ }
694
+ if (
695
+ publicBase &&
696
+ isHtmlDocument(headers, text) &&
697
+ (isNextDocument(text) || isViteDocument(text))
698
+ ) {
699
+ const next = injectNextProxyShim(text, publicBase);
700
+ if (next !== text) {
701
+ text = next;
702
+ mutated = true;
703
+ }
704
+ }
705
+ if (slug !== "ai" && aiPublicBase && isHtmlDocument(headers, text)) {
706
+ const next = injectMaintainerProEmbed(text, aiPublicBase);
707
+ if (next !== text) {
708
+ text = next;
709
+ mutated = true;
710
+ }
711
+ }
712
+
713
+ if (mutated) {
714
+ payload = Buffer.from(text, "utf8");
715
+ delete headers["content-encoding"];
716
+ }
717
+ delete headers.etag;
718
+ delete headers["last-modified"];
719
+ const mode = applyShareCacheHeaders(headers, path);
720
+ const etag = etagFor(payload);
721
+ if (mode !== "no-store") headers.etag = etag;
722
+ if (cachedKey) rememberRewrite(cachedKey, payload, status, headers, etag);
723
+ if (mode !== "no-store" && etagMatches(ifNoneMatch, etag)) {
724
+ return notModifiedResult(headers, etag);
725
+ }
726
+ payload = gzipIfRequested(payload, headers, acceptEncoding);
727
+ headers["content-length"] = String(payload.length);
728
+ return { status, headers, body: payload };
729
+ }