@blaxel/core 0.2.89 → 0.2.90-preview.182

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.
@@ -2,10 +2,12 @@ import { client } from "../client/client.gen.js";
2
2
  import { interceptors } from "../client/interceptors.js";
3
3
  import { responseInterceptors } from "../client/responseInterceptor.js";
4
4
  import { client as clientSandbox } from "../sandbox/client/client.gen.js";
5
+ import { controlPlaneFetch } from "./controlPlaneFetch.js";
5
6
  import { settings } from "./settings.js";
6
7
  export { ensureAutoloaded } from "./lazyInit.js";
7
8
  client.setConfig({
8
9
  baseUrl: settings.baseUrl,
10
+ fetch: controlPlaneFetch,
9
11
  });
10
12
  // Register request interceptors
11
13
  for (const interceptor of interceptors) {
@@ -50,6 +52,7 @@ export function initialize(config) {
50
52
  settings.setConfig(config);
51
53
  client.setConfig({
52
54
  baseUrl: settings.baseUrl,
55
+ fetch: controlPlaneFetch,
53
56
  });
54
57
  clientSandbox.setConfig({
55
58
  baseUrl: settings.baseUrl,
@@ -0,0 +1,51 @@
1
+ import { createPoolBackedH2Fetch } from "./h2fetch.js";
2
+ import { h2Pool } from "./h2pool.js";
3
+ import { settings } from "./settings.js";
4
+ const h2FetchByHost = new Map();
5
+ // Node's global fetch dispatcher only negotiates HTTP/2 by default starting
6
+ // with undici 8 (Node 26+); undici 7 (Node 24) and undici 6 (Node 22) still
7
+ // ALPN to HTTP/1.1 for fetch, verified empirically against api.blaxel.ai. On
8
+ // undici >= 8 the pooled wrapper is redundant, so we prefer the native path.
9
+ export function undiciSupportsNativeH2(undiciVersion) {
10
+ const major = Number(undiciVersion?.split(".")[0] ?? 0);
11
+ return major >= 8;
12
+ }
13
+ // `process`/`process.versions` is absent on Cloudflare Workers and other
14
+ // non-Node runtimes, and `process.versions.undici` is undefined on Bun/Deno;
15
+ // all of those resolve to `false` and keep the wrapper as the fallback.
16
+ export const nativeFetchSupportsH2 = typeof process !== "undefined" && undiciSupportsNativeH2(process.versions?.undici);
17
+ export function shouldUseControlPlaneH2(url, h2Disabled, proxyConfigured = false, nativeH2 = false, forceWrapper = false) {
18
+ if (h2Disabled || proxyConfigured)
19
+ return false;
20
+ if (url.protocol !== "https:")
21
+ return false;
22
+ // Token refresh is sequential and unauthenticated; it cannot contribute to
23
+ // the create burst TLS storm, and device-mode refresh currently relies on the
24
+ // native fetch path.
25
+ if (url.pathname.endsWith("/oauth/token"))
26
+ return false;
27
+ if (!(url.hostname.endsWith("blaxel.ai") || url.hostname.endsWith("blaxel.dev"))) {
28
+ return false;
29
+ }
30
+ // Native fetch already negotiates HTTP/2: skip the redundant wrapper unless
31
+ // explicitly forced (e.g. to exercise the pooled path on a modern runtime).
32
+ if (nativeH2 && !forceWrapper)
33
+ return false;
34
+ return true;
35
+ }
36
+ export function controlPlaneFetch(input) {
37
+ const url = new URL(input.url);
38
+ const proxyConfigured = Boolean(settings.config.proxy);
39
+ // Global disableH2 still wins; disableControlPlaneH2 opts out of just the
40
+ // control-plane wrapper while leaving data-plane H2 in place.
41
+ const h2Disabled = settings.disableH2 || settings.disableControlPlaneH2;
42
+ if (!shouldUseControlPlaneH2(url, h2Disabled, proxyConfigured, nativeFetchSupportsH2, settings.forceControlPlaneH2)) {
43
+ return globalThis.fetch(input);
44
+ }
45
+ let h2Fetch = h2FetchByHost.get(url.hostname);
46
+ if (!h2Fetch) {
47
+ h2Fetch = createPoolBackedH2Fetch(h2Pool, url.hostname);
48
+ h2FetchByHost.set(url.hostname, h2Fetch);
49
+ }
50
+ return h2Fetch(input);
51
+ }
@@ -1,5 +1,6 @@
1
1
  import { client } from "../client/client.gen.js";
2
2
  import { client as clientSandbox } from "../sandbox/client/client.gen.js";
3
+ import { controlPlaneFetch } from "./controlPlaneFetch.js";
3
4
  import { initSentry } from "./sentry.js";
4
5
  import { settings } from "./settings.js";
5
6
  let autoloaded = false;
@@ -33,7 +34,7 @@ export function ensureAutoloaded() {
33
34
  // Keep the clients' baseUrl in sync with the now-resolved env. Without
34
35
  // this, the module-load `client.setConfig({ baseUrl })` would be stuck on
35
36
  // the prod default for users who rely on `config.yaml` (no env vars).
36
- client.setConfig({ baseUrl: settings.baseUrl });
37
+ client.setConfig({ baseUrl: settings.baseUrl, fetch: controlPlaneFetch });
37
38
  clientSandbox.setConfig({ baseUrl: settings.baseUrl });
38
39
  // Initialize Sentry for SDK error tracking.
39
40
  initSentry();
@@ -44,9 +45,9 @@ export function ensureAutoloaded() {
44
45
  if (isNode && !isBrowser && !settings.disableH2) {
45
46
  try {
46
47
  // Pre-warm edge H2 for the configured region so the first
47
- // SandboxInstance.create() gets an instant session via the pool.
48
- // The control-plane client (api.blaxel.ai) stays on regular fetch
49
- // which already benefits from undici's built-in connection pooling.
48
+ // SandboxInstance.create() gets an instant data-plane session via the
49
+ // pool. Control-plane H2 is intentionally not warmed; its cold burst
50
+ // relies on pool deduplication instead.
50
51
  const region = settings.region;
51
52
  if (region) {
52
53
  import("./h2pool.js").then(({ h2Pool }) => {
@@ -22,8 +22,8 @@ function missingCredentialsMessage() {
22
22
  return "No Blaxel credentials found. Set the BL_API_KEY and BL_WORKSPACE environment variables, or run `bl login`.";
23
23
  }
24
24
  // Build info - these placeholders are replaced at build time by build:replace-imports
25
- const BUILD_VERSION = "0.2.89";
26
- const BUILD_COMMIT = "c481e2f6670b3cef6832875dbc9f2bc6b0b198f4";
25
+ const BUILD_VERSION = "0.2.90-preview.182";
26
+ const BUILD_COMMIT = "78dad6b3e2d47dcf5d8464b9688b0829ddc3369c";
27
27
  const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
28
28
  const BLAXEL_API_VERSION = "2026-04-16";
29
29
  // Cache for config.yaml tracking value
@@ -244,6 +244,31 @@ class Settings {
244
244
  }
245
245
  return isDenoRuntime();
246
246
  }
247
+ // Control-plane-only escape hatch: disables the control-plane H2 wrapper
248
+ // without affecting data-plane (edge) H2. `disableH2` is the global override
249
+ // and is checked separately by callers, so it always wins.
250
+ get disableControlPlaneH2() {
251
+ if (typeof this.config.disableControlPlaneH2 === "boolean") {
252
+ return this.config.disableControlPlaneH2;
253
+ }
254
+ const value = env.BL_DISABLE_CONTROL_PLANE_H2;
255
+ if (value) {
256
+ return ["1", "true", "yes", "on"].includes(value.toLowerCase());
257
+ }
258
+ return false;
259
+ }
260
+ // Forces the control-plane H2 wrapper on even when native fetch already
261
+ // supports H2 (undici >= 7). `disableH2`/`disableControlPlaneH2` still win.
262
+ get forceControlPlaneH2() {
263
+ if (typeof this.config.forceControlPlaneH2 === "boolean") {
264
+ return this.config.forceControlPlaneH2;
265
+ }
266
+ const value = env.BL_FORCE_CONTROL_PLANE_H2;
267
+ if (value) {
268
+ return ["1", "true", "yes", "on"].includes(value.toLowerCase());
269
+ }
270
+ return false;
271
+ }
247
272
  get maxConcurrentH2Requests() {
248
273
  if (typeof this.config.maxConcurrentH2Requests === "number") {
249
274
  return this.config.maxConcurrentH2Requests;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blaxel/core",
3
- "version": "0.2.89",
3
+ "version": "0.2.90-preview.182",
4
4
  "description": "Blaxel Core SDK for TypeScript",
5
5
  "license": "MIT",
6
6
  "author": "Blaxel, INC (https://blaxel.ai)",