Skip to content

Commit ac1c6fd

Browse files
Add agent information to UserAgent (#2165)
1 parent 6c2364a commit ac1c6fd

2 files changed

Lines changed: 89 additions & 6 deletions

File tree

src/main/java/com/stripe/net/HttpClient.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.Optional;
1414
import java.util.Properties;
1515
import java.util.concurrent.ThreadLocalRandom;
16+
import java.util.function.Function;
1617

1718
/** Base abstract class for HTTP clients used to send requests to Stripe's API. */
1819
public abstract class HttpClient {
@@ -137,12 +138,39 @@ public StripeResponseStream requestStreamWithRetries(StripeRequest request)
137138
return sendWithRetries(request, (r) -> this.requestStream(r));
138139
}
139140

141+
static String detectAIAgent() {
142+
return detectAIAgent(System::getenv);
143+
}
144+
145+
static String detectAIAgent(Function<String, String> getEnv) {
146+
String[][] agents = {
147+
{"ANTIGRAVITY_CLI_ALIAS", "antigravity"},
148+
{"CLAUDECODE", "claude_code"},
149+
{"CLINE_ACTIVE", "cline"},
150+
{"CODEX_SANDBOX", "codex_cli"},
151+
{"CURSOR_AGENT", "cursor"},
152+
{"GEMINI_CLI", "gemini_cli"},
153+
{"OPENCODE", "open_code"},
154+
};
155+
for (String[] agent : agents) {
156+
String val = getEnv.apply(agent[0]);
157+
if (val != null && !val.isEmpty()) {
158+
return agent[1];
159+
}
160+
}
161+
return "";
162+
}
163+
140164
/**
141165
* Builds the value of the {@code User-Agent} header.
142166
*
143167
* @return a string containing the value of the {@code User-Agent} header
144168
*/
145169
protected static String buildUserAgentString(StripeRequest request) {
170+
return buildUserAgentString(request, detectAIAgent());
171+
}
172+
173+
static String buildUserAgentString(StripeRequest request, String aiAgent) {
146174
String apiMode = request.apiMode() == ApiMode.V2 ? "v2" : "v1";
147175

148176
String userAgent = String.format("Stripe/%s JavaBindings/%s", apiMode, Stripe.VERSION);
@@ -151,6 +179,10 @@ protected static String buildUserAgentString(StripeRequest request) {
151179
userAgent += " " + formatAppInfo(Stripe.getAppInfo());
152180
}
153181

182+
if (!aiAgent.isEmpty()) {
183+
userAgent += " AIAgent/" + aiAgent;
184+
}
185+
154186
return userAgent;
155187
}
156188

@@ -160,6 +192,10 @@ protected static String buildUserAgentString(StripeRequest request) {
160192
* @return a string containing the value of the {@code X-Stripe-Client-User-Agent} header
161193
*/
162194
protected static String buildXStripeClientUserAgentString() {
195+
return buildXStripeClientUserAgentString(detectAIAgent());
196+
}
197+
198+
static String buildXStripeClientUserAgentString(String aiAgent) {
163199
String[] propertyNames = {
164200
"os.name",
165201
"os.version",
@@ -182,6 +218,10 @@ protected static String buildXStripeClientUserAgentString() {
182218
}
183219
getGsonVersion().ifPresent(ver -> propertyMap.put("gson.version", ver));
184220

221+
if (!aiAgent.isEmpty()) {
222+
propertyMap.put("ai_agent", aiAgent);
223+
}
224+
185225
return ApiResource.INTERNAL_GSON.toJson(propertyMap);
186226
}
187227

src/test/java/com/stripe/net/HttpClientTest.java

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,8 @@ public void testV1RequestSetsCorrectUserAgent() throws StripeException {
192192
RequestOptions.builder().setApiKey("sk_test_123").setMaxNetworkRetries(2).build(),
193193
ApiMode.V1);
194194

195-
assertEquals(
196-
HttpClient.buildUserAgentString(request),
197-
String.format("Stripe/v1 JavaBindings/%s", Stripe.VERSION));
195+
String userAgent = HttpClient.buildUserAgentString(request);
196+
assertTrue(userAgent.startsWith(String.format("Stripe/v1 JavaBindings/%s", Stripe.VERSION)));
198197
}
199198

200199
@Test
@@ -207,8 +206,52 @@ public void testV2RequestSetsCorrectUserAgent() throws StripeException {
207206
RequestOptions.builder().setApiKey("sk_test_123").setMaxNetworkRetries(2).build(),
208207
ApiMode.V2);
209208

210-
assertEquals(
211-
HttpClient.buildUserAgentString(request),
212-
String.format("Stripe/v2 JavaBindings/%s", Stripe.VERSION));
209+
String userAgent = HttpClient.buildUserAgentString(request);
210+
assertTrue(userAgent.startsWith(String.format("Stripe/v2 JavaBindings/%s", Stripe.VERSION)));
211+
}
212+
213+
@Test
214+
public void testDetectAIAgent() {
215+
String agent = HttpClient.detectAIAgent(key -> key.equals("CLAUDECODE") ? "1" : null);
216+
assertEquals("claude_code", agent);
217+
}
218+
219+
@Test
220+
public void testDetectAIAgentNoEnv() {
221+
String agent = HttpClient.detectAIAgent(key -> null);
222+
assertEquals("", agent);
223+
}
224+
225+
@Test
226+
public void testDetectAIAgentFirstMatchWins() {
227+
String agent =
228+
HttpClient.detectAIAgent(
229+
key -> {
230+
if (key.equals("CURSOR_AGENT") || key.equals("OPENCODE")) return "1";
231+
return null;
232+
});
233+
assertEquals("cursor", agent);
234+
}
235+
236+
@Test
237+
public void testBuildUserAgentStringWithAIAgent() throws StripeException {
238+
StripeRequest request =
239+
StripeRequest.create(
240+
ApiResource.RequestMethod.GET,
241+
"http://example.com/get",
242+
null,
243+
RequestOptions.builder().setApiKey("sk_test_123").build(),
244+
ApiMode.V1);
245+
246+
String userAgent = HttpClient.buildUserAgentString(request, "cursor");
247+
assertTrue(userAgent.contains("AIAgent/cursor"));
248+
}
249+
250+
@Test
251+
public void testBuildXStripeClientUserAgentStringWithAIAgent() {
252+
String json = HttpClient.buildXStripeClientUserAgentString("cursor");
253+
com.google.gson.JsonObject parsed =
254+
com.google.gson.JsonParser.parseString(json).getAsJsonObject();
255+
assertEquals("cursor", parsed.get("ai_agent").getAsString());
213256
}
214257
}

0 commit comments

Comments
 (0)