@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.
Files changed (35) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/common/coding-agent.js +75 -0
  3. package/dist/cjs/common/coding-agent.test.js +50 -0
  4. package/dist/cjs/common/sentry.js +20 -2
  5. package/dist/cjs/common/sentry.test.js +22 -2
  6. package/dist/cjs/common/settings.js +33 -4
  7. package/dist/cjs/common/settings.test.js +132 -0
  8. package/dist/cjs/types/common/coding-agent.d.ts +7 -0
  9. package/dist/cjs/types/common/coding-agent.test.d.ts +1 -0
  10. package/dist/cjs/types/common/settings.d.ts +6 -0
  11. package/dist/cjs-browser/.tsbuildinfo +1 -1
  12. package/dist/cjs-browser/common/coding-agent.js +75 -0
  13. package/dist/cjs-browser/common/coding-agent.test.js +50 -0
  14. package/dist/cjs-browser/common/sentry.js +20 -2
  15. package/dist/cjs-browser/common/sentry.test.js +22 -2
  16. package/dist/cjs-browser/common/settings.js +33 -4
  17. package/dist/cjs-browser/common/settings.test.js +132 -0
  18. package/dist/cjs-browser/types/common/coding-agent.d.ts +7 -0
  19. package/dist/cjs-browser/types/common/coding-agent.test.d.ts +1 -0
  20. package/dist/cjs-browser/types/common/settings.d.ts +6 -0
  21. package/dist/esm/.tsbuildinfo +1 -1
  22. package/dist/esm/common/coding-agent.js +71 -0
  23. package/dist/esm/common/coding-agent.test.js +48 -0
  24. package/dist/esm/common/sentry.js +20 -2
  25. package/dist/esm/common/sentry.test.js +22 -2
  26. package/dist/esm/common/settings.js +33 -4
  27. package/dist/esm/common/settings.test.js +132 -0
  28. package/dist/esm-browser/.tsbuildinfo +1 -1
  29. package/dist/esm-browser/common/coding-agent.js +71 -0
  30. package/dist/esm-browser/common/coding-agent.test.js +48 -0
  31. package/dist/esm-browser/common/sentry.js +20 -2
  32. package/dist/esm-browser/common/sentry.test.js +22 -2
  33. package/dist/esm-browser/common/settings.js +33 -4
  34. package/dist/esm-browser/common/settings.test.js +132 -0
  35. 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
- * Build an allowlisted event. Raw messages, response bodies, workspace/resource
192
- * context, and absolute host paths are deliberately excluded.
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: "private-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.35";
34
- const BUILD_COMMIT = "5f8cd5a9c6ba61ebc883d5c583b198b67da4167d";
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
- const sdkUserAgent = `blaxel/sdk/typescript/${this.version} (${osArch}) blaxel/${this.commit}`;
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": integration ? `${sdkUserAgent} ${integration}` : sdkUserAgent,
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;