@vercel/sandbox 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/api-client/api-client.js +7 -0
- package/dist/api-client/api-client.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/get-credentials.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -1
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-test.log +0 -24
- package/.turbo/turbo-typecheck.log +0 -4
- package/CHANGELOG.md +0 -277
- package/__mocks__/picocolors.ts +0 -13
- package/scripts/inject-version.ts +0 -11
- package/src/api-client/api-client.test.ts +0 -228
- package/src/api-client/api-client.ts +0 -592
- package/src/api-client/api-error.ts +0 -46
- package/src/api-client/base-client.ts +0 -171
- package/src/api-client/file-writer.ts +0 -90
- package/src/api-client/index.ts +0 -2
- package/src/api-client/validators.ts +0 -146
- package/src/api-client/with-retry.ts +0 -131
- package/src/auth/api.ts +0 -31
- package/src/auth/error.ts +0 -8
- package/src/auth/file.ts +0 -69
- package/src/auth/index.ts +0 -9
- package/src/auth/infer-scope.test.ts +0 -178
- package/src/auth/linked-project.test.ts +0 -86
- package/src/auth/linked-project.ts +0 -40
- package/src/auth/oauth.ts +0 -333
- package/src/auth/poll-for-token.ts +0 -89
- package/src/auth/project.ts +0 -92
- package/src/auth/zod.ts +0 -16
- package/src/command.test.ts +0 -103
- package/src/command.ts +0 -287
- package/src/constants.ts +0 -1
- package/src/index.ts +0 -4
- package/src/sandbox.test.ts +0 -171
- package/src/sandbox.ts +0 -677
- package/src/snapshot.ts +0 -110
- package/src/utils/array.ts +0 -15
- package/src/utils/consume-readable.ts +0 -12
- package/src/utils/decode-base64-url.ts +0 -14
- package/src/utils/dev-credentials.test.ts +0 -217
- package/src/utils/dev-credentials.ts +0 -196
- package/src/utils/get-credentials.test.ts +0 -20
- package/src/utils/get-credentials.ts +0 -183
- package/src/utils/jwt-expiry.test.ts +0 -125
- package/src/utils/jwt-expiry.ts +0 -105
- package/src/utils/log.ts +0 -20
- package/src/utils/normalizePath.test.ts +0 -114
- package/src/utils/normalizePath.ts +0 -33
- package/src/utils/resolveSignal.ts +0 -24
- package/src/utils/types.test.js +0 -7
- package/src/utils/types.ts +0 -23
- package/src/version.ts +0 -2
- package/test-utils/mock-response.ts +0 -12
- package/tsconfig.json +0 -16
- package/turbo.json +0 -9
- package/typedoc.json +0 -13
- package/vercel.json +0 -9
- package/vitest.config.ts +0 -9
- package/vitest.setup.ts +0 -4
|
@@ -1,592 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BaseClient,
|
|
3
|
-
parseOrThrow,
|
|
4
|
-
type Parsed,
|
|
5
|
-
type RequestParams,
|
|
6
|
-
} from "./base-client";
|
|
7
|
-
import {
|
|
8
|
-
CommandFinishedData,
|
|
9
|
-
SandboxAndRoutesResponse,
|
|
10
|
-
SandboxResponse,
|
|
11
|
-
CommandResponse,
|
|
12
|
-
CommandFinishedResponse,
|
|
13
|
-
EmptyResponse,
|
|
14
|
-
LogLine,
|
|
15
|
-
LogLineStdout,
|
|
16
|
-
LogLineStderr,
|
|
17
|
-
SandboxesResponse,
|
|
18
|
-
ExtendTimeoutResponse,
|
|
19
|
-
SnapshotResponse,
|
|
20
|
-
CreateSnapshotResponse,
|
|
21
|
-
type CommandData,
|
|
22
|
-
} from "./validators";
|
|
23
|
-
import { APIError, StreamError } from "./api-error";
|
|
24
|
-
import { FileWriter } from "./file-writer";
|
|
25
|
-
import { VERSION } from "../version";
|
|
26
|
-
import { consumeReadable } from "../utils/consume-readable";
|
|
27
|
-
import { z } from "zod";
|
|
28
|
-
import jsonlines from "jsonlines";
|
|
29
|
-
import os from "os";
|
|
30
|
-
import { Readable } from "stream";
|
|
31
|
-
import { normalizePath } from "../utils/normalizePath";
|
|
32
|
-
import { JwtExpiry } from "../utils/jwt-expiry";
|
|
33
|
-
import { getPrivateParams, WithPrivate } from "../utils/types";
|
|
34
|
-
import { RUNTIMES } from "../constants";
|
|
35
|
-
|
|
36
|
-
export interface WithFetchOptions {
|
|
37
|
-
fetch?: typeof globalThis.fetch;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export class APIClient extends BaseClient {
|
|
41
|
-
private teamId: string;
|
|
42
|
-
private tokenExpiry: JwtExpiry | null;
|
|
43
|
-
|
|
44
|
-
constructor(params: {
|
|
45
|
-
baseUrl?: string;
|
|
46
|
-
teamId: string;
|
|
47
|
-
token: string;
|
|
48
|
-
fetch?: typeof globalThis.fetch;
|
|
49
|
-
}) {
|
|
50
|
-
super({
|
|
51
|
-
baseUrl: params.baseUrl ?? "https://vercel.com/api",
|
|
52
|
-
token: params.token,
|
|
53
|
-
debug: false,
|
|
54
|
-
fetch: params.fetch,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
this.teamId = params.teamId;
|
|
58
|
-
this.tokenExpiry = JwtExpiry.fromToken(params.token);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
private async ensureValidToken(): Promise<void> {
|
|
62
|
-
if (!this.tokenExpiry) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const newExpiry = await this.tokenExpiry.tryRefresh();
|
|
67
|
-
if (!newExpiry) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
this.tokenExpiry = newExpiry;
|
|
72
|
-
this.token = this.tokenExpiry.token;
|
|
73
|
-
if (this.tokenExpiry.payload) {
|
|
74
|
-
this.teamId = this.tokenExpiry.payload?.owner_id;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
protected async request(path: string, params?: RequestParams) {
|
|
79
|
-
await this.ensureValidToken();
|
|
80
|
-
|
|
81
|
-
return super.request(path, {
|
|
82
|
-
...params,
|
|
83
|
-
query: { teamId: this.teamId, ...params?.query },
|
|
84
|
-
headers: {
|
|
85
|
-
"content-type": "application/json",
|
|
86
|
-
"user-agent": `vercel/sandbox/${VERSION} (Node.js/${process.version}; ${os.platform()}/${os.arch()})`,
|
|
87
|
-
...params?.headers,
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async getSandbox(
|
|
93
|
-
params: WithPrivate<{ sandboxId: string; signal?: AbortSignal }>,
|
|
94
|
-
) {
|
|
95
|
-
const privateParams = getPrivateParams(params);
|
|
96
|
-
let querystring = new URLSearchParams(privateParams).toString();
|
|
97
|
-
querystring = querystring ? `?${querystring}` : "";
|
|
98
|
-
return parseOrThrow(
|
|
99
|
-
SandboxAndRoutesResponse,
|
|
100
|
-
await this.request(`/v1/sandboxes/${params.sandboxId}${querystring}`, {
|
|
101
|
-
signal: params.signal,
|
|
102
|
-
}),
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async createSandbox(
|
|
107
|
-
params: WithPrivate<{
|
|
108
|
-
ports?: number[];
|
|
109
|
-
projectId: string;
|
|
110
|
-
source?:
|
|
111
|
-
| {
|
|
112
|
-
type: "git";
|
|
113
|
-
url: string;
|
|
114
|
-
depth?: number;
|
|
115
|
-
revision?: string;
|
|
116
|
-
username?: string;
|
|
117
|
-
password?: string;
|
|
118
|
-
}
|
|
119
|
-
| { type: "tarball"; url: string }
|
|
120
|
-
| { type: "snapshot"; snapshotId: string };
|
|
121
|
-
timeout?: number;
|
|
122
|
-
resources?: { vcpus: number };
|
|
123
|
-
runtime?: RUNTIMES | (string & {});
|
|
124
|
-
signal?: AbortSignal;
|
|
125
|
-
}>,
|
|
126
|
-
) {
|
|
127
|
-
const privateParams = getPrivateParams(params);
|
|
128
|
-
return parseOrThrow(
|
|
129
|
-
SandboxAndRoutesResponse,
|
|
130
|
-
await this.request("/v1/sandboxes", {
|
|
131
|
-
method: "POST",
|
|
132
|
-
body: JSON.stringify({
|
|
133
|
-
projectId: params.projectId,
|
|
134
|
-
ports: params.ports,
|
|
135
|
-
source: params.source,
|
|
136
|
-
timeout: params.timeout,
|
|
137
|
-
resources: params.resources,
|
|
138
|
-
runtime: params.runtime,
|
|
139
|
-
...privateParams,
|
|
140
|
-
}),
|
|
141
|
-
signal: params.signal,
|
|
142
|
-
}),
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async runCommand(params: {
|
|
147
|
-
sandboxId: string;
|
|
148
|
-
cwd?: string;
|
|
149
|
-
command: string;
|
|
150
|
-
args: string[];
|
|
151
|
-
env: Record<string, string>;
|
|
152
|
-
sudo: boolean;
|
|
153
|
-
wait: true;
|
|
154
|
-
signal?: AbortSignal;
|
|
155
|
-
}): Promise<{ command: CommandData; finished: Promise<CommandFinishedData> }>;
|
|
156
|
-
async runCommand(params: {
|
|
157
|
-
sandboxId: string;
|
|
158
|
-
cwd?: string;
|
|
159
|
-
command: string;
|
|
160
|
-
args: string[];
|
|
161
|
-
env: Record<string, string>;
|
|
162
|
-
sudo: boolean;
|
|
163
|
-
wait?: false;
|
|
164
|
-
signal?: AbortSignal;
|
|
165
|
-
}): Promise<Parsed<z.infer<typeof CommandResponse>>>;
|
|
166
|
-
async runCommand(params: {
|
|
167
|
-
sandboxId: string;
|
|
168
|
-
cwd?: string;
|
|
169
|
-
command: string;
|
|
170
|
-
args: string[];
|
|
171
|
-
env: Record<string, string>;
|
|
172
|
-
sudo: boolean;
|
|
173
|
-
wait?: boolean;
|
|
174
|
-
signal?: AbortSignal;
|
|
175
|
-
}) {
|
|
176
|
-
if (params.wait) {
|
|
177
|
-
const response = await this.request(
|
|
178
|
-
`/v1/sandboxes/${params.sandboxId}/cmd`,
|
|
179
|
-
{
|
|
180
|
-
method: "POST",
|
|
181
|
-
body: JSON.stringify({
|
|
182
|
-
command: params.command,
|
|
183
|
-
args: params.args,
|
|
184
|
-
cwd: params.cwd,
|
|
185
|
-
env: params.env,
|
|
186
|
-
sudo: params.sudo,
|
|
187
|
-
wait: true,
|
|
188
|
-
}),
|
|
189
|
-
signal: params.signal,
|
|
190
|
-
},
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
if (response.headers.get("content-type") !== "application/x-ndjson") {
|
|
194
|
-
throw new APIError(response, {
|
|
195
|
-
message: "Expected a stream of command data",
|
|
196
|
-
sandboxId: params.sandboxId,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (response.body === null) {
|
|
201
|
-
throw new APIError(response, {
|
|
202
|
-
message: "No response body",
|
|
203
|
-
sandboxId: params.sandboxId,
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const jsonlinesStream = jsonlines.parse();
|
|
208
|
-
pipe(response.body, jsonlinesStream).catch((err) => {
|
|
209
|
-
console.error("Error piping command stream:", err);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
const iterator = jsonlinesStream[Symbol.asyncIterator]();
|
|
213
|
-
const commandChunk = await iterator.next();
|
|
214
|
-
const { command } = CommandResponse.parse(commandChunk.value);
|
|
215
|
-
|
|
216
|
-
const finished = (async () => {
|
|
217
|
-
const finishedChunk = await iterator.next();
|
|
218
|
-
const { command } = CommandFinishedResponse.parse(finishedChunk.value);
|
|
219
|
-
return command;
|
|
220
|
-
})();
|
|
221
|
-
|
|
222
|
-
return { command, finished };
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return parseOrThrow(
|
|
226
|
-
CommandResponse,
|
|
227
|
-
await this.request(`/v1/sandboxes/${params.sandboxId}/cmd`, {
|
|
228
|
-
method: "POST",
|
|
229
|
-
body: JSON.stringify({
|
|
230
|
-
command: params.command,
|
|
231
|
-
args: params.args,
|
|
232
|
-
cwd: params.cwd,
|
|
233
|
-
env: params.env,
|
|
234
|
-
sudo: params.sudo,
|
|
235
|
-
}),
|
|
236
|
-
signal: params.signal,
|
|
237
|
-
}),
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
async getCommand(params: {
|
|
242
|
-
sandboxId: string;
|
|
243
|
-
cmdId: string;
|
|
244
|
-
wait: true;
|
|
245
|
-
signal?: AbortSignal;
|
|
246
|
-
}): Promise<Parsed<z.infer<typeof CommandFinishedResponse>>>;
|
|
247
|
-
async getCommand(params: {
|
|
248
|
-
sandboxId: string;
|
|
249
|
-
cmdId: string;
|
|
250
|
-
wait?: boolean;
|
|
251
|
-
signal?: AbortSignal;
|
|
252
|
-
}): Promise<Parsed<z.infer<typeof CommandResponse>>>;
|
|
253
|
-
async getCommand(params: {
|
|
254
|
-
sandboxId: string;
|
|
255
|
-
cmdId: string;
|
|
256
|
-
wait?: boolean;
|
|
257
|
-
signal?: AbortSignal;
|
|
258
|
-
}) {
|
|
259
|
-
return params.wait
|
|
260
|
-
? parseOrThrow(
|
|
261
|
-
CommandFinishedResponse,
|
|
262
|
-
await this.request(
|
|
263
|
-
`/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}`,
|
|
264
|
-
{ signal: params.signal, query: { wait: "true" } },
|
|
265
|
-
),
|
|
266
|
-
)
|
|
267
|
-
: parseOrThrow(
|
|
268
|
-
CommandResponse,
|
|
269
|
-
await this.request(
|
|
270
|
-
`/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}`,
|
|
271
|
-
{ signal: params.signal },
|
|
272
|
-
),
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
async mkDir(params: {
|
|
277
|
-
sandboxId: string;
|
|
278
|
-
path: string;
|
|
279
|
-
cwd?: string;
|
|
280
|
-
signal?: AbortSignal;
|
|
281
|
-
}) {
|
|
282
|
-
return parseOrThrow(
|
|
283
|
-
EmptyResponse,
|
|
284
|
-
await this.request(`/v1/sandboxes/${params.sandboxId}/fs/mkdir`, {
|
|
285
|
-
method: "POST",
|
|
286
|
-
body: JSON.stringify({ path: params.path, cwd: params.cwd }),
|
|
287
|
-
signal: params.signal,
|
|
288
|
-
}),
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
getFileWriter(params: {
|
|
293
|
-
sandboxId: string;
|
|
294
|
-
extractDir: string;
|
|
295
|
-
signal?: AbortSignal;
|
|
296
|
-
}) {
|
|
297
|
-
const writer = new FileWriter();
|
|
298
|
-
return {
|
|
299
|
-
response: (async () => {
|
|
300
|
-
return this.request(`/v1/sandboxes/${params.sandboxId}/fs/write`, {
|
|
301
|
-
method: "POST",
|
|
302
|
-
headers: {
|
|
303
|
-
"content-type": "application/gzip",
|
|
304
|
-
"x-cwd": params.extractDir,
|
|
305
|
-
},
|
|
306
|
-
body: await consumeReadable(writer.readable),
|
|
307
|
-
signal: params.signal,
|
|
308
|
-
});
|
|
309
|
-
})(),
|
|
310
|
-
writer,
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
async listSandboxes(params: {
|
|
315
|
-
/**
|
|
316
|
-
* The ID or name of the project to which the sandboxes belong.
|
|
317
|
-
* @example "my-project"
|
|
318
|
-
*/
|
|
319
|
-
projectId: string;
|
|
320
|
-
/**
|
|
321
|
-
* Maximum number of sandboxes to list from a request.
|
|
322
|
-
* @example 10
|
|
323
|
-
*/
|
|
324
|
-
limit?: number;
|
|
325
|
-
/**
|
|
326
|
-
* Get sandboxes created after this JavaScript timestamp.
|
|
327
|
-
* @example 1540095775941
|
|
328
|
-
*/
|
|
329
|
-
since?: number | Date;
|
|
330
|
-
/**
|
|
331
|
-
* Get sandboxes created before this JavaScript timestamp.
|
|
332
|
-
* @example 1540095775951
|
|
333
|
-
*/
|
|
334
|
-
until?: number | Date;
|
|
335
|
-
signal?: AbortSignal;
|
|
336
|
-
}) {
|
|
337
|
-
return parseOrThrow(
|
|
338
|
-
SandboxesResponse,
|
|
339
|
-
await this.request(`/v1/sandboxes`, {
|
|
340
|
-
query: {
|
|
341
|
-
project: params.projectId,
|
|
342
|
-
limit: params.limit,
|
|
343
|
-
since:
|
|
344
|
-
typeof params.since === "number"
|
|
345
|
-
? params.since
|
|
346
|
-
: params.since?.getTime(),
|
|
347
|
-
until:
|
|
348
|
-
typeof params.until === "number"
|
|
349
|
-
? params.until
|
|
350
|
-
: params.until?.getTime(),
|
|
351
|
-
},
|
|
352
|
-
method: "GET",
|
|
353
|
-
signal: params.signal,
|
|
354
|
-
}),
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
async writeFiles(params: {
|
|
359
|
-
sandboxId: string;
|
|
360
|
-
cwd: string;
|
|
361
|
-
files: { path: string; content: Buffer }[];
|
|
362
|
-
extractDir: string;
|
|
363
|
-
signal?: AbortSignal;
|
|
364
|
-
}) {
|
|
365
|
-
const { writer, response } = this.getFileWriter({
|
|
366
|
-
sandboxId: params.sandboxId,
|
|
367
|
-
extractDir: params.extractDir,
|
|
368
|
-
signal: params.signal,
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
for (const file of params.files) {
|
|
372
|
-
await writer.addFile({
|
|
373
|
-
name: normalizePath({
|
|
374
|
-
filePath: file.path,
|
|
375
|
-
extractDir: params.extractDir,
|
|
376
|
-
cwd: params.cwd,
|
|
377
|
-
}),
|
|
378
|
-
content: file.content,
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
writer.end();
|
|
383
|
-
await parseOrThrow(EmptyResponse, await response);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
async readFile(params: {
|
|
387
|
-
sandboxId: string;
|
|
388
|
-
path: string;
|
|
389
|
-
cwd?: string;
|
|
390
|
-
signal?: AbortSignal;
|
|
391
|
-
}): Promise<Readable | null> {
|
|
392
|
-
const response = await this.request(
|
|
393
|
-
`/v1/sandboxes/${params.sandboxId}/fs/read`,
|
|
394
|
-
{
|
|
395
|
-
method: "POST",
|
|
396
|
-
body: JSON.stringify({ path: params.path, cwd: params.cwd }),
|
|
397
|
-
signal: params.signal,
|
|
398
|
-
},
|
|
399
|
-
);
|
|
400
|
-
|
|
401
|
-
if (response.status === 404) {
|
|
402
|
-
return null;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if (response.body === null) {
|
|
406
|
-
return null;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return Readable.fromWeb(response.body);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
async killCommand(params: {
|
|
413
|
-
sandboxId: string;
|
|
414
|
-
commandId: string;
|
|
415
|
-
signal: number;
|
|
416
|
-
abortSignal?: AbortSignal;
|
|
417
|
-
}) {
|
|
418
|
-
return parseOrThrow(
|
|
419
|
-
CommandResponse,
|
|
420
|
-
await this.request(
|
|
421
|
-
`/v1/sandboxes/${params.sandboxId}/${params.commandId}/kill`,
|
|
422
|
-
{
|
|
423
|
-
method: "POST",
|
|
424
|
-
body: JSON.stringify({ signal: params.signal }),
|
|
425
|
-
signal: params.abortSignal,
|
|
426
|
-
},
|
|
427
|
-
),
|
|
428
|
-
);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
getLogs(params: {
|
|
432
|
-
sandboxId: string;
|
|
433
|
-
cmdId: string;
|
|
434
|
-
signal?: AbortSignal;
|
|
435
|
-
}): AsyncGenerator<
|
|
436
|
-
z.infer<typeof LogLineStdout> | z.infer<typeof LogLineStderr>,
|
|
437
|
-
void,
|
|
438
|
-
void
|
|
439
|
-
> &
|
|
440
|
-
Disposable & { close(): void } {
|
|
441
|
-
const self = this;
|
|
442
|
-
const disposer = new AbortController();
|
|
443
|
-
const signal = !params.signal
|
|
444
|
-
? disposer.signal
|
|
445
|
-
: mergeSignals(params.signal, disposer.signal);
|
|
446
|
-
|
|
447
|
-
const generator = (async function* () {
|
|
448
|
-
const url = `/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}/logs`;
|
|
449
|
-
const response = await self.request(url, {
|
|
450
|
-
method: "GET",
|
|
451
|
-
signal,
|
|
452
|
-
});
|
|
453
|
-
if (response.headers.get("content-type") !== "application/x-ndjson") {
|
|
454
|
-
throw new APIError(response, {
|
|
455
|
-
message: "Expected a stream of logs",
|
|
456
|
-
sandboxId: params.sandboxId,
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
if (response.body === null) {
|
|
461
|
-
throw new APIError(response, {
|
|
462
|
-
message: "No response body",
|
|
463
|
-
sandboxId: params.sandboxId,
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
const jsonlinesStream = jsonlines.parse();
|
|
468
|
-
pipe(response.body, jsonlinesStream).catch((err) => {
|
|
469
|
-
console.error("Error piping logs:", err);
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
for await (const chunk of jsonlinesStream) {
|
|
473
|
-
const parsed = LogLine.parse(chunk);
|
|
474
|
-
if (parsed.stream === "error") {
|
|
475
|
-
throw new StreamError(
|
|
476
|
-
parsed.data.code,
|
|
477
|
-
parsed.data.message,
|
|
478
|
-
params.sandboxId,
|
|
479
|
-
);
|
|
480
|
-
}
|
|
481
|
-
yield parsed;
|
|
482
|
-
}
|
|
483
|
-
})();
|
|
484
|
-
|
|
485
|
-
return Object.assign(generator, {
|
|
486
|
-
[Symbol.dispose]() {
|
|
487
|
-
disposer.abort("Disposed");
|
|
488
|
-
},
|
|
489
|
-
close: () => disposer.abort("Disposed"),
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
async stopSandbox(params: {
|
|
494
|
-
sandboxId: string;
|
|
495
|
-
signal?: AbortSignal;
|
|
496
|
-
}): Promise<Parsed<z.infer<typeof SandboxResponse>>> {
|
|
497
|
-
const url = `/v1/sandboxes/${params.sandboxId}/stop`;
|
|
498
|
-
return parseOrThrow(
|
|
499
|
-
SandboxResponse,
|
|
500
|
-
await this.request(url, { method: "POST", signal: params.signal }),
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
async extendTimeout(params: {
|
|
505
|
-
sandboxId: string;
|
|
506
|
-
duration: number;
|
|
507
|
-
signal?: AbortSignal;
|
|
508
|
-
}): Promise<Parsed<z.infer<typeof ExtendTimeoutResponse>>> {
|
|
509
|
-
const url = `/v1/sandboxes/${params.sandboxId}/extend-timeout`;
|
|
510
|
-
return parseOrThrow(
|
|
511
|
-
ExtendTimeoutResponse,
|
|
512
|
-
await this.request(url, {
|
|
513
|
-
method: "POST",
|
|
514
|
-
body: JSON.stringify({ duration: params.duration }),
|
|
515
|
-
signal: params.signal,
|
|
516
|
-
}),
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
async createSnapshot(params: {
|
|
521
|
-
sandboxId: string;
|
|
522
|
-
signal?: AbortSignal;
|
|
523
|
-
}): Promise<Parsed<z.infer<typeof CreateSnapshotResponse>>> {
|
|
524
|
-
const url = `/v1/sandboxes/${params.sandboxId}/snapshot`;
|
|
525
|
-
return parseOrThrow(
|
|
526
|
-
CreateSnapshotResponse,
|
|
527
|
-
await this.request(url, { method: "POST", signal: params.signal }),
|
|
528
|
-
);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
async deleteSnapshot(params: {
|
|
532
|
-
snapshotId: string;
|
|
533
|
-
signal?: AbortSignal;
|
|
534
|
-
}): Promise<Parsed<z.infer<typeof SnapshotResponse>>> {
|
|
535
|
-
const url = `/v1/sandboxes/snapshots/${params.snapshotId}`;
|
|
536
|
-
return parseOrThrow(
|
|
537
|
-
SnapshotResponse,
|
|
538
|
-
await this.request(url, { method: "DELETE", signal: params.signal }),
|
|
539
|
-
);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
async getSnapshot(params: {
|
|
543
|
-
snapshotId: string;
|
|
544
|
-
signal?: AbortSignal;
|
|
545
|
-
}): Promise<Parsed<z.infer<typeof SnapshotResponse>>> {
|
|
546
|
-
const url = `/v1/sandboxes/snapshots/${params.snapshotId}`;
|
|
547
|
-
return parseOrThrow(
|
|
548
|
-
SnapshotResponse,
|
|
549
|
-
await this.request(url, { signal: params.signal }),
|
|
550
|
-
);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
async function pipe(
|
|
555
|
-
readable: ReadableStream<Uint8Array>,
|
|
556
|
-
output: NodeJS.WritableStream,
|
|
557
|
-
) {
|
|
558
|
-
const reader = readable.getReader();
|
|
559
|
-
try {
|
|
560
|
-
while (true) {
|
|
561
|
-
const read = await reader.read();
|
|
562
|
-
if (read.value) {
|
|
563
|
-
output.write(Buffer.from(read.value));
|
|
564
|
-
}
|
|
565
|
-
if (read.done) {
|
|
566
|
-
break;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
} catch (err) {
|
|
570
|
-
output.emit("error", err);
|
|
571
|
-
} finally {
|
|
572
|
-
output.end();
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
function mergeSignals(...signals: [AbortSignal, ...AbortSignal[]]) {
|
|
577
|
-
const controller = new AbortController();
|
|
578
|
-
const onAbort = () => {
|
|
579
|
-
controller.abort();
|
|
580
|
-
for (const signal of signals) {
|
|
581
|
-
signal.removeEventListener("abort", onAbort);
|
|
582
|
-
}
|
|
583
|
-
};
|
|
584
|
-
for (const signal of signals) {
|
|
585
|
-
if (signal.aborted) {
|
|
586
|
-
controller.abort();
|
|
587
|
-
break;
|
|
588
|
-
}
|
|
589
|
-
signal.addEventListener("abort", onAbort);
|
|
590
|
-
}
|
|
591
|
-
return controller.signal;
|
|
592
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
interface Options<ErrorData> {
|
|
2
|
-
message?: string;
|
|
3
|
-
json?: ErrorData;
|
|
4
|
-
text?: string;
|
|
5
|
-
sandboxId?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export class APIError<ErrorData> extends Error {
|
|
9
|
-
public response: Response;
|
|
10
|
-
public message: string;
|
|
11
|
-
public json?: ErrorData;
|
|
12
|
-
public text?: string;
|
|
13
|
-
public sandboxId?: string;
|
|
14
|
-
|
|
15
|
-
constructor(response: Response, options?: Options<ErrorData>) {
|
|
16
|
-
super(response.statusText);
|
|
17
|
-
if (Error.captureStackTrace) {
|
|
18
|
-
Error.captureStackTrace(this, APIError);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
this.response = response;
|
|
22
|
-
this.message = options?.message ?? "";
|
|
23
|
-
this.json = options?.json;
|
|
24
|
-
this.text = options?.text;
|
|
25
|
-
this.sandboxId = options?.sandboxId;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Error thrown when a stream error is received streaming.
|
|
31
|
-
* This typically occurs when the sandbox is stopped while streaming.
|
|
32
|
-
*/
|
|
33
|
-
export class StreamError extends Error {
|
|
34
|
-
public code: string;
|
|
35
|
-
public sandboxId: string;
|
|
36
|
-
|
|
37
|
-
constructor(code: string, message: string, sandboxId: string) {
|
|
38
|
-
super(message);
|
|
39
|
-
this.name = "StreamError";
|
|
40
|
-
this.code = code;
|
|
41
|
-
this.sandboxId = sandboxId;
|
|
42
|
-
if (Error.captureStackTrace) {
|
|
43
|
-
Error.captureStackTrace(this, StreamError);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|