-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsandbox.ts
More file actions
345 lines (319 loc) · 10.6 KB
/
Copy pathsandbox.ts
File metadata and controls
345 lines (319 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
import type {
CreateSnapshotParams,
ObjectStorageMount,
ObjectStorageMountSummary,
ObjectStorageMountUpdate,
PresignedUrl,
SandboxData,
SandboxState,
} from "@/models/index.js";
import { sandboxStateSchema } from "@/models/sandbox.js";
import {
CodeInterpreterClient,
DesktopClient,
FilesystemClient,
GitClient,
LspClient,
ProcessClient,
PtyClient,
SshClient,
} from "@/services/index.js";
import type { Leap0Client } from "@/client/index.js";
/** @internal Symbol used to access service clients on Leap0Client. */
export const SERVICES = Symbol.for("leap0.services");
function isSandboxData(value: unknown): value is SandboxData {
if (!value || typeof value !== "object") {
return false;
}
const record = value as Record<string, unknown>;
return (
typeof record.id === "string" &&
typeof record.templateId === "string" &&
sandboxStateSchema.safeParse(record.state).success &&
typeof record.vcpu === "number" &&
typeof record.memory === "number" &&
typeof record.disk === "number" &&
typeof record.createdAt === "string"
);
}
function unwrapSandboxData(value: unknown): SandboxData {
if (value instanceof Sandbox || isSandboxData(value)) {
return value;
}
if (value && typeof value === "object") {
const record = value as { data?: unknown; toJSON?: () => unknown };
if (isSandboxData(record.data)) {
return record.data;
}
if (typeof record.toJSON === "function") {
const json = record.toJSON();
if (isSandboxData(json)) {
return json;
}
}
}
throw new TypeError("Expected this.client.sandboxes.get() to return SandboxData or Sandbox");
}
type BoundSandboxMethod<Method> = Method extends (
sandbox: infer _Sandbox,
...args: infer Args
) => infer Result
? (...args: Args) => Result
: Method;
type BoundSandboxService<Service extends object> = {
[Key in keyof Service]: BoundSandboxMethod<Service[Key]>;
};
class SandboxServiceProxy<Service extends object> {
constructor(
private readonly service: Service,
private readonly sandbox: Sandbox,
) {}
get proxy(): BoundSandboxService<Service> {
return new Proxy(this.service, {
get: (target, prop, receiver) => {
const value = Reflect.get(target, prop, receiver);
if (typeof value !== "function") {
return value;
}
return (...args: unknown[]) => Reflect.apply(value, target, [this.sandbox, ...args]);
},
}) as BoundSandboxService<Service>;
}
}
/**
* Bound sandbox handle with convenience methods and resource-scoped helpers.
*
* @throws {Leap0Error} If API requests or response validation fail.
*/
export class Sandbox implements SandboxData {
/** Unique sandbox ID. */
id!: string;
/** Template ID the sandbox was created from. */
templateId!: string;
/** Template name, when available from the API response. */
templateName?: string;
/** Current sandbox lifecycle state. */
state!: SandboxState;
/** Number of virtual CPUs assigned to the sandbox. */
vcpu!: number;
/** Allocated memory in MiB. */
memory!: number;
/** Allocated disk in MiB. */
disk!: number;
/** Auto-shutdown timeout in minutes, when configured. */
timeout?: number;
/** Whether the sandbox automatically pauses when idle. */
autoPause?: boolean;
/** Environment variables configured for the sandbox. */
envVars?: Record<string, string>;
/** Network policy attached to the sandbox. */
networkPolicy?: SandboxData["networkPolicy"];
/** Object storage mounts attached to the sandbox. */
mounts?: SandboxData["mounts"];
/** Creation timestamp in ISO 8601 format. */
createdAt!: string;
/** Last update timestamp in ISO 8601 format, when available. */
updatedAt?: string;
[key: string]: unknown;
/** Filesystem operations scoped to this sandbox. */
readonly filesystem: BoundSandboxService<FilesystemClient>;
/** Git operations scoped to this sandbox. */
readonly git: BoundSandboxService<GitClient>;
/** One-shot process execution scoped to this sandbox. */
readonly process: BoundSandboxService<ProcessClient>;
/** PTY session management scoped to this sandbox. */
readonly pty: BoundSandboxService<PtyClient>;
/** Language-server operations scoped to this sandbox. */
readonly lsp: BoundSandboxService<LspClient>;
/** SSH access management scoped to this sandbox. */
readonly ssh: BoundSandboxService<SshClient>;
/** Code interpreter operations scoped to this sandbox. */
readonly codeInterpreter: BoundSandboxService<CodeInterpreterClient>;
/** Desktop automation operations scoped to this sandbox. */
readonly desktop: BoundSandboxService<DesktopClient>;
/** Creates a sandbox handle bound to a parent client. */
constructor(
private readonly client: Leap0Client,
data: SandboxData,
) {
this.update(data);
const svc = client[SERVICES];
this.filesystem = new SandboxServiceProxy(svc.filesystem, this).proxy;
this.git = new SandboxServiceProxy(svc.git, this).proxy;
this.process = new SandboxServiceProxy(svc.process, this).proxy;
this.pty = new SandboxServiceProxy(svc.pty, this).proxy;
this.lsp = new SandboxServiceProxy(svc.lsp, this).proxy;
this.ssh = new SandboxServiceProxy(svc.ssh, this).proxy;
this.codeInterpreter = new SandboxServiceProxy(svc.codeInterpreter, this).proxy;
this.desktop = new SandboxServiceProxy(svc.desktop, this).proxy;
}
/**
* Applies fresh sandbox data onto the current handle.
*
* @param data Latest sandbox payload.
* @returns The updated sandbox handle.
*/
private update(data: SandboxData): this {
this.id = data.id;
this.templateId = data.templateId;
this.templateName = data.templateName;
this.state = data.state;
this.vcpu = data.vcpu;
this.memory = data.memory;
this.disk = data.disk;
this.timeout = data.timeout;
this.autoPause = data.autoPause;
this.envVars = data.envVars;
this.networkPolicy = data.networkPolicy;
this.mounts = data.mounts;
this.createdAt = data.createdAt;
this.updatedAt = data.updatedAt;
return this;
}
/**
* Fetches the latest sandbox state from the API.
*
* @returns The refreshed sandbox handle.
*
* @throws {Leap0Error} If fetching the sandbox fails.
*/
async refresh(): Promise<this> {
const latest = await this.client.sandboxes.get(this.id);
this.update(unwrapSandboxData(latest));
return this;
}
/**
* Pauses the sandbox and updates local state.
*
* @param options Optional request settings.
* @returns The paused sandbox handle.
*
* @throws {Leap0Error} If pausing the sandbox fails.
*/
async pause(options?: { timeout?: number }): Promise<this> {
this.update((await this.client.sandboxes.pause(this.id, options)) as SandboxData);
return this;
}
/**
* Creates a snapshot from this sandbox.
*
* @param params Optional snapshot naming and lifecycle parameters.
* @param options Optional request settings.
* @returns The created snapshot resource.
*
* @throws {Leap0Error} If creating the snapshot fails.
*/
async createSnapshot(
params: CreateSnapshotParams = {},
options?: { timeout?: number },
) {
return this.client.sandboxes.createSnapshot(this.id, params, options);
}
/**
* Deletes the sandbox.
*
* @param options Optional request settings.
*
* @throws {Leap0Error} If deleting the sandbox fails.
*/
async delete(options?: { timeout?: number }): Promise<void> {
await this.client.sandboxes.delete(this.id, options);
}
/**
* Adds an object storage mount to the sandbox.
*
* @param mount Object storage mount configuration.
* @param options Optional request settings.
* @returns The created mount summary.
*/
async addMount(
mount: ObjectStorageMount,
options?: { timeout?: number },
): Promise<ObjectStorageMountSummary> {
return this.client.sandboxes.addMount(this.id, mount, options);
}
/**
* Updates an existing object storage mount on the sandbox.
*
* @param mountID Mount identifier.
* @param mount Partial mount update payload.
* @param options Optional request settings.
* @returns The updated mount summary.
*/
async updateMount(
mountID: string,
mount: ObjectStorageMountUpdate,
options?: { timeout?: number },
): Promise<ObjectStorageMountSummary> {
return this.client.sandboxes.updateMount(this.id, mountID, mount, options);
}
/**
* Deletes an object storage mount from the sandbox.
*
* @param mountID Mount identifier.
* @param options Optional request settings.
*/
async deleteMount(mountID: string, options?: { timeout?: number }): Promise<void> {
await this.client.sandboxes.deleteMount(this.id, mountID, options);
}
/**
* Creates a temporary public URL for a specific sandbox port.
*
* @param params Presigned URL creation parameters.
* @param options Optional request settings.
* @returns The created presigned URL.
*/
async createPresignedUrl(
params: { port: number; expiresIn?: number },
options?: { timeout?: number },
): Promise<PresignedUrl> {
return this.client.sandboxes.createPresignedUrl(this.id, params, options);
}
/**
* Deletes a previously issued presigned URL.
*
* @param id Presigned URL identifier.
* @param options Optional request settings.
*/
async deletePresignedUrl(id: string, options?: { timeout?: number }): Promise<void> {
await this.client.sandboxes.deletePresignedUrl(this.id, id, options);
}
/**
* Returns the public invoke URL for the sandbox.
*
* @param path Route path to append.
* @param port Optional forwarded port.
* @returns The public HTTPS URL.
*/
invokeUrl(path = "/", port?: number): string {
return this.client.sandboxes.invokeUrl(this.id, path, port);
}
/**
* Returns the public websocket URL for the sandbox.
*
* @param path Route path to append.
* @param port Optional forwarded port.
* @returns The public websocket URL.
*/
websocketUrl(path = "/", port?: number): string {
return this.client.sandboxes.websocketUrl(this.id, path, port);
}
/**
* Fetches the resolved home directory for the sandbox user.
*
* @param options Optional request settings.
* @returns The resolved sandbox user home directory.
*/
async getUserHomeDir(options?: { timeout?: number }): Promise<string> {
return this.client.sandboxes.getUserHomeDir(this.id, options);
}
/**
* Fetches the configured working directory for the sandbox.
*
* @param options Optional request settings.
* @returns The configured sandbox workdir.
*/
async getWorkdir(options?: { timeout?: number }): Promise<string> {
return this.client.sandboxes.getWorkdir(this.id, options);
}
}