@sandbox-engine/sdk 0.1.2 → 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/README.md CHANGED
@@ -70,7 +70,7 @@ Creates and starts a new sandbox. Returns when the sandbox is ready.
70
70
  | `apiKey` | `string` | `$SANDBOX_API_KEY` or `dev-api-key` | API key |
71
71
  | `template` | `string` | `'base'` | Pre-built Docker image tag |
72
72
  | `dockerfile` | `string` | — | Custom Dockerfile content |
73
- | `timeout` | `number` | `300000` | TTL in ms before auto-destroy |
73
+ | `timeout` | `number` | `300000` | Timer duration in ms. Resets on every API call. Use `0` to never expire. |
74
74
 
75
75
  ---
76
76
 
@@ -146,6 +146,19 @@ const ports = await sandbox.ports.list()
146
146
 
147
147
  ---
148
148
 
149
+ ### `sandbox.keepalive(timeout?)`
150
+
151
+ Resets the expiry countdown from now. Use when the orchestrator needs to keep the sandbox alive without doing real work (e.g. an LLM reasoning between steps).
152
+
153
+ ```ts
154
+ await sandbox.keepalive() // reset with original timeout
155
+ await sandbox.keepalive(10 * 60_000) // extend to 10 min from now
156
+ ```
157
+
158
+ Any API call (`exec`, `fs`, `ports`, etc.) also resets the timer automatically.
159
+
160
+ ---
161
+
149
162
  ### `sandbox.close()`
150
163
 
151
164
  Destroys the sandbox and frees all resources. Always call this when done.
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);
@@ -112,7 +169,8 @@ interface SandboxApiResponse {
112
169
  status: string;
113
170
  template: string;
114
171
  createdAt: string;
115
- expiresAt: string;
172
+ timeout: number;
173
+ expiresAt: string | null;
116
174
  agentUrl: string;
117
175
  ports: Record<string, number>;
118
176
  }
@@ -120,8 +178,15 @@ declare class Sandbox {
120
178
  private readonly client;
121
179
  readonly id: string;
122
180
  readonly fs: Filesystem;
181
+ readonly processes: ProcessManager;
123
182
  private constructor();
183
+ private static resolveClient;
124
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>;
125
190
  exec(cmd: string, opts?: ExecOptions): Promise<ExecResult>;
126
191
  exec(cmd: string, opts: ExecOptions & {
127
192
  stream: true;
@@ -136,7 +201,8 @@ declare class Sandbox {
136
201
  unexpose: (containerPort: number) => Promise<void>;
137
202
  };
138
203
  info(): Promise<SandboxApiResponse>;
204
+ keepalive(timeout?: number): Promise<SandboxApiResponse>;
139
205
  close(): Promise<void>;
140
206
  }
141
207
 
142
- 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);
@@ -112,7 +169,8 @@ interface SandboxApiResponse {
112
169
  status: string;
113
170
  template: string;
114
171
  createdAt: string;
115
- expiresAt: string;
172
+ timeout: number;
173
+ expiresAt: string | null;
116
174
  agentUrl: string;
117
175
  ports: Record<string, number>;
118
176
  }
@@ -120,8 +178,15 @@ declare class Sandbox {
120
178
  private readonly client;
121
179
  readonly id: string;
122
180
  readonly fs: Filesystem;
181
+ readonly processes: ProcessManager;
123
182
  private constructor();
183
+ private static resolveClient;
124
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>;
125
190
  exec(cmd: string, opts?: ExecOptions): Promise<ExecResult>;
126
191
  exec(cmd: string, opts: ExecOptions & {
127
192
  stream: true;
@@ -136,7 +201,8 @@ declare class Sandbox {
136
201
  unexpose: (containerPort: number) => Promise<void>;
137
202
  };
138
203
  info(): Promise<SandboxApiResponse>;
204
+ keepalive(timeout?: number): Promise<SandboxApiResponse>;
139
205
  close(): Promise<void>;
140
206
  }
141
207
 
142
- 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) {
@@ -313,14 +419,22 @@ var Sandbox = class _Sandbox {
313
419
  async info() {
314
420
  return this.client.get(`/api/sandboxes/${this.id}`);
315
421
  }
422
+ async keepalive(timeout) {
423
+ return this.client.post(
424
+ `/api/sandboxes/${this.id}/keepalive`,
425
+ timeout !== void 0 ? { timeout } : {}
426
+ );
427
+ }
316
428
  async close() {
317
429
  await this.client.delete(`/api/sandboxes/${this.id}`);
318
430
  }
319
431
  };
320
432
  // Annotate the CommonJS export names for ESM import in node:
321
433
  0 && (module.exports = {
434
+ BackgroundProcess,
322
435
  Filesystem,
323
436
  Process,
437
+ ProcessManager,
324
438
  Sandbox,
325
439
  Terminal
326
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) {
@@ -274,13 +378,21 @@ var Sandbox = class _Sandbox {
274
378
  async info() {
275
379
  return this.client.get(`/api/sandboxes/${this.id}`);
276
380
  }
381
+ async keepalive(timeout) {
382
+ return this.client.post(
383
+ `/api/sandboxes/${this.id}/keepalive`,
384
+ timeout !== void 0 ? { timeout } : {}
385
+ );
386
+ }
277
387
  async close() {
278
388
  await this.client.delete(`/api/sandboxes/${this.id}`);
279
389
  }
280
390
  };
281
391
  export {
392
+ BackgroundProcess,
282
393
  Filesystem,
283
394
  Process,
395
+ ProcessManager,
284
396
  Sandbox,
285
397
  Terminal
286
398
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sandbox-engine/sdk",
3
- "version": "0.1.2",
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",