@miosa/sdk 3.2.4 → 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 +75 -8
- package/dist/index.js +151 -48
- package/dist/index.js.map +1 -1
- package/package.json +12 -13
package/dist/index.d.ts
CHANGED
|
@@ -5219,6 +5219,8 @@ interface MiosaErrorBody {
|
|
|
5219
5219
|
message?: string;
|
|
5220
5220
|
details?: unknown;
|
|
5221
5221
|
retryable?: boolean;
|
|
5222
|
+
request_id?: string;
|
|
5223
|
+
retry_after_ms?: number;
|
|
5222
5224
|
};
|
|
5223
5225
|
message?: string;
|
|
5224
5226
|
code?: string;
|
|
@@ -5227,6 +5229,7 @@ interface MiosaErrorBody {
|
|
|
5227
5229
|
reason?: string;
|
|
5228
5230
|
request_id?: string;
|
|
5229
5231
|
retryable?: boolean;
|
|
5232
|
+
retry_after_ms?: number;
|
|
5230
5233
|
}
|
|
5231
5234
|
declare class MiosaError extends Error {
|
|
5232
5235
|
readonly status: number;
|
|
@@ -5234,8 +5237,21 @@ declare class MiosaError extends Error {
|
|
|
5234
5237
|
readonly details: unknown;
|
|
5235
5238
|
readonly requestId: string | undefined;
|
|
5236
5239
|
readonly retryable: boolean;
|
|
5237
|
-
|
|
5238
|
-
|
|
5240
|
+
/**
|
|
5241
|
+
* Server-supplied retry hint in milliseconds, taken from the canonical
|
|
5242
|
+
* error shape's `retry_after_ms` (nested under `error` or top-level) or,
|
|
5243
|
+
* failing that, the HTTP `Retry-After` header. `undefined` when the
|
|
5244
|
+
* response carried neither, in which case a caller should fall back to
|
|
5245
|
+
* its own backoff policy.
|
|
5246
|
+
*/
|
|
5247
|
+
retryAfterMs: number | undefined;
|
|
5248
|
+
constructor(message: string, status: number, code: string, details?: unknown, requestId?: string, retryable?: boolean, retryAfterMs?: number);
|
|
5249
|
+
/**
|
|
5250
|
+
* @param retryAfterHeaderMs The `Retry-After` header value, already
|
|
5251
|
+
* converted to milliseconds by the caller. Used only when the body does
|
|
5252
|
+
* not carry the more precise `retry_after_ms`.
|
|
5253
|
+
*/
|
|
5254
|
+
static fromResponse(status: number, body: MiosaErrorBody, requestId?: string, retryAfterHeaderMs?: number): MiosaError;
|
|
5239
5255
|
}
|
|
5240
5256
|
declare class AuthError extends MiosaError {
|
|
5241
5257
|
constructor(message: string, status?: number, code?: string, details?: unknown, requestId?: string, retryable?: boolean);
|
|
@@ -6732,7 +6748,7 @@ type SandboxSize = "xs" | "small" | "medium" | "large" | "xl";
|
|
|
6732
6748
|
type SandboxId = string & {
|
|
6733
6749
|
readonly __brand: "SandboxId";
|
|
6734
6750
|
};
|
|
6735
|
-
type SandboxState = "provisioning" | "running" | "paused" | "destroying" | "destroyed" | "error";
|
|
6751
|
+
type SandboxState = "provisioning" | "running" | "pausing" | "paused" | "resuming" | "stopped" | "destroying" | "destroyed" | "error";
|
|
6736
6752
|
interface SandboxCreateParams {
|
|
6737
6753
|
templateId?: string;
|
|
6738
6754
|
template_id?: string;
|
|
@@ -6827,8 +6843,18 @@ interface SandboxCreateParams {
|
|
|
6827
6843
|
}
|
|
6828
6844
|
interface SandboxGetOrCreateParams extends SandboxCreateParams {
|
|
6829
6845
|
name: string;
|
|
6830
|
-
/**
|
|
6846
|
+
/**
|
|
6847
|
+
* Resume an existing sandbox before returning it, when it is `paused`,
|
|
6848
|
+
* `stopped` (persistent sandboxes only), or - after a bounded wait - lands
|
|
6849
|
+
* in one of those states from `pausing`. Defaults to true.
|
|
6850
|
+
*/
|
|
6831
6851
|
resume?: boolean;
|
|
6852
|
+
/**
|
|
6853
|
+
* Bound on how long to wait out an in-progress `pausing` transition before
|
|
6854
|
+
* attempting resume, in seconds. The server's own pause work is typically
|
|
6855
|
+
* well under this. Defaults to 30.
|
|
6856
|
+
*/
|
|
6857
|
+
pausingWaitTimeoutSec?: number;
|
|
6832
6858
|
/**
|
|
6833
6859
|
* Wait for readiness before returning a created/resumed sandbox. Defaults to false.
|
|
6834
6860
|
*
|
|
@@ -6861,6 +6887,18 @@ interface SandboxExecOptions {
|
|
|
6861
6887
|
timeout?: number;
|
|
6862
6888
|
timeoutSec?: number;
|
|
6863
6889
|
timeout_sec?: number;
|
|
6890
|
+
/**
|
|
6891
|
+
* `exec.stream` only. Budget (seconds) reserved for the sandbox to wake
|
|
6892
|
+
* and resume before the command's own `timeoutSec` starts counting down,
|
|
6893
|
+
* separate from it. The server opens the SSE response, runs
|
|
6894
|
+
* `ensure_running` (which resumes a paused sandbox), and only then starts
|
|
6895
|
+
* the command's own timeout - so without a separate budget a slow resume
|
|
6896
|
+
* could abort a command client-side while it is still within its own
|
|
6897
|
+
* server-side deadline. Defaults to {@link DEFAULT_EXEC_WAKE_BUDGET_SEC}.
|
|
6898
|
+
* Has no effect when `timeoutSec` is omitted (the stream is unbounded).
|
|
6899
|
+
*/
|
|
6900
|
+
wakeTimeoutSec?: number;
|
|
6901
|
+
wake_timeout_sec?: number;
|
|
6864
6902
|
}
|
|
6865
6903
|
interface SandboxExecResult {
|
|
6866
6904
|
stdout: string;
|
|
@@ -6869,6 +6907,14 @@ interface SandboxExecResult {
|
|
|
6869
6907
|
exit_code: number;
|
|
6870
6908
|
durationMs?: number;
|
|
6871
6909
|
duration_ms?: number;
|
|
6910
|
+
/**
|
|
6911
|
+
* True when the server killed the command for exceeding its timeout.
|
|
6912
|
+
* Without this, a timeout was indistinguishable from the infra error
|
|
6913
|
+
* frame, which also reports `exitCode: -1`. Absent when the server did
|
|
6914
|
+
* not report it (older servers, or a non-timeout exit).
|
|
6915
|
+
*/
|
|
6916
|
+
timedOut?: boolean;
|
|
6917
|
+
timed_out?: boolean;
|
|
6872
6918
|
}
|
|
6873
6919
|
interface SandboxExportFile {
|
|
6874
6920
|
path: string;
|
|
@@ -6902,7 +6948,10 @@ interface SandboxExportParams {
|
|
|
6902
6948
|
* The server tags each SSE frame with an `event:` name (`stdout` / `stderr` /
|
|
6903
6949
|
* `exit`); the SDK maps that name onto `type`. For output frames `data` (and
|
|
6904
6950
|
* its legacy alias `line`) carry the text chunk. The exit frame carries the
|
|
6905
|
-
* process exit code as both `exit_code` and its camelCase alias `exitCode
|
|
6951
|
+
* process exit code as both `exit_code` and its camelCase alias `exitCode`,
|
|
6952
|
+
* plus `timed_out`/`timedOut` when the server reports it (see B1-9: without
|
|
6953
|
+
* this a timeout was indistinguishable from the infra error frame, which
|
|
6954
|
+
* also reports `exit_code: -1`).
|
|
6906
6955
|
*/
|
|
6907
6956
|
type SandboxExecEvent = {
|
|
6908
6957
|
type: "stdout";
|
|
@@ -6916,6 +6965,8 @@ type SandboxExecEvent = {
|
|
|
6916
6965
|
type: "exit";
|
|
6917
6966
|
exit_code: number;
|
|
6918
6967
|
exitCode: number;
|
|
6968
|
+
timed_out?: boolean;
|
|
6969
|
+
timedOut?: boolean;
|
|
6919
6970
|
};
|
|
6920
6971
|
interface SandboxExecRunner {
|
|
6921
6972
|
(command: string, options?: SandboxExecOptions): Promise<SandboxExecResult>;
|
|
@@ -7457,6 +7508,17 @@ declare class Sandbox {
|
|
|
7457
7508
|
[key: string]: unknown;
|
|
7458
7509
|
}>;
|
|
7459
7510
|
pause(): Promise<Sandbox>;
|
|
7511
|
+
/**
|
|
7512
|
+
* Resume a paused (or persistent-stopped) sandbox.
|
|
7513
|
+
*
|
|
7514
|
+
* Always sends an `Idempotency-Key`, generating a fresh one when the
|
|
7515
|
+
* caller doesn't supply one - same rationale as {@link generateIdempotencyKey}
|
|
7516
|
+
* for create(): the key is built once here and threaded through a single
|
|
7517
|
+
* `HttpClient.request` call, whose headers are fixed before its retry loop
|
|
7518
|
+
* runs, so every retry of *this* resume replays the same key. Without this,
|
|
7519
|
+
* a client-side retry of a slow resume could dispatch a second full resume
|
|
7520
|
+
* execution (re-running host reservation) instead of deduping server-side.
|
|
7521
|
+
*/
|
|
7460
7522
|
resume(idempotencyKey?: string): Promise<Sandbox>;
|
|
7461
7523
|
deploy(params?: SandboxDeployParams): Promise<Record<string, unknown>>;
|
|
7462
7524
|
deployDocker(params?: SandboxDeployParams): Promise<Record<string, unknown>>;
|
|
@@ -7575,9 +7637,14 @@ declare class Sandboxes {
|
|
|
7575
7637
|
/**
|
|
7576
7638
|
* Idempotently obtain a persistent sandbox by stable name.
|
|
7577
7639
|
*
|
|
7578
|
-
* If the sandbox exists, it is returned.
|
|
7579
|
-
*
|
|
7580
|
-
*
|
|
7640
|
+
* If the sandbox exists, it is returned. Unless `resume === false`: a
|
|
7641
|
+
* `paused` or persistent `stopped` sandbox is resumed; a `pausing`
|
|
7642
|
+
* sandbox is waited out (bounded by `pausingWaitTimeoutSec`) and then
|
|
7643
|
+
* resumed if it landed on `paused`/`stopped`; a resume answered with a
|
|
7644
|
+
* 409 the server marks `retryable: true` (e.g. a concurrent pause still
|
|
7645
|
+
* finishing) is retried using the server's `retry_after_ms` hint. If the
|
|
7646
|
+
* sandbox does not exist, it is created with the supplied params.
|
|
7647
|
+
* Destroyed sandboxes are permanent and are not resumed by this helper.
|
|
7581
7648
|
*/
|
|
7582
7649
|
getOrCreate(params: SandboxGetOrCreateParams): Promise<Sandbox>;
|
|
7583
7650
|
delete(id: SandboxId | string): Promise<void>;
|
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) {
|
|
@@ -8652,16 +8666,29 @@ function raiseUnresolvableShape(cpuCount, memoryMb, tier) {
|
|
|
8652
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.`
|
|
8653
8667
|
);
|
|
8654
8668
|
}
|
|
8655
|
-
function
|
|
8669
|
+
function validatedExecSeconds(options) {
|
|
8656
8670
|
const seconds = options?.timeout ?? options?.timeoutSec ?? options?.timeout_sec;
|
|
8657
|
-
if (seconds === void 0) return
|
|
8671
|
+
if (seconds === void 0) return void 0;
|
|
8658
8672
|
if (!Number.isFinite(seconds) || seconds <= 0 || seconds > 86400) {
|
|
8659
8673
|
throw new RangeError(
|
|
8660
8674
|
"Exec timeout must be greater than zero and at most 86400 seconds"
|
|
8661
8675
|
);
|
|
8662
8676
|
}
|
|
8677
|
+
return seconds;
|
|
8678
|
+
}
|
|
8679
|
+
function execDeadline(options) {
|
|
8680
|
+
const seconds = validatedExecSeconds(options);
|
|
8681
|
+
if (seconds === void 0) return {};
|
|
8663
8682
|
return { timeout: seconds * 1e3 + 5e3 };
|
|
8664
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
|
+
}
|
|
8665
8692
|
function execBody(command, options = {}) {
|
|
8666
8693
|
return stripUndefined26({
|
|
8667
8694
|
command,
|
|
@@ -8677,7 +8704,14 @@ function normalizeExecEvent(event, payload) {
|
|
|
8677
8704
|
const code = Number(
|
|
8678
8705
|
record.exit_code ?? record.exitCode ?? (typeof payload === "number" ? payload : 0)
|
|
8679
8706
|
);
|
|
8680
|
-
|
|
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
|
+
};
|
|
8681
8715
|
}
|
|
8682
8716
|
const type = event === "stderr" ? "stderr" : "stdout";
|
|
8683
8717
|
const data = typeof payload === "string" ? payload : String(record.line ?? record.data ?? "");
|
|
@@ -9145,12 +9179,18 @@ var Sandbox = class _Sandbox {
|
|
|
9145
9179
|
result.durationMs = response.duration_ms;
|
|
9146
9180
|
result.duration_ms = response.duration_ms;
|
|
9147
9181
|
}
|
|
9182
|
+
const timedOutRaw = response.timed_out ?? response.timedOut;
|
|
9183
|
+
if (typeof timedOutRaw === "boolean") {
|
|
9184
|
+
result.timedOut = timedOutRaw;
|
|
9185
|
+
result.timed_out = timedOutRaw;
|
|
9186
|
+
}
|
|
9148
9187
|
return result;
|
|
9149
9188
|
}
|
|
9150
9189
|
async runCancellableExec(command, options) {
|
|
9151
9190
|
let stdout = "";
|
|
9152
9191
|
let stderr = "";
|
|
9153
9192
|
let exitCode;
|
|
9193
|
+
let timedOut;
|
|
9154
9194
|
for await (const event of this.execStream(command, options)) {
|
|
9155
9195
|
if (event.type === "stdout" && typeof event.line === "string")
|
|
9156
9196
|
stdout += event.line;
|
|
@@ -9158,6 +9198,7 @@ var Sandbox = class _Sandbox {
|
|
|
9158
9198
|
stderr += event.line;
|
|
9159
9199
|
if (event.type === "exit" && typeof event.exit_code === "number") {
|
|
9160
9200
|
exitCode = event.exit_code;
|
|
9201
|
+
timedOut = event.timed_out;
|
|
9161
9202
|
break;
|
|
9162
9203
|
}
|
|
9163
9204
|
}
|
|
@@ -9172,7 +9213,13 @@ var Sandbox = class _Sandbox {
|
|
|
9172
9213
|
false
|
|
9173
9214
|
);
|
|
9174
9215
|
}
|
|
9175
|
-
return {
|
|
9216
|
+
return {
|
|
9217
|
+
stdout,
|
|
9218
|
+
stderr,
|
|
9219
|
+
exitCode,
|
|
9220
|
+
exit_code: exitCode,
|
|
9221
|
+
...timedOut !== void 0 ? { timedOut, timed_out: timedOut } : {}
|
|
9222
|
+
};
|
|
9176
9223
|
}
|
|
9177
9224
|
execStream(command, options) {
|
|
9178
9225
|
this.assertRunning("exec.stream");
|
|
@@ -9182,7 +9229,7 @@ var Sandbox = class _Sandbox {
|
|
|
9182
9229
|
method: "POST",
|
|
9183
9230
|
body: execBody(command, options),
|
|
9184
9231
|
signal: options?.signal,
|
|
9185
|
-
...
|
|
9232
|
+
...execStreamDeadline(options)
|
|
9186
9233
|
}
|
|
9187
9234
|
);
|
|
9188
9235
|
return (async function* () {
|
|
@@ -9456,17 +9503,26 @@ var Sandbox = class _Sandbox {
|
|
|
9456
9503
|
this.data = { ...this.data, ...data };
|
|
9457
9504
|
return this;
|
|
9458
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
|
+
*/
|
|
9459
9517
|
async resume(idempotencyKey11) {
|
|
9460
|
-
const
|
|
9518
|
+
const key = idempotencyKey11 ?? generateIdempotencyKey();
|
|
9519
|
+
const response = await this.http.request(
|
|
9461
9520
|
`/sandboxes/${this.id}/resume`,
|
|
9462
9521
|
{
|
|
9463
9522
|
method: "POST",
|
|
9464
9523
|
body: {},
|
|
9465
|
-
headers: { "Idempotency-Key":
|
|
9524
|
+
headers: { "Idempotency-Key": key }
|
|
9466
9525
|
}
|
|
9467
|
-
) : await this.http.post(
|
|
9468
|
-
`/sandboxes/${this.id}/resume`,
|
|
9469
|
-
{}
|
|
9470
9526
|
);
|
|
9471
9527
|
const data = unwrap48(response);
|
|
9472
9528
|
this.data = { ...this.data, ...data };
|
|
@@ -9805,6 +9861,50 @@ var Sandbox = class _Sandbox {
|
|
|
9805
9861
|
}
|
|
9806
9862
|
}
|
|
9807
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
|
+
}
|
|
9808
9908
|
var Sandboxes = class {
|
|
9809
9909
|
constructor(http) {
|
|
9810
9910
|
this.http = http;
|
|
@@ -9900,9 +10000,14 @@ var Sandboxes = class {
|
|
|
9900
10000
|
/**
|
|
9901
10001
|
* Idempotently obtain a persistent sandbox by stable name.
|
|
9902
10002
|
*
|
|
9903
|
-
* If the sandbox exists, it is returned.
|
|
9904
|
-
*
|
|
9905
|
-
*
|
|
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.
|
|
9906
10011
|
*/
|
|
9907
10012
|
async getOrCreate(params) {
|
|
9908
10013
|
let sandbox;
|
|
@@ -9912,9 +10017,7 @@ var Sandboxes = class {
|
|
|
9912
10017
|
if (!(error instanceof NotFoundError)) throw error;
|
|
9913
10018
|
sandbox = await this.create(params);
|
|
9914
10019
|
}
|
|
9915
|
-
|
|
9916
|
-
await sandbox.resume();
|
|
9917
|
-
}
|
|
10020
|
+
await resumeForGetOrCreate(sandbox, params);
|
|
9918
10021
|
if (params.waitUntilReady === true) {
|
|
9919
10022
|
const ready = await sandbox.waitUntilReady({
|
|
9920
10023
|
timeout: params.waitTimeoutSec ?? 60
|