@blaxel/core 0.3.35 → 0.3.36-preview.347
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/coding-agent.js +75 -0
- package/dist/cjs/common/coding-agent.test.js +50 -0
- package/dist/cjs/common/sentry.js +20 -2
- package/dist/cjs/common/sentry.test.js +22 -2
- package/dist/cjs/common/settings.js +33 -4
- package/dist/cjs/common/settings.test.js +132 -0
- package/dist/cjs/types/common/coding-agent.d.ts +7 -0
- package/dist/cjs/types/common/coding-agent.test.d.ts +1 -0
- package/dist/cjs/types/common/settings.d.ts +6 -0
- package/dist/cjs-browser/.tsbuildinfo +1 -1
- package/dist/cjs-browser/common/coding-agent.js +75 -0
- package/dist/cjs-browser/common/coding-agent.test.js +50 -0
- package/dist/cjs-browser/common/sentry.js +20 -2
- package/dist/cjs-browser/common/sentry.test.js +22 -2
- package/dist/cjs-browser/common/settings.js +33 -4
- package/dist/cjs-browser/common/settings.test.js +132 -0
- package/dist/cjs-browser/types/common/coding-agent.d.ts +7 -0
- package/dist/cjs-browser/types/common/coding-agent.test.d.ts +1 -0
- package/dist/cjs-browser/types/common/settings.d.ts +6 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/common/coding-agent.js +71 -0
- package/dist/esm/common/coding-agent.test.js +48 -0
- package/dist/esm/common/sentry.js +20 -2
- package/dist/esm/common/sentry.test.js +22 -2
- package/dist/esm/common/settings.js +33 -4
- package/dist/esm/common/settings.test.js +132 -0
- package/dist/esm-browser/.tsbuildinfo +1 -1
- package/dist/esm-browser/common/coding-agent.js +71 -0
- package/dist/esm-browser/common/coding-agent.test.js +48 -0
- package/dist/esm-browser/common/sentry.js +20 -2
- package/dist/esm-browser/common/sentry.test.js +22 -2
- package/dist/esm-browser/common/settings.js +33 -4
- package/dist/esm-browser/common/settings.test.js +132 -0
- package/package.json +1 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CODING_AGENT_ENV_VARS = void 0;
|
|
4
|
+
exports.detectCodingAgent = detectCodingAgent;
|
|
5
|
+
/**
|
|
6
|
+
* Environment variables coding agents set for the commands they run, first
|
|
7
|
+
* match wins, with the agent id the SDK reports. Ids match the Blaxel CLI's.
|
|
8
|
+
*/
|
|
9
|
+
const CODING_AGENTS = [
|
|
10
|
+
["CLAUDECODE", "claude-code"],
|
|
11
|
+
["CODEX_THREAD_ID", "codex"],
|
|
12
|
+
["CODEX_SANDBOX", "codex"],
|
|
13
|
+
["CURSOR_AGENT", "cursor"],
|
|
14
|
+
["GEMINI_CLI", "gemini-cli"],
|
|
15
|
+
["QWEN_CODE", "qwen-code"],
|
|
16
|
+
["OPENCODE_TERMINAL", "opencode"],
|
|
17
|
+
["OPENCODE", "opencode"],
|
|
18
|
+
["GOOSE_TERMINAL", "goose"],
|
|
19
|
+
["AUGMENT_AGENT", "augment"],
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Agent names found in the generic `AI_AGENT` and `AGENT` variables, mapped to
|
|
23
|
+
* the same ids. Any other `AI_AGENT` name is reported as "other".
|
|
24
|
+
*/
|
|
25
|
+
const CODING_AGENT_NAMES = {
|
|
26
|
+
"claude-code": "claude-code",
|
|
27
|
+
claude: "claude-code",
|
|
28
|
+
codex: "codex",
|
|
29
|
+
cursor: "cursor",
|
|
30
|
+
"cursor-cli": "cursor",
|
|
31
|
+
"gemini-cli": "gemini-cli",
|
|
32
|
+
gemini: "gemini-cli",
|
|
33
|
+
"qwen-code": "qwen-code",
|
|
34
|
+
opencode: "opencode",
|
|
35
|
+
goose: "goose",
|
|
36
|
+
augment: "augment",
|
|
37
|
+
"augment-cli": "augment",
|
|
38
|
+
crush: "crush",
|
|
39
|
+
"github-copilot": "github-copilot",
|
|
40
|
+
"github-copilot-cli": "github-copilot",
|
|
41
|
+
};
|
|
42
|
+
/** Every environment variable `detectCodingAgent` reads. */
|
|
43
|
+
exports.CODING_AGENT_ENV_VARS = [
|
|
44
|
+
...CODING_AGENTS.map(([name]) => name),
|
|
45
|
+
"AI_AGENT",
|
|
46
|
+
"AGENT",
|
|
47
|
+
];
|
|
48
|
+
function agentName(value) {
|
|
49
|
+
const name = value?.trim().toLowerCase() ?? "";
|
|
50
|
+
// Claude Code sets AI_AGENT=claude-code_<version>_agent: keep the name.
|
|
51
|
+
const end = name.search(/[_/@ ]/);
|
|
52
|
+
return end > 0 ? name.slice(0, end) : name;
|
|
53
|
+
}
|
|
54
|
+
function knownAgentId(name) {
|
|
55
|
+
return Object.prototype.hasOwnProperty.call(CODING_AGENT_NAMES, name)
|
|
56
|
+
? CODING_AGENT_NAMES[name]
|
|
57
|
+
: undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Id of the coding agent whose environment the SDK runs in, "other" for an
|
|
61
|
+
* agent without a known name, or "" for none.
|
|
62
|
+
*/
|
|
63
|
+
function detectCodingAgent(getenv) {
|
|
64
|
+
for (const [name, id] of CODING_AGENTS) {
|
|
65
|
+
if (getenv(name)?.trim()) {
|
|
66
|
+
return id;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const name = agentName(getenv("AI_AGENT"));
|
|
70
|
+
if (name) {
|
|
71
|
+
return knownAgentId(name) ?? "other";
|
|
72
|
+
}
|
|
73
|
+
// AGENT is a generic name, so only a known agent name counts.
|
|
74
|
+
return knownAgentId(agentName(getenv("AGENT"))) ?? "";
|
|
75
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const coding_agent_js_1 = require("./coding-agent.js");
|
|
5
|
+
function detect(vars) {
|
|
6
|
+
return (0, coding_agent_js_1.detectCodingAgent)((name) => vars[name]);
|
|
7
|
+
}
|
|
8
|
+
(0, vitest_1.describe)("detectCodingAgent", () => {
|
|
9
|
+
(0, vitest_1.it)("returns nothing outside a coding agent", () => {
|
|
10
|
+
(0, vitest_1.expect)(detect({})).toBe("");
|
|
11
|
+
(0, vitest_1.expect)(detect({ CLAUDECODE: "", AI_AGENT: " " })).toBe("");
|
|
12
|
+
});
|
|
13
|
+
vitest_1.it.each([
|
|
14
|
+
[{ CLAUDECODE: "1" }, "claude-code"],
|
|
15
|
+
[{ CODEX_THREAD_ID: "thread" }, "codex"],
|
|
16
|
+
[{ CODEX_SANDBOX: "seatbelt" }, "codex"],
|
|
17
|
+
[{ CURSOR_AGENT: "1" }, "cursor"],
|
|
18
|
+
[{ GEMINI_CLI: "1" }, "gemini-cli"],
|
|
19
|
+
[{ QWEN_CODE: "1" }, "qwen-code"],
|
|
20
|
+
[{ OPENCODE_TERMINAL: "1" }, "opencode"],
|
|
21
|
+
[{ OPENCODE: "1", AGENT: "1" }, "opencode"],
|
|
22
|
+
[{ GOOSE_TERMINAL: "1" }, "goose"],
|
|
23
|
+
[{ AUGMENT_AGENT: "1" }, "augment"],
|
|
24
|
+
])("detects the marker %j", (vars, id) => {
|
|
25
|
+
(0, vitest_1.expect)(detect(vars)).toBe(id);
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)("prefers an agent-specific marker over the generic variables", () => {
|
|
28
|
+
(0, vitest_1.expect)(detect({ AI_AGENT: "something-else", CODEX_SANDBOX: "seatbelt" })).toBe("codex");
|
|
29
|
+
});
|
|
30
|
+
vitest_1.it.each([
|
|
31
|
+
["claude-code_2-1-306_agent", "claude-code"],
|
|
32
|
+
["Cursor", "cursor"],
|
|
33
|
+
["crush", "crush"],
|
|
34
|
+
["github-copilot-cli", "github-copilot"],
|
|
35
|
+
["some-new-agent", "other"],
|
|
36
|
+
])("maps AI_AGENT=%j to %j", (value, id) => {
|
|
37
|
+
(0, vitest_1.expect)(detect({ AI_AGENT: value })).toBe(id);
|
|
38
|
+
});
|
|
39
|
+
(0, vitest_1.it)("reads AGENT only for a known agent name", () => {
|
|
40
|
+
(0, vitest_1.expect)(detect({ AGENT: "goose" })).toBe("goose");
|
|
41
|
+
(0, vitest_1.expect)(detect({ AGENT: "1" })).toBe("");
|
|
42
|
+
(0, vitest_1.expect)(detect({ AGENT: "build-runner" })).toBe("");
|
|
43
|
+
(0, vitest_1.expect)(detect({ AGENT: "goose", AI_AGENT: "claude" })).toBe("claude-code");
|
|
44
|
+
});
|
|
45
|
+
(0, vitest_1.it)("never reports a free-form name", () => {
|
|
46
|
+
(0, vitest_1.expect)(detect({ AI_AGENT: "Jane Doe's laptop" })).toBe("other");
|
|
47
|
+
(0, vitest_1.expect)(detect({ AI_AGENT: "toString" })).toBe("other");
|
|
48
|
+
(0, vitest_1.expect)(detect({ AGENT: "toString" })).toBe("");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -187,9 +187,25 @@ function generateEventId() {
|
|
|
187
187
|
return value.toString(16);
|
|
188
188
|
});
|
|
189
189
|
}
|
|
190
|
+
const SAFE_WORKSPACE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
190
191
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
192
|
+
* The workspace name is the only user identifier, so an issue shows how many
|
|
193
|
+
* workspaces it affects. Omitted when unknown or not a workspace name.
|
|
194
|
+
*/
|
|
195
|
+
function sentryUser() {
|
|
196
|
+
try {
|
|
197
|
+
const workspace = settings_js_1.settings.workspace;
|
|
198
|
+
return typeof workspace === "string" && SAFE_WORKSPACE_NAME.test(workspace)
|
|
199
|
+
? { id: workspace }
|
|
200
|
+
: undefined;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Build an allowlisted event. Raw messages, response bodies, resource context,
|
|
208
|
+
* and absolute host paths are deliberately excluded.
|
|
193
209
|
*/
|
|
194
210
|
function errorToSentryEvent(error, ownedFrames) {
|
|
195
211
|
return {
|
|
@@ -199,6 +215,7 @@ function errorToSentryEvent(error, ownedFrames) {
|
|
|
199
215
|
level: "error",
|
|
200
216
|
environment: settings_js_1.settings.env,
|
|
201
217
|
release: `sdk-typescript@${settings_js_1.settings.version}`,
|
|
218
|
+
user: sentryUser(),
|
|
202
219
|
tags: {
|
|
203
220
|
"blaxel.version": settings_js_1.settings.version,
|
|
204
221
|
"blaxel.commit": settings_js_1.settings.commit,
|
|
@@ -292,6 +309,7 @@ function h2DegradationToSentryEvent(reason, domainTag, count) {
|
|
|
292
309
|
release: `sdk-typescript@${settings_js_1.settings.version}`,
|
|
293
310
|
message: `h2 transport degradation: ${reason}`,
|
|
294
311
|
fingerprint: ["h2-degradation", reason, domainTag],
|
|
312
|
+
user: sentryUser(),
|
|
295
313
|
tags: {
|
|
296
314
|
"blaxel.version": settings_js_1.settings.version,
|
|
297
315
|
"blaxel.commit": settings_js_1.settings.commit,
|
|
@@ -40,7 +40,7 @@ const mockSettings = vitest_1.vi.hoisted(() => ({
|
|
|
40
40
|
env: "prod",
|
|
41
41
|
version: "9.9.9",
|
|
42
42
|
commit: "abcdef0",
|
|
43
|
-
workspace: "
|
|
43
|
+
workspace: "acme-staging",
|
|
44
44
|
}));
|
|
45
45
|
vitest_1.vi.mock("./settings.js", () => ({ settings: mockSettings }));
|
|
46
46
|
const appFrameUrl = "file:///Users/customer/private-project/app.ts";
|
|
@@ -164,6 +164,8 @@ function emitUncaughtExceptionMonitor(error) {
|
|
|
164
164
|
"blaxel.error_source": "unhandled-sdk-exception",
|
|
165
165
|
});
|
|
166
166
|
(0, vitest_1.expect)(event.tags).not.toHaveProperty("blaxel.workspace");
|
|
167
|
+
// The workspace is the only user identifier, so issues count affected workspaces.
|
|
168
|
+
(0, vitest_1.expect)(event.user).toEqual({ id: "acme-staging" });
|
|
167
169
|
const frames = event.exception?.values[0]?.stacktrace.frames ?? [];
|
|
168
170
|
(0, vitest_1.expect)(frames.length).toBeGreaterThan(0);
|
|
169
171
|
for (const frame of frames) {
|
|
@@ -172,7 +174,6 @@ function emitUncaughtExceptionMonitor(error) {
|
|
|
172
174
|
const serialized = JSON.stringify(event);
|
|
173
175
|
(0, vitest_1.expect)(serialized).not.toContain("secret response body");
|
|
174
176
|
(0, vitest_1.expect)(serialized).not.toContain("customer-123");
|
|
175
|
-
(0, vitest_1.expect)(serialized).not.toContain("private-workspace");
|
|
176
177
|
(0, vitest_1.expect)(serialized).not.toContain("/Users/customer");
|
|
177
178
|
}
|
|
178
179
|
finally {
|
|
@@ -269,8 +270,27 @@ function emitUncaughtExceptionMonitor(error) {
|
|
|
269
270
|
domainTag,
|
|
270
271
|
},
|
|
271
272
|
extra: { count: 100 },
|
|
273
|
+
user: { id: "acme-staging" },
|
|
272
274
|
});
|
|
273
275
|
});
|
|
276
|
+
(0, vitest_1.it)("leaves the user out when the workspace is not a workspace name", async () => {
|
|
277
|
+
const fetchMock = createFetchMock();
|
|
278
|
+
vitest_1.vi.stubGlobal("fetch", fetchMock);
|
|
279
|
+
const workspace = mockSettings.workspace;
|
|
280
|
+
mockSettings.workspace = "/Users/customer/private project";
|
|
281
|
+
try {
|
|
282
|
+
const { flushSentry, initSentry } = await Promise.resolve().then(() => __importStar(require("./sentry.js")));
|
|
283
|
+
initSentry();
|
|
284
|
+
emitUncaughtExceptionMonitor(makeSdkError());
|
|
285
|
+
await flushSentry();
|
|
286
|
+
const event = eventFromFetch(fetchMock);
|
|
287
|
+
(0, vitest_1.expect)(event.user).toBeUndefined();
|
|
288
|
+
(0, vitest_1.expect)(JSON.stringify(event)).not.toContain("/Users/customer");
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
mockSettings.workspace = workspace;
|
|
292
|
+
}
|
|
293
|
+
});
|
|
274
294
|
(0, vitest_1.it)("groups keys independently and hashes external domains", async () => {
|
|
275
295
|
const fetchMock = createFetchMock();
|
|
276
296
|
vitest_1.vi.stubGlobal("fetch", fetchMock);
|
|
@@ -9,6 +9,7 @@ const apikey_js_1 = require("../authentication/apikey.js");
|
|
|
9
9
|
const clientcredentials_js_1 = require("../authentication/clientcredentials.js");
|
|
10
10
|
const credentials_js_1 = require("../authentication/credentials.js");
|
|
11
11
|
const index_js_1 = require("../authentication/index.js");
|
|
12
|
+
const coding_agent_js_1 = require("../common/coding-agent.js");
|
|
12
13
|
const env_js_1 = require("../common/env.js");
|
|
13
14
|
const errors_js_1 = require("../common/errors.js");
|
|
14
15
|
const h2_runtime_js_1 = require("../common/h2-runtime.js");
|
|
@@ -30,8 +31,8 @@ function missingCredentialsMessage() {
|
|
|
30
31
|
return "No Blaxel credentials found. Set the BL_API_KEY and BL_WORKSPACE environment variables, or run `bl login`.";
|
|
31
32
|
}
|
|
32
33
|
// Build info - these placeholders are replaced at build time by build:replace-imports
|
|
33
|
-
const BUILD_VERSION = "0.3.
|
|
34
|
-
const BUILD_COMMIT = "
|
|
34
|
+
const BUILD_VERSION = "0.3.36-preview.347";
|
|
35
|
+
const BUILD_COMMIT = "bb3cd5806f537daac42970497c263d4ccb4b2455";
|
|
35
36
|
const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
|
|
36
37
|
const BLAXEL_API_VERSION = "2026-09-22";
|
|
37
38
|
// Bun < 1.3.11 never sends connection-level WINDOW_UPDATE: the pooled h2
|
|
@@ -68,6 +69,18 @@ function getConfigTracking() {
|
|
|
68
69
|
}
|
|
69
70
|
return null;
|
|
70
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Whether `DO_NOT_TRACK` (`1` or `true`) opts out of the coding agent token.
|
|
74
|
+
* The process environment is checked on its own as well, so a project `.env`
|
|
75
|
+
* or `blaxel.toml` can't undo an opt-out set for the process. The saved
|
|
76
|
+
* `tracking` setting is not read: it is an opt-in for error reports, and
|
|
77
|
+
* `bl login` saves it as false until the user turns it on.
|
|
78
|
+
*/
|
|
79
|
+
function trackingOptedOut() {
|
|
80
|
+
const optsOut = (value) => value === "true" || value === "1";
|
|
81
|
+
const processValue = typeof process !== "undefined" ? process.env?.DO_NOT_TRACK : undefined;
|
|
82
|
+
return optsOut(processValue) || optsOut(env_js_1.env.DO_NOT_TRACK);
|
|
83
|
+
}
|
|
71
84
|
// Function to get OS and architecture
|
|
72
85
|
function getOsArch() {
|
|
73
86
|
try {
|
|
@@ -224,16 +237,32 @@ class Settings {
|
|
|
224
237
|
set integration(value) {
|
|
225
238
|
this.config.integration = value;
|
|
226
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Id of the coding agent (Claude Code, Codex, Cursor, ...) whose environment
|
|
242
|
+
* runs the SDK, `"other"` for an agent without a known name, or `""` for none
|
|
243
|
+
* or when telemetry is opted out. Sent as an `agent/<id>` User-Agent token.
|
|
244
|
+
*/
|
|
245
|
+
get codingAgent() {
|
|
246
|
+
const agent = (0, coding_agent_js_1.detectCodingAgent)((name) => env_js_1.env[name]);
|
|
247
|
+
return agent && !trackingOptedOut() ? agent : "";
|
|
248
|
+
}
|
|
227
249
|
get headers() {
|
|
228
250
|
this.assertCredentials();
|
|
229
251
|
const osArch = getOsArch();
|
|
230
|
-
|
|
252
|
+
let userAgent = `blaxel/sdk/typescript/${this.version} (${osArch}) blaxel/${this.commit}`;
|
|
231
253
|
const integration = this.integration;
|
|
254
|
+
if (integration) {
|
|
255
|
+
userAgent += ` ${integration}`;
|
|
256
|
+
}
|
|
257
|
+
const codingAgent = this.codingAgent;
|
|
258
|
+
if (codingAgent) {
|
|
259
|
+
userAgent += ` agent/${codingAgent}`;
|
|
260
|
+
}
|
|
232
261
|
return {
|
|
233
262
|
"x-blaxel-authorization": this.authorization,
|
|
234
263
|
"x-blaxel-workspace": this.workspace || "",
|
|
235
264
|
"Blaxel-Version": this.apiVersion,
|
|
236
|
-
"User-Agent":
|
|
265
|
+
"User-Agent": userAgent,
|
|
237
266
|
};
|
|
238
267
|
}
|
|
239
268
|
get name() {
|
|
@@ -33,10 +33,42 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const fs_1 = require("fs");
|
|
37
|
+
const os_1 = require("os");
|
|
38
|
+
const path_1 = require("path");
|
|
36
39
|
const vitest_1 = require("vitest");
|
|
37
40
|
const apikey_js_1 = require("../authentication/apikey.js");
|
|
41
|
+
const coding_agent_js_1 = require("./coding-agent.js");
|
|
38
42
|
const env_js_1 = require("./env.js");
|
|
39
43
|
const h2_runtime_js_1 = require("./h2-runtime.js");
|
|
44
|
+
/**
|
|
45
|
+
* These tests may run inside a coding agent, which sets the variables the SDK
|
|
46
|
+
* reads to tag its User-Agent. Clear them (and DO_NOT_TRACK) for each test.
|
|
47
|
+
*/
|
|
48
|
+
function withoutCodingAgentEnv() {
|
|
49
|
+
const names = [...coding_agent_js_1.CODING_AGENT_ENV_VARS, 'DO_NOT_TRACK', 'HOME', 'USERPROFILE'];
|
|
50
|
+
const saved = {};
|
|
51
|
+
return {
|
|
52
|
+
clear() {
|
|
53
|
+
for (const name of names) {
|
|
54
|
+
saved[name] = process.env[name];
|
|
55
|
+
}
|
|
56
|
+
for (const name of [...coding_agent_js_1.CODING_AGENT_ENV_VARS, 'DO_NOT_TRACK']) {
|
|
57
|
+
delete process.env[name];
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
restore() {
|
|
61
|
+
for (const name of names) {
|
|
62
|
+
if (saved[name] === undefined) {
|
|
63
|
+
delete process.env[name];
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
process.env[name] = saved[name];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
40
72
|
(0, vitest_1.describe)('Settings.apiVersion', () => {
|
|
41
73
|
(0, vitest_1.beforeEach)(() => {
|
|
42
74
|
// Reset the module-level settings singleton between tests if needed
|
|
@@ -253,6 +285,7 @@ const h2_runtime_js_1 = require("./h2-runtime.js");
|
|
|
253
285
|
});
|
|
254
286
|
(0, vitest_1.describe)('Settings.integration', () => {
|
|
255
287
|
const sdkUserAgent = /^blaxel\/sdk\/typescript\/\S+ \([^)]+\) blaxel\/\S+/;
|
|
288
|
+
const agentEnv = withoutCodingAgentEnv();
|
|
256
289
|
async function authenticated() {
|
|
257
290
|
const { settings } = await Promise.resolve().then(() => __importStar(require('./settings.js')));
|
|
258
291
|
settings.credentials = new apikey_js_1.ApiKey({ apiKey: 'test-key', workspace: 'test-ws' });
|
|
@@ -260,7 +293,9 @@ const h2_runtime_js_1 = require("./h2-runtime.js");
|
|
|
260
293
|
delete process.env.BL_INTEGRATION;
|
|
261
294
|
return settings;
|
|
262
295
|
}
|
|
296
|
+
(0, vitest_1.beforeEach)(() => agentEnv.clear());
|
|
263
297
|
(0, vitest_1.afterEach)(async () => {
|
|
298
|
+
agentEnv.restore();
|
|
264
299
|
const { settings } = await Promise.resolve().then(() => __importStar(require('./settings.js')));
|
|
265
300
|
delete settings.config.integration;
|
|
266
301
|
delete process.env.BL_INTEGRATION;
|
|
@@ -321,3 +356,100 @@ const h2_runtime_js_1 = require("./h2-runtime.js");
|
|
|
321
356
|
}
|
|
322
357
|
});
|
|
323
358
|
});
|
|
359
|
+
(0, vitest_1.describe)('Settings.codingAgent', () => {
|
|
360
|
+
const sdkUserAgent = /^blaxel\/sdk\/typescript\/\S+ \([^)]+\) blaxel\/\S+/;
|
|
361
|
+
const agentEnv = withoutCodingAgentEnv();
|
|
362
|
+
let home = '';
|
|
363
|
+
/** A fresh settings module, so it reads the empty test home directory. */
|
|
364
|
+
async function authenticated() {
|
|
365
|
+
const { settings } = await Promise.resolve().then(() => __importStar(require('./settings.js')));
|
|
366
|
+
settings.credentials = new apikey_js_1.ApiKey({ apiKey: 'test-key', workspace: 'test-ws' });
|
|
367
|
+
delete settings.config.integration;
|
|
368
|
+
delete process.env.BL_INTEGRATION;
|
|
369
|
+
return settings;
|
|
370
|
+
}
|
|
371
|
+
/** A fresh settings module whose home directory holds this config.yaml. */
|
|
372
|
+
async function withConfig(config) {
|
|
373
|
+
(0, fs_1.mkdirSync)((0, path_1.join)(home, '.blaxel'));
|
|
374
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(home, '.blaxel', 'config.yaml'), config);
|
|
375
|
+
return authenticated();
|
|
376
|
+
}
|
|
377
|
+
// Keep the tracking choice saved on the machine running the tests out.
|
|
378
|
+
(0, vitest_1.beforeEach)(() => {
|
|
379
|
+
agentEnv.clear();
|
|
380
|
+
home = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'blaxel-home-'));
|
|
381
|
+
process.env.HOME = home;
|
|
382
|
+
process.env.USERPROFILE = home;
|
|
383
|
+
vitest_1.vi.resetModules();
|
|
384
|
+
});
|
|
385
|
+
(0, vitest_1.afterEach)(() => {
|
|
386
|
+
agentEnv.restore();
|
|
387
|
+
delete process.env.BL_INTEGRATION;
|
|
388
|
+
(0, fs_1.rmSync)(home, { recursive: true, force: true });
|
|
389
|
+
vitest_1.vi.resetModules();
|
|
390
|
+
});
|
|
391
|
+
(0, vitest_1.it)('sends no agent token outside a coding agent', async () => {
|
|
392
|
+
const settings = await authenticated();
|
|
393
|
+
(0, vitest_1.expect)(settings.codingAgent).toBe('');
|
|
394
|
+
(0, vitest_1.expect)(settings.headers['User-Agent']).toMatch(new RegExp(sdkUserAgent.source + '$'));
|
|
395
|
+
});
|
|
396
|
+
(0, vitest_1.it)('appends the agent token detected from the environment', async () => {
|
|
397
|
+
const settings = await authenticated();
|
|
398
|
+
process.env.CLAUDECODE = '1';
|
|
399
|
+
(0, vitest_1.expect)(settings.codingAgent).toBe('claude-code');
|
|
400
|
+
const userAgent = settings.headers['User-Agent'];
|
|
401
|
+
(0, vitest_1.expect)(userAgent).toMatch(sdkUserAgent);
|
|
402
|
+
(0, vitest_1.expect)(userAgent.endsWith(' agent/claude-code')).toBe(true);
|
|
403
|
+
});
|
|
404
|
+
(0, vitest_1.it)('puts the agent token after the integration token', async () => {
|
|
405
|
+
const settings = await authenticated();
|
|
406
|
+
process.env.CODEX_SANDBOX = 'seatbelt';
|
|
407
|
+
settings.integration = 'herdr/1.4.0';
|
|
408
|
+
(0, vitest_1.expect)(settings.headers['User-Agent'].endsWith(' herdr/1.4.0 agent/codex')).toBe(true);
|
|
409
|
+
});
|
|
410
|
+
vitest_1.it.each(['1', 'true'])('drops the token when DO_NOT_TRACK=%s', async (value) => {
|
|
411
|
+
const settings = await authenticated();
|
|
412
|
+
process.env.CLAUDECODE = '1';
|
|
413
|
+
process.env.DO_NOT_TRACK = value;
|
|
414
|
+
(0, vitest_1.expect)(settings.codingAgent).toBe('');
|
|
415
|
+
(0, vitest_1.expect)(settings.headers['User-Agent']).toMatch(new RegExp(sdkUserAgent.source + '$'));
|
|
416
|
+
});
|
|
417
|
+
(0, vitest_1.it)('drops the token when the process sets DO_NOT_TRACK=1 and the project .env says false', async () => {
|
|
418
|
+
vitest_1.vi.doMock('./node.js', async (importOriginal) => {
|
|
419
|
+
const original = await importOriginal();
|
|
420
|
+
const fs = original.fs;
|
|
421
|
+
const readFileSync = ((file, ...rest) => file === '.env' ? 'DO_NOT_TRACK=false\n' : fs.readFileSync(file, ...rest));
|
|
422
|
+
return { ...original, fs: { ...fs, readFileSync } };
|
|
423
|
+
});
|
|
424
|
+
try {
|
|
425
|
+
const settings = await authenticated();
|
|
426
|
+
const { env: projectEnv } = await Promise.resolve().then(() => __importStar(require('./env.js')));
|
|
427
|
+
(0, vitest_1.expect)(projectEnv.DO_NOT_TRACK).toBe('false');
|
|
428
|
+
process.env.CLAUDECODE = '1';
|
|
429
|
+
process.env.DO_NOT_TRACK = '1';
|
|
430
|
+
(0, vitest_1.expect)(settings.codingAgent).toBe('');
|
|
431
|
+
(0, vitest_1.expect)(settings.headers['User-Agent']).toMatch(new RegExp(sdkUserAgent.source + '$'));
|
|
432
|
+
}
|
|
433
|
+
finally {
|
|
434
|
+
vitest_1.vi.doUnmock('./node.js');
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
(0, vitest_1.it)('keeps the token when DO_NOT_TRACK turns tracking on', async () => {
|
|
438
|
+
const settings = await authenticated();
|
|
439
|
+
process.env.CLAUDECODE = '1';
|
|
440
|
+
process.env.DO_NOT_TRACK = '0';
|
|
441
|
+
(0, vitest_1.expect)(settings.codingAgent).toBe('claude-code');
|
|
442
|
+
});
|
|
443
|
+
(0, vitest_1.it)('keeps the token when config.yaml says tracking: false', async () => {
|
|
444
|
+
// bl login saves tracking: false until the user opts in to error reports.
|
|
445
|
+
const settings = await withConfig('tracking: false\n');
|
|
446
|
+
process.env.CURSOR_AGENT = '1';
|
|
447
|
+
(0, vitest_1.expect)(settings.codingAgent).toBe('cursor');
|
|
448
|
+
});
|
|
449
|
+
(0, vitest_1.it)('keeps the token when no tracking choice was saved', async () => {
|
|
450
|
+
const settings = await withConfig('context:\n workspace: test-ws\n');
|
|
451
|
+
process.env.CURSOR_AGENT = '1';
|
|
452
|
+
(0, vitest_1.expect)(settings.tracking).toBe(false);
|
|
453
|
+
(0, vitest_1.expect)(settings.codingAgent).toBe('cursor');
|
|
454
|
+
});
|
|
455
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Every environment variable `detectCodingAgent` reads. */
|
|
2
|
+
export declare const CODING_AGENT_ENV_VARS: readonly string[];
|
|
3
|
+
/**
|
|
4
|
+
* Id of the coding agent whose environment the SDK runs in, "other" for an
|
|
5
|
+
* agent without a known name, or "" for none.
|
|
6
|
+
*/
|
|
7
|
+
export declare function detectCodingAgent(getenv: (name: string) => string | undefined): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -120,6 +120,12 @@ declare class Settings {
|
|
|
120
120
|
*/
|
|
121
121
|
get integration(): string;
|
|
122
122
|
set integration(value: string | undefined);
|
|
123
|
+
/**
|
|
124
|
+
* Id of the coding agent (Claude Code, Codex, Cursor, ...) whose environment
|
|
125
|
+
* runs the SDK, `"other"` for an agent without a known name, or `""` for none
|
|
126
|
+
* or when telemetry is opted out. Sent as an `agent/<id>` User-Agent token.
|
|
127
|
+
*/
|
|
128
|
+
get codingAgent(): string;
|
|
123
129
|
get headers(): Record<string, string>;
|
|
124
130
|
get name(): string;
|
|
125
131
|
get type(): string;
|