@lizard-build/sdk 0.1.1
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 +479 -0
- package/dist/index.d.ts +479 -0
- package/dist/index.js +611 -0
- package/dist/index.mjs +579 -0
- package/package.json +38 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
var DEFAULT_API_URL = "https://api.lizard.run";
|
|
3
|
+
var DEFAULT_SANDBOX_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
4
|
+
var ConnectionConfig = class {
|
|
5
|
+
constructor(opts) {
|
|
6
|
+
this.apiKey = opts?.apiKey ?? process.env.LIZARD_API_KEY ?? "";
|
|
7
|
+
this.apiUrl = opts?.apiUrl ?? process.env.LIZARD_API_URL ?? DEFAULT_API_URL;
|
|
8
|
+
this.timeoutMs = opts?.timeoutMs ?? DEFAULT_SANDBOX_TIMEOUT_MS;
|
|
9
|
+
if (!this.apiKey) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
"Lizard API key is required. Set LIZARD_API_KEY env var or pass apiKey in options."
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
get headers() {
|
|
16
|
+
return {
|
|
17
|
+
"X-API-Key": this.apiKey,
|
|
18
|
+
"Content-Type": "application/json"
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/errors.ts
|
|
24
|
+
var LizardError = class extends Error {
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "LizardError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var AuthenticationError = class extends LizardError {
|
|
31
|
+
constructor(message = "Invalid or missing API key") {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "AuthenticationError";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var NotFoundError = class extends LizardError {
|
|
37
|
+
constructor(message = "Sandbox not found") {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "NotFoundError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var TimeoutError = class extends LizardError {
|
|
43
|
+
constructor(message = "Sandbox operation timed out") {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = "TimeoutError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
async function handleApiError(res) {
|
|
49
|
+
let message;
|
|
50
|
+
try {
|
|
51
|
+
const body = await res.json();
|
|
52
|
+
message = body.error ?? res.statusText;
|
|
53
|
+
} catch {
|
|
54
|
+
message = res.statusText;
|
|
55
|
+
}
|
|
56
|
+
if (res.status === 401 || res.status === 403) throw new AuthenticationError(message);
|
|
57
|
+
if (res.status === 404) throw new NotFoundError(message);
|
|
58
|
+
if (res.status === 408 || res.status === 504) throw new TimeoutError(message);
|
|
59
|
+
throw new LizardError(`API error ${res.status}: ${message}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/sandbox/process.ts
|
|
63
|
+
var Process = class {
|
|
64
|
+
constructor(sandboxId, config) {
|
|
65
|
+
this.sandboxId = sandboxId;
|
|
66
|
+
this.config = config;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Execute a command inside the microVM and wait for it to complete.
|
|
70
|
+
*
|
|
71
|
+
* The command runs in a shell inside the Lizard sandbox and returns
|
|
72
|
+
* stdout, stderr, and the exit code when it finishes.
|
|
73
|
+
*
|
|
74
|
+
* @param cmd Shell command to run inside the microVM.
|
|
75
|
+
* @param opts Optional execution options — environment variables, working
|
|
76
|
+
* directory, user, and timeout.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* const result = await sandbox.process.exec('node index.js')
|
|
81
|
+
* console.log(result.stdout)
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @example Run with a custom working directory and env vars:
|
|
85
|
+
* ```ts
|
|
86
|
+
* const result = await sandbox.process.exec('npm test', {
|
|
87
|
+
* workdir: '/app',
|
|
88
|
+
* envs: { NODE_ENV: 'test' },
|
|
89
|
+
* })
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
async exec(cmd, opts) {
|
|
93
|
+
const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/exec`, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: this.config.headers,
|
|
96
|
+
body: JSON.stringify({
|
|
97
|
+
cmd,
|
|
98
|
+
envs: opts?.envs,
|
|
99
|
+
user: opts?.user,
|
|
100
|
+
workdir: opts?.workdir,
|
|
101
|
+
timeoutMs: opts?.timeoutMs
|
|
102
|
+
}),
|
|
103
|
+
signal: AbortSignal.timeout(opts?.timeoutMs ?? 6e4)
|
|
104
|
+
});
|
|
105
|
+
if (!res.ok) await handleApiError(res);
|
|
106
|
+
return res.json();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// src/sandbox/fs.ts
|
|
111
|
+
var Fs = class {
|
|
112
|
+
constructor(sandboxId, config) {
|
|
113
|
+
this.sandboxId = sandboxId;
|
|
114
|
+
this.config = config;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Write a file into the microVM filesystem.
|
|
118
|
+
*
|
|
119
|
+
* Creates parent directories automatically if they don't exist.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* await sandbox.fs.write('/app/index.js', 'console.log("hello")')
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* @example Write binary data:
|
|
127
|
+
* ```ts
|
|
128
|
+
* await sandbox.fs.write('/app/data.bin', buffer)
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
async write(path, data, opts) {
|
|
132
|
+
const content = typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
133
|
+
const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files`, {
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers: this.config.headers,
|
|
136
|
+
body: JSON.stringify({ path, content, user: opts?.user })
|
|
137
|
+
});
|
|
138
|
+
if (!res.ok) await handleApiError(res);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Read a file from the microVM filesystem.
|
|
142
|
+
*
|
|
143
|
+
* @returns The file contents as a UTF-8 string.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* const content = await sandbox.fs.read('/app/index.js')
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
async read(path, opts) {
|
|
151
|
+
const url = new URL(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files`);
|
|
152
|
+
url.searchParams.set("path", path);
|
|
153
|
+
if (opts?.user) url.searchParams.set("user", opts.user);
|
|
154
|
+
const res = await fetch(url.toString(), { headers: this.config.headers });
|
|
155
|
+
if (!res.ok) await handleApiError(res);
|
|
156
|
+
return res.text();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* List files and directories at the given path inside the microVM.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```ts
|
|
163
|
+
* const entries = await sandbox.fs.list('/app')
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
async list(path, opts) {
|
|
167
|
+
const url = new URL(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files/list`);
|
|
168
|
+
url.searchParams.set("path", path);
|
|
169
|
+
if (opts?.user) url.searchParams.set("user", opts.user);
|
|
170
|
+
const res = await fetch(url.toString(), { headers: this.config.headers });
|
|
171
|
+
if (!res.ok) await handleApiError(res);
|
|
172
|
+
return res.json();
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Remove a file or directory from the microVM filesystem.
|
|
176
|
+
*/
|
|
177
|
+
async remove(path, opts) {
|
|
178
|
+
const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files`, {
|
|
179
|
+
method: "DELETE",
|
|
180
|
+
headers: this.config.headers,
|
|
181
|
+
body: JSON.stringify({ path, user: opts?.user })
|
|
182
|
+
});
|
|
183
|
+
if (!res.ok) await handleApiError(res);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Create a directory (and any missing parents) inside the microVM.
|
|
187
|
+
*/
|
|
188
|
+
async makeDir(path, opts) {
|
|
189
|
+
await this.execInternal(`mkdir -p ${JSON.stringify(path)}`, opts?.user);
|
|
190
|
+
}
|
|
191
|
+
async execInternal(cmd, user) {
|
|
192
|
+
const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/exec`, {
|
|
193
|
+
method: "POST",
|
|
194
|
+
headers: this.config.headers,
|
|
195
|
+
body: JSON.stringify({ cmd, user })
|
|
196
|
+
});
|
|
197
|
+
if (!res.ok) await handleApiError(res);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// src/sandbox/client.ts
|
|
202
|
+
var SandboxClient = class {
|
|
203
|
+
static async createSandbox(template, timeoutMs, opts) {
|
|
204
|
+
const config = new ConnectionConfig(opts);
|
|
205
|
+
const res = await fetch(`${config.apiUrl}/api/sandboxes`, {
|
|
206
|
+
method: "POST",
|
|
207
|
+
headers: config.headers,
|
|
208
|
+
body: JSON.stringify({
|
|
209
|
+
template,
|
|
210
|
+
timeoutMs,
|
|
211
|
+
metadata: opts?.metadata,
|
|
212
|
+
envs: opts?.envs
|
|
213
|
+
})
|
|
214
|
+
});
|
|
215
|
+
if (!res.ok) await handleApiError(res);
|
|
216
|
+
return res.json();
|
|
217
|
+
}
|
|
218
|
+
static async killSandbox(sandboxId, opts) {
|
|
219
|
+
const config = new ConnectionConfig(opts);
|
|
220
|
+
const res = await fetch(`${config.apiUrl}/api/sandboxes/${sandboxId}`, {
|
|
221
|
+
method: "DELETE",
|
|
222
|
+
headers: config.headers
|
|
223
|
+
});
|
|
224
|
+
if (res.status === 404) return false;
|
|
225
|
+
if (!res.ok) await handleApiError(res);
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
static async pauseSandbox(sandboxId, opts) {
|
|
229
|
+
const config = new ConnectionConfig(opts);
|
|
230
|
+
const res = await fetch(`${config.apiUrl}/api/sandboxes/${sandboxId}/pause`, {
|
|
231
|
+
method: "POST",
|
|
232
|
+
headers: config.headers
|
|
233
|
+
});
|
|
234
|
+
if (res.status === 404) return false;
|
|
235
|
+
if (!res.ok) await handleApiError(res);
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
static async resumeSandbox(sandboxId, opts) {
|
|
239
|
+
const config = new ConnectionConfig(opts);
|
|
240
|
+
const res = await fetch(`${config.apiUrl}/api/sandboxes/${sandboxId}/resume`, {
|
|
241
|
+
method: "POST",
|
|
242
|
+
headers: config.headers
|
|
243
|
+
});
|
|
244
|
+
if (res.status === 404) return false;
|
|
245
|
+
if (!res.ok) await handleApiError(res);
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
static async listSandboxes(opts) {
|
|
249
|
+
const config = new ConnectionConfig(opts);
|
|
250
|
+
const res = await fetch(`${config.apiUrl}/api/sandboxes`, {
|
|
251
|
+
headers: config.headers
|
|
252
|
+
});
|
|
253
|
+
if (!res.ok) await handleApiError(res);
|
|
254
|
+
return res.json();
|
|
255
|
+
}
|
|
256
|
+
static async getSandboxInfo(sandboxId, opts) {
|
|
257
|
+
const config = new ConnectionConfig(opts);
|
|
258
|
+
const res = await fetch(`${config.apiUrl}/api/sandboxes/${sandboxId}`, {
|
|
259
|
+
headers: config.headers
|
|
260
|
+
});
|
|
261
|
+
if (!res.ok) await handleApiError(res);
|
|
262
|
+
return res.json();
|
|
263
|
+
}
|
|
264
|
+
static async setTimeoutSandbox(sandboxId, timeoutMs, opts) {
|
|
265
|
+
const config = new ConnectionConfig(opts);
|
|
266
|
+
const res = await fetch(`${config.apiUrl}/api/sandboxes/${sandboxId}/timeout`, {
|
|
267
|
+
method: "POST",
|
|
268
|
+
headers: config.headers,
|
|
269
|
+
body: JSON.stringify({ timeoutMs })
|
|
270
|
+
});
|
|
271
|
+
if (!res.ok) await handleApiError(res);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// src/sandbox/index.ts
|
|
276
|
+
var Sandbox = class extends SandboxClient {
|
|
277
|
+
constructor(opts) {
|
|
278
|
+
super();
|
|
279
|
+
this.sandboxId = opts.sandboxId;
|
|
280
|
+
this.connectionConfig = new ConnectionConfig(opts);
|
|
281
|
+
this.fs = new Fs(this.sandboxId, this.connectionConfig);
|
|
282
|
+
this.process = new Process(this.sandboxId, this.connectionConfig);
|
|
283
|
+
}
|
|
284
|
+
static async create(templateOrOpts, opts) {
|
|
285
|
+
const template = typeof templateOrOpts === "string" ? templateOrOpts : templateOrOpts?.template ?? this.defaultTemplate;
|
|
286
|
+
const sandboxOpts = typeof templateOrOpts === "string" ? opts : templateOrOpts;
|
|
287
|
+
const timeoutMs = sandboxOpts?.timeoutMs ?? this.defaultTimeoutMs;
|
|
288
|
+
const { sandboxId } = await SandboxClient.createSandbox(template, timeoutMs, sandboxOpts);
|
|
289
|
+
return new this({ sandboxId, ...sandboxOpts });
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Connect to an existing sandbox by its ID.
|
|
293
|
+
*
|
|
294
|
+
* If the sandbox is currently paused, it will be automatically resumed
|
|
295
|
+
* from its last snapshot before this call returns.
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* ```ts
|
|
299
|
+
* const sandbox = await Sandbox.connect('sandbox_abc123')
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
302
|
+
static async connect(sandboxId, opts) {
|
|
303
|
+
await SandboxClient.resumeSandbox(sandboxId, opts);
|
|
304
|
+
return new this({ sandboxId, ...opts });
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* List all running sandboxes for the authenticated account.
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```ts
|
|
311
|
+
* const sandboxes = await Sandbox.list()
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
static async list(opts) {
|
|
315
|
+
return SandboxClient.listSandboxes(opts);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Kill the sandbox and release its resources immediately.
|
|
319
|
+
*
|
|
320
|
+
* @returns `true` if the microVM was terminated, `false` if it was already gone.
|
|
321
|
+
*/
|
|
322
|
+
async kill(opts) {
|
|
323
|
+
return SandboxClient.killSandbox(this.sandboxId, this.resolveOpts(opts));
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Snapshot and pause the sandbox microVM.
|
|
327
|
+
*
|
|
328
|
+
* The sandbox state — memory, filesystem, and running processes — is saved
|
|
329
|
+
* to a snapshot. Resume with `sandbox.resume()` or `Sandbox.connect(id)`.
|
|
330
|
+
*
|
|
331
|
+
* @returns `true` if paused successfully.
|
|
332
|
+
*/
|
|
333
|
+
async pause(opts) {
|
|
334
|
+
return SandboxClient.pauseSandbox(this.sandboxId, this.resolveOpts(opts));
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Resume a paused sandbox from its last snapshot.
|
|
338
|
+
*
|
|
339
|
+
* @returns `true` if resumed successfully.
|
|
340
|
+
*/
|
|
341
|
+
async resume(opts) {
|
|
342
|
+
return SandboxClient.resumeSandbox(this.sandboxId, this.resolveOpts(opts));
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Get metadata and status information about this sandbox.
|
|
346
|
+
*/
|
|
347
|
+
async getInfo(opts) {
|
|
348
|
+
return SandboxClient.getSandboxInfo(this.sandboxId, this.resolveOpts(opts));
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Extend or reduce the sandbox timeout.
|
|
352
|
+
*
|
|
353
|
+
* @param timeoutMs New timeout in milliseconds measured from now.
|
|
354
|
+
*/
|
|
355
|
+
async setTimeout(timeoutMs, opts) {
|
|
356
|
+
return SandboxClient.setTimeoutSandbox(this.sandboxId, timeoutMs, this.resolveOpts(opts));
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Get the public HTTPS URL for a port exposed inside the sandbox.
|
|
360
|
+
*
|
|
361
|
+
* Useful for accessing HTTP servers started inside the microVM from your
|
|
362
|
+
* agent or tests without additional tunneling.
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```ts
|
|
366
|
+
* await sandbox.process.exec('npx -y serve -p 3000 &')
|
|
367
|
+
* const url = sandbox.getHost(3000)
|
|
368
|
+
* // https://{sandboxId}-3000.sandbox.lizard.run
|
|
369
|
+
* ```
|
|
370
|
+
*/
|
|
371
|
+
getHost(port) {
|
|
372
|
+
const apiHost = new URL(this.connectionConfig.apiUrl).host;
|
|
373
|
+
const baseHost = apiHost.replace(/^api\./, "");
|
|
374
|
+
return `${this.sandboxId}-${port}.sandbox.${baseHost}`;
|
|
375
|
+
}
|
|
376
|
+
resolveOpts(opts) {
|
|
377
|
+
return { ...this.connectionConfig, ...opts };
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
Sandbox.defaultTemplate = "base";
|
|
381
|
+
Sandbox.defaultTimeoutMs = DEFAULT_SANDBOX_TIMEOUT_MS;
|
|
382
|
+
|
|
383
|
+
// src/code-interpreter/types.ts
|
|
384
|
+
var ExecutionError = class extends Error {
|
|
385
|
+
constructor(name, message, traceback) {
|
|
386
|
+
super(message);
|
|
387
|
+
this.name = name;
|
|
388
|
+
this.traceback = traceback;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
var Execution = class {
|
|
392
|
+
constructor() {
|
|
393
|
+
/** All stdout text, concatenated. */
|
|
394
|
+
this.stdout = "";
|
|
395
|
+
/** All stderr text, concatenated. */
|
|
396
|
+
this.stderr = "";
|
|
397
|
+
/** Rich output items (images, JSON, HTML, plain values). */
|
|
398
|
+
this.results = [];
|
|
399
|
+
/** Monotonically increasing counter for this context. */
|
|
400
|
+
this.executionCount = 0;
|
|
401
|
+
}
|
|
402
|
+
get success() {
|
|
403
|
+
return !this.error;
|
|
404
|
+
}
|
|
405
|
+
get text() {
|
|
406
|
+
return this.stdout;
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
// src/code-interpreter/index.ts
|
|
411
|
+
var CODE_INTERPRETER_PORT = 8080;
|
|
412
|
+
var CodeSandbox = class extends Sandbox {
|
|
413
|
+
get serverUrl() {
|
|
414
|
+
return `https://${this.getHost(CODE_INTERPRETER_PORT)}`;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Execute code in a persistent kernel.
|
|
418
|
+
*
|
|
419
|
+
* Variables, imports, and function definitions from previous calls are
|
|
420
|
+
* available in subsequent calls within the same context.
|
|
421
|
+
*
|
|
422
|
+
* @param code Source code to run.
|
|
423
|
+
* @param opts Language, context, env vars, timeout, and streaming callbacks.
|
|
424
|
+
*
|
|
425
|
+
* @returns Execution result with stdout, stderr, results, and any error.
|
|
426
|
+
*
|
|
427
|
+
* @throws {ExecutionError} if you pass neither language nor context and no
|
|
428
|
+
* default context exists — which cannot happen in normal usage.
|
|
429
|
+
*
|
|
430
|
+
* @example
|
|
431
|
+
* ```ts
|
|
432
|
+
* const result = await sandbox.runCode(`
|
|
433
|
+
* import math
|
|
434
|
+
* print(math.sqrt(144))
|
|
435
|
+
* `)
|
|
436
|
+
* console.log(result.stdout) // "12.0\n"
|
|
437
|
+
* ```
|
|
438
|
+
*/
|
|
439
|
+
async runCode(code, opts) {
|
|
440
|
+
if (opts?.context && opts?.language) {
|
|
441
|
+
throw new Error("Provide context or language, not both");
|
|
442
|
+
}
|
|
443
|
+
const body = { code, env_vars: opts?.envs ?? {} };
|
|
444
|
+
if (opts?.context) body.context_id = opts.context.id;
|
|
445
|
+
else if (opts?.language) body.language = opts.language;
|
|
446
|
+
const controller = new AbortController();
|
|
447
|
+
const timer = opts?.timeoutMs ? setTimeout(() => controller.abort(), opts.timeoutMs) : void 0;
|
|
448
|
+
try {
|
|
449
|
+
const res = await fetch(`${this.serverUrl}/execute`, {
|
|
450
|
+
method: "POST",
|
|
451
|
+
headers: { "Content-Type": "application/json" },
|
|
452
|
+
body: JSON.stringify(body),
|
|
453
|
+
signal: controller.signal
|
|
454
|
+
});
|
|
455
|
+
if (!res.ok) {
|
|
456
|
+
throw new Error(`Code interpreter returned ${res.status}: ${await res.text()}`);
|
|
457
|
+
}
|
|
458
|
+
if (!res.body) throw new Error("Empty response body");
|
|
459
|
+
const execution = new Execution();
|
|
460
|
+
for await (const line of readLines(res.body)) {
|
|
461
|
+
let item;
|
|
462
|
+
try {
|
|
463
|
+
item = JSON.parse(line);
|
|
464
|
+
} catch {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (item.type === "stdout") {
|
|
468
|
+
execution.stdout += item.data;
|
|
469
|
+
opts?.onStdout?.(item.data);
|
|
470
|
+
} else if (item.type === "stderr") {
|
|
471
|
+
execution.stderr += item.data;
|
|
472
|
+
opts?.onStderr?.(item.data);
|
|
473
|
+
} else if (item.type === "result") {
|
|
474
|
+
const out = item;
|
|
475
|
+
execution.results.push(out);
|
|
476
|
+
opts?.onResult?.(out);
|
|
477
|
+
} else if (item.type === "error") {
|
|
478
|
+
const err = new ExecutionError(
|
|
479
|
+
item.name,
|
|
480
|
+
item.message,
|
|
481
|
+
item.traceback
|
|
482
|
+
);
|
|
483
|
+
execution.error = err;
|
|
484
|
+
opts?.onError?.(err);
|
|
485
|
+
} else if (item.type === "done") {
|
|
486
|
+
execution.executionCount = item.execution_count ?? 0;
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return execution;
|
|
491
|
+
} finally {
|
|
492
|
+
clearTimeout(timer);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Create a new isolated execution context.
|
|
497
|
+
*
|
|
498
|
+
* Each context maintains its own variable namespace and process state.
|
|
499
|
+
* Useful for running multiple independent sessions in the same sandbox.
|
|
500
|
+
*
|
|
501
|
+
* @example
|
|
502
|
+
* ```ts
|
|
503
|
+
* const ctx = await sandbox.createContext({ language: 'python' })
|
|
504
|
+
* await sandbox.runCode('x = 10', { context: ctx })
|
|
505
|
+
* await sandbox.runCode('print(x)', { context: ctx }) // prints 10
|
|
506
|
+
* ```
|
|
507
|
+
*/
|
|
508
|
+
async createContext(opts) {
|
|
509
|
+
const res = await fetch(`${this.serverUrl}/contexts`, {
|
|
510
|
+
method: "POST",
|
|
511
|
+
headers: { "Content-Type": "application/json" },
|
|
512
|
+
body: JSON.stringify({
|
|
513
|
+
language: opts?.language ?? "python",
|
|
514
|
+
cwd: opts?.cwd ?? "/home/user"
|
|
515
|
+
})
|
|
516
|
+
});
|
|
517
|
+
if (!res.ok) throw new Error(`Failed to create context: ${await res.text()}`);
|
|
518
|
+
return res.json();
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* List all active execution contexts in this sandbox.
|
|
522
|
+
*/
|
|
523
|
+
async listContexts() {
|
|
524
|
+
const res = await fetch(`${this.serverUrl}/contexts`);
|
|
525
|
+
if (!res.ok) throw new Error(`Failed to list contexts: ${await res.text()}`);
|
|
526
|
+
return res.json();
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Delete an execution context and free its resources.
|
|
530
|
+
*/
|
|
531
|
+
async deleteContext(context) {
|
|
532
|
+
const id = typeof context === "string" ? context : context.id;
|
|
533
|
+
const res = await fetch(`${this.serverUrl}/contexts/${id}`, { method: "DELETE" });
|
|
534
|
+
if (!res.ok) throw new Error(`Failed to delete context: ${await res.text()}`);
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Restart a context, clearing all variables and state.
|
|
538
|
+
*/
|
|
539
|
+
async restartContext(context) {
|
|
540
|
+
const id = typeof context === "string" ? context : context.id;
|
|
541
|
+
const res = await fetch(`${this.serverUrl}/contexts/${id}/restart`, { method: "POST" });
|
|
542
|
+
if (!res.ok) throw new Error(`Failed to restart context: ${await res.text()}`);
|
|
543
|
+
}
|
|
544
|
+
static async create(templateOrOpts, opts) {
|
|
545
|
+
return super.create(templateOrOpts, opts);
|
|
546
|
+
}
|
|
547
|
+
static async connect(sandboxId, opts) {
|
|
548
|
+
return super.connect(sandboxId, opts);
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
CodeSandbox.defaultTemplate = "code-interpreter-v1";
|
|
552
|
+
async function* readLines(body) {
|
|
553
|
+
const reader = body.getReader();
|
|
554
|
+
const decoder = new TextDecoder();
|
|
555
|
+
let buf = "";
|
|
556
|
+
try {
|
|
557
|
+
while (true) {
|
|
558
|
+
const { done, value } = await reader.read();
|
|
559
|
+
if (done) break;
|
|
560
|
+
buf += decoder.decode(value, { stream: true });
|
|
561
|
+
const lines = buf.split("\n");
|
|
562
|
+
buf = lines.pop() ?? "";
|
|
563
|
+
for (const line of lines) {
|
|
564
|
+
if (line.trim()) yield line;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (buf.trim()) yield buf;
|
|
568
|
+
} finally {
|
|
569
|
+
reader.releaseLock();
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
export {
|
|
573
|
+
AuthenticationError,
|
|
574
|
+
CodeSandbox,
|
|
575
|
+
LizardError,
|
|
576
|
+
NotFoundError,
|
|
577
|
+
Sandbox,
|
|
578
|
+
TimeoutError
|
|
579
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lizard-build/sdk",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Lizard SDK — cloud sandboxes for AI agents",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
17
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"lint": "eslint src"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"lizard",
|
|
26
|
+
"sandbox",
|
|
27
|
+
"ai",
|
|
28
|
+
"agents",
|
|
29
|
+
"firecracker"
|
|
30
|
+
],
|
|
31
|
+
"license": "Apache-2.0",
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^20.0.0",
|
|
34
|
+
"tsup": "^8.0.0",
|
|
35
|
+
"typescript": "^5.4.5",
|
|
36
|
+
"vitest": "^1.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|