@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 +14 -1
- package/dist/index.d.mts +69 -3
- package/dist/index.d.ts +69 -3
- package/dist/index.js +127 -13
- package/dist/index.mjs +125 -13
- package/package.json +1 -1
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` |
|
|
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<
|
|
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
|
-
|
|
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<
|
|
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
|
-
|
|
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
|
|
90
|
-
throw new Error(`DELETE ${path} failed (${res.status}): ${
|
|
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
|
|
51
|
-
throw new Error(`DELETE ${path} failed (${res.status}): ${
|
|
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