veryfront 0.1.281 → 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 -1
- package/esm/src/sandbox/lazy-sandbox.d.ts +4 -0
- package/esm/src/sandbox/lazy-sandbox.d.ts.map +1 -1
- package/esm/src/sandbox/lazy-sandbox.js +53 -34
- 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 -1
- package/src/src/sandbox/lazy-sandbox.ts +73 -38
- package/src/src/utils/version-constant.ts +1 -1
package/esm/deno.js
CHANGED
|
@@ -5,6 +5,7 @@ export interface LazySandboxOptions extends SandboxOptions {
|
|
|
5
5
|
pollIntervalMs?: number;
|
|
6
6
|
heartbeatIntervalMs?: number;
|
|
7
7
|
heartbeatGraceMs?: number;
|
|
8
|
+
controlRequestTimeoutMs?: number;
|
|
8
9
|
execStartTimeoutMs?: number;
|
|
9
10
|
execStartMaxAttempts?: number;
|
|
10
11
|
execStartRetryDelayMs?: number;
|
|
@@ -22,6 +23,7 @@ export declare class LazySandbox {
|
|
|
22
23
|
private readonly pollIntervalMs;
|
|
23
24
|
private readonly heartbeatIntervalMs;
|
|
24
25
|
private readonly heartbeatGraceMs;
|
|
26
|
+
private readonly controlRequestTimeoutMs;
|
|
25
27
|
private readonly execStartTimeoutMs;
|
|
26
28
|
private readonly execStartMaxAttempts;
|
|
27
29
|
private readonly execStartRetryDelayMs;
|
|
@@ -56,6 +58,7 @@ export declare class LazySandbox {
|
|
|
56
58
|
get isActive(): boolean;
|
|
57
59
|
private bootstrapSession;
|
|
58
60
|
private resolveReadyEndpoint;
|
|
61
|
+
private waitForReadySession;
|
|
59
62
|
private touchSession;
|
|
60
63
|
private startHeartbeatLoop;
|
|
61
64
|
private stopHeartbeatLoop;
|
|
@@ -68,6 +71,7 @@ export declare class LazySandbox {
|
|
|
68
71
|
private updateTrackedCommandJob;
|
|
69
72
|
private startExec;
|
|
70
73
|
private fetchExecStart;
|
|
74
|
+
private fetchControl;
|
|
71
75
|
private waitForExecStartRetry;
|
|
72
76
|
private reprovisionAfterExecStartFailure;
|
|
73
77
|
private resolveRuntimeEndpoint;
|
|
@@ -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":"AAEA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,gBAAgB,EAErB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,eAAe,EAGpB,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,kBAAmB,SAAQ,cAAc;IACxD,YAAY,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,sBAAsB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;CACrF;AAuBD,4EAA4E;AAC5E,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;IAC/D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAS;IACjD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;IAC/C,OAAO,CAAC,QAAQ,CAAC,4BAA4B,CAK/B;IAEd,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,cAAc,CAAuD;IAC7E,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAA6B;gBAE3D,OAAO,GAAE,kBAAuB;IAiBtC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBvB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAsB1E,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,cAAc,CAAC,eAAe,CAAC;IA0CvF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBvC,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB1E,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAqB5E,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAkBjD,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAyB7D,eAAe,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAkBxC,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAmBpD,SAAS,CAAC,KAAK,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAkDvC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoC5B,IAAI,EAAE,IAAI,MAAM,GAAG,IAAI,CAEtB;IAED,IAAI,GAAG,IAAI,MAAM,GAAG,IAAI,CAEvB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;YAEa,gBAAgB;YAiChB,oBAAoB;YAQpB,mBAAmB;YA4BnB,YAAY;IAc1B,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,iBAAiB;YAMX,aAAa;IAO3B,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,kBAAkB;YAKZ,yBAAyB;IAUvC,OAAO,CAAC,uBAAuB;YAgBjB,SAAS;YAkCT,cAAc;YAId,YAAY;IAI1B,OAAO,CAAC,qBAAqB;YAIf,gCAAgC;IAQ9C,OAAO,CAAC,sBAAsB;IAM9B,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,WAAW;CAMpB"}
|
|
@@ -1,10 +1,11 @@
|
|
|
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;
|
|
8
9
|
const DEFAULT_EXEC_START_TIMEOUT_MS = 30_000;
|
|
9
10
|
const DEFAULT_EXEC_START_MAX_ATTEMPTS = 3;
|
|
10
11
|
const DEFAULT_EXEC_START_RETRY_DELAY_MS = 1_000;
|
|
@@ -23,6 +24,7 @@ export class LazySandbox {
|
|
|
23
24
|
pollIntervalMs;
|
|
24
25
|
heartbeatIntervalMs;
|
|
25
26
|
heartbeatGraceMs;
|
|
27
|
+
controlRequestTimeoutMs;
|
|
26
28
|
execStartTimeoutMs;
|
|
27
29
|
execStartMaxAttempts;
|
|
28
30
|
execStartRetryDelayMs;
|
|
@@ -44,6 +46,8 @@ export class LazySandbox {
|
|
|
44
46
|
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
45
47
|
this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
46
48
|
this.heartbeatGraceMs = options.heartbeatGraceMs ?? DEFAULT_HEARTBEAT_GRACE_MS;
|
|
49
|
+
this.controlRequestTimeoutMs = options.controlRequestTimeoutMs ??
|
|
50
|
+
DEFAULT_CONTROL_REQUEST_TIMEOUT_MS;
|
|
47
51
|
this.execStartTimeoutMs = options.execStartTimeoutMs ?? DEFAULT_EXEC_START_TIMEOUT_MS;
|
|
48
52
|
this.execStartMaxAttempts = options.execStartMaxAttempts ?? DEFAULT_EXEC_START_MAX_ATTEMPTS;
|
|
49
53
|
this.execStartRetryDelayMs = options.execStartRetryDelayMs ??
|
|
@@ -126,7 +130,7 @@ export class LazySandbox {
|
|
|
126
130
|
}
|
|
127
131
|
async readFile(path) {
|
|
128
132
|
await this.touchSession();
|
|
129
|
-
const res = await
|
|
133
|
+
const res = await this.fetchControl(`${this.requireEndpoint()}/file?path=${encodeURIComponent(path)}`, {
|
|
130
134
|
headers: this.authHeaders(),
|
|
131
135
|
});
|
|
132
136
|
if (!res.ok) {
|
|
@@ -136,7 +140,7 @@ export class LazySandbox {
|
|
|
136
140
|
}
|
|
137
141
|
async writeFiles(files) {
|
|
138
142
|
await this.touchSession();
|
|
139
|
-
const res = await
|
|
143
|
+
const res = await this.fetchControl(`${this.requireEndpoint()}/files`, {
|
|
140
144
|
method: "POST",
|
|
141
145
|
headers: this.jsonHeaders(),
|
|
142
146
|
body: JSON.stringify({ files }),
|
|
@@ -150,7 +154,7 @@ export class LazySandbox {
|
|
|
150
154
|
async startCommandJob(command, options) {
|
|
151
155
|
await this.touchSession();
|
|
152
156
|
const endpoint = this.resolveRuntimeEndpoint();
|
|
153
|
-
const res = await
|
|
157
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs`, {
|
|
154
158
|
method: "POST",
|
|
155
159
|
headers: this.jsonHeaders(),
|
|
156
160
|
body: JSON.stringify({ command, ...this.resolveExecOptions(options) }),
|
|
@@ -166,7 +170,7 @@ export class LazySandbox {
|
|
|
166
170
|
}
|
|
167
171
|
async getCommandJob(jobId) {
|
|
168
172
|
const endpoint = await this.resolveCommandJobEndpoint(jobId);
|
|
169
|
-
const res = await
|
|
173
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs/${jobId}`, {
|
|
170
174
|
headers: this.authHeaders(),
|
|
171
175
|
});
|
|
172
176
|
if (!res.ok) {
|
|
@@ -180,7 +184,7 @@ export class LazySandbox {
|
|
|
180
184
|
}
|
|
181
185
|
async getCommandJobOutput(jobId) {
|
|
182
186
|
const endpoint = await this.resolveCommandJobEndpoint(jobId);
|
|
183
|
-
const res = await
|
|
187
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs/${jobId}/output`, {
|
|
184
188
|
headers: this.authHeaders(),
|
|
185
189
|
});
|
|
186
190
|
if (!res.ok) {
|
|
@@ -201,7 +205,7 @@ export class LazySandbox {
|
|
|
201
205
|
}
|
|
202
206
|
async listCommandJobs() {
|
|
203
207
|
await this.ensure();
|
|
204
|
-
const res = await
|
|
208
|
+
const res = await this.fetchControl(`${this.requireEndpoint()}/exec/jobs`, {
|
|
205
209
|
headers: this.authHeaders(),
|
|
206
210
|
});
|
|
207
211
|
if (!res.ok) {
|
|
@@ -215,7 +219,7 @@ export class LazySandbox {
|
|
|
215
219
|
}
|
|
216
220
|
async cancelCommandJob(jobId) {
|
|
217
221
|
const endpoint = await this.resolveCommandJobEndpoint(jobId);
|
|
218
|
-
const res = await
|
|
222
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs/${jobId}/cancel`, {
|
|
219
223
|
method: "POST",
|
|
220
224
|
headers: this.authHeaders(),
|
|
221
225
|
});
|
|
@@ -241,7 +245,7 @@ export class LazySandbox {
|
|
|
241
245
|
return;
|
|
242
246
|
}
|
|
243
247
|
const promise = (async () => {
|
|
244
|
-
const res = await
|
|
248
|
+
const res = await this.fetchControl(`${this.apiUrl}/sandbox-sessions/${currentSessionId}/heartbeat`, {
|
|
245
249
|
method: "POST",
|
|
246
250
|
headers: this.authHeaders(),
|
|
247
251
|
});
|
|
@@ -309,7 +313,7 @@ export class LazySandbox {
|
|
|
309
313
|
}
|
|
310
314
|
async bootstrapSession() {
|
|
311
315
|
const projectId = this.resolveProjectId();
|
|
312
|
-
const res = await
|
|
316
|
+
const res = await this.fetchControl(`${this.apiUrl}/sandbox-sessions`, {
|
|
313
317
|
method: "POST",
|
|
314
318
|
headers: this.jsonHeaders(),
|
|
315
319
|
body: JSON.stringify(projectId ? { project_id: projectId } : {}),
|
|
@@ -341,23 +345,29 @@ export class LazySandbox {
|
|
|
341
345
|
if (session.status === "running") {
|
|
342
346
|
return session.endpoint;
|
|
343
347
|
}
|
|
344
|
-
await
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
headers: this.authHeaders(),
|
|
353
|
-
});
|
|
354
|
-
if (!res.ok) {
|
|
355
|
-
throw REQUEST_ERROR.create({
|
|
356
|
-
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(),
|
|
357
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
|
+
}
|
|
358
369
|
}
|
|
359
|
-
|
|
360
|
-
return nextSession.endpoint;
|
|
370
|
+
throw REQUEST_ERROR.create({ detail: "Sandbox did not become ready within timeout" });
|
|
361
371
|
}
|
|
362
372
|
async touchSession() {
|
|
363
373
|
const projectId = this.resolveProjectId();
|
|
@@ -387,7 +397,7 @@ export class LazySandbox {
|
|
|
387
397
|
this.heartbeatTimer = null;
|
|
388
398
|
}
|
|
389
399
|
async deleteSession(sessionId) {
|
|
390
|
-
await
|
|
400
|
+
await this.fetchControl(`${this.apiUrl}/sandbox-sessions/${sessionId}`, {
|
|
391
401
|
method: "DELETE",
|
|
392
402
|
headers: this.authHeaders(),
|
|
393
403
|
});
|
|
@@ -466,14 +476,10 @@ export class LazySandbox {
|
|
|
466
476
|
throw new Error("Sandbox exec failed before a request was made");
|
|
467
477
|
}
|
|
468
478
|
async fetchExecStart(url, init) {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
finally {
|
|
475
|
-
clearTimeout(timeout);
|
|
476
|
-
}
|
|
479
|
+
return fetchWithTimeout(url, this.execStartTimeoutMs, init);
|
|
480
|
+
}
|
|
481
|
+
async fetchControl(url, init = {}) {
|
|
482
|
+
return fetchWithTimeout(url, this.controlRequestTimeoutMs, init);
|
|
477
483
|
}
|
|
478
484
|
waitForExecStartRetry() {
|
|
479
485
|
return new Promise((resolve) => dntShim.setTimeout(resolve, this.execStartRetryDelayMs));
|
|
@@ -523,6 +529,19 @@ function shouldReprovisionAfterExecStartFailure(error) {
|
|
|
523
529
|
return typeof cause.code === "string" &&
|
|
524
530
|
REPROVISIONABLE_EXEC_START_ERROR_CODES.has(cause.code);
|
|
525
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
|
+
}
|
|
526
545
|
function mapCommandJob(json) {
|
|
527
546
|
return {
|
|
528
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,7 @@ export interface LazySandboxOptions extends SandboxOptions {
|
|
|
19
18
|
pollIntervalMs?: number;
|
|
20
19
|
heartbeatIntervalMs?: number;
|
|
21
20
|
heartbeatGraceMs?: number;
|
|
21
|
+
controlRequestTimeoutMs?: number;
|
|
22
22
|
execStartTimeoutMs?: number;
|
|
23
23
|
execStartMaxAttempts?: number;
|
|
24
24
|
execStartRetryDelayMs?: number;
|
|
@@ -35,6 +35,7 @@ const DEFAULT_STARTUP_TIMEOUT_MS = 180_000;
|
|
|
35
35
|
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
36
36
|
const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
37
37
|
const DEFAULT_HEARTBEAT_GRACE_MS = 5_000;
|
|
38
|
+
const DEFAULT_CONTROL_REQUEST_TIMEOUT_MS = 15_000;
|
|
38
39
|
const DEFAULT_EXEC_START_TIMEOUT_MS = 30_000;
|
|
39
40
|
const DEFAULT_EXEC_START_MAX_ATTEMPTS = 3;
|
|
40
41
|
const DEFAULT_EXEC_START_RETRY_DELAY_MS = 1_000;
|
|
@@ -54,6 +55,7 @@ export class LazySandbox {
|
|
|
54
55
|
private readonly pollIntervalMs: number;
|
|
55
56
|
private readonly heartbeatIntervalMs: number;
|
|
56
57
|
private readonly heartbeatGraceMs: number;
|
|
58
|
+
private readonly controlRequestTimeoutMs: number;
|
|
57
59
|
private readonly execStartTimeoutMs: number;
|
|
58
60
|
private readonly execStartMaxAttempts: number;
|
|
59
61
|
private readonly execStartRetryDelayMs: number;
|
|
@@ -82,6 +84,8 @@ export class LazySandbox {
|
|
|
82
84
|
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
83
85
|
this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
84
86
|
this.heartbeatGraceMs = options.heartbeatGraceMs ?? DEFAULT_HEARTBEAT_GRACE_MS;
|
|
87
|
+
this.controlRequestTimeoutMs = options.controlRequestTimeoutMs ??
|
|
88
|
+
DEFAULT_CONTROL_REQUEST_TIMEOUT_MS;
|
|
85
89
|
this.execStartTimeoutMs = options.execStartTimeoutMs ?? DEFAULT_EXEC_START_TIMEOUT_MS;
|
|
86
90
|
this.execStartMaxAttempts = options.execStartMaxAttempts ?? DEFAULT_EXEC_START_MAX_ATTEMPTS;
|
|
87
91
|
this.execStartRetryDelayMs = options.execStartRetryDelayMs ??
|
|
@@ -175,9 +179,12 @@ export class LazySandbox {
|
|
|
175
179
|
async readFile(path: string): Promise<string> {
|
|
176
180
|
await this.touchSession();
|
|
177
181
|
|
|
178
|
-
const res = await
|
|
179
|
-
|
|
180
|
-
|
|
182
|
+
const res = await this.fetchControl(
|
|
183
|
+
`${this.requireEndpoint()}/file?path=${encodeURIComponent(path)}`,
|
|
184
|
+
{
|
|
185
|
+
headers: this.authHeaders(),
|
|
186
|
+
},
|
|
187
|
+
);
|
|
181
188
|
|
|
182
189
|
if (!res.ok) {
|
|
183
190
|
throw REQUEST_ERROR.create({ detail: `Read file failed: ${res.status} ${await res.text()}` });
|
|
@@ -189,7 +196,7 @@ export class LazySandbox {
|
|
|
189
196
|
async writeFiles(files: Array<{ path: string; content: string }>): Promise<void> {
|
|
190
197
|
await this.touchSession();
|
|
191
198
|
|
|
192
|
-
const res = await
|
|
199
|
+
const res = await this.fetchControl(`${this.requireEndpoint()}/files`, {
|
|
193
200
|
method: "POST",
|
|
194
201
|
headers: this.jsonHeaders(),
|
|
195
202
|
body: JSON.stringify({ files }),
|
|
@@ -206,7 +213,7 @@ export class LazySandbox {
|
|
|
206
213
|
await this.touchSession();
|
|
207
214
|
const endpoint = this.resolveRuntimeEndpoint();
|
|
208
215
|
|
|
209
|
-
const res = await
|
|
216
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs`, {
|
|
210
217
|
method: "POST",
|
|
211
218
|
headers: this.jsonHeaders(),
|
|
212
219
|
body: JSON.stringify({ command, ...this.resolveExecOptions(options) }),
|
|
@@ -226,7 +233,7 @@ export class LazySandbox {
|
|
|
226
233
|
async getCommandJob(jobId: string): Promise<CommandJob> {
|
|
227
234
|
const endpoint = await this.resolveCommandJobEndpoint(jobId);
|
|
228
235
|
|
|
229
|
-
const res = await
|
|
236
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs/${jobId}`, {
|
|
230
237
|
headers: this.authHeaders(),
|
|
231
238
|
});
|
|
232
239
|
|
|
@@ -244,7 +251,7 @@ export class LazySandbox {
|
|
|
244
251
|
async getCommandJobOutput(jobId: string): Promise<CommandJobOutput> {
|
|
245
252
|
const endpoint = await this.resolveCommandJobEndpoint(jobId);
|
|
246
253
|
|
|
247
|
-
const res = await
|
|
254
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs/${jobId}/output`, {
|
|
248
255
|
headers: this.authHeaders(),
|
|
249
256
|
});
|
|
250
257
|
|
|
@@ -269,7 +276,7 @@ export class LazySandbox {
|
|
|
269
276
|
async listCommandJobs(): Promise<CommandJob[]> {
|
|
270
277
|
await this.ensure();
|
|
271
278
|
|
|
272
|
-
const res = await
|
|
279
|
+
const res = await this.fetchControl(`${this.requireEndpoint()}/exec/jobs`, {
|
|
273
280
|
headers: this.authHeaders(),
|
|
274
281
|
});
|
|
275
282
|
|
|
@@ -287,7 +294,7 @@ export class LazySandbox {
|
|
|
287
294
|
async cancelCommandJob(jobId: string): Promise<CommandJob> {
|
|
288
295
|
const endpoint = await this.resolveCommandJobEndpoint(jobId);
|
|
289
296
|
|
|
290
|
-
const res = await
|
|
297
|
+
const res = await this.fetchControl(`${endpoint}/exec/jobs/${jobId}/cancel`, {
|
|
291
298
|
method: "POST",
|
|
292
299
|
headers: this.authHeaders(),
|
|
293
300
|
});
|
|
@@ -320,10 +327,13 @@ export class LazySandbox {
|
|
|
320
327
|
}
|
|
321
328
|
|
|
322
329
|
const promise = (async () => {
|
|
323
|
-
const res = await
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
330
|
+
const res = await this.fetchControl(
|
|
331
|
+
`${this.apiUrl}/sandbox-sessions/${currentSessionId}/heartbeat`,
|
|
332
|
+
{
|
|
333
|
+
method: "POST",
|
|
334
|
+
headers: this.authHeaders(),
|
|
335
|
+
},
|
|
336
|
+
);
|
|
327
337
|
|
|
328
338
|
if (!res.ok) {
|
|
329
339
|
if (this.sessionId === currentSessionId) {
|
|
@@ -400,7 +410,7 @@ export class LazySandbox {
|
|
|
400
410
|
|
|
401
411
|
private async bootstrapSession(): Promise<void> {
|
|
402
412
|
const projectId = this.resolveProjectId();
|
|
403
|
-
const res = await
|
|
413
|
+
const res = await this.fetchControl(`${this.apiUrl}/sandbox-sessions`, {
|
|
404
414
|
method: "POST",
|
|
405
415
|
headers: this.jsonHeaders(),
|
|
406
416
|
body: JSON.stringify(projectId ? { project_id: projectId } : {}),
|
|
@@ -436,26 +446,35 @@ export class LazySandbox {
|
|
|
436
446
|
return session.endpoint;
|
|
437
447
|
}
|
|
438
448
|
|
|
439
|
-
await
|
|
440
|
-
|
|
441
|
-
id: session.id,
|
|
442
|
-
authToken: this.authToken,
|
|
443
|
-
maxWaitMs: this.startupTimeoutMs,
|
|
444
|
-
pollIntervalMs: this.pollIntervalMs,
|
|
445
|
-
});
|
|
449
|
+
return (await this.waitForReadySession(session.id)).endpoint;
|
|
450
|
+
}
|
|
446
451
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
});
|
|
452
|
+
private async waitForReadySession(sessionId: string): Promise<SandboxSessionRecord> {
|
|
453
|
+
const start = Date.now();
|
|
450
454
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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(),
|
|
454
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
|
+
}
|
|
455
475
|
}
|
|
456
476
|
|
|
457
|
-
|
|
458
|
-
return nextSession.endpoint;
|
|
477
|
+
throw REQUEST_ERROR.create({ detail: "Sandbox did not become ready within timeout" });
|
|
459
478
|
}
|
|
460
479
|
|
|
461
480
|
private async touchSession(): Promise<void> {
|
|
@@ -489,7 +508,7 @@ export class LazySandbox {
|
|
|
489
508
|
}
|
|
490
509
|
|
|
491
510
|
private async deleteSession(sessionId: string): Promise<void> {
|
|
492
|
-
await
|
|
511
|
+
await this.fetchControl(`${this.apiUrl}/sandbox-sessions/${sessionId}`, {
|
|
493
512
|
method: "DELETE",
|
|
494
513
|
headers: this.authHeaders(),
|
|
495
514
|
});
|
|
@@ -584,14 +603,11 @@ export class LazySandbox {
|
|
|
584
603
|
}
|
|
585
604
|
|
|
586
605
|
private async fetchExecStart(url: string, init: RequestInit): Promise<Response> {
|
|
587
|
-
|
|
588
|
-
|
|
606
|
+
return fetchWithTimeout(url, this.execStartTimeoutMs, init);
|
|
607
|
+
}
|
|
589
608
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
} finally {
|
|
593
|
-
clearTimeout(timeout);
|
|
594
|
-
}
|
|
609
|
+
private async fetchControl(url: string, init: RequestInit = {}): Promise<Response> {
|
|
610
|
+
return fetchWithTimeout(url, this.controlRequestTimeoutMs, init);
|
|
595
611
|
}
|
|
596
612
|
|
|
597
613
|
private waitForExecStartRetry(): Promise<void> {
|
|
@@ -654,6 +670,25 @@ function shouldReprovisionAfterExecStartFailure(error: unknown): boolean {
|
|
|
654
670
|
REPROVISIONABLE_EXEC_START_ERROR_CODES.has(cause.code);
|
|
655
671
|
}
|
|
656
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
|
+
|
|
657
692
|
function mapCommandJob(json: Record<string, unknown>): CommandJob {
|
|
658
693
|
return {
|
|
659
694
|
id: json.id as string,
|