@miosa/sdk 3.2.3 → 3.2.5
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.d.ts +107 -22
- package/dist/index.js +218 -75
- package/dist/index.js.map +1 -1
- package/package.json +12 -13
package/dist/index.js
CHANGED
|
@@ -18,7 +18,15 @@ var MiosaError = class _MiosaError extends Error {
|
|
|
18
18
|
details;
|
|
19
19
|
requestId;
|
|
20
20
|
retryable;
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Server-supplied retry hint in milliseconds, taken from the canonical
|
|
23
|
+
* error shape's `retry_after_ms` (nested under `error` or top-level) or,
|
|
24
|
+
* failing that, the HTTP `Retry-After` header. `undefined` when the
|
|
25
|
+
* response carried neither, in which case a caller should fall back to
|
|
26
|
+
* its own backoff policy.
|
|
27
|
+
*/
|
|
28
|
+
retryAfterMs;
|
|
29
|
+
constructor(message, status, code, details, requestId, retryable = defaultRetryable(status), retryAfterMs) {
|
|
22
30
|
super(message);
|
|
23
31
|
this.name = "MiosaError";
|
|
24
32
|
this.status = status;
|
|
@@ -26,34 +34,33 @@ var MiosaError = class _MiosaError extends Error {
|
|
|
26
34
|
this.details = details;
|
|
27
35
|
this.requestId = requestId;
|
|
28
36
|
this.retryable = retryable;
|
|
37
|
+
this.retryAfterMs = retryAfterMs;
|
|
29
38
|
}
|
|
30
|
-
|
|
39
|
+
/**
|
|
40
|
+
* @param retryAfterHeaderMs The `Retry-After` header value, already
|
|
41
|
+
* converted to milliseconds by the caller. Used only when the body does
|
|
42
|
+
* not carry the more precise `retry_after_ms`.
|
|
43
|
+
*/
|
|
44
|
+
static fromResponse(status, body5, requestId, retryAfterHeaderMs2) {
|
|
31
45
|
const nested = typeof body5.error === "object" ? body5.error : void 0;
|
|
32
46
|
const flatError = typeof body5.error === "string" ? body5.error : void 0;
|
|
33
47
|
const flatErrorIsCode = !!flatError && /^[A-Z][A-Z0-9_]+$/.test(flatError);
|
|
34
48
|
const message = nested?.message ?? body5.message ?? body5.detail ?? body5.reason ?? flatError ?? `HTTP ${status}`;
|
|
35
49
|
const code = nested?.code ?? body5.code ?? (flatErrorIsCode ? flatError : "UNKNOWN_ERROR");
|
|
36
50
|
const details = nested?.details ?? body5.details ?? (body5.detail || body5.reason ? { detail: body5.detail, reason: body5.reason } : void 0);
|
|
37
|
-
requestId ??= body5.request_id;
|
|
51
|
+
requestId ??= nested?.request_id ?? body5.request_id;
|
|
38
52
|
const retryable = nested?.retryable ?? body5.retryable ?? defaultRetryable(status);
|
|
53
|
+
const retryAfterMs = nested?.retry_after_ms ?? body5.retry_after_ms ?? retryAfterHeaderMs2;
|
|
39
54
|
const connectError = connectErrorFromCode(code, message, status, details, requestId, retryable);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
if (status === 402) {
|
|
51
|
-
return new InsufficientCreditsError(message, details, requestId, retryable);
|
|
52
|
-
}
|
|
53
|
-
if (status >= 400 && status < 500) {
|
|
54
|
-
return new ValidationError(message, status, code, details, requestId, retryable);
|
|
55
|
-
}
|
|
56
|
-
return new _MiosaError(message, status, code, details, requestId, retryable);
|
|
55
|
+
const built = connectError ?? (status === 401 || status === 403 ? new AuthError(message, status, code, details, requestId, retryable) : status === 404 ? new NotFoundError(message, code, details, requestId, retryable) : status === 429 ? new RateLimitError(
|
|
56
|
+
message,
|
|
57
|
+
details,
|
|
58
|
+
requestId,
|
|
59
|
+
retryAfterMs === void 0 ? void 0 : retryAfterMs / 1e3,
|
|
60
|
+
retryable
|
|
61
|
+
) : status === 402 ? new InsufficientCreditsError(message, details, requestId, retryable) : status >= 400 && status < 500 ? new ValidationError(message, status, code, details, requestId, retryable) : new _MiosaError(message, status, code, details, requestId, retryable));
|
|
62
|
+
built.retryAfterMs = retryAfterMs;
|
|
63
|
+
return built;
|
|
57
64
|
}
|
|
58
65
|
};
|
|
59
66
|
function connectErrorFromCode(code, message, status, details, requestId, retryable) {
|
|
@@ -174,7 +181,7 @@ var TokenRefreshFailedError = class extends MiosaError {
|
|
|
174
181
|
};
|
|
175
182
|
|
|
176
183
|
// src/version.ts
|
|
177
|
-
var SDK_VERSION = "3.2.
|
|
184
|
+
var SDK_VERSION = "3.2.5";
|
|
178
185
|
var SDK_USER_AGENT = `@miosa/sdk/${SDK_VERSION}`;
|
|
179
186
|
|
|
180
187
|
// src/http.ts
|
|
@@ -206,15 +213,18 @@ async function ensureHttp2Agent() {
|
|
|
206
213
|
ensureHttp2Agent();
|
|
207
214
|
var DEFAULT_TIMEOUT = 3e4;
|
|
208
215
|
var DEFAULT_MAX_RETRIES = 3;
|
|
209
|
-
var RETRY_STATUS = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
210
216
|
var BASE_DELAY_MS = 500;
|
|
211
217
|
function sleep(ms) {
|
|
212
218
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
213
219
|
}
|
|
214
|
-
function
|
|
215
|
-
if (retryAfterHeader
|
|
216
|
-
|
|
217
|
-
|
|
220
|
+
function retryAfterHeaderMs(retryAfterHeader) {
|
|
221
|
+
if (retryAfterHeader === null) return void 0;
|
|
222
|
+
const seconds = parseInt(retryAfterHeader, 10);
|
|
223
|
+
return isNaN(seconds) ? void 0 : seconds * 1e3;
|
|
224
|
+
}
|
|
225
|
+
function retryDelay(attempt, hintMs) {
|
|
226
|
+
if (hintMs !== void 0 && Number.isFinite(hintMs) && hintMs >= 0) {
|
|
227
|
+
return hintMs;
|
|
218
228
|
}
|
|
219
229
|
return BASE_DELAY_MS * 2 ** attempt + Math.random() * 100;
|
|
220
230
|
}
|
|
@@ -289,7 +299,9 @@ var HttpClient = class {
|
|
|
289
299
|
clearTimeout(timer);
|
|
290
300
|
if (!response.ok) {
|
|
291
301
|
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
292
|
-
const
|
|
302
|
+
const retryAfterMs = retryAfterHeaderMs(
|
|
303
|
+
response.headers.get("retry-after")
|
|
304
|
+
);
|
|
293
305
|
let errorBody = {};
|
|
294
306
|
try {
|
|
295
307
|
errorBody = await response.json();
|
|
@@ -298,11 +310,12 @@ var HttpClient = class {
|
|
|
298
310
|
const error = MiosaError.fromResponse(
|
|
299
311
|
response.status,
|
|
300
312
|
errorBody,
|
|
301
|
-
requestId
|
|
313
|
+
requestId,
|
|
314
|
+
retryAfterMs
|
|
302
315
|
);
|
|
303
|
-
if (
|
|
316
|
+
if (error.retryable && attempt < maxRetries) {
|
|
304
317
|
lastError = error;
|
|
305
|
-
await sleep(retryDelay(attempt,
|
|
318
|
+
await sleep(retryDelay(attempt, error.retryAfterMs));
|
|
306
319
|
continue;
|
|
307
320
|
}
|
|
308
321
|
throw error;
|
|
@@ -333,7 +346,7 @@ var HttpClient = class {
|
|
|
333
346
|
);
|
|
334
347
|
if (attempt < maxRetries) {
|
|
335
348
|
lastError = networkErr;
|
|
336
|
-
await sleep(retryDelay(attempt,
|
|
349
|
+
await sleep(retryDelay(attempt, void 0));
|
|
337
350
|
continue;
|
|
338
351
|
}
|
|
339
352
|
throw networkErr;
|
|
@@ -437,7 +450,8 @@ var HttpClient = class {
|
|
|
437
450
|
throw MiosaError.fromResponse(
|
|
438
451
|
response.status,
|
|
439
452
|
errorBody,
|
|
440
|
-
response.headers.get("x-request-id") ?? void 0
|
|
453
|
+
response.headers.get("x-request-id") ?? void 0,
|
|
454
|
+
retryAfterHeaderMs(response.headers.get("retry-after"))
|
|
441
455
|
);
|
|
442
456
|
}
|
|
443
457
|
if (!response.body) {
|
|
@@ -8517,6 +8531,7 @@ function isCommandReady(payload) {
|
|
|
8517
8531
|
return status === "ready";
|
|
8518
8532
|
});
|
|
8519
8533
|
}
|
|
8534
|
+
var READINESS_STREAM_MAX_TIMEOUT_SEC = 120;
|
|
8520
8535
|
var SANDBOX_TEMPLATE = "miosa-sandbox";
|
|
8521
8536
|
var SANDBOX_TIER_BY_CPU = {
|
|
8522
8537
|
1: { size: "xs", memoryMb: 2048 },
|
|
@@ -8651,16 +8666,29 @@ function raiseUnresolvableShape(cpuCount, memoryMb, tier) {
|
|
|
8651
8666
|
`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.`
|
|
8652
8667
|
);
|
|
8653
8668
|
}
|
|
8654
|
-
function
|
|
8669
|
+
function validatedExecSeconds(options) {
|
|
8655
8670
|
const seconds = options?.timeout ?? options?.timeoutSec ?? options?.timeout_sec;
|
|
8656
|
-
if (seconds === void 0) return
|
|
8671
|
+
if (seconds === void 0) return void 0;
|
|
8657
8672
|
if (!Number.isFinite(seconds) || seconds <= 0 || seconds > 86400) {
|
|
8658
8673
|
throw new RangeError(
|
|
8659
8674
|
"Exec timeout must be greater than zero and at most 86400 seconds"
|
|
8660
8675
|
);
|
|
8661
8676
|
}
|
|
8677
|
+
return seconds;
|
|
8678
|
+
}
|
|
8679
|
+
function execDeadline(options) {
|
|
8680
|
+
const seconds = validatedExecSeconds(options);
|
|
8681
|
+
if (seconds === void 0) return {};
|
|
8662
8682
|
return { timeout: seconds * 1e3 + 5e3 };
|
|
8663
8683
|
}
|
|
8684
|
+
var DEFAULT_EXEC_WAKE_BUDGET_SEC = 60;
|
|
8685
|
+
function execStreamDeadline(options) {
|
|
8686
|
+
const seconds = validatedExecSeconds(options);
|
|
8687
|
+
if (seconds === void 0) return {};
|
|
8688
|
+
const wakeSeconds = options?.wakeTimeoutSec ?? options?.wake_timeout_sec;
|
|
8689
|
+
const wakeBudgetSec = wakeSeconds !== void 0 ? Math.max(0, wakeSeconds) : DEFAULT_EXEC_WAKE_BUDGET_SEC;
|
|
8690
|
+
return { timeout: (seconds + wakeBudgetSec) * 1e3 + 5e3 };
|
|
8691
|
+
}
|
|
8664
8692
|
function execBody(command, options = {}) {
|
|
8665
8693
|
return stripUndefined26({
|
|
8666
8694
|
command,
|
|
@@ -8676,7 +8704,14 @@ function normalizeExecEvent(event, payload) {
|
|
|
8676
8704
|
const code = Number(
|
|
8677
8705
|
record.exit_code ?? record.exitCode ?? (typeof payload === "number" ? payload : 0)
|
|
8678
8706
|
);
|
|
8679
|
-
|
|
8707
|
+
const timedOutRaw = record.timed_out ?? record.timedOut;
|
|
8708
|
+
const timedOut = typeof timedOutRaw === "boolean" ? timedOutRaw : void 0;
|
|
8709
|
+
return {
|
|
8710
|
+
type: "exit",
|
|
8711
|
+
exit_code: code,
|
|
8712
|
+
exitCode: code,
|
|
8713
|
+
...timedOut !== void 0 ? { timed_out: timedOut, timedOut } : {}
|
|
8714
|
+
};
|
|
8680
8715
|
}
|
|
8681
8716
|
const type = event === "stderr" ? "stderr" : "stdout";
|
|
8682
8717
|
const data = typeof payload === "string" ? payload : String(record.line ?? record.data ?? "");
|
|
@@ -9144,12 +9179,18 @@ var Sandbox = class _Sandbox {
|
|
|
9144
9179
|
result.durationMs = response.duration_ms;
|
|
9145
9180
|
result.duration_ms = response.duration_ms;
|
|
9146
9181
|
}
|
|
9182
|
+
const timedOutRaw = response.timed_out ?? response.timedOut;
|
|
9183
|
+
if (typeof timedOutRaw === "boolean") {
|
|
9184
|
+
result.timedOut = timedOutRaw;
|
|
9185
|
+
result.timed_out = timedOutRaw;
|
|
9186
|
+
}
|
|
9147
9187
|
return result;
|
|
9148
9188
|
}
|
|
9149
9189
|
async runCancellableExec(command, options) {
|
|
9150
9190
|
let stdout = "";
|
|
9151
9191
|
let stderr = "";
|
|
9152
9192
|
let exitCode;
|
|
9193
|
+
let timedOut;
|
|
9153
9194
|
for await (const event of this.execStream(command, options)) {
|
|
9154
9195
|
if (event.type === "stdout" && typeof event.line === "string")
|
|
9155
9196
|
stdout += event.line;
|
|
@@ -9157,6 +9198,7 @@ var Sandbox = class _Sandbox {
|
|
|
9157
9198
|
stderr += event.line;
|
|
9158
9199
|
if (event.type === "exit" && typeof event.exit_code === "number") {
|
|
9159
9200
|
exitCode = event.exit_code;
|
|
9201
|
+
timedOut = event.timed_out;
|
|
9160
9202
|
break;
|
|
9161
9203
|
}
|
|
9162
9204
|
}
|
|
@@ -9171,7 +9213,13 @@ var Sandbox = class _Sandbox {
|
|
|
9171
9213
|
false
|
|
9172
9214
|
);
|
|
9173
9215
|
}
|
|
9174
|
-
return {
|
|
9216
|
+
return {
|
|
9217
|
+
stdout,
|
|
9218
|
+
stderr,
|
|
9219
|
+
exitCode,
|
|
9220
|
+
exit_code: exitCode,
|
|
9221
|
+
...timedOut !== void 0 ? { timedOut, timed_out: timedOut } : {}
|
|
9222
|
+
};
|
|
9175
9223
|
}
|
|
9176
9224
|
execStream(command, options) {
|
|
9177
9225
|
this.assertRunning("exec.stream");
|
|
@@ -9181,7 +9229,7 @@ var Sandbox = class _Sandbox {
|
|
|
9181
9229
|
method: "POST",
|
|
9182
9230
|
body: execBody(command, options),
|
|
9183
9231
|
signal: options?.signal,
|
|
9184
|
-
...
|
|
9232
|
+
...execStreamDeadline(options)
|
|
9185
9233
|
}
|
|
9186
9234
|
);
|
|
9187
9235
|
return (async function* () {
|
|
@@ -9455,17 +9503,26 @@ var Sandbox = class _Sandbox {
|
|
|
9455
9503
|
this.data = { ...this.data, ...data };
|
|
9456
9504
|
return this;
|
|
9457
9505
|
}
|
|
9506
|
+
/**
|
|
9507
|
+
* Resume a paused (or persistent-stopped) sandbox.
|
|
9508
|
+
*
|
|
9509
|
+
* Always sends an `Idempotency-Key`, generating a fresh one when the
|
|
9510
|
+
* caller doesn't supply one - same rationale as {@link generateIdempotencyKey}
|
|
9511
|
+
* for create(): the key is built once here and threaded through a single
|
|
9512
|
+
* `HttpClient.request` call, whose headers are fixed before its retry loop
|
|
9513
|
+
* runs, so every retry of *this* resume replays the same key. Without this,
|
|
9514
|
+
* a client-side retry of a slow resume could dispatch a second full resume
|
|
9515
|
+
* execution (re-running host reservation) instead of deduping server-side.
|
|
9516
|
+
*/
|
|
9458
9517
|
async resume(idempotencyKey11) {
|
|
9459
|
-
const
|
|
9518
|
+
const key = idempotencyKey11 ?? generateIdempotencyKey();
|
|
9519
|
+
const response = await this.http.request(
|
|
9460
9520
|
`/sandboxes/${this.id}/resume`,
|
|
9461
9521
|
{
|
|
9462
9522
|
method: "POST",
|
|
9463
9523
|
body: {},
|
|
9464
|
-
headers: { "Idempotency-Key":
|
|
9524
|
+
headers: { "Idempotency-Key": key }
|
|
9465
9525
|
}
|
|
9466
|
-
) : await this.http.post(
|
|
9467
|
-
`/sandboxes/${this.id}/resume`,
|
|
9468
|
-
{}
|
|
9469
9526
|
);
|
|
9470
9527
|
const data = unwrap48(response);
|
|
9471
9528
|
this.data = { ...this.data, ...data };
|
|
@@ -9565,47 +9622,66 @@ var Sandbox = class _Sandbox {
|
|
|
9565
9622
|
);
|
|
9566
9623
|
}
|
|
9567
9624
|
/**
|
|
9568
|
-
* Block until the sandbox reports ready, or *timeout* seconds
|
|
9625
|
+
* Block until the sandbox reports ready, or the caller's *timeout* seconds
|
|
9626
|
+
* (the full deadline budget) elapse.
|
|
9569
9627
|
*
|
|
9570
|
-
* When `stream` is `true` (the default) this opens an SSE connection
|
|
9571
|
-
*
|
|
9572
|
-
* `event: ready` frame.
|
|
9573
|
-
*
|
|
9574
|
-
*
|
|
9628
|
+
* When `stream` is `true` (the default) this opens an SSE connection to
|
|
9629
|
+
* `GET /sandboxes/:id/readiness/stream?timeout=<window>` and waits for an
|
|
9630
|
+
* `event: ready` frame. `<window>` is the caller's remaining budget clamped
|
|
9631
|
+
* to {@link READINESS_STREAM_MAX_TIMEOUT_SEC}, the server's own maximum
|
|
9632
|
+
* lease for a single stream connection — a long-restoring persistent
|
|
9633
|
+
* sandbox (e.g. Docker restore, 60-90 s) legitimately outlives one lease.
|
|
9634
|
+
* The server emits `ready` immediately if the sandbox is already ready,
|
|
9635
|
+
* `event: timeout` when *that connection's* window elapses without a
|
|
9636
|
+
* verdict, and `event: error` on a terminal boot failure.
|
|
9575
9637
|
*
|
|
9576
|
-
*
|
|
9577
|
-
*
|
|
9638
|
+
* An `event: timeout` (or the stream closing without a verdict) is NOT
|
|
9639
|
+
* treated as the final answer while the caller's overall deadline still has
|
|
9640
|
+
* budget left — that would silently truncate a 180 s wait down to the
|
|
9641
|
+
* server's lease window and return `false` while the sandbox was still
|
|
9642
|
+
* mid-restore. Instead this reconnects the stream (or falls back to
|
|
9643
|
+
* polling) for the remaining budget. `false` is only returned once the
|
|
9644
|
+
* caller's full deadline has elapsed.
|
|
9578
9645
|
*
|
|
9579
9646
|
* Throws {@link MiosaError} with code `SANDBOX_BOOT_FAILED` as soon as the
|
|
9580
9647
|
* sandbox is observed in a terminal failure state (`error` or `destroyed`)
|
|
9581
9648
|
* while waiting, instead of silently exhausting the timeout and returning
|
|
9582
9649
|
* `false` — a permanent boot failure and "still booting, keep waiting" must
|
|
9583
|
-
* never look the same to the caller.
|
|
9584
|
-
*
|
|
9585
|
-
*
|
|
9586
|
-
* authoritative {@link readiness} check before giving up, to catch a
|
|
9587
|
-
* failure that landed right at the boundary.
|
|
9650
|
+
* never look the same to the caller. On each `event: timeout` boundary this
|
|
9651
|
+
* also makes one authoritative {@link readiness} check before deciding to
|
|
9652
|
+
* reconnect or stop, to catch a failure that landed right at the boundary.
|
|
9588
9653
|
*
|
|
9589
9654
|
* If the SSE endpoint returns 404 (server pre-dates the streaming
|
|
9590
9655
|
* endpoint) this transparently falls back to polling {@link readiness}
|
|
9591
|
-
* every 10 ms until ready,
|
|
9656
|
+
* every 10 ms until ready, the caller's deadline elapses, or terminal
|
|
9657
|
+
* failure.
|
|
9592
9658
|
*/
|
|
9593
9659
|
async waitUntilReady(options = {}) {
|
|
9594
9660
|
const timeout = options.timeout ?? 30;
|
|
9595
9661
|
const stream = options.stream ?? true;
|
|
9596
9662
|
const requireCommandReady = options.requireCommandReady ?? true;
|
|
9663
|
+
const deadlineMs = Date.now() + timeout * 1e3;
|
|
9597
9664
|
if (stream) {
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
await this.
|
|
9605
|
-
|
|
9665
|
+
while (true) {
|
|
9666
|
+
const remainingMs = deadlineMs - Date.now();
|
|
9667
|
+
if (remainingMs <= 0) return false;
|
|
9668
|
+
const remainingSec = remainingMs / 1e3;
|
|
9669
|
+
const clamped = remainingSec > READINESS_STREAM_MAX_TIMEOUT_SEC;
|
|
9670
|
+
const windowSec = clamped ? READINESS_STREAM_MAX_TIMEOUT_SEC : remainingSec;
|
|
9671
|
+
const sseResult = await this.tryReadinessStream(windowSec);
|
|
9672
|
+
if (sseResult === true) {
|
|
9673
|
+
await this.adoptReadyState();
|
|
9674
|
+
return true;
|
|
9675
|
+
}
|
|
9676
|
+
if (sseResult === false) {
|
|
9677
|
+
await this.throwIfTerminallyFailed();
|
|
9678
|
+
if (!clamped || Date.now() >= deadlineMs) return false;
|
|
9679
|
+
continue;
|
|
9680
|
+
}
|
|
9681
|
+
break;
|
|
9606
9682
|
}
|
|
9683
|
+
if (Date.now() >= deadlineMs) return false;
|
|
9607
9684
|
}
|
|
9608
|
-
const deadlineMs = Date.now() + timeout * 1e3;
|
|
9609
9685
|
while (Date.now() < deadlineMs) {
|
|
9610
9686
|
try {
|
|
9611
9687
|
const data = await this.readiness();
|
|
@@ -9669,9 +9745,25 @@ var Sandbox = class _Sandbox {
|
|
|
9669
9745
|
* stream endpoint is unavailable (404 or transport error) so callers
|
|
9670
9746
|
* can fall back to polling.
|
|
9671
9747
|
*/
|
|
9672
|
-
|
|
9748
|
+
/**
|
|
9749
|
+
* Opens one readiness SSE connection for at most *windowSec* seconds (this
|
|
9750
|
+
* is a single lease, not the caller's overall {@link waitUntilReady}
|
|
9751
|
+
* budget — see there for how leases are chained). *windowSec* is sent to
|
|
9752
|
+
* the server as `?timeout=<seconds>` so the server's own stream deadline
|
|
9753
|
+
* matches what was actually requested instead of always falling back to
|
|
9754
|
+
* its 30 s default, which is what let a 180 s `waitUntilReady` call get
|
|
9755
|
+
* cut short at 30 s.
|
|
9756
|
+
*/
|
|
9757
|
+
async tryReadinessStream(windowSec) {
|
|
9758
|
+
const clampedWindowSec = Math.max(
|
|
9759
|
+
1,
|
|
9760
|
+
Math.min(windowSec, READINESS_STREAM_MAX_TIMEOUT_SEC)
|
|
9761
|
+
);
|
|
9673
9762
|
const abort = new AbortController();
|
|
9674
|
-
const timer = setTimeout(
|
|
9763
|
+
const timer = setTimeout(
|
|
9764
|
+
() => abort.abort(),
|
|
9765
|
+
(clampedWindowSec + 5) * 1e3
|
|
9766
|
+
);
|
|
9675
9767
|
try {
|
|
9676
9768
|
const headers = {
|
|
9677
9769
|
Authorization: `Bearer ${this.http.apiKey}`,
|
|
@@ -9683,7 +9775,7 @@ var Sandbox = class _Sandbox {
|
|
|
9683
9775
|
"User-Agent": SDK_USER_AGENT
|
|
9684
9776
|
};
|
|
9685
9777
|
const response = await fetch(
|
|
9686
|
-
`${this.http.baseUrl}/sandboxes/${this.id}/readiness/stream`,
|
|
9778
|
+
`${this.http.baseUrl}/sandboxes/${this.id}/readiness/stream?timeout=${clampedWindowSec}`,
|
|
9687
9779
|
{ method: "GET", headers, signal: abort.signal }
|
|
9688
9780
|
);
|
|
9689
9781
|
if (response.status === 404) return null;
|
|
@@ -9704,6 +9796,7 @@ var Sandbox = class _Sandbox {
|
|
|
9704
9796
|
const evt = line.slice(6).trim();
|
|
9705
9797
|
if (evt === "ready") return true;
|
|
9706
9798
|
if (evt === "timeout") return false;
|
|
9799
|
+
if (evt === "error") throw this.bootFailedError("error");
|
|
9707
9800
|
}
|
|
9708
9801
|
}
|
|
9709
9802
|
}
|
|
@@ -9714,7 +9807,10 @@ var Sandbox = class _Sandbox {
|
|
|
9714
9807
|
}
|
|
9715
9808
|
}
|
|
9716
9809
|
return null;
|
|
9717
|
-
} catch {
|
|
9810
|
+
} catch (err) {
|
|
9811
|
+
if (err instanceof MiosaError && err.code === "SANDBOX_BOOT_FAILED") {
|
|
9812
|
+
throw err;
|
|
9813
|
+
}
|
|
9718
9814
|
return null;
|
|
9719
9815
|
} finally {
|
|
9720
9816
|
clearTimeout(timer);
|
|
@@ -9765,6 +9861,50 @@ var Sandbox = class _Sandbox {
|
|
|
9765
9861
|
}
|
|
9766
9862
|
}
|
|
9767
9863
|
};
|
|
9864
|
+
function sleep8(ms) {
|
|
9865
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9866
|
+
}
|
|
9867
|
+
function isResumableState(state) {
|
|
9868
|
+
return state === "paused" || state === "stopped";
|
|
9869
|
+
}
|
|
9870
|
+
var DEFAULT_PAUSING_WAIT_TIMEOUT_SEC = 30;
|
|
9871
|
+
var PAUSING_POLL_INTERVAL_MS = 500;
|
|
9872
|
+
var RESUME_RETRY_MAX_ATTEMPTS = 5;
|
|
9873
|
+
var RESUME_RETRY_FALLBACK_DELAY_MS = 500;
|
|
9874
|
+
async function waitOutPausingState(sandbox, timeoutSec) {
|
|
9875
|
+
const deadline = Date.now() + timeoutSec * 1e3;
|
|
9876
|
+
while (sandbox.state === "pausing" && Date.now() < deadline) {
|
|
9877
|
+
await sleep8(PAUSING_POLL_INTERVAL_MS);
|
|
9878
|
+
try {
|
|
9879
|
+
await sandbox.refresh();
|
|
9880
|
+
} catch {
|
|
9881
|
+
}
|
|
9882
|
+
}
|
|
9883
|
+
}
|
|
9884
|
+
async function resumeWithRetryHint(sandbox) {
|
|
9885
|
+
for (let attempt = 0; ; attempt++) {
|
|
9886
|
+
try {
|
|
9887
|
+
await sandbox.resume();
|
|
9888
|
+
return;
|
|
9889
|
+
} catch (error) {
|
|
9890
|
+
const canRetry = error instanceof MiosaError && error.status === 409 && error.retryable === true && attempt < RESUME_RETRY_MAX_ATTEMPTS;
|
|
9891
|
+
if (!canRetry) throw error;
|
|
9892
|
+
await sleep8(error.retryAfterMs ?? RESUME_RETRY_FALLBACK_DELAY_MS);
|
|
9893
|
+
}
|
|
9894
|
+
}
|
|
9895
|
+
}
|
|
9896
|
+
async function resumeForGetOrCreate(sandbox, params) {
|
|
9897
|
+
if (params.resume === false) return;
|
|
9898
|
+
if (sandbox.state === "pausing") {
|
|
9899
|
+
await waitOutPausingState(
|
|
9900
|
+
sandbox,
|
|
9901
|
+
params.pausingWaitTimeoutSec ?? DEFAULT_PAUSING_WAIT_TIMEOUT_SEC
|
|
9902
|
+
);
|
|
9903
|
+
}
|
|
9904
|
+
if (isResumableState(sandbox.state)) {
|
|
9905
|
+
await resumeWithRetryHint(sandbox);
|
|
9906
|
+
}
|
|
9907
|
+
}
|
|
9768
9908
|
var Sandboxes = class {
|
|
9769
9909
|
constructor(http) {
|
|
9770
9910
|
this.http = http;
|
|
@@ -9860,9 +10000,14 @@ var Sandboxes = class {
|
|
|
9860
10000
|
/**
|
|
9861
10001
|
* Idempotently obtain a persistent sandbox by stable name.
|
|
9862
10002
|
*
|
|
9863
|
-
* If the sandbox exists, it is returned.
|
|
9864
|
-
*
|
|
9865
|
-
*
|
|
10003
|
+
* If the sandbox exists, it is returned. Unless `resume === false`: a
|
|
10004
|
+
* `paused` or persistent `stopped` sandbox is resumed; a `pausing`
|
|
10005
|
+
* sandbox is waited out (bounded by `pausingWaitTimeoutSec`) and then
|
|
10006
|
+
* resumed if it landed on `paused`/`stopped`; a resume answered with a
|
|
10007
|
+
* 409 the server marks `retryable: true` (e.g. a concurrent pause still
|
|
10008
|
+
* finishing) is retried using the server's `retry_after_ms` hint. If the
|
|
10009
|
+
* sandbox does not exist, it is created with the supplied params.
|
|
10010
|
+
* Destroyed sandboxes are permanent and are not resumed by this helper.
|
|
9866
10011
|
*/
|
|
9867
10012
|
async getOrCreate(params) {
|
|
9868
10013
|
let sandbox;
|
|
@@ -9872,9 +10017,7 @@ var Sandboxes = class {
|
|
|
9872
10017
|
if (!(error instanceof NotFoundError)) throw error;
|
|
9873
10018
|
sandbox = await this.create(params);
|
|
9874
10019
|
}
|
|
9875
|
-
|
|
9876
|
-
await sandbox.resume();
|
|
9877
|
-
}
|
|
10020
|
+
await resumeForGetOrCreate(sandbox, params);
|
|
9878
10021
|
if (params.waitUntilReady === true) {
|
|
9879
10022
|
const ready = await sandbox.waitUntilReady({
|
|
9880
10023
|
timeout: params.waitTimeoutSec ?? 60
|