@blaxel/core 0.3.25 → 0.3.26-preview.310
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/error-types.js +133 -0
- package/dist/cjs/common/settings.js +33 -3
- package/dist/cjs/common/settings.test.js +70 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/sandbox/index.js +1 -0
- package/dist/cjs/types/common/error-types.d.ts +159 -0
- package/dist/cjs/types/common/settings.d.ts +16 -0
- package/dist/cjs/types/index.d.ts +1 -0
- package/dist/cjs/types/sandbox/index.d.ts +1 -0
- package/dist/cjs-browser/.tsbuildinfo +1 -1
- package/dist/cjs-browser/common/error-types.js +133 -0
- package/dist/cjs-browser/common/settings.js +33 -3
- package/dist/cjs-browser/common/settings.test.js +70 -0
- package/dist/cjs-browser/index.js +1 -0
- package/dist/cjs-browser/sandbox/index.js +1 -0
- package/dist/cjs-browser/types/common/error-types.d.ts +159 -0
- package/dist/cjs-browser/types/common/settings.d.ts +16 -0
- package/dist/cjs-browser/types/index.d.ts +1 -0
- package/dist/cjs-browser/types/sandbox/index.d.ts +1 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/common/error-types.js +126 -0
- package/dist/esm/common/settings.js +33 -3
- package/dist/esm/common/settings.test.js +71 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/sandbox/index.js +1 -0
- package/dist/esm-browser/.tsbuildinfo +1 -1
- package/dist/esm-browser/common/error-types.js +126 -0
- package/dist/esm-browser/common/settings.js +33 -3
- package/dist/esm-browser/common/settings.test.js +71 -1
- package/dist/esm-browser/index.js +1 -0
- package/dist/esm-browser/sandbox/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ResponseError } from "../sandbox/action.js";
|
|
2
|
+
import { SandboxCreationTimeoutError } from "../sandbox/sandbox.js";
|
|
3
|
+
function readProperty(value, key) {
|
|
4
|
+
try {
|
|
5
|
+
return typeof value === "object" && value !== null
|
|
6
|
+
? value[key]
|
|
7
|
+
: undefined;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// Catch hostile getters and revoked proxies, including in custom transports.
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const isCode = (value) => (typeof value === "string" && value.length > 0) ||
|
|
15
|
+
(typeof value === "number" && Number.isFinite(value));
|
|
16
|
+
const isStatus = (value) => typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599;
|
|
17
|
+
/**
|
|
18
|
+
* True only for a value that carries a Blaxel error code or HTTP status in a
|
|
19
|
+
* shape the SDK throws for API failures: a `ResponseError` (including
|
|
20
|
+
* `SandboxGatewayError`), a `SandboxCreationTimeoutError`, or a raw control-plane
|
|
21
|
+
* error body (`{ error, code }`, `{ code, message, status_code }` or
|
|
22
|
+
* `{ error: { code, message, status } }`). Plain `Error`s (including network
|
|
23
|
+
* errors with a `code` such as `ECONNRESET`), strings, raw HTML/text bodies, empty
|
|
24
|
+
* bodies and a bare `{ error: string }` are false: they carry neither. Matching
|
|
25
|
+
* `ResponseError` uses `instanceof`, so an error thrown by a second copy of
|
|
26
|
+
* `@blaxel/core` does not match. Does not wrap or mutate the value. Never throws.
|
|
27
|
+
* Like `is_blaxel_error` in the Python SDK, it is false for plain exceptions and
|
|
28
|
+
* strings.
|
|
29
|
+
*/
|
|
30
|
+
export function isBlaxelError(err) {
|
|
31
|
+
try {
|
|
32
|
+
if (err instanceof Error) {
|
|
33
|
+
return err instanceof ResponseError || err instanceof SandboxCreationTimeoutError;
|
|
34
|
+
}
|
|
35
|
+
if (typeof err !== "object" || err === null || Array.isArray(err))
|
|
36
|
+
return false;
|
|
37
|
+
const error = readProperty(err, "error");
|
|
38
|
+
if (typeof error === "string")
|
|
39
|
+
return isStatus(readProperty(err, "code"));
|
|
40
|
+
if (typeof readProperty(err, "code") === "string" &&
|
|
41
|
+
typeof readProperty(err, "message") === "string" &&
|
|
42
|
+
isStatus(readProperty(err, "status_code")))
|
|
43
|
+
return true;
|
|
44
|
+
return (typeof error === "object" && error !== null && !Array.isArray(error) &&
|
|
45
|
+
typeof readProperty(error, "code") === "string" &&
|
|
46
|
+
typeof readProperty(error, "message") === "string" &&
|
|
47
|
+
isStatus(readProperty(error, "status")));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Fixed-depth inspection: ResponseError carries `error`/`data`; gateway bodies
|
|
54
|
+
// nest their fields once more in `error`. No recursion, including on cyclic input.
|
|
55
|
+
function errorCandidates(err) {
|
|
56
|
+
const bodies = [err, readProperty(err, "error"), readProperty(err, "data")];
|
|
57
|
+
return bodies.flatMap((body) => [body, readProperty(body, "error")]);
|
|
58
|
+
}
|
|
59
|
+
function readHeader(err, name) {
|
|
60
|
+
try {
|
|
61
|
+
const headers = readProperty(readProperty(err, "response"), "headers");
|
|
62
|
+
const get = readProperty(headers, "get");
|
|
63
|
+
if (typeof get !== "function")
|
|
64
|
+
return undefined;
|
|
65
|
+
const value = Reflect.apply(get, headers, [name]);
|
|
66
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Read a code from the existing error/body, then X-Blaxel-Error-Code. Never throws. */
|
|
73
|
+
export function getBlaxelErrorCode(err) {
|
|
74
|
+
for (const candidate of errorCandidates(err)) {
|
|
75
|
+
const code = readProperty(candidate, "code");
|
|
76
|
+
if (isCode(code))
|
|
77
|
+
return code;
|
|
78
|
+
}
|
|
79
|
+
return readHeader(err, "x-blaxel-error-code");
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Read HTTP status from the existing error/response, then the body (`status`,
|
|
83
|
+
* `status_code`, or a numeric `code`). Raw text and plain Error have none.
|
|
84
|
+
* Never throws.
|
|
85
|
+
*/
|
|
86
|
+
export function getBlaxelErrorStatus(err) {
|
|
87
|
+
const status = readProperty(err, "status");
|
|
88
|
+
if (isStatus(status))
|
|
89
|
+
return status;
|
|
90
|
+
const responseStatus = readProperty(readProperty(err, "response"), "status");
|
|
91
|
+
if (isStatus(responseStatus))
|
|
92
|
+
return responseStatus;
|
|
93
|
+
for (const candidate of errorCandidates(err)) {
|
|
94
|
+
for (const key of ["status", "status_code", "code"]) {
|
|
95
|
+
const value = readProperty(candidate, key);
|
|
96
|
+
if (isStatus(value))
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Read the existing Error.message, body message/error text, or raw text/HTML.
|
|
104
|
+
* Returns undefined for an empty/unrecognized body. Never throws.
|
|
105
|
+
*/
|
|
106
|
+
export function getBlaxelErrorMessage(err) {
|
|
107
|
+
for (const candidate of errorCandidates(err)) {
|
|
108
|
+
const message = typeof candidate === "string" ? candidate : readProperty(candidate, "message");
|
|
109
|
+
if (typeof message === "string" && message.length > 0)
|
|
110
|
+
return message;
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Read X-Cf-Request-Id, X-Amz-Cf-Id, then CF-Ray from a retained response.
|
|
116
|
+
* Raw control-plane bodies and SandboxCreationTimeoutError do not retain
|
|
117
|
+
* response headers, so their request ID is unavailable. Never throws.
|
|
118
|
+
*/
|
|
119
|
+
export function getBlaxelErrorRequestId(err) {
|
|
120
|
+
for (const name of ["x-cf-request-id", "x-amz-cf-id", "cf-ray"]) {
|
|
121
|
+
const value = readHeader(err, name);
|
|
122
|
+
if (value !== undefined)
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
@@ -24,8 +24,8 @@ function missingCredentialsMessage() {
|
|
|
24
24
|
return "No Blaxel credentials found. Set the BL_API_KEY and BL_WORKSPACE environment variables, or run `bl login`.";
|
|
25
25
|
}
|
|
26
26
|
// Build info - these placeholders are replaced at build time by build:replace-imports
|
|
27
|
-
const BUILD_VERSION = "0.3.
|
|
28
|
-
const BUILD_COMMIT = "
|
|
27
|
+
const BUILD_VERSION = "0.3.26-preview.310";
|
|
28
|
+
const BUILD_COMMIT = "13bd8f75d15c2c81add546bcabeee2f20a224417";
|
|
29
29
|
const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
|
|
30
30
|
const BLAXEL_API_VERSION = "2026-09-22";
|
|
31
31
|
// Bun < 1.3.11 never sends connection-level WINDOW_UPDATE: the pooled h2
|
|
@@ -94,8 +94,14 @@ function getOsArch() {
|
|
|
94
94
|
}
|
|
95
95
|
return "browser/unknown";
|
|
96
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* An integration built on this SDK identifies itself with one User-Agent
|
|
99
|
+
* product token: `<name>/<semver>`, lowercase name.
|
|
100
|
+
*/
|
|
101
|
+
const INTEGRATION_TOKEN_PATTERN = /^[a-z0-9][a-z0-9._-]*\/\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
97
102
|
class Settings {
|
|
98
103
|
_credentials;
|
|
104
|
+
_warnedIntegration = null;
|
|
99
105
|
config;
|
|
100
106
|
constructor() {
|
|
101
107
|
// `credentials` are resolved lazily on first access so that simply
|
|
@@ -190,14 +196,38 @@ class Settings {
|
|
|
190
196
|
get apiVersion() {
|
|
191
197
|
return env.BL_API_VERSION || BLAXEL_API_VERSION;
|
|
192
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Product token identifying the integration built on this SDK, or `""`.
|
|
201
|
+
* Resolved from `config.integration`, then `BL_INTEGRATION`. See `Config.integration`.
|
|
202
|
+
*/
|
|
203
|
+
get integration() {
|
|
204
|
+
const value = this.config.integration ?? env.BL_INTEGRATION ?? "";
|
|
205
|
+
if (!value) {
|
|
206
|
+
return "";
|
|
207
|
+
}
|
|
208
|
+
if (!INTEGRATION_TOKEN_PATTERN.test(value)) {
|
|
209
|
+
// Resolved on every request, so warn once per invalid value rather than per request.
|
|
210
|
+
if (this._warnedIntegration !== value) {
|
|
211
|
+
this._warnedIntegration = value;
|
|
212
|
+
logger.warn(`Ignoring invalid Blaxel integration token ${JSON.stringify(value)}: expected <name>/<semver> with a lowercase name`);
|
|
213
|
+
}
|
|
214
|
+
return "";
|
|
215
|
+
}
|
|
216
|
+
return value;
|
|
217
|
+
}
|
|
218
|
+
set integration(value) {
|
|
219
|
+
this.config.integration = value;
|
|
220
|
+
}
|
|
193
221
|
get headers() {
|
|
194
222
|
this.assertCredentials();
|
|
195
223
|
const osArch = getOsArch();
|
|
224
|
+
const sdkUserAgent = `blaxel/sdk/typescript/${this.version} (${osArch}) blaxel/${this.commit}`;
|
|
225
|
+
const integration = this.integration;
|
|
196
226
|
return {
|
|
197
227
|
"x-blaxel-authorization": this.authorization,
|
|
198
228
|
"x-blaxel-workspace": this.workspace || "",
|
|
199
229
|
"Blaxel-Version": this.apiVersion,
|
|
200
|
-
"User-Agent":
|
|
230
|
+
"User-Agent": integration ? `${sdkUserAgent} ${integration}` : sdkUserAgent,
|
|
201
231
|
};
|
|
202
232
|
}
|
|
203
233
|
get name() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { ApiKey } from '../authentication/apikey.js';
|
|
3
3
|
import { env } from './env.js';
|
|
4
4
|
import { isBrokenBunH2Runtime } from './h2-runtime.js';
|
|
@@ -216,3 +216,73 @@ describe('Settings H2 tuning knobs (config > env > default)', () => {
|
|
|
216
216
|
});
|
|
217
217
|
});
|
|
218
218
|
});
|
|
219
|
+
describe('Settings.integration', () => {
|
|
220
|
+
const sdkUserAgent = /^blaxel\/sdk\/typescript\/\S+ \([^)]+\) blaxel\/\S+/;
|
|
221
|
+
async function authenticated() {
|
|
222
|
+
const { settings } = await import('./settings.js');
|
|
223
|
+
settings.credentials = new ApiKey({ apiKey: 'test-key', workspace: 'test-ws' });
|
|
224
|
+
delete settings.config.integration;
|
|
225
|
+
delete process.env.BL_INTEGRATION;
|
|
226
|
+
return settings;
|
|
227
|
+
}
|
|
228
|
+
afterEach(async () => {
|
|
229
|
+
const { settings } = await import('./settings.js');
|
|
230
|
+
delete settings.config.integration;
|
|
231
|
+
delete process.env.BL_INTEGRATION;
|
|
232
|
+
});
|
|
233
|
+
it('sends the plain SDK User-Agent by default', async () => {
|
|
234
|
+
const settings = await authenticated();
|
|
235
|
+
expect(settings.integration).toBe('');
|
|
236
|
+
expect(settings.headers['User-Agent']).toMatch(new RegExp(sdkUserAgent.source + '$'));
|
|
237
|
+
});
|
|
238
|
+
it('appends the token set programmatically, once', async () => {
|
|
239
|
+
const settings = await authenticated();
|
|
240
|
+
settings.integration = 'deepseek-harness-blaxel-sandbox/0.1.2';
|
|
241
|
+
const userAgent = settings.headers['User-Agent'];
|
|
242
|
+
expect(userAgent).toMatch(sdkUserAgent);
|
|
243
|
+
expect(userAgent.endsWith(' deepseek-harness-blaxel-sandbox/0.1.2')).toBe(true);
|
|
244
|
+
expect(userAgent.split('deepseek-harness-blaxel-sandbox').length).toBe(2);
|
|
245
|
+
});
|
|
246
|
+
it('reads BL_INTEGRATION when nothing is set programmatically, and config wins', async () => {
|
|
247
|
+
const settings = await authenticated();
|
|
248
|
+
process.env.BL_INTEGRATION = 'herdr/1.4.0-rc.1';
|
|
249
|
+
expect(settings.headers['User-Agent'].endsWith(' herdr/1.4.0-rc.1')).toBe(true);
|
|
250
|
+
settings.setConfig({ ...settings.config, integration: 'deepseek-harness-blaxel-sandbox/0.1.2' });
|
|
251
|
+
expect(settings.headers['User-Agent'].endsWith(' deepseek-harness-blaxel-sandbox/0.1.2')).toBe(true);
|
|
252
|
+
});
|
|
253
|
+
it.each(['Deepseek/0.1.2', 'deepseek-harness', 'deepseek/0.1', 'a/1.0.0 b/1.0.0', 'deepseek/0.1.2 (extra)', ' x/1.0.0', 'x/1.0.0\n', 'x/1.0.0\r\n'])('ignores the invalid token %j', async (value) => {
|
|
254
|
+
const settings = await authenticated();
|
|
255
|
+
settings.integration = value;
|
|
256
|
+
expect(settings.integration).toBe('');
|
|
257
|
+
expect(settings.headers['User-Agent']).toMatch(new RegExp(sdkUserAgent.source + '$'));
|
|
258
|
+
});
|
|
259
|
+
it('changes no header other than User-Agent', async () => {
|
|
260
|
+
const settings = await authenticated();
|
|
261
|
+
const plain = { ...settings.headers };
|
|
262
|
+
settings.integration = 'deepseek-harness-blaxel-sandbox/0.1.2';
|
|
263
|
+
const tagged = { ...settings.headers };
|
|
264
|
+
delete plain['User-Agent'];
|
|
265
|
+
delete tagged['User-Agent'];
|
|
266
|
+
expect(tagged).toEqual(plain);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
describe('Settings.integration warnings', () => {
|
|
270
|
+
it('warns once per invalid value, not on every request', async () => {
|
|
271
|
+
const { settings } = await import('./settings.js');
|
|
272
|
+
const { logger } = await import('./logger.js');
|
|
273
|
+
const warn = vi.spyOn(logger, 'warn').mockImplementation(() => { });
|
|
274
|
+
try {
|
|
275
|
+
settings.integration = 'Not-Valid';
|
|
276
|
+
for (let i = 0; i < 5; i++)
|
|
277
|
+
expect(settings.integration).toBe('');
|
|
278
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
279
|
+
settings.integration = 'also bad';
|
|
280
|
+
expect(settings.integration).toBe('');
|
|
281
|
+
expect(warn).toHaveBeenCalledTimes(2);
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
warn.mockRestore();
|
|
285
|
+
delete settings.config.integration;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
});
|
package/dist/esm/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./common/env.js";
|
|
|
7
7
|
export * from "./common/h2-runtime.js";
|
|
8
8
|
export * from "./common/node.js";
|
|
9
9
|
export * from "./common/errors.js";
|
|
10
|
+
export * from "./common/error-types.js";
|
|
10
11
|
export * from "./common/internal.js";
|
|
11
12
|
export * from "./common/logger.js";
|
|
12
13
|
export * from "./common/pagination.js";
|
|
@@ -2,6 +2,7 @@ export {
|
|
|
2
2
|
/* Export SDK functions */
|
|
3
3
|
deleteFilesystemByPath, deleteNetworkProcessByPidMonitor, deleteProcessByIdentifier, deleteProcessByIdentifierKill, getFilesystemByPath, getNetworkProcessByPidPorts, putCodegenFastapplyByPath, getCodegenRerankingByPath, getProcess, getProcessByIdentifier, getProcessByIdentifierLogs, getProcessByIdentifierLogsStream, postNetworkProcessByPidMonitor, postProcess, putFilesystemByPath } from "./client/index.js";
|
|
4
4
|
export { ResponseError, SandboxGatewayError, isGatewayError, isGatewayTimeout } from "./action.js";
|
|
5
|
+
export * from "../common/error-types.js";
|
|
5
6
|
export * from "./filesystem/index.js";
|
|
6
7
|
export * from "./codegen/index.js";
|
|
7
8
|
export { SandboxDrive } from "./drive/index.js";
|