diff --git a/examples/src/main/java/com/google/genai/examples/GenerateVideos.java b/examples/src/main/java/com/google/genai/examples/GenerateVideos.java index 690ee862abd..f979d9b21e7 100644 --- a/examples/src/main/java/com/google/genai/examples/GenerateVideos.java +++ b/examples/src/main/java/com/google/genai/examples/GenerateVideos.java @@ -86,8 +86,7 @@ public static void main(String[] args) { while (!generateVideosOperation.done().filter(Boolean::booleanValue).isPresent()) { try { Thread.sleep(10000); // Sleep for 10 seconds. - generateVideosOperation = - client.operations.getVideosOperation(generateVideosOperation, null); + generateVideosOperation = client.operations.get(generateVideosOperation, null); System.out.println("Waiting for operation to complete..."); } catch (InterruptedException e) { System.out.println("Thread was interrupted while sleeping."); diff --git a/examples/src/main/java/com/google/genai/examples/GenerateVideosAsync.java b/examples/src/main/java/com/google/genai/examples/GenerateVideosAsync.java index 5f97af5c0d3..14bea9f4148 100644 --- a/examples/src/main/java/com/google/genai/examples/GenerateVideosAsync.java +++ b/examples/src/main/java/com/google/genai/examples/GenerateVideosAsync.java @@ -92,7 +92,7 @@ public static void main(String[] args) { try { Thread.sleep(10000); // Sleep for 10 seconds. try { - operation = client.async.operations.getVideosOperation(operation, null).get(); + operation = client.async.operations.get(operation, null).get(); } catch (ExecutionException e) { throw new RuntimeException(e); } diff --git a/src/main/java/com/google/genai/AsyncOperations.java b/src/main/java/com/google/genai/AsyncOperations.java index ba2710233b5..c56811b0347 100644 --- a/src/main/java/com/google/genai/AsyncOperations.java +++ b/src/main/java/com/google/genai/AsyncOperations.java @@ -20,6 +20,7 @@ import com.google.genai.types.GenerateVideosOperation; import com.google.genai.types.GetOperationConfig; +import com.google.genai.types.Operation; import java.util.concurrent.CompletableFuture; /** Async module of {@link Operations} */ @@ -41,4 +42,16 @@ public CompletableFuture getVideosOperation( GenerateVideosOperation operation, GetOperationConfig config) { return CompletableFuture.supplyAsync(() -> operations.getVideosOperation(operation, config)); } + + /** + * Gets the status of an Operation. + * + * @param operation An Operation. + * @param config The configuration for getting the operation. + * @return An Operation with the updated status of the operation. + */ + public > CompletableFuture get( + U operation, GetOperationConfig config) { + return CompletableFuture.supplyAsync(() -> operations.get(operation, config)); + } } diff --git a/src/main/java/com/google/genai/JsonSerializable.java b/src/main/java/com/google/genai/JsonSerializable.java index 4fcd2a0c1b9..a2919940de1 100644 --- a/src/main/java/com/google/genai/JsonSerializable.java +++ b/src/main/java/com/google/genai/JsonSerializable.java @@ -38,7 +38,7 @@ /** A class that can be serialized to JSON and deserialized from JSON. */ public abstract class JsonSerializable { - static final ObjectMapper objectMapper = new ObjectMapper(); + @InternalApi protected static final ObjectMapper objectMapper = new ObjectMapper(); /** Custom Jackson serializer for {@link java.time.Duration} to output "Xs" format. */ static class CustomDurationSerializer extends JsonSerializer { @@ -134,7 +134,8 @@ protected static T fromJsonString( } /** Deserializes a JsonNode to an object of the given type. */ - static T fromJsonNode(JsonNode jsonNode, Class clazz) { + @InternalApi + protected static T fromJsonNode(JsonNode jsonNode, Class clazz) { try { return objectMapper.treeToValue(jsonNode, clazz); } catch (JsonProcessingException e) { diff --git a/src/main/java/com/google/genai/LiveConverters.java b/src/main/java/com/google/genai/LiveConverters.java index faa761be352..21137fa3b12 100644 --- a/src/main/java/com/google/genai/LiveConverters.java +++ b/src/main/java/com/google/genai/LiveConverters.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +/** Internal SDK converter functions. */ final class LiveConverters { private final ApiClient apiClient; diff --git a/src/main/java/com/google/genai/Operations.java b/src/main/java/com/google/genai/Operations.java index fa69713e752..a31f0543309 100644 --- a/src/main/java/com/google/genai/Operations.java +++ b/src/main/java/com/google/genai/Operations.java @@ -29,6 +29,7 @@ import com.google.genai.types.GetOperationConfig; import com.google.genai.types.GetOperationParameters; import com.google.genai.types.HttpOptions; +import com.google.genai.types.Operation; import java.io.IOException; import java.util.Optional; import okhttp3.ResponseBody; @@ -355,8 +356,7 @@ ObjectNode generateVideosOperationFromVertex(JsonNode fromObject, ObjectNode par return toObject; } - GenerateVideosOperation privateGetVideosOperation( - String operationName, GetOperationConfig config) { + JsonNode privateGetVideosOperation(String operationName, GetOperationConfig config) { GetOperationParameters.Builder parameterBuilder = GetOperationParameters.builder(); @@ -408,17 +408,11 @@ GenerateVideosOperation privateGetVideosOperation( throw new GenAiIOException("Failed to read HTTP response.", e); } - JsonNode responseNode = JsonSerializable.stringToJsonNode(responseString); - if (this.apiClient.vertexAI()) { - responseNode = generateVideosOperationFromVertex(responseNode, null); - } else { - responseNode = generateVideosOperationFromMldev(responseNode, null); - } - return JsonSerializable.fromJsonNode(responseNode, GenerateVideosOperation.class); + return JsonSerializable.stringToJsonNode(responseString); } } - GenerateVideosOperation privateFetchPredictVideosOperation( + JsonNode privateFetchPredictVideosOperation( String operationName, String resourceName, FetchPredictOperationConfig config) { FetchPredictOperationParameters.Builder parameterBuilder = @@ -471,14 +465,7 @@ GenerateVideosOperation privateFetchPredictVideosOperation( throw new GenAiIOException("Failed to read HTTP response.", e); } - JsonNode responseNode = JsonSerializable.stringToJsonNode(responseString); - if (this.apiClient.vertexAI()) { - responseNode = generateVideosOperationFromVertex(responseNode, null); - } else { - throw new UnsupportedOperationException( - "This method is only supported in the Vertex AI client."); - } - return JsonSerializable.fromJsonNode(responseNode, GenerateVideosOperation.class); + return JsonSerializable.stringToJsonNode(responseString); } } @@ -491,9 +478,35 @@ GenerateVideosOperation privateFetchPredictVideosOperation( */ public GenerateVideosOperation getVideosOperation( GenerateVideosOperation operation, GetOperationConfig config) { + if (!operation.name().isPresent()) { + throw new IllegalArgumentException("Operation name is required."); + } + if (this.apiClient.vertexAI()) { + String resourceName = operation.name().get().split("/operations/")[0]; + + FetchPredictOperationConfig fetchConfig = FetchPredictOperationConfig.builder().build(); + + JsonNode response = + this.privateFetchPredictVideosOperation( + operation.name().get(), resourceName, fetchConfig); + return operation.fromApiResponse(response, true); + } else { + JsonNode response = this.privateGetVideosOperation(operation.name().get(), config); + return operation.fromApiResponse(response, false); + } + } + + /** + * Gets the status of an Operation. + * + * @param operation An Operation. + * @param config The configuration for getting the operation. + * @return An Operation with the updated status of the operation. + */ + public > U get(U operation, GetOperationConfig config) { if (!operation.name().isPresent()) { - throw new Error("Operation name is required."); + throw new IllegalArgumentException("Operation name is required."); } if (this.apiClient.vertexAI()) { @@ -501,10 +514,13 @@ public GenerateVideosOperation getVideosOperation( FetchPredictOperationConfig fetchConfig = FetchPredictOperationConfig.builder().build(); - return this.privateFetchPredictVideosOperation( - operation.name().get(), resourceName, fetchConfig); + JsonNode response = + this.privateFetchPredictVideosOperation( + operation.name().get(), resourceName, fetchConfig); + return operation.fromApiResponse(response, true); } else { - return this.privateGetVideosOperation(operation.name().get(), config); + JsonNode response = this.privateGetVideosOperation(operation.name().get(), config); + return operation.fromApiResponse(response, false); } } } diff --git a/src/main/java/com/google/genai/OperationsConverters.java b/src/main/java/com/google/genai/OperationsConverters.java index 595e0bea517..134ebc2bb6f 100644 --- a/src/main/java/com/google/genai/OperationsConverters.java +++ b/src/main/java/com/google/genai/OperationsConverters.java @@ -22,8 +22,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.api.core.InternalApi; -final class OperationsConverters { +/** Internal SDK converter functions. */ +@InternalApi +public final class OperationsConverters { private final ApiClient apiClient; public OperationsConverters(ApiClient apiClient) { @@ -31,7 +34,9 @@ public OperationsConverters(ApiClient apiClient) { } @ExcludeFromGeneratedCoverageReport - ObjectNode fetchPredictOperationParametersToMldev(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode fetchPredictOperationParametersToMldev( + JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (!Common.isZero(Common.getValueByPath(fromObject, new String[] {"operationName"}))) { throw new IllegalArgumentException("operationName parameter is not supported in Gemini API."); @@ -49,7 +54,8 @@ ObjectNode fetchPredictOperationParametersToMldev(JsonNode fromObject, ObjectNod } @ExcludeFromGeneratedCoverageReport - ObjectNode getOperationParametersToMldev(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode getOperationParametersToMldev(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"operationName"}) != null) { Common.setValueByPath( @@ -69,7 +75,9 @@ ObjectNode getOperationParametersToMldev(JsonNode fromObject, ObjectNode parentO } @ExcludeFromGeneratedCoverageReport - ObjectNode fetchPredictOperationParametersToVertex(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode fetchPredictOperationParametersToVertex( + JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"operationName"}) != null) { Common.setValueByPath( @@ -96,7 +104,8 @@ ObjectNode fetchPredictOperationParametersToVertex(JsonNode fromObject, ObjectNo } @ExcludeFromGeneratedCoverageReport - ObjectNode getOperationParametersToVertex(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode getOperationParametersToVertex(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"operationName"}) != null) { Common.setValueByPath( @@ -116,7 +125,8 @@ ObjectNode getOperationParametersToVertex(JsonNode fromObject, ObjectNode parent } @ExcludeFromGeneratedCoverageReport - ObjectNode videoFromMldev(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode videoFromMldev(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"video", "uri"}) != null) { Common.setValueByPath( @@ -144,7 +154,8 @@ ObjectNode videoFromMldev(JsonNode fromObject, ObjectNode parentObject) { } @ExcludeFromGeneratedCoverageReport - ObjectNode generatedVideoFromMldev(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode generatedVideoFromMldev(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"_self"}) != null) { Common.setValueByPath( @@ -160,7 +171,8 @@ ObjectNode generatedVideoFromMldev(JsonNode fromObject, ObjectNode parentObject) } @ExcludeFromGeneratedCoverageReport - ObjectNode generateVideosResponseFromMldev(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode generateVideosResponseFromMldev(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"generatedSamples"}) != null) { ArrayNode keyArray = @@ -192,7 +204,8 @@ ObjectNode generateVideosResponseFromMldev(JsonNode fromObject, ObjectNode paren } @ExcludeFromGeneratedCoverageReport - ObjectNode generateVideosOperationFromMldev(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode generateVideosOperationFromMldev(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"name"}) != null) { Common.setValueByPath( @@ -238,7 +251,8 @@ ObjectNode generateVideosOperationFromMldev(JsonNode fromObject, ObjectNode pare } @ExcludeFromGeneratedCoverageReport - ObjectNode videoFromVertex(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode videoFromVertex(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"gcsUri"}) != null) { Common.setValueByPath( @@ -266,7 +280,8 @@ ObjectNode videoFromVertex(JsonNode fromObject, ObjectNode parentObject) { } @ExcludeFromGeneratedCoverageReport - ObjectNode generatedVideoFromVertex(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode generatedVideoFromVertex(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"_self"}) != null) { Common.setValueByPath( @@ -282,7 +297,8 @@ ObjectNode generatedVideoFromVertex(JsonNode fromObject, ObjectNode parentObject } @ExcludeFromGeneratedCoverageReport - ObjectNode generateVideosResponseFromVertex(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode generateVideosResponseFromVertex(JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"videos"}) != null) { ArrayNode keyArray = (ArrayNode) Common.getValueByPath(fromObject, new String[] {"videos"}); @@ -313,7 +329,9 @@ ObjectNode generateVideosResponseFromVertex(JsonNode fromObject, ObjectNode pare } @ExcludeFromGeneratedCoverageReport - ObjectNode generateVideosOperationFromVertex(JsonNode fromObject, ObjectNode parentObject) { + @InternalApi + public ObjectNode generateVideosOperationFromVertex( + JsonNode fromObject, ObjectNode parentObject) { ObjectNode toObject = JsonSerializable.objectMapper.createObjectNode(); if (Common.getValueByPath(fromObject, new String[] {"name"}) != null) { Common.setValueByPath( diff --git a/src/main/java/com/google/genai/TokensConverters.java b/src/main/java/com/google/genai/TokensConverters.java index aca63a40266..5e1c190cf9e 100644 --- a/src/main/java/com/google/genai/TokensConverters.java +++ b/src/main/java/com/google/genai/TokensConverters.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +/** Internal SDK converter functions. */ final class TokensConverters { private final ApiClient apiClient; diff --git a/src/main/java/com/google/genai/types/GenerateVideosOperation.java b/src/main/java/com/google/genai/types/GenerateVideosOperation.java index 8e8d7980379..a1468aa97e4 100644 --- a/src/main/java/com/google/genai/types/GenerateVideosOperation.java +++ b/src/main/java/com/google/genai/types/GenerateVideosOperation.java @@ -20,48 +20,36 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; import com.google.genai.JsonSerializable; +import com.google.genai.OperationsConverters; import java.util.Map; import java.util.Optional; /** A video generation operation. */ @AutoValue @JsonDeserialize(builder = GenerateVideosOperation.Builder.class) -public abstract class GenerateVideosOperation extends JsonSerializable { - /** - * The server-assigned name, which is only unique within the same service that originally returns - * it. If you use the default HTTP mapping, the `name` should be a resource name ending with - * `operations/{unique_id}`. - */ - @JsonProperty("name") - public abstract Optional name(); - - /** - * Service-specific metadata associated with the operation. It typically contains progress - * information and common metadata such as create time. Some services might not provide such - * metadata. Any method that returns a long-running operation should document the metadata type, - * if any. - */ - @JsonProperty("metadata") - public abstract Optional> metadata(); - - /** - * If the value is `false`, it means the operation is still in progress. If `true`, the operation - * is completed, and either `error` or `response` is available. - */ - @JsonProperty("done") - public abstract Optional done(); - - /** The error result of the operation in case of failure or cancellation. */ - @JsonProperty("error") - public abstract Optional> error(); - +public abstract class GenerateVideosOperation + extends Operation { /** The generated videos. */ @JsonProperty("response") + @Override public abstract Optional response(); + @Override + public GenerateVideosOperation fromApiResponse(JsonNode apiResponse, boolean isVertexAi) { + OperationsConverters converter = new OperationsConverters(null); + JsonNode response = null; + if (isVertexAi) { + response = converter.generateVideosOperationFromVertex(apiResponse, null); + } else { + response = converter.generateVideosOperationFromMldev(apiResponse, null); + } + return JsonSerializable.fromJsonNode(response, GenerateVideosOperation.class); + } + /** Instantiates a builder for GenerateVideosOperation. */ @ExcludeFromGeneratedCoverageReport public static Builder builder() { diff --git a/src/main/java/com/google/genai/types/Operation.java b/src/main/java/com/google/genai/types/Operation.java new file mode 100644 index 00000000000..b856a379ad5 --- /dev/null +++ b/src/main/java/com/google/genai/types/Operation.java @@ -0,0 +1,69 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Auto-generated code. Do not edit. + +package com.google.genai.types; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.genai.JsonSerializable; +import java.util.Map; +import java.util.Optional; + +/** A long-running operation. */ +public abstract class Operation> extends JsonSerializable { + /** + * The server-assigned name, which is only unique within the same service that originally returns + * it. If you use the default HTTP mapping, the `name` should be a resource name ending with + * `operations/{unique_id}`. + */ + @JsonProperty("name") + public abstract Optional name(); + + /** + * Service-specific metadata associated with the operation. It typically contains progress + * information and common metadata such as create time. Some services might not provide such + * metadata. Any method that returns a long-running operation should document the metadata type, + * if any. + */ + @JsonProperty("metadata") + public abstract Optional> metadata(); + + /** + * If the value is `false`, it means the operation is still in progress. If `true`, the operation + * is completed, and either `error` or `response` is available. + */ + @JsonProperty("done") + public abstract Optional done(); + + /** The error result of the operation in case of failure or cancellation. */ + @JsonProperty("error") + public abstract Optional> error(); + + /** The generated videos. */ + @JsonProperty("response") + public abstract Optional response(); + + /** + * Creates a new Operation object from an API response. + * + * @param apiResponse The API response. + * @param isVertexAi Whether the API response is from Vertex AI. + * @return The new Operation object. + */ + public abstract O fromApiResponse(JsonNode apiResponse, boolean isVertexAi); +}