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