veryfront 0.1.275 → 0.1.277
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/index.d.ts +1 -0
- package/esm/src/sandbox/index.d.ts.map +1 -1
- package/esm/src/sandbox/index.js +1 -0
- package/esm/src/sandbox/lazy-sandbox.d.ts +57 -0
- package/esm/src/sandbox/lazy-sandbox.d.ts.map +1 -0
- package/esm/src/sandbox/lazy-sandbox.js +403 -0
- package/esm/src/sandbox/sandbox.d.ts +12 -0
- package/esm/src/sandbox/sandbox.d.ts.map +1 -1
- package/esm/src/sandbox/sandbox.js +57 -31
- 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/index.ts +1 -0
- package/src/src/sandbox/lazy-sandbox.ts +504 -0
- package/src/src/sandbox/sandbox.ts +75 -34
- package/src/src/utils/version-constant.ts +1 -1
package/esm/deno.js
CHANGED
|
@@ -18,4 +18,5 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import "../../_dnt.polyfills.js";
|
|
20
20
|
export { type CommandJob, type CommandJobHeartbeatStatus, type CommandJobOutput, type CommandJobStatus, type ExecOptions, type ExecResult, type ExecStreamEvent, Sandbox, type SandboxListOptions, type SandboxListResult, type SandboxOptions, type SandboxSession, } from "./sandbox.js";
|
|
21
|
+
export { LazySandbox, type LazySandboxOptions } from "./lazy-sandbox.js";
|
|
21
22
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/sandbox/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,yBAAyB,CAAC;AAGjC,OAAO,EACL,KAAK,UAAU,EACf,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,OAAO,EACP,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/sandbox/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,yBAAyB,CAAC;AAGjC,OAAO,EACL,KAAK,UAAU,EACf,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,OAAO,EACP,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/esm/src/sandbox/index.js
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type CommandJob, type CommandJobOutput, type ExecOptions, type ExecResult, type ExecStreamEvent, type SandboxOptions } from "./sandbox.js";
|
|
2
|
+
export interface LazySandboxOptions extends SandboxOptions {
|
|
3
|
+
getProjectId?: () => string | null | undefined;
|
|
4
|
+
startupTimeoutMs?: number;
|
|
5
|
+
pollIntervalMs?: number;
|
|
6
|
+
heartbeatIntervalMs?: number;
|
|
7
|
+
heartbeatGraceMs?: number;
|
|
8
|
+
}
|
|
9
|
+
/** Lazily provisions sandbox sessions and keeps them alive while in use. */
|
|
10
|
+
export declare class LazySandbox {
|
|
11
|
+
private readonly apiUrl;
|
|
12
|
+
private readonly authToken;
|
|
13
|
+
private readonly getProjectId;
|
|
14
|
+
private readonly startupTimeoutMs;
|
|
15
|
+
private readonly pollIntervalMs;
|
|
16
|
+
private readonly heartbeatIntervalMs;
|
|
17
|
+
private readonly heartbeatGraceMs;
|
|
18
|
+
private endpoint;
|
|
19
|
+
private sessionId;
|
|
20
|
+
private sessionProjectId;
|
|
21
|
+
private ensurePromise;
|
|
22
|
+
private closePromise;
|
|
23
|
+
private heartbeatPromise;
|
|
24
|
+
private heartbeatTimer;
|
|
25
|
+
private lastHeartbeatAt;
|
|
26
|
+
constructor(options?: LazySandboxOptions);
|
|
27
|
+
ensure(): Promise<void>;
|
|
28
|
+
executeCommand(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
29
|
+
executeStream(command: string, options?: ExecOptions): AsyncGenerator<ExecStreamEvent>;
|
|
30
|
+
readFile(path: string): Promise<string>;
|
|
31
|
+
writeFiles(files: Array<{
|
|
32
|
+
path: string;
|
|
33
|
+
content: string;
|
|
34
|
+
}>): Promise<void>;
|
|
35
|
+
startCommandJob(command: string, options?: ExecOptions): Promise<CommandJob>;
|
|
36
|
+
getCommandJob(jobId: string): Promise<CommandJob>;
|
|
37
|
+
getCommandJobOutput(jobId: string): Promise<CommandJobOutput>;
|
|
38
|
+
listCommandJobs(): Promise<CommandJob[]>;
|
|
39
|
+
cancelCommandJob(jobId: string): Promise<CommandJob>;
|
|
40
|
+
heartbeat(force?: boolean): Promise<void>;
|
|
41
|
+
close(): Promise<void>;
|
|
42
|
+
get id(): string | null;
|
|
43
|
+
get url(): string | null;
|
|
44
|
+
get isActive(): boolean;
|
|
45
|
+
private bootstrapSession;
|
|
46
|
+
private resolveReadyEndpoint;
|
|
47
|
+
private touchSession;
|
|
48
|
+
private startHeartbeatLoop;
|
|
49
|
+
private stopHeartbeatLoop;
|
|
50
|
+
private deleteSession;
|
|
51
|
+
private requireEndpoint;
|
|
52
|
+
private resolveProjectId;
|
|
53
|
+
private resetSessionState;
|
|
54
|
+
private authHeaders;
|
|
55
|
+
private jsonHeaders;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=lazy-sandbox.d.ts.map
|
|
@@ -0,0 +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,EAEpB,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;CAC3B;AAaD,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;IAE1C,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;gBAEhB,OAAO,GAAE,kBAAuB;IAUtC,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;IAwCvF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAcvC,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;IAkB5E,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAgBjD,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAuB7D,eAAe,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAkBxC,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAiBpD,SAAS,CAAC,KAAK,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA6CvC,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;YA2BpB,YAAY;IAc1B,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,iBAAiB;YAMX,aAAa;IAO3B,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,WAAW;CAMpB"}
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import * as dntShim from "../../_dnt.shims.js";
|
|
2
|
+
import { REQUEST_ERROR } from "../errors/index.js";
|
|
3
|
+
import { resolveSandboxApiUrl, resolveSandboxAuthToken, waitForSandboxReady, } from "./sandbox.js";
|
|
4
|
+
const DEFAULT_STARTUP_TIMEOUT_MS = 180_000;
|
|
5
|
+
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
6
|
+
const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
7
|
+
const DEFAULT_HEARTBEAT_GRACE_MS = 5_000;
|
|
8
|
+
/** Lazily provisions sandbox sessions and keeps them alive while in use. */
|
|
9
|
+
export class LazySandbox {
|
|
10
|
+
apiUrl;
|
|
11
|
+
authToken;
|
|
12
|
+
getProjectId;
|
|
13
|
+
startupTimeoutMs;
|
|
14
|
+
pollIntervalMs;
|
|
15
|
+
heartbeatIntervalMs;
|
|
16
|
+
heartbeatGraceMs;
|
|
17
|
+
endpoint = null;
|
|
18
|
+
sessionId = null;
|
|
19
|
+
sessionProjectId = null;
|
|
20
|
+
ensurePromise = null;
|
|
21
|
+
closePromise = null;
|
|
22
|
+
heartbeatPromise = null;
|
|
23
|
+
heartbeatTimer = null;
|
|
24
|
+
lastHeartbeatAt = 0;
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
this.apiUrl = resolveSandboxApiUrl(options);
|
|
27
|
+
this.authToken = resolveSandboxAuthToken(options);
|
|
28
|
+
this.getProjectId = options.getProjectId ?? (() => options.projectId);
|
|
29
|
+
this.startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
|
|
30
|
+
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
31
|
+
this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
32
|
+
this.heartbeatGraceMs = options.heartbeatGraceMs ?? DEFAULT_HEARTBEAT_GRACE_MS;
|
|
33
|
+
}
|
|
34
|
+
async ensure() {
|
|
35
|
+
if (this.endpoint)
|
|
36
|
+
return;
|
|
37
|
+
if (this.ensurePromise) {
|
|
38
|
+
await this.ensurePromise;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const promise = this.bootstrapSession();
|
|
42
|
+
this.ensurePromise = promise;
|
|
43
|
+
try {
|
|
44
|
+
await promise;
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
if (this.ensurePromise === promise) {
|
|
48
|
+
this.ensurePromise = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async executeCommand(command, options) {
|
|
53
|
+
let stdout = "";
|
|
54
|
+
let stderr = "";
|
|
55
|
+
let exitCode = 1;
|
|
56
|
+
for await (const event of this.executeStream(command, options)) {
|
|
57
|
+
switch (event.type) {
|
|
58
|
+
case "stdout":
|
|
59
|
+
stdout += event.data ?? "";
|
|
60
|
+
break;
|
|
61
|
+
case "stderr":
|
|
62
|
+
stderr += event.data ?? "";
|
|
63
|
+
break;
|
|
64
|
+
case "exit":
|
|
65
|
+
exitCode = event.exitCode ?? 1;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { stdout, stderr, exitCode };
|
|
70
|
+
}
|
|
71
|
+
async *executeStream(command, options) {
|
|
72
|
+
await this.touchSession();
|
|
73
|
+
const res = await fetch(`${this.requireEndpoint()}/exec`, {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: this.jsonHeaders(),
|
|
76
|
+
body: JSON.stringify({ command, ...options }),
|
|
77
|
+
});
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
throw REQUEST_ERROR.create({ detail: `Exec failed: ${res.status} ${await res.text()}` });
|
|
80
|
+
}
|
|
81
|
+
if (!res.body) {
|
|
82
|
+
throw new Error("Exec response has no body");
|
|
83
|
+
}
|
|
84
|
+
const reader = res.body.getReader();
|
|
85
|
+
const decoder = new TextDecoder();
|
|
86
|
+
let buffer = "";
|
|
87
|
+
while (true) {
|
|
88
|
+
const { done, value } = await reader.read();
|
|
89
|
+
if (done)
|
|
90
|
+
break;
|
|
91
|
+
buffer += decoder.decode(value, { stream: true });
|
|
92
|
+
const lines = buffer.split("\n");
|
|
93
|
+
buffer = lines.pop() ?? "";
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
if (!line.trim())
|
|
96
|
+
continue;
|
|
97
|
+
yield JSON.parse(line);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (buffer.trim()) {
|
|
101
|
+
yield JSON.parse(buffer);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async readFile(path) {
|
|
105
|
+
await this.touchSession();
|
|
106
|
+
const res = await fetch(`${this.requireEndpoint()}/file?path=${encodeURIComponent(path)}`, {
|
|
107
|
+
headers: this.authHeaders(),
|
|
108
|
+
});
|
|
109
|
+
if (!res.ok) {
|
|
110
|
+
throw REQUEST_ERROR.create({ detail: `Read file failed: ${res.status} ${await res.text()}` });
|
|
111
|
+
}
|
|
112
|
+
return await res.text();
|
|
113
|
+
}
|
|
114
|
+
async writeFiles(files) {
|
|
115
|
+
await this.touchSession();
|
|
116
|
+
const res = await fetch(`${this.requireEndpoint()}/files`, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: this.jsonHeaders(),
|
|
119
|
+
body: JSON.stringify({ files }),
|
|
120
|
+
});
|
|
121
|
+
if (!res.ok) {
|
|
122
|
+
throw REQUEST_ERROR.create({
|
|
123
|
+
detail: `Write files failed: ${res.status} ${await res.text()}`,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async startCommandJob(command, options) {
|
|
128
|
+
await this.touchSession();
|
|
129
|
+
const res = await fetch(`${this.requireEndpoint()}/exec/jobs`, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: this.jsonHeaders(),
|
|
132
|
+
body: JSON.stringify({ command, ...options }),
|
|
133
|
+
});
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
throw REQUEST_ERROR.create({
|
|
136
|
+
detail: `Start command job failed: ${res.status} ${await res.text()}`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return mapCommandJob(await res.json());
|
|
140
|
+
}
|
|
141
|
+
async getCommandJob(jobId) {
|
|
142
|
+
await this.ensure();
|
|
143
|
+
const res = await fetch(`${this.requireEndpoint()}/exec/jobs/${jobId}`, {
|
|
144
|
+
headers: this.authHeaders(),
|
|
145
|
+
});
|
|
146
|
+
if (!res.ok) {
|
|
147
|
+
throw REQUEST_ERROR.create({
|
|
148
|
+
detail: `Get command job failed: ${res.status} ${await res.text()}`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return mapCommandJob(await res.json());
|
|
152
|
+
}
|
|
153
|
+
async getCommandJobOutput(jobId) {
|
|
154
|
+
await this.ensure();
|
|
155
|
+
const res = await fetch(`${this.requireEndpoint()}/exec/jobs/${jobId}/output`, {
|
|
156
|
+
headers: this.authHeaders(),
|
|
157
|
+
});
|
|
158
|
+
if (!res.ok) {
|
|
159
|
+
throw REQUEST_ERROR.create({
|
|
160
|
+
detail: `Get command job output failed: ${res.status} ${await res.text()}`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const json = await res.json();
|
|
164
|
+
return {
|
|
165
|
+
...mapCommandJob(json),
|
|
166
|
+
stdout: json.stdout,
|
|
167
|
+
stderr: json.stderr,
|
|
168
|
+
stdoutTruncated: json.stdout_truncated,
|
|
169
|
+
stderrTruncated: json.stderr_truncated,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
async listCommandJobs() {
|
|
173
|
+
await this.ensure();
|
|
174
|
+
const res = await fetch(`${this.requireEndpoint()}/exec/jobs`, {
|
|
175
|
+
headers: this.authHeaders(),
|
|
176
|
+
});
|
|
177
|
+
if (!res.ok) {
|
|
178
|
+
throw REQUEST_ERROR.create({
|
|
179
|
+
detail: `List command jobs failed: ${res.status} ${await res.text()}`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const json = await res.json();
|
|
183
|
+
const jobs = Array.isArray(json) ? json : (json.jobs ?? []);
|
|
184
|
+
return jobs.map((job) => mapCommandJob(job));
|
|
185
|
+
}
|
|
186
|
+
async cancelCommandJob(jobId) {
|
|
187
|
+
await this.ensure();
|
|
188
|
+
const res = await fetch(`${this.requireEndpoint()}/exec/jobs/${jobId}/cancel`, {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers: this.authHeaders(),
|
|
191
|
+
});
|
|
192
|
+
if (!res.ok) {
|
|
193
|
+
throw REQUEST_ERROR.create({
|
|
194
|
+
detail: `Cancel command job failed: ${res.status} ${await res.text()}`,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return mapCommandJob(await res.json());
|
|
198
|
+
}
|
|
199
|
+
async heartbeat(force = false) {
|
|
200
|
+
const currentSessionId = this.sessionId;
|
|
201
|
+
if (!currentSessionId)
|
|
202
|
+
return;
|
|
203
|
+
if (this.heartbeatPromise) {
|
|
204
|
+
await this.heartbeatPromise;
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!force && this.lastHeartbeatAt > 0 &&
|
|
208
|
+
Date.now() - this.lastHeartbeatAt < this.heartbeatGraceMs) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const promise = (async () => {
|
|
212
|
+
const res = await fetch(`${this.apiUrl}/sandbox-sessions/${currentSessionId}/heartbeat`, {
|
|
213
|
+
method: "POST",
|
|
214
|
+
headers: this.authHeaders(),
|
|
215
|
+
});
|
|
216
|
+
if (!res.ok) {
|
|
217
|
+
if (this.sessionId === currentSessionId) {
|
|
218
|
+
await this.deleteSession(currentSessionId);
|
|
219
|
+
this.resetSessionState(currentSessionId);
|
|
220
|
+
}
|
|
221
|
+
throw new Error(`Sandbox heartbeat failed: ${res.status} ${await res.text()}`);
|
|
222
|
+
}
|
|
223
|
+
this.lastHeartbeatAt = Date.now();
|
|
224
|
+
})();
|
|
225
|
+
this.heartbeatPromise = promise;
|
|
226
|
+
try {
|
|
227
|
+
await promise;
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
if (this.heartbeatPromise === promise) {
|
|
231
|
+
this.heartbeatPromise = null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async close() {
|
|
236
|
+
if (this.closePromise) {
|
|
237
|
+
await this.closePromise;
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const promise = (async () => {
|
|
241
|
+
if (this.ensurePromise) {
|
|
242
|
+
try {
|
|
243
|
+
await this.ensurePromise;
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// startup failure already handled by the caller path
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const currentSessionId = this.sessionId;
|
|
250
|
+
if (!currentSessionId) {
|
|
251
|
+
this.resetSessionState();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
await this.deleteSession(currentSessionId);
|
|
255
|
+
this.resetSessionState(currentSessionId);
|
|
256
|
+
})();
|
|
257
|
+
this.closePromise = promise;
|
|
258
|
+
try {
|
|
259
|
+
await promise;
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
if (this.closePromise === promise) {
|
|
263
|
+
this.closePromise = null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
get id() {
|
|
268
|
+
return this.sessionId;
|
|
269
|
+
}
|
|
270
|
+
get url() {
|
|
271
|
+
return this.endpoint;
|
|
272
|
+
}
|
|
273
|
+
get isActive() {
|
|
274
|
+
return this.endpoint !== null;
|
|
275
|
+
}
|
|
276
|
+
async bootstrapSession() {
|
|
277
|
+
const projectId = this.resolveProjectId();
|
|
278
|
+
const res = await fetch(`${this.apiUrl}/sandbox-sessions`, {
|
|
279
|
+
method: "POST",
|
|
280
|
+
headers: this.jsonHeaders(),
|
|
281
|
+
body: JSON.stringify(projectId ? { project_id: projectId } : {}),
|
|
282
|
+
});
|
|
283
|
+
if (!res.ok) {
|
|
284
|
+
throw REQUEST_ERROR.create({
|
|
285
|
+
detail: `Failed to create sandbox: ${res.status} ${await res.text()}`,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
const session = await res.json();
|
|
289
|
+
this.sessionId = session.id;
|
|
290
|
+
this.sessionProjectId = projectId;
|
|
291
|
+
try {
|
|
292
|
+
const endpoint = await this.resolveReadyEndpoint(session);
|
|
293
|
+
this.endpoint = endpoint;
|
|
294
|
+
await this.heartbeat(true);
|
|
295
|
+
this.startHeartbeatLoop();
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
const currentSessionId = this.sessionId;
|
|
299
|
+
if (currentSessionId) {
|
|
300
|
+
await this.deleteSession(currentSessionId);
|
|
301
|
+
}
|
|
302
|
+
this.resetSessionState(currentSessionId ?? undefined);
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async resolveReadyEndpoint(session) {
|
|
307
|
+
if (session.status === "running") {
|
|
308
|
+
return session.endpoint;
|
|
309
|
+
}
|
|
310
|
+
await waitForSandboxReady({
|
|
311
|
+
apiUrl: this.apiUrl,
|
|
312
|
+
id: session.id,
|
|
313
|
+
authToken: this.authToken,
|
|
314
|
+
maxWaitMs: this.startupTimeoutMs,
|
|
315
|
+
pollIntervalMs: this.pollIntervalMs,
|
|
316
|
+
});
|
|
317
|
+
const res = await fetch(`${this.apiUrl}/sandbox-sessions/${session.id}`, {
|
|
318
|
+
headers: this.authHeaders(),
|
|
319
|
+
});
|
|
320
|
+
if (!res.ok) {
|
|
321
|
+
throw REQUEST_ERROR.create({
|
|
322
|
+
detail: `Failed to get sandbox: ${res.status} ${await res.text()}`,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
const nextSession = await res.json();
|
|
326
|
+
return nextSession.endpoint;
|
|
327
|
+
}
|
|
328
|
+
async touchSession() {
|
|
329
|
+
const projectId = this.resolveProjectId();
|
|
330
|
+
if (this.endpoint && this.sessionProjectId !== projectId) {
|
|
331
|
+
const currentSessionId = this.sessionId;
|
|
332
|
+
if (currentSessionId) {
|
|
333
|
+
await this.deleteSession(currentSessionId);
|
|
334
|
+
}
|
|
335
|
+
this.resetSessionState(currentSessionId ?? undefined);
|
|
336
|
+
}
|
|
337
|
+
await this.ensure();
|
|
338
|
+
await this.heartbeat();
|
|
339
|
+
}
|
|
340
|
+
startHeartbeatLoop() {
|
|
341
|
+
if (!this.sessionId || this.heartbeatTimer)
|
|
342
|
+
return;
|
|
343
|
+
this.heartbeatTimer = dntShim.setInterval(() => {
|
|
344
|
+
void this.heartbeat().catch(() => {
|
|
345
|
+
// next operation will reprovision
|
|
346
|
+
});
|
|
347
|
+
}, this.heartbeatIntervalMs);
|
|
348
|
+
}
|
|
349
|
+
stopHeartbeatLoop() {
|
|
350
|
+
if (!this.heartbeatTimer)
|
|
351
|
+
return;
|
|
352
|
+
clearInterval(this.heartbeatTimer);
|
|
353
|
+
this.heartbeatTimer = null;
|
|
354
|
+
}
|
|
355
|
+
async deleteSession(sessionId) {
|
|
356
|
+
await fetch(`${this.apiUrl}/sandbox-sessions/${sessionId}`, {
|
|
357
|
+
method: "DELETE",
|
|
358
|
+
headers: this.authHeaders(),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
requireEndpoint() {
|
|
362
|
+
if (!this.endpoint) {
|
|
363
|
+
throw new Error("Sandbox endpoint unavailable");
|
|
364
|
+
}
|
|
365
|
+
return this.endpoint;
|
|
366
|
+
}
|
|
367
|
+
resolveProjectId() {
|
|
368
|
+
return this.getProjectId() ?? null;
|
|
369
|
+
}
|
|
370
|
+
resetSessionState(sessionId) {
|
|
371
|
+
if (!sessionId || this.sessionId === sessionId) {
|
|
372
|
+
this.stopHeartbeatLoop();
|
|
373
|
+
this.endpoint = null;
|
|
374
|
+
this.sessionId = null;
|
|
375
|
+
this.sessionProjectId = null;
|
|
376
|
+
this.heartbeatPromise = null;
|
|
377
|
+
this.lastHeartbeatAt = 0;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
authHeaders() {
|
|
381
|
+
return { Authorization: `Bearer ${this.authToken}` };
|
|
382
|
+
}
|
|
383
|
+
jsonHeaders() {
|
|
384
|
+
return {
|
|
385
|
+
...this.authHeaders(),
|
|
386
|
+
"Content-Type": "application/json",
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function mapCommandJob(json) {
|
|
391
|
+
return {
|
|
392
|
+
id: json.id,
|
|
393
|
+
status: json.status,
|
|
394
|
+
exitCode: json.exit_code,
|
|
395
|
+
signal: json.signal,
|
|
396
|
+
startedAt: json.started_at,
|
|
397
|
+
finishedAt: json.finished_at,
|
|
398
|
+
heartbeatStatus: json.heartbeat_status,
|
|
399
|
+
lastHeartbeatAt: json.last_heartbeat_at,
|
|
400
|
+
lastHeartbeatError: json.last_heartbeat_error,
|
|
401
|
+
heartbeatFailureCount: json.heartbeat_failure_count,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LazySandbox, type LazySandboxOptions } from "./lazy-sandbox.js";
|
|
1
2
|
/** Options for command execution: working directory, timeout, and environment variables. */
|
|
2
3
|
export interface ExecOptions {
|
|
3
4
|
/** Working directory for the command. */
|
|
@@ -16,6 +17,8 @@ export interface SandboxOptions {
|
|
|
16
17
|
/** Optional project context for project-billed / project-scoped sandbox sessions. */
|
|
17
18
|
projectId?: string;
|
|
18
19
|
}
|
|
20
|
+
export declare function resolveSandboxApiUrl(options?: SandboxOptions): string;
|
|
21
|
+
export declare function resolveSandboxAuthToken(options?: SandboxOptions): string;
|
|
19
22
|
/** Result of a command execution: stdout, stderr, and exit code. */
|
|
20
23
|
export interface ExecResult {
|
|
21
24
|
stdout: string;
|
|
@@ -91,6 +94,8 @@ export declare class Sandbox {
|
|
|
91
94
|
/** List sandbox sessions with optional pagination. */
|
|
92
95
|
static list(options?: SandboxListOptions): Promise<SandboxListResult>;
|
|
93
96
|
private static waitForReady;
|
|
97
|
+
/** Create a lazily-provisioned sandbox session with automatic heartbeats. */
|
|
98
|
+
static createLazy(options?: LazySandboxOptions): LazySandbox;
|
|
94
99
|
/** Execute a bash command in the sandbox and return buffered result. */
|
|
95
100
|
executeCommand(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
96
101
|
/** Execute a bash command with streaming output (NDJSON). */
|
|
@@ -122,4 +127,11 @@ export declare class Sandbox {
|
|
|
122
127
|
/** Get the sandbox endpoint URL. */
|
|
123
128
|
get url(): string;
|
|
124
129
|
}
|
|
130
|
+
export declare function waitForSandboxReady(input: {
|
|
131
|
+
apiUrl: string;
|
|
132
|
+
id: string;
|
|
133
|
+
authToken: string;
|
|
134
|
+
maxWaitMs?: number;
|
|
135
|
+
pollIntervalMs?: number;
|
|
136
|
+
}): Promise<void>;
|
|
125
137
|
//# sourceMappingURL=sandbox.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../src/src/sandbox/sandbox.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../src/src/sandbox/sandbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,21 @@ import * as dntShim from "../../_dnt.shims.js";
|
|
|
10
10
|
import { createError, INITIALIZATION_ERROR, REQUEST_ERROR, TIMEOUT_ERROR, toError, } from "../errors/index.js";
|
|
11
11
|
import { getVeryfrontCloudAuthToken } from "../platform/cloud/resolver.js";
|
|
12
12
|
import { getHostEnv } from "../platform/compat/process.js";
|
|
13
|
+
import { LazySandbox } from "./lazy-sandbox.js";
|
|
14
|
+
export function resolveSandboxApiUrl(options = {}) {
|
|
15
|
+
return options.apiUrl ||
|
|
16
|
+
getHostEnv("VERYFRONT_API_URL") ||
|
|
17
|
+
"https://api.veryfront.com";
|
|
18
|
+
}
|
|
19
|
+
export function resolveSandboxAuthToken(options = {}) {
|
|
20
|
+
const authToken = options.authToken?.trim() || getVeryfrontCloudAuthToken();
|
|
21
|
+
if (authToken)
|
|
22
|
+
return authToken;
|
|
23
|
+
throw toError(createError({
|
|
24
|
+
type: "config",
|
|
25
|
+
message: "Sandbox auth not configured. Set VERYFRONT_API_TOKEN, provide request-scoped Veryfront credentials, or pass authToken explicitly.",
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
13
28
|
/** Client for isolated ephemeral compute environments with command execution and file I/O. */
|
|
14
29
|
export class Sandbox {
|
|
15
30
|
endpoint;
|
|
@@ -23,18 +38,10 @@ export class Sandbox {
|
|
|
23
38
|
this.apiUrl = apiUrl;
|
|
24
39
|
}
|
|
25
40
|
static resolveApiUrl(options = {}) {
|
|
26
|
-
return options
|
|
27
|
-
getHostEnv("VERYFRONT_API_URL") ||
|
|
28
|
-
"https://api.veryfront.com";
|
|
41
|
+
return resolveSandboxApiUrl(options);
|
|
29
42
|
}
|
|
30
43
|
static resolveAuthToken(options = {}) {
|
|
31
|
-
|
|
32
|
-
if (authToken)
|
|
33
|
-
return authToken;
|
|
34
|
-
throw toError(createError({
|
|
35
|
-
type: "config",
|
|
36
|
-
message: "Sandbox auth not configured. Set VERYFRONT_API_TOKEN, provide request-scoped Veryfront credentials, or pass authToken explicitly.",
|
|
37
|
-
}));
|
|
44
|
+
return resolveSandboxAuthToken(options);
|
|
38
45
|
}
|
|
39
46
|
/** Create a new sandbox session. Claims a warm pod or creates a new one. */
|
|
40
47
|
static async create(options = {}) {
|
|
@@ -112,25 +119,11 @@ export class Sandbox {
|
|
|
112
119
|
};
|
|
113
120
|
}
|
|
114
121
|
static async waitForReady(apiUrl, id, authToken, maxWaitMs = 60_000, pollIntervalMs = 2_000) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
headers: { Authorization: `Bearer ${authToken}` },
|
|
121
|
-
});
|
|
122
|
-
if (res.ok) {
|
|
123
|
-
const data = await res.json();
|
|
124
|
-
if (data.status === "running")
|
|
125
|
-
return;
|
|
126
|
-
if (data.status === "error" || data.status === "deleting") {
|
|
127
|
-
throw INITIALIZATION_ERROR.create({
|
|
128
|
-
detail: `Sandbox failed to start: status=${data.status}`,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
throw TIMEOUT_ERROR.create({ detail: "Sandbox did not become ready within timeout" });
|
|
122
|
+
await waitForSandboxReady({ apiUrl, id, authToken, maxWaitMs, pollIntervalMs });
|
|
123
|
+
}
|
|
124
|
+
/** Create a lazily-provisioned sandbox session with automatic heartbeats. */
|
|
125
|
+
static createLazy(options = {}) {
|
|
126
|
+
return new LazySandbox(options);
|
|
134
127
|
}
|
|
135
128
|
/** Execute a bash command in the sandbox and return buffered result. */
|
|
136
129
|
async executeCommand(command, options) {
|
|
@@ -305,17 +298,27 @@ export class Sandbox {
|
|
|
305
298
|
}
|
|
306
299
|
/** Send a heartbeat to prevent idle timeout. */
|
|
307
300
|
async heartbeat() {
|
|
308
|
-
await fetch(`${this.apiUrl}/sandbox-sessions/${this.sessionId}/heartbeat`, {
|
|
301
|
+
const res = await fetch(`${this.apiUrl}/sandbox-sessions/${this.sessionId}/heartbeat`, {
|
|
309
302
|
method: "POST",
|
|
310
303
|
headers: { Authorization: `Bearer ${this.authToken}` },
|
|
311
304
|
});
|
|
305
|
+
if (!res.ok) {
|
|
306
|
+
throw REQUEST_ERROR.create({
|
|
307
|
+
detail: `Sandbox heartbeat failed: ${res.status} ${await res.text()}`,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
312
310
|
}
|
|
313
311
|
/** Close the sandbox session and mark for deletion. */
|
|
314
312
|
async close() {
|
|
315
|
-
await fetch(`${this.apiUrl}/sandbox-sessions/${this.sessionId}`, {
|
|
313
|
+
const res = await fetch(`${this.apiUrl}/sandbox-sessions/${this.sessionId}`, {
|
|
316
314
|
method: "DELETE",
|
|
317
315
|
headers: { Authorization: `Bearer ${this.authToken}` },
|
|
318
316
|
});
|
|
317
|
+
if (!res.ok) {
|
|
318
|
+
throw REQUEST_ERROR.create({
|
|
319
|
+
detail: `Close sandbox failed: ${res.status} ${await res.text()}`,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
319
322
|
}
|
|
320
323
|
/** Get the session ID. */
|
|
321
324
|
get id() {
|
|
@@ -326,3 +329,26 @@ export class Sandbox {
|
|
|
326
329
|
return this.endpoint;
|
|
327
330
|
}
|
|
328
331
|
}
|
|
332
|
+
export async function waitForSandboxReady(input) {
|
|
333
|
+
const maxWaitMs = input.maxWaitMs ?? 60_000;
|
|
334
|
+
const pollIntervalMs = input.pollIntervalMs ?? 2_000;
|
|
335
|
+
const start = Date.now();
|
|
336
|
+
while (Date.now() - start < maxWaitMs) {
|
|
337
|
+
await new Promise((resolve) => dntShim.setTimeout(resolve, pollIntervalMs));
|
|
338
|
+
const res = await fetch(`${input.apiUrl}/sandbox-sessions/${input.id}`, {
|
|
339
|
+
headers: { Authorization: `Bearer ${input.authToken}` },
|
|
340
|
+
});
|
|
341
|
+
if (!res.ok) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const data = await res.json();
|
|
345
|
+
if (data.status === "running")
|
|
346
|
+
return;
|
|
347
|
+
if (data.status === "error" || data.status === "deleting") {
|
|
348
|
+
throw INITIALIZATION_ERROR.create({
|
|
349
|
+
detail: `Sandbox failed to start: status=${data.status}`,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
throw TIMEOUT_ERROR.create({ detail: "Sandbox did not become ready within timeout" });
|
|
354
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.
|
|
1
|
+
export declare const VERSION = "0.1.277";
|
|
2
2
|
//# sourceMappingURL=version-constant.d.ts.map
|