@lizard-build/sdk 0.1.40 → 0.1.42
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 +58 -258
- package/dist/index.d.mts +87 -56
- package/dist/index.d.ts +87 -56
- package/dist/index.js +83 -47
- package/dist/index.mjs +83 -47
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -61,17 +61,23 @@ var TimeoutError = class extends LizardError {
|
|
|
61
61
|
};
|
|
62
62
|
async function handleApiError(res) {
|
|
63
63
|
let message;
|
|
64
|
+
let code;
|
|
64
65
|
try {
|
|
65
66
|
const body = await res.json();
|
|
66
67
|
message = body.error ?? res.statusText;
|
|
68
|
+
if (typeof body.code === "string") code = body.code;
|
|
67
69
|
} catch {
|
|
68
70
|
message = res.statusText;
|
|
69
71
|
}
|
|
70
|
-
|
|
71
|
-
if (res.status ===
|
|
72
|
-
if (res.status ===
|
|
73
|
-
if (res.status ===
|
|
74
|
-
|
|
72
|
+
let err;
|
|
73
|
+
if (res.status === 401 || res.status === 403) err = new AuthenticationError(message);
|
|
74
|
+
else if (res.status === 404) err = new NotFoundError(message);
|
|
75
|
+
else if (res.status === 409) err = new ConflictError(message);
|
|
76
|
+
else if (res.status === 408 || res.status === 504) err = new TimeoutError(message);
|
|
77
|
+
else err = new LizardError(`API error ${res.status}: ${message}`);
|
|
78
|
+
err.status = res.status;
|
|
79
|
+
err.code = code;
|
|
80
|
+
throw err;
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
// src/platform/client.ts
|
|
@@ -450,12 +456,12 @@ var Process = class {
|
|
|
450
456
|
this.config = config;
|
|
451
457
|
}
|
|
452
458
|
/**
|
|
453
|
-
* Execute a command inside the
|
|
459
|
+
* Execute a command inside the sandbox and wait for it to complete.
|
|
454
460
|
*
|
|
455
461
|
* The command runs in a shell inside the Lizard sandbox and returns
|
|
456
462
|
* stdout, stderr, and the exit code when it finishes.
|
|
457
463
|
*
|
|
458
|
-
* @param cmd Shell command to run inside the
|
|
464
|
+
* @param cmd Shell command to run inside the sandbox.
|
|
459
465
|
* @param opts Optional execution options — environment variables, working
|
|
460
466
|
* directory, user, and timeout.
|
|
461
467
|
*
|
|
@@ -587,18 +593,19 @@ var Fs = class {
|
|
|
587
593
|
this.config = config;
|
|
588
594
|
}
|
|
589
595
|
/**
|
|
590
|
-
* Write a file into the
|
|
596
|
+
* Write a file into the sandbox filesystem.
|
|
591
597
|
*
|
|
592
598
|
* Creates parent directories automatically if they don't exist.
|
|
599
|
+
* Byte input must contain valid UTF-8; arbitrary binary uploads are unsupported.
|
|
593
600
|
*
|
|
594
601
|
* @example
|
|
595
602
|
* ```ts
|
|
596
603
|
* await sandbox.fs.write('/app/index.js', 'console.log("hello")')
|
|
597
604
|
* ```
|
|
598
605
|
*
|
|
599
|
-
* @example Write
|
|
606
|
+
* @example Write UTF-8 bytes:
|
|
600
607
|
* ```ts
|
|
601
|
-
* await sandbox.fs.write('/app/
|
|
608
|
+
* await sandbox.fs.write('/app/notes.txt', new TextEncoder().encode('hello'))
|
|
602
609
|
* ```
|
|
603
610
|
*/
|
|
604
611
|
async write(path, data, opts) {
|
|
@@ -611,7 +618,7 @@ var Fs = class {
|
|
|
611
618
|
if (!res.ok) await handleApiError(res);
|
|
612
619
|
}
|
|
613
620
|
/**
|
|
614
|
-
* Read a file from the
|
|
621
|
+
* Read a file from the sandbox filesystem.
|
|
615
622
|
*
|
|
616
623
|
* @returns The file contents as a UTF-8 string.
|
|
617
624
|
*
|
|
@@ -628,14 +635,7 @@ var Fs = class {
|
|
|
628
635
|
if (!res.ok) await handleApiError(res);
|
|
629
636
|
return res.text();
|
|
630
637
|
}
|
|
631
|
-
/**
|
|
632
|
-
* List files and directories at the given path inside the microVM.
|
|
633
|
-
*
|
|
634
|
-
* @example
|
|
635
|
-
* ```ts
|
|
636
|
-
* const entries = await sandbox.fs.list('/app')
|
|
637
|
-
* ```
|
|
638
|
-
*/
|
|
638
|
+
/** Read a file as bytes. */
|
|
639
639
|
async readBytes(path, opts) {
|
|
640
640
|
const url = new URL(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files`);
|
|
641
641
|
url.searchParams.set("path", path);
|
|
@@ -644,6 +644,14 @@ var Fs = class {
|
|
|
644
644
|
if (!res.ok) await handleApiError(res);
|
|
645
645
|
return new Uint8Array(await res.arrayBuffer());
|
|
646
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* List files and directories at the given path inside the sandbox.
|
|
649
|
+
*
|
|
650
|
+
* @example
|
|
651
|
+
* ```ts
|
|
652
|
+
* const entries = await sandbox.fs.list('/app')
|
|
653
|
+
* ```
|
|
654
|
+
*/
|
|
647
655
|
async list(path, opts) {
|
|
648
656
|
const url = new URL(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files/list`);
|
|
649
657
|
url.searchParams.set("path", path);
|
|
@@ -653,7 +661,7 @@ var Fs = class {
|
|
|
653
661
|
return res.json();
|
|
654
662
|
}
|
|
655
663
|
/**
|
|
656
|
-
* Remove a file or directory from the
|
|
664
|
+
* Remove a file or directory from the sandbox filesystem.
|
|
657
665
|
*/
|
|
658
666
|
async remove(path, opts) {
|
|
659
667
|
const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files`, {
|
|
@@ -664,7 +672,7 @@ var Fs = class {
|
|
|
664
672
|
if (!res.ok) await handleApiError(res);
|
|
665
673
|
}
|
|
666
674
|
/**
|
|
667
|
-
* Create a directory (and any missing parents) inside the
|
|
675
|
+
* Create a directory (and any missing parents) inside the sandbox.
|
|
668
676
|
*/
|
|
669
677
|
async makeDir(path, opts) {
|
|
670
678
|
await this.execInternal(`mkdir -p ${JSON.stringify(path)}`, opts?.user);
|
|
@@ -892,11 +900,7 @@ var Sandbox = class extends SandboxClient {
|
|
|
892
900
|
* Verifies the sandbox exists and is reachable, then returns a handle to it.
|
|
893
901
|
* Throws `NotFoundError` if it has been killed or has expired.
|
|
894
902
|
*
|
|
895
|
-
*
|
|
896
|
-
* reconnecting to might be paused. Sandboxes are pods now and pause/resume is a
|
|
897
|
-
* `501` on every one of them, so that call turned every `connect()` into an
|
|
898
|
-
* error against a perfectly healthy sandbox. Connecting does not need to change
|
|
899
|
-
* a sandbox's state, so it no longer tries to.
|
|
903
|
+
* Connecting reads sandbox metadata without changing its state.
|
|
900
904
|
*
|
|
901
905
|
* @example
|
|
902
906
|
* ```ts
|
|
@@ -921,29 +925,25 @@ var Sandbox = class extends SandboxClient {
|
|
|
921
925
|
/**
|
|
922
926
|
* Kill the sandbox and release its resources immediately.
|
|
923
927
|
*
|
|
924
|
-
* @returns `true` if the
|
|
928
|
+
* @returns `true` if the sandbox was terminated, `false` if it was already gone.
|
|
925
929
|
*/
|
|
926
930
|
async kill(opts) {
|
|
927
931
|
return SandboxClient.killSandbox(this.sandboxId, this.resolveOpts(opts));
|
|
928
932
|
}
|
|
929
933
|
/**
|
|
930
|
-
*
|
|
934
|
+
* Request sandbox pause. The Kubernetes backend returns HTTP 501.
|
|
931
935
|
*
|
|
932
|
-
* @deprecated
|
|
933
|
-
*
|
|
934
|
-
* checkpoint of the pod, which is not built. To park work across a gap, put it on a
|
|
935
|
-
* {@link Volume} and create a fresh sandbox on that volume later; the volume is the
|
|
936
|
-
* part that is meant to outlive a sandbox.
|
|
936
|
+
* @deprecated Unsupported on Kubernetes. Save files to a {@link Volume}
|
|
937
|
+
* and attach it to a new sandbox instead.
|
|
937
938
|
*/
|
|
938
939
|
async pause(opts) {
|
|
939
940
|
return SandboxClient.pauseSandbox(this.sandboxId, this.resolveOpts(opts));
|
|
940
941
|
}
|
|
941
942
|
/**
|
|
942
|
-
*
|
|
943
|
+
* Request sandbox resume. The Kubernetes backend returns HTTP 501.
|
|
943
944
|
*
|
|
944
|
-
* @deprecated
|
|
945
|
-
*
|
|
946
|
-
* calls this, so reconnecting to a running sandbox works without it.
|
|
945
|
+
* @deprecated Unsupported on Kubernetes. Use {@link Sandbox.connect} to
|
|
946
|
+
* reconnect to a running sandbox.
|
|
947
947
|
*/
|
|
948
948
|
async resume(opts) {
|
|
949
949
|
return SandboxClient.resumeSandbox(this.sandboxId, this.resolveOpts(opts));
|
|
@@ -963,25 +963,27 @@ var Sandbox = class extends SandboxClient {
|
|
|
963
963
|
return SandboxClient.setTimeoutSandbox(this.sandboxId, timeoutMs, this.resolveOpts(opts));
|
|
964
964
|
}
|
|
965
965
|
/**
|
|
966
|
-
*
|
|
966
|
+
* Register a public HTTPS route and return its hostname without a scheme.
|
|
967
967
|
*
|
|
968
|
-
* Useful for accessing HTTP servers started inside the
|
|
968
|
+
* Useful for accessing HTTP servers started inside the sandbox from your
|
|
969
969
|
* agent or tests without additional tunneling.
|
|
970
970
|
*
|
|
971
971
|
* @example
|
|
972
972
|
* ```ts
|
|
973
|
-
*
|
|
974
|
-
* const
|
|
975
|
-
*
|
|
973
|
+
* // Start an HTTP server on port 3000 before exposing it.
|
|
974
|
+
* const hostname = await sandbox.getHost(3000)
|
|
975
|
+
* console.log(`https://${hostname}`)
|
|
976
976
|
* ```
|
|
977
977
|
*/
|
|
978
978
|
async getHost(port, opts) {
|
|
979
979
|
const { hostname } = await SandboxClient.exposeSandboxPort(this.sandboxId, port, this.resolveOpts(opts));
|
|
980
980
|
return hostname;
|
|
981
981
|
}
|
|
982
|
+
/** Fork is unsupported on Kubernetes; the backend returns HTTP 501. */
|
|
982
983
|
fork(opts = {}) {
|
|
983
984
|
return new PlatformClient(this.resolveOpts()).post(`/api/sandboxes/${this.sandboxId}/fork`, { count: opts.count ?? 1, timeoutMs: opts.timeoutMs ?? 0 });
|
|
984
985
|
}
|
|
986
|
+
/** Snapshot creation is unsupported on Kubernetes; the backend returns HTTP 501. */
|
|
985
987
|
snapshot(name) {
|
|
986
988
|
return new PlatformClient(this.resolveOpts()).post(`/api/sandboxes/${this.sandboxId}/snapshot`, { name });
|
|
987
989
|
}
|
|
@@ -994,6 +996,7 @@ var Sandbox = class extends SandboxClient {
|
|
|
994
996
|
static snapshots(projectId, opts) {
|
|
995
997
|
return new PlatformClient(opts ?? {}).get(`/api/projects/${projectId}/snapshots`);
|
|
996
998
|
}
|
|
999
|
+
/** Snapshot restore is unsupported on Kubernetes; the backend returns HTTP 501. */
|
|
997
1000
|
static async restore(snapshotId, opts) {
|
|
998
1001
|
const result = await new PlatformClient(opts ?? {}).post(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}/fork`, { timeoutMs: opts?.timeoutMs ?? 0 });
|
|
999
1002
|
return new this({ ...opts, sandboxId: result.sandboxId ?? result.id });
|
|
@@ -1106,7 +1109,7 @@ var AddonsAPI = class {
|
|
|
1106
1109
|
});
|
|
1107
1110
|
return this.get(projectId, addonId);
|
|
1108
1111
|
}
|
|
1109
|
-
/** Restart an addon
|
|
1112
|
+
/** Restart an addon. */
|
|
1110
1113
|
redeploy(projectId, addonId) {
|
|
1111
1114
|
return this.client.post(`/api/projects/${projectId}/addons/${addonId}/redeploy`, {});
|
|
1112
1115
|
}
|
|
@@ -1517,8 +1520,8 @@ var Volume = class _Volume {
|
|
|
1517
1520
|
* exist yet. This is the reason a volume's name is its key: an agent that wants
|
|
1518
1521
|
* "the scratch disk for this task" no longer has to store an id between runs.
|
|
1519
1522
|
*
|
|
1520
|
-
* An existing volume is returned as-is — `sizeGb` applies only to a fresh create
|
|
1521
|
-
*
|
|
1523
|
+
* An existing volume is returned as-is — `sizeGb` applies only to a fresh create,
|
|
1524
|
+
* so an existing volume keeps its size. Call {@link Volume.resize} to change it.
|
|
1522
1525
|
*/
|
|
1523
1526
|
static async getOrCreate(projectId, name, opts) {
|
|
1524
1527
|
const config = new ConnectionConfig(opts);
|
|
@@ -1558,6 +1561,32 @@ var Volume = class _Volume {
|
|
|
1558
1561
|
});
|
|
1559
1562
|
if (!res.ok) await handleApiError(res);
|
|
1560
1563
|
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Resize a volume, by name or by id, to `sizeGb`.
|
|
1566
|
+
*
|
|
1567
|
+
* The change is in place and online: the size is a quota, so no data is copied,
|
|
1568
|
+
* it completes in well under a second, and a sandbox that has the volume mounted
|
|
1569
|
+
* keeps running and sees the new size immediately. Both growing and shrinking are
|
|
1570
|
+
* allowed, but a shrink must leave at least 10% of the new size free — otherwise
|
|
1571
|
+
* it throws `ConflictError` with `code: 'volume_too_full_to_shrink'`.
|
|
1572
|
+
*
|
|
1573
|
+
* Other failures carry a `code` too: `invalid_volume_size`, `volume_not_resizable`,
|
|
1574
|
+
* `volume_resize_in_progress`, `volume_not_provisioned`,
|
|
1575
|
+
* `volume_capacity_unavailable`, and `volume_resize_timeout` (a `TimeoutError`;
|
|
1576
|
+
* the size is left unchanged).
|
|
1577
|
+
*
|
|
1578
|
+
* @returns The volume's updated info, including `sizeEnforced`.
|
|
1579
|
+
*/
|
|
1580
|
+
static async resize(projectId, nameOrId, sizeGb, opts) {
|
|
1581
|
+
const config = new ConnectionConfig(opts);
|
|
1582
|
+
const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
|
|
1583
|
+
method: "PATCH",
|
|
1584
|
+
headers: config.headers,
|
|
1585
|
+
body: JSON.stringify({ sizeGb })
|
|
1586
|
+
});
|
|
1587
|
+
if (!res.ok) await handleApiError(res);
|
|
1588
|
+
return res.json();
|
|
1589
|
+
}
|
|
1561
1590
|
async getInfo(projectId) {
|
|
1562
1591
|
const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
|
|
1563
1592
|
headers: this.config.headers
|
|
@@ -1565,6 +1594,13 @@ var Volume = class _Volume {
|
|
|
1565
1594
|
if (!res.ok) await handleApiError(res);
|
|
1566
1595
|
return res.json();
|
|
1567
1596
|
}
|
|
1597
|
+
/** Resize this volume in place — see the static {@link Volume.resize}. */
|
|
1598
|
+
async resize(projectId, sizeGb) {
|
|
1599
|
+
return _Volume.resize(projectId, this.volumeId, sizeGb, {
|
|
1600
|
+
apiKey: this.config.apiKey,
|
|
1601
|
+
apiUrl: this.config.apiUrl
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1568
1604
|
async delete(projectId) {
|
|
1569
1605
|
const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
|
|
1570
1606
|
method: "DELETE",
|
|
@@ -1598,6 +1634,7 @@ var Lizard = class {
|
|
|
1598
1634
|
getOrCreate: async (name, opts) => Volume.getOrCreate(await this.projectId(), name, { ...await this.connectionOpts(), ...opts }),
|
|
1599
1635
|
get: async (nameOrId, opts) => Volume.get(await this.projectId(), nameOrId, { ...await this.connectionOpts(), ...opts }),
|
|
1600
1636
|
list: async (opts) => Volume.list(await this.projectId(), { ...await this.connectionOpts(), ...opts }),
|
|
1637
|
+
resize: async (nameOrId, sizeGb, opts) => Volume.resize(await this.projectId(), nameOrId, sizeGb, { ...await this.connectionOpts(), ...opts }),
|
|
1601
1638
|
delete: async (nameOrId, opts) => Volume.delete(await this.projectId(), nameOrId, { ...await this.connectionOpts(), ...opts })
|
|
1602
1639
|
};
|
|
1603
1640
|
this.config = new ConnectionConfig(opts);
|
|
@@ -1708,8 +1745,7 @@ var CodeSandbox = class extends Sandbox {
|
|
|
1708
1745
|
*
|
|
1709
1746
|
* @returns Execution result with stdout, stderr, results, and any error.
|
|
1710
1747
|
*
|
|
1711
|
-
*
|
|
1712
|
-
* default context exists — which cannot happen in normal usage.
|
|
1748
|
+
* Code errors appear in `execution.error`; request failures throw.
|
|
1713
1749
|
*
|
|
1714
1750
|
* @example
|
|
1715
1751
|
* ```ts
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lizard-build/sdk",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "TypeScript SDK for Lizard
|
|
3
|
+
"version": "0.1.42",
|
|
4
|
+
"description": "TypeScript SDK for Lizard: Kubernetes sandboxes, code execution and cloud apps.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"keywords": [
|
|
25
25
|
"sandbox",
|
|
26
26
|
"sandboxes",
|
|
27
|
-
"
|
|
28
|
-
"
|
|
27
|
+
"kubernetes",
|
|
28
|
+
"containers",
|
|
29
29
|
"ai-agents",
|
|
30
30
|
"code-interpreter",
|
|
31
31
|
"code-execution",
|