@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,653 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import { MiosaError } from "../errors.js";
3
- import { HttpClient } from "../http.js";
4
- import { Sandbox, Sandboxes } from "./sandboxes.js";
5
-
6
- const mockGet = vi.fn();
7
- const mockPost = vi.fn();
8
- const mockDelete = vi.fn();
9
- const mockRequest = vi.fn();
10
- const mockGetBinary = vi.fn();
11
- const mockStream = vi.fn();
12
-
13
- function makeHttp(): HttpClient {
14
- const http = {} as HttpClient;
15
- http.get = mockGet;
16
- http.post = mockPost;
17
- http.delete = mockDelete;
18
- http.request = mockRequest;
19
- http.getBinary = mockGetBinary;
20
- http.stream = mockStream;
21
- return http;
22
- }
23
-
24
- function sandboxData(overrides: Record<string, unknown> = {}) {
25
- return {
26
- id: "sbx_123",
27
- state: "running",
28
- ready: true,
29
- template_id: "miosa-sandbox",
30
- cpu_count: 1,
31
- memory_mb: 1024,
32
- timeout_sec: 300,
33
- metadata: {},
34
- created_at: "2026-05-13T00:00:00Z",
35
- started_at: "2026-05-13T00:00:01Z",
36
- destroyed_at: null,
37
- total_runtime_sec: null,
38
- ...overrides,
39
- };
40
- }
41
-
42
- beforeEach(() => {
43
- vi.clearAllMocks();
44
- });
45
-
46
- describe("Sandboxes", () => {
47
- it("create() calls native /sandboxes with template_id", async () => {
48
- mockRequest.mockResolvedValue({ data: sandboxData() });
49
-
50
- const client = new Sandboxes(makeHttp());
51
- const sandbox = await client.create({
52
- templateId: "nextjs",
53
- cpuCount: 2,
54
- memoryMb: 2048,
55
- idempotencyKey: "idem-1",
56
- });
57
-
58
- expect(mockRequest).toHaveBeenCalledWith("/sandboxes", {
59
- method: "POST",
60
- body: {
61
- template_id: "nextjs",
62
- cpu_count: 2,
63
- memory_mb: 2048,
64
- },
65
- headers: { "Idempotency-Key": "idem-1" },
66
- });
67
- expect(sandbox).toBeInstanceOf(Sandbox);
68
- expect(sandbox.id).toBe("sbx_123");
69
- });
70
-
71
- it("create() defaults to miosa-sandbox", async () => {
72
- mockRequest.mockResolvedValue(sandboxData());
73
-
74
- await new Sandboxes(makeHttp()).create();
75
-
76
- expect(mockRequest).toHaveBeenCalledWith("/sandboxes", {
77
- method: "POST",
78
- body: { template_id: "miosa-sandbox" },
79
- });
80
- });
81
-
82
- it("list() calls native /sandboxes and maps sandbox handles", async () => {
83
- mockGet.mockResolvedValue({ data: [sandboxData()] });
84
-
85
- const result = await new Sandboxes(makeHttp()).list({
86
- state: "running",
87
- tags: ["agent", "preview"],
88
- });
89
-
90
- expect(mockGet).toHaveBeenCalledWith("/sandboxes", {
91
- state: "running",
92
- tags: "agent,preview",
93
- });
94
- expect(result[0]).toBeInstanceOf(Sandbox);
95
- });
96
-
97
- it("get() and connect() call native /sandboxes/:id", async () => {
98
- mockGet.mockResolvedValue(sandboxData());
99
-
100
- const client = new Sandboxes(makeHttp());
101
- await client.get("sbx_123");
102
- await client.connect("sbx_123");
103
-
104
- expect(mockGet).toHaveBeenNthCalledWith(1, "/sandboxes/sbx_123");
105
- expect(mockGet).toHaveBeenNthCalledWith(2, "/sandboxes/sbx_123");
106
- });
107
-
108
- it("delete() calls native /sandboxes/:id", async () => {
109
- mockDelete.mockResolvedValue(undefined);
110
-
111
- await new Sandboxes(makeHttp()).delete("sbx_123");
112
-
113
- expect(mockDelete).toHaveBeenCalledWith("/sandboxes/sbx_123");
114
- });
115
-
116
- it("gets and validates the public BuildSpec contract", async () => {
117
- mockGet.mockResolvedValue({ version: "2026-05-13" });
118
- mockPost.mockResolvedValue({
119
- valid: true,
120
- build_spec: { from: "node:22-bookworm" },
121
- });
122
-
123
- const client = new Sandboxes(makeHttp());
124
- const schema = await client.getBuildSpecSchema();
125
- const validation = await client.validateBuildSpec({
126
- from: "node:22-bookworm",
127
- startCmd: "pnpm dev --host 0.0.0.0 --port 3000",
128
- });
129
-
130
- expect(mockGet).toHaveBeenCalledWith("/sandbox-templates/build-spec");
131
- expect(mockPost).toHaveBeenCalledWith("/sandbox-templates/validate", {
132
- build_spec: {
133
- from: "node:22-bookworm",
134
- startCmd: "pnpm dev --host 0.0.0.0 --port 3000",
135
- },
136
- });
137
- expect(schema.version).toBe("2026-05-13");
138
- expect(validation.valid).toBe(true);
139
- });
140
-
141
- it("creates templates and queued template builds", async () => {
142
- mockPost
143
- .mockResolvedValueOnce({
144
- data: {
145
- id: "tpl_123",
146
- name: "Agent Web App",
147
- slug: "agent-web-app",
148
- built_in: false,
149
- status: "draft",
150
- },
151
- })
152
- .mockResolvedValueOnce({
153
- data: {
154
- id: "build_123",
155
- sandbox_template_id: "tpl_123",
156
- source_type: "build_spec",
157
- state: "queued",
158
- },
159
- });
160
-
161
- const client = new Sandboxes(makeHttp());
162
- const template = await client.createTemplate({
163
- name: "Agent Web App",
164
- slug: "agent-web-app",
165
- buildSpec: { from: "node:22-bookworm" },
166
- });
167
- const build = await client.createTemplateBuild(template.id);
168
-
169
- expect(mockPost).toHaveBeenNthCalledWith(1, "/sandbox-templates", {
170
- name: "Agent Web App",
171
- slug: "agent-web-app",
172
- build_spec: { from: "node:22-bookworm" },
173
- });
174
- expect(mockPost).toHaveBeenNthCalledWith(
175
- 2,
176
- "/sandbox-templates/tpl_123/builds",
177
- {},
178
- );
179
- expect(template.id).toBe("tpl_123");
180
- expect(build.state).toBe("queued");
181
- });
182
-
183
- it("lists and gets template builds", async () => {
184
- mockGet
185
- .mockResolvedValueOnce({
186
- data: [
187
- {
188
- id: "build_123",
189
- sandbox_template_id: "tpl_123",
190
- source_type: "build_spec",
191
- state: "queued",
192
- },
193
- ],
194
- })
195
- .mockResolvedValueOnce({
196
- data: {
197
- id: "build_123",
198
- sandbox_template_id: "tpl_123",
199
- source_type: "build_spec",
200
- state: "queued",
201
- },
202
- });
203
-
204
- const client = new Sandboxes(makeHttp());
205
- const builds = await client.listTemplateBuilds("tpl_123");
206
- const build = await client.getTemplateBuild("build_123");
207
-
208
- expect(mockGet).toHaveBeenNthCalledWith(
209
- 1,
210
- "/sandbox-templates/tpl_123/builds",
211
- );
212
- expect(mockGet).toHaveBeenNthCalledWith(
213
- 2,
214
- "/sandbox-template-builds/build_123",
215
- );
216
- expect(builds[0]?.id).toBe("build_123");
217
- expect(build.id).toBe("build_123");
218
- });
219
- });
220
-
221
- describe("Sandbox handle", () => {
222
- it("exec() posts to native /sandboxes/:id/exec", async () => {
223
- mockPost.mockResolvedValue({
224
- data: { stdout: "ok\n", stderr: "", exit_code: 0, duration_ms: 12 },
225
- });
226
-
227
- const sandbox = new Sandbox(makeHttp(), sandboxData());
228
- const result = await sandbox.exec("echo ok", {
229
- workingDir: "/workspace",
230
- timeoutSec: 5,
231
- });
232
-
233
- expect(mockPost).toHaveBeenCalledWith("/sandboxes/sbx_123/exec", {
234
- command: "echo ok",
235
- cwd: "/workspace",
236
- timeout: 5,
237
- });
238
- expect(result.stdout).toBe("ok\n");
239
- expect(result.exitCode).toBe(0);
240
- expect(result.exit_code).toBe(0);
241
- });
242
-
243
- it("commands.run() delegates to exec()", async () => {
244
- mockPost.mockResolvedValue({
245
- data: { stdout: "ok", stderr: "", exit_code: 0 },
246
- });
247
-
248
- const sandbox = new Sandbox(makeHttp(), sandboxData());
249
- await sandbox.commands.run("echo ok");
250
-
251
- expect(mockPost).toHaveBeenCalledWith("/sandboxes/sbx_123/exec", {
252
- command: "echo ok",
253
- });
254
- });
255
-
256
- it("exec.run() keeps callable exec parity", async () => {
257
- mockPost.mockResolvedValue({
258
- data: { stdout: "ok", stderr: "", exit_code: 0 },
259
- });
260
-
261
- const sandbox = new Sandbox(makeHttp(), sandboxData());
262
- await sandbox.exec.run("echo ok");
263
- await sandbox.exec("echo ok");
264
-
265
- expect(mockPost).toHaveBeenNthCalledWith(1, "/sandboxes/sbx_123/exec", {
266
- command: "echo ok",
267
- });
268
- expect(mockPost).toHaveBeenNthCalledWith(2, "/sandboxes/sbx_123/exec", {
269
- command: "echo ok",
270
- });
271
- });
272
-
273
- it("exec.stream() opens native sandbox exec SSE", () => {
274
- const iter = (async function* () {})();
275
- mockStream.mockReturnValue(iter);
276
-
277
- const sandbox = new Sandbox(makeHttp(), sandboxData());
278
- const stream = sandbox.exec.stream("pnpm test", {
279
- cwd: "/workspace",
280
- timeout: 30,
281
- });
282
-
283
- expect(stream).toBe(iter);
284
- expect(mockStream).toHaveBeenCalledWith("/sandboxes/sbx_123/exec/stream", {
285
- method: "POST",
286
- body: {
287
- command: "pnpm test",
288
- cwd: "/workspace",
289
- timeout: 30,
290
- },
291
- });
292
- });
293
-
294
- it("files.write() writes through native sandbox file API", async () => {
295
- mockPost.mockResolvedValue({});
296
-
297
- const sandbox = new Sandbox(makeHttp(), sandboxData());
298
- await sandbox.files.write("/workspace/app.txt", "hello");
299
-
300
- expect(mockPost).toHaveBeenCalledWith("/sandboxes/sbx_123/files", {
301
- path: "/workspace/app.txt",
302
- content: "aGVsbG8=",
303
- });
304
- });
305
-
306
- it("files.list() and files.stat() use native sandbox file endpoints", async () => {
307
- mockGet.mockResolvedValue({ data: { path: "/workspace", entries: [] } });
308
- mockPost.mockResolvedValue({
309
- data: { path: "/workspace/app.txt", size: 5 },
310
- });
311
-
312
- const sandbox = new Sandbox(makeHttp(), sandboxData());
313
- const files = await sandbox.files.list("/workspace");
314
- const stat = await sandbox.files.stat("/workspace/app.txt");
315
-
316
- expect(mockGet).toHaveBeenCalledWith("/sandboxes/sbx_123/files", {
317
- path: "/workspace",
318
- });
319
- expect(mockPost).toHaveBeenCalledWith("/sandboxes/sbx_123/files/stat", {
320
- path: "/workspace/app.txt",
321
- });
322
- expect(files.entries).toEqual([]);
323
- expect(stat.size).toBe(5);
324
- });
325
-
326
- it("preview.expose() returns native sandbox preview URL", async () => {
327
- mockPost.mockResolvedValue({
328
- data: { url: "https://5173-sbx.sandbox.miosa.app" },
329
- });
330
-
331
- const sandbox = new Sandbox(makeHttp(), sandboxData());
332
- const url = await sandbox.preview.expose(5173);
333
-
334
- expect(mockPost).toHaveBeenCalledWith("/sandboxes/sbx_123/expose", {
335
- port: 5173,
336
- });
337
- expect(url).toBe("https://5173-sbx.sandbox.miosa.app");
338
- });
339
-
340
- it("logs get/stream use native sandbox log endpoints", async () => {
341
- const iter = (async function* () {})();
342
- mockGet.mockResolvedValue({ data: { lines: ["ok"] } });
343
- mockStream.mockReturnValue(iter);
344
-
345
- const sandbox = new Sandbox(makeHttp(), sandboxData());
346
- const logs = await sandbox.logs.get(100);
347
- const stream = sandbox.logs.stream();
348
-
349
- expect(mockGet).toHaveBeenCalledWith("/sandboxes/sbx_123/logs", {
350
- lines: 100,
351
- });
352
- expect(mockStream).toHaveBeenCalledWith("/sandboxes/sbx_123/logs/stream");
353
- expect(logs).toEqual({ lines: ["ok"] });
354
- expect(stream).toBe(iter);
355
- });
356
-
357
- it("snapshots create/list/restore/delete use native sandbox snapshot endpoints", async () => {
358
- mockPost
359
- .mockResolvedValueOnce({ data: { id: "snap_1", sandbox_id: "sbx_123" } })
360
- .mockResolvedValueOnce({ data: sandboxData({ id: "sbx_restored" }) });
361
- mockGet.mockResolvedValue({
362
- data: [{ id: "snap_1", sandbox_id: "sbx_123" }],
363
- });
364
- mockDelete.mockResolvedValue(undefined);
365
-
366
- const sandbox = new Sandbox(makeHttp(), sandboxData());
367
- const created = await sandbox.snapshots.create("checkpoint");
368
- const snapshots = await sandbox.snapshots.list();
369
- const restored = await sandbox.snapshots.restore("snap_1");
370
- await sandbox.snapshots.delete("snap_1");
371
-
372
- expect(mockPost).toHaveBeenNthCalledWith(
373
- 1,
374
- "/sandboxes/sbx_123/snapshots",
375
- {
376
- comment: "checkpoint",
377
- },
378
- );
379
- expect(mockGet).toHaveBeenCalledWith("/sandboxes/sbx_123/snapshots");
380
- expect(mockPost).toHaveBeenNthCalledWith(
381
- 2,
382
- "/sandboxes/sbx_123/restore/snap_1",
383
- {},
384
- );
385
- expect(mockDelete).toHaveBeenCalledWith(
386
- "/sandboxes/sbx_123/snapshots/snap_1",
387
- );
388
- expect(created.id).toBe("snap_1");
389
- expect(snapshots[0]?.id).toBe("snap_1");
390
- expect(restored.id).toBe("sbx_restored");
391
- });
392
-
393
- it("pause/resume/deploy use native sandbox lifecycle endpoints", async () => {
394
- mockPost
395
- .mockResolvedValueOnce({ data: sandboxData({ state: "paused" }) })
396
- .mockResolvedValueOnce({ data: sandboxData({ state: "running" }) });
397
- mockRequest.mockResolvedValueOnce({
398
- data: { deployment_id: "dep_1", url: "https://app.miosa.app" },
399
- });
400
-
401
- const sandbox = new Sandbox(makeHttp(), sandboxData());
402
- await sandbox.pause();
403
- await sandbox.resume();
404
- const deployment = await sandbox.deploy({
405
- name: "site",
406
- sourcePath: "/workspace/dist",
407
- customDomain: "example.com",
408
- });
409
-
410
- expect(mockPost).toHaveBeenNthCalledWith(1, "/sandboxes/sbx_123/pause", {});
411
- expect(mockPost).toHaveBeenNthCalledWith(
412
- 2,
413
- "/sandboxes/sbx_123/resume",
414
- {},
415
- );
416
- expect(mockRequest).toHaveBeenCalledWith("/sandboxes/sbx_123/deploy", {
417
- method: "POST",
418
- body: {
419
- name: "site",
420
- output_path: "/workspace/dist",
421
- custom_domain: "example.com",
422
- },
423
- });
424
- expect(deployment.deployment_id).toBe("dep_1");
425
- });
426
-
427
- it("deployDocker marks sandbox deployment for Docker Deploy", async () => {
428
- mockRequest.mockResolvedValueOnce({
429
- data: {
430
- deployment_id: "dep_2",
431
- deployment_product: "docker_deploy",
432
- data: { deployment: { docker_deploy_host_id: "ddh_123" } },
433
- },
434
- });
435
-
436
- const sandbox = new Sandbox(makeHttp(), sandboxData());
437
- const deployment = await sandbox.deployDocker({
438
- name: "docker-site",
439
- port: 3000,
440
- });
441
-
442
- expect(mockRequest).toHaveBeenCalledWith("/sandboxes/sbx_123/deploy", {
443
- method: "POST",
444
- body: {
445
- name: "docker-site",
446
- port: 3000,
447
- deployment_type: "docker_deploy",
448
- },
449
- });
450
- expect(deployment.deployment_product).toBe("docker_deploy");
451
- });
452
-
453
- it("throws before operations when not running", async () => {
454
- const sandbox = new Sandbox(
455
- makeHttp(),
456
- sandboxData({ state: "provisioning" }),
457
- );
458
-
459
- await expect(sandbox.exec("echo no")).rejects.toBeInstanceOf(MiosaError);
460
- expect(mockPost).not.toHaveBeenCalled();
461
- });
462
-
463
- it("keeps the canonical sandbox surface off /computers", async () => {
464
- const paths: string[] = [];
465
- const iter = (async function* () {})();
466
- const http = {
467
- request: vi.fn(async (path: string) => {
468
- paths.push(path);
469
- return { data: sandboxData() };
470
- }),
471
- get: vi.fn(async (path: string) => {
472
- paths.push(path);
473
- if (path.endsWith("/files"))
474
- return { data: { path: "/workspace", entries: [] } };
475
- if (path.endsWith("/logs")) return { data: { lines: [] } };
476
- if (path.endsWith("/snapshots")) return { data: [] };
477
- if (path === "/sandbox-templates") return { data: [] };
478
- if (path.startsWith("/sandbox-templates/"))
479
- return { id: "miosa-sandbox" };
480
- return { data: sandboxData() };
481
- }),
482
- post: vi.fn(async (path: string) => {
483
- paths.push(path);
484
- if (path.endsWith("/exec")) {
485
- return { data: { stdout: "ok\n", stderr: "", exit_code: 0 } };
486
- }
487
- if (path.endsWith("/expose"))
488
- return { data: { url: "https://sbx.sandbox.miosa.app" } };
489
- if (path.endsWith("/artifacts")) return { data: { artifacts: {} } };
490
- if (path.endsWith("/files/stat"))
491
- return { data: { path: "/workspace/app.txt", size: 2 } };
492
- if (path.endsWith("/snapshots")) return { data: { id: "snap_1" } };
493
- if (path.includes("/restore/"))
494
- return { data: sandboxData({ id: "sbx_restored" }) };
495
- if (path.endsWith("/deploy"))
496
- return { data: { deployment_id: "dep_1" } };
497
- return { data: sandboxData() };
498
- }),
499
- delete: vi.fn(async (path: string) => {
500
- paths.push(path);
501
- }),
502
- getBinary: vi.fn(async (path: string) => {
503
- paths.push(path);
504
- return new TextEncoder().encode("ok");
505
- }),
506
- stream: vi.fn((path: string) => {
507
- paths.push(path);
508
- return iter;
509
- }),
510
- } as unknown as HttpClient;
511
-
512
- const client = new Sandboxes(http);
513
- await client.create();
514
- await client.list();
515
- await client.get("sbx_123");
516
- await client.connect("sbx_123");
517
- await client.delete("sbx_123");
518
- await client.listTemplates();
519
- await client.getTemplate("miosa-sandbox");
520
-
521
- const sandbox = new Sandbox(http, sandboxData());
522
- await sandbox.commands.run("echo ok");
523
- sandbox.commands.stream("echo ok");
524
- await sandbox.exec.run("echo ok");
525
- sandbox.exec.stream("echo ok");
526
- await sandbox.files.write("/workspace/app.txt", "ok");
527
- await sandbox.files.readText("/workspace/app.txt");
528
- await sandbox.files.list("/workspace");
529
- await sandbox.files.stat("/workspace/app.txt");
530
- await sandbox.preview.expose(5173);
531
- await sandbox.artifacts.list();
532
- await sandbox.logs.get(10);
533
- sandbox.logs.stream();
534
- await sandbox.snapshots.create("checkpoint");
535
- await sandbox.snapshots.list();
536
- await sandbox.snapshots.restore("snap_1");
537
- await sandbox.snapshots.delete("snap_1");
538
- await sandbox.pause();
539
- await sandbox.resume();
540
- await sandbox.deploy({ name: "site", sourcePath: "/workspace/dist" });
541
-
542
- expect(paths).not.toEqual(
543
- expect.arrayContaining([expect.stringContaining("/computers")]),
544
- );
545
- });
546
- });
547
-
548
- // ─── waitUntilReady() ──────────────────────────────────────────────────────
549
-
550
- function makeHttpForStream(): HttpClient {
551
- const http = makeHttp();
552
- // Public properties used by the SSE fetch fallback. Cast through unknown to
553
- // assign readonly fields on the mocked instance.
554
- (http as unknown as { baseUrl: string }).baseUrl =
555
- "https://api.miosa.test/api/v1";
556
- (http as unknown as { apiKey: string }).apiKey = "msk_u_test";
557
- return http;
558
- }
559
-
560
- function sseResponse(body: string): Response {
561
- const encoder = new TextEncoder();
562
- const stream = new ReadableStream<Uint8Array>({
563
- start(controller) {
564
- controller.enqueue(encoder.encode(body));
565
- controller.close();
566
- },
567
- });
568
- return new Response(stream, {
569
- status: 200,
570
- headers: { "content-type": "text/event-stream" },
571
- });
572
- }
573
-
574
- describe("Sandbox.waitUntilReady", () => {
575
- const originalFetch = globalThis.fetch;
576
- afterEach(() => {
577
- globalThis.fetch = originalFetch;
578
- });
579
-
580
- it("returns true when SSE emits event: ready", async () => {
581
- const fetchMock = vi
582
- .fn()
583
- .mockResolvedValue(
584
- sseResponse(
585
- ': keepalive\n\nevent: ready\ndata: {"ready_at":"2026-05-18T00:00:00Z"}\n\n',
586
- ),
587
- );
588
- globalThis.fetch = fetchMock as unknown as typeof fetch;
589
-
590
- const sandbox = new Sandbox(makeHttpForStream(), sandboxData());
591
- const result = await sandbox.waitUntilReady({ timeout: 5 });
592
-
593
- expect(result).toBe(true);
594
- expect(fetchMock).toHaveBeenCalledWith(
595
- "https://api.miosa.test/api/v1/sandboxes/sbx_123/readiness/stream",
596
- expect.objectContaining({
597
- method: "GET",
598
- headers: expect.objectContaining({
599
- Authorization: "Bearer msk_u_test",
600
- Accept: "text/event-stream",
601
- }),
602
- }),
603
- );
604
- expect(mockGet).not.toHaveBeenCalled();
605
- });
606
-
607
- it("returns false when SSE emits event: timeout", async () => {
608
- globalThis.fetch = vi
609
- .fn()
610
- .mockResolvedValue(
611
- sseResponse('event: timeout\ndata: {"reason":"not_ready"}\n\n'),
612
- ) as unknown as typeof fetch;
613
-
614
- const sandbox = new Sandbox(makeHttpForStream(), sandboxData());
615
- const result = await sandbox.waitUntilReady({ timeout: 5 });
616
-
617
- expect(result).toBe(false);
618
- });
619
-
620
- it("falls back to polling when SSE returns 404", async () => {
621
- globalThis.fetch = vi.fn().mockResolvedValue(
622
- new Response('{"error":"not implemented"}', {
623
- status: 404,
624
- headers: { "content-type": "application/json" },
625
- }),
626
- ) as unknown as typeof fetch;
627
-
628
- // First poll: not ready. Second poll: ready.
629
- mockGet
630
- .mockResolvedValueOnce({ data: { ready: false } })
631
- .mockResolvedValueOnce({ data: { ready: true } });
632
-
633
- const sandbox = new Sandbox(makeHttpForStream(), sandboxData());
634
- const result = await sandbox.waitUntilReady({ timeout: 2 });
635
-
636
- expect(result).toBe(true);
637
- expect(mockGet).toHaveBeenCalledWith("/sandboxes/sbx_123/readiness");
638
- expect(mockGet.mock.calls.length).toBeGreaterThanOrEqual(2);
639
- });
640
-
641
- it("stream:false skips SSE and only polls", async () => {
642
- const fetchMock = vi.fn();
643
- globalThis.fetch = fetchMock as unknown as typeof fetch;
644
- mockGet.mockResolvedValue({ data: { ready: true } });
645
-
646
- const sandbox = new Sandbox(makeHttpForStream(), sandboxData());
647
- const result = await sandbox.waitUntilReady({ timeout: 2, stream: false });
648
-
649
- expect(result).toBe(true);
650
- expect(fetchMock).not.toHaveBeenCalled();
651
- expect(mockGet).toHaveBeenCalledWith("/sandboxes/sbx_123/readiness");
652
- });
653
- });