@cloudflare/sandbox 0.7.17 → 0.7.18
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/index.d.ts +2 -2
- package/dist/index.js +630 -204
- package/dist/index.js.map +1 -1
- package/dist/openai/index.d.ts +1 -1
- package/dist/opencode/index.d.ts +8 -1
- package/dist/opencode/index.d.ts.map +1 -1
- package/dist/opencode/index.js +10 -4
- package/dist/opencode/index.js.map +1 -1
- package/dist/{sandbox-DBTljwwc.d.ts → sandbox-CTUXe5eK.d.ts} +65 -6
- package/dist/sandbox-CTUXe5eK.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/sandbox-DBTljwwc.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { _ as filterEnvVars, a as isExecResult, c as parseSSEFrames, d as create
|
|
|
2
2
|
import { t as ErrorCode } from "./errors-CaSfB5Bm.js";
|
|
3
3
|
import { Container, getContainer, switchPort } from "@cloudflare/containers";
|
|
4
4
|
import { AwsClient } from "aws4fetch";
|
|
5
|
+
import path from "node:path/posix";
|
|
5
6
|
|
|
6
7
|
//#region src/errors/classes.ts
|
|
7
8
|
/**
|
|
@@ -742,11 +743,11 @@ var BaseTransport = class {
|
|
|
742
743
|
* This is the primary entry point for making requests. It wraps the
|
|
743
744
|
* transport-specific doFetch() with retry logic for container startup.
|
|
744
745
|
*/
|
|
745
|
-
async fetch(path, options) {
|
|
746
|
+
async fetch(path$1, options) {
|
|
746
747
|
const startTime = Date.now();
|
|
747
748
|
let attempt = 0;
|
|
748
749
|
while (true) {
|
|
749
|
-
const response = await this.doFetch(path, options);
|
|
750
|
+
const response = await this.doFetch(path$1, options);
|
|
750
751
|
if (response.status === 503) {
|
|
751
752
|
const elapsed = Date.now() - startTime;
|
|
752
753
|
const remaining = this.retryTimeoutMs - elapsed;
|
|
@@ -798,13 +799,13 @@ var HttpTransport = class extends BaseTransport {
|
|
|
798
799
|
isConnected() {
|
|
799
800
|
return true;
|
|
800
801
|
}
|
|
801
|
-
async doFetch(path, options) {
|
|
802
|
-
const url = this.buildUrl(path);
|
|
802
|
+
async doFetch(path$1, options) {
|
|
803
|
+
const url = this.buildUrl(path$1);
|
|
803
804
|
if (this.config.stub) return this.config.stub.containerFetch(url, options || {}, this.config.port);
|
|
804
805
|
return globalThis.fetch(url, options);
|
|
805
806
|
}
|
|
806
|
-
async fetchStream(path, body, method = "POST") {
|
|
807
|
-
const url = this.buildUrl(path);
|
|
807
|
+
async fetchStream(path$1, body, method = "POST") {
|
|
808
|
+
const url = this.buildUrl(path$1);
|
|
808
809
|
const options = this.buildStreamOptions(body, method);
|
|
809
810
|
let response;
|
|
810
811
|
if (this.config.stub) response = await this.config.stub.containerFetch(url, options, this.config.port);
|
|
@@ -816,9 +817,9 @@ var HttpTransport = class extends BaseTransport {
|
|
|
816
817
|
if (!response.body) throw new Error("No response body for streaming");
|
|
817
818
|
return response.body;
|
|
818
819
|
}
|
|
819
|
-
buildUrl(path) {
|
|
820
|
-
if (this.config.stub) return `http://localhost:${this.config.port}${path}`;
|
|
821
|
-
return `${this.baseUrl}${path}`;
|
|
820
|
+
buildUrl(path$1) {
|
|
821
|
+
if (this.config.stub) return `http://localhost:${this.config.port}${path$1}`;
|
|
822
|
+
return `${this.baseUrl}${path$1}`;
|
|
822
823
|
}
|
|
823
824
|
buildStreamOptions(body, method) {
|
|
824
825
|
return {
|
|
@@ -877,9 +878,8 @@ var WebSocketTransport = class extends BaseTransport {
|
|
|
877
878
|
this.connectPromise = this.doConnect();
|
|
878
879
|
try {
|
|
879
880
|
await this.connectPromise;
|
|
880
|
-
}
|
|
881
|
+
} finally {
|
|
881
882
|
this.connectPromise = null;
|
|
882
|
-
throw error;
|
|
883
883
|
}
|
|
884
884
|
}
|
|
885
885
|
/**
|
|
@@ -892,11 +892,11 @@ var WebSocketTransport = class extends BaseTransport {
|
|
|
892
892
|
* Transport-specific fetch implementation
|
|
893
893
|
* Converts WebSocket response to standard Response object.
|
|
894
894
|
*/
|
|
895
|
-
async doFetch(path, options) {
|
|
895
|
+
async doFetch(path$1, options) {
|
|
896
896
|
await this.connect();
|
|
897
897
|
const method = options?.method || "GET";
|
|
898
898
|
const body = this.parseBody(options?.body);
|
|
899
|
-
const result = await this.request(method, path, body);
|
|
899
|
+
const result = await this.request(method, path$1, body);
|
|
900
900
|
return new Response(JSON.stringify(result.body), {
|
|
901
901
|
status: result.status,
|
|
902
902
|
headers: { "Content-Type": "application/json" }
|
|
@@ -905,8 +905,8 @@ var WebSocketTransport = class extends BaseTransport {
|
|
|
905
905
|
/**
|
|
906
906
|
* Streaming fetch implementation
|
|
907
907
|
*/
|
|
908
|
-
async fetchStream(path, body, method = "POST") {
|
|
909
|
-
return this.requestStream(method, path, body);
|
|
908
|
+
async fetchStream(path$1, body, method = "POST") {
|
|
909
|
+
return this.requestStream(method, path$1, body);
|
|
910
910
|
}
|
|
911
911
|
/**
|
|
912
912
|
* Parse request body from RequestInit
|
|
@@ -1009,21 +1009,21 @@ var WebSocketTransport = class extends BaseTransport {
|
|
|
1009
1009
|
/**
|
|
1010
1010
|
* Send a request and wait for response
|
|
1011
1011
|
*/
|
|
1012
|
-
async request(method, path, body) {
|
|
1012
|
+
async request(method, path$1, body) {
|
|
1013
1013
|
await this.connect();
|
|
1014
1014
|
const id = generateRequestId();
|
|
1015
1015
|
const request = {
|
|
1016
1016
|
type: "request",
|
|
1017
1017
|
id,
|
|
1018
1018
|
method,
|
|
1019
|
-
path,
|
|
1019
|
+
path: path$1,
|
|
1020
1020
|
body
|
|
1021
1021
|
};
|
|
1022
1022
|
return new Promise((resolve, reject) => {
|
|
1023
1023
|
const timeoutMs = this.config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
1024
1024
|
const timeoutId = setTimeout(() => {
|
|
1025
1025
|
this.pendingRequests.delete(id);
|
|
1026
|
-
reject(/* @__PURE__ */ new Error(`Request timeout after ${timeoutMs}ms: ${method} ${path}`));
|
|
1026
|
+
reject(/* @__PURE__ */ new Error(`Request timeout after ${timeoutMs}ms: ${method} ${path$1}`));
|
|
1027
1027
|
}, timeoutMs);
|
|
1028
1028
|
this.pendingRequests.set(id, {
|
|
1029
1029
|
resolve: (response) => {
|
|
@@ -1065,14 +1065,14 @@ var WebSocketTransport = class extends BaseTransport {
|
|
|
1065
1065
|
* long-running streams (e.g. execStream from an agent) stay alive as long
|
|
1066
1066
|
* as data is flowing. The timer resets on every chunk or response message.
|
|
1067
1067
|
*/
|
|
1068
|
-
async requestStream(method, path, body) {
|
|
1068
|
+
async requestStream(method, path$1, body) {
|
|
1069
1069
|
await this.connect();
|
|
1070
1070
|
const id = generateRequestId();
|
|
1071
1071
|
const request = {
|
|
1072
1072
|
type: "request",
|
|
1073
1073
|
id,
|
|
1074
1074
|
method,
|
|
1075
|
-
path,
|
|
1075
|
+
path: path$1,
|
|
1076
1076
|
body
|
|
1077
1077
|
};
|
|
1078
1078
|
const idleTimeoutMs = this.config.streamIdleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;
|
|
@@ -1082,7 +1082,7 @@ var WebSocketTransport = class extends BaseTransport {
|
|
|
1082
1082
|
const createIdleTimeout = () => {
|
|
1083
1083
|
return setTimeout(() => {
|
|
1084
1084
|
this.pendingRequests.delete(id);
|
|
1085
|
-
const error = /* @__PURE__ */ new Error(`Stream idle timeout after ${idleTimeoutMs}ms: ${method} ${path}`);
|
|
1085
|
+
const error = /* @__PURE__ */ new Error(`Stream idle timeout after ${idleTimeoutMs}ms: ${method} ${path$1}`);
|
|
1086
1086
|
if (firstMessageReceived) try {
|
|
1087
1087
|
streamController?.error(error);
|
|
1088
1088
|
} catch {}
|
|
@@ -1293,6 +1293,7 @@ var WebSocketTransport = class extends BaseTransport {
|
|
|
1293
1293
|
handleClose(event) {
|
|
1294
1294
|
this.state = "disconnected";
|
|
1295
1295
|
this.ws = null;
|
|
1296
|
+
this.connectPromise = null;
|
|
1296
1297
|
const closeError = /* @__PURE__ */ new Error(`WebSocket closed: ${event.code} ${event.reason || "No reason"}`);
|
|
1297
1298
|
for (const [, pending] of this.pendingRequests) {
|
|
1298
1299
|
if (pending.timeoutId) clearTimeout(pending.timeoutId);
|
|
@@ -1396,8 +1397,8 @@ var BaseHttpClient = class {
|
|
|
1396
1397
|
/**
|
|
1397
1398
|
* Core fetch method - delegates to Transport which handles retry logic
|
|
1398
1399
|
*/
|
|
1399
|
-
async doFetch(path, options) {
|
|
1400
|
-
return this.transport.fetch(path, options);
|
|
1400
|
+
async doFetch(path$1, options) {
|
|
1401
|
+
return this.transport.fetch(path$1, options);
|
|
1401
1402
|
}
|
|
1402
1403
|
/**
|
|
1403
1404
|
* Make a POST request with JSON body
|
|
@@ -1480,14 +1481,14 @@ var BaseHttpClient = class {
|
|
|
1480
1481
|
* @param body - Optional request body (for POST requests)
|
|
1481
1482
|
* @param method - HTTP method (default: POST, use GET for process logs)
|
|
1482
1483
|
*/
|
|
1483
|
-
async doStreamFetch(path, body, method = "POST") {
|
|
1484
|
+
async doStreamFetch(path$1, body, method = "POST") {
|
|
1484
1485
|
if (this.transport.getMode() === "websocket") try {
|
|
1485
|
-
return await this.transport.fetchStream(path, body, method);
|
|
1486
|
+
return await this.transport.fetchStream(path$1, body, method);
|
|
1486
1487
|
} catch (error) {
|
|
1487
|
-
this.logError(`stream ${method} ${path}`, error);
|
|
1488
|
+
this.logError(`stream ${method} ${path$1}`, error);
|
|
1488
1489
|
throw error;
|
|
1489
1490
|
}
|
|
1490
|
-
const response = await this.doFetch(path, {
|
|
1491
|
+
const response = await this.doFetch(path$1, {
|
|
1491
1492
|
method,
|
|
1492
1493
|
headers: { "Content-Type": "application/json" },
|
|
1493
1494
|
body: body && method === "POST" ? JSON.stringify(body) : void 0
|
|
@@ -2005,15 +2006,15 @@ var FileClient = class extends BaseHttpClient {
|
|
|
2005
2006
|
* @param sessionId - The session ID for this operation
|
|
2006
2007
|
* @param options - Optional settings (recursive)
|
|
2007
2008
|
*/
|
|
2008
|
-
async mkdir(path, sessionId, options) {
|
|
2009
|
+
async mkdir(path$1, sessionId, options) {
|
|
2009
2010
|
try {
|
|
2010
2011
|
const data = {
|
|
2011
|
-
path,
|
|
2012
|
+
path: path$1,
|
|
2012
2013
|
sessionId,
|
|
2013
2014
|
recursive: options?.recursive ?? false
|
|
2014
2015
|
};
|
|
2015
2016
|
const response = await this.post("/api/mkdir", data);
|
|
2016
|
-
this.logSuccess("Directory created", `${path} (recursive: ${data.recursive})`);
|
|
2017
|
+
this.logSuccess("Directory created", `${path$1} (recursive: ${data.recursive})`);
|
|
2017
2018
|
return response;
|
|
2018
2019
|
} catch (error) {
|
|
2019
2020
|
this.logError("mkdir", error);
|
|
@@ -2027,16 +2028,16 @@ var FileClient = class extends BaseHttpClient {
|
|
|
2027
2028
|
* @param sessionId - The session ID for this operation
|
|
2028
2029
|
* @param options - Optional settings (encoding)
|
|
2029
2030
|
*/
|
|
2030
|
-
async writeFile(path, content, sessionId, options) {
|
|
2031
|
+
async writeFile(path$1, content, sessionId, options) {
|
|
2031
2032
|
try {
|
|
2032
2033
|
const data = {
|
|
2033
|
-
path,
|
|
2034
|
+
path: path$1,
|
|
2034
2035
|
content,
|
|
2035
2036
|
sessionId,
|
|
2036
2037
|
encoding: options?.encoding
|
|
2037
2038
|
};
|
|
2038
2039
|
const response = await this.post("/api/write", data);
|
|
2039
|
-
this.logSuccess("File written", `${path} (${content.length} chars)`);
|
|
2040
|
+
this.logSuccess("File written", `${path$1} (${content.length} chars)`);
|
|
2040
2041
|
return response;
|
|
2041
2042
|
} catch (error) {
|
|
2042
2043
|
this.logError("writeFile", error);
|
|
@@ -2049,15 +2050,15 @@ var FileClient = class extends BaseHttpClient {
|
|
|
2049
2050
|
* @param sessionId - The session ID for this operation
|
|
2050
2051
|
* @param options - Optional settings (encoding)
|
|
2051
2052
|
*/
|
|
2052
|
-
async readFile(path, sessionId, options) {
|
|
2053
|
+
async readFile(path$1, sessionId, options) {
|
|
2053
2054
|
try {
|
|
2054
2055
|
const data = {
|
|
2055
|
-
path,
|
|
2056
|
+
path: path$1,
|
|
2056
2057
|
sessionId,
|
|
2057
2058
|
encoding: options?.encoding
|
|
2058
2059
|
};
|
|
2059
2060
|
const response = await this.post("/api/read", data);
|
|
2060
|
-
this.logSuccess("File read", `${path} (${response.content.length} chars)`);
|
|
2061
|
+
this.logSuccess("File read", `${path$1} (${response.content.length} chars)`);
|
|
2061
2062
|
return response;
|
|
2062
2063
|
} catch (error) {
|
|
2063
2064
|
this.logError("readFile", error);
|
|
@@ -2070,14 +2071,14 @@ var FileClient = class extends BaseHttpClient {
|
|
|
2070
2071
|
* @param path - File path to stream
|
|
2071
2072
|
* @param sessionId - The session ID for this operation
|
|
2072
2073
|
*/
|
|
2073
|
-
async readFileStream(path, sessionId) {
|
|
2074
|
+
async readFileStream(path$1, sessionId) {
|
|
2074
2075
|
try {
|
|
2075
2076
|
const data = {
|
|
2076
|
-
path,
|
|
2077
|
+
path: path$1,
|
|
2077
2078
|
sessionId
|
|
2078
2079
|
};
|
|
2079
2080
|
const stream = await this.doStreamFetch("/api/read/stream", data);
|
|
2080
|
-
this.logSuccess("File stream started", path);
|
|
2081
|
+
this.logSuccess("File stream started", path$1);
|
|
2081
2082
|
return stream;
|
|
2082
2083
|
} catch (error) {
|
|
2083
2084
|
this.logError("readFileStream", error);
|
|
@@ -2089,14 +2090,14 @@ var FileClient = class extends BaseHttpClient {
|
|
|
2089
2090
|
* @param path - File path to delete
|
|
2090
2091
|
* @param sessionId - The session ID for this operation
|
|
2091
2092
|
*/
|
|
2092
|
-
async deleteFile(path, sessionId) {
|
|
2093
|
+
async deleteFile(path$1, sessionId) {
|
|
2093
2094
|
try {
|
|
2094
2095
|
const data = {
|
|
2095
|
-
path,
|
|
2096
|
+
path: path$1,
|
|
2096
2097
|
sessionId
|
|
2097
2098
|
};
|
|
2098
2099
|
const response = await this.post("/api/delete", data);
|
|
2099
|
-
this.logSuccess("File deleted", path);
|
|
2100
|
+
this.logSuccess("File deleted", path$1);
|
|
2100
2101
|
return response;
|
|
2101
2102
|
} catch (error) {
|
|
2102
2103
|
this.logError("deleteFile", error);
|
|
@@ -2109,15 +2110,15 @@ var FileClient = class extends BaseHttpClient {
|
|
|
2109
2110
|
* @param newPath - New file path
|
|
2110
2111
|
* @param sessionId - The session ID for this operation
|
|
2111
2112
|
*/
|
|
2112
|
-
async renameFile(path, newPath, sessionId) {
|
|
2113
|
+
async renameFile(path$1, newPath, sessionId) {
|
|
2113
2114
|
try {
|
|
2114
2115
|
const data = {
|
|
2115
|
-
oldPath: path,
|
|
2116
|
+
oldPath: path$1,
|
|
2116
2117
|
newPath,
|
|
2117
2118
|
sessionId
|
|
2118
2119
|
};
|
|
2119
2120
|
const response = await this.post("/api/rename", data);
|
|
2120
|
-
this.logSuccess("File renamed", `${path} -> ${newPath}`);
|
|
2121
|
+
this.logSuccess("File renamed", `${path$1} -> ${newPath}`);
|
|
2121
2122
|
return response;
|
|
2122
2123
|
} catch (error) {
|
|
2123
2124
|
this.logError("renameFile", error);
|
|
@@ -2130,15 +2131,15 @@ var FileClient = class extends BaseHttpClient {
|
|
|
2130
2131
|
* @param newPath - Destination file path
|
|
2131
2132
|
* @param sessionId - The session ID for this operation
|
|
2132
2133
|
*/
|
|
2133
|
-
async moveFile(path, newPath, sessionId) {
|
|
2134
|
+
async moveFile(path$1, newPath, sessionId) {
|
|
2134
2135
|
try {
|
|
2135
2136
|
const data = {
|
|
2136
|
-
sourcePath: path,
|
|
2137
|
+
sourcePath: path$1,
|
|
2137
2138
|
destinationPath: newPath,
|
|
2138
2139
|
sessionId
|
|
2139
2140
|
};
|
|
2140
2141
|
const response = await this.post("/api/move", data);
|
|
2141
|
-
this.logSuccess("File moved", `${path} -> ${newPath}`);
|
|
2142
|
+
this.logSuccess("File moved", `${path$1} -> ${newPath}`);
|
|
2142
2143
|
return response;
|
|
2143
2144
|
} catch (error) {
|
|
2144
2145
|
this.logError("moveFile", error);
|
|
@@ -2151,15 +2152,15 @@ var FileClient = class extends BaseHttpClient {
|
|
|
2151
2152
|
* @param sessionId - The session ID for this operation
|
|
2152
2153
|
* @param options - Optional settings (recursive, includeHidden)
|
|
2153
2154
|
*/
|
|
2154
|
-
async listFiles(path, sessionId, options) {
|
|
2155
|
+
async listFiles(path$1, sessionId, options) {
|
|
2155
2156
|
try {
|
|
2156
2157
|
const data = {
|
|
2157
|
-
path,
|
|
2158
|
+
path: path$1,
|
|
2158
2159
|
sessionId,
|
|
2159
2160
|
options: options || {}
|
|
2160
2161
|
};
|
|
2161
2162
|
const response = await this.post("/api/list-files", data);
|
|
2162
|
-
this.logSuccess("Files listed", `${path} (${response.count} files)`);
|
|
2163
|
+
this.logSuccess("Files listed", `${path$1} (${response.count} files)`);
|
|
2163
2164
|
return response;
|
|
2164
2165
|
} catch (error) {
|
|
2165
2166
|
this.logError("listFiles", error);
|
|
@@ -2171,14 +2172,14 @@ var FileClient = class extends BaseHttpClient {
|
|
|
2171
2172
|
* @param path - Path to check
|
|
2172
2173
|
* @param sessionId - The session ID for this operation
|
|
2173
2174
|
*/
|
|
2174
|
-
async exists(path, sessionId) {
|
|
2175
|
+
async exists(path$1, sessionId) {
|
|
2175
2176
|
try {
|
|
2176
2177
|
const data = {
|
|
2177
|
-
path,
|
|
2178
|
+
path: path$1,
|
|
2178
2179
|
sessionId
|
|
2179
2180
|
};
|
|
2180
2181
|
const response = await this.post("/api/exists", data);
|
|
2181
|
-
this.logSuccess("Path existence checked", `${path} (exists: ${response.exists})`);
|
|
2182
|
+
this.logSuccess("Path existence checked", `${path$1} (exists: ${response.exists})`);
|
|
2182
2183
|
return response;
|
|
2183
2184
|
} catch (error) {
|
|
2184
2185
|
this.logError("exists", error);
|
|
@@ -3028,6 +3029,401 @@ var CodeInterpreter = class {
|
|
|
3028
3029
|
}
|
|
3029
3030
|
};
|
|
3030
3031
|
|
|
3032
|
+
//#endregion
|
|
3033
|
+
//#region src/sse-parser.ts
|
|
3034
|
+
/**
|
|
3035
|
+
* Server-Sent Events (SSE) parser for streaming responses
|
|
3036
|
+
* Converts ReadableStream<Uint8Array> to typed AsyncIterable<T>
|
|
3037
|
+
*/
|
|
3038
|
+
/**
|
|
3039
|
+
* Parse a ReadableStream of SSE events into typed AsyncIterable
|
|
3040
|
+
* @param stream - The ReadableStream from fetch response
|
|
3041
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3042
|
+
*/
|
|
3043
|
+
async function* parseSSEStream(stream, signal) {
|
|
3044
|
+
const reader = stream.getReader();
|
|
3045
|
+
const decoder = new TextDecoder();
|
|
3046
|
+
let buffer = "";
|
|
3047
|
+
let currentEvent = { data: [] };
|
|
3048
|
+
let isAborted = signal?.aborted ?? false;
|
|
3049
|
+
const emitEvent = (data) => {
|
|
3050
|
+
if (data === "[DONE]" || data.trim() === "") return;
|
|
3051
|
+
try {
|
|
3052
|
+
return JSON.parse(data);
|
|
3053
|
+
} catch {
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
3056
|
+
};
|
|
3057
|
+
const onAbort = () => {
|
|
3058
|
+
isAborted = true;
|
|
3059
|
+
reader.cancel().catch(() => {});
|
|
3060
|
+
};
|
|
3061
|
+
if (signal && !signal.aborted) signal.addEventListener("abort", onAbort);
|
|
3062
|
+
try {
|
|
3063
|
+
while (true) {
|
|
3064
|
+
if (isAborted) throw new Error("Operation was aborted");
|
|
3065
|
+
const { done, value } = await reader.read();
|
|
3066
|
+
if (isAborted) throw new Error("Operation was aborted");
|
|
3067
|
+
if (done) break;
|
|
3068
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3069
|
+
const parsed = parseSSEFrames(buffer, currentEvent);
|
|
3070
|
+
buffer = parsed.remaining;
|
|
3071
|
+
currentEvent = parsed.currentEvent;
|
|
3072
|
+
for (const frame of parsed.events) {
|
|
3073
|
+
const event = emitEvent(frame.data);
|
|
3074
|
+
if (event !== void 0) yield event;
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
if (isAborted) throw new Error("Operation was aborted");
|
|
3078
|
+
const finalParsed = parseSSEFrames(`${buffer}\n\n`, currentEvent);
|
|
3079
|
+
for (const frame of finalParsed.events) {
|
|
3080
|
+
const event = emitEvent(frame.data);
|
|
3081
|
+
if (event !== void 0) yield event;
|
|
3082
|
+
}
|
|
3083
|
+
} finally {
|
|
3084
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
3085
|
+
try {
|
|
3086
|
+
await reader.cancel();
|
|
3087
|
+
} catch {}
|
|
3088
|
+
reader.releaseLock();
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
/**
|
|
3092
|
+
* Helper to convert a Response with SSE stream directly to AsyncIterable
|
|
3093
|
+
* @param response - Response object with SSE stream
|
|
3094
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
3095
|
+
*/
|
|
3096
|
+
async function* responseToAsyncIterable(response, signal) {
|
|
3097
|
+
if (!response.ok) throw new Error(`Response not ok: ${response.status} ${response.statusText}`);
|
|
3098
|
+
if (!response.body) throw new Error("No response body");
|
|
3099
|
+
yield* parseSSEStream(response.body, signal);
|
|
3100
|
+
}
|
|
3101
|
+
/**
|
|
3102
|
+
* Create an SSE-formatted ReadableStream from an AsyncIterable
|
|
3103
|
+
* (Useful for Worker endpoints that need to forward AsyncIterable as SSE)
|
|
3104
|
+
* @param events - AsyncIterable of events
|
|
3105
|
+
* @param options - Stream options
|
|
3106
|
+
*/
|
|
3107
|
+
function asyncIterableToSSEStream(events, options) {
|
|
3108
|
+
const encoder = new TextEncoder();
|
|
3109
|
+
const serialize = options?.serialize || JSON.stringify;
|
|
3110
|
+
return new ReadableStream({
|
|
3111
|
+
async start(controller) {
|
|
3112
|
+
try {
|
|
3113
|
+
for await (const event of events) {
|
|
3114
|
+
if (options?.signal?.aborted) {
|
|
3115
|
+
controller.error(/* @__PURE__ */ new Error("Operation was aborted"));
|
|
3116
|
+
break;
|
|
3117
|
+
}
|
|
3118
|
+
const sseEvent = `data: ${serialize(event)}\n\n`;
|
|
3119
|
+
controller.enqueue(encoder.encode(sseEvent));
|
|
3120
|
+
}
|
|
3121
|
+
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
|
3122
|
+
} catch (error) {
|
|
3123
|
+
controller.error(error);
|
|
3124
|
+
} finally {
|
|
3125
|
+
controller.close();
|
|
3126
|
+
}
|
|
3127
|
+
},
|
|
3128
|
+
cancel() {}
|
|
3129
|
+
});
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
//#endregion
|
|
3133
|
+
//#region src/local-mount-sync.ts
|
|
3134
|
+
const DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
3135
|
+
const DEFAULT_ECHO_SUPPRESS_TTL_MS = 2e3;
|
|
3136
|
+
const MAX_BACKOFF_MS = 3e4;
|
|
3137
|
+
const SYNC_CONCURRENCY = 5;
|
|
3138
|
+
/**
|
|
3139
|
+
* Manages bidirectional sync between an R2 binding and a container directory.
|
|
3140
|
+
*
|
|
3141
|
+
* R2 -> Container: polls bucket.list() to detect changes, then transfers diffs.
|
|
3142
|
+
* Container -> R2: uses inotifywait via the watch API to detect file changes.
|
|
3143
|
+
*/
|
|
3144
|
+
var LocalMountSyncManager = class {
|
|
3145
|
+
bucket;
|
|
3146
|
+
mountPath;
|
|
3147
|
+
prefix;
|
|
3148
|
+
readOnly;
|
|
3149
|
+
client;
|
|
3150
|
+
sessionId;
|
|
3151
|
+
logger;
|
|
3152
|
+
pollIntervalMs;
|
|
3153
|
+
echoSuppressTtlMs;
|
|
3154
|
+
snapshot = /* @__PURE__ */ new Map();
|
|
3155
|
+
echoSuppressSet = /* @__PURE__ */ new Set();
|
|
3156
|
+
pollTimer = null;
|
|
3157
|
+
watchReconnectTimer = null;
|
|
3158
|
+
watchAbortController = null;
|
|
3159
|
+
running = false;
|
|
3160
|
+
consecutivePollFailures = 0;
|
|
3161
|
+
consecutiveWatchFailures = 0;
|
|
3162
|
+
constructor(options) {
|
|
3163
|
+
this.bucket = options.bucket;
|
|
3164
|
+
this.mountPath = options.mountPath;
|
|
3165
|
+
this.prefix = options.prefix;
|
|
3166
|
+
this.readOnly = options.readOnly;
|
|
3167
|
+
this.client = options.client;
|
|
3168
|
+
this.sessionId = options.sessionId;
|
|
3169
|
+
this.logger = options.logger.child({ operation: "local-mount-sync" });
|
|
3170
|
+
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
3171
|
+
this.echoSuppressTtlMs = options.echoSuppressTtlMs ?? DEFAULT_ECHO_SUPPRESS_TTL_MS;
|
|
3172
|
+
}
|
|
3173
|
+
/**
|
|
3174
|
+
* Start bidirectional sync. Performs initial full sync, then starts
|
|
3175
|
+
* the R2 poll loop and (if not readOnly) the container watch loop.
|
|
3176
|
+
*/
|
|
3177
|
+
async start() {
|
|
3178
|
+
this.running = true;
|
|
3179
|
+
await this.client.files.mkdir(this.mountPath, this.sessionId, { recursive: true });
|
|
3180
|
+
await this.fullSyncR2ToContainer();
|
|
3181
|
+
this.schedulePoll();
|
|
3182
|
+
if (!this.readOnly) this.startContainerWatch();
|
|
3183
|
+
this.logger.info("Local mount sync started", {
|
|
3184
|
+
mountPath: this.mountPath,
|
|
3185
|
+
prefix: this.prefix,
|
|
3186
|
+
readOnly: this.readOnly,
|
|
3187
|
+
pollIntervalMs: this.pollIntervalMs
|
|
3188
|
+
});
|
|
3189
|
+
}
|
|
3190
|
+
/**
|
|
3191
|
+
* Stop all sync activity and clean up resources.
|
|
3192
|
+
*/
|
|
3193
|
+
async stop() {
|
|
3194
|
+
this.running = false;
|
|
3195
|
+
if (this.pollTimer) {
|
|
3196
|
+
clearTimeout(this.pollTimer);
|
|
3197
|
+
this.pollTimer = null;
|
|
3198
|
+
}
|
|
3199
|
+
if (this.watchReconnectTimer) {
|
|
3200
|
+
clearTimeout(this.watchReconnectTimer);
|
|
3201
|
+
this.watchReconnectTimer = null;
|
|
3202
|
+
}
|
|
3203
|
+
if (this.watchAbortController) {
|
|
3204
|
+
this.watchAbortController.abort();
|
|
3205
|
+
this.watchAbortController = null;
|
|
3206
|
+
}
|
|
3207
|
+
this.snapshot.clear();
|
|
3208
|
+
this.echoSuppressSet.clear();
|
|
3209
|
+
this.logger.info("Local mount sync stopped", { mountPath: this.mountPath });
|
|
3210
|
+
}
|
|
3211
|
+
async fullSyncR2ToContainer() {
|
|
3212
|
+
const objects = await this.listAllR2Objects();
|
|
3213
|
+
const newSnapshot = /* @__PURE__ */ new Map();
|
|
3214
|
+
for (let i = 0; i < objects.length; i += SYNC_CONCURRENCY) {
|
|
3215
|
+
const batch = objects.slice(i, i + SYNC_CONCURRENCY);
|
|
3216
|
+
await Promise.all(batch.map(async (obj) => {
|
|
3217
|
+
const containerPath = this.r2KeyToContainerPath(obj.key);
|
|
3218
|
+
newSnapshot.set(obj.key, {
|
|
3219
|
+
etag: obj.etag,
|
|
3220
|
+
size: obj.size
|
|
3221
|
+
});
|
|
3222
|
+
await this.ensureParentDir(containerPath);
|
|
3223
|
+
await this.transferR2ObjectToContainer(obj.key, containerPath);
|
|
3224
|
+
}));
|
|
3225
|
+
}
|
|
3226
|
+
this.snapshot = newSnapshot;
|
|
3227
|
+
this.logger.debug("Initial R2 -> Container sync complete", { objectCount: objects.length });
|
|
3228
|
+
}
|
|
3229
|
+
schedulePoll() {
|
|
3230
|
+
if (!this.running) return;
|
|
3231
|
+
const backoffMs = this.consecutivePollFailures > 0 ? Math.min(this.pollIntervalMs * 2 ** this.consecutivePollFailures, MAX_BACKOFF_MS) : this.pollIntervalMs;
|
|
3232
|
+
this.pollTimer = setTimeout(async () => {
|
|
3233
|
+
try {
|
|
3234
|
+
await this.pollR2ForChanges();
|
|
3235
|
+
this.consecutivePollFailures = 0;
|
|
3236
|
+
} catch (error) {
|
|
3237
|
+
this.consecutivePollFailures++;
|
|
3238
|
+
this.logger.error("R2 poll cycle failed", error instanceof Error ? error : new Error(String(error)));
|
|
3239
|
+
}
|
|
3240
|
+
this.schedulePoll();
|
|
3241
|
+
}, backoffMs);
|
|
3242
|
+
}
|
|
3243
|
+
async pollR2ForChanges() {
|
|
3244
|
+
const objects = await this.listAllR2Objects();
|
|
3245
|
+
const newSnapshot = /* @__PURE__ */ new Map();
|
|
3246
|
+
const changed = [];
|
|
3247
|
+
for (const obj of objects) {
|
|
3248
|
+
newSnapshot.set(obj.key, {
|
|
3249
|
+
etag: obj.etag,
|
|
3250
|
+
size: obj.size
|
|
3251
|
+
});
|
|
3252
|
+
const existing = this.snapshot.get(obj.key);
|
|
3253
|
+
if (!existing || existing.etag !== obj.etag) changed.push({
|
|
3254
|
+
key: obj.key,
|
|
3255
|
+
action: existing ? "modified" : "created"
|
|
3256
|
+
});
|
|
3257
|
+
}
|
|
3258
|
+
for (let i = 0; i < changed.length; i += SYNC_CONCURRENCY) {
|
|
3259
|
+
const batch = changed.slice(i, i + SYNC_CONCURRENCY);
|
|
3260
|
+
await Promise.all(batch.map(async ({ key, action }) => {
|
|
3261
|
+
try {
|
|
3262
|
+
const containerPath = this.r2KeyToContainerPath(key);
|
|
3263
|
+
await this.ensureParentDir(containerPath);
|
|
3264
|
+
this.suppressEcho(containerPath);
|
|
3265
|
+
await this.transferR2ObjectToContainer(key, containerPath);
|
|
3266
|
+
this.logger.debug("R2 -> Container: synced object", {
|
|
3267
|
+
key,
|
|
3268
|
+
action
|
|
3269
|
+
});
|
|
3270
|
+
} catch (error) {
|
|
3271
|
+
this.logger.error(`R2 -> Container: failed to sync object ${key}`, error instanceof Error ? error : new Error(String(error)));
|
|
3272
|
+
}
|
|
3273
|
+
}));
|
|
3274
|
+
}
|
|
3275
|
+
for (const [key] of this.snapshot) if (!newSnapshot.has(key)) {
|
|
3276
|
+
const containerPath = this.r2KeyToContainerPath(key);
|
|
3277
|
+
this.suppressEcho(containerPath);
|
|
3278
|
+
try {
|
|
3279
|
+
await this.client.files.deleteFile(containerPath, this.sessionId);
|
|
3280
|
+
this.logger.debug("R2 -> Container: deleted file", { key });
|
|
3281
|
+
} catch (error) {
|
|
3282
|
+
this.logger.error("R2 -> Container: failed to delete", error instanceof Error ? error : new Error(String(error)));
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
this.snapshot = newSnapshot;
|
|
3286
|
+
}
|
|
3287
|
+
async listAllR2Objects() {
|
|
3288
|
+
const results = [];
|
|
3289
|
+
let cursor;
|
|
3290
|
+
do {
|
|
3291
|
+
const listResult = await this.bucket.list({
|
|
3292
|
+
...this.prefix && { prefix: this.prefix },
|
|
3293
|
+
...cursor && { cursor }
|
|
3294
|
+
});
|
|
3295
|
+
for (const obj of listResult.objects) results.push({
|
|
3296
|
+
key: obj.key,
|
|
3297
|
+
etag: obj.etag,
|
|
3298
|
+
size: obj.size
|
|
3299
|
+
});
|
|
3300
|
+
cursor = listResult.truncated ? listResult.cursor : void 0;
|
|
3301
|
+
} while (cursor);
|
|
3302
|
+
return results;
|
|
3303
|
+
}
|
|
3304
|
+
async transferR2ObjectToContainer(key, containerPath) {
|
|
3305
|
+
const obj = await this.bucket.get(key);
|
|
3306
|
+
if (!obj) return;
|
|
3307
|
+
const arrayBuffer = await obj.arrayBuffer();
|
|
3308
|
+
const base64 = uint8ArrayToBase64(new Uint8Array(arrayBuffer));
|
|
3309
|
+
await this.client.files.writeFile(containerPath, base64, this.sessionId, { encoding: "base64" });
|
|
3310
|
+
}
|
|
3311
|
+
async ensureParentDir(containerPath) {
|
|
3312
|
+
const parentDir = containerPath.substring(0, containerPath.lastIndexOf("/"));
|
|
3313
|
+
if (parentDir && parentDir !== this.mountPath) await this.client.files.mkdir(parentDir, this.sessionId, { recursive: true });
|
|
3314
|
+
}
|
|
3315
|
+
startContainerWatch() {
|
|
3316
|
+
this.watchAbortController = new AbortController();
|
|
3317
|
+
this.runWatchWithRetry();
|
|
3318
|
+
}
|
|
3319
|
+
runWatchWithRetry() {
|
|
3320
|
+
if (!this.running) return;
|
|
3321
|
+
this.runContainerWatchLoop().then(() => {
|
|
3322
|
+
this.consecutiveWatchFailures = 0;
|
|
3323
|
+
this.scheduleWatchReconnect();
|
|
3324
|
+
}).catch((error) => {
|
|
3325
|
+
if (!this.running) return;
|
|
3326
|
+
this.consecutiveWatchFailures++;
|
|
3327
|
+
this.logger.error("Container watch loop failed", error instanceof Error ? error : new Error(String(error)));
|
|
3328
|
+
this.scheduleWatchReconnect();
|
|
3329
|
+
});
|
|
3330
|
+
}
|
|
3331
|
+
scheduleWatchReconnect() {
|
|
3332
|
+
if (!this.running) return;
|
|
3333
|
+
const backoffMs = this.consecutiveWatchFailures > 0 ? Math.min(this.pollIntervalMs * 2 ** this.consecutiveWatchFailures, MAX_BACKOFF_MS) : this.pollIntervalMs;
|
|
3334
|
+
this.logger.debug("Reconnecting container watch", {
|
|
3335
|
+
backoffMs,
|
|
3336
|
+
failures: this.consecutiveWatchFailures
|
|
3337
|
+
});
|
|
3338
|
+
this.watchReconnectTimer = setTimeout(() => {
|
|
3339
|
+
this.watchReconnectTimer = null;
|
|
3340
|
+
if (!this.running) return;
|
|
3341
|
+
this.watchAbortController = new AbortController();
|
|
3342
|
+
this.runWatchWithRetry();
|
|
3343
|
+
}, backoffMs);
|
|
3344
|
+
}
|
|
3345
|
+
async runContainerWatchLoop() {
|
|
3346
|
+
const stream = await this.client.watch.watch({
|
|
3347
|
+
path: this.mountPath,
|
|
3348
|
+
recursive: true,
|
|
3349
|
+
sessionId: this.sessionId
|
|
3350
|
+
});
|
|
3351
|
+
for await (const event of parseSSEStream(stream, this.watchAbortController?.signal)) {
|
|
3352
|
+
if (!this.running) break;
|
|
3353
|
+
this.consecutiveWatchFailures = 0;
|
|
3354
|
+
if (event.type !== "event") continue;
|
|
3355
|
+
if (event.isDirectory) continue;
|
|
3356
|
+
const containerPath = event.path;
|
|
3357
|
+
if (this.echoSuppressSet.has(containerPath)) continue;
|
|
3358
|
+
const r2Key = this.containerPathToR2Key(containerPath);
|
|
3359
|
+
if (!r2Key) continue;
|
|
3360
|
+
try {
|
|
3361
|
+
switch (event.eventType) {
|
|
3362
|
+
case "create":
|
|
3363
|
+
case "modify":
|
|
3364
|
+
case "move_to":
|
|
3365
|
+
await this.uploadFileToR2(containerPath, r2Key);
|
|
3366
|
+
this.logger.debug("Container -> R2: synced file", {
|
|
3367
|
+
path: containerPath,
|
|
3368
|
+
key: r2Key,
|
|
3369
|
+
action: event.eventType
|
|
3370
|
+
});
|
|
3371
|
+
break;
|
|
3372
|
+
case "delete":
|
|
3373
|
+
case "move_from":
|
|
3374
|
+
await this.bucket.delete(r2Key);
|
|
3375
|
+
this.snapshot.delete(r2Key);
|
|
3376
|
+
this.logger.debug("Container -> R2: deleted object", {
|
|
3377
|
+
path: containerPath,
|
|
3378
|
+
key: r2Key
|
|
3379
|
+
});
|
|
3380
|
+
break;
|
|
3381
|
+
}
|
|
3382
|
+
} catch (error) {
|
|
3383
|
+
this.logger.error(`Container -> R2 sync failed for ${containerPath}`, error instanceof Error ? error : new Error(String(error)));
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
/**
|
|
3388
|
+
* Read a container file and upload it to R2, then update the local
|
|
3389
|
+
* snapshot so the next poll cycle doesn't echo the write back.
|
|
3390
|
+
*/
|
|
3391
|
+
async uploadFileToR2(containerPath, r2Key) {
|
|
3392
|
+
const bytes = base64ToUint8Array((await this.client.files.readFile(containerPath, this.sessionId, { encoding: "base64" })).content);
|
|
3393
|
+
await this.bucket.put(r2Key, bytes);
|
|
3394
|
+
const head = await this.bucket.head(r2Key);
|
|
3395
|
+
if (head) this.snapshot.set(r2Key, {
|
|
3396
|
+
etag: head.etag,
|
|
3397
|
+
size: head.size
|
|
3398
|
+
});
|
|
3399
|
+
}
|
|
3400
|
+
suppressEcho(containerPath) {
|
|
3401
|
+
this.echoSuppressSet.add(containerPath);
|
|
3402
|
+
setTimeout(() => {
|
|
3403
|
+
this.echoSuppressSet.delete(containerPath);
|
|
3404
|
+
}, this.echoSuppressTtlMs);
|
|
3405
|
+
}
|
|
3406
|
+
r2KeyToContainerPath(key) {
|
|
3407
|
+
let relativePath = key;
|
|
3408
|
+
if (this.prefix) relativePath = key.startsWith(this.prefix) ? key.slice(this.prefix.length) : key;
|
|
3409
|
+
return path.join(this.mountPath, relativePath);
|
|
3410
|
+
}
|
|
3411
|
+
containerPathToR2Key(containerPath) {
|
|
3412
|
+
const resolved = path.resolve(containerPath);
|
|
3413
|
+
const mount = path.resolve(this.mountPath);
|
|
3414
|
+
if (!resolved.startsWith(mount)) return null;
|
|
3415
|
+
const relativePath = path.relative(mount, resolved);
|
|
3416
|
+
if (!relativePath || relativePath.startsWith("..")) return null;
|
|
3417
|
+
return this.prefix ? path.join(this.prefix, relativePath) : relativePath;
|
|
3418
|
+
}
|
|
3419
|
+
};
|
|
3420
|
+
function uint8ArrayToBase64(bytes) {
|
|
3421
|
+
return Buffer.from(bytes).toString("base64");
|
|
3422
|
+
}
|
|
3423
|
+
function base64ToUint8Array(base64) {
|
|
3424
|
+
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3031
3427
|
//#endregion
|
|
3032
3428
|
//#region src/pty/proxy.ts
|
|
3033
3429
|
async function proxyTerminal(stub, sessionId, request, options) {
|
|
@@ -3054,14 +3450,14 @@ async function proxyToSandbox(request, env) {
|
|
|
3054
3450
|
const url = new URL(request.url);
|
|
3055
3451
|
const routeInfo = extractSandboxRoute(url);
|
|
3056
3452
|
if (!routeInfo) return null;
|
|
3057
|
-
const { sandboxId, port, path, token } = routeInfo;
|
|
3453
|
+
const { sandboxId, port, path: path$1, token } = routeInfo;
|
|
3058
3454
|
const sandbox = getSandbox(env.Sandbox, sandboxId, { normalizeId: true });
|
|
3059
3455
|
if (port !== 3e3) {
|
|
3060
3456
|
if (!await sandbox.validatePortToken(port, token)) {
|
|
3061
3457
|
logger.warn("Invalid token access blocked", {
|
|
3062
3458
|
port,
|
|
3063
3459
|
sandboxId,
|
|
3064
|
-
path,
|
|
3460
|
+
path: path$1,
|
|
3065
3461
|
hostname: url.hostname,
|
|
3066
3462
|
url: request.url,
|
|
3067
3463
|
method: request.method,
|
|
@@ -3078,8 +3474,8 @@ async function proxyToSandbox(request, env) {
|
|
|
3078
3474
|
}
|
|
3079
3475
|
if (request.headers.get("Upgrade")?.toLowerCase() === "websocket") return await sandbox.fetch(switchPort(request, port));
|
|
3080
3476
|
let proxyUrl;
|
|
3081
|
-
if (port !== 3e3) proxyUrl = `http://localhost:${port}${path}${url.search}`;
|
|
3082
|
-
else proxyUrl = `http://localhost:3000${path}${url.search}`;
|
|
3477
|
+
if (port !== 3e3) proxyUrl = `http://localhost:${port}${path$1}${url.search}`;
|
|
3478
|
+
else proxyUrl = `http://localhost:3000${path$1}${url.search}`;
|
|
3083
3479
|
const headers = {
|
|
3084
3480
|
"X-Original-URL": request.url,
|
|
3085
3481
|
"X-Forwarded-Host": url.hostname,
|
|
@@ -3141,106 +3537,6 @@ function isLocalhostPattern(hostname) {
|
|
|
3141
3537
|
return hostPart === "localhost" || hostPart === "127.0.0.1" || hostPart === "0.0.0.0";
|
|
3142
3538
|
}
|
|
3143
3539
|
|
|
3144
|
-
//#endregion
|
|
3145
|
-
//#region src/sse-parser.ts
|
|
3146
|
-
/**
|
|
3147
|
-
* Server-Sent Events (SSE) parser for streaming responses
|
|
3148
|
-
* Converts ReadableStream<Uint8Array> to typed AsyncIterable<T>
|
|
3149
|
-
*/
|
|
3150
|
-
/**
|
|
3151
|
-
* Parse a ReadableStream of SSE events into typed AsyncIterable
|
|
3152
|
-
* @param stream - The ReadableStream from fetch response
|
|
3153
|
-
* @param signal - Optional AbortSignal for cancellation
|
|
3154
|
-
*/
|
|
3155
|
-
async function* parseSSEStream(stream, signal) {
|
|
3156
|
-
const reader = stream.getReader();
|
|
3157
|
-
const decoder = new TextDecoder();
|
|
3158
|
-
let buffer = "";
|
|
3159
|
-
let currentEvent = { data: [] };
|
|
3160
|
-
let isAborted = signal?.aborted ?? false;
|
|
3161
|
-
const emitEvent = (data) => {
|
|
3162
|
-
if (data === "[DONE]" || data.trim() === "") return;
|
|
3163
|
-
try {
|
|
3164
|
-
return JSON.parse(data);
|
|
3165
|
-
} catch {
|
|
3166
|
-
return;
|
|
3167
|
-
}
|
|
3168
|
-
};
|
|
3169
|
-
const onAbort = () => {
|
|
3170
|
-
isAborted = true;
|
|
3171
|
-
reader.cancel().catch(() => {});
|
|
3172
|
-
};
|
|
3173
|
-
if (signal && !signal.aborted) signal.addEventListener("abort", onAbort);
|
|
3174
|
-
try {
|
|
3175
|
-
while (true) {
|
|
3176
|
-
if (isAborted) throw new Error("Operation was aborted");
|
|
3177
|
-
const { done, value } = await reader.read();
|
|
3178
|
-
if (isAborted) throw new Error("Operation was aborted");
|
|
3179
|
-
if (done) break;
|
|
3180
|
-
buffer += decoder.decode(value, { stream: true });
|
|
3181
|
-
const parsed = parseSSEFrames(buffer, currentEvent);
|
|
3182
|
-
buffer = parsed.remaining;
|
|
3183
|
-
currentEvent = parsed.currentEvent;
|
|
3184
|
-
for (const frame of parsed.events) {
|
|
3185
|
-
const event = emitEvent(frame.data);
|
|
3186
|
-
if (event !== void 0) yield event;
|
|
3187
|
-
}
|
|
3188
|
-
}
|
|
3189
|
-
if (isAborted) throw new Error("Operation was aborted");
|
|
3190
|
-
const finalParsed = parseSSEFrames(`${buffer}\n\n`, currentEvent);
|
|
3191
|
-
for (const frame of finalParsed.events) {
|
|
3192
|
-
const event = emitEvent(frame.data);
|
|
3193
|
-
if (event !== void 0) yield event;
|
|
3194
|
-
}
|
|
3195
|
-
} finally {
|
|
3196
|
-
if (signal) signal.removeEventListener("abort", onAbort);
|
|
3197
|
-
try {
|
|
3198
|
-
await reader.cancel();
|
|
3199
|
-
} catch {}
|
|
3200
|
-
reader.releaseLock();
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
/**
|
|
3204
|
-
* Helper to convert a Response with SSE stream directly to AsyncIterable
|
|
3205
|
-
* @param response - Response object with SSE stream
|
|
3206
|
-
* @param signal - Optional AbortSignal for cancellation
|
|
3207
|
-
*/
|
|
3208
|
-
async function* responseToAsyncIterable(response, signal) {
|
|
3209
|
-
if (!response.ok) throw new Error(`Response not ok: ${response.status} ${response.statusText}`);
|
|
3210
|
-
if (!response.body) throw new Error("No response body");
|
|
3211
|
-
yield* parseSSEStream(response.body, signal);
|
|
3212
|
-
}
|
|
3213
|
-
/**
|
|
3214
|
-
* Create an SSE-formatted ReadableStream from an AsyncIterable
|
|
3215
|
-
* (Useful for Worker endpoints that need to forward AsyncIterable as SSE)
|
|
3216
|
-
* @param events - AsyncIterable of events
|
|
3217
|
-
* @param options - Stream options
|
|
3218
|
-
*/
|
|
3219
|
-
function asyncIterableToSSEStream(events, options) {
|
|
3220
|
-
const encoder = new TextEncoder();
|
|
3221
|
-
const serialize = options?.serialize || JSON.stringify;
|
|
3222
|
-
return new ReadableStream({
|
|
3223
|
-
async start(controller) {
|
|
3224
|
-
try {
|
|
3225
|
-
for await (const event of events) {
|
|
3226
|
-
if (options?.signal?.aborted) {
|
|
3227
|
-
controller.error(/* @__PURE__ */ new Error("Operation was aborted"));
|
|
3228
|
-
break;
|
|
3229
|
-
}
|
|
3230
|
-
const sseEvent = `data: ${serialize(event)}\n\n`;
|
|
3231
|
-
controller.enqueue(encoder.encode(sseEvent));
|
|
3232
|
-
}
|
|
3233
|
-
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
|
3234
|
-
} catch (error) {
|
|
3235
|
-
controller.error(error);
|
|
3236
|
-
} finally {
|
|
3237
|
-
controller.close();
|
|
3238
|
-
}
|
|
3239
|
-
},
|
|
3240
|
-
cancel() {}
|
|
3241
|
-
});
|
|
3242
|
-
}
|
|
3243
|
-
|
|
3244
3540
|
//#endregion
|
|
3245
3541
|
//#region src/storage-mount/errors.ts
|
|
3246
3542
|
/**
|
|
@@ -3390,21 +3686,79 @@ function buildS3fsSource(bucket, prefix) {
|
|
|
3390
3686
|
* This file is auto-updated by .github/changeset-version.ts during releases
|
|
3391
3687
|
* DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
|
|
3392
3688
|
*/
|
|
3393
|
-
const SDK_VERSION = "0.7.
|
|
3689
|
+
const SDK_VERSION = "0.7.18";
|
|
3394
3690
|
|
|
3395
3691
|
//#endregion
|
|
3396
3692
|
//#region src/sandbox.ts
|
|
3693
|
+
const sandboxConfigurationCache = /* @__PURE__ */ new WeakMap();
|
|
3694
|
+
function getNamespaceConfigurationCache(namespace) {
|
|
3695
|
+
const existing = sandboxConfigurationCache.get(namespace);
|
|
3696
|
+
if (existing) return existing;
|
|
3697
|
+
const created = /* @__PURE__ */ new Map();
|
|
3698
|
+
sandboxConfigurationCache.set(namespace, created);
|
|
3699
|
+
return created;
|
|
3700
|
+
}
|
|
3701
|
+
function sameContainerTimeouts(left, right) {
|
|
3702
|
+
return left?.instanceGetTimeoutMS === right?.instanceGetTimeoutMS && left?.portReadyTimeoutMS === right?.portReadyTimeoutMS && left?.waitIntervalMS === right?.waitIntervalMS;
|
|
3703
|
+
}
|
|
3704
|
+
function buildSandboxConfiguration(effectiveId, options, cached) {
|
|
3705
|
+
const configuration = {};
|
|
3706
|
+
if (cached?.sandboxName !== effectiveId || cached.normalizeId !== options?.normalizeId) configuration.sandboxName = {
|
|
3707
|
+
name: effectiveId,
|
|
3708
|
+
normalizeId: options?.normalizeId
|
|
3709
|
+
};
|
|
3710
|
+
if (options?.baseUrl !== void 0 && cached?.baseUrl !== options.baseUrl) configuration.baseUrl = options.baseUrl;
|
|
3711
|
+
if (options?.sleepAfter !== void 0 && cached?.sleepAfter !== options.sleepAfter) configuration.sleepAfter = options.sleepAfter;
|
|
3712
|
+
if (options?.keepAlive !== void 0 && cached?.keepAlive !== options.keepAlive) configuration.keepAlive = options.keepAlive;
|
|
3713
|
+
if (options?.containerTimeouts && !sameContainerTimeouts(cached?.containerTimeouts, options.containerTimeouts)) configuration.containerTimeouts = options.containerTimeouts;
|
|
3714
|
+
return configuration;
|
|
3715
|
+
}
|
|
3716
|
+
function hasSandboxConfiguration(configuration) {
|
|
3717
|
+
return configuration.sandboxName !== void 0 || configuration.baseUrl !== void 0 || configuration.sleepAfter !== void 0 || configuration.keepAlive !== void 0 || configuration.containerTimeouts !== void 0;
|
|
3718
|
+
}
|
|
3719
|
+
function mergeSandboxConfiguration(cached, configuration) {
|
|
3720
|
+
return {
|
|
3721
|
+
...cached,
|
|
3722
|
+
...configuration.sandboxName && {
|
|
3723
|
+
sandboxName: configuration.sandboxName.name,
|
|
3724
|
+
normalizeId: configuration.sandboxName.normalizeId
|
|
3725
|
+
},
|
|
3726
|
+
...configuration.baseUrl !== void 0 && { baseUrl: configuration.baseUrl },
|
|
3727
|
+
...configuration.sleepAfter !== void 0 && { sleepAfter: configuration.sleepAfter },
|
|
3728
|
+
...configuration.keepAlive !== void 0 && { keepAlive: configuration.keepAlive },
|
|
3729
|
+
...configuration.containerTimeouts !== void 0 && { containerTimeouts: configuration.containerTimeouts }
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
function applySandboxConfiguration(stub, configuration) {
|
|
3733
|
+
if (stub.configure) return stub.configure(configuration);
|
|
3734
|
+
const operations = [];
|
|
3735
|
+
if (configuration.sandboxName) operations.push(stub.setSandboxName?.(configuration.sandboxName.name, configuration.sandboxName.normalizeId) ?? Promise.resolve());
|
|
3736
|
+
if (configuration.baseUrl !== void 0) operations.push(stub.setBaseUrl?.(configuration.baseUrl) ?? Promise.resolve());
|
|
3737
|
+
if (configuration.sleepAfter !== void 0) operations.push(stub.setSleepAfter?.(configuration.sleepAfter) ?? Promise.resolve());
|
|
3738
|
+
if (configuration.keepAlive !== void 0) operations.push(stub.setKeepAlive?.(configuration.keepAlive) ?? Promise.resolve());
|
|
3739
|
+
if (configuration.containerTimeouts !== void 0) operations.push(stub.setContainerTimeouts?.(configuration.containerTimeouts) ?? Promise.resolve());
|
|
3740
|
+
return Promise.all(operations).then(() => void 0);
|
|
3741
|
+
}
|
|
3397
3742
|
function getSandbox(ns, id, options) {
|
|
3398
3743
|
const sanitizedId = sanitizeSandboxId(id);
|
|
3399
3744
|
const effectiveId = options?.normalizeId ? sanitizedId.toLowerCase() : sanitizedId;
|
|
3400
3745
|
const hasUppercase = /[A-Z]/.test(sanitizedId);
|
|
3401
3746
|
if (!options?.normalizeId && hasUppercase) createLogger({ component: "sandbox-do" }).warn(`Sandbox ID "${sanitizedId}" contains uppercase letters, which causes issues with preview URLs (hostnames are case-insensitive). normalizeId will default to true in a future version to prevent this. Use lowercase IDs or pass { normalizeId: true } to prepare.`);
|
|
3402
3747
|
const stub = getContainer(ns, effectiveId);
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
if (
|
|
3407
|
-
|
|
3748
|
+
const namespaceCache = getNamespaceConfigurationCache(ns);
|
|
3749
|
+
const cachedConfiguration = namespaceCache.get(effectiveId);
|
|
3750
|
+
const configuration = buildSandboxConfiguration(effectiveId, options, cachedConfiguration);
|
|
3751
|
+
if (hasSandboxConfiguration(configuration)) {
|
|
3752
|
+
const nextConfiguration = mergeSandboxConfiguration(cachedConfiguration, configuration);
|
|
3753
|
+
namespaceCache.set(effectiveId, nextConfiguration);
|
|
3754
|
+
applySandboxConfiguration(stub, configuration).catch(() => {
|
|
3755
|
+
if (cachedConfiguration) {
|
|
3756
|
+
namespaceCache.set(effectiveId, cachedConfiguration);
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
namespaceCache.delete(effectiveId);
|
|
3760
|
+
});
|
|
3761
|
+
}
|
|
3408
3762
|
const defaultSessionId = `sandbox-${effectiveId}`;
|
|
3409
3763
|
const enhancedMethods = {
|
|
3410
3764
|
fetch: (request) => stub.fetch(request),
|
|
@@ -3618,6 +3972,13 @@ var Sandbox = class Sandbox extends Container {
|
|
|
3618
3972
|
await this.ctx.storage.put("normalizeId", this.normalizeId);
|
|
3619
3973
|
}
|
|
3620
3974
|
}
|
|
3975
|
+
async configure(configuration) {
|
|
3976
|
+
if (configuration.sandboxName) await this.setSandboxName(configuration.sandboxName.name, configuration.sandboxName.normalizeId);
|
|
3977
|
+
if (configuration.baseUrl !== void 0) await this.setBaseUrl(configuration.baseUrl);
|
|
3978
|
+
if (configuration.sleepAfter !== void 0) await this.setSleepAfter(configuration.sleepAfter);
|
|
3979
|
+
if (configuration.keepAlive !== void 0) await this.setKeepAlive(configuration.keepAlive);
|
|
3980
|
+
if (configuration.containerTimeouts !== void 0) await this.setContainerTimeouts(configuration.containerTimeouts);
|
|
3981
|
+
}
|
|
3621
3982
|
async setBaseUrl(baseUrl) {
|
|
3622
3983
|
if (!this.baseUrl) {
|
|
3623
3984
|
this.baseUrl = baseUrl;
|
|
@@ -3700,8 +4061,67 @@ var Sandbox = class Sandbox extends Container {
|
|
|
3700
4061
|
waitIntervalMS: parseAndValidate(getEnvString(env, "SANDBOX_POLL_INTERVAL_MS"), "waitIntervalMS", 100, 5e3)
|
|
3701
4062
|
};
|
|
3702
4063
|
}
|
|
4064
|
+
/**
|
|
4065
|
+
* Mount an S3-compatible bucket as a local directory.
|
|
4066
|
+
*
|
|
4067
|
+
* Requires explicit endpoint URL for production. Credentials are auto-detected from environment
|
|
4068
|
+
* variables or can be provided explicitly.
|
|
4069
|
+
*
|
|
4070
|
+
* @param bucket - Bucket name (or R2 binding name when localBucket is true)
|
|
4071
|
+
* @param mountPath - Absolute path in container to mount at
|
|
4072
|
+
* @param options - Mount configuration
|
|
4073
|
+
* @throws MissingCredentialsError if no credentials found in environment
|
|
4074
|
+
* @throws S3FSMountError if S3FS mount command fails
|
|
4075
|
+
* @throws InvalidMountConfigError if bucket name, mount path, or endpoint is invalid
|
|
4076
|
+
*/
|
|
3703
4077
|
async mountBucket(bucket, mountPath, options) {
|
|
3704
4078
|
this.logger.info(`Mounting bucket ${bucket} to ${mountPath}`);
|
|
4079
|
+
if ("localBucket" in options && options.localBucket) {
|
|
4080
|
+
await this.mountBucketLocal(bucket, mountPath, options);
|
|
4081
|
+
return;
|
|
4082
|
+
}
|
|
4083
|
+
await this.mountBucketFuse(bucket, mountPath, options);
|
|
4084
|
+
}
|
|
4085
|
+
/**
|
|
4086
|
+
* Local dev mount: bidirectional sync via R2 binding + file/watch APIs
|
|
4087
|
+
*/
|
|
4088
|
+
async mountBucketLocal(bucket, mountPath, options) {
|
|
4089
|
+
const r2Binding = this.env[bucket];
|
|
4090
|
+
if (!r2Binding || !isR2Bucket(r2Binding)) throw new InvalidMountConfigError(`R2 binding "${bucket}" not found in env or is not an R2Bucket. Make sure the binding name matches your wrangler.jsonc R2 binding.`);
|
|
4091
|
+
if (!mountPath || !mountPath.startsWith("/")) throw new InvalidMountConfigError(`Invalid mount path: "${mountPath}". Must be an absolute path starting with /`);
|
|
4092
|
+
if (this.activeMounts.has(mountPath)) throw new InvalidMountConfigError(`Mount path already in use: ${mountPath}`);
|
|
4093
|
+
const sessionId = await this.ensureDefaultSession();
|
|
4094
|
+
const syncManager = new LocalMountSyncManager({
|
|
4095
|
+
bucket: r2Binding,
|
|
4096
|
+
mountPath,
|
|
4097
|
+
prefix: options.prefix,
|
|
4098
|
+
readOnly: options.readOnly ?? false,
|
|
4099
|
+
client: this.client,
|
|
4100
|
+
sessionId,
|
|
4101
|
+
logger: this.logger
|
|
4102
|
+
});
|
|
4103
|
+
const mountInfo = {
|
|
4104
|
+
mountType: "local-sync",
|
|
4105
|
+
bucket,
|
|
4106
|
+
mountPath,
|
|
4107
|
+
syncManager,
|
|
4108
|
+
mounted: false
|
|
4109
|
+
};
|
|
4110
|
+
this.activeMounts.set(mountPath, mountInfo);
|
|
4111
|
+
try {
|
|
4112
|
+
await syncManager.start();
|
|
4113
|
+
mountInfo.mounted = true;
|
|
4114
|
+
this.logger.info(`Successfully mounted bucket ${bucket} to ${mountPath} (local sync)`);
|
|
4115
|
+
} catch (error) {
|
|
4116
|
+
await syncManager.stop();
|
|
4117
|
+
this.activeMounts.delete(mountPath);
|
|
4118
|
+
throw error;
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
/**
|
|
4122
|
+
* Production mount: S3FS-FUSE inside the container
|
|
4123
|
+
*/
|
|
4124
|
+
async mountBucketFuse(bucket, mountPath, options) {
|
|
3705
4125
|
const prefix = options.prefix || void 0;
|
|
3706
4126
|
this.validateMountOptions(bucket, mountPath, {
|
|
3707
4127
|
...options,
|
|
@@ -3715,26 +4135,21 @@ var Sandbox = class Sandbox extends Container {
|
|
|
3715
4135
|
});
|
|
3716
4136
|
const credentials = detectCredentials(options, this.envVars);
|
|
3717
4137
|
const passwordFilePath = this.generatePasswordFilePath();
|
|
3718
|
-
|
|
4138
|
+
const mountInfo = {
|
|
4139
|
+
mountType: "fuse",
|
|
3719
4140
|
bucket: s3fsSource,
|
|
3720
4141
|
mountPath,
|
|
3721
4142
|
endpoint: options.endpoint,
|
|
3722
4143
|
provider,
|
|
3723
4144
|
passwordFilePath,
|
|
3724
4145
|
mounted: false
|
|
3725
|
-
}
|
|
4146
|
+
};
|
|
4147
|
+
this.activeMounts.set(mountPath, mountInfo);
|
|
3726
4148
|
try {
|
|
3727
4149
|
await this.createPasswordFile(passwordFilePath, bucket, credentials);
|
|
3728
4150
|
await this.exec(`mkdir -p ${shellEscape(mountPath)}`);
|
|
3729
4151
|
await this.executeS3FSMount(s3fsSource, mountPath, options, provider, passwordFilePath);
|
|
3730
|
-
|
|
3731
|
-
bucket: s3fsSource,
|
|
3732
|
-
mountPath,
|
|
3733
|
-
endpoint: options.endpoint,
|
|
3734
|
-
provider,
|
|
3735
|
-
passwordFilePath,
|
|
3736
|
-
mounted: true
|
|
3737
|
-
});
|
|
4152
|
+
mountInfo.mounted = true;
|
|
3738
4153
|
this.logger.info(`Successfully mounted bucket ${bucket} to ${mountPath}`);
|
|
3739
4154
|
} catch (error) {
|
|
3740
4155
|
await this.deletePasswordFile(passwordFilePath);
|
|
@@ -3752,7 +4167,11 @@ var Sandbox = class Sandbox extends Container {
|
|
|
3752
4167
|
this.logger.info(`Unmounting bucket from ${mountPath}`);
|
|
3753
4168
|
const mountInfo = this.activeMounts.get(mountPath);
|
|
3754
4169
|
if (!mountInfo) throw new InvalidMountConfigError(`No active mount found at path: ${mountPath}`);
|
|
3755
|
-
|
|
4170
|
+
if (mountInfo.mountType === "local-sync") {
|
|
4171
|
+
await mountInfo.syncManager.stop();
|
|
4172
|
+
mountInfo.mounted = false;
|
|
4173
|
+
this.activeMounts.delete(mountPath);
|
|
4174
|
+
} else try {
|
|
3756
4175
|
await this.exec(`fusermount -u ${shellEscape(mountPath)}`);
|
|
3757
4176
|
mountInfo.mounted = false;
|
|
3758
4177
|
this.activeMounts.delete(mountPath);
|
|
@@ -3765,7 +4184,6 @@ var Sandbox = class Sandbox extends Container {
|
|
|
3765
4184
|
* Validate mount options
|
|
3766
4185
|
*/
|
|
3767
4186
|
validateMountOptions(bucket, mountPath, options) {
|
|
3768
|
-
if (!options.endpoint) throw new InvalidMountConfigError("Endpoint is required. Provide the full S3-compatible endpoint URL.");
|
|
3769
4187
|
try {
|
|
3770
4188
|
new URL(options.endpoint);
|
|
3771
4189
|
} catch (error) {
|
|
@@ -3834,7 +4252,14 @@ var Sandbox = class Sandbox extends Container {
|
|
|
3834
4252
|
await this.client.desktop.stop();
|
|
3835
4253
|
} catch {}
|
|
3836
4254
|
this.client.disconnect();
|
|
3837
|
-
for (const [mountPath, mountInfo] of this.activeMounts.entries()) {
|
|
4255
|
+
for (const [mountPath, mountInfo] of this.activeMounts.entries()) if (mountInfo.mountType === "local-sync") try {
|
|
4256
|
+
await mountInfo.syncManager.stop();
|
|
4257
|
+
mountInfo.mounted = false;
|
|
4258
|
+
} catch (error) {
|
|
4259
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4260
|
+
this.logger.warn(`Failed to stop local sync for ${mountPath}: ${errorMsg}`);
|
|
4261
|
+
}
|
|
4262
|
+
else {
|
|
3838
4263
|
if (mountInfo.mounted) try {
|
|
3839
4264
|
this.logger.info(`Unmounting bucket ${mountInfo.bucket} from ${mountPath}`);
|
|
3840
4265
|
await this.exec(`fusermount -u ${shellEscape(mountPath)}`);
|
|
@@ -3878,6 +4303,7 @@ var Sandbox = class Sandbox extends Container {
|
|
|
3878
4303
|
}
|
|
3879
4304
|
async onStop() {
|
|
3880
4305
|
this.logger.debug("Sandbox stopped");
|
|
4306
|
+
for (const [, m] of this.activeMounts) if (m.mountType === "local-sync") await m.syncManager.stop().catch(() => {});
|
|
3881
4307
|
this.defaultSession = null;
|
|
3882
4308
|
this.activeMounts.clear();
|
|
3883
4309
|
await Promise.all([this.ctx.storage.delete("portTokens"), this.ctx.storage.delete("defaultSession")]);
|
|
@@ -4352,17 +4778,17 @@ var Sandbox = class Sandbox extends Container {
|
|
|
4352
4778
|
* Wait for a port to become available (for process readiness checking)
|
|
4353
4779
|
*/
|
|
4354
4780
|
async waitForPortReady(processId, command, port, options) {
|
|
4355
|
-
const { mode = "http", path = "/", status = {
|
|
4781
|
+
const { mode = "http", path: path$1 = "/", status = {
|
|
4356
4782
|
min: 200,
|
|
4357
4783
|
max: 399
|
|
4358
4784
|
}, timeout, interval = 500 } = options ?? {};
|
|
4359
|
-
const conditionStr = mode === "http" ? `port ${port} (HTTP ${path})` : `port ${port} (TCP)`;
|
|
4785
|
+
const conditionStr = mode === "http" ? `port ${port} (HTTP ${path$1})` : `port ${port} (TCP)`;
|
|
4360
4786
|
const statusMin = typeof status === "number" ? status : status.min;
|
|
4361
4787
|
const statusMax = typeof status === "number" ? status : status.max;
|
|
4362
4788
|
const stream = await this.client.ports.watchPort({
|
|
4363
4789
|
port,
|
|
4364
4790
|
mode,
|
|
4365
|
-
path,
|
|
4791
|
+
path: path$1,
|
|
4366
4792
|
statusMin,
|
|
4367
4793
|
statusMax,
|
|
4368
4794
|
processId,
|
|
@@ -4622,17 +5048,17 @@ var Sandbox = class Sandbox extends Container {
|
|
|
4622
5048
|
depth: options?.depth
|
|
4623
5049
|
});
|
|
4624
5050
|
}
|
|
4625
|
-
async mkdir(path, options = {}) {
|
|
5051
|
+
async mkdir(path$1, options = {}) {
|
|
4626
5052
|
const session = options.sessionId ?? await this.ensureDefaultSession();
|
|
4627
|
-
return this.client.files.mkdir(path, session, { recursive: options.recursive });
|
|
5053
|
+
return this.client.files.mkdir(path$1, session, { recursive: options.recursive });
|
|
4628
5054
|
}
|
|
4629
|
-
async writeFile(path, content, options = {}) {
|
|
5055
|
+
async writeFile(path$1, content, options = {}) {
|
|
4630
5056
|
const session = options.sessionId ?? await this.ensureDefaultSession();
|
|
4631
|
-
return this.client.files.writeFile(path, content, session, { encoding: options.encoding });
|
|
5057
|
+
return this.client.files.writeFile(path$1, content, session, { encoding: options.encoding });
|
|
4632
5058
|
}
|
|
4633
|
-
async deleteFile(path, sessionId) {
|
|
5059
|
+
async deleteFile(path$1, sessionId) {
|
|
4634
5060
|
const session = sessionId ?? await this.ensureDefaultSession();
|
|
4635
|
-
return this.client.files.deleteFile(path, session);
|
|
5061
|
+
return this.client.files.deleteFile(path$1, session);
|
|
4636
5062
|
}
|
|
4637
5063
|
async renameFile(oldPath, newPath, sessionId) {
|
|
4638
5064
|
const session = sessionId ?? await this.ensureDefaultSession();
|
|
@@ -4642,9 +5068,9 @@ var Sandbox = class Sandbox extends Container {
|
|
|
4642
5068
|
const session = sessionId ?? await this.ensureDefaultSession();
|
|
4643
5069
|
return this.client.files.moveFile(sourcePath, destinationPath, session);
|
|
4644
5070
|
}
|
|
4645
|
-
async readFile(path, options = {}) {
|
|
5071
|
+
async readFile(path$1, options = {}) {
|
|
4646
5072
|
const session = options.sessionId ?? await this.ensureDefaultSession();
|
|
4647
|
-
return this.client.files.readFile(path, session, { encoding: options.encoding });
|
|
5073
|
+
return this.client.files.readFile(path$1, session, { encoding: options.encoding });
|
|
4648
5074
|
}
|
|
4649
5075
|
/**
|
|
4650
5076
|
* Stream a file from the sandbox using Server-Sent Events
|
|
@@ -4652,17 +5078,17 @@ var Sandbox = class Sandbox extends Container {
|
|
|
4652
5078
|
* @param path - Path to the file to stream
|
|
4653
5079
|
* @param options - Optional session ID
|
|
4654
5080
|
*/
|
|
4655
|
-
async readFileStream(path, options = {}) {
|
|
5081
|
+
async readFileStream(path$1, options = {}) {
|
|
4656
5082
|
const session = options.sessionId ?? await this.ensureDefaultSession();
|
|
4657
|
-
return this.client.files.readFileStream(path, session);
|
|
5083
|
+
return this.client.files.readFileStream(path$1, session);
|
|
4658
5084
|
}
|
|
4659
|
-
async listFiles(path, options) {
|
|
5085
|
+
async listFiles(path$1, options) {
|
|
4660
5086
|
const session = await this.ensureDefaultSession();
|
|
4661
|
-
return this.client.files.listFiles(path, session, options);
|
|
5087
|
+
return this.client.files.listFiles(path$1, session, options);
|
|
4662
5088
|
}
|
|
4663
|
-
async exists(path, sessionId) {
|
|
5089
|
+
async exists(path$1, sessionId) {
|
|
4664
5090
|
const session = sessionId ?? await this.ensureDefaultSession();
|
|
4665
|
-
return this.client.files.exists(path, session);
|
|
5091
|
+
return this.client.files.exists(path$1, session);
|
|
4666
5092
|
}
|
|
4667
5093
|
/**
|
|
4668
5094
|
* Get the noVNC preview URL for browser-based desktop viewing.
|
|
@@ -4711,10 +5137,10 @@ var Sandbox = class Sandbox extends Container {
|
|
|
4711
5137
|
* @param path - Path to watch (absolute or relative to /workspace)
|
|
4712
5138
|
* @param options - Watch options
|
|
4713
5139
|
*/
|
|
4714
|
-
async watch(path, options = {}) {
|
|
5140
|
+
async watch(path$1, options = {}) {
|
|
4715
5141
|
const sessionId = options.sessionId ?? await this.ensureDefaultSession();
|
|
4716
5142
|
return this.client.watch.watch({
|
|
4717
|
-
path,
|
|
5143
|
+
path: path$1,
|
|
4718
5144
|
recursive: options.recursive,
|
|
4719
5145
|
include: options.include,
|
|
4720
5146
|
exclude: options.exclude,
|
|
@@ -4923,28 +5349,28 @@ var Sandbox = class Sandbox extends Container {
|
|
|
4923
5349
|
cleanupCompletedProcesses: () => this.cleanupCompletedProcesses(),
|
|
4924
5350
|
getProcessLogs: (id) => this.getProcessLogs(id),
|
|
4925
5351
|
streamProcessLogs: (processId, options) => this.streamProcessLogs(processId, options),
|
|
4926
|
-
writeFile: (path, content, options) => this.writeFile(path, content, {
|
|
5352
|
+
writeFile: (path$1, content, options) => this.writeFile(path$1, content, {
|
|
4927
5353
|
...options,
|
|
4928
5354
|
sessionId
|
|
4929
5355
|
}),
|
|
4930
|
-
readFile: (path, options) => this.readFile(path, {
|
|
5356
|
+
readFile: (path$1, options) => this.readFile(path$1, {
|
|
4931
5357
|
...options,
|
|
4932
5358
|
sessionId
|
|
4933
5359
|
}),
|
|
4934
|
-
readFileStream: (path) => this.readFileStream(path, { sessionId }),
|
|
4935
|
-
watch: (path, options) => this.watch(path, {
|
|
5360
|
+
readFileStream: (path$1) => this.readFileStream(path$1, { sessionId }),
|
|
5361
|
+
watch: (path$1, options) => this.watch(path$1, {
|
|
4936
5362
|
...options,
|
|
4937
5363
|
sessionId
|
|
4938
5364
|
}),
|
|
4939
|
-
mkdir: (path, options) => this.mkdir(path, {
|
|
5365
|
+
mkdir: (path$1, options) => this.mkdir(path$1, {
|
|
4940
5366
|
...options,
|
|
4941
5367
|
sessionId
|
|
4942
5368
|
}),
|
|
4943
|
-
deleteFile: (path) => this.deleteFile(path, sessionId),
|
|
5369
|
+
deleteFile: (path$1) => this.deleteFile(path$1, sessionId),
|
|
4944
5370
|
renameFile: (oldPath, newPath) => this.renameFile(oldPath, newPath, sessionId),
|
|
4945
5371
|
moveFile: (sourcePath, destPath) => this.moveFile(sourcePath, destPath, sessionId),
|
|
4946
|
-
listFiles: (path, options) => this.client.files.listFiles(path, sessionId, options),
|
|
4947
|
-
exists: (path) => this.exists(path, sessionId),
|
|
5372
|
+
listFiles: (path$1, options) => this.client.files.listFiles(path$1, sessionId, options),
|
|
5373
|
+
exists: (path$1) => this.exists(path$1, sessionId),
|
|
4948
5374
|
gitCheckout: (repoUrl, options) => this.gitCheckout(repoUrl, {
|
|
4949
5375
|
...options,
|
|
4950
5376
|
sessionId
|