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