@alibaba-group/opensandbox 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -33
- package/dist/{chunk-OYTPXLWE.js → chunk-XHEWHFQ6.js} +139 -29
- package/dist/chunk-XHEWHFQ6.js.map +1 -0
- package/dist/cjs/index.cjs +260 -42
- 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 +32 -7
- package/dist/index.js +123 -14
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +290 -14
- package/dist/internal.js +1 -1
- package/dist/{sandboxes-Dc0G4ShU.d.ts → sandboxes-DL9uUHGR.d.ts} +232 -158
- package/package.json +3 -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 +57 -12
- 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 +50 -7
- package/src/openapi/egressClient.ts +45 -0
- package/src/sandbox.ts +82 -12
- package/src/services/egress.ts +27 -0
- package/src/services/execdCommands.ts +24 -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
|
+
}
|
|
358
|
+
return body;
|
|
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?.timeout != null) {
|
|
368
|
+
body.timeout = opts.timeout;
|
|
369
|
+
}
|
|
333
370
|
return body;
|
|
334
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.5";
|
|
1076
1235
|
|
|
1077
1236
|
// src/config/connection.ts
|
|
1078
1237
|
function isNodeRuntime2() {
|
|
@@ -1429,7 +1588,8 @@ var Sandbox = class _Sandbox {
|
|
|
1429
1588
|
_Sandbox._priv.set(this, {
|
|
1430
1589
|
adapterFactory: opts.adapterFactory,
|
|
1431
1590
|
lifecycleBaseUrl: opts.lifecycleBaseUrl,
|
|
1432
|
-
execdBaseUrl: opts.execdBaseUrl
|
|
1591
|
+
execdBaseUrl: opts.execdBaseUrl,
|
|
1592
|
+
egress: opts.egress
|
|
1433
1593
|
});
|
|
1434
1594
|
this.sandboxes = opts.sandboxes;
|
|
1435
1595
|
this.commands = opts.commands;
|
|
@@ -1454,23 +1614,29 @@ var Sandbox = class _Sandbox {
|
|
|
1454
1614
|
}
|
|
1455
1615
|
if (opts.volumes) {
|
|
1456
1616
|
for (const vol of opts.volumes) {
|
|
1457
|
-
const backendsSpecified = [vol.host, vol.pvc].filter((b) => b
|
|
1617
|
+
const backendsSpecified = [vol.host, vol.pvc, vol.ossfs].filter((b) => b != null).length;
|
|
1458
1618
|
if (backendsSpecified === 0) {
|
|
1459
1619
|
throw new Error(
|
|
1460
|
-
`Volume '${vol.name}' must specify exactly one backend (host, pvc), but none was provided.`
|
|
1620
|
+
`Volume '${vol.name}' must specify exactly one backend (host, pvc, ossfs), but none was provided.`
|
|
1461
1621
|
);
|
|
1462
1622
|
}
|
|
1463
1623
|
if (backendsSpecified > 1) {
|
|
1464
1624
|
throw new Error(
|
|
1465
|
-
`Volume '${vol.name}' must specify exactly one backend (host, pvc), but multiple were provided.`
|
|
1625
|
+
`Volume '${vol.name}' must specify exactly one backend (host, pvc, ossfs), but multiple were provided.`
|
|
1466
1626
|
);
|
|
1467
1627
|
}
|
|
1468
1628
|
}
|
|
1469
1629
|
}
|
|
1630
|
+
const rawTimeout = opts.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS;
|
|
1631
|
+
const timeoutSeconds = opts.timeoutSeconds === null ? null : Math.floor(rawTimeout);
|
|
1632
|
+
if (timeoutSeconds !== null && !Number.isFinite(timeoutSeconds)) {
|
|
1633
|
+
throw new Error(
|
|
1634
|
+
`timeoutSeconds must be a finite number, got ${opts.timeoutSeconds}`
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1470
1637
|
const req = {
|
|
1471
1638
|
image: toImageSpec(opts.image),
|
|
1472
1639
|
entrypoint: opts.entrypoint ?? DEFAULT_ENTRYPOINT,
|
|
1473
|
-
timeout: Math.floor(opts.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS),
|
|
1474
1640
|
resourceLimits: opts.resource ?? DEFAULT_RESOURCE_LIMITS,
|
|
1475
1641
|
env: opts.env ?? {},
|
|
1476
1642
|
metadata: opts.metadata ?? {},
|
|
@@ -1481,6 +1647,9 @@ var Sandbox = class _Sandbox {
|
|
|
1481
1647
|
volumes: opts.volumes,
|
|
1482
1648
|
extensions: opts.extensions ?? {}
|
|
1483
1649
|
};
|
|
1650
|
+
if (timeoutSeconds !== null) {
|
|
1651
|
+
req.timeout = timeoutSeconds;
|
|
1652
|
+
}
|
|
1484
1653
|
let sandboxId;
|
|
1485
1654
|
try {
|
|
1486
1655
|
const created = await sandboxes.createSandbox(req);
|
|
@@ -1490,12 +1659,23 @@ var Sandbox = class _Sandbox {
|
|
|
1490
1659
|
DEFAULT_EXECD_PORT,
|
|
1491
1660
|
connectionConfig.useServerProxy
|
|
1492
1661
|
);
|
|
1662
|
+
const egressEndpoint = await sandboxes.getSandboxEndpoint(
|
|
1663
|
+
sandboxId,
|
|
1664
|
+
DEFAULT_EGRESS_PORT,
|
|
1665
|
+
connectionConfig.useServerProxy
|
|
1666
|
+
);
|
|
1493
1667
|
const execdBaseUrl = `${connectionConfig.protocol}://${endpoint.endpoint}`;
|
|
1668
|
+
const egressBaseUrl = `${connectionConfig.protocol}://${egressEndpoint.endpoint}`;
|
|
1494
1669
|
const { commands, files, health, metrics } = adapterFactory.createExecdStack({
|
|
1495
1670
|
connectionConfig,
|
|
1496
1671
|
execdBaseUrl,
|
|
1497
1672
|
endpointHeaders: endpoint.headers
|
|
1498
1673
|
});
|
|
1674
|
+
const { egress } = adapterFactory.createEgressStack({
|
|
1675
|
+
connectionConfig,
|
|
1676
|
+
egressBaseUrl,
|
|
1677
|
+
endpointHeaders: egressEndpoint.headers
|
|
1678
|
+
});
|
|
1499
1679
|
const sbx = new _Sandbox({
|
|
1500
1680
|
id: sandboxId,
|
|
1501
1681
|
connectionConfig,
|
|
@@ -1506,7 +1686,8 @@ var Sandbox = class _Sandbox {
|
|
|
1506
1686
|
commands,
|
|
1507
1687
|
files,
|
|
1508
1688
|
health,
|
|
1509
|
-
metrics
|
|
1689
|
+
metrics,
|
|
1690
|
+
egress
|
|
1510
1691
|
});
|
|
1511
1692
|
if (!(opts.skipHealthCheck ?? false)) {
|
|
1512
1693
|
await sbx.waitUntilReady({
|
|
@@ -1548,12 +1729,23 @@ var Sandbox = class _Sandbox {
|
|
|
1548
1729
|
DEFAULT_EXECD_PORT,
|
|
1549
1730
|
connectionConfig.useServerProxy
|
|
1550
1731
|
);
|
|
1732
|
+
const egressEndpoint = await sandboxes.getSandboxEndpoint(
|
|
1733
|
+
opts.sandboxId,
|
|
1734
|
+
DEFAULT_EGRESS_PORT,
|
|
1735
|
+
connectionConfig.useServerProxy
|
|
1736
|
+
);
|
|
1551
1737
|
const execdBaseUrl = `${connectionConfig.protocol}://${endpoint.endpoint}`;
|
|
1738
|
+
const egressBaseUrl = `${connectionConfig.protocol}://${egressEndpoint.endpoint}`;
|
|
1552
1739
|
const { commands, files, health, metrics } = adapterFactory.createExecdStack({
|
|
1553
1740
|
connectionConfig,
|
|
1554
1741
|
execdBaseUrl,
|
|
1555
1742
|
endpointHeaders: endpoint.headers
|
|
1556
1743
|
});
|
|
1744
|
+
const { egress } = adapterFactory.createEgressStack({
|
|
1745
|
+
connectionConfig,
|
|
1746
|
+
egressBaseUrl,
|
|
1747
|
+
endpointHeaders: egressEndpoint.headers
|
|
1748
|
+
});
|
|
1557
1749
|
const sbx = new _Sandbox({
|
|
1558
1750
|
id: opts.sandboxId,
|
|
1559
1751
|
connectionConfig,
|
|
@@ -1564,7 +1756,8 @@ var Sandbox = class _Sandbox {
|
|
|
1564
1756
|
commands,
|
|
1565
1757
|
files,
|
|
1566
1758
|
health,
|
|
1567
|
-
metrics
|
|
1759
|
+
metrics,
|
|
1760
|
+
egress
|
|
1568
1761
|
});
|
|
1569
1762
|
if (!(opts.skipHealthCheck ?? false)) {
|
|
1570
1763
|
await sbx.waitUntilReady({
|
|
@@ -1652,6 +1845,12 @@ var Sandbox = class _Sandbox {
|
|
|
1652
1845
|
).toISOString();
|
|
1653
1846
|
return await this.sandboxes.renewSandboxExpiration(this.id, { expiresAt });
|
|
1654
1847
|
}
|
|
1848
|
+
async getEgressPolicy() {
|
|
1849
|
+
return await _Sandbox._priv.get(this).egress.getPolicy();
|
|
1850
|
+
}
|
|
1851
|
+
async patchEgressRules(rules) {
|
|
1852
|
+
await _Sandbox._priv.get(this).egress.patchRules(rules);
|
|
1853
|
+
}
|
|
1655
1854
|
/**
|
|
1656
1855
|
* Get sandbox endpoint for a port (STRICT: no scheme), e.g. "localhost:44772" or "domain/route/.../44772".
|
|
1657
1856
|
*/
|
|
@@ -1671,21 +1870,39 @@ var Sandbox = class _Sandbox {
|
|
|
1671
1870
|
}
|
|
1672
1871
|
async waitUntilReady(opts) {
|
|
1673
1872
|
const deadline = Date.now() + opts.readyTimeoutSeconds * 1e3;
|
|
1873
|
+
let attempt = 0;
|
|
1874
|
+
let errorDetail = "Health check returned false continuously.";
|
|
1875
|
+
const buildTimeoutMessage = () => {
|
|
1876
|
+
const context = `domain=${this.connectionConfig.domain}, useServerProxy=${this.connectionConfig.useServerProxy}`;
|
|
1877
|
+
let suggestion = "If this sandbox runs in Docker bridge or remote-network mode, consider enabling useServerProxy=true.";
|
|
1878
|
+
if (!this.connectionConfig.useServerProxy) {
|
|
1879
|
+
suggestion += " You can also configure server-side [docker].host_ip for direct endpoint access.";
|
|
1880
|
+
}
|
|
1881
|
+
return `Sandbox health check timed out after ${opts.readyTimeoutSeconds}s (${attempt} attempts). ${errorDetail} Connection context: ${context}. ${suggestion}`;
|
|
1882
|
+
};
|
|
1674
1883
|
while (true) {
|
|
1675
1884
|
if (Date.now() > deadline) {
|
|
1676
1885
|
throw new SandboxReadyTimeoutException({
|
|
1677
|
-
message:
|
|
1886
|
+
message: buildTimeoutMessage()
|
|
1678
1887
|
});
|
|
1679
1888
|
}
|
|
1889
|
+
attempt++;
|
|
1680
1890
|
try {
|
|
1681
1891
|
if (opts.healthCheck) {
|
|
1682
1892
|
const ok = await opts.healthCheck(this);
|
|
1683
|
-
if (ok)
|
|
1893
|
+
if (ok) {
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1684
1896
|
} else {
|
|
1685
1897
|
const ok = await this.health.ping();
|
|
1686
|
-
if (ok)
|
|
1898
|
+
if (ok) {
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1687
1901
|
}
|
|
1688
|
-
|
|
1902
|
+
errorDetail = "Health check returned false continuously.";
|
|
1903
|
+
} catch (err) {
|
|
1904
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1905
|
+
errorDetail = `Last health check error: ${message}`;
|
|
1689
1906
|
}
|
|
1690
1907
|
await sleep(opts.pollingIntervalMillis);
|
|
1691
1908
|
}
|
|
@@ -1694,6 +1911,7 @@ var Sandbox = class _Sandbox {
|
|
|
1694
1911
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1695
1912
|
0 && (module.exports = {
|
|
1696
1913
|
ConnectionConfig,
|
|
1914
|
+
DEFAULT_EGRESS_PORT,
|
|
1697
1915
|
DEFAULT_ENTRYPOINT,
|
|
1698
1916
|
DEFAULT_EXECD_PORT,
|
|
1699
1917
|
DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS,
|