@alibaba-group/opensandbox 0.1.3 → 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 -32
- package/dist/{chunk-4EF4ODU2.js → chunk-XHEWHFQ6.js} +184 -22
- package/dist/chunk-XHEWHFQ6.js.map +1 -0
- package/dist/cjs/index.cjs +345 -41
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/internal.cjs +182 -21
- package/dist/cjs/internal.cjs.map +1 -1
- package/dist/index.d.ts +47 -7
- package/dist/index.js +163 -20
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +365 -9
- package/dist/internal.js +1 -1
- package/dist/{sandboxes-CLy12BN1.d.ts → sandboxes-DL9uUHGR.d.ts} +296 -134
- package/package.json +3 -2
- package/src/adapters/commandsAdapter.ts +250 -23
- package/src/adapters/egressAdapter.ts +46 -0
- package/src/adapters/sandboxesAdapter.ts +14 -5
- package/src/api/egress.ts +184 -0
- package/src/api/execd.ts +223 -0
- package/src/api/lifecycle.ts +122 -7
- package/src/config/connection.ts +11 -0
- package/src/core/constants.ts +2 -1
- package/src/core/exceptions.ts +5 -4
- package/src/factory/adapterFactory.ts +14 -1
- package/src/factory/defaultAdapterFactory.ts +34 -5
- package/src/index.ts +8 -2
- package/src/models/execd.ts +32 -6
- package/src/models/execution.ts +2 -1
- package/src/models/sandboxes.ts +121 -4
- package/src/openapi/egressClient.ts +45 -0
- package/src/sandbox.ts +114 -12
- package/src/services/egress.ts +27 -0
- package/src/services/execdCommands.ts +41 -2
- package/src/services/sandboxes.ts +6 -2
- package/dist/chunk-4EF4ODU2.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,11 +335,61 @@ function joinUrl(baseUrl, pathname) {
|
|
|
322
335
|
return `${base}${path}`;
|
|
323
336
|
}
|
|
324
337
|
function toRunCommandRequest(command, opts) {
|
|
325
|
-
|
|
338
|
+
if (opts?.gid != null && opts.uid == null) {
|
|
339
|
+
throw new Error("uid is required when gid is provided");
|
|
340
|
+
}
|
|
341
|
+
const body = {
|
|
326
342
|
command,
|
|
327
343
|
cwd: opts?.workingDirectory,
|
|
328
344
|
background: !!opts?.background
|
|
329
345
|
};
|
|
346
|
+
if (opts?.timeoutSeconds != null) {
|
|
347
|
+
body.timeout = Math.round(opts.timeoutSeconds * 1e3);
|
|
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
|
+
}
|
|
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
|
+
}
|
|
382
|
+
function parseOptionalDate(value, field) {
|
|
383
|
+
if (value == null) return void 0;
|
|
384
|
+
if (value instanceof Date) return value;
|
|
385
|
+
if (typeof value !== "string") {
|
|
386
|
+
throw new Error(`Invalid ${field}: expected ISO string, got ${typeof value}`);
|
|
387
|
+
}
|
|
388
|
+
const parsed = new Date(value);
|
|
389
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
390
|
+
throw new Error(`Invalid ${field}: ${value}`);
|
|
391
|
+
}
|
|
392
|
+
return parsed;
|
|
330
393
|
}
|
|
331
394
|
var CommandsAdapter = class {
|
|
332
395
|
constructor(client, opts) {
|
|
@@ -335,43 +398,172 @@ var CommandsAdapter = class {
|
|
|
335
398
|
this.fetch = opts.fetch ?? fetch;
|
|
336
399
|
}
|
|
337
400
|
fetch;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
401
|
+
buildRunStreamSpec(command, opts) {
|
|
402
|
+
assertNonBlank(command, "command");
|
|
403
|
+
return {
|
|
404
|
+
pathname: "/command",
|
|
405
|
+
body: toRunCommandRequest(command, opts),
|
|
406
|
+
fallbackErrorMessage: "Run command failed"
|
|
407
|
+
};
|
|
343
408
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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);
|
|
347
420
|
const res = await this.fetch(url, {
|
|
348
421
|
method: "POST",
|
|
349
422
|
headers: {
|
|
350
|
-
|
|
423
|
+
accept: "text/event-stream",
|
|
351
424
|
"content-type": "application/json",
|
|
352
425
|
...this.opts.headers ?? {}
|
|
353
426
|
},
|
|
354
|
-
body,
|
|
427
|
+
body: JSON.stringify(spec.body),
|
|
355
428
|
signal
|
|
356
429
|
});
|
|
357
|
-
for await (const ev of parseJsonEventStream(res, {
|
|
430
|
+
for await (const ev of parseJsonEventStream(res, {
|
|
431
|
+
fallbackErrorMessage: spec.fallbackErrorMessage
|
|
432
|
+
})) {
|
|
358
433
|
yield ev;
|
|
359
434
|
}
|
|
360
435
|
}
|
|
361
|
-
async
|
|
436
|
+
async consumeExecutionStream(stream, handlers, inferExitCode = false) {
|
|
362
437
|
const execution = {
|
|
363
438
|
logs: { stdout: [], stderr: [] },
|
|
364
439
|
result: []
|
|
365
440
|
};
|
|
366
441
|
const dispatcher = new ExecutionEventDispatcher(execution, handlers);
|
|
367
|
-
for await (const ev of
|
|
442
|
+
for await (const ev of stream) {
|
|
368
443
|
if (ev.type === "init" && (ev.text ?? "") === "" && execution.id) {
|
|
369
444
|
ev.text = execution.id;
|
|
370
445
|
}
|
|
371
446
|
await dispatcher.dispatch(ev);
|
|
372
447
|
}
|
|
448
|
+
if (inferExitCode) {
|
|
449
|
+
execution.exitCode = inferForegroundExitCode(execution);
|
|
450
|
+
}
|
|
373
451
|
return execution;
|
|
374
452
|
}
|
|
453
|
+
async interrupt(sessionId) {
|
|
454
|
+
const { error, response } = await this.client.DELETE("/command", {
|
|
455
|
+
params: { query: { id: sessionId } }
|
|
456
|
+
});
|
|
457
|
+
throwOnOpenApiFetchError({ error, response }, "Interrupt command failed");
|
|
458
|
+
}
|
|
459
|
+
async getCommandStatus(commandId) {
|
|
460
|
+
const { data, error, response } = await this.client.GET("/command/status/{id}", {
|
|
461
|
+
params: { path: { id: commandId } }
|
|
462
|
+
});
|
|
463
|
+
throwOnOpenApiFetchError({ error, response }, "Get command status failed");
|
|
464
|
+
const ok = data;
|
|
465
|
+
if (!ok || typeof ok !== "object") {
|
|
466
|
+
throw new Error("Get command status failed: unexpected response shape");
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
id: ok.id,
|
|
470
|
+
content: ok.content,
|
|
471
|
+
running: ok.running,
|
|
472
|
+
exitCode: ok.exit_code ?? null,
|
|
473
|
+
error: ok.error,
|
|
474
|
+
startedAt: parseOptionalDate(ok.started_at, "startedAt"),
|
|
475
|
+
finishedAt: parseOptionalDate(ok.finished_at, "finishedAt") ?? null
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
async getBackgroundCommandLogs(commandId, cursor) {
|
|
479
|
+
const { data, error, response } = await this.client.GET("/command/{id}/logs", {
|
|
480
|
+
params: { path: { id: commandId }, query: cursor == null ? {} : { cursor } },
|
|
481
|
+
parseAs: "text"
|
|
482
|
+
});
|
|
483
|
+
throwOnOpenApiFetchError({ error, response }, "Get command logs failed");
|
|
484
|
+
const ok = data;
|
|
485
|
+
if (typeof ok !== "string") {
|
|
486
|
+
throw new Error("Get command logs failed: unexpected response shape");
|
|
487
|
+
}
|
|
488
|
+
const cursorHeader = response.headers.get("EXECD-COMMANDS-TAIL-CURSOR");
|
|
489
|
+
const parsedCursor = cursorHeader != null && cursorHeader !== "" ? Number(cursorHeader) : void 0;
|
|
490
|
+
return {
|
|
491
|
+
content: ok,
|
|
492
|
+
cursor: Number.isFinite(parsedCursor ?? NaN) ? parsedCursor : void 0
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
async *runStream(command, opts, signal) {
|
|
496
|
+
for await (const ev of this.streamExecution(
|
|
497
|
+
this.buildRunStreamSpec(command, opts),
|
|
498
|
+
signal
|
|
499
|
+
)) {
|
|
500
|
+
yield ev;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
async run(command, opts, handlers, signal) {
|
|
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");
|
|
519
|
+
}
|
|
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");
|
|
566
|
+
}
|
|
375
567
|
};
|
|
376
568
|
|
|
377
569
|
// src/adapters/filesystemAdapter.ts
|
|
@@ -859,11 +1051,15 @@ var SandboxesAdapter = class {
|
|
|
859
1051
|
}
|
|
860
1052
|
return d;
|
|
861
1053
|
}
|
|
1054
|
+
parseOptionalIsoDate(field, v) {
|
|
1055
|
+
if (v == null) return null;
|
|
1056
|
+
return this.parseIsoDate(field, v);
|
|
1057
|
+
}
|
|
862
1058
|
mapSandboxInfo(raw) {
|
|
863
1059
|
return {
|
|
864
1060
|
...raw ?? {},
|
|
865
1061
|
createdAt: this.parseIsoDate("createdAt", raw?.createdAt),
|
|
866
|
-
expiresAt: this.
|
|
1062
|
+
expiresAt: this.parseOptionalIsoDate("expiresAt", raw?.expiresAt)
|
|
867
1063
|
};
|
|
868
1064
|
}
|
|
869
1065
|
async createSandbox(req) {
|
|
@@ -879,7 +1075,7 @@ var SandboxesAdapter = class {
|
|
|
879
1075
|
return {
|
|
880
1076
|
...raw ?? {},
|
|
881
1077
|
createdAt: this.parseIsoDate("createdAt", raw?.createdAt),
|
|
882
|
-
expiresAt: this.
|
|
1078
|
+
expiresAt: this.parseOptionalIsoDate("expiresAt", raw?.expiresAt)
|
|
883
1079
|
};
|
|
884
1080
|
}
|
|
885
1081
|
async getSandbox(sandboxId) {
|
|
@@ -950,9 +1146,9 @@ var SandboxesAdapter = class {
|
|
|
950
1146
|
expiresAt: raw?.expiresAt ? this.parseIsoDate("expiresAt", raw.expiresAt) : void 0
|
|
951
1147
|
};
|
|
952
1148
|
}
|
|
953
|
-
async getSandboxEndpoint(sandboxId, port) {
|
|
1149
|
+
async getSandboxEndpoint(sandboxId, port, useServerProxy = false) {
|
|
954
1150
|
const { data, error, response } = await this.client.GET("/sandboxes/{sandboxId}/endpoints/{port}", {
|
|
955
|
-
params: { path: { sandboxId, port } }
|
|
1151
|
+
params: { path: { sandboxId, port }, query: { use_server_proxy: useServerProxy } }
|
|
956
1152
|
});
|
|
957
1153
|
throwOnOpenApiFetchError({ error, response }, "Get sandbox endpoint failed");
|
|
958
1154
|
const ok = data;
|
|
@@ -976,9 +1172,13 @@ var DefaultAdapterFactory = class {
|
|
|
976
1172
|
return { sandboxes };
|
|
977
1173
|
}
|
|
978
1174
|
createExecdStack(opts) {
|
|
1175
|
+
const headers = {
|
|
1176
|
+
...opts.connectionConfig.headers ?? {},
|
|
1177
|
+
...opts.endpointHeaders ?? {}
|
|
1178
|
+
};
|
|
979
1179
|
const execdClient = createExecdClient({
|
|
980
1180
|
baseUrl: opts.execdBaseUrl,
|
|
981
|
-
headers
|
|
1181
|
+
headers,
|
|
982
1182
|
fetch: opts.connectionConfig.fetch
|
|
983
1183
|
});
|
|
984
1184
|
const health = new HealthAdapter(execdClient);
|
|
@@ -986,12 +1186,12 @@ var DefaultAdapterFactory = class {
|
|
|
986
1186
|
const files = new FilesystemAdapter(execdClient, {
|
|
987
1187
|
baseUrl: opts.execdBaseUrl,
|
|
988
1188
|
fetch: opts.connectionConfig.fetch,
|
|
989
|
-
headers
|
|
1189
|
+
headers
|
|
990
1190
|
});
|
|
991
1191
|
const commands = new CommandsAdapter(execdClient, {
|
|
992
1192
|
baseUrl: opts.execdBaseUrl,
|
|
993
1193
|
fetch: opts.connectionConfig.sseFetch,
|
|
994
|
-
headers
|
|
1194
|
+
headers
|
|
995
1195
|
});
|
|
996
1196
|
return {
|
|
997
1197
|
commands,
|
|
@@ -1000,6 +1200,20 @@ var DefaultAdapterFactory = class {
|
|
|
1000
1200
|
metrics
|
|
1001
1201
|
};
|
|
1002
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
|
+
}
|
|
1003
1217
|
};
|
|
1004
1218
|
function createDefaultAdapterFactory() {
|
|
1005
1219
|
return new DefaultAdapterFactory();
|
|
@@ -1007,6 +1221,7 @@ function createDefaultAdapterFactory() {
|
|
|
1007
1221
|
|
|
1008
1222
|
// src/core/constants.ts
|
|
1009
1223
|
var DEFAULT_EXECD_PORT = 44772;
|
|
1224
|
+
var DEFAULT_EGRESS_PORT = 18080;
|
|
1010
1225
|
var DEFAULT_ENTRYPOINT = ["tail", "-f", "/dev/null"];
|
|
1011
1226
|
var DEFAULT_RESOURCE_LIMITS = {
|
|
1012
1227
|
cpu: "1",
|
|
@@ -1016,7 +1231,7 @@ var DEFAULT_TIMEOUT_SECONDS = 600;
|
|
|
1016
1231
|
var DEFAULT_READY_TIMEOUT_SECONDS = 30;
|
|
1017
1232
|
var DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS = 200;
|
|
1018
1233
|
var DEFAULT_REQUEST_TIMEOUT_SECONDS = 30;
|
|
1019
|
-
var DEFAULT_USER_AGENT = "OpenSandbox-JS-SDK/0.1.
|
|
1234
|
+
var DEFAULT_USER_AGENT = "OpenSandbox-JS-SDK/0.1.5";
|
|
1020
1235
|
|
|
1021
1236
|
// src/config/connection.ts
|
|
1022
1237
|
function isNodeRuntime2() {
|
|
@@ -1168,6 +1383,10 @@ var ConnectionConfig = class _ConnectionConfig {
|
|
|
1168
1383
|
requestTimeoutSeconds;
|
|
1169
1384
|
debug;
|
|
1170
1385
|
userAgent = DEFAULT_USER_AGENT;
|
|
1386
|
+
/**
|
|
1387
|
+
* Use sandbox server as proxy for endpoint requests (default false).
|
|
1388
|
+
*/
|
|
1389
|
+
useServerProxy;
|
|
1171
1390
|
_closeTransport;
|
|
1172
1391
|
_closePromise = null;
|
|
1173
1392
|
_transportInitialized = false;
|
|
@@ -1188,6 +1407,7 @@ var ConnectionConfig = class _ConnectionConfig {
|
|
|
1188
1407
|
this.apiKey = opts.apiKey ?? envApiKey;
|
|
1189
1408
|
this.requestTimeoutSeconds = typeof opts.requestTimeoutSeconds === "number" ? opts.requestTimeoutSeconds : 30;
|
|
1190
1409
|
this.debug = !!opts.debug;
|
|
1410
|
+
this.useServerProxy = !!opts.useServerProxy;
|
|
1191
1411
|
const headers = { ...opts.headers ?? {} };
|
|
1192
1412
|
if (this.apiKey && !headers["OPEN-SANDBOX-API-KEY"]) {
|
|
1193
1413
|
headers["OPEN-SANDBOX-API-KEY"] = this.apiKey;
|
|
@@ -1251,7 +1471,8 @@ var ConnectionConfig = class _ConnectionConfig {
|
|
|
1251
1471
|
apiKey: this.apiKey,
|
|
1252
1472
|
headers: { ...this.headers },
|
|
1253
1473
|
requestTimeoutSeconds: this.requestTimeoutSeconds,
|
|
1254
|
-
debug: this.debug
|
|
1474
|
+
debug: this.debug,
|
|
1475
|
+
useServerProxy: this.useServerProxy
|
|
1255
1476
|
});
|
|
1256
1477
|
clone.initializeTransport();
|
|
1257
1478
|
return clone;
|
|
@@ -1367,7 +1588,8 @@ var Sandbox = class _Sandbox {
|
|
|
1367
1588
|
_Sandbox._priv.set(this, {
|
|
1368
1589
|
adapterFactory: opts.adapterFactory,
|
|
1369
1590
|
lifecycleBaseUrl: opts.lifecycleBaseUrl,
|
|
1370
|
-
execdBaseUrl: opts.execdBaseUrl
|
|
1591
|
+
execdBaseUrl: opts.execdBaseUrl,
|
|
1592
|
+
egress: opts.egress
|
|
1371
1593
|
});
|
|
1372
1594
|
this.sandboxes = opts.sandboxes;
|
|
1373
1595
|
this.commands = opts.commands;
|
|
@@ -1390,10 +1612,31 @@ var Sandbox = class _Sandbox {
|
|
|
1390
1612
|
await connectionConfig.closeTransport();
|
|
1391
1613
|
throw err;
|
|
1392
1614
|
}
|
|
1615
|
+
if (opts.volumes) {
|
|
1616
|
+
for (const vol of opts.volumes) {
|
|
1617
|
+
const backendsSpecified = [vol.host, vol.pvc, vol.ossfs].filter((b) => b != null).length;
|
|
1618
|
+
if (backendsSpecified === 0) {
|
|
1619
|
+
throw new Error(
|
|
1620
|
+
`Volume '${vol.name}' must specify exactly one backend (host, pvc, ossfs), but none was provided.`
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1623
|
+
if (backendsSpecified > 1) {
|
|
1624
|
+
throw new Error(
|
|
1625
|
+
`Volume '${vol.name}' must specify exactly one backend (host, pvc, ossfs), but multiple were provided.`
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
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
|
+
}
|
|
1393
1637
|
const req = {
|
|
1394
1638
|
image: toImageSpec(opts.image),
|
|
1395
1639
|
entrypoint: opts.entrypoint ?? DEFAULT_ENTRYPOINT,
|
|
1396
|
-
timeout: Math.floor(opts.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS),
|
|
1397
1640
|
resourceLimits: opts.resource ?? DEFAULT_RESOURCE_LIMITS,
|
|
1398
1641
|
env: opts.env ?? {},
|
|
1399
1642
|
metadata: opts.metadata ?? {},
|
|
@@ -1401,20 +1644,37 @@ var Sandbox = class _Sandbox {
|
|
|
1401
1644
|
...opts.networkPolicy,
|
|
1402
1645
|
defaultAction: opts.networkPolicy.defaultAction ?? "deny"
|
|
1403
1646
|
} : void 0,
|
|
1647
|
+
volumes: opts.volumes,
|
|
1404
1648
|
extensions: opts.extensions ?? {}
|
|
1405
1649
|
};
|
|
1650
|
+
if (timeoutSeconds !== null) {
|
|
1651
|
+
req.timeout = timeoutSeconds;
|
|
1652
|
+
}
|
|
1406
1653
|
let sandboxId;
|
|
1407
1654
|
try {
|
|
1408
1655
|
const created = await sandboxes.createSandbox(req);
|
|
1409
1656
|
sandboxId = created.id;
|
|
1410
1657
|
const endpoint = await sandboxes.getSandboxEndpoint(
|
|
1411
1658
|
sandboxId,
|
|
1412
|
-
DEFAULT_EXECD_PORT
|
|
1659
|
+
DEFAULT_EXECD_PORT,
|
|
1660
|
+
connectionConfig.useServerProxy
|
|
1661
|
+
);
|
|
1662
|
+
const egressEndpoint = await sandboxes.getSandboxEndpoint(
|
|
1663
|
+
sandboxId,
|
|
1664
|
+
DEFAULT_EGRESS_PORT,
|
|
1665
|
+
connectionConfig.useServerProxy
|
|
1413
1666
|
);
|
|
1414
1667
|
const execdBaseUrl = `${connectionConfig.protocol}://${endpoint.endpoint}`;
|
|
1668
|
+
const egressBaseUrl = `${connectionConfig.protocol}://${egressEndpoint.endpoint}`;
|
|
1415
1669
|
const { commands, files, health, metrics } = adapterFactory.createExecdStack({
|
|
1416
1670
|
connectionConfig,
|
|
1417
|
-
execdBaseUrl
|
|
1671
|
+
execdBaseUrl,
|
|
1672
|
+
endpointHeaders: endpoint.headers
|
|
1673
|
+
});
|
|
1674
|
+
const { egress } = adapterFactory.createEgressStack({
|
|
1675
|
+
connectionConfig,
|
|
1676
|
+
egressBaseUrl,
|
|
1677
|
+
endpointHeaders: egressEndpoint.headers
|
|
1418
1678
|
});
|
|
1419
1679
|
const sbx = new _Sandbox({
|
|
1420
1680
|
id: sandboxId,
|
|
@@ -1426,7 +1686,8 @@ var Sandbox = class _Sandbox {
|
|
|
1426
1686
|
commands,
|
|
1427
1687
|
files,
|
|
1428
1688
|
health,
|
|
1429
|
-
metrics
|
|
1689
|
+
metrics,
|
|
1690
|
+
egress
|
|
1430
1691
|
});
|
|
1431
1692
|
if (!(opts.skipHealthCheck ?? false)) {
|
|
1432
1693
|
await sbx.waitUntilReady({
|
|
@@ -1465,12 +1726,25 @@ var Sandbox = class _Sandbox {
|
|
|
1465
1726
|
try {
|
|
1466
1727
|
const endpoint = await sandboxes.getSandboxEndpoint(
|
|
1467
1728
|
opts.sandboxId,
|
|
1468
|
-
DEFAULT_EXECD_PORT
|
|
1729
|
+
DEFAULT_EXECD_PORT,
|
|
1730
|
+
connectionConfig.useServerProxy
|
|
1731
|
+
);
|
|
1732
|
+
const egressEndpoint = await sandboxes.getSandboxEndpoint(
|
|
1733
|
+
opts.sandboxId,
|
|
1734
|
+
DEFAULT_EGRESS_PORT,
|
|
1735
|
+
connectionConfig.useServerProxy
|
|
1469
1736
|
);
|
|
1470
1737
|
const execdBaseUrl = `${connectionConfig.protocol}://${endpoint.endpoint}`;
|
|
1738
|
+
const egressBaseUrl = `${connectionConfig.protocol}://${egressEndpoint.endpoint}`;
|
|
1471
1739
|
const { commands, files, health, metrics } = adapterFactory.createExecdStack({
|
|
1472
1740
|
connectionConfig,
|
|
1473
|
-
execdBaseUrl
|
|
1741
|
+
execdBaseUrl,
|
|
1742
|
+
endpointHeaders: endpoint.headers
|
|
1743
|
+
});
|
|
1744
|
+
const { egress } = adapterFactory.createEgressStack({
|
|
1745
|
+
connectionConfig,
|
|
1746
|
+
egressBaseUrl,
|
|
1747
|
+
endpointHeaders: egressEndpoint.headers
|
|
1474
1748
|
});
|
|
1475
1749
|
const sbx = new _Sandbox({
|
|
1476
1750
|
id: opts.sandboxId,
|
|
@@ -1482,7 +1756,8 @@ var Sandbox = class _Sandbox {
|
|
|
1482
1756
|
commands,
|
|
1483
1757
|
files,
|
|
1484
1758
|
health,
|
|
1485
|
-
metrics
|
|
1759
|
+
metrics,
|
|
1760
|
+
egress
|
|
1486
1761
|
});
|
|
1487
1762
|
if (!(opts.skipHealthCheck ?? false)) {
|
|
1488
1763
|
await sbx.waitUntilReady({
|
|
@@ -1570,11 +1845,21 @@ var Sandbox = class _Sandbox {
|
|
|
1570
1845
|
).toISOString();
|
|
1571
1846
|
return await this.sandboxes.renewSandboxExpiration(this.id, { expiresAt });
|
|
1572
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
|
+
}
|
|
1573
1854
|
/**
|
|
1574
1855
|
* Get sandbox endpoint for a port (STRICT: no scheme), e.g. "localhost:44772" or "domain/route/.../44772".
|
|
1575
1856
|
*/
|
|
1576
1857
|
async getEndpoint(port) {
|
|
1577
|
-
return await this.sandboxes.getSandboxEndpoint(
|
|
1858
|
+
return await this.sandboxes.getSandboxEndpoint(
|
|
1859
|
+
this.id,
|
|
1860
|
+
port,
|
|
1861
|
+
this.connectionConfig.useServerProxy
|
|
1862
|
+
);
|
|
1578
1863
|
}
|
|
1579
1864
|
/**
|
|
1580
1865
|
* Get absolute endpoint URL with scheme (convenience for HTTP clients).
|
|
@@ -1585,21 +1870,39 @@ var Sandbox = class _Sandbox {
|
|
|
1585
1870
|
}
|
|
1586
1871
|
async waitUntilReady(opts) {
|
|
1587
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
|
+
};
|
|
1588
1883
|
while (true) {
|
|
1589
1884
|
if (Date.now() > deadline) {
|
|
1590
1885
|
throw new SandboxReadyTimeoutException({
|
|
1591
|
-
message:
|
|
1886
|
+
message: buildTimeoutMessage()
|
|
1592
1887
|
});
|
|
1593
1888
|
}
|
|
1889
|
+
attempt++;
|
|
1594
1890
|
try {
|
|
1595
1891
|
if (opts.healthCheck) {
|
|
1596
1892
|
const ok = await opts.healthCheck(this);
|
|
1597
|
-
if (ok)
|
|
1893
|
+
if (ok) {
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1598
1896
|
} else {
|
|
1599
1897
|
const ok = await this.health.ping();
|
|
1600
|
-
if (ok)
|
|
1898
|
+
if (ok) {
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1601
1901
|
}
|
|
1602
|
-
|
|
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}`;
|
|
1603
1906
|
}
|
|
1604
1907
|
await sleep(opts.pollingIntervalMillis);
|
|
1605
1908
|
}
|
|
@@ -1608,6 +1911,7 @@ var Sandbox = class _Sandbox {
|
|
|
1608
1911
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1609
1912
|
0 && (module.exports = {
|
|
1610
1913
|
ConnectionConfig,
|
|
1914
|
+
DEFAULT_EGRESS_PORT,
|
|
1611
1915
|
DEFAULT_ENTRYPOINT,
|
|
1612
1916
|
DEFAULT_EXECD_PORT,
|
|
1613
1917
|
DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS,
|