@sandbox-engine/sdk 0.1.3 → 0.1.4

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.d.mts CHANGED
@@ -8,7 +8,7 @@ declare class HttpClient {
8
8
  private headers;
9
9
  get<T>(path: string, query?: Record<string, string>): Promise<T>;
10
10
  post<T>(path: string, body?: unknown, timeoutMs?: number): Promise<T>;
11
- delete(path: string, query?: Record<string, string>): Promise<void>;
11
+ delete<T = void>(path: string, query?: Record<string, string>): Promise<T>;
12
12
  openWebSocket(path: string): WebSocket;
13
13
  }
14
14
 
@@ -31,6 +31,32 @@ interface SandboxOptions {
31
31
  dockerfile?: string;
32
32
  timeout?: number;
33
33
  }
34
+ interface SandboxConnectOptions {
35
+ serverUrl?: string;
36
+ apiKey?: string;
37
+ }
38
+ interface StartProcessOptions {
39
+ command?: string;
40
+ cmd?: string;
41
+ args?: string[];
42
+ cwd?: string;
43
+ env?: Record<string, string>;
44
+ processId?: string;
45
+ }
46
+ interface BackgroundProcessInfo {
47
+ id: string;
48
+ pid: number;
49
+ command: string;
50
+ status: 'running' | 'exited';
51
+ exitCode: number | null;
52
+ }
53
+ interface WaitForPortOptions {
54
+ host?: string;
55
+ timeout?: number;
56
+ }
57
+ interface WaitForLogOptions {
58
+ timeout?: number;
59
+ }
34
60
  interface ProxyEnvResult {
35
61
  proxyBase: string;
36
62
  expiresAt: string;
@@ -99,6 +125,37 @@ declare class Process extends EventEmitter {
99
125
  wait(): Promise<ExecResult>;
100
126
  }
101
127
 
128
+ declare class BackgroundProcess {
129
+ private readonly sandboxId;
130
+ private readonly client;
131
+ readonly id: string;
132
+ readonly pid: number;
133
+ readonly command: string;
134
+ constructor(sandboxId: string, client: HttpClient, info: BackgroundProcessInfo);
135
+ private path;
136
+ getStatus(): Promise<BackgroundProcessInfo>;
137
+ getLogs(): Promise<{
138
+ stdout: string;
139
+ stderr: string;
140
+ }>;
141
+ kill(): Promise<void>;
142
+ waitForExit(timeout?: number): Promise<{
143
+ exitCode: number;
144
+ }>;
145
+ waitForPort(port: number, opts?: WaitForPortOptions): Promise<void>;
146
+ waitForLog(pattern: string | RegExp, opts?: WaitForLogOptions): Promise<string>;
147
+ }
148
+ declare class ProcessManager {
149
+ private readonly sandboxId;
150
+ private readonly client;
151
+ constructor(sandboxId: string, client: HttpClient);
152
+ start(opts: StartProcessOptions): Promise<BackgroundProcess>;
153
+ list(): Promise<BackgroundProcessInfo[]>;
154
+ get(processId: string): Promise<BackgroundProcess>;
155
+ kill(processId: string): Promise<void>;
156
+ killAll(): Promise<number>;
157
+ }
158
+
102
159
  declare class Terminal extends EventEmitter {
103
160
  private ws;
104
161
  constructor(ws: WebSocket);
@@ -121,8 +178,15 @@ declare class Sandbox {
121
178
  private readonly client;
122
179
  readonly id: string;
123
180
  readonly fs: Filesystem;
181
+ readonly processes: ProcessManager;
124
182
  private constructor();
183
+ private static resolveClient;
125
184
  static create(opts?: SandboxOptions): Promise<Sandbox>;
185
+ /**
186
+ * Reconnect to an existing sandbox by ID.
187
+ * Works after server restart if the container is still running (auto-adopt).
188
+ */
189
+ static connect(sandboxId: string, opts?: SandboxConnectOptions): Promise<Sandbox>;
126
190
  exec(cmd: string, opts?: ExecOptions): Promise<ExecResult>;
127
191
  exec(cmd: string, opts: ExecOptions & {
128
192
  stream: true;
@@ -141,4 +205,4 @@ declare class Sandbox {
141
205
  close(): Promise<void>;
142
206
  }
143
207
 
144
- export { type ExecOptions, type ExecResult, type FileEntry, type FileWriteEntry, type FileWriteManyResult, Filesystem, type PortMapping, Process, type ProxyEnvResult, Sandbox, type SandboxOptions, Terminal, type WsMessage, type WsMessageType };
208
+ export { BackgroundProcess, type BackgroundProcessInfo, type ExecOptions, type ExecResult, type FileEntry, type FileWriteEntry, type FileWriteManyResult, Filesystem, type PortMapping, Process, ProcessManager, type ProxyEnvResult, Sandbox, type SandboxConnectOptions, type SandboxOptions, type StartProcessOptions, Terminal, type WaitForLogOptions, type WaitForPortOptions, type WsMessage, type WsMessageType };
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ declare class HttpClient {
8
8
  private headers;
9
9
  get<T>(path: string, query?: Record<string, string>): Promise<T>;
10
10
  post<T>(path: string, body?: unknown, timeoutMs?: number): Promise<T>;
11
- delete(path: string, query?: Record<string, string>): Promise<void>;
11
+ delete<T = void>(path: string, query?: Record<string, string>): Promise<T>;
12
12
  openWebSocket(path: string): WebSocket;
13
13
  }
14
14
 
@@ -31,6 +31,32 @@ interface SandboxOptions {
31
31
  dockerfile?: string;
32
32
  timeout?: number;
33
33
  }
34
+ interface SandboxConnectOptions {
35
+ serverUrl?: string;
36
+ apiKey?: string;
37
+ }
38
+ interface StartProcessOptions {
39
+ command?: string;
40
+ cmd?: string;
41
+ args?: string[];
42
+ cwd?: string;
43
+ env?: Record<string, string>;
44
+ processId?: string;
45
+ }
46
+ interface BackgroundProcessInfo {
47
+ id: string;
48
+ pid: number;
49
+ command: string;
50
+ status: 'running' | 'exited';
51
+ exitCode: number | null;
52
+ }
53
+ interface WaitForPortOptions {
54
+ host?: string;
55
+ timeout?: number;
56
+ }
57
+ interface WaitForLogOptions {
58
+ timeout?: number;
59
+ }
34
60
  interface ProxyEnvResult {
35
61
  proxyBase: string;
36
62
  expiresAt: string;
@@ -99,6 +125,37 @@ declare class Process extends EventEmitter {
99
125
  wait(): Promise<ExecResult>;
100
126
  }
101
127
 
128
+ declare class BackgroundProcess {
129
+ private readonly sandboxId;
130
+ private readonly client;
131
+ readonly id: string;
132
+ readonly pid: number;
133
+ readonly command: string;
134
+ constructor(sandboxId: string, client: HttpClient, info: BackgroundProcessInfo);
135
+ private path;
136
+ getStatus(): Promise<BackgroundProcessInfo>;
137
+ getLogs(): Promise<{
138
+ stdout: string;
139
+ stderr: string;
140
+ }>;
141
+ kill(): Promise<void>;
142
+ waitForExit(timeout?: number): Promise<{
143
+ exitCode: number;
144
+ }>;
145
+ waitForPort(port: number, opts?: WaitForPortOptions): Promise<void>;
146
+ waitForLog(pattern: string | RegExp, opts?: WaitForLogOptions): Promise<string>;
147
+ }
148
+ declare class ProcessManager {
149
+ private readonly sandboxId;
150
+ private readonly client;
151
+ constructor(sandboxId: string, client: HttpClient);
152
+ start(opts: StartProcessOptions): Promise<BackgroundProcess>;
153
+ list(): Promise<BackgroundProcessInfo[]>;
154
+ get(processId: string): Promise<BackgroundProcess>;
155
+ kill(processId: string): Promise<void>;
156
+ killAll(): Promise<number>;
157
+ }
158
+
102
159
  declare class Terminal extends EventEmitter {
103
160
  private ws;
104
161
  constructor(ws: WebSocket);
@@ -121,8 +178,15 @@ declare class Sandbox {
121
178
  private readonly client;
122
179
  readonly id: string;
123
180
  readonly fs: Filesystem;
181
+ readonly processes: ProcessManager;
124
182
  private constructor();
183
+ private static resolveClient;
125
184
  static create(opts?: SandboxOptions): Promise<Sandbox>;
185
+ /**
186
+ * Reconnect to an existing sandbox by ID.
187
+ * Works after server restart if the container is still running (auto-adopt).
188
+ */
189
+ static connect(sandboxId: string, opts?: SandboxConnectOptions): Promise<Sandbox>;
126
190
  exec(cmd: string, opts?: ExecOptions): Promise<ExecResult>;
127
191
  exec(cmd: string, opts: ExecOptions & {
128
192
  stream: true;
@@ -141,4 +205,4 @@ declare class Sandbox {
141
205
  close(): Promise<void>;
142
206
  }
143
207
 
144
- export { type ExecOptions, type ExecResult, type FileEntry, type FileWriteEntry, type FileWriteManyResult, Filesystem, type PortMapping, Process, type ProxyEnvResult, Sandbox, type SandboxOptions, Terminal, type WsMessage, type WsMessageType };
208
+ export { BackgroundProcess, type BackgroundProcessInfo, type ExecOptions, type ExecResult, type FileEntry, type FileWriteEntry, type FileWriteManyResult, Filesystem, type PortMapping, Process, ProcessManager, type ProxyEnvResult, Sandbox, type SandboxConnectOptions, type SandboxOptions, type StartProcessOptions, Terminal, type WaitForLogOptions, type WaitForPortOptions, type WsMessage, type WsMessageType };
package/dist/index.js CHANGED
@@ -30,8 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ BackgroundProcess: () => BackgroundProcess,
33
34
  Filesystem: () => Filesystem,
34
35
  Process: () => Process,
36
+ ProcessManager: () => ProcessManager,
35
37
  Sandbox: () => Sandbox,
36
38
  Terminal: () => Terminal
37
39
  });
@@ -86,9 +88,15 @@ var HttpClient = class {
86
88
  signal: AbortSignal.timeout(3e4)
87
89
  });
88
90
  if (!res.ok && res.status !== 404) {
89
- const text = await res.text();
90
- throw new Error(`DELETE ${path} failed (${res.status}): ${text}`);
91
+ const text2 = await res.text();
92
+ throw new Error(`DELETE ${path} failed (${res.status}): ${text2}`);
93
+ }
94
+ if (res.status === 204 || res.headers.get("content-length") === "0") {
95
+ return void 0;
91
96
  }
97
+ const text = await res.text();
98
+ if (!text) return void 0;
99
+ return JSON.parse(text);
92
100
  }
93
101
  openWebSocket(path) {
94
102
  const wsUrl = this.baseUrl.replace(/^http/, "ws") + path;
@@ -186,6 +194,89 @@ var Process = class extends import_node_events.EventEmitter {
186
194
  }
187
195
  };
188
196
 
197
+ // src/background-process.ts
198
+ var BackgroundProcess = class {
199
+ constructor(sandboxId, client, info) {
200
+ this.sandboxId = sandboxId;
201
+ this.client = client;
202
+ this.id = info.id;
203
+ this.pid = info.pid;
204
+ this.command = info.command;
205
+ }
206
+ sandboxId;
207
+ client;
208
+ id;
209
+ pid;
210
+ command;
211
+ path(suffix) {
212
+ return `/api/sandboxes/${this.sandboxId}/processes/${this.id}${suffix}`;
213
+ }
214
+ async getStatus() {
215
+ return this.client.get(this.path(""));
216
+ }
217
+ async getLogs() {
218
+ return this.client.get(this.path("/logs"));
219
+ }
220
+ async kill() {
221
+ await this.client.delete(this.path(""));
222
+ }
223
+ async waitForExit(timeout) {
224
+ return this.client.post(this.path("/wait-exit"), { timeout }, (timeout ?? 3e5) + 5e3);
225
+ }
226
+ async waitForPort(port, opts = {}) {
227
+ await this.client.post(
228
+ this.path("/wait-for-port"),
229
+ { port, host: opts.host, timeout: opts.timeout },
230
+ (opts.timeout ?? 3e4) + 5e3
231
+ );
232
+ }
233
+ async waitForLog(pattern, opts = {}) {
234
+ const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
235
+ const res = await this.client.post(
236
+ this.path("/wait-for-log"),
237
+ { pattern: patternStr, timeout: opts.timeout },
238
+ (opts.timeout ?? 3e4) + 5e3
239
+ );
240
+ return res.match;
241
+ }
242
+ };
243
+ var ProcessManager = class {
244
+ constructor(sandboxId, client) {
245
+ this.sandboxId = sandboxId;
246
+ this.client = client;
247
+ }
248
+ sandboxId;
249
+ client;
250
+ async start(opts) {
251
+ const info = await this.client.post(
252
+ `/api/sandboxes/${this.sandboxId}/processes`,
253
+ opts
254
+ );
255
+ return new BackgroundProcess(this.sandboxId, this.client, info);
256
+ }
257
+ async list() {
258
+ const res = await this.client.get(
259
+ `/api/sandboxes/${this.sandboxId}/processes`
260
+ );
261
+ return res.processes;
262
+ }
263
+ async get(processId) {
264
+ const info = await this.client.get(
265
+ `/api/sandboxes/${this.sandboxId}/processes/${processId}`
266
+ );
267
+ return new BackgroundProcess(this.sandboxId, this.client, info);
268
+ }
269
+ async kill(processId) {
270
+ await this.client.delete(`/api/sandboxes/${this.sandboxId}/processes/${processId}`);
271
+ }
272
+ async killAll() {
273
+ const res = await this.client.delete(
274
+ `/api/sandboxes/${this.sandboxId}/processes`
275
+ );
276
+ return res?.killed ?? 0;
277
+ }
278
+ };
279
+
189
280
  // src/terminal.ts
190
281
  var import_node_events2 = require("events");
191
282
  var Terminal = class extends import_node_events2.EventEmitter {
@@ -227,26 +318,41 @@ var Sandbox = class _Sandbox {
227
318
  this.client = client;
228
319
  this.id = id;
229
320
  this.fs = new Filesystem(id, client);
321
+ this.processes = new ProcessManager(id, client);
230
322
  }
231
323
  client;
232
324
  id;
233
325
  fs;
326
+ processes;
327
+ static resolveClient(opts = {}) {
328
+ const serverUrl = opts.serverUrl ?? process.env.SANDBOX_SERVER_URL ?? "http://localhost:4000";
329
+ const apiKey = opts.apiKey ?? process.env.SANDBOX_API_KEY ?? "dev-api-key";
330
+ return new HttpClient(serverUrl, apiKey);
331
+ }
234
332
  static async create(opts = {}) {
235
- const {
236
- serverUrl = process.env.SANDBOX_SERVER_URL ?? "http://localhost:4000",
237
- apiKey = process.env.SANDBOX_API_KEY ?? "dev-api-key",
238
- template,
239
- dockerfile,
240
- timeout
241
- } = opts;
242
- const client = new HttpClient(serverUrl, apiKey);
333
+ const client = _Sandbox.resolveClient(opts);
243
334
  const data = await client.post("/api/sandboxes", {
244
- template,
245
- dockerfile,
246
- timeout
335
+ template: opts.template,
336
+ dockerfile: opts.dockerfile,
337
+ timeout: opts.timeout
247
338
  });
248
339
  return new _Sandbox(client, data.id);
249
340
  }
341
+ /**
342
+ * Reconnect to an existing sandbox by ID.
343
+ * Works after server restart if the container is still running (auto-adopt).
344
+ */
345
+ static async connect(sandboxId, opts = {}) {
346
+ const client = _Sandbox.resolveClient(opts);
347
+ const data = await client.post(
348
+ `/api/sandboxes/${sandboxId}/connect`,
349
+ {}
350
+ );
351
+ if (data.status === "stopped") {
352
+ throw new Error(`Sandbox ${sandboxId} is stopped`);
353
+ }
354
+ return new _Sandbox(client, data.id);
355
+ }
250
356
  async exec(cmd, opts = {}) {
251
357
  const { args, cwd, env, timeout, stream } = opts;
252
358
  if (stream) {
@@ -325,8 +431,10 @@ var Sandbox = class _Sandbox {
325
431
  };
326
432
  // Annotate the CommonJS export names for ESM import in node:
327
433
  0 && (module.exports = {
434
+ BackgroundProcess,
328
435
  Filesystem,
329
436
  Process,
437
+ ProcessManager,
330
438
  Sandbox,
331
439
  Terminal
332
440
  });
package/dist/index.mjs CHANGED
@@ -47,9 +47,15 @@ var HttpClient = class {
47
47
  signal: AbortSignal.timeout(3e4)
48
48
  });
49
49
  if (!res.ok && res.status !== 404) {
50
- const text = await res.text();
51
- throw new Error(`DELETE ${path} failed (${res.status}): ${text}`);
50
+ const text2 = await res.text();
51
+ throw new Error(`DELETE ${path} failed (${res.status}): ${text2}`);
52
+ }
53
+ if (res.status === 204 || res.headers.get("content-length") === "0") {
54
+ return void 0;
52
55
  }
56
+ const text = await res.text();
57
+ if (!text) return void 0;
58
+ return JSON.parse(text);
53
59
  }
54
60
  openWebSocket(path) {
55
61
  const wsUrl = this.baseUrl.replace(/^http/, "ws") + path;
@@ -147,6 +153,89 @@ var Process = class extends EventEmitter {
147
153
  }
148
154
  };
149
155
 
156
+ // src/background-process.ts
157
+ var BackgroundProcess = class {
158
+ constructor(sandboxId, client, info) {
159
+ this.sandboxId = sandboxId;
160
+ this.client = client;
161
+ this.id = info.id;
162
+ this.pid = info.pid;
163
+ this.command = info.command;
164
+ }
165
+ sandboxId;
166
+ client;
167
+ id;
168
+ pid;
169
+ command;
170
+ path(suffix) {
171
+ return `/api/sandboxes/${this.sandboxId}/processes/${this.id}${suffix}`;
172
+ }
173
+ async getStatus() {
174
+ return this.client.get(this.path(""));
175
+ }
176
+ async getLogs() {
177
+ return this.client.get(this.path("/logs"));
178
+ }
179
+ async kill() {
180
+ await this.client.delete(this.path(""));
181
+ }
182
+ async waitForExit(timeout) {
183
+ return this.client.post(this.path("/wait-exit"), { timeout }, (timeout ?? 3e5) + 5e3);
184
+ }
185
+ async waitForPort(port, opts = {}) {
186
+ await this.client.post(
187
+ this.path("/wait-for-port"),
188
+ { port, host: opts.host, timeout: opts.timeout },
189
+ (opts.timeout ?? 3e4) + 5e3
190
+ );
191
+ }
192
+ async waitForLog(pattern, opts = {}) {
193
+ const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
194
+ const res = await this.client.post(
195
+ this.path("/wait-for-log"),
196
+ { pattern: patternStr, timeout: opts.timeout },
197
+ (opts.timeout ?? 3e4) + 5e3
198
+ );
199
+ return res.match;
200
+ }
201
+ };
202
+ var ProcessManager = class {
203
+ constructor(sandboxId, client) {
204
+ this.sandboxId = sandboxId;
205
+ this.client = client;
206
+ }
207
+ sandboxId;
208
+ client;
209
+ async start(opts) {
210
+ const info = await this.client.post(
211
+ `/api/sandboxes/${this.sandboxId}/processes`,
212
+ opts
213
+ );
214
+ return new BackgroundProcess(this.sandboxId, this.client, info);
215
+ }
216
+ async list() {
217
+ const res = await this.client.get(
218
+ `/api/sandboxes/${this.sandboxId}/processes`
219
+ );
220
+ return res.processes;
221
+ }
222
+ async get(processId) {
223
+ const info = await this.client.get(
224
+ `/api/sandboxes/${this.sandboxId}/processes/${processId}`
225
+ );
226
+ return new BackgroundProcess(this.sandboxId, this.client, info);
227
+ }
228
+ async kill(processId) {
229
+ await this.client.delete(`/api/sandboxes/${this.sandboxId}/processes/${processId}`);
230
+ }
231
+ async killAll() {
232
+ const res = await this.client.delete(
233
+ `/api/sandboxes/${this.sandboxId}/processes`
234
+ );
235
+ return res?.killed ?? 0;
236
+ }
237
+ };
238
+
150
239
  // src/terminal.ts
151
240
  import { EventEmitter as EventEmitter2 } from "events";
152
241
  var Terminal = class extends EventEmitter2 {
@@ -188,26 +277,41 @@ var Sandbox = class _Sandbox {
188
277
  this.client = client;
189
278
  this.id = id;
190
279
  this.fs = new Filesystem(id, client);
280
+ this.processes = new ProcessManager(id, client);
191
281
  }
192
282
  client;
193
283
  id;
194
284
  fs;
285
+ processes;
286
+ static resolveClient(opts = {}) {
287
+ const serverUrl = opts.serverUrl ?? process.env.SANDBOX_SERVER_URL ?? "http://localhost:4000";
288
+ const apiKey = opts.apiKey ?? process.env.SANDBOX_API_KEY ?? "dev-api-key";
289
+ return new HttpClient(serverUrl, apiKey);
290
+ }
195
291
  static async create(opts = {}) {
196
- const {
197
- serverUrl = process.env.SANDBOX_SERVER_URL ?? "http://localhost:4000",
198
- apiKey = process.env.SANDBOX_API_KEY ?? "dev-api-key",
199
- template,
200
- dockerfile,
201
- timeout
202
- } = opts;
203
- const client = new HttpClient(serverUrl, apiKey);
292
+ const client = _Sandbox.resolveClient(opts);
204
293
  const data = await client.post("/api/sandboxes", {
205
- template,
206
- dockerfile,
207
- timeout
294
+ template: opts.template,
295
+ dockerfile: opts.dockerfile,
296
+ timeout: opts.timeout
208
297
  });
209
298
  return new _Sandbox(client, data.id);
210
299
  }
300
+ /**
301
+ * Reconnect to an existing sandbox by ID.
302
+ * Works after server restart if the container is still running (auto-adopt).
303
+ */
304
+ static async connect(sandboxId, opts = {}) {
305
+ const client = _Sandbox.resolveClient(opts);
306
+ const data = await client.post(
307
+ `/api/sandboxes/${sandboxId}/connect`,
308
+ {}
309
+ );
310
+ if (data.status === "stopped") {
311
+ throw new Error(`Sandbox ${sandboxId} is stopped`);
312
+ }
313
+ return new _Sandbox(client, data.id);
314
+ }
211
315
  async exec(cmd, opts = {}) {
212
316
  const { args, cwd, env, timeout, stream } = opts;
213
317
  if (stream) {
@@ -285,8 +389,10 @@ var Sandbox = class _Sandbox {
285
389
  }
286
390
  };
287
391
  export {
392
+ BackgroundProcess,
288
393
  Filesystem,
289
394
  Process,
395
+ ProcessManager,
290
396
  Sandbox,
291
397
  Terminal
292
398
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sandbox-engine/sdk",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "SDK for creating and managing isolated sandbox environments",
5
5
  "keywords": ["sandbox", "docker", "code-execution", "isolated", "e2b"],
6
6
  "license": "MIT",