@miosa/sdk 3.2.2 → 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/README.md +18 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +124 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -268,6 +268,23 @@ await sbx.pause();
|
|
|
268
268
|
await sbx.resume();
|
|
269
269
|
```
|
|
270
270
|
|
|
271
|
+
### Cancelling sandbox commands
|
|
272
|
+
|
|
273
|
+
Pass an `AbortSignal` to `sandbox.exec.run(command, { signal, timeoutSec })` or `sandbox.exec.stream(command, { signal, timeoutSec })`.
|
|
274
|
+
Aborting rejects with the signal's abort reason (an `AbortError` by default); it does not return a successful exit code or replay the command.
|
|
275
|
+
Signal-bearing `exec.run` uses the request-owned stream and aggregates its exact stdout, stderr, and terminal exit code.
|
|
276
|
+
If that stream ends without an exit event, `exec.run` rejects with `EXEC_STREAM_INCOMPLETE` (502, not retryable).
|
|
277
|
+
Ordinary execution without a signal keeps the standard exec endpoint.
|
|
278
|
+
An explicit `timeoutSec` sets the guest deadline and allows five additional seconds for its response.
|
|
279
|
+
A guest that overruns that deadline rejects with `TimeoutError`, never with `AbortError`.
|
|
280
|
+
Cancellation requires the server's request-owned streaming implementation and the guest's process-group cancellation support.
|
|
281
|
+
|
|
282
|
+
### Confirming sandbox deletion
|
|
283
|
+
|
|
284
|
+
`sandbox.destroy()` acknowledges deletion and updates the handle to the server's state.
|
|
285
|
+
If deletion is still in progress, the handle reports `destroying` and exposes `sandbox.data.operation_id` when supplied by the server.
|
|
286
|
+
Use `sandbox.refresh()` to verify completion; an acknowledgement alone does not prove resource cleanup.
|
|
287
|
+
|
|
271
288
|
## Publish from sandbox
|
|
272
289
|
|
|
273
290
|
Preview is mutable; publish creates a durable deployment version. Static output
|
|
@@ -414,6 +431,7 @@ try {
|
|
|
414
431
|
```
|
|
415
432
|
|
|
416
433
|
The SDK retries `429` and `5xx` automatically (3 retries, exponential backoff + jitter). Set `maxRetries: 0` to disable.
|
|
434
|
+
Sandbox `exec` requests are never retried, because a replayed command would run twice; see [Cancelling sandbox commands](#cancelling-sandbox-commands).
|
|
417
435
|
Every `MiosaError` carries `retryable` (server-supplied when present, otherwise `true` for 408/429/5xx and transport failures) and `requestId` for support correlation.
|
|
418
436
|
|
|
419
437
|
## Configuration
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ interface RequestOptions {
|
|
|
6
6
|
headers?: Record<string, string>;
|
|
7
7
|
body?: unknown;
|
|
8
8
|
timeout?: number;
|
|
9
|
+
/** Cancel this request and its response stream without retrying. */
|
|
10
|
+
signal?: AbortSignal | undefined;
|
|
11
|
+
/** Override retries for operations which cannot safely be replayed. */
|
|
12
|
+
maxRetries?: number;
|
|
9
13
|
/** Return raw Response instead of parsing JSON */
|
|
10
14
|
rawResponse?: boolean;
|
|
11
15
|
/** Send body as multipart/form-data */
|
|
@@ -6728,7 +6732,7 @@ type SandboxSize = "xs" | "small" | "medium" | "large" | "xl";
|
|
|
6728
6732
|
type SandboxId = string & {
|
|
6729
6733
|
readonly __brand: "SandboxId";
|
|
6730
6734
|
};
|
|
6731
|
-
type SandboxState = "provisioning" | "running" | "paused" | "destroyed" | "error";
|
|
6735
|
+
type SandboxState = "provisioning" | "running" | "paused" | "destroying" | "destroyed" | "error";
|
|
6732
6736
|
interface SandboxCreateParams {
|
|
6733
6737
|
templateId?: string;
|
|
6734
6738
|
template_id?: string;
|
|
@@ -6848,6 +6852,8 @@ interface SandboxListParams {
|
|
|
6848
6852
|
external_project_id?: string;
|
|
6849
6853
|
}
|
|
6850
6854
|
interface SandboxExecOptions {
|
|
6855
|
+
/** Abort the request-scoped execution, including its response stream. */
|
|
6856
|
+
signal?: AbortSignal;
|
|
6851
6857
|
cwd?: string;
|
|
6852
6858
|
workingDir?: string;
|
|
6853
6859
|
working_dir?: string;
|
|
@@ -6917,6 +6923,8 @@ interface SandboxExecRunner {
|
|
|
6917
6923
|
stream(command: string, options?: SandboxExecOptions): AsyncIterableIterator<SandboxExecEvent>;
|
|
6918
6924
|
}
|
|
6919
6925
|
interface SandboxData {
|
|
6926
|
+
operation_id?: string | null;
|
|
6927
|
+
request_id?: string | null;
|
|
6920
6928
|
id: SandboxId;
|
|
6921
6929
|
state: SandboxState;
|
|
6922
6930
|
slug?: string;
|
|
@@ -7372,6 +7380,7 @@ declare class Sandbox {
|
|
|
7372
7380
|
*/
|
|
7373
7381
|
prompt(prompt: string, options?: SandboxPromptOptions): Promise<AgentRun>;
|
|
7374
7382
|
private runExec;
|
|
7383
|
+
private runCancellableExec;
|
|
7375
7384
|
private execStream;
|
|
7376
7385
|
writeFile(path: string, content: string | Uint8Array): Promise<void>;
|
|
7377
7386
|
download(path: string): Promise<Uint8Array>;
|
package/dist/index.js
CHANGED
|
@@ -174,7 +174,7 @@ var TokenRefreshFailedError = class extends MiosaError {
|
|
|
174
174
|
};
|
|
175
175
|
|
|
176
176
|
// src/version.ts
|
|
177
|
-
var SDK_VERSION = "3.2.
|
|
177
|
+
var SDK_VERSION = "3.2.3";
|
|
178
178
|
var SDK_USER_AGENT = `@miosa/sdk/${SDK_VERSION}`;
|
|
179
179
|
|
|
180
180
|
// src/http.ts
|
|
@@ -271,8 +271,12 @@ var HttpClient = class {
|
|
|
271
271
|
fetchBody = JSON.stringify(body5);
|
|
272
272
|
}
|
|
273
273
|
let lastError;
|
|
274
|
-
|
|
274
|
+
const maxRetries = options.maxRetries ?? this.maxRetries;
|
|
275
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
276
|
+
options.signal?.throwIfAborted();
|
|
275
277
|
const controller = new AbortController();
|
|
278
|
+
const cancel = () => controller.abort(options.signal?.reason);
|
|
279
|
+
options.signal?.addEventListener("abort", cancel, { once: true });
|
|
276
280
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
277
281
|
try {
|
|
278
282
|
const response = await fetch(this.buildUrl(path), {
|
|
@@ -296,7 +300,7 @@ var HttpClient = class {
|
|
|
296
300
|
errorBody,
|
|
297
301
|
requestId
|
|
298
302
|
);
|
|
299
|
-
if (RETRY_STATUS.has(response.status) && attempt <
|
|
303
|
+
if (RETRY_STATUS.has(response.status) && attempt < maxRetries) {
|
|
300
304
|
lastError = error;
|
|
301
305
|
await sleep(retryDelay(attempt, retryAfter));
|
|
302
306
|
continue;
|
|
@@ -313,7 +317,7 @@ var HttpClient = class {
|
|
|
313
317
|
if (options.rawResponse) return response;
|
|
314
318
|
return await response.json();
|
|
315
319
|
} catch (err) {
|
|
316
|
-
|
|
320
|
+
options.signal?.throwIfAborted();
|
|
317
321
|
if (err instanceof MiosaError) {
|
|
318
322
|
throw err;
|
|
319
323
|
}
|
|
@@ -327,7 +331,7 @@ var HttpClient = class {
|
|
|
327
331
|
`Network error reaching ${path}: ${err.message}`,
|
|
328
332
|
err
|
|
329
333
|
);
|
|
330
|
-
if (attempt <
|
|
334
|
+
if (attempt < maxRetries) {
|
|
331
335
|
lastError = networkErr;
|
|
332
336
|
await sleep(retryDelay(attempt, null));
|
|
333
337
|
continue;
|
|
@@ -335,6 +339,9 @@ var HttpClient = class {
|
|
|
335
339
|
throw networkErr;
|
|
336
340
|
}
|
|
337
341
|
throw err;
|
|
342
|
+
} finally {
|
|
343
|
+
clearTimeout(timer);
|
|
344
|
+
options.signal?.removeEventListener("abort", cancel);
|
|
338
345
|
}
|
|
339
346
|
}
|
|
340
347
|
throw lastError ?? new MiosaError(
|
|
@@ -394,44 +401,52 @@ var HttpClient = class {
|
|
|
394
401
|
headers = { ...headers, "Content-Type": "application/json" };
|
|
395
402
|
body5 = JSON.stringify(options.body);
|
|
396
403
|
}
|
|
404
|
+
options.signal?.throwIfAborted();
|
|
397
405
|
const controller = new AbortController();
|
|
398
|
-
|
|
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;
|
|
399
413
|
try {
|
|
400
|
-
response
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
|
411
441
|
);
|
|
412
442
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if (!response.ok) {
|
|
416
|
-
let errorBody = {};
|
|
417
|
-
try {
|
|
418
|
-
errorBody = await response.json();
|
|
419
|
-
} catch {
|
|
443
|
+
if (!response.body) {
|
|
444
|
+
throw new MiosaError("SSE stream has no body", 0, "NO_STREAM_BODY");
|
|
420
445
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
if (!response.body) {
|
|
428
|
-
throw new MiosaError("SSE stream has no body", 0, "NO_STREAM_BODY");
|
|
429
|
-
}
|
|
430
|
-
const reader = response.body.getReader();
|
|
431
|
-
const decoder = new TextDecoder();
|
|
432
|
-
let buffer = "";
|
|
433
|
-
let event = null;
|
|
434
|
-
try {
|
|
446
|
+
reader = response.body.getReader();
|
|
447
|
+
const decoder = new TextDecoder();
|
|
448
|
+
let buffer = "";
|
|
449
|
+
let event = null;
|
|
435
450
|
while (true) {
|
|
436
451
|
const { done, value } = await reader.read();
|
|
437
452
|
if (done) break;
|
|
@@ -459,9 +474,17 @@ var HttpClient = class {
|
|
|
459
474
|
}
|
|
460
475
|
}
|
|
461
476
|
}
|
|
477
|
+
} catch (err) {
|
|
478
|
+
options.signal?.throwIfAborted();
|
|
479
|
+
if (deadline !== void 0 && controller.signal.reason === deadline) {
|
|
480
|
+
throw deadline;
|
|
481
|
+
}
|
|
482
|
+
throw err;
|
|
462
483
|
} finally {
|
|
484
|
+
clearTimeout(timer);
|
|
485
|
+
options.signal?.removeEventListener("abort", cancel);
|
|
463
486
|
controller.abort();
|
|
464
|
-
reader
|
|
487
|
+
reader?.releaseLock();
|
|
465
488
|
}
|
|
466
489
|
}
|
|
467
490
|
/**
|
|
@@ -8628,6 +8651,16 @@ function raiseUnresolvableShape(cpuCount, memoryMb, tier) {
|
|
|
8628
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.`
|
|
8629
8652
|
);
|
|
8630
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
|
+
}
|
|
8631
8664
|
function execBody(command, options = {}) {
|
|
8632
8665
|
return stripUndefined26({
|
|
8633
8666
|
command,
|
|
@@ -9087,10 +9120,17 @@ var Sandbox = class _Sandbox {
|
|
|
9087
9120
|
}
|
|
9088
9121
|
async runExec(command, options) {
|
|
9089
9122
|
this.assertRunning("exec");
|
|
9123
|
+
if (options?.signal) return this.runCancellableExec(command, options);
|
|
9090
9124
|
const response = unwrap48(
|
|
9091
|
-
await this.http.
|
|
9125
|
+
await this.http.request(
|
|
9092
9126
|
`/sandboxes/${this.id}/exec`,
|
|
9093
|
-
|
|
9127
|
+
{
|
|
9128
|
+
method: "POST",
|
|
9129
|
+
body: execBody(command, options),
|
|
9130
|
+
signal: options?.signal,
|
|
9131
|
+
maxRetries: 0,
|
|
9132
|
+
...execDeadline(options)
|
|
9133
|
+
}
|
|
9094
9134
|
)
|
|
9095
9135
|
);
|
|
9096
9136
|
const exitCode = Number(response.exit_code ?? response.exitCode ?? 0);
|
|
@@ -9106,13 +9146,42 @@ var Sandbox = class _Sandbox {
|
|
|
9106
9146
|
}
|
|
9107
9147
|
return result;
|
|
9108
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
|
+
}
|
|
9109
9176
|
execStream(command, options) {
|
|
9110
9177
|
this.assertRunning("exec.stream");
|
|
9111
9178
|
const frames = this.http.streamFrames(
|
|
9112
9179
|
`/sandboxes/${this.id}/exec/stream`,
|
|
9113
9180
|
{
|
|
9114
9181
|
method: "POST",
|
|
9115
|
-
body: execBody(command, options)
|
|
9182
|
+
body: execBody(command, options),
|
|
9183
|
+
signal: options?.signal,
|
|
9184
|
+
...execDeadline(options)
|
|
9116
9185
|
}
|
|
9117
9186
|
);
|
|
9118
9187
|
return (async function* () {
|
|
@@ -9656,12 +9725,21 @@ var Sandbox = class _Sandbox {
|
|
|
9656
9725
|
}
|
|
9657
9726
|
}
|
|
9658
9727
|
async destroy() {
|
|
9659
|
-
if (this.data.state === "destroyed")
|
|
9660
|
-
|
|
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;
|
|
9661
9737
|
this.data = {
|
|
9662
9738
|
...this.data,
|
|
9663
|
-
state: "destroyed",
|
|
9664
|
-
|
|
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
|
|
9665
9743
|
};
|
|
9666
9744
|
}
|
|
9667
9745
|
delete() {
|