computesdk 1.10.0 → 1.10.2
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/README.md +253 -541
- package/dist/index.d.mts +3085 -647
- package/dist/index.d.ts +3085 -647
- package/dist/index.js +3287 -1175
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3277 -1169
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -2
package/dist/index.mjs
CHANGED
|
@@ -1,847 +1,3111 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
var PROVIDER_NAMES = Object.keys(PROVIDER_AUTH);
|
|
24
|
-
var PROVIDER_HEADERS = {
|
|
25
|
-
e2b: {
|
|
26
|
-
apiKey: "X-E2B-API-Key"
|
|
27
|
-
},
|
|
28
|
-
modal: {
|
|
29
|
-
tokenId: "X-Modal-Token-Id",
|
|
30
|
-
tokenSecret: "X-Modal-Token-Secret"
|
|
31
|
-
},
|
|
32
|
-
railway: {
|
|
33
|
-
apiToken: "X-Railway-API-Token"
|
|
34
|
-
},
|
|
35
|
-
daytona: {
|
|
36
|
-
apiKey: "X-Daytona-API-Key"
|
|
37
|
-
},
|
|
38
|
-
vercel: {
|
|
39
|
-
oidcToken: "X-Vercel-OIDC-Token",
|
|
40
|
-
token: "X-Vercel-Token",
|
|
41
|
-
teamId: "X-Vercel-Team-Id",
|
|
42
|
-
projectId: "X-Vercel-Project-Id"
|
|
43
|
-
},
|
|
44
|
-
runloop: {
|
|
45
|
-
apiKey: "X-Runloop-API-Key"
|
|
46
|
-
},
|
|
47
|
-
cloudflare: {
|
|
48
|
-
apiToken: "X-Cloudflare-API-Token",
|
|
49
|
-
accountId: "X-Cloudflare-Account-Id"
|
|
50
|
-
},
|
|
51
|
-
codesandbox: {
|
|
52
|
-
apiKey: "X-CSB-API-Key"
|
|
53
|
-
},
|
|
54
|
-
blaxel: {
|
|
55
|
-
apiKey: "X-BL-API-Key",
|
|
56
|
-
workspace: "X-BL-Workspace"
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
var PROVIDER_ENV_MAP = {
|
|
60
|
-
e2b: {
|
|
61
|
-
E2B_API_KEY: "apiKey"
|
|
62
|
-
},
|
|
63
|
-
modal: {
|
|
64
|
-
MODAL_TOKEN_ID: "tokenId",
|
|
65
|
-
MODAL_TOKEN_SECRET: "tokenSecret"
|
|
66
|
-
},
|
|
67
|
-
railway: {
|
|
68
|
-
RAILWAY_API_KEY: "apiToken",
|
|
69
|
-
RAILWAY_PROJECT_ID: "projectId",
|
|
70
|
-
RAILWAY_ENVIRONMENT_ID: "environmentId"
|
|
71
|
-
},
|
|
72
|
-
daytona: {
|
|
73
|
-
DAYTONA_API_KEY: "apiKey"
|
|
74
|
-
},
|
|
75
|
-
vercel: {
|
|
76
|
-
VERCEL_OIDC_TOKEN: "oidcToken",
|
|
77
|
-
VERCEL_TOKEN: "token",
|
|
78
|
-
VERCEL_TEAM_ID: "teamId",
|
|
79
|
-
VERCEL_PROJECT_ID: "projectId"
|
|
80
|
-
},
|
|
81
|
-
runloop: {
|
|
82
|
-
RUNLOOP_API_KEY: "apiKey"
|
|
83
|
-
},
|
|
84
|
-
cloudflare: {
|
|
85
|
-
CLOUDFLARE_API_TOKEN: "apiToken",
|
|
86
|
-
CLOUDFLARE_ACCOUNT_ID: "accountId"
|
|
87
|
-
},
|
|
88
|
-
codesandbox: {
|
|
89
|
-
CSB_API_KEY: "apiKey"
|
|
90
|
-
},
|
|
91
|
-
blaxel: {
|
|
92
|
-
BL_API_KEY: "apiKey",
|
|
93
|
-
BL_WORKSPACE: "workspace"
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
var PROVIDER_DASHBOARD_URLS = {
|
|
97
|
-
e2b: "https://e2b.dev/dashboard",
|
|
98
|
-
modal: "https://modal.com/settings",
|
|
99
|
-
railway: "https://railway.app/account/tokens",
|
|
100
|
-
daytona: "https://daytona.io/dashboard",
|
|
101
|
-
vercel: "https://vercel.com/account/tokens",
|
|
102
|
-
runloop: "https://runloop.ai/dashboard",
|
|
103
|
-
cloudflare: "https://dash.cloudflare.com/profile/api-tokens",
|
|
104
|
-
codesandbox: "https://codesandbox.io/dashboard/settings",
|
|
105
|
-
blaxel: "https://blaxel.ai/dashboard"
|
|
106
|
-
};
|
|
107
|
-
function isValidProvider(name) {
|
|
108
|
-
return name in PROVIDER_AUTH;
|
|
1
|
+
// src/client/protocol.ts
|
|
2
|
+
var MessageType = /* @__PURE__ */ ((MessageType2) => {
|
|
3
|
+
MessageType2[MessageType2["Subscribe"] = 1] = "Subscribe";
|
|
4
|
+
MessageType2[MessageType2["Unsubscribe"] = 2] = "Unsubscribe";
|
|
5
|
+
MessageType2[MessageType2["Data"] = 3] = "Data";
|
|
6
|
+
MessageType2[MessageType2["Error"] = 4] = "Error";
|
|
7
|
+
MessageType2[MessageType2["Connected"] = 5] = "Connected";
|
|
8
|
+
return MessageType2;
|
|
9
|
+
})(MessageType || {});
|
|
10
|
+
var textEncoder = new TextEncoder();
|
|
11
|
+
var textDecoder = new TextDecoder();
|
|
12
|
+
function getValueSize(value) {
|
|
13
|
+
if (typeof value === "string") {
|
|
14
|
+
return textEncoder.encode(value).length;
|
|
15
|
+
} else if (typeof value === "number") {
|
|
16
|
+
return 8;
|
|
17
|
+
} else if (typeof value === "boolean") {
|
|
18
|
+
return 1;
|
|
19
|
+
} else if (value instanceof Uint8Array) {
|
|
20
|
+
return value.length;
|
|
21
|
+
}
|
|
22
|
+
return 0;
|
|
109
23
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
for (const [
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
24
|
+
function encodeKeyValue(data) {
|
|
25
|
+
let totalSize = 2;
|
|
26
|
+
const fields = Object.entries(data);
|
|
27
|
+
for (const [key, value] of fields) {
|
|
28
|
+
const keyBytes = textEncoder.encode(key);
|
|
29
|
+
totalSize += 2;
|
|
30
|
+
totalSize += keyBytes.length;
|
|
31
|
+
totalSize += 1;
|
|
32
|
+
totalSize += 4;
|
|
33
|
+
totalSize += getValueSize(value);
|
|
34
|
+
}
|
|
35
|
+
const buffer = new Uint8Array(totalSize);
|
|
36
|
+
const view = new DataView(buffer.buffer);
|
|
37
|
+
let offset = 0;
|
|
38
|
+
view.setUint16(offset, fields.length, false);
|
|
39
|
+
offset += 2;
|
|
40
|
+
for (const [key, value] of fields) {
|
|
41
|
+
const keyBytes = textEncoder.encode(key);
|
|
42
|
+
view.setUint16(offset, keyBytes.length, false);
|
|
43
|
+
offset += 2;
|
|
44
|
+
buffer.set(keyBytes, offset);
|
|
45
|
+
offset += keyBytes.length;
|
|
46
|
+
if (typeof value === "string") {
|
|
47
|
+
buffer[offset] = 1 /* String */;
|
|
48
|
+
offset++;
|
|
49
|
+
const valueBytes = textEncoder.encode(value);
|
|
50
|
+
view.setUint32(offset, valueBytes.length, false);
|
|
51
|
+
offset += 4;
|
|
52
|
+
buffer.set(valueBytes, offset);
|
|
53
|
+
offset += valueBytes.length;
|
|
54
|
+
} else if (typeof value === "number") {
|
|
55
|
+
buffer[offset] = 2 /* Number */;
|
|
56
|
+
offset++;
|
|
57
|
+
view.setUint32(offset, 8, false);
|
|
58
|
+
offset += 4;
|
|
59
|
+
view.setFloat64(offset, value, false);
|
|
60
|
+
offset += 8;
|
|
61
|
+
} else if (typeof value === "boolean") {
|
|
62
|
+
buffer[offset] = 3 /* Boolean */;
|
|
63
|
+
offset++;
|
|
64
|
+
view.setUint32(offset, 1, false);
|
|
65
|
+
offset += 4;
|
|
66
|
+
buffer[offset] = value ? 1 : 0;
|
|
67
|
+
offset++;
|
|
68
|
+
} else if (value instanceof Uint8Array) {
|
|
69
|
+
buffer[offset] = 4 /* Bytes */;
|
|
70
|
+
offset++;
|
|
71
|
+
view.setUint32(offset, value.length, false);
|
|
72
|
+
offset += 4;
|
|
73
|
+
buffer.set(value, offset);
|
|
74
|
+
offset += value.length;
|
|
75
|
+
} else {
|
|
76
|
+
throw new Error(`Unsupported value type for key ${key}: ${typeof value}`);
|
|
117
77
|
}
|
|
118
78
|
}
|
|
119
|
-
return
|
|
79
|
+
return buffer;
|
|
120
80
|
}
|
|
121
|
-
function
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
81
|
+
function decodeKeyValue(data) {
|
|
82
|
+
if (data.length < 2) {
|
|
83
|
+
throw new Error("Data too short for key-value encoding");
|
|
84
|
+
}
|
|
85
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
86
|
+
const result = {};
|
|
87
|
+
let offset = 0;
|
|
88
|
+
const numFields = view.getUint16(offset, false);
|
|
89
|
+
offset += 2;
|
|
90
|
+
for (let i = 0; i < numFields; i++) {
|
|
91
|
+
if (offset + 2 > data.length) {
|
|
92
|
+
throw new Error(`Invalid key length at field ${i}`);
|
|
93
|
+
}
|
|
94
|
+
const keyLen = view.getUint16(offset, false);
|
|
95
|
+
offset += 2;
|
|
96
|
+
if (offset + keyLen > data.length) {
|
|
97
|
+
throw new Error(`Key data truncated at field ${i}`);
|
|
98
|
+
}
|
|
99
|
+
const key = textDecoder.decode(data.slice(offset, offset + keyLen));
|
|
100
|
+
offset += keyLen;
|
|
101
|
+
if (offset + 1 > data.length) {
|
|
102
|
+
throw new Error(`Invalid value type at field ${i}`);
|
|
103
|
+
}
|
|
104
|
+
const valueType = data[offset];
|
|
105
|
+
offset++;
|
|
106
|
+
if (offset + 4 > data.length) {
|
|
107
|
+
throw new Error(`Invalid value length at field ${i}`);
|
|
108
|
+
}
|
|
109
|
+
const valueLen = view.getUint32(offset, false);
|
|
110
|
+
offset += 4;
|
|
111
|
+
if (offset + valueLen > data.length) {
|
|
112
|
+
throw new Error(`Value data truncated at field ${i}`);
|
|
113
|
+
}
|
|
114
|
+
const valueData = data.slice(offset, offset + valueLen);
|
|
115
|
+
offset += valueLen;
|
|
116
|
+
switch (valueType) {
|
|
117
|
+
case 1 /* String */:
|
|
118
|
+
result[key] = textDecoder.decode(valueData);
|
|
119
|
+
break;
|
|
120
|
+
case 2 /* Number */:
|
|
121
|
+
if (valueData.length !== 8) {
|
|
122
|
+
throw new Error(`Invalid number length for field ${key}`);
|
|
123
|
+
}
|
|
124
|
+
const valueView = new DataView(valueData.buffer, valueData.byteOffset);
|
|
125
|
+
result[key] = valueView.getFloat64(0, false);
|
|
126
|
+
break;
|
|
127
|
+
case 3 /* Boolean */:
|
|
128
|
+
if (valueData.length !== 1) {
|
|
129
|
+
throw new Error(`Invalid boolean length for field ${key}`);
|
|
130
|
+
}
|
|
131
|
+
result[key] = valueData[0] !== 0;
|
|
132
|
+
break;
|
|
133
|
+
case 4 /* Bytes */:
|
|
134
|
+
result[key] = valueData;
|
|
135
|
+
break;
|
|
136
|
+
default:
|
|
137
|
+
throw new Error(`Unknown value type 0x${valueType.toString(16)} for field ${key}`);
|
|
128
138
|
}
|
|
129
139
|
}
|
|
130
|
-
return
|
|
140
|
+
return result;
|
|
131
141
|
}
|
|
132
|
-
function
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
142
|
+
function encodeBinaryMessage(message) {
|
|
143
|
+
let messageType;
|
|
144
|
+
let channel = "";
|
|
145
|
+
let msgType = "";
|
|
146
|
+
let data = {};
|
|
147
|
+
if (message.type === "subscribe") {
|
|
148
|
+
messageType = 1 /* Subscribe */;
|
|
149
|
+
channel = message.channel || "";
|
|
150
|
+
msgType = "subscribe";
|
|
151
|
+
data = {};
|
|
152
|
+
} else if (message.type === "unsubscribe") {
|
|
153
|
+
messageType = 2 /* Unsubscribe */;
|
|
154
|
+
channel = message.channel || "";
|
|
155
|
+
msgType = "unsubscribe";
|
|
156
|
+
data = {};
|
|
157
|
+
} else {
|
|
158
|
+
messageType = 3 /* Data */;
|
|
159
|
+
channel = message.channel || "";
|
|
160
|
+
msgType = message.type || "";
|
|
161
|
+
data = message.data || message;
|
|
162
|
+
}
|
|
163
|
+
const channelBytes = encodeUTF8(channel);
|
|
164
|
+
const msgTypeBytes = encodeUTF8(msgType);
|
|
165
|
+
let dataBytes;
|
|
166
|
+
if (data === void 0 || data === null) {
|
|
167
|
+
dataBytes = new Uint8Array(0);
|
|
168
|
+
} else if (typeof data === "string") {
|
|
169
|
+
dataBytes = encodeUTF8(data);
|
|
170
|
+
} else if (data instanceof Uint8Array) {
|
|
171
|
+
dataBytes = data;
|
|
172
|
+
} else if (typeof data === "object") {
|
|
173
|
+
dataBytes = encodeKeyValue(data);
|
|
174
|
+
} else {
|
|
175
|
+
throw new Error(`Unsupported data type: ${typeof data}`);
|
|
176
|
+
}
|
|
177
|
+
const totalSize = 1 + 2 + channelBytes.length + 2 + msgTypeBytes.length + 4 + dataBytes.length;
|
|
178
|
+
const buffer = new ArrayBuffer(totalSize);
|
|
179
|
+
const view = new DataView(buffer);
|
|
180
|
+
let offset = 0;
|
|
181
|
+
view.setUint8(offset, messageType);
|
|
182
|
+
offset += 1;
|
|
183
|
+
view.setUint16(offset, channelBytes.length, false);
|
|
184
|
+
offset += 2;
|
|
185
|
+
const uint8View = new Uint8Array(buffer);
|
|
186
|
+
uint8View.set(channelBytes, offset);
|
|
187
|
+
offset += channelBytes.length;
|
|
188
|
+
view.setUint16(offset, msgTypeBytes.length, false);
|
|
189
|
+
offset += 2;
|
|
190
|
+
uint8View.set(msgTypeBytes, offset);
|
|
191
|
+
offset += msgTypeBytes.length;
|
|
192
|
+
view.setUint32(offset, dataBytes.length, false);
|
|
193
|
+
offset += 4;
|
|
194
|
+
uint8View.set(dataBytes, offset);
|
|
195
|
+
return buffer;
|
|
196
|
+
}
|
|
197
|
+
function decodeBinaryMessage(buffer) {
|
|
198
|
+
const arrayBuffer = buffer instanceof Uint8Array ? buffer.buffer : buffer;
|
|
199
|
+
const view = new DataView(arrayBuffer);
|
|
200
|
+
const uint8View = new Uint8Array(arrayBuffer);
|
|
201
|
+
let offset = 0;
|
|
202
|
+
const messageType = view.getUint8(offset);
|
|
203
|
+
offset += 1;
|
|
204
|
+
const channelLength = view.getUint16(offset, false);
|
|
205
|
+
offset += 2;
|
|
206
|
+
const channelBytes = uint8View.slice(offset, offset + channelLength);
|
|
207
|
+
const channel = decodeUTF8(channelBytes);
|
|
208
|
+
offset += channelLength;
|
|
209
|
+
const msgTypeLength = view.getUint16(offset, false);
|
|
210
|
+
offset += 2;
|
|
211
|
+
const msgTypeBytes = uint8View.slice(offset, offset + msgTypeLength);
|
|
212
|
+
const msgType = decodeUTF8(msgTypeBytes);
|
|
213
|
+
offset += msgTypeLength;
|
|
214
|
+
const dataLength = view.getUint32(offset, false);
|
|
215
|
+
offset += 4;
|
|
216
|
+
const dataBytes = uint8View.slice(offset, offset + dataLength);
|
|
217
|
+
const shouldTryKeyValue = ["terminal:input", "terminal:resize", "file:changed", "terminal:output", "signal", "test"].includes(msgType);
|
|
218
|
+
let data;
|
|
219
|
+
if (dataBytes.length === 0) {
|
|
220
|
+
data = {};
|
|
221
|
+
} else if (shouldTryKeyValue) {
|
|
222
|
+
try {
|
|
223
|
+
data = decodeKeyValue(dataBytes);
|
|
224
|
+
} catch {
|
|
225
|
+
data = dataBytes;
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
data = dataBytes;
|
|
137
229
|
}
|
|
138
|
-
|
|
230
|
+
if (messageType === 1 /* Subscribe */ || messageType === 2 /* Unsubscribe */) {
|
|
231
|
+
return {
|
|
232
|
+
type: msgType,
|
|
233
|
+
channel
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
type: msgType,
|
|
238
|
+
channel,
|
|
239
|
+
data
|
|
240
|
+
};
|
|
139
241
|
}
|
|
140
|
-
function
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
242
|
+
function encodeUTF8(str) {
|
|
243
|
+
if (typeof TextEncoder !== "undefined") {
|
|
244
|
+
const encoder = new TextEncoder();
|
|
245
|
+
return encoder.encode(str);
|
|
246
|
+
}
|
|
247
|
+
const utf8 = [];
|
|
248
|
+
for (let i = 0; i < str.length; i++) {
|
|
249
|
+
let charcode = str.charCodeAt(i);
|
|
250
|
+
if (charcode < 128) {
|
|
251
|
+
utf8.push(charcode);
|
|
252
|
+
} else if (charcode < 2048) {
|
|
253
|
+
utf8.push(192 | charcode >> 6, 128 | charcode & 63);
|
|
254
|
+
} else if (charcode < 55296 || charcode >= 57344) {
|
|
255
|
+
utf8.push(224 | charcode >> 12, 128 | charcode >> 6 & 63, 128 | charcode & 63);
|
|
256
|
+
} else {
|
|
257
|
+
i++;
|
|
258
|
+
charcode = 65536 + ((charcode & 1023) << 10 | str.charCodeAt(i) & 1023);
|
|
259
|
+
utf8.push(
|
|
260
|
+
240 | charcode >> 18,
|
|
261
|
+
128 | charcode >> 12 & 63,
|
|
262
|
+
128 | charcode >> 6 & 63,
|
|
263
|
+
128 | charcode & 63
|
|
264
|
+
);
|
|
152
265
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
266
|
+
}
|
|
267
|
+
return new Uint8Array(utf8);
|
|
268
|
+
}
|
|
269
|
+
function decodeUTF8(bytes) {
|
|
270
|
+
if (typeof TextDecoder !== "undefined") {
|
|
271
|
+
const decoder = new TextDecoder();
|
|
272
|
+
return decoder.decode(bytes);
|
|
273
|
+
}
|
|
274
|
+
let str = "";
|
|
275
|
+
let i = 0;
|
|
276
|
+
while (i < bytes.length) {
|
|
277
|
+
const c = bytes[i++];
|
|
278
|
+
if (c < 128) {
|
|
279
|
+
str += String.fromCharCode(c);
|
|
280
|
+
} else if (c < 224) {
|
|
281
|
+
str += String.fromCharCode((c & 31) << 6 | bytes[i++] & 63);
|
|
282
|
+
} else if (c < 240) {
|
|
283
|
+
str += String.fromCharCode((c & 15) << 12 | (bytes[i++] & 63) << 6 | bytes[i++] & 63);
|
|
284
|
+
} else {
|
|
285
|
+
const c2 = (c & 7) << 18 | (bytes[i++] & 63) << 12 | (bytes[i++] & 63) << 6 | bytes[i++] & 63;
|
|
286
|
+
const c3 = c2 - 65536;
|
|
287
|
+
str += String.fromCharCode(55296 | c3 >> 10, 56320 | c3 & 1023);
|
|
156
288
|
}
|
|
157
289
|
}
|
|
158
|
-
return
|
|
290
|
+
return str;
|
|
291
|
+
}
|
|
292
|
+
function isBinaryData(data) {
|
|
293
|
+
return data instanceof ArrayBuffer || data instanceof Uint8Array || data instanceof Blob;
|
|
294
|
+
}
|
|
295
|
+
async function blobToArrayBuffer(blob) {
|
|
296
|
+
if (blob.arrayBuffer) {
|
|
297
|
+
return blob.arrayBuffer();
|
|
298
|
+
}
|
|
299
|
+
return new Promise((resolve, reject) => {
|
|
300
|
+
const reader = new FileReader();
|
|
301
|
+
reader.onload = () => resolve(reader.result);
|
|
302
|
+
reader.onerror = reject;
|
|
303
|
+
reader.readAsArrayBuffer(blob);
|
|
304
|
+
});
|
|
159
305
|
}
|
|
160
306
|
|
|
161
|
-
// src/
|
|
162
|
-
var
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
runloop: ["RUNLOOP_API_KEY"],
|
|
180
|
-
vercel: ["VERCEL_TOKEN", "VERCEL_TEAM_ID", "VERCEL_PROJECT_ID"],
|
|
181
|
-
cloudflare: ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"],
|
|
182
|
-
codesandbox: ["CSB_API_KEY"],
|
|
183
|
-
blaxel: ["BL_API_KEY", "BL_WORKSPACE"]
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
// src/providers/gateway.ts
|
|
187
|
-
import { Sandbox as ClientSandbox } from "@computesdk/client";
|
|
188
|
-
|
|
189
|
-
// src/factory.ts
|
|
190
|
-
import { cmd } from "@computesdk/cmd";
|
|
191
|
-
var UnsupportedFileSystem = class {
|
|
192
|
-
constructor(providerName) {
|
|
193
|
-
this.providerName = providerName;
|
|
307
|
+
// src/client/websocket.ts
|
|
308
|
+
var WebSocketManager = class {
|
|
309
|
+
constructor(config) {
|
|
310
|
+
this.ws = null;
|
|
311
|
+
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
312
|
+
this.reconnectAttempts = 0;
|
|
313
|
+
this.reconnectTimer = null;
|
|
314
|
+
this.subscribedChannels = /* @__PURE__ */ new Set();
|
|
315
|
+
this.isManualClose = false;
|
|
316
|
+
this.config = {
|
|
317
|
+
url: config.url,
|
|
318
|
+
WebSocket: config.WebSocket,
|
|
319
|
+
autoReconnect: config.autoReconnect ?? true,
|
|
320
|
+
reconnectDelay: config.reconnectDelay ?? 1e3,
|
|
321
|
+
maxReconnectAttempts: config.maxReconnectAttempts ?? 5,
|
|
322
|
+
debug: config.debug ?? false,
|
|
323
|
+
protocol: config.protocol ?? "binary"
|
|
324
|
+
};
|
|
194
325
|
}
|
|
195
|
-
|
|
196
|
-
|
|
326
|
+
// ============================================================================
|
|
327
|
+
// Connection Management
|
|
328
|
+
// ============================================================================
|
|
329
|
+
/**
|
|
330
|
+
* Connect to WebSocket server
|
|
331
|
+
*/
|
|
332
|
+
connect() {
|
|
333
|
+
return new Promise((resolve, reject) => {
|
|
334
|
+
try {
|
|
335
|
+
this.isManualClose = false;
|
|
336
|
+
this.log("Connecting to WebSocket URL:", this.config.url);
|
|
337
|
+
this.ws = new this.config.WebSocket(this.config.url);
|
|
338
|
+
this.ws.onopen = () => {
|
|
339
|
+
this.log("Connected to WebSocket server");
|
|
340
|
+
this.reconnectAttempts = 0;
|
|
341
|
+
if (this.subscribedChannels.size > 0) {
|
|
342
|
+
this.log("Resubscribing to channels:", Array.from(this.subscribedChannels));
|
|
343
|
+
this.subscribedChannels.forEach((channel) => {
|
|
344
|
+
this.sendRaw({ type: "subscribe", channel });
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
this.emit("open");
|
|
348
|
+
resolve();
|
|
349
|
+
};
|
|
350
|
+
this.ws.onmessage = async (event) => {
|
|
351
|
+
try {
|
|
352
|
+
let message;
|
|
353
|
+
if (this.config.protocol === "binary" && isBinaryData(event.data)) {
|
|
354
|
+
let buffer;
|
|
355
|
+
if (event.data instanceof Blob) {
|
|
356
|
+
buffer = await blobToArrayBuffer(event.data);
|
|
357
|
+
} else {
|
|
358
|
+
buffer = event.data;
|
|
359
|
+
}
|
|
360
|
+
message = decodeBinaryMessage(buffer);
|
|
361
|
+
this.log("Received binary message:", message);
|
|
362
|
+
} else {
|
|
363
|
+
message = JSON.parse(event.data);
|
|
364
|
+
this.log("Received JSON message:", message);
|
|
365
|
+
}
|
|
366
|
+
this.handleMessage(message);
|
|
367
|
+
} catch (error) {
|
|
368
|
+
this.log("Failed to parse message:", error);
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
this.ws.onerror = (error) => {
|
|
372
|
+
this.log("WebSocket error:", error);
|
|
373
|
+
this.emit("error", error);
|
|
374
|
+
reject(error);
|
|
375
|
+
};
|
|
376
|
+
this.ws.onclose = () => {
|
|
377
|
+
this.log("WebSocket connection closed");
|
|
378
|
+
this.emit("close");
|
|
379
|
+
if (this.config.autoReconnect && !this.isManualClose) {
|
|
380
|
+
this.attemptReconnect();
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
} catch (error) {
|
|
384
|
+
reject(error);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
197
387
|
}
|
|
198
|
-
|
|
199
|
-
|
|
388
|
+
/**
|
|
389
|
+
* Disconnect from WebSocket server
|
|
390
|
+
*/
|
|
391
|
+
disconnect() {
|
|
392
|
+
this.isManualClose = true;
|
|
393
|
+
if (this.reconnectTimer) {
|
|
394
|
+
clearTimeout(this.reconnectTimer);
|
|
395
|
+
this.reconnectTimer = null;
|
|
396
|
+
}
|
|
397
|
+
if (this.ws) {
|
|
398
|
+
this.ws.close();
|
|
399
|
+
this.ws = null;
|
|
400
|
+
}
|
|
200
401
|
}
|
|
201
|
-
|
|
202
|
-
|
|
402
|
+
/**
|
|
403
|
+
* Check if WebSocket is connected
|
|
404
|
+
*/
|
|
405
|
+
isConnected() {
|
|
406
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
203
407
|
}
|
|
204
|
-
|
|
205
|
-
|
|
408
|
+
/**
|
|
409
|
+
* Attempt to reconnect to WebSocket server
|
|
410
|
+
*/
|
|
411
|
+
attemptReconnect() {
|
|
412
|
+
if (this.config.maxReconnectAttempts > 0 && this.reconnectAttempts >= this.config.maxReconnectAttempts) {
|
|
413
|
+
this.log("Max reconnection attempts reached");
|
|
414
|
+
this.emit("reconnect-failed");
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
this.reconnectAttempts++;
|
|
418
|
+
this.log(`Reconnecting... (attempt ${this.reconnectAttempts})`);
|
|
419
|
+
this.reconnectTimer = setTimeout(() => {
|
|
420
|
+
this.connect().catch((error) => {
|
|
421
|
+
this.log("Reconnection failed:", error);
|
|
422
|
+
});
|
|
423
|
+
}, this.config.reconnectDelay);
|
|
206
424
|
}
|
|
207
|
-
|
|
208
|
-
|
|
425
|
+
// ============================================================================
|
|
426
|
+
// Channel Subscription
|
|
427
|
+
// ============================================================================
|
|
428
|
+
/**
|
|
429
|
+
* Subscribe to a channel
|
|
430
|
+
* @param channel - Channel name (e.g., 'terminal:term_abc123', 'watcher:watcher_xyz789', 'signals')
|
|
431
|
+
*/
|
|
432
|
+
subscribe(channel) {
|
|
433
|
+
this.subscribedChannels.add(channel);
|
|
434
|
+
this.sendRaw({ type: "subscribe", channel });
|
|
435
|
+
this.log("Subscribed to channel:", channel);
|
|
209
436
|
}
|
|
210
|
-
|
|
211
|
-
|
|
437
|
+
/**
|
|
438
|
+
* Unsubscribe from a channel
|
|
439
|
+
*/
|
|
440
|
+
unsubscribe(channel) {
|
|
441
|
+
this.subscribedChannels.delete(channel);
|
|
442
|
+
this.sendRaw({ type: "unsubscribe", channel });
|
|
443
|
+
this.log("Unsubscribed from channel:", channel);
|
|
212
444
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
this.
|
|
218
|
-
this.allMethods = allMethods;
|
|
445
|
+
/**
|
|
446
|
+
* Get list of subscribed channels
|
|
447
|
+
*/
|
|
448
|
+
getSubscribedChannels() {
|
|
449
|
+
return Array.from(this.subscribedChannels);
|
|
219
450
|
}
|
|
220
|
-
|
|
221
|
-
|
|
451
|
+
// ============================================================================
|
|
452
|
+
// Message Sending
|
|
453
|
+
// ============================================================================
|
|
454
|
+
/**
|
|
455
|
+
* Send raw message to server
|
|
456
|
+
*/
|
|
457
|
+
sendRaw(message) {
|
|
458
|
+
if (!this.isConnected()) {
|
|
459
|
+
throw new Error("WebSocket is not connected");
|
|
460
|
+
}
|
|
461
|
+
if (this.config.protocol === "binary") {
|
|
462
|
+
const buffer = encodeBinaryMessage(message);
|
|
463
|
+
this.ws.send(buffer);
|
|
464
|
+
this.log("Sent binary message:", message);
|
|
465
|
+
} else {
|
|
466
|
+
this.ws.send(JSON.stringify(message));
|
|
467
|
+
this.log("Sent JSON message:", message);
|
|
468
|
+
}
|
|
222
469
|
}
|
|
223
|
-
|
|
224
|
-
|
|
470
|
+
/**
|
|
471
|
+
* Send input to a terminal (sent as-is, not encoded)
|
|
472
|
+
*/
|
|
473
|
+
sendTerminalInput(terminalId, input) {
|
|
474
|
+
this.sendRaw({
|
|
475
|
+
type: "terminal:input",
|
|
476
|
+
data: { terminal_id: terminalId, input }
|
|
477
|
+
});
|
|
225
478
|
}
|
|
226
|
-
|
|
227
|
-
|
|
479
|
+
/**
|
|
480
|
+
* Resize terminal window
|
|
481
|
+
*/
|
|
482
|
+
resizeTerminal(terminalId, cols, rows) {
|
|
483
|
+
this.sendRaw({
|
|
484
|
+
type: "terminal:resize",
|
|
485
|
+
data: { terminal_id: terminalId, cols, rows }
|
|
486
|
+
});
|
|
228
487
|
}
|
|
229
|
-
|
|
230
|
-
|
|
488
|
+
on(event, handler) {
|
|
489
|
+
if (!this.eventHandlers.has(event)) {
|
|
490
|
+
this.eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
491
|
+
}
|
|
492
|
+
this.eventHandlers.get(event).add(handler);
|
|
231
493
|
}
|
|
232
|
-
|
|
233
|
-
|
|
494
|
+
/**
|
|
495
|
+
* Unregister event handler
|
|
496
|
+
*/
|
|
497
|
+
off(event, handler) {
|
|
498
|
+
const handlers = this.eventHandlers.get(event);
|
|
499
|
+
if (handlers) {
|
|
500
|
+
handlers.delete(handler);
|
|
501
|
+
if (handlers.size === 0) {
|
|
502
|
+
this.eventHandlers.delete(event);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
234
505
|
}
|
|
235
|
-
|
|
236
|
-
|
|
506
|
+
/**
|
|
507
|
+
* Unregister all event handlers for an event
|
|
508
|
+
*/
|
|
509
|
+
offAll(event) {
|
|
510
|
+
this.eventHandlers.delete(event);
|
|
237
511
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
this.filesystem = new UnsupportedFileSystem(providerName);
|
|
512
|
+
/**
|
|
513
|
+
* Emit event to registered handlers
|
|
514
|
+
*/
|
|
515
|
+
emit(event, data) {
|
|
516
|
+
const handlers = this.eventHandlers.get(event);
|
|
517
|
+
if (handlers) {
|
|
518
|
+
handlers.forEach((handler) => {
|
|
519
|
+
try {
|
|
520
|
+
handler(data);
|
|
521
|
+
} catch (error) {
|
|
522
|
+
this.log("Error in event handler:", error);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
252
525
|
}
|
|
253
526
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
527
|
+
/**
|
|
528
|
+
* Handle incoming message
|
|
529
|
+
*/
|
|
530
|
+
handleMessage(message) {
|
|
531
|
+
this.emit(message.type, message);
|
|
532
|
+
if ("channel" in message && message.channel) {
|
|
533
|
+
this.emit(message.channel, message);
|
|
257
534
|
}
|
|
258
|
-
return this.sandbox;
|
|
259
535
|
}
|
|
260
|
-
|
|
261
|
-
|
|
536
|
+
// ============================================================================
|
|
537
|
+
// Utility Methods
|
|
538
|
+
// ============================================================================
|
|
539
|
+
/**
|
|
540
|
+
* Log debug message if debug mode is enabled
|
|
541
|
+
*/
|
|
542
|
+
log(...args) {
|
|
543
|
+
if (this.config.debug) {
|
|
544
|
+
console.log("[WebSocketManager]", ...args);
|
|
545
|
+
}
|
|
262
546
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
background: options.background
|
|
280
|
-
});
|
|
281
|
-
const [wrappedCmd, ...wrappedArgs] = wrappedCommand;
|
|
282
|
-
return await this.methods.runCommand(this.sandbox, wrappedCmd, wrappedArgs, void 0);
|
|
547
|
+
/**
|
|
548
|
+
* Get current connection state
|
|
549
|
+
*/
|
|
550
|
+
getState() {
|
|
551
|
+
if (!this.ws) return "closed";
|
|
552
|
+
switch (this.ws.readyState) {
|
|
553
|
+
case WebSocket.CONNECTING:
|
|
554
|
+
return "connecting";
|
|
555
|
+
case WebSocket.OPEN:
|
|
556
|
+
return "open";
|
|
557
|
+
case WebSocket.CLOSING:
|
|
558
|
+
return "closing";
|
|
559
|
+
case WebSocket.CLOSED:
|
|
560
|
+
return "closed";
|
|
561
|
+
default:
|
|
562
|
+
return "closed";
|
|
283
563
|
}
|
|
284
|
-
return await this.methods.runCommand(this.sandbox, command, args, options);
|
|
285
564
|
}
|
|
286
|
-
|
|
287
|
-
|
|
565
|
+
/**
|
|
566
|
+
* Get reconnection attempt count
|
|
567
|
+
*/
|
|
568
|
+
getReconnectAttempts() {
|
|
569
|
+
return this.reconnectAttempts;
|
|
288
570
|
}
|
|
289
|
-
|
|
290
|
-
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
// src/client/resources/command.ts
|
|
574
|
+
var Command = class {
|
|
575
|
+
constructor(data) {
|
|
576
|
+
this.id = data.cmdId;
|
|
577
|
+
this.terminalId = data.terminalId;
|
|
578
|
+
this.command = data.command;
|
|
579
|
+
this._status = data.status;
|
|
580
|
+
this._stdout = data.stdout;
|
|
581
|
+
this._stderr = data.stderr;
|
|
582
|
+
this._exitCode = data.exitCode;
|
|
583
|
+
this._durationMs = data.durationMs;
|
|
584
|
+
this._startedAt = data.startedAt;
|
|
585
|
+
this._finishedAt = data.finishedAt;
|
|
291
586
|
}
|
|
292
|
-
|
|
293
|
-
return this.
|
|
587
|
+
get status() {
|
|
588
|
+
return this._status;
|
|
294
589
|
}
|
|
295
|
-
|
|
296
|
-
|
|
590
|
+
get stdout() {
|
|
591
|
+
return this._stdout;
|
|
297
592
|
}
|
|
298
|
-
|
|
299
|
-
|
|
593
|
+
get stderr() {
|
|
594
|
+
return this._stderr;
|
|
300
595
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (config.mode) {
|
|
304
|
-
return config.mode;
|
|
596
|
+
get exitCode() {
|
|
597
|
+
return this._exitCode;
|
|
305
598
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
var GeneratedSandboxManager = class {
|
|
309
|
-
constructor(config, providerName, methods, providerInstance, defaultMode) {
|
|
310
|
-
this.config = config;
|
|
311
|
-
this.providerName = providerName;
|
|
312
|
-
this.methods = methods;
|
|
313
|
-
this.providerInstance = providerInstance;
|
|
314
|
-
this.effectiveMode = getEffectiveMode(config, defaultMode);
|
|
599
|
+
get durationMs() {
|
|
600
|
+
return this._durationMs;
|
|
315
601
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const result = await this.methods.create(this.config, optionsWithDefaults);
|
|
319
|
-
return new GeneratedSandbox(
|
|
320
|
-
result.sandbox,
|
|
321
|
-
result.sandboxId,
|
|
322
|
-
this.providerName,
|
|
323
|
-
this.methods,
|
|
324
|
-
this.config,
|
|
325
|
-
this.methods.destroy,
|
|
326
|
-
this.providerInstance
|
|
327
|
-
);
|
|
602
|
+
get startedAt() {
|
|
603
|
+
return this._startedAt;
|
|
328
604
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if (!result) {
|
|
332
|
-
return null;
|
|
333
|
-
}
|
|
334
|
-
return new GeneratedSandbox(
|
|
335
|
-
result.sandbox,
|
|
336
|
-
result.sandboxId,
|
|
337
|
-
this.providerName,
|
|
338
|
-
this.methods,
|
|
339
|
-
this.config,
|
|
340
|
-
this.methods.destroy,
|
|
341
|
-
this.providerInstance
|
|
342
|
-
);
|
|
605
|
+
get finishedAt() {
|
|
606
|
+
return this._finishedAt;
|
|
343
607
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
this.methods,
|
|
351
|
-
this.config,
|
|
352
|
-
this.methods.destroy,
|
|
353
|
-
this.providerInstance
|
|
354
|
-
));
|
|
355
|
-
}
|
|
356
|
-
async destroy(sandboxId) {
|
|
357
|
-
await this.methods.destroy(this.config, sandboxId);
|
|
358
|
-
}
|
|
359
|
-
async findOrCreate(options) {
|
|
360
|
-
if (!this.methods.findOrCreate) {
|
|
361
|
-
throw new Error(
|
|
362
|
-
`Provider '${this.providerName}' does not support findOrCreate.
|
|
363
|
-
This feature requires gateway provider with named sandbox support.`
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
const result = await this.methods.findOrCreate(this.config, options);
|
|
367
|
-
return new GeneratedSandbox(
|
|
368
|
-
result.sandbox,
|
|
369
|
-
result.sandboxId,
|
|
370
|
-
this.providerName,
|
|
371
|
-
this.methods,
|
|
372
|
-
this.config,
|
|
373
|
-
this.methods.destroy,
|
|
374
|
-
this.providerInstance
|
|
375
|
-
);
|
|
608
|
+
/**
|
|
609
|
+
* Set the wait handler (called by TerminalCommands)
|
|
610
|
+
* @internal
|
|
611
|
+
*/
|
|
612
|
+
setWaitHandler(handler) {
|
|
613
|
+
this.waitHandler = handler;
|
|
376
614
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
615
|
+
/**
|
|
616
|
+
* Set the retrieve handler (called by TerminalCommands)
|
|
617
|
+
* @internal
|
|
618
|
+
*/
|
|
619
|
+
setRetrieveHandler(handler) {
|
|
620
|
+
this.retrieveHandler = handler;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Wait for the command to complete
|
|
624
|
+
* @param timeout - Optional timeout in seconds (0 = no timeout)
|
|
625
|
+
* @returns This command with updated status
|
|
626
|
+
*/
|
|
627
|
+
async wait(timeout) {
|
|
628
|
+
if (!this.waitHandler) {
|
|
629
|
+
throw new Error("Wait handler not set");
|
|
383
630
|
}
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
return new GeneratedSandbox(
|
|
389
|
-
result.sandbox,
|
|
390
|
-
result.sandboxId,
|
|
391
|
-
this.providerName,
|
|
392
|
-
this.methods,
|
|
393
|
-
this.config,
|
|
394
|
-
this.methods.destroy,
|
|
395
|
-
this.providerInstance
|
|
396
|
-
);
|
|
631
|
+
const response = await this.waitHandler(timeout);
|
|
632
|
+
this.updateFromResponse(response);
|
|
633
|
+
return this;
|
|
397
634
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
635
|
+
/**
|
|
636
|
+
* Refresh the command status from the server
|
|
637
|
+
* @returns This command with updated status
|
|
638
|
+
*/
|
|
639
|
+
async refresh() {
|
|
640
|
+
if (!this.retrieveHandler) {
|
|
641
|
+
throw new Error("Retrieve handler not set");
|
|
404
642
|
}
|
|
405
|
-
await this.
|
|
643
|
+
const response = await this.retrieveHandler();
|
|
644
|
+
this.updateFromResponse(response);
|
|
645
|
+
return this;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Update internal state from API response
|
|
649
|
+
*/
|
|
650
|
+
updateFromResponse(response) {
|
|
651
|
+
this._status = response.data.status;
|
|
652
|
+
this._stdout = response.data.stdout;
|
|
653
|
+
this._stderr = response.data.stderr;
|
|
654
|
+
this._exitCode = response.data.exit_code;
|
|
655
|
+
this._durationMs = response.data.duration_ms;
|
|
656
|
+
this._finishedAt = response.data.finished_at;
|
|
406
657
|
}
|
|
407
658
|
};
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
659
|
+
|
|
660
|
+
// src/client/resources/terminal-command.ts
|
|
661
|
+
var TerminalCommand = class {
|
|
662
|
+
constructor(terminalId, handlers) {
|
|
663
|
+
this.terminalId = terminalId;
|
|
664
|
+
this.runHandler = handlers.run;
|
|
665
|
+
this.listHandler = handlers.list;
|
|
666
|
+
this.retrieveHandler = handlers.retrieve;
|
|
667
|
+
this.waitHandler = handlers.wait;
|
|
412
668
|
}
|
|
413
|
-
|
|
414
|
-
|
|
669
|
+
/**
|
|
670
|
+
* Run a command in the terminal
|
|
671
|
+
* @param command - The command to execute
|
|
672
|
+
* @param options - Execution options
|
|
673
|
+
* @param options.background - If true, returns immediately without waiting for completion
|
|
674
|
+
* @returns Command object with results or status
|
|
675
|
+
*/
|
|
676
|
+
async run(command, options) {
|
|
677
|
+
const response = await this.runHandler(command, options?.background);
|
|
678
|
+
const cmd2 = new Command({
|
|
679
|
+
cmdId: response.data.cmd_id || "",
|
|
680
|
+
terminalId: this.terminalId,
|
|
681
|
+
command: response.data.command,
|
|
682
|
+
status: response.data.status || (options?.background ? "running" : "completed"),
|
|
683
|
+
stdout: response.data.stdout,
|
|
684
|
+
stderr: response.data.stderr,
|
|
685
|
+
exitCode: response.data.exit_code,
|
|
686
|
+
durationMs: response.data.duration_ms,
|
|
687
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
688
|
+
});
|
|
689
|
+
cmd2.setWaitHandler((timeout) => this.waitHandler(cmd2.id, timeout));
|
|
690
|
+
cmd2.setRetrieveHandler(() => this.retrieveHandler(cmd2.id));
|
|
691
|
+
return cmd2;
|
|
415
692
|
}
|
|
416
|
-
|
|
417
|
-
|
|
693
|
+
/**
|
|
694
|
+
* List all commands executed in this terminal
|
|
695
|
+
* @returns Array of Command objects
|
|
696
|
+
*/
|
|
697
|
+
async list() {
|
|
698
|
+
const response = await this.listHandler();
|
|
699
|
+
return response.data.commands.map((item) => {
|
|
700
|
+
const cmd2 = new Command({
|
|
701
|
+
cmdId: item.cmd_id,
|
|
702
|
+
terminalId: this.terminalId,
|
|
703
|
+
command: item.command,
|
|
704
|
+
status: item.status,
|
|
705
|
+
stdout: "",
|
|
706
|
+
// Not included in list response
|
|
707
|
+
stderr: "",
|
|
708
|
+
// Not included in list response
|
|
709
|
+
exitCode: item.exit_code,
|
|
710
|
+
durationMs: item.duration_ms,
|
|
711
|
+
startedAt: item.started_at,
|
|
712
|
+
finishedAt: item.finished_at
|
|
713
|
+
});
|
|
714
|
+
cmd2.setWaitHandler((timeout) => this.waitHandler(cmd2.id, timeout));
|
|
715
|
+
cmd2.setRetrieveHandler(() => this.retrieveHandler(cmd2.id));
|
|
716
|
+
return cmd2;
|
|
717
|
+
});
|
|
418
718
|
}
|
|
419
|
-
|
|
420
|
-
|
|
719
|
+
/**
|
|
720
|
+
* Retrieve a specific command by ID
|
|
721
|
+
* @param cmdId - The command ID
|
|
722
|
+
* @returns Command object with full details
|
|
723
|
+
*/
|
|
724
|
+
async retrieve(cmdId) {
|
|
725
|
+
const response = await this.retrieveHandler(cmdId);
|
|
726
|
+
const cmd2 = new Command({
|
|
727
|
+
cmdId: response.data.cmd_id,
|
|
728
|
+
terminalId: this.terminalId,
|
|
729
|
+
command: response.data.command,
|
|
730
|
+
status: response.data.status,
|
|
731
|
+
stdout: response.data.stdout,
|
|
732
|
+
stderr: response.data.stderr,
|
|
733
|
+
exitCode: response.data.exit_code,
|
|
734
|
+
durationMs: response.data.duration_ms,
|
|
735
|
+
startedAt: response.data.started_at,
|
|
736
|
+
finishedAt: response.data.finished_at
|
|
737
|
+
});
|
|
738
|
+
cmd2.setWaitHandler((timeout) => this.waitHandler(cmd2.id, timeout));
|
|
739
|
+
cmd2.setRetrieveHandler(() => this.retrieveHandler(cmd2.id));
|
|
740
|
+
return cmd2;
|
|
421
741
|
}
|
|
422
742
|
};
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
743
|
+
|
|
744
|
+
// src/client/terminal.ts
|
|
745
|
+
function decodeBase64(str) {
|
|
746
|
+
if (typeof window !== "undefined" && typeof window.atob === "function") {
|
|
747
|
+
return window.atob(str);
|
|
748
|
+
} else if (typeof Buffer !== "undefined") {
|
|
749
|
+
return Buffer.from(str, "base64").toString("utf-8");
|
|
750
|
+
}
|
|
751
|
+
throw new Error("No base64 decoding available");
|
|
752
|
+
}
|
|
753
|
+
var TerminalInstance = class {
|
|
754
|
+
constructor(id, pty, status, channel, ws, encoding = "raw") {
|
|
755
|
+
this._eventHandlers = /* @__PURE__ */ new Map();
|
|
756
|
+
this._id = id;
|
|
757
|
+
this._pty = pty;
|
|
758
|
+
this._status = status === "active" ? "running" : status;
|
|
759
|
+
this._channel = channel;
|
|
760
|
+
this._ws = ws;
|
|
761
|
+
this._encoding = encoding;
|
|
762
|
+
this.command = new TerminalCommand(id, {
|
|
763
|
+
run: async (command, background) => {
|
|
764
|
+
if (!this._executeHandler) {
|
|
765
|
+
throw new Error("Execute handler not set");
|
|
766
|
+
}
|
|
767
|
+
return this._executeHandler(command, background);
|
|
768
|
+
},
|
|
769
|
+
list: async () => {
|
|
770
|
+
if (!this._listCommandsHandler) {
|
|
771
|
+
throw new Error("List commands handler not set");
|
|
772
|
+
}
|
|
773
|
+
return this._listCommandsHandler();
|
|
774
|
+
},
|
|
775
|
+
retrieve: async (cmdId) => {
|
|
776
|
+
if (!this._retrieveCommandHandler) {
|
|
777
|
+
throw new Error("Retrieve command handler not set");
|
|
778
|
+
}
|
|
779
|
+
return this._retrieveCommandHandler(cmdId);
|
|
780
|
+
},
|
|
781
|
+
wait: async (cmdId, timeout) => {
|
|
782
|
+
if (!this._waitCommandHandler) {
|
|
783
|
+
throw new Error("Wait command handler not set");
|
|
784
|
+
}
|
|
785
|
+
return this._waitCommandHandler(cmdId, timeout);
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
if (this._pty && this._ws && this._channel) {
|
|
789
|
+
this._ws.subscribe(this._channel);
|
|
790
|
+
this.setupWebSocketHandlers();
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Set up WebSocket event handlers (PTY mode only)
|
|
795
|
+
*/
|
|
796
|
+
setupWebSocketHandlers() {
|
|
797
|
+
if (!this._ws || !this._channel) {
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
this._ws.on("terminal:output", (msg) => {
|
|
801
|
+
if (msg.channel === this._channel) {
|
|
802
|
+
const encoding = msg.data.encoding || this._encoding;
|
|
803
|
+
const output = encoding === "base64" ? decodeBase64(msg.data.output) : msg.data.output;
|
|
804
|
+
this.emit("output", output);
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
this._ws.on("terminal:error", (msg) => {
|
|
808
|
+
if (msg.channel === this._channel) {
|
|
809
|
+
this.emit("error", msg.data.error);
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
this._ws.on("terminal:destroyed", (msg) => {
|
|
813
|
+
if (msg.channel === this._channel) {
|
|
814
|
+
this._status = "stopped";
|
|
815
|
+
this.emit("destroyed");
|
|
816
|
+
this.cleanup();
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Terminal ID
|
|
822
|
+
*/
|
|
823
|
+
get id() {
|
|
824
|
+
return this._id;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Get terminal ID (deprecated, use .id property)
|
|
828
|
+
* @deprecated Use .id property instead
|
|
829
|
+
*/
|
|
830
|
+
getId() {
|
|
831
|
+
return this._id;
|
|
427
832
|
}
|
|
428
|
-
|
|
429
|
-
|
|
833
|
+
/**
|
|
834
|
+
* Terminal status
|
|
835
|
+
*/
|
|
836
|
+
get status() {
|
|
837
|
+
return this._status;
|
|
430
838
|
}
|
|
431
|
-
|
|
432
|
-
|
|
839
|
+
/**
|
|
840
|
+
* Get terminal status (deprecated, use .status property)
|
|
841
|
+
* @deprecated Use .status property instead
|
|
842
|
+
*/
|
|
843
|
+
getStatus() {
|
|
844
|
+
return this._status;
|
|
433
845
|
}
|
|
434
|
-
|
|
435
|
-
|
|
846
|
+
/**
|
|
847
|
+
* Terminal channel (null for exec mode)
|
|
848
|
+
*/
|
|
849
|
+
get channel() {
|
|
850
|
+
return this._channel;
|
|
436
851
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
852
|
+
/**
|
|
853
|
+
* Get terminal channel (deprecated, use .channel property)
|
|
854
|
+
* @deprecated Use .channel property instead
|
|
855
|
+
*/
|
|
856
|
+
getChannel() {
|
|
857
|
+
return this._channel;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Whether this is a PTY terminal
|
|
861
|
+
*/
|
|
862
|
+
get pty() {
|
|
863
|
+
return this._pty;
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Get terminal PTY mode (deprecated, use .pty property)
|
|
867
|
+
* @deprecated Use .pty property instead
|
|
868
|
+
*/
|
|
869
|
+
isPTY() {
|
|
870
|
+
return this._pty;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Check if terminal is running
|
|
874
|
+
*/
|
|
875
|
+
isRunning() {
|
|
876
|
+
return this._status === "running";
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Write input to the terminal (PTY mode only)
|
|
880
|
+
*/
|
|
881
|
+
write(input) {
|
|
882
|
+
if (!this._pty) {
|
|
883
|
+
throw new Error("write() is only available for PTY terminals. Use commands.run() for exec mode terminals.");
|
|
450
884
|
}
|
|
451
|
-
if (
|
|
452
|
-
|
|
885
|
+
if (!this._ws) {
|
|
886
|
+
throw new Error("WebSocket not available");
|
|
453
887
|
}
|
|
888
|
+
if (!this.isRunning()) {
|
|
889
|
+
console.warn('[Terminal] Warning: Terminal status is not "running", but attempting to write anyway. Status:', this._status);
|
|
890
|
+
}
|
|
891
|
+
this._ws.sendTerminalInput(this._id, input);
|
|
454
892
|
}
|
|
455
|
-
|
|
456
|
-
|
|
893
|
+
/**
|
|
894
|
+
* Resize terminal window (PTY mode only)
|
|
895
|
+
*/
|
|
896
|
+
resize(cols, rows) {
|
|
897
|
+
if (!this._pty) {
|
|
898
|
+
throw new Error("resize() is only available for PTY terminals");
|
|
899
|
+
}
|
|
900
|
+
if (!this._ws) {
|
|
901
|
+
throw new Error("WebSocket not available");
|
|
902
|
+
}
|
|
903
|
+
if (!this.isRunning()) {
|
|
904
|
+
throw new Error("Terminal is not running");
|
|
905
|
+
}
|
|
906
|
+
this._ws.resizeTerminal(this._id, cols, rows);
|
|
457
907
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
await new Promise((resolve) => setTimeout(resolve, currentDelay));
|
|
501
|
-
currentDelay = Math.min(currentDelay * backoffFactor, maxDelayMs);
|
|
908
|
+
/**
|
|
909
|
+
* Set execute command handler (called by Sandbox)
|
|
910
|
+
* @internal
|
|
911
|
+
*/
|
|
912
|
+
setExecuteHandler(handler) {
|
|
913
|
+
this._executeHandler = handler;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Set list commands handler (called by Sandbox)
|
|
917
|
+
* @internal
|
|
918
|
+
*/
|
|
919
|
+
setListCommandsHandler(handler) {
|
|
920
|
+
this._listCommandsHandler = handler;
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Set retrieve command handler (called by Sandbox)
|
|
924
|
+
* @internal
|
|
925
|
+
*/
|
|
926
|
+
setRetrieveCommandHandler(handler) {
|
|
927
|
+
this._retrieveCommandHandler = handler;
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Set wait command handler (called by Sandbox)
|
|
931
|
+
* @internal
|
|
932
|
+
*/
|
|
933
|
+
setWaitCommandHandler(handler) {
|
|
934
|
+
this._waitCommandHandler = handler;
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Set destroy handler (called by Sandbox)
|
|
938
|
+
* @internal
|
|
939
|
+
*/
|
|
940
|
+
setDestroyHandler(handler) {
|
|
941
|
+
this._destroyHandler = handler;
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Execute a command in the terminal (deprecated, use command.run())
|
|
945
|
+
* @deprecated Use terminal.command.run() instead
|
|
946
|
+
*/
|
|
947
|
+
async execute(command, options) {
|
|
948
|
+
if (!this._executeHandler) {
|
|
949
|
+
throw new Error("Execute handler not set");
|
|
502
950
|
}
|
|
951
|
+
return this._executeHandler(command, options?.background);
|
|
503
952
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
var DEFAULT_GATEWAY_URL = "https://gateway.computesdk.com";
|
|
514
|
-
var DEFAULT_REQUEST_TIMEOUT = 3e4;
|
|
515
|
-
var DEFAULT_MAX_RETRIES = 3;
|
|
516
|
-
var DEFAULT_RETRY_DELAY = 1e3;
|
|
517
|
-
var DEFAULT_RETRYABLE_STATUSES = [408, 429, 502, 503, 504];
|
|
518
|
-
var hasWarnedAboutMissingToken = false;
|
|
519
|
-
var GatewayError = class extends Error {
|
|
520
|
-
constructor(message, statusCode, provider, sandboxId, requestId) {
|
|
521
|
-
super(message);
|
|
522
|
-
this.statusCode = statusCode;
|
|
523
|
-
this.provider = provider;
|
|
524
|
-
this.sandboxId = sandboxId;
|
|
525
|
-
this.requestId = requestId;
|
|
526
|
-
this.name = "GatewayError";
|
|
953
|
+
/**
|
|
954
|
+
* Destroy the terminal
|
|
955
|
+
*/
|
|
956
|
+
async destroy() {
|
|
957
|
+
if (!this._destroyHandler) {
|
|
958
|
+
throw new Error("Destroy handler not set");
|
|
959
|
+
}
|
|
960
|
+
await this._destroyHandler();
|
|
961
|
+
this.cleanup();
|
|
527
962
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
if (
|
|
554
|
-
|
|
555
|
-
}
|
|
556
|
-
if (!response.ok) {
|
|
557
|
-
if (response.status === 404) {
|
|
558
|
-
return { success: false };
|
|
559
|
-
}
|
|
560
|
-
const errorText = await response.text().catch(() => response.statusText);
|
|
561
|
-
const requestId = response.headers.get("x-request-id");
|
|
562
|
-
const isRetryable = retryableStatuses.includes(response.status);
|
|
563
|
-
const shouldRetry = isRetryable && attempt < maxRetries;
|
|
564
|
-
if (shouldRetry) {
|
|
565
|
-
const delay = calculateBackoff(attempt, retryDelay);
|
|
566
|
-
if (process.env.COMPUTESDK_DEBUG) {
|
|
567
|
-
console.log(`[Gateway] Retry ${attempt + 1}/${maxRetries} after ${delay.toFixed(0)}ms (status: ${response.status})...`);
|
|
568
|
-
}
|
|
569
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
let errorMessage = `Gateway API error: ${errorText}`;
|
|
573
|
-
if (response.status === 401) {
|
|
574
|
-
errorMessage = `Invalid ComputeSDK API key.
|
|
575
|
-
|
|
576
|
-
Check your COMPUTESDK_API_KEY environment variable.
|
|
577
|
-
Get your key at: https://computesdk.com/dashboard`;
|
|
578
|
-
} else if (response.status === 403) {
|
|
579
|
-
errorMessage = `Access forbidden. Your API key may not have permission to use provider "${config.provider}".
|
|
580
|
-
|
|
581
|
-
Visit https://computesdk.com/dashboard to check your plan.`;
|
|
582
|
-
} else if (response.status === 429) {
|
|
583
|
-
errorMessage = `Rate limit exceeded. Please try again in a moment.
|
|
584
|
-
|
|
585
|
-
If this persists, visit https://computesdk.com/dashboard to upgrade your plan.`;
|
|
586
|
-
} else if (response.status >= 500) {
|
|
587
|
-
errorMessage = `Gateway server error (${response.status}). This is temporary - please try again.
|
|
588
|
-
|
|
589
|
-
If this persists, check status at https://status.computesdk.com`;
|
|
590
|
-
}
|
|
591
|
-
throw new GatewayError(
|
|
592
|
-
errorMessage,
|
|
593
|
-
response.status,
|
|
594
|
-
config.provider,
|
|
595
|
-
void 0,
|
|
596
|
-
requestId || void 0
|
|
597
|
-
);
|
|
598
|
-
}
|
|
599
|
-
return response.json();
|
|
600
|
-
} catch (error) {
|
|
601
|
-
clearTimeout(timeoutId);
|
|
602
|
-
if (error instanceof GatewayError) {
|
|
603
|
-
lastError = error;
|
|
604
|
-
throw error;
|
|
605
|
-
}
|
|
606
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
607
|
-
const timeoutError = new GatewayError(
|
|
608
|
-
`Request timed out after ${timeout}ms.
|
|
609
|
-
|
|
610
|
-
The gateway may be experiencing high load or network issues.
|
|
611
|
-
Check your connection and try again.`,
|
|
612
|
-
408,
|
|
613
|
-
config.provider
|
|
614
|
-
);
|
|
615
|
-
if (attempt < maxRetries) {
|
|
616
|
-
const delay = calculateBackoff(attempt, retryDelay);
|
|
617
|
-
if (process.env.COMPUTESDK_DEBUG) {
|
|
618
|
-
console.log(`[Gateway] Retry ${attempt + 1}/${maxRetries} after ${delay.toFixed(0)}ms (timeout)...`);
|
|
619
|
-
}
|
|
620
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
621
|
-
lastError = timeoutError;
|
|
622
|
-
continue;
|
|
623
|
-
}
|
|
624
|
-
throw timeoutError;
|
|
963
|
+
/**
|
|
964
|
+
* Clean up resources
|
|
965
|
+
*/
|
|
966
|
+
cleanup() {
|
|
967
|
+
if (this._ws && this._channel) {
|
|
968
|
+
this._ws.unsubscribe(this._channel);
|
|
969
|
+
}
|
|
970
|
+
this._eventHandlers.clear();
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Register event handler
|
|
974
|
+
*/
|
|
975
|
+
on(event, handler) {
|
|
976
|
+
if (!this._eventHandlers.has(event)) {
|
|
977
|
+
this._eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
978
|
+
}
|
|
979
|
+
this._eventHandlers.get(event).add(handler);
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Unregister event handler
|
|
983
|
+
*/
|
|
984
|
+
off(event, handler) {
|
|
985
|
+
const handlers = this._eventHandlers.get(event);
|
|
986
|
+
if (handlers) {
|
|
987
|
+
handlers.delete(handler);
|
|
988
|
+
if (handlers.size === 0) {
|
|
989
|
+
this._eventHandlers.delete(event);
|
|
625
990
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Emit event to registered handlers
|
|
995
|
+
*/
|
|
996
|
+
emit(event, ...args) {
|
|
997
|
+
const handlers = this._eventHandlers.get(event);
|
|
998
|
+
if (handlers) {
|
|
999
|
+
handlers.forEach((handler) => {
|
|
1000
|
+
try {
|
|
1001
|
+
handler(...args);
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
console.error("Error in terminal event handler:", error);
|
|
637
1004
|
}
|
|
638
|
-
|
|
639
|
-
lastError = networkError;
|
|
640
|
-
continue;
|
|
641
|
-
}
|
|
642
|
-
throw networkError;
|
|
1005
|
+
});
|
|
643
1006
|
}
|
|
644
1007
|
}
|
|
645
|
-
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
// src/client/file-watcher.ts
|
|
1011
|
+
function decodeBase642(str) {
|
|
1012
|
+
if (typeof window !== "undefined" && typeof window.atob === "function") {
|
|
1013
|
+
return window.atob(str);
|
|
1014
|
+
} else if (typeof Buffer !== "undefined") {
|
|
1015
|
+
return Buffer.from(str, "base64").toString("utf-8");
|
|
1016
|
+
}
|
|
1017
|
+
throw new Error("No base64 decoding available");
|
|
646
1018
|
}
|
|
647
|
-
var
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
`[Gateway] No token received from gateway for sandbox ${sandboxId}. Falling back to API key for authentication. This may indicate the gateway is running an outdated version. Check gateway deployment at ${config.gatewayUrl || DEFAULT_GATEWAY_URL}`
|
|
674
|
-
);
|
|
675
|
-
}
|
|
676
|
-
const sandbox = new ClientSandbox({
|
|
677
|
-
sandboxUrl: url,
|
|
678
|
-
sandboxId,
|
|
679
|
-
provider,
|
|
680
|
-
token: token || config.apiKey,
|
|
681
|
-
// Use token from gateway, fallback to API key
|
|
682
|
-
metadata: {
|
|
683
|
-
...metadata,
|
|
684
|
-
...name && { name },
|
|
685
|
-
...namespace && { namespace }
|
|
686
|
-
},
|
|
687
|
-
WebSocket: globalThis.WebSocket
|
|
1019
|
+
var FileWatcher = class {
|
|
1020
|
+
constructor(id, path, status, channel, includeContent, ignored, ws, encoding = "raw") {
|
|
1021
|
+
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
1022
|
+
this.id = id;
|
|
1023
|
+
this.path = path;
|
|
1024
|
+
this.status = status;
|
|
1025
|
+
this.channel = channel;
|
|
1026
|
+
this.includeContent = includeContent;
|
|
1027
|
+
this.ignored = ignored;
|
|
1028
|
+
this.encoding = encoding;
|
|
1029
|
+
this.ws = ws;
|
|
1030
|
+
this.ws.subscribe(this.channel);
|
|
1031
|
+
this.setupWebSocketHandlers();
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Set up WebSocket event handlers
|
|
1035
|
+
*/
|
|
1036
|
+
setupWebSocketHandlers() {
|
|
1037
|
+
this.ws.on("file:changed", (msg) => {
|
|
1038
|
+
if (msg.channel === this.channel) {
|
|
1039
|
+
const encoding = msg.data.encoding || this.encoding;
|
|
1040
|
+
const content = msg.data.content && encoding === "base64" ? decodeBase642(msg.data.content) : msg.data.content;
|
|
1041
|
+
this.emit("change", {
|
|
1042
|
+
event: msg.data.event,
|
|
1043
|
+
path: msg.data.path,
|
|
1044
|
+
content
|
|
688
1045
|
});
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
this.ws.on("watcher:destroyed", (msg) => {
|
|
1049
|
+
if (msg.channel === this.channel) {
|
|
1050
|
+
this.status = "stopped";
|
|
1051
|
+
this.emit("destroyed");
|
|
1052
|
+
this.cleanup();
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Get watcher ID
|
|
1058
|
+
*/
|
|
1059
|
+
getId() {
|
|
1060
|
+
return this.id;
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Get watched path
|
|
1064
|
+
*/
|
|
1065
|
+
getPath() {
|
|
1066
|
+
return this.path;
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Get watcher status
|
|
1070
|
+
*/
|
|
1071
|
+
getStatus() {
|
|
1072
|
+
return this.status;
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Get watcher channel
|
|
1076
|
+
*/
|
|
1077
|
+
getChannel() {
|
|
1078
|
+
return this.channel;
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Check if content is included in events
|
|
1082
|
+
*/
|
|
1083
|
+
isIncludingContent() {
|
|
1084
|
+
return this.includeContent;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Get ignored patterns
|
|
1088
|
+
*/
|
|
1089
|
+
getIgnoredPatterns() {
|
|
1090
|
+
return [...this.ignored];
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Check if watcher is active
|
|
1094
|
+
*/
|
|
1095
|
+
isActive() {
|
|
1096
|
+
return this.status === "active";
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Set destroy handler (called by client)
|
|
1100
|
+
*/
|
|
1101
|
+
setDestroyHandler(handler) {
|
|
1102
|
+
this.destroyWatcher = handler;
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Destroy the watcher
|
|
1106
|
+
*/
|
|
1107
|
+
async destroy() {
|
|
1108
|
+
if (!this.destroyWatcher) {
|
|
1109
|
+
throw new Error("Destroy handler not set");
|
|
1110
|
+
}
|
|
1111
|
+
await this.destroyWatcher();
|
|
1112
|
+
this.cleanup();
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Clean up resources
|
|
1116
|
+
*/
|
|
1117
|
+
cleanup() {
|
|
1118
|
+
this.ws.unsubscribe(this.channel);
|
|
1119
|
+
this.eventHandlers.clear();
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Register event handler
|
|
1123
|
+
*/
|
|
1124
|
+
on(event, handler) {
|
|
1125
|
+
if (!this.eventHandlers.has(event)) {
|
|
1126
|
+
this.eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
1127
|
+
}
|
|
1128
|
+
this.eventHandlers.get(event).add(handler);
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Unregister event handler
|
|
1132
|
+
*/
|
|
1133
|
+
off(event, handler) {
|
|
1134
|
+
const handlers = this.eventHandlers.get(event);
|
|
1135
|
+
if (handlers) {
|
|
1136
|
+
handlers.delete(handler);
|
|
1137
|
+
if (handlers.size === 0) {
|
|
1138
|
+
this.eventHandlers.delete(event);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Emit event to registered handlers
|
|
1144
|
+
*/
|
|
1145
|
+
emit(event, ...args) {
|
|
1146
|
+
const handlers = this.eventHandlers.get(event);
|
|
1147
|
+
if (handlers) {
|
|
1148
|
+
handlers.forEach((handler) => {
|
|
1149
|
+
try {
|
|
1150
|
+
handler(...args);
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
console.error("Error in file watcher event handler:", error);
|
|
779
1153
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
|
|
1159
|
+
// src/client/signal-service.ts
|
|
1160
|
+
var SignalService = class {
|
|
1161
|
+
constructor(status, channel, ws) {
|
|
1162
|
+
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
1163
|
+
this.status = status;
|
|
1164
|
+
this.channel = channel;
|
|
1165
|
+
this.ws = ws;
|
|
1166
|
+
this.ws.subscribe(this.channel);
|
|
1167
|
+
this.setupWebSocketHandlers();
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Set up WebSocket event handlers
|
|
1171
|
+
*/
|
|
1172
|
+
setupWebSocketHandlers() {
|
|
1173
|
+
this.ws.on("signal", (msg) => {
|
|
1174
|
+
if (msg.channel === this.channel) {
|
|
1175
|
+
const event = {
|
|
1176
|
+
signal: msg.data.signal,
|
|
1177
|
+
...msg.data.port && { port: msg.data.port },
|
|
1178
|
+
...msg.data.url && { url: msg.data.url },
|
|
1179
|
+
...msg.data.message && { message: msg.data.message }
|
|
1180
|
+
};
|
|
1181
|
+
if (msg.data.signal === "port" || msg.data.signal === "server-ready") {
|
|
1182
|
+
this.emit("port", event);
|
|
1183
|
+
} else if (msg.data.signal === "error") {
|
|
1184
|
+
this.emit("error", event);
|
|
788
1185
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1186
|
+
this.emit("signal", event);
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Get service status
|
|
1192
|
+
*/
|
|
1193
|
+
getStatus() {
|
|
1194
|
+
return this.status;
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Get service channel
|
|
1198
|
+
*/
|
|
1199
|
+
getChannel() {
|
|
1200
|
+
return this.channel;
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Check if service is active
|
|
1204
|
+
*/
|
|
1205
|
+
isActive() {
|
|
1206
|
+
return this.status === "active";
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Set stop handler (called by client)
|
|
1210
|
+
*/
|
|
1211
|
+
setStopHandler(handler) {
|
|
1212
|
+
this.stopService = handler;
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Stop the signal service
|
|
1216
|
+
*/
|
|
1217
|
+
async stop() {
|
|
1218
|
+
if (!this.stopService) {
|
|
1219
|
+
throw new Error("Stop handler not set");
|
|
1220
|
+
}
|
|
1221
|
+
await this.stopService();
|
|
1222
|
+
this.cleanup();
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Clean up resources
|
|
1226
|
+
*/
|
|
1227
|
+
cleanup() {
|
|
1228
|
+
this.status = "stopped";
|
|
1229
|
+
this.ws.unsubscribe(this.channel);
|
|
1230
|
+
this.eventHandlers.clear();
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Register event handler
|
|
1234
|
+
*/
|
|
1235
|
+
on(event, handler) {
|
|
1236
|
+
if (!this.eventHandlers.has(event)) {
|
|
1237
|
+
this.eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
1238
|
+
}
|
|
1239
|
+
this.eventHandlers.get(event).add(handler);
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Unregister event handler
|
|
1243
|
+
*/
|
|
1244
|
+
off(event, handler) {
|
|
1245
|
+
const handlers = this.eventHandlers.get(event);
|
|
1246
|
+
if (handlers) {
|
|
1247
|
+
handlers.delete(handler);
|
|
1248
|
+
if (handlers.size === 0) {
|
|
1249
|
+
this.eventHandlers.delete(event);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Emit event to registered handlers
|
|
1255
|
+
*/
|
|
1256
|
+
emit(event, ...args) {
|
|
1257
|
+
const handlers = this.eventHandlers.get(event);
|
|
1258
|
+
if (handlers) {
|
|
1259
|
+
handlers.forEach((handler) => {
|
|
1260
|
+
try {
|
|
1261
|
+
handler(...args);
|
|
1262
|
+
} catch (error) {
|
|
1263
|
+
console.error("Error in signal service event handler:", error);
|
|
809
1264
|
}
|
|
810
|
-
|
|
811
|
-
method: "POST",
|
|
812
|
-
body: JSON.stringify({ duration })
|
|
813
|
-
});
|
|
814
|
-
},
|
|
815
|
-
// All operations delegate directly to ClientSandbox
|
|
816
|
-
runCode: async (sandbox, code, runtime) => sandbox.runCode(code, runtime),
|
|
817
|
-
runCommand: async (sandbox, command, args) => sandbox.runCommand(command, args),
|
|
818
|
-
getUrl: async (sandbox, options) => sandbox.getUrl(options),
|
|
819
|
-
getInfo: async (sandbox) => {
|
|
820
|
-
const info = await sandbox.getInfo();
|
|
821
|
-
return {
|
|
822
|
-
id: info.id,
|
|
823
|
-
provider: info.provider,
|
|
824
|
-
runtime: info.runtime,
|
|
825
|
-
status: info.status,
|
|
826
|
-
createdAt: info.createdAt,
|
|
827
|
-
timeout: info.timeout,
|
|
828
|
-
metadata: info.metadata || {}
|
|
829
|
-
};
|
|
830
|
-
},
|
|
831
|
-
// Filesystem delegates directly to ClientSandbox
|
|
832
|
-
filesystem: {
|
|
833
|
-
readFile: async (sandbox, path) => sandbox.filesystem.readFile(path),
|
|
834
|
-
writeFile: async (sandbox, path, content) => sandbox.filesystem.writeFile(path, content),
|
|
835
|
-
mkdir: async (sandbox, path) => sandbox.filesystem.mkdir(path),
|
|
836
|
-
readdir: async (sandbox, path) => sandbox.filesystem.readdir(path),
|
|
837
|
-
exists: async (sandbox, path) => sandbox.filesystem.exists(path),
|
|
838
|
-
remove: async (sandbox, path) => sandbox.filesystem.remove(path)
|
|
839
|
-
},
|
|
840
|
-
// getInstance returns the ClientSandbox directly
|
|
841
|
-
getInstance: (sandbox) => sandbox
|
|
1265
|
+
});
|
|
842
1266
|
}
|
|
843
1267
|
}
|
|
844
|
-
}
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
// src/client/index.ts
|
|
1271
|
+
import { cmd, escapeArgs, mkdir, test } from "@computesdk/cmd";
|
|
1272
|
+
|
|
1273
|
+
// src/client/resources/terminal.ts
|
|
1274
|
+
var Terminal = class {
|
|
1275
|
+
constructor(handlers) {
|
|
1276
|
+
this.createHandler = handlers.create;
|
|
1277
|
+
this.listHandler = handlers.list;
|
|
1278
|
+
this.retrieveHandler = handlers.retrieve;
|
|
1279
|
+
this.destroyHandler = handlers.destroy;
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Create a new terminal session
|
|
1283
|
+
*
|
|
1284
|
+
* @param options - Terminal creation options
|
|
1285
|
+
* @param options.shell - Shell to use (e.g., '/bin/bash') - PTY mode only
|
|
1286
|
+
* @param options.encoding - Encoding: 'raw' (default) or 'base64' (binary-safe)
|
|
1287
|
+
* @param options.pty - Terminal mode: true = PTY (interactive), false = exec (command tracking)
|
|
1288
|
+
* @returns TerminalInstance
|
|
1289
|
+
*/
|
|
1290
|
+
async create(options) {
|
|
1291
|
+
return this.createHandler(options);
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* List all active terminals
|
|
1295
|
+
* @returns Array of terminal responses
|
|
1296
|
+
*/
|
|
1297
|
+
async list() {
|
|
1298
|
+
return this.listHandler();
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Retrieve a specific terminal by ID
|
|
1302
|
+
* @param id - The terminal ID
|
|
1303
|
+
* @returns Terminal response
|
|
1304
|
+
*/
|
|
1305
|
+
async retrieve(id) {
|
|
1306
|
+
return this.retrieveHandler(id);
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Destroy a terminal by ID
|
|
1310
|
+
* @param id - The terminal ID
|
|
1311
|
+
*/
|
|
1312
|
+
async destroy(id) {
|
|
1313
|
+
return this.destroyHandler(id);
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
// src/client/resources/server.ts
|
|
1318
|
+
var Server = class {
|
|
1319
|
+
constructor(handlers) {
|
|
1320
|
+
this.startHandler = handlers.start;
|
|
1321
|
+
this.listHandler = handlers.list;
|
|
1322
|
+
this.retrieveHandler = handlers.retrieve;
|
|
1323
|
+
this.stopHandler = handlers.stop;
|
|
1324
|
+
this.restartHandler = handlers.restart;
|
|
1325
|
+
this.updateStatusHandler = handlers.updateStatus;
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Start a new managed server
|
|
1329
|
+
* @param options - Server configuration
|
|
1330
|
+
* @param options.slug - Unique server slug (URL-safe identifier)
|
|
1331
|
+
* @param options.command - Command to start the server
|
|
1332
|
+
* @param options.path - Working directory (optional)
|
|
1333
|
+
* @param options.env_file - Path to env file (optional)
|
|
1334
|
+
* @returns Server info
|
|
1335
|
+
*/
|
|
1336
|
+
async start(options) {
|
|
1337
|
+
const response = await this.startHandler(options);
|
|
1338
|
+
return response.data.server;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* List all managed servers
|
|
1342
|
+
* @returns Array of server info
|
|
1343
|
+
*/
|
|
1344
|
+
async list() {
|
|
1345
|
+
const response = await this.listHandler();
|
|
1346
|
+
return response.data.servers;
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Retrieve a specific server by slug
|
|
1350
|
+
* @param slug - The server slug
|
|
1351
|
+
* @returns Server info
|
|
1352
|
+
*/
|
|
1353
|
+
async retrieve(slug) {
|
|
1354
|
+
const response = await this.retrieveHandler(slug);
|
|
1355
|
+
return response.data.server;
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Stop a server by slug
|
|
1359
|
+
* @param slug - The server slug
|
|
1360
|
+
*/
|
|
1361
|
+
async stop(slug) {
|
|
1362
|
+
await this.stopHandler(slug);
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Restart a server by slug
|
|
1366
|
+
* @param slug - The server slug
|
|
1367
|
+
* @returns Server info
|
|
1368
|
+
*/
|
|
1369
|
+
async restart(slug) {
|
|
1370
|
+
const response = await this.restartHandler(slug);
|
|
1371
|
+
return response.data.server;
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Update server status (internal use)
|
|
1375
|
+
* @param slug - The server slug
|
|
1376
|
+
* @param status - New status
|
|
1377
|
+
*/
|
|
1378
|
+
async updateStatus(slug, status) {
|
|
1379
|
+
await this.updateStatusHandler(slug, status);
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
// src/client/resources/watcher.ts
|
|
1384
|
+
var Watcher = class {
|
|
1385
|
+
constructor(handlers) {
|
|
1386
|
+
this.createHandler = handlers.create;
|
|
1387
|
+
this.listHandler = handlers.list;
|
|
1388
|
+
this.retrieveHandler = handlers.retrieve;
|
|
1389
|
+
this.destroyHandler = handlers.destroy;
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Create a new file watcher
|
|
1393
|
+
* @param path - Path to watch
|
|
1394
|
+
* @param options - Watcher options
|
|
1395
|
+
* @param options.includeContent - Include file content in change events
|
|
1396
|
+
* @param options.ignored - Patterns to ignore
|
|
1397
|
+
* @param options.encoding - Encoding: 'raw' (default) or 'base64' (binary-safe)
|
|
1398
|
+
* @returns FileWatcher instance
|
|
1399
|
+
*/
|
|
1400
|
+
async create(path, options) {
|
|
1401
|
+
return this.createHandler(path, options);
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* List all active file watchers
|
|
1405
|
+
* @returns Array of watcher info
|
|
1406
|
+
*/
|
|
1407
|
+
async list() {
|
|
1408
|
+
const response = await this.listHandler();
|
|
1409
|
+
return response.data.watchers;
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Retrieve a specific watcher by ID
|
|
1413
|
+
* @param id - The watcher ID
|
|
1414
|
+
* @returns Watcher info
|
|
1415
|
+
*/
|
|
1416
|
+
async retrieve(id) {
|
|
1417
|
+
const response = await this.retrieveHandler(id);
|
|
1418
|
+
return response.data;
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Destroy a watcher by ID
|
|
1422
|
+
* @param id - The watcher ID
|
|
1423
|
+
*/
|
|
1424
|
+
async destroy(id) {
|
|
1425
|
+
return this.destroyHandler(id);
|
|
1426
|
+
}
|
|
1427
|
+
};
|
|
1428
|
+
|
|
1429
|
+
// src/client/resources/session-token.ts
|
|
1430
|
+
var SessionToken = class {
|
|
1431
|
+
constructor(handlers) {
|
|
1432
|
+
this.createHandler = handlers.create;
|
|
1433
|
+
this.listHandler = handlers.list;
|
|
1434
|
+
this.retrieveHandler = handlers.retrieve;
|
|
1435
|
+
this.revokeHandler = handlers.revoke;
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Create a new session token (requires access token)
|
|
1439
|
+
* @param options - Token configuration
|
|
1440
|
+
* @param options.description - Description for the token
|
|
1441
|
+
* @param options.expiresIn - Expiration time in seconds (default: 7 days)
|
|
1442
|
+
* @returns Session token info including the token value
|
|
1443
|
+
*/
|
|
1444
|
+
async create(options) {
|
|
1445
|
+
const response = await this.createHandler(options);
|
|
1446
|
+
return {
|
|
1447
|
+
id: response.id,
|
|
1448
|
+
token: response.token,
|
|
1449
|
+
description: response.description,
|
|
1450
|
+
createdAt: response.createdAt,
|
|
1451
|
+
expiresAt: response.expiresAt
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* List all session tokens
|
|
1456
|
+
* @returns Array of session token info
|
|
1457
|
+
*/
|
|
1458
|
+
async list() {
|
|
1459
|
+
const response = await this.listHandler();
|
|
1460
|
+
return response.data.tokens.map((t) => ({
|
|
1461
|
+
id: t.id,
|
|
1462
|
+
description: t.description,
|
|
1463
|
+
createdAt: t.created_at,
|
|
1464
|
+
expiresAt: t.expires_at,
|
|
1465
|
+
lastUsedAt: t.last_used_at
|
|
1466
|
+
}));
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Retrieve a specific session token by ID
|
|
1470
|
+
* @param id - The token ID
|
|
1471
|
+
* @returns Session token info
|
|
1472
|
+
*/
|
|
1473
|
+
async retrieve(id) {
|
|
1474
|
+
const response = await this.retrieveHandler(id);
|
|
1475
|
+
return {
|
|
1476
|
+
id: response.id,
|
|
1477
|
+
description: response.description,
|
|
1478
|
+
createdAt: response.createdAt,
|
|
1479
|
+
expiresAt: response.expiresAt
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Revoke a session token
|
|
1484
|
+
* @param id - The token ID to revoke
|
|
1485
|
+
*/
|
|
1486
|
+
async revoke(id) {
|
|
1487
|
+
return this.revokeHandler(id);
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
|
|
1491
|
+
// src/client/resources/magic-link.ts
|
|
1492
|
+
var MagicLink = class {
|
|
1493
|
+
constructor(handlers) {
|
|
1494
|
+
this.createHandler = handlers.create;
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Create a magic link for browser authentication (requires access token)
|
|
1498
|
+
*
|
|
1499
|
+
* Magic links are one-time URLs that automatically create a session token
|
|
1500
|
+
* and set it as a cookie in the user's browser.
|
|
1501
|
+
*
|
|
1502
|
+
* @param options - Magic link configuration
|
|
1503
|
+
* @param options.redirectUrl - URL to redirect to after authentication
|
|
1504
|
+
* @returns Magic link info including the URL
|
|
1505
|
+
*/
|
|
1506
|
+
async create(options) {
|
|
1507
|
+
const response = await this.createHandler(options);
|
|
1508
|
+
return {
|
|
1509
|
+
url: response.data.magic_url,
|
|
1510
|
+
expiresAt: response.data.expires_at,
|
|
1511
|
+
redirectUrl: response.data.redirect_url
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
|
|
1516
|
+
// src/client/resources/signal.ts
|
|
1517
|
+
var Signal = class {
|
|
1518
|
+
constructor(handlers) {
|
|
1519
|
+
this.startHandler = handlers.start;
|
|
1520
|
+
this.statusHandler = handlers.status;
|
|
1521
|
+
this.stopHandler = handlers.stop;
|
|
1522
|
+
this.emitPortHandler = handlers.emitPort;
|
|
1523
|
+
this.emitErrorHandler = handlers.emitError;
|
|
1524
|
+
this.emitServerReadyHandler = handlers.emitServerReady;
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Start the signal service
|
|
1528
|
+
* @returns SignalService instance with event handling
|
|
1529
|
+
*/
|
|
1530
|
+
async start() {
|
|
1531
|
+
return this.startHandler();
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Get the signal service status
|
|
1535
|
+
* @returns Signal service status info
|
|
1536
|
+
*/
|
|
1537
|
+
async status() {
|
|
1538
|
+
const response = await this.statusHandler();
|
|
1539
|
+
return {
|
|
1540
|
+
status: response.data.status,
|
|
1541
|
+
channel: response.data.channel,
|
|
1542
|
+
wsUrl: response.data.ws_url
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Stop the signal service
|
|
1547
|
+
*/
|
|
1548
|
+
async stop() {
|
|
1549
|
+
return this.stopHandler();
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Emit a port signal
|
|
1553
|
+
* @param port - Port number
|
|
1554
|
+
* @param type - Signal type ('open' or 'close')
|
|
1555
|
+
* @param url - URL associated with the port
|
|
1556
|
+
*/
|
|
1557
|
+
async emitPort(port, type, url) {
|
|
1558
|
+
await this.emitPortHandler(port, type, url);
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Emit an error signal
|
|
1562
|
+
* @param message - Error message
|
|
1563
|
+
*/
|
|
1564
|
+
async emitError(message) {
|
|
1565
|
+
await this.emitErrorHandler(message);
|
|
1566
|
+
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Emit a server ready signal
|
|
1569
|
+
* @param port - Port number
|
|
1570
|
+
* @param url - Server URL
|
|
1571
|
+
*/
|
|
1572
|
+
async emitServerReady(port, url) {
|
|
1573
|
+
await this.emitServerReadyHandler(port, url);
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
|
|
1577
|
+
// src/client/resources/file.ts
|
|
1578
|
+
var File = class {
|
|
1579
|
+
constructor(handlers) {
|
|
1580
|
+
this.createHandler = handlers.create;
|
|
1581
|
+
this.listHandler = handlers.list;
|
|
1582
|
+
this.retrieveHandler = handlers.retrieve;
|
|
1583
|
+
this.destroyHandler = handlers.destroy;
|
|
1584
|
+
this.batchWriteHandler = handlers.batchWrite;
|
|
1585
|
+
this.existsHandler = handlers.exists;
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Create a new file with optional content
|
|
1589
|
+
* @param path - File path
|
|
1590
|
+
* @param content - File content (optional)
|
|
1591
|
+
* @returns File info
|
|
1592
|
+
*/
|
|
1593
|
+
async create(path, content) {
|
|
1594
|
+
const response = await this.createHandler(path, content);
|
|
1595
|
+
return response.data.file;
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* List files at the specified path
|
|
1599
|
+
* @param path - Directory path (default: '/')
|
|
1600
|
+
* @returns Array of file info
|
|
1601
|
+
*/
|
|
1602
|
+
async list(path = "/") {
|
|
1603
|
+
const response = await this.listHandler(path);
|
|
1604
|
+
return response.data.files;
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Retrieve file content
|
|
1608
|
+
* @param path - File path
|
|
1609
|
+
* @returns File content as string
|
|
1610
|
+
*/
|
|
1611
|
+
async retrieve(path) {
|
|
1612
|
+
return this.retrieveHandler(path);
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Destroy (delete) a file or directory
|
|
1616
|
+
* @param path - File or directory path
|
|
1617
|
+
*/
|
|
1618
|
+
async destroy(path) {
|
|
1619
|
+
return this.destroyHandler(path);
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Batch file operations (write or delete multiple files)
|
|
1623
|
+
*
|
|
1624
|
+
* Features:
|
|
1625
|
+
* - Deduplication: Last operation wins per path
|
|
1626
|
+
* - File locking: Prevents race conditions
|
|
1627
|
+
* - Deterministic ordering: Alphabetical path sorting
|
|
1628
|
+
* - Partial failure handling: Returns per-file results
|
|
1629
|
+
*
|
|
1630
|
+
* @param files - Array of file operations
|
|
1631
|
+
* @returns Results for each file operation
|
|
1632
|
+
*/
|
|
1633
|
+
async batchWrite(files) {
|
|
1634
|
+
const response = await this.batchWriteHandler(files);
|
|
1635
|
+
return response.data.results;
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Check if a file exists
|
|
1639
|
+
* @param path - File path
|
|
1640
|
+
* @returns True if file exists
|
|
1641
|
+
*/
|
|
1642
|
+
async exists(path) {
|
|
1643
|
+
return this.existsHandler(path);
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
|
|
1647
|
+
// src/client/resources/env.ts
|
|
1648
|
+
var Env = class {
|
|
1649
|
+
constructor(handlers) {
|
|
1650
|
+
this.retrieveHandler = handlers.retrieve;
|
|
1651
|
+
this.updateHandler = handlers.update;
|
|
1652
|
+
this.removeHandler = handlers.remove;
|
|
1653
|
+
this.existsHandler = handlers.exists;
|
|
1654
|
+
}
|
|
1655
|
+
/**
|
|
1656
|
+
* Retrieve environment variables from a file
|
|
1657
|
+
* @param file - Path to the .env file (relative to sandbox root)
|
|
1658
|
+
* @returns Key-value map of environment variables
|
|
1659
|
+
*/
|
|
1660
|
+
async retrieve(file) {
|
|
1661
|
+
const response = await this.retrieveHandler(file);
|
|
1662
|
+
return response.data.variables;
|
|
1663
|
+
}
|
|
1664
|
+
/**
|
|
1665
|
+
* Update (merge) environment variables in a file
|
|
1666
|
+
* @param file - Path to the .env file (relative to sandbox root)
|
|
1667
|
+
* @param variables - Key-value pairs to set
|
|
1668
|
+
* @returns Keys that were updated
|
|
1669
|
+
*/
|
|
1670
|
+
async update(file, variables) {
|
|
1671
|
+
const response = await this.updateHandler(file, variables);
|
|
1672
|
+
return response.data.keys;
|
|
1673
|
+
}
|
|
1674
|
+
/**
|
|
1675
|
+
* Remove environment variables from a file
|
|
1676
|
+
* @param file - Path to the .env file (relative to sandbox root)
|
|
1677
|
+
* @param keys - Keys to remove
|
|
1678
|
+
* @returns Keys that were removed
|
|
1679
|
+
*/
|
|
1680
|
+
async remove(file, keys) {
|
|
1681
|
+
const response = await this.removeHandler(file, keys);
|
|
1682
|
+
return response.data.keys;
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* Check if an environment file exists
|
|
1686
|
+
* @param file - Path to the .env file (relative to sandbox root)
|
|
1687
|
+
* @returns True if file exists
|
|
1688
|
+
*/
|
|
1689
|
+
async exists(file) {
|
|
1690
|
+
return this.existsHandler(file);
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
|
|
1694
|
+
// src/client/resources/auth.ts
|
|
1695
|
+
var Auth = class {
|
|
1696
|
+
constructor(handlers) {
|
|
1697
|
+
this.statusHandler = handlers.status;
|
|
1698
|
+
this.infoHandler = handlers.info;
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* Check authentication status
|
|
1702
|
+
* @returns Authentication status info
|
|
1703
|
+
*/
|
|
1704
|
+
async status() {
|
|
1705
|
+
const response = await this.statusHandler();
|
|
1706
|
+
return {
|
|
1707
|
+
authenticated: response.data.authenticated,
|
|
1708
|
+
tokenType: response.data.token_type,
|
|
1709
|
+
expiresAt: response.data.expires_at
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* Get authentication information and usage instructions
|
|
1714
|
+
* @returns Authentication info
|
|
1715
|
+
*/
|
|
1716
|
+
async info() {
|
|
1717
|
+
const response = await this.infoHandler();
|
|
1718
|
+
return {
|
|
1719
|
+
message: response.data.message,
|
|
1720
|
+
instructions: response.data.instructions,
|
|
1721
|
+
endpoints: {
|
|
1722
|
+
createSessionToken: response.data.endpoints.create_session_token,
|
|
1723
|
+
listSessionTokens: response.data.endpoints.list_session_tokens,
|
|
1724
|
+
getSessionToken: response.data.endpoints.get_session_token,
|
|
1725
|
+
revokeSessionToken: response.data.endpoints.revoke_session_token,
|
|
1726
|
+
createMagicLink: response.data.endpoints.create_magic_link,
|
|
1727
|
+
authStatus: response.data.endpoints.auth_status,
|
|
1728
|
+
authInfo: response.data.endpoints.auth_info
|
|
1729
|
+
}
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1733
|
+
|
|
1734
|
+
// src/client/resources/run.ts
|
|
1735
|
+
var Run = class {
|
|
1736
|
+
constructor(handlers) {
|
|
1737
|
+
this.codeHandler = handlers.code;
|
|
1738
|
+
this.commandHandler = handlers.command;
|
|
1739
|
+
}
|
|
1740
|
+
/**
|
|
1741
|
+
* Execute code with automatic language detection
|
|
1742
|
+
*
|
|
1743
|
+
* Supports: python, python3, node, javascript, js, bash, sh, ruby
|
|
1744
|
+
*
|
|
1745
|
+
* @param code - The code to execute
|
|
1746
|
+
* @param options - Execution options
|
|
1747
|
+
* @param options.language - Programming language (auto-detected if not specified)
|
|
1748
|
+
* @returns Code execution result with output, exit code, and detected language
|
|
1749
|
+
*/
|
|
1750
|
+
async code(code, options) {
|
|
1751
|
+
return this.codeHandler(code, options);
|
|
1752
|
+
}
|
|
1753
|
+
/**
|
|
1754
|
+
* Execute a shell command
|
|
1755
|
+
*
|
|
1756
|
+
* @param command - The command to execute
|
|
1757
|
+
* @param options - Execution options
|
|
1758
|
+
* @param options.shell - Shell to use (optional)
|
|
1759
|
+
* @param options.background - Run in background (optional)
|
|
1760
|
+
* @returns Command execution result with stdout, stderr, exit code, and duration
|
|
1761
|
+
*/
|
|
1762
|
+
async command(command, options) {
|
|
1763
|
+
return this.commandHandler(command, options);
|
|
1764
|
+
}
|
|
1765
|
+
};
|
|
1766
|
+
|
|
1767
|
+
// src/client/resources/child.ts
|
|
1768
|
+
var Child = class {
|
|
1769
|
+
constructor(handlers) {
|
|
1770
|
+
this.createHandler = handlers.create;
|
|
1771
|
+
this.listHandler = handlers.list;
|
|
1772
|
+
this.retrieveHandler = handlers.retrieve;
|
|
1773
|
+
this.destroyHandler = handlers.destroy;
|
|
1774
|
+
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Create a new child sandbox
|
|
1777
|
+
* @returns Child sandbox info including URL and subdomain
|
|
1778
|
+
*/
|
|
1779
|
+
async create() {
|
|
1780
|
+
return this.createHandler();
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1783
|
+
* List all child sandboxes
|
|
1784
|
+
* @returns Array of child sandbox info
|
|
1785
|
+
*/
|
|
1786
|
+
async list() {
|
|
1787
|
+
const response = await this.listHandler();
|
|
1788
|
+
return response.sandboxes;
|
|
1789
|
+
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Retrieve a specific child sandbox by subdomain
|
|
1792
|
+
* @param subdomain - The child subdomain (e.g., 'sandbox-12345')
|
|
1793
|
+
* @returns Child sandbox info
|
|
1794
|
+
*/
|
|
1795
|
+
async retrieve(subdomain) {
|
|
1796
|
+
return this.retrieveHandler(subdomain);
|
|
1797
|
+
}
|
|
1798
|
+
/**
|
|
1799
|
+
* Destroy (delete) a child sandbox
|
|
1800
|
+
* @param subdomain - The child subdomain
|
|
1801
|
+
* @param options - Destroy options
|
|
1802
|
+
* @param options.deleteFiles - Whether to delete the child's files (default: false)
|
|
1803
|
+
*/
|
|
1804
|
+
async destroy(subdomain, options) {
|
|
1805
|
+
return this.destroyHandler(subdomain, options?.deleteFiles ?? false);
|
|
1806
|
+
}
|
|
1807
|
+
};
|
|
1808
|
+
|
|
1809
|
+
// src/client/types.ts
|
|
1810
|
+
var CommandExitError = class extends Error {
|
|
1811
|
+
constructor(result) {
|
|
1812
|
+
super(`Command exited with code ${result.exitCode}`);
|
|
1813
|
+
this.result = result;
|
|
1814
|
+
this.name = "CommandExitError";
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
function isCommandExitError(error) {
|
|
1818
|
+
return typeof error === "object" && error !== null && "name" in error && error.name === "CommandExitError" && "result" in error;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
// src/client/index.ts
|
|
1822
|
+
var Sandbox = class {
|
|
1823
|
+
constructor(config) {
|
|
1824
|
+
this._token = null;
|
|
1825
|
+
this._ws = null;
|
|
1826
|
+
this.sandboxId = config.sandboxId;
|
|
1827
|
+
this.provider = config.provider;
|
|
1828
|
+
let sandboxUrlResolved = config.sandboxUrl;
|
|
1829
|
+
let tokenFromUrl = null;
|
|
1830
|
+
let sandboxUrlFromUrl = null;
|
|
1831
|
+
if (typeof window !== "undefined" && window.location && typeof localStorage !== "undefined") {
|
|
1832
|
+
const params = new URLSearchParams(window.location.search);
|
|
1833
|
+
tokenFromUrl = params.get("session_token");
|
|
1834
|
+
sandboxUrlFromUrl = params.get("sandbox_url");
|
|
1835
|
+
let urlChanged = false;
|
|
1836
|
+
if (tokenFromUrl) {
|
|
1837
|
+
params.delete("session_token");
|
|
1838
|
+
localStorage.setItem("session_token", tokenFromUrl);
|
|
1839
|
+
urlChanged = true;
|
|
1840
|
+
}
|
|
1841
|
+
if (sandboxUrlFromUrl) {
|
|
1842
|
+
params.delete("sandbox_url");
|
|
1843
|
+
localStorage.setItem("sandbox_url", sandboxUrlFromUrl);
|
|
1844
|
+
urlChanged = true;
|
|
1845
|
+
}
|
|
1846
|
+
if (urlChanged) {
|
|
1847
|
+
const search = params.toString() ? `?${params.toString()}` : "";
|
|
1848
|
+
const newUrl = `${window.location.pathname}${search}${window.location.hash}`;
|
|
1849
|
+
window.history.replaceState({}, "", newUrl);
|
|
1850
|
+
}
|
|
1851
|
+
if (!config.sandboxUrl) {
|
|
1852
|
+
sandboxUrlResolved = sandboxUrlFromUrl || localStorage.getItem("sandbox_url") || "";
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
this.config = {
|
|
1856
|
+
sandboxUrl: (sandboxUrlResolved || "").replace(/\/$/, ""),
|
|
1857
|
+
// Remove trailing slash
|
|
1858
|
+
sandboxId: config.sandboxId || "",
|
|
1859
|
+
provider: config.provider || "",
|
|
1860
|
+
token: config.token || "",
|
|
1861
|
+
headers: config.headers || {},
|
|
1862
|
+
timeout: config.timeout || 3e4,
|
|
1863
|
+
protocol: config.protocol || "binary",
|
|
1864
|
+
metadata: config.metadata
|
|
1865
|
+
};
|
|
1866
|
+
this.WebSocketImpl = config.WebSocket || globalThis.WebSocket;
|
|
1867
|
+
if (!this.WebSocketImpl) {
|
|
1868
|
+
throw new Error(
|
|
1869
|
+
'WebSocket is not available. In Node.js, pass WebSocket implementation:\nimport WebSocket from "ws";\nnew Sandbox({ sandboxUrl: "...", WebSocket })'
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
if (config.token) {
|
|
1873
|
+
this._token = config.token;
|
|
1874
|
+
} else if (tokenFromUrl) {
|
|
1875
|
+
this._token = tokenFromUrl;
|
|
1876
|
+
} else if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
|
|
1877
|
+
this._token = localStorage.getItem("session_token");
|
|
1878
|
+
}
|
|
1879
|
+
this.filesystem = {
|
|
1880
|
+
readFile: async (path) => this.readFile(path),
|
|
1881
|
+
writeFile: async (path, content) => {
|
|
1882
|
+
await this.writeFile(path, content);
|
|
1883
|
+
},
|
|
1884
|
+
mkdir: async (path) => {
|
|
1885
|
+
await this.runCommand(mkdir(path));
|
|
1886
|
+
},
|
|
1887
|
+
readdir: async (path) => {
|
|
1888
|
+
const response = await this.listFiles(path);
|
|
1889
|
+
return response.data.files.map((f) => ({
|
|
1890
|
+
name: f.name,
|
|
1891
|
+
type: f.is_dir ? "directory" : "file",
|
|
1892
|
+
size: f.size,
|
|
1893
|
+
modified: new Date(f.modified_at)
|
|
1894
|
+
}));
|
|
1895
|
+
},
|
|
1896
|
+
exists: async (path) => {
|
|
1897
|
+
const result = await this.runCommand(test.exists(path));
|
|
1898
|
+
return result.exitCode === 0;
|
|
1899
|
+
},
|
|
1900
|
+
remove: async (path) => {
|
|
1901
|
+
await this.deleteFile(path);
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
this.terminal = new Terminal({
|
|
1905
|
+
create: async (options) => this.createTerminal(options),
|
|
1906
|
+
list: async () => this.listTerminals(),
|
|
1907
|
+
retrieve: async (id) => this.getTerminal(id),
|
|
1908
|
+
destroy: async (id) => {
|
|
1909
|
+
await this.request(`/terminals/${id}`, { method: "DELETE" });
|
|
1910
|
+
}
|
|
1911
|
+
});
|
|
1912
|
+
this.run = new Run({
|
|
1913
|
+
code: async (code, options) => {
|
|
1914
|
+
const result = await this.runCodeRequest(code, options?.language);
|
|
1915
|
+
return {
|
|
1916
|
+
output: result.data.output,
|
|
1917
|
+
exitCode: result.data.exit_code,
|
|
1918
|
+
language: result.data.language
|
|
1919
|
+
};
|
|
1920
|
+
},
|
|
1921
|
+
command: async (command, options) => {
|
|
1922
|
+
const result = await this.runCommandRequest({ command, shell: options?.shell, background: options?.background });
|
|
1923
|
+
return {
|
|
1924
|
+
stdout: result.data.stdout,
|
|
1925
|
+
stderr: result.data.stderr,
|
|
1926
|
+
exitCode: result.data.exit_code ?? 0,
|
|
1927
|
+
durationMs: result.data.duration_ms ?? 0
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1930
|
+
});
|
|
1931
|
+
this.server = new Server({
|
|
1932
|
+
start: async (options) => this.startServer(options),
|
|
1933
|
+
list: async () => this.listServers(),
|
|
1934
|
+
retrieve: async (slug) => this.getServer(slug),
|
|
1935
|
+
stop: async (slug) => {
|
|
1936
|
+
await this.stopServer(slug);
|
|
1937
|
+
},
|
|
1938
|
+
restart: async (slug) => this.restartServer(slug),
|
|
1939
|
+
updateStatus: async (slug, status) => {
|
|
1940
|
+
await this.updateServerStatus(slug, status);
|
|
1941
|
+
}
|
|
1942
|
+
});
|
|
1943
|
+
this.watcher = new Watcher({
|
|
1944
|
+
create: async (path, options) => this.createWatcher(path, options),
|
|
1945
|
+
list: async () => this.listWatchers(),
|
|
1946
|
+
retrieve: async (id) => this.getWatcher(id),
|
|
1947
|
+
destroy: async (id) => {
|
|
1948
|
+
await this.request(`/watchers/${id}`, { method: "DELETE" });
|
|
1949
|
+
}
|
|
1950
|
+
});
|
|
1951
|
+
this.sessionToken = new SessionToken({
|
|
1952
|
+
create: async (options) => this.createSessionToken(options),
|
|
1953
|
+
list: async () => this.listSessionTokens(),
|
|
1954
|
+
retrieve: async (id) => this.getSessionToken(id),
|
|
1955
|
+
revoke: async (id) => this.revokeSessionToken(id)
|
|
1956
|
+
});
|
|
1957
|
+
this.magicLink = new MagicLink({
|
|
1958
|
+
create: async (options) => this.createMagicLink(options)
|
|
1959
|
+
});
|
|
1960
|
+
this.signal = new Signal({
|
|
1961
|
+
start: async () => this.startSignals(),
|
|
1962
|
+
status: async () => this.getSignalStatus(),
|
|
1963
|
+
stop: async () => {
|
|
1964
|
+
await this.request("/signals/stop", { method: "POST" });
|
|
1965
|
+
},
|
|
1966
|
+
emitPort: async (port, type, url) => this.emitPortSignal(port, type, url),
|
|
1967
|
+
emitError: async (message) => this.emitErrorSignal(message),
|
|
1968
|
+
emitServerReady: async (port, url) => this.emitServerReadySignal(port, url)
|
|
1969
|
+
});
|
|
1970
|
+
this.file = new File({
|
|
1971
|
+
create: async (path, content) => this.createFile(path, content),
|
|
1972
|
+
list: async (path) => this.listFiles(path),
|
|
1973
|
+
retrieve: async (path) => this.readFile(path),
|
|
1974
|
+
destroy: async (path) => this.deleteFile(path),
|
|
1975
|
+
batchWrite: async (files) => this.batchWriteFiles(files),
|
|
1976
|
+
exists: async (path) => this.checkFileExists(path)
|
|
1977
|
+
});
|
|
1978
|
+
this.env = new Env({
|
|
1979
|
+
retrieve: async (file) => this.getEnv(file),
|
|
1980
|
+
update: async (file, variables) => this.setEnv(file, variables),
|
|
1981
|
+
remove: async (file, keys) => this.deleteEnv(file, keys),
|
|
1982
|
+
exists: async (file) => this.checkEnvFile(file)
|
|
1983
|
+
});
|
|
1984
|
+
this.auth = new Auth({
|
|
1985
|
+
status: async () => this.getAuthStatus(),
|
|
1986
|
+
info: async () => this.getAuthInfo()
|
|
1987
|
+
});
|
|
1988
|
+
this.child = new Child({
|
|
1989
|
+
create: async () => this.createSandbox(),
|
|
1990
|
+
list: async () => this.listSandboxes(),
|
|
1991
|
+
retrieve: async (subdomain) => this.getSandbox(subdomain),
|
|
1992
|
+
destroy: async (subdomain, deleteFiles) => this.deleteSandbox(subdomain, deleteFiles)
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Get or create internal WebSocket manager
|
|
1997
|
+
*/
|
|
1998
|
+
async ensureWebSocket() {
|
|
1999
|
+
if (!this._ws || this._ws.getState() === "closed") {
|
|
2000
|
+
this._ws = new WebSocketManager({
|
|
2001
|
+
url: this.getWebSocketUrl(),
|
|
2002
|
+
WebSocket: this.WebSocketImpl,
|
|
2003
|
+
autoReconnect: true,
|
|
2004
|
+
debug: false,
|
|
2005
|
+
protocol: this.config.protocol
|
|
2006
|
+
});
|
|
2007
|
+
await this._ws.connect();
|
|
2008
|
+
}
|
|
2009
|
+
return this._ws;
|
|
2010
|
+
}
|
|
2011
|
+
// ============================================================================
|
|
2012
|
+
// Private Helper Methods
|
|
2013
|
+
// ============================================================================
|
|
2014
|
+
async request(endpoint, options = {}) {
|
|
2015
|
+
const controller = new AbortController();
|
|
2016
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
2017
|
+
try {
|
|
2018
|
+
const headers = {
|
|
2019
|
+
...this.config.headers
|
|
2020
|
+
};
|
|
2021
|
+
if (options.body) {
|
|
2022
|
+
headers["Content-Type"] = "application/json";
|
|
2023
|
+
}
|
|
2024
|
+
if (this._token) {
|
|
2025
|
+
headers["Authorization"] = `Bearer ${this._token}`;
|
|
2026
|
+
}
|
|
2027
|
+
const response = await fetch(`${this.config.sandboxUrl}${endpoint}`, {
|
|
2028
|
+
...options,
|
|
2029
|
+
headers: {
|
|
2030
|
+
...headers,
|
|
2031
|
+
...options.headers
|
|
2032
|
+
},
|
|
2033
|
+
signal: controller.signal
|
|
2034
|
+
});
|
|
2035
|
+
clearTimeout(timeoutId);
|
|
2036
|
+
if (response.status === 204) {
|
|
2037
|
+
return void 0;
|
|
2038
|
+
}
|
|
2039
|
+
const text = await response.text();
|
|
2040
|
+
let data;
|
|
2041
|
+
try {
|
|
2042
|
+
data = JSON.parse(text);
|
|
2043
|
+
} catch (jsonError) {
|
|
2044
|
+
throw new Error(
|
|
2045
|
+
`Failed to parse JSON response from ${endpoint}: ${jsonError instanceof Error ? jsonError.message : String(jsonError)}
|
|
2046
|
+
Response body (first 200 chars): ${text.substring(0, 200)}${text.length > 200 ? "..." : ""}`
|
|
2047
|
+
);
|
|
2048
|
+
}
|
|
2049
|
+
if (!response.ok) {
|
|
2050
|
+
const error = data.error || response.statusText;
|
|
2051
|
+
if (response.status === 403 && endpoint.startsWith("/auth/")) {
|
|
2052
|
+
if (endpoint.includes("/session_tokens") || endpoint.includes("/magic-links")) {
|
|
2053
|
+
throw new Error(
|
|
2054
|
+
`Access token required. This operation requires an access token, not a session token.
|
|
2055
|
+
API request failed (${response.status}): ${error}`
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
throw new Error(`API request failed (${response.status}): ${error}`);
|
|
2060
|
+
}
|
|
2061
|
+
return data;
|
|
2062
|
+
} catch (error) {
|
|
2063
|
+
clearTimeout(timeoutId);
|
|
2064
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2065
|
+
throw new Error(`Request timeout after ${this.config.timeout}ms`);
|
|
2066
|
+
}
|
|
2067
|
+
throw error;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
// ============================================================================
|
|
2071
|
+
// Health Check
|
|
2072
|
+
// ============================================================================
|
|
2073
|
+
/**
|
|
2074
|
+
* Check service health
|
|
2075
|
+
*/
|
|
2076
|
+
async health() {
|
|
2077
|
+
return this.request("/health");
|
|
2078
|
+
}
|
|
2079
|
+
// ============================================================================
|
|
2080
|
+
// Authentication
|
|
2081
|
+
// ============================================================================
|
|
2082
|
+
/**
|
|
2083
|
+
* Create a session token (requires access token)
|
|
2084
|
+
*
|
|
2085
|
+
* Session tokens are delegated credentials that can authenticate API requests
|
|
2086
|
+
* without exposing your access token. Only access tokens can create session tokens.
|
|
2087
|
+
*
|
|
2088
|
+
* @param options - Token configuration
|
|
2089
|
+
* @throws {Error} 403 Forbidden if called with a session token
|
|
2090
|
+
*/
|
|
2091
|
+
async createSessionToken(options) {
|
|
2092
|
+
return this.request("/auth/session_tokens", {
|
|
2093
|
+
method: "POST",
|
|
2094
|
+
body: JSON.stringify(options || {})
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2098
|
+
* List all session tokens (requires access token)
|
|
2099
|
+
*
|
|
2100
|
+
* @throws {Error} 403 Forbidden if called with a session token
|
|
2101
|
+
*/
|
|
2102
|
+
async listSessionTokens() {
|
|
2103
|
+
return this.request("/auth/session_tokens");
|
|
2104
|
+
}
|
|
2105
|
+
/**
|
|
2106
|
+
* Get details of a specific session token (requires access token)
|
|
2107
|
+
*
|
|
2108
|
+
* @param tokenId - The token ID
|
|
2109
|
+
* @throws {Error} 403 Forbidden if called with a session token
|
|
2110
|
+
*/
|
|
2111
|
+
async getSessionToken(tokenId) {
|
|
2112
|
+
return this.request(`/auth/session_tokens/${tokenId}`);
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* Revoke a session token (requires access token)
|
|
2116
|
+
*
|
|
2117
|
+
* @param tokenId - The token ID to revoke
|
|
2118
|
+
* @throws {Error} 403 Forbidden if called with a session token
|
|
2119
|
+
*/
|
|
2120
|
+
async revokeSessionToken(tokenId) {
|
|
2121
|
+
return this.request(`/auth/session_tokens/${tokenId}`, {
|
|
2122
|
+
method: "DELETE"
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
/**
|
|
2126
|
+
* Generate a magic link for browser authentication (requires access token)
|
|
2127
|
+
*
|
|
2128
|
+
* Magic links are one-time URLs that automatically create a session token
|
|
2129
|
+
* and set it as a cookie in the user's browser. This provides an easy way
|
|
2130
|
+
* to authenticate users in browser-based applications.
|
|
2131
|
+
*
|
|
2132
|
+
* The generated link:
|
|
2133
|
+
* - Expires after 5 minutes or first use (whichever comes first)
|
|
2134
|
+
* - Automatically creates a new session token (7 day expiry)
|
|
2135
|
+
* - Sets the session token as an HttpOnly cookie
|
|
2136
|
+
* - Redirects to the specified URL
|
|
2137
|
+
*
|
|
2138
|
+
* @param options - Magic link configuration
|
|
2139
|
+
* @throws {Error} 403 Forbidden if called with a session token
|
|
2140
|
+
*/
|
|
2141
|
+
async createMagicLink(options) {
|
|
2142
|
+
return this.request("/auth/magic-links", {
|
|
2143
|
+
method: "POST",
|
|
2144
|
+
body: JSON.stringify(options || {})
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Check authentication status
|
|
2149
|
+
* Does not require authentication
|
|
2150
|
+
*/
|
|
2151
|
+
async getAuthStatus() {
|
|
2152
|
+
return this.request("/auth/status");
|
|
2153
|
+
}
|
|
2154
|
+
/**
|
|
2155
|
+
* Get authentication information and usage instructions
|
|
2156
|
+
* Does not require authentication
|
|
2157
|
+
*/
|
|
2158
|
+
async getAuthInfo() {
|
|
2159
|
+
return this.request("/auth/info");
|
|
2160
|
+
}
|
|
2161
|
+
/**
|
|
2162
|
+
* Set authentication token manually
|
|
2163
|
+
* @param token - Access token or session token
|
|
2164
|
+
*/
|
|
2165
|
+
setToken(token) {
|
|
2166
|
+
this._token = token;
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Get current authentication token
|
|
2170
|
+
*/
|
|
2171
|
+
getToken() {
|
|
2172
|
+
return this._token;
|
|
2173
|
+
}
|
|
2174
|
+
/**
|
|
2175
|
+
* Get current sandbox URL
|
|
2176
|
+
*/
|
|
2177
|
+
getSandboxUrl() {
|
|
2178
|
+
return this.config.sandboxUrl;
|
|
2179
|
+
}
|
|
2180
|
+
// ============================================================================
|
|
2181
|
+
// Command Execution
|
|
2182
|
+
// ============================================================================
|
|
2183
|
+
/**
|
|
2184
|
+
* Execute a one-off command without creating a persistent terminal
|
|
2185
|
+
*
|
|
2186
|
+
* @example
|
|
2187
|
+
* ```typescript
|
|
2188
|
+
* // Synchronous execution (waits for completion)
|
|
2189
|
+
* const result = await sandbox.execute({ command: 'npm test' });
|
|
2190
|
+
* console.log(result.data.exit_code);
|
|
2191
|
+
*
|
|
2192
|
+
* // Background execution (returns immediately)
|
|
2193
|
+
* const result = await sandbox.execute({
|
|
2194
|
+
* command: 'npm install',
|
|
2195
|
+
* background: true
|
|
2196
|
+
* });
|
|
2197
|
+
* // Use result.data.terminal_id and result.data.cmd_id to track
|
|
2198
|
+
* const cmd = await sandbox.getCommand(result.data.terminal_id!, result.data.cmd_id!);
|
|
2199
|
+
* ```
|
|
2200
|
+
*/
|
|
2201
|
+
async execute(options) {
|
|
2202
|
+
return this.request("/execute", {
|
|
2203
|
+
method: "POST",
|
|
2204
|
+
body: JSON.stringify(options)
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
2207
|
+
/**
|
|
2208
|
+
* Execute code with automatic language detection (POST /run/code)
|
|
2209
|
+
*
|
|
2210
|
+
* @param code - The code to execute
|
|
2211
|
+
* @param language - Programming language (optional - auto-detects if not specified)
|
|
2212
|
+
* @returns Code execution result with output, exit code, and detected language
|
|
2213
|
+
*
|
|
2214
|
+
* @example
|
|
2215
|
+
* ```typescript
|
|
2216
|
+
* // Auto-detect language
|
|
2217
|
+
* const result = await sandbox.runCodeRequest('print("Hello")');
|
|
2218
|
+
* console.log(result.data.output); // "Hello\n"
|
|
2219
|
+
* console.log(result.data.language); // "python"
|
|
2220
|
+
*
|
|
2221
|
+
* // Explicit language
|
|
2222
|
+
* const result = await sandbox.runCodeRequest('console.log("Hi")', 'node');
|
|
2223
|
+
* ```
|
|
2224
|
+
*/
|
|
2225
|
+
async runCodeRequest(code, language) {
|
|
2226
|
+
const body = { code };
|
|
2227
|
+
if (language) {
|
|
2228
|
+
body.language = language;
|
|
2229
|
+
}
|
|
2230
|
+
return this.request("/run/code", {
|
|
2231
|
+
method: "POST",
|
|
2232
|
+
body: JSON.stringify(body)
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
/**
|
|
2236
|
+
* Execute a shell command (POST /run/command)
|
|
2237
|
+
*
|
|
2238
|
+
* @param options - Command options
|
|
2239
|
+
* @param options.command - The command to execute
|
|
2240
|
+
* @param options.shell - Shell to use (optional)
|
|
2241
|
+
* @param options.background - Run in background (optional)
|
|
2242
|
+
* @returns Command execution result
|
|
2243
|
+
*
|
|
2244
|
+
* @example
|
|
2245
|
+
* ```typescript
|
|
2246
|
+
* const result = await sandbox.runCommandRequest({ command: 'ls -la' });
|
|
2247
|
+
* console.log(result.data.stdout);
|
|
2248
|
+
* ```
|
|
2249
|
+
*/
|
|
2250
|
+
async runCommandRequest(options) {
|
|
2251
|
+
return this.request("/run/command", {
|
|
2252
|
+
method: "POST",
|
|
2253
|
+
body: JSON.stringify(options)
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2256
|
+
// ============================================================================
|
|
2257
|
+
// File Operations
|
|
2258
|
+
// ============================================================================
|
|
2259
|
+
/**
|
|
2260
|
+
* List files at the specified path
|
|
2261
|
+
*/
|
|
2262
|
+
async listFiles(path = "/") {
|
|
2263
|
+
const params = new URLSearchParams({ path });
|
|
2264
|
+
return this.request(`/files?${params}`);
|
|
2265
|
+
}
|
|
2266
|
+
/**
|
|
2267
|
+
* Create a new file with optional content
|
|
2268
|
+
*/
|
|
2269
|
+
async createFile(path, content) {
|
|
2270
|
+
return this.request("/files", {
|
|
2271
|
+
method: "POST",
|
|
2272
|
+
body: JSON.stringify({ path, content })
|
|
2273
|
+
});
|
|
2274
|
+
}
|
|
2275
|
+
/**
|
|
2276
|
+
* Get file metadata (without content)
|
|
2277
|
+
*/
|
|
2278
|
+
async getFile(path) {
|
|
2279
|
+
return this.request(`/files/${encodeURIComponent(path)}`);
|
|
2280
|
+
}
|
|
2281
|
+
/**
|
|
2282
|
+
* Read file content
|
|
2283
|
+
*/
|
|
2284
|
+
async readFile(path) {
|
|
2285
|
+
const params = new URLSearchParams({ content: "true" });
|
|
2286
|
+
const pathWithoutLeadingSlash = path.startsWith("/") ? path.slice(1) : path;
|
|
2287
|
+
const segments = pathWithoutLeadingSlash.split("/");
|
|
2288
|
+
const encodedPath = segments.map((s) => encodeURIComponent(s)).join("/");
|
|
2289
|
+
const response = await this.request(
|
|
2290
|
+
`/files/${encodedPath}?${params}`
|
|
2291
|
+
);
|
|
2292
|
+
return response.data.content || "";
|
|
2293
|
+
}
|
|
2294
|
+
/**
|
|
2295
|
+
* Write file content (creates or updates)
|
|
2296
|
+
*/
|
|
2297
|
+
async writeFile(path, content) {
|
|
2298
|
+
return this.request("/files", {
|
|
2299
|
+
method: "POST",
|
|
2300
|
+
body: JSON.stringify({ path, content })
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Delete a file or directory
|
|
2305
|
+
*/
|
|
2306
|
+
async deleteFile(path) {
|
|
2307
|
+
return this.request(`/files/${encodeURIComponent(path)}`, {
|
|
2308
|
+
method: "DELETE"
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
/**
|
|
2312
|
+
* Check if a file exists (HEAD request)
|
|
2313
|
+
* @returns true if file exists, false otherwise
|
|
2314
|
+
*/
|
|
2315
|
+
async checkFileExists(path) {
|
|
2316
|
+
try {
|
|
2317
|
+
const controller = new AbortController();
|
|
2318
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
2319
|
+
const headers = {
|
|
2320
|
+
...this.config.headers
|
|
2321
|
+
};
|
|
2322
|
+
if (this._token) {
|
|
2323
|
+
headers["Authorization"] = `Bearer ${this._token}`;
|
|
2324
|
+
}
|
|
2325
|
+
const response = await fetch(
|
|
2326
|
+
`${this.config.sandboxUrl}/files/${encodeURIComponent(path)}`,
|
|
2327
|
+
{
|
|
2328
|
+
method: "HEAD",
|
|
2329
|
+
headers,
|
|
2330
|
+
signal: controller.signal
|
|
2331
|
+
}
|
|
2332
|
+
);
|
|
2333
|
+
clearTimeout(timeoutId);
|
|
2334
|
+
return response.ok;
|
|
2335
|
+
} catch {
|
|
2336
|
+
return false;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
/**
|
|
2340
|
+
* Batch file operations (write or delete multiple files)
|
|
2341
|
+
*
|
|
2342
|
+
* Features:
|
|
2343
|
+
* - Deduplication: Last operation wins per path
|
|
2344
|
+
* - File locking: Prevents race conditions
|
|
2345
|
+
* - Deterministic ordering: Alphabetical path sorting
|
|
2346
|
+
* - Partial failure handling: Returns 207 Multi-Status with per-file results
|
|
2347
|
+
*
|
|
2348
|
+
* @param files - Array of file operations
|
|
2349
|
+
* @returns Results for each file operation
|
|
2350
|
+
*
|
|
2351
|
+
* @example
|
|
2352
|
+
* ```typescript
|
|
2353
|
+
* // Write multiple files
|
|
2354
|
+
* const results = await sandbox.batchWriteFiles([
|
|
2355
|
+
* { path: '/app/file1.txt', operation: 'write', content: 'Hello' },
|
|
2356
|
+
* { path: '/app/file2.txt', operation: 'write', content: 'World' },
|
|
2357
|
+
* ]);
|
|
2358
|
+
*
|
|
2359
|
+
* // Mixed operations (write and delete)
|
|
2360
|
+
* const results = await sandbox.batchWriteFiles([
|
|
2361
|
+
* { path: '/app/new.txt', operation: 'write', content: 'New file' },
|
|
2362
|
+
* { path: '/app/old.txt', operation: 'delete' },
|
|
2363
|
+
* ]);
|
|
2364
|
+
* ```
|
|
2365
|
+
*/
|
|
2366
|
+
async batchWriteFiles(files) {
|
|
2367
|
+
return this.request("/files/batch", {
|
|
2368
|
+
method: "POST",
|
|
2369
|
+
body: JSON.stringify({ files })
|
|
2370
|
+
});
|
|
2371
|
+
}
|
|
2372
|
+
// ============================================================================
|
|
2373
|
+
// Terminal Management
|
|
2374
|
+
// ============================================================================
|
|
2375
|
+
/**
|
|
2376
|
+
* Create a new persistent terminal session
|
|
2377
|
+
*
|
|
2378
|
+
* Terminal Modes:
|
|
2379
|
+
* - **PTY mode** (pty: true): Interactive shell with real-time WebSocket streaming
|
|
2380
|
+
* - Use for: Interactive shells, vim/nano, real-time output
|
|
2381
|
+
* - Methods: write(), resize(), on('output')
|
|
2382
|
+
*
|
|
2383
|
+
* - **Exec mode** (pty: false, default): Command tracking with HTTP polling
|
|
2384
|
+
* - Use for: CI/CD, automation, command tracking, exit codes
|
|
2385
|
+
* - Methods: execute(), getCommand(), listCommands(), waitForCommand()
|
|
2386
|
+
*
|
|
2387
|
+
* @example
|
|
2388
|
+
* ```typescript
|
|
2389
|
+
* // PTY mode - Interactive shell
|
|
2390
|
+
* const pty = await sandbox.createTerminal({ pty: true, shell: '/bin/bash' });
|
|
2391
|
+
* pty.on('output', (data) => console.log(data));
|
|
2392
|
+
* pty.write('npm install\n');
|
|
2393
|
+
*
|
|
2394
|
+
* // Exec mode - Command tracking
|
|
2395
|
+
* const exec = await sandbox.createTerminal({ pty: false });
|
|
2396
|
+
* const result = await exec.execute('npm test', { background: true });
|
|
2397
|
+
* const cmd = await sandbox.waitForCommand(exec.getId(), result.data.cmd_id);
|
|
2398
|
+
* console.log(cmd.data.exit_code);
|
|
2399
|
+
*
|
|
2400
|
+
* // Backward compatible - creates PTY terminal
|
|
2401
|
+
* const terminal = await sandbox.createTerminal('/bin/bash');
|
|
2402
|
+
* ```
|
|
2403
|
+
*
|
|
2404
|
+
* @param options - Terminal creation options
|
|
2405
|
+
* @param options.shell - Shell to use (e.g., '/bin/bash', '/bin/sh') - PTY mode only
|
|
2406
|
+
* @param options.encoding - Encoding for terminal I/O: 'raw' (default) or 'base64' (binary-safe)
|
|
2407
|
+
* @param options.pty - Terminal mode: true = PTY (interactive shell), false = exec (command tracking, default)
|
|
2408
|
+
* @returns Terminal instance with event handling
|
|
2409
|
+
*/
|
|
2410
|
+
async createTerminal(shellOrOptions, encoding) {
|
|
2411
|
+
let pty;
|
|
2412
|
+
let shell;
|
|
2413
|
+
let enc;
|
|
2414
|
+
if (typeof shellOrOptions === "string") {
|
|
2415
|
+
pty = true;
|
|
2416
|
+
shell = shellOrOptions;
|
|
2417
|
+
enc = encoding;
|
|
2418
|
+
} else {
|
|
2419
|
+
pty = shellOrOptions?.pty ?? false;
|
|
2420
|
+
enc = shellOrOptions?.encoding;
|
|
2421
|
+
shell = shellOrOptions?.shell;
|
|
2422
|
+
}
|
|
2423
|
+
const body = {};
|
|
2424
|
+
if (shell) body.shell = shell;
|
|
2425
|
+
if (enc) body.encoding = enc;
|
|
2426
|
+
if (pty !== void 0) body.pty = pty;
|
|
2427
|
+
const response = await this.request("/terminals", {
|
|
2428
|
+
method: "POST",
|
|
2429
|
+
body: JSON.stringify(body)
|
|
2430
|
+
});
|
|
2431
|
+
let ws = null;
|
|
2432
|
+
if (response.data.pty) {
|
|
2433
|
+
ws = await this.ensureWebSocket();
|
|
2434
|
+
await new Promise((resolve) => {
|
|
2435
|
+
const handler = (msg) => {
|
|
2436
|
+
if (msg.data?.id === response.data.id) {
|
|
2437
|
+
if (ws) ws.off("terminal:created", handler);
|
|
2438
|
+
resolve();
|
|
2439
|
+
}
|
|
2440
|
+
};
|
|
2441
|
+
if (ws) {
|
|
2442
|
+
ws.on("terminal:created", handler);
|
|
2443
|
+
setTimeout(() => {
|
|
2444
|
+
if (ws) ws.off("terminal:created", handler);
|
|
2445
|
+
resolve();
|
|
2446
|
+
}, 5e3);
|
|
2447
|
+
} else {
|
|
2448
|
+
resolve();
|
|
2449
|
+
}
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2452
|
+
const terminal = new TerminalInstance(
|
|
2453
|
+
response.data.id,
|
|
2454
|
+
response.data.pty,
|
|
2455
|
+
response.data.status,
|
|
2456
|
+
response.data.channel || null,
|
|
2457
|
+
ws,
|
|
2458
|
+
response.data.encoding || "raw"
|
|
2459
|
+
);
|
|
2460
|
+
const terminalId = response.data.id;
|
|
2461
|
+
terminal.setExecuteHandler(async (command, background) => {
|
|
2462
|
+
return this.request(`/terminals/${terminalId}/execute`, {
|
|
2463
|
+
method: "POST",
|
|
2464
|
+
body: JSON.stringify({ command, background })
|
|
2465
|
+
});
|
|
2466
|
+
});
|
|
2467
|
+
terminal.setListCommandsHandler(async () => {
|
|
2468
|
+
return this.request(`/terminals/${terminalId}/commands`);
|
|
2469
|
+
});
|
|
2470
|
+
terminal.setRetrieveCommandHandler(async (cmdId) => {
|
|
2471
|
+
return this.request(`/terminals/${terminalId}/commands/${cmdId}`);
|
|
2472
|
+
});
|
|
2473
|
+
terminal.setWaitCommandHandler(async (cmdId, timeout) => {
|
|
2474
|
+
const params = timeout ? new URLSearchParams({ timeout: timeout.toString() }) : "";
|
|
2475
|
+
const endpoint = `/terminals/${terminalId}/commands/${cmdId}/wait${params ? `?${params}` : ""}`;
|
|
2476
|
+
return this.request(endpoint);
|
|
2477
|
+
});
|
|
2478
|
+
terminal.setDestroyHandler(async () => {
|
|
2479
|
+
await this.request(`/terminals/${terminalId}`, {
|
|
2480
|
+
method: "DELETE"
|
|
2481
|
+
});
|
|
2482
|
+
});
|
|
2483
|
+
return terminal;
|
|
2484
|
+
}
|
|
2485
|
+
/**
|
|
2486
|
+
* List all active terminals (fetches from API)
|
|
2487
|
+
*/
|
|
2488
|
+
async listTerminals() {
|
|
2489
|
+
const response = await this.request("/terminals");
|
|
2490
|
+
return response.data.terminals;
|
|
2491
|
+
}
|
|
2492
|
+
/**
|
|
2493
|
+
* Get terminal by ID
|
|
2494
|
+
*/
|
|
2495
|
+
async getTerminal(id) {
|
|
2496
|
+
return this.request(`/terminals/${id}`);
|
|
2497
|
+
}
|
|
2498
|
+
// ============================================================================
|
|
2499
|
+
// Command Tracking (Exec Mode Terminals)
|
|
2500
|
+
// ============================================================================
|
|
2501
|
+
/**
|
|
2502
|
+
* List all commands executed in a terminal (exec mode only)
|
|
2503
|
+
* @param terminalId - The terminal ID
|
|
2504
|
+
* @returns List of all commands with their status
|
|
2505
|
+
* @throws {Error} If terminal is in PTY mode (command tracking not available)
|
|
2506
|
+
*/
|
|
2507
|
+
async listCommands(terminalId) {
|
|
2508
|
+
return this.request(`/terminals/${terminalId}/commands`);
|
|
2509
|
+
}
|
|
2510
|
+
/**
|
|
2511
|
+
* Get details of a specific command execution (exec mode only)
|
|
2512
|
+
* @param terminalId - The terminal ID
|
|
2513
|
+
* @param cmdId - The command ID
|
|
2514
|
+
* @returns Command execution details including stdout, stderr, and exit code
|
|
2515
|
+
* @throws {Error} If terminal is in PTY mode or command not found
|
|
2516
|
+
*/
|
|
2517
|
+
async getCommand(terminalId, cmdId) {
|
|
2518
|
+
return this.request(`/terminals/${terminalId}/commands/${cmdId}`);
|
|
2519
|
+
}
|
|
2520
|
+
/**
|
|
2521
|
+
* Wait for a command to complete (HTTP long-polling, exec mode only)
|
|
2522
|
+
* @param terminalId - The terminal ID
|
|
2523
|
+
* @param cmdId - The command ID
|
|
2524
|
+
* @param timeout - Optional timeout in seconds (0 = no timeout)
|
|
2525
|
+
* @returns Command execution details when completed
|
|
2526
|
+
* @throws {Error} If terminal is in PTY mode, command not found, or timeout occurs
|
|
2527
|
+
*/
|
|
2528
|
+
async waitForCommand(terminalId, cmdId, timeout) {
|
|
2529
|
+
const params = timeout ? new URLSearchParams({ timeout: timeout.toString() }) : "";
|
|
2530
|
+
const endpoint = `/terminals/${terminalId}/commands/${cmdId}/wait${params ? `?${params}` : ""}`;
|
|
2531
|
+
return this.request(endpoint);
|
|
2532
|
+
}
|
|
2533
|
+
// ============================================================================
|
|
2534
|
+
// File Watchers
|
|
2535
|
+
// ============================================================================
|
|
2536
|
+
/**
|
|
2537
|
+
* Create a new file watcher with WebSocket integration
|
|
2538
|
+
* @param path - Path to watch
|
|
2539
|
+
* @param options - Watcher options
|
|
2540
|
+
* @param options.includeContent - Include file content in change events
|
|
2541
|
+
* @param options.ignored - Patterns to ignore
|
|
2542
|
+
* @param options.encoding - Encoding for file content: 'raw' (default) or 'base64' (binary-safe)
|
|
2543
|
+
* @returns FileWatcher instance with event handling
|
|
2544
|
+
*/
|
|
2545
|
+
async createWatcher(path, options) {
|
|
2546
|
+
const ws = await this.ensureWebSocket();
|
|
2547
|
+
const response = await this.request("/watchers", {
|
|
2548
|
+
method: "POST",
|
|
2549
|
+
body: JSON.stringify({ path, ...options })
|
|
2550
|
+
});
|
|
2551
|
+
const watcher = new FileWatcher(
|
|
2552
|
+
response.data.id,
|
|
2553
|
+
response.data.path,
|
|
2554
|
+
response.data.status,
|
|
2555
|
+
response.data.channel,
|
|
2556
|
+
response.data.includeContent,
|
|
2557
|
+
response.data.ignored,
|
|
2558
|
+
ws,
|
|
2559
|
+
response.data.encoding || "raw"
|
|
2560
|
+
);
|
|
2561
|
+
watcher.setDestroyHandler(async () => {
|
|
2562
|
+
await this.request(`/watchers/${response.data.id}`, {
|
|
2563
|
+
method: "DELETE"
|
|
2564
|
+
});
|
|
2565
|
+
});
|
|
2566
|
+
return watcher;
|
|
2567
|
+
}
|
|
2568
|
+
/**
|
|
2569
|
+
* List all active file watchers (fetches from API)
|
|
2570
|
+
*/
|
|
2571
|
+
async listWatchers() {
|
|
2572
|
+
return this.request("/watchers");
|
|
2573
|
+
}
|
|
2574
|
+
/**
|
|
2575
|
+
* Get file watcher by ID
|
|
2576
|
+
*/
|
|
2577
|
+
async getWatcher(id) {
|
|
2578
|
+
return this.request(`/watchers/${id}`);
|
|
2579
|
+
}
|
|
2580
|
+
// ============================================================================
|
|
2581
|
+
// Signal Service
|
|
2582
|
+
// ============================================================================
|
|
2583
|
+
/**
|
|
2584
|
+
* Start the signal service with WebSocket integration
|
|
2585
|
+
* @returns SignalService instance with event handling
|
|
2586
|
+
*/
|
|
2587
|
+
async startSignals() {
|
|
2588
|
+
const ws = await this.ensureWebSocket();
|
|
2589
|
+
const response = await this.request("/signals/start", {
|
|
2590
|
+
method: "POST"
|
|
2591
|
+
});
|
|
2592
|
+
const signalService = new SignalService(
|
|
2593
|
+
response.data.status,
|
|
2594
|
+
response.data.channel,
|
|
2595
|
+
ws
|
|
2596
|
+
);
|
|
2597
|
+
signalService.setStopHandler(async () => {
|
|
2598
|
+
await this.request("/signals/stop", {
|
|
2599
|
+
method: "POST"
|
|
2600
|
+
});
|
|
2601
|
+
});
|
|
2602
|
+
return signalService;
|
|
2603
|
+
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Get the signal service status (fetches from API)
|
|
2606
|
+
*/
|
|
2607
|
+
async getSignalStatus() {
|
|
2608
|
+
return this.request("/signals/status");
|
|
2609
|
+
}
|
|
2610
|
+
/**
|
|
2611
|
+
* Emit a port signal
|
|
2612
|
+
*/
|
|
2613
|
+
async emitPortSignal(port, type, url) {
|
|
2614
|
+
return this.request("/signals/port", {
|
|
2615
|
+
method: "POST",
|
|
2616
|
+
body: JSON.stringify({ port, type, url })
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
/**
|
|
2620
|
+
* Emit a port signal (alternative endpoint using path parameters)
|
|
2621
|
+
*/
|
|
2622
|
+
async emitPortSignalAlt(port, type) {
|
|
2623
|
+
return this.request(`/signals/port/${port}/${type}`, {
|
|
2624
|
+
method: "POST"
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
/**
|
|
2628
|
+
* Emit an error signal
|
|
2629
|
+
*/
|
|
2630
|
+
async emitErrorSignal(message) {
|
|
2631
|
+
return this.request("/signals/error", {
|
|
2632
|
+
method: "POST",
|
|
2633
|
+
body: JSON.stringify({ message })
|
|
2634
|
+
});
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* Emit a server ready signal
|
|
2638
|
+
*/
|
|
2639
|
+
async emitServerReadySignal(port, url) {
|
|
2640
|
+
return this.request("/signals/server-ready", {
|
|
2641
|
+
method: "POST",
|
|
2642
|
+
body: JSON.stringify({ port, url })
|
|
2643
|
+
});
|
|
2644
|
+
}
|
|
2645
|
+
// ============================================================================
|
|
2646
|
+
// Environment Variables
|
|
2647
|
+
// ============================================================================
|
|
2648
|
+
/**
|
|
2649
|
+
* Get environment variables from a .env file
|
|
2650
|
+
* @param file - Path to the .env file (relative to sandbox root)
|
|
2651
|
+
*/
|
|
2652
|
+
async getEnv(file) {
|
|
2653
|
+
const params = new URLSearchParams({ file });
|
|
2654
|
+
return this.request(`/env?${params}`);
|
|
2655
|
+
}
|
|
2656
|
+
/**
|
|
2657
|
+
* Set (merge) environment variables in a .env file
|
|
2658
|
+
* @param file - Path to the .env file (relative to sandbox root)
|
|
2659
|
+
* @param variables - Key-value pairs to set
|
|
2660
|
+
*/
|
|
2661
|
+
async setEnv(file, variables) {
|
|
2662
|
+
const params = new URLSearchParams({ file });
|
|
2663
|
+
return this.request(`/env?${params}`, {
|
|
2664
|
+
method: "POST",
|
|
2665
|
+
body: JSON.stringify({ variables })
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
/**
|
|
2669
|
+
* Delete environment variables from a .env file
|
|
2670
|
+
* @param file - Path to the .env file (relative to sandbox root)
|
|
2671
|
+
* @param keys - Keys to delete
|
|
2672
|
+
*/
|
|
2673
|
+
async deleteEnv(file, keys) {
|
|
2674
|
+
const params = new URLSearchParams({ file });
|
|
2675
|
+
return this.request(`/env?${params}`, {
|
|
2676
|
+
method: "DELETE",
|
|
2677
|
+
body: JSON.stringify({ keys })
|
|
2678
|
+
});
|
|
2679
|
+
}
|
|
2680
|
+
/**
|
|
2681
|
+
* Check if an environment file exists (HEAD request)
|
|
2682
|
+
* @param file - Path to the .env file (relative to sandbox root)
|
|
2683
|
+
* @returns true if file exists, false otherwise
|
|
2684
|
+
*/
|
|
2685
|
+
async checkEnvFile(file) {
|
|
2686
|
+
try {
|
|
2687
|
+
const controller = new AbortController();
|
|
2688
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
2689
|
+
const headers = {
|
|
2690
|
+
...this.config.headers
|
|
2691
|
+
};
|
|
2692
|
+
if (this._token) {
|
|
2693
|
+
headers["Authorization"] = `Bearer ${this._token}`;
|
|
2694
|
+
}
|
|
2695
|
+
const params = new URLSearchParams({ file });
|
|
2696
|
+
const response = await fetch(`${this.config.sandboxUrl}/env?${params}`, {
|
|
2697
|
+
method: "HEAD",
|
|
2698
|
+
headers,
|
|
2699
|
+
signal: controller.signal
|
|
2700
|
+
});
|
|
2701
|
+
clearTimeout(timeoutId);
|
|
2702
|
+
return response.ok;
|
|
2703
|
+
} catch {
|
|
2704
|
+
return false;
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
// ============================================================================
|
|
2708
|
+
// Server Management
|
|
2709
|
+
// ============================================================================
|
|
2710
|
+
/**
|
|
2711
|
+
* List all managed servers
|
|
2712
|
+
*/
|
|
2713
|
+
async listServers() {
|
|
2714
|
+
return this.request("/servers");
|
|
2715
|
+
}
|
|
2716
|
+
/**
|
|
2717
|
+
* Start a new managed server
|
|
2718
|
+
* @param options - Server configuration
|
|
2719
|
+
*/
|
|
2720
|
+
async startServer(options) {
|
|
2721
|
+
return this.request("/servers", {
|
|
2722
|
+
method: "POST",
|
|
2723
|
+
body: JSON.stringify(options)
|
|
2724
|
+
});
|
|
2725
|
+
}
|
|
2726
|
+
/**
|
|
2727
|
+
* Get information about a specific server
|
|
2728
|
+
* @param slug - Server slug
|
|
2729
|
+
*/
|
|
2730
|
+
async getServer(slug) {
|
|
2731
|
+
return this.request(`/servers/${encodeURIComponent(slug)}`);
|
|
2732
|
+
}
|
|
2733
|
+
/**
|
|
2734
|
+
* Stop a managed server
|
|
2735
|
+
* @param slug - Server slug
|
|
2736
|
+
*/
|
|
2737
|
+
async stopServer(slug) {
|
|
2738
|
+
return this.request(
|
|
2739
|
+
`/servers/${encodeURIComponent(slug)}`,
|
|
2740
|
+
{
|
|
2741
|
+
method: "DELETE"
|
|
2742
|
+
}
|
|
2743
|
+
);
|
|
2744
|
+
}
|
|
2745
|
+
/**
|
|
2746
|
+
* Restart a managed server
|
|
2747
|
+
* @param slug - Server slug
|
|
2748
|
+
*/
|
|
2749
|
+
async restartServer(slug) {
|
|
2750
|
+
return this.request(
|
|
2751
|
+
`/servers/${encodeURIComponent(slug)}/restart`,
|
|
2752
|
+
{
|
|
2753
|
+
method: "POST"
|
|
2754
|
+
}
|
|
2755
|
+
);
|
|
2756
|
+
}
|
|
2757
|
+
/**
|
|
2758
|
+
* Update server status (internal use)
|
|
2759
|
+
* @param slug - Server slug
|
|
2760
|
+
* @param status - New server status
|
|
2761
|
+
*/
|
|
2762
|
+
async updateServerStatus(slug, status) {
|
|
2763
|
+
return this.request(
|
|
2764
|
+
`/servers/${encodeURIComponent(slug)}/status`,
|
|
2765
|
+
{
|
|
2766
|
+
method: "PATCH",
|
|
2767
|
+
body: JSON.stringify({ status })
|
|
2768
|
+
}
|
|
2769
|
+
);
|
|
2770
|
+
}
|
|
2771
|
+
// ============================================================================
|
|
2772
|
+
// Sandbox Management
|
|
2773
|
+
// ============================================================================
|
|
2774
|
+
/**
|
|
2775
|
+
* Create a new sandbox environment
|
|
2776
|
+
*/
|
|
2777
|
+
async createSandbox() {
|
|
2778
|
+
return this.request("/sandboxes", {
|
|
2779
|
+
method: "POST",
|
|
2780
|
+
body: JSON.stringify({})
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
/**
|
|
2784
|
+
* List all sandboxes
|
|
2785
|
+
*/
|
|
2786
|
+
async listSandboxes() {
|
|
2787
|
+
return this.request("/sandboxes");
|
|
2788
|
+
}
|
|
2789
|
+
/**
|
|
2790
|
+
* Get sandbox details
|
|
2791
|
+
*/
|
|
2792
|
+
async getSandbox(subdomain) {
|
|
2793
|
+
return this.request(`/sandboxes/${subdomain}`);
|
|
2794
|
+
}
|
|
2795
|
+
/**
|
|
2796
|
+
* Delete a sandbox
|
|
2797
|
+
*/
|
|
2798
|
+
async deleteSandbox(subdomain, deleteFiles = false) {
|
|
2799
|
+
const params = new URLSearchParams({ delete_files: String(deleteFiles) });
|
|
2800
|
+
return this.request(`/sandboxes/${subdomain}?${params}`, {
|
|
2801
|
+
method: "DELETE"
|
|
2802
|
+
});
|
|
2803
|
+
}
|
|
2804
|
+
// ============================================================================
|
|
2805
|
+
// WebSocket Connection (Internal)
|
|
2806
|
+
// ============================================================================
|
|
2807
|
+
/**
|
|
2808
|
+
* Get WebSocket URL for real-time communication
|
|
2809
|
+
* @private
|
|
2810
|
+
*/
|
|
2811
|
+
getWebSocketUrl() {
|
|
2812
|
+
const wsProtocol = this.config.sandboxUrl.startsWith("https") ? "wss" : "ws";
|
|
2813
|
+
const url = this.config.sandboxUrl.replace(/^https?:/, `${wsProtocol}:`);
|
|
2814
|
+
const params = new URLSearchParams();
|
|
2815
|
+
if (this._token) {
|
|
2816
|
+
params.set("token", this._token);
|
|
2817
|
+
}
|
|
2818
|
+
params.set("protocol", this.config.protocol || "binary");
|
|
2819
|
+
const queryString = params.toString();
|
|
2820
|
+
return `${url}/ws${queryString ? `?${queryString}` : ""}`;
|
|
2821
|
+
}
|
|
2822
|
+
// ============================================================================
|
|
2823
|
+
// Sandbox Interface Implementation
|
|
2824
|
+
// ============================================================================
|
|
2825
|
+
/**
|
|
2826
|
+
* Execute code in the sandbox (convenience method)
|
|
2827
|
+
*
|
|
2828
|
+
* Delegates to sandbox.run.code() - prefer using that directly for new code.
|
|
2829
|
+
*
|
|
2830
|
+
* @param code - The code to execute
|
|
2831
|
+
* @param language - Programming language (auto-detected if not specified)
|
|
2832
|
+
* @returns Code execution result
|
|
2833
|
+
*/
|
|
2834
|
+
async runCode(code, language) {
|
|
2835
|
+
return this.run.code(code, language ? { language } : void 0);
|
|
2836
|
+
}
|
|
2837
|
+
/**
|
|
2838
|
+
* Execute shell command in the sandbox (convenience method)
|
|
2839
|
+
*
|
|
2840
|
+
* Delegates to sandbox.run.command() - prefer using that directly for new code.
|
|
2841
|
+
*
|
|
2842
|
+
* @param command - The command to execute (string or array form)
|
|
2843
|
+
* @param argsOrOptions - Arguments array or options object
|
|
2844
|
+
* @param maybeOptions - Options when using (command, args, options) form
|
|
2845
|
+
* @returns Command execution result
|
|
2846
|
+
*/
|
|
2847
|
+
async runCommand(commandOrArray, argsOrOptions, maybeOptions) {
|
|
2848
|
+
let commandParts;
|
|
2849
|
+
let options;
|
|
2850
|
+
if (Array.isArray(commandOrArray)) {
|
|
2851
|
+
commandParts = commandOrArray;
|
|
2852
|
+
options = argsOrOptions;
|
|
2853
|
+
} else {
|
|
2854
|
+
const args = Array.isArray(argsOrOptions) ? argsOrOptions : [];
|
|
2855
|
+
commandParts = [commandOrArray, ...args];
|
|
2856
|
+
options = Array.isArray(argsOrOptions) ? maybeOptions : argsOrOptions;
|
|
2857
|
+
}
|
|
2858
|
+
const finalCommand = cmd(commandParts, options);
|
|
2859
|
+
const fullCommand = escapeArgs(finalCommand);
|
|
2860
|
+
return this.run.command(fullCommand, { background: options?.background });
|
|
2861
|
+
}
|
|
2862
|
+
/**
|
|
2863
|
+
* Get server information
|
|
2864
|
+
* Returns details about the server including auth status, main subdomain, sandbox count, and version
|
|
2865
|
+
*/
|
|
2866
|
+
async getServerInfo() {
|
|
2867
|
+
return this.request("/info");
|
|
2868
|
+
}
|
|
2869
|
+
/**
|
|
2870
|
+
* Get sandbox information
|
|
2871
|
+
*/
|
|
2872
|
+
async getInfo() {
|
|
2873
|
+
return {
|
|
2874
|
+
id: this.sandboxId || "",
|
|
2875
|
+
provider: this.provider || "",
|
|
2876
|
+
runtime: "node",
|
|
2877
|
+
status: "running",
|
|
2878
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
2879
|
+
timeout: this.config.timeout,
|
|
2880
|
+
metadata: this.config.metadata
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2883
|
+
/**
|
|
2884
|
+
* Get URL for accessing sandbox on a specific port (Sandbox interface method)
|
|
2885
|
+
*/
|
|
2886
|
+
async getUrl(options) {
|
|
2887
|
+
const protocol = options.protocol || "https";
|
|
2888
|
+
const url = new URL(this.config.sandboxUrl);
|
|
2889
|
+
const parts = url.hostname.split(".");
|
|
2890
|
+
const subdomain = parts[0];
|
|
2891
|
+
const baseDomain = parts.slice(1).join(".");
|
|
2892
|
+
const previewDomain = baseDomain.replace("sandbox.computesdk.com", "preview.computesdk.com");
|
|
2893
|
+
return `${protocol}://${subdomain}-${options.port}.${previewDomain}`;
|
|
2894
|
+
}
|
|
2895
|
+
/**
|
|
2896
|
+
* Get provider instance
|
|
2897
|
+
* Note: Not available when using Sandbox directly - only available through gateway provider
|
|
2898
|
+
*/
|
|
2899
|
+
getProvider() {
|
|
2900
|
+
throw new Error(
|
|
2901
|
+
"getProvider() is not available on Sandbox. This method is only available when using provider sandboxes through the gateway."
|
|
2902
|
+
);
|
|
2903
|
+
}
|
|
2904
|
+
/**
|
|
2905
|
+
* Get native provider instance
|
|
2906
|
+
* Returns the Sandbox itself since this IS the sandbox implementation
|
|
2907
|
+
*/
|
|
2908
|
+
getInstance() {
|
|
2909
|
+
return this;
|
|
2910
|
+
}
|
|
2911
|
+
/**
|
|
2912
|
+
* Destroy the sandbox (Sandbox interface method)
|
|
2913
|
+
*/
|
|
2914
|
+
async destroy() {
|
|
2915
|
+
await this.disconnect();
|
|
2916
|
+
}
|
|
2917
|
+
/**
|
|
2918
|
+
* Disconnect WebSocket
|
|
2919
|
+
*
|
|
2920
|
+
* Note: This only disconnects the WebSocket. Terminals, watchers, and signals
|
|
2921
|
+
* will continue running on the server until explicitly destroyed via their
|
|
2922
|
+
* respective destroy() methods or the DELETE endpoints.
|
|
2923
|
+
*/
|
|
2924
|
+
async disconnect() {
|
|
2925
|
+
if (this._ws) {
|
|
2926
|
+
this._ws.disconnect();
|
|
2927
|
+
this._ws = null;
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
};
|
|
2931
|
+
|
|
2932
|
+
// src/provider-config.ts
|
|
2933
|
+
var PROVIDER_AUTH = {
|
|
2934
|
+
e2b: [["E2B_API_KEY"]],
|
|
2935
|
+
modal: [["MODAL_TOKEN_ID", "MODAL_TOKEN_SECRET"]],
|
|
2936
|
+
railway: [["RAILWAY_API_KEY", "RAILWAY_PROJECT_ID", "RAILWAY_ENVIRONMENT_ID"]],
|
|
2937
|
+
daytona: [["DAYTONA_API_KEY"]],
|
|
2938
|
+
vercel: [
|
|
2939
|
+
["VERCEL_OIDC_TOKEN"],
|
|
2940
|
+
["VERCEL_TOKEN", "VERCEL_TEAM_ID", "VERCEL_PROJECT_ID"]
|
|
2941
|
+
],
|
|
2942
|
+
runloop: [["RUNLOOP_API_KEY"]],
|
|
2943
|
+
cloudflare: [["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"]],
|
|
2944
|
+
codesandbox: [["CSB_API_KEY"]],
|
|
2945
|
+
blaxel: [["BL_API_KEY", "BL_WORKSPACE"]]
|
|
2946
|
+
};
|
|
2947
|
+
var PROVIDER_NAMES = Object.keys(PROVIDER_AUTH);
|
|
2948
|
+
var PROVIDER_HEADERS = {
|
|
2949
|
+
e2b: {
|
|
2950
|
+
apiKey: "X-E2B-API-Key"
|
|
2951
|
+
},
|
|
2952
|
+
modal: {
|
|
2953
|
+
tokenId: "X-Modal-Token-Id",
|
|
2954
|
+
tokenSecret: "X-Modal-Token-Secret"
|
|
2955
|
+
},
|
|
2956
|
+
railway: {
|
|
2957
|
+
apiToken: "X-Railway-API-Token"
|
|
2958
|
+
},
|
|
2959
|
+
daytona: {
|
|
2960
|
+
apiKey: "X-Daytona-API-Key"
|
|
2961
|
+
},
|
|
2962
|
+
vercel: {
|
|
2963
|
+
oidcToken: "X-Vercel-OIDC-Token",
|
|
2964
|
+
token: "X-Vercel-Token",
|
|
2965
|
+
teamId: "X-Vercel-Team-Id",
|
|
2966
|
+
projectId: "X-Vercel-Project-Id"
|
|
2967
|
+
},
|
|
2968
|
+
runloop: {
|
|
2969
|
+
apiKey: "X-Runloop-API-Key"
|
|
2970
|
+
},
|
|
2971
|
+
cloudflare: {
|
|
2972
|
+
apiToken: "X-Cloudflare-API-Token",
|
|
2973
|
+
accountId: "X-Cloudflare-Account-Id"
|
|
2974
|
+
},
|
|
2975
|
+
codesandbox: {
|
|
2976
|
+
apiKey: "X-CSB-API-Key"
|
|
2977
|
+
},
|
|
2978
|
+
blaxel: {
|
|
2979
|
+
apiKey: "X-BL-API-Key",
|
|
2980
|
+
workspace: "X-BL-Workspace"
|
|
2981
|
+
}
|
|
2982
|
+
};
|
|
2983
|
+
var PROVIDER_ENV_MAP = {
|
|
2984
|
+
e2b: {
|
|
2985
|
+
E2B_API_KEY: "apiKey"
|
|
2986
|
+
},
|
|
2987
|
+
modal: {
|
|
2988
|
+
MODAL_TOKEN_ID: "tokenId",
|
|
2989
|
+
MODAL_TOKEN_SECRET: "tokenSecret"
|
|
2990
|
+
},
|
|
2991
|
+
railway: {
|
|
2992
|
+
RAILWAY_API_KEY: "apiToken",
|
|
2993
|
+
RAILWAY_PROJECT_ID: "projectId",
|
|
2994
|
+
RAILWAY_ENVIRONMENT_ID: "environmentId"
|
|
2995
|
+
},
|
|
2996
|
+
daytona: {
|
|
2997
|
+
DAYTONA_API_KEY: "apiKey"
|
|
2998
|
+
},
|
|
2999
|
+
vercel: {
|
|
3000
|
+
VERCEL_OIDC_TOKEN: "oidcToken",
|
|
3001
|
+
VERCEL_TOKEN: "token",
|
|
3002
|
+
VERCEL_TEAM_ID: "teamId",
|
|
3003
|
+
VERCEL_PROJECT_ID: "projectId"
|
|
3004
|
+
},
|
|
3005
|
+
runloop: {
|
|
3006
|
+
RUNLOOP_API_KEY: "apiKey"
|
|
3007
|
+
},
|
|
3008
|
+
cloudflare: {
|
|
3009
|
+
CLOUDFLARE_API_TOKEN: "apiToken",
|
|
3010
|
+
CLOUDFLARE_ACCOUNT_ID: "accountId"
|
|
3011
|
+
},
|
|
3012
|
+
codesandbox: {
|
|
3013
|
+
CSB_API_KEY: "apiKey"
|
|
3014
|
+
},
|
|
3015
|
+
blaxel: {
|
|
3016
|
+
BL_API_KEY: "apiKey",
|
|
3017
|
+
BL_WORKSPACE: "workspace"
|
|
3018
|
+
}
|
|
3019
|
+
};
|
|
3020
|
+
var PROVIDER_DASHBOARD_URLS = {
|
|
3021
|
+
e2b: "https://e2b.dev/dashboard",
|
|
3022
|
+
modal: "https://modal.com/settings",
|
|
3023
|
+
railway: "https://railway.app/account/tokens",
|
|
3024
|
+
daytona: "https://daytona.io/dashboard",
|
|
3025
|
+
vercel: "https://vercel.com/account/tokens",
|
|
3026
|
+
runloop: "https://runloop.ai/dashboard",
|
|
3027
|
+
cloudflare: "https://dash.cloudflare.com/profile/api-tokens",
|
|
3028
|
+
codesandbox: "https://codesandbox.io/dashboard/settings",
|
|
3029
|
+
blaxel: "https://blaxel.ai/dashboard"
|
|
3030
|
+
};
|
|
3031
|
+
function isValidProvider(name) {
|
|
3032
|
+
return name in PROVIDER_AUTH;
|
|
3033
|
+
}
|
|
3034
|
+
function buildProviderHeaders(provider, config) {
|
|
3035
|
+
const headers = {};
|
|
3036
|
+
const headerMap = PROVIDER_HEADERS[provider];
|
|
3037
|
+
for (const [configKey, headerName] of Object.entries(headerMap)) {
|
|
3038
|
+
const value = config[configKey];
|
|
3039
|
+
if (value) {
|
|
3040
|
+
headers[headerName] = value;
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
return headers;
|
|
3044
|
+
}
|
|
3045
|
+
function getProviderConfigFromEnv(provider) {
|
|
3046
|
+
const config = {};
|
|
3047
|
+
const envMap = PROVIDER_ENV_MAP[provider];
|
|
3048
|
+
for (const [envVar, configKey] of Object.entries(envMap)) {
|
|
3049
|
+
const value = process.env[envVar];
|
|
3050
|
+
if (value) {
|
|
3051
|
+
config[configKey] = value;
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
return config;
|
|
3055
|
+
}
|
|
3056
|
+
function isProviderAuthComplete(provider) {
|
|
3057
|
+
const authOptions = PROVIDER_AUTH[provider];
|
|
3058
|
+
for (const option of authOptions) {
|
|
3059
|
+
const allPresent = option.every((envVar) => !!process.env[envVar]);
|
|
3060
|
+
if (allPresent) return true;
|
|
3061
|
+
}
|
|
3062
|
+
return false;
|
|
3063
|
+
}
|
|
3064
|
+
function getMissingEnvVars(provider) {
|
|
3065
|
+
const authOptions = PROVIDER_AUTH[provider];
|
|
3066
|
+
let bestOption = null;
|
|
3067
|
+
for (const option of authOptions) {
|
|
3068
|
+
const missing = [];
|
|
3069
|
+
let presentCount = 0;
|
|
3070
|
+
for (const envVar of option) {
|
|
3071
|
+
if (process.env[envVar]) {
|
|
3072
|
+
presentCount++;
|
|
3073
|
+
} else {
|
|
3074
|
+
missing.push(envVar);
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
if (missing.length === 0) return [];
|
|
3078
|
+
if (!bestOption || presentCount > bestOption.presentCount) {
|
|
3079
|
+
bestOption = { presentCount, missing };
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
return bestOption?.missing ?? [];
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
// src/constants.ts
|
|
3086
|
+
var GATEWAY_URL = "https://gateway.computesdk.com";
|
|
3087
|
+
var PROVIDER_PRIORITY = [
|
|
3088
|
+
"e2b",
|
|
3089
|
+
"railway",
|
|
3090
|
+
"daytona",
|
|
3091
|
+
"modal",
|
|
3092
|
+
"runloop",
|
|
3093
|
+
"vercel",
|
|
3094
|
+
"cloudflare",
|
|
3095
|
+
"codesandbox",
|
|
3096
|
+
"blaxel"
|
|
3097
|
+
];
|
|
3098
|
+
var PROVIDER_ENV_VARS = {
|
|
3099
|
+
e2b: ["E2B_API_KEY"],
|
|
3100
|
+
railway: ["RAILWAY_API_KEY", "RAILWAY_PROJECT_ID", "RAILWAY_ENVIRONMENT_ID"],
|
|
3101
|
+
daytona: ["DAYTONA_API_KEY"],
|
|
3102
|
+
modal: ["MODAL_TOKEN_ID", "MODAL_TOKEN_SECRET"],
|
|
3103
|
+
runloop: ["RUNLOOP_API_KEY"],
|
|
3104
|
+
vercel: ["VERCEL_TOKEN", "VERCEL_TEAM_ID", "VERCEL_PROJECT_ID"],
|
|
3105
|
+
cloudflare: ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"],
|
|
3106
|
+
codesandbox: ["CSB_API_KEY"],
|
|
3107
|
+
blaxel: ["BL_API_KEY", "BL_WORKSPACE"]
|
|
3108
|
+
};
|
|
845
3109
|
|
|
846
3110
|
// src/auto-detect.ts
|
|
847
3111
|
function isGatewayModeEnabled() {
|
|
@@ -1018,12 +3282,13 @@ Check your COMPUTESDK_GATEWAY_URL environment variable.`
|
|
|
1018
3282
|
console.log(`\u{1F310} Gateway: ${gatewayUrl}`);
|
|
1019
3283
|
console.log(`\u{1F511} Provider headers:`, Object.keys(providerHeaders).join(", "));
|
|
1020
3284
|
}
|
|
1021
|
-
|
|
1022
|
-
gatewayUrl,
|
|
3285
|
+
const config = {
|
|
1023
3286
|
apiKey: computesdkApiKey,
|
|
3287
|
+
gatewayUrl,
|
|
1024
3288
|
provider,
|
|
1025
3289
|
providerHeaders
|
|
1026
|
-
}
|
|
3290
|
+
};
|
|
3291
|
+
return config;
|
|
1027
3292
|
}
|
|
1028
3293
|
|
|
1029
3294
|
// src/explicit-config.ts
|
|
@@ -1085,7 +3350,7 @@ function buildConfigExample(provider, authOptions) {
|
|
|
1085
3350
|
});
|
|
1086
3351
|
return options.join("\n\n");
|
|
1087
3352
|
}
|
|
1088
|
-
function
|
|
3353
|
+
function createConfigFromExplicit(config) {
|
|
1089
3354
|
if (!config.apiKey) {
|
|
1090
3355
|
throw new Error(
|
|
1091
3356
|
`Missing ComputeSDK API key. The 'apiKey' field is required.
|
|
@@ -1095,213 +3360,302 @@ Get your API key at: https://computesdk.com/dashboard`
|
|
|
1095
3360
|
}
|
|
1096
3361
|
validateProviderConfig(config);
|
|
1097
3362
|
const providerHeaders = buildProviderHeaders2(config);
|
|
1098
|
-
return
|
|
3363
|
+
return {
|
|
1099
3364
|
apiKey: config.apiKey,
|
|
3365
|
+
gatewayUrl: config.gatewayUrl || GATEWAY_URL,
|
|
1100
3366
|
provider: config.provider,
|
|
1101
3367
|
providerHeaders
|
|
1102
|
-
}
|
|
3368
|
+
};
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
// src/compute-daemon/lifecycle.ts
|
|
3372
|
+
async function waitForComputeReady(client, options = {}) {
|
|
3373
|
+
const maxRetries = options.maxRetries ?? 30;
|
|
3374
|
+
const initialDelayMs = options.initialDelayMs ?? 500;
|
|
3375
|
+
const maxDelayMs = options.maxDelayMs ?? 5e3;
|
|
3376
|
+
const backoffFactor = options.backoffFactor ?? 1.5;
|
|
3377
|
+
let lastError = null;
|
|
3378
|
+
let currentDelay = initialDelayMs;
|
|
3379
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
3380
|
+
try {
|
|
3381
|
+
await client.health();
|
|
3382
|
+
if (process.env.COMPUTESDK_DEBUG) {
|
|
3383
|
+
console.log(`[Lifecycle] Sandbox ready after ${i + 1} attempt${i === 0 ? "" : "s"}`);
|
|
3384
|
+
}
|
|
3385
|
+
return;
|
|
3386
|
+
} catch (error) {
|
|
3387
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3388
|
+
if (i === maxRetries - 1) {
|
|
3389
|
+
throw new Error(
|
|
3390
|
+
`Sandbox failed to become ready after ${maxRetries} attempts.
|
|
3391
|
+
Last error: ${lastError.message}
|
|
3392
|
+
|
|
3393
|
+
Possible causes:
|
|
3394
|
+
1. Sandbox failed to start (check provider dashboard for errors)
|
|
3395
|
+
2. Network connectivity issues between your app and the sandbox
|
|
3396
|
+
3. Sandbox is taking longer than expected to initialize
|
|
3397
|
+
4. Invalid sandbox URL or authentication credentials
|
|
3398
|
+
|
|
3399
|
+
Troubleshooting:
|
|
3400
|
+
- Check sandbox logs in your provider dashboard
|
|
3401
|
+
- Verify your network connection
|
|
3402
|
+
- Try increasing maxRetries if initialization is slow
|
|
3403
|
+
- Enable debug mode: export COMPUTESDK_DEBUG=1`
|
|
3404
|
+
);
|
|
3405
|
+
}
|
|
3406
|
+
await new Promise((resolve) => setTimeout(resolve, currentDelay));
|
|
3407
|
+
currentDelay = Math.min(currentDelay * backoffFactor, maxDelayMs);
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
1103
3410
|
}
|
|
1104
3411
|
|
|
1105
3412
|
// src/compute.ts
|
|
3413
|
+
async function gatewayFetch(url, config, options = {}) {
|
|
3414
|
+
const timeout = 3e4;
|
|
3415
|
+
const controller = new AbortController();
|
|
3416
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
3417
|
+
try {
|
|
3418
|
+
const response = await fetch(url, {
|
|
3419
|
+
...options,
|
|
3420
|
+
signal: controller.signal,
|
|
3421
|
+
headers: {
|
|
3422
|
+
"Content-Type": "application/json",
|
|
3423
|
+
"X-ComputeSDK-API-Key": config.apiKey,
|
|
3424
|
+
"X-Provider": config.provider,
|
|
3425
|
+
...config.providerHeaders,
|
|
3426
|
+
...options.headers
|
|
3427
|
+
}
|
|
3428
|
+
});
|
|
3429
|
+
clearTimeout(timeoutId);
|
|
3430
|
+
if (!response.ok) {
|
|
3431
|
+
if (response.status === 404) {
|
|
3432
|
+
return { success: false };
|
|
3433
|
+
}
|
|
3434
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
3435
|
+
let errorMessage = `Gateway API error: ${errorText}`;
|
|
3436
|
+
if (response.status === 401) {
|
|
3437
|
+
errorMessage = `Invalid ComputeSDK API key. Check your COMPUTESDK_API_KEY environment variable.`;
|
|
3438
|
+
} else if (response.status === 403) {
|
|
3439
|
+
errorMessage = `Access forbidden. Your API key may not have permission to use provider "${config.provider}".`;
|
|
3440
|
+
}
|
|
3441
|
+
throw new Error(errorMessage);
|
|
3442
|
+
}
|
|
3443
|
+
return await response.json();
|
|
3444
|
+
} catch (error) {
|
|
3445
|
+
clearTimeout(timeoutId);
|
|
3446
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
3447
|
+
throw new Error(`Request timed out after ${timeout}ms`);
|
|
3448
|
+
}
|
|
3449
|
+
throw error;
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
1106
3452
|
var ComputeManager = class {
|
|
1107
3453
|
constructor() {
|
|
1108
3454
|
this.config = null;
|
|
1109
3455
|
this.autoConfigured = false;
|
|
1110
3456
|
this.sandbox = {
|
|
1111
3457
|
/**
|
|
1112
|
-
* Create a sandbox
|
|
1113
|
-
*
|
|
1114
|
-
* @example
|
|
1115
|
-
* ```typescript
|
|
1116
|
-
* import { e2b } from '@computesdk/e2b'
|
|
1117
|
-
* import { compute } from 'computesdk'
|
|
1118
|
-
*
|
|
1119
|
-
* // With explicit provider
|
|
1120
|
-
* const sandbox = await compute.sandbox.create({
|
|
1121
|
-
* provider: e2b({ apiKey: 'your-key' })
|
|
1122
|
-
* })
|
|
1123
|
-
*
|
|
1124
|
-
* // With default provider
|
|
1125
|
-
* compute.setConfig({ defaultProvider: e2b({ apiKey: 'your-key' }) })
|
|
1126
|
-
* const sandbox = await compute.sandbox.create()
|
|
1127
|
-
* ```
|
|
3458
|
+
* Create a new sandbox
|
|
1128
3459
|
*/
|
|
1129
|
-
create: async (
|
|
1130
|
-
const
|
|
1131
|
-
const
|
|
1132
|
-
|
|
3460
|
+
create: async (options) => {
|
|
3461
|
+
const config = this.getGatewayConfig();
|
|
3462
|
+
const result = await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes`, config, {
|
|
3463
|
+
method: "POST",
|
|
3464
|
+
body: JSON.stringify(options || {})
|
|
3465
|
+
});
|
|
3466
|
+
if (!result.success || !result.data) {
|
|
3467
|
+
throw new Error(`Gateway returned invalid response`);
|
|
3468
|
+
}
|
|
3469
|
+
const { sandboxId, url, token, provider, metadata, name, namespace } = result.data;
|
|
3470
|
+
const sandbox = new Sandbox({
|
|
3471
|
+
sandboxUrl: url,
|
|
3472
|
+
sandboxId,
|
|
3473
|
+
provider,
|
|
3474
|
+
token: token || config.apiKey,
|
|
3475
|
+
metadata: {
|
|
3476
|
+
...metadata,
|
|
3477
|
+
...name && { name },
|
|
3478
|
+
...namespace && { namespace }
|
|
3479
|
+
},
|
|
3480
|
+
WebSocket: globalThis.WebSocket
|
|
3481
|
+
});
|
|
3482
|
+
await waitForComputeReady(sandbox);
|
|
3483
|
+
return sandbox;
|
|
1133
3484
|
},
|
|
1134
3485
|
/**
|
|
1135
|
-
* Get an existing sandbox by ID
|
|
3486
|
+
* Get an existing sandbox by ID
|
|
1136
3487
|
*/
|
|
1137
|
-
getById: async (
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
if (!sandboxId) {
|
|
1143
|
-
throw new Error("sandboxId is required when provider is specified");
|
|
1144
|
-
}
|
|
1145
|
-
return await providerOrSandboxId.sandbox.getById(sandboxId);
|
|
3488
|
+
getById: async (sandboxId) => {
|
|
3489
|
+
const config = this.getGatewayConfig();
|
|
3490
|
+
const result = await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config);
|
|
3491
|
+
if (!result.success || !result.data) {
|
|
3492
|
+
return null;
|
|
1146
3493
|
}
|
|
3494
|
+
const { url, token, provider, metadata } = result.data;
|
|
3495
|
+
const sandbox = new Sandbox({
|
|
3496
|
+
sandboxUrl: url,
|
|
3497
|
+
sandboxId,
|
|
3498
|
+
provider,
|
|
3499
|
+
token: token || config.apiKey,
|
|
3500
|
+
metadata,
|
|
3501
|
+
WebSocket: globalThis.WebSocket
|
|
3502
|
+
});
|
|
3503
|
+
await waitForComputeReady(sandbox);
|
|
3504
|
+
return sandbox;
|
|
1147
3505
|
},
|
|
1148
3506
|
/**
|
|
1149
|
-
* List all active sandboxes
|
|
3507
|
+
* List all active sandboxes
|
|
1150
3508
|
*/
|
|
1151
|
-
list: async (
|
|
1152
|
-
|
|
1153
|
-
|
|
3509
|
+
list: async () => {
|
|
3510
|
+
throw new Error(
|
|
3511
|
+
"The gateway does not support listing sandboxes. Use getById() with a known sandbox ID instead."
|
|
3512
|
+
);
|
|
1154
3513
|
},
|
|
1155
3514
|
/**
|
|
1156
|
-
* Destroy a sandbox
|
|
3515
|
+
* Destroy a sandbox
|
|
1157
3516
|
*/
|
|
1158
|
-
destroy: async (
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
}
|
|
1163
|
-
if (!sandboxId) {
|
|
1164
|
-
throw new Error("sandboxId is required when provider is specified");
|
|
1165
|
-
}
|
|
1166
|
-
return await providerOrSandboxId.sandbox.destroy(sandboxId);
|
|
1167
|
-
}
|
|
3517
|
+
destroy: async (sandboxId) => {
|
|
3518
|
+
const config = this.getGatewayConfig();
|
|
3519
|
+
await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
|
|
3520
|
+
method: "DELETE"
|
|
3521
|
+
});
|
|
1168
3522
|
},
|
|
1169
3523
|
/**
|
|
1170
3524
|
* Find existing or create new sandbox by (namespace, name)
|
|
1171
|
-
*
|
|
1172
|
-
* @example
|
|
1173
|
-
* ```typescript
|
|
1174
|
-
* // Find or create sandbox for a user's project
|
|
1175
|
-
* const sandbox = await compute.sandbox.findOrCreate({
|
|
1176
|
-
* name: 'my-app',
|
|
1177
|
-
* namespace: 'user-123',
|
|
1178
|
-
* timeout: 1800000
|
|
1179
|
-
* });
|
|
1180
|
-
* ```
|
|
1181
3525
|
*/
|
|
1182
3526
|
findOrCreate: async (options) => {
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
3527
|
+
const config = this.getGatewayConfig();
|
|
3528
|
+
const { name, namespace, ...restOptions } = options;
|
|
3529
|
+
const result = await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/find-or-create`, config, {
|
|
3530
|
+
method: "POST",
|
|
3531
|
+
body: JSON.stringify({
|
|
3532
|
+
namespace: namespace || "default",
|
|
3533
|
+
name,
|
|
3534
|
+
...restOptions
|
|
3535
|
+
})
|
|
3536
|
+
});
|
|
3537
|
+
if (!result.success || !result.data) {
|
|
3538
|
+
throw new Error(`Gateway returned invalid response`);
|
|
1189
3539
|
}
|
|
1190
|
-
|
|
3540
|
+
const { sandboxId, url, token, provider, metadata } = result.data;
|
|
3541
|
+
const sandbox = new Sandbox({
|
|
3542
|
+
sandboxUrl: url,
|
|
3543
|
+
sandboxId,
|
|
3544
|
+
provider,
|
|
3545
|
+
token: token || config.apiKey,
|
|
3546
|
+
metadata: {
|
|
3547
|
+
...metadata,
|
|
3548
|
+
name: result.data.name,
|
|
3549
|
+
namespace: result.data.namespace
|
|
3550
|
+
},
|
|
3551
|
+
WebSocket: globalThis.WebSocket
|
|
3552
|
+
});
|
|
3553
|
+
await waitForComputeReady(sandbox);
|
|
3554
|
+
return sandbox;
|
|
1191
3555
|
},
|
|
1192
3556
|
/**
|
|
1193
3557
|
* Find existing sandbox by (namespace, name) without creating
|
|
1194
|
-
*
|
|
1195
|
-
* @example
|
|
1196
|
-
* ```typescript
|
|
1197
|
-
* // Find existing sandbox
|
|
1198
|
-
* const sandbox = await compute.sandbox.find({
|
|
1199
|
-
* name: 'my-app',
|
|
1200
|
-
* namespace: 'user-123'
|
|
1201
|
-
* });
|
|
1202
|
-
*
|
|
1203
|
-
* if (sandbox) {
|
|
1204
|
-
* console.log('Found sandbox:', sandbox.sandboxId);
|
|
1205
|
-
* }
|
|
1206
|
-
* ```
|
|
1207
3558
|
*/
|
|
1208
3559
|
find: async (options) => {
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
3560
|
+
const config = this.getGatewayConfig();
|
|
3561
|
+
const result = await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/find`, config, {
|
|
3562
|
+
method: "POST",
|
|
3563
|
+
body: JSON.stringify({
|
|
3564
|
+
namespace: options.namespace || "default",
|
|
3565
|
+
name: options.name
|
|
3566
|
+
})
|
|
3567
|
+
});
|
|
3568
|
+
if (!result.success || !result.data) {
|
|
3569
|
+
return null;
|
|
1215
3570
|
}
|
|
1216
|
-
|
|
3571
|
+
const { sandboxId, url, token, provider, metadata, name, namespace } = result.data;
|
|
3572
|
+
const sandbox = new Sandbox({
|
|
3573
|
+
sandboxUrl: url,
|
|
3574
|
+
sandboxId,
|
|
3575
|
+
provider,
|
|
3576
|
+
token: token || config.apiKey,
|
|
3577
|
+
metadata: {
|
|
3578
|
+
...metadata,
|
|
3579
|
+
name,
|
|
3580
|
+
namespace
|
|
3581
|
+
},
|
|
3582
|
+
WebSocket: globalThis.WebSocket
|
|
3583
|
+
});
|
|
3584
|
+
await waitForComputeReady(sandbox);
|
|
3585
|
+
return sandbox;
|
|
1217
3586
|
},
|
|
1218
3587
|
/**
|
|
1219
3588
|
* Extend sandbox timeout/expiration
|
|
1220
|
-
*
|
|
1221
|
-
* @example
|
|
1222
|
-
* ```typescript
|
|
1223
|
-
* // Extend timeout by 15 minutes (default)
|
|
1224
|
-
* await compute.sandbox.extendTimeout('sandbox-123');
|
|
1225
|
-
*
|
|
1226
|
-
* // Extend timeout by custom duration
|
|
1227
|
-
* await compute.sandbox.extendTimeout('sandbox-123', {
|
|
1228
|
-
* duration: 1800000 // 30 minutes
|
|
1229
|
-
* });
|
|
1230
|
-
* ```
|
|
1231
3589
|
*/
|
|
1232
3590
|
extendTimeout: async (sandboxId, options) => {
|
|
1233
|
-
const
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
}
|
|
1240
|
-
return await provider.sandbox.extendTimeout(sandboxId, options);
|
|
3591
|
+
const config = this.getGatewayConfig();
|
|
3592
|
+
const duration = options?.duration ?? 9e5;
|
|
3593
|
+
await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}/extend`, config, {
|
|
3594
|
+
method: "POST",
|
|
3595
|
+
body: JSON.stringify({ duration })
|
|
3596
|
+
});
|
|
1241
3597
|
}
|
|
1242
3598
|
};
|
|
1243
3599
|
}
|
|
1244
|
-
/**
|
|
1245
|
-
* Set default configuration with generic type preservation
|
|
1246
|
-
*/
|
|
1247
|
-
setConfig(config) {
|
|
1248
|
-
if (!config.defaultProvider && !config.provider) {
|
|
1249
|
-
throw new Error("Either defaultProvider or provider must be specified in setConfig");
|
|
1250
|
-
}
|
|
1251
|
-
if (config.defaultProvider && config.provider) {
|
|
1252
|
-
console.warn("Both defaultProvider and provider specified in setConfig. Using defaultProvider. The provider key is deprecated, please use defaultProvider instead.");
|
|
1253
|
-
}
|
|
1254
|
-
const actualProvider = config.defaultProvider || config.provider;
|
|
1255
|
-
this.config = {
|
|
1256
|
-
provider: actualProvider,
|
|
1257
|
-
defaultProvider: actualProvider
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1260
|
-
/**
|
|
1261
|
-
* Get current configuration
|
|
1262
|
-
*/
|
|
1263
|
-
getConfig() {
|
|
1264
|
-
return this.config;
|
|
1265
|
-
}
|
|
1266
|
-
/**
|
|
1267
|
-
* Clear current configuration
|
|
1268
|
-
*/
|
|
1269
|
-
clearConfig() {
|
|
1270
|
-
this.config = null;
|
|
1271
|
-
}
|
|
1272
3600
|
/**
|
|
1273
3601
|
* Lazy auto-configure from environment if not explicitly configured
|
|
1274
3602
|
*/
|
|
1275
3603
|
ensureConfigured() {
|
|
1276
3604
|
if (this.config) return;
|
|
1277
3605
|
if (this.autoConfigured) return;
|
|
1278
|
-
const
|
|
3606
|
+
const config = autoConfigureCompute();
|
|
1279
3607
|
this.autoConfigured = true;
|
|
1280
|
-
if (
|
|
1281
|
-
this.config =
|
|
1282
|
-
provider,
|
|
1283
|
-
defaultProvider: provider
|
|
1284
|
-
};
|
|
3608
|
+
if (config) {
|
|
3609
|
+
this.config = config;
|
|
1285
3610
|
}
|
|
1286
3611
|
}
|
|
1287
3612
|
/**
|
|
1288
|
-
* Get
|
|
3613
|
+
* Get gateway config, throwing if not configured
|
|
1289
3614
|
*/
|
|
1290
|
-
|
|
3615
|
+
getGatewayConfig() {
|
|
1291
3616
|
this.ensureConfigured();
|
|
1292
|
-
|
|
1293
|
-
if (!provider) {
|
|
3617
|
+
if (!this.config) {
|
|
1294
3618
|
throw new Error(
|
|
1295
|
-
|
|
3619
|
+
`No ComputeSDK configuration found.
|
|
3620
|
+
|
|
3621
|
+
Options:
|
|
3622
|
+
1. Zero-config: Set COMPUTESDK_API_KEY and provider credentials (e.g., E2B_API_KEY)
|
|
3623
|
+
2. Explicit: Call compute.setConfig({ provider: "e2b", apiKey: "...", e2b: { apiKey: "..." } })
|
|
3624
|
+
3. Use provider directly: import { e2b } from '@computesdk/e2b'
|
|
3625
|
+
|
|
3626
|
+
Docs: https://computesdk.com/docs/quickstart`
|
|
1296
3627
|
);
|
|
1297
3628
|
}
|
|
1298
|
-
return
|
|
3629
|
+
return this.config;
|
|
3630
|
+
}
|
|
3631
|
+
/**
|
|
3632
|
+
* Explicitly configure the compute singleton
|
|
3633
|
+
*
|
|
3634
|
+
* @example
|
|
3635
|
+
* ```typescript
|
|
3636
|
+
* import { compute } from 'computesdk';
|
|
3637
|
+
*
|
|
3638
|
+
* compute.setConfig({
|
|
3639
|
+
* provider: 'e2b',
|
|
3640
|
+
* apiKey: 'computesdk_xxx',
|
|
3641
|
+
* e2b: { apiKey: 'e2b_xxx' }
|
|
3642
|
+
* });
|
|
3643
|
+
*
|
|
3644
|
+
* const sandbox = await compute.sandbox.create();
|
|
3645
|
+
* ```
|
|
3646
|
+
*/
|
|
3647
|
+
setConfig(config) {
|
|
3648
|
+
const gatewayConfig = createConfigFromExplicit(config);
|
|
3649
|
+
this.config = gatewayConfig;
|
|
3650
|
+
this.autoConfigured = false;
|
|
1299
3651
|
}
|
|
1300
3652
|
};
|
|
1301
3653
|
var singletonInstance = new ComputeManager();
|
|
1302
3654
|
function computeFactory(config) {
|
|
1303
|
-
const
|
|
1304
|
-
|
|
3655
|
+
const gatewayConfig = createConfigFromExplicit(config);
|
|
3656
|
+
const manager = new ComputeManager();
|
|
3657
|
+
manager["config"] = gatewayConfig;
|
|
3658
|
+
return manager;
|
|
1305
3659
|
}
|
|
1306
3660
|
var compute = new Proxy(
|
|
1307
3661
|
computeFactory,
|
|
@@ -1319,256 +3673,12 @@ var compute = new Proxy(
|
|
|
1319
3673
|
}
|
|
1320
3674
|
}
|
|
1321
3675
|
);
|
|
1322
|
-
function createCompute(config) {
|
|
1323
|
-
const manager = new ComputeManager();
|
|
1324
|
-
if (!config) {
|
|
1325
|
-
return manager;
|
|
1326
|
-
}
|
|
1327
|
-
const actualProvider = config.defaultProvider || config.provider;
|
|
1328
|
-
manager["config"] = {
|
|
1329
|
-
provider: actualProvider,
|
|
1330
|
-
defaultProvider: actualProvider
|
|
1331
|
-
};
|
|
1332
|
-
const api = {
|
|
1333
|
-
setConfig: (cfg) => createCompute(cfg),
|
|
1334
|
-
getConfig: () => manager.getConfig(),
|
|
1335
|
-
clearConfig: () => manager.clearConfig(),
|
|
1336
|
-
sandbox: {
|
|
1337
|
-
create: async (params) => {
|
|
1338
|
-
const sandbox = await manager.sandbox.create(params);
|
|
1339
|
-
return sandbox;
|
|
1340
|
-
},
|
|
1341
|
-
getById: async (sandboxId) => {
|
|
1342
|
-
const sandbox = await manager.sandbox.getById(sandboxId);
|
|
1343
|
-
if (!sandbox) return null;
|
|
1344
|
-
return sandbox;
|
|
1345
|
-
},
|
|
1346
|
-
list: async () => {
|
|
1347
|
-
const sandboxes = await manager.sandbox.list();
|
|
1348
|
-
return sandboxes;
|
|
1349
|
-
},
|
|
1350
|
-
destroy: async (sandboxId) => {
|
|
1351
|
-
return await manager.sandbox.destroy(sandboxId);
|
|
1352
|
-
},
|
|
1353
|
-
findOrCreate: async (options) => {
|
|
1354
|
-
const sandbox = await manager.sandbox.findOrCreate(options);
|
|
1355
|
-
return sandbox;
|
|
1356
|
-
},
|
|
1357
|
-
find: async (options) => {
|
|
1358
|
-
const sandbox = await manager.sandbox.find(options);
|
|
1359
|
-
if (!sandbox) return null;
|
|
1360
|
-
return sandbox;
|
|
1361
|
-
},
|
|
1362
|
-
extendTimeout: async (sandboxId, options) => {
|
|
1363
|
-
return await manager.sandbox.extendTimeout(sandboxId, options);
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
};
|
|
1367
|
-
return api;
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
// src/request-handler.ts
|
|
1371
|
-
async function executeAction(body, provider) {
|
|
1372
|
-
try {
|
|
1373
|
-
const { action, sandboxId } = body;
|
|
1374
|
-
if (action === "compute.sandbox.create") {
|
|
1375
|
-
const sandbox2 = await compute.sandbox.create({
|
|
1376
|
-
provider,
|
|
1377
|
-
options: body.options || { runtime: "python" }
|
|
1378
|
-
});
|
|
1379
|
-
return {
|
|
1380
|
-
success: true,
|
|
1381
|
-
sandboxId: sandbox2.sandboxId,
|
|
1382
|
-
provider: provider.name
|
|
1383
|
-
};
|
|
1384
|
-
}
|
|
1385
|
-
if (action === "compute.sandbox.list") {
|
|
1386
|
-
const sandboxes = await compute.sandbox.list(provider);
|
|
1387
|
-
return {
|
|
1388
|
-
success: true,
|
|
1389
|
-
sandboxId: "",
|
|
1390
|
-
provider: provider.name,
|
|
1391
|
-
sandboxes: sandboxes.map((sb) => ({
|
|
1392
|
-
sandboxId: sb.sandboxId,
|
|
1393
|
-
provider: sb.provider
|
|
1394
|
-
}))
|
|
1395
|
-
};
|
|
1396
|
-
}
|
|
1397
|
-
if (action === "compute.sandbox.destroy") {
|
|
1398
|
-
if (!sandboxId) {
|
|
1399
|
-
throw new Error("sandboxId is required for destroy action");
|
|
1400
|
-
}
|
|
1401
|
-
await compute.sandbox.destroy(provider, sandboxId);
|
|
1402
|
-
return {
|
|
1403
|
-
success: true,
|
|
1404
|
-
sandboxId,
|
|
1405
|
-
provider: provider.name
|
|
1406
|
-
};
|
|
1407
|
-
}
|
|
1408
|
-
if (!sandboxId) {
|
|
1409
|
-
throw new Error("sandboxId is required for this action");
|
|
1410
|
-
}
|
|
1411
|
-
const sandbox = await compute.sandbox.getById(provider, sandboxId);
|
|
1412
|
-
if (!sandbox) {
|
|
1413
|
-
throw new Error(`Sandbox ${sandboxId} not found`);
|
|
1414
|
-
}
|
|
1415
|
-
if (action === "compute.sandbox.getInfo") {
|
|
1416
|
-
const result = await sandbox.getInfo();
|
|
1417
|
-
return {
|
|
1418
|
-
success: true,
|
|
1419
|
-
sandboxId,
|
|
1420
|
-
provider: provider.name,
|
|
1421
|
-
info: {
|
|
1422
|
-
id: result.id,
|
|
1423
|
-
provider: result.provider,
|
|
1424
|
-
runtime: result.runtime,
|
|
1425
|
-
status: result.status,
|
|
1426
|
-
createdAt: result.createdAt.toISOString(),
|
|
1427
|
-
timeout: result.timeout,
|
|
1428
|
-
metadata: result.metadata
|
|
1429
|
-
}
|
|
1430
|
-
};
|
|
1431
|
-
}
|
|
1432
|
-
if (action === "compute.sandbox.runCode") {
|
|
1433
|
-
if (!body.code) throw new Error("code is required");
|
|
1434
|
-
const result = await sandbox.runCode(body.code, body.runtime);
|
|
1435
|
-
return {
|
|
1436
|
-
success: true,
|
|
1437
|
-
sandboxId,
|
|
1438
|
-
provider: provider.name,
|
|
1439
|
-
result: {
|
|
1440
|
-
output: result.output,
|
|
1441
|
-
exitCode: result.exitCode,
|
|
1442
|
-
language: result.language
|
|
1443
|
-
}
|
|
1444
|
-
};
|
|
1445
|
-
}
|
|
1446
|
-
if (action === "compute.sandbox.runCommand") {
|
|
1447
|
-
if (!body.command) throw new Error("command is required");
|
|
1448
|
-
const result = await sandbox.runCommand(body.command, body.args, body.commandOptions);
|
|
1449
|
-
return {
|
|
1450
|
-
success: true,
|
|
1451
|
-
sandboxId,
|
|
1452
|
-
provider: provider.name,
|
|
1453
|
-
result: {
|
|
1454
|
-
stdout: result.stdout,
|
|
1455
|
-
stderr: result.stderr,
|
|
1456
|
-
exitCode: result.exitCode,
|
|
1457
|
-
durationMs: result.durationMs
|
|
1458
|
-
}
|
|
1459
|
-
};
|
|
1460
|
-
}
|
|
1461
|
-
if (action === "compute.sandbox.filesystem.readFile") {
|
|
1462
|
-
if (!body.path) throw new Error("path is required");
|
|
1463
|
-
const result = await sandbox.filesystem.readFile(body.path);
|
|
1464
|
-
return {
|
|
1465
|
-
success: true,
|
|
1466
|
-
sandboxId,
|
|
1467
|
-
provider: provider.name,
|
|
1468
|
-
fileContent: result
|
|
1469
|
-
};
|
|
1470
|
-
}
|
|
1471
|
-
if (action === "compute.sandbox.filesystem.writeFile") {
|
|
1472
|
-
if (!body.path) throw new Error("path is required");
|
|
1473
|
-
if (body.content === void 0) throw new Error("content is required");
|
|
1474
|
-
await sandbox.filesystem.writeFile(body.path, body.content);
|
|
1475
|
-
return { success: true, sandboxId, provider: provider.name };
|
|
1476
|
-
}
|
|
1477
|
-
if (action === "compute.sandbox.filesystem.mkdir") {
|
|
1478
|
-
if (!body.path) throw new Error("path is required");
|
|
1479
|
-
await sandbox.filesystem.mkdir(body.path);
|
|
1480
|
-
return { success: true, sandboxId, provider: provider.name };
|
|
1481
|
-
}
|
|
1482
|
-
if (action === "compute.sandbox.filesystem.readdir") {
|
|
1483
|
-
if (!body.path) throw new Error("path is required");
|
|
1484
|
-
const result = await sandbox.filesystem.readdir(body.path);
|
|
1485
|
-
return {
|
|
1486
|
-
success: true,
|
|
1487
|
-
sandboxId,
|
|
1488
|
-
provider: provider.name,
|
|
1489
|
-
files: result.map((entry) => ({
|
|
1490
|
-
name: entry.name,
|
|
1491
|
-
path: entry.path,
|
|
1492
|
-
isDirectory: entry.isDirectory,
|
|
1493
|
-
size: entry.size,
|
|
1494
|
-
lastModified: entry.lastModified.toISOString()
|
|
1495
|
-
}))
|
|
1496
|
-
};
|
|
1497
|
-
}
|
|
1498
|
-
if (action === "compute.sandbox.filesystem.exists") {
|
|
1499
|
-
if (!body.path) throw new Error("path is required");
|
|
1500
|
-
const result = await sandbox.filesystem.exists(body.path);
|
|
1501
|
-
return {
|
|
1502
|
-
success: true,
|
|
1503
|
-
sandboxId,
|
|
1504
|
-
provider: provider.name,
|
|
1505
|
-
exists: result
|
|
1506
|
-
};
|
|
1507
|
-
}
|
|
1508
|
-
if (action === "compute.sandbox.filesystem.remove") {
|
|
1509
|
-
if (!body.path) throw new Error("path is required");
|
|
1510
|
-
await sandbox.filesystem.remove(body.path);
|
|
1511
|
-
return { success: true, sandboxId, provider: provider.name };
|
|
1512
|
-
}
|
|
1513
|
-
throw new Error(`Unknown action: ${action}`);
|
|
1514
|
-
} catch (error) {
|
|
1515
|
-
return {
|
|
1516
|
-
success: false,
|
|
1517
|
-
error: error instanceof Error ? error.message : "Unknown error occurred",
|
|
1518
|
-
sandboxId: body.sandboxId || "",
|
|
1519
|
-
provider: provider.name
|
|
1520
|
-
};
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
async function handleComputeRequest(paramsOrRequestOrBody, provider) {
|
|
1524
|
-
if (typeof paramsOrRequestOrBody === "object" && "request" in paramsOrRequestOrBody && "provider" in paramsOrRequestOrBody) {
|
|
1525
|
-
const params = paramsOrRequestOrBody;
|
|
1526
|
-
return await executeAction(params.request, params.provider);
|
|
1527
|
-
}
|
|
1528
|
-
if (!provider) {
|
|
1529
|
-
throw new Error("Provider is required when not using object-style API");
|
|
1530
|
-
}
|
|
1531
|
-
const requestOrBody = paramsOrRequestOrBody;
|
|
1532
|
-
try {
|
|
1533
|
-
let body;
|
|
1534
|
-
if (requestOrBody instanceof Request) {
|
|
1535
|
-
if (requestOrBody.method !== "POST") {
|
|
1536
|
-
return Response.json({
|
|
1537
|
-
success: false,
|
|
1538
|
-
error: "Only POST requests are supported",
|
|
1539
|
-
sandboxId: "",
|
|
1540
|
-
provider: provider.name
|
|
1541
|
-
}, { status: 405 });
|
|
1542
|
-
}
|
|
1543
|
-
try {
|
|
1544
|
-
body = await requestOrBody.json();
|
|
1545
|
-
} catch (parseError) {
|
|
1546
|
-
return Response.json({
|
|
1547
|
-
success: false,
|
|
1548
|
-
error: "Invalid JSON in request body",
|
|
1549
|
-
sandboxId: "",
|
|
1550
|
-
provider: provider.name
|
|
1551
|
-
}, { status: 400 });
|
|
1552
|
-
}
|
|
1553
|
-
} else {
|
|
1554
|
-
body = requestOrBody;
|
|
1555
|
-
}
|
|
1556
|
-
const result = await executeAction(body, provider);
|
|
1557
|
-
return Response.json(result, {
|
|
1558
|
-
status: result.success ? 200 : 500
|
|
1559
|
-
});
|
|
1560
|
-
} catch (error) {
|
|
1561
|
-
return Response.json({
|
|
1562
|
-
success: false,
|
|
1563
|
-
error: error instanceof Error ? error.message : "Request handling failed",
|
|
1564
|
-
sandboxId: "",
|
|
1565
|
-
provider: provider.name
|
|
1566
|
-
}, { status: 500 });
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
3676
|
export {
|
|
1570
3677
|
CommandExitError,
|
|
3678
|
+
FileWatcher,
|
|
1571
3679
|
GATEWAY_URL,
|
|
3680
|
+
Sandbox as GatewaySandbox,
|
|
3681
|
+
MessageType,
|
|
1572
3682
|
PROVIDER_AUTH,
|
|
1573
3683
|
PROVIDER_DASHBOARD_URLS,
|
|
1574
3684
|
PROVIDER_ENV_MAP,
|
|
@@ -1577,19 +3687,17 @@ export {
|
|
|
1577
3687
|
PROVIDER_NAMES,
|
|
1578
3688
|
PROVIDER_PRIORITY,
|
|
1579
3689
|
Sandbox,
|
|
3690
|
+
SignalService,
|
|
3691
|
+
TerminalInstance,
|
|
1580
3692
|
autoConfigureCompute,
|
|
1581
3693
|
buildProviderHeaders,
|
|
1582
|
-
calculateBackoff,
|
|
1583
3694
|
compute,
|
|
1584
|
-
|
|
1585
|
-
createProvider,
|
|
1586
|
-
createProviderFromConfig,
|
|
3695
|
+
decodeBinaryMessage,
|
|
1587
3696
|
detectProvider,
|
|
1588
|
-
|
|
3697
|
+
encodeBinaryMessage,
|
|
1589
3698
|
getMissingEnvVars,
|
|
1590
3699
|
getProviderConfigFromEnv,
|
|
1591
3700
|
getProviderHeaders,
|
|
1592
|
-
handleComputeRequest,
|
|
1593
3701
|
isCommandExitError,
|
|
1594
3702
|
isGatewayModeEnabled,
|
|
1595
3703
|
isProviderAuthComplete,
|