@aerol-ai/aerolvm-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/MicroVM.d.ts +36 -0
- package/dist/MicroVM.js +103 -0
- package/dist/Sandbox.d.ts +5 -0
- package/dist/Sandbox.js +6 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/internal/client.d.ts +93 -0
- package/dist/internal/client.js +629 -0
- package/dist/types.d.ts +193 -0
- package/dist/types.js +1 -0
- package/package.json +25 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Sandbox } from "./Sandbox.js";
|
|
2
|
+
import type { CreateOptions, CreateSessionOptions, ExecStreamHandle, ExecStreamOptions, HealthStatus, Lifecycle, MountSpecRedacted, ResizeOptions, Session, SessionAttachHandle, SessionAttachOptions } from "./types.js";
|
|
3
|
+
type FetchLike = typeof fetch;
|
|
4
|
+
export interface MicroVMConfig {
|
|
5
|
+
patToken?: string;
|
|
6
|
+
apiUrl?: string;
|
|
7
|
+
fetch?: FetchLike;
|
|
8
|
+
}
|
|
9
|
+
export declare class MicroVM {
|
|
10
|
+
readonly apiUrl: string;
|
|
11
|
+
readonly patToken: string;
|
|
12
|
+
private readonly client;
|
|
13
|
+
constructor(config?: MicroVMConfig);
|
|
14
|
+
create(options: CreateOptions): Promise<Sandbox>;
|
|
15
|
+
list(): Promise<Sandbox[]>;
|
|
16
|
+
get(id: string): Promise<Sandbox>;
|
|
17
|
+
start(id: string): Promise<Sandbox>;
|
|
18
|
+
stop(id: string): Promise<Sandbox>;
|
|
19
|
+
destroy(id: string): Promise<void>;
|
|
20
|
+
resize(id: string, options: ResizeOptions): Promise<Sandbox>;
|
|
21
|
+
updateLifecycle(id: string, lifecycle: Lifecycle): Promise<Sandbox>;
|
|
22
|
+
health(): Promise<HealthStatus>;
|
|
23
|
+
mounts(sandboxID: string): Promise<MountSpecRedacted[]>;
|
|
24
|
+
execStream(sandboxID: string, options: ExecStreamOptions): ExecStreamHandle;
|
|
25
|
+
createSession(sandboxID: string, options: CreateSessionOptions): Promise<Session>;
|
|
26
|
+
listSessions(sandboxID: string): Promise<Session[]>;
|
|
27
|
+
getSession(sandboxID: string, sessionID: string): Promise<Session>;
|
|
28
|
+
deleteSession(sandboxID: string, sessionID: string): Promise<void>;
|
|
29
|
+
signalSession(sandboxID: string, sessionID: string, signal: string): Promise<void>;
|
|
30
|
+
resizeSession(sandboxID: string, sessionID: string, cols: number, rows: number): Promise<void>;
|
|
31
|
+
sessionLog(sandboxID: string, sessionID: string): Promise<Uint8Array>;
|
|
32
|
+
sessionRecording(sandboxID: string, sessionID: string): Promise<Uint8Array>;
|
|
33
|
+
attachSession(sandboxID: string, sessionID: string, options?: SessionAttachOptions): SessionAttachHandle;
|
|
34
|
+
private wrap;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
package/dist/MicroVM.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { APIClient } from "./internal/client.js";
|
|
2
|
+
import { Sandbox } from "./Sandbox.js";
|
|
3
|
+
const defaultAPIURL = "http://127.0.0.1:8080";
|
|
4
|
+
const authRequiredErrorMessage = "PAT token is required. Set patToken or SB_PAT_TOKEN.";
|
|
5
|
+
export class MicroVM {
|
|
6
|
+
apiUrl;
|
|
7
|
+
patToken;
|
|
8
|
+
client;
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
const patToken = config.patToken ?? readEnv("SB_PAT_TOKEN") ?? "";
|
|
11
|
+
const apiUrl = normalizeURL(config.apiUrl ?? readEnv("SB_API_URL") ?? defaultAPIURL);
|
|
12
|
+
if (patToken === "") {
|
|
13
|
+
throw new Error(authRequiredErrorMessage);
|
|
14
|
+
}
|
|
15
|
+
this.apiUrl = apiUrl;
|
|
16
|
+
this.patToken = patToken;
|
|
17
|
+
this.client = new APIClient({
|
|
18
|
+
baseURL: apiUrl,
|
|
19
|
+
patToken,
|
|
20
|
+
fetch: config.fetch,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async create(options) {
|
|
24
|
+
const sandbox = await this.client.create(options);
|
|
25
|
+
return this.wrap(sandbox.toJSON());
|
|
26
|
+
}
|
|
27
|
+
async list() {
|
|
28
|
+
const sandboxes = await this.client.list();
|
|
29
|
+
return sandboxes.map((sandbox) => this.wrap(sandbox.toJSON()));
|
|
30
|
+
}
|
|
31
|
+
async get(id) {
|
|
32
|
+
const sandbox = await this.client.get(id);
|
|
33
|
+
return this.wrap(sandbox.toJSON());
|
|
34
|
+
}
|
|
35
|
+
async start(id) {
|
|
36
|
+
const sandbox = await this.client.start(id);
|
|
37
|
+
return this.wrap(sandbox.toJSON());
|
|
38
|
+
}
|
|
39
|
+
async stop(id) {
|
|
40
|
+
const sandbox = await this.client.stop(id);
|
|
41
|
+
return this.wrap(sandbox.toJSON());
|
|
42
|
+
}
|
|
43
|
+
async destroy(id) {
|
|
44
|
+
await this.client.destroy(id);
|
|
45
|
+
}
|
|
46
|
+
async resize(id, options) {
|
|
47
|
+
const sandbox = await this.client.resize(id, options);
|
|
48
|
+
return this.wrap(sandbox.toJSON());
|
|
49
|
+
}
|
|
50
|
+
async updateLifecycle(id, lifecycle) {
|
|
51
|
+
const sandbox = await this.client.updateLifecycle(id, lifecycle);
|
|
52
|
+
return this.wrap(sandbox.toJSON());
|
|
53
|
+
}
|
|
54
|
+
async health() {
|
|
55
|
+
return this.client.health();
|
|
56
|
+
}
|
|
57
|
+
async mounts(sandboxID) {
|
|
58
|
+
return this.client.mounts(sandboxID);
|
|
59
|
+
}
|
|
60
|
+
execStream(sandboxID, options) {
|
|
61
|
+
return this.client.execStream(sandboxID, options);
|
|
62
|
+
}
|
|
63
|
+
async createSession(sandboxID, options) {
|
|
64
|
+
return this.client.createSession(sandboxID, options);
|
|
65
|
+
}
|
|
66
|
+
async listSessions(sandboxID) {
|
|
67
|
+
return this.client.listSessions(sandboxID);
|
|
68
|
+
}
|
|
69
|
+
async getSession(sandboxID, sessionID) {
|
|
70
|
+
return this.client.getSession(sandboxID, sessionID);
|
|
71
|
+
}
|
|
72
|
+
async deleteSession(sandboxID, sessionID) {
|
|
73
|
+
await this.client.deleteSession(sandboxID, sessionID);
|
|
74
|
+
}
|
|
75
|
+
async signalSession(sandboxID, sessionID, signal) {
|
|
76
|
+
await this.client.signalSession(sandboxID, sessionID, signal);
|
|
77
|
+
}
|
|
78
|
+
async resizeSession(sandboxID, sessionID, cols, rows) {
|
|
79
|
+
await this.client.resizeSession(sandboxID, sessionID, cols, rows);
|
|
80
|
+
}
|
|
81
|
+
async sessionLog(sandboxID, sessionID) {
|
|
82
|
+
return this.client.sessionLog(sandboxID, sessionID);
|
|
83
|
+
}
|
|
84
|
+
async sessionRecording(sandboxID, sessionID) {
|
|
85
|
+
return this.client.sessionRecording(sandboxID, sessionID);
|
|
86
|
+
}
|
|
87
|
+
attachSession(sandboxID, sessionID, options = {}) {
|
|
88
|
+
return this.client.attachSession(sandboxID, sessionID, options);
|
|
89
|
+
}
|
|
90
|
+
wrap(sandbox) {
|
|
91
|
+
return new Sandbox(this.client, sandbox);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function normalizeURL(value) {
|
|
95
|
+
return value.replace(/\/+$/, "");
|
|
96
|
+
}
|
|
97
|
+
function readEnv(name) {
|
|
98
|
+
if (typeof process === "undefined" || !process.env) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
const value = process.env[name];
|
|
102
|
+
return typeof value === "string" && value !== "" ? value : undefined;
|
|
103
|
+
}
|
package/dist/Sandbox.js
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { MicroVM } from "./MicroVM.js";
|
|
2
|
+
export { Sandbox } from "./Sandbox.js";
|
|
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";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { BinaryLike, CreateOptions, CreateSessionOptions, ExecRequest, ExecResult, ExecStreamHandle, ExecStreamOptions, ExposedPort, HealthStatus, Lifecycle, MountSpecRedacted, ResizeOptions, Sandbox, Session, SessionAttachHandle, SessionAttachOptions } from "../types.js";
|
|
2
|
+
type FetchLike = typeof fetch;
|
|
3
|
+
export interface APIClientConfig {
|
|
4
|
+
baseURL: string;
|
|
5
|
+
patToken?: string;
|
|
6
|
+
fetch?: FetchLike;
|
|
7
|
+
}
|
|
8
|
+
export declare class APIClient {
|
|
9
|
+
readonly baseURL: string;
|
|
10
|
+
private readonly patToken;
|
|
11
|
+
private readonly fetchFn;
|
|
12
|
+
constructor(config: APIClientConfig);
|
|
13
|
+
create(options: CreateOptions): Promise<SandboxResource>;
|
|
14
|
+
list(): Promise<SandboxResource[]>;
|
|
15
|
+
get(id: string): Promise<SandboxResource>;
|
|
16
|
+
start(id: string): Promise<SandboxResource>;
|
|
17
|
+
stop(id: string): Promise<SandboxResource>;
|
|
18
|
+
destroy(id: string): Promise<void>;
|
|
19
|
+
resize(id: string, options: ResizeOptions): Promise<SandboxResource>;
|
|
20
|
+
updateLifecycle(id: string, lifecycle: Lifecycle): Promise<SandboxResource>;
|
|
21
|
+
exec(id: string, request: ExecRequest): Promise<ExecResult>;
|
|
22
|
+
execStream(id: string, options: ExecStreamOptions): ExecStreamHandle;
|
|
23
|
+
createSession(id: string, options: CreateSessionOptions): Promise<Session>;
|
|
24
|
+
listSessions(id: string): Promise<Session[]>;
|
|
25
|
+
getSession(id: string, sessionID: string): Promise<Session>;
|
|
26
|
+
deleteSession(id: string, sessionID: string): Promise<void>;
|
|
27
|
+
signalSession(id: string, sessionID: string, signal: string): Promise<void>;
|
|
28
|
+
resizeSession(id: string, sessionID: string, cols: number, rows: number): Promise<void>;
|
|
29
|
+
sessionLog(id: string, sessionID: string): Promise<Uint8Array>;
|
|
30
|
+
sessionRecording(id: string, sessionID: string): Promise<Uint8Array>;
|
|
31
|
+
attachSession(id: string, sessionID: string, options?: SessionAttachOptions): SessionAttachHandle;
|
|
32
|
+
uploadFile(id: string, targetPath: string, data: BinaryLike): Promise<void>;
|
|
33
|
+
downloadFile(id: string, targetPath: string): Promise<Uint8Array>;
|
|
34
|
+
exposePort(id: string, port: number): Promise<string>;
|
|
35
|
+
unexposePort(id: string, port: number): Promise<void>;
|
|
36
|
+
health(): Promise<HealthStatus>;
|
|
37
|
+
mounts(id: string): Promise<MountSpecRedacted[]>;
|
|
38
|
+
private wrap;
|
|
39
|
+
private doJSON;
|
|
40
|
+
private doBytes;
|
|
41
|
+
private request;
|
|
42
|
+
}
|
|
43
|
+
export declare class SandboxResource implements Sandbox {
|
|
44
|
+
id: string;
|
|
45
|
+
image: string;
|
|
46
|
+
status: Sandbox["status"];
|
|
47
|
+
publicURL: string;
|
|
48
|
+
containerID?: string;
|
|
49
|
+
containerIP?: string;
|
|
50
|
+
cpu: number;
|
|
51
|
+
memoryMB: number;
|
|
52
|
+
diskGB: number;
|
|
53
|
+
osUser: string;
|
|
54
|
+
env?: Record<string, string>;
|
|
55
|
+
networkBlockAll: boolean;
|
|
56
|
+
toolboxEnabled: boolean;
|
|
57
|
+
sshPublicKey?: string;
|
|
58
|
+
sshPrivateKey?: string;
|
|
59
|
+
exposedPorts?: ExposedPort[];
|
|
60
|
+
createdAt: string;
|
|
61
|
+
updatedAt: string;
|
|
62
|
+
lastActiveAt: string;
|
|
63
|
+
lastError?: string;
|
|
64
|
+
containerCommand?: string[];
|
|
65
|
+
lifecycle: Lifecycle;
|
|
66
|
+
runtime: Sandbox["runtime"];
|
|
67
|
+
protected readonly client: APIClient;
|
|
68
|
+
constructor(client: APIClient, sandbox: Sandbox);
|
|
69
|
+
refresh(): Promise<this>;
|
|
70
|
+
exec(command: string | ExecRequest): Promise<ExecResult>;
|
|
71
|
+
execStream(options: ExecStreamOptions): ExecStreamHandle;
|
|
72
|
+
createSession(options: CreateSessionOptions): Promise<Session>;
|
|
73
|
+
listSessions(): Promise<Session[]>;
|
|
74
|
+
getSession(sessionID: string): Promise<Session>;
|
|
75
|
+
deleteSession(sessionID: string): Promise<void>;
|
|
76
|
+
signalSession(sessionID: string, signal: string): Promise<void>;
|
|
77
|
+
resizeSession(sessionID: string, cols: number, rows: number): Promise<void>;
|
|
78
|
+
sessionLog(sessionID: string): Promise<Uint8Array>;
|
|
79
|
+
sessionRecording(sessionID: string): Promise<Uint8Array>;
|
|
80
|
+
attachSession(sessionID: string, options?: SessionAttachOptions): SessionAttachHandle;
|
|
81
|
+
uploadFile(targetPath: string, data: BinaryLike): Promise<void>;
|
|
82
|
+
downloadFile(targetPath: string): Promise<Uint8Array>;
|
|
83
|
+
exposePort(port: number): Promise<string>;
|
|
84
|
+
unexposePort(port: number): Promise<void>;
|
|
85
|
+
start(): Promise<this>;
|
|
86
|
+
stop(): Promise<this>;
|
|
87
|
+
destroy(): Promise<void>;
|
|
88
|
+
resize(options: ResizeOptions): Promise<this>;
|
|
89
|
+
updateLifecycle(lifecycle: Lifecycle): Promise<this>;
|
|
90
|
+
toJSON(): Sandbox;
|
|
91
|
+
private apply;
|
|
92
|
+
}
|
|
93
|
+
export {};
|
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
export class APIClient {
|
|
3
|
+
baseURL;
|
|
4
|
+
patToken;
|
|
5
|
+
fetchFn;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.baseURL = config.baseURL.replace(/\/+$/, "");
|
|
8
|
+
this.patToken = config.patToken ?? "";
|
|
9
|
+
this.fetchFn = config.fetch ?? fetch;
|
|
10
|
+
}
|
|
11
|
+
async create(options) {
|
|
12
|
+
const response = await this.doJSON("POST", "/v1/sandboxes", toApiCreateOptions(options));
|
|
13
|
+
return new SandboxResource(this, fromApiCreateSandboxResponse(response));
|
|
14
|
+
}
|
|
15
|
+
async list() {
|
|
16
|
+
const response = await this.doJSON("GET", "/v1/sandboxes");
|
|
17
|
+
return response.map((item) => this.wrap(item));
|
|
18
|
+
}
|
|
19
|
+
async get(id) {
|
|
20
|
+
const response = await this.doJSON("GET", `/v1/sandboxes/${id}`);
|
|
21
|
+
return this.wrap(response);
|
|
22
|
+
}
|
|
23
|
+
async start(id) {
|
|
24
|
+
const response = await this.doJSON("POST", `/v1/sandboxes/${id}/start`);
|
|
25
|
+
return this.wrap(response);
|
|
26
|
+
}
|
|
27
|
+
async stop(id) {
|
|
28
|
+
const response = await this.doJSON("POST", `/v1/sandboxes/${id}/stop`);
|
|
29
|
+
return this.wrap(response);
|
|
30
|
+
}
|
|
31
|
+
async destroy(id) {
|
|
32
|
+
await this.doJSON("DELETE", `/v1/sandboxes/${id}`);
|
|
33
|
+
}
|
|
34
|
+
async resize(id, options) {
|
|
35
|
+
const response = await this.doJSON("POST", `/v1/sandboxes/${id}/resize`, toApiResizeOptions(options));
|
|
36
|
+
return this.wrap(response);
|
|
37
|
+
}
|
|
38
|
+
async updateLifecycle(id, lifecycle) {
|
|
39
|
+
const response = await this.doJSON("PUT", `/v1/sandboxes/${id}/lifecycle`, toApiLifecycle(lifecycle));
|
|
40
|
+
return this.wrap(response);
|
|
41
|
+
}
|
|
42
|
+
async exec(id, request) {
|
|
43
|
+
const response = await this.doJSON("POST", `/v1/sandboxes/${id}/toolbox/process/execute`, toApiExecRequest(request));
|
|
44
|
+
return fromApiExecResult(response);
|
|
45
|
+
}
|
|
46
|
+
execStream(id, options) {
|
|
47
|
+
return openExecStream(this.baseURL, this.patToken, id, options);
|
|
48
|
+
}
|
|
49
|
+
async createSession(id, options) {
|
|
50
|
+
const response = await this.doJSON("POST", `/v1/sandboxes/${id}/sessions`, toApiCreateSessionOptions(options));
|
|
51
|
+
return fromApiSession(response);
|
|
52
|
+
}
|
|
53
|
+
async listSessions(id) {
|
|
54
|
+
const response = await this.doJSON("GET", `/v1/sandboxes/${id}/sessions`);
|
|
55
|
+
return response.sessions.map(fromApiSession);
|
|
56
|
+
}
|
|
57
|
+
async getSession(id, sessionID) {
|
|
58
|
+
const response = await this.doJSON("GET", `/v1/sandboxes/${id}/sessions/${sessionID}`);
|
|
59
|
+
return fromApiSession(response);
|
|
60
|
+
}
|
|
61
|
+
async deleteSession(id, sessionID) {
|
|
62
|
+
await this.doJSON("DELETE", `/v1/sandboxes/${id}/sessions/${sessionID}`);
|
|
63
|
+
}
|
|
64
|
+
async signalSession(id, sessionID, signal) {
|
|
65
|
+
await this.doJSON("POST", `/v1/sandboxes/${id}/sessions/${sessionID}/signal`, { signal });
|
|
66
|
+
}
|
|
67
|
+
async resizeSession(id, sessionID, cols, rows) {
|
|
68
|
+
await this.doJSON("POST", `/v1/sandboxes/${id}/sessions/${sessionID}/resize`, { cols, rows });
|
|
69
|
+
}
|
|
70
|
+
async sessionLog(id, sessionID) {
|
|
71
|
+
return this.doBytes(`/v1/sandboxes/${id}/sessions/${sessionID}/log`);
|
|
72
|
+
}
|
|
73
|
+
async sessionRecording(id, sessionID) {
|
|
74
|
+
return this.doBytes(`/v1/sandboxes/${id}/sessions/${sessionID}/recording`);
|
|
75
|
+
}
|
|
76
|
+
attachSession(id, sessionID, options = {}) {
|
|
77
|
+
return openSessionAttach(this.baseURL, this.patToken, id, sessionID, options);
|
|
78
|
+
}
|
|
79
|
+
async uploadFile(id, targetPath, data) {
|
|
80
|
+
const form = new FormData();
|
|
81
|
+
form.set("path", targetPath);
|
|
82
|
+
form.set("file", toBlob(data), basename(targetPath));
|
|
83
|
+
const response = await this.request("POST", `/v1/sandboxes/${id}/toolbox/files/upload`, { body: form });
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
throw await decodeError(response);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async downloadFile(id, targetPath) {
|
|
89
|
+
const response = await this.request("GET", `/v1/sandboxes/${id}/toolbox/files/download?path=${encodeURIComponent(targetPath)}`);
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw await decodeError(response);
|
|
92
|
+
}
|
|
93
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
94
|
+
}
|
|
95
|
+
async exposePort(id, port) {
|
|
96
|
+
const response = await this.doJSON("POST", `/v1/sandboxes/${id}/ports/${port}`);
|
|
97
|
+
return response.public_url;
|
|
98
|
+
}
|
|
99
|
+
async unexposePort(id, port) {
|
|
100
|
+
await this.doJSON("DELETE", `/v1/sandboxes/${id}/ports/${port}`);
|
|
101
|
+
}
|
|
102
|
+
async health() {
|
|
103
|
+
const response = await this.doJSON("GET", "/health");
|
|
104
|
+
return fromApiHealthStatus(response);
|
|
105
|
+
}
|
|
106
|
+
async mounts(id) {
|
|
107
|
+
const response = await this.doJSON("GET", `/v1/sandboxes/${id}/mounts`);
|
|
108
|
+
return response.mounts.map(fromApiMountSpecRedacted);
|
|
109
|
+
}
|
|
110
|
+
wrap(sandbox) {
|
|
111
|
+
return new SandboxResource(this, fromApiSandbox(sandbox));
|
|
112
|
+
}
|
|
113
|
+
async doJSON(method, path, body) {
|
|
114
|
+
const init = {};
|
|
115
|
+
if (body !== undefined) {
|
|
116
|
+
init.body = JSON.stringify(body);
|
|
117
|
+
init.headers = {
|
|
118
|
+
"Content-Type": "application/json",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const response = await this.request(method, path, init);
|
|
122
|
+
if (!response.ok) {
|
|
123
|
+
throw await decodeError(response);
|
|
124
|
+
}
|
|
125
|
+
if (response.status === 204) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
return (await response.json());
|
|
129
|
+
}
|
|
130
|
+
async doBytes(path) {
|
|
131
|
+
const response = await this.request("GET", path);
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
throw await decodeError(response);
|
|
134
|
+
}
|
|
135
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
136
|
+
}
|
|
137
|
+
request(method, path, init = {}) {
|
|
138
|
+
const headers = new Headers(init.headers);
|
|
139
|
+
if (this.patToken !== "") {
|
|
140
|
+
headers.set("Authorization", `Bearer ${this.patToken}`);
|
|
141
|
+
}
|
|
142
|
+
return this.fetchFn(`${this.baseURL}${path}`, {
|
|
143
|
+
...init,
|
|
144
|
+
method,
|
|
145
|
+
headers,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export class SandboxResource {
|
|
150
|
+
client;
|
|
151
|
+
constructor(client, sandbox) {
|
|
152
|
+
this.client = client;
|
|
153
|
+
this.apply(sandbox);
|
|
154
|
+
}
|
|
155
|
+
async refresh() {
|
|
156
|
+
const updated = await this.client.get(this.id);
|
|
157
|
+
this.apply(updated.toJSON());
|
|
158
|
+
return this;
|
|
159
|
+
}
|
|
160
|
+
async exec(command) {
|
|
161
|
+
return this.client.exec(this.id, typeof command === "string" ? { command } : command);
|
|
162
|
+
}
|
|
163
|
+
execStream(options) {
|
|
164
|
+
return this.client.execStream(this.id, options);
|
|
165
|
+
}
|
|
166
|
+
async createSession(options) {
|
|
167
|
+
return this.client.createSession(this.id, options);
|
|
168
|
+
}
|
|
169
|
+
async listSessions() {
|
|
170
|
+
return this.client.listSessions(this.id);
|
|
171
|
+
}
|
|
172
|
+
async getSession(sessionID) {
|
|
173
|
+
return this.client.getSession(this.id, sessionID);
|
|
174
|
+
}
|
|
175
|
+
async deleteSession(sessionID) {
|
|
176
|
+
await this.client.deleteSession(this.id, sessionID);
|
|
177
|
+
}
|
|
178
|
+
async signalSession(sessionID, signal) {
|
|
179
|
+
await this.client.signalSession(this.id, sessionID, signal);
|
|
180
|
+
}
|
|
181
|
+
async resizeSession(sessionID, cols, rows) {
|
|
182
|
+
await this.client.resizeSession(this.id, sessionID, cols, rows);
|
|
183
|
+
}
|
|
184
|
+
async sessionLog(sessionID) {
|
|
185
|
+
return this.client.sessionLog(this.id, sessionID);
|
|
186
|
+
}
|
|
187
|
+
async sessionRecording(sessionID) {
|
|
188
|
+
return this.client.sessionRecording(this.id, sessionID);
|
|
189
|
+
}
|
|
190
|
+
attachSession(sessionID, options = {}) {
|
|
191
|
+
return this.client.attachSession(this.id, sessionID, options);
|
|
192
|
+
}
|
|
193
|
+
async uploadFile(targetPath, data) {
|
|
194
|
+
await this.client.uploadFile(this.id, targetPath, data);
|
|
195
|
+
}
|
|
196
|
+
async downloadFile(targetPath) {
|
|
197
|
+
return this.client.downloadFile(this.id, targetPath);
|
|
198
|
+
}
|
|
199
|
+
async exposePort(port) {
|
|
200
|
+
return this.client.exposePort(this.id, port);
|
|
201
|
+
}
|
|
202
|
+
async unexposePort(port) {
|
|
203
|
+
await this.client.unexposePort(this.id, port);
|
|
204
|
+
}
|
|
205
|
+
async start() {
|
|
206
|
+
const updated = await this.client.start(this.id);
|
|
207
|
+
this.apply(updated.toJSON());
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
async stop() {
|
|
211
|
+
const updated = await this.client.stop(this.id);
|
|
212
|
+
this.apply(updated.toJSON());
|
|
213
|
+
return this;
|
|
214
|
+
}
|
|
215
|
+
async destroy() {
|
|
216
|
+
await this.client.destroy(this.id);
|
|
217
|
+
}
|
|
218
|
+
async resize(options) {
|
|
219
|
+
const updated = await this.client.resize(this.id, options);
|
|
220
|
+
this.apply(updated.toJSON());
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
async updateLifecycle(lifecycle) {
|
|
224
|
+
const updated = await this.client.updateLifecycle(this.id, lifecycle);
|
|
225
|
+
this.apply(updated.toJSON());
|
|
226
|
+
return this;
|
|
227
|
+
}
|
|
228
|
+
toJSON() {
|
|
229
|
+
return cloneSandbox(this);
|
|
230
|
+
}
|
|
231
|
+
apply(sandbox) {
|
|
232
|
+
Object.assign(this, cloneSandbox(sandbox));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function toApiCreateOptions(options) {
|
|
236
|
+
return {
|
|
237
|
+
image: options.image,
|
|
238
|
+
cpu: options.cpu,
|
|
239
|
+
memory_mb: options.memoryMB,
|
|
240
|
+
disk_gb: options.diskGB,
|
|
241
|
+
env: options.env,
|
|
242
|
+
os_user: options.osUser,
|
|
243
|
+
network_block_all: options.networkBlockAll,
|
|
244
|
+
registry: options.registry,
|
|
245
|
+
container_command: options.containerCommand,
|
|
246
|
+
mounts: options.mounts?.map(toApiMountSpec),
|
|
247
|
+
lifecycle: options.lifecycle ? toApiLifecycle(options.lifecycle) : undefined,
|
|
248
|
+
runtime: options.runtime,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function toApiResizeOptions(options) {
|
|
252
|
+
return {
|
|
253
|
+
cpu: options.cpu,
|
|
254
|
+
memory_mb: options.memoryMB,
|
|
255
|
+
disk_gb: options.diskGB,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function toApiExecRequest(request) {
|
|
259
|
+
return {
|
|
260
|
+
command: request.command,
|
|
261
|
+
workdir: request.workDir,
|
|
262
|
+
env: request.env,
|
|
263
|
+
timeout_seconds: request.timeoutSeconds,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function toApiCreateSessionOptions(options) {
|
|
267
|
+
return {
|
|
268
|
+
name: options.name,
|
|
269
|
+
argv: options.argv,
|
|
270
|
+
command: options.command,
|
|
271
|
+
workdir: options.workDir,
|
|
272
|
+
env: options.env,
|
|
273
|
+
pty: options.pty,
|
|
274
|
+
cols: options.cols,
|
|
275
|
+
rows: options.rows,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function toApiLifecycle(lifecycle) {
|
|
279
|
+
return {
|
|
280
|
+
stop_if_idle_for: lifecycle.stopIfIdleFor,
|
|
281
|
+
destroy_if_idle_for: lifecycle.destroyIfIdleFor,
|
|
282
|
+
stop_at_age: lifecycle.stopAtAge,
|
|
283
|
+
destroy_at_age: lifecycle.destroyAtAge,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function fromApiSandbox(sandbox) {
|
|
287
|
+
return {
|
|
288
|
+
id: sandbox.id,
|
|
289
|
+
image: sandbox.image,
|
|
290
|
+
status: sandbox.status,
|
|
291
|
+
publicURL: sandbox.public_url,
|
|
292
|
+
containerID: sandbox.container_id,
|
|
293
|
+
containerIP: sandbox.container_ip,
|
|
294
|
+
cpu: sandbox.cpu,
|
|
295
|
+
memoryMB: sandbox.memory_mb,
|
|
296
|
+
diskGB: sandbox.disk_gb,
|
|
297
|
+
osUser: sandbox.os_user,
|
|
298
|
+
env: sandbox.env,
|
|
299
|
+
networkBlockAll: sandbox.network_block_all,
|
|
300
|
+
toolboxEnabled: sandbox.toolbox_enabled,
|
|
301
|
+
sshPublicKey: sandbox.ssh_public_key,
|
|
302
|
+
exposedPorts: sandbox.exposed_ports?.map(fromApiExposedPort),
|
|
303
|
+
createdAt: sandbox.created_at,
|
|
304
|
+
updatedAt: sandbox.updated_at,
|
|
305
|
+
lastActiveAt: sandbox.last_active_at,
|
|
306
|
+
lastError: sandbox.last_error,
|
|
307
|
+
containerCommand: sandbox.container_command,
|
|
308
|
+
lifecycle: fromApiLifecycle(sandbox.lifecycle),
|
|
309
|
+
runtime: normalizeRuntime(sandbox.runtime),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
// normalizeRuntime narrows the wire-level string to the union we expose, while
|
|
313
|
+
// tolerating older sandboxd versions that don't send the field at all (treat
|
|
314
|
+
// as "" — i.e. host default at start time).
|
|
315
|
+
function normalizeRuntime(value) {
|
|
316
|
+
if (value === "docker" || value === "gvisor" || value === "kata") {
|
|
317
|
+
return value;
|
|
318
|
+
}
|
|
319
|
+
return "";
|
|
320
|
+
}
|
|
321
|
+
function fromApiCreateSandboxResponse(response) {
|
|
322
|
+
return {
|
|
323
|
+
...fromApiSandbox(response),
|
|
324
|
+
sshPrivateKey: response.ssh_private_key,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function fromApiSession(session) {
|
|
328
|
+
return {
|
|
329
|
+
id: session.id,
|
|
330
|
+
name: session.name,
|
|
331
|
+
argv: session.argv,
|
|
332
|
+
workDir: session.workdir,
|
|
333
|
+
pty: session.pty,
|
|
334
|
+
status: session.status,
|
|
335
|
+
exitCode: session.exit_code,
|
|
336
|
+
exitSignal: session.exit_signal,
|
|
337
|
+
createdAt: session.created_at,
|
|
338
|
+
startedAt: session.started_at,
|
|
339
|
+
exitedAt: session.exited_at,
|
|
340
|
+
recording: session.recording,
|
|
341
|
+
bytes: session.bytes,
|
|
342
|
+
attached: session.attached,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function fromApiExposedPort(port) {
|
|
346
|
+
return {
|
|
347
|
+
sandboxID: port.sandbox_id,
|
|
348
|
+
port: port.port,
|
|
349
|
+
publicURL: port.public_url,
|
|
350
|
+
createdAt: port.created_at,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function fromApiExecResult(result) {
|
|
354
|
+
return {
|
|
355
|
+
stdout: result.stdout,
|
|
356
|
+
stderr: result.stderr,
|
|
357
|
+
exitCode: result.exit_code,
|
|
358
|
+
durationMS: result.duration_ms,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function fromApiHealthStatus(status) {
|
|
362
|
+
return {
|
|
363
|
+
status: status.status,
|
|
364
|
+
sandboxes: status.sandboxes,
|
|
365
|
+
docker: status.docker,
|
|
366
|
+
caddy: status.caddy,
|
|
367
|
+
sshGateway: status.ssh_gateway ?? "",
|
|
368
|
+
version: status.version,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function toApiMountSpec(mount) {
|
|
372
|
+
return {
|
|
373
|
+
type: mount.type,
|
|
374
|
+
target: mount.target,
|
|
375
|
+
source: mount.source,
|
|
376
|
+
options: mount.options,
|
|
377
|
+
credentials: mount.credentials,
|
|
378
|
+
read_only: mount.readOnly,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function fromApiMountSpecRedacted(mount) {
|
|
382
|
+
return {
|
|
383
|
+
type: mount.type,
|
|
384
|
+
target: mount.target,
|
|
385
|
+
source: mount.source,
|
|
386
|
+
options: mount.options,
|
|
387
|
+
readOnly: mount.read_only ?? false,
|
|
388
|
+
hasCredentials: mount.has_credentials,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function fromApiLifecycle(lifecycle) {
|
|
392
|
+
const result = {};
|
|
393
|
+
if (lifecycle?.stop_if_idle_for !== undefined) {
|
|
394
|
+
result.stopIfIdleFor = lifecycle.stop_if_idle_for;
|
|
395
|
+
}
|
|
396
|
+
if (lifecycle?.destroy_if_idle_for !== undefined) {
|
|
397
|
+
result.destroyIfIdleFor = lifecycle.destroy_if_idle_for;
|
|
398
|
+
}
|
|
399
|
+
if (lifecycle?.stop_at_age !== undefined) {
|
|
400
|
+
result.stopAtAge = lifecycle.stop_at_age;
|
|
401
|
+
}
|
|
402
|
+
if (lifecycle?.destroy_at_age !== undefined) {
|
|
403
|
+
result.destroyAtAge = lifecycle.destroy_at_age;
|
|
404
|
+
}
|
|
405
|
+
return result;
|
|
406
|
+
}
|
|
407
|
+
function cloneSandbox(sandbox) {
|
|
408
|
+
return {
|
|
409
|
+
...sandbox,
|
|
410
|
+
env: sandbox.env ? { ...sandbox.env } : undefined,
|
|
411
|
+
exposedPorts: sandbox.exposedPorts?.map((port) => ({ ...port })),
|
|
412
|
+
containerCommand: sandbox.containerCommand ? [...sandbox.containerCommand] : undefined,
|
|
413
|
+
lifecycle: { ...sandbox.lifecycle },
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function toBlob(data) {
|
|
417
|
+
if (data instanceof Blob) {
|
|
418
|
+
return data;
|
|
419
|
+
}
|
|
420
|
+
if (typeof data === "string") {
|
|
421
|
+
return new Blob([data]);
|
|
422
|
+
}
|
|
423
|
+
if (data instanceof ArrayBuffer) {
|
|
424
|
+
return new Blob([new Uint8Array(data)]);
|
|
425
|
+
}
|
|
426
|
+
return new Blob([Uint8Array.from(data)]);
|
|
427
|
+
}
|
|
428
|
+
async function decodeError(response) {
|
|
429
|
+
try {
|
|
430
|
+
const payload = (await response.json());
|
|
431
|
+
if (typeof payload.error === "string" && payload.error !== "") {
|
|
432
|
+
return new Error(payload.error);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
// Fall through to status-based error.
|
|
437
|
+
}
|
|
438
|
+
return new Error(`request failed with status ${response.status}`);
|
|
439
|
+
}
|
|
440
|
+
const STREAM_PREFIX_STDOUT = 0x01;
|
|
441
|
+
const STREAM_PREFIX_STDERR = 0x02;
|
|
442
|
+
function openExecStream(baseURL, patToken, sandboxID, options) {
|
|
443
|
+
const wsURL = baseURL.replace(/^http/, "ws") + `/v1/sandboxes/${encodeURIComponent(sandboxID)}/toolbox/process/exec/stream`;
|
|
444
|
+
const WS = globalThis.WebSocket;
|
|
445
|
+
if (!WS) {
|
|
446
|
+
throw new Error("WebSocket is not available in this runtime — Node 22+ or a browser is required");
|
|
447
|
+
}
|
|
448
|
+
// Browsers cannot attach Authorization headers to WebSocket handshakes,
|
|
449
|
+
// so sandboxd accepts the PAT via `Sec-WebSocket-Protocol` as
|
|
450
|
+
// `sandbox.bearer, <token>`.
|
|
451
|
+
const ws = new WS(wsURL, ["sandbox.bearer", patToken]);
|
|
452
|
+
ws.binaryType = "arraybuffer";
|
|
453
|
+
let exitResolve;
|
|
454
|
+
let exitReject;
|
|
455
|
+
const done = new Promise((resolve, reject) => {
|
|
456
|
+
exitResolve = resolve;
|
|
457
|
+
exitReject = reject;
|
|
458
|
+
});
|
|
459
|
+
let resolved = false;
|
|
460
|
+
const finishWith = (info) => {
|
|
461
|
+
if (resolved)
|
|
462
|
+
return;
|
|
463
|
+
resolved = true;
|
|
464
|
+
exitResolve?.(info);
|
|
465
|
+
};
|
|
466
|
+
const failWith = (msg) => {
|
|
467
|
+
if (resolved)
|
|
468
|
+
return;
|
|
469
|
+
resolved = true;
|
|
470
|
+
exitReject?.(new Error(msg));
|
|
471
|
+
};
|
|
472
|
+
ws.addEventListener("open", () => {
|
|
473
|
+
ws.send(JSON.stringify({
|
|
474
|
+
command: options.command,
|
|
475
|
+
workdir: options.workdir,
|
|
476
|
+
env: options.env,
|
|
477
|
+
tty: options.tty ?? false,
|
|
478
|
+
cols: options.cols ?? 0,
|
|
479
|
+
rows: options.rows ?? 0,
|
|
480
|
+
}));
|
|
481
|
+
});
|
|
482
|
+
ws.addEventListener("message", (event) => {
|
|
483
|
+
if (typeof event.data === "string") {
|
|
484
|
+
try {
|
|
485
|
+
const msg = JSON.parse(event.data);
|
|
486
|
+
if (msg.type === "exit") {
|
|
487
|
+
finishWith({ code: msg.code ?? 0, signal: msg.signal });
|
|
488
|
+
ws.close();
|
|
489
|
+
}
|
|
490
|
+
else if (msg.type === "error") {
|
|
491
|
+
options.onError?.(msg.message ?? "stream error");
|
|
492
|
+
failWith(msg.message ?? "stream error");
|
|
493
|
+
ws.close();
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
// ignore
|
|
498
|
+
}
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const buf = event.data instanceof ArrayBuffer ? new Uint8Array(event.data) : new Uint8Array(event.data.buffer);
|
|
502
|
+
if (buf.length === 0)
|
|
503
|
+
return;
|
|
504
|
+
const stream = buf[0];
|
|
505
|
+
const payload = buf.subarray(1);
|
|
506
|
+
if (stream === STREAM_PREFIX_STDOUT) {
|
|
507
|
+
options.onStdout?.(payload);
|
|
508
|
+
}
|
|
509
|
+
else if (stream === STREAM_PREFIX_STDERR) {
|
|
510
|
+
options.onStderr?.(payload);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
ws.addEventListener("close", () => {
|
|
514
|
+
finishWith({ code: 0 });
|
|
515
|
+
});
|
|
516
|
+
ws.addEventListener("error", () => {
|
|
517
|
+
failWith("websocket error");
|
|
518
|
+
});
|
|
519
|
+
const sendBinary = (data) => {
|
|
520
|
+
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
521
|
+
ws.send(bytes);
|
|
522
|
+
};
|
|
523
|
+
return {
|
|
524
|
+
write: sendBinary,
|
|
525
|
+
resize(cols, rows) {
|
|
526
|
+
ws.send(JSON.stringify({ type: "resize", cols, rows }));
|
|
527
|
+
},
|
|
528
|
+
signal(name) {
|
|
529
|
+
ws.send(JSON.stringify({ type: "signal", signal: name }));
|
|
530
|
+
},
|
|
531
|
+
close() {
|
|
532
|
+
ws.send(JSON.stringify({ type: "close" }));
|
|
533
|
+
ws.close();
|
|
534
|
+
},
|
|
535
|
+
done,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function openSessionAttach(baseURL, patToken, sandboxID, sessionID, options) {
|
|
539
|
+
const wsURL = baseURL.replace(/^http/, "ws") + `/v1/sandboxes/${encodeURIComponent(sandboxID)}/sessions/${encodeURIComponent(sessionID)}/attach`;
|
|
540
|
+
const WS = globalThis.WebSocket;
|
|
541
|
+
if (!WS) {
|
|
542
|
+
throw new Error("WebSocket is not available in this runtime — Node 22+ or a browser is required");
|
|
543
|
+
}
|
|
544
|
+
const ws = new WS(wsURL, ["sandbox.bearer", patToken]);
|
|
545
|
+
ws.binaryType = "arraybuffer";
|
|
546
|
+
let exitResolve;
|
|
547
|
+
let exitReject;
|
|
548
|
+
const done = new Promise((resolve, reject) => {
|
|
549
|
+
exitResolve = resolve;
|
|
550
|
+
exitReject = reject;
|
|
551
|
+
});
|
|
552
|
+
let settled = false;
|
|
553
|
+
const finishWith = (info) => {
|
|
554
|
+
if (settled)
|
|
555
|
+
return;
|
|
556
|
+
settled = true;
|
|
557
|
+
options.onExit?.(info);
|
|
558
|
+
exitResolve?.(info);
|
|
559
|
+
};
|
|
560
|
+
const failWith = (msg) => {
|
|
561
|
+
if (settled)
|
|
562
|
+
return;
|
|
563
|
+
settled = true;
|
|
564
|
+
exitReject?.(new Error(msg));
|
|
565
|
+
};
|
|
566
|
+
ws.addEventListener("open", () => {
|
|
567
|
+
if ((options.cols ?? 0) > 0 && (options.rows ?? 0) > 0) {
|
|
568
|
+
ws.send(JSON.stringify({ type: "resize", cols: options.cols, rows: options.rows }));
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
ws.addEventListener("message", (event) => {
|
|
572
|
+
if (typeof event.data === "string") {
|
|
573
|
+
try {
|
|
574
|
+
const msg = JSON.parse(event.data);
|
|
575
|
+
if (msg.type === "exit") {
|
|
576
|
+
finishWith({ code: msg.code ?? 0, signal: msg.signal });
|
|
577
|
+
ws.close();
|
|
578
|
+
}
|
|
579
|
+
else if (msg.type === "error") {
|
|
580
|
+
options.onError?.(msg.message ?? "session error");
|
|
581
|
+
failWith(msg.message ?? "session error");
|
|
582
|
+
ws.close();
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
catch {
|
|
586
|
+
// ignore malformed control frames
|
|
587
|
+
}
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const buf = event.data instanceof ArrayBuffer ? new Uint8Array(event.data) : new Uint8Array(event.data.buffer);
|
|
591
|
+
if (buf.length === 0) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const stream = buf[0];
|
|
595
|
+
const payload = buf.subarray(1);
|
|
596
|
+
if (stream === STREAM_PREFIX_STDOUT) {
|
|
597
|
+
options.onStdout?.(payload);
|
|
598
|
+
}
|
|
599
|
+
else if (stream === STREAM_PREFIX_STDERR) {
|
|
600
|
+
options.onStderr?.(payload);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
ws.addEventListener("close", () => {
|
|
604
|
+
if (!settled) {
|
|
605
|
+
failWith("session stream closed before exit");
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
ws.addEventListener("error", () => {
|
|
609
|
+
failWith("websocket error");
|
|
610
|
+
});
|
|
611
|
+
const sendBinary = (data) => {
|
|
612
|
+
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
613
|
+
ws.send(bytes);
|
|
614
|
+
};
|
|
615
|
+
return {
|
|
616
|
+
write: sendBinary,
|
|
617
|
+
resize(cols, rows) {
|
|
618
|
+
ws.send(JSON.stringify({ type: "resize", cols, rows }));
|
|
619
|
+
},
|
|
620
|
+
signal(name) {
|
|
621
|
+
ws.send(JSON.stringify({ type: "signal", signal: name }));
|
|
622
|
+
},
|
|
623
|
+
close() {
|
|
624
|
+
ws.send(JSON.stringify({ type: "close" }));
|
|
625
|
+
ws.close();
|
|
626
|
+
},
|
|
627
|
+
done,
|
|
628
|
+
};
|
|
629
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
export type SandboxStatus = "creating" | "started" | "stopped" | "destroyed" | "error";
|
|
2
|
+
export interface RegistryAuth {
|
|
3
|
+
server: string;
|
|
4
|
+
username: string;
|
|
5
|
+
password: string;
|
|
6
|
+
}
|
|
7
|
+
export type MountType = "s3" | "nfs" | "sshfs" | "rclone";
|
|
8
|
+
export interface MountSpec {
|
|
9
|
+
type: MountType;
|
|
10
|
+
target: string;
|
|
11
|
+
source: string;
|
|
12
|
+
options?: Record<string, string>;
|
|
13
|
+
credentials?: Record<string, string>;
|
|
14
|
+
readOnly?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface MountSpecRedacted {
|
|
17
|
+
type: MountType;
|
|
18
|
+
target: string;
|
|
19
|
+
source: string;
|
|
20
|
+
options?: Record<string, string>;
|
|
21
|
+
readOnly: boolean;
|
|
22
|
+
hasCredentials: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface Lifecycle {
|
|
25
|
+
/** Duration in integer nanoseconds. */
|
|
26
|
+
stopIfIdleFor?: number;
|
|
27
|
+
/** Duration in integer nanoseconds. */
|
|
28
|
+
destroyIfIdleFor?: number;
|
|
29
|
+
/** Duration in integer nanoseconds. */
|
|
30
|
+
stopAtAge?: number;
|
|
31
|
+
/** Duration in integer nanoseconds. */
|
|
32
|
+
destroyAtAge?: number;
|
|
33
|
+
}
|
|
34
|
+
export type UpdateLifecycleOptions = Lifecycle;
|
|
35
|
+
export interface CreateOptions {
|
|
36
|
+
image: string;
|
|
37
|
+
/** Number of CPU cores to allocate. Fractional values are supported (e.g. 0.5 = half a core). */
|
|
38
|
+
cpu?: number;
|
|
39
|
+
memoryMB?: number;
|
|
40
|
+
diskGB?: number;
|
|
41
|
+
env?: Record<string, string>;
|
|
42
|
+
osUser?: string;
|
|
43
|
+
networkBlockAll?: boolean;
|
|
44
|
+
registry?: RegistryAuth;
|
|
45
|
+
containerCommand?: string[];
|
|
46
|
+
mounts?: MountSpec[];
|
|
47
|
+
lifecycle?: Lifecycle;
|
|
48
|
+
/**
|
|
49
|
+
* Container runtime to use for this sandbox. Omit to inherit the host
|
|
50
|
+
* default (`SB_CONTAINER_RUNTIME`). Use `"gvisor"` for runsc-backed
|
|
51
|
+
* isolation when running untrusted workloads — note that gVisor rejects
|
|
52
|
+
* privileged containers and ignores per-sandbox disk quotas. `"kata"` is
|
|
53
|
+
* reserved for future Kata Containers support and is rejected by the API
|
|
54
|
+
* today.
|
|
55
|
+
*/
|
|
56
|
+
runtime?: "docker" | "gvisor" | "kata";
|
|
57
|
+
}
|
|
58
|
+
export interface ResizeOptions {
|
|
59
|
+
/** Number of CPU cores. Fractional values supported (e.g. 0.5). */
|
|
60
|
+
cpu?: number;
|
|
61
|
+
memoryMB?: number;
|
|
62
|
+
diskGB?: number;
|
|
63
|
+
}
|
|
64
|
+
export interface CreateSessionOptions {
|
|
65
|
+
name?: string;
|
|
66
|
+
argv?: string[];
|
|
67
|
+
command?: string;
|
|
68
|
+
workDir?: string;
|
|
69
|
+
env?: Record<string, string>;
|
|
70
|
+
pty?: boolean;
|
|
71
|
+
cols?: number;
|
|
72
|
+
rows?: number;
|
|
73
|
+
}
|
|
74
|
+
export type SessionStatus = "running" | "exited" | "killed" | "failed";
|
|
75
|
+
export interface Session {
|
|
76
|
+
id: string;
|
|
77
|
+
name: string;
|
|
78
|
+
argv: string[];
|
|
79
|
+
workDir?: string;
|
|
80
|
+
pty: boolean;
|
|
81
|
+
status: SessionStatus;
|
|
82
|
+
exitCode: number;
|
|
83
|
+
exitSignal?: string;
|
|
84
|
+
createdAt: string;
|
|
85
|
+
startedAt: string;
|
|
86
|
+
exitedAt?: string;
|
|
87
|
+
recording: boolean;
|
|
88
|
+
bytes: number;
|
|
89
|
+
attached: number;
|
|
90
|
+
}
|
|
91
|
+
export interface ExposedPort {
|
|
92
|
+
sandboxID: string;
|
|
93
|
+
port: number;
|
|
94
|
+
publicURL: string;
|
|
95
|
+
createdAt: string;
|
|
96
|
+
}
|
|
97
|
+
export interface Sandbox {
|
|
98
|
+
id: string;
|
|
99
|
+
image: string;
|
|
100
|
+
status: SandboxStatus;
|
|
101
|
+
publicURL: string;
|
|
102
|
+
containerID?: string;
|
|
103
|
+
containerIP?: string;
|
|
104
|
+
cpu: number;
|
|
105
|
+
memoryMB: number;
|
|
106
|
+
diskGB: number;
|
|
107
|
+
osUser: string;
|
|
108
|
+
env?: Record<string, string>;
|
|
109
|
+
networkBlockAll: boolean;
|
|
110
|
+
toolboxEnabled: boolean;
|
|
111
|
+
sshPublicKey?: string;
|
|
112
|
+
sshPrivateKey?: string;
|
|
113
|
+
exposedPorts?: ExposedPort[];
|
|
114
|
+
createdAt: string;
|
|
115
|
+
updatedAt: string;
|
|
116
|
+
lastActiveAt: string;
|
|
117
|
+
lastError?: string;
|
|
118
|
+
containerCommand?: string[];
|
|
119
|
+
lifecycle: Lifecycle;
|
|
120
|
+
/**
|
|
121
|
+
* Container runtime this sandbox is running under. Empty string indicates
|
|
122
|
+
* a pre-migration row that resolves to the host default at start time.
|
|
123
|
+
*/
|
|
124
|
+
runtime: "" | "docker" | "gvisor" | "kata";
|
|
125
|
+
}
|
|
126
|
+
export interface ExecRequest {
|
|
127
|
+
command: string;
|
|
128
|
+
workDir?: string;
|
|
129
|
+
env?: Record<string, string>;
|
|
130
|
+
timeoutSeconds?: number;
|
|
131
|
+
}
|
|
132
|
+
export interface ExecResult {
|
|
133
|
+
stdout: string;
|
|
134
|
+
stderr: string;
|
|
135
|
+
exitCode: number;
|
|
136
|
+
durationMS: number;
|
|
137
|
+
}
|
|
138
|
+
export interface ExecStreamOptions {
|
|
139
|
+
command: string;
|
|
140
|
+
workdir?: string;
|
|
141
|
+
env?: Record<string, string>;
|
|
142
|
+
tty?: boolean;
|
|
143
|
+
cols?: number;
|
|
144
|
+
rows?: number;
|
|
145
|
+
onStdout?: (chunk: Uint8Array) => void;
|
|
146
|
+
onStderr?: (chunk: Uint8Array) => void;
|
|
147
|
+
onError?: (message: string) => void;
|
|
148
|
+
}
|
|
149
|
+
export interface ExecExitInfo {
|
|
150
|
+
code: number;
|
|
151
|
+
signal?: string;
|
|
152
|
+
}
|
|
153
|
+
export interface ExecStreamHandle {
|
|
154
|
+
/** Send raw stdin bytes (or a UTF-8 string) to the process. */
|
|
155
|
+
write(data: Uint8Array | string): void;
|
|
156
|
+
/** PTY mode only — tell the process its terminal size has changed. */
|
|
157
|
+
resize(cols: number, rows: number): void;
|
|
158
|
+
/** Send a signal (e.g. "INT", "TERM") to the process group. */
|
|
159
|
+
signal(name: string): void;
|
|
160
|
+
/** Close stdin and gracefully end the stream. The process keeps running until it exits on its own. */
|
|
161
|
+
close(): void;
|
|
162
|
+
/** Resolves when the process exits, with its final exit code/signal. */
|
|
163
|
+
done: Promise<ExecExitInfo>;
|
|
164
|
+
}
|
|
165
|
+
export interface SessionAttachOptions {
|
|
166
|
+
onStdout?: (chunk: Uint8Array) => void;
|
|
167
|
+
onStderr?: (chunk: Uint8Array) => void;
|
|
168
|
+
onExit?: (info: ExecExitInfo) => void;
|
|
169
|
+
onError?: (message: string) => void;
|
|
170
|
+
cols?: number;
|
|
171
|
+
rows?: number;
|
|
172
|
+
}
|
|
173
|
+
export interface SessionAttachHandle {
|
|
174
|
+
/** Send raw stdin bytes (or a UTF-8 string) to the attached session. */
|
|
175
|
+
write(data: Uint8Array | string): void;
|
|
176
|
+
/** PTY sessions only — tell the session its terminal size has changed. */
|
|
177
|
+
resize(cols: number, rows: number): void;
|
|
178
|
+
/** Send a signal (e.g. "INT", "TERM", "KILL") to the session process group. */
|
|
179
|
+
signal(name: string): void;
|
|
180
|
+
/** Detach from the live stream without killing the session. */
|
|
181
|
+
close(): void;
|
|
182
|
+
/** Resolves when the session exits, or rejects if the attach stream drops first. */
|
|
183
|
+
done: Promise<ExecExitInfo>;
|
|
184
|
+
}
|
|
185
|
+
export interface HealthStatus {
|
|
186
|
+
status: string;
|
|
187
|
+
sandboxes: number;
|
|
188
|
+
docker: string;
|
|
189
|
+
caddy: string;
|
|
190
|
+
sshGateway: string;
|
|
191
|
+
version: string;
|
|
192
|
+
}
|
|
193
|
+
export type BinaryLike = Uint8Array | ArrayBuffer | Blob | string;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aerol-ai/aerolvm-sdk",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc -p tsconfig.json",
|
|
16
|
+
"example:create": "npm run build && node dist/examples/createSandbox.js",
|
|
17
|
+
"example:create:src": "npx tsx src/examples/createSandbox.ts",
|
|
18
|
+
"test": "tsc -p tsconfig.test.json --outDir dist-test && node --test dist-test/*.test.js"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^22.15.17",
|
|
22
|
+
"typescript": "^5.8.3",
|
|
23
|
+
"tsx": "^4.21.0"
|
|
24
|
+
}
|
|
25
|
+
}
|