veryfront 0.1.280 → 0.1.282
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/esm/deno.js +1 -2
- package/esm/src/sandbox/lazy-sandbox.d.ts +21 -0
- package/esm/src/sandbox/lazy-sandbox.d.ts.map +1 -1
- package/esm/src/sandbox/lazy-sandbox.js +152 -35
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
- package/src/deno.js +1 -2
- package/src/src/sandbox/lazy-sandbox.ts +198 -41
- package/src/src/utils/version-constant.ts +1 -1
package/esm/deno.js
CHANGED
|
@@ -5,6 +5,14 @@ export interface LazySandboxOptions extends SandboxOptions {
|
|
|
5
5
|
pollIntervalMs?: number;
|
|
6
6
|
heartbeatIntervalMs?: number;
|
|
7
7
|
heartbeatGraceMs?: number;
|
|
8
|
+
controlRequestTimeoutMs?: number;
|
|
9
|
+
execStartTimeoutMs?: number;
|
|
10
|
+
execStartMaxAttempts?: number;
|
|
11
|
+
execStartRetryDelayMs?: number;
|
|
12
|
+
resolveRuntimeEndpoint?: (input: {
|
|
13
|
+
endpoint: string;
|
|
14
|
+
sessionId: string;
|
|
15
|
+
}) => string;
|
|
8
16
|
}
|
|
9
17
|
/** Lazily provisions sandbox sessions and keeps them alive while in use. */
|
|
10
18
|
export declare class LazySandbox {
|
|
@@ -15,6 +23,11 @@ export declare class LazySandbox {
|
|
|
15
23
|
private readonly pollIntervalMs;
|
|
16
24
|
private readonly heartbeatIntervalMs;
|
|
17
25
|
private readonly heartbeatGraceMs;
|
|
26
|
+
private readonly controlRequestTimeoutMs;
|
|
27
|
+
private readonly execStartTimeoutMs;
|
|
28
|
+
private readonly execStartMaxAttempts;
|
|
29
|
+
private readonly execStartRetryDelayMs;
|
|
30
|
+
private readonly resolveRuntimeEndpointOption;
|
|
18
31
|
private endpoint;
|
|
19
32
|
private sessionId;
|
|
20
33
|
private sessionProjectId;
|
|
@@ -45,6 +58,7 @@ export declare class LazySandbox {
|
|
|
45
58
|
get isActive(): boolean;
|
|
46
59
|
private bootstrapSession;
|
|
47
60
|
private resolveReadyEndpoint;
|
|
61
|
+
private waitForReadySession;
|
|
48
62
|
private touchSession;
|
|
49
63
|
private startHeartbeatLoop;
|
|
50
64
|
private stopHeartbeatLoop;
|
|
@@ -55,6 +69,13 @@ export declare class LazySandbox {
|
|
|
55
69
|
private resolveExecOptions;
|
|
56
70
|
private resolveCommandJobEndpoint;
|
|
57
71
|
private updateTrackedCommandJob;
|
|
72
|
+
private startExec;
|
|
73
|
+
private fetchExecStart;
|
|
74
|
+
private fetchControl;
|
|
75
|
+
private waitForExecStartRetry;
|
|
76
|
+
private reprovisionAfterExecStartFailure;
|
|
77
|
+
private resolveRuntimeEndpoint;
|
|
78
|
+
private requireSessionId;
|
|
58
79
|
private authHeaders;
|
|
59
80
|
private jsonHeaders;
|
|
60
81
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-sandbox.d.ts","sourceRoot":"","sources":["../../../src/src/sandbox/lazy-sandbox.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,gBAAgB,EAErB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,eAAe,EAGpB,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"lazy-sandbox.d.ts","sourceRoot":"","sources":["../../../src/src/sandbox/lazy-sandbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import * as dntShim from "../../_dnt.shims.js";
|
|
2
2
|
import { REQUEST_ERROR } from "../errors/index.js";
|
|
3
|
-
import { resolveSandboxApiUrl, resolveSandboxAuthToken,
|
|
3
|
+
import { resolveSandboxApiUrl, resolveSandboxAuthToken, } from "./sandbox.js";
|
|
4
4
|
const DEFAULT_STARTUP_TIMEOUT_MS = 180_000;
|
|
5
5
|
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
6
6
|
const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
7
7
|
const DEFAULT_HEARTBEAT_GRACE_MS = 5_000;
|
|
8
|
+
const DEFAULT_CONTROL_REQUEST_TIMEOUT_MS = 15_000;
|
|
9
|
+
const DEFAULT_EXEC_START_TIMEOUT_MS = 30_000;
|
|
10
|
+
const DEFAULT_EXEC_START_MAX_ATTEMPTS = 3;
|
|
11
|
+
const DEFAULT_EXEC_START_RETRY_DELAY_MS = 1_000;
|
|
12
|
+
const REPROVISIONABLE_EXEC_START_ERROR_CODES = new Set([
|
|
13
|
+
"ECONNREFUSED",
|
|
14
|
+
"ECONNRESET",
|
|
15
|
+
"ENOTFOUND",
|
|
16
|
+
"EHOSTUNREACH",
|
|
17
|
+
]);
|
|
8
18
|
/** Lazily provisions sandbox sessions and keeps them alive while in use. */
|
|
9
19
|
export class LazySandbox {
|
|
10
20
|
apiUrl;
|
|
@@ -14,6 +24,11 @@ export class LazySandbox {
|
|
|
14
24
|
pollIntervalMs;
|
|
15
25
|
heartbeatIntervalMs;
|
|
16
26
|
heartbeatGraceMs;
|
|
27
|
+
controlRequestTimeoutMs;
|
|
28
|
+
execStartTimeoutMs;
|
|
29
|
+
execStartMaxAttempts;
|
|
30
|
+
execStartRetryDelayMs;
|
|
31
|
+
resolveRuntimeEndpointOption;
|
|
17
32
|
endpoint = null;
|
|
18
33
|
sessionId = null;
|
|
19
34
|
sessionProjectId = null;
|
|
@@ -31,6 +46,13 @@ export class LazySandbox {
|
|
|
31
46
|
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
32
47
|
this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
33
48
|
this.heartbeatGraceMs = options.heartbeatGraceMs ?? DEFAULT_HEARTBEAT_GRACE_MS;
|
|
49
|
+
this.controlRequestTimeoutMs = options.controlRequestTimeoutMs ??
|
|
50
|
+
DEFAULT_CONTROL_REQUEST_TIMEOUT_MS;
|
|
51
|
+
this.execStartTimeoutMs = options.execStartTimeoutMs ?? DEFAULT_EXEC_START_TIMEOUT_MS;
|
|
52
|
+
this.execStartMaxAttempts = options.execStartMaxAttempts ?? DEFAULT_EXEC_START_MAX_ATTEMPTS;
|
|
53
|
+
this.execStartRetryDelayMs = options.execStartRetryDelayMs ??
|
|
54
|
+
DEFAULT_EXEC_START_RETRY_DELAY_MS;
|
|
55
|
+
this.resolveRuntimeEndpointOption = options.resolveRuntimeEndpoint;
|
|
34
56
|
}
|
|
35
57
|
async ensure() {
|
|
36
58
|
if (this.endpoint)
|
|
@@ -71,13 +93,17 @@ export class LazySandbox {
|
|
|
71
93
|
}
|
|
72
94
|
async *executeStream(command, options) {
|
|
73
95
|
await this.touchSession();
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
96
|
+
let res;
|
|
97
|
+
try {
|
|
98
|
+
res = await this.startExec(command, options);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (!shouldReprovisionAfterExecStartFailure(error)) {
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
await this.reprovisionAfterExecStartFailure();
|
|
105
|
+
await this.touchSession();
|
|
106
|
+
res = await this.startExec(command, options);
|
|
81
107
|
}
|
|
82
108
|
if (!res.body) {
|
|
83
109
|
throw new Error("Exec response has no body");
|
|
@@ -104,7 +130,7 @@ export class LazySandbox {
|
|
|
104
130
|
}
|
|
105
131
|
async readFile(path) {
|
|
106
132
|
await this.touchSession();
|
|
107
|
-
const res = await
|
|
133
|
+
const res = await this.fetchControl(`${this.requireEndpoint()}/file?path=${encodeURIComponent(path)}`, {
|
|
108
134
|
headers: this.authHeaders(),
|
|
109
135
|
});
|
|
110
136
|
if (!res.ok) {
|
|
@@ -114,7 +140,7 @@ export class LazySandbox {
|
|
|
114
140
|
}
|
|
115
141
|
async writeFiles(files) {
|
|
116
142
|
await this.touchSession();
|
|
117
|
-
const res = await
|
|
143
|
+
const res = await this.fetchControl(`${this.requireEndpoint()}/files`, {
|
|
118
144
|
method: "POST",
|
|
119
145
|
headers: this.jsonHeaders(),
|
|
120
146
|
body: JSON.stringify({ files }),
|
|
@@ -127,8 +153,8 @@ export class LazySandbox {
|
|
|
127
153
|
}
|
|
128
154
|
async startCommandJob(command, options) {
|
|
129
155
|
await this.touchSession();
|
|
130
|
-
const endpoint = this.
|
|
131
|
-
const res = await
|
|
156
|
+
const endpoint = this.resolveRuntimeEndpoint();
|
|
157
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs`, {
|
|
132
158
|
method: "POST",
|
|
133
159
|
headers: this.jsonHeaders(),
|
|
134
160
|
body: JSON.stringify({ command, ...this.resolveExecOptions(options) }),
|
|
@@ -144,7 +170,7 @@ export class LazySandbox {
|
|
|
144
170
|
}
|
|
145
171
|
async getCommandJob(jobId) {
|
|
146
172
|
const endpoint = await this.resolveCommandJobEndpoint(jobId);
|
|
147
|
-
const res = await
|
|
173
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs/${jobId}`, {
|
|
148
174
|
headers: this.authHeaders(),
|
|
149
175
|
});
|
|
150
176
|
if (!res.ok) {
|
|
@@ -158,7 +184,7 @@ export class LazySandbox {
|
|
|
158
184
|
}
|
|
159
185
|
async getCommandJobOutput(jobId) {
|
|
160
186
|
const endpoint = await this.resolveCommandJobEndpoint(jobId);
|
|
161
|
-
const res = await
|
|
187
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs/${jobId}/output`, {
|
|
162
188
|
headers: this.authHeaders(),
|
|
163
189
|
});
|
|
164
190
|
if (!res.ok) {
|
|
@@ -179,7 +205,7 @@ export class LazySandbox {
|
|
|
179
205
|
}
|
|
180
206
|
async listCommandJobs() {
|
|
181
207
|
await this.ensure();
|
|
182
|
-
const res = await
|
|
208
|
+
const res = await this.fetchControl(`${this.requireEndpoint()}/exec/jobs`, {
|
|
183
209
|
headers: this.authHeaders(),
|
|
184
210
|
});
|
|
185
211
|
if (!res.ok) {
|
|
@@ -193,7 +219,7 @@ export class LazySandbox {
|
|
|
193
219
|
}
|
|
194
220
|
async cancelCommandJob(jobId) {
|
|
195
221
|
const endpoint = await this.resolveCommandJobEndpoint(jobId);
|
|
196
|
-
const res = await
|
|
222
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs/${jobId}/cancel`, {
|
|
197
223
|
method: "POST",
|
|
198
224
|
headers: this.authHeaders(),
|
|
199
225
|
});
|
|
@@ -219,7 +245,7 @@ export class LazySandbox {
|
|
|
219
245
|
return;
|
|
220
246
|
}
|
|
221
247
|
const promise = (async () => {
|
|
222
|
-
const res = await
|
|
248
|
+
const res = await this.fetchControl(`${this.apiUrl}/sandbox-sessions/${currentSessionId}/heartbeat`, {
|
|
223
249
|
method: "POST",
|
|
224
250
|
headers: this.authHeaders(),
|
|
225
251
|
});
|
|
@@ -287,7 +313,7 @@ export class LazySandbox {
|
|
|
287
313
|
}
|
|
288
314
|
async bootstrapSession() {
|
|
289
315
|
const projectId = this.resolveProjectId();
|
|
290
|
-
const res = await
|
|
316
|
+
const res = await this.fetchControl(`${this.apiUrl}/sandbox-sessions`, {
|
|
291
317
|
method: "POST",
|
|
292
318
|
headers: this.jsonHeaders(),
|
|
293
319
|
body: JSON.stringify(projectId ? { project_id: projectId } : {}),
|
|
@@ -319,23 +345,29 @@ export class LazySandbox {
|
|
|
319
345
|
if (session.status === "running") {
|
|
320
346
|
return session.endpoint;
|
|
321
347
|
}
|
|
322
|
-
await
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
headers: this.authHeaders(),
|
|
331
|
-
});
|
|
332
|
-
if (!res.ok) {
|
|
333
|
-
throw REQUEST_ERROR.create({
|
|
334
|
-
detail: `Failed to get sandbox: ${res.status} ${await res.text()}`,
|
|
348
|
+
return (await this.waitForReadySession(session.id)).endpoint;
|
|
349
|
+
}
|
|
350
|
+
async waitForReadySession(sessionId) {
|
|
351
|
+
const start = Date.now();
|
|
352
|
+
while (Date.now() - start < this.startupTimeoutMs) {
|
|
353
|
+
await new Promise((resolve) => dntShim.setTimeout(resolve, this.pollIntervalMs));
|
|
354
|
+
const res = await this.fetchControl(`${this.apiUrl}/sandbox-sessions/${sessionId}`, {
|
|
355
|
+
headers: this.authHeaders(),
|
|
335
356
|
});
|
|
357
|
+
if (!res.ok) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const session = await res.json();
|
|
361
|
+
if (session.status === "running") {
|
|
362
|
+
return session;
|
|
363
|
+
}
|
|
364
|
+
if (session.status === "error" || session.status === "deleting") {
|
|
365
|
+
throw REQUEST_ERROR.create({
|
|
366
|
+
detail: `Sandbox failed to start: status=${session.status}`,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
336
369
|
}
|
|
337
|
-
|
|
338
|
-
return nextSession.endpoint;
|
|
370
|
+
throw REQUEST_ERROR.create({ detail: "Sandbox did not become ready within timeout" });
|
|
339
371
|
}
|
|
340
372
|
async touchSession() {
|
|
341
373
|
const projectId = this.resolveProjectId();
|
|
@@ -365,7 +397,7 @@ export class LazySandbox {
|
|
|
365
397
|
this.heartbeatTimer = null;
|
|
366
398
|
}
|
|
367
399
|
async deleteSession(sessionId) {
|
|
368
|
-
await
|
|
400
|
+
await this.fetchControl(`${this.apiUrl}/sandbox-sessions/${sessionId}`, {
|
|
369
401
|
method: "DELETE",
|
|
370
402
|
headers: this.authHeaders(),
|
|
371
403
|
});
|
|
@@ -400,7 +432,7 @@ export class LazySandbox {
|
|
|
400
432
|
return trackedEndpoint;
|
|
401
433
|
}
|
|
402
434
|
await this.ensure();
|
|
403
|
-
return this.
|
|
435
|
+
return this.resolveRuntimeEndpoint();
|
|
404
436
|
}
|
|
405
437
|
updateTrackedCommandJob(job, endpoint) {
|
|
406
438
|
if (job.status === "running") {
|
|
@@ -415,6 +447,61 @@ export class LazySandbox {
|
|
|
415
447
|
this.startHeartbeatLoop();
|
|
416
448
|
}
|
|
417
449
|
}
|
|
450
|
+
async startExec(command, options) {
|
|
451
|
+
const endpoint = this.resolveRuntimeEndpoint();
|
|
452
|
+
const body = JSON.stringify({ command, ...this.resolveExecOptions(options) });
|
|
453
|
+
for (let attempt = 1; attempt <= this.execStartMaxAttempts; attempt += 1) {
|
|
454
|
+
try {
|
|
455
|
+
const res = await this.fetchExecStart(`${endpoint}/exec`, {
|
|
456
|
+
method: "POST",
|
|
457
|
+
headers: this.jsonHeaders(),
|
|
458
|
+
body,
|
|
459
|
+
});
|
|
460
|
+
if (res.ok) {
|
|
461
|
+
return res;
|
|
462
|
+
}
|
|
463
|
+
if (isRetryableExecStartStatus(res.status) && attempt < this.execStartMaxAttempts) {
|
|
464
|
+
await this.waitForExecStartRetry();
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
throw REQUEST_ERROR.create({ detail: `Exec failed: ${res.status} ${await res.text()}` });
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
if (!isRetryableExecStartError(error) || attempt >= this.execStartMaxAttempts) {
|
|
471
|
+
throw error;
|
|
472
|
+
}
|
|
473
|
+
await this.waitForExecStartRetry();
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
throw new Error("Sandbox exec failed before a request was made");
|
|
477
|
+
}
|
|
478
|
+
async fetchExecStart(url, init) {
|
|
479
|
+
return fetchWithTimeout(url, this.execStartTimeoutMs, init);
|
|
480
|
+
}
|
|
481
|
+
async fetchControl(url, init = {}) {
|
|
482
|
+
return fetchWithTimeout(url, this.controlRequestTimeoutMs, init);
|
|
483
|
+
}
|
|
484
|
+
waitForExecStartRetry() {
|
|
485
|
+
return new Promise((resolve) => dntShim.setTimeout(resolve, this.execStartRetryDelayMs));
|
|
486
|
+
}
|
|
487
|
+
async reprovisionAfterExecStartFailure() {
|
|
488
|
+
const sessionId = this.sessionId;
|
|
489
|
+
if (!sessionId)
|
|
490
|
+
return;
|
|
491
|
+
await this.deleteSession(sessionId);
|
|
492
|
+
this.resetSessionState(sessionId);
|
|
493
|
+
}
|
|
494
|
+
resolveRuntimeEndpoint() {
|
|
495
|
+
const endpoint = this.requireEndpoint();
|
|
496
|
+
const sessionId = this.requireSessionId();
|
|
497
|
+
return this.resolveRuntimeEndpointOption?.({ endpoint, sessionId }) ?? endpoint;
|
|
498
|
+
}
|
|
499
|
+
requireSessionId() {
|
|
500
|
+
if (!this.sessionId) {
|
|
501
|
+
throw new Error("Sandbox session unavailable");
|
|
502
|
+
}
|
|
503
|
+
return this.sessionId;
|
|
504
|
+
}
|
|
418
505
|
authHeaders() {
|
|
419
506
|
return { Authorization: `Bearer ${this.authToken}` };
|
|
420
507
|
}
|
|
@@ -425,6 +512,36 @@ export class LazySandbox {
|
|
|
425
512
|
};
|
|
426
513
|
}
|
|
427
514
|
}
|
|
515
|
+
function isRetryableExecStartStatus(status) {
|
|
516
|
+
return status === 502 || status === 503 || status === 504;
|
|
517
|
+
}
|
|
518
|
+
function isRetryableExecStartError(error) {
|
|
519
|
+
return error instanceof Error && /fetch failed/i.test(error.message);
|
|
520
|
+
}
|
|
521
|
+
function shouldReprovisionAfterExecStartFailure(error) {
|
|
522
|
+
if (!(error instanceof Error)) {
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
const cause = error.cause;
|
|
526
|
+
if (typeof cause !== "object" || cause === null || !("code" in cause)) {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
return typeof cause.code === "string" &&
|
|
530
|
+
REPROVISIONABLE_EXEC_START_ERROR_CODES.has(cause.code);
|
|
531
|
+
}
|
|
532
|
+
async function fetchWithTimeout(url, timeoutMs, init = {}) {
|
|
533
|
+
if (timeoutMs <= 0) {
|
|
534
|
+
return await fetch(url, init);
|
|
535
|
+
}
|
|
536
|
+
const controller = new AbortController();
|
|
537
|
+
const timeout = dntShim.setTimeout(() => controller.abort(), timeoutMs);
|
|
538
|
+
try {
|
|
539
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
540
|
+
}
|
|
541
|
+
finally {
|
|
542
|
+
clearTimeout(timeout);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
428
545
|
function mapCommandJob(json) {
|
|
429
546
|
return {
|
|
430
547
|
id: json.id,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.
|
|
1
|
+
export declare const VERSION = "0.1.282";
|
|
2
2
|
//# sourceMappingURL=version-constant.d.ts.map
|
package/package.json
CHANGED
package/src/deno.js
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
resolveSandboxApiUrl,
|
|
11
11
|
resolveSandboxAuthToken,
|
|
12
12
|
type SandboxOptions,
|
|
13
|
-
waitForSandboxReady,
|
|
14
13
|
} from "./sandbox.js";
|
|
15
14
|
|
|
16
15
|
export interface LazySandboxOptions extends SandboxOptions {
|
|
@@ -19,6 +18,11 @@ export interface LazySandboxOptions extends SandboxOptions {
|
|
|
19
18
|
pollIntervalMs?: number;
|
|
20
19
|
heartbeatIntervalMs?: number;
|
|
21
20
|
heartbeatGraceMs?: number;
|
|
21
|
+
controlRequestTimeoutMs?: number;
|
|
22
|
+
execStartTimeoutMs?: number;
|
|
23
|
+
execStartMaxAttempts?: number;
|
|
24
|
+
execStartRetryDelayMs?: number;
|
|
25
|
+
resolveRuntimeEndpoint?: (input: { endpoint: string; sessionId: string }) => string;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
interface SandboxSessionRecord {
|
|
@@ -31,6 +35,16 @@ const DEFAULT_STARTUP_TIMEOUT_MS = 180_000;
|
|
|
31
35
|
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
32
36
|
const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
33
37
|
const DEFAULT_HEARTBEAT_GRACE_MS = 5_000;
|
|
38
|
+
const DEFAULT_CONTROL_REQUEST_TIMEOUT_MS = 15_000;
|
|
39
|
+
const DEFAULT_EXEC_START_TIMEOUT_MS = 30_000;
|
|
40
|
+
const DEFAULT_EXEC_START_MAX_ATTEMPTS = 3;
|
|
41
|
+
const DEFAULT_EXEC_START_RETRY_DELAY_MS = 1_000;
|
|
42
|
+
const REPROVISIONABLE_EXEC_START_ERROR_CODES = new Set([
|
|
43
|
+
"ECONNREFUSED",
|
|
44
|
+
"ECONNRESET",
|
|
45
|
+
"ENOTFOUND",
|
|
46
|
+
"EHOSTUNREACH",
|
|
47
|
+
]);
|
|
34
48
|
|
|
35
49
|
/** Lazily provisions sandbox sessions and keeps them alive while in use. */
|
|
36
50
|
export class LazySandbox {
|
|
@@ -41,6 +55,16 @@ export class LazySandbox {
|
|
|
41
55
|
private readonly pollIntervalMs: number;
|
|
42
56
|
private readonly heartbeatIntervalMs: number;
|
|
43
57
|
private readonly heartbeatGraceMs: number;
|
|
58
|
+
private readonly controlRequestTimeoutMs: number;
|
|
59
|
+
private readonly execStartTimeoutMs: number;
|
|
60
|
+
private readonly execStartMaxAttempts: number;
|
|
61
|
+
private readonly execStartRetryDelayMs: number;
|
|
62
|
+
private readonly resolveRuntimeEndpointOption:
|
|
63
|
+
| ((input: {
|
|
64
|
+
endpoint: string;
|
|
65
|
+
sessionId: string;
|
|
66
|
+
}) => string)
|
|
67
|
+
| undefined;
|
|
44
68
|
|
|
45
69
|
private endpoint: string | null = null;
|
|
46
70
|
private sessionId: string | null = null;
|
|
@@ -60,6 +84,13 @@ export class LazySandbox {
|
|
|
60
84
|
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
61
85
|
this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
62
86
|
this.heartbeatGraceMs = options.heartbeatGraceMs ?? DEFAULT_HEARTBEAT_GRACE_MS;
|
|
87
|
+
this.controlRequestTimeoutMs = options.controlRequestTimeoutMs ??
|
|
88
|
+
DEFAULT_CONTROL_REQUEST_TIMEOUT_MS;
|
|
89
|
+
this.execStartTimeoutMs = options.execStartTimeoutMs ?? DEFAULT_EXEC_START_TIMEOUT_MS;
|
|
90
|
+
this.execStartMaxAttempts = options.execStartMaxAttempts ?? DEFAULT_EXEC_START_MAX_ATTEMPTS;
|
|
91
|
+
this.execStartRetryDelayMs = options.execStartRetryDelayMs ??
|
|
92
|
+
DEFAULT_EXEC_START_RETRY_DELAY_MS;
|
|
93
|
+
this.resolveRuntimeEndpointOption = options.resolveRuntimeEndpoint;
|
|
63
94
|
}
|
|
64
95
|
|
|
65
96
|
async ensure(): Promise<void> {
|
|
@@ -105,15 +136,17 @@ export class LazySandbox {
|
|
|
105
136
|
|
|
106
137
|
async *executeStream(command: string, options?: ExecOptions): AsyncGenerator<ExecStreamEvent> {
|
|
107
138
|
await this.touchSession();
|
|
139
|
+
let res: Response;
|
|
140
|
+
try {
|
|
141
|
+
res = await this.startExec(command, options);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (!shouldReprovisionAfterExecStartFailure(error)) {
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
108
146
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
body: JSON.stringify({ command, ...this.resolveExecOptions(options) }),
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
if (!res.ok) {
|
|
116
|
-
throw REQUEST_ERROR.create({ detail: `Exec failed: ${res.status} ${await res.text()}` });
|
|
147
|
+
await this.reprovisionAfterExecStartFailure();
|
|
148
|
+
await this.touchSession();
|
|
149
|
+
res = await this.startExec(command, options);
|
|
117
150
|
}
|
|
118
151
|
|
|
119
152
|
if (!res.body) {
|
|
@@ -146,9 +179,12 @@ export class LazySandbox {
|
|
|
146
179
|
async readFile(path: string): Promise<string> {
|
|
147
180
|
await this.touchSession();
|
|
148
181
|
|
|
149
|
-
const res = await
|
|
150
|
-
|
|
151
|
-
|
|
182
|
+
const res = await this.fetchControl(
|
|
183
|
+
`${this.requireEndpoint()}/file?path=${encodeURIComponent(path)}`,
|
|
184
|
+
{
|
|
185
|
+
headers: this.authHeaders(),
|
|
186
|
+
},
|
|
187
|
+
);
|
|
152
188
|
|
|
153
189
|
if (!res.ok) {
|
|
154
190
|
throw REQUEST_ERROR.create({ detail: `Read file failed: ${res.status} ${await res.text()}` });
|
|
@@ -160,7 +196,7 @@ export class LazySandbox {
|
|
|
160
196
|
async writeFiles(files: Array<{ path: string; content: string }>): Promise<void> {
|
|
161
197
|
await this.touchSession();
|
|
162
198
|
|
|
163
|
-
const res = await
|
|
199
|
+
const res = await this.fetchControl(`${this.requireEndpoint()}/files`, {
|
|
164
200
|
method: "POST",
|
|
165
201
|
headers: this.jsonHeaders(),
|
|
166
202
|
body: JSON.stringify({ files }),
|
|
@@ -175,9 +211,9 @@ export class LazySandbox {
|
|
|
175
211
|
|
|
176
212
|
async startCommandJob(command: string, options?: ExecOptions): Promise<CommandJob> {
|
|
177
213
|
await this.touchSession();
|
|
178
|
-
const endpoint = this.
|
|
214
|
+
const endpoint = this.resolveRuntimeEndpoint();
|
|
179
215
|
|
|
180
|
-
const res = await
|
|
216
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs`, {
|
|
181
217
|
method: "POST",
|
|
182
218
|
headers: this.jsonHeaders(),
|
|
183
219
|
body: JSON.stringify({ command, ...this.resolveExecOptions(options) }),
|
|
@@ -197,7 +233,7 @@ export class LazySandbox {
|
|
|
197
233
|
async getCommandJob(jobId: string): Promise<CommandJob> {
|
|
198
234
|
const endpoint = await this.resolveCommandJobEndpoint(jobId);
|
|
199
235
|
|
|
200
|
-
const res = await
|
|
236
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs/${jobId}`, {
|
|
201
237
|
headers: this.authHeaders(),
|
|
202
238
|
});
|
|
203
239
|
|
|
@@ -215,7 +251,7 @@ export class LazySandbox {
|
|
|
215
251
|
async getCommandJobOutput(jobId: string): Promise<CommandJobOutput> {
|
|
216
252
|
const endpoint = await this.resolveCommandJobEndpoint(jobId);
|
|
217
253
|
|
|
218
|
-
const res = await
|
|
254
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs/${jobId}/output`, {
|
|
219
255
|
headers: this.authHeaders(),
|
|
220
256
|
});
|
|
221
257
|
|
|
@@ -240,7 +276,7 @@ export class LazySandbox {
|
|
|
240
276
|
async listCommandJobs(): Promise<CommandJob[]> {
|
|
241
277
|
await this.ensure();
|
|
242
278
|
|
|
243
|
-
const res = await
|
|
279
|
+
const res = await this.fetchControl(`${this.requireEndpoint()}/exec/jobs`, {
|
|
244
280
|
headers: this.authHeaders(),
|
|
245
281
|
});
|
|
246
282
|
|
|
@@ -258,7 +294,7 @@ export class LazySandbox {
|
|
|
258
294
|
async cancelCommandJob(jobId: string): Promise<CommandJob> {
|
|
259
295
|
const endpoint = await this.resolveCommandJobEndpoint(jobId);
|
|
260
296
|
|
|
261
|
-
const res = await
|
|
297
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs/${jobId}/cancel`, {
|
|
262
298
|
method: "POST",
|
|
263
299
|
headers: this.authHeaders(),
|
|
264
300
|
});
|
|
@@ -291,10 +327,13 @@ export class LazySandbox {
|
|
|
291
327
|
}
|
|
292
328
|
|
|
293
329
|
const promise = (async () => {
|
|
294
|
-
const res = await
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
330
|
+
const res = await this.fetchControl(
|
|
331
|
+
`${this.apiUrl}/sandbox-sessions/${currentSessionId}/heartbeat`,
|
|
332
|
+
{
|
|
333
|
+
method: "POST",
|
|
334
|
+
headers: this.authHeaders(),
|
|
335
|
+
},
|
|
336
|
+
);
|
|
298
337
|
|
|
299
338
|
if (!res.ok) {
|
|
300
339
|
if (this.sessionId === currentSessionId) {
|
|
@@ -371,7 +410,7 @@ export class LazySandbox {
|
|
|
371
410
|
|
|
372
411
|
private async bootstrapSession(): Promise<void> {
|
|
373
412
|
const projectId = this.resolveProjectId();
|
|
374
|
-
const res = await
|
|
413
|
+
const res = await this.fetchControl(`${this.apiUrl}/sandbox-sessions`, {
|
|
375
414
|
method: "POST",
|
|
376
415
|
headers: this.jsonHeaders(),
|
|
377
416
|
body: JSON.stringify(projectId ? { project_id: projectId } : {}),
|
|
@@ -407,26 +446,35 @@ export class LazySandbox {
|
|
|
407
446
|
return session.endpoint;
|
|
408
447
|
}
|
|
409
448
|
|
|
410
|
-
await
|
|
411
|
-
|
|
412
|
-
id: session.id,
|
|
413
|
-
authToken: this.authToken,
|
|
414
|
-
maxWaitMs: this.startupTimeoutMs,
|
|
415
|
-
pollIntervalMs: this.pollIntervalMs,
|
|
416
|
-
});
|
|
449
|
+
return (await this.waitForReadySession(session.id)).endpoint;
|
|
450
|
+
}
|
|
417
451
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
});
|
|
452
|
+
private async waitForReadySession(sessionId: string): Promise<SandboxSessionRecord> {
|
|
453
|
+
const start = Date.now();
|
|
421
454
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
455
|
+
while (Date.now() - start < this.startupTimeoutMs) {
|
|
456
|
+
await new Promise((resolve) => dntShim.setTimeout(resolve, this.pollIntervalMs));
|
|
457
|
+
|
|
458
|
+
const res = await this.fetchControl(`${this.apiUrl}/sandbox-sessions/${sessionId}`, {
|
|
459
|
+
headers: this.authHeaders(),
|
|
425
460
|
});
|
|
461
|
+
|
|
462
|
+
if (!res.ok) {
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const session = await res.json() as SandboxSessionRecord;
|
|
467
|
+
if (session.status === "running") {
|
|
468
|
+
return session;
|
|
469
|
+
}
|
|
470
|
+
if (session.status === "error" || session.status === "deleting") {
|
|
471
|
+
throw REQUEST_ERROR.create({
|
|
472
|
+
detail: `Sandbox failed to start: status=${session.status}`,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
426
475
|
}
|
|
427
476
|
|
|
428
|
-
|
|
429
|
-
return nextSession.endpoint;
|
|
477
|
+
throw REQUEST_ERROR.create({ detail: "Sandbox did not become ready within timeout" });
|
|
430
478
|
}
|
|
431
479
|
|
|
432
480
|
private async touchSession(): Promise<void> {
|
|
@@ -460,7 +508,7 @@ export class LazySandbox {
|
|
|
460
508
|
}
|
|
461
509
|
|
|
462
510
|
private async deleteSession(sessionId: string): Promise<void> {
|
|
463
|
-
await
|
|
511
|
+
await this.fetchControl(`${this.apiUrl}/sandbox-sessions/${sessionId}`, {
|
|
464
512
|
method: "DELETE",
|
|
465
513
|
headers: this.authHeaders(),
|
|
466
514
|
});
|
|
@@ -501,7 +549,7 @@ export class LazySandbox {
|
|
|
501
549
|
}
|
|
502
550
|
|
|
503
551
|
await this.ensure();
|
|
504
|
-
return this.
|
|
552
|
+
return this.resolveRuntimeEndpoint();
|
|
505
553
|
}
|
|
506
554
|
|
|
507
555
|
private updateTrackedCommandJob(job: Pick<CommandJob, "id" | "status">, endpoint: string): void {
|
|
@@ -520,6 +568,74 @@ export class LazySandbox {
|
|
|
520
568
|
}
|
|
521
569
|
}
|
|
522
570
|
|
|
571
|
+
private async startExec(command: string, options?: ExecOptions): Promise<Response> {
|
|
572
|
+
const endpoint = this.resolveRuntimeEndpoint();
|
|
573
|
+
const body = JSON.stringify({ command, ...this.resolveExecOptions(options) });
|
|
574
|
+
|
|
575
|
+
for (let attempt = 1; attempt <= this.execStartMaxAttempts; attempt += 1) {
|
|
576
|
+
try {
|
|
577
|
+
const res = await this.fetchExecStart(`${endpoint}/exec`, {
|
|
578
|
+
method: "POST",
|
|
579
|
+
headers: this.jsonHeaders(),
|
|
580
|
+
body,
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
if (res.ok) {
|
|
584
|
+
return res;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (isRetryableExecStartStatus(res.status) && attempt < this.execStartMaxAttempts) {
|
|
588
|
+
await this.waitForExecStartRetry();
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
throw REQUEST_ERROR.create({ detail: `Exec failed: ${res.status} ${await res.text()}` });
|
|
593
|
+
} catch (error) {
|
|
594
|
+
if (!isRetryableExecStartError(error) || attempt >= this.execStartMaxAttempts) {
|
|
595
|
+
throw error;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
await this.waitForExecStartRetry();
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
throw new Error("Sandbox exec failed before a request was made");
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
private async fetchExecStart(url: string, init: RequestInit): Promise<Response> {
|
|
606
|
+
return fetchWithTimeout(url, this.execStartTimeoutMs, init);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
private async fetchControl(url: string, init: RequestInit = {}): Promise<Response> {
|
|
610
|
+
return fetchWithTimeout(url, this.controlRequestTimeoutMs, init);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
private waitForExecStartRetry(): Promise<void> {
|
|
614
|
+
return new Promise((resolve) => dntShim.setTimeout(resolve, this.execStartRetryDelayMs));
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
private async reprovisionAfterExecStartFailure(): Promise<void> {
|
|
618
|
+
const sessionId = this.sessionId;
|
|
619
|
+
if (!sessionId) return;
|
|
620
|
+
|
|
621
|
+
await this.deleteSession(sessionId);
|
|
622
|
+
this.resetSessionState(sessionId);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
private resolveRuntimeEndpoint(): string {
|
|
626
|
+
const endpoint = this.requireEndpoint();
|
|
627
|
+
const sessionId = this.requireSessionId();
|
|
628
|
+
return this.resolveRuntimeEndpointOption?.({ endpoint, sessionId }) ?? endpoint;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
private requireSessionId(): string {
|
|
632
|
+
if (!this.sessionId) {
|
|
633
|
+
throw new Error("Sandbox session unavailable");
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return this.sessionId;
|
|
637
|
+
}
|
|
638
|
+
|
|
523
639
|
private authHeaders(): HeadersInit {
|
|
524
640
|
return { Authorization: `Bearer ${this.authToken}` };
|
|
525
641
|
}
|
|
@@ -532,6 +648,47 @@ export class LazySandbox {
|
|
|
532
648
|
}
|
|
533
649
|
}
|
|
534
650
|
|
|
651
|
+
function isRetryableExecStartStatus(status: number): boolean {
|
|
652
|
+
return status === 502 || status === 503 || status === 504;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function isRetryableExecStartError(error: unknown): boolean {
|
|
656
|
+
return error instanceof Error && /fetch failed/i.test(error.message);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function shouldReprovisionAfterExecStartFailure(error: unknown): boolean {
|
|
660
|
+
if (!(error instanceof Error)) {
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const cause = error.cause;
|
|
665
|
+
if (typeof cause !== "object" || cause === null || !("code" in cause)) {
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return typeof cause.code === "string" &&
|
|
670
|
+
REPROVISIONABLE_EXEC_START_ERROR_CODES.has(cause.code);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async function fetchWithTimeout(
|
|
674
|
+
url: string,
|
|
675
|
+
timeoutMs: number,
|
|
676
|
+
init: RequestInit = {},
|
|
677
|
+
): Promise<Response> {
|
|
678
|
+
if (timeoutMs <= 0) {
|
|
679
|
+
return await fetch(url, init);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const controller = new AbortController();
|
|
683
|
+
const timeout = dntShim.setTimeout(() => controller.abort(), timeoutMs);
|
|
684
|
+
|
|
685
|
+
try {
|
|
686
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
687
|
+
} finally {
|
|
688
|
+
clearTimeout(timeout);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
535
692
|
function mapCommandJob(json: Record<string, unknown>): CommandJob {
|
|
536
693
|
return {
|
|
537
694
|
id: json.id as string,
|