@blaxel/core 0.3.9-preview.236 → 0.3.9-preview.238

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.
@@ -30,8 +30,8 @@ function missingCredentialsMessage() {
30
30
  return "No Blaxel credentials found. Set the BL_API_KEY and BL_WORKSPACE environment variables, or run `bl login`.";
31
31
  }
32
32
  // Build info - these placeholders are replaced at build time by build:replace-imports
33
- const BUILD_VERSION = "0.3.9-preview.236";
34
- const BUILD_COMMIT = "08b0c831175e1332396a44bcf769cd4a101c3e28";
33
+ const BUILD_VERSION = "0.3.9-preview.238";
34
+ const BUILD_COMMIT = "e8e4a67eba43ee3946e0682f613efb0e50dd0320";
35
35
  const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
36
36
  const BLAXEL_API_VERSION = "2026-04-28";
37
37
  // Bun < 1.3.11 never sends connection-level WINDOW_UPDATE: the pooled h2
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GATEWAY_ERROR_STATUSES = void 0;
4
+ exports.isRetryableGatewayError = isRetryableGatewayError;
3
5
  exports.isTransientResetError = isTransientResetError;
4
6
  exports.backoffDelayMs = backoffDelayMs;
5
7
  exports.retryOnTransientReset = retryOnTransientReset;
@@ -22,6 +24,14 @@ const TRANSIENT_RESET_MARKERS = [
22
24
  "HTTP/2 session closed before response", // thrown by our own h2 transport
23
25
  "HTTP/2 session sent GOAWAY before response",
24
26
  ];
27
+ // HTTP statuses the edge/CDN (e.g. CloudFront) returns when it — not the
28
+ // sandbox — fails to get a usable response from origin: 502 Bad Gateway,
29
+ // 503 Service Unavailable, 504 Gateway Timeout. On an IDEMPOTENT request these
30
+ // are safe to retry (the sandbox is likely waking from standby, or the edge
31
+ // briefly could not reach origin). Retried with a small, separate budget from
32
+ // transport resets because each attempt can itself burn the edge's ~60s
33
+ // origin-read timeout before failing.
34
+ exports.GATEWAY_ERROR_STATUSES = new Set([502, 503, 504]);
25
35
  // Node-level error codes (from `error.code` / `error.cause.code`) that mean
26
36
  // the connection itself dropped mid-flight and the request never completed.
27
37
  // These are safe to retry for an idempotent request.
