@alibaba-group/opensandbox 0.1.4 → 0.1.6
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 +102 -34
- package/dist/{chunk-OYTPXLWE.js → chunk-AFWIGM3C.js} +139 -29
- package/dist/chunk-AFWIGM3C.js.map +1 -0
- package/dist/cjs/index.cjs +277 -53
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/internal.cjs +137 -28
- package/dist/cjs/internal.cjs.map +1 -1
- package/dist/index.d.ts +33 -8
- package/dist/index.js +140 -25
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +292 -15
- package/dist/internal.js +1 -1
- package/dist/{sandboxes-Dc0G4ShU.d.ts → sandboxes-C-AZxcv6.d.ts} +234 -158
- package/package.json +4 -2
- package/src/adapters/commandsAdapter.ts +189 -27
- package/src/adapters/egressAdapter.ts +46 -0
- package/src/adapters/sandboxesAdapter.ts +8 -3
- package/src/api/egress.ts +184 -0
- package/src/api/execd.ts +216 -1
- package/src/api/lifecycle.ts +59 -13
- package/src/core/constants.ts +2 -1
- package/src/core/exceptions.ts +5 -4
- package/src/factory/adapterFactory.ts +13 -1
- package/src/factory/defaultAdapterFactory.ts +27 -2
- package/src/index.ts +3 -1
- package/src/models/execd.ts +12 -0
- package/src/models/execution.ts +2 -1
- package/src/models/sandboxes.ts +52 -7
- package/src/openapi/egressClient.ts +45 -0
- package/src/sandbox.ts +102 -25
- package/src/services/egress.ts +27 -0
- package/src/services/execdCommands.ts +27 -1
- package/src/services/sandboxes.ts +1 -1
- package/dist/chunk-OYTPXLWE.js.map +0 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
ConnectionConfig: () => ConnectionConfig,
|
|
34
|
+
DEFAULT_EGRESS_PORT: () => DEFAULT_EGRESS_PORT,
|
|
34
35
|
DEFAULT_ENTRYPOINT: () => DEFAULT_ENTRYPOINT,
|
|
35
36
|
DEFAULT_EXECD_PORT: () => DEFAULT_EXECD_PORT,
|
|
36
37
|
DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS: () => DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS,
|
|
@@ -69,25 +70,26 @@ var SandboxException = class extends Error {
|
|
|
69
70
|
name = "SandboxException";
|
|
70
71
|
error;
|
|
71
72
|
cause;
|
|
73
|
+
requestId;
|
|
72
74
|
constructor(opts = {}) {
|
|
73
75
|
super(opts.message);
|
|
74
76
|
this.cause = opts.cause;
|
|
75
77
|
this.error = opts.error ?? new SandboxError(SandboxError.INTERNAL_UNKNOWN_ERROR);
|
|
78
|
+
this.requestId = opts.requestId;
|
|
76
79
|
}
|
|
77
80
|
};
|
|
78
81
|
var SandboxApiException = class extends SandboxException {
|
|
79
82
|
name = "SandboxApiException";
|
|
80
83
|
statusCode;
|
|
81
|
-
requestId;
|
|
82
84
|
rawBody;
|
|
83
85
|
constructor(opts) {
|
|
84
86
|
super({
|
|
85
87
|
message: opts.message,
|
|
86
88
|
cause: opts.cause,
|
|
87
|
-
error: opts.error ?? new SandboxError(SandboxError.UNEXPECTED_RESPONSE, opts.message)
|
|
89
|
+
error: opts.error ?? new SandboxError(SandboxError.UNEXPECTED_RESPONSE, opts.message),
|
|
90
|
+
requestId: opts.requestId
|
|
88
91
|
});
|
|
89
92
|
this.statusCode = opts.statusCode;
|
|
90
|
-
this.requestId = opts.requestId;
|
|
91
93
|
this.rawBody = opts.rawBody;
|
|
92
94
|
}
|
|
93
95
|
};
|
|
@@ -143,8 +145,19 @@ function createExecdClient(opts) {
|
|
|
143
145
|
});
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
// src/openapi/
|
|
148
|
+
// src/openapi/egressClient.ts
|
|
147
149
|
var import_openapi_fetch2 = __toESM(require("openapi-fetch"), 1);
|
|
150
|
+
function createEgressClient(opts) {
|
|
151
|
+
const createClientFn = import_openapi_fetch2.default.default ?? import_openapi_fetch2.default;
|
|
152
|
+
return createClientFn({
|
|
153
|
+
baseUrl: opts.baseUrl,
|
|
154
|
+
headers: opts.headers,
|
|
155
|
+
fetch: opts.fetch
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/openapi/lifecycleClient.ts
|
|
160
|
+
var import_openapi_fetch3 = __toESM(require("openapi-fetch"), 1);
|
|
148
161
|
function readEnvApiKey() {
|
|
149
162
|
const env = globalThis?.process?.env;
|
|
150
163
|
const v = env?.OPEN_SANDBOX_API_KEY;
|
|
@@ -158,7 +171,7 @@ function createLifecycleClient(opts = {}) {
|
|
|
158
171
|
if (apiKey && !headers["OPEN-SANDBOX-API-KEY"]) {
|
|
159
172
|
headers["OPEN-SANDBOX-API-KEY"] = apiKey;
|
|
160
173
|
}
|
|
161
|
-
const createClientFn =
|
|
174
|
+
const createClientFn = import_openapi_fetch3.default.default ?? import_openapi_fetch3.default;
|
|
162
175
|
return createClientFn({
|
|
163
176
|
baseUrl: opts.baseUrl ?? "http://localhost:8080/v1",
|
|
164
177
|
headers,
|
|
@@ -322,6 +335,9 @@ function joinUrl(baseUrl, pathname) {
|
|
|
322
335
|
return `${base}${path}`;
|
|
323
336
|
}
|
|
324
337
|
function toRunCommandRequest(command, opts) {
|
|
338
|
+
if (opts?.gid != null && opts.uid == null) {
|
|
339
|
+
throw new Error("uid is required when gid is provided");
|
|
340
|
+
}
|
|
325
341
|
const body = {
|
|
326
342
|
command,
|
|
327
343
|
cwd: opts?.workingDirectory,
|
|
@@ -330,8 +346,39 @@ function toRunCommandRequest(command, opts) {
|
|
|
330
346
|
if (opts?.timeoutSeconds != null) {
|
|
331
347
|
body.timeout = Math.round(opts.timeoutSeconds * 1e3);
|
|
332
348
|
}
|
|
349
|
+
if (opts?.uid != null) {
|
|
350
|
+
body.uid = opts.uid;
|
|
351
|
+
}
|
|
352
|
+
if (opts?.gid != null) {
|
|
353
|
+
body.gid = opts.gid;
|
|
354
|
+
}
|
|
355
|
+
if (opts?.envs != null) {
|
|
356
|
+
body.envs = opts.envs;
|
|
357
|
+
}
|
|
333
358
|
return body;
|
|
334
359
|
}
|
|
360
|
+
function toRunInSessionRequest(command, opts) {
|
|
361
|
+
const body = {
|
|
362
|
+
command
|
|
363
|
+
};
|
|
364
|
+
if (opts?.workingDirectory != null) {
|
|
365
|
+
body.cwd = opts.workingDirectory;
|
|
366
|
+
}
|
|
367
|
+
if (opts?.timeoutSeconds != null) {
|
|
368
|
+
body.timeout = Math.round(opts.timeoutSeconds * 1e3);
|
|
369
|
+
}
|
|
370
|
+
return body;
|
|
371
|
+
}
|
|
372
|
+
function inferForegroundExitCode(execution) {
|
|
373
|
+
const errorValue = execution.error?.value?.trim();
|
|
374
|
+
const parsedExitCode = errorValue && /^-?\d+$/.test(errorValue) ? Number(errorValue) : Number.NaN;
|
|
375
|
+
return execution.error != null ? Number.isFinite(parsedExitCode) ? parsedExitCode : null : execution.complete ? 0 : null;
|
|
376
|
+
}
|
|
377
|
+
function assertNonBlank(value, field) {
|
|
378
|
+
if (!value.trim()) {
|
|
379
|
+
throw new Error(`${field} cannot be empty`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
335
382
|
function parseOptionalDate(value, field) {
|
|
336
383
|
if (value == null) return void 0;
|
|
337
384
|
if (value instanceof Date) return value;
|
|
@@ -351,6 +398,58 @@ var CommandsAdapter = class {
|
|
|
351
398
|
this.fetch = opts.fetch ?? fetch;
|
|
352
399
|
}
|
|
353
400
|
fetch;
|
|
401
|
+
buildRunStreamSpec(command, opts) {
|
|
402
|
+
assertNonBlank(command, "command");
|
|
403
|
+
return {
|
|
404
|
+
pathname: "/command",
|
|
405
|
+
body: toRunCommandRequest(command, opts),
|
|
406
|
+
fallbackErrorMessage: "Run command failed"
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
buildRunInSessionStreamSpec(sessionId, command, opts) {
|
|
410
|
+
assertNonBlank(sessionId, "sessionId");
|
|
411
|
+
assertNonBlank(command, "command");
|
|
412
|
+
return {
|
|
413
|
+
pathname: `/session/${encodeURIComponent(sessionId)}/run`,
|
|
414
|
+
body: toRunInSessionRequest(command, opts),
|
|
415
|
+
fallbackErrorMessage: "Run in session failed"
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
async *streamExecution(spec, signal) {
|
|
419
|
+
const url = joinUrl(this.opts.baseUrl, spec.pathname);
|
|
420
|
+
const res = await this.fetch(url, {
|
|
421
|
+
method: "POST",
|
|
422
|
+
headers: {
|
|
423
|
+
accept: "text/event-stream",
|
|
424
|
+
"content-type": "application/json",
|
|
425
|
+
...this.opts.headers ?? {}
|
|
426
|
+
},
|
|
427
|
+
body: JSON.stringify(spec.body),
|
|
428
|
+
signal
|
|
429
|
+
});
|
|
430
|
+
for await (const ev of parseJsonEventStream(res, {
|
|
431
|
+
fallbackErrorMessage: spec.fallbackErrorMessage
|
|
432
|
+
})) {
|
|
433
|
+
yield ev;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
async consumeExecutionStream(stream, handlers, inferExitCode = false) {
|
|
437
|
+
const execution = {
|
|
438
|
+
logs: { stdout: [], stderr: [] },
|
|
439
|
+
result: []
|
|
440
|
+
};
|
|
441
|
+
const dispatcher = new ExecutionEventDispatcher(execution, handlers);
|
|
442
|
+
for await (const ev of stream) {
|
|
443
|
+
if (ev.type === "init" && (ev.text ?? "") === "" && execution.id) {
|
|
444
|
+
ev.text = execution.id;
|
|
445
|
+
}
|
|
446
|
+
await dispatcher.dispatch(ev);
|
|
447
|
+
}
|
|
448
|
+
if (inferExitCode) {
|
|
449
|
+
execution.exitCode = inferForegroundExitCode(execution);
|
|
450
|
+
}
|
|
451
|
+
return execution;
|
|
452
|
+
}
|
|
354
453
|
async interrupt(sessionId) {
|
|
355
454
|
const { error, response } = await this.client.DELETE("/command", {
|
|
356
455
|
params: { query: { id: sessionId } }
|
|
@@ -394,35 +493,76 @@ var CommandsAdapter = class {
|
|
|
394
493
|
};
|
|
395
494
|
}
|
|
396
495
|
async *runStream(command, opts, signal) {
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
const res = await this.fetch(url, {
|
|
400
|
-
method: "POST",
|
|
401
|
-
headers: {
|
|
402
|
-
"accept": "text/event-stream",
|
|
403
|
-
"content-type": "application/json",
|
|
404
|
-
...this.opts.headers ?? {}
|
|
405
|
-
},
|
|
406
|
-
body,
|
|
496
|
+
for await (const ev of this.streamExecution(
|
|
497
|
+
this.buildRunStreamSpec(command, opts),
|
|
407
498
|
signal
|
|
408
|
-
|
|
409
|
-
for await (const ev of parseJsonEventStream(res, { fallbackErrorMessage: "Run command failed" })) {
|
|
499
|
+
)) {
|
|
410
500
|
yield ev;
|
|
411
501
|
}
|
|
412
502
|
}
|
|
413
503
|
async run(command, opts, handlers, signal) {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
504
|
+
return this.consumeExecutionStream(
|
|
505
|
+
this.runStream(command, opts, signal),
|
|
506
|
+
handlers,
|
|
507
|
+
!opts?.background
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
async createSession(options) {
|
|
511
|
+
const body = options?.workingDirectory != null ? { cwd: options.workingDirectory } : {};
|
|
512
|
+
const { data, error, response } = await this.client.POST("/session", {
|
|
513
|
+
body
|
|
514
|
+
});
|
|
515
|
+
throwOnOpenApiFetchError({ error, response }, "Create session failed");
|
|
516
|
+
const ok = data;
|
|
517
|
+
if (!ok || typeof ok.session_id !== "string") {
|
|
518
|
+
throw new Error("Create session failed: unexpected response shape");
|
|
424
519
|
}
|
|
425
|
-
return
|
|
520
|
+
return ok.session_id;
|
|
521
|
+
}
|
|
522
|
+
async *runInSessionStream(sessionId, command, opts, signal) {
|
|
523
|
+
for await (const ev of this.streamExecution(
|
|
524
|
+
this.buildRunInSessionStreamSpec(sessionId, command, opts),
|
|
525
|
+
signal
|
|
526
|
+
)) {
|
|
527
|
+
yield ev;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
async runInSession(sessionId, command, options, handlers, signal) {
|
|
531
|
+
return this.consumeExecutionStream(
|
|
532
|
+
this.runInSessionStream(sessionId, command, options, signal),
|
|
533
|
+
handlers,
|
|
534
|
+
true
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
async deleteSession(sessionId) {
|
|
538
|
+
const { error, response } = await this.client.DELETE(
|
|
539
|
+
"/session/{sessionId}",
|
|
540
|
+
{ params: { path: { sessionId } } }
|
|
541
|
+
);
|
|
542
|
+
throwOnOpenApiFetchError({ error, response }, "Delete session failed");
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
// src/adapters/egressAdapter.ts
|
|
547
|
+
var EgressAdapter = class {
|
|
548
|
+
constructor(client) {
|
|
549
|
+
this.client = client;
|
|
550
|
+
}
|
|
551
|
+
async getPolicy() {
|
|
552
|
+
const { data, error, response } = await this.client.GET("/policy");
|
|
553
|
+
throwOnOpenApiFetchError({ error, response }, "Get sandbox egress policy failed");
|
|
554
|
+
const raw = data;
|
|
555
|
+
if (!raw || typeof raw !== "object" || !raw.policy || typeof raw.policy !== "object") {
|
|
556
|
+
throw new Error("Get sandbox egress policy failed: unexpected response shape");
|
|
557
|
+
}
|
|
558
|
+
return raw.policy;
|
|
559
|
+
}
|
|
560
|
+
async patchRules(rules) {
|
|
561
|
+
const body = rules;
|
|
562
|
+
const { error, response } = await this.client.PATCH("/policy", {
|
|
563
|
+
body
|
|
564
|
+
});
|
|
565
|
+
throwOnOpenApiFetchError({ error, response }, "Patch sandbox egress rules failed");
|
|
426
566
|
}
|
|
427
567
|
};
|
|
428
568
|
|
|
@@ -911,11 +1051,15 @@ var SandboxesAdapter = class {
|
|
|
911
1051
|
}
|
|
912
1052
|
return d;
|
|
913
1053
|
}
|
|
1054
|
+
parseOptionalIsoDate(field, v) {
|
|
1055
|
+
if (v == null) return null;
|
|
1056
|
+
return this.parseIsoDate(field, v);
|
|
1057
|
+
}
|
|
914
1058
|
mapSandboxInfo(raw) {
|
|
915
1059
|
return {
|
|
916
1060
|
...raw ?? {},
|
|
917
1061
|
createdAt: this.parseIsoDate("createdAt", raw?.createdAt),
|
|
918
|
-
expiresAt: this.
|
|
1062
|
+
expiresAt: this.parseOptionalIsoDate("expiresAt", raw?.expiresAt)
|
|
919
1063
|
};
|
|
920
1064
|
}
|
|
921
1065
|
async createSandbox(req) {
|
|
@@ -931,7 +1075,7 @@ var SandboxesAdapter = class {
|
|
|
931
1075
|
return {
|
|
932
1076
|
...raw ?? {},
|
|
933
1077
|
createdAt: this.parseIsoDate("createdAt", raw?.createdAt),
|
|
934
|
-
expiresAt: this.
|
|
1078
|
+
expiresAt: this.parseOptionalIsoDate("expiresAt", raw?.expiresAt)
|
|
935
1079
|
};
|
|
936
1080
|
}
|
|
937
1081
|
async getSandbox(sandboxId) {
|
|
@@ -1056,6 +1200,20 @@ var DefaultAdapterFactory = class {
|
|
|
1056
1200
|
metrics
|
|
1057
1201
|
};
|
|
1058
1202
|
}
|
|
1203
|
+
createEgressStack(opts) {
|
|
1204
|
+
const headers = {
|
|
1205
|
+
...opts.connectionConfig.headers ?? {},
|
|
1206
|
+
...opts.endpointHeaders ?? {}
|
|
1207
|
+
};
|
|
1208
|
+
const egressClient = createEgressClient({
|
|
1209
|
+
baseUrl: opts.egressBaseUrl,
|
|
1210
|
+
headers,
|
|
1211
|
+
fetch: opts.connectionConfig.fetch
|
|
1212
|
+
});
|
|
1213
|
+
return {
|
|
1214
|
+
egress: new EgressAdapter(egressClient)
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1059
1217
|
};
|
|
1060
1218
|
function createDefaultAdapterFactory() {
|
|
1061
1219
|
return new DefaultAdapterFactory();
|
|
@@ -1063,6 +1221,7 @@ function createDefaultAdapterFactory() {
|
|
|
1063
1221
|
|
|
1064
1222
|
// src/core/constants.ts
|
|
1065
1223
|
var DEFAULT_EXECD_PORT = 44772;
|
|
1224
|
+
var DEFAULT_EGRESS_PORT = 18080;
|
|
1066
1225
|
var DEFAULT_ENTRYPOINT = ["tail", "-f", "/dev/null"];
|
|
1067
1226
|
var DEFAULT_RESOURCE_LIMITS = {
|
|
1068
1227
|
cpu: "1",
|
|
@@ -1072,7 +1231,7 @@ var DEFAULT_TIMEOUT_SECONDS = 600;
|
|
|
1072
1231
|
var DEFAULT_READY_TIMEOUT_SECONDS = 30;
|
|
1073
1232
|
var DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS = 200;
|
|
1074
1233
|
var DEFAULT_REQUEST_TIMEOUT_SECONDS = 30;
|
|
1075
|
-
var DEFAULT_USER_AGENT = "OpenSandbox-JS-SDK/0.1.
|
|
1234
|
+
var DEFAULT_USER_AGENT = "OpenSandbox-JS-SDK/0.1.6";
|
|
1076
1235
|
|
|
1077
1236
|
// src/config/connection.ts
|
|
1078
1237
|
function isNodeRuntime2() {
|
|
@@ -1393,6 +1552,7 @@ var SandboxManager = class _SandboxManager {
|
|
|
1393
1552
|
};
|
|
1394
1553
|
|
|
1395
1554
|
// src/sandbox.ts
|
|
1555
|
+
var HOST_PATH_PATTERN = /^([/]|[A-Za-z]:[\\/])/;
|
|
1396
1556
|
function sleep(ms) {
|
|
1397
1557
|
return new Promise((r) => setTimeout(r, ms));
|
|
1398
1558
|
}
|
|
@@ -1429,7 +1589,8 @@ var Sandbox = class _Sandbox {
|
|
|
1429
1589
|
_Sandbox._priv.set(this, {
|
|
1430
1590
|
adapterFactory: opts.adapterFactory,
|
|
1431
1591
|
lifecycleBaseUrl: opts.lifecycleBaseUrl,
|
|
1432
|
-
execdBaseUrl: opts.execdBaseUrl
|
|
1592
|
+
execdBaseUrl: opts.execdBaseUrl,
|
|
1593
|
+
egress: opts.egress
|
|
1433
1594
|
});
|
|
1434
1595
|
this.sandboxes = opts.sandboxes;
|
|
1435
1596
|
this.commands = opts.commands;
|
|
@@ -1438,6 +1599,26 @@ var Sandbox = class _Sandbox {
|
|
|
1438
1599
|
this.metrics = opts.metrics;
|
|
1439
1600
|
}
|
|
1440
1601
|
static async create(opts) {
|
|
1602
|
+
if (opts.volumes) {
|
|
1603
|
+
for (const vol of opts.volumes) {
|
|
1604
|
+
const backendsSpecified = [vol.host, vol.pvc, vol.ossfs].filter((b) => b != null).length;
|
|
1605
|
+
if (backendsSpecified === 0) {
|
|
1606
|
+
throw new Error(
|
|
1607
|
+
`Volume '${vol.name}' must specify exactly one backend (host, pvc, ossfs), but none was provided.`
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
if (backendsSpecified > 1) {
|
|
1611
|
+
throw new Error(
|
|
1612
|
+
`Volume '${vol.name}' must specify exactly one backend (host, pvc, ossfs), but multiple were provided.`
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
if (vol.host && !HOST_PATH_PATTERN.test(vol.host.path)) {
|
|
1616
|
+
throw new Error(
|
|
1617
|
+
"Host path must be an absolute path starting with '/' or a Windows drive letter (e.g. 'C:\\' or 'D:/')"
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1441
1622
|
const baseConnectionConfig = opts.connectionConfig instanceof ConnectionConfig ? opts.connectionConfig : new ConnectionConfig(opts.connectionConfig);
|
|
1442
1623
|
const connectionConfig = baseConnectionConfig.withTransportIfMissing();
|
|
1443
1624
|
const lifecycleBaseUrl = connectionConfig.getBaseUrl();
|
|
@@ -1452,25 +1633,16 @@ var Sandbox = class _Sandbox {
|
|
|
1452
1633
|
await connectionConfig.closeTransport();
|
|
1453
1634
|
throw err;
|
|
1454
1635
|
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
);
|
|
1462
|
-
}
|
|
1463
|
-
if (backendsSpecified > 1) {
|
|
1464
|
-
throw new Error(
|
|
1465
|
-
`Volume '${vol.name}' must specify exactly one backend (host, pvc), but multiple were provided.`
|
|
1466
|
-
);
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1636
|
+
const rawTimeout = opts.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS;
|
|
1637
|
+
const timeoutSeconds = opts.timeoutSeconds === null ? null : Math.floor(rawTimeout);
|
|
1638
|
+
if (timeoutSeconds !== null && !Number.isFinite(timeoutSeconds)) {
|
|
1639
|
+
throw new Error(
|
|
1640
|
+
`timeoutSeconds must be a finite number, got ${opts.timeoutSeconds}`
|
|
1641
|
+
);
|
|
1469
1642
|
}
|
|
1470
1643
|
const req = {
|
|
1471
1644
|
image: toImageSpec(opts.image),
|
|
1472
1645
|
entrypoint: opts.entrypoint ?? DEFAULT_ENTRYPOINT,
|
|
1473
|
-
timeout: Math.floor(opts.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS),
|
|
1474
1646
|
resourceLimits: opts.resource ?? DEFAULT_RESOURCE_LIMITS,
|
|
1475
1647
|
env: opts.env ?? {},
|
|
1476
1648
|
metadata: opts.metadata ?? {},
|
|
@@ -1481,6 +1653,9 @@ var Sandbox = class _Sandbox {
|
|
|
1481
1653
|
volumes: opts.volumes,
|
|
1482
1654
|
extensions: opts.extensions ?? {}
|
|
1483
1655
|
};
|
|
1656
|
+
if (timeoutSeconds !== null) {
|
|
1657
|
+
req.timeout = timeoutSeconds;
|
|
1658
|
+
}
|
|
1484
1659
|
let sandboxId;
|
|
1485
1660
|
try {
|
|
1486
1661
|
const created = await sandboxes.createSandbox(req);
|
|
@@ -1490,12 +1665,23 @@ var Sandbox = class _Sandbox {
|
|
|
1490
1665
|
DEFAULT_EXECD_PORT,
|
|
1491
1666
|
connectionConfig.useServerProxy
|
|
1492
1667
|
);
|
|
1668
|
+
const egressEndpoint = await sandboxes.getSandboxEndpoint(
|
|
1669
|
+
sandboxId,
|
|
1670
|
+
DEFAULT_EGRESS_PORT,
|
|
1671
|
+
connectionConfig.useServerProxy
|
|
1672
|
+
);
|
|
1493
1673
|
const execdBaseUrl = `${connectionConfig.protocol}://${endpoint.endpoint}`;
|
|
1674
|
+
const egressBaseUrl = `${connectionConfig.protocol}://${egressEndpoint.endpoint}`;
|
|
1494
1675
|
const { commands, files, health, metrics } = adapterFactory.createExecdStack({
|
|
1495
1676
|
connectionConfig,
|
|
1496
1677
|
execdBaseUrl,
|
|
1497
1678
|
endpointHeaders: endpoint.headers
|
|
1498
1679
|
});
|
|
1680
|
+
const { egress } = adapterFactory.createEgressStack({
|
|
1681
|
+
connectionConfig,
|
|
1682
|
+
egressBaseUrl,
|
|
1683
|
+
endpointHeaders: egressEndpoint.headers
|
|
1684
|
+
});
|
|
1499
1685
|
const sbx = new _Sandbox({
|
|
1500
1686
|
id: sandboxId,
|
|
1501
1687
|
connectionConfig,
|
|
@@ -1506,7 +1692,8 @@ var Sandbox = class _Sandbox {
|
|
|
1506
1692
|
commands,
|
|
1507
1693
|
files,
|
|
1508
1694
|
health,
|
|
1509
|
-
metrics
|
|
1695
|
+
metrics,
|
|
1696
|
+
egress
|
|
1510
1697
|
});
|
|
1511
1698
|
if (!(opts.skipHealthCheck ?? false)) {
|
|
1512
1699
|
await sbx.waitUntilReady({
|
|
@@ -1548,12 +1735,23 @@ var Sandbox = class _Sandbox {
|
|
|
1548
1735
|
DEFAULT_EXECD_PORT,
|
|
1549
1736
|
connectionConfig.useServerProxy
|
|
1550
1737
|
);
|
|
1738
|
+
const egressEndpoint = await sandboxes.getSandboxEndpoint(
|
|
1739
|
+
opts.sandboxId,
|
|
1740
|
+
DEFAULT_EGRESS_PORT,
|
|
1741
|
+
connectionConfig.useServerProxy
|
|
1742
|
+
);
|
|
1551
1743
|
const execdBaseUrl = `${connectionConfig.protocol}://${endpoint.endpoint}`;
|
|
1744
|
+
const egressBaseUrl = `${connectionConfig.protocol}://${egressEndpoint.endpoint}`;
|
|
1552
1745
|
const { commands, files, health, metrics } = adapterFactory.createExecdStack({
|
|
1553
1746
|
connectionConfig,
|
|
1554
1747
|
execdBaseUrl,
|
|
1555
1748
|
endpointHeaders: endpoint.headers
|
|
1556
1749
|
});
|
|
1750
|
+
const { egress } = adapterFactory.createEgressStack({
|
|
1751
|
+
connectionConfig,
|
|
1752
|
+
egressBaseUrl,
|
|
1753
|
+
endpointHeaders: egressEndpoint.headers
|
|
1754
|
+
});
|
|
1557
1755
|
const sbx = new _Sandbox({
|
|
1558
1756
|
id: opts.sandboxId,
|
|
1559
1757
|
connectionConfig,
|
|
@@ -1564,7 +1762,8 @@ var Sandbox = class _Sandbox {
|
|
|
1564
1762
|
commands,
|
|
1565
1763
|
files,
|
|
1566
1764
|
health,
|
|
1567
|
-
metrics
|
|
1765
|
+
metrics,
|
|
1766
|
+
egress
|
|
1568
1767
|
});
|
|
1569
1768
|
if (!(opts.skipHealthCheck ?? false)) {
|
|
1570
1769
|
await sbx.waitUntilReady({
|
|
@@ -1652,6 +1851,12 @@ var Sandbox = class _Sandbox {
|
|
|
1652
1851
|
).toISOString();
|
|
1653
1852
|
return await this.sandboxes.renewSandboxExpiration(this.id, { expiresAt });
|
|
1654
1853
|
}
|
|
1854
|
+
async getEgressPolicy() {
|
|
1855
|
+
return await _Sandbox._priv.get(this).egress.getPolicy();
|
|
1856
|
+
}
|
|
1857
|
+
async patchEgressRules(rules) {
|
|
1858
|
+
await _Sandbox._priv.get(this).egress.patchRules(rules);
|
|
1859
|
+
}
|
|
1655
1860
|
/**
|
|
1656
1861
|
* Get sandbox endpoint for a port (STRICT: no scheme), e.g. "localhost:44772" or "domain/route/.../44772".
|
|
1657
1862
|
*/
|
|
@@ -1671,21 +1876,39 @@ var Sandbox = class _Sandbox {
|
|
|
1671
1876
|
}
|
|
1672
1877
|
async waitUntilReady(opts) {
|
|
1673
1878
|
const deadline = Date.now() + opts.readyTimeoutSeconds * 1e3;
|
|
1879
|
+
let attempt = 0;
|
|
1880
|
+
let errorDetail = "Health check returned false continuously.";
|
|
1881
|
+
const buildTimeoutMessage = () => {
|
|
1882
|
+
const context = `domain=${this.connectionConfig.domain}, useServerProxy=${this.connectionConfig.useServerProxy}`;
|
|
1883
|
+
let suggestion = "If this sandbox runs in Docker bridge or remote-network mode, consider enabling useServerProxy=true.";
|
|
1884
|
+
if (!this.connectionConfig.useServerProxy) {
|
|
1885
|
+
suggestion += " You can also configure server-side [docker].host_ip for direct endpoint access.";
|
|
1886
|
+
}
|
|
1887
|
+
return `Sandbox health check timed out after ${opts.readyTimeoutSeconds}s (${attempt} attempts). ${errorDetail} Connection context: ${context}. ${suggestion}`;
|
|
1888
|
+
};
|
|
1674
1889
|
while (true) {
|
|
1675
1890
|
if (Date.now() > deadline) {
|
|
1676
1891
|
throw new SandboxReadyTimeoutException({
|
|
1677
|
-
message:
|
|
1892
|
+
message: buildTimeoutMessage()
|
|
1678
1893
|
});
|
|
1679
1894
|
}
|
|
1895
|
+
attempt++;
|
|
1680
1896
|
try {
|
|
1681
1897
|
if (opts.healthCheck) {
|
|
1682
1898
|
const ok = await opts.healthCheck(this);
|
|
1683
|
-
if (ok)
|
|
1899
|
+
if (ok) {
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1684
1902
|
} else {
|
|
1685
1903
|
const ok = await this.health.ping();
|
|
1686
|
-
if (ok)
|
|
1904
|
+
if (ok) {
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1687
1907
|
}
|
|
1688
|
-
|
|
1908
|
+
errorDetail = "Health check returned false continuously.";
|
|
1909
|
+
} catch (err) {
|
|
1910
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1911
|
+
errorDetail = `Last health check error: ${message}`;
|
|
1689
1912
|
}
|
|
1690
1913
|
await sleep(opts.pollingIntervalMillis);
|
|
1691
1914
|
}
|
|
@@ -1694,6 +1917,7 @@ var Sandbox = class _Sandbox {
|
|
|
1694
1917
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1695
1918
|
0 && (module.exports = {
|
|
1696
1919
|
ConnectionConfig,
|
|
1920
|
+
DEFAULT_EGRESS_PORT,
|
|
1697
1921
|
DEFAULT_ENTRYPOINT,
|
|
1698
1922
|
DEFAULT_EXECD_PORT,
|
|
1699
1923
|
DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS,
|