@miosa/sdk 1.2.4 → 1.2.6

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.
Files changed (100) hide show
  1. package/README.md +75 -3
  2. package/dist/index.d.ts +323 -175
  3. package/dist/index.js +441 -386
  4. package/dist/index.js.map +1 -1
  5. package/package.json +1 -2
  6. package/src/client.ts +0 -293
  7. package/src/errors.ts +0 -136
  8. package/src/http.test.ts +0 -374
  9. package/src/http.ts +0 -390
  10. package/src/index.ts +0 -564
  11. package/src/resources/admin.ts +0 -359
  12. package/src/resources/analytics.ts +0 -60
  13. package/src/resources/api-keys.ts +0 -135
  14. package/src/resources/audit-log.ts +0 -64
  15. package/src/resources/benchmarks.ts +0 -104
  16. package/src/resources/builder-sessions.ts +0 -75
  17. package/src/resources/channels.ts +0 -143
  18. package/src/resources/checkpoints.ts +0 -225
  19. package/src/resources/command-center.ts +0 -73
  20. package/src/resources/community.ts +0 -103
  21. package/src/resources/completions.ts +0 -104
  22. package/src/resources/computer-auto-stop.ts +0 -43
  23. package/src/resources/computer-env.ts +0 -76
  24. package/src/resources/computer-logs.ts +0 -50
  25. package/src/resources/computer-osa.ts +0 -76
  26. package/src/resources/computer-ports.ts +0 -91
  27. package/src/resources/computer-terminal.ts +0 -61
  28. package/src/resources/computer-volumes.ts +0 -64
  29. package/src/resources/computer.ts +0 -551
  30. package/src/resources/computers.ts +0 -75
  31. package/src/resources/credits.ts +0 -43
  32. package/src/resources/cron-jobs.ts +0 -191
  33. package/src/resources/custom_domains.ts +0 -123
  34. package/src/resources/dashboard.ts +0 -49
  35. package/src/resources/databases.ts +0 -218
  36. package/src/resources/deployments.test.ts +0 -194
  37. package/src/resources/deployments.ts +0 -1060
  38. package/src/resources/desktop.ts +0 -134
  39. package/src/resources/docker-deploy.test.ts +0 -102
  40. package/src/resources/docker-deploy.ts +0 -183
  41. package/src/resources/egress.test.ts +0 -318
  42. package/src/resources/egressAudit.ts +0 -245
  43. package/src/resources/egressNetwork.ts +0 -450
  44. package/src/resources/egressSecrets.ts +0 -577
  45. package/src/resources/email.ts +0 -212
  46. package/src/resources/embeddings.ts +0 -36
  47. package/src/resources/events.ts +0 -296
  48. package/src/resources/exec.ts +0 -319
  49. package/src/resources/external-keys.ts +0 -79
  50. package/src/resources/files.test.ts +0 -339
  51. package/src/resources/files.ts +0 -220
  52. package/src/resources/flat-custom-domains.ts +0 -127
  53. package/src/resources/functions.ts +0 -178
  54. package/src/resources/governance.test.ts +0 -355
  55. package/src/resources/governance.ts +0 -528
  56. package/src/resources/health-checks.ts +0 -165
  57. package/src/resources/integrations.ts +0 -183
  58. package/src/resources/mcp.ts +0 -70
  59. package/src/resources/models.ts +0 -45
  60. package/src/resources/network_policy.ts +0 -73
  61. package/src/resources/open-computers/agents.ts +0 -91
  62. package/src/resources/open-computers/apps.ts +0 -102
  63. package/src/resources/open-computers/clusters.ts +0 -88
  64. package/src/resources/open-computers/desktop.ts +0 -34
  65. package/src/resources/open-computers/files.ts +0 -97
  66. package/src/resources/open-computers/hosts.ts +0 -85
  67. package/src/resources/open-computers/index.ts +0 -98
  68. package/src/resources/open-computers/jobs.ts +0 -75
  69. package/src/resources/open-computers/open_computers.test.ts +0 -288
  70. package/src/resources/open-computers/secrets.ts +0 -115
  71. package/src/resources/open-computers/terminal.ts +0 -33
  72. package/src/resources/open-computers/tunnels.ts +0 -87
  73. package/src/resources/open-computers/types.ts +0 -343
  74. package/src/resources/open-computers/workspaces.ts +0 -135
  75. package/src/resources/org-invites.ts +0 -189
  76. package/src/resources/phase1.test.ts +0 -187
  77. package/src/resources/project-auth.ts +0 -142
  78. package/src/resources/project-integrations.ts +0 -133
  79. package/src/resources/provider-defaults.ts +0 -89
  80. package/src/resources/quotas.ts +0 -77
  81. package/src/resources/regions.ts +0 -94
  82. package/src/resources/sandbox-processes.ts +0 -112
  83. package/src/resources/sandbox-shares.ts +0 -83
  84. package/src/resources/sandbox-templates.ts +0 -195
  85. package/src/resources/sandboxes.live.test.ts +0 -92
  86. package/src/resources/sandboxes.test.ts +0 -653
  87. package/src/resources/sandboxes.ts +0 -1433
  88. package/src/resources/settings.ts +0 -143
  89. package/src/resources/snapshots-standalone.ts +0 -51
  90. package/src/resources/storage.ts +0 -221
  91. package/src/resources/tenant-events.ts +0 -32
  92. package/src/resources/tenant.ts +0 -148
  93. package/src/resources/usage.ts +0 -85
  94. package/src/resources/volumes.ts +0 -117
  95. package/src/resources/webhooks.ts +0 -224
  96. package/src/resources/workspace-invites.ts +0 -188
  97. package/src/resources/workspace-members.test.ts +0 -121
  98. package/src/resources/workspace-members.ts +0 -143
  99. package/src/resources/workspaces.ts +0 -285
  100. package/src/types.ts +0 -463
