@blaxel/core 0.2.52 → 0.2.53
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 +3 -0
- package/dist/cjs/common/sentry.js +226 -0
- package/dist/cjs/common/settings.js +50 -41
- package/dist/cjs/types/common/sentry.d.ts +17 -0
- package/dist/cjs/types/common/settings.d.ts +3 -1
- package/dist/cjs-browser/.tsbuildinfo +1 -1
- package/dist/cjs-browser/common/autoload.js +3 -0
- package/dist/cjs-browser/common/sentry.js +226 -0
- package/dist/cjs-browser/common/settings.js +50 -41
- package/dist/cjs-browser/types/common/sentry.d.ts +17 -0
- package/dist/cjs-browser/types/common/settings.d.ts +3 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/common/autoload.js +3 -0
- package/dist/esm/common/sentry.js +188 -0
- package/dist/esm/common/settings.js +47 -41
- package/dist/esm-browser/.tsbuildinfo +1 -1
- package/dist/esm-browser/common/autoload.js +3 -0
- package/dist/esm-browser/common/sentry.js +188 -0
- package/dist/esm-browser/common/settings.js +47 -41
- package/package.json +4 -5
|
@@ -2,6 +2,7 @@ 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 { initSentry } from "./sentry.js";
|
|
5
6
|
import { settings } from "./settings.js";
|
|
6
7
|
client.setConfig({
|
|
7
8
|
baseUrl: settings.baseUrl,
|
|
@@ -18,6 +19,8 @@ for (const interceptor of responseInterceptors) {
|
|
|
18
19
|
client.interceptors.response.use(interceptor);
|
|
19
20
|
clientSandbox.interceptors.response.use(interceptor);
|
|
20
21
|
}
|
|
22
|
+
// Initialize Sentry for SDK error tracking immediately when module loads
|
|
23
|
+
initSentry();
|
|
21
24
|
// Allow to set custom configuration for browser environment
|
|
22
25
|
export function initialize(config) {
|
|
23
26
|
settings.setConfig(config);
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { settings } from "./settings.js";
|
|
2
|
+
import * as Sentry from "@sentry/node";
|
|
3
|
+
import { makeNodeTransport } from "@sentry/node";
|
|
4
|
+
// Isolated Sentry client for SDK-only error tracking (doesn't interfere with user's Sentry)
|
|
5
|
+
let sentryClient = null;
|
|
6
|
+
const capturedExceptions = new Set();
|
|
7
|
+
let handlersRegistered = false;
|
|
8
|
+
// SDK path patterns to identify errors originating from our SDK
|
|
9
|
+
const SDK_PATTERNS = [
|
|
10
|
+
"@blaxel/",
|
|
11
|
+
"blaxel-sdk",
|
|
12
|
+
"/node_modules/@blaxel/",
|
|
13
|
+
"/@blaxel/core/",
|
|
14
|
+
"/@blaxel/telemetry/",
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Check if an error originated from the SDK based on its stack trace.
|
|
18
|
+
* Returns true if the stack trace contains any SDK-related paths.
|
|
19
|
+
*/
|
|
20
|
+
function isFromSDK(error) {
|
|
21
|
+
const stack = error.stack || "";
|
|
22
|
+
return SDK_PATTERNS.some((pattern) => stack.includes(pattern));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Initialize an isolated Sentry client for SDK error tracking.
|
|
26
|
+
* This creates a separate Sentry instance that won't interfere with any
|
|
27
|
+
* Sentry configuration the user might have in their application.
|
|
28
|
+
*/
|
|
29
|
+
export function initSentry() {
|
|
30
|
+
try {
|
|
31
|
+
// Check if tracking is disabled
|
|
32
|
+
if (!settings.tracking) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const dsn = settings.sentryDsn;
|
|
36
|
+
if (!dsn) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Create an isolated Sentry client that doesn't touch the global scope
|
|
40
|
+
// This allows users to have their own Sentry.init() without conflicts
|
|
41
|
+
sentryClient = new Sentry.NodeClient({
|
|
42
|
+
dsn,
|
|
43
|
+
environment: settings.env,
|
|
44
|
+
release: `sdk-typescript@${settings.version}`,
|
|
45
|
+
transport: makeNodeTransport,
|
|
46
|
+
stackParser: Sentry.defaultStackParser,
|
|
47
|
+
// No integrations - we handle error capturing manually
|
|
48
|
+
integrations: [],
|
|
49
|
+
// Disable traces for the SDK client
|
|
50
|
+
tracesSampleRate: 0,
|
|
51
|
+
// Filter errors before sending - only send SDK errors
|
|
52
|
+
beforeSend(event, hint) {
|
|
53
|
+
if (event.environment !== 'dev' && event.environment !== 'prod') {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const error = hint.originalException;
|
|
57
|
+
if (error instanceof Error) {
|
|
58
|
+
if (!isFromSDK(error)) {
|
|
59
|
+
// Drop errors that don't originate from SDK
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return event;
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
sentryClient.init();
|
|
67
|
+
// Set SDK-specific tags
|
|
68
|
+
const scope = new Sentry.Scope();
|
|
69
|
+
scope.setTag("blaxel.workspace", settings.workspace);
|
|
70
|
+
scope.setTag("blaxel.version", settings.version);
|
|
71
|
+
scope.setTag("blaxel.commit", settings.commit);
|
|
72
|
+
scope.setClient(sentryClient);
|
|
73
|
+
// Register process handlers for uncaught errors (Node.js only)
|
|
74
|
+
// Only register once to prevent memory leaks
|
|
75
|
+
if (typeof process !== "undefined" &&
|
|
76
|
+
typeof process.on === "function" &&
|
|
77
|
+
!handlersRegistered) {
|
|
78
|
+
handlersRegistered = true;
|
|
79
|
+
// For SIGTERM/SIGINT, flush before exit
|
|
80
|
+
const signalHandler = (signal) => {
|
|
81
|
+
flushSentry(500)
|
|
82
|
+
.catch(() => {
|
|
83
|
+
// Silently fail
|
|
84
|
+
})
|
|
85
|
+
.finally(() => {
|
|
86
|
+
process.exit(signal === "SIGTERM" ? 143 : 130);
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
// Uncaught exception handler - only capture SDK errors
|
|
90
|
+
const uncaughtExceptionHandler = (error) => {
|
|
91
|
+
if (isFromSDK(error)) {
|
|
92
|
+
captureException(error);
|
|
93
|
+
}
|
|
94
|
+
// Let the default Node.js behavior handle the process exit
|
|
95
|
+
};
|
|
96
|
+
// Unhandled rejection handler - only capture SDK errors
|
|
97
|
+
const unhandledRejectionHandler = (reason) => {
|
|
98
|
+
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
99
|
+
if (isFromSDK(error)) {
|
|
100
|
+
captureException(error);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
process.on("SIGTERM", () => signalHandler("SIGTERM"));
|
|
104
|
+
process.on("SIGINT", () => signalHandler("SIGINT"));
|
|
105
|
+
process.on("uncaughtException", uncaughtExceptionHandler);
|
|
106
|
+
process.on("unhandledRejection", unhandledRejectionHandler);
|
|
107
|
+
// Intercept console.error to capture SDK errors that are caught and logged
|
|
108
|
+
const originalConsoleError = console.error;
|
|
109
|
+
console.error = function (...args) {
|
|
110
|
+
// Call the original console.error first
|
|
111
|
+
originalConsoleError.apply(console, args);
|
|
112
|
+
// Check if any argument is an Error from SDK and capture it
|
|
113
|
+
for (const arg of args) {
|
|
114
|
+
if (arg instanceof Error && isFromSDK(arg)) {
|
|
115
|
+
captureException(arg);
|
|
116
|
+
break; // Only capture the first SDK error to avoid duplicates
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
// Silently fail - Sentry initialization should never break the SDK
|
|
124
|
+
if (settings.env !== "production") {
|
|
125
|
+
console.error("[Blaxel SDK] Error initializing Sentry:", error);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Capture an exception to the SDK's isolated Sentry client.
|
|
131
|
+
* Only errors originating from SDK code will be captured.
|
|
132
|
+
*
|
|
133
|
+
* @param error - The error to capture
|
|
134
|
+
*/
|
|
135
|
+
function captureException(error) {
|
|
136
|
+
if (sentryClient === null) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Double-check that error is from SDK (defense in depth)
|
|
140
|
+
if (!isFromSDK(error)) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
// Create a unique identifier for this exception to avoid duplicates
|
|
145
|
+
const errorKey = `${error.name}:${error.message}:${error.stack?.slice(0, 200)}`;
|
|
146
|
+
if (capturedExceptions.has(errorKey)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
capturedExceptions.add(errorKey);
|
|
150
|
+
// Clean up old exception keys to prevent memory leak
|
|
151
|
+
if (capturedExceptions.size > 1000) {
|
|
152
|
+
capturedExceptions.clear();
|
|
153
|
+
}
|
|
154
|
+
// Create a scope with SDK tags and capture the exception
|
|
155
|
+
const scope = new Sentry.Scope();
|
|
156
|
+
scope.setTag("blaxel.workspace", settings.workspace);
|
|
157
|
+
scope.setTag("blaxel.version", settings.version);
|
|
158
|
+
scope.setTag("blaxel.commit", settings.commit);
|
|
159
|
+
scope.setClient(sentryClient);
|
|
160
|
+
scope.captureException(error);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Silently fail - error capturing should never break the SDK
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Flush pending Sentry events.
|
|
168
|
+
* This should be called before the process exits to ensure all events are sent.
|
|
169
|
+
*
|
|
170
|
+
* @param timeout - Maximum time in milliseconds to wait for flush (default: 2000)
|
|
171
|
+
*/
|
|
172
|
+
export async function flushSentry(timeout = 2000) {
|
|
173
|
+
if (sentryClient === null) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
await sentryClient.flush(timeout);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Silently fail
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Check if Sentry is initialized and available.
|
|
185
|
+
*/
|
|
186
|
+
export function isSentryInitialized() {
|
|
187
|
+
return sentryClient !== null;
|
|
188
|
+
}
|
|
@@ -1,24 +1,35 @@
|
|
|
1
|
+
import yaml from 'yaml';
|
|
1
2
|
import { authentication } from "../authentication/index.js";
|
|
2
3
|
import { env } from "../common/env.js";
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
import { fs, os, path } from "../common/node.js";
|
|
5
|
+
// Build info - these placeholders are replaced at build time by build:replace-imports
|
|
6
|
+
const BUILD_VERSION = "0.2.53";
|
|
7
|
+
const BUILD_COMMIT = "1a161063708104c65ae116d189af51e3b9cc5ef7";
|
|
8
|
+
const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
|
|
9
|
+
// Cache for config.yaml tracking value
|
|
10
|
+
let configTrackingValue = null;
|
|
11
|
+
let configTrackingLoaded = false;
|
|
12
|
+
function getConfigTracking() {
|
|
13
|
+
if (configTrackingLoaded) {
|
|
14
|
+
return configTrackingValue;
|
|
15
|
+
}
|
|
16
|
+
configTrackingLoaded = true;
|
|
17
|
+
if (os === null || fs === null || path === null) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
5
20
|
try {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return
|
|
12
|
-
}
|
|
13
|
-
else {
|
|
14
|
-
// ESM environment - return unknown
|
|
15
|
-
return "unknown";
|
|
21
|
+
const homeDir = os.homedir();
|
|
22
|
+
const config = fs.readFileSync(path.join(homeDir, ".blaxel/config.yaml"), "utf8");
|
|
23
|
+
const configJson = yaml.parse(config);
|
|
24
|
+
if (typeof configJson.tracking === 'boolean') {
|
|
25
|
+
configTrackingValue = configJson.tracking;
|
|
26
|
+
return configTrackingValue;
|
|
16
27
|
}
|
|
17
28
|
}
|
|
18
29
|
catch {
|
|
19
|
-
//
|
|
20
|
-
return "unknown";
|
|
30
|
+
// If any error, return null
|
|
21
31
|
}
|
|
32
|
+
return null;
|
|
22
33
|
}
|
|
23
34
|
// Function to get OS and architecture
|
|
24
35
|
function getOsArch() {
|
|
@@ -52,30 +63,9 @@ function getOsArch() {
|
|
|
52
63
|
}
|
|
53
64
|
return "browser/unknown";
|
|
54
65
|
}
|
|
55
|
-
// Function to get commit hash
|
|
56
|
-
function getCommitHash() {
|
|
57
|
-
try {
|
|
58
|
-
// Check if require is available (CommonJS environment)
|
|
59
|
-
if (typeof require !== "undefined") {
|
|
60
|
-
// Try to require package.json and look for commit field (set during build)
|
|
61
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
62
|
-
const packageJson = {"version":"0.2.52","commit":"4acd7b5efa6c9e04c5131f55e7a87029c4889b1d"};
|
|
63
|
-
// Check for commit in various possible locations
|
|
64
|
-
const commit = packageJson.commit || packageJson.buildInfo?.commit;
|
|
65
|
-
if (commit) {
|
|
66
|
-
return commit.length > 7 ? commit.substring(0, 7) : commit;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
// Fallback for browser environments or if require fails
|
|
72
|
-
}
|
|
73
|
-
return "unknown";
|
|
74
|
-
}
|
|
75
66
|
class Settings {
|
|
76
67
|
credentials;
|
|
77
68
|
config;
|
|
78
|
-
_version = null;
|
|
79
69
|
constructor() {
|
|
80
70
|
this.credentials = authentication();
|
|
81
71
|
this.config = {
|
|
@@ -130,18 +120,21 @@ class Settings {
|
|
|
130
120
|
return this.credentials.token;
|
|
131
121
|
}
|
|
132
122
|
get version() {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
123
|
+
return BUILD_VERSION || "unknown";
|
|
124
|
+
}
|
|
125
|
+
get commit() {
|
|
126
|
+
const commit = BUILD_COMMIT || "unknown";
|
|
127
|
+
return commit.length > 7 ? commit.substring(0, 7) : commit;
|
|
128
|
+
}
|
|
129
|
+
get sentryDsn() {
|
|
130
|
+
return BUILD_SENTRY_DSN || "";
|
|
137
131
|
}
|
|
138
132
|
get headers() {
|
|
139
133
|
const osArch = getOsArch();
|
|
140
|
-
const commitHash = getCommitHash();
|
|
141
134
|
return {
|
|
142
135
|
"x-blaxel-authorization": this.authorization,
|
|
143
136
|
"x-blaxel-workspace": this.workspace || "",
|
|
144
|
-
"User-Agent": `blaxel/sdk/typescript/${this.version} (${osArch}) blaxel/${
|
|
137
|
+
"User-Agent": `blaxel/sdk/typescript/${this.version} (${osArch}) blaxel/${this.commit}`,
|
|
145
138
|
};
|
|
146
139
|
}
|
|
147
140
|
get name() {
|
|
@@ -168,6 +161,19 @@ class Settings {
|
|
|
168
161
|
get loggerType() {
|
|
169
162
|
return env.BL_LOGGER || "http";
|
|
170
163
|
}
|
|
164
|
+
get tracking() {
|
|
165
|
+
// Environment variable has highest priority
|
|
166
|
+
if (env.BL_TRACKING !== undefined) {
|
|
167
|
+
return env.BL_TRACKING === "true";
|
|
168
|
+
}
|
|
169
|
+
// Then check config.yaml
|
|
170
|
+
const configValue = getConfigTracking();
|
|
171
|
+
if (configValue !== null) {
|
|
172
|
+
return configValue;
|
|
173
|
+
}
|
|
174
|
+
// Default to true if neither is set
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
171
177
|
async authenticate() {
|
|
172
178
|
await this.credentials.authenticate();
|
|
173
179
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blaxel/core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.53",
|
|
4
4
|
"description": "Blaxel Core SDK for TypeScript",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Blaxel, INC (https://blaxel.ai)",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@hey-api/client-fetch": "^0.10.0",
|
|
55
55
|
"@modelcontextprotocol/sdk": "^1.20.0",
|
|
56
|
+
"@sentry/node": "^10.27.0",
|
|
56
57
|
"axios": "^1.9.0",
|
|
57
58
|
"dotenv": "^16.5.0",
|
|
58
59
|
"form-data": "^4.0.2",
|
|
@@ -74,18 +75,16 @@
|
|
|
74
75
|
"vite": "^5.2.0",
|
|
75
76
|
"vitest": "^1.5.0"
|
|
76
77
|
},
|
|
77
|
-
"commit": "4acd7b5efa6c9e04c5131f55e7a87029c4889b1d",
|
|
78
78
|
"scripts": {
|
|
79
79
|
"lint": "eslint src/",
|
|
80
80
|
"dev": "tsc --watch",
|
|
81
|
-
"build": "npm run build:
|
|
81
|
+
"build": "npm run build:cjs && npm run build:esm && npm run build:fix-esm && npm run build:esm-package && npm run build:replace-imports && npm run build:browser",
|
|
82
82
|
"build:cjs": "NODE_OPTIONS='--max-old-space-size=8192' tsc -p tsconfig.cjs.json",
|
|
83
83
|
"build:esm": "NODE_OPTIONS='--max-old-space-size=8192' tsc -p tsconfig.esm.json",
|
|
84
84
|
"build:fix-esm": "node fix-esm-imports.js",
|
|
85
85
|
"build:esm-package": "echo '{\"type\":\"module\"}' > dist/esm/package.json",
|
|
86
86
|
"build:browser": "node fix-browser-imports.js",
|
|
87
|
-
"build:
|
|
88
|
-
"build:replace-imports": "node -e \"const fs=require('fs'),path=require('path'),pkg=require('./package.json');const paths=['dist/cjs/common/settings.js','dist/esm/common/settings.js','dist/cjs-browser/common/settings.js','dist/esm-browser/common/settings.js'];paths.forEach(settingsPath=>{if(fs.existsSync(settingsPath)){let content=fs.readFileSync(settingsPath,'utf8');content=content.replace(/require\\(\\\".*?\\/package\\.json\\\"\\)/g,JSON.stringify({version:pkg.version,commit:pkg.commit||'unknown'}));fs.writeFileSync(settingsPath,content);console.log('✅ Replaced imports in',settingsPath);}});\"",
|
|
87
|
+
"build:replace-imports": "node -e \"const fs=require('fs'),{execSync}=require('child_process'),pkg=require('./package.json');let commit='unknown';try{commit=execSync('git rev-parse HEAD',{encoding:'utf8'}).trim()}catch(e){console.log('⚠️ Could not get commit:',e.message)}const sentryDsn=process.env.SENTRY_DSN||'';const paths=['dist/cjs/common/settings.js','dist/esm/common/settings.js','dist/cjs-browser/common/settings.js','dist/esm-browser/common/settings.js'];paths.forEach(p=>{if(fs.existsSync(p)){let c=fs.readFileSync(p,'utf8');c=c.replace(/__BUILD_VERSION__/g,pkg.version);c=c.replace(/__BUILD_COMMIT__/g,commit);c=c.replace(/__BUILD_SENTRY_DSN__/g,sentryDsn);fs.writeFileSync(p,c);console.log('✅ Injected build info in',p,'(v'+pkg.version+', commit:'+commit.substring(0,7)+')');}});\"",
|
|
89
88
|
"test": "vitest --run",
|
|
90
89
|
"test:watch": "vitest --watch"
|
|
91
90
|
}
|