@miosa/sdk 3.2.4 → 3.3.0
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 +121 -0
- package/dist/index.d.ts +872 -13
- package/dist/index.js +1325 -50
- package/dist/index.js.map +1 -1
- package/native/h3-runner/index.d.ts +61 -0
- package/native/h3-runner/index.js +60 -0
- package/native/h3-runner/npm/darwin-arm64/h3-runner-native.darwin-arm64.node +0 -0
- package/native/h3-runner/npm/darwin-x64/h3-runner-native.darwin-x64.node +0 -0
- package/native/h3-runner/npm/linux-arm64-gnu/h3-runner-native.linux-arm64-gnu.node +0 -0
- package/native/h3-runner/npm/linux-x64-gnu/h3-runner-native.linux-x64-gnu.node +0 -0
- package/native/h3-runner/package.json +9 -0
- package/package.json +17 -14
package/dist/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { createHmac, timingSafeEqual, randomUUID } from 'crypto';
|
|
2
2
|
import EventEmitter from 'events';
|
|
3
|
+
import { isIP } from 'net';
|
|
4
|
+
import { resolve4 } from 'dns/promises';
|
|
5
|
+
import * as http2 from 'http2';
|
|
6
|
+
import { createRequire } from 'module';
|
|
3
7
|
|
|
4
8
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
9
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
@@ -18,7 +22,15 @@ var MiosaError = class _MiosaError extends Error {
|
|
|
18
22
|
details;
|
|
19
23
|
requestId;
|
|
20
24
|
retryable;
|
|
21
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Server-supplied retry hint in milliseconds, taken from the canonical
|
|
27
|
+
* error shape's `retry_after_ms` (nested under `error` or top-level) or,
|
|
28
|
+
* failing that, the HTTP `Retry-After` header. `undefined` when the
|
|
29
|
+
* response carried neither, in which case a caller should fall back to
|
|
30
|
+
* its own backoff policy.
|
|
31
|
+
*/
|
|
32
|
+
retryAfterMs;
|
|
33
|
+
constructor(message, status, code, details, requestId, retryable = defaultRetryable(status), retryAfterMs) {
|
|
22
34
|
super(message);
|
|
23
35
|
this.name = "MiosaError";
|
|
24
36
|
this.status = status;
|
|
@@ -26,34 +38,33 @@ var MiosaError = class _MiosaError extends Error {
|
|
|
26
38
|
this.details = details;
|
|
27
39
|
this.requestId = requestId;
|
|
28
40
|
this.retryable = retryable;
|
|
41
|
+
this.retryAfterMs = retryAfterMs;
|
|
29
42
|
}
|
|
30
|
-
|
|
43
|
+
/**
|
|
44
|
+
* @param retryAfterHeaderMs The `Retry-After` header value, already
|
|
45
|
+
* converted to milliseconds by the caller. Used only when the body does
|
|
46
|
+
* not carry the more precise `retry_after_ms`.
|
|
47
|
+
*/
|
|
48
|
+
static fromResponse(status, body5, requestId, retryAfterHeaderMs2) {
|
|
31
49
|
const nested = typeof body5.error === "object" ? body5.error : void 0;
|
|
32
50
|
const flatError = typeof body5.error === "string" ? body5.error : void 0;
|
|
33
51
|
const flatErrorIsCode = !!flatError && /^[A-Z][A-Z0-9_]+$/.test(flatError);
|
|
34
52
|
const message = nested?.message ?? body5.message ?? body5.detail ?? body5.reason ?? flatError ?? `HTTP ${status}`;
|
|
35
53
|
const code = nested?.code ?? body5.code ?? (flatErrorIsCode ? flatError : "UNKNOWN_ERROR");
|
|
36
54
|
const details = nested?.details ?? body5.details ?? (body5.detail || body5.reason ? { detail: body5.detail, reason: body5.reason } : void 0);
|
|
37
|
-
requestId ??= body5.request_id;
|
|
55
|
+
requestId ??= nested?.request_id ?? body5.request_id;
|
|
38
56
|
const retryable = nested?.retryable ?? body5.retryable ?? defaultRetryable(status);
|
|
57
|
+
const retryAfterMs = nested?.retry_after_ms ?? body5.retry_after_ms ?? retryAfterHeaderMs2;
|
|
39
58
|
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);
|
|
59
|
+
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(
|
|
60
|
+
message,
|
|
61
|
+
details,
|
|
62
|
+
requestId,
|
|
63
|
+
retryAfterMs === void 0 ? void 0 : retryAfterMs / 1e3,
|
|
64
|
+
retryable
|
|
65
|
+
) : 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));
|
|
66
|
+
built.retryAfterMs = retryAfterMs;
|
|
67
|
+
return built;
|
|
57
68
|
}
|
|
58
69
|
};
|
|
59
70
|
function connectErrorFromCode(code, message, status, details, requestId, retryable) {
|
|
@@ -174,7 +185,7 @@ var TokenRefreshFailedError = class extends MiosaError {
|
|
|
174
185
|
};
|
|
175
186
|
|
|
176
187
|
// src/version.ts
|
|
177
|
-
var SDK_VERSION = "3.
|
|
188
|
+
var SDK_VERSION = "3.3.0";
|
|
178
189
|
var SDK_USER_AGENT = `@miosa/sdk/${SDK_VERSION}`;
|
|
179
190
|
|
|
180
191
|
// src/http.ts
|
|
@@ -206,15 +217,18 @@ async function ensureHttp2Agent() {
|
|
|
206
217
|
ensureHttp2Agent();
|
|
207
218
|
var DEFAULT_TIMEOUT = 3e4;
|
|
208
219
|
var DEFAULT_MAX_RETRIES = 3;
|
|
209
|
-
var RETRY_STATUS = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
210
220
|
var BASE_DELAY_MS = 500;
|
|
211
221
|
function sleep(ms) {
|
|
212
222
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
213
223
|
}
|
|
214
|
-
function
|
|
215
|
-
if (retryAfterHeader
|
|
216
|
-
|
|
217
|
-
|
|
224
|
+
function retryAfterHeaderMs(retryAfterHeader) {
|
|
225
|
+
if (retryAfterHeader === null) return void 0;
|
|
226
|
+
const seconds = parseInt(retryAfterHeader, 10);
|
|
227
|
+
return isNaN(seconds) ? void 0 : seconds * 1e3;
|
|
228
|
+
}
|
|
229
|
+
function retryDelay(attempt, hintMs) {
|
|
230
|
+
if (hintMs !== void 0 && Number.isFinite(hintMs) && hintMs >= 0) {
|
|
231
|
+
return hintMs;
|
|
218
232
|
}
|
|
219
233
|
return BASE_DELAY_MS * 2 ** attempt + Math.random() * 100;
|
|
220
234
|
}
|
|
@@ -289,7 +303,9 @@ var HttpClient = class {
|
|
|
289
303
|
clearTimeout(timer);
|
|
290
304
|
if (!response.ok) {
|
|
291
305
|
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
292
|
-
const
|
|
306
|
+
const retryAfterMs = retryAfterHeaderMs(
|
|
307
|
+
response.headers.get("retry-after")
|
|
308
|
+
);
|
|
293
309
|
let errorBody = {};
|
|
294
310
|
try {
|
|
295
311
|
errorBody = await response.json();
|
|
@@ -298,11 +314,12 @@ var HttpClient = class {
|
|
|
298
314
|
const error = MiosaError.fromResponse(
|
|
299
315
|
response.status,
|
|
300
316
|
errorBody,
|
|
301
|
-
requestId
|
|
317
|
+
requestId,
|
|
318
|
+
retryAfterMs
|
|
302
319
|
);
|
|
303
|
-
if (
|
|
320
|
+
if (error.retryable && attempt < maxRetries) {
|
|
304
321
|
lastError = error;
|
|
305
|
-
await sleep(retryDelay(attempt,
|
|
322
|
+
await sleep(retryDelay(attempt, error.retryAfterMs));
|
|
306
323
|
continue;
|
|
307
324
|
}
|
|
308
325
|
throw error;
|
|
@@ -333,7 +350,7 @@ var HttpClient = class {
|
|
|
333
350
|
);
|
|
334
351
|
if (attempt < maxRetries) {
|
|
335
352
|
lastError = networkErr;
|
|
336
|
-
await sleep(retryDelay(attempt,
|
|
353
|
+
await sleep(retryDelay(attempt, void 0));
|
|
337
354
|
continue;
|
|
338
355
|
}
|
|
339
356
|
throw networkErr;
|
|
@@ -437,7 +454,8 @@ var HttpClient = class {
|
|
|
437
454
|
throw MiosaError.fromResponse(
|
|
438
455
|
response.status,
|
|
439
456
|
errorBody,
|
|
440
|
-
response.headers.get("x-request-id") ?? void 0
|
|
457
|
+
response.headers.get("x-request-id") ?? void 0,
|
|
458
|
+
retryAfterHeaderMs(response.headers.get("retry-after"))
|
|
441
459
|
);
|
|
442
460
|
}
|
|
443
461
|
if (!response.body) {
|
|
@@ -8652,16 +8670,29 @@ function raiseUnresolvableShape(cpuCount, memoryMb, tier) {
|
|
|
8652
8670
|
`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
8671
|
);
|
|
8654
8672
|
}
|
|
8655
|
-
function
|
|
8673
|
+
function validatedExecSeconds(options) {
|
|
8656
8674
|
const seconds = options?.timeout ?? options?.timeoutSec ?? options?.timeout_sec;
|
|
8657
|
-
if (seconds === void 0) return
|
|
8675
|
+
if (seconds === void 0) return void 0;
|
|
8658
8676
|
if (!Number.isFinite(seconds) || seconds <= 0 || seconds > 86400) {
|
|
8659
8677
|
throw new RangeError(
|
|
8660
8678
|
"Exec timeout must be greater than zero and at most 86400 seconds"
|
|
8661
8679
|
);
|
|
8662
8680
|
}
|
|
8681
|
+
return seconds;
|
|
8682
|
+
}
|
|
8683
|
+
function execDeadline(options) {
|
|
8684
|
+
const seconds = validatedExecSeconds(options);
|
|
8685
|
+
if (seconds === void 0) return {};
|
|
8663
8686
|
return { timeout: seconds * 1e3 + 5e3 };
|
|
8664
8687
|
}
|
|
8688
|
+
var DEFAULT_EXEC_WAKE_BUDGET_SEC = 60;
|
|
8689
|
+
function execStreamDeadline(options) {
|
|
8690
|
+
const seconds = validatedExecSeconds(options);
|
|
8691
|
+
if (seconds === void 0) return {};
|
|
8692
|
+
const wakeSeconds = options?.wakeTimeoutSec ?? options?.wake_timeout_sec;
|
|
8693
|
+
const wakeBudgetSec = wakeSeconds !== void 0 ? Math.max(0, wakeSeconds) : DEFAULT_EXEC_WAKE_BUDGET_SEC;
|
|
8694
|
+
return { timeout: (seconds + wakeBudgetSec) * 1e3 + 5e3 };
|
|
8695
|
+
}
|
|
8665
8696
|
function execBody(command, options = {}) {
|
|
8666
8697
|
return stripUndefined26({
|
|
8667
8698
|
command,
|
|
@@ -8677,7 +8708,14 @@ function normalizeExecEvent(event, payload) {
|
|
|
8677
8708
|
const code = Number(
|
|
8678
8709
|
record.exit_code ?? record.exitCode ?? (typeof payload === "number" ? payload : 0)
|
|
8679
8710
|
);
|
|
8680
|
-
|
|
8711
|
+
const timedOutRaw = record.timed_out ?? record.timedOut;
|
|
8712
|
+
const timedOut = typeof timedOutRaw === "boolean" ? timedOutRaw : void 0;
|
|
8713
|
+
return {
|
|
8714
|
+
type: "exit",
|
|
8715
|
+
exit_code: code,
|
|
8716
|
+
exitCode: code,
|
|
8717
|
+
...timedOut !== void 0 ? { timed_out: timedOut, timedOut } : {}
|
|
8718
|
+
};
|
|
8681
8719
|
}
|
|
8682
8720
|
const type = event === "stderr" ? "stderr" : "stdout";
|
|
8683
8721
|
const data = typeof payload === "string" ? payload : String(record.line ?? record.data ?? "");
|
|
@@ -8744,7 +8782,16 @@ var SandboxFiles = class {
|
|
|
8744
8782
|
}
|
|
8745
8783
|
return response;
|
|
8746
8784
|
}
|
|
8747
|
-
/**
|
|
8785
|
+
/**
|
|
8786
|
+
* Write a failure-atomic batch on a batch-capable guest.
|
|
8787
|
+
* The guest's guarantee is whatever the response advertises: rely on it only
|
|
8788
|
+
* after `atomicity === "failure_atomic"` and `read_isolation ===
|
|
8789
|
+
* "file_api_operations"` compare equal, because the fields are forwarded
|
|
8790
|
+
* verbatim and an older guest omits them. Never treat an unknown value or a
|
|
8791
|
+
* missing field as the guarantee, and never substitute sequential writes.
|
|
8792
|
+
* Shell/Docker readers may see intermediate publication; coordinate them
|
|
8793
|
+
* with the write if a consistent multi-file view is required.
|
|
8794
|
+
*/
|
|
8748
8795
|
async writeMany(files) {
|
|
8749
8796
|
const http = this.sandbox.http;
|
|
8750
8797
|
const payload = files.map((f) => ({
|
|
@@ -8819,12 +8866,54 @@ var SandboxSnapshots = class {
|
|
|
8819
8866
|
return this.sandbox.deleteSnapshot(snapshotId);
|
|
8820
8867
|
}
|
|
8821
8868
|
};
|
|
8869
|
+
var MAX_TERMINAL_ARGS = 256;
|
|
8870
|
+
function invalidTerminalOptions(message, details) {
|
|
8871
|
+
return new MiosaError(
|
|
8872
|
+
message,
|
|
8873
|
+
400,
|
|
8874
|
+
"INVALID_TERMINAL_OPTIONS",
|
|
8875
|
+
details,
|
|
8876
|
+
void 0,
|
|
8877
|
+
false
|
|
8878
|
+
);
|
|
8879
|
+
}
|
|
8880
|
+
function assertTerminalOptions(params) {
|
|
8881
|
+
if (params.shell !== void 0 && params.command !== void 0) {
|
|
8882
|
+
throw invalidTerminalOptions(
|
|
8883
|
+
"Terminal options cannot combine shell with command: shell names the session shell executable, command starts an executable directly",
|
|
8884
|
+
{ shell: params.shell, command: params.command }
|
|
8885
|
+
);
|
|
8886
|
+
}
|
|
8887
|
+
if (params.args === void 0) return;
|
|
8888
|
+
if (params.command === void 0) {
|
|
8889
|
+
throw invalidTerminalOptions(
|
|
8890
|
+
"Terminal options supplied args without command: arguments belong to a directly launched executable",
|
|
8891
|
+
{ args: params.args.length }
|
|
8892
|
+
);
|
|
8893
|
+
}
|
|
8894
|
+
if (params.args.length > MAX_TERMINAL_ARGS) {
|
|
8895
|
+
throw invalidTerminalOptions(
|
|
8896
|
+
`Terminal options supplied ${params.args.length} args, at most ${MAX_TERMINAL_ARGS} are accepted`,
|
|
8897
|
+
{ args: params.args.length }
|
|
8898
|
+
);
|
|
8899
|
+
}
|
|
8900
|
+
const index = params.args.findIndex(
|
|
8901
|
+
(arg) => typeof arg !== "string" || arg.includes("\0")
|
|
8902
|
+
);
|
|
8903
|
+
if (index !== -1) {
|
|
8904
|
+
throw invalidTerminalOptions(
|
|
8905
|
+
`Terminal options args[${index}] is not a NUL-free string; arguments are passed to process creation literally`,
|
|
8906
|
+
{ index }
|
|
8907
|
+
);
|
|
8908
|
+
}
|
|
8909
|
+
}
|
|
8822
8910
|
var SandboxTerminal = class {
|
|
8823
8911
|
constructor(sandbox) {
|
|
8824
8912
|
this.sandbox = sandbox;
|
|
8825
8913
|
}
|
|
8826
8914
|
sandbox;
|
|
8827
8915
|
async create(params = {}) {
|
|
8916
|
+
assertTerminalOptions(params);
|
|
8828
8917
|
const body5 = Object.fromEntries(
|
|
8829
8918
|
Object.entries(params).filter(([, v]) => v !== void 0)
|
|
8830
8919
|
);
|
|
@@ -9145,12 +9234,18 @@ var Sandbox = class _Sandbox {
|
|
|
9145
9234
|
result.durationMs = response.duration_ms;
|
|
9146
9235
|
result.duration_ms = response.duration_ms;
|
|
9147
9236
|
}
|
|
9237
|
+
const timedOutRaw = response.timed_out ?? response.timedOut;
|
|
9238
|
+
if (typeof timedOutRaw === "boolean") {
|
|
9239
|
+
result.timedOut = timedOutRaw;
|
|
9240
|
+
result.timed_out = timedOutRaw;
|
|
9241
|
+
}
|
|
9148
9242
|
return result;
|
|
9149
9243
|
}
|
|
9150
9244
|
async runCancellableExec(command, options) {
|
|
9151
9245
|
let stdout = "";
|
|
9152
9246
|
let stderr = "";
|
|
9153
9247
|
let exitCode;
|
|
9248
|
+
let timedOut;
|
|
9154
9249
|
for await (const event of this.execStream(command, options)) {
|
|
9155
9250
|
if (event.type === "stdout" && typeof event.line === "string")
|
|
9156
9251
|
stdout += event.line;
|
|
@@ -9158,6 +9253,7 @@ var Sandbox = class _Sandbox {
|
|
|
9158
9253
|
stderr += event.line;
|
|
9159
9254
|
if (event.type === "exit" && typeof event.exit_code === "number") {
|
|
9160
9255
|
exitCode = event.exit_code;
|
|
9256
|
+
timedOut = event.timed_out;
|
|
9161
9257
|
break;
|
|
9162
9258
|
}
|
|
9163
9259
|
}
|
|
@@ -9172,7 +9268,13 @@ var Sandbox = class _Sandbox {
|
|
|
9172
9268
|
false
|
|
9173
9269
|
);
|
|
9174
9270
|
}
|
|
9175
|
-
return {
|
|
9271
|
+
return {
|
|
9272
|
+
stdout,
|
|
9273
|
+
stderr,
|
|
9274
|
+
exitCode,
|
|
9275
|
+
exit_code: exitCode,
|
|
9276
|
+
...timedOut !== void 0 ? { timedOut, timed_out: timedOut } : {}
|
|
9277
|
+
};
|
|
9176
9278
|
}
|
|
9177
9279
|
execStream(command, options) {
|
|
9178
9280
|
this.assertRunning("exec.stream");
|
|
@@ -9182,7 +9284,7 @@ var Sandbox = class _Sandbox {
|
|
|
9182
9284
|
method: "POST",
|
|
9183
9285
|
body: execBody(command, options),
|
|
9184
9286
|
signal: options?.signal,
|
|
9185
|
-
...
|
|
9287
|
+
...execStreamDeadline(options)
|
|
9186
9288
|
}
|
|
9187
9289
|
);
|
|
9188
9290
|
return (async function* () {
|
|
@@ -9456,17 +9558,26 @@ var Sandbox = class _Sandbox {
|
|
|
9456
9558
|
this.data = { ...this.data, ...data };
|
|
9457
9559
|
return this;
|
|
9458
9560
|
}
|
|
9561
|
+
/**
|
|
9562
|
+
* Resume a paused (or persistent-stopped) sandbox.
|
|
9563
|
+
*
|
|
9564
|
+
* Always sends an `Idempotency-Key`, generating a fresh one when the
|
|
9565
|
+
* caller doesn't supply one - same rationale as {@link generateIdempotencyKey}
|
|
9566
|
+
* for create(): the key is built once here and threaded through a single
|
|
9567
|
+
* `HttpClient.request` call, whose headers are fixed before its retry loop
|
|
9568
|
+
* runs, so every retry of *this* resume replays the same key. Without this,
|
|
9569
|
+
* a client-side retry of a slow resume could dispatch a second full resume
|
|
9570
|
+
* execution (re-running host reservation) instead of deduping server-side.
|
|
9571
|
+
*/
|
|
9459
9572
|
async resume(idempotencyKey11) {
|
|
9460
|
-
const
|
|
9573
|
+
const key = idempotencyKey11 ?? generateIdempotencyKey();
|
|
9574
|
+
const response = await this.http.request(
|
|
9461
9575
|
`/sandboxes/${this.id}/resume`,
|
|
9462
9576
|
{
|
|
9463
9577
|
method: "POST",
|
|
9464
9578
|
body: {},
|
|
9465
|
-
headers: { "Idempotency-Key":
|
|
9579
|
+
headers: { "Idempotency-Key": key }
|
|
9466
9580
|
}
|
|
9467
|
-
) : await this.http.post(
|
|
9468
|
-
`/sandboxes/${this.id}/resume`,
|
|
9469
|
-
{}
|
|
9470
9581
|
);
|
|
9471
9582
|
const data = unwrap48(response);
|
|
9472
9583
|
this.data = { ...this.data, ...data };
|
|
@@ -9805,6 +9916,50 @@ var Sandbox = class _Sandbox {
|
|
|
9805
9916
|
}
|
|
9806
9917
|
}
|
|
9807
9918
|
};
|
|
9919
|
+
function sleep8(ms) {
|
|
9920
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9921
|
+
}
|
|
9922
|
+
function isResumableState(state) {
|
|
9923
|
+
return state === "paused" || state === "stopped";
|
|
9924
|
+
}
|
|
9925
|
+
var DEFAULT_PAUSING_WAIT_TIMEOUT_SEC = 30;
|
|
9926
|
+
var PAUSING_POLL_INTERVAL_MS = 500;
|
|
9927
|
+
var RESUME_RETRY_MAX_ATTEMPTS = 5;
|
|
9928
|
+
var RESUME_RETRY_FALLBACK_DELAY_MS = 500;
|
|
9929
|
+
async function waitOutPausingState(sandbox, timeoutSec) {
|
|
9930
|
+
const deadline = Date.now() + timeoutSec * 1e3;
|
|
9931
|
+
while (sandbox.state === "pausing" && Date.now() < deadline) {
|
|
9932
|
+
await sleep8(PAUSING_POLL_INTERVAL_MS);
|
|
9933
|
+
try {
|
|
9934
|
+
await sandbox.refresh();
|
|
9935
|
+
} catch {
|
|
9936
|
+
}
|
|
9937
|
+
}
|
|
9938
|
+
}
|
|
9939
|
+
async function resumeWithRetryHint(sandbox) {
|
|
9940
|
+
for (let attempt = 0; ; attempt++) {
|
|
9941
|
+
try {
|
|
9942
|
+
await sandbox.resume();
|
|
9943
|
+
return;
|
|
9944
|
+
} catch (error) {
|
|
9945
|
+
const canRetry = error instanceof MiosaError && error.status === 409 && error.retryable === true && attempt < RESUME_RETRY_MAX_ATTEMPTS;
|
|
9946
|
+
if (!canRetry) throw error;
|
|
9947
|
+
await sleep8(error.retryAfterMs ?? RESUME_RETRY_FALLBACK_DELAY_MS);
|
|
9948
|
+
}
|
|
9949
|
+
}
|
|
9950
|
+
}
|
|
9951
|
+
async function resumeForGetOrCreate(sandbox, params) {
|
|
9952
|
+
if (params.resume === false) return;
|
|
9953
|
+
if (sandbox.state === "pausing") {
|
|
9954
|
+
await waitOutPausingState(
|
|
9955
|
+
sandbox,
|
|
9956
|
+
params.pausingWaitTimeoutSec ?? DEFAULT_PAUSING_WAIT_TIMEOUT_SEC
|
|
9957
|
+
);
|
|
9958
|
+
}
|
|
9959
|
+
if (isResumableState(sandbox.state)) {
|
|
9960
|
+
await resumeWithRetryHint(sandbox);
|
|
9961
|
+
}
|
|
9962
|
+
}
|
|
9808
9963
|
var Sandboxes = class {
|
|
9809
9964
|
constructor(http) {
|
|
9810
9965
|
this.http = http;
|
|
@@ -9900,9 +10055,14 @@ var Sandboxes = class {
|
|
|
9900
10055
|
/**
|
|
9901
10056
|
* Idempotently obtain a persistent sandbox by stable name.
|
|
9902
10057
|
*
|
|
9903
|
-
* If the sandbox exists, it is returned.
|
|
9904
|
-
*
|
|
9905
|
-
*
|
|
10058
|
+
* If the sandbox exists, it is returned. Unless `resume === false`: a
|
|
10059
|
+
* `paused` or persistent `stopped` sandbox is resumed; a `pausing`
|
|
10060
|
+
* sandbox is waited out (bounded by `pausingWaitTimeoutSec`) and then
|
|
10061
|
+
* resumed if it landed on `paused`/`stopped`; a resume answered with a
|
|
10062
|
+
* 409 the server marks `retryable: true` (e.g. a concurrent pause still
|
|
10063
|
+
* finishing) is retried using the server's `retry_after_ms` hint. If the
|
|
10064
|
+
* sandbox does not exist, it is created with the supplied params.
|
|
10065
|
+
* Destroyed sandboxes are permanent and are not resumed by this helper.
|
|
9906
10066
|
*/
|
|
9907
10067
|
async getOrCreate(params) {
|
|
9908
10068
|
let sandbox;
|
|
@@ -9912,9 +10072,7 @@ var Sandboxes = class {
|
|
|
9912
10072
|
if (!(error instanceof NotFoundError)) throw error;
|
|
9913
10073
|
sandbox = await this.create(params);
|
|
9914
10074
|
}
|
|
9915
|
-
|
|
9916
|
-
await sandbox.resume();
|
|
9917
|
-
}
|
|
10075
|
+
await resumeForGetOrCreate(sandbox, params);
|
|
9918
10076
|
if (params.waitUntilReady === true) {
|
|
9919
10077
|
const ready = await sandbox.waitUntilReady({
|
|
9920
10078
|
timeout: params.waitTimeoutSec ?? 60
|
|
@@ -11025,10 +11183,1101 @@ var WorkspaceMembers = class {
|
|
|
11025
11183
|
}
|
|
11026
11184
|
};
|
|
11027
11185
|
|
|
11186
|
+
// src/runner/region.ts
|
|
11187
|
+
var DEFAULT_RUNNER_REGION = "us";
|
|
11188
|
+
var KNOWN_RUNNER_REGIONS = /* @__PURE__ */ new Set();
|
|
11189
|
+
function regionFromApiKey(apiKey) {
|
|
11190
|
+
const parts = apiKey.split("_");
|
|
11191
|
+
const candidate = parts[1];
|
|
11192
|
+
if (parts.length >= 3 && candidate !== void 0 && KNOWN_RUNNER_REGIONS.has(candidate)) {
|
|
11193
|
+
return candidate;
|
|
11194
|
+
}
|
|
11195
|
+
return DEFAULT_RUNNER_REGION;
|
|
11196
|
+
}
|
|
11197
|
+
var DEFAULT_RUNNER_BASE_DOMAIN = "miosa.ai";
|
|
11198
|
+
function regionHostname(region, baseDomain = DEFAULT_RUNNER_BASE_DOMAIN) {
|
|
11199
|
+
return `run-${region}.${baseDomain}`;
|
|
11200
|
+
}
|
|
11201
|
+
function tagHostname(region, tag, baseDomain = DEFAULT_RUNNER_BASE_DOMAIN) {
|
|
11202
|
+
return `${tag}.${regionHostname(region, baseDomain)}`;
|
|
11203
|
+
}
|
|
11204
|
+
function sandboxTag(sandboxId) {
|
|
11205
|
+
const first = sandboxId.charAt(0);
|
|
11206
|
+
if (!first) {
|
|
11207
|
+
throw new TypeError("sandboxTag: sandboxId must not be empty");
|
|
11208
|
+
}
|
|
11209
|
+
return first.toLowerCase();
|
|
11210
|
+
}
|
|
11211
|
+
var DnsAddressResolver = class {
|
|
11212
|
+
constructor(ttlMs = 6e4) {
|
|
11213
|
+
this.ttlMs = ttlMs;
|
|
11214
|
+
}
|
|
11215
|
+
ttlMs;
|
|
11216
|
+
cache = /* @__PURE__ */ new Map();
|
|
11217
|
+
async resolve(hostname) {
|
|
11218
|
+
if (isIP(hostname) !== 0) return [hostname];
|
|
11219
|
+
if (hostname === "localhost") return ["127.0.0.1"];
|
|
11220
|
+
const cached = this.cache.get(hostname);
|
|
11221
|
+
if (cached && cached.expiresAt > Date.now()) return cached.addresses;
|
|
11222
|
+
const addresses = await resolve4(hostname);
|
|
11223
|
+
if (addresses.length === 0) {
|
|
11224
|
+
throw new Error(`DnsAddressResolver: ${hostname} resolved to zero A records`);
|
|
11225
|
+
}
|
|
11226
|
+
this.cache.set(hostname, { addresses, expiresAt: Date.now() + this.ttlMs });
|
|
11227
|
+
return addresses;
|
|
11228
|
+
}
|
|
11229
|
+
};
|
|
11230
|
+
var StaticAddressResolver = class {
|
|
11231
|
+
constructor(table) {
|
|
11232
|
+
this.table = table;
|
|
11233
|
+
}
|
|
11234
|
+
table;
|
|
11235
|
+
async resolve(hostname) {
|
|
11236
|
+
const addresses = this.table.get(hostname);
|
|
11237
|
+
if (!addresses || addresses.length === 0) {
|
|
11238
|
+
throw new Error(`StaticAddressResolver: no addresses configured for ${hostname}`);
|
|
11239
|
+
}
|
|
11240
|
+
return addresses;
|
|
11241
|
+
}
|
|
11242
|
+
};
|
|
11243
|
+
|
|
11244
|
+
// src/runner/transport.ts
|
|
11245
|
+
var RunnerTimeoutError = class extends Error {
|
|
11246
|
+
constructor(hostname, path, timeoutMs) {
|
|
11247
|
+
super(`runner request to ${hostname}${path} timed out after ${timeoutMs}ms`);
|
|
11248
|
+
this.name = "RunnerTimeoutError";
|
|
11249
|
+
}
|
|
11250
|
+
};
|
|
11251
|
+
var RunnerConnectError = class extends Error {
|
|
11252
|
+
constructor(hostname, address, cause) {
|
|
11253
|
+
super(
|
|
11254
|
+
`runner transport failed to connect to ${hostname} (${address}): ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
11255
|
+
{ cause }
|
|
11256
|
+
);
|
|
11257
|
+
this.name = "RunnerConnectError";
|
|
11258
|
+
}
|
|
11259
|
+
};
|
|
11260
|
+
function isConnectFailure(error) {
|
|
11261
|
+
if (error instanceof RunnerConnectError || error instanceof RunnerTimeoutError) {
|
|
11262
|
+
return true;
|
|
11263
|
+
}
|
|
11264
|
+
if (error && typeof error === "object" && "code" in error) {
|
|
11265
|
+
const code = error.code;
|
|
11266
|
+
return code === "ECONNREFUSED" || code === "ECONNRESET" || code === "ETIMEDOUT" || code === "EHOSTUNREACH" || code === "ENETUNREACH" || code === "ERR_HTTP2_GOAWAY_SESSION";
|
|
11267
|
+
}
|
|
11268
|
+
return false;
|
|
11269
|
+
}
|
|
11270
|
+
|
|
11271
|
+
// src/runner/http2-transport.ts
|
|
11272
|
+
function markHandled(promise) {
|
|
11273
|
+
promise.catch(() => void 0);
|
|
11274
|
+
return promise;
|
|
11275
|
+
}
|
|
11276
|
+
var DEFAULT_SESSIONS_PER_ADDRESS = 8;
|
|
11277
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
11278
|
+
var Http2Transport = class {
|
|
11279
|
+
protocol = "h2";
|
|
11280
|
+
defaultSessionsPerAddress;
|
|
11281
|
+
tls;
|
|
11282
|
+
port;
|
|
11283
|
+
resolver;
|
|
11284
|
+
defaultTimeoutMs;
|
|
11285
|
+
pools = /* @__PURE__ */ new Map();
|
|
11286
|
+
constructor(options = {}) {
|
|
11287
|
+
this.defaultSessionsPerAddress = options.defaultSessionsPerAddress ?? DEFAULT_SESSIONS_PER_ADDRESS;
|
|
11288
|
+
this.tls = options.tls ?? true;
|
|
11289
|
+
this.port = options.port ?? (this.tls ? 443 : 80);
|
|
11290
|
+
this.resolver = options.resolver ?? new DnsAddressResolver();
|
|
11291
|
+
this.defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
11292
|
+
}
|
|
11293
|
+
prewarm(regionHostname2, sessionsPerAddress) {
|
|
11294
|
+
this.poolFor(regionHostname2, sessionsPerAddress);
|
|
11295
|
+
}
|
|
11296
|
+
async addressCount(regionHostname2) {
|
|
11297
|
+
const addresses = await this.poolFor(regionHostname2).addresses;
|
|
11298
|
+
return addresses.length;
|
|
11299
|
+
}
|
|
11300
|
+
async ready(regionHostname2, addressIndex) {
|
|
11301
|
+
await this.acquireSession(regionHostname2, addressIndex);
|
|
11302
|
+
}
|
|
11303
|
+
async request(regionHostname2, addressIndex, authority, path, init) {
|
|
11304
|
+
const { session } = await this.acquireSession(regionHostname2, addressIndex);
|
|
11305
|
+
return this.sendRequest(regionHostname2, authority, path, session, init);
|
|
11306
|
+
}
|
|
11307
|
+
async streamRequest(regionHostname2, addressIndex, authority, path, init) {
|
|
11308
|
+
const { session } = await this.acquireSession(regionHostname2, addressIndex);
|
|
11309
|
+
return this.sendStreamRequest(authority, path, session, init);
|
|
11310
|
+
}
|
|
11311
|
+
/** Shared by request() and ready() - the latter just discards the session. */
|
|
11312
|
+
async acquireSession(regionHostname2, addressIndex) {
|
|
11313
|
+
const pool = this.poolFor(regionHostname2);
|
|
11314
|
+
const addresses = await pool.addresses;
|
|
11315
|
+
const n = addresses.length;
|
|
11316
|
+
const addrIdx = (addressIndex % n + n) % n;
|
|
11317
|
+
const address = addresses[addrIdx];
|
|
11318
|
+
const groups = await pool.groups;
|
|
11319
|
+
const group = groups[addrIdx];
|
|
11320
|
+
if (group.length === 0) {
|
|
11321
|
+
throw new Error(
|
|
11322
|
+
`Http2Transport: ${regionHostname2} address ${addrIdx} has zero sessions configured (prewarm/request with sessionsPerAddress > 0 first)`
|
|
11323
|
+
);
|
|
11324
|
+
}
|
|
11325
|
+
const cursor = pool.nextSlot.get(addrIdx) ?? 0;
|
|
11326
|
+
pool.nextSlot.set(addrIdx, (cursor + 1) % group.length);
|
|
11327
|
+
let session;
|
|
11328
|
+
try {
|
|
11329
|
+
session = await group[cursor];
|
|
11330
|
+
} catch (error) {
|
|
11331
|
+
throw new RunnerConnectError(regionHostname2, address, error);
|
|
11332
|
+
}
|
|
11333
|
+
if (session.closed || session.destroyed) {
|
|
11334
|
+
const fresh = this.connectSession(regionHostname2, address);
|
|
11335
|
+
group[cursor] = fresh;
|
|
11336
|
+
try {
|
|
11337
|
+
session = await fresh;
|
|
11338
|
+
} catch (error) {
|
|
11339
|
+
throw new RunnerConnectError(regionHostname2, address, error);
|
|
11340
|
+
}
|
|
11341
|
+
}
|
|
11342
|
+
return { session };
|
|
11343
|
+
}
|
|
11344
|
+
async close() {
|
|
11345
|
+
const pools = [...this.pools.values()];
|
|
11346
|
+
this.pools.clear();
|
|
11347
|
+
for (const pool of pools) {
|
|
11348
|
+
const groups = await pool.groups.catch(() => []);
|
|
11349
|
+
for (const group of groups) {
|
|
11350
|
+
for (const slot of group) {
|
|
11351
|
+
try {
|
|
11352
|
+
const session = await slot;
|
|
11353
|
+
if (!session.closed) session.close();
|
|
11354
|
+
} catch {
|
|
11355
|
+
}
|
|
11356
|
+
}
|
|
11357
|
+
}
|
|
11358
|
+
}
|
|
11359
|
+
}
|
|
11360
|
+
poolFor(regionHostname2, sessionsPerAddress) {
|
|
11361
|
+
let pool = this.pools.get(regionHostname2);
|
|
11362
|
+
if (!pool) {
|
|
11363
|
+
pool = this.buildPool(regionHostname2, sessionsPerAddress ?? this.defaultSessionsPerAddress);
|
|
11364
|
+
this.pools.set(regionHostname2, pool);
|
|
11365
|
+
}
|
|
11366
|
+
return pool;
|
|
11367
|
+
}
|
|
11368
|
+
buildPool(regionHostname2, sessionsPerAddress) {
|
|
11369
|
+
const addresses = this.resolver.resolve(regionHostname2);
|
|
11370
|
+
const groups = addresses.then(
|
|
11371
|
+
(resolved) => resolved.map(
|
|
11372
|
+
(address) => (
|
|
11373
|
+
// sessionsPerAddress: 0 is a deliberate "open nothing" (e.g. a
|
|
11374
|
+
// count-only probe).
|
|
11375
|
+
Array.from(
|
|
11376
|
+
{ length: sessionsPerAddress },
|
|
11377
|
+
() => markHandled(this.connectSession(regionHostname2, address))
|
|
11378
|
+
)
|
|
11379
|
+
)
|
|
11380
|
+
)
|
|
11381
|
+
);
|
|
11382
|
+
markHandled(addresses);
|
|
11383
|
+
markHandled(groups);
|
|
11384
|
+
return { addresses, groups, nextSlot: /* @__PURE__ */ new Map() };
|
|
11385
|
+
}
|
|
11386
|
+
connectSession(regionHostname2, address) {
|
|
11387
|
+
return new Promise((resolve, reject) => {
|
|
11388
|
+
const authority = `${this.tls ? "https" : "http"}://${address}:${this.port}`;
|
|
11389
|
+
const connectOptions = this.tls ? { servername: regionHostname2 } : {};
|
|
11390
|
+
const session = http2.connect(authority, connectOptions);
|
|
11391
|
+
const onError = (error) => {
|
|
11392
|
+
session.destroy();
|
|
11393
|
+
reject(error);
|
|
11394
|
+
};
|
|
11395
|
+
session.once("error", onError);
|
|
11396
|
+
session.once("connect", () => {
|
|
11397
|
+
session.removeListener("error", onError);
|
|
11398
|
+
session.on("error", () => void 0);
|
|
11399
|
+
resolve(session);
|
|
11400
|
+
});
|
|
11401
|
+
});
|
|
11402
|
+
}
|
|
11403
|
+
sendRequest(regionHostname2, authority, path, session, init) {
|
|
11404
|
+
const timeoutMs = init.timeoutMs ?? this.defaultTimeoutMs;
|
|
11405
|
+
return new Promise((resolve, reject) => {
|
|
11406
|
+
const requestHeaders = {
|
|
11407
|
+
":method": init.method,
|
|
11408
|
+
":path": path,
|
|
11409
|
+
":authority": authority,
|
|
11410
|
+
...init.headers
|
|
11411
|
+
};
|
|
11412
|
+
const stream = session.request(requestHeaders);
|
|
11413
|
+
let status = 0;
|
|
11414
|
+
const responseHeaders = {};
|
|
11415
|
+
const chunks = [];
|
|
11416
|
+
let settled = false;
|
|
11417
|
+
const timer = setTimeout(() => {
|
|
11418
|
+
if (settled) return;
|
|
11419
|
+
settled = true;
|
|
11420
|
+
stream.close(http2.constants.NGHTTP2_CANCEL);
|
|
11421
|
+
reject(new RunnerTimeoutError(regionHostname2, path, timeoutMs));
|
|
11422
|
+
}, timeoutMs);
|
|
11423
|
+
const onAbort = () => {
|
|
11424
|
+
if (settled) return;
|
|
11425
|
+
settled = true;
|
|
11426
|
+
clearTimeout(timer);
|
|
11427
|
+
stream.close(http2.constants.NGHTTP2_CANCEL);
|
|
11428
|
+
reject(new DOMException("runner request aborted", "AbortError"));
|
|
11429
|
+
};
|
|
11430
|
+
init.signal?.addEventListener("abort", onAbort, { once: true });
|
|
11431
|
+
stream.on("response", (headers) => {
|
|
11432
|
+
status = Number(headers[":status"] ?? 0);
|
|
11433
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
11434
|
+
if (key.startsWith(":") || value === void 0) continue;
|
|
11435
|
+
responseHeaders[key] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
11436
|
+
}
|
|
11437
|
+
});
|
|
11438
|
+
stream.on("data", (chunk) => chunks.push(chunk));
|
|
11439
|
+
stream.on("end", () => {
|
|
11440
|
+
if (settled) return;
|
|
11441
|
+
settled = true;
|
|
11442
|
+
clearTimeout(timer);
|
|
11443
|
+
init.signal?.removeEventListener("abort", onAbort);
|
|
11444
|
+
resolve({ status, headers: responseHeaders, body: Buffer.concat(chunks).toString("utf8") });
|
|
11445
|
+
});
|
|
11446
|
+
stream.on("error", (error) => {
|
|
11447
|
+
if (settled) return;
|
|
11448
|
+
settled = true;
|
|
11449
|
+
clearTimeout(timer);
|
|
11450
|
+
init.signal?.removeEventListener("abort", onAbort);
|
|
11451
|
+
reject(error);
|
|
11452
|
+
});
|
|
11453
|
+
if (init.body !== void 0) {
|
|
11454
|
+
stream.end(init.body);
|
|
11455
|
+
} else {
|
|
11456
|
+
stream.end();
|
|
11457
|
+
}
|
|
11458
|
+
});
|
|
11459
|
+
}
|
|
11460
|
+
sendStreamRequest(authority, path, session, init) {
|
|
11461
|
+
return new Promise((resolve, reject) => {
|
|
11462
|
+
const requestHeaders = {
|
|
11463
|
+
":method": init.method,
|
|
11464
|
+
":path": path,
|
|
11465
|
+
":authority": authority,
|
|
11466
|
+
...init.headers
|
|
11467
|
+
};
|
|
11468
|
+
const stream = session.request(requestHeaders);
|
|
11469
|
+
let settled = false;
|
|
11470
|
+
stream.once("response", (headers) => {
|
|
11471
|
+
if (settled) return;
|
|
11472
|
+
settled = true;
|
|
11473
|
+
const responseHeaders = {};
|
|
11474
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
11475
|
+
if (key.startsWith(":") || value === void 0) continue;
|
|
11476
|
+
responseHeaders[key] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
11477
|
+
}
|
|
11478
|
+
resolve({
|
|
11479
|
+
status: Number(headers[":status"] ?? 0),
|
|
11480
|
+
headers: responseHeaders,
|
|
11481
|
+
chunks: textChunks(stream)
|
|
11482
|
+
});
|
|
11483
|
+
});
|
|
11484
|
+
stream.once("error", (error) => {
|
|
11485
|
+
if (settled) return;
|
|
11486
|
+
settled = true;
|
|
11487
|
+
reject(error);
|
|
11488
|
+
});
|
|
11489
|
+
if (init.body !== void 0) {
|
|
11490
|
+
stream.end(init.body);
|
|
11491
|
+
} else {
|
|
11492
|
+
stream.end();
|
|
11493
|
+
}
|
|
11494
|
+
});
|
|
11495
|
+
}
|
|
11496
|
+
};
|
|
11497
|
+
async function* textChunks(stream) {
|
|
11498
|
+
const decoder = new TextDecoder();
|
|
11499
|
+
try {
|
|
11500
|
+
for await (const chunk of stream) {
|
|
11501
|
+
yield decoder.decode(chunk, { stream: true });
|
|
11502
|
+
}
|
|
11503
|
+
} finally {
|
|
11504
|
+
if (!stream.closed) stream.close();
|
|
11505
|
+
}
|
|
11506
|
+
}
|
|
11507
|
+
function markHandled2(promise) {
|
|
11508
|
+
promise.catch(() => void 0);
|
|
11509
|
+
return promise;
|
|
11510
|
+
}
|
|
11511
|
+
var cachedNative;
|
|
11512
|
+
function loadNative() {
|
|
11513
|
+
if (cachedNative !== void 0) return cachedNative;
|
|
11514
|
+
try {
|
|
11515
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
11516
|
+
const candidates = ["../native/h3-runner/index.js", "../../native/h3-runner/index.js"];
|
|
11517
|
+
let native = null;
|
|
11518
|
+
for (const specifier of candidates) {
|
|
11519
|
+
try {
|
|
11520
|
+
native = nodeRequire(specifier);
|
|
11521
|
+
if (native) break;
|
|
11522
|
+
} catch {
|
|
11523
|
+
}
|
|
11524
|
+
}
|
|
11525
|
+
if (!native) {
|
|
11526
|
+
cachedNative = null;
|
|
11527
|
+
return null;
|
|
11528
|
+
}
|
|
11529
|
+
cachedNative = native.available ? native : null;
|
|
11530
|
+
} catch {
|
|
11531
|
+
cachedNative = null;
|
|
11532
|
+
}
|
|
11533
|
+
return cachedNative;
|
|
11534
|
+
}
|
|
11535
|
+
function detectHttp3Support() {
|
|
11536
|
+
const native = loadNative();
|
|
11537
|
+
if (native) {
|
|
11538
|
+
return {
|
|
11539
|
+
available: true,
|
|
11540
|
+
reason: `native HTTP/3 addon loaded (${native.platformArchKey()})`
|
|
11541
|
+
};
|
|
11542
|
+
}
|
|
11543
|
+
return {
|
|
11544
|
+
available: false,
|
|
11545
|
+
reason: `no native HTTP/3 addon for this platform/architecture (Node ${process.version}, see native/h3-runner/README.md for which platforms are built). Falling back to HTTP/2.`
|
|
11546
|
+
};
|
|
11547
|
+
}
|
|
11548
|
+
function resetHttp3DetectionCache() {
|
|
11549
|
+
cachedNative = void 0;
|
|
11550
|
+
}
|
|
11551
|
+
var DEFAULT_SESSIONS_PER_ADDRESS2 = 4;
|
|
11552
|
+
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
11553
|
+
var DEFAULT_CONNECT_TIMEOUT_MS = 2e3;
|
|
11554
|
+
var Http3Transport = class {
|
|
11555
|
+
protocol = "h3";
|
|
11556
|
+
native;
|
|
11557
|
+
defaultSessionsPerAddress;
|
|
11558
|
+
resolver;
|
|
11559
|
+
defaultTimeoutMs;
|
|
11560
|
+
connectTimeoutMs;
|
|
11561
|
+
insecure;
|
|
11562
|
+
port;
|
|
11563
|
+
pools = /* @__PURE__ */ new Map();
|
|
11564
|
+
constructor(options = {}) {
|
|
11565
|
+
const native = loadNative();
|
|
11566
|
+
if (!native) {
|
|
11567
|
+
throw new Error("Http3Transport: no native HTTP/3 addon available on this platform");
|
|
11568
|
+
}
|
|
11569
|
+
this.native = native;
|
|
11570
|
+
this.defaultSessionsPerAddress = options.defaultSessionsPerAddress ?? DEFAULT_SESSIONS_PER_ADDRESS2;
|
|
11571
|
+
this.resolver = options.resolver ?? new DnsAddressResolver();
|
|
11572
|
+
this.defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
11573
|
+
this.connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
11574
|
+
this.insecure = options.insecure ?? false;
|
|
11575
|
+
this.port = options.port ?? 443;
|
|
11576
|
+
}
|
|
11577
|
+
prewarm(regionHostname2, sessionsPerAddress) {
|
|
11578
|
+
this.poolFor(regionHostname2, sessionsPerAddress);
|
|
11579
|
+
}
|
|
11580
|
+
async addressCount(regionHostname2) {
|
|
11581
|
+
const addresses = await this.poolFor(regionHostname2).addresses;
|
|
11582
|
+
return addresses.length;
|
|
11583
|
+
}
|
|
11584
|
+
async ready(regionHostname2, addressIndex) {
|
|
11585
|
+
await this.acquireHandle(regionHostname2, addressIndex);
|
|
11586
|
+
}
|
|
11587
|
+
async request(regionHostname2, addressIndex, authority, path, init) {
|
|
11588
|
+
const { address, handle, reconnect } = await this.acquireHandle(regionHostname2, addressIndex);
|
|
11589
|
+
return this.sendRequest(regionHostname2, address, authority, path, handle, init, reconnect);
|
|
11590
|
+
}
|
|
11591
|
+
/**
|
|
11592
|
+
* The native addon has no streaming request support yet - buffers the
|
|
11593
|
+
* whole response (same as request()) and hands it back as a single
|
|
11594
|
+
* chunk. Correct, not incrementally streamed; see transport.ts's doc
|
|
11595
|
+
* comment on streamRequest.
|
|
11596
|
+
*/
|
|
11597
|
+
async streamRequest(regionHostname2, addressIndex, authority, path, init) {
|
|
11598
|
+
const response = await this.request(regionHostname2, addressIndex, authority, path, init);
|
|
11599
|
+
return { status: response.status, headers: response.headers, chunks: singleChunk(response.body) };
|
|
11600
|
+
}
|
|
11601
|
+
/** Shared by request() and ready() - the latter just discards the handle. */
|
|
11602
|
+
async acquireHandle(regionHostname2, addressIndex) {
|
|
11603
|
+
const pool = this.poolFor(regionHostname2);
|
|
11604
|
+
const addresses = await pool.addresses;
|
|
11605
|
+
const n = addresses.length;
|
|
11606
|
+
const addrIdx = (addressIndex % n + n) % n;
|
|
11607
|
+
const address = addresses[addrIdx];
|
|
11608
|
+
const groups = await pool.groups;
|
|
11609
|
+
const group = groups[addrIdx];
|
|
11610
|
+
if (group.length === 0) {
|
|
11611
|
+
throw new Error(
|
|
11612
|
+
`Http3Transport: ${regionHostname2} address ${addrIdx} has zero sessions configured (prewarm/request with sessionsPerAddress > 0 first)`
|
|
11613
|
+
);
|
|
11614
|
+
}
|
|
11615
|
+
const cursor = pool.nextSlot.get(addrIdx) ?? 0;
|
|
11616
|
+
pool.nextSlot.set(addrIdx, (cursor + 1) % group.length);
|
|
11617
|
+
let handle;
|
|
11618
|
+
try {
|
|
11619
|
+
handle = await group[cursor];
|
|
11620
|
+
} catch (error) {
|
|
11621
|
+
throw new RunnerConnectError(regionHostname2, address, error);
|
|
11622
|
+
}
|
|
11623
|
+
const reconnect = () => {
|
|
11624
|
+
const fresh = this.connectSession(regionHostname2, address);
|
|
11625
|
+
group[cursor] = fresh;
|
|
11626
|
+
return fresh;
|
|
11627
|
+
};
|
|
11628
|
+
return { address, handle, reconnect };
|
|
11629
|
+
}
|
|
11630
|
+
async close() {
|
|
11631
|
+
const pools = [...this.pools.values()];
|
|
11632
|
+
this.pools.clear();
|
|
11633
|
+
for (const pool of pools) {
|
|
11634
|
+
const groups = await pool.groups.catch(() => []);
|
|
11635
|
+
for (const group of groups) {
|
|
11636
|
+
for (const slot of group) {
|
|
11637
|
+
try {
|
|
11638
|
+
const handle = await slot;
|
|
11639
|
+
await this.native.h3Close(handle);
|
|
11640
|
+
} catch {
|
|
11641
|
+
}
|
|
11642
|
+
}
|
|
11643
|
+
}
|
|
11644
|
+
}
|
|
11645
|
+
}
|
|
11646
|
+
poolFor(regionHostname2, sessionsPerAddress) {
|
|
11647
|
+
let pool = this.pools.get(regionHostname2);
|
|
11648
|
+
if (!pool) {
|
|
11649
|
+
pool = this.buildPool(regionHostname2, sessionsPerAddress ?? this.defaultSessionsPerAddress);
|
|
11650
|
+
this.pools.set(regionHostname2, pool);
|
|
11651
|
+
}
|
|
11652
|
+
return pool;
|
|
11653
|
+
}
|
|
11654
|
+
buildPool(regionHostname2, sessionsPerAddress) {
|
|
11655
|
+
const addresses = this.resolver.resolve(regionHostname2);
|
|
11656
|
+
const groups = addresses.then(
|
|
11657
|
+
(resolved) => resolved.map(
|
|
11658
|
+
(address) => Array.from(
|
|
11659
|
+
{ length: sessionsPerAddress },
|
|
11660
|
+
() => markHandled2(this.connectSession(regionHostname2, address))
|
|
11661
|
+
)
|
|
11662
|
+
)
|
|
11663
|
+
);
|
|
11664
|
+
markHandled2(addresses);
|
|
11665
|
+
markHandled2(groups);
|
|
11666
|
+
return { addresses, groups, nextSlot: /* @__PURE__ */ new Map() };
|
|
11667
|
+
}
|
|
11668
|
+
connectSession(regionHostname2, address) {
|
|
11669
|
+
return this.native.h3Connect(address, regionHostname2, this.port, this.insecure, this.connectTimeoutMs);
|
|
11670
|
+
}
|
|
11671
|
+
async sendRequest(regionHostname2, address, authority, path, handle, init, reconnect) {
|
|
11672
|
+
const timeoutMs = init.timeoutMs ?? this.defaultTimeoutMs;
|
|
11673
|
+
const body5 = init.body !== void 0 ? Buffer.from(init.body) : void 0;
|
|
11674
|
+
const attempt = async (activeHandle) => {
|
|
11675
|
+
const requestPromise = this.native.h3Request(
|
|
11676
|
+
activeHandle,
|
|
11677
|
+
init.method,
|
|
11678
|
+
authority,
|
|
11679
|
+
path,
|
|
11680
|
+
init.headers ?? {},
|
|
11681
|
+
body5
|
|
11682
|
+
);
|
|
11683
|
+
const result = await raceWithTimeout(
|
|
11684
|
+
requestPromise,
|
|
11685
|
+
timeoutMs,
|
|
11686
|
+
new RunnerTimeoutError(regionHostname2, path, timeoutMs)
|
|
11687
|
+
);
|
|
11688
|
+
return { status: result.status, headers: result.headers, body: result.body.toString("utf8") };
|
|
11689
|
+
};
|
|
11690
|
+
try {
|
|
11691
|
+
return await attempt(handle);
|
|
11692
|
+
} catch (error) {
|
|
11693
|
+
let fresh;
|
|
11694
|
+
try {
|
|
11695
|
+
fresh = await reconnect();
|
|
11696
|
+
} catch (reconnectError) {
|
|
11697
|
+
throw new RunnerConnectError(regionHostname2, address, reconnectError);
|
|
11698
|
+
}
|
|
11699
|
+
return attempt(fresh);
|
|
11700
|
+
}
|
|
11701
|
+
}
|
|
11702
|
+
};
|
|
11703
|
+
function raceWithTimeout(promise, timeoutMs, timeoutError) {
|
|
11704
|
+
return new Promise((resolve, reject) => {
|
|
11705
|
+
const timer = setTimeout(() => reject(timeoutError), timeoutMs);
|
|
11706
|
+
promise.then(
|
|
11707
|
+
(value) => {
|
|
11708
|
+
clearTimeout(timer);
|
|
11709
|
+
resolve(value);
|
|
11710
|
+
},
|
|
11711
|
+
(error) => {
|
|
11712
|
+
clearTimeout(timer);
|
|
11713
|
+
reject(error);
|
|
11714
|
+
}
|
|
11715
|
+
);
|
|
11716
|
+
});
|
|
11717
|
+
}
|
|
11718
|
+
async function* singleChunk(body5) {
|
|
11719
|
+
if (body5) yield body5;
|
|
11720
|
+
}
|
|
11721
|
+
|
|
11722
|
+
// src/runner/transport-factory.ts
|
|
11723
|
+
function createBestTransport(options = {}) {
|
|
11724
|
+
const http3 = detectHttp3Support();
|
|
11725
|
+
if (!http3.available) {
|
|
11726
|
+
return { transport: new Http2Transport(options.http2), http3 };
|
|
11727
|
+
}
|
|
11728
|
+
const transport = new RacingTransport(new Http2Transport(options.http2), new Http3Transport(options.http3));
|
|
11729
|
+
return { transport, http3 };
|
|
11730
|
+
}
|
|
11731
|
+
var RacingTransport = class {
|
|
11732
|
+
constructor(http22, http3) {
|
|
11733
|
+
this.http2 = http22;
|
|
11734
|
+
this.http3 = http3;
|
|
11735
|
+
}
|
|
11736
|
+
http2;
|
|
11737
|
+
http3;
|
|
11738
|
+
// Aspirational label for a transport that may use either protocol per
|
|
11739
|
+
// host - perHostProtocol() is the ground truth once a host has decided.
|
|
11740
|
+
protocol = "h3";
|
|
11741
|
+
protocolByHost = /* @__PURE__ */ new Map();
|
|
11742
|
+
watching = /* @__PURE__ */ new Set();
|
|
11743
|
+
/** The protocol a region host is currently using - "h2" until HTTP/3 proves itself, per the class doc comment. */
|
|
11744
|
+
perHostProtocol(regionHostname2) {
|
|
11745
|
+
return this.protocolByHost.get(regionHostname2) ?? "h2";
|
|
11746
|
+
}
|
|
11747
|
+
prewarm(regionHostname2, sessionsPerAddress) {
|
|
11748
|
+
this.http2.prewarm(regionHostname2, sessionsPerAddress);
|
|
11749
|
+
this.http3.prewarm(regionHostname2, sessionsPerAddress);
|
|
11750
|
+
this.watchHttp3(regionHostname2);
|
|
11751
|
+
}
|
|
11752
|
+
addressCount(regionHostname2) {
|
|
11753
|
+
return this.http2.addressCount(regionHostname2);
|
|
11754
|
+
}
|
|
11755
|
+
async ready(regionHostname2, addressIndex) {
|
|
11756
|
+
await this.transportFor(regionHostname2).ready(regionHostname2, addressIndex);
|
|
11757
|
+
}
|
|
11758
|
+
request(regionHostname2, addressIndex, authority, path, init) {
|
|
11759
|
+
return this.transportFor(regionHostname2).request(regionHostname2, addressIndex, authority, path, init);
|
|
11760
|
+
}
|
|
11761
|
+
streamRequest(regionHostname2, addressIndex, authority, path, init) {
|
|
11762
|
+
return this.transportFor(regionHostname2).streamRequest(regionHostname2, addressIndex, authority, path, init);
|
|
11763
|
+
}
|
|
11764
|
+
async close() {
|
|
11765
|
+
await Promise.all([this.http2.close(), this.http3.close()]);
|
|
11766
|
+
}
|
|
11767
|
+
/** Whatever protocol is decided for regionHostname right now - never waits. */
|
|
11768
|
+
transportFor(regionHostname2) {
|
|
11769
|
+
this.watchHttp3(regionHostname2);
|
|
11770
|
+
return this.perHostProtocol(regionHostname2) === "h3" ? this.http3 : this.http2;
|
|
11771
|
+
}
|
|
11772
|
+
/**
|
|
11773
|
+
* Starts (once per host) a background wait on HTTP/3's own readiness
|
|
11774
|
+
* for address 0 - connection setup, not a request - and promotes the
|
|
11775
|
+
* host to "h3" the moment it resolves. Never awaited by any caller;
|
|
11776
|
+
* purely a side effect that later `transportFor` calls observe.
|
|
11777
|
+
*/
|
|
11778
|
+
watchHttp3(regionHostname2) {
|
|
11779
|
+
if (this.watching.has(regionHostname2)) return;
|
|
11780
|
+
this.watching.add(regionHostname2);
|
|
11781
|
+
this.http3.ready(regionHostname2, 0).then(
|
|
11782
|
+
() => this.protocolByHost.set(regionHostname2, "h3"),
|
|
11783
|
+
() => this.protocolByHost.set(regionHostname2, "h2")
|
|
11784
|
+
// failed outright - stay on HTTP/2 for this host
|
|
11785
|
+
);
|
|
11786
|
+
}
|
|
11787
|
+
};
|
|
11788
|
+
|
|
11789
|
+
// src/runner/errors.ts
|
|
11790
|
+
var RunnerError = class _RunnerError extends Error {
|
|
11791
|
+
status;
|
|
11792
|
+
code;
|
|
11793
|
+
runnerUrl;
|
|
11794
|
+
/** Only meaningful for the nested "platform"/envelope shape - undefined for the flat C2 shape. */
|
|
11795
|
+
retryable;
|
|
11796
|
+
constructor(status, code, options = {}) {
|
|
11797
|
+
super(options.message ?? `runner request failed with ${status} ${code}`);
|
|
11798
|
+
this.name = "RunnerError";
|
|
11799
|
+
this.status = status;
|
|
11800
|
+
this.code = code;
|
|
11801
|
+
this.runnerUrl = options.runnerUrl;
|
|
11802
|
+
this.retryable = options.retryable;
|
|
11803
|
+
}
|
|
11804
|
+
static fromResponse(status, body5) {
|
|
11805
|
+
let parsed = {};
|
|
11806
|
+
try {
|
|
11807
|
+
parsed = JSON.parse(body5);
|
|
11808
|
+
} catch {
|
|
11809
|
+
}
|
|
11810
|
+
if (typeof parsed.error === "string") {
|
|
11811
|
+
return new _RunnerError(status, parsed.error, { runnerUrl: parsed.runner_url });
|
|
11812
|
+
}
|
|
11813
|
+
if (parsed.error && typeof parsed.error === "object") {
|
|
11814
|
+
return new _RunnerError(status, parsed.error.code || `http_${status}`, {
|
|
11815
|
+
message: parsed.error.message,
|
|
11816
|
+
retryable: parsed.error.retryable
|
|
11817
|
+
});
|
|
11818
|
+
}
|
|
11819
|
+
return new _RunnerError(status, `http_${status}`);
|
|
11820
|
+
}
|
|
11821
|
+
get isMisdirected() {
|
|
11822
|
+
return this.status === 421;
|
|
11823
|
+
}
|
|
11824
|
+
/**
|
|
11825
|
+
* C2 (revised): admission-cap-reached ("runtime_busy") moved from 503 to
|
|
11826
|
+
* 429 with `Retry-After: 0`; `feed_stale` stayed on 503. Checked by
|
|
11827
|
+
* code, not status, so this stays correct regardless of which of the
|
|
11828
|
+
* two carries it.
|
|
11829
|
+
*/
|
|
11830
|
+
get isBusy() {
|
|
11831
|
+
return this.code === "runtime_busy" || this.code === "feed_stale";
|
|
11832
|
+
}
|
|
11833
|
+
/** C2: the runner doesn't serve this create shape at all - send it to api.miosa.ai instead. */
|
|
11834
|
+
get isUnsupportedRequest() {
|
|
11835
|
+
return this.code === "RUNNER_UNSUPPORTED_REQUEST";
|
|
11836
|
+
}
|
|
11837
|
+
};
|
|
11838
|
+
|
|
11839
|
+
// src/runner/envelope.ts
|
|
11840
|
+
function encodeSomaBytes(content) {
|
|
11841
|
+
const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
11842
|
+
const maybeBuffer = globalThis.Buffer;
|
|
11843
|
+
if (maybeBuffer) return maybeBuffer.from(bytes).toString("base64");
|
|
11844
|
+
let bin = "";
|
|
11845
|
+
for (const byte of bytes) bin += String.fromCharCode(byte);
|
|
11846
|
+
return btoa(bin);
|
|
11847
|
+
}
|
|
11848
|
+
function decodeSomaBytes(bytes) {
|
|
11849
|
+
const maybeBuffer = globalThis.Buffer;
|
|
11850
|
+
if (maybeBuffer) return maybeBuffer.from(bytes.data, "base64");
|
|
11851
|
+
const binary = atob(bytes.data);
|
|
11852
|
+
const out = new Uint8Array(binary.length);
|
|
11853
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
11854
|
+
return out;
|
|
11855
|
+
}
|
|
11856
|
+
function hyphenateInstanceId(instanceId) {
|
|
11857
|
+
if (instanceId.length !== 32 || instanceId.includes("-")) return instanceId;
|
|
11858
|
+
return [instanceId.slice(0, 8), instanceId.slice(8, 12), instanceId.slice(12, 16), instanceId.slice(16, 20), instanceId.slice(20)].join(
|
|
11859
|
+
"-"
|
|
11860
|
+
);
|
|
11861
|
+
}
|
|
11862
|
+
function unwrapSomaEnvelope(httpStatus, body5) {
|
|
11863
|
+
let envelope = {};
|
|
11864
|
+
try {
|
|
11865
|
+
envelope = JSON.parse(body5 || "{}");
|
|
11866
|
+
} catch {
|
|
11867
|
+
throw new RunnerError(httpStatus, `http_${httpStatus}`, {
|
|
11868
|
+
message: "runner response was not a valid soma.api.v1 envelope"
|
|
11869
|
+
});
|
|
11870
|
+
}
|
|
11871
|
+
if (envelope.status === "error" || envelope.result === null || envelope.result === void 0) {
|
|
11872
|
+
const detail = envelope.error;
|
|
11873
|
+
throw new RunnerError(httpStatus, detail?.code || `http_${httpStatus}`, {
|
|
11874
|
+
message: detail?.message,
|
|
11875
|
+
retryable: detail?.retryable
|
|
11876
|
+
});
|
|
11877
|
+
}
|
|
11878
|
+
return envelope.result;
|
|
11879
|
+
}
|
|
11880
|
+
|
|
11881
|
+
// src/runner/runner-client.ts
|
|
11882
|
+
var DEFAULT_SESSIONS_PER_ADDRESS3 = 8;
|
|
11883
|
+
function createSupportedByRunner(params) {
|
|
11884
|
+
const hasTemplate = params["template_id"] !== void 0 || params["templateId"] !== void 0 || params["image"] !== void 0;
|
|
11885
|
+
const hasSnapshot = params["snapshot_id"] !== void 0 || params["snapshotId"] !== void 0 || params["source_snapshot_path"] !== void 0 || params["sourceSnapshotPath"] !== void 0;
|
|
11886
|
+
const hasCwd = params["cwd"] !== void 0 || params["entrypoint"] !== void 0;
|
|
11887
|
+
const isPersistent = params["persistent"] === true || params["always_on"] === true || params["alwaysOn"] === true;
|
|
11888
|
+
const size = params["size"];
|
|
11889
|
+
const isNonXs = size !== void 0 && size !== "xs";
|
|
11890
|
+
return !(hasTemplate || hasSnapshot || hasCwd || isPersistent || isNonXs);
|
|
11891
|
+
}
|
|
11892
|
+
var RunnerClient = class {
|
|
11893
|
+
region;
|
|
11894
|
+
hostname;
|
|
11895
|
+
baseDomain;
|
|
11896
|
+
apiKey;
|
|
11897
|
+
sessionsPerAddress;
|
|
11898
|
+
transport;
|
|
11899
|
+
fallbackCreate;
|
|
11900
|
+
/** tag -> which of the region's resolved addresses answers for it, learned from /healthz. */
|
|
11901
|
+
addressIndexByTag = /* @__PURE__ */ new Map();
|
|
11902
|
+
/** sandbox id -> tag, set only once a 421 corrects the id-derived guess. */
|
|
11903
|
+
correctedTag = /* @__PURE__ */ new Map();
|
|
11904
|
+
constructor(options) {
|
|
11905
|
+
this.apiKey = options.apiKey;
|
|
11906
|
+
this.region = options.region ?? regionFromApiKey(options.apiKey);
|
|
11907
|
+
this.baseDomain = options.baseDomain ?? DEFAULT_RUNNER_BASE_DOMAIN;
|
|
11908
|
+
this.hostname = regionHostname(this.region, this.baseDomain);
|
|
11909
|
+
this.sessionsPerAddress = options.sessionsPerAddress ?? DEFAULT_SESSIONS_PER_ADDRESS3;
|
|
11910
|
+
this.transport = options.transport ?? createBestTransport().transport;
|
|
11911
|
+
this.fallbackCreate = options.fallbackCreate;
|
|
11912
|
+
this.transport.prewarm(this.hostname, this.sessionsPerAddress);
|
|
11913
|
+
void this.discoverTags();
|
|
11914
|
+
}
|
|
11915
|
+
get protocol() {
|
|
11916
|
+
return this.transport.protocol;
|
|
11917
|
+
}
|
|
11918
|
+
/** The tag a 421 has corrected sandboxId to, if any. */
|
|
11919
|
+
correctedTagFor(sandboxId) {
|
|
11920
|
+
return this.correctedTag.get(sandboxId);
|
|
11921
|
+
}
|
|
11922
|
+
async healthzByIndex(addressIndex) {
|
|
11923
|
+
const res = await this.transport.request(this.hostname, addressIndex, this.hostname, "/healthz", {
|
|
11924
|
+
method: "GET"
|
|
11925
|
+
});
|
|
11926
|
+
return JSON.parse(res.body || "{}");
|
|
11927
|
+
}
|
|
11928
|
+
/**
|
|
11929
|
+
* POST /api/v1/sandboxes against the region hostname. Create shapes the
|
|
11930
|
+
* runner doesn't serve (C2) go straight to `fallbackCreate` without
|
|
11931
|
+
* ever reaching the runner; a `400 RUNNER_UNSUPPORTED_REQUEST` the
|
|
11932
|
+
* runner returns anyway falls back the same way, once. On `503
|
|
11933
|
+
* runtime_busy`/`feed_stale` or a connect failure, retries on the next
|
|
11934
|
+
* resolved IP, at most n-1 times (C6).
|
|
11935
|
+
*/
|
|
11936
|
+
async createSandbox(params = {}, opts = {}) {
|
|
11937
|
+
if (!createSupportedByRunner(params)) {
|
|
11938
|
+
return this.createViaFallback(params, opts);
|
|
11939
|
+
}
|
|
11940
|
+
const n = await this.transport.addressCount(this.hostname);
|
|
11941
|
+
if (n === 0) {
|
|
11942
|
+
throw new Error(`RunnerClient: no runner addresses for ${this.hostname}`);
|
|
11943
|
+
}
|
|
11944
|
+
let lastError;
|
|
11945
|
+
for (let attempt = 0; attempt < n; attempt++) {
|
|
11946
|
+
let res;
|
|
11947
|
+
try {
|
|
11948
|
+
res = await this.transport.request(this.hostname, attempt, this.hostname, "/api/v1/sandboxes", {
|
|
11949
|
+
method: "POST",
|
|
11950
|
+
headers: this.authHeaders(opts.idempotencyKey),
|
|
11951
|
+
body: JSON.stringify(params)
|
|
11952
|
+
});
|
|
11953
|
+
} catch (error) {
|
|
11954
|
+
lastError = error;
|
|
11955
|
+
continue;
|
|
11956
|
+
}
|
|
11957
|
+
if (res.status >= 400) {
|
|
11958
|
+
const error = RunnerError.fromResponse(res.status, res.body);
|
|
11959
|
+
if (error.isUnsupportedRequest) {
|
|
11960
|
+
return this.createViaFallback(params, opts);
|
|
11961
|
+
}
|
|
11962
|
+
if (error.isBusy) {
|
|
11963
|
+
lastError = error;
|
|
11964
|
+
continue;
|
|
11965
|
+
}
|
|
11966
|
+
throw error;
|
|
11967
|
+
}
|
|
11968
|
+
const data = res.body ? JSON.parse(res.body) : {};
|
|
11969
|
+
const runnerUrl = this.runnerUrlFrom(res, data);
|
|
11970
|
+
const id = typeof data["id"] === "string" ? data["id"] : void 0;
|
|
11971
|
+
if (runnerUrl !== void 0) {
|
|
11972
|
+
this.addressIndexByTag.set(tagFromUrl(runnerUrl), attempt);
|
|
11973
|
+
}
|
|
11974
|
+
return { id: id ?? "", runnerUrl: runnerUrl ?? "", data };
|
|
11975
|
+
}
|
|
11976
|
+
throw lastError instanceof Error ? lastError : new Error(`RunnerClient: createSandbox exhausted every address for ${this.hostname}`);
|
|
11977
|
+
}
|
|
11978
|
+
async exec(sandboxId, command, opts = {}) {
|
|
11979
|
+
return this.requestToSandbox(sandboxId, "POST", `/api/v1/sandboxes/${sandboxId}/exec`, {
|
|
11980
|
+
command,
|
|
11981
|
+
...opts
|
|
11982
|
+
});
|
|
11983
|
+
}
|
|
11984
|
+
/** POST /api/v1/sandboxes/{id}/exec/stream - server-sent events (C2). */
|
|
11985
|
+
async *execStream(sandboxId, command, opts = {}) {
|
|
11986
|
+
const streamRes = await this.sendToSandboxStream(
|
|
11987
|
+
sandboxId,
|
|
11988
|
+
"POST",
|
|
11989
|
+
`/api/v1/sandboxes/${sandboxId}/exec/stream`,
|
|
11990
|
+
{ command, ...opts }
|
|
11991
|
+
);
|
|
11992
|
+
if (streamRes.status >= 400) {
|
|
11993
|
+
let errorBody = "";
|
|
11994
|
+
for await (const chunk of streamRes.chunks) errorBody += chunk;
|
|
11995
|
+
throw RunnerError.fromResponse(streamRes.status, errorBody);
|
|
11996
|
+
}
|
|
11997
|
+
yield* parseSseEvents(streamRes.chunks);
|
|
11998
|
+
}
|
|
11999
|
+
/** Returns the sandbox's final usage: cpu_ms, lifetime_ms (C2). */
|
|
12000
|
+
async destroySandbox(sandboxId) {
|
|
12001
|
+
const result = await this.requestToSandbox(sandboxId, "DELETE", `/api/v1/sandboxes/${sandboxId}`);
|
|
12002
|
+
this.correctedTag.delete(sandboxId);
|
|
12003
|
+
return result;
|
|
12004
|
+
}
|
|
12005
|
+
// ─── C2b: per-sandbox routes (soma.api.v1 envelope) ──────────────────────
|
|
12006
|
+
async get(sandboxId, opts = {}) {
|
|
12007
|
+
const body5 = opts.operationId !== void 0 ? { operation_id: opts.operationId } : void 0;
|
|
12008
|
+
const result = await this.requestEnvelopeToSandbox(
|
|
12009
|
+
sandboxId,
|
|
12010
|
+
"GET",
|
|
12011
|
+
`/api/v1/sandboxes/${sandboxId}`,
|
|
12012
|
+
body5
|
|
12013
|
+
);
|
|
12014
|
+
return { ...result, instance_id: hyphenateInstanceId(result.instance_id) };
|
|
12015
|
+
}
|
|
12016
|
+
async stop(sandboxId, opts = {}) {
|
|
12017
|
+
const body5 = opts.operationId !== void 0 ? { operation_id: opts.operationId } : void 0;
|
|
12018
|
+
const result = await this.requestEnvelopeToSandbox(
|
|
12019
|
+
sandboxId,
|
|
12020
|
+
"POST",
|
|
12021
|
+
`/api/v1/sandboxes/${sandboxId}/stop`,
|
|
12022
|
+
body5
|
|
12023
|
+
);
|
|
12024
|
+
return { ...result, instance_id: hyphenateInstanceId(result.instance_id) };
|
|
12025
|
+
}
|
|
12026
|
+
/**
|
|
12027
|
+
* PATCH /api/v1/sandboxes/{id} {"timeout":seconds} - resets the idle
|
|
12028
|
+
* timeout (default 300s; every call to the sandbox already resets it).
|
|
12029
|
+
* Flat C2 response shape (C2b line 84), not the envelope - errors still
|
|
12030
|
+
* use the platform shape (400 INVALID_PARAM, 404 NOT_FOUND), which
|
|
12031
|
+
* RunnerError.fromResponse already parses either way.
|
|
12032
|
+
*/
|
|
12033
|
+
async setTimeout(sandboxId, seconds) {
|
|
12034
|
+
const result = await this.requestToSandbox(sandboxId, "PATCH", `/api/v1/sandboxes/${sandboxId}`, {
|
|
12035
|
+
timeout: seconds
|
|
12036
|
+
});
|
|
12037
|
+
return result;
|
|
12038
|
+
}
|
|
12039
|
+
/**
|
|
12040
|
+
* GET /api/v1/sandboxes - scoped to ONE runner host (C2b: "the calling
|
|
12041
|
+
* tenant's sandboxes on this runner"), not the whole region. Defaults
|
|
12042
|
+
* to address 0; pass `tag` to target the host currently known for that
|
|
12043
|
+
* tag, or `addressIndex` directly.
|
|
12044
|
+
*/
|
|
12045
|
+
async list(opts = {}) {
|
|
12046
|
+
const addressIndex = opts.tag !== void 0 ? await this.resolveAddressIndexForTag(opts.tag) : opts.addressIndex ?? 0;
|
|
12047
|
+
const res = await this.transport.request(this.hostname, addressIndex, this.hostname, "/api/v1/sandboxes", {
|
|
12048
|
+
method: "GET",
|
|
12049
|
+
headers: this.authHeaders()
|
|
12050
|
+
});
|
|
12051
|
+
if (res.status >= 400) throw RunnerError.fromResponse(res.status, res.body);
|
|
12052
|
+
const result = unwrapSomaEnvelope(res.status, res.body);
|
|
12053
|
+
return {
|
|
12054
|
+
...result,
|
|
12055
|
+
sandboxes: result.sandboxes.map((entry) => ({ ...entry, instance_id: hyphenateInstanceId(entry.instance_id) }))
|
|
12056
|
+
};
|
|
12057
|
+
}
|
|
12058
|
+
filesystem = {
|
|
12059
|
+
read: (sandboxId, path) => this.filesystemOp(sandboxId, "read", { path }),
|
|
12060
|
+
write: (sandboxId, path, content) => this.filesystemOp(sandboxId, "write", { path, content: encodeSomaBytes(content) }),
|
|
12061
|
+
list: (sandboxId, path) => this.filesystemOp(sandboxId, "list", { path }),
|
|
12062
|
+
exists: (sandboxId, path) => this.filesystemOp(sandboxId, "exists", { path }),
|
|
12063
|
+
remove: (sandboxId, path, recursive) => this.filesystemOp(sandboxId, "remove", { path, ...recursive !== void 0 ? { recursive } : {} }),
|
|
12064
|
+
mkdir: (sandboxId, path) => this.filesystemOp(sandboxId, "mkdir", { path }),
|
|
12065
|
+
/** Decodes a read() result's base64 `content` into bytes. */
|
|
12066
|
+
decode: decodeSomaBytes
|
|
12067
|
+
};
|
|
12068
|
+
terminal = {
|
|
12069
|
+
open: (sandboxId, columns, rows) => this.terminalOp(sandboxId, "open", { columns, rows }),
|
|
12070
|
+
resize: (sandboxId, columns, rows) => this.terminalOp(sandboxId, "resize", { columns, rows }),
|
|
12071
|
+
write: (sandboxId, input) => this.terminalOp(sandboxId, "write", { input: encodeSomaBytes(input) }),
|
|
12072
|
+
read: (sandboxId, waitMs) => this.terminalOp(sandboxId, "read", waitMs !== void 0 ? { wait_ms: waitMs } : {}),
|
|
12073
|
+
close: (sandboxId) => this.terminalOp(sandboxId, "close", {}),
|
|
12074
|
+
/** Decodes a read() result's base64 `output` into bytes. */
|
|
12075
|
+
decode: decodeSomaBytes
|
|
12076
|
+
};
|
|
12077
|
+
/**
|
|
12078
|
+
* Low-level escape hatch: sends method/path straight to sandboxId's
|
|
12079
|
+
* runner host, with the same tag-routing, 421-follow and auth every
|
|
12080
|
+
* named method above uses, parsing the response as the flat C2 shape
|
|
12081
|
+
* (not the C2b envelope - use filesystem/terminal/get/stop/list, or
|
|
12082
|
+
* requestEnvelope() for an un-wrapped route not named here yet).
|
|
12083
|
+
*/
|
|
12084
|
+
request(sandboxId, method, path, body5) {
|
|
12085
|
+
return this.requestToSandbox(sandboxId, method, path, body5);
|
|
12086
|
+
}
|
|
12087
|
+
/** Same as request(), but unwraps the response as a C2b soma.api.v1 envelope. */
|
|
12088
|
+
requestEnvelope(sandboxId, method, path, body5) {
|
|
12089
|
+
return this.requestEnvelopeToSandbox(sandboxId, method, path, body5);
|
|
12090
|
+
}
|
|
12091
|
+
async close() {
|
|
12092
|
+
await this.transport.close();
|
|
12093
|
+
}
|
|
12094
|
+
filesystemOp(sandboxId, op, body5) {
|
|
12095
|
+
return this.requestEnvelopeToSandbox(sandboxId, "POST", `/api/v1/sandboxes/${sandboxId}/filesystem/${op}`, body5);
|
|
12096
|
+
}
|
|
12097
|
+
terminalOp(sandboxId, op, body5) {
|
|
12098
|
+
return this.requestEnvelopeToSandbox(sandboxId, "POST", `/api/v1/sandboxes/${sandboxId}/terminal/${op}`, body5);
|
|
12099
|
+
}
|
|
12100
|
+
async createViaFallback(params, opts) {
|
|
12101
|
+
if (!this.fallbackCreate) {
|
|
12102
|
+
throw new Error(
|
|
12103
|
+
"RunnerClient: this create isn't served by the runner (C2) and no fallbackCreate was configured - wire RunnerClientOptions.fallbackCreate to the control-plane create path (api.miosa.ai)"
|
|
12104
|
+
);
|
|
12105
|
+
}
|
|
12106
|
+
return this.fallbackCreate(params, opts);
|
|
12107
|
+
}
|
|
12108
|
+
/**
|
|
12109
|
+
* Learns which resolved region address answers for which tag, via each
|
|
12110
|
+
* one's own unauthenticated `/healthz` (C2) - best effort, never awaited
|
|
12111
|
+
* by the constructor. A tag missed here (a host that joined after this
|
|
12112
|
+
* client started, or a transient `/healthz` failure) just costs a
|
|
12113
|
+
* repeat of this same probe on that sandbox's first real request,
|
|
12114
|
+
* instead of none at all.
|
|
12115
|
+
*/
|
|
12116
|
+
async discoverTags() {
|
|
12117
|
+
try {
|
|
12118
|
+
const n = await this.transport.addressCount(this.hostname);
|
|
12119
|
+
const probes = await Promise.allSettled(
|
|
12120
|
+
Array.from({ length: n }, (_, i) => this.healthzByIndex(i))
|
|
12121
|
+
);
|
|
12122
|
+
probes.forEach((probe, addressIndex) => {
|
|
12123
|
+
if (probe.status === "fulfilled" && probe.value.tag) {
|
|
12124
|
+
this.addressIndexByTag.set(probe.value.tag, addressIndex);
|
|
12125
|
+
}
|
|
12126
|
+
});
|
|
12127
|
+
} catch {
|
|
12128
|
+
}
|
|
12129
|
+
}
|
|
12130
|
+
/**
|
|
12131
|
+
* Sends method/path to sandboxId's runner host - the id-derived tag, or
|
|
12132
|
+
* a previously 421-corrected one - following a single `421 misdirected`
|
|
12133
|
+
* to the `runner_url` it names (C6). A connect failure here is not
|
|
12134
|
+
* retried across other hosts: a sandbox has exactly one owner. Returns
|
|
12135
|
+
* the raw response; requestToSandbox/requestEnvelopeToSandbox each
|
|
12136
|
+
* parse it according to their route's response shape (C2 vs C2b).
|
|
12137
|
+
*/
|
|
12138
|
+
async sendToSandbox(sandboxId, method, path, body5) {
|
|
12139
|
+
let tag = this.correctedTag.get(sandboxId) ?? sandboxTag(sandboxId);
|
|
12140
|
+
let addressIndex = await this.resolveAddressIndexForTag(tag);
|
|
12141
|
+
let followed = false;
|
|
12142
|
+
for (; ; ) {
|
|
12143
|
+
const authority = tagHostname(this.region, tag, this.baseDomain);
|
|
12144
|
+
const res = await this.transport.request(this.hostname, addressIndex, authority, path, {
|
|
12145
|
+
method,
|
|
12146
|
+
headers: this.authHeaders(),
|
|
12147
|
+
...body5 !== void 0 ? { body: JSON.stringify(body5) } : {}
|
|
12148
|
+
});
|
|
12149
|
+
if (res.status === 421 && !followed) {
|
|
12150
|
+
const misdirected = RunnerError.fromResponse(res.status, res.body);
|
|
12151
|
+
if (misdirected.runnerUrl) {
|
|
12152
|
+
tag = tagFromUrl(misdirected.runnerUrl);
|
|
12153
|
+
addressIndex = await this.resolveAddressIndexForTag(tag);
|
|
12154
|
+
followed = true;
|
|
12155
|
+
continue;
|
|
12156
|
+
}
|
|
12157
|
+
}
|
|
12158
|
+
if (followed) this.correctedTag.set(sandboxId, tag);
|
|
12159
|
+
return res;
|
|
12160
|
+
}
|
|
12161
|
+
}
|
|
12162
|
+
/** Same routing as sendToSandbox, for a streaming (SSE) response. */
|
|
12163
|
+
async sendToSandboxStream(sandboxId, method, path, body5) {
|
|
12164
|
+
let tag = this.correctedTag.get(sandboxId) ?? sandboxTag(sandboxId);
|
|
12165
|
+
let addressIndex = await this.resolveAddressIndexForTag(tag);
|
|
12166
|
+
let followed = false;
|
|
12167
|
+
for (; ; ) {
|
|
12168
|
+
const authority = tagHostname(this.region, tag, this.baseDomain);
|
|
12169
|
+
const streamRes = await this.transport.streamRequest(this.hostname, addressIndex, authority, path, {
|
|
12170
|
+
method,
|
|
12171
|
+
headers: this.authHeaders(),
|
|
12172
|
+
...body5 !== void 0 ? { body: JSON.stringify(body5) } : {}
|
|
12173
|
+
});
|
|
12174
|
+
if (streamRes.status === 421 && !followed) {
|
|
12175
|
+
let errorBody = "";
|
|
12176
|
+
for await (const chunk of streamRes.chunks) errorBody += chunk;
|
|
12177
|
+
const misdirected = RunnerError.fromResponse(streamRes.status, errorBody);
|
|
12178
|
+
if (misdirected.runnerUrl) {
|
|
12179
|
+
tag = tagFromUrl(misdirected.runnerUrl);
|
|
12180
|
+
addressIndex = await this.resolveAddressIndexForTag(tag);
|
|
12181
|
+
followed = true;
|
|
12182
|
+
continue;
|
|
12183
|
+
}
|
|
12184
|
+
if (followed) this.correctedTag.set(sandboxId, tag);
|
|
12185
|
+
return { status: streamRes.status, headers: streamRes.headers, chunks: singleChunk2(errorBody) };
|
|
12186
|
+
}
|
|
12187
|
+
if (followed) this.correctedTag.set(sandboxId, tag);
|
|
12188
|
+
return streamRes;
|
|
12189
|
+
}
|
|
12190
|
+
}
|
|
12191
|
+
/** C2's flat shape: parses >=400 with RunnerError, else JSON.parses the raw body. */
|
|
12192
|
+
async requestToSandbox(sandboxId, method, path, body5) {
|
|
12193
|
+
const res = await this.sendToSandbox(sandboxId, method, path, body5);
|
|
12194
|
+
if (res.status >= 400) throw RunnerError.fromResponse(res.status, res.body);
|
|
12195
|
+
return res.body ? JSON.parse(res.body) : {};
|
|
12196
|
+
}
|
|
12197
|
+
/**
|
|
12198
|
+
* C2b's envelope shape: a >=400 response may still be the flat C2 shape
|
|
12199
|
+
* (401/403/429/503/421 happen before the envelope layer) - RunnerError
|
|
12200
|
+
* .fromResponse parses either, so it's used directly rather than
|
|
12201
|
+
* routing through the envelope parser. Only a <400 response is
|
|
12202
|
+
* unwrapped as a genuine soma.api.v1 envelope.
|
|
12203
|
+
*/
|
|
12204
|
+
async requestEnvelopeToSandbox(sandboxId, method, path, body5) {
|
|
12205
|
+
const res = await this.sendToSandbox(sandboxId, method, path, body5);
|
|
12206
|
+
if (res.status >= 400) throw RunnerError.fromResponse(res.status, res.body);
|
|
12207
|
+
return unwrapSomaEnvelope(res.status, res.body);
|
|
12208
|
+
}
|
|
12209
|
+
/**
|
|
12210
|
+
* `addressIndexByTag` first; if that tag hasn't been seen yet (a host
|
|
12211
|
+
* that joined after construction, or this is the very first request
|
|
12212
|
+
* racing the background discovery), refreshes discovery once and falls
|
|
12213
|
+
* back to address 0 as a last resort - the request itself still ends up
|
|
12214
|
+
* correct either way, via a 421 follow, just one round trip slower.
|
|
12215
|
+
*/
|
|
12216
|
+
async resolveAddressIndexForTag(tag) {
|
|
12217
|
+
const known = this.addressIndexByTag.get(tag);
|
|
12218
|
+
if (known !== void 0) return known;
|
|
12219
|
+
await this.discoverTags();
|
|
12220
|
+
return this.addressIndexByTag.get(tag) ?? 0;
|
|
12221
|
+
}
|
|
12222
|
+
runnerUrlFrom(res, data) {
|
|
12223
|
+
const header = res.headers["soma-runner-url"];
|
|
12224
|
+
if (header) return header;
|
|
12225
|
+
const fromBody = data["runner_url"];
|
|
12226
|
+
return typeof fromBody === "string" ? fromBody : void 0;
|
|
12227
|
+
}
|
|
12228
|
+
authHeaders(idempotencyKey11) {
|
|
12229
|
+
const headers = {
|
|
12230
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
12231
|
+
"content-type": "application/json",
|
|
12232
|
+
accept: "application/json"
|
|
12233
|
+
};
|
|
12234
|
+
if (idempotencyKey11) headers["idempotency-key"] = idempotencyKey11;
|
|
12235
|
+
return headers;
|
|
12236
|
+
}
|
|
12237
|
+
};
|
|
12238
|
+
function tagFromUrl(url) {
|
|
12239
|
+
let hostname;
|
|
12240
|
+
try {
|
|
12241
|
+
hostname = new URL(url).hostname;
|
|
12242
|
+
} catch {
|
|
12243
|
+
hostname = url;
|
|
12244
|
+
}
|
|
12245
|
+
return hostname.split(".")[0] ?? hostname;
|
|
12246
|
+
}
|
|
12247
|
+
async function* singleChunk2(body5) {
|
|
12248
|
+
if (body5) yield body5;
|
|
12249
|
+
}
|
|
12250
|
+
async function* parseSseEvents(chunks) {
|
|
12251
|
+
let buffer = "";
|
|
12252
|
+
for await (const chunk of chunks) {
|
|
12253
|
+
buffer += chunk;
|
|
12254
|
+
let newlineIdx;
|
|
12255
|
+
while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
|
|
12256
|
+
const line = buffer.slice(0, newlineIdx).replace(/\r$/, "");
|
|
12257
|
+
buffer = buffer.slice(newlineIdx + 1);
|
|
12258
|
+
if (line.startsWith("data:")) {
|
|
12259
|
+
const raw = line.slice(5).trim();
|
|
12260
|
+
if (raw === "" || raw === "[DONE]") continue;
|
|
12261
|
+
try {
|
|
12262
|
+
yield JSON.parse(raw);
|
|
12263
|
+
} catch {
|
|
12264
|
+
}
|
|
12265
|
+
}
|
|
12266
|
+
}
|
|
12267
|
+
}
|
|
12268
|
+
}
|
|
12269
|
+
|
|
11028
12270
|
// src/client.ts
|
|
11029
12271
|
var DEFAULT_BASE_URL = "https://api.miosa.ai/api/v1";
|
|
11030
12272
|
var DEFAULT_TIMEOUT2 = 3e4;
|
|
11031
12273
|
var DEFAULT_MAX_RETRIES2 = 3;
|
|
12274
|
+
function runnerModeFromEnv() {
|
|
12275
|
+
return typeof process !== "undefined" && typeof process.env !== "undefined" && process.env["MIOSA_RUNNER_MODE"] === "1";
|
|
12276
|
+
}
|
|
12277
|
+
function runnerRegionFromEnv() {
|
|
12278
|
+
if (typeof process === "undefined" || typeof process.env === "undefined") return void 0;
|
|
12279
|
+
return process.env["MIOSA_RUNNER_REGION"];
|
|
12280
|
+
}
|
|
11032
12281
|
var Miosa = class {
|
|
11033
12282
|
/** Per-workspace user roster — list, add, update role, remove. */
|
|
11034
12283
|
workspaceMembers;
|
|
@@ -11165,6 +12414,14 @@ var Miosa = class {
|
|
|
11165
12414
|
network;
|
|
11166
12415
|
/** Egress audit log — every outbound request, paginated query + tail. */
|
|
11167
12416
|
audit;
|
|
12417
|
+
/**
|
|
12418
|
+
* SOMA one-hop runner path (opt-in via `config.runner.enabled` or
|
|
12419
|
+
* `MIOSA_RUNNER_MODE=1`) - talks straight to `run-<region>.miosa.ai` for
|
|
12420
|
+
* sandbox create/exec/destroy, bypassing the control-plane hop. `undefined`
|
|
12421
|
+
* unless enabled; every other resource above is unaffected either way.
|
|
12422
|
+
* See ONE-HOP-RUNNER-DESIGN-2026-10-02.md and RUNNER-CONTRACTS-2026-10-02.md.
|
|
12423
|
+
*/
|
|
12424
|
+
runner;
|
|
11168
12425
|
http;
|
|
11169
12426
|
constructor(config) {
|
|
11170
12427
|
if (config.apiKey && config.accessToken) {
|
|
@@ -11249,6 +12506,24 @@ var Miosa = class {
|
|
|
11249
12506
|
this.secrets = new EgressSecrets(this.http);
|
|
11250
12507
|
this.network = new EgressNetwork(this.http);
|
|
11251
12508
|
this.audit = new EgressAudit(this.http);
|
|
12509
|
+
const runnerEnabled = config.runner?.enabled ?? runnerModeFromEnv();
|
|
12510
|
+
const runnerRegion = config.runner?.region ?? runnerRegionFromEnv();
|
|
12511
|
+
this.runner = runnerEnabled ? new RunnerClient({
|
|
12512
|
+
apiKey: credential,
|
|
12513
|
+
...runnerRegion ? { region: runnerRegion } : {},
|
|
12514
|
+
...config.runner?.baseDomain ? { baseDomain: config.runner.baseDomain } : {},
|
|
12515
|
+
...config.tenant ? { tenant: config.tenant } : {},
|
|
12516
|
+
// C2: creates the runner doesn't serve (template/image/snapshot/
|
|
12517
|
+
// cwd/persistent/non-xs) go through the existing control-plane
|
|
12518
|
+
// path instead of failing outright.
|
|
12519
|
+
fallbackCreate: async (params, fallbackOpts) => {
|
|
12520
|
+
const sandbox = await this.sandboxes.create({
|
|
12521
|
+
...params,
|
|
12522
|
+
...fallbackOpts.idempotencyKey ? { idempotencyKey: fallbackOpts.idempotencyKey } : {}
|
|
12523
|
+
});
|
|
12524
|
+
return { id: sandbox.id, runnerUrl: "", data: sandbox.data };
|
|
12525
|
+
}
|
|
12526
|
+
}) : void 0;
|
|
11252
12527
|
}
|
|
11253
12528
|
};
|
|
11254
12529
|
|
|
@@ -11740,6 +13015,6 @@ var AppAuth = class {
|
|
|
11740
13015
|
}
|
|
11741
13016
|
};
|
|
11742
13017
|
|
|
11743
|
-
export { AGENT_BUILD_KIND_SPECS, Admin, AgentDefinitions, AgentRuntimeProfiles, Analytics, ApiKeys, AppAuth, AppDocuments, AuditLog, AuthError, Benchmarks, BuilderSessions, Channels, Checkpoints, Cloud, CommandCenter, Community, Completions, Computer, ComputerAudit, ComputerAutoStop, ComputerConnectors, ComputerEnv, ComputerInbox, ComputerLogs, ComputerNetwork, ComputerOsa, ComputerPorts, ComputerSecrets, ComputerTerminal, ComputerVolumes, Computers, Connectors, Credits, CronJobs, DEFAULT_AGENT_BUILD_OUTPUT_ROOT, DEFAULT_AGENT_BUILD_PACKET_VERSION, Dashboard, Databases, DeploymentConnectors, DeploymentDomains, DeploymentReleases, DeploymentRuntimeInstances, DeploymentVersions, Deployments, Desktop, Devices, DockerDeploy, EgressAudit, EgressHostNotAllowedError, EgressNetwork, EgressSecrets, Email, EmailCampaigns, EmailInbox, EmailTemplates, Embeddings, Exec, ExternalKeys, Files, FlatCustomDomains, Forge, ForgeContractError, ForgePolicyViolationError, ForgeRepositories, ForgeStorageError, ForgeUnavailableError, Functions, HealthChecks, InstallationRequiredError, InsufficientCreditsError, Integrations, ManagedProviderBindingOnlyError, Mcp, Miosa, MiosaError, Models, NetworkError, NetworkPolicy, NotFoundError, OAuthFlow, OpenComputers, OrgInvites, OrganizationMembers, Organizations, ProjectAuth, ProjectIntegrations, ProjectNotLinkedError, ProviderDefaults, RateLimitError, Regions, RunGroups, Runs, RuntimeCapabilitiesResource, RuntimeEnv, SANDBOX_TEMPLATE, Sandbox, SandboxAudit, SandboxCommands, SandboxConnectors, SandboxEnv, SandboxEvents, SandboxFiles, SandboxNetwork, SandboxPreview, SandboxPreviews, SandboxSecrets, SandboxTags, SandboxTemplates, SandboxTerminal, Sandboxes, ScopeNotAllowedError, ScopedFs, Settings, SnapshotsStandalone, Storage, SubjectNotAllowedError, Templates, Tenant, TimeoutError, TokenRefreshFailedError, Usage, UserAuthorizationRequiredError, ValidationError, Volumes, Webhooks, WorkspaceInvites, WorkspaceMembers, createAgentBuildExecutionPacket, createAgentBuildExpectedOutputs, createAgentBuildPrompt, createBuildRunParams, getAgentBuildKindSpec, resolveAgentBuildKind, verifySignature };
|
|
13018
|
+
export { AGENT_BUILD_KIND_SPECS, Admin, AgentDefinitions, AgentRuntimeProfiles, Analytics, ApiKeys, AppAuth, AppDocuments, AuditLog, AuthError, Benchmarks, BuilderSessions, Channels, Checkpoints, Cloud, CommandCenter, Community, Completions, Computer, ComputerAudit, ComputerAutoStop, ComputerConnectors, ComputerEnv, ComputerInbox, ComputerLogs, ComputerNetwork, ComputerOsa, ComputerPorts, ComputerSecrets, ComputerTerminal, ComputerVolumes, Computers, Connectors, Credits, CronJobs, DEFAULT_AGENT_BUILD_OUTPUT_ROOT, DEFAULT_AGENT_BUILD_PACKET_VERSION, Dashboard, Databases, DeploymentConnectors, DeploymentDomains, DeploymentReleases, DeploymentRuntimeInstances, DeploymentVersions, Deployments, Desktop, Devices, DnsAddressResolver, DockerDeploy, EgressAudit, EgressHostNotAllowedError, EgressNetwork, EgressSecrets, Email, EmailCampaigns, EmailInbox, EmailTemplates, Embeddings, Exec, ExternalKeys, Files, FlatCustomDomains, Forge, ForgeContractError, ForgePolicyViolationError, ForgeRepositories, ForgeStorageError, ForgeUnavailableError, Functions, HealthChecks, Http2Transport, Http3Transport, InstallationRequiredError, InsufficientCreditsError, Integrations, ManagedProviderBindingOnlyError, Mcp, Miosa, MiosaError, Models, NetworkError, NetworkPolicy, NotFoundError, OAuthFlow, OpenComputers, OrgInvites, OrganizationMembers, Organizations, ProjectAuth, ProjectIntegrations, ProjectNotLinkedError, ProviderDefaults, RacingTransport, RateLimitError, Regions, RunGroups, RunnerClient, RunnerConnectError, RunnerError, RunnerTimeoutError, Runs, RuntimeCapabilitiesResource, RuntimeEnv, SANDBOX_TEMPLATE, Sandbox, SandboxAudit, SandboxCommands, SandboxConnectors, SandboxEnv, SandboxEvents, SandboxFiles, SandboxNetwork, SandboxPreview, SandboxPreviews, SandboxSecrets, SandboxTags, SandboxTemplates, SandboxTerminal, Sandboxes, ScopeNotAllowedError, ScopedFs, Settings, SnapshotsStandalone, StaticAddressResolver, Storage, SubjectNotAllowedError, Templates, Tenant, TimeoutError, TokenRefreshFailedError, Usage, UserAuthorizationRequiredError, ValidationError, Volumes, Webhooks, WorkspaceInvites, WorkspaceMembers, createAgentBuildExecutionPacket, createAgentBuildExpectedOutputs, createAgentBuildPrompt, createBestTransport, createBuildRunParams, decodeSomaBytes, detectHttp3Support, encodeSomaBytes, getAgentBuildKindSpec, hyphenateInstanceId, isConnectFailure, regionFromApiKey, regionHostname, resetHttp3DetectionCache, resolveAgentBuildKind, sandboxTag, tagHostname, unwrapSomaEnvelope, verifySignature };
|
|
11744
13019
|
//# sourceMappingURL=index.js.map
|
|
11745
13020
|
//# sourceMappingURL=index.js.map
|