@@ -1,1433 +0,0 @@
1
- import { MiosaError } from "../errors.js";
2
- import { HttpClient } from "../http.js";
3
- import { SandboxAudit } from "./egressAudit.js";
4
- import { SandboxNetwork } from "./egressNetwork.js";
5
- import { SandboxSecrets } from "./egressSecrets.js";
6
-
7
- function encodeContent(content: string | Uint8Array): string {
8
- const bytes =
9
- typeof content === "string" ? new TextEncoder().encode(content) : content;
10
- const maybeBuffer = (
11
- globalThis as {
12
- Buffer?: { from(b: Uint8Array): { toString(e: string): string } };
13
- }
14
- ).Buffer;
15
- if (maybeBuffer) return maybeBuffer.from(bytes).toString("base64");
16
- let bin = "";
17
- for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]!);
18
- return btoa(bin);
19
- }
20
-
21
- export const SANDBOX_TEMPLATE = "miosa-sandbox";
22
-
23
- export type SandboxId = string & { readonly __brand: "SandboxId" };
24
- export type SandboxState =
25
- | "provisioning"
26
- | "running"
27
- | "paused"
28
- | "destroyed"
29
- | "error";
30
-
31
- export interface SandboxCreateParams {
32
- templateId?: string;
33
- template_id?: string;
34
- image?: string;
35
- cpuCount?: number;
36
- cpu_count?: number;
37
- memoryMb?: number;
38
- memory_mb?: number;
39
- diskMb?: number;
40
- disk_mb?: number;
41
- diskSizeMb?: number;
42
- disk_size_mb?: number;
43
- timeoutSec?: number;
44
- timeout_sec?: number;
45
- idleTimeoutSec?: number;
46
- idle_timeout_sec?: number;
47
- alwaysOn?: boolean;
48
- always_on?: boolean;
49
- env?: Record<string, string>;
50
- metadata?: Record<string, unknown>;
51
- services?: Array<Record<string, unknown>>;
52
- readinessProbe?: Record<string, unknown>;
53
- readiness_probe?: Record<string, unknown>;
54
- database?: Record<string, unknown> | boolean;
55
- githubRepoUrl?: string;
56
- github_repo_url?: string;
57
- githubBranch?: string;
58
- github_branch?: string;
59
- githubClonePath?: string;
60
- github_clone_path?: string;
61
- name?: string;
62
- region?: string;
63
- entrypoint?: string;
64
- tags?: string[];
65
- idempotencyKey?: string;
66
- idempotency_key?: string;
67
- slug?: string;
68
- // White-label attribution. See platform/attribution docs.
69
- externalWorkspaceId?: string;
70
- external_workspace_id?: string;
71
- externalUserId?: string;
72
- external_user_id?: string;
73
- externalProjectId?: string;
74
- external_project_id?: string;
75
- }
76
-
77
- export interface SandboxListParams {
78
- state?: SandboxState | string;
79
- tags?: string[];
80
- externalWorkspaceId?: string;
81
- external_workspace_id?: string;
82
- externalUserId?: string;
83
- external_user_id?: string;
84
- externalProjectId?: string;
85
- external_project_id?: string;
86
- }
87
-
88
- export interface SandboxExecOptions {
89
- cwd?: string;
90
- workingDir?: string;
91
- working_dir?: string;
92
- env?: Record<string, string>;
93
- timeout?: number;
94
- timeoutSec?: number;
95
- timeout_sec?: number;
96
- }
97
-
98
- export interface SandboxExecResult {
99
- stdout: string;
100
- stderr: string;
101
- exitCode: number;
102
- exit_code: number;
103
- durationMs?: number;
104
- duration_ms?: number;
105
- }
106
-
107
- export type SandboxExecEvent =
108
- | { type?: "stdout"; line: string }
109
- | { type?: "stderr"; line: string }
110
- | { type?: "exit"; exit_code: number; exitCode?: number }
111
- | Record<string, unknown>;
112
-
113
- export interface SandboxExecRunner {
114
- (command: string, options?: SandboxExecOptions): Promise<SandboxExecResult>;
115
- run(
116
- command: string,
117
- options?: SandboxExecOptions,
118
- ): Promise<SandboxExecResult>;
119
- stream(
120
- command: string,
121
- options?: SandboxExecOptions,
122
- ): AsyncIterableIterator<SandboxExecEvent>;
123
- }
124
-
125
- export interface SandboxData {
126
- id: SandboxId;
127
- state: SandboxState;
128
- ready?: boolean;
129
- template_id?: string;
130
- image_id?: string | null;
131
- cpu_count?: number | null;
132
- memory_mb?: number | null;
133
- disk_mb?: number | null;
134
- disk_size_mb?: number | null;
135
- timeout_sec?: number | null;
136
- boot_path?: string | null;
137
- boot_ms?: number | null;
138
- ready_at?: string | null;
139
- preview_url?: string | null;
140
- metadata?: Record<string, unknown>;
141
- inserted_at?: string;
142
- created_at?: string;
143
- started_at?: string | null;
144
- destroyed_at?: string | null;
145
- total_runtime_sec?: number | null;
146
- }
147
-
148
- export interface SandboxTemplate {
149
- id: string;
150
- name?: string;
151
- slug?: string;
152
- description?: string;
153
- image_id?: string;
154
- built_in?: boolean;
155
- status?: string;
156
- preview_port?: number | null;
157
- workdir?: string;
158
- install_command?: string | null;
159
- start_command?: string | null;
160
- readiness_probe?: Record<string, unknown> | null;
161
- artifact_paths?: string[];
162
- runtimes?: string[];
163
- tags?: string[];
164
- build_spec?: Record<string, unknown>;
165
- current_build_id?: string | null;
166
- }
167
-
168
- export interface SandboxTemplateList {
169
- data: SandboxTemplate[];
170
- default_template_id?: string;
171
- }
172
-
173
- export interface SandboxBuildSpec {
174
- from: string;
175
- vcpu?: number;
176
- memoryMib?: number;
177
- diskMib?: number;
178
- steps?: Array<Record<string, unknown>>;
179
- env?: Record<string, string>;
180
- workdir?: string;
181
- user?: string;
182
- startCmd?: string;
183
- readyCmd?: string;
184
- previewPort?: number;
185
- artifactPaths?: string[];
186
- }
187
-
188
- export interface SandboxBuildSpecError {
189
- code: string;
190
- field: string;
191
- message: string;
192
- }
193
-
194
- export interface SandboxBuildSpecValidation {
195
- valid: boolean;
196
- build_spec?: Record<string, unknown>;
197
- errors?: SandboxBuildSpecError[];
198
- }
199
-
200
- export interface SandboxTemplateCreateParams {
201
- name: string;
202
- buildSpec?: SandboxBuildSpec;
203
- build_spec?: SandboxBuildSpec;
204
- slug?: string;
205
- description?: string;
206
- metadata?: Record<string, unknown>;
207
- }
208
-
209
- export interface SandboxTemplateBuildCreateParams {
210
- buildSpec?: SandboxBuildSpec;
211
- build_spec?: SandboxBuildSpec;
212
- metadata?: Record<string, unknown>;
213
- }
214
-
215
- export interface SandboxTemplateBuild {
216
- id: string;
217
- sandbox_template_id: string;
218
- source_type: "build_spec" | string;
219
- state:
220
- | "queued"
221
- | "building"
222
- | "certifying"
223
- | "snapshotting"
224
- | "ready"
225
- | "failed"
226
- | "cancelled";
227
- image_id?: string | null;
228
- rootfs_path?: string | null;
229
- snapshot_manifest?: Record<string, unknown>;
230
- log_url?: string | null;
231
- error_code?: string | null;
232
- error_message?: string | null;
233
- started_at?: string | null;
234
- finished_at?: string | null;
235
- duration_ms?: number | null;
236
- build_spec?: Record<string, unknown>;
237
- inserted_at?: string;
238
- updated_at?: string;
239
- }
240
-
241
- export interface SandboxFileEntry {
242
- name: string;
243
- path: string;
244
- size?: number;
245
- is_dir?: boolean;
246
- isDir?: boolean;
247
- modified_at?: string;
248
- modifiedAt?: string;
249
- [key: string]: unknown;
250
- }
251
-
252
- export interface SandboxFileTreeNode {
253
- path: string;
254
- name: string;
255
- type: "file" | "dir";
256
- size?: number;
257
- modified_at?: string;
258
- children?: SandboxFileTreeNode[];
259
- }
260
-
261
- export interface SandboxWriteManyEntry {
262
- path: string;
263
- content: string | Uint8Array;
264
- }
265
-
266
- export interface SandboxWriteManyResult {
267
- written: Array<{ path: string; size_bytes: number }>;
268
- failed: Array<{ path: string; error: string }>;
269
- }
270
-
271
- export interface SandboxFileChange {
272
- type: "created" | "modified" | "deleted";
273
- path: string;
274
- size_bytes?: number;
275
- }
276
-
277
- export interface SandboxEnvVar {
278
- key: string;
279
- encrypted: boolean;
280
- value?: string;
281
- }
282
-
283
- export interface SandboxFileList {
284
- path?: string;
285
- entries: SandboxFileEntry[];
286
- }
287
-
288
- export interface SandboxFileStat {
289
- path: string;
290
- size?: number;
291
- is_dir?: boolean;
292
- isDir?: boolean;
293
- mode?: string;
294
- modified_at?: string;
295
- modifiedAt?: string;
296
- [key: string]: unknown;
297
- }
298
-
299
- export interface SandboxSnapshot {
300
- id: string;
301
- sandbox_id?: string;
302
- status?: string;
303
- comment?: string;
304
- created_at?: string;
305
- updated_at?: string;
306
- [key: string]: unknown;
307
- }
308
-
309
- export interface SandboxDeployParams {
310
- name?: string;
311
- deploymentId?: string;
312
- deployment_id?: string;
313
- path?: string;
314
- sourcePath?: string;
315
- source_path?: string;
316
- outputPath?: string;
317
- output_path?: string;
318
- sourceSnapshotPath?: string;
319
- source_snapshot_path?: string;
320
- entrypoint?: string;
321
- buildCommand?: string;
322
- build_command?: string;
323
- runCommand?: string;
324
- run_command?: string;
325
- startCommand?: string;
326
- start_command?: string;
327
- port?: number;
328
- healthCheckPath?: string;
329
- health_check_path?: string;
330
- deploymentType?: "miosa_deploy" | "docker_deploy" | "docker-deploy" | string;
331
- deployment_type?: "miosa_deploy" | "docker_deploy" | "docker-deploy" | string;
332
- type?: "static" | "dynamic" | "server" | string;
333
- mode?: "static" | "dynamic" | "server" | string;
334
- database?: boolean | Record<string, unknown>;
335
- resources?: Record<string, unknown>;
336
- domain?: string;
337
- customDomain?: string;
338
- custom_domain?: string;
339
- idempotencyKey?: string;
340
- idempotency_key?: string;
341
- }
342
-
343
- type WireEnvelope<T> = T | { data: T };
344
-
345
- function unwrap<T>(payload: WireEnvelope<T>): T {
346
- if (
347
- payload !== null &&
348
- typeof payload === "object" &&
349
- "data" in payload &&
350
- (payload as { data?: unknown }).data !== undefined
351
- ) {
352
- return (payload as { data: T }).data;
353
- }
354
- return payload as T;
355
- }
356
-
357
- function listItems(
358
- payload:
359
- | WireEnvelope<SandboxData[]>
360
- | { sandboxes: SandboxData[] }
361
- | { items: SandboxData[] },
362
- ): SandboxData[] {
363
- if (Array.isArray(payload)) return payload;
364
- if ("data" in payload && Array.isArray(payload.data)) return payload.data;
365
- if ("sandboxes" in payload && Array.isArray(payload.sandboxes))
366
- return payload.sandboxes;
367
- if ("items" in payload && Array.isArray(payload.items)) return payload.items;
368
- return [];
369
- }
370
-
371
- function createBody(params: SandboxCreateParams = {}): Record<string, unknown> {
372
- const templateId =
373
- params.templateId ?? params.template_id ?? params.image ?? SANDBOX_TEMPLATE;
374
- return stripUndefined({
375
- template_id: templateId,
376
- cpu_count: params.cpuCount ?? params.cpu_count,
377
- memory_mb: params.memoryMb ?? params.memory_mb,
378
- disk_mb: params.diskMb ?? params.disk_mb,
379
- disk_size_mb: params.diskSizeMb ?? params.disk_size_mb,
380
- timeout_sec: params.timeoutSec ?? params.timeout_sec,
381
- idle_timeout_sec: params.idleTimeoutSec ?? params.idle_timeout_sec,
382
- always_on: params.alwaysOn ?? params.always_on,
383
- env: params.env,
384
- metadata: params.metadata,
385
- services: params.services,
386
- readiness_probe: params.readinessProbe ?? params.readiness_probe,
387
- database: params.database,
388
- github_repo_url: params.githubRepoUrl ?? params.github_repo_url,
389
- github_branch: params.githubBranch ?? params.github_branch,
390
- github_clone_path: params.githubClonePath ?? params.github_clone_path,
391
- name: params.name,
392
- region: params.region,
393
- entrypoint: params.entrypoint,
394
- tags: params.tags,
395
- slug: params.slug,
396
- external_workspace_id:
397
- params.externalWorkspaceId ?? params.external_workspace_id,
398
- external_user_id: params.externalUserId ?? params.external_user_id,
399
- external_project_id: params.externalProjectId ?? params.external_project_id,
400
- });
401
- }
402
-
403
- function execBody(
404
- command: string,
405
- options: SandboxExecOptions = {},
406
- ): Record<string, unknown> {
407
- return stripUndefined({
408
- command,
409
- cwd: options.cwd ?? options.workingDir ?? options.working_dir,
410
- env: options.env,
411
- timeout: options.timeout ?? options.timeoutSec ?? options.timeout_sec,
412
- });
413
- }
414
-
415
- function stripUndefined(
416
- input: Record<string, unknown>,
417
- ): Record<string, unknown> {
418
- return Object.fromEntries(
419
- Object.entries(input).filter(([, value]) => value !== undefined),
420
- );
421
- }
422
-
423
- export class SandboxCommands {
424
- constructor(private readonly sandbox: Sandbox) {}
425
-
426
- run(
427
- command: string,
428
- options?: SandboxExecOptions,
429
- ): Promise<SandboxExecResult> {
430
- return this.sandbox.exec.run(command, options);
431
- }
432
-
433
- stream(
434
- command: string,
435
- options?: SandboxExecOptions,
436
- ): AsyncIterableIterator<SandboxExecEvent> {
437
- return this.sandbox.exec.stream(command, options);
438
- }
439
- }
440
-
441
- export class SandboxFiles {
442
- constructor(private readonly sandbox: Sandbox) {}
443
-
444
- async write(path: string, content: string | Uint8Array): Promise<void> {
445
- await this.sandbox.writeFile(path, content);
446
- }
447
-
448
- async read(path: string): Promise<Uint8Array> {
449
- return this.sandbox.download(path);
450
- }
451
-
452
- async readText(path: string): Promise<string> {
453
- return new TextDecoder().decode(await this.read(path));
454
- }
455
-
456
- list(path = "/workspace"): Promise<SandboxFileList> {
457
- return this.sandbox.listFiles(path);
458
- }
459
-
460
- stat(path: string): Promise<SandboxFileStat> {
461
- return this.sandbox.statFile(path);
462
- }
463
-
464
- upload(path: string, content: string | Uint8Array): Promise<void> {
465
- return this.write(path, content);
466
- }
467
-
468
- download(path: string): Promise<Uint8Array> {
469
- return this.read(path);
470
- }
471
-
472
- /** GET /api/v1/sandboxes/{id}/files/tree — recursive directory tree. */
473
- async tree(path = "/workspace", depth = 3): Promise<SandboxFileTreeNode> {
474
- const http = (this.sandbox as unknown as { http: HttpClient }).http;
475
- const response = await http.get<unknown>(
476
- `/sandboxes/${this.sandbox.id}/files/tree`,
477
- { path, depth },
478
- );
479
- if (
480
- response &&
481
- typeof response === "object" &&
482
- "data" in (response as object)
483
- ) {
484
- return (response as { data: SandboxFileTreeNode }).data;
485
- }
486
- return response as SandboxFileTreeNode;
487
- }
488
-
489
- /** POST /api/v1/sandboxes/{id}/files/write-many — write multiple files atomically. */
490
- async writeMany(
491
- files: SandboxWriteManyEntry[],
492
- ): Promise<SandboxWriteManyResult> {
493
- const http = (this.sandbox as unknown as { http: HttpClient }).http;
494
- const payload = files.map((f) => ({
495
- path: f.path,
496
- content_base64: encodeContent(f.content),
497
- }));
498
- const response = await http.post<unknown>(
499
- `/sandboxes/${this.sandbox.id}/files/write-many`,
500
- { files: payload },
501
- );
502
- if (
503
- response &&
504
- typeof response === "object" &&
505
- "data" in (response as object)
506
- ) {
507
- return (response as { data: SandboxWriteManyResult }).data;
508
- }
509
- return response as SandboxWriteManyResult;
510
- }
511
-
512
- /** GET /api/v1/sandboxes/{id}/files/watch (SSE) — live file change events. */
513
- watch(): AsyncIterableIterator<SandboxFileChange> {
514
- const http = (this.sandbox as unknown as { http: HttpClient }).http;
515
- return http.stream<SandboxFileChange>(
516
- `/sandboxes/${this.sandbox.id}/files/watch`,
517
- );
518
- }
519
- }
520
-
521
- export class SandboxPreview {
522
- constructor(private readonly sandbox: Sandbox) {}
523
-
524
- expose(port?: number): Promise<string> {
525
- return this.sandbox.expose(port);
526
- }
527
- }
528
-
529
- export class SandboxArtifacts {
530
- constructor(private readonly sandbox: Sandbox) {}
531
-
532
- list(): Promise<Record<string, unknown>> {
533
- return this.sandbox.getArtifacts();
534
- }
535
- }
536
-
537
- export class SandboxLogs {
538
- constructor(private readonly sandbox: Sandbox) {}
539
-
540
- get(lines?: number): Promise<string | Record<string, unknown>> {
541
- return this.sandbox.getLogs(lines);
542
- }
543
-
544
- stream(): AsyncIterableIterator<Record<string, unknown>> {
545
- return this.sandbox.streamLogs();
546
- }
547
- }
548
-
549
- export class SandboxSnapshots {
550
- constructor(private readonly sandbox: Sandbox) {}
551
-
552
- create(comment?: string): Promise<SandboxSnapshot> {
553
- return this.sandbox.createSnapshot(comment);
554
- }
555
-
556
- list(): Promise<SandboxSnapshot[]> {
557
- return this.sandbox.listSnapshots();
558
- }
559
-
560
- restore(snapshotId: string): Promise<Sandbox> {
561
- return this.sandbox.restoreSnapshot(snapshotId);
562
- }
563
-
564
- delete(snapshotId: string): Promise<void> {
565
- return this.sandbox.deleteSnapshot(snapshotId);
566
- }
567
- }
568
-
569
- export class SandboxTerminal {
570
- constructor(private readonly sandbox: Sandbox) {}
571
-
572
- async create(
573
- params: {
574
- cols?: number;
575
- rows?: number;
576
- shell?: string;
577
- cwd?: string;
578
- env?: Record<string, string>;
579
- } = {},
580
- ): Promise<Record<string, unknown>> {
581
- const body = Object.fromEntries(
582
- Object.entries(params).filter(([, v]) => v !== undefined),
583
- );
584
- const response = unwrap(
585
- await (this.sandbox as unknown as { http: HttpClient }).http.post<
586
- WireEnvelope<Record<string, unknown>>
587
- >(`/sandboxes/${this.sandbox.id}/terminal`, body),
588
- );
589
- return response as Record<string, unknown>;
590
- }
591
-
592
- async delete(sessionId: string): Promise<void> {
593
- await (this.sandbox as unknown as { http: HttpClient }).http.delete(
594
- `/sandboxes/${this.sandbox.id}/terminal/${sessionId}`,
595
- );
596
- }
597
- }
598
-
599
- export class SandboxEvents {
600
- constructor(private readonly sandbox: Sandbox) {}
601
-
602
- /** Stream live sandbox events via SSE. */
603
- stream(): AsyncIterableIterator<Record<string, unknown>> {
604
- return (this.sandbox as unknown as { http: HttpClient }).http.stream<
605
- Record<string, unknown>
606
- >(`/sandboxes/${this.sandbox.id}/events`);
607
- }
608
- }
609
-
610
- export class SandboxPreviews {
611
- constructor(private readonly sandbox: Sandbox) {}
612
-
613
- private get http(): HttpClient {
614
- return (this.sandbox as unknown as { http: HttpClient }).http;
615
- }
616
-
617
- async list(): Promise<Record<string, unknown>[]> {
618
- const response = await this.http.get<unknown>(
619
- `/sandboxes/${this.sandbox.id}/previews`,
620
- );
621
- if (Array.isArray(response)) return response as Record<string, unknown>[];
622
- if (response && typeof response === "object") {
623
- const r = response as Record<string, unknown>;
624
- for (const k of ["data", "previews", "items"]) {
625
- if (Array.isArray(r[k])) return r[k] as Record<string, unknown>[];
626
- }
627
- }
628
- return [];
629
- }
630
-
631
- async create(
632
- port: number,
633
- opts: Record<string, unknown> = {},
634
- ): Promise<Record<string, unknown>> {
635
- const body = {
636
- port,
637
- ...Object.fromEntries(
638
- Object.entries(opts).filter(([, v]) => v !== undefined),
639
- ),
640
- };
641
- return unwrap(
642
- await this.http.post<WireEnvelope<Record<string, unknown>>>(
643
- `/sandboxes/${this.sandbox.id}/previews`,
644
- body,
645
- ),
646
- ) as Record<string, unknown>;
647
- }
648
-
649
- async get(previewId: string): Promise<Record<string, unknown>> {
650
- return unwrap(
651
- await this.http.get<WireEnvelope<Record<string, unknown>>>(
652
- `/sandboxes/${this.sandbox.id}/previews/${previewId}`,
653
- ),
654
- ) as Record<string, unknown>;
655
- }
656
-
657
- async delete(previewId: string): Promise<void> {
658
- await this.http.delete(
659
- `/sandboxes/${this.sandbox.id}/previews/${previewId}`,
660
- );
661
- }
662
-
663
- /** Mint a share token for previewId. */
664
- async share(
665
- previewId: string,
666
- opts: { ttl_seconds?: number; expires_in_sec?: number } = {},
667
- ): Promise<Record<string, unknown>> {
668
- return unwrap(
669
- await this.http.post<WireEnvelope<Record<string, unknown>>>(
670
- `/sandboxes/${this.sandbox.id}/previews/${previewId}/share`,
671
- { ttl_seconds: opts.ttl_seconds ?? opts.expires_in_sec ?? 3600 },
672
- ),
673
- ) as Record<string, unknown>;
674
- }
675
-
676
- /** Invalidate every share token associated with previewId. */
677
- async revokeShare(previewId: string): Promise<void> {
678
- await this.http.delete(
679
- `/sandboxes/${this.sandbox.id}/previews/${previewId}/share`,
680
- );
681
- }
682
- }
683
-
684
- export class SandboxEnv {
685
- constructor(private readonly sandbox: Sandbox) {}
686
-
687
- private get http(): HttpClient {
688
- return (this.sandbox as unknown as { http: HttpClient }).http;
689
- }
690
-
691
- /** GET /api/v1/sandboxes/{id}/env → list of env vars. */
692
- async get(): Promise<SandboxEnvVar[]> {
693
- const response = await this.http.get<unknown>(
694
- `/sandboxes/${this.sandbox.id}/env`,
695
- );
696
- if (Array.isArray(response)) return response as SandboxEnvVar[];
697
- if (response && typeof response === "object") {
698
- const r = response as Record<string, unknown>;
699
- for (const k of ["data", "vars", "env", "items"]) {
700
- if (Array.isArray(r[k])) return r[k] as SandboxEnvVar[];
701
- }
702
- }
703
- return [];
704
- }
705
-
706
- /** @deprecated Use get() */
707
- async list(): Promise<SandboxEnvVar[]> {
708
- return this.get();
709
- }
710
-
711
- /** PUT /api/v1/sandboxes/{id}/env — set (replace) env vars. */
712
- async set(
713
- vars: Array<{ key: string; value: string; encrypted?: boolean }>,
714
- ): Promise<SandboxEnvVar[]> {
715
- const response = await this.http.put<unknown>(
716
- `/sandboxes/${this.sandbox.id}/env`,
717
- { vars },
718
- );
719
- if (Array.isArray(response)) return response as SandboxEnvVar[];
720
- if (response && typeof response === "object") {
721
- const r = response as Record<string, unknown>;
722
- for (const k of ["data", "vars", "env", "items"]) {
723
- if (Array.isArray(r[k])) return r[k] as SandboxEnvVar[];
724
- }
725
- }
726
- return [];
727
- }
728
-
729
- /** DELETE /api/v1/sandboxes/{id}/env/{key} — remove a single env var. */
730
- async delete(key: string): Promise<void> {
731
- await this.http.delete<unknown>(
732
- `/sandboxes/${this.sandbox.id}/env/${encodeURIComponent(key)}`,
733
- );
734
- }
735
- }
736
-
737
- export class SandboxTags {
738
- constructor(private readonly sandbox: Sandbox) {}
739
-
740
- /** Replace the full tag list with tags. */
741
- async set(tags: string[]): Promise<Record<string, unknown>> {
742
- return unwrap(
743
- await (this.sandbox as unknown as { http: HttpClient }).http.patch<
744
- WireEnvelope<Record<string, unknown>>
745
- >(`/sandboxes/${this.sandbox.id}/tags`, { tags }),
746
- ) as Record<string, unknown>;
747
- }
748
- }
749
-
750
- export class Sandbox {
751
- data: SandboxData;
752
- readonly commands: SandboxCommands;
753
- readonly exec: SandboxExecRunner;
754
- readonly files: SandboxFiles;
755
- readonly preview: SandboxPreview;
756
- readonly artifacts: SandboxArtifacts;
757
- readonly logs: SandboxLogs;
758
- readonly snapshots: SandboxSnapshots;
759
- /** PTY session control — create/delete. */
760
- readonly terminal: SandboxTerminal;
761
- /** SSE event stream. */
762
- readonly events: SandboxEvents;
763
- /** Preview CRUD + share/revokeShare. */
764
- readonly previews: SandboxPreviews;
765
- /** Read-only env var listing. */
766
- readonly env: SandboxEnv;
767
- /** Tag replacement. */
768
- readonly tags: SandboxTags;
769
- /** Encrypted secrets + OAuth credentials scoped to this sandbox. */
770
- readonly secrets: SandboxSecrets;
771
- /** Egress allowlist + policies scoped to this sandbox. */
772
- readonly network: SandboxNetwork;
773
- /** Egress audit log + live tail scoped to this sandbox. */
774
- readonly audit: SandboxAudit;
775
-
776
- constructor(
777
- private readonly http: HttpClient,
778
- data: SandboxData,
779
- ) {
780
- this.data = data;
781
- const runExec = (command: string, options?: SandboxExecOptions) =>
782
- this.runExec(command, options);
783
- this.exec = Object.assign(runExec, {
784
- run: runExec,
785
- stream: (command: string, options?: SandboxExecOptions) =>
786
- this.execStream(command, options),
787
- });
788
- this.commands = new SandboxCommands(this);
789
- this.files = new SandboxFiles(this);
790
- this.preview = new SandboxPreview(this);
791
- this.artifacts = new SandboxArtifacts(this);
792
- this.logs = new SandboxLogs(this);
793
- this.snapshots = new SandboxSnapshots(this);
794
- this.terminal = new SandboxTerminal(this);
795
- this.events = new SandboxEvents(this);
796
- this.previews = new SandboxPreviews(this);
797
- this.env = new SandboxEnv(this);
798
- this.tags = new SandboxTags(this);
799
- // Egress (security) namespaces — pre-scoped to this sandbox id.
800
- const sandboxId = data.id as string;
801
- this.secrets = new SandboxSecrets(http, sandboxId);
802
- this.network = new SandboxNetwork(http, sandboxId);
803
- this.audit = new SandboxAudit(http, sandboxId);
804
- }
805
-
806
- get id(): SandboxId {
807
- return this.data.id;
808
- }
809
-
810
- get state(): SandboxState {
811
- return this.data.state;
812
- }
813
-
814
- get ready(): boolean {
815
- return this.data.ready ?? this.data.state === "running";
816
- }
817
-
818
- get templateId(): string {
819
- return this.data.template_id ?? this.data.image_id ?? "";
820
- }
821
-
822
- async refresh(): Promise<Sandbox> {
823
- this.data = unwrap(
824
- await this.http.get<WireEnvelope<SandboxData>>(`/sandboxes/${this.id}`),
825
- );
826
- return this;
827
- }
828
-
829
- private async runExec(
830
- command: string,
831
- options?: SandboxExecOptions,
832
- ): Promise<SandboxExecResult> {
833
- this.assertRunning("exec");
834
- const response = unwrap(
835
- await this.http.post<WireEnvelope<Record<string, unknown>>>(
836
- `/sandboxes/${this.id}/exec`,
837
- execBody(command, options),
838
- ),
839
- );
840
- const exitCode = Number(response.exit_code ?? response.exitCode ?? 0);
841
- const result: SandboxExecResult = {
842
- stdout: String(response.stdout ?? ""),
843
- stderr: String(response.stderr ?? ""),
844
- exitCode,
845
- exit_code: exitCode,
846
- };
847
- if (typeof response.duration_ms === "number") {
848
- result.durationMs = response.duration_ms;
849
- result.duration_ms = response.duration_ms;
850
- }
851
- return result;
852
- }
853
-
854
- private execStream(
855
- command: string,
856
- options?: SandboxExecOptions,
857
- ): AsyncIterableIterator<SandboxExecEvent> {
858
- this.assertRunning("exec.stream");
859
- return this.http.stream<SandboxExecEvent>(
860
- `/sandboxes/${this.id}/exec/stream`,
861
- {
862
- method: "POST",
863
- body: execBody(command, options),
864
- },
865
- );
866
- }
867
-
868
- async writeFile(path: string, content: string | Uint8Array): Promise<void> {
869
- this.assertRunning("writeFile");
870
- const bytes =
871
- typeof content === "string" ? new TextEncoder().encode(content) : content;
872
- await this.http.post(`/sandboxes/${this.id}/files`, {
873
- path,
874
- content: toBase64(bytes),
875
- });
876
- }
877
-
878
- async download(path: string): Promise<Uint8Array> {
879
- this.assertRunning("download");
880
- return this.http.getBinary(
881
- `/sandboxes/${this.id}/files/${path.replace(/^\/+/, "")}`,
882
- );
883
- }
884
-
885
- async readFile(path: string): Promise<string> {
886
- return new TextDecoder().decode(await this.download(path));
887
- }
888
-
889
- async listFiles(path = "/workspace"): Promise<SandboxFileList> {
890
- this.assertRunning("files.list");
891
- const response = unwrap(
892
- await this.http.get<WireEnvelope<SandboxFileList>>(
893
- `/sandboxes/${this.id}/files`,
894
- { path },
895
- ),
896
- );
897
- return response;
898
- }
899
-
900
- async statFile(path: string): Promise<SandboxFileStat> {
901
- this.assertRunning("files.stat");
902
- return unwrap(
903
- await this.http.post<WireEnvelope<SandboxFileStat>>(
904
- `/sandboxes/${this.id}/files/stat`,
905
- { path },
906
- ),
907
- );
908
- }
909
-
910
- async expose(port?: number): Promise<string> {
911
- this.assertRunning("expose");
912
- const response = unwrap(
913
- await this.http.post<WireEnvelope<Record<string, unknown>>>(
914
- `/sandboxes/${this.id}/expose`,
915
- port === undefined ? {} : { port },
916
- ),
917
- );
918
- return String(response.url ?? response.preview_url ?? "");
919
- }
920
-
921
- async startTemplate(
922
- options: Record<string, unknown> = {},
923
- ): Promise<Record<string, unknown>> {
924
- this.assertRunning("startTemplate");
925
- return unwrap(
926
- await this.http.post<WireEnvelope<Record<string, unknown>>>(
927
- `/sandboxes/${this.id}/template/start`,
928
- options,
929
- ),
930
- );
931
- }
932
-
933
- async getArtifacts(): Promise<Record<string, unknown>> {
934
- return unwrap(
935
- await this.http.get<WireEnvelope<Record<string, unknown>>>(
936
- `/sandboxes/${this.id}/artifacts`,
937
- ),
938
- );
939
- }
940
-
941
- async getLogs(lines?: number): Promise<string | Record<string, unknown>> {
942
- const response = await this.http.get<WireEnvelope<Record<string, unknown>>>(
943
- `/sandboxes/${this.id}/logs`,
944
- { lines },
945
- );
946
- return unwrap(response);
947
- }
948
-
949
- streamLogs(): AsyncIterableIterator<Record<string, unknown>> {
950
- return this.http.stream<Record<string, unknown>>(
951
- `/sandboxes/${this.id}/logs/stream`,
952
- );
953
- }
954
-
955
- async createSnapshot(comment?: string): Promise<SandboxSnapshot> {
956
- this.assertRunning("snapshots.create");
957
- return unwrap(
958
- await this.http.post<WireEnvelope<SandboxSnapshot>>(
959
- `/sandboxes/${this.id}/snapshots`,
960
- comment ? { comment } : {},
961
- ),
962
- );
963
- }
964
-
965
- async listSnapshots(): Promise<SandboxSnapshot[]> {
966
- return unwrap(
967
- await this.http.get<WireEnvelope<SandboxSnapshot[]>>(
968
- `/sandboxes/${this.id}/snapshots`,
969
- ),
970
- );
971
- }
972
-
973
- async restoreSnapshot(snapshotId: string): Promise<Sandbox> {
974
- const data = unwrap(
975
- await this.http.post<WireEnvelope<SandboxData>>(
976
- `/sandboxes/${this.id}/restore/${snapshotId}`,
977
- {},
978
- ),
979
- );
980
- return new Sandbox(this.http, data);
981
- }
982
-
983
- async deleteSnapshot(snapshotId: string): Promise<void> {
984
- await this.http.delete(`/sandboxes/${this.id}/snapshots/${snapshotId}`);
985
- }
986
-
987
- /**
988
- * Fork (clone) this sandbox into a new sandbox via copy-on-write snapshot.
989
- * The original sandbox continues running unchanged.
990
- */
991
- async fork(
992
- opts: { name?: string; metadata?: Record<string, unknown> } = {},
993
- ): Promise<Sandbox> {
994
- this.assertRunning("fork");
995
- const body: Record<string, unknown> = {};
996
- if (opts.name !== undefined) body.name = opts.name;
997
- if (opts.metadata !== undefined) body.metadata = opts.metadata;
998
- const data = unwrap(
999
- await this.http.post<WireEnvelope<SandboxData>>(
1000
- `/sandboxes/${this.id}/fork`,
1001
- body,
1002
- ),
1003
- );
1004
- return new Sandbox(this.http, data);
1005
- }
1006
-
1007
- /**
1008
- * PATCH /api/v1/sandboxes/{id} — update mutable sandbox fields.
1009
- */
1010
- async update(params: {
1011
- name?: string;
1012
- slug?: string;
1013
- tags?: string[];
1014
- metadata?: Record<string, unknown>;
1015
- always_on?: boolean;
1016
- timeout_sec?: number;
1017
- idle_timeout_sec?: number;
1018
- }): Promise<Sandbox> {
1019
- const body: Record<string, unknown> = {};
1020
- for (const [k, v] of Object.entries(params)) {
1021
- if (v !== undefined) body[k] = v;
1022
- }
1023
- const data = unwrap(
1024
- await this.http.patch<WireEnvelope<SandboxData>>(
1025
- `/sandboxes/${this.id}`,
1026
- body,
1027
- ),
1028
- );
1029
- this.data = data;
1030
- return this;
1031
- }
1032
-
1033
- /**
1034
- * POST /api/v1/sandboxes/{id}/preview-token → {token, url, expires_at, scope}
1035
- */
1036
- async previewToken(
1037
- expiresIn = 3600,
1038
- scope = "read",
1039
- ): Promise<{
1040
- token: string;
1041
- url: string;
1042
- expires_at: string;
1043
- scope: string;
1044
- [key: string]: unknown;
1045
- }> {
1046
- const raw = await this.http.post<unknown>(
1047
- `/sandboxes/${this.id}/preview-token`,
1048
- { expires_in: expiresIn, scope },
1049
- );
1050
- if (raw && typeof raw === "object" && "data" in (raw as object)) {
1051
- return (
1052
- raw as {
1053
- data: {
1054
- token: string;
1055
- url: string;
1056
- expires_at: string;
1057
- scope: string;
1058
- };
1059
- }
1060
- ).data;
1061
- }
1062
- return raw as {
1063
- token: string;
1064
- url: string;
1065
- expires_at: string;
1066
- scope: string;
1067
- };
1068
- }
1069
-
1070
- async pause(): Promise<Sandbox> {
1071
- const data = unwrap(
1072
- await this.http.post<WireEnvelope<SandboxData>>(
1073
- `/sandboxes/${this.id}/pause`,
1074
- {},
1075
- ),
1076
- );
1077
- this.data = data;
1078
- return this;
1079
- }
1080
-
1081
- async resume(): Promise<Sandbox> {
1082
- const data = unwrap(
1083
- await this.http.post<WireEnvelope<SandboxData>>(
1084
- `/sandboxes/${this.id}/resume`,
1085
- {},
1086
- ),
1087
- );
1088
- this.data = data;
1089
- return this;
1090
- }
1091
-
1092
- async deploy(
1093
- params: SandboxDeployParams = {},
1094
- ): Promise<Record<string, unknown>> {
1095
- const idempotencyKey = params.idempotencyKey ?? params.idempotency_key;
1096
- const requestOptions: Parameters<HttpClient["request"]>[1] = {
1097
- method: "POST",
1098
- body: stripUndefined({
1099
- name: params.name,
1100
- deployment_id: params.deploymentId ?? params.deployment_id,
1101
- output_path:
1102
- params.outputPath ??
1103
- params.output_path ??
1104
- params.path ??
1105
- params.sourcePath ??
1106
- params.source_path,
1107
- source_snapshot_path:
1108
- params.sourceSnapshotPath ?? params.source_snapshot_path,
1109
- entrypoint: params.entrypoint,
1110
- build_command: params.buildCommand ?? params.build_command,
1111
- run_command: params.runCommand ?? params.run_command,
1112
- start_command: params.startCommand ?? params.start_command,
1113
- port: params.port,
1114
- health_check_path: params.healthCheckPath ?? params.health_check_path,
1115
- deployment_type: params.deploymentType ?? params.deployment_type,
1116
- type: params.type,
1117
- mode: params.mode,
1118
- database: params.database,
1119
- resources: params.resources,
1120
- domain: params.domain,
1121
- custom_domain: params.customDomain ?? params.custom_domain,
1122
- }),
1123
- };
1124
- if (idempotencyKey) {
1125
- requestOptions.headers = { "Idempotency-Key": idempotencyKey };
1126
- }
1127
-
1128
- return unwrap(
1129
- await this.http.request<WireEnvelope<Record<string, unknown>>>(
1130
- `/sandboxes/${this.id}/deploy`,
1131
- requestOptions,
1132
- ),
1133
- );
1134
- }
1135
-
1136
- async deployDocker(
1137
- params: SandboxDeployParams = {},
1138
- ): Promise<Record<string, unknown>> {
1139
- return this.deploy({ ...params, deploymentType: "docker_deploy" });
1140
- }
1141
-
1142
- /** Check readiness of the sandbox (GET /sandboxes/:id/readiness). */
1143
- async readiness(): Promise<Record<string, unknown>> {
1144
- return unwrap(
1145
- await this.http.get<WireEnvelope<Record<string, unknown>>>(
1146
- `/sandboxes/${this.id}/readiness`,
1147
- ),
1148
- ) as Record<string, unknown>;
1149
- }
1150
-
1151
- /**
1152
- * Block until the sandbox reports ready, or *timeout* seconds elapse.
1153
- *
1154
- * When `stream` is `true` (the default) this opens an SSE connection
1155
- * to `GET /sandboxes/:id/readiness/stream` and waits for an
1156
- * `event: ready` frame. The server emits `ready` immediately if the
1157
- * sandbox is already ready, otherwise as soon as the readiness PubSub
1158
- * message fires.
1159
- *
1160
- * Returns `true` once the sandbox is ready, `false` on `event: timeout`
1161
- * or when the local timeout elapses before ready.
1162
- *
1163
- * If the SSE endpoint returns 404 (server pre-dates the streaming
1164
- * endpoint) this transparently falls back to polling
1165
- * {@link readiness} every 10 ms until ready or timeout.
1166
- */
1167
- async waitUntilReady(
1168
- options: { timeout?: number; stream?: boolean } = {},
1169
- ): Promise<boolean> {
1170
- const timeout = options.timeout ?? 30;
1171
- const stream = options.stream ?? true;
1172
-
1173
- if (stream) {
1174
- const sseResult = await this.tryReadinessStream(timeout);
1175
- if (sseResult !== null) return sseResult;
1176
- // null === fall through to polling fallback (404 or transport error)
1177
- }
1178
-
1179
- // Polling fallback — fixed 10 ms tick, no exponential backoff.
1180
- const deadlineMs = Date.now() + timeout * 1000;
1181
- while (Date.now() < deadlineMs) {
1182
- try {
1183
- const data = await this.readiness();
1184
- if (data.ready === true || data.status === "ready") return true;
1185
- } catch {
1186
- // swallow transient errors and keep polling
1187
- }
1188
- await new Promise((resolve) => setTimeout(resolve, 10));
1189
- }
1190
- return false;
1191
- }
1192
-
1193
- /**
1194
- * Returns `true` / `false` for terminal SSE events, or `null` if the
1195
- * stream endpoint is unavailable (404 or transport error) so callers
1196
- * can fall back to polling.
1197
- */
1198
- private async tryReadinessStream(
1199
- timeoutSec: number,
1200
- ): Promise<boolean | null> {
1201
- const abort = new AbortController();
1202
- const timer = setTimeout(() => abort.abort(), (timeoutSec + 5) * 1000);
1203
- try {
1204
- const headers: Record<string, string> = {
1205
- Authorization: `Bearer ${this.http.apiKey}`,
1206
- Accept: "text/event-stream",
1207
- "User-Agent": "@miosa/sdk/1.0.0",
1208
- };
1209
- const response = await fetch(
1210
- `${this.http.baseUrl}/sandboxes/${this.id}/readiness/stream`,
1211
- { method: "GET", headers, signal: abort.signal },
1212
- );
1213
- if (response.status === 404) return null;
1214
- if (!response.ok || !response.body) return null;
1215
-
1216
- const reader = response.body.getReader();
1217
- const decoder = new TextDecoder();
1218
- let buffer = "";
1219
- try {
1220
- while (true) {
1221
- const { done, value } = await reader.read();
1222
- if (done) break;
1223
- buffer += decoder.decode(value, { stream: true });
1224
- let newlineIdx: number;
1225
- while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
1226
- const line = buffer.slice(0, newlineIdx).replace(/\r$/, "");
1227
- buffer = buffer.slice(newlineIdx + 1);
1228
- if (line.startsWith("event:")) {
1229
- const evt = line.slice(6).trim();
1230
- if (evt === "ready") return true;
1231
- if (evt === "timeout") return false;
1232
- }
1233
- }
1234
- }
1235
- } finally {
1236
- try {
1237
- reader.releaseLock();
1238
- } catch {
1239
- // already released
1240
- }
1241
- }
1242
- // stream closed without a terminal event — let caller decide
1243
- return null;
1244
- } catch {
1245
- return null;
1246
- } finally {
1247
- clearTimeout(timer);
1248
- try {
1249
- abort.abort();
1250
- } catch {
1251
- // signal already aborted
1252
- }
1253
- }
1254
- }
1255
-
1256
- async destroy(): Promise<void> {
1257
- if (this.data.state === "destroyed") return;
1258
- await this.http.delete(`/sandboxes/${this.id}`);
1259
- this.data = {
1260
- ...this.data,
1261
- state: "destroyed",
1262
- destroyed_at: new Date().toISOString(),
1263
- };
1264
- }
1265
-
1266
- delete(): Promise<void> {
1267
- return this.destroy();
1268
- }
1269
-
1270
- private assertRunning(operation: string): void {
1271
- if (this.data.state === "destroyed") {
1272
- throw new MiosaError(
1273
- `Sandbox ${this.id} has been destroyed`,
1274
- 400,
1275
- "SANDBOX_DESTROYED",
1276
- );
1277
- }
1278
- if (this.data.state !== "running") {
1279
- throw new MiosaError(
1280
- `Cannot ${operation} on sandbox ${this.id}: state is ${this.data.state}, expected running`,
1281
- 409,
1282
- "SANDBOX_NOT_RUNNING",
1283
- );
1284
- }
1285
- }
1286
- }
1287
-
1288
- export class Sandboxes {
1289
- constructor(private readonly http: HttpClient) {}
1290
-
1291
- async create(params: SandboxCreateParams = {}): Promise<Sandbox> {
1292
- const idempotencyKey = params.idempotencyKey ?? params.idempotency_key;
1293
- const requestOptions: Parameters<HttpClient["request"]>[1] = {
1294
- method: "POST",
1295
- body: createBody(params),
1296
- };
1297
- if (idempotencyKey) {
1298
- requestOptions.headers = { "Idempotency-Key": idempotencyKey };
1299
- }
1300
-
1301
- const data = unwrap(
1302
- await this.http.request<WireEnvelope<SandboxData>>(
1303
- "/sandboxes",
1304
- requestOptions,
1305
- ),
1306
- );
1307
- return new Sandbox(this.http, data);
1308
- }
1309
-
1310
- async list(params: SandboxListParams = {}): Promise<Sandbox[]> {
1311
- const data = await this.http.get<
1312
- | WireEnvelope<SandboxData[]>
1313
- | { sandboxes: SandboxData[] }
1314
- | { items: SandboxData[] }
1315
- >("/sandboxes", {
1316
- state: params.state,
1317
- tags: params.tags?.join(","),
1318
- external_workspace_id:
1319
- params.externalWorkspaceId ?? params.external_workspace_id,
1320
- external_user_id: params.externalUserId ?? params.external_user_id,
1321
- external_project_id:
1322
- params.externalProjectId ?? params.external_project_id,
1323
- });
1324
- return listItems(data).map((item) => new Sandbox(this.http, item));
1325
- }
1326
-
1327
- async get(id: SandboxId | string): Promise<Sandbox> {
1328
- const data = unwrap(
1329
- await this.http.get<WireEnvelope<SandboxData>>(`/sandboxes/${id}`),
1330
- );
1331
- return new Sandbox(this.http, data);
1332
- }
1333
-
1334
- connect(id: SandboxId | string): Promise<Sandbox> {
1335
- return this.get(id);
1336
- }
1337
-
1338
- async delete(id: SandboxId | string): Promise<void> {
1339
- await this.http.delete(`/sandboxes/${id}`);
1340
- }
1341
-
1342
- async listTemplates(
1343
- options: { includeAliases?: boolean } = {},
1344
- ): Promise<SandboxTemplateList> {
1345
- return this.http.get<SandboxTemplateList>("/sandbox-templates", {
1346
- include_aliases: options.includeAliases,
1347
- });
1348
- }
1349
-
1350
- async getTemplate(id: string): Promise<SandboxTemplate> {
1351
- return this.http.get<SandboxTemplate>(`/sandbox-templates/${id}`);
1352
- }
1353
-
1354
- async getBuildSpecSchema(): Promise<Record<string, unknown>> {
1355
- return this.http.get<Record<string, unknown>>(
1356
- "/sandbox-templates/build-spec",
1357
- );
1358
- }
1359
-
1360
- async validateBuildSpec(
1361
- buildSpec: SandboxBuildSpec,
1362
- ): Promise<SandboxBuildSpecValidation> {
1363
- return this.http.post<SandboxBuildSpecValidation>(
1364
- "/sandbox-templates/validate",
1365
- {
1366
- build_spec: buildSpec,
1367
- },
1368
- );
1369
- }
1370
-
1371
- async createTemplate(
1372
- params: SandboxTemplateCreateParams,
1373
- ): Promise<SandboxTemplate> {
1374
- const response = await this.http.post<WireEnvelope<SandboxTemplate>>(
1375
- "/sandbox-templates",
1376
- stripUndefined({
1377
- name: params.name,
1378
- slug: params.slug,
1379
- description: params.description,
1380
- build_spec: params.buildSpec ?? params.build_spec,
1381
- metadata: params.metadata,
1382
- }),
1383
- );
1384
- return unwrap(response);
1385
- }
1386
-
1387
- async createTemplateBuild(
1388
- templateId: string,
1389
- params: SandboxTemplateBuildCreateParams = {},
1390
- ): Promise<SandboxTemplateBuild> {
1391
- const response = await this.http.post<WireEnvelope<SandboxTemplateBuild>>(
1392
- `/sandbox-templates/${templateId}/builds`,
1393
- stripUndefined({
1394
- build_spec: params.buildSpec ?? params.build_spec,
1395
- metadata: params.metadata,
1396
- }),
1397
- );
1398
- return unwrap(response);
1399
- }
1400
-
1401
- async listTemplateBuilds(
1402
- templateId: string,
1403
- ): Promise<SandboxTemplateBuild[]> {
1404
- const response = await this.http.get<WireEnvelope<SandboxTemplateBuild[]>>(
1405
- `/sandbox-templates/${templateId}/builds`,
1406
- );
1407
- return unwrap(response);
1408
- }
1409
-
1410
- async getTemplateBuild(buildId: string): Promise<SandboxTemplateBuild> {
1411
- const response = await this.http.get<WireEnvelope<SandboxTemplateBuild>>(
1412
- `/sandbox-template-builds/${buildId}`,
1413
- );
1414
- return unwrap(response);
1415
- }
1416
- }
1417
-
1418
- function toBase64(bytes: Uint8Array): string {
1419
- const maybeBuffer = (
1420
- globalThis as {
1421
- Buffer?: {
1422
- from(data: Uint8Array): { toString(encoding: string): string };
1423
- };
1424
- }
1425
- ).Buffer;
1426
- if (maybeBuffer) return maybeBuffer.from(bytes).toString("base64");
1427
-
1428
- let binary = "";
1429
- for (let index = 0; index < bytes.length; index += 1) {
1430
- binary += String.fromCharCode(bytes[index]!);
1431
- }
1432
- return btoa(binary);
1433
- }