Skip to content

Commit e9c4eaa

Browse files
committed
Address review feedback on agent detection
- Add NEXT_CHANGELOG.md entry covering the expanded agent list, the AGENT standard, and the empty-string semantics change. - When the main matcher loop finds no match and AGENT is set to a known product name, return that product name instead of "unknown" (implicit known-product fallback). Known matchers still win over the fallback, so AGENT=cursor + CLAUDECODE=1 still yields claude-code. - Restore alphabetical ordering: openclaw before opencode. - Add provenance comments on new agent entries (goose, amp, augment, copilot-vscode, kiro, windsurf). - New tests: testAgentProviderAgentEnvAmp, testAgentProviderAgentEnvCursor, testAgentProviderKnownMatcherWinsOverAgentFallback. Co-authored-by: Isaac Signed-off-by: simon <simon.faltum@databricks.com>
1 parent 14282c0 commit e9c4eaa

3 files changed

Lines changed: 70 additions & 13 deletions

File tree

NEXT_CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
### New Features and Improvements
66
* Add support for authentication through Azure Managed Service Identity (MSI) via the new `azure-msi` credential provider.
77
* Support `default_profile` in `[__settings__]` section of `.databrickscfg` for consistent default profile resolution across CLI and SDKs.
8-
* Added automatic detection of AI coding agents (Antigravity, Claude Code, Cline, Codex, Copilot CLI, Cursor, Gemini CLI, OpenCode) in the user-agent string. The SDK now appends `agent/<name>` to HTTP request headers when running inside a known AI agent environment.
8+
* Added automatic detection of AI coding agents (Amp, Antigravity, Augment, Claude Code, Cline, Codex, Copilot CLI, Copilot VS Code, Cursor, Gemini CLI, Goose, Kiro, OpenClaw, OpenCode, Windsurf) in the user-agent string. The SDK now appends `agent/<name>` to HTTP request headers when running inside a known AI agent environment. Also honors the `AGENT=<name>` standard: when `AGENT` is set to a known product name the SDK reports that product, and when set to an unrecognized non-empty value the SDK reports `agent/unknown`. Environment variables set to the empty string (e.g. `CLAUDECODE=""`) now count as "set" for presence-only matchers, matching `databricks-sdk-go` semantics; previously they were treated as unset.
99

1010
### Bug Fixes
1111
* Fixed non-JSON error responses (e.g. plain-text "Invalid Token" with HTTP 403) producing `Unknown` instead of the correct typed exception (`PermissionDenied`, `Unauthenticated`, etc.). The error message no longer contains Jackson deserialization internals.

databricks-sdk-java/src/main/java/com/databricks/sdk/core/UserAgent.java

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -298,12 +298,16 @@ private static List<KnownAgent> listKnownAgents() {
298298
new KnownAgent(
299299
"amp",
300300
Arrays.asList(
301-
new EnvMatcher("AMP_CURRENT_THREAD_ID"), new EnvMatcher(AGENT_ENV_VAR, "amp"))),
301+
new EnvMatcher("AMP_CURRENT_THREAD_ID"),
302+
new EnvMatcher(AGENT_ENV_VAR, "amp"))), // https://ampcode.com/
302303
new KnownAgent(
303304
"antigravity",
304305
Collections.singletonList(
305306
new EnvMatcher("ANTIGRAVITY_AGENT"))), // Closed source (Google)
306-
new KnownAgent("augment", Collections.singletonList(new EnvMatcher("AUGMENT_AGENT"))),
307+
new KnownAgent(
308+
"augment",
309+
Collections.singletonList(
310+
new EnvMatcher("AUGMENT_AGENT"))), // https://www.augmentcode.com/
307311
new KnownAgent(
308312
"claude-code",
309313
Collections.singletonList(
@@ -320,9 +324,9 @@ private static List<KnownAgent> listKnownAgents() {
320324
"copilot-cli",
321325
Collections.singletonList(
322326
new EnvMatcher("COPILOT_CLI"))), // https://github.com/features/copilot
327+
// VS Code Copilot terminal; best-effort heuristic, not officially identified.
323328
new KnownAgent(
324-
"copilot-vscode",
325-
Collections.singletonList(new EnvMatcher("COPILOT_MODEL"))), // VS Code Copilot
329+
"copilot-vscode", Collections.singletonList(new EnvMatcher("COPILOT_MODEL"))),
326330
new KnownAgent(
327331
"cursor", Collections.singletonList(new EnvMatcher("CURSOR_AGENT"))), // Closed source
328332
new KnownAgent(
@@ -332,17 +336,23 @@ private static List<KnownAgent> listKnownAgents() {
332336
new KnownAgent(
333337
"goose",
334338
Arrays.asList(
335-
new EnvMatcher("GOOSE_TERMINAL"), new EnvMatcher(AGENT_ENV_VAR, "goose"))),
336-
new KnownAgent("kiro", Collections.singletonList(new EnvMatcher("KIRO"))),
339+
new EnvMatcher("GOOSE_TERMINAL"),
340+
new EnvMatcher(AGENT_ENV_VAR, "goose"))), // https://block.github.io/goose/
341+
new KnownAgent(
342+
"kiro",
343+
Collections.singletonList(new EnvMatcher("KIRO"))), // https://kiro.dev/ (Amazon)
344+
new KnownAgent(
345+
"openclaw",
346+
Collections.singletonList(
347+
new EnvMatcher("OPENCLAW_SHELL"))), // https://github.com/anthropics/openclaw
337348
new KnownAgent(
338349
"opencode",
339350
Collections.singletonList(
340351
new EnvMatcher("OPENCODE"))), // https://github.com/opencode-ai/opencode
341352
new KnownAgent(
342-
"openclaw",
353+
"windsurf",
343354
Collections.singletonList(
344-
new EnvMatcher("OPENCLAW_SHELL"))), // https://github.com/anthropics/openclaw
345-
new KnownAgent("windsurf", Collections.singletonList(new EnvMatcher("WINDSURF_AGENT"))));
355+
new EnvMatcher("WINDSURF_AGENT")))); // https://codeium.com/windsurf (Codeium)
346356
}
347357

