Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/main/java/com/google/genai/ApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public abstract class ApiClient implements AutoCloseable {
// {x-version-update-start:google-genai:released}
private static final String SDK_VERSION = "1.80.0";
// {x-version-update-end:google-genai:released}
public static final String GOOGLE_AI_API_DEFAULT_VERSION = "v1";
private static final Logger logger = Logger.getLogger(ApiClient.class.getName());

private static final ImmutableSet<String> METHODS_WITH_BODY =
Expand Down Expand Up @@ -769,8 +770,10 @@ static HttpOptions defaultHttpOptions(
HttpOptions.builder().headers(defaultHeaders.build());

ImmutableMap<String, String> defaultEnvironmentVariables = defaultEnvironmentVariables();
String envApiVersion = defaultEnvironmentVariables.get("apiVersion");
if (vertexAI) {
defaultHttpOptionsBuilder.apiVersion("v1beta1");
defaultHttpOptionsBuilder.apiVersion(
envApiVersion != null && !envApiVersion.isEmpty() ? envApiVersion : "v1beta1");
String defaultBaseUrl =
vertexBaseUrl.orElseGet(() -> defaultEnvironmentVariables.get("vertexBaseUrl"));
if (defaultBaseUrl != null) {
Expand All @@ -791,7 +794,10 @@ static HttpOptions defaultHttpOptions(
String.format("https://%s-aiplatform.googleapis.com", location.get()));
}
} else {
defaultHttpOptionsBuilder.apiVersion("v1beta");
defaultHttpOptionsBuilder.apiVersion(
envApiVersion != null && !envApiVersion.isEmpty()
? envApiVersion
: GOOGLE_AI_API_DEFAULT_VERSION);
String defaultBaseUrl =
geminiBaseUrl.orElseGet(() -> defaultEnvironmentVariables.get("geminiBaseUrl"));
if (defaultBaseUrl != null) {
Expand Down Expand Up @@ -909,6 +915,10 @@ public static ImmutableMap<String, String> defaultEnvironmentVariables() {
if (value != null && !value.isEmpty()) {
mapBuilder.put("vertexBaseUrl", value);
}
value = System.getenv("GOOGLE_GENAI_API_VERSION");
if (value != null && !value.isEmpty()) {
mapBuilder.put("apiVersion", value);
}
return mapBuilder.buildOrThrow();
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/google/genai/AsyncLive.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ private URI getWebSocketUri() {
logger.warning(
"Warning: Ephemeral token support is experimental and may change in future"
+ " versions.");
if (!apiClient.httpOptions().apiVersion().orElse("v1beta").equals("v1alpha")) {
if (!apiClient.httpOptions().apiVersion().orElse(ApiClient.GOOGLE_AI_API_DEFAULT_VERSION).equals("v1alpha")) {
logger.warning(
"Warning: The SDK's ephemeral token support is in v1alpha only. Please use client"
+ " = Client.builder().httpOptions(HttpOptions.builder().apiVersion(\"v1alpha\").build()).build()"
Expand All @@ -126,7 +126,7 @@ private URI getWebSocketUri() {
return new URI(
String.format(
"%s/ws/google.ai.generativelanguage.%s.GenerativeService.%s",
wsBaseUrl, apiClient.httpOptions().apiVersion().orElse("v1beta"), method));
wsBaseUrl, apiClient.httpOptions().apiVersion().orElse(ApiClient.GOOGLE_AI_API_DEFAULT_VERSION), method));
} else {
return new URI(
String.format(
Expand Down
23 changes: 22 additions & 1 deletion src/main/java/com/google/genai/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ public Async(ApiClient apiClient) {
/** Builder for {@link Client}. */
public static class Builder {
private Optional<String> apiKey = Optional.empty();
private Optional<String> apiVersion = Optional.empty();
private Optional<String> project = Optional.empty();
private Optional<String> location = Optional.empty();
private Optional<GoogleCredentials> credentials = Optional.empty();
Expand All @@ -155,20 +156,40 @@ public static class Builder {

/** Builds the {@link Client} instance. */
public Client build() {
Optional<HttpOptions> resolvedHttpOptions = this.httpOptions;
if (this.apiVersion.isPresent()) {
if (this.httpOptions.isPresent() && this.httpOptions.get().apiVersion().isPresent()) {
throw new IllegalArgumentException(
"apiVersion is set in both Client.Builder and HttpOptions. Please specify apiVersion"
+ " in only one place.");
}
HttpOptions currentOptions =
this.httpOptions.orElseGet(() -> HttpOptions.builder().build());
resolvedHttpOptions =
Optional.of(currentOptions.toBuilder().apiVersion(this.apiVersion.get()).build());
}

return new Client(
apiKey,
project,
location,
credentials,
httpOptions,
resolvedHttpOptions,
clientOptions,
vertexAI,
enterprise,
debugConfig,
asyncRetryScheduler);
}

/** Sets the API version for the client (e.g. "v1" or "v1beta"). */
@CanIgnoreReturnValue
public Builder apiVersion(String apiVersion) {
checkNotNull(apiVersion, "apiVersion cannot be null");
this.apiVersion = Optional.of(apiVersion);
return this;
}

/** Sets the API key for Gemini API. */
@CanIgnoreReturnValue
public Builder apiKey(String apiKey) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/google/genai/gaos/Files.java
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ String buildDownloadUrl(String environment, String path, Options options) {
String baseUrl = this.sdkConfiguration.serverUrl();
String apiVersion = (String) this.sdkConfiguration.globals.getParam("pathParam", "api_version")
.map(Object::toString)
.orElse(SDKConfiguration.OPENAPI_DOC_VERSION);
.orElse(com.google.genai.ApiClient.GOOGLE_AI_API_DEFAULT_VERSION);
StringBuilder urlBuilder = new StringBuilder();
if (baseUrl.endsWith("/")) {
urlBuilder.append(baseUrl.substring(0, baseUrl.length() - 1));
Expand Down
100 changes: 100 additions & 0 deletions src/test/java/com/google/genai/ClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,41 @@ public void testInitClientFromBuilder_withCredentialsAndHttpOptions() {
assertEquals("test-url", client.baseUrl().orElse(null));
}

@Test
public void testInitClientFromBuilder_withApiVersion() {
// Act
Client client =
Client.builder()
.apiKey(API_KEY)
.vertexAI(false)
.apiVersion("v1beta")
.build();

// Assert
assertEquals(API_KEY, client.apiKey());
assertFalse(client.vertexAI());
}

@Test
public void testInitClientFromBuilder_withApiVersionConflict_throwsException() {
// Act
IllegalArgumentException exception =
assertThrows(
IllegalArgumentException.class,
() ->
Client.builder()
.apiKey(API_KEY)
.vertexAI(false)
.apiVersion("v1beta")
.httpOptions(HttpOptions.builder().apiVersion("v1").build())
.build());

// Assert
assertEquals(
"apiVersion is set in both Client.Builder and HttpOptions. Please specify apiVersion in only one place.",
exception.getMessage());
}

@Test
public void testReplayClient_mldev() {
// Act
Expand Down Expand Up @@ -374,6 +409,71 @@ void defaultIsFalse_whenNoFlagsOrEnvsAreSet() {
}
}

@Test
void apiVersionEnvVar_isRespected_mldev() {
try (MockedStatic<ApiClient> mocked =
Mockito.mockStatic(ApiClient.class, Mockito.CALLS_REAL_METHODS)) {
mocked
.when(ApiClient::defaultEnvironmentVariables)
.thenReturn(ImmutableMap.of("googleApiKey", "api-key", "apiVersion", "v1alpha"));
Client client = new Client();
assertFalse(client.vertexAI());
assertEquals(Optional.of("v1alpha"), client.models.apiClient.httpOptions().apiVersion());
}
}

@Test
void apiVersionEnvVar_isRespected_vertex() {
try (MockedStatic<ApiClient> mocked =
Mockito.mockStatic(ApiClient.class, Mockito.CALLS_REAL_METHODS);
MockedStatic<GoogleCredentials> mockedCredentials =
Mockito.mockStatic(GoogleCredentials.class)) {
mockedCredentials.when(GoogleCredentials::getApplicationDefault).thenReturn(CREDENTIALS);

mocked
.when(ApiClient::defaultEnvironmentVariables)
.thenReturn(
ImmutableMap.of(
"vertexAI",
"true",
"project",
"project",
"location",
"location",
"apiVersion",
"v1"));
Client client = new Client();
assertTrue(client.vertexAI());
assertEquals(Optional.of("v1"), client.models.apiClient.httpOptions().apiVersion());
}
}

@Test
void explicitApiVersion_overridesEnvVar_withoutThrowing() {
try (MockedStatic<ApiClient> mocked =
Mockito.mockStatic(ApiClient.class, Mockito.CALLS_REAL_METHODS)) {
mocked
.when(ApiClient::defaultEnvironmentVariables)
.thenReturn(ImmutableMap.of("googleApiKey", "api-key", "apiVersion", "v1alpha"));

Client clientWithBuilderApiVersion =
Client.builder().apiKey(API_KEY).vertexAI(false).apiVersion("v1").build();
assertEquals(
Optional.of("v1"),
clientWithBuilderApiVersion.models.apiClient.httpOptions().apiVersion());

Client clientWithHttpOptionsApiVersion =
Client.builder()
.apiKey(API_KEY)
.vertexAI(false)
.httpOptions(HttpOptions.builder().apiVersion("v1beta").build())
.build();
assertEquals(
Optional.of("v1beta"),
clientWithHttpOptionsApiVersion.models.apiClient.httpOptions().apiVersion());
}
}

static class TestApiClient extends ApiClient {
TestApiClient(HttpOptions httpOptions) {
super(Optional.of("dummy-api-key"), Optional.of(httpOptions), Optional.empty());
Expand Down
33 changes: 18 additions & 15 deletions src/test/java/com/google/genai/CredentialsLifecycleTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ public HttpResponse<InputStream> send(HttpRequest request) {
}
}

if ("GET".equals(method) && "/v1beta/credentials".equals(path)) {
if ("GET".equals(method)
&& ("/" + ApiClient.GOOGLE_AI_API_DEFAULT_VERSION + "/credentials")
.equals(path)) {
return createMockResponse(
authorized,
200,
Expand Down Expand Up @@ -275,17 +277,18 @@ public void testRoutesThroughGoogleGenAiClientForBearerOAuth2AndEnvVars() throws
// 9. Delete credential
ai.credentials.delete("cred_bearer_123");

String v = ApiClient.GOOGLE_AI_API_DEFAULT_VERSION;
assertEquals(
Arrays.asList(
"POST /v1beta/credentials",
"POST /v1beta/credentials",
"POST /v1beta/credentials",
"GET /v1beta/credentials",
"GET /v1beta/credentials/cred_bearer_123",
"PATCH /v1beta/credentials/cred_bearer_123",
"PATCH /v1beta/credentials/cred_env_123",
"PATCH /v1beta/credentials/cred_oauth_123",
"DELETE /v1beta/credentials/cred_bearer_123"),
"POST /" + v + "/credentials",
"POST /" + v + "/credentials",
"POST /" + v + "/credentials",
"GET /" + v + "/credentials",
"GET /" + v + "/credentials/cred_bearer_123",
"PATCH /" + v + "/credentials/cred_bearer_123",
"PATCH /" + v + "/credentials/cred_env_123",
"PATCH /" + v + "/credentials/cred_oauth_123",
"DELETE /" + v + "/credentials/cred_bearer_123"),
captured);

// Verify request bodies
Expand Down Expand Up @@ -370,11 +373,11 @@ public void testAsyncCredentialsLifecycle() throws Exception {

assertEquals(
Arrays.asList(
"POST /v1beta/credentials",
"GET /v1beta/credentials",
"GET /v1beta/credentials/cred_env_123",
"PATCH /v1beta/credentials/cred_env_123",
"DELETE /v1beta/credentials/cred_env_123"),
"POST /" + ApiClient.GOOGLE_AI_API_DEFAULT_VERSION + "/credentials",
"GET /" + ApiClient.GOOGLE_AI_API_DEFAULT_VERSION + "/credentials",
"GET /" + ApiClient.GOOGLE_AI_API_DEFAULT_VERSION + "/credentials/cred_env_123",
"PATCH /" + ApiClient.GOOGLE_AI_API_DEFAULT_VERSION + "/credentials/cred_env_123",
"DELETE /" + ApiClient.GOOGLE_AI_API_DEFAULT_VERSION + "/credentials/cred_env_123"),
captured);
}

Expand Down
12 changes: 9 additions & 3 deletions src/test/java/com/google/genai/GaosClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,11 @@ public CompletableFuture<HttpResponse<InputStream>> sendAsync(HttpRequest reques
assertNotNull(req);
assertEquals("POST", req.method());

URI expectedUri = URI.create("https://generativelanguage.googleapis.com/v1beta/interactions");
URI expectedUri =
URI.create(
"https://generativelanguage.googleapis.com/"
+ ApiClient.GOOGLE_AI_API_DEFAULT_VERSION
+ "/interactions");
assertEquals(expectedUri, req.uri());
assertEquals("test-api-key", req.headers().firstValue("x-goog-api-key").orElse(null));
assertTrue(req.headers().firstValue("user-agent").orElse("").contains("google-genai-sdk/"));
Expand Down Expand Up @@ -387,7 +391,8 @@ public CompletableFuture<HttpResponse<InputStream>> sendAsync(HttpRequest reques

String agentId = "test-agent-id";
String webhookId = "test-webhook-id";
String expectedUrlPrefix = "https://generativelanguage.googleapis.com/v1beta";
String expectedUrlPrefix =
"https://generativelanguage.googleapis.com/" + ApiClient.GOOGLE_AI_API_DEFAULT_VERSION;

// 1. Test Get Agent
client.agents.get(agentId);
Expand Down Expand Up @@ -434,7 +439,8 @@ public CompletableFuture<HttpResponse<InputStream>> sendAsync(HttpRequest reques

String credentialId = "test-credential-id";
String triggerId = "test-trigger-id";
String expectedUrlPrefix = "https://generativelanguage.googleapis.com/v1beta";
String expectedUrlPrefix =
"https://generativelanguage.googleapis.com/" + ApiClient.GOOGLE_AI_API_DEFAULT_VERSION;

// 1. Test Get Credential
client.credentials.get(credentialId);
Expand Down
10 changes: 8 additions & 2 deletions src/test/java/com/google/genai/HttpApiClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,10 @@ public void testRequestGetMethodWithMldev() throws Exception {

assertEquals("GET", capturedRequest.method());
assertEquals(
"https://generativelanguage.googleapis.com/v1beta/" + TEST_PATH,
"https://generativelanguage.googleapis.com/"
+ ApiClient.GOOGLE_AI_API_DEFAULT_VERSION
+ "/"
+ TEST_PATH,
capturedRequest.url().toString());
assertNotNull(capturedRequest.header("x-goog-api-key"));
assertEquals(API_KEY, capturedRequest.header("x-goog-api-key"));
Expand All @@ -232,7 +235,10 @@ public void testRequestDeleteMethodWithMldev() throws Exception {

assertEquals("DELETE", capturedRequest.method());
assertEquals(
"https://generativelanguage.googleapis.com/v1beta/" + TEST_PATH,
"https://generativelanguage.googleapis.com/"
+ ApiClient.GOOGLE_AI_API_DEFAULT_VERSION
+ "/"
+ TEST_PATH,
capturedRequest.url().toString());
assertNotNull(capturedRequest.header("x-goog-api-key"));
assertEquals(API_KEY, capturedRequest.header("x-goog-api-key"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private Hook.BeforeRequestContext createContext(Security security) {
private HttpRequest createBaseRequest() {
return HttpRequest.builder()
.method("POST")
.uri(URI.create("https://generativelanguage.googleapis.com/v1beta/interactions"))
.uri(URI.create("https://generativelanguage.googleapis.com/v1/interactions"))
.build();
}

Expand Down
Loading