From 4ccfa47a4cf590763111d5a79c4dd10b765661d5 Mon Sep 17 00:00:00 2001 From: Mark Daoust Date: Wed, 12 Aug 2026 16:03:10 -0700 Subject: [PATCH] feat: implement environments files upload and download across Go, Java, .NET, Python, and TypeScript SDKs PiperOrigin-RevId: 963709775 --- .../genai/examples/EnvironmentFiles.java | 35 +- .../com/google/genai/gaos/AsyncFiles.java | 370 +++++++++++++ .../java/com/google/genai/gaos/Files.java | 494 +++++++++++++++++- .../CreateEnvironmentRequest.java | 51 +- .../interactions/FunctionResultDelta.java | 48 +- .../interactions/RetrievalCallStep.java | 263 ++++++++++ .../RetrievalCallStepRetrievalType.java | 156 ++++++ .../interactions/RetrievalResultStep.java | 224 ++++++++ .../interactions/StepTypeIdResolver.java | 2 + .../UploadEnvironmentFileRequest.java | 139 +++++ .../UploadEnvironmentFileResponse.java | 73 +++ 11 files changed, 1801 insertions(+), 54 deletions(-) create mode 100644 src/main/java/com/google/genai/gaos/models/interactions/RetrievalCallStep.java create mode 100644 src/main/java/com/google/genai/gaos/models/interactions/RetrievalCallStepRetrievalType.java create mode 100644 src/main/java/com/google/genai/gaos/models/interactions/RetrievalResultStep.java create mode 100644 src/main/java/com/google/genai/gaos/models/operations/UploadEnvironmentFileRequest.java create mode 100644 src/main/java/com/google/genai/gaos/models/operations/UploadEnvironmentFileResponse.java diff --git a/examples/src/main/java/com/google/genai/examples/EnvironmentFiles.java b/examples/src/main/java/com/google/genai/examples/EnvironmentFiles.java index 6dda096727d..6bc134b9634 100644 --- a/examples/src/main/java/com/google/genai/examples/EnvironmentFiles.java +++ b/examples/src/main/java/com/google/genai/examples/EnvironmentFiles.java @@ -51,11 +51,18 @@ import com.google.genai.gaos.models.operations.GetEnvironmentFilesRequest; import java.util.Arrays; import java.util.List; +import java.util.Optional; /** An example of using the Unified Gen AI Java SDK to create environments and query environment files. */ public final class EnvironmentFiles { public static void main(String[] args) { - Client client = new Client(); + com.google.genai.types.HttpOptions.Builder httpOptionsBuilder = + com.google.genai.types.HttpOptions.builder().apiVersion("v1alpha"); + String baseUrl = System.getenv("GOOGLE_GENAI_BASE_URL"); + if (baseUrl != null && !baseUrl.isEmpty()) { + httpOptionsBuilder.baseUrl(baseUrl); + } + Client client = Client.builder().httpOptions(httpOptionsBuilder.build()).build(); if (client.vertexAI()) { System.out.println( @@ -157,8 +164,32 @@ public static void main(String[] args) { System.out.println("main.py file size: " + file.sizeBytes().orElse("0")); } }); + + System.out.println("\n--- 5. Uploading a New File (path=\"uploaded.txt\") ---"); + byte[] contentToUpload = + "Hello from Java Environment Files upload demo!\n".getBytes(java.nio.charset.StandardCharsets.UTF_8); + com.google.genai.gaos.models.operations.UploadEnvironmentFileResponse uploadResponse = + client.environments.files().upload( + envId, + "uploaded.txt", + contentToUpload, + "text/plain", + true, + false); + System.out.println( + "Uploaded file name: " + + uploadResponse + .files() + .flatMap(f -> f.files().flatMap(list -> list.isEmpty() ? Optional.empty() : list.get(0).name())) + .orElse("unknown")); + + System.out.println("\n--- 6. Downloading File Content (path=\"uploaded.txt\") ---"); + byte[] downloadedBytes = client.environments.files().download(envId, "uploaded.txt"); + System.out.println( + "Downloaded uploaded.txt content: " + + new String(downloadedBytes, java.nio.charset.StandardCharsets.UTF_8).trim()); } finally { - System.out.println("\n--- 5. Cleaning up Environment ID: " + envId + " ---"); + System.out.println("\n--- 7. Cleaning up Environment ID: " + envId + " ---"); DeleteEnvironmentResponse deleteRes = client.environments.deleteEnvironment(envId); System.out.println("Environment deleted successfully: " + deleteRes.statusCode()); } diff --git a/src/main/java/com/google/genai/gaos/AsyncFiles.java b/src/main/java/com/google/genai/gaos/AsyncFiles.java index 09b68a025ef..10ad73f7e44 100644 --- a/src/main/java/com/google/genai/gaos/AsyncFiles.java +++ b/src/main/java/com/google/genai/gaos/AsyncFiles.java @@ -21,15 +21,29 @@ import static com.google.genai.gaos.operations.Operations.AsyncRequestOperation; +import com.google.genai.gaos.models.errors.GaosApiException; import com.google.genai.gaos.models.operations.GetEnvironmentFilesRequest; +import com.google.genai.gaos.models.operations.UploadEnvironmentFileRequest; +import com.google.genai.gaos.models.operations.UploadEnvironmentFileResponse; import com.google.genai.gaos.models.operations.async.GetEnvironmentFilesRequestBuilder; import com.google.genai.gaos.models.operations.async.GetEnvironmentFilesResponse; import com.google.genai.gaos.operations.GetEnvironmentFiles; import com.google.genai.gaos.operations.Operations; import com.google.genai.gaos.utils.Headers; import com.google.genai.gaos.utils.Options; +import com.google.genai.gaos.utils.transport.HttpBody; +import com.google.genai.gaos.utils.transport.HttpRequest; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.file.Path; +import java.util.Arrays; import java.util.concurrent.CompletableFuture; @@ -91,4 +105,360 @@ public CompletableFuture list(@Nonnull GetEnvironme operation::handleResponse), operation); } + /** + * Downloads binary file content from an environment workspace. + * + * @param environment The environment ID or resource name. + * @param path The relative file path to download. + * @return CompletableFuture containing the binary file content as a byte array. + */ + public CompletableFuture download(@Nonnull String environment, @Nonnull String path) { + return download(environment, path, null); + } + + /** + * Downloads binary file content from an environment workspace with custom options. + * + * @param environment The environment ID or resource name. + * @param path The relative file path to download. + * @param options Additional request options. + * @return CompletableFuture containing the binary file content as a byte array. + */ + public CompletableFuture download(@Nonnull String environment, @Nonnull String path, @Nullable Options options) { + String url = syncSDK.buildDownloadUrl(environment, path, options); + HttpRequest.Builder requestBuilder = HttpRequest.builder() + .method("GET") + .uri(URI.create(url)) + .setHeader("Accept", "application/octet-stream") + .setHeader("user-agent", SDKConfiguration.USER_AGENT); + _headers.forEach((k, list) -> list.forEach(v -> requestBuilder.header(k, v))); + + return this.sdkConfiguration.client().sendAsync(requestBuilder.build()) + .thenApply(response -> { + if (response.statusCode() >= 400) { + throw GaosApiException.from("Download failed with status: " + response.statusCode(), response); + } + try (InputStream in = response.body()) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int r; + while ((r = in.read(buf)) != -1) { + out.write(buf, 0, r); + } + return out.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("Failed to read downloaded content", e); + } + }); + } + + /** + * Downloads binary file content as an InputStream. + * + * @param environment The environment ID or resource name. + * @param path The relative file path to download. + * @return CompletableFuture containing an InputStream of the downloaded file content. + */ + public CompletableFuture downloadStream(@Nonnull String environment, @Nonnull String path) { + return download(environment, path).thenApply(ByteArrayInputStream::new); + } + + /** + * Downloads a file from an environment workspace and saves it to a local destination file. + * + * @param environment The environment ID or resource name. + * @param path The relative file path to download. + * @param destination The local File to write to. + * @return CompletableFuture that completes when writing completes. + */ + public CompletableFuture downloadToFile(@Nonnull String environment, @Nonnull String path, @Nonnull File destination) { + return download(environment, path).thenAccept(data -> { + try { + if (destination.getParentFile() != null) { + destination.getParentFile().mkdirs(); + } + java.nio.file.Files.write(destination.toPath(), data); + } catch (IOException e) { + throw new RuntimeException("Failed to write downloaded file to " + destination.getAbsolutePath(), e); + } + }); + } + + /** + * Downloads a file from an environment workspace and saves it to a local destination Path. + * + * @param environment The environment ID or resource name. + * @param path The relative file path to download. + * @param destination The local Path to write to. + * @return CompletableFuture that completes when writing completes. + */ + public CompletableFuture downloadToFile(@Nonnull String environment, @Nonnull String path, @Nonnull Path destination) { + return downloadToFile(environment, path, destination.toFile()); + } + + /** + * Uploads a file or extracts an archive inside an environment workspace asynchronously. + * + * @param request The upload request containing environment, path, content, and options. + * @return CompletableFuture containing the response. + */ + public CompletableFuture upload(@Nonnull UploadEnvironmentFileRequest request) { + return upload(request, null); + } + + /** + * Uploads a file or extracts an archive inside an environment workspace asynchronously with custom options. + * + * @param request The upload request. + * @param options Additional request options. + * @return CompletableFuture containing the response. + */ + public CompletableFuture upload( + @Nonnull UploadEnvironmentFileRequest request, + @Nullable Options options) { + String envId = request.environment().startsWith("environments/") + ? request.environment().substring("environments/".length()) + : request.environment(); + String cleanPath = request.path().replaceAll("^/+", ""); + + String baseUrl = this.sdkConfiguration.serverUrl(); + String apiVersion = request.apiVersion().orElseGet(() -> + (String) this.sdkConfiguration.globals.getParam("pathParam", "api_version") + .map(Object::toString) + .orElse(SDKConfiguration.OPENAPI_DOC_VERSION)); + + byte[] inMemoryBytes = null; + File file = request.file().orElse(null); + long sizeBytes; + if (request.bytes().isPresent()) { + inMemoryBytes = request.bytes().get(); + sizeBytes = inMemoryBytes.length; + } else if (file != null) { + sizeBytes = file.length(); + } else if (request.sizeBytes().isPresent()) { + sizeBytes = request.sizeBytes().get(); + } else { + CompletableFuture failed = new CompletableFuture<>(); + failed.completeExceptionally(new IllegalArgumentException("One of file, bytes, or stream with sizeBytes must be provided for upload.")); + return failed; + } + + String mimeType = request.mimeType().orElseGet(() -> Files.inferMimeType(request.path())); + + StringBuilder urlBuilder = new StringBuilder(); + if (baseUrl.endsWith("/")) { + urlBuilder.append(baseUrl.substring(0, baseUrl.length() - 1)); + } else { + urlBuilder.append(baseUrl); + } + urlBuilder.append("/upload/").append(apiVersion); + urlBuilder.append("/environments/").append(envId); + urlBuilder.append("/files/").append(cleanPath); + + StringBuilder query = new StringBuilder(); + if (request.overwrite().isPresent()) { + query.append("overwrite=").append(request.overwrite().get() ? "true" : "false"); + } + if (request.extract().isPresent()) { + if (query.length() > 0) { + query.append("&"); + } + query.append("extract=").append(request.extract().get() ? "true" : "false"); + } + if (query.length() > 0) { + urlBuilder.append("?").append(query.toString()); + } + + HttpRequest.Builder handshakeBuilder = HttpRequest.builder() + .method("PUT") + .uri(URI.create(urlBuilder.toString())) + .setHeader("X-Goog-Upload-Protocol", "resumable") + .setHeader("X-Goog-Upload-Command", "start") + .setHeader("X-Goog-Upload-Header-Content-Length", String.valueOf(sizeBytes)) + .setHeader("X-Goog-Upload-Header-Content-Type", mimeType) + .setHeader("user-agent", SDKConfiguration.USER_AGENT) + .setHeader("Accept", "application/json") + .body(HttpBody.empty()); + + _headers.forEach((k, list) -> list.forEach(v -> handshakeBuilder.header(k, v))); + + final byte[] finalInMemoryBytes = inMemoryBytes; + final File finalFile = file; + final long finalSizeBytes = sizeBytes; + + return this.sdkConfiguration.client().sendAsync(handshakeBuilder.build()) + .thenCompose(handshakeResponse -> { + if (handshakeResponse.statusCode() >= 400) { + throw GaosApiException.from("Upload handshake failed with status: " + handshakeResponse.statusCode(), handshakeResponse); + } + String uploadUrl = handshakeResponse.headers().first("x-goog-upload-url") + .orElseGet(() -> handshakeResponse.headers().first("X-Goog-Upload-URL") + .orElseThrow(() -> new IllegalStateException("Failed to get upload URL from upload handshake response."))); + + if (finalSizeBytes == 0) { + HttpRequest uploadReq = HttpRequest.builder() + .method("POST") + .uri(URI.create(uploadUrl)) + .setHeader("X-Goog-Upload-Command", "upload, finalize") + .setHeader("X-Goog-Upload-Offset", "0") + .setHeader("user-agent", SDKConfiguration.USER_AGENT) + .body(HttpBody.empty()) + .build(); + return this.sdkConfiguration.client().sendAsync(uploadReq) + .thenApply(uploadResp -> { + if (uploadResp.statusCode() >= 400) { + throw GaosApiException.from("Upload chunk failed with status: " + uploadResp.statusCode(), uploadResp); + } + return Files.parseUploadResponse(uploadResp); + }); + } + + InputStream inStream; + try { + if (finalInMemoryBytes != null) { + inStream = new ByteArrayInputStream(finalInMemoryBytes); + } else if (finalFile != null) { + inStream = new FileInputStream(finalFile); + } else { + inStream = request.stream().get(); + } + } catch (IOException e) { + CompletableFuture failed = new CompletableFuture<>(); + failed.completeExceptionally(e); + return failed; + } + + return uploadChunksAsync(uploadUrl, inStream, 0, finalSizeBytes) + .whenComplete((res, ex) -> { + if (finalFile != null && inStream != null) { + try { + inStream.close(); + } catch (IOException ignored) {} + } + }); + }); + } + + private CompletableFuture uploadChunksAsync( + String uploadUrl, + InputStream inStream, + long offset, + long totalSize) { + final int CHUNK_SIZE = 8 * 1024 * 1024; + int toRead = (int) Math.min(CHUNK_SIZE, totalSize - offset); + byte[] buffer = new byte[toRead]; + int bytesRead = 0; + try { + while (bytesRead < toRead) { + int r = inStream.read(buffer, bytesRead, toRead - bytesRead); + if (r == -1) break; + bytesRead += r; + } + } catch (IOException e) { + CompletableFuture failed = new CompletableFuture<>(); + failed.completeExceptionally(e); + return failed; + } + + if (bytesRead == 0) { + CompletableFuture failed = new CompletableFuture<>(); + failed.completeExceptionally(new IllegalStateException("Unexpected end of stream at offset " + offset)); + return failed; + } + + long nextOffset = offset + bytesRead; + boolean isFinal = nextOffset >= totalSize; + String uploadCommand = isFinal ? "upload, finalize" : "upload"; + + byte[] chunk = bytesRead == buffer.length ? buffer : Arrays.copyOf(buffer, bytesRead); + HttpRequest uploadReq = HttpRequest.builder() + .method("POST") + .uri(URI.create(uploadUrl)) + .setHeader("X-Goog-Upload-Command", uploadCommand) + .setHeader("X-Goog-Upload-Offset", String.valueOf(offset)) + .setHeader("user-agent", SDKConfiguration.USER_AGENT) + .body(HttpBody.of(chunk)) + .build(); + + return this.sdkConfiguration.client().sendAsync(uploadReq) + .thenCompose(response -> { + if (response.statusCode() >= 400) { + throw GaosApiException.from("Upload chunk failed with status: " + response.statusCode(), response); + } + if (isFinal) { + return CompletableFuture.completedFuture(Files.parseUploadResponse(response)); + } else { + return uploadChunksAsync(uploadUrl, inStream, nextOffset, totalSize); + } + }); + } + + public CompletableFuture upload( + @Nonnull String environment, + @Nonnull String path, + @Nonnull File file) { + return upload(environment, path, file, null, null, null); + } + + public CompletableFuture upload( + @Nonnull String environment, + @Nonnull String path, + @Nonnull File file, + @Nullable String mimeType, + @Nullable Boolean overwrite, + @Nullable Boolean extract) { + return upload( + UploadEnvironmentFileRequest.builder() + .environment(environment) + .path(path) + .file(file) + .mimeType(mimeType) + .overwrite(overwrite) + .extract(extract) + .build()); + } + + public CompletableFuture upload( + @Nonnull String environment, + @Nonnull String path, + @Nonnull byte[] bytes) { + return upload(environment, path, bytes, null, null, null); + } + + public CompletableFuture upload( + @Nonnull String environment, + @Nonnull String path, + @Nonnull byte[] bytes, + @Nullable String mimeType, + @Nullable Boolean overwrite, + @Nullable Boolean extract) { + return upload( + UploadEnvironmentFileRequest.builder() + .environment(environment) + .path(path) + .bytes(bytes) + .mimeType(mimeType) + .overwrite(overwrite) + .extract(extract) + .build()); + } + + public CompletableFuture upload( + @Nonnull String environment, + @Nonnull String path, + @Nonnull InputStream stream, + long sizeBytes, + @Nullable String mimeType, + @Nullable Boolean overwrite, + @Nullable Boolean extract) { + return upload( + UploadEnvironmentFileRequest.builder() + .environment(environment) + .path(path) + .stream(stream, sizeBytes) + .mimeType(mimeType) + .overwrite(overwrite) + .extract(extract) + .build()); + } } diff --git a/src/main/java/com/google/genai/gaos/Files.java b/src/main/java/com/google/genai/gaos/Files.java index db7a1daae55..cd2e19cdaf0 100644 --- a/src/main/java/com/google/genai/gaos/Files.java +++ b/src/main/java/com/google/genai/gaos/Files.java @@ -21,15 +21,36 @@ import static com.google.genai.gaos.operations.Operations.RequestOperation; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.genai.gaos.models.environments.EnvironmentFile; +import com.google.genai.gaos.models.environments.GetEnvironmentFilesResponse; +import com.google.genai.gaos.models.errors.GaosApiException; import com.google.genai.gaos.models.operations.GetEnvironmentFilesRequest; import com.google.genai.gaos.models.operations.GetEnvironmentFilesRequestBuilder; -import com.google.genai.gaos.models.operations.GetEnvironmentFilesResponse; +import com.google.genai.gaos.models.operations.UploadEnvironmentFileRequest; +import com.google.genai.gaos.models.operations.UploadEnvironmentFileResponse; import com.google.genai.gaos.operations.GetEnvironmentFiles; import com.google.genai.gaos.utils.Headers; import com.google.genai.gaos.utils.Options; +import com.google.genai.gaos.utils.Utils; +import com.google.genai.gaos.utils.transport.HttpBody; +import com.google.genai.gaos.utils.transport.HttpRequest; +import com.google.genai.gaos.utils.transport.HttpResponse; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; - +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Locale; public class Files { private static final Headers _headers = Headers.EMPTY; @@ -68,7 +89,7 @@ public GetEnvironmentFilesRequestBuilder list() { * @return The response from the API call * @throws RuntimeException subclass if the API call fails */ - public GetEnvironmentFilesResponse list(@Nonnull GetEnvironmentFilesRequest request) { + public com.google.genai.gaos.models.operations.GetEnvironmentFilesResponse list(@Nonnull GetEnvironmentFilesRequest request) { return list(request, null); } @@ -81,10 +102,473 @@ public GetEnvironmentFilesResponse list(@Nonnull GetEnvironmentFilesRequest requ * @return The response from the API call * @throws RuntimeException subclass if the API call fails */ - public GetEnvironmentFilesResponse list(@Nonnull GetEnvironmentFilesRequest request, @Nullable Options options) { - RequestOperation operation + public com.google.genai.gaos.models.operations.GetEnvironmentFilesResponse list(@Nonnull GetEnvironmentFilesRequest request, @Nullable Options options) { + RequestOperation operation = new GetEnvironmentFiles.Sync(sdkConfiguration, options, _headers); return operation.handleResponse(operation.doRequest(request)); } + /** + * Downloads binary file content from an environment workspace. + * + * @param environment The environment ID or resource name. + * @param path The relative file path to download. + * @return The binary file content as a byte array. + */ + public byte[] download(@Nonnull String environment, @Nonnull String path) { + return download(environment, path, null); + } + + /** + * Downloads binary file content from an environment workspace with custom options. + * + * @param environment The environment ID or resource name. + * @param path The relative file path to download. + * @param options Additional request options. + * @return The binary file content as a byte array. + */ + public byte[] download(@Nonnull String environment, @Nonnull String path, @Nullable Options options) { + String url = buildDownloadUrl(environment, path, options); + HttpRequest.Builder requestBuilder = HttpRequest.builder() + .method("GET") + .uri(URI.create(url)) + .setHeader("Accept", "application/octet-stream") + .setHeader("user-agent", SDKConfiguration.USER_AGENT); + _headers.forEach((k, list) -> list.forEach(v -> requestBuilder.header(k, v))); + try { + HttpResponse response = this.sdkConfiguration.client().send(requestBuilder.build()); + if (response.statusCode() >= 400) { + throw GaosApiException.from("Download failed with status: " + response.statusCode(), response); + } + try (InputStream in = response.body()) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int r; + while ((r = in.read(buf)) != -1) { + out.write(buf, 0, r); + } + return out.toByteArray(); + } + } catch (IOException e) { + throw new RuntimeException("Failed to download file", e); + } + } + + /** + * Downloads binary file content as an InputStream. + * + * @param environment The environment ID or resource name. + * @param path The relative file path to download. + * @return An InputStream of the downloaded file content. + */ + public InputStream downloadStream(@Nonnull String environment, @Nonnull String path) { + return new ByteArrayInputStream(download(environment, path)); + } + + /** + * Downloads a file from an environment workspace and saves it to a local destination file. + * + * @param environment The environment ID or resource name. + * @param path The relative file path to download. + * @param destination The local File to write to. + */ + public void downloadToFile(@Nonnull String environment, @Nonnull String path, @Nonnull File destination) { + byte[] data = download(environment, path); + try { + if (destination.getParentFile() != null) { + destination.getParentFile().mkdirs(); + } + java.nio.file.Files.write(destination.toPath(), data); + } catch (IOException e) { + throw new RuntimeException("Failed to write downloaded file to " + destination.getAbsolutePath(), e); + } + } + + /** + * Downloads a file from an environment workspace and saves it to a local destination Path. + * + * @param environment The environment ID or resource name. + * @param path The relative file path to download. + * @param destination The local Path to write to. + */ + public void downloadToFile(@Nonnull String environment, @Nonnull String path, @Nonnull Path destination) { + downloadToFile(environment, path, destination.toFile()); + } + + /** + * Uploads a file or extracts an archive inside an environment workspace. + * + * @param request The upload request containing environment, path, content, and options. + * @return The response containing the uploaded file metadata or extracted files. + */ + public UploadEnvironmentFileResponse upload(@Nonnull UploadEnvironmentFileRequest request) { + return upload(request, null); + } + + /** + * Uploads a file or extracts an archive inside an environment workspace with custom options. + * + * @param request The upload request. + * @param options Additional request options. + * @return The response containing the uploaded file metadata or extracted files. + */ + public UploadEnvironmentFileResponse upload( + @Nonnull UploadEnvironmentFileRequest request, + @Nullable Options options) { + String envId = request.environment().startsWith("environments/") + ? request.environment().substring("environments/".length()) + : request.environment(); + String cleanPath = request.path().replaceAll("^/+", ""); + + String baseUrl = this.sdkConfiguration.serverUrl(); + String apiVersion = request.apiVersion().orElseGet(() -> + (String) this.sdkConfiguration.globals.getParam("pathParam", "api_version") + .map(Object::toString) + .orElse(SDKConfiguration.OPENAPI_DOC_VERSION)); + + byte[] inMemoryBytes = null; + File file = request.file().orElse(null); + long sizeBytes; + if (request.bytes().isPresent()) { + inMemoryBytes = request.bytes().get(); + sizeBytes = inMemoryBytes.length; + } else if (file != null) { + sizeBytes = file.length(); + } else if (request.sizeBytes().isPresent()) { + sizeBytes = request.sizeBytes().get(); + } else { + throw new IllegalArgumentException("One of file, bytes, or stream with sizeBytes must be provided for upload."); + } + + String mimeType = request.mimeType().orElseGet(() -> inferMimeType(request.path())); + + StringBuilder urlBuilder = new StringBuilder(); + if (baseUrl.endsWith("/")) { + urlBuilder.append(baseUrl.substring(0, baseUrl.length() - 1)); + } else { + urlBuilder.append(baseUrl); + } + urlBuilder.append("/upload/").append(apiVersion); + urlBuilder.append("/environments/").append(envId); + urlBuilder.append("/files/").append(cleanPath); + + StringBuilder query = new StringBuilder(); + if (request.overwrite().isPresent()) { + query.append("overwrite=").append(request.overwrite().get() ? "true" : "false"); + } + if (request.extract().isPresent()) { + if (query.length() > 0) { + query.append("&"); + } + query.append("extract=").append(request.extract().get() ? "true" : "false"); + } + if (query.length() > 0) { + urlBuilder.append("?").append(query.toString()); + } + + HttpRequest.Builder handshakeBuilder = HttpRequest.builder() + .method("PUT") + .uri(URI.create(urlBuilder.toString())) + .setHeader("X-Goog-Upload-Protocol", "resumable") + .setHeader("X-Goog-Upload-Command", "start") + .setHeader("X-Goog-Upload-Header-Content-Length", String.valueOf(sizeBytes)) + .setHeader("X-Goog-Upload-Header-Content-Type", mimeType) + .setHeader("user-agent", SDKConfiguration.USER_AGENT) + .setHeader("Accept", "application/json") + .body(HttpBody.empty()); + + _headers.forEach((k, list) -> list.forEach(v -> handshakeBuilder.header(k, v))); + + HttpResponse handshakeResponse; + try { + handshakeResponse = this.sdkConfiguration.client().send(handshakeBuilder.build()); + } catch (IOException e) { + throw new RuntimeException("Failed to send upload handshake request", e); + } + + if (handshakeResponse.statusCode() >= 400) { + throw GaosApiException.from("Upload handshake failed with status: " + handshakeResponse.statusCode(), handshakeResponse); + } + + String uploadUrl = handshakeResponse.headers().first("x-goog-upload-url") + .orElseGet(() -> handshakeResponse.headers().first("X-Goog-Upload-URL") + .orElseThrow(() -> new IllegalStateException("Failed to get upload URL from upload handshake response."))); + + final int CHUNK_SIZE = 8 * 1024 * 1024; + HttpResponse uploadResponse = null; + + try { + if (sizeBytes == 0) { + HttpRequest uploadReq = HttpRequest.builder() + .method("POST") + .uri(URI.create(uploadUrl)) + .setHeader("X-Goog-Upload-Command", "upload, finalize") + .setHeader("X-Goog-Upload-Offset", "0") + .setHeader("user-agent", SDKConfiguration.USER_AGENT) + .body(HttpBody.empty()) + .build(); + uploadResponse = this.sdkConfiguration.client().send(uploadReq); + if (uploadResponse.statusCode() >= 400) { + throw GaosApiException.from("Upload chunk failed with status: " + uploadResponse.statusCode(), uploadResponse); + } + } else { + InputStream inputStream = null; + try { + if (inMemoryBytes != null) { + inputStream = new ByteArrayInputStream(inMemoryBytes); + } else if (file != null) { + inputStream = new FileInputStream(file); + } else if (request.stream().isPresent()) { + inputStream = request.stream().get(); + } + + long offset = 0; + byte[] buffer = new byte[CHUNK_SIZE]; + while (offset < sizeBytes) { + int toRead = (int) Math.min(CHUNK_SIZE, sizeBytes - offset); + int bytesRead = 0; + while (bytesRead < toRead) { + int r = inputStream.read(buffer, bytesRead, toRead - bytesRead); + if (r == -1) { + break; + } + bytesRead += r; + } + if (bytesRead == 0) { + break; + } + + long nextOffset = offset + bytesRead; + boolean isFinal = nextOffset >= sizeBytes; + String command = isFinal ? "upload, finalize" : "upload"; + + byte[] chunkBytes = Arrays.copyOf(buffer, bytesRead); + HttpRequest chunkReq = HttpRequest.builder() + .method("POST") + .uri(URI.create(uploadUrl)) + .setHeader("X-Goog-Upload-Command", command) + .setHeader("X-Goog-Upload-Offset", String.valueOf(offset)) + .setHeader("user-agent", SDKConfiguration.USER_AGENT) + .body(HttpBody.of(chunkBytes)) + .build(); + uploadResponse = this.sdkConfiguration.client().send(chunkReq); + if (uploadResponse.statusCode() >= 400) { + throw GaosApiException.from("Upload chunk failed with status: " + uploadResponse.statusCode(), uploadResponse); + } + offset = nextOffset; + } + } finally { + if (inputStream != null && !request.stream().isPresent()) { + try { + inputStream.close(); + } catch (IOException ignored) {} + } + } + } + } catch (IOException e) { + throw new RuntimeException("Failed during upload chunk transfer", e); + } + + if (uploadResponse == null) { + throw new IllegalStateException("No upload response received"); + } + + return parseUploadResponse(uploadResponse); + } + + public UploadEnvironmentFileResponse upload( + @Nonnull String environment, + @Nonnull String path, + @Nonnull File file) { + return upload( + UploadEnvironmentFileRequest.builder() + .environment(environment) + .path(path) + .file(file) + .build()); + } + + public UploadEnvironmentFileResponse upload( + @Nonnull String environment, + @Nonnull String path, + @Nonnull File file, + @Nullable String mimeType, + @Nullable Boolean overwrite, + @Nullable Boolean extract) { + return upload( + UploadEnvironmentFileRequest.builder() + .environment(environment) + .path(path) + .file(file) + .mimeType(mimeType) + .overwrite(overwrite) + .extract(extract) + .build()); + } + + public UploadEnvironmentFileResponse upload( + @Nonnull String environment, + @Nonnull String path, + @Nonnull byte[] bytes) { + return upload( + UploadEnvironmentFileRequest.builder() + .environment(environment) + .path(path) + .bytes(bytes) + .build()); + } + + public UploadEnvironmentFileResponse upload( + @Nonnull String environment, + @Nonnull String path, + @Nonnull byte[] bytes, + @Nullable String mimeType, + @Nullable Boolean overwrite, + @Nullable Boolean extract) { + return upload( + UploadEnvironmentFileRequest.builder() + .environment(environment) + .path(path) + .bytes(bytes) + .mimeType(mimeType) + .overwrite(overwrite) + .extract(extract) + .build()); + } + + public UploadEnvironmentFileResponse upload( + @Nonnull String environment, + @Nonnull String path, + @Nonnull InputStream stream, + long sizeBytes, + @Nullable String mimeType, + @Nullable Boolean overwrite, + @Nullable Boolean extract) { + return upload( + UploadEnvironmentFileRequest.builder() + .environment(environment) + .path(path) + .stream(stream, sizeBytes) + .mimeType(mimeType) + .overwrite(overwrite) + .extract(extract) + .build()); + } + + String buildDownloadUrl(String environment, String path, Options options) { + String envId = environment.startsWith("environments/") + ? environment.substring("environments/".length()) + : environment; + String cleanPath = path.replaceAll("^/+", ""); + String baseUrl = this.sdkConfiguration.serverUrl(); + String apiVersion = (String) this.sdkConfiguration.globals.getParam("pathParam", "api_version") + .map(Object::toString) + .orElse(SDKConfiguration.OPENAPI_DOC_VERSION); + StringBuilder urlBuilder = new StringBuilder(); + if (baseUrl.endsWith("/")) { + urlBuilder.append(baseUrl.substring(0, baseUrl.length() - 1)); + } else { + urlBuilder.append(baseUrl); + } + urlBuilder.append("/").append(apiVersion); + urlBuilder.append("/environments/").append(envId); + urlBuilder.append("/files/").append(cleanPath); + urlBuilder.append("?alt=media"); + return urlBuilder.toString(); + } + + static UploadEnvironmentFileResponse parseUploadResponse(HttpResponse uploadResponse) { + String responseBodyString; + try (InputStream in = uploadResponse.body()) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] b = new byte[4096]; + int r; + while ((r = in.read(b)) != -1) { + out.write(b, 0, r); + } + responseBodyString = out.toString(StandardCharsets.UTF_8.name()); + } catch (IOException e) { + throw new RuntimeException("Failed to read upload response body", e); + } + + GetEnvironmentFilesResponse files = null; + + try { + ObjectMapper mapper = Utils.mapper(); + JsonNode node = mapper.readTree(responseBodyString); + if (node != null && node.isObject()) { + if (node.has("files")) { + JsonNode filesNode = node.get("files"); + if (filesNode.isArray()) { + for (JsonNode item : filesNode) { + if (item instanceof ObjectNode) { + ObjectNode obj = (ObjectNode) item; + if (obj.has("size_bytes") && obj.get("size_bytes").isNumber()) { + obj.put("size_bytes", obj.get("size_bytes").asText()); + } + } + } + } + files = mapper.treeToValue(node, GetEnvironmentFilesResponse.class); + } else if (node.has("file")) { + JsonNode fileNode = node.get("file"); + if (fileNode instanceof ObjectNode) { + ObjectNode obj = (ObjectNode) fileNode; + if (obj.has("size_bytes") && obj.get("size_bytes").isNumber()) { + obj.put("size_bytes", obj.get("size_bytes").asText()); + } + } + EnvironmentFile file = mapper.treeToValue(fileNode, EnvironmentFile.class); + files = new GetEnvironmentFilesResponse(Arrays.asList(file), null); + } else if (node.has("name") || node.has("path") || node.has("mime_type")) { + if (node instanceof ObjectNode) { + ObjectNode obj = (ObjectNode) node; + if (obj.has("size_bytes") && obj.get("size_bytes").isNumber()) { + obj.put("size_bytes", obj.get("size_bytes").asText()); + } + } + EnvironmentFile file = mapper.treeToValue(node, EnvironmentFile.class); + files = new GetEnvironmentFilesResponse(Arrays.asList(file), null); + } + } + } catch (Exception ignored) {} + + return new UploadEnvironmentFileResponse( + uploadResponse.statusCode(), + uploadResponse.contentType().orElse("application/json"), + uploadResponse, + files); + } + + static String inferMimeType(String path) { + if (path == null) { + return "application/octet-stream"; + } + try { + String probed = java.nio.file.Files.probeContentType(java.nio.file.Paths.get(path)); + if (probed != null && !probed.isEmpty()) { + return probed; + } + } catch (Exception ignored) {} + String lower = path.toLowerCase(Locale.ENGLISH); + if (lower.endsWith(".txt")) return "text/plain"; + if (lower.endsWith(".json")) return "application/json"; + if (lower.endsWith(".py")) return "text/x-python"; + if (lower.endsWith(".js") || lower.endsWith(".mjs")) return "application/javascript"; + if (lower.endsWith(".ts")) return "application/typescript"; + if (lower.endsWith(".html") || lower.endsWith(".htm")) return "text/html"; + if (lower.endsWith(".css")) return "text/css"; + if (lower.endsWith(".csv")) return "text/csv"; + if (lower.endsWith(".png")) return "image/png"; + if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg"; + if (lower.endsWith(".gif")) return "image/gif"; + if (lower.endsWith(".webp")) return "image/webp"; + if (lower.endsWith(".svg")) return "image/svg+xml"; + if (lower.endsWith(".pdf")) return "application/pdf"; + if (lower.endsWith(".zip")) return "application/zip"; + if (lower.endsWith(".tar")) return "application/x-tar"; + if (lower.endsWith(".gz") || lower.endsWith(".tar.gz") || lower.endsWith(".tgz")) return "application/gzip"; + return "application/octet-stream"; + } + } diff --git a/src/main/java/com/google/genai/gaos/models/environments/CreateEnvironmentRequest.java b/src/main/java/com/google/genai/gaos/models/environments/CreateEnvironmentRequest.java index 8c267ec599a..999ed96d48a 100644 --- a/src/main/java/com/google/genai/gaos/models/environments/CreateEnvironmentRequest.java +++ b/src/main/java/com/google/genai/gaos/models/environments/CreateEnvironmentRequest.java @@ -37,6 +37,15 @@ *

Request for `CreateEnvironment`. */ public class CreateEnvironmentRequest { + /** + * Optional. The source environment to copy/fork from. + * Format: `environments/{environment_id}` or `{environment_id}`. + * When specified, `sources` and `env` must be empty. + */ + @JsonInclude(Include.NON_ABSENT) + @JsonProperty("from_environment") + private String fromEnvironment; + /** * Network configuration for the environment. */ @@ -53,14 +62,25 @@ public class CreateEnvironmentRequest { @JsonCreator public CreateEnvironmentRequest( + @JsonProperty("from_environment") @Nullable String fromEnvironment, @JsonProperty("network") @Nullable CreateEnvironmentRequestNetworkUnion network, @JsonProperty("sources") @Nullable List sources) { + this.fromEnvironment = fromEnvironment; this.network = network; this.sources = sources; } public CreateEnvironmentRequest() { - this(null, null); + this(null, null, null); + } + + /** + * Optional. The source environment to copy/fork from. + * Format: `environments/{environment_id}` or `{environment_id}`. + * When specified, `sources` and `env` must be empty. + */ + public Optional fromEnvironment() { + return Optional.ofNullable(this.fromEnvironment); } /** @@ -82,6 +102,17 @@ public static Builder builder() { } + /** + * Optional. The source environment to copy/fork from. + * Format: `environments/{environment_id}` or `{environment_id}`. + * When specified, `sources` and `env` must be empty. + */ + public CreateEnvironmentRequest withFromEnvironment(@Nullable String fromEnvironment) { + this.fromEnvironment = fromEnvironment; + return this; + } + + /** * Network configuration for the environment. */ @@ -110,6 +141,7 @@ public boolean equals(java.lang.Object o) { } CreateEnvironmentRequest other = (CreateEnvironmentRequest) o; return + Utils.enhancedDeepEquals(this.fromEnvironment, other.fromEnvironment) && Utils.enhancedDeepEquals(this.network, other.network) && Utils.enhancedDeepEquals(this.sources, other.sources); } @@ -117,12 +149,13 @@ public boolean equals(java.lang.Object o) { @Override public int hashCode() { return Utils.enhancedHash( - network, sources); + fromEnvironment, network, sources); } @Override public String toString() { return Utils.toString(CreateEnvironmentRequest.class, + "fromEnvironment", fromEnvironment, "network", network, "sources", sources); } @@ -130,6 +163,8 @@ public String toString() { @SuppressWarnings("UnusedReturnValue") public final static class Builder { + private String fromEnvironment; + private CreateEnvironmentRequestNetworkUnion network; private List sources; @@ -138,6 +173,16 @@ private Builder() { // force use of static builder() method } + /** + * Optional. The source environment to copy/fork from. + * Format: `environments/{environment_id}` or `{environment_id}`. + * When specified, `sources` and `env` must be empty. + */ + public Builder fromEnvironment(@Nullable String fromEnvironment) { + this.fromEnvironment = fromEnvironment; + return this; + } + /** * Network configuration for the environment. */ @@ -156,7 +201,7 @@ public Builder sources(@Nullable List sources) { public CreateEnvironmentRequest build() { return new CreateEnvironmentRequest( - network, sources); + fromEnvironment, network, sources); } } diff --git a/src/main/java/com/google/genai/gaos/models/interactions/FunctionResultDelta.java b/src/main/java/com/google/genai/gaos/models/interactions/FunctionResultDelta.java index d81f5428903..b8988d1bbe5 100644 --- a/src/main/java/com/google/genai/gaos/models/interactions/FunctionResultDelta.java +++ b/src/main/java/com/google/genai/gaos/models/interactions/FunctionResultDelta.java @@ -35,12 +35,6 @@ public class FunctionResultDelta implements StepDeltaData { - /** - * Required. ID to match the ID from the function call block. - */ - @JsonProperty("call_id") - private String callId; - @JsonInclude(Include.NON_ABSENT) @JsonProperty("is_error") @@ -61,12 +55,9 @@ public class FunctionResultDelta implements StepDeltaData { @JsonCreator public FunctionResultDelta( - @JsonProperty("call_id") @Nonnull String callId, @JsonProperty("is_error") @Nullable Boolean isError, @JsonProperty("name") @Nullable String name, @JsonProperty("result") @Nonnull FunctionResultDeltaResultUnion result) { - this.callId = Optional.ofNullable(callId) - .orElseThrow(() -> new IllegalArgumentException("callId cannot be null")); this.isError = isError; this.name = name; this.result = Optional.ofNullable(result) @@ -75,17 +66,8 @@ public FunctionResultDelta( } public FunctionResultDelta( - @Nonnull String callId, @Nonnull FunctionResultDeltaResultUnion result) { - this(callId, null, null, - result); - } - - /** - * Required. ID to match the ID from the function call block. - */ - public Optional callId() { - return Optional.ofNullable(this.callId); + this(null, null, result); } public Optional isError() { @@ -110,15 +92,6 @@ public static Builder builder() { } - /** - * Required. ID to match the ID from the function call block. - */ - public FunctionResultDelta withCallId(@Nonnull String callId) { - this.callId = Utils.checkNotNull(callId, "callId"); - return this; - } - - public FunctionResultDelta withIsError(@Nullable Boolean isError) { this.isError = isError; return this; @@ -147,7 +120,6 @@ public boolean equals(java.lang.Object o) { } FunctionResultDelta other = (FunctionResultDelta) o; return - Utils.enhancedDeepEquals(this.callId, other.callId) && Utils.enhancedDeepEquals(this.isError, other.isError) && Utils.enhancedDeepEquals(this.name, other.name) && Utils.enhancedDeepEquals(this.result, other.result) && @@ -157,14 +129,13 @@ public boolean equals(java.lang.Object o) { @Override public int hashCode() { return Utils.enhancedHash( - callId, isError, name, - result, type); + isError, name, result, + type); } @Override public String toString() { return Utils.toString(FunctionResultDelta.class, - "callId", callId, "isError", isError, "name", name, "result", result, @@ -174,8 +145,6 @@ public String toString() { @SuppressWarnings("UnusedReturnValue") public final static class Builder { - private String callId; - private Boolean isError; private String name; @@ -186,14 +155,6 @@ private Builder() { // force use of static builder() method } - /** - * Required. ID to match the ID from the function call block. - */ - public Builder callId(@Nonnull String callId) { - this.callId = Utils.checkNotNull(callId, "callId"); - return this; - } - public Builder isError(@Nullable Boolean isError) { this.isError = isError; return this; @@ -211,8 +172,7 @@ public Builder result(@Nonnull FunctionResultDeltaResultUnion result) { public FunctionResultDelta build() { return new FunctionResultDelta( - callId, isError, name, - result); + isError, name, result); } diff --git a/src/main/java/com/google/genai/gaos/models/interactions/RetrievalCallStep.java b/src/main/java/com/google/genai/gaos/models/interactions/RetrievalCallStep.java new file mode 100644 index 00000000000..486f928e036 --- /dev/null +++ b/src/main/java/com/google/genai/gaos/models/interactions/RetrievalCallStep.java @@ -0,0 +1,263 @@ +/* + * Copyright 2026 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 + * + * http://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. + */ + +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ +package com.google.genai.gaos.models.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.genai.gaos.utils.LazySingletonValue; +import com.google.genai.gaos.utils.Utils; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.lang.Override; +import java.lang.String; +import java.util.Optional; + +/** + * RetrievalCallStep + * + *

Retrieval call step. + * Used by Vertex Retrieval tools such as Parallel AI, Exa AI, Vertex AI Search, + * etc. RetrievalType decides which tool is used. + */ +public class RetrievalCallStep implements Step { + /** + * The arguments to pass to Retrieval tools. + */ + @JsonProperty("arguments") + private RetrievalCallArguments arguments; + + /** + * Required. A unique ID for this specific tool call. + */ + @JsonProperty("id") + private String id; + + /** + * The type of retrieval tools. + */ + @JsonInclude(Include.NON_ABSENT) + @JsonProperty("retrieval_type") + private RetrievalCallStepRetrievalType retrievalType; + + /** + * A signature hash for backend validation. + */ + @JsonInclude(Include.NON_ABSENT) + @JsonProperty("signature") + private String signature; + + + @JsonProperty("type") + private String type; + + @JsonCreator + public RetrievalCallStep( + @JsonProperty("arguments") @Nonnull RetrievalCallArguments arguments, + @JsonProperty("id") @Nonnull String id, + @JsonProperty("retrieval_type") @Nullable RetrievalCallStepRetrievalType retrievalType, + @JsonProperty("signature") @Nullable String signature) { + this.arguments = Optional.ofNullable(arguments) + .orElseThrow(() -> new IllegalArgumentException("arguments cannot be null")); + this.id = Optional.ofNullable(id) + .orElseThrow(() -> new IllegalArgumentException("id cannot be null")); + this.retrievalType = retrievalType; + this.signature = signature; + this.type = Builder._SINGLETON_VALUE_Type.value(); + } + + public RetrievalCallStep( + @Nonnull RetrievalCallArguments arguments, + @Nonnull String id) { + this(arguments, id, null, + null); + } + + /** + * The arguments to pass to Retrieval tools. + */ + public Optional arguments() { + return Optional.ofNullable(this.arguments); + } + + /** + * Required. A unique ID for this specific tool call. + */ + public Optional id() { + return Optional.ofNullable(this.id); + } + + /** + * The type of retrieval tools. + */ + public Optional retrievalType() { + return Optional.ofNullable(this.retrievalType); + } + + /** + * A signature hash for backend validation. + */ + public Optional signature() { + return Optional.ofNullable(this.signature); + } + + @Override + public String type() { + return Utils.discriminatorToString(type); + } + + public static Builder builder() { + return new Builder(); + } + + + /** + * The arguments to pass to Retrieval tools. + */ + public RetrievalCallStep withArguments(@Nonnull RetrievalCallArguments arguments) { + this.arguments = Utils.checkNotNull(arguments, "arguments"); + return this; + } + + + /** + * Required. A unique ID for this specific tool call. + */ + public RetrievalCallStep withId(@Nonnull String id) { + this.id = Utils.checkNotNull(id, "id"); + return this; + } + + + /** + * The type of retrieval tools. + */ + public RetrievalCallStep withRetrievalType(@Nullable RetrievalCallStepRetrievalType retrievalType) { + this.retrievalType = retrievalType; + return this; + } + + + /** + * A signature hash for backend validation. + */ + public RetrievalCallStep withSignature(@Nullable String signature) { + this.signature = signature; + return this; + } + + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RetrievalCallStep other = (RetrievalCallStep) o; + return + Utils.enhancedDeepEquals(this.arguments, other.arguments) && + Utils.enhancedDeepEquals(this.id, other.id) && + Utils.enhancedDeepEquals(this.retrievalType, other.retrievalType) && + Utils.enhancedDeepEquals(this.signature, other.signature) && + Utils.enhancedDeepEquals(this.type, other.type); + } + + @Override + public int hashCode() { + return Utils.enhancedHash( + arguments, id, retrievalType, + signature, type); + } + + @Override + public String toString() { + return Utils.toString(RetrievalCallStep.class, + "arguments", arguments, + "id", id, + "retrievalType", retrievalType, + "signature", signature, + "type", type); + } + + @SuppressWarnings("UnusedReturnValue") + public final static class Builder { + + private RetrievalCallArguments arguments; + + private String id; + + private RetrievalCallStepRetrievalType retrievalType; + + private String signature; + + private Builder() { + // force use of static builder() method + } + + /** + * The arguments to pass to Retrieval tools. + */ + public Builder arguments(@Nonnull RetrievalCallArguments arguments) { + this.arguments = Utils.checkNotNull(arguments, "arguments"); + return this; + } + + /** + * Required. A unique ID for this specific tool call. + */ + public Builder id(@Nonnull String id) { + this.id = Utils.checkNotNull(id, "id"); + return this; + } + + /** + * The type of retrieval tools. + */ + public Builder retrievalType(@Nullable RetrievalCallStepRetrievalType retrievalType) { + this.retrievalType = retrievalType; + return this; + } + + /** + * A signature hash for backend validation. + */ + public Builder signature(@Nullable String signature) { + this.signature = signature; + return this; + } + + public RetrievalCallStep build() { + return new RetrievalCallStep( + arguments, id, retrievalType, + signature); + } + + + private static final LazySingletonValue _SINGLETON_VALUE_Type = + new LazySingletonValue<>( + "type", + "\"retrieval_call\"", + new TypeReference() {}); + } +} diff --git a/src/main/java/com/google/genai/gaos/models/interactions/RetrievalCallStepRetrievalType.java b/src/main/java/com/google/genai/gaos/models/interactions/RetrievalCallStepRetrievalType.java new file mode 100644 index 00000000000..79800e08cca --- /dev/null +++ b/src/main/java/com/google/genai/gaos/models/interactions/RetrievalCallStepRetrievalType.java @@ -0,0 +1,156 @@ +/* + * Copyright 2026 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 + * + * http://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. + */ + +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ +package com.google.genai.gaos.models.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import java.lang.Override; +import java.lang.String; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * Wrapper for an "open" enum that can handle unknown values from API responses + * without runtime errors. Instances are immutable singletons with reference equality. + * Use {@code asEnum()} for switch expressions. + */ +/** + * RetrievalCallStepRetrievalType + * + *

The type of retrieval tools. + */ +public class RetrievalCallStepRetrievalType { + + public static final RetrievalCallStepRetrievalType VERTEX_AI_SEARCH = new RetrievalCallStepRetrievalType("vertex_ai_search"); + public static final RetrievalCallStepRetrievalType RAG_STORE = new RetrievalCallStepRetrievalType("rag_store"); + public static final RetrievalCallStepRetrievalType EXA_AI_SEARCH = new RetrievalCallStepRetrievalType("exa_ai_search"); + public static final RetrievalCallStepRetrievalType PARALLEL_AI_SEARCH = new RetrievalCallStepRetrievalType("parallel_ai_search"); + + // This map will grow whenever a Color gets created with a new + // unrecognized value (a potential memory leak if the user is not + // careful). Keep this field lower case to avoid clashing with + // generated member names which will always be upper cased (Java + // convention) + private static final Map values = createValuesMap(); + private static final Map enums = createEnumsMap(); + + private final String value; + + private RetrievalCallStepRetrievalType(String value) { + this.value = value; + } + + /** + * Returns a RetrievalCallStepRetrievalType with the given value. For a specific value the + * returned object will always be a singleton so reference equality + * is satisfied when the values are the same. + * + * @param value value to be wrapped as RetrievalCallStepRetrievalType + */ + @JsonCreator + public static RetrievalCallStepRetrievalType of(String value) { + synchronized (RetrievalCallStepRetrievalType.class) { + return values.computeIfAbsent(value, v -> new RetrievalCallStepRetrievalType(v)); + } + } + + @JsonValue + public String value() { + return value; + } + + public Optional asEnum() { + return Optional.ofNullable(enums.getOrDefault(value, null)); + } + + public boolean isKnown() { + return asEnum().isPresent(); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public boolean equals(java.lang.Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RetrievalCallStepRetrievalType other = (RetrievalCallStepRetrievalType) obj; + return Objects.equals(value, other.value); + } + + @Override + public String toString() { + return "RetrievalCallStepRetrievalType [value=" + value + "]"; + } + + // return an array just like an enum + public static RetrievalCallStepRetrievalType[] values() { + synchronized (RetrievalCallStepRetrievalType.class) { + return values.values().toArray(new RetrievalCallStepRetrievalType[] {}); + } + } + + private static final Map createValuesMap() { + Map map = new LinkedHashMap<>(); + map.put("vertex_ai_search", VERTEX_AI_SEARCH); + map.put("rag_store", RAG_STORE); + map.put("exa_ai_search", EXA_AI_SEARCH); + map.put("parallel_ai_search", PARALLEL_AI_SEARCH); + return map; + } + + private static final Map createEnumsMap() { + Map map = new HashMap<>(); + map.put("vertex_ai_search", RetrievalCallStepRetrievalTypeEnum.VERTEX_AI_SEARCH); + map.put("rag_store", RetrievalCallStepRetrievalTypeEnum.RAG_STORE); + map.put("exa_ai_search", RetrievalCallStepRetrievalTypeEnum.EXA_AI_SEARCH); + map.put("parallel_ai_search", RetrievalCallStepRetrievalTypeEnum.PARALLEL_AI_SEARCH); + return map; + } + + + public enum RetrievalCallStepRetrievalTypeEnum { + + VERTEX_AI_SEARCH("vertex_ai_search"), + RAG_STORE("rag_store"), + EXA_AI_SEARCH("exa_ai_search"), + PARALLEL_AI_SEARCH("parallel_ai_search"),; + + private final String value; + + private RetrievalCallStepRetrievalTypeEnum(String value) { + this.value = value; + } + + public String value() { + return value; + } + } +} + diff --git a/src/main/java/com/google/genai/gaos/models/interactions/RetrievalResultStep.java b/src/main/java/com/google/genai/gaos/models/interactions/RetrievalResultStep.java new file mode 100644 index 00000000000..a735bf7c5aa --- /dev/null +++ b/src/main/java/com/google/genai/gaos/models/interactions/RetrievalResultStep.java @@ -0,0 +1,224 @@ +/* + * Copyright 2026 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 + * + * http://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. + */ + +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ +package com.google.genai.gaos.models.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.genai.gaos.utils.LazySingletonValue; +import com.google.genai.gaos.utils.Utils; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.lang.Boolean; +import java.lang.Override; +import java.lang.String; +import java.util.Optional; + +/** + * RetrievalResultStep + * + *

Vertex Retrieval result step. + * Used by Vertex Retrieval tools such as Parallel AI, Exa AI, Vertex AI Search, + * etc. + */ +public class RetrievalResultStep implements Step { + /** + * Required. ID to match the ID from the function call block. + */ + @JsonProperty("call_id") + private String callId; + + /** + * Whether the retrieval resulted in an error. + */ + @JsonInclude(Include.NON_ABSENT) + @JsonProperty("is_error") + private Boolean isError; + + /** + * A signature hash for backend validation. + */ + @JsonInclude(Include.NON_ABSENT) + @JsonProperty("signature") + private String signature; + + + @JsonProperty("type") + private String type; + + @JsonCreator + public RetrievalResultStep( + @JsonProperty("call_id") @Nonnull String callId, + @JsonProperty("is_error") @Nullable Boolean isError, + @JsonProperty("signature") @Nullable String signature) { + this.callId = Optional.ofNullable(callId) + .orElseThrow(() -> new IllegalArgumentException("callId cannot be null")); + this.isError = isError; + this.signature = signature; + this.type = Builder._SINGLETON_VALUE_Type.value(); + } + + public RetrievalResultStep( + @Nonnull String callId) { + this(callId, null, null); + } + + /** + * Required. ID to match the ID from the function call block. + */ + public Optional callId() { + return Optional.ofNullable(this.callId); + } + + /** + * Whether the retrieval resulted in an error. + */ + public Optional isError() { + return Optional.ofNullable(this.isError); + } + + /** + * A signature hash for backend validation. + */ + public Optional signature() { + return Optional.ofNullable(this.signature); + } + + @Override + public String type() { + return Utils.discriminatorToString(type); + } + + public static Builder builder() { + return new Builder(); + } + + + /** + * Required. ID to match the ID from the function call block. + */ + public RetrievalResultStep withCallId(@Nonnull String callId) { + this.callId = Utils.checkNotNull(callId, "callId"); + return this; + } + + + /** + * Whether the retrieval resulted in an error. + */ + public RetrievalResultStep withIsError(@Nullable Boolean isError) { + this.isError = isError; + return this; + } + + + /** + * A signature hash for backend validation. + */ + public RetrievalResultStep withSignature(@Nullable String signature) { + this.signature = signature; + return this; + } + + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RetrievalResultStep other = (RetrievalResultStep) o; + return + Utils.enhancedDeepEquals(this.callId, other.callId) && + Utils.enhancedDeepEquals(this.isError, other.isError) && + Utils.enhancedDeepEquals(this.signature, other.signature) && + Utils.enhancedDeepEquals(this.type, other.type); + } + + @Override + public int hashCode() { + return Utils.enhancedHash( + callId, isError, signature, + type); + } + + @Override + public String toString() { + return Utils.toString(RetrievalResultStep.class, + "callId", callId, + "isError", isError, + "signature", signature, + "type", type); + } + + @SuppressWarnings("UnusedReturnValue") + public final static class Builder { + + private String callId; + + private Boolean isError; + + private String signature; + + private Builder() { + // force use of static builder() method + } + + /** + * Required. ID to match the ID from the function call block. + */ + public Builder callId(@Nonnull String callId) { + this.callId = Utils.checkNotNull(callId, "callId"); + return this; + } + + /** + * Whether the retrieval resulted in an error. + */ + public Builder isError(@Nullable Boolean isError) { + this.isError = isError; + return this; + } + + /** + * A signature hash for backend validation. + */ + public Builder signature(@Nullable String signature) { + this.signature = signature; + return this; + } + + public RetrievalResultStep build() { + return new RetrievalResultStep( + callId, isError, signature); + } + + + private static final LazySingletonValue _SINGLETON_VALUE_Type = + new LazySingletonValue<>( + "type", + "\"retrieval_result\"", + new TypeReference() {}); + } +} diff --git a/src/main/java/com/google/genai/gaos/models/interactions/StepTypeIdResolver.java b/src/main/java/com/google/genai/gaos/models/interactions/StepTypeIdResolver.java index 5907cd4b630..148a83eabb7 100644 --- a/src/main/java/com/google/genai/gaos/models/interactions/StepTypeIdResolver.java +++ b/src/main/java/com/google/genai/gaos/models/interactions/StepTypeIdResolver.java @@ -55,6 +55,8 @@ private void initializeTypeMap() { registerType("model_output", ModelOutputStep.class); registerType("processing_call", ProcessingCallStep.class); registerType("processing_result", ProcessingResultStep.class); + registerType("retrieval_call", RetrievalCallStep.class); + registerType("retrieval_result", RetrievalResultStep.class); registerType("thought", ThoughtStep.class); registerType("url_context_call", URLContextCallStep.class); registerType("url_context_result", URLContextResultStep.class); diff --git a/src/main/java/com/google/genai/gaos/models/operations/UploadEnvironmentFileRequest.java b/src/main/java/com/google/genai/gaos/models/operations/UploadEnvironmentFileRequest.java new file mode 100644 index 00000000000..f65d09f18da --- /dev/null +++ b/src/main/java/com/google/genai/gaos/models/operations/UploadEnvironmentFileRequest.java @@ -0,0 +1,139 @@ +/* + * Copyright 2026 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 + * + * http://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. + */ + +package com.google.genai.gaos.models.operations; + +import com.google.genai.gaos.utils.Utils; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.io.File; +import java.io.InputStream; +import java.util.Optional; + +public class UploadEnvironmentFileRequest { + private final String environment; + private final String path; + private final File file; + private final byte[] bytes; + private final InputStream stream; + private final Long sizeBytes; + private final String mimeType; + private final Boolean overwrite; + private final Boolean extract; + private final String apiVersion; + + private UploadEnvironmentFileRequest(Builder builder) { + this.environment = Utils.checkNotNull(builder.environment, "environment"); + this.path = Utils.checkNotNull(builder.path, "path"); + this.file = builder.file; + this.bytes = builder.bytes; + this.stream = builder.stream; + this.sizeBytes = builder.sizeBytes; + this.mimeType = builder.mimeType; + this.overwrite = builder.overwrite; + this.extract = builder.extract; + this.apiVersion = builder.apiVersion; + } + + public String environment() { return environment; } + public String path() { return path; } + public Optional file() { return Optional.ofNullable(file); } + public Optional bytes() { return Optional.ofNullable(bytes); } + public Optional stream() { return Optional.ofNullable(stream); } + public Optional sizeBytes() { return Optional.ofNullable(sizeBytes); } + public Optional mimeType() { return Optional.ofNullable(mimeType); } + public Optional overwrite() { return Optional.ofNullable(overwrite); } + public Optional extract() { return Optional.ofNullable(extract); } + public Optional apiVersion() { return Optional.ofNullable(apiVersion); } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String environment; + private String path; + private File file; + private byte[] bytes; + private InputStream stream; + private Long sizeBytes; + private String mimeType; + private Boolean overwrite; + private Boolean extract; + private String apiVersion; + + public Builder environment(@Nonnull String environment) { + this.environment = environment; + return this; + } + + public Builder path(@Nonnull String path) { + this.path = path; + return this; + } + + public Builder file(@Nonnull File file) { + this.file = file; + if (this.sizeBytes == null) { + this.sizeBytes = file.length(); + } + return this; + } + + public Builder bytes(@Nonnull byte[] bytes) { + this.bytes = bytes; + if (this.sizeBytes == null) { + this.sizeBytes = (long) bytes.length; + } + return this; + } + + public Builder stream(@Nonnull InputStream stream, long sizeBytes) { + this.stream = stream; + this.sizeBytes = sizeBytes; + return this; + } + + public Builder sizeBytes(@Nullable Long sizeBytes) { + this.sizeBytes = sizeBytes; + return this; + } + + public Builder mimeType(@Nullable String mimeType) { + this.mimeType = mimeType; + return this; + } + + public Builder overwrite(@Nullable Boolean overwrite) { + this.overwrite = overwrite; + return this; + } + + public Builder extract(@Nullable Boolean extract) { + this.extract = extract; + return this; + } + + public Builder apiVersion(@Nullable String apiVersion) { + this.apiVersion = apiVersion; + return this; + } + + public UploadEnvironmentFileRequest build() { + return new UploadEnvironmentFileRequest(this); + } + } +} diff --git a/src/main/java/com/google/genai/gaos/models/operations/UploadEnvironmentFileResponse.java b/src/main/java/com/google/genai/gaos/models/operations/UploadEnvironmentFileResponse.java new file mode 100644 index 00000000000..faf34f55814 --- /dev/null +++ b/src/main/java/com/google/genai/gaos/models/operations/UploadEnvironmentFileResponse.java @@ -0,0 +1,73 @@ +/* + * Copyright 2026 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 + * + * http://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. + */ + +package com.google.genai.gaos.models.operations; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.google.genai.gaos.models.environments.GetEnvironmentFilesResponse; +import com.google.genai.gaos.utils.Response; +import com.google.genai.gaos.utils.Utils; +import com.google.genai.gaos.utils.transport.HttpResponse; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.io.InputStream; +import java.util.Optional; + +public class UploadEnvironmentFileResponse implements Response { + private final String contentType; + private final int statusCode; + private final HttpResponse rawResponse; + private final GetEnvironmentFilesResponse files; + + @JsonCreator + public UploadEnvironmentFileResponse( + int statusCode, + @Nonnull String contentType, + @Nonnull HttpResponse rawResponse, + @Nullable GetEnvironmentFilesResponse files) { + this.statusCode = statusCode; + this.contentType = Utils.checkNotNull(contentType, "contentType"); + this.rawResponse = Utils.checkNotNull(rawResponse, "rawResponse"); + this.files = files; + } + + @Override + public String contentType() { + return contentType; + } + + @Override + public int statusCode() { + return statusCode; + } + + @Override + public HttpResponse rawResponse() { + return rawResponse; + } + + public Optional files() { + return Optional.ofNullable(files); + } + + @Override + public String toString() { + return Utils.toString(UploadEnvironmentFileResponse.class, + "statusCode", statusCode, + "contentType", contentType, + "files", files); + } +}