@cloudflare/sandbox 0.12.10 → 0.12.12
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/dist/bridge/index.js +23 -10
- package/dist/bridge/index.js.map +1 -1
- package/dist/{contexts-C186NnAB.d.ts → contexts-CxAJGHFf.d.ts} +6 -1
- package/dist/contexts-CxAJGHFf.d.ts.map +1 -0
- package/dist/{dist-B_eXrP83.js → dist-CqPJfREN.js} +34 -52
- package/dist/dist-CqPJfREN.js.map +1 -0
- package/dist/{errors-CpkKPbWC.js → errors-CHhwDJNh.js} +11 -1
- package/dist/errors-CHhwDJNh.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/openai/index.d.ts +1 -1
- package/dist/openai/index.d.ts.map +1 -1
- package/dist/openai/index.js +10 -34
- package/dist/openai/index.js.map +1 -1
- package/dist/opencode/index.d.ts +7 -3
- package/dist/opencode/index.d.ts.map +1 -1
- package/dist/opencode/index.js +32 -20
- package/dist/opencode/index.js.map +1 -1
- package/dist/{sandbox-Co2NYKhX.js → sandbox-BYPanejK.js} +490 -170
- package/dist/sandbox-BYPanejK.js.map +1 -0
- package/dist/{sandbox-BtaWcmmG.d.ts → sandbox-mYLglwK1.d.ts} +55 -8
- package/dist/sandbox-mYLglwK1.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/contexts-C186NnAB.d.ts.map +0 -1
- package/dist/dist-B_eXrP83.js.map +0 -1
- package/dist/errors-CpkKPbWC.js.map +0 -1
- package/dist/sandbox-BtaWcmmG.d.ts.map +0 -1
- package/dist/sandbox-Co2NYKhX.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
import { n as getHttpStatus, r as ErrorCode, t as getSuggestion } from "./errors-
|
|
1
|
+
import { S as partitionEnvVars, _ as DEFAULT_GIT_CLONE_TIMEOUT_MS, a as isWSStreamChunk, b as filterEnvVars, d as createLogger, f as createNoOpLogger, g as ResultImpl, h as Execution, i as isWSResponse, l as parseSSEFrames, m as logCanonicalEvent, n as generateRequestId, p as TraceContext, r as isWSError, t as WS_KEEPALIVE_EVENT, u as shellEscape, v as GitLogger, x as getEnvString, y as extractRepoName } from "./dist-CqPJfREN.js";
|
|
2
|
+
import { n as getHttpStatus, r as ErrorCode, t as getSuggestion } from "./errors-CHhwDJNh.js";
|
|
3
3
|
import { Container, ContainerProxy, getContainer, switchPort } from "@cloudflare/containers";
|
|
4
4
|
import { AwsClient } from "aws4fetch";
|
|
5
5
|
import { RpcSession, RpcTarget } from "capnweb";
|
|
@@ -1431,6 +1431,7 @@ var WebSocketTransport = class extends BaseTransport {
|
|
|
1431
1431
|
pending.onFirstChunk = void 0;
|
|
1432
1432
|
}
|
|
1433
1433
|
if (pending.isStreaming) this.resetStreamIdleTimeout(chunk.id, pending);
|
|
1434
|
+
if (chunk.event === WS_KEEPALIVE_EVENT) return;
|
|
1434
1435
|
if (!pending.streamController) {
|
|
1435
1436
|
if (!pending.bufferedChunks) pending.bufferedChunks = [];
|
|
1436
1437
|
const encoder$1 = new TextEncoder();
|
|
@@ -1853,14 +1854,15 @@ var FileClient = class extends BaseHttpClient {
|
|
|
1853
1854
|
* @param path - File path to write to
|
|
1854
1855
|
* @param content - Content to write
|
|
1855
1856
|
* @param sessionId - The session ID for this operation
|
|
1856
|
-
* @param options - Optional settings (encoding)
|
|
1857
|
+
* @param options - Optional settings (encoding, ownerOnly)
|
|
1857
1858
|
*/
|
|
1858
1859
|
async writeFile(path$1, content, sessionId, options) {
|
|
1859
1860
|
const data = {
|
|
1860
1861
|
path: path$1,
|
|
1861
1862
|
content,
|
|
1862
1863
|
sessionId,
|
|
1863
|
-
encoding: options?.encoding
|
|
1864
|
+
encoding: options?.encoding,
|
|
1865
|
+
...options?.ownerOnly === true && { ownerOnly: true }
|
|
1864
1866
|
};
|
|
1865
1867
|
return await this.post("/api/write", data);
|
|
1866
1868
|
}
|
|
@@ -1878,11 +1880,13 @@ var FileClient = class extends BaseHttpClient {
|
|
|
1878
1880
|
* Returns a ReadableStream of SSE events containing metadata, chunks, and completion
|
|
1879
1881
|
* @param path - File path to stream
|
|
1880
1882
|
* @param sessionId - The session ID for this operation
|
|
1883
|
+
* @param options - Optional stream encoding; base64 preserves arbitrary bytes
|
|
1881
1884
|
*/
|
|
1882
|
-
async readFileStream(path$1, sessionId) {
|
|
1885
|
+
async readFileStream(path$1, sessionId, options) {
|
|
1883
1886
|
const data = {
|
|
1884
1887
|
path: path$1,
|
|
1885
|
-
sessionId
|
|
1888
|
+
sessionId,
|
|
1889
|
+
encoding: options?.encoding
|
|
1886
1890
|
};
|
|
1887
1891
|
return await this.doStreamFetch("/api/read/stream", data);
|
|
1888
1892
|
}
|
|
@@ -2600,6 +2604,19 @@ function isDurableObjectCodeUpdateReset(error) {
|
|
|
2600
2604
|
return false;
|
|
2601
2605
|
}
|
|
2602
2606
|
/**
|
|
2607
|
+
* Whether an error was raised because the Durable Object itself was reset, by
|
|
2608
|
+
* a code update or a storage startup failure, rather than by the operation
|
|
2609
|
+
* that observed it.
|
|
2610
|
+
*/
|
|
2611
|
+
function isDurableObjectReset(error) {
|
|
2612
|
+
for (const candidate of selfAndCauses(error)) {
|
|
2613
|
+
const message = errorMessageOf(candidate);
|
|
2614
|
+
if (SUPERSEDED_ISOLATE_PATTERN.test(message)) return true;
|
|
2615
|
+
if (DO_STORAGE_STARTUP_RESET_PATTERN.test(message)) return true;
|
|
2616
|
+
}
|
|
2617
|
+
return false;
|
|
2618
|
+
}
|
|
2619
|
+
/**
|
|
2603
2620
|
* Whether an error represents a transient platform lifecycle/storage failure,
|
|
2604
2621
|
* not an application-level sandbox failure.
|
|
2605
2622
|
*/
|
|
@@ -4343,18 +4360,49 @@ async function* parseSSE(stream) {
|
|
|
4343
4360
|
* ```
|
|
4344
4361
|
*/
|
|
4345
4362
|
async function* streamFile(stream) {
|
|
4346
|
-
|
|
4347
|
-
|
|
4363
|
+
const { chunks } = await openFileStream(stream);
|
|
4364
|
+
return yield* chunks;
|
|
4365
|
+
}
|
|
4366
|
+
/**
|
|
4367
|
+
* Read the metadata event at the head of a file stream and return it with
|
|
4368
|
+
* the decoded chunks that follow. Callers that need the file size before
|
|
4369
|
+
* consuming the body, such as sizing a FixedLengthStream, use this instead of
|
|
4370
|
+
* streamFile(). Call cancel() to release the stream without reading the rest.
|
|
4371
|
+
*/
|
|
4372
|
+
async function openFileStream(stream) {
|
|
4373
|
+
const events = parseSSE(stream);
|
|
4374
|
+
const cancel = async () => {
|
|
4375
|
+
await events.return(void 0);
|
|
4376
|
+
};
|
|
4377
|
+
try {
|
|
4378
|
+
const first = await events.next();
|
|
4379
|
+
if (first.done) throw new Error("Stream ended unexpectedly");
|
|
4380
|
+
const metadata = readMetadataEvent(first.value);
|
|
4381
|
+
return {
|
|
4382
|
+
metadata,
|
|
4383
|
+
chunks: decodeChunks(events, metadata),
|
|
4384
|
+
cancel
|
|
4385
|
+
};
|
|
4386
|
+
} catch (error) {
|
|
4387
|
+
await cancel();
|
|
4388
|
+
throw error;
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4391
|
+
function readMetadataEvent(event) {
|
|
4392
|
+
switch (event.type) {
|
|
4393
|
+
case "metadata": return toFileMetadata(event);
|
|
4394
|
+
case "chunk": throw new Error("Received chunk before metadata");
|
|
4395
|
+
case "complete": throw new Error("Stream completed without metadata");
|
|
4396
|
+
case "error": throw new Error(`File streaming error: ${event.error}`);
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
async function* decodeChunks(events, initialMetadata) {
|
|
4400
|
+
let metadata = initialMetadata;
|
|
4401
|
+
for await (const event of events) switch (event.type) {
|
|
4348
4402
|
case "metadata":
|
|
4349
|
-
metadata =
|
|
4350
|
-
mimeType: event.mimeType,
|
|
4351
|
-
size: event.size,
|
|
4352
|
-
isBinary: event.isBinary,
|
|
4353
|
-
encoding: event.encoding
|
|
4354
|
-
};
|
|
4403
|
+
metadata = toFileMetadata(event);
|
|
4355
4404
|
break;
|
|
4356
4405
|
case "chunk":
|
|
4357
|
-
if (!metadata) throw new Error("Received chunk before metadata");
|
|
4358
4406
|
if (metadata.isBinary && metadata.encoding === "base64") {
|
|
4359
4407
|
const binaryString = atob(event.data);
|
|
4360
4408
|
const bytes = new Uint8Array(binaryString.length);
|
|
@@ -4362,13 +4410,19 @@ async function* streamFile(stream) {
|
|
|
4362
4410
|
yield bytes;
|
|
4363
4411
|
} else yield event.data;
|
|
4364
4412
|
break;
|
|
4365
|
-
case "complete":
|
|
4366
|
-
if (!metadata) throw new Error("Stream completed without metadata");
|
|
4367
|
-
return metadata;
|
|
4413
|
+
case "complete": return metadata;
|
|
4368
4414
|
case "error": throw new Error(`File streaming error: ${event.error}`);
|
|
4369
4415
|
}
|
|
4370
4416
|
throw new Error("Stream ended unexpectedly");
|
|
4371
4417
|
}
|
|
4418
|
+
function toFileMetadata(event) {
|
|
4419
|
+
return {
|
|
4420
|
+
mimeType: event.mimeType,
|
|
4421
|
+
size: event.size,
|
|
4422
|
+
isBinary: event.isBinary,
|
|
4423
|
+
encoding: event.encoding
|
|
4424
|
+
};
|
|
4425
|
+
}
|
|
4372
4426
|
/**
|
|
4373
4427
|
* Collect an entire file into memory from a stream
|
|
4374
4428
|
*
|
|
@@ -4871,6 +4925,10 @@ const DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
|
4871
4925
|
const DEFAULT_ECHO_SUPPRESS_TTL_MS = 2e3;
|
|
4872
4926
|
const MAX_BACKOFF_MS = 3e4;
|
|
4873
4927
|
const SYNC_CONCURRENCY = 5;
|
|
4928
|
+
const STREAM_TO_CONTAINER_THRESHOLD_BYTES = 4 * 1024 * 1024;
|
|
4929
|
+
const UPLOAD_DEBOUNCE_MS = 1500;
|
|
4930
|
+
/** Watch rejections that a retry with the same request cannot fix. */
|
|
4931
|
+
const PERMANENT_WATCH_ERRORS = new Set([ErrorCode.PERMISSION_DENIED, ErrorCode.VALIDATION_FAILED]);
|
|
4874
4932
|
/**
|
|
4875
4933
|
* Manages bidirectional sync between an R2 binding and a container directory.
|
|
4876
4934
|
*
|
|
@@ -4888,7 +4946,9 @@ var LocalMountSyncManager = class {
|
|
|
4888
4946
|
pollIntervalMs;
|
|
4889
4947
|
echoSuppressTtlMs;
|
|
4890
4948
|
snapshot = /* @__PURE__ */ new Map();
|
|
4891
|
-
|
|
4949
|
+
echoSuppressTimers = /* @__PURE__ */ new Map();
|
|
4950
|
+
uploadTimers = /* @__PURE__ */ new Map();
|
|
4951
|
+
containerToR2Queue = Promise.resolve();
|
|
4892
4952
|
pollTimer = null;
|
|
4893
4953
|
watchReconnectTimer = null;
|
|
4894
4954
|
watchAbortController = null;
|
|
@@ -4941,8 +5001,11 @@ var LocalMountSyncManager = class {
|
|
|
4941
5001
|
this.watchAbortController.abort();
|
|
4942
5002
|
this.watchAbortController = null;
|
|
4943
5003
|
}
|
|
5004
|
+
for (const timer of this.uploadTimers.values()) clearTimeout(timer);
|
|
5005
|
+
this.uploadTimers.clear();
|
|
5006
|
+
for (const timer of this.echoSuppressTimers.values()) clearTimeout(timer);
|
|
5007
|
+
this.echoSuppressTimers.clear();
|
|
4944
5008
|
this.snapshot.clear();
|
|
4945
|
-
this.echoSuppressSet.clear();
|
|
4946
5009
|
this.logger.info("Local mount sync stopped", { mountPath: this.mountPath });
|
|
4947
5010
|
}
|
|
4948
5011
|
async fullSyncR2ToContainer() {
|
|
@@ -5000,6 +5063,7 @@ var LocalMountSyncManager = class {
|
|
|
5000
5063
|
await this.ensureParentDir(containerPath);
|
|
5001
5064
|
this.suppressEcho(containerPath);
|
|
5002
5065
|
await this.transferR2ObjectToContainer(key, containerPath);
|
|
5066
|
+
this.suppressEcho(containerPath);
|
|
5003
5067
|
this.logger.debug("R2 -> Container: synced object", {
|
|
5004
5068
|
key,
|
|
5005
5069
|
action
|
|
@@ -5041,6 +5105,15 @@ var LocalMountSyncManager = class {
|
|
|
5041
5105
|
async transferR2ObjectToContainer(key, containerPath) {
|
|
5042
5106
|
const obj = await this.bucket.get(key);
|
|
5043
5107
|
if (!obj) return;
|
|
5108
|
+
if (obj.size > STREAM_TO_CONTAINER_THRESHOLD_BYTES && this.client.getTransportMode() === "rpc") {
|
|
5109
|
+
try {
|
|
5110
|
+
await this.client.files.writeFileStream(containerPath, obj.body, this.sessionId);
|
|
5111
|
+
} catch (error) {
|
|
5112
|
+
await obj.body.cancel().catch(() => {});
|
|
5113
|
+
throw error;
|
|
5114
|
+
}
|
|
5115
|
+
return;
|
|
5116
|
+
}
|
|
5044
5117
|
const arrayBuffer = await obj.arrayBuffer();
|
|
5045
5118
|
const base64 = uint8ArrayToBase64(new Uint8Array(arrayBuffer));
|
|
5046
5119
|
await this.client.files.writeFile(containerPath, base64, this.sessionId, { encoding: "base64" });
|
|
@@ -5060,8 +5133,13 @@ var LocalMountSyncManager = class {
|
|
|
5060
5133
|
this.scheduleWatchReconnect();
|
|
5061
5134
|
}).catch((error) => {
|
|
5062
5135
|
if (!this.running) return;
|
|
5136
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
5137
|
+
if (PERMANENT_WATCH_ERRORS.has(Reflect.get(err, "code"))) {
|
|
5138
|
+
this.logger.error("Container rejected the mount watch; changes made in the container will not sync to R2. Update the container image to match the SDK version.", err, { mountPath: this.mountPath });
|
|
5139
|
+
return;
|
|
5140
|
+
}
|
|
5063
5141
|
this.consecutiveWatchFailures++;
|
|
5064
|
-
this.logger.error("Container watch loop failed",
|
|
5142
|
+
this.logger.error("Container watch loop failed", err);
|
|
5065
5143
|
this.scheduleWatchReconnect();
|
|
5066
5144
|
});
|
|
5067
5145
|
}
|
|
@@ -5091,54 +5169,108 @@ var LocalMountSyncManager = class {
|
|
|
5091
5169
|
if (event.type !== "event") continue;
|
|
5092
5170
|
if (event.isDirectory) continue;
|
|
5093
5171
|
const containerPath = event.path;
|
|
5094
|
-
if (this.
|
|
5172
|
+
if (this.echoSuppressTimers.has(containerPath)) continue;
|
|
5095
5173
|
const r2Key = this.containerPathToR2Key(containerPath);
|
|
5096
5174
|
if (!r2Key) continue;
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
});
|
|
5108
|
-
break;
|
|
5109
|
-
case "delete":
|
|
5110
|
-
case "move_from":
|
|
5175
|
+
switch (event.eventType) {
|
|
5176
|
+
case "create":
|
|
5177
|
+
case "modify":
|
|
5178
|
+
case "move_to":
|
|
5179
|
+
this.scheduleUpload(containerPath, r2Key);
|
|
5180
|
+
break;
|
|
5181
|
+
case "delete":
|
|
5182
|
+
case "move_from":
|
|
5183
|
+
this.cancelScheduledUpload(containerPath);
|
|
5184
|
+
this.enqueueContainerToR2(containerPath, async () => {
|
|
5111
5185
|
await this.bucket.delete(r2Key);
|
|
5112
5186
|
this.snapshot.delete(r2Key);
|
|
5113
5187
|
this.logger.debug("Container -> R2: deleted object", {
|
|
5114
5188
|
path: containerPath,
|
|
5115
5189
|
key: r2Key
|
|
5116
5190
|
});
|
|
5117
|
-
|
|
5118
|
-
|
|
5191
|
+
});
|
|
5192
|
+
break;
|
|
5193
|
+
}
|
|
5194
|
+
}
|
|
5195
|
+
}
|
|
5196
|
+
/**
|
|
5197
|
+
* Upload a changed file once its burst of watch events settles. Each new
|
|
5198
|
+
* event for the path restarts the wait.
|
|
5199
|
+
*/
|
|
5200
|
+
scheduleUpload(containerPath, r2Key) {
|
|
5201
|
+
this.cancelScheduledUpload(containerPath);
|
|
5202
|
+
const timer = setTimeout(() => {
|
|
5203
|
+
this.uploadTimers.delete(containerPath);
|
|
5204
|
+
this.enqueueContainerToR2(containerPath, async () => {
|
|
5205
|
+
await this.uploadFileToR2(containerPath, r2Key);
|
|
5206
|
+
this.logger.debug("Container -> R2: synced file", {
|
|
5207
|
+
path: containerPath,
|
|
5208
|
+
key: r2Key
|
|
5209
|
+
});
|
|
5210
|
+
});
|
|
5211
|
+
}, UPLOAD_DEBOUNCE_MS);
|
|
5212
|
+
this.uploadTimers.set(containerPath, timer);
|
|
5213
|
+
}
|
|
5214
|
+
cancelScheduledUpload(containerPath) {
|
|
5215
|
+
const timer = this.uploadTimers.get(containerPath);
|
|
5216
|
+
if (timer !== void 0) {
|
|
5217
|
+
clearTimeout(timer);
|
|
5218
|
+
this.uploadTimers.delete(containerPath);
|
|
5219
|
+
}
|
|
5220
|
+
}
|
|
5221
|
+
enqueueContainerToR2(containerPath, operation) {
|
|
5222
|
+
this.containerToR2Queue = this.containerToR2Queue.then(async () => {
|
|
5223
|
+
if (!this.running) return;
|
|
5224
|
+
try {
|
|
5225
|
+
await operation();
|
|
5119
5226
|
} catch (error) {
|
|
5120
5227
|
this.logger.error(`Container -> R2 sync failed for ${containerPath}`, error instanceof Error ? error : new Error(String(error)));
|
|
5121
5228
|
}
|
|
5122
|
-
}
|
|
5229
|
+
});
|
|
5123
5230
|
}
|
|
5124
5231
|
/**
|
|
5125
|
-
*
|
|
5126
|
-
*
|
|
5232
|
+
* Stream a container file into R2, then update the local snapshot so the
|
|
5233
|
+
* next poll cycle doesn't echo the write back.
|
|
5127
5234
|
*/
|
|
5128
5235
|
async uploadFileToR2(containerPath, r2Key) {
|
|
5129
|
-
const
|
|
5130
|
-
await this.
|
|
5236
|
+
const file = await openFileStream(await this.client.files.readFileStream(containerPath, this.sessionId, { encoding: "base64" }));
|
|
5237
|
+
if (file.metadata.isBinary && file.metadata.encoding === "base64") await this.putFileStream(r2Key, file);
|
|
5238
|
+
else {
|
|
5239
|
+
await file.cancel();
|
|
5240
|
+
const result = await this.client.files.readFile(containerPath, this.sessionId, { encoding: "base64" });
|
|
5241
|
+
await this.bucket.put(r2Key, base64ToUint8Array(result.content));
|
|
5242
|
+
}
|
|
5131
5243
|
const head = await this.bucket.head(r2Key);
|
|
5132
5244
|
if (head) this.snapshot.set(r2Key, {
|
|
5133
5245
|
etag: head.etag,
|
|
5134
5246
|
size: head.size
|
|
5135
5247
|
});
|
|
5136
5248
|
}
|
|
5249
|
+
async putFileStream(r2Key, file) {
|
|
5250
|
+
const body = new ReadableStream({
|
|
5251
|
+
async pull(controller) {
|
|
5252
|
+
const next = await file.chunks.next();
|
|
5253
|
+
if (next.done) controller.close();
|
|
5254
|
+
else if (typeof next.value === "string") controller.error(/* @__PURE__ */ new Error("Expected binary file chunks"));
|
|
5255
|
+
else controller.enqueue(next.value);
|
|
5256
|
+
},
|
|
5257
|
+
cancel: () => file.cancel()
|
|
5258
|
+
});
|
|
5259
|
+
const sized = new FixedLengthStream(file.metadata.size);
|
|
5260
|
+
const pipeAbort = new AbortController();
|
|
5261
|
+
body.pipeTo(sized.writable, { signal: pipeAbort.signal }).catch(() => {});
|
|
5262
|
+
try {
|
|
5263
|
+
await this.bucket.put(r2Key, sized.readable);
|
|
5264
|
+
} catch (error) {
|
|
5265
|
+
pipeAbort.abort(error);
|
|
5266
|
+
throw error;
|
|
5267
|
+
}
|
|
5268
|
+
}
|
|
5137
5269
|
suppressEcho(containerPath) {
|
|
5138
|
-
this.
|
|
5139
|
-
setTimeout(() => {
|
|
5140
|
-
this.
|
|
5141
|
-
}, this.echoSuppressTtlMs);
|
|
5270
|
+
clearTimeout(this.echoSuppressTimers.get(containerPath));
|
|
5271
|
+
this.echoSuppressTimers.set(containerPath, setTimeout(() => {
|
|
5272
|
+
this.echoSuppressTimers.delete(containerPath);
|
|
5273
|
+
}, this.echoSuppressTtlMs));
|
|
5142
5274
|
}
|
|
5143
5275
|
r2KeyToContainerPath(key) {
|
|
5144
5276
|
let relativePath = key;
|
|
@@ -5187,8 +5319,12 @@ async function forwardPreviewRequest(tcpPort, request, lifecycle) {
|
|
|
5187
5319
|
};
|
|
5188
5320
|
} catch (error) {
|
|
5189
5321
|
settleForward();
|
|
5322
|
+
if (isDurableObjectReset(error)) throw error;
|
|
5190
5323
|
if (error instanceof Error && error.message.includes("Network connection lost.")) return { status: "network-lost" };
|
|
5191
|
-
|
|
5324
|
+
return {
|
|
5325
|
+
status: "failed",
|
|
5326
|
+
error
|
|
5327
|
+
};
|
|
5192
5328
|
}
|
|
5193
5329
|
}
|
|
5194
5330
|
function bridgePreviewWebSocket(response, lifecycle, settleForward) {
|
|
@@ -6655,6 +6791,23 @@ function isTunnelAlreadyRunningError(error) {
|
|
|
6655
6791
|
return hasErrorCode(error, "TUNNEL_ALREADY_RUNNING");
|
|
6656
6792
|
}
|
|
6657
6793
|
/**
|
|
6794
|
+
* The run holding `request.port` when the container rejected `request`
|
|
6795
|
+
* with a port conflict. Containers older than this SDK omit
|
|
6796
|
+
* `activeTunnelId`, which `stopTunnelRun` needs, so their conflicts yield
|
|
6797
|
+
* `null`.
|
|
6798
|
+
*/
|
|
6799
|
+
function conflictingRunOnPort(error, request) {
|
|
6800
|
+
if (!hasErrorCode(error, "TUNNEL_RUN_CONFLICT")) return null;
|
|
6801
|
+
const context = error.context;
|
|
6802
|
+
const runId = context?.activeRunId;
|
|
6803
|
+
const tunnelId = context?.activeTunnelId;
|
|
6804
|
+
if (context?.port !== request.port || typeof runId !== "string" || typeof tunnelId !== "string" || runId === request.runId) return null;
|
|
6805
|
+
return {
|
|
6806
|
+
tunnelId,
|
|
6807
|
+
runId
|
|
6808
|
+
};
|
|
6809
|
+
}
|
|
6810
|
+
/**
|
|
6658
6811
|
* Concrete `TunnelsHandler` implementation.
|
|
6659
6812
|
*
|
|
6660
6813
|
* Extends `RpcTarget` for forward compatibility with direct Workers RPC
|
|
@@ -6768,6 +6921,7 @@ var TunnelsRpcTarget = class extends RpcTarget$1 {
|
|
|
6768
6921
|
const interrupted = translateTunnelInterruption(error, "provisioning", "unknown");
|
|
6769
6922
|
if (!interrupted || options?.name || !interrupted.context.retryable) throw error;
|
|
6770
6923
|
if (recoveryAttempts >= TUNNEL_GET_MAX_RECOVERY_ATTEMPTS) {
|
|
6924
|
+
await this.#stopAbandonedQuickRun(port, recovery.quickRun);
|
|
6771
6925
|
await this.#clearQuickTunnelStorage(port);
|
|
6772
6926
|
throw createTunnelInterruptedError({
|
|
6773
6927
|
reason: "recovery_exhausted",
|
|
@@ -6779,10 +6933,50 @@ var TunnelsRpcTarget = class extends RpcTarget$1 {
|
|
|
6779
6933
|
});
|
|
6780
6934
|
}
|
|
6781
6935
|
recoveryAttempts++;
|
|
6782
|
-
if (interrupted.context.reason === "runtime_replaced")
|
|
6936
|
+
if (interrupted.context.reason === "runtime_replaced") {
|
|
6937
|
+
await this.#stopAbandonedQuickRun(port, recovery.quickRun);
|
|
6938
|
+
recovery.quickRun = void 0;
|
|
6939
|
+
}
|
|
6783
6940
|
await this.#clearQuickTunnelStorage(port);
|
|
6784
6941
|
}
|
|
6785
6942
|
}
|
|
6943
|
+
/**
|
|
6944
|
+
* Stop a quick tunnel run that `get()` is giving up on, so its
|
|
6945
|
+
* cloudflared does not keep holding the port. Best effort: the run may
|
|
6946
|
+
* have died with a replaced container.
|
|
6947
|
+
*/
|
|
6948
|
+
async #stopAbandonedQuickRun(port, run) {
|
|
6949
|
+
if (!run) return;
|
|
6950
|
+
try {
|
|
6951
|
+
await this.#host.client.tunnels.stopTunnelRun(run);
|
|
6952
|
+
} catch (error) {
|
|
6953
|
+
this.#host.logger.warn("tunnel.get: abandoned run cleanup failed", {
|
|
6954
|
+
port,
|
|
6955
|
+
...run,
|
|
6956
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6957
|
+
});
|
|
6958
|
+
}
|
|
6959
|
+
}
|
|
6960
|
+
/**
|
|
6961
|
+
* Start or replay a quick tunnel run. The Durable Object owns every
|
|
6962
|
+
* tunnel run in its container and holds the port lock, so a different
|
|
6963
|
+
* run already on the port lost its storage record. Stop it and retry
|
|
6964
|
+
* once instead of failing every later `get()` for the port.
|
|
6965
|
+
*/
|
|
6966
|
+
async #ensureQuickTunnelRun(request) {
|
|
6967
|
+
try {
|
|
6968
|
+
return await this.#host.client.tunnels.ensureTunnelRun(request);
|
|
6969
|
+
} catch (error) {
|
|
6970
|
+
const orphan = conflictingRunOnPort(error, request);
|
|
6971
|
+
if (!orphan) throw error;
|
|
6972
|
+
this.#host.logger.warn("tunnel.get: stopping orphaned run on port", {
|
|
6973
|
+
port: request.port,
|
|
6974
|
+
...orphan
|
|
6975
|
+
});
|
|
6976
|
+
await this.#host.client.tunnels.stopTunnelRun(orphan);
|
|
6977
|
+
return await this.#host.client.tunnels.ensureTunnelRun(request);
|
|
6978
|
+
}
|
|
6979
|
+
}
|
|
6786
6980
|
async #clearQuickTunnelStorage(port) {
|
|
6787
6981
|
await this.#host.storage.transaction(async (txn) => {
|
|
6788
6982
|
const map = await readMap(txn);
|
|
@@ -6863,7 +7057,7 @@ var TunnelsRpcTarget = class extends RpcTarget$1 {
|
|
|
6863
7057
|
let ensureResult;
|
|
6864
7058
|
let runtime = runtimeBeforeAdmission;
|
|
6865
7059
|
try {
|
|
6866
|
-
ensureResult = await this.#
|
|
7060
|
+
ensureResult = await this.#ensureQuickTunnelRun({
|
|
6867
7061
|
tunnelId,
|
|
6868
7062
|
runId,
|
|
6869
7063
|
mode: "quick",
|
|
@@ -7092,13 +7286,33 @@ var TunnelsRpcTarget = class extends RpcTarget$1 {
|
|
|
7092
7286
|
return Object.values(map);
|
|
7093
7287
|
}
|
|
7094
7288
|
};
|
|
7095
|
-
|
|
7289
|
+
/**
|
|
7290
|
+
* Per-port serialization lock. Any operation that mutates the tunnel for
|
|
7291
|
+
* a given port — get() on a cache miss, destroy(), the exit callback —
|
|
7292
|
+
* queues behind the previous operation on the same port. Two
|
|
7293
|
+
* consequences:
|
|
7294
|
+
*
|
|
7295
|
+
* - get(8080) followed by destroy(8080) is well-ordered: the destroy
|
|
7296
|
+
* observes whatever the get just wrote, even though both yield to
|
|
7297
|
+
* external RPCs in the middle.
|
|
7298
|
+
* - Two concurrent get(8080) calls share the first call's record: the
|
|
7299
|
+
* second runs after the first writes storage and takes the hit branch
|
|
7300
|
+
* (so no double cloudflared spawn).
|
|
7301
|
+
*
|
|
7302
|
+
* The lock is a plain Promise chain keyed by port. `transaction()` on the
|
|
7303
|
+
* storage write still matters for *cross-port* writes — a get(8080) and
|
|
7304
|
+
* get(8081) running in parallel are independent here.
|
|
7305
|
+
*/
|
|
7306
|
+
function createPortLock() {
|
|
7096
7307
|
const portLocks = /* @__PURE__ */ new Map();
|
|
7097
|
-
|
|
7308
|
+
return (port, fn) => {
|
|
7098
7309
|
const next = (portLocks.get(port) ?? Promise.resolve()).then(fn, fn);
|
|
7099
7310
|
portLocks.set(port, next.catch(() => void 0));
|
|
7100
7311
|
return next;
|
|
7101
7312
|
};
|
|
7313
|
+
}
|
|
7314
|
+
function createTunnelsHandler(host) {
|
|
7315
|
+
const withPortLock = host.withPortLock ?? createPortLock();
|
|
7102
7316
|
const tunnels = new TunnelsRpcTarget(host, withPortLock);
|
|
7103
7317
|
const handleTunnelExit = async (id, port, exitCode, runId) => {
|
|
7104
7318
|
const startTime = Date.now();
|
|
@@ -7185,7 +7399,7 @@ function createTunnelsHandler(host) {
|
|
|
7185
7399
|
* This file is auto-updated by .github/changeset-version.ts during releases
|
|
7186
7400
|
* DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
|
|
7187
7401
|
*/
|
|
7188
|
-
const SDK_VERSION = "0.12.
|
|
7402
|
+
const SDK_VERSION = "0.12.12";
|
|
7189
7403
|
|
|
7190
7404
|
//#endregion
|
|
7191
7405
|
//#region src/sandbox.ts
|
|
@@ -7194,12 +7408,32 @@ const ACTIVE_PREVIEW_PORTS_STORAGE_KEY = "activePreviewPorts";
|
|
|
7194
7408
|
const CONTAINER_PROXY_CLASS_NAME = "ContainerProxy";
|
|
7195
7409
|
const S3_CREDENTIAL_PROXY_HOST = "s3-credential-proxy.internal";
|
|
7196
7410
|
const S3_CREDENTIAL_PROXY_DIAGNOSTIC_HOST = "s3-credential-proxy.sandbox.test";
|
|
7197
|
-
|
|
7198
|
-
Object.defineProperty(ContainerProxyOutboundTarget, "name", { value: CONTAINER_PROXY_CLASS_NAME });
|
|
7199
|
-
ContainerProxyOutboundTarget.outboundHandlers = {
|
|
7411
|
+
const SDK_OUTBOUND_HANDLERS = {
|
|
7200
7412
|
r2EgressMount: r2EgressHandler,
|
|
7201
7413
|
s3CredentialProxyMount: s3CredentialProxyHandler
|
|
7202
7414
|
};
|
|
7415
|
+
var ContainerProxyOutboundTarget = class extends Container {};
|
|
7416
|
+
Object.defineProperty(ContainerProxyOutboundTarget, "name", { value: CONTAINER_PROXY_CLASS_NAME });
|
|
7417
|
+
ContainerProxyOutboundTarget.outboundHandlers = SDK_OUTBOUND_HANDLERS;
|
|
7418
|
+
function noRestoredMountRoutes() {
|
|
7419
|
+
return {
|
|
7420
|
+
r2Buckets: {},
|
|
7421
|
+
credentialProxyMounts: {}
|
|
7422
|
+
};
|
|
7423
|
+
}
|
|
7424
|
+
/**
|
|
7425
|
+
* Read the bucket mount routes that the base Container restored from storage
|
|
7426
|
+
* in its constructor. `outboundByHostOverrides` is private in its types.
|
|
7427
|
+
*/
|
|
7428
|
+
function readRestoredMountRoutes(container) {
|
|
7429
|
+
const overrides = container.outboundByHostOverrides ?? {};
|
|
7430
|
+
const r2 = overrides["r2.internal"];
|
|
7431
|
+
const credentialProxy = overrides[S3_CREDENTIAL_PROXY_HOST];
|
|
7432
|
+
return {
|
|
7433
|
+
r2Buckets: r2?.method === "r2EgressMount" ? { ...r2.params?.buckets } : {},
|
|
7434
|
+
credentialProxyMounts: credentialProxy?.method === "s3CredentialProxyMount" ? { ...credentialProxy.params?.mounts } : {}
|
|
7435
|
+
};
|
|
7436
|
+
}
|
|
7203
7437
|
/**
|
|
7204
7438
|
* SDK-level ContainerProxy that directly dispatches SDK-internal mount hosts
|
|
7205
7439
|
* (r2.internal, s3-credential-proxy.internal) without relying on
|
|
@@ -7527,6 +7761,7 @@ var Sandbox = class Sandbox extends Container {
|
|
|
7527
7761
|
tunnelsHandler = null;
|
|
7528
7762
|
tunnelExitHandler = null;
|
|
7529
7763
|
destroyAllTunnels = null;
|
|
7764
|
+
tunnelPortLock = createPortLock();
|
|
7530
7765
|
controlCallback;
|
|
7531
7766
|
normalizeId = false;
|
|
7532
7767
|
defaultSession = null;
|
|
@@ -7536,6 +7771,11 @@ var Sandbox = class Sandbox extends Container {
|
|
|
7536
7771
|
logger;
|
|
7537
7772
|
keepAliveEnabled = false;
|
|
7538
7773
|
activeMounts = /* @__PURE__ */ new Map();
|
|
7774
|
+
restoredMountRoutes = noRestoredMountRoutes();
|
|
7775
|
+
staleMountRoutes = {
|
|
7776
|
+
r2: false,
|
|
7777
|
+
credentialProxy: false
|
|
7778
|
+
};
|
|
7539
7779
|
mountOperationQueue = Promise.resolve();
|
|
7540
7780
|
currentRuntime;
|
|
7541
7781
|
currentLifetime;
|
|
@@ -7684,7 +7924,15 @@ var Sandbox = class Sandbox extends Container {
|
|
|
7684
7924
|
return this.createSandboxClient();
|
|
7685
7925
|
}
|
|
7686
7926
|
constructor(ctx, env$1) {
|
|
7927
|
+
if (ctx.exports?.ContainerProxy) {
|
|
7928
|
+
const registry = new.target;
|
|
7929
|
+
registry.outboundHandlers = {
|
|
7930
|
+
...registry.outboundHandlers,
|
|
7931
|
+
...SDK_OUTBOUND_HANDLERS
|
|
7932
|
+
};
|
|
7933
|
+
}
|
|
7687
7934
|
super(ctx, env$1);
|
|
7935
|
+
this.restoredMountRoutes = readRestoredMountRoutes(this);
|
|
7688
7936
|
const envObj = env$1;
|
|
7689
7937
|
["SANDBOX_LOG_LEVEL", "SANDBOX_LOG_FORMAT"].forEach((key) => {
|
|
7690
7938
|
if (envObj?.[key]) this.envVars[key] = String(envObj[key]);
|
|
@@ -8062,23 +8310,18 @@ var Sandbox = class Sandbox extends Container {
|
|
|
8062
8310
|
const prefix = options.prefix;
|
|
8063
8311
|
let mountOutcome = "error";
|
|
8064
8312
|
let mountError;
|
|
8065
|
-
let passwordFilePath;
|
|
8066
|
-
let additionalHeaderFilePath;
|
|
8067
8313
|
try {
|
|
8068
8314
|
validateBucketBindingName(bucket, mountPath);
|
|
8069
8315
|
this.validateMountPath(mountPath);
|
|
8070
8316
|
this.validateProtectedS3fsOptions(options.s3fsOptions, "R2 binding");
|
|
8317
|
+
const restoredRoute = this.restoredMountRoutes.r2Buckets[bucket];
|
|
8318
|
+
if (restoredRoute && (restoredRoute.prefix !== prefix || (restoredRoute.readOnly ?? false) !== (options.readOnly ?? false))) throw new InvalidMountConfigError(`R2 binding "${bucket}" is already mounted with a different prefix or readOnly setting. Mount the same binding only once, or use the same settings for additional mounts.`);
|
|
8071
8319
|
for (const [existingMountPath, mountInfo$1] of this.activeMounts) {
|
|
8072
8320
|
if (mountInfo$1.mountType === "r2-egress" && mountInfo$1.bucket === bucket && mountInfo$1.prefix !== prefix) throw new InvalidMountConfigError(`R2 binding "${bucket}" is already mounted at ${existingMountPath} with a different prefix. Mount the same binding only once, or use the same prefix for additional mounts.`);
|
|
8073
8321
|
if (mountInfo$1.mountType === "r2-egress" && mountInfo$1.bucket === bucket && mountInfo$1.readOnly !== (options.readOnly ?? false)) throw new InvalidMountConfigError(`R2 binding "${bucket}" is already mounted at ${existingMountPath} with a different readOnly setting. Mount the same binding only once, or use the same readOnly value for additional mounts.`);
|
|
8074
8322
|
}
|
|
8075
|
-
passwordFilePath = this.generatePasswordFilePath();
|
|
8076
|
-
additionalHeaderFilePath = this.generateS3FSAdditionalHeaderFilePath();
|
|
8077
|
-
await this.createPasswordFile(passwordFilePath, bucket, {
|
|
8078
|
-
accessKeyId: "x",
|
|
8079
|
-
secretAccessKey: "x"
|
|
8080
|
-
});
|
|
8081
|
-
await this.createDisableExpectHeaderFile(additionalHeaderFilePath);
|
|
8323
|
+
const passwordFilePath = this.generatePasswordFilePath();
|
|
8324
|
+
const additionalHeaderFilePath = this.generateS3FSAdditionalHeaderFilePath();
|
|
8082
8325
|
const mountInfo = {
|
|
8083
8326
|
mountId: crypto.randomUUID(),
|
|
8084
8327
|
mountType: "r2-egress",
|
|
@@ -8091,48 +8334,47 @@ var Sandbox = class Sandbox extends Container {
|
|
|
8091
8334
|
readOnly: options.readOnly ?? false
|
|
8092
8335
|
};
|
|
8093
8336
|
this.activeMounts.set(mountPath, mountInfo);
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8337
|
+
try {
|
|
8338
|
+
await this.createPasswordFile(passwordFilePath, bucket, {
|
|
8339
|
+
accessKeyId: "x",
|
|
8340
|
+
secretAccessKey: "x"
|
|
8341
|
+
});
|
|
8342
|
+
await this.createDisableExpectHeaderFile(additionalHeaderFilePath);
|
|
8343
|
+
await this.configureR2EgressOutbound(this.getR2EgressParams());
|
|
8344
|
+
await this.execInternal(`mkdir -p ${shellEscape(mountPath)}`);
|
|
8345
|
+
const s3fsSource = bucket;
|
|
8346
|
+
const optionsStr = shellEscape(serializeS3fsOptions({
|
|
8347
|
+
passwd_file: passwordFilePath,
|
|
8348
|
+
...R2_DEFAULT_S3FS_OPTIONS,
|
|
8349
|
+
...parseS3fsOptions(resolveS3fsOptions("r2", options.s3fsOptions)),
|
|
8350
|
+
use_path_request_style: true,
|
|
8351
|
+
url: "http://r2.internal",
|
|
8352
|
+
ahbe_conf: additionalHeaderFilePath,
|
|
8353
|
+
...options.readOnly ? { ro: true } : {}
|
|
8354
|
+
}));
|
|
8355
|
+
const mountCmd = `s3fs ${shellEscape(s3fsSource)} ${shellEscape(mountPath)} -o ${optionsStr}`;
|
|
8356
|
+
this.logger.debug("r2-egress: running s3fs", { mountCmd });
|
|
8357
|
+
const result = await this.execInternal(mountCmd);
|
|
8358
|
+
this.logger.debug("r2-egress: s3fs exited", {
|
|
8359
|
+
exitCode: result.exitCode,
|
|
8360
|
+
stdout: result.stdout,
|
|
8361
|
+
stderr: result.stderr
|
|
8362
|
+
});
|
|
8363
|
+
if (result.exitCode !== 0) throw new S3FSMountError(`S3FS mount failed: ${result.stderr || result.stdout || "Unknown error"}`);
|
|
8364
|
+
const mountpointCheck = await this.execInternal(`mountpoint -q ${shellEscape(mountPath)} && echo 'FUSE_MOUNTED' || echo 'NOT_FUSE_MOUNTED'`);
|
|
8365
|
+
this.logger.debug("r2-egress: mountpoint check", {
|
|
8366
|
+
stdout: mountpointCheck.stdout.trim(),
|
|
8367
|
+
exitCode: mountpointCheck.exitCode
|
|
8368
|
+
});
|
|
8369
|
+
if (mountpointCheck.stdout.trim() !== "FUSE_MOUNTED") throw new S3FSMountError(`s3fs exited 0 but mount was not established at ${mountPath}`);
|
|
8370
|
+
} catch (error) {
|
|
8371
|
+
await this.releaseFailedR2EgressMount(mountInfo);
|
|
8372
|
+
throw error;
|
|
8373
|
+
}
|
|
8121
8374
|
mountInfo.mounted = true;
|
|
8122
8375
|
mountOutcome = "success";
|
|
8123
8376
|
} catch (error) {
|
|
8124
8377
|
mountError = error instanceof Error ? error : new Error(String(error));
|
|
8125
|
-
const failedMount = this.activeMounts.get(mountPath);
|
|
8126
|
-
this.activeMounts.delete(mountPath);
|
|
8127
|
-
if (failedMount?.mountType === "r2-egress") {
|
|
8128
|
-
await this.deletePasswordFile(failedMount.passwordFilePath).catch(() => {});
|
|
8129
|
-
if (failedMount.additionalHeaderFilePath) await this.deleteAdditionalHeaderFile(failedMount.additionalHeaderFilePath).catch(() => {});
|
|
8130
|
-
} else {
|
|
8131
|
-
if (passwordFilePath) await this.deletePasswordFile(passwordFilePath).catch(() => {});
|
|
8132
|
-
if (additionalHeaderFilePath) await this.deleteAdditionalHeaderFile(additionalHeaderFilePath).catch(() => {});
|
|
8133
|
-
}
|
|
8134
|
-
const remainingParams = this.getR2EgressParams();
|
|
8135
|
-
await this.configureR2EgressOutbound(remainingParams).catch(() => {});
|
|
8136
8378
|
throw error;
|
|
8137
8379
|
} finally {
|
|
8138
8380
|
logCanonicalEvent(this.logger, {
|
|
@@ -8148,6 +8390,16 @@ var Sandbox = class Sandbox extends Container {
|
|
|
8148
8390
|
}
|
|
8149
8391
|
}
|
|
8150
8392
|
/**
|
|
8393
|
+
* Undo a failed R2 binding mount: drop its reservation and support files,
|
|
8394
|
+
* then rebuild the `r2.internal` routes from the remaining mounts.
|
|
8395
|
+
*/
|
|
8396
|
+
async releaseFailedR2EgressMount(mountInfo) {
|
|
8397
|
+
this.activeMounts.delete(mountInfo.mountPath);
|
|
8398
|
+
await this.deletePasswordFile(mountInfo.passwordFilePath);
|
|
8399
|
+
if (mountInfo.additionalHeaderFilePath) await this.deleteAdditionalHeaderFile(mountInfo.additionalHeaderFilePath);
|
|
8400
|
+
await this.configureR2EgressOutbound(this.getR2EgressParams()).catch(() => {});
|
|
8401
|
+
}
|
|
8402
|
+
/**
|
|
8151
8403
|
* Production mount: S3FS-FUSE inside the container
|
|
8152
8404
|
*/
|
|
8153
8405
|
async mountBucketFuse(bucket, mountPath, options) {
|
|
@@ -8155,10 +8407,7 @@ var Sandbox = class Sandbox extends Container {
|
|
|
8155
8407
|
const prefix = options.prefix;
|
|
8156
8408
|
let mountOutcome = "error";
|
|
8157
8409
|
let mountError;
|
|
8158
|
-
let passwordFilePath;
|
|
8159
|
-
let additionalHeaderFilePath;
|
|
8160
8410
|
let provider = null;
|
|
8161
|
-
let dirExisted = true;
|
|
8162
8411
|
try {
|
|
8163
8412
|
this.validateMountOptions(bucket, mountPath, {
|
|
8164
8413
|
...options,
|
|
@@ -8180,8 +8429,8 @@ var Sandbox = class Sandbox extends Container {
|
|
|
8180
8429
|
});
|
|
8181
8430
|
const credentialProxyEnabled = options.credentialProxy === true;
|
|
8182
8431
|
if (credentialProxyEnabled) this.validateProtectedS3fsOptions(options.s3fsOptions, "credential proxy", ["ahbe_conf", "use_path_request_style"]);
|
|
8183
|
-
passwordFilePath = this.generatePasswordFilePath();
|
|
8184
|
-
|
|
8432
|
+
const passwordFilePath = this.generatePasswordFilePath();
|
|
8433
|
+
const additionalHeaderFilePath = credentialProxyEnabled ? this.generateS3FSAdditionalHeaderFilePath() : void 0;
|
|
8185
8434
|
const mountId = crypto.randomUUID();
|
|
8186
8435
|
const mountInfo = {
|
|
8187
8436
|
mountId,
|
|
@@ -8204,55 +8453,37 @@ var Sandbox = class Sandbox extends Container {
|
|
|
8204
8453
|
} } : {}
|
|
8205
8454
|
};
|
|
8206
8455
|
this.activeMounts.set(mountPath, mountInfo);
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8456
|
+
let dirExisted = true;
|
|
8457
|
+
try {
|
|
8458
|
+
await this.createPasswordFile(passwordFilePath, bucket, credentialProxyEnabled ? {
|
|
8459
|
+
accessKeyId: "x",
|
|
8460
|
+
secretAccessKey: "x"
|
|
8461
|
+
} : credentials);
|
|
8462
|
+
if (credentialProxyEnabled) {
|
|
8463
|
+
if (additionalHeaderFilePath) await this.createDisableExpectHeaderFile(additionalHeaderFilePath);
|
|
8464
|
+
await this.configureS3CredentialProxyOutbound(this.getS3CredentialProxyParams());
|
|
8465
|
+
}
|
|
8466
|
+
dirExisted = (await this.execInternal(`test -d ${shellEscape(mountPath)}`)).exitCode === 0;
|
|
8467
|
+
await this.execInternal(`mkdir -p ${shellEscape(mountPath)}`);
|
|
8468
|
+
const effectiveOptions = credentialProxyEnabled ? {
|
|
8469
|
+
...options,
|
|
8470
|
+
endpoint: `http://${S3_CREDENTIAL_PROXY_HOST}/${mountId}`,
|
|
8471
|
+
s3fsOptions: [
|
|
8472
|
+
...provider === "r2" ? R2_DEFAULT_S3FS_OPTION_ENTRIES : [],
|
|
8473
|
+
...options.s3fsOptions ?? [],
|
|
8474
|
+
...additionalHeaderFilePath ? [`ahbe_conf=${additionalHeaderFilePath}`] : [],
|
|
8475
|
+
"use_path_request_style"
|
|
8476
|
+
]
|
|
8477
|
+
} : options;
|
|
8478
|
+
await this.executeS3FSMount(s3fsSource, mountPath, effectiveOptions, provider, passwordFilePath);
|
|
8479
|
+
} catch (error) {
|
|
8480
|
+
await this.releaseFailedFuseMount(mountInfo, !dirExisted);
|
|
8481
|
+
throw error;
|
|
8214
8482
|
}
|
|
8215
|
-
dirExisted = (await this.execInternal(`test -d ${shellEscape(mountPath)}`)).exitCode === 0;
|
|
8216
|
-
await this.execInternal(`mkdir -p ${shellEscape(mountPath)}`);
|
|
8217
|
-
const effectiveOptions = credentialProxyEnabled ? {
|
|
8218
|
-
...options,
|
|
8219
|
-
endpoint: `http://${S3_CREDENTIAL_PROXY_HOST}/${mountId}`,
|
|
8220
|
-
s3fsOptions: [
|
|
8221
|
-
...provider === "r2" ? R2_DEFAULT_S3FS_OPTION_ENTRIES : [],
|
|
8222
|
-
...options.s3fsOptions ?? [],
|
|
8223
|
-
...additionalHeaderFilePath ? [`ahbe_conf=${additionalHeaderFilePath}`] : [],
|
|
8224
|
-
"use_path_request_style"
|
|
8225
|
-
]
|
|
8226
|
-
} : options;
|
|
8227
|
-
await this.executeS3FSMount(s3fsSource, mountPath, effectiveOptions, provider, passwordFilePath);
|
|
8228
8483
|
mountInfo.mounted = true;
|
|
8229
8484
|
mountOutcome = "success";
|
|
8230
8485
|
} catch (error) {
|
|
8231
8486
|
mountError = error instanceof Error ? error : new Error(String(error));
|
|
8232
|
-
try {
|
|
8233
|
-
await this.execInternal(`mountpoint -q ${shellEscape(mountPath)} && fusermount -u ${shellEscape(mountPath)}`);
|
|
8234
|
-
} catch {}
|
|
8235
|
-
if (passwordFilePath) await this.deletePasswordFile(passwordFilePath);
|
|
8236
|
-
if (additionalHeaderFilePath) await this.deleteAdditionalHeaderFile(additionalHeaderFilePath);
|
|
8237
|
-
if (!dirExisted) try {
|
|
8238
|
-
await this.execInternal(`rmdir ${shellEscape(mountPath)} 2>/dev/null`);
|
|
8239
|
-
} catch {}
|
|
8240
|
-
const failedMount = this.activeMounts.get(mountPath);
|
|
8241
|
-
if (failedMount?.mountType === "fuse" && failedMount.credentialProxy) try {
|
|
8242
|
-
await this.configureS3CredentialProxyOutbound(this.getS3CredentialProxyParams({ excludeMountId: failedMount.mountId }));
|
|
8243
|
-
this.activeMounts.delete(mountPath);
|
|
8244
|
-
evictSigV4ClientCacheEntry(failedMount.mountId);
|
|
8245
|
-
evictDirectoryMarkerCacheForMount(failedMount.mountId);
|
|
8246
|
-
} catch (cleanupError) {
|
|
8247
|
-
this.logger.warn("credential proxy cleanup failed", {
|
|
8248
|
-
mountPath,
|
|
8249
|
-
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
|
|
8250
|
-
});
|
|
8251
|
-
this.activeMounts.delete(mountPath);
|
|
8252
|
-
evictSigV4ClientCacheEntry(failedMount.mountId);
|
|
8253
|
-
evictDirectoryMarkerCacheForMount(failedMount.mountId);
|
|
8254
|
-
}
|
|
8255
|
-
else this.activeMounts.delete(mountPath);
|
|
8256
8487
|
throw error;
|
|
8257
8488
|
} finally {
|
|
8258
8489
|
logCanonicalEvent(this.logger, {
|
|
@@ -8268,6 +8499,35 @@ var Sandbox = class Sandbox extends Container {
|
|
|
8268
8499
|
}
|
|
8269
8500
|
}
|
|
8270
8501
|
/**
|
|
8502
|
+
* Undo a failed FUSE mount: unmount anything s3fs established, then drop
|
|
8503
|
+
* its support files, the directory if this mount created it, and its
|
|
8504
|
+
* reservation.
|
|
8505
|
+
*/
|
|
8506
|
+
async releaseFailedFuseMount(mountInfo, removeMountDirectory) {
|
|
8507
|
+
const { mountPath } = mountInfo;
|
|
8508
|
+
try {
|
|
8509
|
+
await this.execInternal(`mountpoint -q ${shellEscape(mountPath)} && fusermount -u ${shellEscape(mountPath)}`);
|
|
8510
|
+
} catch {}
|
|
8511
|
+
await this.deletePasswordFile(mountInfo.passwordFilePath);
|
|
8512
|
+
if (mountInfo.additionalHeaderFilePath) await this.deleteAdditionalHeaderFile(mountInfo.additionalHeaderFilePath);
|
|
8513
|
+
if (removeMountDirectory) try {
|
|
8514
|
+
await this.execInternal(`rmdir ${shellEscape(mountPath)} 2>/dev/null`);
|
|
8515
|
+
} catch {}
|
|
8516
|
+
if (mountInfo.credentialProxy) {
|
|
8517
|
+
try {
|
|
8518
|
+
await this.configureS3CredentialProxyOutbound(this.getS3CredentialProxyParams({ excludeMountId: mountInfo.mountId }));
|
|
8519
|
+
} catch (cleanupError) {
|
|
8520
|
+
this.logger.warn("credential proxy cleanup failed", {
|
|
8521
|
+
mountPath,
|
|
8522
|
+
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
|
|
8523
|
+
});
|
|
8524
|
+
}
|
|
8525
|
+
evictSigV4ClientCacheEntry(mountInfo.mountId);
|
|
8526
|
+
evictDirectoryMarkerCacheForMount(mountInfo.mountId);
|
|
8527
|
+
}
|
|
8528
|
+
this.activeMounts.delete(mountPath);
|
|
8529
|
+
}
|
|
8530
|
+
/**
|
|
8271
8531
|
* Manually unmount a bucket filesystem
|
|
8272
8532
|
*
|
|
8273
8533
|
* @param mountPath - Absolute path where the bucket is mounted
|
|
@@ -8407,11 +8667,9 @@ var Sandbox = class Sandbox extends Container {
|
|
|
8407
8667
|
}
|
|
8408
8668
|
/**
|
|
8409
8669
|
* Create s3fs ahbe_conf file that suppresses the Expect: 100-continue header.
|
|
8410
|
-
* Restricted to 0600 so s3fs will accept it (same requirement as passwd files).
|
|
8411
8670
|
*/
|
|
8412
8671
|
async createDisableExpectHeaderFile(headerFilePath) {
|
|
8413
|
-
await this.
|
|
8414
|
-
await this.execInternal(`chmod 0600 ${shellEscape(headerFilePath)}`);
|
|
8672
|
+
await this.writeOwnerOnlyFile(headerFilePath, S3FS_DISABLE_EXPECT_HEADER_CONFIG);
|
|
8415
8673
|
}
|
|
8416
8674
|
/**
|
|
8417
8675
|
* Create password file with s3fs credentials
|
|
@@ -8419,8 +8677,16 @@ var Sandbox = class Sandbox extends Container {
|
|
|
8419
8677
|
*/
|
|
8420
8678
|
async createPasswordFile(passwordFilePath, bucket, credentials) {
|
|
8421
8679
|
const content = `${bucket}:${credentials.accessKeyId}:${credentials.secretAccessKey}`;
|
|
8422
|
-
await this.
|
|
8423
|
-
|
|
8680
|
+
await this.writeOwnerOnlyFile(passwordFilePath, content);
|
|
8681
|
+
}
|
|
8682
|
+
/**
|
|
8683
|
+
* s3fs refuses support files that other users can read. Create them with
|
|
8684
|
+
* mode 0600 so the password file is never readable while it is being
|
|
8685
|
+
* written. The chmod covers container images that ignore `ownerOnly`.
|
|
8686
|
+
*/
|
|
8687
|
+
async writeOwnerOnlyFile(path$1, content) {
|
|
8688
|
+
await this.client.files.writeFile(path$1, content, DISABLE_SESSION_TOKEN, { ownerOnly: true });
|
|
8689
|
+
await this.execInternal(`chmod 0600 ${shellEscape(path$1)}`);
|
|
8424
8690
|
}
|
|
8425
8691
|
/**
|
|
8426
8692
|
* Delete password file
|
|
@@ -8588,6 +8854,7 @@ var Sandbox = class Sandbox extends Container {
|
|
|
8588
8854
|
async onStart() {
|
|
8589
8855
|
this.logger.debug("Sandbox started");
|
|
8590
8856
|
await this.currentRuntime.markStarted();
|
|
8857
|
+
await this.removeStaleMountRoutes();
|
|
8591
8858
|
this.checkVersionCompatibility().catch((error) => {
|
|
8592
8859
|
this.logger.error("Version compatibility check failed", error instanceof Error ? error : new Error(String(error)));
|
|
8593
8860
|
});
|
|
@@ -8669,6 +8936,7 @@ var Sandbox = class Sandbox extends Container {
|
|
|
8669
8936
|
this.containerGeneration++;
|
|
8670
8937
|
this.defaultSession = null;
|
|
8671
8938
|
this.defaultSessionInit = null;
|
|
8939
|
+
const disconnectClient = this.transport !== "rpc" || this.client.isWebSocketConnected();
|
|
8672
8940
|
await this.currentRuntime.clear();
|
|
8673
8941
|
await this.clearActivePreviewPorts();
|
|
8674
8942
|
try {
|
|
@@ -8676,7 +8944,8 @@ var Sandbox = class Sandbox extends Container {
|
|
|
8676
8944
|
} catch (error) {
|
|
8677
8945
|
this.logger.error("Failed to reconcile tunnel storage after container stop", error instanceof Error ? error : new Error(String(error)));
|
|
8678
8946
|
}
|
|
8679
|
-
this.client.disconnect(this.buildDisconnectCause("runtime_replaced", "The sandbox container stopped while the operation was pending.", params));
|
|
8947
|
+
if (disconnectClient) this.client.disconnect(this.buildDisconnectCause("runtime_replaced", "The sandbox container stopped while the operation was pending.", params));
|
|
8948
|
+
await this.releaseRestoredMountRoutes();
|
|
8680
8949
|
let hadR2EgressMount = false;
|
|
8681
8950
|
let hadCredentialProxyMount = false;
|
|
8682
8951
|
for (const [, m] of this.activeMounts) if (m.mountType === "local-sync") await m.syncManager.stop().catch(() => {});
|
|
@@ -8824,6 +9093,13 @@ var Sandbox = class Sandbox extends Container {
|
|
|
8824
9093
|
/**
|
|
8825
9094
|
* Explicit container-start hook for the RPC transport.
|
|
8826
9095
|
*
|
|
9096
|
+
* The control connection calls this hook every time it connects, including
|
|
9097
|
+
* when it reconnects after an idle disconnect. A running, healthy container
|
|
9098
|
+
* is left alone, as in {@link containerFetch}: the base class runs
|
|
9099
|
+
* `onStart()` on every `startAndWaitForPorts()`, and `onStart()` treats the
|
|
9100
|
+
* container as new, recording a new runtime identity and dropping quick
|
|
9101
|
+
* tunnels from storage.
|
|
9102
|
+
*
|
|
8827
9103
|
* Runs `startAndWaitForPorts` under a short instance-get budget
|
|
8828
9104
|
* ({@link RPC_START_INSTANCE_GET_TIMEOUT_MS}) so a single attempt fails fast
|
|
8829
9105
|
* under capacity pressure — the base class throws the classifiable
|
|
@@ -8843,6 +9119,7 @@ var Sandbox = class Sandbox extends Container {
|
|
|
8843
9119
|
*/
|
|
8844
9120
|
async startContainerForRPC() {
|
|
8845
9121
|
try {
|
|
9122
|
+
if ((await this.getState()).status === "healthy" && this.ctx.container?.running !== false) return;
|
|
8846
9123
|
await this.startAndWaitForPorts({
|
|
8847
9124
|
ports: 3e3,
|
|
8848
9125
|
cancellationOptions: {
|
|
@@ -9058,6 +9335,14 @@ var Sandbox = class Sandbox extends Container {
|
|
|
9058
9335
|
if (!await this.currentRuntime.isActive(runtime)) return this.stalePreviewURLResponse();
|
|
9059
9336
|
return new Response("Container suddenly disconnected, try again", { status: 500 });
|
|
9060
9337
|
}
|
|
9338
|
+
if (result.status === "failed") {
|
|
9339
|
+
if (!await this.currentRuntime.isActive(runtime)) return this.stalePreviewURLResponse();
|
|
9340
|
+
this.logger.error("Preview request to container port failed", result.error instanceof Error ? result.error : new Error(String(result.error)), {
|
|
9341
|
+
port,
|
|
9342
|
+
method: request.method
|
|
9343
|
+
});
|
|
9344
|
+
return new Response(`Could not reach port ${port} in the container`, { status: 502 });
|
|
9345
|
+
}
|
|
9061
9346
|
return result.response;
|
|
9062
9347
|
}
|
|
9063
9348
|
buildPreviewProxyRequest(request, port, sandboxId) {
|
|
@@ -9069,10 +9354,6 @@ var Sandbox = class Sandbox extends Container {
|
|
|
9069
9354
|
headers.set("X-Forwarded-Host", url.hostname);
|
|
9070
9355
|
headers.set("X-Forwarded-Proto", url.protocol.replace(":", ""));
|
|
9071
9356
|
headers.set("X-Sandbox-Name", this.sandboxName ?? sandboxId);
|
|
9072
|
-
if (request.headers.get("Upgrade")?.toLowerCase() === "websocket") return new Request(request, {
|
|
9073
|
-
headers,
|
|
9074
|
-
redirect: "manual"
|
|
9075
|
-
});
|
|
9076
9357
|
return new Request(proxyUrl, {
|
|
9077
9358
|
method: request.method,
|
|
9078
9359
|
headers,
|
|
@@ -9323,7 +9604,6 @@ var Sandbox = class Sandbox extends Container {
|
|
|
9323
9604
|
logCanonicalEvent(this.logger, {
|
|
9324
9605
|
event: "sandbox.exec",
|
|
9325
9606
|
outcome: execError ? "error" : "success",
|
|
9326
|
-
command,
|
|
9327
9607
|
exitCode: execOutcome?.exitCode,
|
|
9328
9608
|
durationMs: Date.now() - startTime,
|
|
9329
9609
|
sessionId: this.getPublicExecutionSessionId(sessionId),
|
|
@@ -10058,6 +10338,7 @@ var Sandbox = class Sandbox extends Container {
|
|
|
10058
10338
|
sandboxId: this.ctx.id.toString(),
|
|
10059
10339
|
currentRuntime: this.currentRuntime,
|
|
10060
10340
|
currentLifetime: this.currentLifetime,
|
|
10341
|
+
withPortLock: this.tunnelPortLock,
|
|
10061
10342
|
getNamedTunnelConfig: async () => {
|
|
10062
10343
|
const envObj = this.env;
|
|
10063
10344
|
const token = getEnvString(envObj, "CLOUDFLARE_API_TOKEN");
|
|
@@ -10839,9 +11120,9 @@ var Sandbox = class Sandbox extends Container {
|
|
|
10839
11120
|
...startLines,
|
|
10840
11121
|
...waitLines,
|
|
10841
11122
|
`FAILED=$(( ${exitVars.join(" + ")} ))`,
|
|
10842
|
-
`if [ "$FAILED" -ne 0 ]; then rm -f ${shellEscape(tmpPath)}; exit 1; fi`
|
|
11123
|
+
`if [ "$FAILED" -ne 0 ]; then echo "download part exit codes: ${exitVars.join(" ")}" >&2; rm -f ${shellEscape(tmpPath)}; exit 1; fi`
|
|
10843
11124
|
].join("; ");
|
|
10844
|
-
const result = await this.execWithSession(script
|
|
11125
|
+
const result = await this.execWithSession(`( ${script} )`, backupSession, {
|
|
10845
11126
|
timeout: 181e4,
|
|
10846
11127
|
origin: "internal"
|
|
10847
11128
|
});
|
|
@@ -11504,11 +11785,47 @@ var Sandbox = class Sandbox extends Container {
|
|
|
11504
11785
|
});
|
|
11505
11786
|
}
|
|
11506
11787
|
}
|
|
11507
|
-
|
|
11788
|
+
/**
|
|
11789
|
+
* Drop the routes restored for the mounts of a container that has stopped.
|
|
11790
|
+
*/
|
|
11791
|
+
async releaseRestoredMountRoutes() {
|
|
11792
|
+
const { r2Buckets, credentialProxyMounts } = this.restoredMountRoutes;
|
|
11793
|
+
this.restoredMountRoutes = noRestoredMountRoutes();
|
|
11794
|
+
for (const mountId of Object.keys(credentialProxyMounts)) {
|
|
11795
|
+
evictSigV4ClientCacheEntry(mountId);
|
|
11796
|
+
evictDirectoryMarkerCacheForMount(mountId);
|
|
11797
|
+
}
|
|
11798
|
+
if (Object.keys(r2Buckets).length > 0) this.staleMountRoutes.r2 = true;
|
|
11799
|
+
if (Object.keys(credentialProxyMounts).length > 0) this.staleMountRoutes.credentialProxy = true;
|
|
11800
|
+
await this.removeStaleMountRoutes();
|
|
11801
|
+
}
|
|
11802
|
+
/**
|
|
11803
|
+
* Rewrite the routes that still carry a stopped container's restored
|
|
11804
|
+
* mounts so they cover only the mounts in activeMounts. A failed update
|
|
11805
|
+
* stays marked and is retried on the next container start or stop.
|
|
11806
|
+
*/
|
|
11807
|
+
async removeStaleMountRoutes() {
|
|
11808
|
+
if (this.staleMountRoutes.r2) try {
|
|
11809
|
+
await this.configureR2EgressOutbound(this.getR2EgressParams());
|
|
11810
|
+
this.staleMountRoutes.r2 = false;
|
|
11811
|
+
} catch (error) {
|
|
11812
|
+
this.logger.warn("Failed to remove stale R2 mount routes", { error: error instanceof Error ? error.message : String(error) });
|
|
11813
|
+
}
|
|
11814
|
+
if (this.staleMountRoutes.credentialProxy) try {
|
|
11815
|
+
await this.configureS3CredentialProxyOutbound(this.getS3CredentialProxyParams());
|
|
11816
|
+
this.staleMountRoutes.credentialProxy = false;
|
|
11817
|
+
} catch (error) {
|
|
11818
|
+
this.logger.warn("Failed to remove stale credential proxy mount routes", { error: error instanceof Error ? error.message : String(error) });
|
|
11819
|
+
}
|
|
11820
|
+
}
|
|
11821
|
+
async configureR2EgressOutbound(activeParams) {
|
|
11822
|
+
const params = { buckets: {
|
|
11823
|
+
...this.restoredMountRoutes.r2Buckets,
|
|
11824
|
+
...activeParams.buckets
|
|
11825
|
+
} };
|
|
11508
11826
|
const ctx = this.ctx;
|
|
11509
11827
|
if (!ctx.container?.interceptOutboundHttp) throw new InvalidMountConfigError("R2 binding mounts require container outbound interception support");
|
|
11510
11828
|
if (!ctx.exports?.ContainerProxy) throw new InvalidMountConfigError("R2 binding mounts require exporting ContainerProxy from the Worker entrypoint");
|
|
11511
|
-
this.constructor.outboundHandlers = { r2EgressMount: r2EgressHandler };
|
|
11512
11829
|
if (Object.keys(params.buckets).length > 0) await this.setOutboundByHost("r2.internal", "r2EgressMount", params);
|
|
11513
11830
|
else await this.removeOutboundByHost("r2.internal");
|
|
11514
11831
|
this.logger.debug("r2 egress: registering host interception", {
|
|
@@ -11528,12 +11845,15 @@ var Sandbox = class Sandbox extends Container {
|
|
|
11528
11845
|
if (!isFetcher(fetcher)) throw new InvalidMountConfigError("R2 binding mounts require ContainerProxy to return a valid Fetcher");
|
|
11529
11846
|
await ctx.container.interceptOutboundHttp("r2.internal", fetcher);
|
|
11530
11847
|
}
|
|
11531
|
-
async configureS3CredentialProxyOutbound(
|
|
11848
|
+
async configureS3CredentialProxyOutbound(activeParams) {
|
|
11849
|
+
const params = { mounts: {
|
|
11850
|
+
...this.restoredMountRoutes.credentialProxyMounts,
|
|
11851
|
+
...activeParams.mounts
|
|
11852
|
+
} };
|
|
11532
11853
|
const ctx = this.ctx;
|
|
11533
11854
|
if (!ctx.container?.interceptOutboundHttp) throw new InvalidMountConfigError("Credential proxy bucket mounts require container outbound interception support");
|
|
11534
11855
|
if (!ctx.exports?.ContainerProxy) throw new InvalidMountConfigError("Credential proxy bucket mounts require exporting ContainerProxy from the Worker entrypoint");
|
|
11535
11856
|
const hosts = [S3_CREDENTIAL_PROXY_HOST, S3_CREDENTIAL_PROXY_DIAGNOSTIC_HOST];
|
|
11536
|
-
this.constructor.outboundHandlers = { s3CredentialProxyMount: s3CredentialProxyHandler };
|
|
11537
11857
|
if (Object.keys(params.mounts).length > 0) for (const host of hosts) await this.setOutboundByHost(host, "s3CredentialProxyMount", params);
|
|
11538
11858
|
else for (const host of hosts) await this.removeOutboundByHost(host);
|
|
11539
11859
|
const hostOverrides = {};
|
|
@@ -11566,4 +11886,4 @@ var Sandbox = class Sandbox extends Container {
|
|
|
11566
11886
|
|
|
11567
11887
|
//#endregion
|
|
11568
11888
|
export { PortClient as A, InvalidBackupConfigError as B, collectFile as C, SandboxClient as D, isPlatformTransientError as E, BackupCreateError as F, SessionTerminatedError as G, ProcessExitedBeforeReadyError as H, BackupExpiredError as I, BackupNotFoundError as L, FileClient as M, CommandClient as N, UtilityClient as O, BackupClient as P, BackupRestoreError as R, validateTunnelName as S, isDurableObjectCodeUpdateReset as T, ProcessReadyTimeoutError as U, OperationInterruptedError as V, RPCTransportError as W, responseToAsyncIterable as _, PREVIEW_PROXY_HEADER as a, sanitizeSandboxId as b, PREVIEW_PROXY_SANDBOX_ID_HEADER as c, BucketUnmountError as d, InvalidMountConfigError as f, parseSSEStream as g, asyncIterableToSSEStream as h, proxyTerminal as i, GitClient as j, ProcessClient as k, PREVIEW_PROXY_TOKEN_HEADER as l, S3FSMountError as m, Sandbox as n, PREVIEW_PROXY_HEADERS as o, MissingCredentialsError as p, getSandbox as r, PREVIEW_PROXY_PORT_HEADER as s, ContainerProxy$1 as t, BucketMountError as u, CodeInterpreter as v, streamFile as w, validatePort as x, SandboxSecurityError as y, ContainerUnavailableError as z };
|
|
11569
|
-
//# sourceMappingURL=sandbox-
|
|
11889
|
+
//# sourceMappingURL=sandbox-BYPanejK.js.map
|