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