@cloudflare/sandbox 0.7.7 → 0.7.10
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/Dockerfile +60 -1
- package/dist/{contexts-B0qA8qmx.d.ts → contexts-C5xSPEYL.d.ts} +32 -2
- package/dist/contexts-C5xSPEYL.d.ts.map +1 -0
- package/dist/{dist-D9B_6gn_.js → dist-CwUZf_TJ.js} +49 -2
- package/dist/dist-CwUZf_TJ.js.map +1 -0
- package/dist/{errors-CAZT-Gtg.js → errors-8W0q5Gll.js} +19 -1
- package/dist/errors-8W0q5Gll.js.map +1 -0
- package/dist/index.d.ts +21 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +788 -88
- package/dist/index.js.map +1 -1
- package/dist/openai/index.d.ts +1 -1
- package/dist/openai/index.js +1 -1
- package/dist/opencode/index.d.ts +2 -2
- package/dist/opencode/index.js +2 -2
- package/dist/{sandbox-DGAjk7r3.d.ts → sandbox-BYNjxjyr.d.ts} +367 -2
- package/dist/sandbox-BYNjxjyr.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/contexts-B0qA8qmx.d.ts.map +0 -1
- package/dist/dist-D9B_6gn_.js.map +0 -1
- package/dist/errors-CAZT-Gtg.js.map +0 -1
- package/dist/sandbox-DGAjk7r3.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
import { t as ErrorCode } from "./errors-
|
|
1
|
+
import { _ as filterEnvVars, a as isExecResult, c as parseSSEFrames, d as createNoOpLogger, f as TraceContext, g as extractRepoName, h as GitLogger, i as isWSStreamChunk, l as shellEscape, m as ResultImpl, n as isWSError, o as isProcess, p as Execution, r as isWSResponse, s as isProcessStatus, t as generateRequestId, u as createLogger, v as getEnvString, y as partitionEnvVars } from "./dist-CwUZf_TJ.js";
|
|
2
|
+
import { t as ErrorCode } from "./errors-8W0q5Gll.js";
|
|
3
3
|
import { Container, getContainer, switchPort } from "@cloudflare/containers";
|
|
4
4
|
import { AwsClient } from "aws4fetch";
|
|
5
5
|
|
|
@@ -593,6 +593,42 @@ var BackupRestoreError = class extends SandboxError {
|
|
|
593
593
|
return this.context.backupId;
|
|
594
594
|
}
|
|
595
595
|
};
|
|
596
|
+
var DesktopNotStartedError = class extends SandboxError {
|
|
597
|
+
constructor(errorResponse) {
|
|
598
|
+
super(errorResponse);
|
|
599
|
+
this.name = "DesktopNotStartedError";
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
var DesktopStartFailedError = class extends SandboxError {
|
|
603
|
+
constructor(errorResponse) {
|
|
604
|
+
super(errorResponse);
|
|
605
|
+
this.name = "DesktopStartFailedError";
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
var DesktopUnavailableError = class extends SandboxError {
|
|
609
|
+
constructor(errorResponse) {
|
|
610
|
+
super(errorResponse);
|
|
611
|
+
this.name = "DesktopUnavailableError";
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
var DesktopProcessCrashedError = class extends SandboxError {
|
|
615
|
+
constructor(errorResponse) {
|
|
616
|
+
super(errorResponse);
|
|
617
|
+
this.name = "DesktopProcessCrashedError";
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
var DesktopInvalidOptionsError = class extends SandboxError {
|
|
621
|
+
constructor(errorResponse) {
|
|
622
|
+
super(errorResponse);
|
|
623
|
+
this.name = "DesktopInvalidOptionsError";
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
var DesktopInvalidCoordinatesError = class extends SandboxError {
|
|
627
|
+
constructor(errorResponse) {
|
|
628
|
+
super(errorResponse);
|
|
629
|
+
this.name = "DesktopInvalidCoordinatesError";
|
|
630
|
+
}
|
|
631
|
+
};
|
|
596
632
|
|
|
597
633
|
//#endregion
|
|
598
634
|
//#region src/errors/adapter.ts
|
|
@@ -648,6 +684,12 @@ function createErrorFromResponse(errorResponse) {
|
|
|
648
684
|
case ErrorCode.INTERPRETER_NOT_READY: return new InterpreterNotReadyError(errorResponse);
|
|
649
685
|
case ErrorCode.CONTEXT_NOT_FOUND: return new ContextNotFoundError(errorResponse);
|
|
650
686
|
case ErrorCode.CODE_EXECUTION_ERROR: return new CodeExecutionError(errorResponse);
|
|
687
|
+
case ErrorCode.DESKTOP_NOT_STARTED: return new DesktopNotStartedError(errorResponse);
|
|
688
|
+
case ErrorCode.DESKTOP_START_FAILED: return new DesktopStartFailedError(errorResponse);
|
|
689
|
+
case ErrorCode.DESKTOP_UNAVAILABLE: return new DesktopUnavailableError(errorResponse);
|
|
690
|
+
case ErrorCode.DESKTOP_PROCESS_CRASHED: return new DesktopProcessCrashedError(errorResponse);
|
|
691
|
+
case ErrorCode.DESKTOP_INVALID_OPTIONS: return new DesktopInvalidOptionsError(errorResponse);
|
|
692
|
+
case ErrorCode.DESKTOP_INVALID_COORDINATES: return new DesktopInvalidCoordinatesError(errorResponse);
|
|
651
693
|
case ErrorCode.VALIDATION_FAILED: return new ValidationFailedError(errorResponse);
|
|
652
694
|
case ErrorCode.INVALID_JSON_RESPONSE:
|
|
653
695
|
case ErrorCode.UNKNOWN_ERROR:
|
|
@@ -990,6 +1032,10 @@ var WebSocketTransport = class extends BaseTransport {
|
|
|
990
1032
|
*
|
|
991
1033
|
* The stream will receive data chunks as they arrive over the WebSocket.
|
|
992
1034
|
* Format matches SSE for compatibility with existing streaming code.
|
|
1035
|
+
*
|
|
1036
|
+
* This method waits for the first message before returning. If the server
|
|
1037
|
+
* responds with an error (non-streaming response), it throws immediately
|
|
1038
|
+
* rather than returning a stream that will error later.
|
|
993
1039
|
*/
|
|
994
1040
|
async requestStream(method, path, body) {
|
|
995
1041
|
await this.connect();
|
|
@@ -1001,41 +1047,75 @@ var WebSocketTransport = class extends BaseTransport {
|
|
|
1001
1047
|
path,
|
|
1002
1048
|
body
|
|
1003
1049
|
};
|
|
1004
|
-
return new
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1050
|
+
return new Promise((resolveStream, rejectStream) => {
|
|
1051
|
+
let streamController;
|
|
1052
|
+
let firstMessageReceived = false;
|
|
1053
|
+
const timeoutMs = this.config.requestTimeoutMs ?? 12e4;
|
|
1054
|
+
const timeoutId = setTimeout(() => {
|
|
1055
|
+
this.pendingRequests.delete(id);
|
|
1056
|
+
const error = /* @__PURE__ */ new Error(`Stream timeout after ${timeoutMs}ms: ${method} ${path}`);
|
|
1057
|
+
if (firstMessageReceived) streamController?.error(error);
|
|
1058
|
+
else rejectStream(error);
|
|
1059
|
+
}, timeoutMs);
|
|
1060
|
+
const stream = new ReadableStream({
|
|
1061
|
+
start: (controller) => {
|
|
1062
|
+
streamController = controller;
|
|
1063
|
+
},
|
|
1064
|
+
cancel: () => {
|
|
1065
|
+
const pending = this.pendingRequests.get(id);
|
|
1066
|
+
if (pending?.timeoutId) clearTimeout(pending.timeoutId);
|
|
1067
|
+
try {
|
|
1068
|
+
this.send({
|
|
1069
|
+
type: "cancel",
|
|
1070
|
+
id
|
|
1071
|
+
});
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
this.logger.debug("Failed to send stream cancel message", {
|
|
1074
|
+
id,
|
|
1075
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1008
1078
|
this.pendingRequests.delete(id);
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
}
|
|
1023
|
-
streamController
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
});
|
|
1027
|
-
try {
|
|
1028
|
-
this.send(request);
|
|
1029
|
-
} catch (error) {
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
this.pendingRequests.set(id, {
|
|
1082
|
+
resolve: (response) => {
|
|
1083
|
+
clearTimeout(timeoutId);
|
|
1084
|
+
this.pendingRequests.delete(id);
|
|
1085
|
+
if (!firstMessageReceived) {
|
|
1086
|
+
firstMessageReceived = true;
|
|
1087
|
+
if (response.status >= 400) rejectStream(/* @__PURE__ */ new Error(`Stream error: ${response.status} - ${JSON.stringify(response.body)}`));
|
|
1088
|
+
else {
|
|
1089
|
+
streamController?.close();
|
|
1090
|
+
resolveStream(stream);
|
|
1091
|
+
}
|
|
1092
|
+
} else if (response.status >= 400) streamController?.error(/* @__PURE__ */ new Error(`Stream error: ${response.status} - ${JSON.stringify(response.body)}`));
|
|
1093
|
+
else streamController?.close();
|
|
1094
|
+
},
|
|
1095
|
+
reject: (error) => {
|
|
1030
1096
|
clearTimeout(timeoutId);
|
|
1031
1097
|
this.pendingRequests.delete(id);
|
|
1032
|
-
|
|
1098
|
+
if (firstMessageReceived) streamController?.error(error);
|
|
1099
|
+
else rejectStream(error);
|
|
1100
|
+
},
|
|
1101
|
+
streamController: void 0,
|
|
1102
|
+
isStreaming: true,
|
|
1103
|
+
timeoutId,
|
|
1104
|
+
onFirstChunk: () => {
|
|
1105
|
+
if (!firstMessageReceived) {
|
|
1106
|
+
firstMessageReceived = true;
|
|
1107
|
+
const pending = this.pendingRequests.get(id);
|
|
1108
|
+
if (pending) pending.streamController = streamController;
|
|
1109
|
+
resolveStream(stream);
|
|
1110
|
+
}
|
|
1033
1111
|
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1112
|
+
});
|
|
1113
|
+
try {
|
|
1114
|
+
this.send(request);
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
clearTimeout(timeoutId);
|
|
1038
1117
|
this.pendingRequests.delete(id);
|
|
1118
|
+
rejectStream(error instanceof Error ? error : new Error(String(error)));
|
|
1039
1119
|
}
|
|
1040
1120
|
});
|
|
1041
1121
|
}
|
|
@@ -1047,8 +1127,9 @@ var WebSocketTransport = class extends BaseTransport {
|
|
|
1047
1127
|
this.ws.send(JSON.stringify(message));
|
|
1048
1128
|
this.logger.debug("WebSocket sent", {
|
|
1049
1129
|
id: message.id,
|
|
1050
|
-
|
|
1051
|
-
|
|
1130
|
+
type: message.type,
|
|
1131
|
+
method: message.type === "request" ? message.method : void 0,
|
|
1132
|
+
path: message.type === "request" ? message.path : void 0
|
|
1052
1133
|
});
|
|
1053
1134
|
}
|
|
1054
1135
|
/**
|
|
@@ -1086,10 +1167,18 @@ var WebSocketTransport = class extends BaseTransport {
|
|
|
1086
1167
|
*/
|
|
1087
1168
|
handleStreamChunk(chunk) {
|
|
1088
1169
|
const pending = this.pendingRequests.get(chunk.id);
|
|
1089
|
-
if (!pending
|
|
1170
|
+
if (!pending) {
|
|
1090
1171
|
this.logger.warn("Received stream chunk for unknown request", { id: chunk.id });
|
|
1091
1172
|
return;
|
|
1092
1173
|
}
|
|
1174
|
+
if (pending.onFirstChunk) {
|
|
1175
|
+
pending.onFirstChunk();
|
|
1176
|
+
pending.onFirstChunk = void 0;
|
|
1177
|
+
}
|
|
1178
|
+
if (!pending.streamController) {
|
|
1179
|
+
this.logger.warn("Stream chunk received but controller not ready", { id: chunk.id });
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1093
1182
|
const encoder = new TextEncoder();
|
|
1094
1183
|
let sseData;
|
|
1095
1184
|
if (chunk.event) sseData = `event: ${chunk.event}\ndata: ${chunk.data}\n\n`;
|
|
@@ -1455,6 +1544,369 @@ var CommandClient = class extends BaseHttpClient {
|
|
|
1455
1544
|
}
|
|
1456
1545
|
};
|
|
1457
1546
|
|
|
1547
|
+
//#endregion
|
|
1548
|
+
//#region src/clients/desktop-client.ts
|
|
1549
|
+
/**
|
|
1550
|
+
* Client for desktop environment lifecycle, input, and screen operations
|
|
1551
|
+
*/
|
|
1552
|
+
var DesktopClient = class extends BaseHttpClient {
|
|
1553
|
+
/**
|
|
1554
|
+
* Start the desktop environment with optional resolution and DPI.
|
|
1555
|
+
*/
|
|
1556
|
+
async start(options) {
|
|
1557
|
+
try {
|
|
1558
|
+
const data = {
|
|
1559
|
+
...options?.resolution !== void 0 && { resolution: options.resolution },
|
|
1560
|
+
...options?.dpi !== void 0 && { dpi: options.dpi }
|
|
1561
|
+
};
|
|
1562
|
+
const response = await this.post("/api/desktop/start", data);
|
|
1563
|
+
this.logSuccess("Desktop started", `${response.resolution[0]}x${response.resolution[1]}`);
|
|
1564
|
+
return response;
|
|
1565
|
+
} catch (error) {
|
|
1566
|
+
this.logError("desktop.start", error);
|
|
1567
|
+
this.options.onError?.(error instanceof Error ? error.message : String(error));
|
|
1568
|
+
throw error;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* Stop the desktop environment and all related processes.
|
|
1573
|
+
*/
|
|
1574
|
+
async stop() {
|
|
1575
|
+
try {
|
|
1576
|
+
const response = await this.post("/api/desktop/stop", {});
|
|
1577
|
+
this.logSuccess("Desktop stopped");
|
|
1578
|
+
return response;
|
|
1579
|
+
} catch (error) {
|
|
1580
|
+
this.logError("desktop.stop", error);
|
|
1581
|
+
this.options.onError?.(error instanceof Error ? error.message : String(error));
|
|
1582
|
+
throw error;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Get desktop lifecycle and process health status.
|
|
1587
|
+
*/
|
|
1588
|
+
async status() {
|
|
1589
|
+
try {
|
|
1590
|
+
const response = await this.get("/api/desktop/status");
|
|
1591
|
+
this.logSuccess("Desktop status retrieved", response.status);
|
|
1592
|
+
return response;
|
|
1593
|
+
} catch (error) {
|
|
1594
|
+
this.logError("desktop.status", error);
|
|
1595
|
+
throw error;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
async screenshot(options) {
|
|
1599
|
+
try {
|
|
1600
|
+
const wantsBytes = options?.format === "bytes";
|
|
1601
|
+
const data = {
|
|
1602
|
+
format: "base64",
|
|
1603
|
+
...options?.imageFormat !== void 0 && { imageFormat: options.imageFormat },
|
|
1604
|
+
...options?.quality !== void 0 && { quality: options.quality },
|
|
1605
|
+
...options?.showCursor !== void 0 && { showCursor: options.showCursor }
|
|
1606
|
+
};
|
|
1607
|
+
const response = await this.post("/api/desktop/screenshot", data);
|
|
1608
|
+
this.logSuccess("Screenshot captured", `${response.width}x${response.height}`);
|
|
1609
|
+
if (wantsBytes) {
|
|
1610
|
+
const binaryString = atob(response.data);
|
|
1611
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
1612
|
+
for (let i = 0; i < binaryString.length; i++) bytes[i] = binaryString.charCodeAt(i);
|
|
1613
|
+
return {
|
|
1614
|
+
...response,
|
|
1615
|
+
data: bytes
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
return response;
|
|
1619
|
+
} catch (error) {
|
|
1620
|
+
this.logError("desktop.screenshot", error);
|
|
1621
|
+
throw error;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
async screenshotRegion(region, options) {
|
|
1625
|
+
try {
|
|
1626
|
+
const wantsBytes = options?.format === "bytes";
|
|
1627
|
+
const data = {
|
|
1628
|
+
region,
|
|
1629
|
+
format: "base64",
|
|
1630
|
+
...options?.imageFormat !== void 0 && { imageFormat: options.imageFormat },
|
|
1631
|
+
...options?.quality !== void 0 && { quality: options.quality },
|
|
1632
|
+
...options?.showCursor !== void 0 && { showCursor: options.showCursor }
|
|
1633
|
+
};
|
|
1634
|
+
const response = await this.post("/api/desktop/screenshot/region", data);
|
|
1635
|
+
this.logSuccess("Region screenshot captured", `${region.width}x${region.height}`);
|
|
1636
|
+
if (wantsBytes) {
|
|
1637
|
+
const binaryString = atob(response.data);
|
|
1638
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
1639
|
+
for (let i = 0; i < binaryString.length; i++) bytes[i] = binaryString.charCodeAt(i);
|
|
1640
|
+
return {
|
|
1641
|
+
...response,
|
|
1642
|
+
data: bytes
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
return response;
|
|
1646
|
+
} catch (error) {
|
|
1647
|
+
this.logError("desktop.screenshotRegion", error);
|
|
1648
|
+
throw error;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Single-click at the given coordinates.
|
|
1653
|
+
*/
|
|
1654
|
+
async click(x, y, options) {
|
|
1655
|
+
try {
|
|
1656
|
+
await this.post("/api/desktop/mouse/click", {
|
|
1657
|
+
x,
|
|
1658
|
+
y,
|
|
1659
|
+
button: options?.button ?? "left",
|
|
1660
|
+
clickCount: 1
|
|
1661
|
+
});
|
|
1662
|
+
this.logSuccess("Mouse click", `(${x}, ${y})`);
|
|
1663
|
+
} catch (error) {
|
|
1664
|
+
this.logError("desktop.click", error);
|
|
1665
|
+
throw error;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Double-click at the given coordinates.
|
|
1670
|
+
*/
|
|
1671
|
+
async doubleClick(x, y, options) {
|
|
1672
|
+
try {
|
|
1673
|
+
await this.post("/api/desktop/mouse/click", {
|
|
1674
|
+
x,
|
|
1675
|
+
y,
|
|
1676
|
+
button: options?.button ?? "left",
|
|
1677
|
+
clickCount: 2
|
|
1678
|
+
});
|
|
1679
|
+
this.logSuccess("Mouse double click", `(${x}, ${y})`);
|
|
1680
|
+
} catch (error) {
|
|
1681
|
+
this.logError("desktop.doubleClick", error);
|
|
1682
|
+
throw error;
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
/**
|
|
1686
|
+
* Triple-click at the given coordinates.
|
|
1687
|
+
*/
|
|
1688
|
+
async tripleClick(x, y, options) {
|
|
1689
|
+
try {
|
|
1690
|
+
await this.post("/api/desktop/mouse/click", {
|
|
1691
|
+
x,
|
|
1692
|
+
y,
|
|
1693
|
+
button: options?.button ?? "left",
|
|
1694
|
+
clickCount: 3
|
|
1695
|
+
});
|
|
1696
|
+
this.logSuccess("Mouse triple click", `(${x}, ${y})`);
|
|
1697
|
+
} catch (error) {
|
|
1698
|
+
this.logError("desktop.tripleClick", error);
|
|
1699
|
+
throw error;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Right-click at the given coordinates.
|
|
1704
|
+
*/
|
|
1705
|
+
async rightClick(x, y) {
|
|
1706
|
+
try {
|
|
1707
|
+
await this.post("/api/desktop/mouse/click", {
|
|
1708
|
+
x,
|
|
1709
|
+
y,
|
|
1710
|
+
button: "right",
|
|
1711
|
+
clickCount: 1
|
|
1712
|
+
});
|
|
1713
|
+
this.logSuccess("Mouse right click", `(${x}, ${y})`);
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
this.logError("desktop.rightClick", error);
|
|
1716
|
+
throw error;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* Middle-click at the given coordinates.
|
|
1721
|
+
*/
|
|
1722
|
+
async middleClick(x, y) {
|
|
1723
|
+
try {
|
|
1724
|
+
await this.post("/api/desktop/mouse/click", {
|
|
1725
|
+
x,
|
|
1726
|
+
y,
|
|
1727
|
+
button: "middle",
|
|
1728
|
+
clickCount: 1
|
|
1729
|
+
});
|
|
1730
|
+
this.logSuccess("Mouse middle click", `(${x}, ${y})`);
|
|
1731
|
+
} catch (error) {
|
|
1732
|
+
this.logError("desktop.middleClick", error);
|
|
1733
|
+
throw error;
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Press and hold a mouse button.
|
|
1738
|
+
*/
|
|
1739
|
+
async mouseDown(x, y, options) {
|
|
1740
|
+
try {
|
|
1741
|
+
await this.post("/api/desktop/mouse/down", {
|
|
1742
|
+
...x !== void 0 && { x },
|
|
1743
|
+
...y !== void 0 && { y },
|
|
1744
|
+
button: options?.button ?? "left"
|
|
1745
|
+
});
|
|
1746
|
+
this.logSuccess("Mouse down", x !== void 0 ? `(${x}, ${y})` : "current position");
|
|
1747
|
+
} catch (error) {
|
|
1748
|
+
this.logError("desktop.mouseDown", error);
|
|
1749
|
+
throw error;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Release a held mouse button.
|
|
1754
|
+
*/
|
|
1755
|
+
async mouseUp(x, y, options) {
|
|
1756
|
+
try {
|
|
1757
|
+
await this.post("/api/desktop/mouse/up", {
|
|
1758
|
+
...x !== void 0 && { x },
|
|
1759
|
+
...y !== void 0 && { y },
|
|
1760
|
+
button: options?.button ?? "left"
|
|
1761
|
+
});
|
|
1762
|
+
this.logSuccess("Mouse up", x !== void 0 ? `(${x}, ${y})` : "current position");
|
|
1763
|
+
} catch (error) {
|
|
1764
|
+
this.logError("desktop.mouseUp", error);
|
|
1765
|
+
throw error;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
/**
|
|
1769
|
+
* Move the mouse cursor to coordinates.
|
|
1770
|
+
*/
|
|
1771
|
+
async moveMouse(x, y) {
|
|
1772
|
+
try {
|
|
1773
|
+
await this.post("/api/desktop/mouse/move", {
|
|
1774
|
+
x,
|
|
1775
|
+
y
|
|
1776
|
+
});
|
|
1777
|
+
this.logSuccess("Mouse move", `(${x}, ${y})`);
|
|
1778
|
+
} catch (error) {
|
|
1779
|
+
this.logError("desktop.moveMouse", error);
|
|
1780
|
+
throw error;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Drag from start coordinates to end coordinates.
|
|
1785
|
+
*/
|
|
1786
|
+
async drag(startX, startY, endX, endY, options) {
|
|
1787
|
+
try {
|
|
1788
|
+
await this.post("/api/desktop/mouse/drag", {
|
|
1789
|
+
startX,
|
|
1790
|
+
startY,
|
|
1791
|
+
endX,
|
|
1792
|
+
endY,
|
|
1793
|
+
button: options?.button ?? "left"
|
|
1794
|
+
});
|
|
1795
|
+
this.logSuccess("Mouse drag", `(${startX},${startY}) -> (${endX},${endY})`);
|
|
1796
|
+
} catch (error) {
|
|
1797
|
+
this.logError("desktop.drag", error);
|
|
1798
|
+
throw error;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
/**
|
|
1802
|
+
* Scroll at coordinates in the specified direction.
|
|
1803
|
+
*/
|
|
1804
|
+
async scroll(x, y, direction, amount = 3) {
|
|
1805
|
+
try {
|
|
1806
|
+
await this.post("/api/desktop/mouse/scroll", {
|
|
1807
|
+
x,
|
|
1808
|
+
y,
|
|
1809
|
+
direction,
|
|
1810
|
+
amount
|
|
1811
|
+
});
|
|
1812
|
+
this.logSuccess("Mouse scroll", `${direction} ${amount} at (${x}, ${y})`);
|
|
1813
|
+
} catch (error) {
|
|
1814
|
+
this.logError("desktop.scroll", error);
|
|
1815
|
+
throw error;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Get the current cursor coordinates.
|
|
1820
|
+
*/
|
|
1821
|
+
async getCursorPosition() {
|
|
1822
|
+
try {
|
|
1823
|
+
const response = await this.get("/api/desktop/mouse/position");
|
|
1824
|
+
this.logSuccess("Cursor position retrieved", `(${response.x}, ${response.y})`);
|
|
1825
|
+
return response;
|
|
1826
|
+
} catch (error) {
|
|
1827
|
+
this.logError("desktop.getCursorPosition", error);
|
|
1828
|
+
throw error;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Type text into the focused element.
|
|
1833
|
+
*/
|
|
1834
|
+
async type(text, options) {
|
|
1835
|
+
try {
|
|
1836
|
+
await this.post("/api/desktop/keyboard/type", {
|
|
1837
|
+
text,
|
|
1838
|
+
...options?.delayMs !== void 0 && { delayMs: options.delayMs }
|
|
1839
|
+
});
|
|
1840
|
+
this.logSuccess("Keyboard type", `${text.length} chars`);
|
|
1841
|
+
} catch (error) {
|
|
1842
|
+
this.logError("desktop.type", error);
|
|
1843
|
+
throw error;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Press and release a key or key combination.
|
|
1848
|
+
*/
|
|
1849
|
+
async press(key) {
|
|
1850
|
+
try {
|
|
1851
|
+
await this.post("/api/desktop/keyboard/press", { key });
|
|
1852
|
+
this.logSuccess("Key press", key);
|
|
1853
|
+
} catch (error) {
|
|
1854
|
+
this.logError("desktop.press", error);
|
|
1855
|
+
throw error;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Press and hold a key.
|
|
1860
|
+
*/
|
|
1861
|
+
async keyDown(key) {
|
|
1862
|
+
try {
|
|
1863
|
+
await this.post("/api/desktop/keyboard/down", { key });
|
|
1864
|
+
this.logSuccess("Key down", key);
|
|
1865
|
+
} catch (error) {
|
|
1866
|
+
this.logError("desktop.keyDown", error);
|
|
1867
|
+
throw error;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
/**
|
|
1871
|
+
* Release a held key.
|
|
1872
|
+
*/
|
|
1873
|
+
async keyUp(key) {
|
|
1874
|
+
try {
|
|
1875
|
+
await this.post("/api/desktop/keyboard/up", { key });
|
|
1876
|
+
this.logSuccess("Key up", key);
|
|
1877
|
+
} catch (error) {
|
|
1878
|
+
this.logError("desktop.keyUp", error);
|
|
1879
|
+
throw error;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
/**
|
|
1883
|
+
* Get the active desktop screen size.
|
|
1884
|
+
*/
|
|
1885
|
+
async getScreenSize() {
|
|
1886
|
+
try {
|
|
1887
|
+
const response = await this.get("/api/desktop/screen/size");
|
|
1888
|
+
this.logSuccess("Screen size retrieved", `${response.width}x${response.height}`);
|
|
1889
|
+
return response;
|
|
1890
|
+
} catch (error) {
|
|
1891
|
+
this.logError("desktop.getScreenSize", error);
|
|
1892
|
+
throw error;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Get health status for a specific desktop process.
|
|
1897
|
+
*/
|
|
1898
|
+
async getProcessStatus(name) {
|
|
1899
|
+
try {
|
|
1900
|
+
const response = await this.get(`/api/desktop/process/${encodeURIComponent(name)}/status`);
|
|
1901
|
+
this.logSuccess("Desktop process status retrieved", name);
|
|
1902
|
+
return response;
|
|
1903
|
+
} catch (error) {
|
|
1904
|
+
this.logError("desktop.getProcessStatus", error);
|
|
1905
|
+
throw error;
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
};
|
|
1909
|
+
|
|
1458
1910
|
//#endregion
|
|
1459
1911
|
//#region src/clients/file-client.ts
|
|
1460
1912
|
/**
|
|
@@ -2139,6 +2591,106 @@ var UtilityClient = class extends BaseHttpClient {
|
|
|
2139
2591
|
}
|
|
2140
2592
|
};
|
|
2141
2593
|
|
|
2594
|
+
//#endregion
|
|
2595
|
+
//#region src/clients/watch-client.ts
|
|
2596
|
+
/**
|
|
2597
|
+
* Client for file watch operations
|
|
2598
|
+
* Uses inotify under the hood for native filesystem event notifications
|
|
2599
|
+
*
|
|
2600
|
+
* @internal This client is used internally by the SDK.
|
|
2601
|
+
* Users should use `sandbox.watch()` instead.
|
|
2602
|
+
*/
|
|
2603
|
+
var WatchClient = class extends BaseHttpClient {
|
|
2604
|
+
/**
|
|
2605
|
+
* Start watching a directory for changes.
|
|
2606
|
+
* The returned promise resolves only after the watcher is established
|
|
2607
|
+
* on the filesystem (i.e. the `watching` SSE event has been received).
|
|
2608
|
+
* The returned stream still contains the `watching` event so consumers
|
|
2609
|
+
* using `parseSSEStream` will see the full event sequence.
|
|
2610
|
+
*
|
|
2611
|
+
* @param request - Watch request with path and options
|
|
2612
|
+
*/
|
|
2613
|
+
async watch(request) {
|
|
2614
|
+
try {
|
|
2615
|
+
const stream = await this.doStreamFetch("/api/watch", request);
|
|
2616
|
+
const readyStream = await this.waitForReadiness(stream);
|
|
2617
|
+
this.logSuccess("File watch started", request.path);
|
|
2618
|
+
return readyStream;
|
|
2619
|
+
} catch (error) {
|
|
2620
|
+
this.logError("watch", error);
|
|
2621
|
+
throw error;
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
/**
|
|
2625
|
+
* Read SSE chunks until the `watching` event appears, then return a
|
|
2626
|
+
* wrapper stream that replays the buffered chunks followed by the
|
|
2627
|
+
* remaining original stream data.
|
|
2628
|
+
*/
|
|
2629
|
+
async waitForReadiness(stream) {
|
|
2630
|
+
const reader = stream.getReader();
|
|
2631
|
+
const bufferedChunks = [];
|
|
2632
|
+
const decoder = new TextDecoder();
|
|
2633
|
+
let buffer = "";
|
|
2634
|
+
let currentEvent = { data: [] };
|
|
2635
|
+
let watcherReady = false;
|
|
2636
|
+
const processEventData = (eventData) => {
|
|
2637
|
+
let event;
|
|
2638
|
+
try {
|
|
2639
|
+
event = JSON.parse(eventData);
|
|
2640
|
+
} catch {
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
if (event.type === "watching") watcherReady = true;
|
|
2644
|
+
if (event.type === "error") throw new Error(event.error || "Watch failed to establish");
|
|
2645
|
+
};
|
|
2646
|
+
try {
|
|
2647
|
+
while (!watcherReady) {
|
|
2648
|
+
const { done, value } = await reader.read();
|
|
2649
|
+
if (done) {
|
|
2650
|
+
const finalParsed = parseSSEFrames(`${buffer}\n\n`, currentEvent);
|
|
2651
|
+
for (const frame of finalParsed.events) {
|
|
2652
|
+
processEventData(frame.data);
|
|
2653
|
+
if (watcherReady) break;
|
|
2654
|
+
}
|
|
2655
|
+
if (watcherReady) break;
|
|
2656
|
+
throw new Error("Watch stream ended before watcher was established");
|
|
2657
|
+
}
|
|
2658
|
+
bufferedChunks.push(value);
|
|
2659
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2660
|
+
const parsed = parseSSEFrames(buffer, currentEvent);
|
|
2661
|
+
buffer = parsed.remaining;
|
|
2662
|
+
currentEvent = parsed.currentEvent;
|
|
2663
|
+
for (const frame of parsed.events) {
|
|
2664
|
+
processEventData(frame.data);
|
|
2665
|
+
if (watcherReady) break;
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
} catch (error) {
|
|
2669
|
+
reader.cancel().catch(() => {});
|
|
2670
|
+
throw error;
|
|
2671
|
+
}
|
|
2672
|
+
let replayIndex = 0;
|
|
2673
|
+
return new ReadableStream({
|
|
2674
|
+
pull(controller) {
|
|
2675
|
+
if (replayIndex < bufferedChunks.length) {
|
|
2676
|
+
controller.enqueue(bufferedChunks[replayIndex++]);
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
return reader.read().then(({ done: d, value: v }) => {
|
|
2680
|
+
if (d) {
|
|
2681
|
+
controller.close();
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
controller.enqueue(v);
|
|
2685
|
+
});
|
|
2686
|
+
},
|
|
2687
|
+
cancel() {
|
|
2688
|
+
return reader.cancel();
|
|
2689
|
+
}
|
|
2690
|
+
});
|
|
2691
|
+
}
|
|
2692
|
+
};
|
|
2693
|
+
|
|
2142
2694
|
//#endregion
|
|
2143
2695
|
//#region src/clients/sandbox-client.ts
|
|
2144
2696
|
/**
|
|
@@ -2160,6 +2712,8 @@ var SandboxClient = class {
|
|
|
2160
2712
|
git;
|
|
2161
2713
|
interpreter;
|
|
2162
2714
|
utils;
|
|
2715
|
+
desktop;
|
|
2716
|
+
watch;
|
|
2163
2717
|
transport = null;
|
|
2164
2718
|
constructor(options) {
|
|
2165
2719
|
if (options.transportMode === "websocket" && options.wsUrl) this.transport = createTransport({
|
|
@@ -2183,6 +2737,8 @@ var SandboxClient = class {
|
|
|
2183
2737
|
this.git = new GitClient(clientOptions);
|
|
2184
2738
|
this.interpreter = new InterpreterClient(clientOptions);
|
|
2185
2739
|
this.utils = new UtilityClient(clientOptions);
|
|
2740
|
+
this.desktop = new DesktopClient(clientOptions);
|
|
2741
|
+
this.watch = new WatchClient(clientOptions);
|
|
2186
2742
|
}
|
|
2187
2743
|
/**
|
|
2188
2744
|
* Get the current transport mode
|
|
@@ -2490,32 +3046,44 @@ async function* parseSSEStream(stream, signal) {
|
|
|
2490
3046
|
const reader = stream.getReader();
|
|
2491
3047
|
const decoder = new TextDecoder();
|
|
2492
3048
|
let buffer = "";
|
|
3049
|
+
let currentEvent = { data: [] };
|
|
3050
|
+
let isAborted = signal?.aborted ?? false;
|
|
3051
|
+
const emitEvent = (data) => {
|
|
3052
|
+
if (data === "[DONE]" || data.trim() === "") return;
|
|
3053
|
+
try {
|
|
3054
|
+
return JSON.parse(data);
|
|
3055
|
+
} catch {
|
|
3056
|
+
return;
|
|
3057
|
+
}
|
|
3058
|
+
};
|
|
3059
|
+
const onAbort = () => {
|
|
3060
|
+
isAborted = true;
|
|
3061
|
+
reader.cancel().catch(() => {});
|
|
3062
|
+
};
|
|
3063
|
+
if (signal && !signal.aborted) signal.addEventListener("abort", onAbort);
|
|
2493
3064
|
try {
|
|
2494
3065
|
while (true) {
|
|
2495
|
-
if (
|
|
3066
|
+
if (isAborted) throw new Error("Operation was aborted");
|
|
2496
3067
|
const { done, value } = await reader.read();
|
|
3068
|
+
if (isAborted) throw new Error("Operation was aborted");
|
|
2497
3069
|
if (done) break;
|
|
2498
3070
|
buffer += decoder.decode(value, { stream: true });
|
|
2499
|
-
const
|
|
2500
|
-
buffer =
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
if (data === "[DONE]" || data.trim() === "") continue;
|
|
2506
|
-
try {
|
|
2507
|
-
yield JSON.parse(data);
|
|
2508
|
-
} catch {}
|
|
2509
|
-
}
|
|
3071
|
+
const parsed = parseSSEFrames(buffer, currentEvent);
|
|
3072
|
+
buffer = parsed.remaining;
|
|
3073
|
+
currentEvent = parsed.currentEvent;
|
|
3074
|
+
for (const frame of parsed.events) {
|
|
3075
|
+
const event = emitEvent(frame.data);
|
|
3076
|
+
if (event !== void 0) yield event;
|
|
2510
3077
|
}
|
|
2511
3078
|
}
|
|
2512
|
-
if (
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
3079
|
+
if (isAborted) throw new Error("Operation was aborted");
|
|
3080
|
+
const finalParsed = parseSSEFrames(`${buffer}\n\n`, currentEvent);
|
|
3081
|
+
for (const frame of finalParsed.events) {
|
|
3082
|
+
const event = emitEvent(frame.data);
|
|
3083
|
+
if (event !== void 0) yield event;
|
|
2517
3084
|
}
|
|
2518
3085
|
} finally {
|
|
3086
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
2519
3087
|
try {
|
|
2520
3088
|
await reader.cancel();
|
|
2521
3089
|
} catch {}
|
|
@@ -2712,7 +3280,7 @@ function buildS3fsSource(bucket, prefix) {
|
|
|
2712
3280
|
* This file is auto-updated by .github/changeset-version.ts during releases
|
|
2713
3281
|
* DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
|
|
2714
3282
|
*/
|
|
2715
|
-
const SDK_VERSION = "0.7.
|
|
3283
|
+
const SDK_VERSION = "0.7.10";
|
|
2716
3284
|
|
|
2717
3285
|
//#endregion
|
|
2718
3286
|
//#region src/sandbox.ts
|
|
@@ -2737,7 +3305,11 @@ function getSandbox(ns, id, options) {
|
|
|
2737
3305
|
return enhanceSession(stub, await stub.getSession(sessionId));
|
|
2738
3306
|
},
|
|
2739
3307
|
terminal: (request, opts) => proxyTerminal(stub, defaultSessionId, request, opts),
|
|
2740
|
-
wsConnect: connect(stub)
|
|
3308
|
+
wsConnect: connect(stub),
|
|
3309
|
+
desktop: new Proxy({}, { get(_, method) {
|
|
3310
|
+
if (typeof method !== "string" || method === "then") return void 0;
|
|
3311
|
+
return (...args) => stub.callDesktop(method, args);
|
|
3312
|
+
} })
|
|
2741
3313
|
};
|
|
2742
3314
|
return new Proxy(stub, { get(target, prop) {
|
|
2743
3315
|
if (typeof prop === "string" && prop in enhancedMethods) return enhancedMethods[prop];
|
|
@@ -2811,6 +3383,56 @@ var Sandbox = class Sandbox extends Container {
|
|
|
2811
3383
|
*/
|
|
2812
3384
|
containerTimeouts = { ...this.DEFAULT_CONTAINER_TIMEOUTS };
|
|
2813
3385
|
/**
|
|
3386
|
+
* Desktop environment operations.
|
|
3387
|
+
* Within the DO, this getter provides direct access to DesktopClient.
|
|
3388
|
+
* Over RPC, the getSandbox() proxy intercepts this property and routes
|
|
3389
|
+
* calls through callDesktop() instead.
|
|
3390
|
+
*/
|
|
3391
|
+
get desktop() {
|
|
3392
|
+
return this.client.desktop;
|
|
3393
|
+
}
|
|
3394
|
+
/**
|
|
3395
|
+
* Allowed desktop methods — derived from the Desktop interface.
|
|
3396
|
+
* Restricts callDesktop() to a known set of operations.
|
|
3397
|
+
*/
|
|
3398
|
+
static DESKTOP_METHODS = new Set([
|
|
3399
|
+
"start",
|
|
3400
|
+
"stop",
|
|
3401
|
+
"status",
|
|
3402
|
+
"screenshot",
|
|
3403
|
+
"screenshotRegion",
|
|
3404
|
+
"click",
|
|
3405
|
+
"doubleClick",
|
|
3406
|
+
"tripleClick",
|
|
3407
|
+
"rightClick",
|
|
3408
|
+
"middleClick",
|
|
3409
|
+
"mouseDown",
|
|
3410
|
+
"mouseUp",
|
|
3411
|
+
"moveMouse",
|
|
3412
|
+
"drag",
|
|
3413
|
+
"scroll",
|
|
3414
|
+
"getCursorPosition",
|
|
3415
|
+
"type",
|
|
3416
|
+
"press",
|
|
3417
|
+
"keyDown",
|
|
3418
|
+
"keyUp",
|
|
3419
|
+
"getScreenSize",
|
|
3420
|
+
"getProcessStatus"
|
|
3421
|
+
]);
|
|
3422
|
+
/**
|
|
3423
|
+
* Dispatch method for desktop operations.
|
|
3424
|
+
* Called by the client-side proxy created in getSandbox() to provide
|
|
3425
|
+
* the `sandbox.desktop.status()` API without relying on RPC pipelining
|
|
3426
|
+
* through property getters.
|
|
3427
|
+
*/
|
|
3428
|
+
async callDesktop(method, args) {
|
|
3429
|
+
if (!Sandbox.DESKTOP_METHODS.has(method)) throw new Error(`Unknown desktop method: ${method}`);
|
|
3430
|
+
const client = this.client.desktop;
|
|
3431
|
+
const fn = client[method];
|
|
3432
|
+
if (typeof fn !== "function") throw new Error(`Unknown desktop method: ${method}`);
|
|
3433
|
+
return fn.apply(client, args);
|
|
3434
|
+
}
|
|
3435
|
+
/**
|
|
2814
3436
|
* Create a SandboxClient with current transport settings
|
|
2815
3437
|
*/
|
|
2816
3438
|
createSandboxClient() {
|
|
@@ -3080,6 +3702,9 @@ var Sandbox = class Sandbox extends Container {
|
|
|
3080
3702
|
*/
|
|
3081
3703
|
async destroy() {
|
|
3082
3704
|
this.logger.info("Destroying sandbox container");
|
|
3705
|
+
if (this.ctx.container?.running) try {
|
|
3706
|
+
await this.client.desktop.stop();
|
|
3707
|
+
} catch {}
|
|
3083
3708
|
this.client.disconnect();
|
|
3084
3709
|
for (const [mountPath, mountInfo] of this.activeMounts.entries()) {
|
|
3085
3710
|
if (mountInfo.mounted) try {
|
|
@@ -3138,34 +3763,43 @@ var Sandbox = class Sandbox extends Container {
|
|
|
3138
3763
|
*/
|
|
3139
3764
|
async containerFetch(requestOrUrl, portOrInit, portParam) {
|
|
3140
3765
|
const { request, port } = this.parseContainerFetchArgs(requestOrUrl, portOrInit, portParam);
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
this.
|
|
3162
|
-
return new Response("Container is starting. Please retry in a moment.", {
|
|
3766
|
+
const state = await this.getState();
|
|
3767
|
+
const containerRunning = this.ctx.container?.running;
|
|
3768
|
+
const staleStateDetected = state.status === "healthy" && containerRunning === false;
|
|
3769
|
+
if (state.status !== "healthy" || containerRunning === false) {
|
|
3770
|
+
if (staleStateDetected) this.logger.debug("Stale container state detected: persisted state is healthy but container is not running");
|
|
3771
|
+
try {
|
|
3772
|
+
this.logger.debug("Starting container with configured timeouts", {
|
|
3773
|
+
instanceTimeout: this.containerTimeouts.instanceGetTimeoutMS,
|
|
3774
|
+
portTimeout: this.containerTimeouts.portReadyTimeoutMS
|
|
3775
|
+
});
|
|
3776
|
+
await this.startAndWaitForPorts({
|
|
3777
|
+
ports: port,
|
|
3778
|
+
cancellationOptions: {
|
|
3779
|
+
instanceGetTimeoutMS: this.containerTimeouts.instanceGetTimeoutMS,
|
|
3780
|
+
portReadyTimeoutMS: this.containerTimeouts.portReadyTimeoutMS,
|
|
3781
|
+
waitInterval: this.containerTimeouts.waitIntervalMS,
|
|
3782
|
+
abort: request.signal
|
|
3783
|
+
}
|
|
3784
|
+
});
|
|
3785
|
+
} catch (e) {
|
|
3786
|
+
if (this.isNoInstanceError(e)) return new Response("Container is currently provisioning. This can take several minutes on first deployment. Please retry in a moment.", {
|
|
3163
3787
|
status: 503,
|
|
3164
|
-
headers: { "Retry-After": "
|
|
3788
|
+
headers: { "Retry-After": "10" }
|
|
3165
3789
|
});
|
|
3790
|
+
if (this.isTransientStartupError(e)) {
|
|
3791
|
+
if (staleStateDetected) {
|
|
3792
|
+
this.logger.warn("Container startup failed after stale state detection, aborting DO for recovery", { error: e instanceof Error ? e.message : String(e) });
|
|
3793
|
+
this.ctx.abort();
|
|
3794
|
+
} else this.logger.debug("Transient container startup error, returning 503", { error: e instanceof Error ? e.message : String(e) });
|
|
3795
|
+
return new Response("Container is starting. Please retry in a moment.", {
|
|
3796
|
+
status: 503,
|
|
3797
|
+
headers: { "Retry-After": "3" }
|
|
3798
|
+
});
|
|
3799
|
+
}
|
|
3800
|
+
this.logger.error("Container startup failed with permanent error", e instanceof Error ? e : new Error(String(e)));
|
|
3801
|
+
return new Response(`Failed to start container: ${e instanceof Error ? e.message : String(e)}`, { status: 500 });
|
|
3166
3802
|
}
|
|
3167
|
-
this.logger.error("Container startup failed with permanent error", e instanceof Error ? e : new Error(String(e)));
|
|
3168
|
-
return new Response(`Failed to start container: ${e instanceof Error ? e.message : String(e)}`, { status: 500 });
|
|
3169
3803
|
}
|
|
3170
3804
|
return await super.containerFetch(requestOrUrl, portOrInit, portParam);
|
|
3171
3805
|
}
|
|
@@ -3202,6 +3836,8 @@ var Sandbox = class Sandbox extends Container {
|
|
|
3202
3836
|
"network connection lost",
|
|
3203
3837
|
"container suddenly disconnected",
|
|
3204
3838
|
"monitor failed to find container",
|
|
3839
|
+
"container exited with unexpected exit code",
|
|
3840
|
+
"container exited before we could determine",
|
|
3205
3841
|
"timed out",
|
|
3206
3842
|
"timeout",
|
|
3207
3843
|
"the operation was aborted"
|
|
@@ -3828,6 +4464,63 @@ var Sandbox = class Sandbox extends Container {
|
|
|
3828
4464
|
return this.client.files.exists(path, session);
|
|
3829
4465
|
}
|
|
3830
4466
|
/**
|
|
4467
|
+
* Get the noVNC preview URL for browser-based desktop viewing.
|
|
4468
|
+
* Confirms desktop is active, then uses exposePort() to generate
|
|
4469
|
+
* a token-authenticated preview URL for the noVNC port (6080).
|
|
4470
|
+
*
|
|
4471
|
+
* @param hostname - The custom domain hostname for preview URLs
|
|
4472
|
+
* (e.g., 'preview.example.com'). Required because preview URLs
|
|
4473
|
+
* use subdomain patterns that .workers.dev doesn't support.
|
|
4474
|
+
* @param options - Optional settings
|
|
4475
|
+
* @param options.token - Reuse an existing token instead of generating a new one
|
|
4476
|
+
* @returns The authenticated noVNC preview URL
|
|
4477
|
+
*/
|
|
4478
|
+
async getDesktopStreamUrl(hostname, options) {
|
|
4479
|
+
if ((await this.client.desktop.status()).status === "inactive") throw new Error("Desktop is not running. Call sandbox.desktop.start() first.");
|
|
4480
|
+
let url;
|
|
4481
|
+
try {
|
|
4482
|
+
url = (await this.exposePort(6080, {
|
|
4483
|
+
hostname,
|
|
4484
|
+
token: options?.token
|
|
4485
|
+
})).url;
|
|
4486
|
+
} catch {
|
|
4487
|
+
const existingToken = (await this.ctx.storage.get("portTokens") || {})["6080"];
|
|
4488
|
+
if (existingToken && this.sandboxName) url = this.constructPreviewUrl(6080, this.sandboxName, hostname, existingToken);
|
|
4489
|
+
else throw new Error("Failed to get desktop stream URL: port 6080 could not be exposed and no existing token found.");
|
|
4490
|
+
}
|
|
4491
|
+
try {
|
|
4492
|
+
await this.waitForPort({
|
|
4493
|
+
portToCheck: 6080,
|
|
4494
|
+
retries: 30,
|
|
4495
|
+
waitInterval: 500
|
|
4496
|
+
});
|
|
4497
|
+
} catch {}
|
|
4498
|
+
return { url };
|
|
4499
|
+
}
|
|
4500
|
+
/**
|
|
4501
|
+
* Watch a directory for file system changes using native inotify.
|
|
4502
|
+
*
|
|
4503
|
+
* The returned promise resolves only after the watcher is established on the
|
|
4504
|
+
* filesystem, so callers can immediately perform actions that depend on the
|
|
4505
|
+
* watch being active. The returned stream contains the full event sequence
|
|
4506
|
+
* starting with the `watching` event.
|
|
4507
|
+
*
|
|
4508
|
+
* Consume the stream with `parseSSEStream<FileWatchSSEEvent>(stream)`.
|
|
4509
|
+
*
|
|
4510
|
+
* @param path - Path to watch (absolute or relative to /workspace)
|
|
4511
|
+
* @param options - Watch options
|
|
4512
|
+
*/
|
|
4513
|
+
async watch(path, options = {}) {
|
|
4514
|
+
const sessionId = options.sessionId ?? await this.ensureDefaultSession();
|
|
4515
|
+
return this.client.watch.watch({
|
|
4516
|
+
path,
|
|
4517
|
+
recursive: options.recursive,
|
|
4518
|
+
include: options.include,
|
|
4519
|
+
exclude: options.exclude,
|
|
4520
|
+
sessionId
|
|
4521
|
+
});
|
|
4522
|
+
}
|
|
4523
|
+
/**
|
|
3831
4524
|
* Expose a port and get a preview URL for accessing services running in the sandbox
|
|
3832
4525
|
*
|
|
3833
4526
|
* @param port - Port number to expose (1024-65535)
|
|
@@ -4037,6 +4730,10 @@ var Sandbox = class Sandbox extends Container {
|
|
|
4037
4730
|
sessionId
|
|
4038
4731
|
}),
|
|
4039
4732
|
readFileStream: (path) => this.readFileStream(path, { sessionId }),
|
|
4733
|
+
watch: (path, options) => this.watch(path, {
|
|
4734
|
+
...options,
|
|
4735
|
+
sessionId
|
|
4736
|
+
}),
|
|
4040
4737
|
mkdir: (path, options) => this.mkdir(path, {
|
|
4041
4738
|
...options,
|
|
4042
4739
|
sessionId
|
|
@@ -4575,20 +5272,23 @@ async function* parseSSE(stream) {
|
|
|
4575
5272
|
const reader = stream.getReader();
|
|
4576
5273
|
const decoder = new TextDecoder();
|
|
4577
5274
|
let buffer = "";
|
|
5275
|
+
let currentEvent = { data: [] };
|
|
4578
5276
|
try {
|
|
4579
5277
|
while (true) {
|
|
4580
5278
|
const { done, value } = await reader.read();
|
|
4581
5279
|
if (done) break;
|
|
4582
5280
|
buffer += decoder.decode(value, { stream: true });
|
|
4583
|
-
const
|
|
4584
|
-
buffer =
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
} catch {}
|
|
4590
|
-
}
|
|
5281
|
+
const parsed = parseSSEFrames(buffer, currentEvent);
|
|
5282
|
+
buffer = parsed.remaining;
|
|
5283
|
+
currentEvent = parsed.currentEvent;
|
|
5284
|
+
for (const frame of parsed.events) try {
|
|
5285
|
+
yield JSON.parse(frame.data);
|
|
5286
|
+
} catch {}
|
|
4591
5287
|
}
|
|
5288
|
+
const finalParsed = parseSSEFrames(`${buffer}\n\n`, currentEvent);
|
|
5289
|
+
for (const frame of finalParsed.events) try {
|
|
5290
|
+
yield JSON.parse(frame.data);
|
|
5291
|
+
} catch {}
|
|
4592
5292
|
} finally {
|
|
4593
5293
|
try {
|
|
4594
5294
|
await reader.cancel();
|
|
@@ -4686,5 +5386,5 @@ async function collectFile(stream) {
|
|
|
4686
5386
|
}
|
|
4687
5387
|
|
|
4688
5388
|
//#endregion
|
|
4689
|
-
export { BackupClient, BackupCreateError, BackupExpiredError, BackupNotFoundError, BackupRestoreError, BucketMountError, CodeInterpreter, CommandClient, FileClient, GitClient, InvalidBackupConfigError, InvalidMountConfigError, MissingCredentialsError, PortClient, ProcessClient, ProcessExitedBeforeReadyError, ProcessReadyTimeoutError, S3FSMountError, Sandbox, SandboxClient, UtilityClient, asyncIterableToSSEStream, collectFile, getSandbox, isExecResult, isProcess, isProcessStatus, parseSSEStream, proxyTerminal, proxyToSandbox, responseToAsyncIterable, streamFile };
|
|
5389
|
+
export { BackupClient, BackupCreateError, BackupExpiredError, BackupNotFoundError, BackupRestoreError, BucketMountError, CodeInterpreter, CommandClient, DesktopClient, DesktopInvalidCoordinatesError, DesktopInvalidOptionsError, DesktopNotStartedError, DesktopProcessCrashedError, DesktopStartFailedError, DesktopUnavailableError, FileClient, GitClient, InvalidBackupConfigError, InvalidMountConfigError, MissingCredentialsError, PortClient, ProcessClient, ProcessExitedBeforeReadyError, ProcessReadyTimeoutError, S3FSMountError, Sandbox, SandboxClient, UtilityClient, asyncIterableToSSEStream, collectFile, getSandbox, isExecResult, isProcess, isProcessStatus, parseSSEStream, proxyTerminal, proxyToSandbox, responseToAsyncIterable, streamFile };
|
|
4690
5390
|
//# sourceMappingURL=index.js.map
|