@vercel/sandbox 0.0.1

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.
@@ -0,0 +1,4 @@
1
+
2
+ > @vercel/sandbox@0.0.1 build /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
3
+ > tsc
4
+
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @vercel/sandbox
2
+
3
+ ## 0.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Initial release ([#11](https://github.com/vercel/sandbox-sdk/pull/11))
package/README.md ADDED
@@ -0,0 +1,79 @@
1
+ Vercel Sandbox allows you to run arbitrary code in isolated, ephemeral Linux
2
+ VMs. This product is in private beta.
3
+
4
+ ## What is a sandbox?
5
+
6
+ A sandbox is an isolated Linux system for your experimentation and use.
7
+ Internally, it is a Firecracker MicroVM that is powered by [the same
8
+ infrastructure][hive] that powers 1M+ builds a day at Vercel.
9
+
10
+ ## Getting started
11
+
12
+ Vercel Sandbox is in private beta. These examples will not work unless these
13
+ APIs are enabled for your team.
14
+
15
+ - Go to your team settings, and copy the team ID.
16
+ - Go to your Vercel account settings and [create a token][create-token]. Make
17
+ sure it is scoped to the team ID from the previous step.
18
+ - Create a new project:
19
+
20
+ ```
21
+ $ mkdir sandbox-test
22
+ $ pnpm init
23
+ $ pnpm add @vercel/sandbox
24
+ $ pnpm add --save-dev tsx
25
+ ```
26
+
27
+ Now create `whoami.ts`:
28
+
29
+ {@includeCode ../cli/src/example-whoami.ts}
30
+
31
+ Run it like this:
32
+
33
+ ```
34
+ $ VERCEL_TEAM_ID=<team_id> VERCEL_TOKEN=<token> pnpm tsx ./whoami.ts
35
+ Running as: vercel-sandbox
36
+ Working dir: /vercel/sandbox
37
+ ```
38
+
39
+ Now have a look at the sidebar for which classes exist.
40
+
41
+ ## Limitations
42
+
43
+ - Sandbox only supports cloning public repositories at the moment.
44
+ - `sudo` is not available. User code is executed as the `vercel-sandbox` user.
45
+ - Max resources: 2 cores, 4096 MiB of memory.
46
+ - All APIs of the SDK are subject to change. **We make no stability promises.**
47
+
48
+ ## System
49
+
50
+ The base system is an Amazon Linux 2023 system with the following additional
51
+ packages installed.
52
+
53
+ ```
54
+ bind-utils
55
+ bzip2
56
+ findutils
57
+ git
58
+ gzip
59
+ iputils
60
+ libicu
61
+ libjpeg
62
+ libpng
63
+ ncurses-libs
64
+ openssl
65
+ openssl-libs
66
+ procps
67
+ tar
68
+ unzip
69
+ which
70
+ whois
71
+ zstd
72
+ ```
73
+
74
+ - The `node22` system ships a Node 22 runtime under `/vercel/runtimes/node22`.
75
+ - User code is executed as the `vercel-sandbox` user.
76
+ - `/vercel/sandbox` is writable.
77
+
78
+ [create-token]: https://vercel.com/account/settings/tokens
79
+ [hive]: https://vercel.com/blog/a-deep-dive-into-hive-vercels-builds-infrastructure
@@ -0,0 +1,14 @@
1
+ import type { Response } from "node-fetch";
2
+ interface Options<ErrorData> {
3
+ message?: string;
4
+ json?: ErrorData;
5
+ text?: string;
6
+ }
7
+ export declare class APIError<ErrorData> extends Error {
8
+ response: Response;
9
+ message: string;
10
+ json?: ErrorData;
11
+ text?: string;
12
+ constructor(response: Response, options?: Options<ErrorData>);
13
+ }
14
+ export {};
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.APIError = void 0;
4
+ class APIError extends Error {
5
+ constructor(response, options) {
6
+ super(response.statusText);
7
+ if (Error.captureStackTrace) {
8
+ Error.captureStackTrace(this, APIError);
9
+ }
10
+ this.response = response;
11
+ this.message = options?.message ?? "";
12
+ this.json = options?.json;
13
+ this.text = options?.text;
14
+ }
15
+ }
16
+ exports.APIError = APIError;
@@ -0,0 +1,43 @@
1
+ import type { Options as RetryOptions } from "async-retry";
2
+ import { APIError } from "./api-error";
3
+ import { ZodType } from "zod";
4
+ import { type RequestOptions } from "./with-retry";
5
+ import nodeFetch, { type Response, type RequestInit } from "node-fetch";
6
+ export interface RequestParams extends RequestInit {
7
+ headers?: Record<string, string>;
8
+ method?: string;
9
+ onRetry?(error: any, options: RequestOptions): void;
10
+ query?: Record<string, number | string | null | undefined | string[]>;
11
+ retry?: Partial<RetryOptions>;
12
+ }
13
+ /**
14
+ * A base API client that provides a convenience wrapper for fetching where
15
+ * we can pass query parameters as an object, support retries, debugging
16
+ * and automatic authorization.
17
+ */
18
+ export declare class APIClient {
19
+ protected token?: string;
20
+ private fetch;
21
+ private debug;
22
+ private host;
23
+ constructor(params: {
24
+ debug?: boolean;
25
+ host: string;
26
+ token?: string;
27
+ });
28
+ protected request(path: string, opts?: RequestParams): Promise<nodeFetch.Response>;
29
+ }
30
+ export interface Parsed<Data> {
31
+ response: Response;
32
+ text: string;
33
+ json: Data;
34
+ }
35
+ /**
36
+ * Allows to read the response text and parse it as JSON casting to the given
37
+ * type. If the response is not ok or cannot be parsed it will return error.
38
+ *
39
+ * @param response Response to parse.
40
+ * @returns Parsed response or error.
41
+ */
42
+ export declare function parse<Data, ErrorData>(validator: ZodType<Data>, response: Response): Promise<Parsed<Data> | APIError<ErrorData>>;
43
+ export declare function parseOrThrow<Data, ErrorData>(validator: ZodType<Data>, response: Response): Promise<Parsed<Data>>;
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.APIClient = void 0;
7
+ exports.parse = parse;
8
+ exports.parseOrThrow = parseOrThrow;
9
+ const api_error_1 = require("./api-error");
10
+ const array_1 = require("../utils/array");
11
+ const with_retry_1 = require("./with-retry");
12
+ const node_fetch_1 = __importDefault(require("node-fetch"));
13
+ /**
14
+ * A base API client that provides a convenience wrapper for fetching where
15
+ * we can pass query parameters as an object, support retries, debugging
16
+ * and automatic authorization.
17
+ */
18
+ class APIClient {
19
+ constructor(params) {
20
+ this.fetch = (0, with_retry_1.withRetry)(node_fetch_1.default);
21
+ this.host = params.host;
22
+ this.debug = params.debug ?? process.env.DEBUG_FETCH === "true";
23
+ this.token = params.token;
24
+ }
25
+ async request(path, opts) {
26
+ const url = new URL(path, this.host);
27
+ if (opts?.query) {
28
+ for (const [key, value] of Object.entries(opts.query)) {
29
+ (0, array_1.array)(value).forEach((value) => {
30
+ url.searchParams.append(key, value.toString());
31
+ });
32
+ }
33
+ }
34
+ const start = Date.now();
35
+ const response = await this.fetch(url.toString(), {
36
+ ...opts,
37
+ body: opts?.body,
38
+ method: opts?.method || "GET",
39
+ headers: this.token
40
+ ? { Authorization: `Bearer ${this.token}`, ...opts?.headers }
41
+ : opts?.headers,
42
+ });
43
+ if (this.debug) {
44
+ const duration = Date.now() - start;
45
+ console.log(`[API] ${url} (${response.status}) ${duration}ms`);
46
+ if (response.status === 429) {
47
+ const retry = parseInt(response.headers.get("Retry-After") ?? "", 10);
48
+ const hours = Math.floor(retry / 60 / 60);
49
+ const minutes = Math.floor(retry / 60) % 60;
50
+ const seconds = retry % 60;
51
+ console.warn(`[API] ${url} Rate Limited, Retry After ${hours}h ${minutes}m ${seconds}s`);
52
+ }
53
+ }
54
+ return response;
55
+ }
56
+ }
57
+ exports.APIClient = APIClient;
58
+ /**
59
+ * Allows to read the response text and parse it as JSON casting to the given
60
+ * type. If the response is not ok or cannot be parsed it will return error.
61
+ *
62
+ * @param response Response to parse.
63
+ * @returns Parsed response or error.
64
+ */
65
+ async function parse(validator, response) {
66
+ const text = await response.text().catch((err) => {
67
+ return new api_error_1.APIError(response, {
68
+ message: `Can't read response text: ${String(err)}`,
69
+ });
70
+ });
71
+ if (typeof text !== "string") {
72
+ return text;
73
+ }
74
+ let json;
75
+ try {
76
+ json = JSON.parse(text || "{}");
77
+ }
78
+ catch (error) {
79
+ return new api_error_1.APIError(response, {
80
+ message: `Can't parse JSON: ${String(error)}`,
81
+ text,
82
+ });
83
+ }
84
+ if (!response.ok) {
85
+ return new api_error_1.APIError(response, {
86
+ message: `Status code ${response.status} is not ok`,
87
+ json: json,
88
+ text,
89
+ });
90
+ }
91
+ const validated = validator.safeParse(json);
92
+ if (!validated.success) {
93
+ return new api_error_1.APIError(response, {
94
+ message: `Response JSON is not valid: ${validated.error}`,
95
+ json: json,
96
+ text,
97
+ });
98
+ }
99
+ return {
100
+ json: validated.data,
101
+ response,
102
+ text,
103
+ };
104
+ }
105
+ async function parseOrThrow(validator, response) {
106
+ const result = await parse(validator, response);
107
+ if (result instanceof api_error_1.APIError) {
108
+ throw result;
109
+ }
110
+ return result;
111
+ }
@@ -0,0 +1,63 @@
1
+ import { APIClient, type RequestParams } from "./base-client";
2
+ import { Readable } from "stream";
3
+ export declare class SandboxClient extends APIClient {
4
+ private teamId;
5
+ constructor(params: {
6
+ host?: string;
7
+ teamId: string;
8
+ token: string;
9
+ });
10
+ protected request(path: string, params?: RequestParams): Promise<import("node-fetch").Response>;
11
+ createSandbox(params: {
12
+ source: {
13
+ type: "git";
14
+ url: string;
15
+ };
16
+ ports: number[];
17
+ timeout?: number;
18
+ }): Promise<import("./base-client").Parsed<{
19
+ sandboxId: string;
20
+ routes: {
21
+ port: number;
22
+ subdomain: string;
23
+ }[];
24
+ }>>;
25
+ runCommand(params: {
26
+ sandboxId: string;
27
+ cwd?: string;
28
+ command: string;
29
+ args: string[];
30
+ }): Promise<import("./base-client").Parsed<{
31
+ cmdId: string;
32
+ }>>;
33
+ getCommand(params: {
34
+ sandboxId: string;
35
+ cmdId: string;
36
+ wait?: boolean;
37
+ }): Promise<import("./base-client").Parsed<{
38
+ name: string;
39
+ cwd: string;
40
+ args: string[];
41
+ cmdId: string;
42
+ exitCode: number | null;
43
+ }>>;
44
+ writeFiles(params: {
45
+ sandboxId: string;
46
+ files: {
47
+ path: string;
48
+ stream: Readable | Buffer;
49
+ }[];
50
+ }): Promise<void>;
51
+ readFile(params: {
52
+ sandboxId: string;
53
+ path: string;
54
+ cwd?: string;
55
+ }): Promise<NodeJS.ReadableStream | null>;
56
+ getLogs(params: {
57
+ sandboxId: string;
58
+ cmdId: string;
59
+ }): AsyncGenerator<{
60
+ data: string;
61
+ stream: "stdout" | "stderr";
62
+ }, void, void>;
63
+ }
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SandboxClient = void 0;
7
+ const form_data_1 = __importDefault(require("form-data"));
8
+ const base_client_1 = require("./base-client");
9
+ const validators_1 = require("./validators");
10
+ const api_error_1 = require("./api-error");
11
+ const deferred_generator_1 = require("../utils/deferred-generator");
12
+ const jsonlines_1 = __importDefault(require("jsonlines"));
13
+ class SandboxClient extends base_client_1.APIClient {
14
+ constructor(params) {
15
+ super({
16
+ host: params.host ?? "https://api.vercel.com",
17
+ token: params.token,
18
+ debug: false,
19
+ });
20
+ this.teamId = params.teamId;
21
+ }
22
+ async request(path, params) {
23
+ return super.request(path, {
24
+ ...params,
25
+ query: { teamId: this.teamId, ...params?.query },
26
+ headers: {
27
+ "content-type": "application/json",
28
+ ...params?.headers,
29
+ },
30
+ });
31
+ }
32
+ async createSandbox(params) {
33
+ return (0, base_client_1.parseOrThrow)(validators_1.CreatedSandbox, await this.request("/v1/sandboxes", {
34
+ method: "POST",
35
+ body: JSON.stringify({
36
+ ports: params.ports,
37
+ source: params.source,
38
+ timeout: params.timeout,
39
+ }),
40
+ }));
41
+ }
42
+ async runCommand(params) {
43
+ return (0, base_client_1.parseOrThrow)(validators_1.CreatedCommand, await this.request(`/v1/sandboxes/${params.sandboxId}/cmd`, {
44
+ method: "POST",
45
+ body: JSON.stringify({
46
+ command: params.command,
47
+ args: params.args,
48
+ cwd: params.cwd,
49
+ }),
50
+ }));
51
+ }
52
+ async getCommand(params) {
53
+ return (0, base_client_1.parseOrThrow)(validators_1.Command, await this.request(`/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}`, { query: { wait: params.wait ? "true" : undefined } }));
54
+ }
55
+ async writeFiles(params) {
56
+ const formData = new form_data_1.default();
57
+ for (const file of params.files) {
58
+ formData.append(file.path, file.stream, file.path);
59
+ }
60
+ await (0, base_client_1.parseOrThrow)(validators_1.WrittenFile, await this.request(`/v1/sandboxes/${params.sandboxId}/fs/write`, {
61
+ method: "POST",
62
+ headers: { ...formData.getHeaders() },
63
+ body: formData,
64
+ }));
65
+ }
66
+ async readFile(params) {
67
+ const response = await this.request(`/v1/sandboxes/${params.sandboxId}/fs/read`, {
68
+ method: "POST",
69
+ body: JSON.stringify({ path: params.path, cwd: params.cwd }),
70
+ });
71
+ if (response.status === 404) {
72
+ return null;
73
+ }
74
+ return response.body;
75
+ }
76
+ getLogs(params) {
77
+ const deferred = (0, deferred_generator_1.createDeferredGenerator)();
78
+ (async () => {
79
+ const response = await this.request(`/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}/logs`, { method: "GET" });
80
+ if (response.headers.get("content-type") !== "application/x-ndjson") {
81
+ throw new api_error_1.APIError(response, {
82
+ message: "Expected a stream of logs",
83
+ });
84
+ }
85
+ const parser = jsonlines_1.default.parse();
86
+ response.body.pipe(parser);
87
+ parser.on("data", (data) => {
88
+ const parsed = validators_1.LogLine.safeParse(data);
89
+ if (parsed.success) {
90
+ deferred.next({
91
+ value: parsed.data,
92
+ done: false,
93
+ });
94
+ }
95
+ else {
96
+ deferred.next({
97
+ value: Promise.reject(parsed.error),
98
+ done: false,
99
+ });
100
+ }
101
+ });
102
+ parser.on("error", (err) => {
103
+ deferred.next({
104
+ value: Promise.reject(err),
105
+ done: false,
106
+ });
107
+ });
108
+ parser.on("end", () => {
109
+ deferred.next({
110
+ value: undefined,
111
+ done: true,
112
+ });
113
+ });
114
+ })();
115
+ return deferred.generator();
116
+ }
117
+ }
118
+ exports.SandboxClient = SandboxClient;
@@ -0,0 +1,70 @@
1
+ import { z } from "zod";
2
+ export declare const CreatedSandbox: z.ZodObject<{
3
+ sandboxId: z.ZodString;
4
+ routes: z.ZodArray<z.ZodObject<{
5
+ subdomain: z.ZodString;
6
+ port: z.ZodNumber;
7
+ }, "strip", z.ZodTypeAny, {
8
+ port: number;
9
+ subdomain: string;
10
+ }, {
11
+ port: number;
12
+ subdomain: string;
13
+ }>, "many">;
14
+ }, "strip", z.ZodTypeAny, {
15
+ sandboxId: string;
16
+ routes: {
17
+ port: number;
18
+ subdomain: string;
19
+ }[];
20
+ }, {
21
+ sandboxId: string;
22
+ routes: {
23
+ port: number;
24
+ subdomain: string;
25
+ }[];
26
+ }>;
27
+ export declare const CreatedCommand: z.ZodObject<{
28
+ cmdId: z.ZodString;
29
+ }, "strip", z.ZodTypeAny, {
30
+ cmdId: string;
31
+ }, {
32
+ cmdId: string;
33
+ }>;
34
+ export declare const Command: z.ZodObject<{
35
+ args: z.ZodArray<z.ZodString, "many">;
36
+ cmdId: z.ZodString;
37
+ cwd: z.ZodString;
38
+ exitCode: z.ZodNullable<z.ZodNumber>;
39
+ name: z.ZodString;
40
+ }, "strip", z.ZodTypeAny, {
41
+ name: string;
42
+ cwd: string;
43
+ args: string[];
44
+ cmdId: string;
45
+ exitCode: number | null;
46
+ }, {
47
+ name: string;
48
+ cwd: string;
49
+ args: string[];
50
+ cmdId: string;
51
+ exitCode: number | null;
52
+ }>;
53
+ export declare const FinishedCommand: z.ZodObject<{
54
+ cmdId: z.ZodString;
55
+ }, "strip", z.ZodTypeAny, {
56
+ cmdId: string;
57
+ }, {
58
+ cmdId: string;
59
+ }>;
60
+ export declare const WrittenFile: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
61
+ export declare const LogLine: z.ZodObject<{
62
+ stream: z.ZodEnum<["stdout", "stderr"]>;
63
+ data: z.ZodString;
64
+ }, "strip", z.ZodTypeAny, {
65
+ data: string;
66
+ stream: "stdout" | "stderr";
67
+ }, {
68
+ data: string;
69
+ stream: "stdout" | "stderr";
70
+ }>;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LogLine = exports.WrittenFile = exports.FinishedCommand = exports.Command = exports.CreatedCommand = exports.CreatedSandbox = void 0;
4
+ const zod_1 = require("zod");
5
+ exports.CreatedSandbox = zod_1.z.object({
6
+ sandboxId: zod_1.z.string(),
7
+ routes: zod_1.z.array(zod_1.z.object({ subdomain: zod_1.z.string(), port: zod_1.z.number() })),
8
+ });
9
+ exports.CreatedCommand = zod_1.z.object({
10
+ cmdId: zod_1.z.string(),
11
+ });
12
+ exports.Command = zod_1.z.object({
13
+ args: zod_1.z.array(zod_1.z.string()),
14
+ cmdId: zod_1.z.string(),
15
+ cwd: zod_1.z.string(),
16
+ exitCode: zod_1.z.number().nullable(),
17
+ name: zod_1.z.string(),
18
+ });
19
+ exports.FinishedCommand = zod_1.z.object({
20
+ cmdId: zod_1.z.string(),
21
+ });
22
+ exports.WrittenFile = zod_1.z.object({});
23
+ exports.LogLine = zod_1.z.object({
24
+ stream: zod_1.z.enum(["stdout", "stderr"]),
25
+ data: zod_1.z.string(),
26
+ });
@@ -0,0 +1,15 @@
1
+ import type { RequestInit, Response } from "node-fetch";
2
+ import type { Options as RetryOptions } from "async-retry";
3
+ export interface RequestOptions {
4
+ onRetry?(error: any, options: RequestOptions): void;
5
+ retry?: Partial<RetryOptions>;
6
+ }
7
+ /**
8
+ * Wraps a fetch function with retry logic. The retry logic will retry
9
+ * on network errors, 429 responses and 5xx responses. The retry logic
10
+ * will not retry on 4xx responses.
11
+ *
12
+ * @param rawFetch The fetch function to wrap.
13
+ * @returns The wrapped fetch function.
14
+ */
15
+ export declare function withRetry<T extends RequestInit>(rawFetch: (url: URL | string, init?: T) => Promise<Response>): (url: URL | string, opts?: T & RequestOptions) => Promise<Response>;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.withRetry = withRetry;
7
+ const api_error_1 = require("./api-error");
8
+ const promises_1 = require("timers/promises");
9
+ const async_retry_1 = __importDefault(require("async-retry"));
10
+ /**
11
+ * Wraps a fetch function with retry logic. The retry logic will retry
12
+ * on network errors, 429 responses and 5xx responses. The retry logic
13
+ * will not retry on 4xx responses.
14
+ *
15
+ * @param rawFetch The fetch function to wrap.
16
+ * @returns The wrapped fetch function.
17
+ */
18
+ function withRetry(rawFetch) {
19
+ return async (url, opts = {}) => {
20
+ /**
21
+ * Timeouts by default will be [10, 60, 360, 2160, 12960]
22
+ * before randomization is added.
23
+ */
24
+ const retryOpts = Object.assign({
25
+ minTimeout: 10,
26
+ retries: 5,
27
+ factor: 6,
28
+ maxRetryAfter: 20,
29
+ }, opts.retry);
30
+ if (opts.onRetry) {
31
+ retryOpts.onRetry = (error, attempts) => {
32
+ opts.onRetry(error, opts);
33
+ if (opts.retry && opts.retry.onRetry) {
34
+ opts.retry.onRetry(error, attempts);
35
+ }
36
+ };
37
+ }
38
+ try {
39
+ return (await (0, async_retry_1.default)(async (bail) => {
40
+ try {
41
+ const response = await rawFetch(url, opts);
42
+ /**
43
+ * When the response is 429 we will try to parse the Retry-After
44
+ * header. If the header exists we will try to parse it and, if
45
+ * the wait time is higher than the maximum defined, we respond.
46
+ * Otherwise we wait for the time given in the header and throw
47
+ * to retry.
48
+ */
49
+ if (response.status === 429) {
50
+ const retryAfter = parseInt(response.headers.get("retry-after") || "", 10);
51
+ if (retryAfter && !isNaN(retryAfter)) {
52
+ if (retryAfter > retryOpts.maxRetryAfter) {
53
+ return response;
54
+ }
55
+ await (0, promises_1.setTimeout)(retryAfter * 1e3);
56
+ }
57
+ throw new api_error_1.APIError(response);
58
+ }
59
+ /**
60
+ * If the response is a a retryable error, we throw in
61
+ * order to retry.
62
+ */
63
+ if (response.status >= 500 && response.status < 600) {
64
+ throw new api_error_1.APIError(response);
65
+ }
66
+ return response;
67
+ }
68
+ catch (error) {
69
+ /**
70
+ * If the request was aborted using the AbortController
71
+ * we bail from retrying throwing the original error.
72
+ */
73
+ if (isAbortError(error)) {
74
+ return bail(error);
75
+ }
76
+ throw error;
77
+ }
78
+ }, retryOpts));
79
+ }
80
+ catch (error) {
81
+ /**
82
+ * The ResponseError is only intended for retries so in case we
83
+ * ran out of attempts we will respond with the last response
84
+ * we obtained.
85
+ */
86
+ if (error instanceof api_error_1.APIError) {
87
+ return error.response;
88
+ }
89
+ throw error;
90
+ }
91
+ };
92
+ }
93
+ function isAbortError(error) {
94
+ return (error !== undefined &&
95
+ error !== null &&
96
+ error.name === "AbortError");
97
+ }