348358
// Looks up the active agent provider based on environment variables.
@@ -352,15 +362,18 @@ private static List<KnownAgent> listKnownAgents() {
352362
// - Exactly one agent matched: return its product name.
353363
// - More than one agent matched: return "" (ambiguity).
354364
// - Zero agents matched: if the agents.md standard AGENT env var is set to
355-
// any non-empty value, return "unknown". Otherwise return "".
365+
// a known product name, return that product name. If it is set to any
366+
// other non-empty value, return "unknown". Otherwise return "".
356367
//
357368
// Unlike CI/CD detection (which returns the first match), agent detection
358369
// uses an ambiguity guard because agent env vars can be stacked (e.g.,
359-
// running Cline inside Cursor).
370+
// running Cline inside Cursor). Known matchers always win over the AGENT
371+
// fallback, so e.g. AGENT=cursor + CLAUDECODE=1 yields "claude-code".
360372
private static String lookupAgentProvider(Environment env) {
373+
List<KnownAgent> knownAgents = listKnownAgents();
361374
String detected = "";
362375
int count = 0;
363-
for (KnownAgent agent : listKnownAgents()) {
376+
for (KnownAgent agent : knownAgents) {
364377
if (agent.fires(env)) {
365378
detected = agent.product;
366379
count++;
@@ -375,6 +388,11 @@ private static String lookupAgentProvider(Environment env) {
375388
if (count == 0) {
376389
String agentValue = env.get(AGENT_ENV_VAR);
377390
if (agentValue != null && !agentValue.isEmpty()) {
391+
for (KnownAgent agent : knownAgents) {
392+
if (agent.product.equals(agentValue)) {
393+
return agentValue;
394+
}
395+
}
378396
return "unknown";
379397
}
380398
}

databricks-sdk-java/src/test/java/com/databricks/sdk/core/UserAgentTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,45 @@ public void testAgentProviderAgentEnvGoose() {
295295
Assertions.assertTrue(UserAgent.asString().contains("agent/goose"));
296296
}
297297

298+
@Test
299+
public void testAgentProviderAgentEnvAmp() {
300+
setupAgentEnv(
301+
new HashMap<String, String>() {
302+
{
303+
put("AGENT", "amp");
304+
}
305+
});
306+
Assertions.assertTrue(UserAgent.asString().contains("agent/amp"));
307+
}
308+
309+
@Test
310+
public void testAgentProviderAgentEnvCursor() {
311+
// AGENT=cursor with no cursor-specific env var. Falls through to the
312+
// AGENT fallback and matches "cursor" as a known product name.
313+
setupAgentEnv(
314+
new HashMap<String, String>() {
315+
{
316+
put("AGENT", "cursor");
317+
}
318+
});
319+
Assertions.assertTrue(UserAgent.asString().contains("agent/cursor"));
320+
}
321+
322+
@Test
323+
public void testAgentProviderKnownMatcherWinsOverAgentFallback() {
324+
// Known matchers always win over the AGENT fallback. AGENT=somethingweird
325+
// alone would yield "unknown", but CLAUDECODE=1 takes precedence.
326+
setupAgentEnv(
327+
new HashMap<String, String>() {
328+
{
329+
put("AGENT", "somethingweird");
330+
put("CLAUDECODE", "1");
331+
}
332+
});
333+
Assertions.assertTrue(UserAgent.asString().contains("agent/claude-code"));
334+
Assertions.assertFalse(UserAgent.asString().contains("agent/unknown"));
335+
}
336+
298337
@Test
299338
public void testAgentProviderGooseBothMatchers() {
300339
// GOOSE_TERMINAL and AGENT=goose both fire the goose matcher. Since they

0 commit comments

Comments
 (0)