diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index cf600f17c..8b4186d0f 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -4,6 +4,8 @@ ### New Features and Improvements +- Added a `meta-harness` user-agent dimension that reports the omnigent meta-harness (detected via the `OMNIGENT` environment variable) independently of agent detection. + ### Breaking Changes ### Bug Fixes diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/UserAgent.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/UserAgent.java index a2bf24c8b..c834a7e2f 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/UserAgent.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/UserAgent.java @@ -133,6 +133,10 @@ public static String asString() { if (!agent.isEmpty()) { segments.add(String.format("agent/%s", agent)); } + String metaHarness = metaHarnessProvider(); + if (!metaHarness.isEmpty()) { + segments.add(String.format("meta-harness/%s", metaHarness)); + } // Concurrent iteration over ArrayList must be guarded with synchronized. synchronized (otherInfo) { segments.addAll( @@ -174,6 +178,8 @@ private static List listCiCdProviders() { protected static volatile String agentProvider = null; + protected static volatile String metaHarnessProvider = null; + protected static Environment env = null; // Represents an environment variable with its name and expected value @@ -362,6 +368,47 @@ private static String agentProvider() { return agentProvider; } + // Describes a single meta-harness: the env var that identifies it and the + // product name reported in the user agent. + private static class KnownMetaHarness { + private final String envVar; + private final String product; + + KnownMetaHarness(String envVar, String product) { + this.envVar = envVar; + this.product = product; + } + } + + // Canonical list of known meta-harnesses, detected by presence. + // Keep in sync with databricks-sdk-go and databricks-sdk-py. + // OMNIGENT is set by the omnigent meta-harness (https://github.com/omnigent-ai/omnigent). + private static List listKnownMetaHarnesses() { + return Arrays.asList(new KnownMetaHarness("OMNIGENT", "omnigent")); + } + + // Looks up the active meta-harness by env var presence, independent of the agent. + private static String lookupMetaHarnessProvider(Environment env) { + for (KnownMetaHarness h : listKnownMetaHarnesses()) { + if (env.get(h.envVar) != null) { + return h.product; + } + } + return ""; + } + + // Thread-safe lazy initialization of meta-harness detection + private static String metaHarnessProvider() { + if (metaHarnessProvider == null) { + synchronized (UserAgent.class) { + if (metaHarnessProvider == null) { + metaHarnessProvider = lookupMetaHarnessProvider(env()); + } + } + } + return metaHarnessProvider; + } + private static Environment env() { if (env == null) { env = diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/UserAgentTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/UserAgentTest.java index 2a2a5cfe7..dd7794ea8 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/UserAgentTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/UserAgentTest.java @@ -18,6 +18,7 @@ void tearDown() { private void setupAgentEnv(Map envMap) { UserAgent.agentProvider = null; UserAgent.cicdProvider = null; + UserAgent.metaHarnessProvider = null; UserAgent.env = new Environment(envMap, new ArrayList<>(), System.getProperty("os.name")); } @@ -25,6 +26,7 @@ private void cleanupAgentEnv() { UserAgent.env = null; UserAgent.agentProvider = null; UserAgent.cicdProvider = null; + UserAgent.metaHarnessProvider = null; } @Test @@ -617,4 +619,68 @@ public void testAgentProviderCached() { Assertions.assertTrue(UserAgent.asString().contains("agent/cursor")); Assertions.assertFalse(UserAgent.asString().contains("agent/claude-code")); } + + @Test + public void testMetaHarnessProviderOmnigent() { + setupAgentEnv( + new HashMap() { + { + put("OMNIGENT", "1"); + } + }); + Assertions.assertTrue(UserAgent.asString().contains("meta-harness/omnigent")); + } + + @Test + public void testMetaHarnessProviderNoHarness() { + setupAgentEnv(new HashMap<>()); + Assertions.assertFalse(UserAgent.asString().contains("meta-harness/")); + } + + @Test + public void testMetaHarnessProviderEmptyValueStillSet() { + // Empty string still counts as "set" for presence-only detection, + // matching the agent dimension and databricks-sdk-go semantics. + setupAgentEnv( + new HashMap() { + { + put("OMNIGENT", ""); + } + }); + Assertions.assertTrue(UserAgent.asString().contains("meta-harness/omnigent")); + } + + @Test + public void testMetaHarnessProviderIndependentOfAgent() { + // Under omnigent both OMNIGENT and the agent marker are set: report both + // dimensions, and the meta-harness must not trip the agent "multiple" logic. + setupAgentEnv( + new HashMap() { + { + put("OMNIGENT", "1"); + put("CLAUDECODE", "1"); + } + }); + String userAgent = UserAgent.asString(); + Assertions.assertTrue(userAgent.contains("agent/claude-code")); + Assertions.assertTrue(userAgent.contains("meta-harness/omnigent")); + Assertions.assertFalse(userAgent.contains("agent/multiple")); + } + + @Test + public void testMetaHarnessProviderCached() { + // Set up with omnigent. + setupAgentEnv( + new HashMap() { + { + put("OMNIGENT", "1"); + } + }); + Assertions.assertTrue(UserAgent.asString().contains("meta-harness/omnigent")); + + // Change env after caching. Cached result should persist. + UserAgent.env = + new Environment(new HashMap<>(), new ArrayList<>(), System.getProperty("os.name")); + Assertions.assertTrue(UserAgent.asString().contains("meta-harness/omnigent")); + } }