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