@fastgpt-sdk/sandbox-adapter 0.0.30 → 0.0.32
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/dist/adapters/BaseSandboxAdapter.d.ts +2 -1
- package/dist/adapters/E2BAdapter/type.d.ts +1 -1
- package/dist/adapters/OpenSandboxAdapter/index.d.ts +9 -10
- package/dist/adapters/OpenSandboxAdapter/type.d.ts +1 -1
- package/dist/adapters/SealosDevboxAdapter/index.d.ts +0 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/index.cjs +106 -44
- package/dist/index.d.ts +1 -0
- package/dist/index.js +106 -44
- package/dist/interfaces/ISandboxLifecycle.d.ts +2 -1
- package/package.json +2 -4
- package/opensandbox/chunk-PHJSF3IJ.js +0 -1067
- package/opensandbox/chunk-PHJSF3IJ.js.map +0 -1
- package/opensandbox/cjs/index.cjs +0 -1912
- package/opensandbox/cjs/index.cjs.map +0 -1
- package/opensandbox/cjs/internal.cjs +0 -1060
- package/opensandbox/cjs/internal.cjs.map +0 -1
- package/opensandbox/index.d.ts +0 -463
- package/opensandbox/index.js +0 -826
- package/opensandbox/index.js.map +0 -1
- package/opensandbox/internal.d.ts +0 -2862
- package/opensandbox/internal.js +0 -19
- package/opensandbox/internal.js.map +0 -1
- package/opensandbox/package.json +0 -15
- package/opensandbox/sandboxes-pbhLrfFS.d.ts +0 -498
|
@@ -1,1912 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// src/index.ts
|
|
31
|
-
var index_exports = {};
|
|
32
|
-
__export(index_exports, {
|
|
33
|
-
ConnectionConfig: () => ConnectionConfig,
|
|
34
|
-
DEFAULT_EGRESS_PORT: () => DEFAULT_EGRESS_PORT,
|
|
35
|
-
DEFAULT_ENTRYPOINT: () => DEFAULT_ENTRYPOINT,
|
|
36
|
-
DEFAULT_EXECD_PORT: () => DEFAULT_EXECD_PORT,
|
|
37
|
-
DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS: () => DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS,
|
|
38
|
-
DEFAULT_READY_TIMEOUT_SECONDS: () => DEFAULT_READY_TIMEOUT_SECONDS,
|
|
39
|
-
DEFAULT_REQUEST_TIMEOUT_SECONDS: () => DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
|
40
|
-
DEFAULT_RESOURCE_LIMITS: () => DEFAULT_RESOURCE_LIMITS,
|
|
41
|
-
DEFAULT_TIMEOUT_SECONDS: () => DEFAULT_TIMEOUT_SECONDS,
|
|
42
|
-
DefaultAdapterFactory: () => DefaultAdapterFactory,
|
|
43
|
-
ExecutionEventDispatcher: () => ExecutionEventDispatcher,
|
|
44
|
-
InvalidArgumentException: () => InvalidArgumentException,
|
|
45
|
-
Sandbox: () => Sandbox,
|
|
46
|
-
SandboxApiException: () => SandboxApiException,
|
|
47
|
-
SandboxError: () => SandboxError,
|
|
48
|
-
SandboxException: () => SandboxException,
|
|
49
|
-
SandboxInternalException: () => SandboxInternalException,
|
|
50
|
-
SandboxManager: () => SandboxManager,
|
|
51
|
-
SandboxReadyTimeoutException: () => SandboxReadyTimeoutException,
|
|
52
|
-
SandboxUnhealthyException: () => SandboxUnhealthyException,
|
|
53
|
-
createDefaultAdapterFactory: () => createDefaultAdapterFactory
|
|
54
|
-
});
|
|
55
|
-
module.exports = __toCommonJS(index_exports);
|
|
56
|
-
|
|
57
|
-
// src/core/exceptions.ts
|
|
58
|
-
var SandboxError = class {
|
|
59
|
-
constructor(code, message) {
|
|
60
|
-
this.code = code;
|
|
61
|
-
this.message = message;
|
|
62
|
-
}
|
|
63
|
-
static INTERNAL_UNKNOWN_ERROR = "INTERNAL_UNKNOWN_ERROR";
|
|
64
|
-
static READY_TIMEOUT = "READY_TIMEOUT";
|
|
65
|
-
static UNHEALTHY = "UNHEALTHY";
|
|
66
|
-
static INVALID_ARGUMENT = "INVALID_ARGUMENT";
|
|
67
|
-
static UNEXPECTED_RESPONSE = "UNEXPECTED_RESPONSE";
|
|
68
|
-
};
|
|
69
|
-
var SandboxException = class extends Error {
|
|
70
|
-
name = "SandboxException";
|
|
71
|
-
error;
|
|
72
|
-
cause;
|
|
73
|
-
requestId;
|
|
74
|
-
constructor(opts = {}) {
|
|
75
|
-
super(opts.message);
|
|
76
|
-
this.cause = opts.cause;
|
|
77
|
-
this.error = opts.error ?? new SandboxError(SandboxError.INTERNAL_UNKNOWN_ERROR);
|
|
78
|
-
this.requestId = opts.requestId;
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
var SandboxApiException = class extends SandboxException {
|
|
82
|
-
name = "SandboxApiException";
|
|
83
|
-
statusCode;
|
|
84
|
-
rawBody;
|
|
85
|
-
constructor(opts) {
|
|
86
|
-
super({
|
|
87
|
-
message: opts.message,
|
|
88
|
-
cause: opts.cause,
|
|
89
|
-
error: opts.error ?? new SandboxError(SandboxError.UNEXPECTED_RESPONSE, opts.message),
|
|
90
|
-
requestId: opts.requestId
|
|
91
|
-
});
|
|
92
|
-
this.statusCode = opts.statusCode;
|
|
93
|
-
this.rawBody = opts.rawBody;
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
var SandboxInternalException = class extends SandboxException {
|
|
97
|
-
name = "SandboxInternalException";
|
|
98
|
-
constructor(opts) {
|
|
99
|
-
super({
|
|
100
|
-
message: opts.message,
|
|
101
|
-
cause: opts.cause,
|
|
102
|
-
error: new SandboxError(SandboxError.INTERNAL_UNKNOWN_ERROR, opts.message)
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
var SandboxUnhealthyException = class extends SandboxException {
|
|
107
|
-
name = "SandboxUnhealthyException";
|
|
108
|
-
constructor(opts) {
|
|
109
|
-
super({
|
|
110
|
-
message: opts.message,
|
|
111
|
-
cause: opts.cause,
|
|
112
|
-
error: new SandboxError(SandboxError.UNHEALTHY, opts.message)
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
var SandboxReadyTimeoutException = class extends SandboxException {
|
|
117
|
-
name = "SandboxReadyTimeoutException";
|
|
118
|
-
constructor(opts) {
|
|
119
|
-
super({
|
|
120
|
-
message: opts.message,
|
|
121
|
-
cause: opts.cause,
|
|
122
|
-
error: new SandboxError(SandboxError.READY_TIMEOUT, opts.message)
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
var InvalidArgumentException = class extends SandboxException {
|
|
127
|
-
name = "InvalidArgumentException";
|
|
128
|
-
constructor(opts) {
|
|
129
|
-
super({
|
|
130
|
-
message: opts.message,
|
|
131
|
-
cause: opts.cause,
|
|
132
|
-
error: new SandboxError(SandboxError.INVALID_ARGUMENT, opts.message)
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// src/openapi/execdClient.ts
|
|
138
|
-
var import_openapi_fetch = __toESM(require("openapi-fetch"), 1);
|
|
139
|
-
function createExecdClient(opts) {
|
|
140
|
-
const createClientFn = import_openapi_fetch.default.default ?? import_openapi_fetch.default;
|
|
141
|
-
return createClientFn({
|
|
142
|
-
baseUrl: opts.baseUrl,
|
|
143
|
-
headers: opts.headers,
|
|
144
|
-
fetch: opts.fetch
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// src/openapi/egressClient.ts
|
|
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);
|
|
161
|
-
function readEnvApiKey() {
|
|
162
|
-
const env = globalThis?.process?.env;
|
|
163
|
-
const v = env?.OPEN_SANDBOX_API_KEY;
|
|
164
|
-
return typeof v === "string" && v.length ? v : void 0;
|
|
165
|
-
}
|
|
166
|
-
function createLifecycleClient(opts = {}) {
|
|
167
|
-
const apiKey = opts.apiKey ?? readEnvApiKey();
|
|
168
|
-
const headers = {
|
|
169
|
-
...opts.headers ?? {}
|
|
170
|
-
};
|
|
171
|
-
if (apiKey && !headers["OPEN-SANDBOX-API-KEY"]) {
|
|
172
|
-
headers["OPEN-SANDBOX-API-KEY"] = apiKey;
|
|
173
|
-
}
|
|
174
|
-
const createClientFn = import_openapi_fetch3.default.default ?? import_openapi_fetch3.default;
|
|
175
|
-
return createClientFn({
|
|
176
|
-
baseUrl: opts.baseUrl ?? "http://localhost:8080/v1",
|
|
177
|
-
headers,
|
|
178
|
-
fetch: opts.fetch
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// src/adapters/openapiError.ts
|
|
183
|
-
function throwOnOpenApiFetchError(result, fallbackMessage) {
|
|
184
|
-
if (!result.error) return;
|
|
185
|
-
const requestId = result.response.headers.get("x-request-id") ?? void 0;
|
|
186
|
-
const status = result.response.status ?? 0;
|
|
187
|
-
const err = result.error;
|
|
188
|
-
const message = err?.message ?? err?.error?.message ?? fallbackMessage;
|
|
189
|
-
const code = err?.code ?? err?.error?.code;
|
|
190
|
-
const msg = err?.message ?? err?.error?.message ?? message;
|
|
191
|
-
throw new SandboxApiException({
|
|
192
|
-
message: msg,
|
|
193
|
-
statusCode: status,
|
|
194
|
-
requestId,
|
|
195
|
-
error: code ? new SandboxError(String(code), String(msg ?? "")) : new SandboxError(SandboxError.UNEXPECTED_RESPONSE, String(msg ?? "")),
|
|
196
|
-
rawBody: result.error
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// src/adapters/sse.ts
|
|
201
|
-
function tryParseJson(line) {
|
|
202
|
-
try {
|
|
203
|
-
return JSON.parse(line);
|
|
204
|
-
} catch {
|
|
205
|
-
return void 0;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
async function* parseJsonEventStream(res, opts) {
|
|
209
|
-
if (!res.ok) {
|
|
210
|
-
const text = await res.text().catch(() => "");
|
|
211
|
-
const parsed = tryParseJson(text);
|
|
212
|
-
const err = parsed && typeof parsed === "object" ? parsed : void 0;
|
|
213
|
-
const requestId = res.headers.get("x-request-id") ?? void 0;
|
|
214
|
-
const message = err?.message ?? opts?.fallbackErrorMessage ?? `Stream request failed (status=${res.status})`;
|
|
215
|
-
const code = err?.code ? String(err.code) : SandboxError.UNEXPECTED_RESPONSE;
|
|
216
|
-
throw new SandboxApiException({
|
|
217
|
-
message,
|
|
218
|
-
statusCode: res.status,
|
|
219
|
-
requestId,
|
|
220
|
-
error: new SandboxError(code, err?.message ? String(err.message) : message),
|
|
221
|
-
rawBody: parsed ?? text
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
if (!res.body) {
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
const reader = res.body.getReader();
|
|
228
|
-
const decoder = new TextDecoder("utf-8");
|
|
229
|
-
let buf = "";
|
|
230
|
-
while (true) {
|
|
231
|
-
const { value, done } = await reader.read();
|
|
232
|
-
if (done) break;
|
|
233
|
-
buf += decoder.decode(value, { stream: true });
|
|
234
|
-
let idx;
|
|
235
|
-
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
236
|
-
const rawLine = buf.slice(0, idx);
|
|
237
|
-
buf = buf.slice(idx + 1);
|
|
238
|
-
const line = rawLine.trim();
|
|
239
|
-
if (!line) continue;
|
|
240
|
-
if (line.startsWith(":")) continue;
|
|
241
|
-
if (line.startsWith("event:") || line.startsWith("id:") || line.startsWith("retry:")) continue;
|
|
242
|
-
const jsonLine = line.startsWith("data:") ? line.slice("data:".length).trim() : line;
|
|
243
|
-
if (!jsonLine) continue;
|
|
244
|
-
const parsed = tryParseJson(jsonLine);
|
|
245
|
-
if (!parsed) continue;
|
|
246
|
-
yield parsed;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
buf += decoder.decode();
|
|
250
|
-
const last = buf.trim();
|
|
251
|
-
if (last) {
|
|
252
|
-
const jsonLine = last.startsWith("data:") ? last.slice("data:".length).trim() : last;
|
|
253
|
-
const parsed = tryParseJson(jsonLine);
|
|
254
|
-
if (parsed) yield parsed;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// src/models/executionEventDispatcher.ts
|
|
259
|
-
function extractText(results) {
|
|
260
|
-
if (!results || typeof results !== "object") return void 0;
|
|
261
|
-
const r = results;
|
|
262
|
-
const v = r["text/plain"] ?? r.text ?? r.textPlain;
|
|
263
|
-
return v == null ? void 0 : String(v);
|
|
264
|
-
}
|
|
265
|
-
var ExecutionEventDispatcher = class {
|
|
266
|
-
constructor(execution, handlers) {
|
|
267
|
-
this.execution = execution;
|
|
268
|
-
this.handlers = handlers;
|
|
269
|
-
}
|
|
270
|
-
async dispatch(ev) {
|
|
271
|
-
await this.handlers?.onEvent?.(ev);
|
|
272
|
-
const ts = ev.timestamp ?? Date.now();
|
|
273
|
-
switch (ev.type) {
|
|
274
|
-
case "init": {
|
|
275
|
-
const id = ev.text ?? "";
|
|
276
|
-
if (id) this.execution.id = id;
|
|
277
|
-
const init = { id, timestamp: ts };
|
|
278
|
-
await this.handlers?.onInit?.(init);
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
case "stdout": {
|
|
282
|
-
const msg = { text: ev.text ?? "", timestamp: ts, isError: false };
|
|
283
|
-
this.execution.logs.stdout.push(msg);
|
|
284
|
-
await this.handlers?.onStdout?.(msg);
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
case "stderr": {
|
|
288
|
-
const msg = { text: ev.text ?? "", timestamp: ts, isError: true };
|
|
289
|
-
this.execution.logs.stderr.push(msg);
|
|
290
|
-
await this.handlers?.onStderr?.(msg);
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
case "result": {
|
|
294
|
-
const r = { text: extractText(ev.results), timestamp: ts, raw: ev.results };
|
|
295
|
-
this.execution.result.push(r);
|
|
296
|
-
await this.handlers?.onResult?.(r);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
case "execution_count": {
|
|
300
|
-
const c = ev.execution_count;
|
|
301
|
-
if (typeof c === "number") this.execution.executionCount = c;
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
case "execution_complete": {
|
|
305
|
-
const ms = ev.execution_time;
|
|
306
|
-
const complete = { timestamp: ts, executionTimeMs: typeof ms === "number" ? ms : 0 };
|
|
307
|
-
this.execution.complete = complete;
|
|
308
|
-
await this.handlers?.onExecutionComplete?.(complete);
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
case "error": {
|
|
312
|
-
const e = ev.error;
|
|
313
|
-
if (e) {
|
|
314
|
-
const err = {
|
|
315
|
-
name: String(e.ename ?? e.name ?? ""),
|
|
316
|
-
value: String(e.evalue ?? e.value ?? ""),
|
|
317
|
-
timestamp: ts,
|
|
318
|
-
traceback: Array.isArray(e.traceback) ? e.traceback.map(String) : []
|
|
319
|
-
};
|
|
320
|
-
this.execution.error = err;
|
|
321
|
-
await this.handlers?.onError?.(err);
|
|
322
|
-
}
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
default:
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
// src/adapters/commandsAdapter.ts
|
|
332
|
-
function joinUrl(baseUrl, pathname) {
|
|
333
|
-
const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
334
|
-
const path = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
335
|
-
return `${base}${path}`;
|
|
336
|
-
}
|
|
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
|
-
}
|
|
341
|
-
const body = {
|
|
342
|
-
command,
|
|
343
|
-
cwd: opts?.workingDirectory,
|
|
344
|
-
background: !!opts?.background
|
|
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 parseOptionalDate(value, field) {
|
|
361
|
-
if (value == null) return void 0;
|
|
362
|
-
if (value instanceof Date) return value;
|
|
363
|
-
if (typeof value !== "string") {
|
|
364
|
-
throw new Error(`Invalid ${field}: expected ISO string, got ${typeof value}`);
|
|
365
|
-
}
|
|
366
|
-
const parsed = new Date(value);
|
|
367
|
-
if (Number.isNaN(parsed.getTime())) {
|
|
368
|
-
throw new Error(`Invalid ${field}: ${value}`);
|
|
369
|
-
}
|
|
370
|
-
return parsed;
|
|
371
|
-
}
|
|
372
|
-
var CommandsAdapter = class {
|
|
373
|
-
constructor(client, opts) {
|
|
374
|
-
this.client = client;
|
|
375
|
-
this.opts = opts;
|
|
376
|
-
this.fetch = opts.fetch ?? fetch;
|
|
377
|
-
}
|
|
378
|
-
fetch;
|
|
379
|
-
async interrupt(sessionId) {
|
|
380
|
-
const { error, response } = await this.client.DELETE("/command", {
|
|
381
|
-
params: { query: { id: sessionId } }
|
|
382
|
-
});
|
|
383
|
-
throwOnOpenApiFetchError({ error, response }, "Interrupt command failed");
|
|
384
|
-
}
|
|
385
|
-
async getCommandStatus(commandId) {
|
|
386
|
-
const { data, error, response } = await this.client.GET("/command/status/{id}", {
|
|
387
|
-
params: { path: { id: commandId } }
|
|
388
|
-
});
|
|
389
|
-
throwOnOpenApiFetchError({ error, response }, "Get command status failed");
|
|
390
|
-
const ok = data;
|
|
391
|
-
if (!ok || typeof ok !== "object") {
|
|
392
|
-
throw new Error("Get command status failed: unexpected response shape");
|
|
393
|
-
}
|
|
394
|
-
return {
|
|
395
|
-
id: ok.id,
|
|
396
|
-
content: ok.content,
|
|
397
|
-
running: ok.running,
|
|
398
|
-
exitCode: ok.exit_code ?? null,
|
|
399
|
-
error: ok.error,
|
|
400
|
-
startedAt: parseOptionalDate(ok.started_at, "startedAt"),
|
|
401
|
-
finishedAt: parseOptionalDate(ok.finished_at, "finishedAt") ?? null
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
async getBackgroundCommandLogs(commandId, cursor) {
|
|
405
|
-
const { data, error, response } = await this.client.GET("/command/{id}/logs", {
|
|
406
|
-
params: { path: { id: commandId }, query: cursor == null ? {} : { cursor } },
|
|
407
|
-
parseAs: "text"
|
|
408
|
-
});
|
|
409
|
-
throwOnOpenApiFetchError({ error, response }, "Get command logs failed");
|
|
410
|
-
const ok = data;
|
|
411
|
-
if (typeof ok !== "string") {
|
|
412
|
-
throw new Error("Get command logs failed: unexpected response shape");
|
|
413
|
-
}
|
|
414
|
-
const cursorHeader = response.headers.get("EXECD-COMMANDS-TAIL-CURSOR");
|
|
415
|
-
const parsedCursor = cursorHeader != null && cursorHeader !== "" ? Number(cursorHeader) : void 0;
|
|
416
|
-
return {
|
|
417
|
-
content: ok,
|
|
418
|
-
cursor: Number.isFinite(parsedCursor ?? NaN) ? parsedCursor : void 0
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
async *runStream(command, opts, signal) {
|
|
422
|
-
const url = joinUrl(this.opts.baseUrl, "/command");
|
|
423
|
-
const body = JSON.stringify(toRunCommandRequest(command, opts));
|
|
424
|
-
const res = await this.fetch(url, {
|
|
425
|
-
method: "POST",
|
|
426
|
-
headers: {
|
|
427
|
-
"accept": "text/event-stream",
|
|
428
|
-
"content-type": "application/json",
|
|
429
|
-
...this.opts.headers ?? {}
|
|
430
|
-
},
|
|
431
|
-
body,
|
|
432
|
-
signal
|
|
433
|
-
});
|
|
434
|
-
for await (const ev of parseJsonEventStream(res, { fallbackErrorMessage: "Run command failed" })) {
|
|
435
|
-
yield ev;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
async run(command, opts, handlers, signal) {
|
|
439
|
-
const execution = {
|
|
440
|
-
logs: { stdout: [], stderr: [] },
|
|
441
|
-
result: []
|
|
442
|
-
};
|
|
443
|
-
const dispatcher = new ExecutionEventDispatcher(execution, handlers);
|
|
444
|
-
for await (const ev of this.runStream(command, opts, signal)) {
|
|
445
|
-
if (ev.type === "init" && (ev.text ?? "") === "" && execution.id) {
|
|
446
|
-
ev.text = execution.id;
|
|
447
|
-
}
|
|
448
|
-
await dispatcher.dispatch(ev);
|
|
449
|
-
}
|
|
450
|
-
if (!opts?.background) {
|
|
451
|
-
const errorValue = execution.error?.value?.trim();
|
|
452
|
-
const parsedExitCode = errorValue && /^-?\d+$/.test(errorValue) ? Number(errorValue) : Number.NaN;
|
|
453
|
-
execution.exitCode = execution.error != null ? Number.isFinite(parsedExitCode) ? parsedExitCode : null : execution.complete ? 0 : null;
|
|
454
|
-
}
|
|
455
|
-
return execution;
|
|
456
|
-
}
|
|
457
|
-
async createSession(options) {
|
|
458
|
-
const body = options?.cwd != null ? { cwd: options.cwd } : {};
|
|
459
|
-
const { data, error, response } = await this.client.POST("/session", {
|
|
460
|
-
body
|
|
461
|
-
});
|
|
462
|
-
throwOnOpenApiFetchError({ error, response }, "Create session failed");
|
|
463
|
-
const ok = data;
|
|
464
|
-
if (!ok || typeof ok.session_id !== "string") {
|
|
465
|
-
throw new Error("Create session failed: unexpected response shape");
|
|
466
|
-
}
|
|
467
|
-
return ok.session_id;
|
|
468
|
-
}
|
|
469
|
-
async *runInSessionStream(sessionId, code, opts, signal) {
|
|
470
|
-
const url = joinUrl(
|
|
471
|
-
this.opts.baseUrl,
|
|
472
|
-
`/session/${encodeURIComponent(sessionId)}/run`
|
|
473
|
-
);
|
|
474
|
-
const body = {
|
|
475
|
-
code
|
|
476
|
-
};
|
|
477
|
-
if (opts?.cwd != null) body.cwd = opts.cwd;
|
|
478
|
-
if (opts?.timeoutMs != null) body.timeout_ms = opts.timeoutMs;
|
|
479
|
-
const res = await this.fetch(url, {
|
|
480
|
-
method: "POST",
|
|
481
|
-
headers: {
|
|
482
|
-
accept: "text/event-stream",
|
|
483
|
-
"content-type": "application/json",
|
|
484
|
-
...this.opts.headers ?? {}
|
|
485
|
-
},
|
|
486
|
-
body: JSON.stringify(body),
|
|
487
|
-
signal
|
|
488
|
-
});
|
|
489
|
-
for await (const ev of parseJsonEventStream(res, {
|
|
490
|
-
fallbackErrorMessage: "Run in session failed"
|
|
491
|
-
})) {
|
|
492
|
-
yield ev;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
async runInSession(sessionId, code, options, handlers, signal) {
|
|
496
|
-
const execution = {
|
|
497
|
-
logs: { stdout: [], stderr: [] },
|
|
498
|
-
result: []
|
|
499
|
-
};
|
|
500
|
-
const dispatcher = new ExecutionEventDispatcher(execution, handlers);
|
|
501
|
-
for await (const ev of this.runInSessionStream(
|
|
502
|
-
sessionId,
|
|
503
|
-
code,
|
|
504
|
-
options,
|
|
505
|
-
signal
|
|
506
|
-
)) {
|
|
507
|
-
if (ev.type === "init" && (ev.text ?? "") === "" && execution.id) {
|
|
508
|
-
ev.text = execution.id;
|
|
509
|
-
}
|
|
510
|
-
await dispatcher.dispatch(ev);
|
|
511
|
-
}
|
|
512
|
-
return execution;
|
|
513
|
-
}
|
|
514
|
-
async deleteSession(sessionId) {
|
|
515
|
-
const { error, response } = await this.client.DELETE(
|
|
516
|
-
"/session/{sessionId}",
|
|
517
|
-
{ params: { path: { sessionId } } }
|
|
518
|
-
);
|
|
519
|
-
throwOnOpenApiFetchError({ error, response }, "Delete session failed");
|
|
520
|
-
}
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
// src/adapters/egressAdapter.ts
|
|
524
|
-
var EgressAdapter = class {
|
|
525
|
-
constructor(client) {
|
|
526
|
-
this.client = client;
|
|
527
|
-
}
|
|
528
|
-
async getPolicy() {
|
|
529
|
-
const { data, error, response } = await this.client.GET("/policy");
|
|
530
|
-
throwOnOpenApiFetchError({ error, response }, "Get sandbox egress policy failed");
|
|
531
|
-
const raw = data;
|
|
532
|
-
if (!raw || typeof raw !== "object" || !raw.policy || typeof raw.policy !== "object") {
|
|
533
|
-
throw new Error("Get sandbox egress policy failed: unexpected response shape");
|
|
534
|
-
}
|
|
535
|
-
return raw.policy;
|
|
536
|
-
}
|
|
537
|
-
async patchRules(rules) {
|
|
538
|
-
const body = rules;
|
|
539
|
-
const { error, response } = await this.client.PATCH("/policy", {
|
|
540
|
-
body
|
|
541
|
-
});
|
|
542
|
-
throwOnOpenApiFetchError({ error, response }, "Patch sandbox egress rules failed");
|
|
543
|
-
}
|
|
544
|
-
};
|
|
545
|
-
|
|
546
|
-
// src/adapters/filesystemAdapter.ts
|
|
547
|
-
function joinUrl2(baseUrl, pathname) {
|
|
548
|
-
const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
549
|
-
const path = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
550
|
-
return `${base}${path}`;
|
|
551
|
-
}
|
|
552
|
-
function toUploadBlob(data) {
|
|
553
|
-
if (typeof data === "string") return new Blob([data]);
|
|
554
|
-
if (data instanceof Blob) return data;
|
|
555
|
-
if (data instanceof ArrayBuffer) return new Blob([data]);
|
|
556
|
-
const copied = Uint8Array.from(data);
|
|
557
|
-
return new Blob([copied.buffer]);
|
|
558
|
-
}
|
|
559
|
-
function isReadableStream(v) {
|
|
560
|
-
return !!v && typeof v.getReader === "function";
|
|
561
|
-
}
|
|
562
|
-
function isAsyncIterable(v) {
|
|
563
|
-
return !!v && typeof v[Symbol.asyncIterator] === "function";
|
|
564
|
-
}
|
|
565
|
-
function isNodeRuntime() {
|
|
566
|
-
const p = globalThis?.process;
|
|
567
|
-
return !!p?.versions?.node;
|
|
568
|
-
}
|
|
569
|
-
async function collectBytes(source) {
|
|
570
|
-
const chunks = [];
|
|
571
|
-
let total = 0;
|
|
572
|
-
if (isReadableStream(source)) {
|
|
573
|
-
const reader = source.getReader();
|
|
574
|
-
try {
|
|
575
|
-
while (true) {
|
|
576
|
-
const { done, value } = await reader.read();
|
|
577
|
-
if (done) break;
|
|
578
|
-
if (value) {
|
|
579
|
-
chunks.push(value);
|
|
580
|
-
total += value.length;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
} finally {
|
|
584
|
-
reader.releaseLock();
|
|
585
|
-
}
|
|
586
|
-
} else {
|
|
587
|
-
for await (const chunk of source) {
|
|
588
|
-
chunks.push(chunk);
|
|
589
|
-
total += chunk.length;
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
const out = new Uint8Array(total);
|
|
593
|
-
let offset = 0;
|
|
594
|
-
for (const chunk of chunks) {
|
|
595
|
-
out.set(chunk, offset);
|
|
596
|
-
offset += chunk.length;
|
|
597
|
-
}
|
|
598
|
-
return out;
|
|
599
|
-
}
|
|
600
|
-
function toReadableStream(it) {
|
|
601
|
-
const RS = ReadableStream;
|
|
602
|
-
if (typeof RS?.from === "function") return RS.from(it);
|
|
603
|
-
const iterator = it[Symbol.asyncIterator]();
|
|
604
|
-
return new ReadableStream({
|
|
605
|
-
async pull(controller) {
|
|
606
|
-
const r = await iterator.next();
|
|
607
|
-
if (r.done) {
|
|
608
|
-
controller.close();
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
controller.enqueue(r.value);
|
|
612
|
-
},
|
|
613
|
-
async cancel() {
|
|
614
|
-
await iterator.return?.();
|
|
615
|
-
}
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
function basename(p) {
|
|
619
|
-
const parts = p.split("/").filter(Boolean);
|
|
620
|
-
return parts.length ? parts[parts.length - 1] : "file";
|
|
621
|
-
}
|
|
622
|
-
function encodeUtf8(s) {
|
|
623
|
-
return new TextEncoder().encode(s);
|
|
624
|
-
}
|
|
625
|
-
async function* multipartUploadBody(opts) {
|
|
626
|
-
const b = opts.boundary;
|
|
627
|
-
yield encodeUtf8(`--${b}\r
|
|
628
|
-
`);
|
|
629
|
-
yield encodeUtf8(
|
|
630
|
-
`Content-Disposition: form-data; name="metadata"; filename="metadata"\r
|
|
631
|
-
`
|
|
632
|
-
);
|
|
633
|
-
yield encodeUtf8(`Content-Type: application/json\r
|
|
634
|
-
\r
|
|
635
|
-
`);
|
|
636
|
-
yield encodeUtf8(opts.metadataJson);
|
|
637
|
-
yield encodeUtf8(`\r
|
|
638
|
-
`);
|
|
639
|
-
yield encodeUtf8(`--${b}\r
|
|
640
|
-
`);
|
|
641
|
-
yield encodeUtf8(
|
|
642
|
-
`Content-Disposition: form-data; name="file"; filename="${opts.fileName}"\r
|
|
643
|
-
`
|
|
644
|
-
);
|
|
645
|
-
yield encodeUtf8(`Content-Type: ${opts.fileContentType}\r
|
|
646
|
-
\r
|
|
647
|
-
`);
|
|
648
|
-
if (isReadableStream(opts.file)) {
|
|
649
|
-
const reader = opts.file.getReader();
|
|
650
|
-
try {
|
|
651
|
-
while (true) {
|
|
652
|
-
const { done, value } = await reader.read();
|
|
653
|
-
if (done) break;
|
|
654
|
-
if (value) yield value;
|
|
655
|
-
}
|
|
656
|
-
} finally {
|
|
657
|
-
reader.releaseLock();
|
|
658
|
-
}
|
|
659
|
-
} else {
|
|
660
|
-
for await (const chunk of opts.file) {
|
|
661
|
-
yield chunk;
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
yield encodeUtf8(`\r
|
|
665
|
-
--${b}--\r
|
|
666
|
-
`);
|
|
667
|
-
}
|
|
668
|
-
function toPermission(e) {
|
|
669
|
-
return {
|
|
670
|
-
mode: e.mode ?? 755,
|
|
671
|
-
owner: e.owner,
|
|
672
|
-
group: e.group
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
var FilesystemAdapter = class {
|
|
676
|
-
constructor(client, opts) {
|
|
677
|
-
this.client = client;
|
|
678
|
-
this.opts = opts;
|
|
679
|
-
this.fetch = opts.fetch ?? fetch;
|
|
680
|
-
}
|
|
681
|
-
fetch;
|
|
682
|
-
static Api = {
|
|
683
|
-
// This is intentionally derived from OpenAPI schema types so API changes surface quickly.
|
|
684
|
-
SearchFilesOk: null,
|
|
685
|
-
FilesInfoOk: null,
|
|
686
|
-
MakeDirsRequest: null,
|
|
687
|
-
SetPermissionsRequest: null,
|
|
688
|
-
MoveFilesRequest: null,
|
|
689
|
-
ReplaceContentsRequest: null
|
|
690
|
-
};
|
|
691
|
-
parseIsoDate(field, v) {
|
|
692
|
-
if (typeof v !== "string" || !v) {
|
|
693
|
-
throw new Error(`Invalid ${field}: expected ISO string, got ${typeof v}`);
|
|
694
|
-
}
|
|
695
|
-
const d = new Date(v);
|
|
696
|
-
if (Number.isNaN(d.getTime())) {
|
|
697
|
-
throw new Error(`Invalid ${field}: ${v}`);
|
|
698
|
-
}
|
|
699
|
-
return d;
|
|
700
|
-
}
|
|
701
|
-
static _ApiFileInfo = null;
|
|
702
|
-
mapApiFileInfo(raw) {
|
|
703
|
-
const { path, size, created_at, modified_at, mode, owner, group, ...rest } = raw;
|
|
704
|
-
return {
|
|
705
|
-
...rest,
|
|
706
|
-
path,
|
|
707
|
-
size,
|
|
708
|
-
mode,
|
|
709
|
-
owner,
|
|
710
|
-
group,
|
|
711
|
-
createdAt: created_at ? this.parseIsoDate("createdAt", created_at) : void 0,
|
|
712
|
-
modifiedAt: modified_at ? this.parseIsoDate("modifiedAt", modified_at) : void 0
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
async getFileInfo(paths) {
|
|
716
|
-
const { data, error, response } = await this.client.GET("/files/info", {
|
|
717
|
-
params: { query: { path: paths } }
|
|
718
|
-
});
|
|
719
|
-
throwOnOpenApiFetchError({ error, response }, "Get file info failed");
|
|
720
|
-
const raw = data;
|
|
721
|
-
if (!raw) return {};
|
|
722
|
-
if (typeof raw !== "object") {
|
|
723
|
-
throw new Error(
|
|
724
|
-
`Get file info failed: unexpected response shape (got ${typeof raw})`
|
|
725
|
-
);
|
|
726
|
-
}
|
|
727
|
-
const out = {};
|
|
728
|
-
for (const [k, v] of Object.entries(raw)) {
|
|
729
|
-
if (!v || typeof v !== "object") {
|
|
730
|
-
throw new Error(
|
|
731
|
-
`Get file info failed: invalid file info for path=${k}`
|
|
732
|
-
);
|
|
733
|
-
}
|
|
734
|
-
out[k] = this.mapApiFileInfo(v);
|
|
735
|
-
}
|
|
736
|
-
return out;
|
|
737
|
-
}
|
|
738
|
-
async deleteFiles(paths) {
|
|
739
|
-
const { error, response } = await this.client.DELETE("/files", {
|
|
740
|
-
params: { query: { path: paths } }
|
|
741
|
-
});
|
|
742
|
-
throwOnOpenApiFetchError({ error, response }, "Delete files failed");
|
|
743
|
-
}
|
|
744
|
-
async createDirectories(entries) {
|
|
745
|
-
const map = {};
|
|
746
|
-
for (const e of entries) {
|
|
747
|
-
map[e.path] = toPermission(e);
|
|
748
|
-
}
|
|
749
|
-
const body = map;
|
|
750
|
-
const { error, response } = await this.client.POST("/directories", {
|
|
751
|
-
body
|
|
752
|
-
});
|
|
753
|
-
throwOnOpenApiFetchError({ error, response }, "Create directories failed");
|
|
754
|
-
}
|
|
755
|
-
async deleteDirectories(paths) {
|
|
756
|
-
const { error, response } = await this.client.DELETE("/directories", {
|
|
757
|
-
params: { query: { path: paths } }
|
|
758
|
-
});
|
|
759
|
-
throwOnOpenApiFetchError({ error, response }, "Delete directories failed");
|
|
760
|
-
}
|
|
761
|
-
async setPermissions(entries) {
|
|
762
|
-
const req = {};
|
|
763
|
-
for (const e of entries) {
|
|
764
|
-
req[e.path] = toPermission(e);
|
|
765
|
-
}
|
|
766
|
-
const body = req;
|
|
767
|
-
const { error, response } = await this.client.POST("/files/permissions", {
|
|
768
|
-
body
|
|
769
|
-
});
|
|
770
|
-
throwOnOpenApiFetchError({ error, response }, "Set permissions failed");
|
|
771
|
-
}
|
|
772
|
-
async moveFiles(entries) {
|
|
773
|
-
const req = entries.map((e) => ({
|
|
774
|
-
src: e.src,
|
|
775
|
-
dest: e.dest
|
|
776
|
-
}));
|
|
777
|
-
const body = req;
|
|
778
|
-
const { error, response } = await this.client.POST("/files/mv", {
|
|
779
|
-
body
|
|
780
|
-
});
|
|
781
|
-
throwOnOpenApiFetchError({ error, response }, "Move files failed");
|
|
782
|
-
}
|
|
783
|
-
async replaceContents(entries) {
|
|
784
|
-
const req = {};
|
|
785
|
-
for (const e of entries) {
|
|
786
|
-
req[e.path] = { old: e.oldContent, new: e.newContent };
|
|
787
|
-
}
|
|
788
|
-
const body = req;
|
|
789
|
-
const { error, response } = await this.client.POST("/files/replace", {
|
|
790
|
-
body
|
|
791
|
-
});
|
|
792
|
-
throwOnOpenApiFetchError({ error, response }, "Replace contents failed");
|
|
793
|
-
}
|
|
794
|
-
async search(entry) {
|
|
795
|
-
const { data, error, response } = await this.client.GET("/files/search", {
|
|
796
|
-
params: { query: { path: entry.path, pattern: entry.pattern } }
|
|
797
|
-
});
|
|
798
|
-
throwOnOpenApiFetchError({ error, response }, "Search files failed");
|
|
799
|
-
const ok = data;
|
|
800
|
-
if (!ok) return [];
|
|
801
|
-
if (!Array.isArray(ok)) {
|
|
802
|
-
throw new Error(
|
|
803
|
-
`Search files failed: unexpected response shape (expected array, got ${typeof ok})`
|
|
804
|
-
);
|
|
805
|
-
}
|
|
806
|
-
return ok.map((x) => this.mapApiFileInfo(x));
|
|
807
|
-
}
|
|
808
|
-
async uploadFile(meta, data) {
|
|
809
|
-
const url = joinUrl2(this.opts.baseUrl, "/files/upload");
|
|
810
|
-
const fileName = basename(meta.path);
|
|
811
|
-
const metadataJson = JSON.stringify(meta);
|
|
812
|
-
if (isReadableStream(data) || isAsyncIterable(data)) {
|
|
813
|
-
if (!isNodeRuntime()) {
|
|
814
|
-
const bytes = await collectBytes(data);
|
|
815
|
-
return await this.uploadFile(meta, bytes);
|
|
816
|
-
}
|
|
817
|
-
const boundary = `opensandbox_${Math.random().toString(16).slice(2)}_${Date.now()}`;
|
|
818
|
-
const bodyIt = multipartUploadBody({
|
|
819
|
-
boundary,
|
|
820
|
-
metadataJson,
|
|
821
|
-
fileName,
|
|
822
|
-
fileContentType: "application/octet-stream",
|
|
823
|
-
file: data
|
|
824
|
-
});
|
|
825
|
-
const stream = toReadableStream(bodyIt);
|
|
826
|
-
const res2 = await this.fetch(url, {
|
|
827
|
-
method: "POST",
|
|
828
|
-
headers: {
|
|
829
|
-
"content-type": `multipart/form-data; boundary=${boundary}`,
|
|
830
|
-
...this.opts.headers ?? {}
|
|
831
|
-
},
|
|
832
|
-
body: stream,
|
|
833
|
-
// Node fetch (undici) requires duplex for streaming request bodies.
|
|
834
|
-
duplex: "half"
|
|
835
|
-
});
|
|
836
|
-
if (!res2.ok) {
|
|
837
|
-
const requestId = res2.headers.get("x-request-id") ?? void 0;
|
|
838
|
-
const rawBody = await res2.text().catch(() => void 0);
|
|
839
|
-
throw new SandboxApiException({
|
|
840
|
-
message: `Upload failed (status=${res2.status})`,
|
|
841
|
-
statusCode: res2.status,
|
|
842
|
-
requestId,
|
|
843
|
-
error: new SandboxError(
|
|
844
|
-
SandboxError.UNEXPECTED_RESPONSE,
|
|
845
|
-
"Upload failed"
|
|
846
|
-
),
|
|
847
|
-
rawBody
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
const form = new FormData();
|
|
853
|
-
form.append(
|
|
854
|
-
"metadata",
|
|
855
|
-
new Blob([metadataJson], { type: "application/json" }),
|
|
856
|
-
"metadata"
|
|
857
|
-
);
|
|
858
|
-
if (typeof data === "string") {
|
|
859
|
-
const textBlob = new Blob([data], { type: "text/plain; charset=utf-8" });
|
|
860
|
-
form.append("file", textBlob, fileName);
|
|
861
|
-
} else {
|
|
862
|
-
const blob = toUploadBlob(data);
|
|
863
|
-
const fileBlob = blob.type ? blob : new Blob([blob], { type: "application/octet-stream" });
|
|
864
|
-
form.append("file", fileBlob, fileName);
|
|
865
|
-
}
|
|
866
|
-
const res = await this.fetch(url, {
|
|
867
|
-
method: "POST",
|
|
868
|
-
headers: {
|
|
869
|
-
...this.opts.headers ?? {}
|
|
870
|
-
},
|
|
871
|
-
body: form
|
|
872
|
-
});
|
|
873
|
-
if (!res.ok) {
|
|
874
|
-
const requestId = res.headers.get("x-request-id") ?? void 0;
|
|
875
|
-
const rawBody = await res.text().catch(() => void 0);
|
|
876
|
-
throw new SandboxApiException({
|
|
877
|
-
message: `Upload failed (status=${res.status})`,
|
|
878
|
-
statusCode: res.status,
|
|
879
|
-
requestId,
|
|
880
|
-
error: new SandboxError(
|
|
881
|
-
SandboxError.UNEXPECTED_RESPONSE,
|
|
882
|
-
"Upload failed"
|
|
883
|
-
),
|
|
884
|
-
rawBody
|
|
885
|
-
});
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
async readBytes(path, opts) {
|
|
889
|
-
const url = joinUrl2(this.opts.baseUrl, "/files/download") + `?path=${encodeURIComponent(path)}`;
|
|
890
|
-
const res = await this.fetch(url, {
|
|
891
|
-
method: "GET",
|
|
892
|
-
headers: {
|
|
893
|
-
...this.opts.headers ?? {},
|
|
894
|
-
...opts?.range ? { Range: opts.range } : {}
|
|
895
|
-
}
|
|
896
|
-
});
|
|
897
|
-
if (!res.ok) {
|
|
898
|
-
const requestId = res.headers.get("x-request-id") ?? void 0;
|
|
899
|
-
const rawBody = await res.text().catch(() => void 0);
|
|
900
|
-
throw new SandboxApiException({
|
|
901
|
-
message: "Download failed",
|
|
902
|
-
statusCode: res.status,
|
|
903
|
-
requestId,
|
|
904
|
-
error: new SandboxError(
|
|
905
|
-
SandboxError.UNEXPECTED_RESPONSE,
|
|
906
|
-
"Download failed"
|
|
907
|
-
),
|
|
908
|
-
rawBody
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
const ab = await res.arrayBuffer();
|
|
912
|
-
return new Uint8Array(ab);
|
|
913
|
-
}
|
|
914
|
-
readBytesStream(path, opts) {
|
|
915
|
-
return this.downloadStream(path, opts);
|
|
916
|
-
}
|
|
917
|
-
async *downloadStream(path, opts) {
|
|
918
|
-
const url = joinUrl2(this.opts.baseUrl, "/files/download") + `?path=${encodeURIComponent(path)}`;
|
|
919
|
-
const res = await this.fetch(url, {
|
|
920
|
-
method: "GET",
|
|
921
|
-
headers: {
|
|
922
|
-
...this.opts.headers ?? {},
|
|
923
|
-
...opts?.range ? { Range: opts.range } : {}
|
|
924
|
-
}
|
|
925
|
-
});
|
|
926
|
-
if (!res.ok) {
|
|
927
|
-
const requestId = res.headers.get("x-request-id") ?? void 0;
|
|
928
|
-
const rawBody = await res.text().catch(() => void 0);
|
|
929
|
-
throw new SandboxApiException({
|
|
930
|
-
message: "Download stream failed",
|
|
931
|
-
statusCode: res.status,
|
|
932
|
-
requestId,
|
|
933
|
-
error: new SandboxError(
|
|
934
|
-
SandboxError.UNEXPECTED_RESPONSE,
|
|
935
|
-
"Download stream failed"
|
|
936
|
-
),
|
|
937
|
-
rawBody
|
|
938
|
-
});
|
|
939
|
-
}
|
|
940
|
-
const body = res.body;
|
|
941
|
-
if (!body) return;
|
|
942
|
-
const reader = body.getReader();
|
|
943
|
-
while (true) {
|
|
944
|
-
const { done, value } = await reader.read();
|
|
945
|
-
if (done) return;
|
|
946
|
-
if (value) yield value;
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
async readFile(path, opts) {
|
|
950
|
-
const bytes = await this.readBytes(path, { range: opts?.range });
|
|
951
|
-
const encoding = opts?.encoding ?? "utf-8";
|
|
952
|
-
return new TextDecoder(encoding).decode(bytes);
|
|
953
|
-
}
|
|
954
|
-
async writeFiles(entries) {
|
|
955
|
-
for (const e of entries) {
|
|
956
|
-
const meta = {
|
|
957
|
-
path: e.path,
|
|
958
|
-
owner: e.owner,
|
|
959
|
-
group: e.group,
|
|
960
|
-
mode: e.mode
|
|
961
|
-
};
|
|
962
|
-
await this.uploadFile(meta, e.data ?? "");
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
};
|
|
966
|
-
|
|
967
|
-
// src/adapters/healthAdapter.ts
|
|
968
|
-
var HealthAdapter = class {
|
|
969
|
-
constructor(client) {
|
|
970
|
-
this.client = client;
|
|
971
|
-
}
|
|
972
|
-
async ping() {
|
|
973
|
-
const { error, response } = await this.client.GET("/ping");
|
|
974
|
-
throwOnOpenApiFetchError({ error, response }, "Execd ping failed");
|
|
975
|
-
return true;
|
|
976
|
-
}
|
|
977
|
-
};
|
|
978
|
-
|
|
979
|
-
// src/adapters/metricsAdapter.ts
|
|
980
|
-
function normalizeMetrics(m) {
|
|
981
|
-
const cpuCount = m.cpu_count ?? 0;
|
|
982
|
-
const cpuUsedPercentage = m.cpu_used_pct ?? 0;
|
|
983
|
-
const memoryTotalMiB = m.mem_total_mib ?? 0;
|
|
984
|
-
const memoryUsedMiB = m.mem_used_mib ?? 0;
|
|
985
|
-
const timestamp = m.timestamp ?? 0;
|
|
986
|
-
return {
|
|
987
|
-
cpuCount: Number(cpuCount),
|
|
988
|
-
cpuUsedPercentage: Number(cpuUsedPercentage),
|
|
989
|
-
memoryTotalMiB: Number(memoryTotalMiB),
|
|
990
|
-
memoryUsedMiB: Number(memoryUsedMiB),
|
|
991
|
-
timestamp: Number(timestamp)
|
|
992
|
-
};
|
|
993
|
-
}
|
|
994
|
-
var MetricsAdapter = class {
|
|
995
|
-
constructor(client) {
|
|
996
|
-
this.client = client;
|
|
997
|
-
}
|
|
998
|
-
async getMetrics() {
|
|
999
|
-
const { data, error, response } = await this.client.GET("/metrics");
|
|
1000
|
-
throwOnOpenApiFetchError({ error, response }, "Get execd metrics failed");
|
|
1001
|
-
const ok = data;
|
|
1002
|
-
if (!ok || typeof ok !== "object") {
|
|
1003
|
-
throw new Error("Get execd metrics failed: unexpected response shape");
|
|
1004
|
-
}
|
|
1005
|
-
return normalizeMetrics(ok);
|
|
1006
|
-
}
|
|
1007
|
-
};
|
|
1008
|
-
|
|
1009
|
-
// src/adapters/sandboxesAdapter.ts
|
|
1010
|
-
function encodeMetadataFilter(metadata) {
|
|
1011
|
-
const parts = [];
|
|
1012
|
-
for (const [k, v] of Object.entries(metadata)) {
|
|
1013
|
-
parts.push(`${k}=${v}`);
|
|
1014
|
-
}
|
|
1015
|
-
return parts.join("&");
|
|
1016
|
-
}
|
|
1017
|
-
var SandboxesAdapter = class {
|
|
1018
|
-
constructor(client) {
|
|
1019
|
-
this.client = client;
|
|
1020
|
-
}
|
|
1021
|
-
parseIsoDate(field, v) {
|
|
1022
|
-
if (typeof v !== "string" || !v) {
|
|
1023
|
-
throw new Error(`Invalid ${field}: expected ISO string, got ${typeof v}`);
|
|
1024
|
-
}
|
|
1025
|
-
const d = new Date(v);
|
|
1026
|
-
if (Number.isNaN(d.getTime())) {
|
|
1027
|
-
throw new Error(`Invalid ${field}: ${v}`);
|
|
1028
|
-
}
|
|
1029
|
-
return d;
|
|
1030
|
-
}
|
|
1031
|
-
parseOptionalIsoDate(field, v) {
|
|
1032
|
-
if (v == null) return null;
|
|
1033
|
-
return this.parseIsoDate(field, v);
|
|
1034
|
-
}
|
|
1035
|
-
mapSandboxInfo(raw) {
|
|
1036
|
-
return {
|
|
1037
|
-
...raw ?? {},
|
|
1038
|
-
createdAt: this.parseIsoDate("createdAt", raw?.createdAt),
|
|
1039
|
-
expiresAt: this.parseOptionalIsoDate("expiresAt", raw?.expiresAt)
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
async createSandbox(req) {
|
|
1043
|
-
const body = req;
|
|
1044
|
-
const { data, error, response } = await this.client.POST("/sandboxes", {
|
|
1045
|
-
body
|
|
1046
|
-
});
|
|
1047
|
-
throwOnOpenApiFetchError({ error, response }, "Create sandbox failed");
|
|
1048
|
-
const raw = data;
|
|
1049
|
-
if (!raw || typeof raw !== "object") {
|
|
1050
|
-
throw new Error("Create sandbox failed: unexpected response shape");
|
|
1051
|
-
}
|
|
1052
|
-
return {
|
|
1053
|
-
...raw ?? {},
|
|
1054
|
-
createdAt: this.parseIsoDate("createdAt", raw?.createdAt),
|
|
1055
|
-
expiresAt: this.parseOptionalIsoDate("expiresAt", raw?.expiresAt)
|
|
1056
|
-
};
|
|
1057
|
-
}
|
|
1058
|
-
async getSandbox(sandboxId) {
|
|
1059
|
-
const { data, error, response } = await this.client.GET("/sandboxes/{sandboxId}", {
|
|
1060
|
-
params: { path: { sandboxId } }
|
|
1061
|
-
});
|
|
1062
|
-
throwOnOpenApiFetchError({ error, response }, "Get sandbox failed");
|
|
1063
|
-
const ok = data;
|
|
1064
|
-
if (!ok || typeof ok !== "object") {
|
|
1065
|
-
throw new Error("Get sandbox failed: unexpected response shape");
|
|
1066
|
-
}
|
|
1067
|
-
return this.mapSandboxInfo(ok);
|
|
1068
|
-
}
|
|
1069
|
-
async listSandboxes(params = {}) {
|
|
1070
|
-
const query = {};
|
|
1071
|
-
if (params.states?.length) query.state = params.states;
|
|
1072
|
-
if (params.metadata && Object.keys(params.metadata).length) {
|
|
1073
|
-
query.metadata = encodeMetadataFilter(params.metadata);
|
|
1074
|
-
}
|
|
1075
|
-
if (params.page != null) query.page = params.page;
|
|
1076
|
-
if (params.pageSize != null) query.pageSize = params.pageSize;
|
|
1077
|
-
const { data, error, response } = await this.client.GET("/sandboxes", {
|
|
1078
|
-
params: { query }
|
|
1079
|
-
});
|
|
1080
|
-
throwOnOpenApiFetchError({ error, response }, "List sandboxes failed");
|
|
1081
|
-
const raw = data;
|
|
1082
|
-
if (!raw || typeof raw !== "object") {
|
|
1083
|
-
throw new Error("List sandboxes failed: unexpected response shape");
|
|
1084
|
-
}
|
|
1085
|
-
const itemsRaw = raw.items;
|
|
1086
|
-
if (!Array.isArray(itemsRaw)) throw new Error("List sandboxes failed: unexpected items shape");
|
|
1087
|
-
return {
|
|
1088
|
-
...raw ?? {},
|
|
1089
|
-
items: itemsRaw.map((x) => this.mapSandboxInfo(x))
|
|
1090
|
-
};
|
|
1091
|
-
}
|
|
1092
|
-
async deleteSandbox(sandboxId) {
|
|
1093
|
-
const { error, response } = await this.client.DELETE("/sandboxes/{sandboxId}", {
|
|
1094
|
-
params: { path: { sandboxId } }
|
|
1095
|
-
});
|
|
1096
|
-
throwOnOpenApiFetchError({ error, response }, "Delete sandbox failed");
|
|
1097
|
-
}
|
|
1098
|
-
async pauseSandbox(sandboxId) {
|
|
1099
|
-
const { error, response } = await this.client.POST("/sandboxes/{sandboxId}/pause", {
|
|
1100
|
-
params: { path: { sandboxId } }
|
|
1101
|
-
});
|
|
1102
|
-
throwOnOpenApiFetchError({ error, response }, "Pause sandbox failed");
|
|
1103
|
-
}
|
|
1104
|
-
async resumeSandbox(sandboxId) {
|
|
1105
|
-
const { error, response } = await this.client.POST("/sandboxes/{sandboxId}/resume", {
|
|
1106
|
-
params: { path: { sandboxId } }
|
|
1107
|
-
});
|
|
1108
|
-
throwOnOpenApiFetchError({ error, response }, "Resume sandbox failed");
|
|
1109
|
-
}
|
|
1110
|
-
async renewSandboxExpiration(sandboxId, req) {
|
|
1111
|
-
const body = req;
|
|
1112
|
-
const { data, error, response } = await this.client.POST("/sandboxes/{sandboxId}/renew-expiration", {
|
|
1113
|
-
params: { path: { sandboxId } },
|
|
1114
|
-
body
|
|
1115
|
-
});
|
|
1116
|
-
throwOnOpenApiFetchError({ error, response }, "Renew sandbox expiration failed");
|
|
1117
|
-
const raw = data;
|
|
1118
|
-
if (!raw || typeof raw !== "object") {
|
|
1119
|
-
throw new Error("Renew sandbox expiration failed: unexpected response shape");
|
|
1120
|
-
}
|
|
1121
|
-
return {
|
|
1122
|
-
...raw ?? {},
|
|
1123
|
-
expiresAt: raw?.expiresAt ? this.parseIsoDate("expiresAt", raw.expiresAt) : void 0
|
|
1124
|
-
};
|
|
1125
|
-
}
|
|
1126
|
-
async getSandboxEndpoint(sandboxId, port, useServerProxy = false) {
|
|
1127
|
-
const { data, error, response } = await this.client.GET("/sandboxes/{sandboxId}/endpoints/{port}", {
|
|
1128
|
-
params: { path: { sandboxId, port }, query: { use_server_proxy: useServerProxy } }
|
|
1129
|
-
});
|
|
1130
|
-
throwOnOpenApiFetchError({ error, response }, "Get sandbox endpoint failed");
|
|
1131
|
-
const ok = data;
|
|
1132
|
-
if (!ok || typeof ok !== "object") {
|
|
1133
|
-
throw new Error("Get sandbox endpoint failed: unexpected response shape");
|
|
1134
|
-
}
|
|
1135
|
-
return ok;
|
|
1136
|
-
}
|
|
1137
|
-
};
|
|
1138
|
-
|
|
1139
|
-
// src/factory/defaultAdapterFactory.ts
|
|
1140
|
-
var DefaultAdapterFactory = class {
|
|
1141
|
-
createLifecycleStack(opts) {
|
|
1142
|
-
const lifecycleClient = createLifecycleClient({
|
|
1143
|
-
baseUrl: opts.lifecycleBaseUrl,
|
|
1144
|
-
apiKey: opts.connectionConfig.apiKey,
|
|
1145
|
-
headers: opts.connectionConfig.headers,
|
|
1146
|
-
fetch: opts.connectionConfig.fetch
|
|
1147
|
-
});
|
|
1148
|
-
const sandboxes = new SandboxesAdapter(lifecycleClient);
|
|
1149
|
-
return { sandboxes };
|
|
1150
|
-
}
|
|
1151
|
-
createExecdStack(opts) {
|
|
1152
|
-
const headers = {
|
|
1153
|
-
...opts.connectionConfig.headers ?? {},
|
|
1154
|
-
...opts.endpointHeaders ?? {}
|
|
1155
|
-
};
|
|
1156
|
-
const execdClient = createExecdClient({
|
|
1157
|
-
baseUrl: opts.execdBaseUrl,
|
|
1158
|
-
headers,
|
|
1159
|
-
fetch: opts.connectionConfig.fetch
|
|
1160
|
-
});
|
|
1161
|
-
const health = new HealthAdapter(execdClient);
|
|
1162
|
-
const metrics = new MetricsAdapter(execdClient);
|
|
1163
|
-
const files = new FilesystemAdapter(execdClient, {
|
|
1164
|
-
baseUrl: opts.execdBaseUrl,
|
|
1165
|
-
fetch: opts.connectionConfig.fetch,
|
|
1166
|
-
headers
|
|
1167
|
-
});
|
|
1168
|
-
const commands = new CommandsAdapter(execdClient, {
|
|
1169
|
-
baseUrl: opts.execdBaseUrl,
|
|
1170
|
-
fetch: opts.connectionConfig.sseFetch,
|
|
1171
|
-
headers
|
|
1172
|
-
});
|
|
1173
|
-
return {
|
|
1174
|
-
commands,
|
|
1175
|
-
files,
|
|
1176
|
-
health,
|
|
1177
|
-
metrics
|
|
1178
|
-
};
|
|
1179
|
-
}
|
|
1180
|
-
createEgressStack(opts) {
|
|
1181
|
-
const headers = {
|
|
1182
|
-
...opts.connectionConfig.headers ?? {},
|
|
1183
|
-
...opts.endpointHeaders ?? {}
|
|
1184
|
-
};
|
|
1185
|
-
const egressClient = createEgressClient({
|
|
1186
|
-
baseUrl: opts.egressBaseUrl,
|
|
1187
|
-
headers,
|
|
1188
|
-
fetch: opts.connectionConfig.fetch
|
|
1189
|
-
});
|
|
1190
|
-
return {
|
|
1191
|
-
egress: new EgressAdapter(egressClient)
|
|
1192
|
-
};
|
|
1193
|
-
}
|
|
1194
|
-
};
|
|
1195
|
-
function createDefaultAdapterFactory() {
|
|
1196
|
-
return new DefaultAdapterFactory();
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
// src/core/constants.ts
|
|
1200
|
-
var DEFAULT_EXECD_PORT = 44772;
|
|
1201
|
-
var DEFAULT_EGRESS_PORT = 18080;
|
|
1202
|
-
var DEFAULT_ENTRYPOINT = ["tail", "-f", "/dev/null"];
|
|
1203
|
-
var DEFAULT_RESOURCE_LIMITS = {
|
|
1204
|
-
cpu: "1",
|
|
1205
|
-
memory: "2Gi"
|
|
1206
|
-
};
|
|
1207
|
-
var DEFAULT_TIMEOUT_SECONDS = 600;
|
|
1208
|
-
var DEFAULT_READY_TIMEOUT_SECONDS = 30;
|
|
1209
|
-
var DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS = 200;
|
|
1210
|
-
var DEFAULT_REQUEST_TIMEOUT_SECONDS = 30;
|
|
1211
|
-
var DEFAULT_USER_AGENT = "OpenSandbox-JS-SDK/0.1.5";
|
|
1212
|
-
|
|
1213
|
-
// src/config/connection.ts
|
|
1214
|
-
function isNodeRuntime2() {
|
|
1215
|
-
const p = globalThis?.process;
|
|
1216
|
-
return !!p?.versions?.node;
|
|
1217
|
-
}
|
|
1218
|
-
function redactHeaders(headers) {
|
|
1219
|
-
const out = { ...headers };
|
|
1220
|
-
for (const k of Object.keys(out)) {
|
|
1221
|
-
if (k.toLowerCase() === "open-sandbox-api-key") out[k] = "***";
|
|
1222
|
-
}
|
|
1223
|
-
return out;
|
|
1224
|
-
}
|
|
1225
|
-
function readEnv(name) {
|
|
1226
|
-
const env = globalThis?.process?.env;
|
|
1227
|
-
const v = env?.[name];
|
|
1228
|
-
return typeof v === "string" && v.length ? v : void 0;
|
|
1229
|
-
}
|
|
1230
|
-
function stripTrailingSlashes(s) {
|
|
1231
|
-
return s.replace(/\/+$/, "");
|
|
1232
|
-
}
|
|
1233
|
-
function stripV1Suffix(s) {
|
|
1234
|
-
const trimmed = stripTrailingSlashes(s);
|
|
1235
|
-
return trimmed.endsWith("/v1") ? trimmed.slice(0, -3) : trimmed;
|
|
1236
|
-
}
|
|
1237
|
-
var DEFAULT_KEEPALIVE_TIMEOUT_MS = 3e4;
|
|
1238
|
-
function normalizeDomainBase(input) {
|
|
1239
|
-
if (input.startsWith("http://") || input.startsWith("https://")) {
|
|
1240
|
-
const u = new URL(input);
|
|
1241
|
-
const proto = u.protocol === "https:" ? "https" : "http";
|
|
1242
|
-
const base = `${u.origin}${u.pathname}`;
|
|
1243
|
-
return { protocol: proto, domainBase: stripV1Suffix(base) };
|
|
1244
|
-
}
|
|
1245
|
-
return { domainBase: stripV1Suffix(input) };
|
|
1246
|
-
}
|
|
1247
|
-
function createNodeFetch() {
|
|
1248
|
-
if (!isNodeRuntime2()) {
|
|
1249
|
-
return {
|
|
1250
|
-
fetch,
|
|
1251
|
-
close: async () => {
|
|
1252
|
-
}
|
|
1253
|
-
};
|
|
1254
|
-
}
|
|
1255
|
-
const baseFetch = fetch;
|
|
1256
|
-
let dispatcher;
|
|
1257
|
-
let dispatcherPromise = null;
|
|
1258
|
-
const nodeFetch = async (input, init) => {
|
|
1259
|
-
dispatcherPromise ??= (async () => {
|
|
1260
|
-
try {
|
|
1261
|
-
const mod = await import("undici");
|
|
1262
|
-
const Agent = mod.Agent;
|
|
1263
|
-
if (!Agent) {
|
|
1264
|
-
return void 0;
|
|
1265
|
-
}
|
|
1266
|
-
dispatcher = new Agent({
|
|
1267
|
-
keepAliveTimeout: DEFAULT_KEEPALIVE_TIMEOUT_MS,
|
|
1268
|
-
keepAliveMaxTimeout: DEFAULT_KEEPALIVE_TIMEOUT_MS
|
|
1269
|
-
});
|
|
1270
|
-
return dispatcher;
|
|
1271
|
-
} catch {
|
|
1272
|
-
return void 0;
|
|
1273
|
-
}
|
|
1274
|
-
})();
|
|
1275
|
-
if (dispatcherPromise) {
|
|
1276
|
-
await dispatcherPromise;
|
|
1277
|
-
}
|
|
1278
|
-
if (dispatcher) {
|
|
1279
|
-
const mergedInit = { ...init ?? {}, dispatcher };
|
|
1280
|
-
return baseFetch(input, mergedInit);
|
|
1281
|
-
}
|
|
1282
|
-
return baseFetch(input, init);
|
|
1283
|
-
};
|
|
1284
|
-
return {
|
|
1285
|
-
fetch: nodeFetch,
|
|
1286
|
-
close: async () => {
|
|
1287
|
-
if (dispatcherPromise) {
|
|
1288
|
-
await dispatcherPromise.catch(() => void 0);
|
|
1289
|
-
}
|
|
1290
|
-
if (dispatcher && typeof dispatcher === "object" && typeof dispatcher.close === "function") {
|
|
1291
|
-
try {
|
|
1292
|
-
await dispatcher.close();
|
|
1293
|
-
} catch {
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
};
|
|
1298
|
-
}
|
|
1299
|
-
function createTimedFetch(opts) {
|
|
1300
|
-
const baseFetch = opts.baseFetch;
|
|
1301
|
-
const timeoutSeconds = opts.timeoutSeconds;
|
|
1302
|
-
const debug = opts.debug;
|
|
1303
|
-
const defaultHeaders = opts.defaultHeaders ?? {};
|
|
1304
|
-
const label = opts.label;
|
|
1305
|
-
return async (input, init) => {
|
|
1306
|
-
const method = init?.method ?? "GET";
|
|
1307
|
-
const url = typeof input === "string" ? input : input?.toString?.() ?? String(input);
|
|
1308
|
-
const ac = new AbortController();
|
|
1309
|
-
const timeoutMs = Math.floor(timeoutSeconds * 1e3);
|
|
1310
|
-
const t = Number.isFinite(timeoutMs) && timeoutMs > 0 ? setTimeout(
|
|
1311
|
-
() => ac.abort(
|
|
1312
|
-
new Error(
|
|
1313
|
-
`[${label}] Request timed out (timeoutSeconds=${timeoutSeconds})`
|
|
1314
|
-
)
|
|
1315
|
-
),
|
|
1316
|
-
timeoutMs
|
|
1317
|
-
) : void 0;
|
|
1318
|
-
const onAbort = () => ac.abort(init?.signal?.reason ?? new Error("Aborted"));
|
|
1319
|
-
if (init?.signal) {
|
|
1320
|
-
if (init.signal.aborted) onAbort();
|
|
1321
|
-
else
|
|
1322
|
-
init.signal.addEventListener("abort", onAbort, { once: true });
|
|
1323
|
-
}
|
|
1324
|
-
const mergedInit = {
|
|
1325
|
-
...init,
|
|
1326
|
-
signal: ac.signal
|
|
1327
|
-
};
|
|
1328
|
-
if (debug) {
|
|
1329
|
-
const mergedHeaders = {
|
|
1330
|
-
...defaultHeaders,
|
|
1331
|
-
...init?.headers ?? {}
|
|
1332
|
-
};
|
|
1333
|
-
console.log(
|
|
1334
|
-
`[opensandbox:${label}] ->`,
|
|
1335
|
-
method,
|
|
1336
|
-
url,
|
|
1337
|
-
redactHeaders(mergedHeaders)
|
|
1338
|
-
);
|
|
1339
|
-
}
|
|
1340
|
-
try {
|
|
1341
|
-
const res = await baseFetch(input, mergedInit);
|
|
1342
|
-
if (debug) {
|
|
1343
|
-
console.log(`[opensandbox:${label}] <-`, method, url, res.status);
|
|
1344
|
-
}
|
|
1345
|
-
return res;
|
|
1346
|
-
} finally {
|
|
1347
|
-
if (t) clearTimeout(t);
|
|
1348
|
-
if (init?.signal)
|
|
1349
|
-
init.signal.removeEventListener("abort", onAbort);
|
|
1350
|
-
}
|
|
1351
|
-
};
|
|
1352
|
-
}
|
|
1353
|
-
var ConnectionConfig = class _ConnectionConfig {
|
|
1354
|
-
protocol;
|
|
1355
|
-
domain;
|
|
1356
|
-
apiKey;
|
|
1357
|
-
headers;
|
|
1358
|
-
_fetch;
|
|
1359
|
-
_sseFetch;
|
|
1360
|
-
requestTimeoutSeconds;
|
|
1361
|
-
debug;
|
|
1362
|
-
userAgent = DEFAULT_USER_AGENT;
|
|
1363
|
-
/**
|
|
1364
|
-
* Use sandbox server as proxy for endpoint requests (default false).
|
|
1365
|
-
*/
|
|
1366
|
-
useServerProxy;
|
|
1367
|
-
_closeTransport;
|
|
1368
|
-
_closePromise = null;
|
|
1369
|
-
_transportInitialized = false;
|
|
1370
|
-
/**
|
|
1371
|
-
* Create a connection configuration.
|
|
1372
|
-
*
|
|
1373
|
-
* Environment variables (optional):
|
|
1374
|
-
* - `OPEN_SANDBOX_DOMAIN` (default: `localhost:8080`)
|
|
1375
|
-
* - `OPEN_SANDBOX_API_KEY`
|
|
1376
|
-
*/
|
|
1377
|
-
constructor(opts = {}) {
|
|
1378
|
-
const envDomain = readEnv("OPEN_SANDBOX_DOMAIN");
|
|
1379
|
-
const envApiKey = readEnv("OPEN_SANDBOX_API_KEY");
|
|
1380
|
-
const rawDomain = opts.domain ?? envDomain ?? "localhost:8080";
|
|
1381
|
-
const normalized = normalizeDomainBase(rawDomain);
|
|
1382
|
-
this.protocol = normalized.protocol ?? opts.protocol ?? "http";
|
|
1383
|
-
this.domain = normalized.domainBase;
|
|
1384
|
-
this.apiKey = opts.apiKey ?? envApiKey;
|
|
1385
|
-
this.requestTimeoutSeconds = typeof opts.requestTimeoutSeconds === "number" ? opts.requestTimeoutSeconds : 30;
|
|
1386
|
-
this.debug = !!opts.debug;
|
|
1387
|
-
this.useServerProxy = !!opts.useServerProxy;
|
|
1388
|
-
const headers = { ...opts.headers ?? {} };
|
|
1389
|
-
if (this.apiKey && !headers["OPEN-SANDBOX-API-KEY"]) {
|
|
1390
|
-
headers["OPEN-SANDBOX-API-KEY"] = this.apiKey;
|
|
1391
|
-
}
|
|
1392
|
-
if (isNodeRuntime2() && this.userAgent && !headers["user-agent"] && !headers["User-Agent"]) {
|
|
1393
|
-
headers["user-agent"] = this.userAgent;
|
|
1394
|
-
}
|
|
1395
|
-
this.headers = headers;
|
|
1396
|
-
this._fetch = null;
|
|
1397
|
-
this._sseFetch = null;
|
|
1398
|
-
this._closeTransport = async () => {
|
|
1399
|
-
};
|
|
1400
|
-
this._transportInitialized = false;
|
|
1401
|
-
}
|
|
1402
|
-
get fetch() {
|
|
1403
|
-
return this._fetch ?? fetch;
|
|
1404
|
-
}
|
|
1405
|
-
get sseFetch() {
|
|
1406
|
-
return this._sseFetch ?? fetch;
|
|
1407
|
-
}
|
|
1408
|
-
getBaseUrl() {
|
|
1409
|
-
if (this.domain.startsWith("http://") || this.domain.startsWith("https://")) {
|
|
1410
|
-
return `${stripV1Suffix(this.domain)}/v1`;
|
|
1411
|
-
}
|
|
1412
|
-
return `${this.protocol}://${stripV1Suffix(this.domain)}/v1`;
|
|
1413
|
-
}
|
|
1414
|
-
initializeTransport() {
|
|
1415
|
-
if (this._transportInitialized) return;
|
|
1416
|
-
const { fetch: baseFetch, close } = createNodeFetch();
|
|
1417
|
-
this._fetch = createTimedFetch({
|
|
1418
|
-
baseFetch,
|
|
1419
|
-
timeoutSeconds: this.requestTimeoutSeconds,
|
|
1420
|
-
debug: this.debug,
|
|
1421
|
-
defaultHeaders: this.headers,
|
|
1422
|
-
label: "http"
|
|
1423
|
-
});
|
|
1424
|
-
this._sseFetch = createTimedFetch({
|
|
1425
|
-
baseFetch,
|
|
1426
|
-
timeoutSeconds: 0,
|
|
1427
|
-
debug: this.debug,
|
|
1428
|
-
defaultHeaders: this.headers,
|
|
1429
|
-
label: "sse"
|
|
1430
|
-
});
|
|
1431
|
-
this._closeTransport = close;
|
|
1432
|
-
this._transportInitialized = true;
|
|
1433
|
-
}
|
|
1434
|
-
/**
|
|
1435
|
-
* Ensure this configuration has transport helpers (fetch/SSE) allocated.
|
|
1436
|
-
*
|
|
1437
|
-
* On Node.js this creates a dedicated `undici` dispatcher; on browsers it
|
|
1438
|
-
* simply reuses the global fetch. Returns either `this` or a cloned config
|
|
1439
|
-
* with the transport initialized.
|
|
1440
|
-
*/
|
|
1441
|
-
withTransportIfMissing() {
|
|
1442
|
-
if (this._transportInitialized) {
|
|
1443
|
-
return this;
|
|
1444
|
-
}
|
|
1445
|
-
const clone = new _ConnectionConfig({
|
|
1446
|
-
domain: this.domain,
|
|
1447
|
-
protocol: this.protocol,
|
|
1448
|
-
apiKey: this.apiKey,
|
|
1449
|
-
headers: { ...this.headers },
|
|
1450
|
-
requestTimeoutSeconds: this.requestTimeoutSeconds,
|
|
1451
|
-
debug: this.debug,
|
|
1452
|
-
useServerProxy: this.useServerProxy
|
|
1453
|
-
});
|
|
1454
|
-
clone.initializeTransport();
|
|
1455
|
-
return clone;
|
|
1456
|
-
}
|
|
1457
|
-
/**
|
|
1458
|
-
* Close the Node.js agent owned by this configuration.
|
|
1459
|
-
*/
|
|
1460
|
-
async closeTransport() {
|
|
1461
|
-
if (!this._transportInitialized) return;
|
|
1462
|
-
this._closePromise ??= this._closeTransport();
|
|
1463
|
-
await this._closePromise;
|
|
1464
|
-
}
|
|
1465
|
-
};
|
|
1466
|
-
|
|
1467
|
-
// src/manager.ts
|
|
1468
|
-
var SandboxManager = class _SandboxManager {
|
|
1469
|
-
sandboxes;
|
|
1470
|
-
connectionConfig;
|
|
1471
|
-
constructor(opts) {
|
|
1472
|
-
this.sandboxes = opts.sandboxes;
|
|
1473
|
-
this.connectionConfig = opts.connectionConfig;
|
|
1474
|
-
}
|
|
1475
|
-
static create(opts = {}) {
|
|
1476
|
-
const baseConnectionConfig = opts.connectionConfig instanceof ConnectionConfig ? opts.connectionConfig : new ConnectionConfig(opts.connectionConfig);
|
|
1477
|
-
const connectionConfig = baseConnectionConfig.withTransportIfMissing();
|
|
1478
|
-
const lifecycleBaseUrl = connectionConfig.getBaseUrl();
|
|
1479
|
-
const adapterFactory = opts.adapterFactory ?? createDefaultAdapterFactory();
|
|
1480
|
-
let sandboxes;
|
|
1481
|
-
try {
|
|
1482
|
-
sandboxes = adapterFactory.createLifecycleStack({
|
|
1483
|
-
connectionConfig,
|
|
1484
|
-
lifecycleBaseUrl
|
|
1485
|
-
}).sandboxes;
|
|
1486
|
-
} catch (err) {
|
|
1487
|
-
void connectionConfig.closeTransport().catch(() => void 0);
|
|
1488
|
-
throw err;
|
|
1489
|
-
}
|
|
1490
|
-
return new _SandboxManager({ sandboxes, connectionConfig });
|
|
1491
|
-
}
|
|
1492
|
-
listSandboxInfos(filter = {}) {
|
|
1493
|
-
return this.sandboxes.listSandboxes({
|
|
1494
|
-
states: filter.states,
|
|
1495
|
-
metadata: filter.metadata,
|
|
1496
|
-
page: filter.page,
|
|
1497
|
-
pageSize: filter.pageSize
|
|
1498
|
-
});
|
|
1499
|
-
}
|
|
1500
|
-
getSandboxInfo(sandboxId) {
|
|
1501
|
-
return this.sandboxes.getSandbox(sandboxId);
|
|
1502
|
-
}
|
|
1503
|
-
killSandbox(sandboxId) {
|
|
1504
|
-
return this.sandboxes.deleteSandbox(sandboxId);
|
|
1505
|
-
}
|
|
1506
|
-
pauseSandbox(sandboxId) {
|
|
1507
|
-
return this.sandboxes.pauseSandbox(sandboxId);
|
|
1508
|
-
}
|
|
1509
|
-
resumeSandbox(sandboxId) {
|
|
1510
|
-
return this.sandboxes.resumeSandbox(sandboxId);
|
|
1511
|
-
}
|
|
1512
|
-
/**
|
|
1513
|
-
* Renew expiration by setting expiresAt to now + timeoutSeconds.
|
|
1514
|
-
*/
|
|
1515
|
-
async renewSandbox(sandboxId, timeoutSeconds) {
|
|
1516
|
-
const expiresAt = new Date(Date.now() + timeoutSeconds * 1e3).toISOString();
|
|
1517
|
-
await this.sandboxes.renewSandboxExpiration(sandboxId, { expiresAt });
|
|
1518
|
-
}
|
|
1519
|
-
/**
|
|
1520
|
-
* Release the HTTP agent resources allocated for this manager instance.
|
|
1521
|
-
*
|
|
1522
|
-
* Each manager clone owns a scoped `ConnectionConfig` clone.
|
|
1523
|
-
*
|
|
1524
|
-
* This mirrors the Python SDK's default transport lifecycle.
|
|
1525
|
-
*/
|
|
1526
|
-
async close() {
|
|
1527
|
-
await this.connectionConfig.closeTransport();
|
|
1528
|
-
}
|
|
1529
|
-
};
|
|
1530
|
-
|
|
1531
|
-
// src/sandbox.ts
|
|
1532
|
-
function sleep(ms) {
|
|
1533
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
1534
|
-
}
|
|
1535
|
-
function toImageSpec(image) {
|
|
1536
|
-
if (typeof image === "string") return { uri: image };
|
|
1537
|
-
return { uri: image.uri, auth: image.auth };
|
|
1538
|
-
}
|
|
1539
|
-
var Sandbox = class _Sandbox {
|
|
1540
|
-
id;
|
|
1541
|
-
connectionConfig;
|
|
1542
|
-
/**
|
|
1543
|
-
* Lifecycle (sandbox management) service.
|
|
1544
|
-
*/
|
|
1545
|
-
sandboxes;
|
|
1546
|
-
/**
|
|
1547
|
-
* Execd services.
|
|
1548
|
-
*/
|
|
1549
|
-
commands;
|
|
1550
|
-
/**
|
|
1551
|
-
* High-level filesystem facade (JS-friendly).
|
|
1552
|
-
*/
|
|
1553
|
-
files;
|
|
1554
|
-
health;
|
|
1555
|
-
metrics;
|
|
1556
|
-
/**
|
|
1557
|
-
* Internal state kept out of the public instance shape.
|
|
1558
|
-
*
|
|
1559
|
-
* This avoids nominal typing issues when multiple copies of the SDK exist in a dependency graph.
|
|
1560
|
-
*/
|
|
1561
|
-
static _priv = /* @__PURE__ */ new WeakMap();
|
|
1562
|
-
constructor(opts) {
|
|
1563
|
-
this.id = opts.id;
|
|
1564
|
-
this.connectionConfig = opts.connectionConfig;
|
|
1565
|
-
_Sandbox._priv.set(this, {
|
|
1566
|
-
adapterFactory: opts.adapterFactory,
|
|
1567
|
-
lifecycleBaseUrl: opts.lifecycleBaseUrl,
|
|
1568
|
-
execdBaseUrl: opts.execdBaseUrl,
|
|
1569
|
-
egress: opts.egress
|
|
1570
|
-
});
|
|
1571
|
-
this.sandboxes = opts.sandboxes;
|
|
1572
|
-
this.commands = opts.commands;
|
|
1573
|
-
this.files = opts.files;
|
|
1574
|
-
this.health = opts.health;
|
|
1575
|
-
this.metrics = opts.metrics;
|
|
1576
|
-
}
|
|
1577
|
-
static async create(opts) {
|
|
1578
|
-
const baseConnectionConfig = opts.connectionConfig instanceof ConnectionConfig ? opts.connectionConfig : new ConnectionConfig(opts.connectionConfig);
|
|
1579
|
-
const connectionConfig = baseConnectionConfig.withTransportIfMissing();
|
|
1580
|
-
const lifecycleBaseUrl = connectionConfig.getBaseUrl();
|
|
1581
|
-
const adapterFactory = opts.adapterFactory ?? createDefaultAdapterFactory();
|
|
1582
|
-
let sandboxes;
|
|
1583
|
-
try {
|
|
1584
|
-
sandboxes = adapterFactory.createLifecycleStack({
|
|
1585
|
-
connectionConfig,
|
|
1586
|
-
lifecycleBaseUrl
|
|
1587
|
-
}).sandboxes;
|
|
1588
|
-
} catch (err) {
|
|
1589
|
-
await connectionConfig.closeTransport();
|
|
1590
|
-
throw err;
|
|
1591
|
-
}
|
|
1592
|
-
if (opts.volumes) {
|
|
1593
|
-
for (const vol of opts.volumes) {
|
|
1594
|
-
const backendsSpecified = [vol.host, vol.pvc].filter((b) => b !== void 0).length;
|
|
1595
|
-
if (backendsSpecified === 0) {
|
|
1596
|
-
throw new Error(
|
|
1597
|
-
`Volume '${vol.name}' must specify exactly one backend (host, pvc), but none was provided.`
|
|
1598
|
-
);
|
|
1599
|
-
}
|
|
1600
|
-
if (backendsSpecified > 1) {
|
|
1601
|
-
throw new Error(
|
|
1602
|
-
`Volume '${vol.name}' must specify exactly one backend (host, pvc), but multiple were provided.`
|
|
1603
|
-
);
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
const rawTimeout = opts.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS;
|
|
1608
|
-
const timeoutSeconds = opts.timeoutSeconds === null ? null : Math.floor(rawTimeout);
|
|
1609
|
-
if (timeoutSeconds !== null && !Number.isFinite(timeoutSeconds)) {
|
|
1610
|
-
throw new Error(
|
|
1611
|
-
`timeoutSeconds must be a finite number, got ${opts.timeoutSeconds}`
|
|
1612
|
-
);
|
|
1613
|
-
}
|
|
1614
|
-
const req = {
|
|
1615
|
-
image: toImageSpec(opts.image),
|
|
1616
|
-
entrypoint: opts.entrypoint ?? DEFAULT_ENTRYPOINT,
|
|
1617
|
-
resourceLimits: opts.resource ?? DEFAULT_RESOURCE_LIMITS,
|
|
1618
|
-
env: opts.env ?? {},
|
|
1619
|
-
metadata: opts.metadata ?? {},
|
|
1620
|
-
networkPolicy: opts.networkPolicy ? {
|
|
1621
|
-
...opts.networkPolicy,
|
|
1622
|
-
defaultAction: opts.networkPolicy.defaultAction ?? "deny"
|
|
1623
|
-
} : void 0,
|
|
1624
|
-
volumes: opts.volumes,
|
|
1625
|
-
extensions: opts.extensions ?? {}
|
|
1626
|
-
};
|
|
1627
|
-
if (timeoutSeconds !== null) {
|
|
1628
|
-
req.timeout = timeoutSeconds;
|
|
1629
|
-
}
|
|
1630
|
-
let sandboxId;
|
|
1631
|
-
try {
|
|
1632
|
-
const created = await sandboxes.createSandbox(req);
|
|
1633
|
-
sandboxId = created.id;
|
|
1634
|
-
const endpoint = await sandboxes.getSandboxEndpoint(
|
|
1635
|
-
sandboxId,
|
|
1636
|
-
DEFAULT_EXECD_PORT,
|
|
1637
|
-
connectionConfig.useServerProxy
|
|
1638
|
-
);
|
|
1639
|
-
const egressEndpoint = await sandboxes.getSandboxEndpoint(
|
|
1640
|
-
sandboxId,
|
|
1641
|
-
DEFAULT_EGRESS_PORT,
|
|
1642
|
-
connectionConfig.useServerProxy
|
|
1643
|
-
);
|
|
1644
|
-
const execdBaseUrl = `${connectionConfig.protocol}://${endpoint.endpoint}`;
|
|
1645
|
-
const egressBaseUrl = `${connectionConfig.protocol}://${egressEndpoint.endpoint}`;
|
|
1646
|
-
const { commands, files, health, metrics } = adapterFactory.createExecdStack({
|
|
1647
|
-
connectionConfig,
|
|
1648
|
-
execdBaseUrl,
|
|
1649
|
-
endpointHeaders: endpoint.headers
|
|
1650
|
-
});
|
|
1651
|
-
const { egress } = adapterFactory.createEgressStack({
|
|
1652
|
-
connectionConfig,
|
|
1653
|
-
egressBaseUrl,
|
|
1654
|
-
endpointHeaders: egressEndpoint.headers
|
|
1655
|
-
});
|
|
1656
|
-
const sbx = new _Sandbox({
|
|
1657
|
-
id: sandboxId,
|
|
1658
|
-
connectionConfig,
|
|
1659
|
-
adapterFactory,
|
|
1660
|
-
lifecycleBaseUrl,
|
|
1661
|
-
execdBaseUrl,
|
|
1662
|
-
sandboxes,
|
|
1663
|
-
commands,
|
|
1664
|
-
files,
|
|
1665
|
-
health,
|
|
1666
|
-
metrics,
|
|
1667
|
-
egress
|
|
1668
|
-
});
|
|
1669
|
-
if (!(opts.skipHealthCheck ?? false)) {
|
|
1670
|
-
await sbx.waitUntilReady({
|
|
1671
|
-
readyTimeoutSeconds: opts.readyTimeoutSeconds ?? DEFAULT_READY_TIMEOUT_SECONDS,
|
|
1672
|
-
pollingIntervalMillis: opts.healthCheckPollingInterval ?? DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS,
|
|
1673
|
-
healthCheck: opts.healthCheck
|
|
1674
|
-
});
|
|
1675
|
-
}
|
|
1676
|
-
return sbx;
|
|
1677
|
-
} catch (err) {
|
|
1678
|
-
if (sandboxId) {
|
|
1679
|
-
try {
|
|
1680
|
-
await sandboxes.deleteSandbox(sandboxId);
|
|
1681
|
-
} catch {
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
await connectionConfig.closeTransport();
|
|
1685
|
-
throw err;
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
static async connect(opts) {
|
|
1689
|
-
const baseConnectionConfig = opts.connectionConfig instanceof ConnectionConfig ? opts.connectionConfig : new ConnectionConfig(opts.connectionConfig);
|
|
1690
|
-
const connectionConfig = baseConnectionConfig.withTransportIfMissing();
|
|
1691
|
-
const adapterFactory = opts.adapterFactory ?? createDefaultAdapterFactory();
|
|
1692
|
-
const lifecycleBaseUrl = connectionConfig.getBaseUrl();
|
|
1693
|
-
let sandboxes;
|
|
1694
|
-
try {
|
|
1695
|
-
sandboxes = adapterFactory.createLifecycleStack({
|
|
1696
|
-
connectionConfig,
|
|
1697
|
-
lifecycleBaseUrl
|
|
1698
|
-
}).sandboxes;
|
|
1699
|
-
} catch (err) {
|
|
1700
|
-
await connectionConfig.closeTransport();
|
|
1701
|
-
throw err;
|
|
1702
|
-
}
|
|
1703
|
-
try {
|
|
1704
|
-
const endpoint = await sandboxes.getSandboxEndpoint(
|
|
1705
|
-
opts.sandboxId,
|
|
1706
|
-
DEFAULT_EXECD_PORT,
|
|
1707
|
-
connectionConfig.useServerProxy
|
|
1708
|
-
);
|
|
1709
|
-
const egressEndpoint = await sandboxes.getSandboxEndpoint(
|
|
1710
|
-
opts.sandboxId,
|
|
1711
|
-
DEFAULT_EGRESS_PORT,
|
|
1712
|
-
connectionConfig.useServerProxy
|
|
1713
|
-
);
|
|
1714
|
-
const execdBaseUrl = `${connectionConfig.protocol}://${endpoint.endpoint}`;
|
|
1715
|
-
const egressBaseUrl = `${connectionConfig.protocol}://${egressEndpoint.endpoint}`;
|
|
1716
|
-
const { commands, files, health, metrics } = adapterFactory.createExecdStack({
|
|
1717
|
-
connectionConfig,
|
|
1718
|
-
execdBaseUrl,
|
|
1719
|
-
endpointHeaders: endpoint.headers
|
|
1720
|
-
});
|
|
1721
|
-
const { egress } = adapterFactory.createEgressStack({
|
|
1722
|
-
connectionConfig,
|
|
1723
|
-
egressBaseUrl,
|
|
1724
|
-
endpointHeaders: egressEndpoint.headers
|
|
1725
|
-
});
|
|
1726
|
-
const sbx = new _Sandbox({
|
|
1727
|
-
id: opts.sandboxId,
|
|
1728
|
-
connectionConfig,
|
|
1729
|
-
adapterFactory,
|
|
1730
|
-
lifecycleBaseUrl,
|
|
1731
|
-
execdBaseUrl,
|
|
1732
|
-
sandboxes,
|
|
1733
|
-
commands,
|
|
1734
|
-
files,
|
|
1735
|
-
health,
|
|
1736
|
-
metrics,
|
|
1737
|
-
egress
|
|
1738
|
-
});
|
|
1739
|
-
if (!(opts.skipHealthCheck ?? false)) {
|
|
1740
|
-
await sbx.waitUntilReady({
|
|
1741
|
-
readyTimeoutSeconds: opts.readyTimeoutSeconds ?? DEFAULT_READY_TIMEOUT_SECONDS,
|
|
1742
|
-
pollingIntervalMillis: opts.healthCheckPollingInterval ?? DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS,
|
|
1743
|
-
healthCheck: opts.healthCheck
|
|
1744
|
-
});
|
|
1745
|
-
}
|
|
1746
|
-
return sbx;
|
|
1747
|
-
} catch (err) {
|
|
1748
|
-
await connectionConfig.closeTransport();
|
|
1749
|
-
throw err;
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
async getInfo() {
|
|
1753
|
-
return await this.sandboxes.getSandbox(this.id);
|
|
1754
|
-
}
|
|
1755
|
-
async isHealthy() {
|
|
1756
|
-
try {
|
|
1757
|
-
return await this.health.ping();
|
|
1758
|
-
} catch {
|
|
1759
|
-
return false;
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
async getMetrics() {
|
|
1763
|
-
return await this.metrics.getMetrics();
|
|
1764
|
-
}
|
|
1765
|
-
async pause() {
|
|
1766
|
-
await this.sandboxes.pauseSandbox(this.id);
|
|
1767
|
-
}
|
|
1768
|
-
/**
|
|
1769
|
-
* Resume a paused sandbox and return a fresh, connected Sandbox instance.
|
|
1770
|
-
*
|
|
1771
|
-
* After resume, the execd endpoint may change, so this method returns a new
|
|
1772
|
-
* {@link Sandbox} instance with a refreshed execd base URL.
|
|
1773
|
-
*/
|
|
1774
|
-
async resume(opts = {}) {
|
|
1775
|
-
await this.sandboxes.resumeSandbox(this.id);
|
|
1776
|
-
return await _Sandbox.connect({
|
|
1777
|
-
sandboxId: this.id,
|
|
1778
|
-
connectionConfig: this.connectionConfig,
|
|
1779
|
-
adapterFactory: _Sandbox._priv.get(this).adapterFactory,
|
|
1780
|
-
skipHealthCheck: opts.skipHealthCheck ?? false,
|
|
1781
|
-
readyTimeoutSeconds: opts.readyTimeoutSeconds,
|
|
1782
|
-
healthCheckPollingInterval: opts.healthCheckPollingInterval
|
|
1783
|
-
});
|
|
1784
|
-
}
|
|
1785
|
-
/**
|
|
1786
|
-
* Resume a paused sandbox by id, then connect to its execd endpoint.
|
|
1787
|
-
*/
|
|
1788
|
-
static async resume(opts) {
|
|
1789
|
-
const baseConnectionConfig = opts.connectionConfig instanceof ConnectionConfig ? opts.connectionConfig : new ConnectionConfig(opts.connectionConfig);
|
|
1790
|
-
const adapterFactory = opts.adapterFactory ?? createDefaultAdapterFactory();
|
|
1791
|
-
const resumeConnectionConfig = baseConnectionConfig.withTransportIfMissing();
|
|
1792
|
-
const lifecycleBaseUrl = resumeConnectionConfig.getBaseUrl();
|
|
1793
|
-
let sandboxes;
|
|
1794
|
-
try {
|
|
1795
|
-
sandboxes = adapterFactory.createLifecycleStack({
|
|
1796
|
-
connectionConfig: resumeConnectionConfig,
|
|
1797
|
-
lifecycleBaseUrl
|
|
1798
|
-
}).sandboxes;
|
|
1799
|
-
await sandboxes.resumeSandbox(opts.sandboxId);
|
|
1800
|
-
} catch (err) {
|
|
1801
|
-
await resumeConnectionConfig.closeTransport();
|
|
1802
|
-
throw err;
|
|
1803
|
-
}
|
|
1804
|
-
await resumeConnectionConfig.closeTransport();
|
|
1805
|
-
return await _Sandbox.connect({ ...opts, connectionConfig: baseConnectionConfig, adapterFactory });
|
|
1806
|
-
}
|
|
1807
|
-
async kill() {
|
|
1808
|
-
await this.sandboxes.deleteSandbox(this.id);
|
|
1809
|
-
}
|
|
1810
|
-
/**
|
|
1811
|
-
* Release any client-side resources (e.g. Node.js HTTP agents) owned by this Sandbox instance.
|
|
1812
|
-
*/
|
|
1813
|
-
async close() {
|
|
1814
|
-
await this.connectionConfig.closeTransport();
|
|
1815
|
-
}
|
|
1816
|
-
/**
|
|
1817
|
-
* Renew expiration by setting expiresAt to now + timeoutSeconds.
|
|
1818
|
-
*/
|
|
1819
|
-
async renew(timeoutSeconds) {
|
|
1820
|
-
const expiresAt = new Date(
|
|
1821
|
-
Date.now() + timeoutSeconds * 1e3
|
|
1822
|
-
).toISOString();
|
|
1823
|
-
return await this.sandboxes.renewSandboxExpiration(this.id, { expiresAt });
|
|
1824
|
-
}
|
|
1825
|
-
async getEgressPolicy() {
|
|
1826
|
-
return await _Sandbox._priv.get(this).egress.getPolicy();
|
|
1827
|
-
}
|
|
1828
|
-
async patchEgressRules(rules) {
|
|
1829
|
-
await _Sandbox._priv.get(this).egress.patchRules(rules);
|
|
1830
|
-
}
|
|
1831
|
-
/**
|
|
1832
|
-
* Get sandbox endpoint for a port (STRICT: no scheme), e.g. "localhost:44772" or "domain/route/.../44772".
|
|
1833
|
-
*/
|
|
1834
|
-
async getEndpoint(port) {
|
|
1835
|
-
return await this.sandboxes.getSandboxEndpoint(
|
|
1836
|
-
this.id,
|
|
1837
|
-
port,
|
|
1838
|
-
this.connectionConfig.useServerProxy
|
|
1839
|
-
);
|
|
1840
|
-
}
|
|
1841
|
-
/**
|
|
1842
|
-
* Get absolute endpoint URL with scheme (convenience for HTTP clients).
|
|
1843
|
-
*/
|
|
1844
|
-
async getEndpointUrl(port) {
|
|
1845
|
-
const ep = await this.getEndpoint(port);
|
|
1846
|
-
return `${this.connectionConfig.protocol}://${ep.endpoint}`;
|
|
1847
|
-
}
|
|
1848
|
-
async waitUntilReady(opts) {
|
|
1849
|
-
const deadline = Date.now() + opts.readyTimeoutSeconds * 1e3;
|
|
1850
|
-
let attempt = 0;
|
|
1851
|
-
let errorDetail = "Health check returned false continuously.";
|
|
1852
|
-
const buildTimeoutMessage = () => {
|
|
1853
|
-
const context = `domain=${this.connectionConfig.domain}, useServerProxy=${this.connectionConfig.useServerProxy}`;
|
|
1854
|
-
let suggestion = "If this sandbox runs in Docker bridge or remote-network mode, consider enabling useServerProxy=true.";
|
|
1855
|
-
if (!this.connectionConfig.useServerProxy) {
|
|
1856
|
-
suggestion += " You can also configure server-side [docker].host_ip for direct endpoint access.";
|
|
1857
|
-
}
|
|
1858
|
-
return `Sandbox health check timed out after ${opts.readyTimeoutSeconds}s (${attempt} attempts). ${errorDetail} Connection context: ${context}. ${suggestion}`;
|
|
1859
|
-
};
|
|
1860
|
-
while (true) {
|
|
1861
|
-
if (Date.now() > deadline) {
|
|
1862
|
-
throw new SandboxReadyTimeoutException({
|
|
1863
|
-
message: buildTimeoutMessage()
|
|
1864
|
-
});
|
|
1865
|
-
}
|
|
1866
|
-
attempt++;
|
|
1867
|
-
try {
|
|
1868
|
-
if (opts.healthCheck) {
|
|
1869
|
-
const ok = await opts.healthCheck(this);
|
|
1870
|
-
if (ok) {
|
|
1871
|
-
return;
|
|
1872
|
-
}
|
|
1873
|
-
} else {
|
|
1874
|
-
const ok = await this.health.ping();
|
|
1875
|
-
if (ok) {
|
|
1876
|
-
return;
|
|
1877
|
-
}
|
|
1878
|
-
}
|
|
1879
|
-
errorDetail = "Health check returned false continuously.";
|
|
1880
|
-
} catch (err) {
|
|
1881
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1882
|
-
errorDetail = `Last health check error: ${message}`;
|
|
1883
|
-
}
|
|
1884
|
-
await sleep(opts.pollingIntervalMillis);
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
};
|
|
1888
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
1889
|
-
0 && (module.exports = {
|
|
1890
|
-
ConnectionConfig,
|
|
1891
|
-
DEFAULT_EGRESS_PORT,
|
|
1892
|
-
DEFAULT_ENTRYPOINT,
|
|
1893
|
-
DEFAULT_EXECD_PORT,
|
|
1894
|
-
DEFAULT_HEALTH_CHECK_POLLING_INTERVAL_MILLIS,
|
|
1895
|
-
DEFAULT_READY_TIMEOUT_SECONDS,
|
|
1896
|
-
DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
|
1897
|
-
DEFAULT_RESOURCE_LIMITS,
|
|
1898
|
-
DEFAULT_TIMEOUT_SECONDS,
|
|
1899
|
-
DefaultAdapterFactory,
|
|
1900
|
-
ExecutionEventDispatcher,
|
|
1901
|
-
InvalidArgumentException,
|
|
1902
|
-
Sandbox,
|
|
1903
|
-
SandboxApiException,
|
|
1904
|
-
SandboxError,
|
|
1905
|
-
SandboxException,
|
|
1906
|
-
SandboxInternalException,
|
|
1907
|
-
SandboxManager,
|
|
1908
|
-
SandboxReadyTimeoutException,
|
|
1909
|
-
SandboxUnhealthyException,
|
|
1910
|
-
createDefaultAdapterFactory
|
|
1911
|
-
});
|
|
1912
|
-
//# sourceMappingURL=index.cjs.map
|