@@ -56,19 +66,36 @@ function collectErrorText(error) {
56
66
  // like "GOAWAY" or "ERR_HTTP2". Guarding on this stops a marker-bearing 4xx/5xx
57
67
  // body from being misread as transient and retried (the over-match Codex flagged
58
68
  // for the now default-on idempotent-read retry).
59
- function hasHttpResponseStatus(error) {
69
+ function getHttpResponseStatus(error) {
60
70
  let current = error;
61
71
  for (let depth = 0; depth < 5 && current && typeof current === "object"; depth++) {
62
72
  const node = current;
63
73
  if (typeof node.status === "number")
64
- return true;
65
- if (node.response && typeof node.response === "object" &&
66
- typeof node.response.status === "number") {
67
- return true;
74
+ return node.status;
75
+ if (node.response && typeof node.response === "object") {
76
+ const responseStatus = node.response.status;
77
+ if (typeof responseStatus === "number")
78
+ return responseStatus;
68
79
  }
69
80
  current = node.cause;
70
81
  }
71
- return false;
82
+ return null;
83
+ }
84
+ function hasHttpResponseStatus(error) {
85
+ return getHttpResponseStatus(error) !== null;
86
+ }
87
+ /**
88
+ * True when the error carries an edge gateway status (502/503/504). Unlike a
89
+ * transport reset (see isTransientResetError), this DOES carry an HTTP status —
90
+ * it is the edge failing to reach origin, not the sandbox returning an
91
+ * application error — so it is retried on idempotent operations with its own
92
+ * small budget.
93
+ */
94
+ function isRetryableGatewayError(error) {
95
+ if (!error || typeof error !== "object")
96
+ return false;
97
+ const status = getHttpResponseStatus(error);
98
+ return status !== null && exports.GATEWAY_ERROR_STATUSES.has(status);
72
99
  }
73
100
  /**
74
101
  * True only for transport-level resets/drops that are safe to retry on an
@@ -104,6 +131,11 @@ function isTransientResetError(error) {
104
131
  }
105
132
  const DEFAULT_BASE_DELAY_MS = 200;
106
133
  const DEFAULT_MAX_DELAY_MS = 2000;
134
+ // Gateway (502/503/504) retries get their own small budget: each attempt can
135
+ // burn the edge's ~60s origin-read timeout before failing, so a large budget
136
+ // would stall a caller for minutes. Two extra attempts is enough to ride out a
137
+ // standby wake without turning a hard outage into a multi-minute hang.
138
+ const DEFAULT_GATEWAY_RETRIES = 2;
107
139
  // Exponential backoff with full-jitter on top of one base delay, capped so a
108
140
  // single wait never blocks unreasonably long. Exponential (rather than linear)
109
141
  // gives a later attempt room to span a multi-second sandbox cold-start/standby
@@ -123,20 +155,38 @@ function backoffDelayMs(attempt, baseDelayMs, maxDelayMs) {
123
155
  * PUT of the same bytes) — never a non-idempotent POST such as process.exec,
124
156
  * which would duplicate the side effect (ENG-2340).
125
157
  *
158
+ * Also retries edge gateway statuses (502/503/504) on their own small budget
159
+ * (`gatewayRetries`, default 2): these carry an HTTP status but come from the
160
+ * edge failing to reach origin (a standby wake, not an application error), so
161
+ * they are safe to retry on an idempotent request.
162
+ *
126
163
  * Defaults to `settings.sandboxReadRetries` (the higher idempotent-read budget,
127
164
  * sized for a multi-second standby wake). The upload path passes
128
165
  * `{ retries: settings.fsPartRetries }` to keep its own (lower) budget.
129
166
  */
130
167
  async function retryOnTransientReset(fn, options = {}) {
131
168
  const retries = options.retries ?? settings_js_1.settings.sandboxReadRetries;
169
+ const gatewayRetries = options.gatewayRetries ?? DEFAULT_GATEWAY_RETRIES;
132
170
  const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
133
171
  const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
134
172
  let attempt = 0;
173
+ let gatewayAttempt = 0;
135
174
  for (;;) {
136
175
  try {
137
176
  return await fn();
138
177
  }
139
178
  catch (error) {
179
+ // Edge gateway timeouts carry an HTTP status, so they are handled here
180
+ // rather than by the transport-reset branch (which ignores anything with
181
+ // a status), on their own smaller budget.
182
+ if (isRetryableGatewayError(error)) {
183
+ gatewayAttempt++;
184
+ if (gatewayRetries <= 0 || gatewayAttempt > gatewayRetries) {
185
+ throw error;
186
+ }
187
+ await new Promise((resolve) => setTimeout(resolve, backoffDelayMs(gatewayAttempt, baseDelayMs, maxDelayMs)));
188
+ continue;
189
+ }
140
190
  attempt++;
141
191
  if (retries <= 0 || attempt > retries || !isTransientResetError(error)) {
142
192
  throw error;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SandboxAction = exports.ResponseError = void 0;
3
+ exports.SandboxAction = exports.SandboxGatewayError = exports.ResponseError = void 0;
4
+ exports.isGatewayError = isGatewayError;
5
+ exports.isGatewayTimeout = isGatewayTimeout;
4
6
  const client_fetch_1 = require("@hey-api/client-fetch");
5
7
  const interceptors_js_1 = require("../client/interceptors.js");
6
8
  const responseInterceptor_js_1 = require("../client/responseInterceptor.js");
@@ -8,32 +10,87 @@ const h2fetch_js_1 = require("../common/h2fetch.js");
8
10
  const h2pool_js_1 = require("../common/h2pool.js");
9
11
  const internal_js_1 = require("../common/internal.js");
10
12
  const settings_js_1 = require("../common/settings.js");
13
+ const transient_retry_js_1 = require("../common/transient-retry.js");
11
14
  const client_gen_js_1 = require("./client/client.gen.js");
15
+ const GATEWAY_STATUS_TEXT = {
16
+ 502: "Bad Gateway",
17
+ 503: "Gateway Service Unavailable",
18
+ 504: "Gateway Timeout",
19
+ };
20
+ // Pull a short, human-readable detail out of the response payload, if any.
21
+ function extractErrorDetail(data, error) {
22
+ const fromError = error && typeof error === "object" && "error" in error
23
+ ? error.error
24
+ : undefined;
25
+ const fromData = data && typeof data === "object" && "error" in data
26
+ ? data.error
27
+ : undefined;
28
+ const detail = fromError ?? fromData;
29
+ if (detail === undefined || detail === null)
30
+ return undefined;
31
+ if (typeof detail === "string")
32
+ return detail;
33
+ if (typeof detail === "number" || typeof detail === "boolean")
34
+ return String(detail);
35
+ try {
36
+ return JSON.stringify(detail);
37
+ }
38
+ catch {
39
+ return undefined;
40
+ }
41
+ }
12
42
  class ResponseError extends Error {
13
43
  response;
14
- data;
15
44
  error;
45
+ /** HTTP status code of the failing response, if any. */
46
+ status;
47
+ statusText;
48
+ /** Parsed response body, when the client managed to parse one. */
49
+ data;
16
50
  constructor(response, data, error) {
17
- let dataError = {};
18
- if (data && typeof data === 'object' && 'error' in data) {
19
- dataError = data;
20
- }
21
- if (error && typeof error === 'object' && 'error' in error) {
22
- dataError['error'] = error.error;
23
- }
24
- if (response.status) {
25
- dataError['status'] = response.status;
26
- }
27
- if (response.statusText) {
28
- dataError['statusText'] = response.statusText;
29
- }
30
- super(JSON.stringify(dataError));
51
+ const status = response.status || undefined;
52
+ const statusText = response.statusText || undefined;
53
+ const detail = extractErrorDetail(data, error);
54
+ const label = status !== undefined ? String(status) : "unknown";
55
+ const suffix = statusText ? ` ${statusText}` : "";
56
+ const message = `Sandbox request failed with status ${label}${suffix}${detail ? `: ${detail}` : ""}`;
57
+ super(message);
31
58
  this.response = response;
32
- this.data = data;
33
59
  this.error = error;
60
+ this.name = "ResponseError";
61
+ this.status = status;
62
+ this.statusText = statusText;
63
+ this.data = data;
34
64
  }
35
65
  }
36
66
  exports.ResponseError = ResponseError;
67
+ /**
68
+ * Thrown when the edge/CDN in front of the sandbox returns a gateway status
69
+ * (502/503/504) — the request never got a usable answer from the sandbox
70
+ * itself (it is waking from standby, the command outran the edge's ~60s
71
+ * origin-read timeout, or the edge could not reach origin). Safe to retry on an
72
+ * idempotent operation. Catch it with `err instanceof SandboxGatewayError` or
73
+ * `isGatewayTimeout(err)`.
74
+ */
75
+ class SandboxGatewayError extends ResponseError {
76
+ constructor(response, data, error) {
77
+ super(response, data, error);
78
+ this.name = "SandboxGatewayError";
79
+ const known = this.status !== undefined ? GATEWAY_STATUS_TEXT[this.status] : undefined;
80
+ this.message = `Sandbox unreachable at the edge gateway (${this.status ?? "unknown"}${known ? ` ${known}` : ""}). The sandbox may be waking from standby or the request outran the edge timeout; retry an idempotent request or poll the sandbox.`;
81
+ }
82
+ }
83
+ exports.SandboxGatewayError = SandboxGatewayError;
84
+ /** True when `err` is a gateway status (502/503/504) from the edge. */
85
+ function isGatewayError(err) {
86
+ return (err instanceof ResponseError &&
87
+ err.status !== undefined &&
88
+ transient_retry_js_1.GATEWAY_ERROR_STATUSES.has(err.status));
89
+ }
90
+ /** True when `err` is specifically an edge gateway timeout (504). */
91
+ function isGatewayTimeout(err) {
92
+ return err instanceof ResponseError && err.status === 504;
93
+ }
37
94
  class SandboxAction {
38
95
  sandbox;
39
96
  _h2Client = null;
@@ -123,6 +180,9 @@ class SandboxAction {
123
180
  }
124
181
  handleResponseError(response, data, error) {
125
182
  if (!response.ok || !data) {
183
+ if (transient_retry_js_1.GATEWAY_ERROR_STATUSES.has(response.status)) {
184
+ throw new SandboxGatewayError(response, data, error);
185
+ }
126
186
  throw new ResponseError(response, data, error);
127
187
  }
128
188
  }
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.SandboxDrive = exports.putFilesystemByPath = exports.postProcess = exports.postNetworkProcessByPidMonitor = exports.getProcessByIdentifierLogsStream = exports.getProcessByIdentifierLogs = exports.getProcessByIdentifier = exports.getProcess = exports.getCodegenRerankingByPath = exports.putCodegenFastapplyByPath = exports.getNetworkProcessByPidPorts = exports.getFilesystemByPath = exports.deleteProcessByIdentifierKill = exports.deleteProcessByIdentifier = exports.deleteNetworkProcessByPidMonitor = exports.deleteFilesystemByPath = void 0;
17
+ exports.SandboxDrive = exports.isGatewayTimeout = exports.isGatewayError = exports.SandboxGatewayError = exports.ResponseError = exports.putFilesystemByPath = exports.postProcess = exports.postNetworkProcessByPidMonitor = exports.getProcessByIdentifierLogsStream = exports.getProcessByIdentifierLogs = exports.getProcessByIdentifier = exports.getProcess = exports.getCodegenRerankingByPath = exports.putCodegenFastapplyByPath = exports.getNetworkProcessByPidPorts = exports.getFilesystemByPath = exports.deleteProcessByIdentifierKill = exports.deleteProcessByIdentifier = exports.deleteNetworkProcessByPidMonitor = exports.deleteFilesystemByPath = void 0;
18
18
  var index_js_1 = require("./client/index.js");
19
19
  /* Export SDK functions */
20
20
  Object.defineProperty(exports, "deleteFilesystemByPath", { enumerable: true, get: function () { return index_js_1.deleteFilesystemByPath; } });
@@ -32,6 +32,11 @@ Object.defineProperty(exports, "getProcessByIdentifierLogsStream", { enumerable:
32
32
  Object.defineProperty(exports, "postNetworkProcessByPidMonitor", { enumerable: true, get: function () { return index_js_1.postNetworkProcessByPidMonitor; } });
33
33
  Object.defineProperty(exports, "postProcess", { enumerable: true, get: function () { return index_js_1.postProcess; } });
34
34
  Object.defineProperty(exports, "putFilesystemByPath", { enumerable: true, get: function () { return index_js_1.putFilesystemByPath; } });
35
+ var action_js_1 = require("./action.js");
36
+ Object.defineProperty(exports, "ResponseError", { enumerable: true, get: function () { return action_js_1.ResponseError; } });
37
+ Object.defineProperty(exports, "SandboxGatewayError", { enumerable: true, get: function () { return action_js_1.SandboxGatewayError; } });
38
+ Object.defineProperty(exports, "isGatewayError", { enumerable: true, get: function () { return action_js_1.isGatewayError; } });
39
+ Object.defineProperty(exports, "isGatewayTimeout", { enumerable: true, get: function () { return action_js_1.isGatewayTimeout; } });
35
40
  __exportStar(require("./filesystem/index.js"), exports);
36
41
  __exportStar(require("./codegen/index.js"), exports);
37
42
  var index_js_2 = require("./drive/index.js");
@@ -1,3 +1,12 @@
1
+ export declare const GATEWAY_ERROR_STATUSES: Set<number>;
2
+ /**
3
+ * True when the error carries an edge gateway status (502/503/504). Unlike a
4
+ * transport reset (see isTransientResetError), this DOES carry an HTTP status —
5
+ * it is the edge failing to reach origin, not the sandbox returning an
6
+ * application error — so it is retried on idempotent operations with its own
7
+ * small budget.
8
+ */
9
+ export declare function isRetryableGatewayError(error: unknown): boolean;
1
10
  /**
2
11
  * True only for transport-level resets/drops that are safe to retry on an
3
12
  * IDEMPOTENT request (transient HTTP/2 stream reset, GOAWAY, or a dropped
@@ -14,6 +23,12 @@ export declare function isTransientResetError(error: unknown): boolean;
14
23
  export declare function backoffDelayMs(attempt: number, baseDelayMs: number, maxDelayMs: number): number;
15
24
  export type RetryOptions = {
16
25
  retries?: number;
26
+ /**
27
+ * Retry budget for edge gateway statuses (502/503/504). Defaults to a small
28
+ * value (2) because each attempt can cost the edge's ~60s origin-read timeout.
29
+ * Set `0` to disable gateway retries.
30
+ */
31
+ gatewayRetries?: number;
17
32
  baseDelayMs?: number;
18
33
  maxDelayMs?: number;
19
34
  };
@@ -24,6 +39,11 @@ export type RetryOptions = {
24
39
  * PUT of the same bytes) — never a non-idempotent POST such as process.exec,
25
40
  * which would duplicate the side effect (ENG-2340).
26
41
  *
42
+ * Also retries edge gateway statuses (502/503/504) on their own small budget
43
+ * (`gatewayRetries`, default 2): these carry an HTTP status but come from the
44
+ * edge failing to reach origin (a standby wake, not an application error), so
45
+ * they are safe to retry on an idempotent request.
46
+ *
27
47
  * Defaults to `settings.sandboxReadRetries` (the higher idempotent-read budget,
28
48
  * sized for a multi-second standby wake). The upload path passes
29
49
  * `{ retries: settings.fsPartRetries }` to keep its own (lower) budget.
@@ -2,10 +2,29 @@ import { type Client } from "@hey-api/client-fetch";
2
2
  import { SandboxConfiguration } from "./types.js";
3
3
  export declare class ResponseError extends Error {
4
4
  response: Response;
5
- data: unknown;
6
5
  error: unknown;
6
+ /** HTTP status code of the failing response, if any. */
7
+ readonly status?: number;
8
+ readonly statusText?: string;
9
+ /** Parsed response body, when the client managed to parse one. */
10
+ readonly data: unknown;
7
11
  constructor(response: Response, data: unknown, error: unknown);
8
12
  }
13
+ /**
14
+ * Thrown when the edge/CDN in front of the sandbox returns a gateway status
15
+ * (502/503/504) — the request never got a usable answer from the sandbox
16
+ * itself (it is waking from standby, the command outran the edge's ~60s
17
+ * origin-read timeout, or the edge could not reach origin). Safe to retry on an
18
+ * idempotent operation. Catch it with `err instanceof SandboxGatewayError` or
19
+ * `isGatewayTimeout(err)`.
20
+ */
21
+ export declare class SandboxGatewayError extends ResponseError {
22
+ constructor(response: Response, data: unknown, error: unknown);
23
+ }
24
+ /** True when `err` is a gateway status (502/503/504) from the edge. */
25
+ export declare function isGatewayError(err: unknown): boolean;
26
+ /** True when `err` is specifically an edge gateway timeout (504). */
27
+ export declare function isGatewayTimeout(err: unknown): boolean;
9
28
  export declare class SandboxAction {
10
29
  protected sandbox: SandboxConfiguration;
11
30
  private _h2Client;
@@ -1,4 +1,5 @@
1
1
  export { deleteFilesystemByPath, deleteNetworkProcessByPidMonitor, deleteProcessByIdentifier, deleteProcessByIdentifierKill, Directory, ErrorResponse, File, FileRequest, FileWithContent, getFilesystemByPath, getNetworkProcessByPidPorts, putCodegenFastapplyByPath, ApplyEditResponse, ApplyEditRequest, getCodegenRerankingByPath, RerankingResponse, getProcess, getProcessByIdentifier, getProcessByIdentifierLogs, getProcessByIdentifierLogsStream, PortMonitorRequest, postNetworkProcessByPidMonitor, postProcess, ProcessRequest, ProcessResponse, putFilesystemByPath, SuccessResponse } from "./client/index.js";
2
+ export { ResponseError, SandboxGatewayError, isGatewayError, isGatewayTimeout } from "./action.js";
2
3
  export * from "./filesystem/index.js";
3
4
  export * from "./codegen/index.js";
4
5
  export { SandboxDrive, type DriveMountRequest, type DriveMountResponse, type DriveMountInfo, type DriveUnmountResponse } from "./drive/index.js";