11package ai .wavespeed .api ;
22
33import ai .wavespeed .Config ;
4+ import ai .wavespeed .Version ;
45import com .google .gson .Gson ;
56import com .google .gson .reflect .TypeToken ;
67import okhttp3 .*;
@@ -54,6 +55,12 @@ public class Client {
5455 private final int maxRetries ;
5556 private final int maxConnectionRetries ;
5657 private final double retryInterval ;
58+ private String clientName ;
59+
60+ /**
61+ * Default value for the X-Client-Name channel-attribution header.
62+ */
63+ private static final String DEFAULT_CLIENT_NAME = "wavespeed-java" ;
5764
5865 /**
5966 * Initialize the client.
@@ -127,6 +134,71 @@ public Client() {
127134 this (null , null , null , null , null , null );
128135 }
129136
137+ /**
138+ * Set the client name reported in the X-Client-Name header for channel attribution.
139+ *
140+ * <p>The WAVESPEED_CLIENT_NAME environment variable takes precedence over this value.</p>
141+ *
142+ * @param clientName Client name to report
143+ * @return This client, for chaining
144+ */
145+ public Client setClientName (String clientName ) {
146+ this .clientName = clientName ;
147+ return this ;
148+ }
149+
150+ /**
151+ * Resolve the value for the X-Client-Name header.
152+ *
153+ * <p>Precedence: WAVESPEED_CLIENT_NAME environment variable > setClientName() > default.</p>
154+ *
155+ * @return Client name for channel attribution
156+ */
157+ private String resolveClientName () {
158+ String envName = System .getenv ("WAVESPEED_CLIENT_NAME" );
159+ if (envName != null && !envName .isEmpty ()) {
160+ return envName ;
161+ }
162+ if (clientName != null && !clientName .isEmpty ()) {
163+ return clientName ;
164+ }
165+ return DEFAULT_CLIENT_NAME ;
166+ }
167+
168+ /**
169+ * Get the operating system name for the X-Client-OS header
170+ * (lowercase: darwin/linux/windows).
171+ *
172+ * @return Normalized operating system name
173+ */
174+ private static String clientOs () {
175+ String osName = System .getProperty ("os.name" , "" ).toLowerCase ();
176+ if (osName .contains ("mac" ) || osName .contains ("darwin" )) {
177+ return "darwin" ;
178+ }
179+ if (osName .contains ("win" )) {
180+ return "windows" ;
181+ }
182+ if (osName .contains ("nux" ) || osName .contains ("nix" )) {
183+ return "linux" ;
184+ }
185+ return osName ;
186+ }
187+
188+ /**
189+ * Add the channel-attribution headers (X-Client-Name, X-Client-Version,
190+ * X-Client-OS) sent on every API request.
191+ *
192+ * @param builder Request builder to add headers to
193+ * @return The same builder, for chaining
194+ */
195+ private Request .Builder addClientHeaders (Request .Builder builder ) {
196+ return builder
197+ .addHeader ("X-Client-Name" , resolveClientName ())
198+ .addHeader ("X-Client-Version" , Version .VERSION )
199+ .addHeader ("X-Client-OS" , clientOs ());
200+ }
201+
130202 /**
131203 * Get request headers with authentication.
132204 *
@@ -142,6 +214,9 @@ private Map<String, String> getHeaders() {
142214 Map <String , String > headers = new HashMap <>();
143215 headers .put ("Content-Type" , "application/json" );
144216 headers .put ("Authorization" , "Bearer " + apiKey );
217+ headers .put ("X-Client-Name" , resolveClientName ());
218+ headers .put ("X-Client-Version" , Version .VERSION );
219+ headers .put ("X-Client-OS" , clientOs ());
145220 return headers ;
146221 }
147222
@@ -179,14 +254,14 @@ private SubmitResult submit(
179254
180255 for (int retry = 0 ; retry <= maxConnectionRetries ; retry ++) {
181256 try {
182- Request request = new Request .Builder ()
257+ Request request = addClientHeaders ( new Request .Builder ()
183258 .url (url )
184259 .post (RequestBody .create (
185260 gson .toJson (body ),
186261 MediaType .parse ("application/json" )
187262 ))
188263 .addHeader ("Authorization" , "Bearer " + apiKey )
189- .addHeader ("Content-Type" , "application/json" )
264+ .addHeader ("Content-Type" , "application/json" ))
190265 .build ();
191266
192267 try (Response response = httpClient .newCall (request ).execute ()) {
@@ -260,10 +335,10 @@ private Map<String, Object> getResult(String requestId, Double timeout) {
260335
261336 for (int retry = 0 ; retry <= maxConnectionRetries ; retry ++) {
262337 try {
263- Request request = new Request .Builder ()
338+ Request request = addClientHeaders ( new Request .Builder ()
264339 .url (url )
265340 .get ()
266- .addHeader ("Authorization" , "Bearer " + apiKey )
341+ .addHeader ("Authorization" , "Bearer " + apiKey ))
267342 .build ();
268343
269344 try (Response response = httpClient .newCall (request ).execute ()) {
@@ -587,10 +662,10 @@ public String upload(String file, Double timeout) {
587662 payload .put ("content_type" , contentType );
588663 }
589664
590- Request request = new Request .Builder ()
665+ Request request = addClientHeaders ( new Request .Builder ()
591666 .url (this .baseUrl + "/api/v3/media/uploads" )
592667 .post (RequestBody .create (gson .toJson (payload ), MediaType .parse ("application/json" )))
593- .addHeader ("Authorization" , "Bearer " + apiKey )
668+ .addHeader ("Authorization" , "Bearer " + apiKey ))
594669 .build ();
595670
596671 try (Response response = httpClient .newCall (request ).execute ()) {
0 commit comments