From 220b5dff610353896f30b17c0f2289b4b5dc467f Mon Sep 17 00:00:00 2001 From: David Brownman Date: Fri, 27 Feb 2026 15:32:51 -0800 Subject: [PATCH] Add agent information to UserAgent --- src/main/java/com/stripe/net/HttpClient.java | 40 ++++++++++++++ .../java/com/stripe/net/HttpClientTest.java | 55 +++++++++++++++++-- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/stripe/net/HttpClient.java b/src/main/java/com/stripe/net/HttpClient.java index 2d4942d9964..ebbae9e9842 100644 --- a/src/main/java/com/stripe/net/HttpClient.java +++ b/src/main/java/com/stripe/net/HttpClient.java @@ -13,6 +13,7 @@ import java.util.Optional; import java.util.Properties; import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Function; /** Base abstract class for HTTP clients used to send requests to Stripe's API. */ public abstract class HttpClient { @@ -137,12 +138,39 @@ public StripeResponseStream requestStreamWithRetries(StripeRequest request) return sendWithRetries(request, (r) -> this.requestStream(r)); } + static String detectAIAgent() { + return detectAIAgent(System::getenv); + } + + static String detectAIAgent(Function getEnv) { + String[][] agents = { + {"ANTIGRAVITY_CLI_ALIAS", "antigravity"}, + {"CLAUDECODE", "claude_code"}, + {"CLINE_ACTIVE", "cline"}, + {"CODEX_SANDBOX", "codex_cli"}, + {"CURSOR_AGENT", "cursor"}, + {"GEMINI_CLI", "gemini_cli"}, + {"OPENCODE", "open_code"}, + }; + for (String[] agent : agents) { + String val = getEnv.apply(agent[0]); + if (val != null && !val.isEmpty()) { + return agent[1]; + } + } + return ""; + } + /** * Builds the value of the {@code User-Agent} header. * * @return a string containing the value of the {@code User-Agent} header */ protected static String buildUserAgentString(StripeRequest request) { + return buildUserAgentString(request, detectAIAgent()); + } + + static String buildUserAgentString(StripeRequest request, String aiAgent) { String apiMode = request.apiMode() == ApiMode.V2 ? "v2" : "v1"; String userAgent = String.format("Stripe/%s JavaBindings/%s", apiMode, Stripe.VERSION); @@ -151,6 +179,10 @@ protected static String buildUserAgentString(StripeRequest request) { userAgent += " " + formatAppInfo(Stripe.getAppInfo()); } + if (!aiAgent.isEmpty()) { + userAgent += " AIAgent/" + aiAgent; + } + return userAgent; } @@ -160,6 +192,10 @@ protected static String buildUserAgentString(StripeRequest request) { * @return a string containing the value of the {@code X-Stripe-Client-User-Agent} header */ protected static String buildXStripeClientUserAgentString() { + return buildXStripeClientUserAgentString(detectAIAgent()); + } + + static String buildXStripeClientUserAgentString(String aiAgent) { String[] propertyNames = { "os.name", "os.version", @@ -182,6 +218,10 @@ protected static String buildXStripeClientUserAgentString() { } getGsonVersion().ifPresent(ver -> propertyMap.put("gson.version", ver)); + if (!aiAgent.isEmpty()) { + propertyMap.put("ai_agent", aiAgent); + } + return ApiResource.INTERNAL_GSON.toJson(propertyMap); } diff --git a/src/test/java/com/stripe/net/HttpClientTest.java b/src/test/java/com/stripe/net/HttpClientTest.java index a7e679b17eb..9edcb6a0717 100644 --- a/src/test/java/com/stripe/net/HttpClientTest.java +++ b/src/test/java/com/stripe/net/HttpClientTest.java @@ -192,9 +192,8 @@ public void testV1RequestSetsCorrectUserAgent() throws StripeException { RequestOptions.builder().setApiKey("sk_test_123").setMaxNetworkRetries(2).build(), ApiMode.V1); - assertEquals( - HttpClient.buildUserAgentString(request), - String.format("Stripe/v1 JavaBindings/%s", Stripe.VERSION)); + String userAgent = HttpClient.buildUserAgentString(request); + assertTrue(userAgent.startsWith(String.format("Stripe/v1 JavaBindings/%s", Stripe.VERSION))); } @Test @@ -207,8 +206,52 @@ public void testV2RequestSetsCorrectUserAgent() throws StripeException { RequestOptions.builder().setApiKey("sk_test_123").setMaxNetworkRetries(2).build(), ApiMode.V2); - assertEquals( - HttpClient.buildUserAgentString(request), - String.format("Stripe/v2 JavaBindings/%s", Stripe.VERSION)); + String userAgent = HttpClient.buildUserAgentString(request); + assertTrue(userAgent.startsWith(String.format("Stripe/v2 JavaBindings/%s", Stripe.VERSION))); + } + + @Test + public void testDetectAIAgent() { + String agent = HttpClient.detectAIAgent(key -> key.equals("CLAUDECODE") ? "1" : null); + assertEquals("claude_code", agent); + } + + @Test + public void testDetectAIAgentNoEnv() { + String agent = HttpClient.detectAIAgent(key -> null); + assertEquals("", agent); + } + + @Test + public void testDetectAIAgentFirstMatchWins() { + String agent = + HttpClient.detectAIAgent( + key -> { + if (key.equals("CURSOR_AGENT") || key.equals("OPENCODE")) return "1"; + return null; + }); + assertEquals("cursor", agent); + } + + @Test + public void testBuildUserAgentStringWithAIAgent() throws StripeException { + StripeRequest request = + StripeRequest.create( + ApiResource.RequestMethod.GET, + "http://example.com/get", + null, + RequestOptions.builder().setApiKey("sk_test_123").build(), + ApiMode.V1); + + String userAgent = HttpClient.buildUserAgentString(request, "cursor"); + assertTrue(userAgent.contains("AIAgent/cursor")); + } + + @Test + public void testBuildXStripeClientUserAgentStringWithAIAgent() { + String json = HttpClient.buildXStripeClientUserAgentString("cursor"); + com.google.gson.JsonObject parsed = + com.google.gson.JsonParser.parseString(json).getAsJsonObject(); + assertEquals("cursor", parsed.get("ai_agent").getAsString()); } }