@miosa/sdk 3.2.1 → 3.2.3

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/index.js CHANGED
@@ -9,18 +9,23 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
9
9
  });
10
10
 
11
11
  // src/errors.ts
12
+ function defaultRetryable(status) {
13
+ return status === 408 || status === 429 || status >= 500;
14
+ }
12
15
  var MiosaError = class _MiosaError extends Error {
13
16
  status;
14
17
  code;
15
18
  details;
16
19
  requestId;
17
- constructor(message, status, code, details, requestId) {
20
+ retryable;
21
+ constructor(message, status, code, details, requestId, retryable = defaultRetryable(status)) {
18
22
  super(message);
19
23
  this.name = "MiosaError";
20
24
  this.status = status;
21
25
  this.code = code;
22
26
  this.details = details;
23
27
  this.requestId = requestId;
28
+ this.retryable = retryable;
24
29
  }
25
30
  static fromResponse(status, body5, requestId) {
26
31
  const nested = typeof body5.error === "object" ? body5.error : void 0;
@@ -30,145 +35,146 @@ var MiosaError = class _MiosaError extends Error {
30
35
  const code = nested?.code ?? body5.code ?? (flatErrorIsCode ? flatError : "UNKNOWN_ERROR");
31
36
  const details = nested?.details ?? body5.details ?? (body5.detail || body5.reason ? { detail: body5.detail, reason: body5.reason } : void 0);
32
37
  requestId ??= body5.request_id;
33
- const connectError = connectErrorFromCode(code, message, status, details, requestId);
38
+ const retryable = nested?.retryable ?? body5.retryable ?? defaultRetryable(status);
39
+ const connectError = connectErrorFromCode(code, message, status, details, requestId, retryable);
34
40
  if (connectError) return connectError;
35
41
  if (status === 401 || status === 403) {
36
- return new AuthError(message, status, code, details, requestId);
42
+ return new AuthError(message, status, code, details, requestId, retryable);
37
43
  }
38
44
  if (status === 404) {
39
- return new NotFoundError(message, code, details, requestId);
45
+ return new NotFoundError(message, code, details, requestId, retryable);
40
46
  }
41
47
  if (status === 429) {
42
- return new RateLimitError(message, details, requestId);
48
+ return new RateLimitError(message, details, requestId, void 0, retryable);
43
49
  }
44
50
  if (status === 402) {
45
- return new InsufficientCreditsError(message, details, requestId);
51
+ return new InsufficientCreditsError(message, details, requestId, retryable);
46
52
  }
47
53
  if (status >= 400 && status < 500) {
48
- return new ValidationError(message, status, code, details, requestId);
54
+ return new ValidationError(message, status, code, details, requestId, retryable);
49
55
  }
50
- return new _MiosaError(message, status, code, details, requestId);
56
+ return new _MiosaError(message, status, code, details, requestId, retryable);
51
57
  }
52
58
  };
53
- function connectErrorFromCode(code, message, status, details, requestId) {
59
+ function connectErrorFromCode(code, message, status, details, requestId, retryable) {
54
60
  switch (code) {
55
61
  case "PROJECT_NOT_LINKED":
56
- return new ProjectNotLinkedError(message, status, details, requestId);
62
+ return new ProjectNotLinkedError(message, status, details, requestId, retryable);
57
63
  case "SUBJECT_NOT_ALLOWED":
58
- return new SubjectNotAllowedError(message, status, details, requestId);
64
+ return new SubjectNotAllowedError(message, status, details, requestId, retryable);
59
65
  case "SCOPE_NOT_ALLOWED":
60
- return new ScopeNotAllowedError(message, status, details, requestId);
66
+ return new ScopeNotAllowedError(message, status, details, requestId, retryable);
61
67
  case "MANAGED_PROVIDER_BINDING_ONLY":
62
- return new ManagedProviderBindingOnlyError(message, status, details, requestId);
68
+ return new ManagedProviderBindingOnlyError(message, status, details, requestId, retryable);
63
69
  case "INSTALLATION_REQUIRED":
64
- return new InstallationRequiredError(message, status, details, requestId);
70
+ return new InstallationRequiredError(message, status, details, requestId, retryable);
65
71
  case "USER_AUTHORIZATION_REQUIRED":
66
- return new UserAuthorizationRequiredError(message, status, details, requestId);
72
+ return new UserAuthorizationRequiredError(message, status, details, requestId, retryable);
67
73
  case "EGRESS_HOST_NOT_ALLOWED":
68
- return new EgressHostNotAllowedError(message, status, details, requestId);
74
+ return new EgressHostNotAllowedError(message, status, details, requestId, retryable);
69
75
  case "TOKEN_REFRESH_FAILED":
70
- return new TokenRefreshFailedError(message, status, details, requestId);
76
+ return new TokenRefreshFailedError(message, status, details, requestId, retryable);
71
77
  default:
72
78
  return void 0;
73
79
  }
74
80
  }
75
81
  var AuthError = class extends MiosaError {
76
- constructor(message, status = 401, code = "UNAUTHORIZED", details, requestId) {
77
- super(message, status, code, details, requestId);
82
+ constructor(message, status = 401, code = "UNAUTHORIZED", details, requestId, retryable) {
83
+ super(message, status, code, details, requestId, retryable);
78
84
  this.name = "AuthError";
79
85
  }
80
86
  };
81
87
  var NotFoundError = class extends MiosaError {
82
- constructor(message, code = "NOT_FOUND", details, requestId) {
83
- super(message, 404, code, details, requestId);
88
+ constructor(message, code = "NOT_FOUND", details, requestId, retryable) {
89
+ super(message, 404, code, details, requestId, retryable);
84
90
  this.name = "NotFoundError";
85
91
  }
86
92
  };
87
93
  var RateLimitError = class extends MiosaError {
88
94
  retryAfter;
89
- constructor(message, details, requestId, retryAfter) {
90
- super(message, 429, "RATE_LIMITED", details, requestId);
95
+ constructor(message, details, requestId, retryAfter, retryable) {
96
+ super(message, 429, "RATE_LIMITED", details, requestId, retryable);
91
97
  this.name = "RateLimitError";
92
98
  this.retryAfter = retryAfter;
93
99
  }
94
100
  };
95
101
  var InsufficientCreditsError = class extends MiosaError {
96
- constructor(message, details, requestId) {
97
- super(message, 402, "INSUFFICIENT_CREDITS", details, requestId);
102
+ constructor(message, details, requestId, retryable) {
103
+ super(message, 402, "INSUFFICIENT_CREDITS", details, requestId, retryable);
98
104
  this.name = "InsufficientCreditsError";
99
105
  }
100
106
  };
101
107
  var ValidationError = class extends MiosaError {
102
- constructor(message, status, code = "VALIDATION_ERROR", details, requestId) {
103
- super(message, status, code, details, requestId);
108
+ constructor(message, status, code = "VALIDATION_ERROR", details, requestId, retryable) {
109
+ super(message, status, code, details, requestId, retryable);
104
110
  this.name = "ValidationError";
105
111
  }
106
112
  };
107
113
  var TimeoutError = class extends MiosaError {
108
114
  constructor(message = "Request timed out") {
109
- super(message, 408, "TIMEOUT");
115
+ super(message, 408, "TIMEOUT", void 0, void 0, true);
110
116
  this.name = "TimeoutError";
111
117
  }
112
118
  };
113
119
  var NetworkError = class extends MiosaError {
114
120
  cause;
115
121
  constructor(message, cause) {
116
- super(message, 0, "NETWORK_ERROR");
122
+ super(message, 0, "NETWORK_ERROR", void 0, void 0, true);
117
123
  this.name = "NetworkError";
118
124
  this.cause = cause;
119
125
  }
120
126
  };
121
127
  var ProjectNotLinkedError = class extends MiosaError {
122
- constructor(message, status = 403, details, requestId) {
123
- super(message, status, "PROJECT_NOT_LINKED", details, requestId);
128
+ constructor(message, status = 403, details, requestId, retryable) {
129
+ super(message, status, "PROJECT_NOT_LINKED", details, requestId, retryable);
124
130
  this.name = "ProjectNotLinkedError";
125
131
  }
126
132
  };
127
133
  var SubjectNotAllowedError = class extends MiosaError {
128
- constructor(message, status = 403, details, requestId) {
129
- super(message, status, "SUBJECT_NOT_ALLOWED", details, requestId);
134
+ constructor(message, status = 403, details, requestId, retryable) {
135
+ super(message, status, "SUBJECT_NOT_ALLOWED", details, requestId, retryable);
130
136
  this.name = "SubjectNotAllowedError";
131
137
  }
132
138
  };
133
139
  var ScopeNotAllowedError = class extends MiosaError {
134
- constructor(message, status = 403, details, requestId) {
135
- super(message, status, "SCOPE_NOT_ALLOWED", details, requestId);
140
+ constructor(message, status = 403, details, requestId, retryable) {
141
+ super(message, status, "SCOPE_NOT_ALLOWED", details, requestId, retryable);
136
142
  this.name = "ScopeNotAllowedError";
137
143
  }
138
144
  };
139
145
  var ManagedProviderBindingOnlyError = class extends MiosaError {
140
- constructor(message, status = 403, details, requestId) {
141
- super(message, status, "MANAGED_PROVIDER_BINDING_ONLY", details, requestId);
146
+ constructor(message, status = 403, details, requestId, retryable) {
147
+ super(message, status, "MANAGED_PROVIDER_BINDING_ONLY", details, requestId, retryable);
142
148
  this.name = "ManagedProviderBindingOnlyError";
143
149
  }
144
150
  };
145
151
  var InstallationRequiredError = class extends MiosaError {
146
- constructor(message, status = 409, details, requestId) {
147
- super(message, status, "INSTALLATION_REQUIRED", details, requestId);
152
+ constructor(message, status = 409, details, requestId, retryable) {
153
+ super(message, status, "INSTALLATION_REQUIRED", details, requestId, retryable);
148
154
  this.name = "InstallationRequiredError";
149
155
  }
150
156
  };
151
157
  var UserAuthorizationRequiredError = class extends MiosaError {
152
- constructor(message, status = 403, details, requestId) {
153
- super(message, status, "USER_AUTHORIZATION_REQUIRED", details, requestId);
158
+ constructor(message, status = 403, details, requestId, retryable) {
159
+ super(message, status, "USER_AUTHORIZATION_REQUIRED", details, requestId, retryable);
154
160
  this.name = "UserAuthorizationRequiredError";
155
161
  }
156
162
  };
157
163
  var EgressHostNotAllowedError = class extends MiosaError {
158
- constructor(message, status = 403, details, requestId) {
159
- super(message, status, "EGRESS_HOST_NOT_ALLOWED", details, requestId);
164
+ constructor(message, status = 403, details, requestId, retryable) {
165
+ super(message, status, "EGRESS_HOST_NOT_ALLOWED", details, requestId, retryable);
160
166
  this.name = "EgressHostNotAllowedError";
161
167
  }
162
168
  };
163
169
  var TokenRefreshFailedError = class extends MiosaError {
164
- constructor(message, status = 502, details, requestId) {
165
- super(message, status, "TOKEN_REFRESH_FAILED", details, requestId);
170
+ constructor(message, status = 502, details, requestId, retryable) {
171
+ super(message, status, "TOKEN_REFRESH_FAILED", details, requestId, retryable);
166
172
  this.name = "TokenRefreshFailedError";
167
173
  }
168
174
  };
169
175
 
170
176
  // src/version.ts
171
- var SDK_VERSION = "3.2.1";
177
+ var SDK_VERSION = "3.2.3";
172
178
  var SDK_USER_AGENT = `@miosa/sdk/${SDK_VERSION}`;
173
179
 
174
180
  // src/http.ts
@@ -265,8 +271,12 @@ var HttpClient = class {
265
271
  fetchBody = JSON.stringify(body5);
266
272
  }
267
273
  let lastError;
268
- for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
274
+ const maxRetries = options.maxRetries ?? this.maxRetries;
275
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
276
+ options.signal?.throwIfAborted();
269
277
  const controller = new AbortController();
278
+ const cancel = () => controller.abort(options.signal?.reason);
279
+ options.signal?.addEventListener("abort", cancel, { once: true });
270
280
  const timer = setTimeout(() => controller.abort(), timeout);
271
281
  try {
272
282
  const response = await fetch(this.buildUrl(path), {
@@ -290,7 +300,7 @@ var HttpClient = class {
290
300
  errorBody,
291
301
  requestId
292
302
  );
293
- if (RETRY_STATUS.has(response.status) && attempt < this.maxRetries) {
303
+ if (RETRY_STATUS.has(response.status) && attempt < maxRetries) {
294
304
  lastError = error;
295
305
  await sleep(retryDelay(attempt, retryAfter));
296
306
  continue;
@@ -307,7 +317,7 @@ var HttpClient = class {
307
317
  if (options.rawResponse) return response;
308
318
  return await response.json();
309
319
  } catch (err) {
310
- clearTimeout(timer);
320
+ options.signal?.throwIfAborted();
311
321
  if (err instanceof MiosaError) {
312
322
  throw err;
313
323
  }
@@ -321,7 +331,7 @@ var HttpClient = class {
321
331
  `Network error reaching ${path}: ${err.message}`,
322
332
  err
323
333
  );
324
- if (attempt < this.maxRetries) {
334
+ if (attempt < maxRetries) {
325
335
  lastError = networkErr;
326
336
  await sleep(retryDelay(attempt, null));
327
337
  continue;
@@ -329,9 +339,19 @@ var HttpClient = class {
329
339
  throw networkErr;
330
340
  }
331
341
  throw err;
342
+ } finally {
343
+ clearTimeout(timer);
344
+ options.signal?.removeEventListener("abort", cancel);
332
345
  }
333
346
  }
334
- throw lastError ?? new MiosaError("Max retries exceeded", 0, "MAX_RETRIES_EXCEEDED");
347
+ throw lastError ?? new MiosaError(
348
+ "Max retries exceeded",
349
+ 0,
350
+ "MAX_RETRIES_EXCEEDED",
351
+ void 0,
352
+ void 0,
353
+ true
354
+ );
335
355
  }
336
356
  async get(path, query3) {
337
357
  let fullPath = path;
@@ -381,44 +401,52 @@ var HttpClient = class {
381
401
  headers = { ...headers, "Content-Type": "application/json" };
382
402
  body5 = JSON.stringify(options.body);
383
403
  }
404
+ options.signal?.throwIfAborted();
384
405
  const controller = new AbortController();
385
- let response;
406
+ const cancel = () => controller.abort(options.signal?.reason);
407
+ options.signal?.addEventListener("abort", cancel, { once: true });
408
+ const deadline = options.timeout === void 0 ? void 0 : new TimeoutError(
409
+ `Stream ${path} timed out after ${options.timeout}ms`
410
+ );
411
+ const timer = deadline === void 0 ? void 0 : setTimeout(() => controller.abort(deadline), options.timeout);
412
+ let reader;
386
413
  try {
387
- response = await fetch(`${this.baseUrl}${path}`, {
388
- method,
389
- headers,
390
- body: body5,
391
- signal: controller.signal
392
- });
393
- } catch (err) {
394
- if (err instanceof TypeError) {
395
- throw new NetworkError(
396
- `Network error opening SSE stream: ${err.message}`,
397
- err
414
+ let response;
415
+ try {
416
+ response = await fetch(this.buildUrl(path), {
417
+ method,
418
+ headers,
419
+ body: body5,
420
+ signal: controller.signal
421
+ });
422
+ } catch (err) {
423
+ if (err instanceof TypeError) {
424
+ throw new NetworkError(
425
+ `Network error opening SSE stream: ${err.message}`,
426
+ err
427
+ );
428
+ }
429
+ throw err;
430
+ }
431
+ if (!response.ok) {
432
+ let errorBody = {};
433
+ try {
434
+ errorBody = await response.json();
435
+ } catch {
436
+ }
437
+ throw MiosaError.fromResponse(
438
+ response.status,
439
+ errorBody,
440
+ response.headers.get("x-request-id") ?? void 0
398
441
  );
399
442
  }
400
- throw err;
401
- }
402
- if (!response.ok) {
403
- let errorBody = {};
404
- try {
405
- errorBody = await response.json();
406
- } catch {
443
+ if (!response.body) {
444
+ throw new MiosaError("SSE stream has no body", 0, "NO_STREAM_BODY");
407
445
  }
408
- throw MiosaError.fromResponse(
409
- response.status,
410
- errorBody,
411
- response.headers.get("x-request-id") ?? void 0
412
- );
413
- }
414
- if (!response.body) {
415
- throw new MiosaError("SSE stream has no body", 0, "NO_STREAM_BODY");
416
- }
417
- const reader = response.body.getReader();
418
- const decoder = new TextDecoder();
419
- let buffer = "";
420
- let event = null;
421
- try {
446
+ reader = response.body.getReader();
447
+ const decoder = new TextDecoder();
448
+ let buffer = "";
449
+ let event = null;
422
450
  while (true) {
423
451
  const { done, value } = await reader.read();
424
452
  if (done) break;
@@ -446,9 +474,17 @@ var HttpClient = class {
446
474
  }
447
475
  }
448
476
  }
477
+ } catch (err) {
478
+ options.signal?.throwIfAborted();
479
+ if (deadline !== void 0 && controller.signal.reason === deadline) {
480
+ throw deadline;
481
+ }
482
+ throw err;
449
483
  } finally {
484
+ clearTimeout(timer);
485
+ options.signal?.removeEventListener("abort", cancel);
450
486
  controller.abort();
451
- reader.releaseLock();
487
+ reader?.releaseLock();
452
488
  }
453
489
  }
454
490
  /**
@@ -8615,6 +8651,16 @@ function raiseUnresolvableShape(cpuCount, memoryMb, tier) {
8615
8651
  `Unsupported cpu/memory combination (${supplied}). Supported tiers: xs (1/2048), small (2/4096), medium (4/8192), large (8/16384), xl (16/32768). Pass a matching cpuCount + memoryMb (any diskSizeMb is allowed), use size, or supply cpuCount, memoryMb, and diskSizeMb together for a custom shape.`
8616
8652
  );
8617
8653
  }
8654
+ function execDeadline(options) {
8655
+ const seconds = options?.timeout ?? options?.timeoutSec ?? options?.timeout_sec;
8656
+ if (seconds === void 0) return {};
8657
+ if (!Number.isFinite(seconds) || seconds <= 0 || seconds > 86400) {
8658
+ throw new RangeError(
8659
+ "Exec timeout must be greater than zero and at most 86400 seconds"
8660
+ );
8661
+ }
8662
+ return { timeout: seconds * 1e3 + 5e3 };
8663
+ }
8618
8664
  function execBody(command, options = {}) {
8619
8665
  return stripUndefined26({
8620
8666
  command,
@@ -8784,7 +8830,7 @@ var SandboxTerminal = class {
8784
8830
  const response = unwrap48(
8785
8831
  await this.sandbox.http.post(`/sandboxes/${this.sandbox.id}/terminal`, body5)
8786
8832
  );
8787
- return response;
8833
+ return normalizeTerminalSession(response);
8788
8834
  }
8789
8835
  async delete(sessionId) {
8790
8836
  await this.sandbox.http.delete(
@@ -8792,6 +8838,35 @@ var SandboxTerminal = class {
8792
8838
  );
8793
8839
  }
8794
8840
  };
8841
+ function normalizeTerminalSession(payload) {
8842
+ const sessionId = payload.session_id;
8843
+ const wsUrl = payload.ws_url;
8844
+ const streamAuth = payload.stream_auth;
8845
+ const streamAuthExpiresAt = payload.stream_auth_expires_at;
8846
+ const expiresAt = payload.expires_at;
8847
+ if (typeof sessionId !== "string" || typeof wsUrl !== "string" || typeof streamAuth !== "string" || typeof streamAuthExpiresAt !== "number" || typeof expiresAt !== "number") {
8848
+ throw new MiosaError(
8849
+ "Terminal create response did not contain a valid miosa-terminal-v1 session",
8850
+ 502,
8851
+ "INVALID_TERMINAL_SESSION",
8852
+ { received: Object.keys(payload) },
8853
+ void 0,
8854
+ false
8855
+ );
8856
+ }
8857
+ return {
8858
+ sessionId,
8859
+ wsUrl,
8860
+ streamAuth,
8861
+ streamAuthExpiresAt,
8862
+ expiresAt,
8863
+ session_id: sessionId,
8864
+ ws_url: wsUrl,
8865
+ stream_auth: streamAuth,
8866
+ stream_auth_expires_at: streamAuthExpiresAt,
8867
+ expires_at: expiresAt
8868
+ };
8869
+ }
8795
8870
  var SandboxEvents = class {
8796
8871
  constructor(sandbox) {
8797
8872
  this.sandbox = sandbox;
@@ -9045,10 +9120,17 @@ var Sandbox = class _Sandbox {
9045
9120
  }
9046
9121
  async runExec(command, options) {
9047
9122
  this.assertRunning("exec");
9123
+ if (options?.signal) return this.runCancellableExec(command, options);
9048
9124
  const response = unwrap48(
9049
- await this.http.post(
9125
+ await this.http.request(
9050
9126
  `/sandboxes/${this.id}/exec`,
9051
- execBody(command, options)
9127
+ {
9128
+ method: "POST",
9129
+ body: execBody(command, options),
9130
+ signal: options?.signal,
9131
+ maxRetries: 0,
9132
+ ...execDeadline(options)
9133
+ }
9052
9134
  )
9053
9135
  );
9054
9136
  const exitCode = Number(response.exit_code ?? response.exitCode ?? 0);
@@ -9064,13 +9146,42 @@ var Sandbox = class _Sandbox {
9064
9146
  }
9065
9147
  return result;
9066
9148
  }
9149
+ async runCancellableExec(command, options) {
9150
+ let stdout = "";
9151
+ let stderr = "";
9152
+ let exitCode;
9153
+ for await (const event of this.execStream(command, options)) {
9154
+ if (event.type === "stdout" && typeof event.line === "string")
9155
+ stdout += event.line;
9156
+ if (event.type === "stderr" && typeof event.line === "string")
9157
+ stderr += event.line;
9158
+ if (event.type === "exit" && typeof event.exit_code === "number") {
9159
+ exitCode = event.exit_code;
9160
+ break;
9161
+ }
9162
+ }
9163
+ options.signal?.throwIfAborted();
9164
+ if (exitCode === void 0) {
9165
+ throw new MiosaError(
9166
+ "Execution stream ended without a terminal result",
9167
+ 502,
9168
+ "EXEC_STREAM_INCOMPLETE",
9169
+ void 0,
9170
+ void 0,
9171
+ false
9172
+ );
9173
+ }
9174
+ return { stdout, stderr, exitCode, exit_code: exitCode };
9175
+ }
9067
9176
  execStream(command, options) {
9068
9177
  this.assertRunning("exec.stream");
9069
9178
  const frames = this.http.streamFrames(
9070
9179
  `/sandboxes/${this.id}/exec/stream`,
9071
9180
  {
9072
9181
  method: "POST",
9073
- body: execBody(command, options)
9182
+ body: execBody(command, options),
9183
+ signal: options?.signal,
9184
+ ...execDeadline(options)
9074
9185
  }
9075
9186
  );
9076
9187
  return (async function* () {
@@ -9520,7 +9631,9 @@ var Sandbox = class _Sandbox {
9520
9631
  `Sandbox ${this.id} entered terminal state '${state}' while waiting for readiness`,
9521
9632
  502,
9522
9633
  "SANDBOX_BOOT_FAILED",
9523
- { sandbox_id: this.id, state }
9634
+ { sandbox_id: this.id, state },
9635
+ void 0,
9636
+ false
9524
9637
  );
9525
9638
  }
9526
9639
  /** Best-effort final check so an SSE `event: timeout` does not mask a boot failure that just landed. */
@@ -9612,12 +9725,21 @@ var Sandbox = class _Sandbox {
9612
9725
  }
9613
9726
  }
9614
9727
  async destroy() {
9615
- if (this.data.state === "destroyed") return;
9616
- await this.http.delete(`/sandboxes/${this.id}`);
9728
+ if (this.data.state === "destroyed" || this.data.state === "destroying") {
9729
+ return;
9730
+ }
9731
+ const receipt = unwrap48(
9732
+ await this.http.delete(
9733
+ `/sandboxes/${this.id}`
9734
+ )
9735
+ ) ?? {};
9736
+ const settled = receipt.state === "destroyed" || receipt.state === void 0 && receipt.operation_id == null;
9617
9737
  this.data = {
9618
9738
  ...this.data,
9619
- state: "destroyed",
9620
- destroyed_at: (/* @__PURE__ */ new Date()).toISOString()
9739
+ state: settled ? "destroyed" : "destroying",
9740
+ ready: false,
9741
+ operation_id: receipt.operation_id ?? this.data.operation_id ?? null,
9742
+ destroyed_at: receipt.destroyed_at ?? this.data.destroyed_at ?? null
9621
9743
  };
9622
9744
  }
9623
9745
  delete() {
@@ -9754,7 +9876,25 @@ var Sandboxes = class {
9754
9876
  await sandbox.resume();
9755
9877
  }
9756
9878
  if (params.waitUntilReady === true) {
9757
- await sandbox.waitUntilReady({ timeout: params.waitTimeoutSec ?? 60 });
9879
+ const ready = await sandbox.waitUntilReady({
9880
+ timeout: params.waitTimeoutSec ?? 60
9881
+ });
9882
+ if (!ready) {
9883
+ throw new MiosaError(
9884
+ `Sandbox ${sandbox.id} did not become ready before the timeout`,
9885
+ 504,
9886
+ "SANDBOX_READY_TIMEOUT"
9887
+ );
9888
+ }
9889
+ await sandbox.refresh();
9890
+ const executable = sandbox.state === "running" && (sandbox.ready || isCommandReady(await sandbox.readiness()));
9891
+ if (!executable) {
9892
+ throw new MiosaError(
9893
+ `Sandbox ${sandbox.id} reported readiness but is not executable`,
9894
+ 409,
9895
+ "SANDBOX_NOT_READY"
9896
+ );
9897
+ }
9758
9898
  }
9759
9899
  return sandbox;
9760
9900
  }