@blaxel/core 0.2.71-preview.104 → 0.2.71
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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/common/autoload.js +64 -0
- package/dist/cjs/common/h2fetch.js +208 -0
- package/dist/cjs/common/h2pool.js +137 -0
- package/dist/cjs/common/h2warm.js +54 -0
- package/dist/cjs/common/settings.js +2 -2
- package/dist/cjs/sandbox/action.js +33 -0
- package/dist/cjs/sandbox/drive/drive.js +3 -3
- package/dist/cjs/sandbox/filesystem/filesystem.js +1 -2
- package/dist/cjs/sandbox/interpreter.js +12 -2
- package/dist/cjs/sandbox/process/process.js +2 -2
- package/dist/cjs/sandbox/sandbox.js +84 -10
- package/dist/cjs/types/common/autoload.d.ts +5 -0
- package/dist/cjs/types/common/h2fetch.d.ts +22 -0
- package/dist/cjs/types/common/h2pool.d.ts +38 -0
- package/dist/cjs/types/common/h2warm.d.ts +2 -0
- package/dist/cjs/types/sandbox/action.d.ts +8 -1
- package/dist/cjs/types/sandbox/interpreter.d.ts +1 -0
- package/dist/cjs/types/sandbox/sandbox.d.ts +6 -0
- package/dist/cjs/types/sandbox/types.d.ts +2 -0
- package/dist/cjs-browser/.tsbuildinfo +1 -1
- package/dist/cjs-browser/common/autoload.js +64 -0
- package/dist/cjs-browser/common/h2fetch.js +4 -0
- package/dist/cjs-browser/common/h2pool.js +4 -0
- package/dist/cjs-browser/common/h2warm.js +2 -0
- package/dist/cjs-browser/common/settings.js +2 -2
- package/dist/cjs-browser/sandbox/action.js +33 -0
- package/dist/cjs-browser/sandbox/drive/drive.js +3 -3
- package/dist/cjs-browser/sandbox/filesystem/filesystem.js +1 -2
- package/dist/cjs-browser/sandbox/interpreter.js +12 -2
- package/dist/cjs-browser/sandbox/process/process.js +2 -2
- package/dist/cjs-browser/sandbox/sandbox.js +84 -10
- package/dist/cjs-browser/types/common/autoload.d.ts +5 -0
- package/dist/cjs-browser/types/common/h2fetch.d.ts +22 -0
- package/dist/cjs-browser/types/common/h2pool.d.ts +38 -0
- package/dist/cjs-browser/types/common/h2warm.d.ts +2 -0
- package/dist/cjs-browser/types/sandbox/action.d.ts +8 -1
- package/dist/cjs-browser/types/sandbox/interpreter.d.ts +1 -0
- package/dist/cjs-browser/types/sandbox/sandbox.d.ts +6 -0
- package/dist/cjs-browser/types/sandbox/types.d.ts +2 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/common/autoload.js +30 -0
- package/dist/esm/common/h2fetch.js +203 -0
- package/dist/esm/common/h2pool.js +101 -0
- package/dist/esm/common/h2warm.js +48 -0
- package/dist/esm/common/settings.js +2 -2
- package/dist/esm/sandbox/action.js +33 -0
- package/dist/esm/sandbox/drive/drive.js +3 -3
- package/dist/esm/sandbox/filesystem/filesystem.js +1 -2
- package/dist/esm/sandbox/interpreter.js +12 -2
- package/dist/esm/sandbox/process/process.js +2 -2
- package/dist/esm/sandbox/sandbox.js +51 -10
- package/dist/esm-browser/.tsbuildinfo +1 -1
- package/dist/esm-browser/common/autoload.js +30 -0
- package/dist/esm-browser/common/h2fetch.js +4 -0
- package/dist/esm-browser/common/h2pool.js +4 -0
- package/dist/esm-browser/common/h2warm.js +2 -0
- package/dist/esm-browser/common/settings.js +2 -2
- package/dist/esm-browser/sandbox/action.js +33 -0
- package/dist/esm-browser/sandbox/drive/drive.js +3 -3
- package/dist/esm-browser/sandbox/filesystem/filesystem.js +1 -2
- package/dist/esm-browser/sandbox/interpreter.js +12 -2
- package/dist/esm-browser/sandbox/process/process.js +2 -2
- package/dist/esm-browser/sandbox/sandbox.js +51 -10
- package/package.json +1 -1
|
@@ -1,7 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.initialize = initialize;
|
|
4
37
|
exports.authenticate = authenticate;
|
|
38
|
+
exports.closeConnections = closeConnections;
|
|
5
39
|
const client_gen_js_1 = require("../client/client.gen.js");
|
|
6
40
|
const interceptors_js_1 = require("../client/interceptors.js");
|
|
7
41
|
const responseInterceptor_js_1 = require("../client/responseInterceptor.js");
|
|
@@ -25,6 +59,28 @@ for (const interceptor of responseInterceptor_js_1.responseInterceptors) {
|
|
|
25
59
|
}
|
|
26
60
|
// Initialize Sentry for SDK error tracking immediately when module loads
|
|
27
61
|
(0, sentry_js_1.initSentry)();
|
|
62
|
+
// Background H2 connection warming (Node.js only)
|
|
63
|
+
const isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
64
|
+
/* eslint-disable */
|
|
65
|
+
const isBrowser = typeof globalThis !== "undefined" && globalThis?.window !== undefined;
|
|
66
|
+
if (isNode && !isBrowser) {
|
|
67
|
+
try {
|
|
68
|
+
// Pre-warm edge H2 for the configured region so the first
|
|
69
|
+
// SandboxInstance.create() gets an instant session via the pool.
|
|
70
|
+
// The control-plane client (api.blaxel.ai) stays on regular fetch
|
|
71
|
+
// which already benefits from undici's built-in connection pooling.
|
|
72
|
+
const region = settings_js_1.settings.region;
|
|
73
|
+
if (region) {
|
|
74
|
+
Promise.resolve().then(() => __importStar(require("./h2pool.js"))).then(({ h2Pool }) => {
|
|
75
|
+
const edgeSuffix = settings_js_1.settings.env === "prod" ? "bl.run" : "runv2.blaxel.dev";
|
|
76
|
+
h2Pool.warm(`any.${region}.${edgeSuffix}`);
|
|
77
|
+
}).catch(() => { });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Silently ignore warming failures
|
|
82
|
+
}
|
|
83
|
+
}
|
|
28
84
|
// Allow to set custom configuration for browser environment
|
|
29
85
|
function initialize(config) {
|
|
30
86
|
settings_js_1.settings.setConfig(config);
|
|
@@ -38,3 +94,11 @@ function initialize(config) {
|
|
|
38
94
|
function authenticate() {
|
|
39
95
|
return settings_js_1.settings.authenticate();
|
|
40
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Close all pooled H2 connections. Call this for explicit cleanup
|
|
99
|
+
* (e.g. in test teardown or before process exit).
|
|
100
|
+
*/
|
|
101
|
+
async function closeConnections() {
|
|
102
|
+
const { h2Pool } = await Promise.resolve().then(() => __importStar(require("./h2pool.js")));
|
|
103
|
+
h2Pool.closeAll();
|
|
104
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createH2Fetch = createH2Fetch;
|
|
4
|
+
exports.createPoolBackedH2Fetch = createPoolBackedH2Fetch;
|
|
5
|
+
exports.h2RequestDirect = h2RequestDirect;
|
|
6
|
+
const H2_REQUEST_TIMEOUT_MS = 10_000;
|
|
7
|
+
/**
|
|
8
|
+
* Creates a fetch()-compatible function that sends requests over an existing
|
|
9
|
+
* HTTP/2 session. Falls back to global fetch() if the session is closed,
|
|
10
|
+
* destroyed, or if the H2 request times out.
|
|
11
|
+
*/
|
|
12
|
+
function createH2Fetch(session) {
|
|
13
|
+
return (input) => {
|
|
14
|
+
if (session.closed || session.destroyed) {
|
|
15
|
+
return globalThis.fetch(input);
|
|
16
|
+
}
|
|
17
|
+
return _h2Request(session, input);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Creates a fetch()-compatible function backed by the H2 session pool.
|
|
22
|
+
*
|
|
23
|
+
* Non-blocking: checks the pool cache synchronously. If a warm session is
|
|
24
|
+
* available it's used immediately; otherwise the request goes through
|
|
25
|
+
* regular fetch with zero delay (the pool keeps warming in the background
|
|
26
|
+
* so subsequent calls get H2).
|
|
27
|
+
*/
|
|
28
|
+
function createPoolBackedH2Fetch(pool, domain) {
|
|
29
|
+
return (input) => {
|
|
30
|
+
const session = pool.tryGet(domain);
|
|
31
|
+
if (session) {
|
|
32
|
+
return _h2Request(session, input);
|
|
33
|
+
}
|
|
34
|
+
return globalThis.fetch(input);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Low-level H2 request that takes raw URL + init, skipping Request construction.
|
|
39
|
+
* Used by SandboxAction.h2Fetch() for direct calls from subsystems.
|
|
40
|
+
*/
|
|
41
|
+
function h2RequestDirect(session, url, init) {
|
|
42
|
+
if (session.closed || session.destroyed) {
|
|
43
|
+
return globalThis.fetch(url, init);
|
|
44
|
+
}
|
|
45
|
+
const parsed = new URL(url);
|
|
46
|
+
const method = init?.method || "GET";
|
|
47
|
+
const h2Headers = {
|
|
48
|
+
":method": method,
|
|
49
|
+
":path": parsed.pathname + parsed.search,
|
|
50
|
+
":authority": parsed.host,
|
|
51
|
+
};
|
|
52
|
+
if (init?.headers) {
|
|
53
|
+
const entries = init.headers instanceof Headers
|
|
54
|
+
? init.headers.entries()
|
|
55
|
+
: Array.isArray(init.headers)
|
|
56
|
+
? init.headers.values()
|
|
57
|
+
: Object.entries(init.headers).values();
|
|
58
|
+
for (const [key, value] of entries) {
|
|
59
|
+
const k = key.toLowerCase();
|
|
60
|
+
if (k === "host")
|
|
61
|
+
continue;
|
|
62
|
+
h2Headers[k] = value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
let body;
|
|
66
|
+
if (init?.body) {
|
|
67
|
+
if (typeof init.body === "string") {
|
|
68
|
+
body = Buffer.from(init.body);
|
|
69
|
+
}
|
|
70
|
+
else if (Buffer.isBuffer(init.body)) {
|
|
71
|
+
body = init.body;
|
|
72
|
+
}
|
|
73
|
+
else if (init.body instanceof ArrayBuffer) {
|
|
74
|
+
body = Buffer.from(init.body);
|
|
75
|
+
}
|
|
76
|
+
else if (init.body instanceof Uint8Array) {
|
|
77
|
+
body = Buffer.from(init.body.buffer, init.body.byteOffset, init.body.byteLength);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// FormData, ReadableStream, Blob, etc. can't be serialized to Buffer
|
|
81
|
+
// for manual H2 framing — fall back to regular fetch.
|
|
82
|
+
return globalThis.fetch(url, init);
|
|
83
|
+
}
|
|
84
|
+
if (!h2Headers["content-length"]) {
|
|
85
|
+
h2Headers["content-length"] = body.byteLength;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return _h2Send(session, h2Headers, body, init?.signal ?? null, url, init);
|
|
89
|
+
}
|
|
90
|
+
async function _h2Request(session, input) {
|
|
91
|
+
const url = new URL(input.url);
|
|
92
|
+
const method = input.method || "GET";
|
|
93
|
+
const h2Headers = {
|
|
94
|
+
":method": method,
|
|
95
|
+
":path": url.pathname + url.search,
|
|
96
|
+
":authority": url.host,
|
|
97
|
+
};
|
|
98
|
+
for (const [key, value] of input.headers.entries()) {
|
|
99
|
+
if (key === "host")
|
|
100
|
+
continue;
|
|
101
|
+
h2Headers[key] = value;
|
|
102
|
+
}
|
|
103
|
+
let body;
|
|
104
|
+
if (input.body) {
|
|
105
|
+
body = Buffer.from(await input.arrayBuffer());
|
|
106
|
+
if (!h2Headers["content-length"]) {
|
|
107
|
+
h2Headers["content-length"] = body.byteLength;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return _h2Send(session, h2Headers, body, input.signal, input.url, {
|
|
111
|
+
method,
|
|
112
|
+
headers: input.headers,
|
|
113
|
+
body,
|
|
114
|
+
signal: input.signal,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function _h2Send(session, h2Headers, body, signal, fallbackUrl, fallbackInit) {
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
let req;
|
|
120
|
+
try {
|
|
121
|
+
req = session.request(h2Headers);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return globalThis.fetch(fallbackUrl, fallbackInit).then(resolve, reject);
|
|
125
|
+
}
|
|
126
|
+
const timer = setTimeout(() => {
|
|
127
|
+
if (settled)
|
|
128
|
+
return;
|
|
129
|
+
settled = true;
|
|
130
|
+
req.close();
|
|
131
|
+
globalThis.fetch(fallbackUrl, fallbackInit).then(resolve, reject);
|
|
132
|
+
}, H2_REQUEST_TIMEOUT_MS);
|
|
133
|
+
if (signal) {
|
|
134
|
+
if (signal.aborted) {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
req.close();
|
|
137
|
+
reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
signal.addEventListener("abort", () => {
|
|
141
|
+
clearTimeout(timer);
|
|
142
|
+
req.close();
|
|
143
|
+
if (!settled) {
|
|
144
|
+
settled = true;
|
|
145
|
+
reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
146
|
+
}
|
|
147
|
+
}, { once: true });
|
|
148
|
+
}
|
|
149
|
+
let settled = false;
|
|
150
|
+
req.on("response", (headers) => {
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
if (settled)
|
|
153
|
+
return;
|
|
154
|
+
settled = true;
|
|
155
|
+
const status = headers[":status"] ?? 200;
|
|
156
|
+
const resHeaders = new Headers();
|
|
157
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
158
|
+
if (k.startsWith(":"))
|
|
159
|
+
continue;
|
|
160
|
+
if (v === undefined)
|
|
161
|
+
continue;
|
|
162
|
+
resHeaders.set(k, Array.isArray(v) ? v.join(", ") : String(v));
|
|
163
|
+
}
|
|
164
|
+
let streamClosed = false;
|
|
165
|
+
const readable = new ReadableStream({
|
|
166
|
+
start(controller) {
|
|
167
|
+
req.on("data", (chunk) => {
|
|
168
|
+
if (!streamClosed)
|
|
169
|
+
controller.enqueue(new Uint8Array(chunk));
|
|
170
|
+
});
|
|
171
|
+
req.on("end", () => {
|
|
172
|
+
if (!streamClosed) {
|
|
173
|
+
streamClosed = true;
|
|
174
|
+
controller.close();
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
req.on("error", (err) => {
|
|
178
|
+
if (!streamClosed) {
|
|
179
|
+
streamClosed = true;
|
|
180
|
+
controller.error(err);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
signal?.addEventListener("abort", () => {
|
|
184
|
+
req.close();
|
|
185
|
+
if (!streamClosed) {
|
|
186
|
+
streamClosed = true;
|
|
187
|
+
controller.error(new DOMException("The operation was aborted.", "AbortError"));
|
|
188
|
+
}
|
|
189
|
+
}, { once: true });
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
resolve(new Response(readable, { status, headers: resHeaders }));
|
|
193
|
+
});
|
|
194
|
+
req.on("error", () => {
|
|
195
|
+
clearTimeout(timer);
|
|
196
|
+
if (settled)
|
|
197
|
+
return;
|
|
198
|
+
settled = true;
|
|
199
|
+
globalThis.fetch(fallbackUrl, fallbackInit).then(resolve, reject);
|
|
200
|
+
});
|
|
201
|
+
if (body) {
|
|
202
|
+
req.end(body);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
req.end();
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.h2Pool = void 0;
|
|
37
|
+
/**
|
|
38
|
+
* Singleton H2 session pool keyed by edge domain.
|
|
39
|
+
*
|
|
40
|
+
* - `warm(domain)` starts a background connection (fire-and-forget).
|
|
41
|
+
* - `get(domain)` returns a live session immediately if cached, or awaits
|
|
42
|
+
* an in-flight warming, or establishes a fresh one.
|
|
43
|
+
* - Closed / destroyed sessions are automatically evicted.
|
|
44
|
+
*/
|
|
45
|
+
class H2Pool {
|
|
46
|
+
sessions = new Map();
|
|
47
|
+
inflight = new Map();
|
|
48
|
+
_establish = null;
|
|
49
|
+
/**
|
|
50
|
+
* Lazily resolve the establish function so the http2 / tls / dns modules
|
|
51
|
+
* are only imported in Node.js environments.
|
|
52
|
+
*/
|
|
53
|
+
async establish(domain) {
|
|
54
|
+
if (!this._establish) {
|
|
55
|
+
const { establishH2 } = await Promise.resolve().then(() => __importStar(require("./h2warm.js")));
|
|
56
|
+
this._establish = establishH2;
|
|
57
|
+
}
|
|
58
|
+
return this._establish(domain);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Fire-and-forget background warming. Safe to call multiple times for
|
|
62
|
+
* the same domain — only one connection attempt per domain at a time.
|
|
63
|
+
*/
|
|
64
|
+
warm(domain) {
|
|
65
|
+
const existing = this.sessions.get(domain);
|
|
66
|
+
if (existing && !existing.closed && !existing.destroyed)
|
|
67
|
+
return;
|
|
68
|
+
if (this.inflight.has(domain))
|
|
69
|
+
return;
|
|
70
|
+
const p = this.establish(domain)
|
|
71
|
+
.then((session) => {
|
|
72
|
+
this.sessions.set(domain, session);
|
|
73
|
+
return session;
|
|
74
|
+
})
|
|
75
|
+
.catch(() => null)
|
|
76
|
+
.finally(() => {
|
|
77
|
+
this.inflight.delete(domain);
|
|
78
|
+
});
|
|
79
|
+
this.inflight.set(domain, p);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Synchronous cache check. Returns a live cached session or `null`.
|
|
83
|
+
* Never blocks, never establishes — use for non-blocking fast paths.
|
|
84
|
+
*/
|
|
85
|
+
tryGet(domain) {
|
|
86
|
+
const cached = this.sessions.get(domain);
|
|
87
|
+
if (cached && !cached.closed && !cached.destroyed)
|
|
88
|
+
return cached;
|
|
89
|
+
this.sessions.delete(domain);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get a live H2 session for `domain`. Returns immediately from cache,
|
|
94
|
+
* joins an in-flight warming, or starts a new one.
|
|
95
|
+
*/
|
|
96
|
+
async get(domain) {
|
|
97
|
+
const fast = this.tryGet(domain);
|
|
98
|
+
if (fast)
|
|
99
|
+
return fast;
|
|
100
|
+
// Join in-flight warming if one exists
|
|
101
|
+
const pending = this.inflight.get(domain);
|
|
102
|
+
if (pending) {
|
|
103
|
+
const session = await pending;
|
|
104
|
+
if (session && !session.closed && !session.destroyed)
|
|
105
|
+
return session;
|
|
106
|
+
}
|
|
107
|
+
// Start fresh, deduplicating concurrent callers via inflight
|
|
108
|
+
// Re-check: another caller may have started a fresh one while we awaited
|
|
109
|
+
const existingInflight = this.inflight.get(domain);
|
|
110
|
+
if (existingInflight)
|
|
111
|
+
return existingInflight;
|
|
112
|
+
const freshCached = this.tryGet(domain);
|
|
113
|
+
if (freshCached)
|
|
114
|
+
return freshCached;
|
|
115
|
+
const p = this.establish(domain)
|
|
116
|
+
.then((session) => {
|
|
117
|
+
this.sessions.set(domain, session);
|
|
118
|
+
return session;
|
|
119
|
+
})
|
|
120
|
+
.catch(() => null)
|
|
121
|
+
.finally(() => {
|
|
122
|
+
this.inflight.delete(domain);
|
|
123
|
+
});
|
|
124
|
+
this.inflight.set(domain, p);
|
|
125
|
+
return p;
|
|
126
|
+
}
|
|
127
|
+
/** Close all sessions (for cleanup). */
|
|
128
|
+
closeAll() {
|
|
129
|
+
for (const [, session] of this.sessions) {
|
|
130
|
+
if (!session.closed && !session.destroyed)
|
|
131
|
+
session.close();
|
|
132
|
+
}
|
|
133
|
+
this.sessions.clear();
|
|
134
|
+
this.inflight.clear();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
exports.h2Pool = new H2Pool();
|
|
@@ -0,0 +1,54 @@
|
|
|
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.establishH2 = establishH2;
|
|
7
|
+
const promises_1 = __importDefault(require("dns/promises"));
|
|
8
|
+
const http2_1 = __importDefault(require("http2"));
|
|
9
|
+
const tls_1 = __importDefault(require("tls"));
|
|
10
|
+
async function establishH2(sniHostname) {
|
|
11
|
+
let timedOut = false;
|
|
12
|
+
let timer;
|
|
13
|
+
const timeout = new Promise((_, reject) => {
|
|
14
|
+
timer = setTimeout(() => {
|
|
15
|
+
timedOut = true;
|
|
16
|
+
reject(new Error("H2 warm-up timed out"));
|
|
17
|
+
}, 5000);
|
|
18
|
+
});
|
|
19
|
+
const attempt = _establishH2(sniHostname).then((session) => {
|
|
20
|
+
// If the timeout already fired, destroy the orphaned session immediately
|
|
21
|
+
if (timedOut) {
|
|
22
|
+
session.destroy();
|
|
23
|
+
}
|
|
24
|
+
return session;
|
|
25
|
+
});
|
|
26
|
+
return Promise.race([attempt, timeout]).finally(() => clearTimeout(timer));
|
|
27
|
+
}
|
|
28
|
+
async function _establishH2(sniHostname) {
|
|
29
|
+
const { address } = await promises_1.default.lookup(sniHostname);
|
|
30
|
+
const tlsSocket = tls_1.default.connect({
|
|
31
|
+
host: address,
|
|
32
|
+
port: 443,
|
|
33
|
+
servername: sniHostname,
|
|
34
|
+
ALPNProtocols: ["h2"],
|
|
35
|
+
});
|
|
36
|
+
const session = http2_1.default.connect(`https://${sniHostname}:443`, {
|
|
37
|
+
createConnection: () => tlsSocket,
|
|
38
|
+
});
|
|
39
|
+
await new Promise((resolve, reject) => {
|
|
40
|
+
session.on("connect", resolve);
|
|
41
|
+
session.on("error", (err) => {
|
|
42
|
+
// Ensure the TLS socket is cleaned up on connection error
|
|
43
|
+
if (!tlsSocket.destroyed)
|
|
44
|
+
tlsSocket.destroy();
|
|
45
|
+
reject(err);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
// Complete the SETTINGS exchange so the first real request has zero
|
|
49
|
+
// protocol overhead. This RTT is hidden by the parallel createSandbox() call.
|
|
50
|
+
await new Promise((resolve) => session.ping(() => resolve()));
|
|
51
|
+
// Unref so the session doesn't prevent process exit
|
|
52
|
+
session.unref();
|
|
53
|
+
return session;
|
|
54
|
+
}
|
|
@@ -9,8 +9,8 @@ const index_js_1 = require("../authentication/index.js");
|
|
|
9
9
|
const env_js_1 = require("../common/env.js");
|
|
10
10
|
const node_js_1 = require("../common/node.js");
|
|
11
11
|
// Build info - these placeholders are replaced at build time by build:replace-imports
|
|
12
|
-
const BUILD_VERSION = "0.2.71
|
|
13
|
-
const BUILD_COMMIT = "
|
|
12
|
+
const BUILD_VERSION = "0.2.71";
|
|
13
|
+
const BUILD_COMMIT = "625389c3b067d3817cf11ae51e8032af6d8fab56";
|
|
14
14
|
const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
|
|
15
15
|
// Cache for config.yaml tracking value
|
|
16
16
|
let configTrackingValue = null;
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SandboxAction = exports.ResponseError = void 0;
|
|
4
4
|
const client_fetch_1 = require("@hey-api/client-fetch");
|
|
5
|
+
const interceptors_js_1 = require("../client/interceptors.js");
|
|
6
|
+
const responseInterceptor_js_1 = require("../client/responseInterceptor.js");
|
|
7
|
+
const h2fetch_js_1 = require("../common/h2fetch.js");
|
|
5
8
|
const internal_js_1 = require("../common/internal.js");
|
|
6
9
|
const settings_js_1 = require("../common/settings.js");
|
|
7
10
|
const client_gen_js_1 = require("./client/client.gen.js");
|
|
@@ -32,6 +35,7 @@ class ResponseError extends Error {
|
|
|
32
35
|
exports.ResponseError = ResponseError;
|
|
33
36
|
class SandboxAction {
|
|
34
37
|
sandbox;
|
|
38
|
+
_h2Client = null;
|
|
35
39
|
constructor(sandbox) {
|
|
36
40
|
this.sandbox = sandbox;
|
|
37
41
|
}
|
|
@@ -58,8 +62,37 @@ class SandboxAction {
|
|
|
58
62
|
headers: this.sandbox.headers,
|
|
59
63
|
});
|
|
60
64
|
}
|
|
65
|
+
const session = this.sandbox.h2Session;
|
|
66
|
+
if (session && !session.closed && !session.destroyed) {
|
|
67
|
+
if (!this._h2Client) {
|
|
68
|
+
this._h2Client = (0, client_fetch_1.createClient)({
|
|
69
|
+
fetch: (0, h2fetch_js_1.createH2Fetch)(session),
|
|
70
|
+
});
|
|
71
|
+
for (const interceptor of interceptors_js_1.interceptors) {
|
|
72
|
+
// @ts-expect-error - Interceptor is not typed
|
|
73
|
+
this._h2Client.interceptors.request.use(interceptor);
|
|
74
|
+
}
|
|
75
|
+
for (const interceptor of responseInterceptor_js_1.responseInterceptors) {
|
|
76
|
+
this._h2Client.interceptors.response.use(interceptor);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return this._h2Client;
|
|
80
|
+
}
|
|
81
|
+
// Invalidate cached H2 client when session is no longer usable
|
|
82
|
+
this._h2Client = null;
|
|
61
83
|
return client_gen_js_1.client;
|
|
62
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Routes through the H2 session when available, falling back to
|
|
87
|
+
* globalThis.fetch. Uses a direct H2 path that avoids Request allocation.
|
|
88
|
+
*/
|
|
89
|
+
h2Fetch(input, init) {
|
|
90
|
+
const session = this.sandbox.h2Session;
|
|
91
|
+
if (session && !session.closed && !session.destroyed) {
|
|
92
|
+
return (0, h2fetch_js_1.h2RequestDirect)(session, input.toString(), init);
|
|
93
|
+
}
|
|
94
|
+
return globalThis.fetch(input, init);
|
|
95
|
+
}
|
|
63
96
|
get forcedUrl() {
|
|
64
97
|
if (this.sandbox.forceUrl)
|
|
65
98
|
return this.sandbox.forceUrl;
|
|
@@ -17,7 +17,7 @@ class SandboxDrive extends action_js_1.SandboxAction {
|
|
|
17
17
|
mountPath: request.mountPath,
|
|
18
18
|
drivePath: request.drivePath || "/",
|
|
19
19
|
};
|
|
20
|
-
const response = await
|
|
20
|
+
const response = await this.h2Fetch(`${this.url}/drives/mount`, {
|
|
21
21
|
method: 'POST',
|
|
22
22
|
headers: {
|
|
23
23
|
...headers,
|
|
@@ -40,7 +40,7 @@ class SandboxDrive extends action_js_1.SandboxAction {
|
|
|
40
40
|
const normalizedPath = mountPath.startsWith('/') ? mountPath : `/${mountPath}`;
|
|
41
41
|
// Remove leading slash for URL (DELETE /drives/mnt/test not /drives//mnt/test)
|
|
42
42
|
const urlPath = normalizedPath.substring(1);
|
|
43
|
-
const response = await
|
|
43
|
+
const response = await this.h2Fetch(`${this.url}/drives/mount/${urlPath}`, {
|
|
44
44
|
method: 'DELETE',
|
|
45
45
|
headers,
|
|
46
46
|
});
|
|
@@ -55,7 +55,7 @@ class SandboxDrive extends action_js_1.SandboxAction {
|
|
|
55
55
|
*/
|
|
56
56
|
async list() {
|
|
57
57
|
const headers = this.sandbox.forceUrl ? this.sandbox.headers : settings_js_1.settings.headers;
|
|
58
|
-
const response = await
|
|
58
|
+
const response = await this.h2Fetch(`${this.url}/drives/mount`, {
|
|
59
59
|
method: 'GET',
|
|
60
60
|
headers,
|
|
61
61
|
});
|
|
@@ -101,8 +101,7 @@ class SandboxFileSystem extends action_js_1.SandboxAction {
|
|
|
101
101
|
if (this.forcedUrl) {
|
|
102
102
|
url = `${this.forcedUrl.toString()}/filesystem/${path}`;
|
|
103
103
|
}
|
|
104
|
-
|
|
105
|
-
const response = await fetch(url, {
|
|
104
|
+
const response = await this.h2Fetch(url, {
|
|
106
105
|
method: 'PUT',
|
|
107
106
|
headers: {
|
|
108
107
|
...settings_js_1.settings.headers,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CodeInterpreter = void 0;
|
|
4
|
+
const h2fetch_js_1 = require("../common/h2fetch.js");
|
|
4
5
|
const logger_js_1 = require("../common/logger.js");
|
|
5
6
|
const settings_js_1 = require("../common/settings.js");
|
|
6
7
|
const sandbox_js_1 = require("./sandbox.js");
|
|
@@ -26,6 +27,7 @@ class CodeInterpreter extends sandbox_js_1.SandboxInstance {
|
|
|
26
27
|
spec: base.spec,
|
|
27
28
|
status: base.status,
|
|
28
29
|
events: base.events,
|
|
30
|
+
h2Session: base.h2Session,
|
|
29
31
|
};
|
|
30
32
|
return new CodeInterpreter(config);
|
|
31
33
|
}
|
|
@@ -83,6 +85,7 @@ class CodeInterpreter extends sandbox_js_1.SandboxInstance {
|
|
|
83
85
|
spec: baseInstance.spec,
|
|
84
86
|
status: baseInstance.status,
|
|
85
87
|
events: baseInstance.events,
|
|
88
|
+
h2Session: baseInstance.h2Session,
|
|
86
89
|
};
|
|
87
90
|
// Preserve forceUrl and headers from input if it was a dict-like object
|
|
88
91
|
if (sandbox && typeof sandbox === "object" && !Array.isArray(sandbox)) {
|
|
@@ -101,6 +104,13 @@ class CodeInterpreter extends sandbox_js_1.SandboxInstance {
|
|
|
101
104
|
get _jupyterUrl() {
|
|
102
105
|
return this.process.url;
|
|
103
106
|
}
|
|
107
|
+
_fetch(input, init) {
|
|
108
|
+
const session = this._sandboxConfig.h2Session;
|
|
109
|
+
if (session && !session.closed && !session.destroyed) {
|
|
110
|
+
return (0, h2fetch_js_1.h2RequestDirect)(session, input.toString(), init);
|
|
111
|
+
}
|
|
112
|
+
return globalThis.fetch(input, init);
|
|
113
|
+
}
|
|
104
114
|
static OutputMessage = class {
|
|
105
115
|
text;
|
|
106
116
|
timestamp;
|
|
@@ -260,7 +270,7 @@ class CodeInterpreter extends sandbox_js_1.SandboxInstance {
|
|
|
260
270
|
}, readTimeout * 1000);
|
|
261
271
|
}
|
|
262
272
|
try {
|
|
263
|
-
const response = await
|
|
273
|
+
const response = await this._fetch(`${this._jupyterUrl}/port/8888/execute`, {
|
|
264
274
|
method: "POST",
|
|
265
275
|
headers: {
|
|
266
276
|
...headers,
|
|
@@ -359,7 +369,7 @@ class CodeInterpreter extends sandbox_js_1.SandboxInstance {
|
|
|
359
369
|
}, requestTimeout * 1000);
|
|
360
370
|
}
|
|
361
371
|
try {
|
|
362
|
-
const response = await
|
|
372
|
+
const response = await this._fetch(`${this._jupyterUrl}/port/8888/contexts`, {
|
|
363
373
|
method: "POST",
|
|
364
374
|
headers: {
|
|
365
375
|
...headers,
|
|
@@ -21,7 +21,7 @@ class SandboxProcess extends action_js_1.SandboxAction {
|
|
|
21
21
|
const done = (async () => {
|
|
22
22
|
try {
|
|
23
23
|
const headers = this.sandbox.forceUrl ? this.sandbox.headers : settings_js_1.settings.headers;
|
|
24
|
-
const stream = await
|
|
24
|
+
const stream = await this.h2Fetch(`${this.url}/process/${identifier}/logs/stream`, {
|
|
25
25
|
method: 'GET',
|
|
26
26
|
signal: controller.signal,
|
|
27
27
|
headers,
|
|
@@ -123,7 +123,7 @@ class SandboxProcess extends action_js_1.SandboxAction {
|
|
|
123
123
|
async execWithStreaming(processRequest, options) {
|
|
124
124
|
const headers = this.sandbox.forceUrl ? this.sandbox.headers : settings_js_1.settings.headers;
|
|
125
125
|
const controller = new AbortController();
|
|
126
|
-
const response = await
|
|
126
|
+
const response = await this.h2Fetch(`${this.url}/process`, {
|
|
127
127
|
method: 'POST',
|
|
128
128
|
signal: controller.signal,
|
|
129
129
|
headers: {
|