Skip to content

Commit abbe820

Browse files
jaycee-licopybara-github
authored andcommitted
feat: enable request level http options(set through XxxConfig)
PiperOrigin-RevId: 757963487
1 parent 971177d commit abbe820

File tree

9 files changed

+142
-43
lines changed

9 files changed

+142
-43
lines changed

src/main/java/com/google/genai/ApiClient.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ abstract class ApiClient {
6464
this.httpOptions = defaultHttpOptions(/* vertexAI= */ false, this.location);
6565

6666
if (customHttpOptions.isPresent()) {
67-
applyHttpOptions(customHttpOptions.get());
67+
this.httpOptions = applyHttpOptions(customHttpOptions.get());
6868
}
6969

7070
this.httpClient = createHttpClient(httpOptions.timeout());
@@ -109,7 +109,7 @@ abstract class ApiClient {
109109
this.httpOptions = defaultHttpOptions(/* vertexAI= */ true, this.location);
110110

111111
if (customHttpOptions.isPresent()) {
112-
applyHttpOptions(customHttpOptions.get());
112+
this.httpOptions = applyHttpOptions(customHttpOptions.get());
113113
}
114114
this.apiKey = Optional.empty();
115115
this.vertexAI = true;
@@ -128,7 +128,8 @@ private CloseableHttpClient createHttpClient(Optional<Integer> timeout) {
128128
}
129129

130130
/** Sends a Http request given the http method, path, and request json string. */
131-
public abstract ApiResponse request(String httpMethod, String path, String requestJson);
131+
public abstract ApiResponse request(
132+
String httpMethod, String path, String requestJson, HttpOptions httpOptions);
132133

133134
/** Returns the library version. */
134135
static String libraryVersion() {
@@ -172,7 +173,16 @@ private Optional<Map<String, String>> getTimeoutHeader(HttpOptions httpOptionsTo
172173
return Optional.empty();
173174
}
174175

175-
private void applyHttpOptions(HttpOptions httpOptionsToApply) {
176+
/**
177+
* Applies the http options to the client's http options.
178+
*
179+
* @param httpOptionsToApply the http options to apply
180+
* @return the merged http options
181+
*/
182+
HttpOptions applyHttpOptions(HttpOptions httpOptionsToApply) {
183+
if (httpOptionsToApply == null) {
184+
return this.httpOptions;
185+
}
176186
HttpOptions.Builder mergedHttpOptionsBuilder = this.httpOptions.toBuilder();
177187
if (httpOptionsToApply.baseUrl().isPresent()) {
178188
mergedHttpOptionsBuilder.baseUrl(httpOptionsToApply.baseUrl().get());
@@ -192,7 +202,7 @@ private void applyHttpOptions(HttpOptions httpOptionsToApply) {
192202
.build();
193203
mergedHttpOptionsBuilder.headers(mergedHeaders);
194204
}
195-
this.httpOptions = mergedHttpOptionsBuilder.build();
205+
return mergedHttpOptionsBuilder.build();
196206
}
197207

198208
static HttpOptions defaultHttpOptions(boolean vertexAI, Optional<String> location) {

src/main/java/com/google/genai/HttpApiClient.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,11 @@ public class HttpApiClient extends ApiClient {
4949
super(project, location, credentials, httpOptions);
5050
}
5151

52-
/** Sends a Http request given the http method, path, and request json string. */
52+
/** Sends a Http request given the http method, path, request json string, and http options. */
5353
@Override
54-
public HttpApiResponse request(String httpMethod, String path, String requestJson) {
54+
public HttpApiResponse request(
55+
String httpMethod, String path, String requestJson, HttpOptions httpOptions) {
56+
HttpOptions requestHttpOptions = applyHttpOptions(httpOptions);
5557
boolean queryBaseModel =
5658
httpMethod.equalsIgnoreCase("GET") && path.startsWith("publishers/google/models/");
5759
if (this.vertexAI() && !path.startsWith("projects/") && !queryBaseModel) {
@@ -61,30 +63,31 @@ public HttpApiResponse request(String httpMethod, String path, String requestJso
6163
}
6264
String requestUrl =
6365
String.format(
64-
"%s/%s/%s", httpOptions.baseUrl().get(), httpOptions.apiVersion().get(), path);
66+
"%s/%s/%s",
67+
requestHttpOptions.baseUrl().get(), requestHttpOptions.apiVersion().get(), path);
6568

6669
if (httpMethod.equalsIgnoreCase("POST")) {
6770
HttpPost httpPost = new HttpPost(requestUrl);
68-
setHeaders(httpPost);
71+
setHeaders(httpPost, requestHttpOptions);
6972
httpPost.setEntity(new StringEntity(requestJson, ContentType.APPLICATION_JSON));
7073
return executeRequest(httpPost);
7174
} else if (httpMethod.equalsIgnoreCase("GET")) {
7275
HttpGet httpGet = new HttpGet(requestUrl);
73-
setHeaders(httpGet);
76+
setHeaders(httpGet, requestHttpOptions);
7477
return executeRequest(httpGet);
7578
} else if (httpMethod.equalsIgnoreCase("DELETE")) {
7679
HttpDelete httpDelete = new HttpDelete(requestUrl);
77-
setHeaders(httpDelete);
80+
setHeaders(httpDelete, requestHttpOptions);
7881
return executeRequest(httpDelete);
7982
} else {
8083
throw new IllegalArgumentException("Unsupported HTTP method: " + httpMethod);
8184
}
8285
}
8386

8487
/** Sets the required headers (including auth) on the request object. */
85-
private void setHeaders(HttpRequestBase request) {
88+
private void setHeaders(HttpRequestBase request, HttpOptions requestHttpOptions) {
8689
for (Map.Entry<String, String> header :
87-
httpOptions.headers().orElse(ImmutableMap.of()).entrySet()) {
90+
requestHttpOptions.headers().orElse(ImmutableMap.of()).entrySet()) {
8891
request.setHeader(header.getKey(), header.getValue());
8992
}
9093

src/main/java/com/google/genai/Models.java

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import com.google.genai.types.GenerateVideosOperation;
4848
import com.google.genai.types.GenerateVideosParameters;
4949
import com.google.genai.types.GeneratedImage;
50+
import com.google.genai.types.HttpOptions;
5051
import com.google.genai.types.Image;
5152
import com.google.genai.types.Part;
5253
import com.google.genai.types.ReferenceImage;
@@ -4116,8 +4117,13 @@ private GenerateContentResponse privateGenerateContent(
41164117
// TODO: Remove the hack that removes config.
41174118
body.remove("config");
41184119

4120+
HttpOptions httpOptions = null;
4121+
if (config != null) {
4122+
httpOptions = config.httpOptions().orElse(null);
4123+
}
4124+
41194125
try (ApiResponse response =
4120-
this.apiClient.request("post", path, JsonSerializable.toJsonString(body))) {
4126+
this.apiClient.request("post", path, JsonSerializable.toJsonString(body), httpOptions)) {
41214127
HttpEntity entity = response.getEntity();
41224128
String responseString;
41234129
try {
@@ -4167,8 +4173,13 @@ private ResponseStream<GenerateContentResponse> privateGenerateContentStream(
41674173
// TODO: Remove the hack that removes config.
41684174
body.remove("config");
41694175

4176+
HttpOptions httpOptions = null;
4177+
if (config != null) {
4178+
httpOptions = config.httpOptions().orElse(null);
4179+
}
4180+
41704181
ApiResponse response =
4171-
this.apiClient.request("post", path, JsonSerializable.toJsonString(body));
4182+
this.apiClient.request("post", path, JsonSerializable.toJsonString(body), httpOptions);
41724183
String converterName;
41734184
if (this.apiClient.vertexAI()) {
41744185
converterName = "generateContentResponseFromVertex";
@@ -4211,8 +4222,13 @@ private EmbedContentResponse privateEmbedContent(
42114222
// TODO: Remove the hack that removes config.
42124223
body.remove("config");
42134224

4225+
HttpOptions httpOptions = null;
4226+
if (config != null) {
4227+
httpOptions = config.httpOptions().orElse(null);
4228+
}
4229+
42144230
try (ApiResponse response =
4215-
this.apiClient.request("post", path, JsonSerializable.toJsonString(body))) {
4231+
this.apiClient.request("post", path, JsonSerializable.toJsonString(body), httpOptions)) {
42164232
HttpEntity entity = response.getEntity();
42174233
String responseString;
42184234
try {
@@ -4262,8 +4278,13 @@ private GenerateImagesResponse privateGenerateImages(
42624278
// TODO: Remove the hack that removes config.
42634279
body.remove("config");
42644280

4281+
HttpOptions httpOptions = null;
4282+
if (config != null) {
4283+
httpOptions = config.httpOptions().orElse(null);
4284+
}
4285+
42654286
try (ApiResponse response =
4266-
this.apiClient.request("post", path, JsonSerializable.toJsonString(body))) {
4287+
this.apiClient.request("post", path, JsonSerializable.toJsonString(body), httpOptions)) {
42674288
HttpEntity entity = response.getEntity();
42684289
String responseString;
42694290
try {
@@ -4319,8 +4340,13 @@ private EditImageResponse privateEditImage(
43194340
// TODO: Remove the hack that removes config.
43204341
body.remove("config");
43214342

4343+
HttpOptions httpOptions = null;
4344+
if (config != null) {
4345+
httpOptions = config.httpOptions().orElse(null);
4346+
}
4347+
43224348
try (ApiResponse response =
4323-
this.apiClient.request("post", path, JsonSerializable.toJsonString(body))) {
4349+
this.apiClient.request("post", path, JsonSerializable.toJsonString(body), httpOptions)) {
43244350
HttpEntity entity = response.getEntity();
43254351
String responseString;
43264352
try {
@@ -4374,8 +4400,13 @@ private UpscaleImageResponse privateUpscaleImage(
43744400
// TODO: Remove the hack that removes config.
43754401
body.remove("config");
43764402

4403+
HttpOptions httpOptions = null;
4404+
if (config != null) {
4405+
httpOptions = config.httpOptions().orElse(null);
4406+
}
4407+
43774408
try (ApiResponse response =
4378-
this.apiClient.request("post", path, JsonSerializable.toJsonString(body))) {
4409+
this.apiClient.request("post", path, JsonSerializable.toJsonString(body), httpOptions)) {
43794410
HttpEntity entity = response.getEntity();
43804411
String responseString;
43814412
try {
@@ -4426,8 +4457,13 @@ public CountTokensResponse countTokens(
44264457
// TODO: Remove the hack that removes config.
44274458
body.remove("config");
44284459

4460+
HttpOptions httpOptions = null;
4461+
if (config != null) {
4462+
httpOptions = config.httpOptions().orElse(null);
4463+
}
4464+
44294465
try (ApiResponse response =
4430-
this.apiClient.request("post", path, JsonSerializable.toJsonString(body))) {
4466+
this.apiClient.request("post", path, JsonSerializable.toJsonString(body), httpOptions)) {
44314467
HttpEntity entity = response.getEntity();
44324468
String responseString;
44334469
try {
@@ -4477,8 +4513,13 @@ public ComputeTokensResponse computeTokens(
44774513
// TODO: Remove the hack that removes config.
44784514
body.remove("config");
44794515

4516+
HttpOptions httpOptions = null;
4517+
if (config != null) {
4518+
httpOptions = config.httpOptions().orElse(null);
4519+
}
4520+
44804521
try (ApiResponse response =
4481-
this.apiClient.request("post", path, JsonSerializable.toJsonString(body))) {
4522+
this.apiClient.request("post", path, JsonSerializable.toJsonString(body), httpOptions)) {
44824523
HttpEntity entity = response.getEntity();
44834524
String responseString;
44844525
try {
@@ -4545,8 +4586,13 @@ public GenerateVideosOperation generateVideos(
45454586
// TODO: Remove the hack that removes config.
45464587
body.remove("config");
45474588

4589+
HttpOptions httpOptions = null;
4590+
if (config != null) {
4591+
httpOptions = config.httpOptions().orElse(null);
4592+
}
4593+
45484594
try (ApiResponse response =
4549-
this.apiClient.request("post", path, JsonSerializable.toJsonString(body))) {
4595+
this.apiClient.request("post", path, JsonSerializable.toJsonString(body), httpOptions)) {
45504596
HttpEntity entity = response.getEntity();
45514597
String responseString;
45524598
try {

src/main/java/com/google/genai/Operations.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.google.genai.types.GenerateVideosOperation;
2929
import com.google.genai.types.GetOperationConfig;
3030
import com.google.genai.types.GetOperationParameters;
31+
import com.google.genai.types.HttpOptions;
3132
import java.io.IOException;
3233
import org.apache.http.HttpEntity;
3334
import org.apache.http.util.EntityUtils;
@@ -402,8 +403,13 @@ private GenerateVideosOperation privateGetVideosOperation(
402403
// TODO: Remove the hack that removes config.
403404
body.remove("config");
404405

406+
HttpOptions httpOptions = null;
407+
if (config != null) {
408+
httpOptions = config.httpOptions().orElse(null);
409+
}
410+
405411
try (ApiResponse response =
406-
this.apiClient.request("get", path, JsonSerializable.toJsonString(body))) {
412+
this.apiClient.request("get", path, JsonSerializable.toJsonString(body), httpOptions)) {
407413
HttpEntity entity = response.getEntity();
408414
String responseString;
409415
try {
@@ -454,8 +460,13 @@ private GenerateVideosOperation privateFetchPredictVideosOperation(
454460
// TODO: Remove the hack that removes config.
455461
body.remove("config");
456462

463+
HttpOptions httpOptions = null;
464+
if (config != null) {
465+
httpOptions = config.httpOptions().orElse(null);
466+
}
467+
457468
try (ApiResponse response =
458-
this.apiClient.request("post", path, JsonSerializable.toJsonString(body))) {
469+
this.apiClient.request("post", path, JsonSerializable.toJsonString(body), httpOptions)) {
459470
HttpEntity entity = response.getEntity();
460471
String responseString;
461472
try {

src/main/java/com/google/genai/ReplayApiClient.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ void initializeReplaySession(String replayId) {
121121
/** Sends a Http Post request given the path and request json string. */
122122
@SuppressWarnings("unchecked")
123123
@Override
124-
public ApiResponse request(String httpMethod, String path, String requestJson) {
124+
public ApiResponse request(
125+
String httpMethod, String path, String requestJson, HttpOptions httpOptions) {
125126
if (this.clientMode.equals("replay") || this.clientMode.equals("auto")) {
126127
System.out.println(" === Using replay for ID: " + this.replayId);
127128
List<Object> interactions = Arrays.asList(this.replaySession.get("interactions"));

src/test/java/com/google/genai/ChatTest.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static org.junit.jupiter.api.Assertions.assertNotNull;
2020
import static org.junit.jupiter.api.Assertions.assertThrows;
21+
import static org.mockito.ArgumentMatchers.any;
2122
import static org.mockito.ArgumentMatchers.anyString;
2223
import static org.mockito.Mockito.when;
2324

@@ -142,7 +143,8 @@ public class ChatTest {
142143
void setUp() {
143144
mockedClient = Mockito.mock(ApiClient.class);
144145
mockedResponse = Mockito.mock(ApiResponse.class);
145-
when(mockedClient.request(anyString(), anyString(), anyString())).thenReturn(mockedResponse);
146+
when(mockedClient.request(anyString(), anyString(), anyString(), any()))
147+
.thenReturn(mockedResponse);
146148
mockedEntity = Mockito.mock(HttpEntity.class);
147149

148150
client = Client.builder().build();
@@ -433,7 +435,7 @@ public void testIterateOverResponseStream() throws Exception {
433435
when(mockedEntity1.getContent()).thenReturn(inputStream1);
434436
when(mockedEntity2.getContent()).thenReturn(inputStream2);
435437
when(mockedEntity3.getContent()).thenReturn(inputStream3);
436-
when(mockedClient.request(anyString(), anyString(), anyString()))
438+
when(mockedClient.request(anyString(), anyString(), anyString(), any()))
437439
.thenReturn(mockedResponse1, mockedResponse2, mockedResponse3);
438440

439441
assert chatSession.getHistory(false).size() == 0;
@@ -519,7 +521,7 @@ public void testThrowsIfStreamResponseIsNotConsumed() throws Exception {
519521
when(mockedResponse2.getEntity()).thenReturn(mockedEntity2);
520522
when(mockedEntity1.getContent()).thenReturn(inputStream1);
521523
when(mockedEntity2.getContent()).thenReturn(inputStream2);
522-
when(mockedClient.request(anyString(), anyString(), anyString()))
524+
when(mockedClient.request(anyString(), anyString(), anyString(), any()))
523525
.thenReturn(mockedResponse1, mockedResponse2);
524526

525527
assert chatSession.getHistory(false).size() == 0;

src/test/java/com/google/genai/DefaultValuesTest.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.genai;
1818

1919
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.mockito.ArgumentMatchers.any;
2021
import static org.mockito.ArgumentMatchers.anyString;
2122
import static org.mockito.Mockito.verify;
2223
import static org.mockito.Mockito.when;
@@ -37,7 +38,8 @@ public void testDefaultValues() throws Exception {
3738
// Mocks and test setup.
3839
ApiClient httpClientSpy = Mockito.spy(Mockito.mock(ApiClient.class));
3940
ApiResponse mockedResponse = Mockito.mock(ApiResponse.class);
40-
when(httpClientSpy.request(anyString(), anyString(), anyString())).thenReturn(mockedResponse);
41+
when(httpClientSpy.request(anyString(), anyString(), anyString(), any()))
42+
.thenReturn(mockedResponse);
4143
HttpEntity mockedEntity = Mockito.mock(HttpEntity.class);
4244
GenerateContentResponse returnResponse = GenerateContentResponse.builder().build();
4345
StringEntity content = new StringEntity(returnResponse.toJson());
@@ -55,7 +57,8 @@ public void testDefaultValues() throws Exception {
5557

5658
ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
5759
verify(httpClientSpy)
58-
.request(argumentCaptor.capture(), argumentCaptor.capture(), argumentCaptor.capture());
60+
.request(
61+
argumentCaptor.capture(), argumentCaptor.capture(), argumentCaptor.capture(), any());
5962
GenerateContentConfig spiedConfig =
6063
GenerateContentConfig.fromJson(argumentCaptor.getAllValues().get(2));
6164

0 commit comments

Comments
 (0)