@aerol-ai/aerolvm-sdk 0.1.6 → 0.1.8
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/MicroVM.d.ts +10 -1
- package/dist/MicroVM.js +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/internal/api/v1/paths.d.ts +1 -0
- package/dist/internal/api/v1/paths.js +7 -0
- package/dist/internal/client.d.ts +42 -16
- package/dist/internal/client.js +128 -57
- package/dist/types.d.ts +72 -0
- package/package.json +3 -2
package/dist/MicroVM.d.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
import { type APIVersion } from "./internal/client.js";
|
|
1
2
|
import { Sandbox } from "./Sandbox.js";
|
|
2
|
-
import type { CreateOptions, CreateSessionOptions, ExecStreamHandle, ExecStreamOptions, HealthStatus, Lifecycle, MountSpecRedacted, ResizeOptions, Session, SessionAttachHandle, SessionAttachOptions } from "./types.js";
|
|
3
|
+
import type { CreateOptions, CreateSessionOptions, ExecStreamHandle, ExecStreamOptions, HealthStatus, Lifecycle, MountSpecRedacted, ResizeOptions, SandboxSnapshot, Session, SessionAttachHandle, SessionAttachOptions } from "./types.js";
|
|
3
4
|
type FetchLike = typeof fetch;
|
|
4
5
|
export interface MicroVMConfig {
|
|
5
6
|
patToken?: string;
|
|
6
7
|
apiUrl?: string;
|
|
7
8
|
fetch?: FetchLike;
|
|
9
|
+
/**
|
|
10
|
+
* Wire version of the sandbox daemon API to call. Defaults to the SDK's
|
|
11
|
+
* pinned default ("v1" today). The SDK package version and the API wire
|
|
12
|
+
* version evolve independently — you can pin one without affecting the
|
|
13
|
+
* other.
|
|
14
|
+
*/
|
|
15
|
+
apiVersion?: APIVersion;
|
|
8
16
|
}
|
|
9
17
|
export declare class MicroVM {
|
|
10
18
|
readonly apiUrl: string;
|
|
@@ -16,6 +24,7 @@ export declare class MicroVM {
|
|
|
16
24
|
get(id: string): Promise<Sandbox>;
|
|
17
25
|
start(id: string): Promise<Sandbox>;
|
|
18
26
|
stop(id: string): Promise<Sandbox>;
|
|
27
|
+
createSnapshot(id: string, name: string): Promise<SandboxSnapshot>;
|
|
19
28
|
destroy(id: string): Promise<void>;
|
|
20
29
|
resize(id: string, options: ResizeOptions): Promise<Sandbox>;
|
|
21
30
|
updateLifecycle(id: string, lifecycle: Lifecycle): Promise<Sandbox>;
|
package/dist/MicroVM.js
CHANGED
|
@@ -18,6 +18,7 @@ export class MicroVM {
|
|
|
18
18
|
baseURL: apiUrl,
|
|
19
19
|
patToken,
|
|
20
20
|
fetch: config.fetch,
|
|
21
|
+
apiVersion: config.apiVersion,
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
24
|
async create(options) {
|
|
@@ -40,6 +41,9 @@ export class MicroVM {
|
|
|
40
41
|
const sandbox = await this.client.stop(id);
|
|
41
42
|
return this.wrap(sandbox.toJSON());
|
|
42
43
|
}
|
|
44
|
+
async createSnapshot(id, name) {
|
|
45
|
+
return this.client.createSnapshot(id, name);
|
|
46
|
+
}
|
|
43
47
|
async destroy(id) {
|
|
44
48
|
await this.client.destroy(id);
|
|
45
49
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { MicroVM } from "./MicroVM.js";
|
|
2
2
|
export { Sandbox } from "./Sandbox.js";
|
|
3
3
|
export type { MicroVMConfig } from "./MicroVM.js";
|
|
4
|
-
export type { BinaryLike, CreateOptions, CreateSessionOptions, ExecRequest, ExecResult, ExecExitInfo, ExposedPort, HealthStatus, Lifecycle, MountSpec, MountSpecRedacted, MountType, RegistryAuth, ResizeOptions, Sandbox as SandboxData, SandboxStatus, Session, SessionAttachHandle, SessionAttachOptions, SessionStatus, UpdateLifecycleOptions, } from "./types.js";
|
|
4
|
+
export type { BinaryLike, CreateOptions, CreateSessionOptions, ExecRequest, ExecResult, ExecExitInfo, ExposedPort, ExposePortOptions, ExposeProtocol, ExposeResult, HealthStatus, Lifecycle, MountSpec, MountSpecRedacted, MountType, RegistryAuth, ResizeOptions, Sandbox as SandboxData, SandboxSnapshot, SandboxStatus, Session, SessionAttachHandle, SessionAttachOptions, SessionStatus, UpdateLifecycleOptions, } from "./types.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PATH_PREFIX = "/v1";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// v1 path-prefix constant. The TypeScript SDK uses this to build URLs against
|
|
2
|
+
// the v1 surface of the sandbox daemon. When v2 lands, a sibling
|
|
3
|
+
// internal/api/v2/paths.ts will export its own PATH_PREFIX, and the client
|
|
4
|
+
// constructor's apiVersion option selects which one to use.
|
|
5
|
+
//
|
|
6
|
+
// Keep this in sync with pkg/api/v1/dto.go::PathPrefix on the server.
|
|
7
|
+
export const PATH_PREFIX = "/v1";
|
|
@@ -1,20 +1,43 @@
|
|
|
1
|
-
import type { BinaryLike, CreateOptions, CreateSessionOptions, ExecRequest, ExecResult, ExecStreamHandle, ExecStreamOptions, ExposedPort, HealthStatus, Lifecycle, MountSpecRedacted, ResizeOptions, Sandbox, Session, SessionAttachHandle, SessionAttachOptions } from "../types.js";
|
|
1
|
+
import type { BinaryLike, CreateOptions, CreateSessionOptions, ExecRequest, ExecResult, ExecStreamHandle, ExecStreamOptions, ExposedPort, ExposePortOptions, ExposeResult, HealthStatus, Lifecycle, MountSpecRedacted, NetworkUsage, SetNetworkLimitsOptions, ResizeOptions, Sandbox, SandboxSnapshot, Session, SessionAttachHandle, SessionAttachOptions } from "../types.js";
|
|
2
2
|
type FetchLike = typeof fetch;
|
|
3
|
+
/**
|
|
4
|
+
* Wire version of the sandbox daemon API this client speaks. The SDK and the
|
|
5
|
+
* server version independently — bumping the SDK package does not move the
|
|
6
|
+
* wire version. Today only "v1" exists; "v2" will be added when a wire-level
|
|
7
|
+
* breaking change is needed on the server.
|
|
8
|
+
*/
|
|
9
|
+
export type APIVersion = "v1";
|
|
3
10
|
export interface APIClientConfig {
|
|
4
11
|
baseURL: string;
|
|
5
12
|
patToken?: string;
|
|
6
13
|
fetch?: FetchLike;
|
|
14
|
+
/**
|
|
15
|
+
* Wire version to call. Defaults to "v1". Pin this if you need a specific
|
|
16
|
+
* version — otherwise the SDK will track its own default, which may move in
|
|
17
|
+
* a future major SDK release.
|
|
18
|
+
*/
|
|
19
|
+
apiVersion?: APIVersion;
|
|
7
20
|
}
|
|
8
21
|
export declare class APIClient {
|
|
9
22
|
readonly baseURL: string;
|
|
23
|
+
readonly apiVersion: APIVersion;
|
|
10
24
|
private readonly patToken;
|
|
11
25
|
private readonly fetchFn;
|
|
26
|
+
private readonly versionPrefix;
|
|
12
27
|
constructor(config: APIClientConfig);
|
|
28
|
+
/**
|
|
29
|
+
* Build a versioned API path. Pass the suffix beginning with "/" (e.g.
|
|
30
|
+
* "/sandboxes") and the active version's prefix is prepended. Use this for
|
|
31
|
+
* every versioned API call so v2 (when it lands) can be selected by the
|
|
32
|
+
* apiVersion option without touching call sites.
|
|
33
|
+
*/
|
|
34
|
+
private versioned;
|
|
13
35
|
create(options: CreateOptions): Promise<SandboxResource>;
|
|
14
36
|
list(): Promise<SandboxResource[]>;
|
|
15
37
|
get(id: string): Promise<SandboxResource>;
|
|
16
38
|
start(id: string): Promise<SandboxResource>;
|
|
17
39
|
stop(id: string): Promise<SandboxResource>;
|
|
40
|
+
createSnapshot(id: string, name: string): Promise<SandboxSnapshot>;
|
|
18
41
|
destroy(id: string): Promise<void>;
|
|
19
42
|
resize(id: string, options: ResizeOptions): Promise<SandboxResource>;
|
|
20
43
|
updateLifecycle(id: string, lifecycle: Lifecycle): Promise<SandboxResource>;
|
|
@@ -31,24 +54,26 @@ export declare class APIClient {
|
|
|
31
54
|
attachSession(id: string, sessionID: string, options?: SessionAttachOptions): SessionAttachHandle;
|
|
32
55
|
uploadFile(id: string, targetPath: string, data: BinaryLike): Promise<void>;
|
|
33
56
|
downloadFile(id: string, targetPath: string): Promise<Uint8Array>;
|
|
34
|
-
exposePort(id: string, port: number): Promise<string>;
|
|
35
|
-
/**
|
|
36
|
-
* Publish a raw TCP port through caddy-l4. The returned URL is in
|
|
37
|
-
* `tcp://<host>:<port>` form and works directly with `psql`, `redis-cli`,
|
|
38
|
-
* `mysql`, `mongosh`, and any other native-protocol client.
|
|
39
|
-
*/
|
|
40
|
-
exposeTCPPort(id: string, port: number): Promise<string>;
|
|
41
57
|
/**
|
|
42
|
-
* Publish a
|
|
43
|
-
* `
|
|
44
|
-
*
|
|
58
|
+
* Publish a sandbox container port. The chosen wire protocol is selected by
|
|
59
|
+
* `options.protocol` (defaults to `"http"`):
|
|
60
|
+
*
|
|
61
|
+
* - `"http"`: Caddy HTTP reverse proxy at `https://<id>-<port>.<domain>`.
|
|
62
|
+
* - `"tcp"`: raw caddy-l4 listener on a parent-host port — pair with
|
|
63
|
+
* native protocol clients (psql, redis-cli, mysql, mongosh).
|
|
64
|
+
* - `"tls"`: caddy-l4 TLS-SNI route on the shared listener. Requires the
|
|
65
|
+
* daemon to have a domain configured AND `SB_L4_TLS_LISTEN` set.
|
|
66
|
+
*
|
|
67
|
+
* Returns a discriminated result keyed on `protocol`. Only the `"tcp"`
|
|
68
|
+
* variant carries `host` / `hostPort` — everything else is in `url`.
|
|
45
69
|
*/
|
|
46
|
-
|
|
47
|
-
private exposePortWithProtocol;
|
|
70
|
+
exposePort(id: string, port: number, options?: ExposePortOptions): Promise<ExposeResult>;
|
|
48
71
|
unexposePort(id: string, port: number): Promise<void>;
|
|
49
72
|
reconcile(): Promise<void>;
|
|
50
73
|
health(): Promise<HealthStatus>;
|
|
51
74
|
mounts(id: string): Promise<MountSpecRedacted[]>;
|
|
75
|
+
getNetworkUsage(id: string): Promise<NetworkUsage>;
|
|
76
|
+
setNetworkLimits(id: string, options: SetNetworkLimitsOptions): Promise<NetworkUsage>;
|
|
52
77
|
private wrap;
|
|
53
78
|
private doJSON;
|
|
54
79
|
private doBytes;
|
|
@@ -94,15 +119,16 @@ export declare class SandboxResource implements Sandbox {
|
|
|
94
119
|
attachSession(sessionID: string, options?: SessionAttachOptions): SessionAttachHandle;
|
|
95
120
|
uploadFile(targetPath: string, data: BinaryLike): Promise<void>;
|
|
96
121
|
downloadFile(targetPath: string): Promise<Uint8Array>;
|
|
97
|
-
exposePort(port: number): Promise<
|
|
98
|
-
exposeTCPPort(port: number): Promise<string>;
|
|
99
|
-
exposeTLSPort(port: number): Promise<string>;
|
|
122
|
+
exposePort(port: number, options?: ExposePortOptions): Promise<ExposeResult>;
|
|
100
123
|
unexposePort(port: number): Promise<void>;
|
|
124
|
+
createSnapshot(name: string): Promise<SandboxSnapshot>;
|
|
101
125
|
start(): Promise<this>;
|
|
102
126
|
stop(): Promise<this>;
|
|
103
127
|
destroy(): Promise<void>;
|
|
104
128
|
resize(options: ResizeOptions): Promise<this>;
|
|
105
129
|
updateLifecycle(lifecycle: Lifecycle): Promise<this>;
|
|
130
|
+
getNetworkUsage(): Promise<NetworkUsage>;
|
|
131
|
+
setNetworkLimits(options: SetNetworkLimitsOptions): Promise<NetworkUsage>;
|
|
106
132
|
toJSON(): Sandbox;
|
|
107
133
|
private apply;
|
|
108
134
|
}
|
package/dist/internal/client.js
CHANGED
|
@@ -1,135 +1,159 @@
|
|
|
1
1
|
import { basename } from "node:path";
|
|
2
|
+
import { PATH_PREFIX as V1_PATH_PREFIX } from "./api/v1/paths.js";
|
|
3
|
+
const DEFAULT_API_VERSION = "v1";
|
|
4
|
+
const PATH_PREFIXES = {
|
|
5
|
+
v1: V1_PATH_PREFIX,
|
|
6
|
+
};
|
|
2
7
|
export class APIClient {
|
|
3
8
|
baseURL;
|
|
9
|
+
apiVersion;
|
|
4
10
|
patToken;
|
|
5
11
|
fetchFn;
|
|
12
|
+
versionPrefix;
|
|
6
13
|
constructor(config) {
|
|
7
14
|
this.baseURL = config.baseURL.replace(/\/+$/, "");
|
|
8
15
|
this.patToken = config.patToken ?? "";
|
|
9
16
|
this.fetchFn = config.fetch ?? fetch;
|
|
17
|
+
this.apiVersion = config.apiVersion ?? DEFAULT_API_VERSION;
|
|
18
|
+
this.versionPrefix = PATH_PREFIXES[this.apiVersion];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build a versioned API path. Pass the suffix beginning with "/" (e.g.
|
|
22
|
+
* "/sandboxes") and the active version's prefix is prepended. Use this for
|
|
23
|
+
* every versioned API call so v2 (when it lands) can be selected by the
|
|
24
|
+
* apiVersion option without touching call sites.
|
|
25
|
+
*/
|
|
26
|
+
versioned(suffix) {
|
|
27
|
+
return `${this.versionPrefix}${suffix}`;
|
|
10
28
|
}
|
|
11
29
|
async create(options) {
|
|
12
|
-
const response = await this.doJSON("POST", "/
|
|
30
|
+
const response = await this.doJSON("POST", this.versioned("/sandboxes"), toApiCreateOptions(options));
|
|
13
31
|
return new SandboxResource(this, fromApiCreateSandboxResponse(response));
|
|
14
32
|
}
|
|
15
33
|
async list() {
|
|
16
|
-
const response = await this.doJSON("GET", "/
|
|
34
|
+
const response = await this.doJSON("GET", this.versioned("/sandboxes"));
|
|
17
35
|
return response.map((item) => this.wrap(item));
|
|
18
36
|
}
|
|
19
37
|
async get(id) {
|
|
20
|
-
const response = await this.doJSON("GET",
|
|
38
|
+
const response = await this.doJSON("GET", `${this.versionPrefix}/sandboxes/${id}`);
|
|
21
39
|
return this.wrap(response);
|
|
22
40
|
}
|
|
23
41
|
async start(id) {
|
|
24
|
-
const response = await this.doJSON("POST",
|
|
42
|
+
const response = await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/start`);
|
|
25
43
|
return this.wrap(response);
|
|
26
44
|
}
|
|
27
45
|
async stop(id) {
|
|
28
|
-
const response = await this.doJSON("POST",
|
|
46
|
+
const response = await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/stop`);
|
|
29
47
|
return this.wrap(response);
|
|
30
48
|
}
|
|
49
|
+
async createSnapshot(id, name) {
|
|
50
|
+
const response = await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/snapshot`, { name });
|
|
51
|
+
return fromApiSandboxSnapshot(response);
|
|
52
|
+
}
|
|
31
53
|
async destroy(id) {
|
|
32
|
-
await this.doJSON("DELETE",
|
|
54
|
+
await this.doJSON("DELETE", `${this.versionPrefix}/sandboxes/${id}`);
|
|
33
55
|
}
|
|
34
56
|
async resize(id, options) {
|
|
35
|
-
const response = await this.doJSON("POST",
|
|
57
|
+
const response = await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/resize`, toApiResizeOptions(options));
|
|
36
58
|
return this.wrap(response);
|
|
37
59
|
}
|
|
38
60
|
async updateLifecycle(id, lifecycle) {
|
|
39
|
-
const response = await this.doJSON("PUT",
|
|
61
|
+
const response = await this.doJSON("PUT", `${this.versionPrefix}/sandboxes/${id}/lifecycle`, toApiLifecycle(lifecycle));
|
|
40
62
|
return this.wrap(response);
|
|
41
63
|
}
|
|
42
64
|
async exec(id, request) {
|
|
43
|
-
const response = await this.doJSON("POST",
|
|
65
|
+
const response = await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/toolbox/process/execute`, toApiExecRequest(request));
|
|
44
66
|
return fromApiExecResult(response);
|
|
45
67
|
}
|
|
46
68
|
execStream(id, options) {
|
|
47
|
-
return openExecStream(this.baseURL, this.patToken, id, options);
|
|
69
|
+
return openExecStream(this.baseURL, this.versionPrefix, this.patToken, id, options);
|
|
48
70
|
}
|
|
49
71
|
async createSession(id, options) {
|
|
50
|
-
const response = await this.doJSON("POST",
|
|
72
|
+
const response = await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/sessions`, toApiCreateSessionOptions(options));
|
|
51
73
|
return fromApiSession(response);
|
|
52
74
|
}
|
|
53
75
|
async listSessions(id) {
|
|
54
|
-
const response = await this.doJSON("GET",
|
|
76
|
+
const response = await this.doJSON("GET", `${this.versionPrefix}/sandboxes/${id}/sessions`);
|
|
55
77
|
return response.sessions.map(fromApiSession);
|
|
56
78
|
}
|
|
57
79
|
async getSession(id, sessionID) {
|
|
58
|
-
const response = await this.doJSON("GET",
|
|
80
|
+
const response = await this.doJSON("GET", `${this.versionPrefix}/sandboxes/${id}/sessions/${sessionID}`);
|
|
59
81
|
return fromApiSession(response);
|
|
60
82
|
}
|
|
61
83
|
async deleteSession(id, sessionID) {
|
|
62
|
-
await this.doJSON("DELETE",
|
|
84
|
+
await this.doJSON("DELETE", `${this.versionPrefix}/sandboxes/${id}/sessions/${sessionID}`);
|
|
63
85
|
}
|
|
64
86
|
async signalSession(id, sessionID, signal) {
|
|
65
|
-
await this.doJSON("POST",
|
|
87
|
+
await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/sessions/${sessionID}/signal`, { signal });
|
|
66
88
|
}
|
|
67
89
|
async resizeSession(id, sessionID, cols, rows) {
|
|
68
|
-
await this.doJSON("POST",
|
|
90
|
+
await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/sessions/${sessionID}/resize`, { cols, rows });
|
|
69
91
|
}
|
|
70
92
|
async sessionLog(id, sessionID) {
|
|
71
|
-
return this.doBytes(
|
|
93
|
+
return this.doBytes(`${this.versionPrefix}/sandboxes/${id}/sessions/${sessionID}/log`);
|
|
72
94
|
}
|
|
73
95
|
async sessionRecording(id, sessionID) {
|
|
74
|
-
return this.doBytes(
|
|
96
|
+
return this.doBytes(`${this.versionPrefix}/sandboxes/${id}/sessions/${sessionID}/recording`);
|
|
75
97
|
}
|
|
76
98
|
attachSession(id, sessionID, options = {}) {
|
|
77
|
-
return openSessionAttach(this.baseURL, this.patToken, id, sessionID, options);
|
|
99
|
+
return openSessionAttach(this.baseURL, this.versionPrefix, this.patToken, id, sessionID, options);
|
|
78
100
|
}
|
|
79
101
|
async uploadFile(id, targetPath, data) {
|
|
80
102
|
const form = new FormData();
|
|
81
103
|
form.set("path", targetPath);
|
|
82
104
|
form.set("file", toBlob(data), basename(targetPath));
|
|
83
|
-
const response = await this.request("POST",
|
|
105
|
+
const response = await this.request("POST", `${this.versionPrefix}/sandboxes/${id}/toolbox/files/upload`, { body: form });
|
|
84
106
|
if (!response.ok) {
|
|
85
107
|
throw await decodeError(response);
|
|
86
108
|
}
|
|
87
109
|
}
|
|
88
110
|
async downloadFile(id, targetPath) {
|
|
89
|
-
const response = await this.request("GET",
|
|
111
|
+
const response = await this.request("GET", `${this.versionPrefix}/sandboxes/${id}/toolbox/files/download?path=${encodeURIComponent(targetPath)}`);
|
|
90
112
|
if (!response.ok) {
|
|
91
113
|
throw await decodeError(response);
|
|
92
114
|
}
|
|
93
115
|
return new Uint8Array(await response.arrayBuffer());
|
|
94
116
|
}
|
|
95
|
-
async exposePort(id, port) {
|
|
96
|
-
return this.exposePortWithProtocol(id, port);
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Publish a raw TCP port through caddy-l4. The returned URL is in
|
|
100
|
-
* `tcp://<host>:<port>` form and works directly with `psql`, `redis-cli`,
|
|
101
|
-
* `mysql`, `mongosh`, and any other native-protocol client.
|
|
102
|
-
*/
|
|
103
|
-
async exposeTCPPort(id, port) {
|
|
104
|
-
return this.exposePortWithProtocol(id, port, "tcp");
|
|
105
|
-
}
|
|
106
117
|
/**
|
|
107
|
-
* Publish a
|
|
108
|
-
* `
|
|
109
|
-
*
|
|
118
|
+
* Publish a sandbox container port. The chosen wire protocol is selected by
|
|
119
|
+
* `options.protocol` (defaults to `"http"`):
|
|
120
|
+
*
|
|
121
|
+
* - `"http"`: Caddy HTTP reverse proxy at `https://<id>-<port>.<domain>`.
|
|
122
|
+
* - `"tcp"`: raw caddy-l4 listener on a parent-host port — pair with
|
|
123
|
+
* native protocol clients (psql, redis-cli, mysql, mongosh).
|
|
124
|
+
* - `"tls"`: caddy-l4 TLS-SNI route on the shared listener. Requires the
|
|
125
|
+
* daemon to have a domain configured AND `SB_L4_TLS_LISTEN` set.
|
|
126
|
+
*
|
|
127
|
+
* Returns a discriminated result keyed on `protocol`. Only the `"tcp"`
|
|
128
|
+
* variant carries `host` / `hostPort` — everything else is in `url`.
|
|
110
129
|
*/
|
|
111
|
-
async
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const body = protocol ? { protocol } : undefined;
|
|
116
|
-
const response = await this.doJSON("POST", `/v1/sandboxes/${id}/ports/${port}`, body);
|
|
117
|
-
return response.public_url;
|
|
130
|
+
async exposePort(id, port, options = {}) {
|
|
131
|
+
const body = options.protocol ? { protocol: options.protocol } : undefined;
|
|
132
|
+
const response = await this.doJSON("POST", `${this.versionPrefix}/sandboxes/${id}/ports/${port}`, body);
|
|
133
|
+
return fromApiExposePortResponse(response);
|
|
118
134
|
}
|
|
119
135
|
async unexposePort(id, port) {
|
|
120
|
-
await this.doJSON("DELETE",
|
|
136
|
+
await this.doJSON("DELETE", `${this.versionPrefix}/sandboxes/${id}/ports/${port}`);
|
|
121
137
|
}
|
|
122
138
|
async reconcile() {
|
|
123
|
-
await this.doJSON("POST", "/
|
|
139
|
+
await this.doJSON("POST", this.versioned("/admin/reconcile"));
|
|
124
140
|
}
|
|
125
141
|
async health() {
|
|
126
142
|
const response = await this.doJSON("GET", "/health");
|
|
127
143
|
return fromApiHealthStatus(response);
|
|
128
144
|
}
|
|
129
145
|
async mounts(id) {
|
|
130
|
-
const response = await this.doJSON("GET",
|
|
146
|
+
const response = await this.doJSON("GET", `${this.versionPrefix}/sandboxes/${id}/mounts`);
|
|
131
147
|
return response.mounts.map(fromApiMountSpecRedacted);
|
|
132
148
|
}
|
|
149
|
+
async getNetworkUsage(id) {
|
|
150
|
+
const response = await this.doJSON("GET", `${this.versionPrefix}/sandboxes/${id}/network/usage`);
|
|
151
|
+
return fromApiNetworkUsage(response);
|
|
152
|
+
}
|
|
153
|
+
async setNetworkLimits(id, options) {
|
|
154
|
+
const response = await this.doJSON("PATCH", `${this.versionPrefix}/sandboxes/${id}/network/limits`, toApiSetNetworkLimitsOptions(options));
|
|
155
|
+
return fromApiNetworkUsage(response);
|
|
156
|
+
}
|
|
133
157
|
wrap(sandbox) {
|
|
134
158
|
return new SandboxResource(this, fromApiSandbox(sandbox));
|
|
135
159
|
}
|
|
@@ -219,18 +243,15 @@ export class SandboxResource {
|
|
|
219
243
|
async downloadFile(targetPath) {
|
|
220
244
|
return this.client.downloadFile(this.id, targetPath);
|
|
221
245
|
}
|
|
222
|
-
async exposePort(port) {
|
|
223
|
-
return this.client.exposePort(this.id, port);
|
|
224
|
-
}
|
|
225
|
-
async exposeTCPPort(port) {
|
|
226
|
-
return this.client.exposeTCPPort(this.id, port);
|
|
227
|
-
}
|
|
228
|
-
async exposeTLSPort(port) {
|
|
229
|
-
return this.client.exposeTLSPort(this.id, port);
|
|
246
|
+
async exposePort(port, options) {
|
|
247
|
+
return this.client.exposePort(this.id, port, options);
|
|
230
248
|
}
|
|
231
249
|
async unexposePort(port) {
|
|
232
250
|
await this.client.unexposePort(this.id, port);
|
|
233
251
|
}
|
|
252
|
+
async createSnapshot(name) {
|
|
253
|
+
return this.client.createSnapshot(this.id, name);
|
|
254
|
+
}
|
|
234
255
|
async start() {
|
|
235
256
|
const updated = await this.client.start(this.id);
|
|
236
257
|
this.apply(updated.toJSON());
|
|
@@ -254,6 +275,12 @@ export class SandboxResource {
|
|
|
254
275
|
this.apply(updated.toJSON());
|
|
255
276
|
return this;
|
|
256
277
|
}
|
|
278
|
+
async getNetworkUsage() {
|
|
279
|
+
return this.client.getNetworkUsage(this.id);
|
|
280
|
+
}
|
|
281
|
+
async setNetworkLimits(options) {
|
|
282
|
+
return this.client.setNetworkLimits(this.id, options);
|
|
283
|
+
}
|
|
257
284
|
toJSON() {
|
|
258
285
|
return cloneSandbox(this);
|
|
259
286
|
}
|
|
@@ -270,6 +297,8 @@ function toApiCreateOptions(options) {
|
|
|
270
297
|
env: options.env,
|
|
271
298
|
os_user: options.osUser,
|
|
272
299
|
network_block_all: options.networkBlockAll,
|
|
300
|
+
network_bytes_in_limit: options.networkBytesInLimit,
|
|
301
|
+
network_bytes_out_limit: options.networkBytesOutLimit,
|
|
273
302
|
registry: options.registry,
|
|
274
303
|
container_command: options.containerCommand,
|
|
275
304
|
mounts: options.mounts?.map(toApiMountSpec),
|
|
@@ -353,6 +382,15 @@ function fromApiCreateSandboxResponse(response) {
|
|
|
353
382
|
sshPrivateKey: response.ssh_private_key,
|
|
354
383
|
};
|
|
355
384
|
}
|
|
385
|
+
function fromApiSandboxSnapshot(snapshot) {
|
|
386
|
+
return {
|
|
387
|
+
name: snapshot.name,
|
|
388
|
+
image: snapshot.image,
|
|
389
|
+
imageID: snapshot.image_id,
|
|
390
|
+
sourceSandboxID: snapshot.source_sandbox_id,
|
|
391
|
+
createdAt: snapshot.created_at,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
356
394
|
function fromApiSession(session) {
|
|
357
395
|
return {
|
|
358
396
|
id: session.id,
|
|
@@ -379,6 +417,21 @@ function fromApiExposedPort(port) {
|
|
|
379
417
|
createdAt: port.created_at,
|
|
380
418
|
};
|
|
381
419
|
}
|
|
420
|
+
function fromApiExposePortResponse(response) {
|
|
421
|
+
switch (response.protocol) {
|
|
422
|
+
case "tcp":
|
|
423
|
+
return {
|
|
424
|
+
protocol: "tcp",
|
|
425
|
+
url: response.public_url,
|
|
426
|
+
host: response.host ?? "",
|
|
427
|
+
hostPort: response.host_port ?? 0,
|
|
428
|
+
};
|
|
429
|
+
case "tls":
|
|
430
|
+
return { protocol: "tls", url: response.public_url };
|
|
431
|
+
case "http":
|
|
432
|
+
return { protocol: "http", url: response.public_url };
|
|
433
|
+
}
|
|
434
|
+
}
|
|
382
435
|
function fromApiExecResult(result) {
|
|
383
436
|
return {
|
|
384
437
|
stdout: result.stdout,
|
|
@@ -417,6 +470,24 @@ function fromApiMountSpecRedacted(mount) {
|
|
|
417
470
|
hasCredentials: mount.has_credentials,
|
|
418
471
|
};
|
|
419
472
|
}
|
|
473
|
+
function fromApiNetworkUsage(usage) {
|
|
474
|
+
return {
|
|
475
|
+
sandboxID: usage.sandbox_id,
|
|
476
|
+
bytesIn: usage.bytes_in,
|
|
477
|
+
bytesOut: usage.bytes_out,
|
|
478
|
+
bytesInLimit: usage.bytes_in_limit,
|
|
479
|
+
bytesOutLimit: usage.bytes_out_limit,
|
|
480
|
+
quotaExceeded: usage.quota_exceeded,
|
|
481
|
+
quotaExceededAt: usage.quota_exceeded_at ?? undefined,
|
|
482
|
+
lastSampledAt: usage.last_sampled_at ?? undefined,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
function toApiSetNetworkLimitsOptions(options) {
|
|
486
|
+
return {
|
|
487
|
+
network_bytes_in_limit: options.networkBytesInLimit,
|
|
488
|
+
network_bytes_out_limit: options.networkBytesOutLimit,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
420
491
|
function fromApiLifecycle(lifecycle) {
|
|
421
492
|
const result = {};
|
|
422
493
|
if (lifecycle?.stop_if_idle_for !== undefined) {
|
|
@@ -503,8 +574,8 @@ function toWebSocketBinaryFrame(data) {
|
|
|
503
574
|
frame.set(bytes);
|
|
504
575
|
return frame;
|
|
505
576
|
}
|
|
506
|
-
function openExecStream(baseURL, patToken, sandboxID, options) {
|
|
507
|
-
const wsURL = baseURL.replace(/^http/, "ws") +
|
|
577
|
+
function openExecStream(baseURL, versionPrefix, patToken, sandboxID, options) {
|
|
578
|
+
const wsURL = baseURL.replace(/^http/, "ws") + `${versionPrefix}/sandboxes/${encodeURIComponent(sandboxID)}/toolbox/process/exec/stream`;
|
|
508
579
|
const WS = globalThis.WebSocket;
|
|
509
580
|
if (!WS) {
|
|
510
581
|
throw new Error("WebSocket is not available in this runtime — Node 22+ or a browser is required");
|
|
@@ -609,8 +680,8 @@ function openExecStream(baseURL, patToken, sandboxID, options) {
|
|
|
609
680
|
done,
|
|
610
681
|
};
|
|
611
682
|
}
|
|
612
|
-
function openSessionAttach(baseURL, patToken, sandboxID, sessionID, options) {
|
|
613
|
-
const wsURL = baseURL.replace(/^http/, "ws") +
|
|
683
|
+
function openSessionAttach(baseURL, versionPrefix, patToken, sandboxID, sessionID, options) {
|
|
684
|
+
const wsURL = baseURL.replace(/^http/, "ws") + `${versionPrefix}/sandboxes/${encodeURIComponent(sandboxID)}/sessions/${encodeURIComponent(sessionID)}/attach`;
|
|
614
685
|
const WS = globalThis.WebSocket;
|
|
615
686
|
if (!WS) {
|
|
616
687
|
throw new Error("WebSocket is not available in this runtime — Node 22+ or a browser is required");
|
package/dist/types.d.ts
CHANGED
|
@@ -69,6 +69,19 @@ export interface CreateOptions {
|
|
|
69
69
|
env?: Record<string, string>;
|
|
70
70
|
osUser?: string;
|
|
71
71
|
networkBlockAll?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Cap on bytes the sandbox may receive from outside the container before its
|
|
74
|
+
* ingress is dropped via per-IP iptables rule. `0` (default) is unlimited.
|
|
75
|
+
* Limits can be raised or lifted at runtime via `setNetworkLimits`.
|
|
76
|
+
*/
|
|
77
|
+
networkBytesInLimit?: number;
|
|
78
|
+
/**
|
|
79
|
+
* Cap on bytes the sandbox may send to outside the container before its
|
|
80
|
+
* egress is dropped. `0` (default) is unlimited. The block reuses the same
|
|
81
|
+
* iptables row as `networkBlockAll`; clearing the quota does not lift an
|
|
82
|
+
* operator-set blanket egress block.
|
|
83
|
+
*/
|
|
84
|
+
networkBytesOutLimit?: number;
|
|
72
85
|
registry?: RegistryAuth;
|
|
73
86
|
containerCommand?: string[];
|
|
74
87
|
mounts?: MountSpec[];
|
|
@@ -129,6 +142,42 @@ export interface ExposedPort {
|
|
|
129
142
|
publicURL: string;
|
|
130
143
|
createdAt: string;
|
|
131
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Wire protocol an exposure publishes through:
|
|
147
|
+
* - "http": Caddy HTTP reverse proxy at https://<id>-<port>.<domain>.
|
|
148
|
+
* - "tcp": raw caddy-l4 listener on a parent-host port.
|
|
149
|
+
* - "tls": caddy-l4 TLS-SNI route on the shared :443 listener.
|
|
150
|
+
*/
|
|
151
|
+
export type ExposeProtocol = "http" | "tcp" | "tls";
|
|
152
|
+
export interface ExposePortOptions {
|
|
153
|
+
/** Defaults to "http" when omitted. */
|
|
154
|
+
protocol?: ExposeProtocol;
|
|
155
|
+
}
|
|
156
|
+
export interface SandboxSnapshot {
|
|
157
|
+
name: string;
|
|
158
|
+
image: string;
|
|
159
|
+
imageID?: string;
|
|
160
|
+
sourceSandboxID: string;
|
|
161
|
+
createdAt: string;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Discriminated result from `exposePort`. Branch on `protocol` to access the
|
|
165
|
+
* fields that are meaningful for the chosen wire protocol — only the raw-TCP
|
|
166
|
+
* variant carries `host` and `hostPort`, which are what native protocol
|
|
167
|
+
* clients (psql, redis-cli, mysql, mongosh, …) need to dial.
|
|
168
|
+
*/
|
|
169
|
+
export type ExposeResult = {
|
|
170
|
+
protocol: "http";
|
|
171
|
+
url: string;
|
|
172
|
+
} | {
|
|
173
|
+
protocol: "tcp";
|
|
174
|
+
url: string;
|
|
175
|
+
host: string;
|
|
176
|
+
hostPort: number;
|
|
177
|
+
} | {
|
|
178
|
+
protocol: "tls";
|
|
179
|
+
url: string;
|
|
180
|
+
};
|
|
132
181
|
export interface Sandbox {
|
|
133
182
|
id: string;
|
|
134
183
|
image: string;
|
|
@@ -160,6 +209,29 @@ export interface Sandbox {
|
|
|
160
209
|
/** GPU configuration this sandbox was created with. Absent means no GPU. */
|
|
161
210
|
gpus?: GPUOptions;
|
|
162
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Per-sandbox network byte counters and the configured caps that drive the
|
|
214
|
+
* quota enforcer. `bytesIn` is traffic the container received (ingress);
|
|
215
|
+
* `bytesOut` is traffic the container sent (egress). A `*Limit` of `0` means
|
|
216
|
+
* unlimited. `quotaExceeded` flips true the first time the meter crosses
|
|
217
|
+
* either configured cap and stays true until the operator raises the limit.
|
|
218
|
+
*/
|
|
219
|
+
export interface NetworkUsage {
|
|
220
|
+
sandboxID: string;
|
|
221
|
+
bytesIn: number;
|
|
222
|
+
bytesOut: number;
|
|
223
|
+
bytesInLimit: number;
|
|
224
|
+
bytesOutLimit: number;
|
|
225
|
+
quotaExceeded: boolean;
|
|
226
|
+
quotaExceededAt?: string;
|
|
227
|
+
/** Absent until the netstats poller has produced at least one sample. */
|
|
228
|
+
lastSampledAt?: string;
|
|
229
|
+
}
|
|
230
|
+
export interface SetNetworkLimitsOptions {
|
|
231
|
+
/** Omit (undefined) to leave unchanged. `0` means unlimited. */
|
|
232
|
+
networkBytesInLimit?: number;
|
|
233
|
+
networkBytesOutLimit?: number;
|
|
234
|
+
}
|
|
163
235
|
export interface ExecRequest {
|
|
164
236
|
command: string;
|
|
165
237
|
workDir?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aerol-ai/aerolvm-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsc -p tsconfig.json",
|
|
16
|
+
"example:browser-isolation:src": "npx tsx src/examples/browserIsolation.ts",
|
|
16
17
|
"example:create": "npm run build && node dist/examples/createSandbox.js",
|
|
17
18
|
"example:create:src": "npx tsx src/examples/createSandbox.ts",
|
|
18
19
|
"example:delete": "npm run build && node dist/examples/deleteSandbox.js",
|
|
19
20
|
"example:delete:src": "npx tsx src/examples/deleteSandbox.ts",
|
|
20
|
-
"test": "tsc -p tsconfig.test.json --outDir dist-test && node --test dist-test/*.test.js"
|
|
21
|
+
"test": "tsc -p tsconfig.test.json --outDir dist-test && node --test dist-test/*.test.js dist-test/internal/*.test.js"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"@types/node": "^25.6.2",
|