@maintainer-pro/ai-bridge 0.1.35 → 0.1.36
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 +1 -1
- package/src/daemon.mjs +60 -6
- package/src/share-rewrite.mjs +19 -1
package/package.json
CHANGED
package/src/daemon.mjs
CHANGED
|
@@ -81,6 +81,11 @@ const WS_RECONNECT_MAX_MS = 30_000;
|
|
|
81
81
|
const WS_CONNECT_TIMEOUT_MS = 20_000;
|
|
82
82
|
const WS_WATCHDOG_MS = 5_000;
|
|
83
83
|
|
|
84
|
+
// Temporary: default debug in prod so we can catch leftover issues. Revisit later.
|
|
85
|
+
if (!process.env.LOG_LEVEL?.trim() && !process.env.AI_LOG_LEVEL?.trim()) {
|
|
86
|
+
process.env.LOG_LEVEL = "debug";
|
|
87
|
+
}
|
|
88
|
+
|
|
84
89
|
/** @type {import("@maintainer-pro/ai-cli").Logger} */
|
|
85
90
|
let logger = createLogger("ai-bridge");
|
|
86
91
|
const SECRET_KEY_RE =
|
|
@@ -142,6 +147,8 @@ const intakeServers = new Map();
|
|
|
142
147
|
|
|
143
148
|
/** @type {(payload: Record<string, unknown>) => boolean} */
|
|
144
149
|
let bridgeSend = () => false;
|
|
150
|
+
/** @type {() => Promise<void>} */
|
|
151
|
+
let waitForBridgeDrain = async () => {};
|
|
145
152
|
|
|
146
153
|
function activity(sandboxId, level, message) {
|
|
147
154
|
const text = String(message || "").trim();
|
|
@@ -374,7 +381,7 @@ Flags:
|
|
|
374
381
|
--help
|
|
375
382
|
|
|
376
383
|
Logging (env):
|
|
377
|
-
LOG_LEVEL=info|debug|warn|error|silent (default:
|
|
384
|
+
LOG_LEVEL=info|debug|warn|error|silent (default: debug)
|
|
378
385
|
LOG_PRETTY=0 disable pretty TTY output
|
|
379
386
|
`);
|
|
380
387
|
}
|
|
@@ -2542,7 +2549,8 @@ const embeddedChat = new Map();
|
|
|
2542
2549
|
/** @type {Map<string, Promise<object | null>>} */
|
|
2543
2550
|
const embeddedChatStarting = new Map();
|
|
2544
2551
|
|
|
2545
|
-
const PROXY_CHUNK_BYTES =
|
|
2552
|
+
const PROXY_CHUNK_BYTES = 64 * 1024;
|
|
2553
|
+
const PROXY_SEND_HIGH_WATER = 512 * 1024;
|
|
2546
2554
|
/** @type {Map<string, { req: import("node:http").ClientRequest, rewrite?: boolean, chunks?: Buffer[], headers?: Record<string, string>, portUrls?: Record<string, string> } | import("node:http").ClientRequest>} */
|
|
2547
2555
|
const proxyHttpReqs = new Map();
|
|
2548
2556
|
/** Reuse sockets to the local app — Next/Vite fetch many files per page. */
|
|
@@ -2632,6 +2640,10 @@ function replyProxyHttp(id, status, headers, body) {
|
|
|
2632
2640
|
out[String(key)] = String(value);
|
|
2633
2641
|
}
|
|
2634
2642
|
bridgeSend({ type: "proxy.http.start", id, stream, status, headers: out });
|
|
2643
|
+
void sendProxyHttpChunks(id, stream, buf);
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
async function sendProxyHttpChunks(id, stream, buf) {
|
|
2635
2647
|
for (let offset = 0; offset < buf.length; offset += PROXY_CHUNK_BYTES) {
|
|
2636
2648
|
const end = Math.min(offset + PROXY_CHUNK_BYTES, buf.length);
|
|
2637
2649
|
bridgeSend({
|
|
@@ -2641,6 +2653,7 @@ function replyProxyHttp(id, status, headers, body) {
|
|
|
2641
2653
|
data: buf.subarray(offset, end).toString("base64"),
|
|
2642
2654
|
eof: false,
|
|
2643
2655
|
});
|
|
2656
|
+
await waitForBridgeDrain();
|
|
2644
2657
|
}
|
|
2645
2658
|
bridgeSend({ type: "proxy.http.chunk", id, stream, data: "", eof: true });
|
|
2646
2659
|
}
|
|
@@ -2803,6 +2816,18 @@ async function handleAiProxyHttpFromAdmin(msg, ws) {
|
|
|
2803
2816
|
);
|
|
2804
2817
|
|
|
2805
2818
|
if (pathname === "/ai-ui.iife.js") {
|
|
2819
|
+
if (method === "GET" || method === "HEAD") {
|
|
2820
|
+
const cached = lookupShareResponse({
|
|
2821
|
+
publicBase: ctx.publicBase,
|
|
2822
|
+
path: reqPath,
|
|
2823
|
+
ifNoneMatch: ctx.ifNoneMatch,
|
|
2824
|
+
acceptEncoding: ctx.acceptEncoding,
|
|
2825
|
+
});
|
|
2826
|
+
if (cached) {
|
|
2827
|
+
replyProxyHttp(id, cached.status, cached.headers, cached.body);
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2806
2831
|
const file = findIife();
|
|
2807
2832
|
if (!file || !fs.existsSync(file)) {
|
|
2808
2833
|
sendProcessedProxyHttp(
|
|
@@ -3149,7 +3174,9 @@ function handleProxyHttpFromAdmin(msg) {
|
|
|
3149
3174
|
return;
|
|
3150
3175
|
}
|
|
3151
3176
|
if (isAiServerAppId(appId, ws)) {
|
|
3152
|
-
|
|
3177
|
+
setImmediate(() => {
|
|
3178
|
+
void handleAiProxyHttpFromAdmin(msg, ws);
|
|
3179
|
+
});
|
|
3153
3180
|
return;
|
|
3154
3181
|
}
|
|
3155
3182
|
const existing = proxyHttpReqs.get(id);
|
|
@@ -3261,7 +3288,7 @@ function handleProxyHttpFromAdmin(msg) {
|
|
|
3261
3288
|
}
|
|
3262
3289
|
const status = res.statusCode || 502;
|
|
3263
3290
|
applyShareCacheHeaders(outHeaders, path);
|
|
3264
|
-
if (shouldProcessShareResponse(outHeaders)) {
|
|
3291
|
+
if (shouldProcessShareResponse(outHeaders, path)) {
|
|
3265
3292
|
/** @type {Buffer[]} */
|
|
3266
3293
|
const chunks = [];
|
|
3267
3294
|
res.on("data", (chunk) => {
|
|
@@ -3294,14 +3321,22 @@ function handleProxyHttpFromAdmin(msg) {
|
|
|
3294
3321
|
offset += PROXY_CHUNK_BYTES
|
|
3295
3322
|
) {
|
|
3296
3323
|
const end = Math.min(offset + PROXY_CHUNK_BYTES, buf.length);
|
|
3297
|
-
bridgeSend({
|
|
3324
|
+
const queued = bridgeSend({
|
|
3298
3325
|
type: "proxy.http.chunk",
|
|
3299
3326
|
id,
|
|
3300
3327
|
stream,
|
|
3301
3328
|
data: Buffer.from(buf.subarray(offset, end)).toString("base64"),
|
|
3302
3329
|
eof: false,
|
|
3303
3330
|
});
|
|
3331
|
+
if (queued === false) res.pause();
|
|
3304
3332
|
}
|
|
3333
|
+
void waitForBridgeDrain().then(() => {
|
|
3334
|
+
try {
|
|
3335
|
+
res.resume();
|
|
3336
|
+
} catch {
|
|
3337
|
+
/* ignore */
|
|
3338
|
+
}
|
|
3339
|
+
});
|
|
3305
3340
|
});
|
|
3306
3341
|
res.on("end", () => {
|
|
3307
3342
|
proxyHttpReqs.delete(id);
|
|
@@ -5926,7 +5961,7 @@ async function main() {
|
|
|
5926
5961
|
}
|
|
5927
5962
|
|
|
5928
5963
|
logger.info(
|
|
5929
|
-
{ versions: PACKAGE_VERSIONS },
|
|
5964
|
+
{ versions: PACKAGE_VERSIONS, logLevel: logger.level },
|
|
5930
5965
|
`bridge v${PACKAGE_VERSION} starting (${formatPackageVersions()})`
|
|
5931
5966
|
);
|
|
5932
5967
|
if (usingWsFallback) {
|
|
@@ -6029,6 +6064,7 @@ async function main() {
|
|
|
6029
6064
|
return false;
|
|
6030
6065
|
}
|
|
6031
6066
|
sendChain = sendChain
|
|
6067
|
+
.then(() => waitSocketDrain(current))
|
|
6032
6068
|
.then(() => {
|
|
6033
6069
|
if (!socket || socket !== current || socket.readyState !== 1) return;
|
|
6034
6070
|
socket.send(text);
|
|
@@ -6036,7 +6072,25 @@ async function main() {
|
|
|
6036
6072
|
.catch(() => {});
|
|
6037
6073
|
return true;
|
|
6038
6074
|
};
|
|
6075
|
+
const waitSocketDrain = (current) =>
|
|
6076
|
+
new Promise((resolve) => {
|
|
6077
|
+
const tick = () => {
|
|
6078
|
+
if (!current || current.readyState !== 1) {
|
|
6079
|
+
resolve();
|
|
6080
|
+
return;
|
|
6081
|
+
}
|
|
6082
|
+
const buffered =
|
|
6083
|
+
typeof current.bufferedAmount === "number" ? current.bufferedAmount : 0;
|
|
6084
|
+
if (buffered < PROXY_SEND_HIGH_WATER) {
|
|
6085
|
+
setImmediate(resolve);
|
|
6086
|
+
return;
|
|
6087
|
+
}
|
|
6088
|
+
setTimeout(tick, 25);
|
|
6089
|
+
};
|
|
6090
|
+
tick();
|
|
6091
|
+
});
|
|
6039
6092
|
bridgeSend = sendJson;
|
|
6093
|
+
waitForBridgeDrain = () => waitSocketDrain(socket);
|
|
6040
6094
|
|
|
6041
6095
|
const clearHeartbeatTimer = () => {
|
|
6042
6096
|
if (heartbeatTimer) {
|
package/src/share-rewrite.mjs
CHANGED
|
@@ -140,7 +140,9 @@ export function shouldRewriteBody(headers) {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
/** Redirects have no Content-Type, but Location still has to stay under the share prefix. */
|
|
143
|
-
export function shouldProcessShareResponse(headers) {
|
|
143
|
+
export function shouldProcessShareResponse(headers, path = "") {
|
|
144
|
+
// Widget bundle is self-contained — rewriting it as text blocks the bridge.
|
|
145
|
+
if (isLongCacheScriptPath(path)) return false;
|
|
144
146
|
if (headerGet(headers, "location")) return true;
|
|
145
147
|
if (shouldRewriteBody(headers)) return true;
|
|
146
148
|
const ct = headerGet(headers, "content-type").toLowerCase();
|
|
@@ -1656,6 +1658,22 @@ export function processShareHttpResponse(opts) {
|
|
|
1656
1658
|
|
|
1657
1659
|
rewriteHeaderUrls(headers, publicBase, port, portUrls);
|
|
1658
1660
|
|
|
1661
|
+
if (isLongCacheScriptPath(path)) {
|
|
1662
|
+
delete headers.etag;
|
|
1663
|
+
delete headers["last-modified"];
|
|
1664
|
+
const mode = applyShareCacheHeaders(headers, path);
|
|
1665
|
+
const etag = etagFor(payload);
|
|
1666
|
+
if (mode !== "no-store") headers.etag = etag;
|
|
1667
|
+
const cachedKey = rewriteCacheKey(publicBase, path);
|
|
1668
|
+
if (cachedKey) rememberRewrite(cachedKey, payload, status, headers, etag);
|
|
1669
|
+
if (mode !== "no-store" && etagMatches(ifNoneMatch, etag)) {
|
|
1670
|
+
return notModifiedResult(headers, etag);
|
|
1671
|
+
}
|
|
1672
|
+
payload = gzipIfRequested(payload, headers, acceptEncoding);
|
|
1673
|
+
headers["content-length"] = String(payload.length);
|
|
1674
|
+
return { status, headers, body: payload };
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1659
1677
|
const cachedKey = rewriteCacheKey(publicBase, path);
|
|
1660
1678
|
const hit =
|
|
1661
1679
|
cachedKey && Date.now() - (rewriteCache.get(cachedKey)?.at || 0) < REWRITE_CACHE_MS
|