Skip to content

Commit adb5467

Browse files
authored
Merge branch 'main' into telemetry-2-infrastructure
2 parents 79c1d31 + 0781ad8 commit adb5467

File tree

6 files changed

+100
-45
lines changed

6 files changed

+100
-45
lines changed

.github/workflows/release.yml

Lines changed: 0 additions & 42 deletions
This file was deleted.

.npmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
package-lock=false
1+
package-lock=true

lib/utils/agentDetector.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Detects whether the Node.js SQL driver is being invoked by an AI coding agent
3+
* by checking for well-known environment variables that agents set in their
4+
* spawned shell processes.
5+
*
6+
* Detection only succeeds when exactly one agent environment variable is present,
7+
* to avoid ambiguous attribution when multiple agent environments overlap.
8+
*
9+
* Adding a new agent requires only a new entry in `knownAgents`.
10+
*
11+
* References for each environment variable:
12+
* - ANTIGRAVITY_AGENT: Closed source. Google Antigravity sets this variable.
13+
* - CLAUDECODE: https://github.com/anthropics/claude-code (sets CLAUDECODE=1)
14+
* - CLINE_ACTIVE: https://github.com/cline/cline (shipped in v3.24.0)
15+
* - CODEX_CI: https://github.com/openai/codex (part of UNIFIED_EXEC_ENV array in codex-rs)
16+
* - CURSOR_AGENT: Closed source. Referenced in a gist by johnlindquist.
17+
* - GEMINI_CLI: https://google-gemini.github.io/gemini-cli/docs/tools/shell.html (sets GEMINI_CLI=1)
18+
* - OPENCODE: https://github.com/opencode-ai/opencode (sets OPENCODE=1)
19+
*/
20+
21+
const knownAgents: Array<{ envVar: string; product: string }> = [
22+
{ envVar: 'ANTIGRAVITY_AGENT', product: 'antigravity' },
23+
{ envVar: 'CLAUDECODE', product: 'claude-code' },
24+
{ envVar: 'CLINE_ACTIVE', product: 'cline' },
25+
{ envVar: 'CODEX_CI', product: 'codex' },
26+
{ envVar: 'CURSOR_AGENT', product: 'cursor' },
27+
{ envVar: 'GEMINI_CLI', product: 'gemini-cli' },
28+
{ envVar: 'OPENCODE', product: 'opencode' },
29+
];
30+
31+
export default function detectAgent(env: Record<string, string | undefined> = process.env): string {
32+
const detected = knownAgents.filter((a) => env[a.envVar]).map((a) => a.product);
33+
34+
if (detected.length === 1) {
35+
return detected[0];
36+
}
37+
return '';
38+
}

lib/utils/buildUserAgentString.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os from 'os';
22
import packageVersion from '../version';
3+
import detectAgent from './agentDetector';
34

45
const productName = 'NodejsDatabricksSqlConnector';
56

@@ -27,5 +28,12 @@ export default function buildUserAgentString(userAgentEntry?: string): string {
2728
}
2829

2930
const extra = [userAgentEntry, getNodeVersion(), getOperatingSystemVersion()].filter(Boolean);
30-
return `${productName}/${packageVersion} (${extra.join('; ')})`;
31+
let ua = `${productName}/${packageVersion} (${extra.join('; ')})`;
32+
33+
const agentProduct = detectAgent();
34+
if (agentProduct) {
35+
ua += ` agent/${agentProduct}`;
36+
}
37+
38+
return ua;
3139
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { expect } from 'chai';
2+
import detectAgent from '../../../lib/utils/agentDetector';
3+
4+
describe('detectAgent', () => {
5+
const allAgents = [
6+
{ envVar: 'ANTIGRAVITY_AGENT', product: 'antigravity' },
7+
{ envVar: 'CLAUDECODE', product: 'claude-code' },
8+
{ envVar: 'CLINE_ACTIVE', product: 'cline' },
9+
{ envVar: 'CODEX_CI', product: 'codex' },
10+
{ envVar: 'CURSOR_AGENT', product: 'cursor' },
11+
{ envVar: 'GEMINI_CLI', product: 'gemini-cli' },
12+
{ envVar: 'OPENCODE', product: 'opencode' },
13+
];
14+
15+
for (const { envVar, product } of allAgents) {
16+
it(`detects ${product} when ${envVar} is set`, () => {
17+
expect(detectAgent({ [envVar]: '1' })).to.equal(product);
18+
});
19+
}
20+
21+
it('returns empty string when no agent is detected', () => {
22+
expect(detectAgent({})).to.equal('');
23+
});
24+
25+
it('returns empty string when multiple agents are detected', () => {
26+
expect(detectAgent({ CLAUDECODE: '1', CURSOR_AGENT: '1' })).to.equal('');
27+
});
28+
29+
it('ignores empty env var values', () => {
30+
expect(detectAgent({ CLAUDECODE: '' })).to.equal('');
31+
});
32+
33+
it('ignores undefined env var values', () => {
34+
expect(detectAgent({ CLAUDECODE: undefined })).to.equal('');
35+
});
36+
});

tests/unit/utils/utils.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ describe('buildUserAgentString', () => {
3232
// Prefix: 'NodejsDatabricksSqlConnector/'
3333
// Version: three period-separated digits and optional suffix
3434
const re =
35-
/^(?<productName>NodejsDatabricksSqlConnector)\/(?<productVersion>\d+\.\d+\.\d+(-[^(]+)?)\s*\((?<comment>[^)]+)\)$/i;
35+
/^(?<productName>NodejsDatabricksSqlConnector)\/(?<productVersion>\d+\.\d+\.\d+(-[^(]+)?)\s*\((?<comment>[^)]+)\)(\s+agent\/[a-z-]+)?$/i;
3636
const match = re.exec(ua);
3737
expect(match).to.not.be.eq(null);
3838

@@ -62,6 +62,21 @@ describe('buildUserAgentString', () => {
6262
const userAgentString = buildUserAgentString(userAgentEntry);
6363
expect(userAgentString).to.include('<REDACTED>');
6464
});
65+
66+
it('appends agent suffix when agent env var is set', () => {
67+
const orig = process.env.CLAUDECODE;
68+
try {
69+
process.env.CLAUDECODE = '1';
70+
const ua = buildUserAgentString();
71+
expect(ua).to.include('agent/claude-code');
72+
} finally {
73+
if (orig === undefined) {
74+
delete process.env.CLAUDECODE;
75+
} else {
76+
process.env.CLAUDECODE = orig;
77+
}
78+
}
79+
});
6580
});
6681

6782
describe('formatProgress', () => {

0 commit comments

Comments
 (0)