diff --git a/examples/src/main/java/com/google/genai/examples/FileSearchCallContentExample.java b/examples/src/main/java/com/google/genai/examples/FileSearchCallContentExample.java new file mode 100644 index 00000000000..a535d7fbae7 --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/FileSearchCallContentExample.java @@ -0,0 +1,65 @@ +/* + * 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. + */ + +package com.google.genai.examples; + +import com.google.genai.types.interactions.content.FileSearchCallContent; + +/** + * Example demonstrating the usage of FileSearchCallContent. + * + *

FileSearchCallContent represents a file search operation call in the Interactions API. + * Unlike other tool calls, FileSearchCallContent contains only the type and call ID. + * All search configuration (stores, top_k, filters) is defined in the FileSearch. + */ +public class FileSearchCallContentExample { + + public static void main(String[] args) { + // Example 1: Create FileSearchCallContent using builder + FileSearchCallContent content1 = + FileSearchCallContent.builder() + .id("file-search-call-123") + .build(); + + System.out.println("FileSearchCallContent (using builder) JSON:"); + System.out.println(content1.toJson()); + System.out.println(); + + // Example 2: Create FileSearchCallContent using convenience method + FileSearchCallContent content2 = + FileSearchCallContent.of("call-456"); + + System.out.println("FileSearchCallContent (using of() method) JSON:"); + System.out.println(content2.toJson()); + System.out.println(); + + // Example 3: Deserialize from JSON + String json = content2.toJson(); + FileSearchCallContent deserialized = FileSearchCallContent.fromJson(json); + + System.out.println("Deserialized FileSearchCallContent:"); + System.out.println(" ID: " + deserialized.id()); + System.out.println(); + + // Example 4: Create FileSearchCallContent without ID (optional) + FileSearchCallContent content3 = + FileSearchCallContent.builder() + .build(); + + System.out.println("FileSearchCallContent (no ID) JSON:"); + System.out.println(content3.toJson()); + } +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsAsyncCreate.java b/examples/src/main/java/com/google/genai/examples/InteractionsAsyncCreate.java new file mode 100644 index 00000000000..e4a5c82f395 --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsAsyncCreate.java @@ -0,0 +1,124 @@ +/* + * 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. + */ + +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; +import java.util.concurrent.CompletableFuture; + +/** + * Example: Async Create with the Interactions API + * + *

Demonstrates async operations using CompletableFuture for non-blocking requests. + * + *

To run this example: + *

    + *
  1. Set the GOOGLE_API_KEY environment variable: {@code export GOOGLE_API_KEY=YOUR_API_KEY} + *
  2. Compile the examples: {@code mvn clean compile} + *
  3. Run: {@code mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsAsyncCreate"} + *
+ * + *

Note: The Interactions API is currently in beta. + */ +public final class InteractionsAsyncCreate { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== Interactions API: Async Create Example ===\n"); + + try { + // ===== Basic Async Create ===== + System.out.println("--- Async Create with Callback ---\n"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("What is the capital of France?") + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + System.out.println("Sending async request...\n"); + + CompletableFuture future = client.async.interactions.create(config); + + // Wait for completion and print response + Interaction interaction = future.join(); + + System.out.println("=== RESPONSE ==="); + System.out.println(interaction.toJson()); + System.out.println(); + + printResults(interaction); + + // ===== Multiple Parallel Creates ===== + System.out.println("\n--- Multiple Parallel Creates ---\n"); + + CompletableFuture p1 = + client.async.interactions.create( + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("What is machine learning?") + .build()); + + CompletableFuture p2 = + client.async.interactions.create( + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("What is quantum computing?") + .build()); + + p1.thenAccept(i -> System.out.println("Request 1 completed: " + i.id())); + p2.thenAccept(i -> System.out.println("Request 2 completed: " + i.id())); + + CompletableFuture.allOf(p1, p2) + .thenRun(() -> System.out.println("\nAll parallel requests completed!")); + + CompletableFuture.allOf(p1, p2).join(); + + System.out.println("\n=== Example completed ==="); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + + if (interaction.outputs().isPresent() && !interaction.outputs().get().isEmpty()) { + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + System.out.println(" Text: " + ((TextContent) output).text().orElse("(empty)")); + } else { + System.out.println(" " + output.getClass().getSimpleName()); + } + } + } + } + + private InteractionsAsyncCreate() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsAudioContent.java b/examples/src/main/java/com/google/genai/examples/InteractionsAudioContent.java new file mode 100644 index 00000000000..e265cb76348 --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsAudioContent.java @@ -0,0 +1,133 @@ +/* + * 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. + */ + +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.AudioContent; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; + +/** + * Example demonstrating audio transcription and analysis using AudioContent with the Interactions + * API. + * + *

This example shows: + *

+ * + *

To run this example: + *

    + *
  1. Set the GOOGLE_API_KEY environment variable: {@code export GOOGLE_API_KEY=YOUR_API_KEY} + *
  2. Compile the examples: {@code mvn clean compile} + *
  3. Run: {@code mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsAudioContent"} + *
+ * + *

Note: The Interactions API is currently in beta. + */ +public final class InteractionsAudioContent { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== Interactions API: Audio Content Example ===\n"); + + try { + // ===== PART 1: Audio from URI ===== + System.out.println("--- PART 1: Audio from URI ---\n"); + + String audioUri = "https://storage.googleapis.com/cloud-samples-data/speech/brooklyn_bridge.mp3"; + AudioContent audioContent = AudioContent.fromUri(audioUri, "audio/mp3"); + TextContent textPrompt = TextContent.builder() + .text("Transcribe this audio.") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromContents(textPrompt, audioContent) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + Interaction response = client.interactions.create(config); + + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + printResults(response); + + // ===== PART 2: Audio from Inline Data (base64) ===== + System.out.println("\n--- PART 2: Audio from Inline Data (Base64) ---\n"); + + // Small WAV file header encoded as base64 + String base64Data = "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQAAAAA="; + AudioContent audioFromData = AudioContent.fromData(base64Data, "audio/wav"); + TextContent textPrompt2 = TextContent.builder() + .text("What do you hear in this audio?") + .build(); + + CreateInteractionConfig config2 = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromContents(textPrompt2, audioFromData) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config2.toJson()); + System.out.println(); + + Interaction response2 = client.interactions.create(config2); + + System.out.println("=== RESPONSE ==="); + System.out.println(response2.toJson()); + System.out.println(); + + printResults(response2); + + System.out.println("\n=== Example completed ==="); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + + if (interaction.outputs().isPresent() && !interaction.outputs().get().isEmpty()) { + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + System.out.println(" Text: " + ((TextContent) output).text().orElse("(empty)")); + } else { + System.out.println(" " + output.getClass().getSimpleName()); + } + } + } + } + + private InteractionsAudioContent() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsCancelExample.java b/examples/src/main/java/com/google/genai/examples/InteractionsCancelExample.java new file mode 100644 index 00000000000..9e6f16282d9 --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsCancelExample.java @@ -0,0 +1,135 @@ +/* + * 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. + */ + +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CancelInteractionConfig; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.GetInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; + +/** + * Example: Cancel Interaction API + * + *

Demonstrates the interactions.cancel() API for background interactions. + * + *

Note: Only interactions created with background=true can be cancelled. + * + *

To run this example: + *

    + *
  1. Set the GOOGLE_API_KEY environment variable: {@code export GOOGLE_API_KEY=YOUR_API_KEY} + *
  2. Compile the examples: {@code mvn clean compile} + *
  3. Run: {@code mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsCancelExample"} + *
+ * + *

Note: The Interactions API is currently in beta. + */ +public final class InteractionsCancelExample { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== Interactions API: Cancel Example ===\n"); + + try { + // ===== STEP 1: Create Background Interaction ===== + System.out.println("--- STEP 1: Create Background Interaction ---\n"); + + CreateInteractionConfig createConfig = + CreateInteractionConfig.builder() + .agent("deep-research-pro-preview-12-2025") + .input("Write an essay about space exploration.") + .background(true) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(createConfig.toJson()); + System.out.println(); + + Interaction interaction = client.interactions.create(createConfig); + + System.out.println("=== RESPONSE ==="); + System.out.println(interaction.toJson()); + System.out.println(); + + printResults(interaction); + + String interactionId = interaction.id(); + + // ===== STEP 2: Cancel the Interaction ===== + System.out.println("\n--- STEP 2: Cancel the Interaction ---\n"); + + try { + CancelInteractionConfig cancelConfig = CancelInteractionConfig.builder().build(); + Interaction cancelled = client.interactions.cancel(interactionId, cancelConfig); + + System.out.println("=== RESPONSE ==="); + System.out.println(cancelled.toJson()); + System.out.println(); + + printResults(cancelled); + } catch (Exception e) { + System.out.println("Results:"); + System.out.println(" Cancel failed: " + e.getMessage()); + System.out.println(" (May happen if interaction completed too quickly)"); + } + + // ===== STEP 3: Verify Final State ===== + System.out.println("\n--- STEP 3: Verify Final State ---\n"); + + try { + GetInteractionConfig getConfig = GetInteractionConfig.builder().build(); + Interaction retrieved = client.interactions.get(interactionId, getConfig); + + System.out.println("=== RESPONSE ==="); + System.out.println(retrieved.toJson()); + System.out.println(); + + printResults(retrieved); + } catch (Exception e) { + System.out.println("Results:"); + System.out.println(" Get failed: " + e.getMessage()); + } + + System.out.println("\n=== Example completed ==="); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + + if (interaction.outputs().isPresent() && !interaction.outputs().get().isEmpty()) { + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + System.out.println(" Text: " + ((TextContent) output).text().orElse("(empty)")); + } else { + System.out.println(" " + output.getClass().getSimpleName()); + } + } + } + } + + private InteractionsCancelExample() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsCodeExecution.java b/examples/src/main/java/com/google/genai/examples/InteractionsCodeExecution.java new file mode 100644 index 00000000000..fe9f9fad30d --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsCodeExecution.java @@ -0,0 +1,140 @@ +/* + * 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. + */ + +/** + * Usage: + * + *

Note: The Interactions API is currently only available via the Gemini Developer API (not + * Vertex AI). + * + *

1. Set an API key environment variable. You can find a list of available API keys here: + * https://aistudio.google.com/app/apikey + * + *

export GOOGLE_API_KEY=YOUR_API_KEY + * + *

2. Compile the java package and run the sample code. + * + *

mvn clean compile + * + *

mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsCodeExecution" + */ +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.CodeExecutionCallContent; +import com.google.genai.types.interactions.content.CodeExecutionResultContent; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; +import com.google.genai.types.interactions.tools.CodeExecution; + +/** + * Example: Code Execution Tool with the Interactions API + * + *

Demonstrates how to use the CodeExecution to enable the model to execute code as part of + * generation. The model can write and run code to solve problems. + * + *

Note: The Interactions API is in beta and subject to change. + */ +public final class InteractionsCodeExecution { + + public static void main(String[] args) { + // Instantiate the client. The client gets the API key from the environment variable + // `GOOGLE_API_KEY`. + // + Client client = new Client(); + + System.out.println("=== Interactions API: Code Execution Tool Example ===\n"); + + // ===== STEP 1: Create CodeExecution ===== + System.out.println("STEP 1: Create CodeExecution\n"); + + CodeExecution codeTool = CodeExecution.builder().build(); + + System.out.println("CodeExecution created successfully\n"); + + // ===== STEP 2: Create interaction with Code Execution enabled ===== + System.out.println("---\n"); + System.out.println("STEP 2: Create interaction with Code Execution enabled\n"); + + String userQuestion = + "Calculate the first 20 Fibonacci numbers and find their sum. " + + "Show me the code and the result."; + System.out.println("User: " + userQuestion + "\n"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input(userQuestion) + .tools(codeTool) + .build(); + + // Print the request JSON + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + Interaction response = client.interactions.create(config); + + // Print the response JSON + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + // ===== STEP 3: Extract and display the results ===== + System.out.println("---\n"); + System.out.println("STEP 3: Extract and display the results\n"); + + System.out.println("Response received. Interaction ID: " + response.id()); + System.out.println(); + + if (response.outputs().isPresent()) { + for (Content content : response.outputs().get()) { + if (content instanceof TextContent) { + System.out.println("Text: " + ((TextContent) content).text().orElse("(empty)")); + System.out.println(); + } else if (content instanceof CodeExecutionCallContent) { + CodeExecutionCallContent codeCall = (CodeExecutionCallContent) content; + System.out.println("Code Execution Call:"); + System.out.println(" ID: " + codeCall.id()); + if (codeCall.arguments().isPresent()) { + System.out.println(" Language: " + codeCall.arguments().get().language().orElse("N/A")); + System.out.println(" Code:"); + System.out.println(" ---"); + System.out.println(codeCall.arguments().get().code().orElse("(empty)")); + System.out.println(" ---"); + } + System.out.println(); + } else if (content instanceof CodeExecutionResultContent) { + CodeExecutionResultContent codeResult = (CodeExecutionResultContent) content; + System.out.println("Code Execution Result:"); + System.out.println(" Call ID: " + codeResult.callId().orElse("N/A")); + System.out.println(" Is Error: " + codeResult.isError().orElse(false)); + System.out.println(" Result:"); + System.out.println(" ---"); + System.out.println(codeResult.result().orElse("(empty)")); + System.out.println(" ---"); + System.out.println(); + } + } + } + + System.out.println("\n=== Example completed ==="); + } + + private InteractionsCodeExecution() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsComputerUse.java b/examples/src/main/java/com/google/genai/examples/InteractionsComputerUse.java new file mode 100644 index 00000000000..18f6d265a22 --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsComputerUse.java @@ -0,0 +1,172 @@ +/* + * 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. + */ + +/** + * Usage: + * + *

Note: The Interactions API is currently only available via the Gemini Developer API (not + * Vertex AI). + * + *

IMPORTANT: Computer Use tool may require special API access. This example demonstrates the + * configuration but may not work without proper authorization. + * + *

1. Set an API key environment variable. You can find a list of available API keys here: + * https://aistudio.google.com/app/apikey + * + *

export GOOGLE_API_KEY=YOUR_API_KEY + * + *

2. Compile the java package and run the sample code. + * + *

mvn clean compile + * + *

mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsComputerUse" + */ +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; +import com.google.genai.types.interactions.tools.ComputerUse; + +/** + * Example: Computer Use Tool with the Interactions API + * + *

Demonstrates how to use the ComputerUse to enable the model to interact with a computer + * environment. This tool allows the model to control applications, browse web pages, and perform + * other computer-based tasks. + * + *

IMPORTANT NOTES: + *

+ * + *

Note: The Interactions API is in beta and subject to change. + */ +public final class InteractionsComputerUse { + + public static void main(String[] args) { + // Instantiate the client. The client gets the API key from the environment variable + // `GOOGLE_API_KEY`. + // + Client client = new Client(); + + System.out.println("=== Interactions API: Computer Use Tool Example ===\n"); + + System.out.println( + "IMPORTANT: Computer Use tool may require special API access and authorization.\n"); + + // ===== STEP 1: Create ComputerUse ===== + System.out.println("STEP 1: Create ComputerUse\n"); + + // Configure the computer use environment + // Common environments: "browser" for web browsing, "desktop" for full desktop access + // + // IMPORTANT: ComputerUse is a configuration that enables computer use capabilities. + // Unlike FunctionCall or CodeExecution, it does NOT have corresponding + // ComputerUseCallContent or ComputerUseResultContent types. + // Results are returned as standard content types (TextContent, ImageContent, etc.) + ComputerUse computerTool = + ComputerUse.builder() + .environment("browser") + // Optionally exclude predefined functions + // .excludedPredefinedFunctions("function1", "function2") + .build(); + + System.out.println("ComputerUse created successfully"); + System.out.println("Environment: " + computerTool.environment().orElse("default")); + System.out.println(); + + // ===== STEP 2: Create interaction with Computer Use enabled ===== + System.out.println("---\n"); + System.out.println("STEP 2: Create interaction with Computer Use enabled\n"); + + String userQuestion = "Navigate to https://www.example.com and tell me what you see."; + System.out.println("User: " + userQuestion + "\n"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input(userQuestion) + .tools(computerTool) + .build(); + + // Print the request JSON + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + try { + Interaction response = client.interactions.create(config); + + // Print the response JSON + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + // ===== STEP 3: Extract and display the results ===== + System.out.println("---\n"); + System.out.println("STEP 3: Extract and display the results\n"); + + System.out.println("Response received. Interaction ID: " + response.id()); + System.out.println(); + + if (response.outputs().isPresent()) { + for (Content content : response.outputs().get()) { + System.out.println("Content Type: " + content.getClass().getSimpleName()); + + if (content instanceof TextContent) { + System.out.println("Text: " + ((TextContent) content).text().orElse("(empty)")); + System.out.println(); + } + + // IMPORTANT: ComputerUse does NOT have dedicated content types + // (no ComputerUseCallContent or ComputerUseResultContent). + // + // Instead, computer use operations return results as: + // - TextContent: For textual responses and descriptions + // - ImageContent: For screenshots of computer actions + // - Other standard content types as needed + // + // This is different from tools like FunctionCall, CodeExecution, etc., + // which have explicit call/result content type pairs. + } + } + + System.out.println("\n=== Example completed successfully ==="); + + } catch (Exception e) { + System.err.println("\n=== Error occurred ==="); + System.err.println("Error: " + e.getMessage()); + System.err.println( + "\nNote: Computer Use tool may require special API access or authorization."); + System.err.println("Please check:"); + System.err.println(" 1. Your API key has Computer Use permissions"); + System.err.println(" 2. The feature is enabled for your account"); + System.err.println(" 3. You're using a model that supports Computer Use"); + e.printStackTrace(); + } + } + + private InteractionsComputerUse() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsCreateExample.java b/examples/src/main/java/com/google/genai/examples/InteractionsCreateExample.java new file mode 100644 index 00000000000..a54de14389e --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsCreateExample.java @@ -0,0 +1,290 @@ +/* + * 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. + */ + +/** + * Usage: + * + *

Note: The Interactions API is currently only available via the Gemini Developer API (not + * Vertex AI). + * + *

1. Set an API key environment variable. You can find a list of available API keys here: + * https://aistudio.google.com/app/apikey + * + *

export GOOGLE_API_KEY=YOUR_API_KEY + * + *

2. Compile the java package and run the sample code. + * + *

mvn clean compile + * + *

mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsCreateExample" + */ +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.GetInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.AudioContent; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.DocumentContent; +import com.google.genai.types.interactions.content.ImageContent; +import com.google.genai.types.interactions.content.TextContent; +import com.google.genai.types.interactions.content.ThoughtContent; +import com.google.genai.types.interactions.content.VideoContent; + +/** + * Example: Create Interaction with All Content Types + * + *

Demonstrates interactions.create() with all available INPUT content types: + * + *

+ * + *

Also shows all possible OUTPUT content types that may be returned. + * + *

Note: The Interactions API is in beta and subject to change. + */ +public final class InteractionsCreateExample { + + public static void main(String[] args) { + // Instantiate the client. The client gets the API key from the environment variable + // `GOOGLE_API_KEY`. + // + Client client = new Client(); + + System.out.println("=== Interactions API: All Content Types Example ===\n"); + + try { + // ===== PART 1: TextContent ===== + System.out.println("--- PART 1: TextContent ---\n"); + + TextContent textContent = TextContent.builder() + .text("What is the meaning of life?") + .build(); + + CreateInteractionConfig textConfig = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromContents(textContent) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(textConfig.toJson()); + System.out.println(); + + Interaction textResponse = client.interactions.create(textConfig); + + System.out.println("=== RESPONSE ==="); + System.out.println(textResponse.toJson()); + System.out.println(); + + printResults(textResponse); + + // ===== PART 2: ImageContent ===== + System.out.println("\n--- PART 2: ImageContent ---\n"); + + String imageUri = "https://storage.googleapis.com/generativeai-downloads/images/cake.jpg"; + ImageContent imageContent = ImageContent.fromUri(imageUri, "image/jpeg"); + TextContent imagePrompt = TextContent.builder() + .text("Describe this image in detail.") + .build(); + + CreateInteractionConfig imageConfig = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromContents(imagePrompt, imageContent) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(imageConfig.toJson()); + System.out.println(); + + Interaction imageResponse = client.interactions.create(imageConfig); + + System.out.println("=== RESPONSE ==="); + System.out.println(imageResponse.toJson()); + System.out.println(); + + printResults(imageResponse); + + // ===== PART 3: AudioContent ===== + System.out.println("\n--- PART 3: AudioContent ---\n"); + + String audioUri = "https://storage.googleapis.com/cloud-samples-data/speech/brooklyn_bridge.mp3"; + AudioContent audioContent = AudioContent.fromUri(audioUri, "audio/mp3"); + TextContent audioPrompt = TextContent.builder() + .text("Transcribe this audio.") + .build(); + + CreateInteractionConfig audioConfig = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromContents(audioPrompt, audioContent) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(audioConfig.toJson()); + System.out.println(); + + Interaction audioResponse = client.interactions.create(audioConfig); + + System.out.println("=== RESPONSE ==="); + System.out.println(audioResponse.toJson()); + System.out.println(); + + printResults(audioResponse); + + // ===== PART 4: VideoContent ===== + System.out.println("\n--- PART 4: VideoContent ---\n"); + + String videoUri = "https://storage.googleapis.com/cloud-samples-data/generative-ai/video/pixel8.mp4"; + VideoContent videoContent = VideoContent.fromUri(videoUri, "video/mp4"); + TextContent videoPrompt = TextContent.builder() + .text("Describe what happens in this video.") + .build(); + + CreateInteractionConfig videoConfig = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromContents(videoPrompt, videoContent) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(videoConfig.toJson()); + System.out.println(); + + Interaction videoResponse = client.interactions.create(videoConfig); + + System.out.println("=== RESPONSE ==="); + System.out.println(videoResponse.toJson()); + System.out.println(); + + printResults(videoResponse); + + // ===== PART 5: DocumentContent (PDF) ===== + System.out.println("\n--- PART 5: DocumentContent ---\n"); + + String pdfUri = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"; + DocumentContent documentContent = DocumentContent.fromUri(pdfUri, "application/pdf"); + TextContent docPrompt = TextContent.builder() + .text("Summarize the main points of this document.") + .build(); + + CreateInteractionConfig docConfig = + CreateInteractionConfig.builder() + .model("gemini-3-pro-preview") + .inputFromContents(docPrompt, documentContent) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(docConfig.toJson()); + System.out.println(); + + Interaction docResponse = client.interactions.create(docConfig); + + System.out.println("=== RESPONSE ==="); + System.out.println(docResponse.toJson()); + System.out.println(); + + printResults(docResponse); + + // ===== PART 6: Multiple Content Types Combined ===== + System.out.println("\n--- PART 6: Multiple Content Types Combined ---\n"); + + // Note: Using gemini-3-pro-preview for multi-modal (image + video) combined requests + ImageContent cakeImage = ImageContent.fromUri( + "https://storage.googleapis.com/generativeai-downloads/images/cake.jpg", "image/jpeg"); + VideoContent pixelVideo = VideoContent.fromUri( + "https://storage.googleapis.com/cloud-samples-data/generative-ai/video/pixel8.mp4", "video/mp4"); + TextContent multiPrompt = TextContent.builder() + .text("Compare the cake image with what you see in the video. Are they related?") + .build(); + + CreateInteractionConfig multiConfig = + CreateInteractionConfig.builder() + .model("gemini-3-pro-preview") + .inputFromContents(multiPrompt, cakeImage, pixelVideo) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(multiConfig.toJson()); + System.out.println(); + + Interaction multiResponse = client.interactions.create(multiConfig); + + System.out.println("=== RESPONSE ==="); + System.out.println(multiResponse.toJson()); + System.out.println(); + + printResults(multiResponse); + + // ===== PART 7: Verify with Get API ===== + System.out.println("\n--- PART 7: Verify Last Interaction (Get API) ---\n"); + + GetInteractionConfig getConfig = GetInteractionConfig.builder().build(); + + Interaction retrieved = client.interactions.get(multiResponse.id(), getConfig); + + System.out.println("=== RESPONSE ==="); + System.out.println(retrieved.toJson()); + System.out.println(); + + printResults(retrieved); + + System.out.println("\n=== Example completed ==="); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** Prints extracted results from the interaction response. */ + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + + if (!interaction.outputs().isPresent() || interaction.outputs().get().isEmpty()) { + System.out.println(" Outputs: (none)"); + return; + } + + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + TextContent t = (TextContent) output; + String text = t.text().orElse("(empty)"); + System.out.println(" Text: " + text); + } else if (output instanceof ThoughtContent) { + ThoughtContent tc = (ThoughtContent) output; + System.out.println(" ThoughtContent:"); + System.out.println(" signature: " + tc.signature().orElse("(none)")); + if (tc.summary().isPresent()) { + System.out.println(" summaries: " + tc.summary().get().size()); + } + } else { + System.out.println(" " + output.getClass().getSimpleName()); + } + } + } + + private InteractionsCreateExample() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsDeleteExample.java b/examples/src/main/java/com/google/genai/examples/InteractionsDeleteExample.java new file mode 100644 index 00000000000..ca98a506be4 --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsDeleteExample.java @@ -0,0 +1,122 @@ +/* + * 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. + */ + +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.DeleteInteractionConfig; +import com.google.genai.types.interactions.DeleteInteractionResponse; +import com.google.genai.types.interactions.GetInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; + +/** + * Example: Delete Interaction API + * + *

Demonstrates the interactions.delete() API to delete an interaction. + * + *

To run this example: + *

    + *
  1. Set the GOOGLE_API_KEY environment variable: {@code export GOOGLE_API_KEY=YOUR_API_KEY} + *
  2. Compile the examples: {@code mvn clean compile} + *
  3. Run: {@code mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsDeleteExample"} + *
+ * + *

Note: The Interactions API is currently in beta. + */ +public final class InteractionsDeleteExample { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== Interactions API: Delete Example ===\n"); + + try { + // ===== STEP 1: Create an Interaction ===== + System.out.println("--- STEP 1: Create an Interaction ---\n"); + + CreateInteractionConfig createConfig = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("What is the capital of France?") + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(createConfig.toJson()); + System.out.println(); + + Interaction interaction = client.interactions.create(createConfig); + + System.out.println("=== RESPONSE ==="); + System.out.println(interaction.toJson()); + System.out.println(); + + printResults(interaction); + + String interactionId = interaction.id(); + + // ===== STEP 2: Delete the Interaction ===== + System.out.println("\n--- STEP 2: Delete the Interaction ---\n"); + + DeleteInteractionConfig deleteConfig = DeleteInteractionConfig.builder().build(); + DeleteInteractionResponse deleteResponse = + client.interactions.delete(interactionId, deleteConfig); + + System.out.println("Results:"); + System.out.println(" Deleted interaction: " + interactionId); + System.out.println(" Response: " + deleteResponse); + + // ===== STEP 3: Verify Deletion ===== + System.out.println("\n--- STEP 3: Verify Deletion (Get should fail) ---\n"); + + try { + GetInteractionConfig getConfig = GetInteractionConfig.builder().build(); + client.interactions.get(interactionId, getConfig); + System.out.println("ERROR: Should have thrown an exception!"); + } catch (Exception e) { + System.out.println("Results:"); + System.out.println(" Error: " + e.getMessage()); + System.out.println(" (Expected - interaction was deleted)"); + } + + System.out.println("\n=== Example completed ==="); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + + if (interaction.outputs().isPresent() && !interaction.outputs().get().isEmpty()) { + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + System.out.println(" Text: " + ((TextContent) output).text().orElse("(empty)")); + } else { + System.out.println(" " + output.getClass().getSimpleName()); + } + } + } + } + + private InteractionsDeleteExample() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsDocumentContent.java b/examples/src/main/java/com/google/genai/examples/InteractionsDocumentContent.java new file mode 100644 index 00000000000..cd477ae88d2 --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsDocumentContent.java @@ -0,0 +1,133 @@ +/* + * 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. + */ + +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.DocumentContent; +import com.google.genai.types.interactions.content.TextContent; + +/** + * Example demonstrating document analysis using DocumentContent with the Interactions API. + * + *

This example shows: + *

+ * + *

To run this example: + *

    + *
  1. Set the GOOGLE_API_KEY environment variable: {@code export GOOGLE_API_KEY=YOUR_API_KEY} + *
  2. Compile the examples: {@code mvn clean compile} + *
  3. Run: {@code mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsDocumentContent"} + *
+ * + *

Note: The Interactions API is currently in beta. + */ +public final class InteractionsDocumentContent { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== Interactions API: Document Content Example ===\n"); + + try { + // ===== PART 1: Document from URI ===== + System.out.println("--- PART 1: Document from URI ---\n"); + + String documentUri = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"; + DocumentContent documentContent = DocumentContent.fromUri(documentUri, "application/pdf"); + TextContent textPrompt = TextContent.builder() + .text("Summarize this document.") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-3-pro-preview") + .inputFromContents(textPrompt, documentContent) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + Interaction response = client.interactions.create(config); + + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + printResults(response); + + // ===== PART 2: Document from Inline Data (base64) ===== + System.out.println("\n--- PART 2: Document from Inline Data (Base64) ---\n"); + + // Minimal valid PDF document (1 page with "Hello World") + String base64Data = + "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovUGFnZXMgMiAwIFIKPj4KZW5kb2JqCjIgMCBvYmoKPDwKL1R5cGUgL1BhZ2VzCi9LaWRzIFszIDAgUl0KL0NvdW50IDEKL01lZGlhQm94IFswIDAgNjEyIDc5Ml0KPj4KZW5kb2JqCjMgMCBvYmoKPDwKL1R5cGUgL1BhZ2UKL1BhcmVudCAyIDAgUgovUmVzb3VyY2VzIDw8Ci9Gb250IDw8Ci9GMSA0IDAgUgo+Pgo+PgovQ29udGVudHMgNSAwIFIKPj4KZW5kb2JqCjQgMCBvYmoKPDwKL1R5cGUgL0ZvbnQKL1N1YnR5cGUgL1R5cGUxCi9CYXNlRm9udCAvSGVsdmV0aWNhCj4+CmVuZG9iago1IDAgb2JqCjw8Ci9MZW5ndGggNDQKPj4Kc3RyZWFtCkJUCi9GMSA0OCBUZgoxMCA3MDAgVGQKKEhlbGxvIFdvcmxkKSBUagpFVAplbmRzdHJlYW0KZW5kb2JqCnhyZWYKMCA2CjAwMDAwMDAwMDAgNjU1MzUgZgogCjAwMDAwMDAwMTUgMDAwMDAgbiAKMDAwMDAwMDA2NCAwMDAwMCBuIAowMDAwMDAwMTUxIDAwMDAwIG4gCjAwMDAwMDAyNjIgMDAwMDAgbiAKMDAwMDAwMDM0OSAwMDAwMCBuIAp0cmFpbGVyCjw8Ci9TaXplIDYKL1Jvb3QgMSAwIFIKPj4Kc3RhcnR4cmVmCjQ0MgolJUVPRgo="; + DocumentContent documentFromData = DocumentContent.fromData(base64Data, "application/pdf"); + TextContent textPrompt2 = TextContent.builder() + .text("What is the content of this document?") + .build(); + + CreateInteractionConfig config2 = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromContents(textPrompt2, documentFromData) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config2.toJson()); + System.out.println(); + + Interaction response2 = client.interactions.create(config2); + + System.out.println("=== RESPONSE ==="); + System.out.println(response2.toJson()); + System.out.println(); + + printResults(response2); + + System.out.println("\n=== Example completed ==="); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + + if (interaction.outputs().isPresent() && !interaction.outputs().get().isEmpty()) { + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + System.out.println(" Text: " + ((TextContent) output).text().orElse("(empty)")); + } else { + System.out.println(" " + output.getClass().getSimpleName()); + } + } + } + } + + private InteractionsDocumentContent() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsFileSearch.java b/examples/src/main/java/com/google/genai/examples/InteractionsFileSearch.java new file mode 100644 index 00000000000..6553a8e685b --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsFileSearch.java @@ -0,0 +1,199 @@ +/* + * 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. + */ + +/** + * Usage: + * + *

Note: The Interactions API is currently only available via the Gemini Developer API (not + * Vertex AI). + * + *

IMPORTANT: This example requires file search stores to be created beforehand. + * + *

Setup Instructions: + * + *

1. Create a file search store using the Google AI API: + * + *

Using gcloud CLI or API: + * + *

curl -X POST https://generativelanguage.googleapis.com/v1beta/fileSearchStores \ + * + *

-H "Authorization: Bearer $(gcloud auth print-access-token)" \ + * + *

-H "Content-Type: application/json" \ + * + *

-d '{"displayName": "my-document-store"}' + * + *

2. Upload files to the store: + * + *

curl -X POST + * https://generativelanguage.googleapis.com/v1beta/fileSearchStores/STORE_NAME/documents \ + * + *

-H "Authorization: Bearer $(gcloud auth print-access-token)" \ + * + *

-F "file=@/path/to/document.pdf" + * + *

3. Set an API key environment variable: + * + *

export GOOGLE_API_KEY=YOUR_API_KEY + * + *

4. Update the store name in this example to match your created store. + * + *

5. Compile and run: + * + *

mvn clean compile + * + *

mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsFileSearch" + */ +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.FileSearchResult; +import com.google.genai.types.interactions.content.FileSearchResultContent; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; +import com.google.genai.types.interactions.tools.FileSearch; + +/** + * Example: File Search Tool with the Interactions API + * + *

Demonstrates how to use the FileSearch to enable the model to search through file stores. + * This is useful for RAG (Retrieval-Augmented Generation) use cases where you want the model to + * answer questions based on your documents. + * + *

IMPORTANT: Requires file search stores to be created beforehand. See usage instructions + * above. + * + *

Note: The Interactions API is in beta and subject to change. + */ +public final class InteractionsFileSearch { + + public static void main(String[] args) { + // Instantiate the client. The client gets the API key from the environment variable + // `GOOGLE_API_KEY`. + // + Client client = new Client(); + + System.out.println("=== Interactions API: File Search Tool Example ===\n"); + + System.out.println("IMPORTANT: This example requires file search stores to be created.\n"); + System.out.println("See the file header comments for setup instructions.\n"); + + // ===== STEP 1: Create FileSearch ===== + System.out.println("STEP 1: Create FileSearch\n"); + + // Configure the file search tool + // Update the store names to match your created file search stores + FileSearch fileSearchTool = + FileSearch.builder() + .fileSearchStoreNames( + "my-document-store-1", + "my-document-store-2" // You can search across multiple stores + ) + .topK(10) // Maximum number of results to return + // Optional: Add metadata filter + // .metadataFilter(ImmutableMap.of("category", "technical", "year", 2025)) + .build(); + + System.out.println("FileSearch created successfully"); + System.out.println( + "Store Names: " + fileSearchTool.fileSearchStoreNames().orElse(java.util.List.of())); + System.out.println("Top K: " + fileSearchTool.topK().orElse(10)); + System.out.println(); + + // ===== STEP 2: Create interaction with File Search enabled ===== + System.out.println("---\n"); + System.out.println("STEP 2: Create interaction with File Search enabled\n"); + + String userQuestion = + "What are the main features described in the documentation? " + + "Please search through the uploaded files."; + System.out.println("User: " + userQuestion + "\n"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input(userQuestion) + .tools(fileSearchTool) + .build(); + + // Print the request JSON + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + try { + Interaction response = client.interactions.create(config); + + // Print the response JSON + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + // ===== STEP 3: Extract and display the results ===== + System.out.println("---\n"); + System.out.println("STEP 3: Extract and display the results\n"); + + System.out.println("Response received. Interaction ID: " + response.id()); + System.out.println(); + + if (response.outputs().isPresent()) { + for (Content content : response.outputs().get()) { + if (content instanceof TextContent) { + System.out.println("Text: " + ((TextContent) content).text().orElse("(empty)")); + System.out.println(); + } else if (content instanceof FileSearchResultContent) { + FileSearchResultContent searchResult = (FileSearchResultContent) content; + System.out.println("File Search Result:"); + + if (searchResult.result().isPresent()) { + for (FileSearchResult result : searchResult.result().get()) { + System.out.println(" Title: " + result.title().orElse("N/A")); + System.out.println(" File Search Store: " + result.fileSearchStore().orElse("N/A")); + + if (result.text().isPresent()) { + String text = result.text().get(); + System.out.println(" Text (first 500 chars):"); + System.out.println(" ---"); + System.out.println(text.length() > 500 ? text.substring(0, 500) + "..." : text); + System.out.println(" ---"); + System.out.println(" Total text length: " + text.length() + " chars"); + } + } + } + System.out.println(); + } + } + } + + System.out.println("\n=== Example completed successfully ==="); + + } catch (Exception e) { + System.err.println("\n=== Error occurred ==="); + System.err.println("Error: " + e.getMessage()); + System.err.println("\nNote: This example requires file search stores to be created."); + System.err.println("Please check:"); + System.err.println(" 1. File search stores exist with the names specified"); + System.err.println(" 2. Files have been uploaded to the stores"); + System.err.println(" 3. Your API key has access to the stores"); + System.err.println(" 4. The store names in this example match your actual store names"); + e.printStackTrace(); + } + } + + private InteractionsFileSearch() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsFunctionCalling.java b/examples/src/main/java/com/google/genai/examples/InteractionsFunctionCalling.java new file mode 100644 index 00000000000..aedfdacb3a5 --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsFunctionCalling.java @@ -0,0 +1,190 @@ +/* + * 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. + */ + +package com.google.genai.examples; + +import com.google.common.collect.ImmutableMap; +import com.google.genai.Client; +import com.google.genai.types.Schema; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.FunctionCallContent; +import com.google.genai.types.interactions.content.FunctionResultContent; +import com.google.genai.types.interactions.content.TextContent; +import com.google.genai.types.interactions.tools.Function; +import java.util.Map; + +/** + * Example: Function Calling with the Interactions API + * + *

Demonstrates manual function calling where you define functions, extract calls from responses, + * execute them, and send results back. + * + *

To run this example: + *

    + *
  1. Set the GOOGLE_API_KEY environment variable: {@code export GOOGLE_API_KEY=YOUR_API_KEY} + *
  2. Compile the examples: {@code mvn clean compile} + *
  3. Run: {@code mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsFunctionCalling"} + *
+ * + *

Note: The Interactions API is currently in beta. + */ +public final class InteractionsFunctionCalling { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== Interactions API: Function Calling Example ===\n"); + + try { + // ===== STEP 1: Define the Function ===== + System.out.println("--- STEP 1: Define the Function ---\n"); + + Function weatherTool = + Function.builder() + .name("get_weather") + .description("Get the current weather for a location") + .parameters( + Schema.builder() + .type("object") + .properties( + Map.of( + "location", + Schema.builder() + .type("string") + .description("The city name") + .build())) + .required("location") + .build()) + .build(); + + System.out.println("Function defined: get_weather\n"); + + // ===== STEP 2: First Request - Ask about weather ===== + System.out.println("--- STEP 2: First Request (triggers function call) ---\n"); + + CreateInteractionConfig config1 = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("What's the weather like in Paris?") + .tools(weatherTool) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config1.toJson()); + System.out.println(); + + Interaction response1 = client.interactions.create(config1); + + System.out.println("=== RESPONSE ==="); + System.out.println(response1.toJson()); + System.out.println(); + + printResults(response1); + + // Extract function call + FunctionCallContent functionCall = extractFunctionCall(response1); + if (functionCall == null) { + System.out.println("ERROR: Expected a function call but didn't receive one."); + return; + } + + System.out.println(" Function Call ID: " + functionCall.id()); + System.out.println(" Function Name: " + functionCall.name()); + System.out.println(" Arguments: " + functionCall.arguments()); + + // ===== STEP 3: Execute Function and Send Result ===== + System.out.println("\n--- STEP 3: Execute Function and Send Result ---\n"); + + Map weatherResult = executeGetWeather(functionCall.arguments()); + System.out.println("Function executed. Result: " + weatherResult + "\n"); + + FunctionResultContent functionResult = + FunctionResultContent.builder() + .id(functionCall.id()) + .name(functionCall.name()) + .result(weatherResult) + .build(); + + CreateInteractionConfig config2 = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromContents(functionResult) + .previousInteractionId(response1.id()) + .tools(weatherTool) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config2.toJson()); + System.out.println(); + + Interaction response2 = client.interactions.create(config2); + + System.out.println("=== RESPONSE ==="); + System.out.println(response2.toJson()); + System.out.println(); + + printResults(response2); + + System.out.println("\n=== Example completed ==="); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static Map executeGetWeather(Map arguments) { + String location = (String) arguments.getOrDefault("location", "Unknown"); + return ImmutableMap.of( + "location", location, + "temperature", "22", + "unit", "celsius", + "condition", "sunny with a few clouds"); + } + + private static FunctionCallContent extractFunctionCall(Interaction interaction) { + if (interaction.outputs().isPresent()) { + for (Content output : interaction.outputs().get()) { + if (output instanceof FunctionCallContent) { + return (FunctionCallContent) output; + } + } + } + return null; + } + + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + + if (interaction.outputs().isPresent() && !interaction.outputs().get().isEmpty()) { + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + System.out.println(" Text: " + ((TextContent) output).text().orElse("(empty)")); + } else if (output instanceof FunctionCallContent) { + System.out.println(" FunctionCallContent:"); + } else { + System.out.println(" " + output.getClass().getSimpleName()); + } + } + } + } + + private InteractionsFunctionCalling() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsGetExample.java b/examples/src/main/java/com/google/genai/examples/InteractionsGetExample.java new file mode 100644 index 00000000000..f4848d7c989 --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsGetExample.java @@ -0,0 +1,108 @@ +/* + * 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. + */ + +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.GetInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; + +/** + * Example: Get Interaction API + * + *

Demonstrates the interactions.get() API to retrieve an interaction by ID. + * + *

To run this example: + *

    + *
  1. Set the GOOGLE_API_KEY environment variable: {@code export GOOGLE_API_KEY=YOUR_API_KEY} + *
  2. Compile the examples: {@code mvn clean compile} + *
  3. Run: {@code mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsGetExample"} + *
+ * + *

Note: The Interactions API is currently in beta. + */ +public final class InteractionsGetExample { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== Interactions API: Get Example ===\n"); + + try { + // ===== STEP 1: Create an Interaction ===== + System.out.println("--- STEP 1: Create an Interaction ---\n"); + + CreateInteractionConfig createConfig = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("Explain quantum computing in 2-3 sentences.") + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(createConfig.toJson()); + System.out.println(); + + Interaction interaction = client.interactions.create(createConfig); + + System.out.println("=== RESPONSE ==="); + System.out.println(interaction.toJson()); + System.out.println(); + + printResults(interaction); + + // ===== STEP 2: Get Interaction by ID ===== + System.out.println("\n--- STEP 2: Get Interaction by ID ---\n"); + + String interactionId = interaction.id(); + GetInteractionConfig getConfig = GetInteractionConfig.builder().build(); + + Interaction retrieved = client.interactions.get(interactionId, getConfig); + + System.out.println("=== RESPONSE ==="); + System.out.println(retrieved.toJson()); + System.out.println(); + + printResults(retrieved); + + System.out.println("\n=== Example completed ==="); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + + if (interaction.outputs().isPresent() && !interaction.outputs().get().isEmpty()) { + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + System.out.println(" Text: " + ((TextContent) output).text().orElse("(empty)")); + } else { + System.out.println(" " + output.getClass().getSimpleName()); + } + } + } + } + + private InteractionsGetExample() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsGoogleSearch.java b/examples/src/main/java/com/google/genai/examples/InteractionsGoogleSearch.java new file mode 100644 index 00000000000..b544da2767f --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsGoogleSearch.java @@ -0,0 +1,118 @@ +/* + * 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. + */ + +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.GoogleSearchResult; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.GoogleSearchCallContent; +import com.google.genai.types.interactions.content.GoogleSearchResultContent; +import com.google.genai.types.interactions.content.TextContent; +import com.google.genai.types.interactions.tools.GoogleSearch; +import java.util.List; + +/** + * Example: Google Search Tool with the Interactions API + * + *

Demonstrates how to use the GoogleSearch tool to enable the model to search the web. + * + *

To run this example: + *

    + *
  1. Set the GOOGLE_API_KEY environment variable: {@code export GOOGLE_API_KEY=YOUR_API_KEY} + *
  2. Compile the examples: {@code mvn clean compile} + *
  3. Run: {@code mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsGoogleSearch"} + *
+ * + *

Note: The Interactions API is currently in beta. + */ +public final class InteractionsGoogleSearch { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== Interactions API: Google Search Tool Example ===\n"); + + try { + GoogleSearch searchTool = GoogleSearch.builder().build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("What are the latest developments in quantum computing in 2025?") + .tools(searchTool) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + Interaction response = client.interactions.create(config); + + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + printResults(response); + + System.out.println("\n=== Example completed ==="); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + + if (!interaction.outputs().isPresent() || interaction.outputs().get().isEmpty()) { + System.out.println(" Outputs: (none)"); + return; + } + + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + System.out.println(" Text: " + ((TextContent) output).text().orElse("(empty)")); + } else if (output instanceof GoogleSearchCallContent) { + GoogleSearchCallContent searchCall = (GoogleSearchCallContent) output; + System.out.println(" GoogleSearchCall: id=" + searchCall.id()); + if (searchCall.arguments().isPresent()) { + System.out.println(" queries: " + searchCall.arguments().get().queries().orElse(List.of())); + } + } else if (output instanceof GoogleSearchResultContent) { + GoogleSearchResultContent searchResult = (GoogleSearchResultContent) output; + System.out.println(" GoogleSearchResult: callId=" + searchResult.callId().orElse("N/A")); + if (searchResult.result().isPresent()) { + List results = searchResult.result().get(); + System.out.println(" results count: " + results.size()); + for (int i = 0; i < Math.min(3, results.size()); i++) { + GoogleSearchResult result = results.get(i); + System.out.println(" [" + (i + 1) + "] " + result.title().orElse("N/A")); + } + } + } else { + System.out.println(" " + output.getClass().getSimpleName()); + } + } + } + + private InteractionsGoogleSearch() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsImageContent.java b/examples/src/main/java/com/google/genai/examples/InteractionsImageContent.java new file mode 100644 index 00000000000..a1a90262aa5 --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsImageContent.java @@ -0,0 +1,133 @@ +/* + * 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. + */ + +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.ImageContent; +import com.google.genai.types.interactions.content.TextContent; + +/** + * Example demonstrating image analysis using ImageContent with the Interactions API. + * + *

This example shows: + *

+ * + *

To run this example: + *

    + *
  1. Set the GOOGLE_API_KEY environment variable: {@code export GOOGLE_API_KEY=YOUR_API_KEY} + *
  2. Compile the examples: {@code mvn clean compile} + *
  3. Run: {@code mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsImageContent"} + *
+ * + *

Note: The Interactions API is currently in beta. + */ +public final class InteractionsImageContent { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== Interactions API: Image Content Example ===\n"); + + try { + // ===== PART 1: Image from URI ===== + System.out.println("--- PART 1: Image from URI ---\n"); + + String imageUri = "https://storage.googleapis.com/generativeai-downloads/images/cake.jpg"; + ImageContent imageContent = ImageContent.fromUri(imageUri, "image/jpeg"); + TextContent textPrompt = TextContent.builder() + .text("Describe what you see in this image.") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromContents(textPrompt, imageContent) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + Interaction response = client.interactions.create(config); + + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + printResults(response); + + // ===== PART 2: Image from Inline Data (base64) ===== + System.out.println("\n--- PART 2: Image from Inline Data (Base64) ---\n"); + + // Small 1x1 red pixel PNG encoded as base64 + String base64Data = + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=="; + ImageContent imageFromData = ImageContent.fromData(base64Data, "image/png"); + TextContent textPrompt2 = TextContent.builder() + .text("What color is this image?") + .build(); + + CreateInteractionConfig config2 = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromContents(textPrompt2, imageFromData) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config2.toJson()); + System.out.println(); + + Interaction response2 = client.interactions.create(config2); + + System.out.println("=== RESPONSE ==="); + System.out.println(response2.toJson()); + System.out.println(); + + printResults(response2); + + System.out.println("\n=== Example completed ==="); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + + if (interaction.outputs().isPresent() && !interaction.outputs().get().isEmpty()) { + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + System.out.println(" Text: " + ((TextContent) output).text().orElse("(empty)")); + } else { + System.out.println(" " + output.getClass().getSimpleName()); + } + } + } + } + + private InteractionsImageContent() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsMcpServer.java b/examples/src/main/java/com/google/genai/examples/InteractionsMcpServer.java new file mode 100644 index 00000000000..d24646f524a --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsMcpServer.java @@ -0,0 +1,186 @@ +/* + * 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. + */ + +/** + * Usage: + * + *

Note: The Interactions API is currently only available via the Gemini Developer API (not + * Vertex AI). + * + *

IMPORTANT: This example requires a running MCP (Model Context Protocol) server. + * + *

Setup Instructions: + * + *

1. Set up an MCP server. You can use one of the reference implementations: + * + *

- MCP Server SDK: https://github.com/modelcontextprotocol/servers + * + *

- Example: Weather MCP Server (Python) + * + *

pip install mcp + * + *

python -m mcp.server.weather --port 8080 + * + *

2. Set an API key environment variable: + * + *

export GOOGLE_API_KEY=YOUR_API_KEY + * + *

3. Update the MCP server URL in this example to point to your running MCP server. + * + *

4. Compile and run: + * + *

mvn clean compile + * + *

mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsMcpServer" + */ +package com.google.genai.examples; + +import com.google.common.collect.ImmutableMap; +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.McpServerToolCallContent; +import com.google.genai.types.interactions.content.McpServerToolResultContent; +import com.google.genai.types.interactions.content.TextContent; +import com.google.genai.types.interactions.tools.McpServer; + +/** + * Example: MCP Server Tool with the Interactions API + * + *

Demonstrates how to use the McpServer to enable the model to interact with an MCP (Model + * Context Protocol) server. This allows integration with external tools and services that implement + * the MCP protocol. + * + *

IMPORTANT: Requires a running MCP server. See usage instructions above. + * + *

Note: The Interactions API is in beta and subject to change. + */ +public final class InteractionsMcpServer { + + public static void main(String[] args) { + // Instantiate the client. The client gets the API key from the environment variable + // `GOOGLE_API_KEY`. + // + Client client = new Client(); + + System.out.println("=== Interactions API: MCP Server Tool Example ===\n"); + + System.out.println("IMPORTANT: This example requires a running MCP server.\n"); + System.out.println("See the file header comments for setup instructions.\n"); + + // ===== STEP 1: Create McpServer ===== + System.out.println("STEP 1: Create McpServer\n"); + + // Configure the MCP server connection + // Update these values to match your MCP server setup + McpServer mcpTool = + McpServer.builder() + .name("my-mcp-server") + .url("http://localhost:8080/mcp") // Update this URL to your MCP server + // Optional: Add custom headers if your MCP server requires authentication + .headers( + ImmutableMap.of( + "Content-Type", "application/json" + // "Authorization", "Bearer YOUR_TOKEN" + )) + // Optional: Restrict to specific tools from the MCP server + // .allowedTools("weather", "calculator") + .build(); + + System.out.println("McpServer created successfully"); + System.out.println("Name: " + mcpTool.name().orElse("N/A")); + System.out.println("URL: " + mcpTool.url().orElse("N/A")); + System.out.println(); + + // ===== STEP 2: Create interaction with MCP Server enabled ===== + System.out.println("---\n"); + System.out.println("STEP 2: Create interaction with MCP Server enabled\n"); + + String userQuestion = + "What's the weather like in San Francisco? " + + "(This uses the MCP server's weather tool)"; + System.out.println("User: " + userQuestion + "\n"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input(userQuestion) + .tools(mcpTool) + .build(); + + // Print the request JSON + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + try { + Interaction response = client.interactions.create(config); + + // Print the response JSON + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + // ===== STEP 3: Extract and display the results ===== + System.out.println("---\n"); + System.out.println("STEP 3: Extract and display the results\n"); + + System.out.println("Response received. Interaction ID: " + response.id()); + System.out.println(); + + if (response.outputs().isPresent()) { + for (Content content : response.outputs().get()) { + if (content instanceof TextContent) { + System.out.println("Text: " + ((TextContent) content).text().orElse("(empty)")); + System.out.println(); + } else if (content instanceof McpServerToolCallContent) { + McpServerToolCallContent mcpCall = (McpServerToolCallContent) content; + System.out.println("MCP Server Tool Call:"); + System.out.println(" ID: " + mcpCall.id()); + System.out.println(" Tool Name: " + mcpCall.name()); + System.out.println(" Server Name: " + mcpCall.serverName()); + System.out.println(" Arguments: " + mcpCall.arguments()); + System.out.println(); + } else if (content instanceof McpServerToolResultContent) { + McpServerToolResultContent mcpResult = (McpServerToolResultContent) content; + System.out.println("MCP Server Tool Result:"); + System.out.println(" Call ID: " + mcpResult.callId()); + System.out.println(" Tool Name: " + mcpResult.name().orElse("N/A")); + System.out.println(" Server Name: " + mcpResult.serverName().orElse("N/A")); + System.out.println(" Result: " + mcpResult.result()); + System.out.println(); + } + } + } + + System.out.println("\n=== Example completed successfully ==="); + + } catch (Exception e) { + System.err.println("\n=== Error occurred ==="); + System.err.println("Error: " + e.getMessage()); + System.err.println("\nNote: This example requires a running MCP server."); + System.err.println("Please check:"); + System.err.println(" 1. Your MCP server is running and accessible"); + System.err.println(" 2. The URL in this example matches your MCP server endpoint"); + System.err.println(" 3. Any required authentication headers are configured"); + System.err.println(" 4. Your firewall allows connections to the MCP server"); + e.printStackTrace(); + } + } + + private InteractionsMcpServer() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsMediaResolution.java b/examples/src/main/java/com/google/genai/examples/InteractionsMediaResolution.java new file mode 100644 index 00000000000..ab1b25894dc --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsMediaResolution.java @@ -0,0 +1,106 @@ +/* + * 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. + */ + +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.MediaResolution; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.ImageContent; +import com.google.genai.types.interactions.content.TextContent; + +/** + * Example demonstrating MediaResolution with ImageContent in the Interactions API. + * + *

This example shows how to specify image resolution when analyzing images. + * + *

To run this example: + *

    + *
  1. Set the GOOGLE_API_KEY environment variable: {@code export GOOGLE_API_KEY=YOUR_API_KEY} + *
  2. Compile the examples: {@code mvn clean compile} + *
  3. Run: {@code mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsMediaResolution"} + *
+ * + *

Note: The Interactions API is currently in beta. + */ +public final class InteractionsMediaResolution { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== Interactions API: Media Resolution Example ===\n"); + + try { + String imageUri = "https://storage.googleapis.com/generativeai-downloads/images/cake.jpg"; + + // Create ImageContent with HIGH resolution + MediaResolution resolution = new MediaResolution(MediaResolution.Known.HIGH); + ImageContent imageContent = ImageContent.builder() + .uri(imageUri) + .mimeType(new com.google.genai.types.interactions.ImageMimeType("image/jpeg")) + .resolution(resolution) + .build(); + + TextContent textPrompt = TextContent.builder() + .text("Describe this image in detail.") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromContents(textPrompt, imageContent) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + Interaction response = client.interactions.create(config); + + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + printResults(response); + + System.out.println("\n=== Example completed ==="); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + + if (interaction.outputs().isPresent() && !interaction.outputs().get().isEmpty()) { + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + System.out.println(" Text: " + ((TextContent) output).text().orElse("(empty)")); + } else { + System.out.println(" " + output.getClass().getSimpleName()); + } + } + } + } + + private InteractionsMediaResolution() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsMultiTurnConversation.java b/examples/src/main/java/com/google/genai/examples/InteractionsMultiTurnConversation.java new file mode 100644 index 00000000000..e5624d16d03 --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsMultiTurnConversation.java @@ -0,0 +1,105 @@ +/* + * 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. + */ + +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.Turn; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; + +/** + * Example: Multi-Turn Conversation with Turn objects + * + *

Demonstrates a multi-turn conversation using Turn objects with role assignment. + * + *

To run this example: + *

    + *
  1. Set the GOOGLE_API_KEY environment variable: {@code export GOOGLE_API_KEY=YOUR_API_KEY} + *
  2. Compile the examples: {@code mvn clean compile} + *
  3. Run: {@code mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsMultiTurnConversation"} + *
+ * + *

Note: The Interactions API is currently in beta. + */ +public final class InteractionsMultiTurnConversation { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== Interactions API: Multi-Turn Conversation Example ===\n"); + + try { + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromTurns( + Turn.builder() + .role("user") + .content(TextContent.builder().text("I'm planning a trip to Paris.").build()) + .build(), + Turn.builder() + .role("model") + .content(TextContent.builder() + .text("That's wonderful! Paris is a beautiful city. What would you like to know?") + .build()) + .build(), + Turn.builder() + .role("user") + .content(TextContent.builder().text("What are the top 3 must-see attractions?").build()) + .build()) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + Interaction response = client.interactions.create(config); + + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + printResults(response); + + System.out.println("\n=== Example completed ==="); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + + if (interaction.outputs().isPresent() && !interaction.outputs().get().isEmpty()) { + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + System.out.println(" Text: " + ((TextContent) output).text().orElse("(empty)")); + } else { + System.out.println(" " + output.getClass().getSimpleName()); + } + } + } + } + + private InteractionsMultiTurnConversation() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsMultipleContents.java b/examples/src/main/java/com/google/genai/examples/InteractionsMultipleContents.java new file mode 100644 index 00000000000..9721e9573bf --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsMultipleContents.java @@ -0,0 +1,97 @@ +/* + * 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. + */ + +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; + +/** + * Example: Single Turn with Multiple Content Items + * + *

Demonstrates using Content objects to send multiple content items in a single turn. + * + *

To run this example: + *

    + *
  1. Set the GOOGLE_API_KEY environment variable: {@code export GOOGLE_API_KEY=YOUR_API_KEY} + *
  2. Compile the examples: {@code mvn clean compile} + *
  3. Run: {@code mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsMultipleContents"} + *
+ * + *

Note: The Interactions API is currently in beta. + */ +public final class InteractionsMultipleContents { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== Interactions API: Multiple Contents Example ===\n"); + + try { + TextContent content1 = TextContent.builder() + .text("Tell me about the Eiffel Tower.") + .build(); + TextContent content2 = TextContent.builder() + .text("Keep it brief, under 50 words.") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromContents(content1, content2) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + Interaction response = client.interactions.create(config); + + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + printResults(response); + + System.out.println("\n=== Example completed ==="); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + + if (interaction.outputs().isPresent() && !interaction.outputs().get().isEmpty()) { + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + System.out.println(" Text: " + ((TextContent) output).text().orElse("(empty)")); + } else { + System.out.println(" " + output.getClass().getSimpleName()); + } + } + } + } + + private InteractionsMultipleContents() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsMultipleToolsLifecycle.java.backup b/examples/src/main/java/com/google/genai/examples/InteractionsMultipleToolsLifecycle.java.backup new file mode 100644 index 00000000000..282c5190e55 --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsMultipleToolsLifecycle.java.backup @@ -0,0 +1,592 @@ +/* + * 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. + */ + +/** + * Usage: + * + *

Note: The Interactions API is currently only available via the Gemini Developer API (not + * Vertex AI). + * + *

1. Set an API key environment variable. You can find a list of available API keys here: + * https://aistudio.google.com/app/apikey + * + *

export GOOGLE_API_KEY=YOUR_API_KEY + * + *

2. Compile the java package and run the sample code. + * + *

mvn clean compile + * + *

mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsMultipleToolsLifecycle" + */ +package com.google.genai.examples; + +import com.google.common.collect.ImmutableMap; +import com.google.genai.Client; +import com.google.genai.types.Schema; +import com.google.genai.types.interactions.CancelInteractionConfig; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.DeleteInteractionConfig; +import com.google.genai.types.interactions.DeleteInteractionResponse; +import com.google.genai.types.interactions.GetInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.FunctionCallContent; +import com.google.genai.types.interactions.content.TextContent; +import com.google.genai.types.interactions.tools.CodeExecutionTool; +import com.google.genai.types.interactions.tools.FunctionTool; +import com.google.genai.types.interactions.tools.GoogleSearchTool; +import com.google.genai.types.interactions.tools.Tool; +import com.google.genai.types.interactions.tools.UrlContextTool; +import java.lang.reflect.Method; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Example: Multiple Tools with Full Interaction Lifecycle + * + *

Demonstrates: + * + *

    + *
  1. CREATE - Create an interaction with multiple tools: + * + *
  2. GET - Retrieve interaction and validate it matches CREATE response + *
  3. CANCEL - Cancel the interaction + *
  4. GET after CANCEL - Verify status changed to CANCELLED + *
  5. DELETE - Delete the interaction + *
  6. GET after DELETE - Verify interaction is deleted (should fail) + *
+ * + *

All requests and responses are logged and validated. + * + *

Note: The Interactions API is in beta and subject to change. + */ +public final class InteractionsMultipleToolsLifecycle { + + private static final String SEPARATOR = "=".repeat(80); + private static final String SUB_SEPARATOR = "-".repeat(80); + private static final DateTimeFormatter TIMESTAMP_FORMAT = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + + public static void main(String[] args) throws Exception { + // Instantiate the client + Client client = new Client(); + + System.out.println(SEPARATOR); + System.out.println("Interactions API: Multiple Tools Full Lifecycle Example"); + System.out.println(SEPARATOR); + System.out.println(); + + try { + // ======================================== + // STEP 1: CREATE - Create interaction with multiple tools + // ======================================== + Interaction createResponse = stepCreate(client); + + // ======================================== + // STEP 2: GET - Retrieve and validate + // ======================================== + Interaction getResponse = stepGet(client, createResponse); + + // ======================================== + // STEP 3: CANCEL - Cancel the interaction + // ======================================== + Interaction cancelResponse = stepCancel(client, createResponse.id()); + + // ======================================== + // STEP 4: GET after CANCEL - Verify cancelled + // ======================================== + Interaction getAfterCancelResponse = stepGetAfterCancel(client, createResponse.id()); + + // ======================================== + // STEP 5: DELETE - Delete the interaction + // ======================================== + DeleteInteractionResponse deleteResponse = stepDelete(client, createResponse.id()); + + // ======================================== + // STEP 6: GET after DELETE - Verify deleted + // ======================================== + stepGetAfterDelete(client, createResponse.id()); + + System.out.println(); + System.out.println(SEPARATOR); + System.out.println("✅ ALL LIFECYCLE STEPS COMPLETED SUCCESSFULLY"); + System.out.println(SEPARATOR); + + } catch (Exception e) { + System.err.println(); + System.err.println(SEPARATOR); + System.err.println("❌ ERROR: " + e.getMessage()); + System.err.println(SEPARATOR); + e.printStackTrace(); + System.exit(1); + } + } + + // ================================================================================== + // STEP 1: CREATE + // ================================================================================== + + private static Interaction stepCreate(Client client) throws Exception { + printStepHeader("STEP 1: CREATE", "Create interaction with multiple tools"); + + // Define all tools + List tools = new ArrayList<>(); + + // 1. FunctionTool WITH AFC (Automatic Function Calling) + System.out.println("Configuring FunctionTool WITH AFC (getWeather)..."); + Method getWeatherMethod = + InteractionsMultipleToolsLifecycle.class.getMethod("getWeather", String.class); + FunctionTool afcFunctionTool = + FunctionTool.fromMethod("Get the current weather for a city", getWeatherMethod); + tools.add(afcFunctionTool); + System.out.println(" ✓ AFC enabled: " + afcFunctionTool.method().isPresent()); + + // 2. FunctionTool WITHOUT AFC (Manual Function Calling) + System.out.println("Configuring FunctionTool WITHOUT AFC (calculateSum)..."); + FunctionTool manualFunctionTool = + FunctionTool.builder() + .name("calculate_sum") + .description("Calculate the sum of two numbers") + .parameters( + Schema.builder() + .type("object") + .properties( + Map.of( + "a", + Schema.builder() + .type("number") + .description("First number") + .build(), + "b", + Schema.builder() + .type("number") + .description("Second number") + .build())) + .required("a", "b") + .build()) + .build(); + tools.add(manualFunctionTool); + System.out.println(" ✓ Manual mode (no method): " + !manualFunctionTool.method().isPresent()); + + // 3. GoogleSearchTool + System.out.println("Configuring GoogleSearchTool..."); + GoogleSearchTool googleSearchTool = GoogleSearchTool.builder().build(); + tools.add(googleSearchTool); + System.out.println(" ✓ Google Search enabled"); + + // 4. UrlContextTool + System.out.println("Configuring UrlContextTool..."); + UrlContextTool urlContextTool = UrlContextTool.builder().build(); + tools.add(urlContextTool); + System.out.println(" ✓ URL Context enabled"); + + // 5. CodeExecutionTool + System.out.println("Configuring CodeExecutionTool..."); + CodeExecutionTool codeExecutionTool = CodeExecutionTool.builder().build(); + tools.add(codeExecutionTool); + System.out.println(" ✓ Code Execution enabled"); + + System.out.println(); + System.out.println("Total tools configured: " + tools.size()); + System.out.println(); + + // Create the interaction config + String userInput = "What's the weather like in Tokyo? Also calculate 42 + 58."; + System.out.println("User Input: " + userInput); + System.out.println(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input(userInput) + .tools(tools) + // .background(true) // Disabled: gemini-2.5-flash doesn't support background mode + .build(); + + // Print request + printSubSection("CREATE REQUEST"); + System.out.println("Model: " + config.model().orElse("N/A")); + System.out.println("Input: " + config.input().getValue().toString()); + System.out.println("Background: " + config.background().orElse(false)); + System.out.println("Tools count: " + config.tools().map(List::size).orElse(0)); + System.out.println(); + + // Make the API call + System.out.println("Calling client.interactions.create()..."); + long startTime = System.currentTimeMillis(); + Interaction response = client.interactions.create(config); + long duration = System.currentTimeMillis() - startTime; + + // Print response + printSubSection("CREATE RESPONSE"); + printInteractionDetails(response, duration); + + // Save response + saveResponse("1_create_response.json", response.toJson()); + + System.out.println(); + return response; + } + + // ================================================================================== + // STEP 2: GET + // ================================================================================== + + private static Interaction stepGet(Client client, Interaction createResponse) { + printStepHeader("STEP 2: GET", "Retrieve interaction and validate"); + + String interactionId = createResponse.id(); + System.out.println("Interaction ID: " + interactionId); + System.out.println(); + + // Print request + printSubSection("GET REQUEST"); + System.out.println("Interaction ID: " + interactionId); + System.out.println(); + + // Make the API call + System.out.println("Calling client.interactions.get()..."); + long startTime = System.currentTimeMillis(); + GetInteractionConfig config = GetInteractionConfig.builder().build(); + Interaction response = client.interactions.get(interactionId, config); + long duration = System.currentTimeMillis() - startTime; + + // Print response + printSubSection("GET RESPONSE"); + printInteractionDetails(response, duration); + + // Save response + saveResponse("2_get_response.json", response.toJson()); + + // Validate GET matches CREATE + printSubSection("VALIDATION: GET vs CREATE"); + validateInteractions(createResponse, response, "CREATE", "GET"); + + System.out.println(); + return response; + } + + // ================================================================================== + // STEP 3: CANCEL + // ================================================================================== + + private static Interaction stepCancel(Client client, String interactionId) { + printStepHeader("STEP 3: CANCEL", "Cancel the interaction"); + + System.out.println("Interaction ID: " + interactionId); + System.out.println(); + + // Print request + printSubSection("CANCEL REQUEST"); + System.out.println("Interaction ID: " + interactionId); + System.out.println(); + + // Make the API call + System.out.println("Calling client.interactions.cancel()..."); + long startTime = System.currentTimeMillis(); + CancelInteractionConfig config = CancelInteractionConfig.builder().build(); + Interaction response = client.interactions.cancel(interactionId, config); + long duration = System.currentTimeMillis() - startTime; + + // Print response + printSubSection("CANCEL RESPONSE"); + printInteractionDetails(response, duration); + + // Save response + saveResponse("3_cancel_response.json", response.toJson()); + + System.out.println(); + return response; + } + + // ================================================================================== + // STEP 4: GET after CANCEL + // ================================================================================== + + private static Interaction stepGetAfterCancel(Client client, String interactionId) { + printStepHeader("STEP 4: GET after CANCEL", "Verify status changed to CANCELLED"); + + System.out.println("Interaction ID: " + interactionId); + System.out.println(); + + // Print request + printSubSection("GET (after CANCEL) REQUEST"); + System.out.println("Interaction ID: " + interactionId); + System.out.println(); + + // Make the API call + System.out.println("Calling client.interactions.get()..."); + long startTime = System.currentTimeMillis(); + GetInteractionConfig config = GetInteractionConfig.builder().build(); + Interaction response = client.interactions.get(interactionId, config); + long duration = System.currentTimeMillis() - startTime; + + // Print response + printSubSection("GET (after CANCEL) RESPONSE"); + printInteractionDetails(response, duration); + + // Save response + saveResponse("4_get_after_cancel_response.json", response.toJson()); + + // Validate status is CANCELLED + printSubSection("VALIDATION: Status should be CANCELLED"); + String status = response.status().toString(); + if ("CANCELLED".equals(status)) { + System.out.println("✅ PASS: Status is CANCELLED"); + } else { + System.out.println("❌ FAIL: Expected status CANCELLED, got: " + status); + } + + System.out.println(); + return response; + } + + // ================================================================================== + // STEP 5: DELETE + // ================================================================================== + + private static DeleteInteractionResponse stepDelete(Client client, String interactionId) { + printStepHeader("STEP 5: DELETE", "Delete the interaction"); + + System.out.println("Interaction ID: " + interactionId); + System.out.println(); + + // Print request + printSubSection("DELETE REQUEST"); + System.out.println("Interaction ID: " + interactionId); + System.out.println(); + + // Make the API call + System.out.println("Calling client.interactions.delete()..."); + long startTime = System.currentTimeMillis(); + DeleteInteractionConfig config = DeleteInteractionConfig.builder().build(); + DeleteInteractionResponse response = client.interactions.delete(interactionId, config); + long duration = System.currentTimeMillis() - startTime; + + // Print response + printSubSection("DELETE RESPONSE"); + System.out.println("Duration: " + duration + "ms"); + System.out.println("HTTP Response: " + response.sdkHttpResponse().orElse(null)); + System.out.println(); + + // Save response + saveResponse("5_delete_response.json", response.toJson()); + + System.out.println(); + return response; + } + + // ================================================================================== + // STEP 6: GET after DELETE + // ================================================================================== + + private static void stepGetAfterDelete(Client client, String interactionId) { + printStepHeader("STEP 6: GET after DELETE", "Verify interaction is deleted (should fail)"); + + System.out.println("Interaction ID: " + interactionId); + System.out.println(); + + // Print request + printSubSection("GET (after DELETE) REQUEST"); + System.out.println("Interaction ID: " + interactionId); + System.out.println(); + + // Make the API call - expect this to fail + System.out.println("Calling client.interactions.get() (expecting error)..."); + try { + long startTime = System.currentTimeMillis(); + GetInteractionConfig config = GetInteractionConfig.builder().build(); + Interaction response = client.interactions.get(interactionId, config); + long duration = System.currentTimeMillis() - startTime; + + // If we get here, the interaction was NOT deleted (unexpected) + printSubSection("GET (after DELETE) RESPONSE - UNEXPECTED SUCCESS"); + System.out.println("❌ UNEXPECTED: GET succeeded after DELETE"); + printInteractionDetails(response, duration); + saveResponse("6_get_after_delete_response_unexpected.json", response.toJson()); + + } catch (Exception e) { + // Expected: GET should fail because interaction was deleted + printSubSection("GET (after DELETE) RESPONSE - EXPECTED ERROR"); + System.out.println("✅ PASS: GET failed as expected after DELETE"); + System.out.println("Error type: " + e.getClass().getSimpleName()); + System.out.println("Error message: " + e.getMessage()); + + // Save error + saveResponse( + "6_get_after_delete_error.txt", + "Error Type: " + e.getClass().getName() + "\nMessage: " + e.getMessage()); + } + + System.out.println(); + } + + // ================================================================================== + // HELPER METHODS + // ================================================================================== + + /** + * AFC-enabled function: Get weather for a city. + * + *

This method is public and static so it can be invoked via reflection for AFC. + */ + public static Map getWeather(String location) { + System.out.println(" [AFC] Executing getWeather(\"" + location + "\")"); + return ImmutableMap.of( + "location", location, + "temperature", "18", + "unit", "celsius", + "condition", "partly cloudy", + "humidity", "65%"); + } + + private static void printStepHeader(String stepTitle, String description) { + System.out.println(SEPARATOR); + System.out.println(stepTitle + ": " + description); + System.out.println(SEPARATOR); + System.out.println(); + } + + private static void printSubSection(String title) { + System.out.println(SUB_SEPARATOR); + System.out.println(title); + System.out.println(SUB_SEPARATOR); + } + + private static void printInteractionDetails(Interaction interaction, long duration) { + System.out.println("Duration: " + duration + "ms"); + System.out.println("ID: " + interaction.id()); + System.out.println("Status: " + interaction.status()); + System.out.println("Model: " + interaction.model().orElse("N/A")); + System.out.println("Created: " + interaction.created().map(Object::toString).orElse("N/A")); + System.out.println("Updated: " + interaction.updated().map(Object::toString).orElse("N/A")); + + // Print outputs + if (interaction.outputs().isPresent() && !interaction.outputs().get().isEmpty()) { + System.out.println(); + System.out.println("Outputs (" + interaction.outputs().get().size() + "):"); + int index = 1; + for (Content output : interaction.outputs().get()) { + System.out.println(" [" + index + "] " + getContentTypeDescription(output)); + if (output instanceof TextContent) { + TextContent textContent = (TextContent) output; + String text = textContent.text().orElse("(empty)"); + // Truncate long text + if (text.length() > 100) { + System.out.println(" " + text.substring(0, 100) + "..."); + } else { + System.out.println(" " + text); + } + } else if (output instanceof FunctionCallContent) { + FunctionCallContent fc = (FunctionCallContent) output; + System.out.println(" Function: " + fc.name()); + System.out.println(" Arguments: " + fc.arguments()); + } + index++; + } + } else { + System.out.println("Outputs: (none)"); + } + + // Print AFC history if present + if (interaction.automaticFunctionCallingHistory().isPresent()) { + List history = interaction.automaticFunctionCallingHistory().get(); + System.out.println(); + System.out.println("AFC History (" + history.size() + " interactions):"); + for (int i = 0; i < history.size(); i++) { + System.out.println(" [" + (i + 1) + "] " + history.get(i).id()); + } + } + + System.out.println(); + } + + private static String getContentTypeDescription(Content content) { + if (content instanceof TextContent) { + return "TextContent"; + } else if (content instanceof FunctionCallContent) { + return "FunctionCallContent"; + } else { + return content.getClass().getSimpleName(); + } + } + + private static void validateInteractions( + Interaction expected, Interaction actual, String expectedLabel, String actualLabel) { + boolean allPassed = true; + + // Validate ID + if (expected.id().equals(actual.id())) { + System.out.println("✅ ID matches: " + expected.id()); + } else { + System.out.println( + "❌ ID mismatch: " + expectedLabel + "=" + expected.id() + ", " + actualLabel + "=" + + actual.id()); + allPassed = false; + } + + // Validate Status + String expectedStatus = expected.status().toString(); + String actualStatus = actual.status().toString(); + if (expectedStatus.equals(actualStatus)) { + System.out.println("✅ Status matches: " + expectedStatus); + } else { + System.out.println( + "❌ Status mismatch: " + expectedLabel + "=" + expectedStatus + ", " + actualLabel + "=" + + actualStatus); + allPassed = false; + } + + // Validate Model + String expectedModel = expected.model().orElse("N/A"); + String actualModel = actual.model().orElse("N/A"); + if (expectedModel.equals(actualModel)) { + System.out.println("✅ Model matches: " + expectedModel); + } else { + System.out.println( + "❌ Model mismatch: " + expectedLabel + "=" + expectedModel + ", " + actualLabel + "=" + + actualModel); + allPassed = false; + } + + // Summary + System.out.println(); + if (allPassed) { + System.out.println("✅ ALL VALIDATIONS PASSED"); + } else { + System.out.println("❌ SOME VALIDATIONS FAILED"); + } + } + + private static void saveResponse(String filename, String content) { + String timestamp = LocalDateTime.now().format(TIMESTAMP_FORMAT); + System.out.println("📝 Response saved to: interaction_lifecycle_logs/" + filename); + System.out.println(" Timestamp: " + timestamp); + // In a real implementation, you would write to file here + // For now, just log that we would save it + } + + private InteractionsMultipleToolsLifecycle() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsPreviousInteractionId.java b/examples/src/main/java/com/google/genai/examples/InteractionsPreviousInteractionId.java new file mode 100644 index 00000000000..984c7ccb237 --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsPreviousInteractionId.java @@ -0,0 +1,177 @@ +/* + * 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. + */ + +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.Usage; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; + +/** + * Example: Conversation Continuity with previousInteractionId + * + *

Demonstrates how to link interactions using previousInteractionId to maintain conversation + * context across separate API calls. This example verifies context is preserved by asking for + * 10 random numbers, then asking the model to perform various math operations on them: + * addition, multiplication, and calculating mean/median/mode. + * + *

To run this example: + * + *

    + *
  1. Set the GOOGLE_API_KEY environment variable: {@code export GOOGLE_API_KEY=YOUR_API_KEY} + *
  2. Compile the examples: {@code mvn clean compile} + *
  3. Run: {@code mvn exec:java + * -Dexec.mainClass="com.google.genai.examples.InteractionsPreviousInteractionId"} + *
+ * + *

Note: The Interactions API is currently in beta. + */ +public final class InteractionsPreviousInteractionId { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== Interactions API: Previous Interaction ID Example ===\n"); + + try { + // ===== First Interaction ===== + System.out.println("--- First Interaction ---\n"); + + CreateInteractionConfig config1 = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("Give me 10 random numbers less than 50.") + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config1.toJson()); + System.out.println(); + + Interaction response1 = client.interactions.create(config1); + + System.out.println("=== RESPONSE ==="); + System.out.println(response1.toJson()); + System.out.println(); + + printResults(response1); + + // ===== Second Interaction (linked to first) ===== + System.out.println("\n--- Second Interaction (linked) ---\n"); + + CreateInteractionConfig config2 = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("Add all the numbers you just gave me.") + .previousInteractionId(response1.id()) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config2.toJson()); + System.out.println(); + + Interaction response2 = client.interactions.create(config2); + + System.out.println("=== RESPONSE ==="); + System.out.println(response2.toJson()); + System.out.println(); + + printResults(response2); + + // ===== Third Interaction (linked to second) ===== + System.out.println("\n--- Third Interaction (linked) ---\n"); + + CreateInteractionConfig config3 = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("Now multiply all those original numbers together.") + .previousInteractionId(response2.id()) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config3.toJson()); + System.out.println(); + + Interaction response3 = client.interactions.create(config3); + + System.out.println("=== RESPONSE ==="); + System.out.println(response3.toJson()); + System.out.println(); + + printResults(response3); + + // ===== Fourth Interaction (linked to third) ===== + System.out.println("\n--- Fourth Interaction (linked) ---\n"); + + CreateInteractionConfig config4 = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("which number is the greatest ? .") + .previousInteractionId(response3.id()) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config4.toJson()); + System.out.println(); + + Interaction response4 = client.interactions.create(config4); + + System.out.println("=== RESPONSE ==="); + System.out.println(response4.toJson()); + System.out.println(); + + printResults(response4); + + System.out.println("\n=== Example completed ==="); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + if (interaction.previousInteractionId().isPresent()) { + System.out.println( + " Previous Interaction ID: " + interaction.previousInteractionId().get()); + } + + if (interaction.outputs().isPresent() && !interaction.outputs().get().isEmpty()) { + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + System.out.println(" Text: " + ((TextContent) output).text().orElse("(empty)")); + } else { + System.out.println(" " + output.getClass().getSimpleName()); + } + } + } + + if (interaction.usage().isPresent()) { + Usage usage = interaction.usage().get(); + System.out.println(" Usage:"); + System.out.println(" Input tokens: " + usage.totalInputTokens().orElse(0)); + System.out.println(" Output tokens: " + usage.totalOutputTokens().orElse(0)); + System.out.println(" Total tokens: " + usage.totalTokens().orElse(0)); + } + } + + private InteractionsPreviousInteractionId() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsTextAnnotations.java b/examples/src/main/java/com/google/genai/examples/InteractionsTextAnnotations.java new file mode 100644 index 00000000000..cd9f0bf743b --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsTextAnnotations.java @@ -0,0 +1,204 @@ +/* + * 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. + */ + +/** + * Usage: + * + *

Note: The Interactions API is currently only available via the Gemini Developer API (not + * Vertex AI). + * + *

1. Set an API key environment variable: + * + *

export GOOGLE_API_KEY=YOUR_API_KEY + * + *

2. Compile and run: + * + *

mvn clean compile + * + *

mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsTextAnnotations" + */ +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.Annotation; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; +import java.util.List; + +/** + * TextContent Annotations Testing Example + * + *

This example demonstrates testing for the annotations field in TextContent responses + * from the Interactions API. The annotations field provides citation information for + * model-generated content. + * + *

Structure: TextContent contains: + * - type: "text" + * - text: The actual text content + * - annotations: Optional array of Annotation objects for citations + * + *

Each Annotation contains: + * - start_index: Start byte position of cited segment + * - end_index: End byte position of cited segment (exclusive) + * - source: Source reference (URL, title, etc.) + * + *

This example tests various prompts to see if the API returns annotations. + * + *

Note: The Interactions API is in beta and subject to change. + */ +public final class InteractionsTextAnnotations { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== TextContent Annotations Testing Example ===\n"); + System.out.println("Testing if the Interactions API returns annotations in TextContent\n"); + + // Test Case 1: Simple factual question + testAnnotations( + client, + "Test Case 1: Simple Factual Question", + "What is the capital of France?"); + + // Test Case 2: Question that might trigger citations + testAnnotations( + client, + "Test Case 2: Request with Explicit Citation Request", + "Tell me about climate change and cite your sources."); + + // Test Case 3: Research-oriented question + testAnnotations( + client, + "Test Case 3: Research Question", + "What are the latest advancements in quantum computing? Please provide citations."); + + // Test Case 4: Grounding/search request + testAnnotations( + client, + "Test Case 4: Grounding Request", + "Search for information about the Paris Agreement and summarize it with citations."); + + System.out.println("\n=== All test cases completed ==="); + } + + /** + * Test helper that makes an API call and checks for annotations in the response. + */ + private static void testAnnotations(Client client, String testName, String prompt) { + System.out.println("\n--- " + testName + " ---\n"); + + try { + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input(prompt) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + Interaction response = client.interactions.create(config); + + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + analyzeAnnotations(response); + + } catch (Exception e) { + System.err.println("ERROR in " + testName + ":"); + System.err.println(" Exception: " + e.getClass().getName()); + System.err.println(" Message: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Analyzes interaction outputs to find and display annotations. + */ + private static void analyzeAnnotations(Interaction interaction) { + System.out.println("Results:"); + + if (!interaction.outputs().isPresent() || interaction.outputs().get().isEmpty()) { + System.out.println(" ❌ No outputs found in response"); + return; + } + + boolean foundAnnotations = false; + int textContentCount = 0; + int totalAnnotations = 0; + + for (Content content : interaction.outputs().get()) { + if (content instanceof TextContent) { + textContentCount++; + TextContent textContent = (TextContent) content; + + System.out.println("\n TextContent #" + textContentCount + ":"); + System.out.println(" Text: " + textContent.text().orElse("(empty)")); + + if (textContent.annotations().isPresent() && !textContent.annotations().get().isEmpty()) { + foundAnnotations = true; + List annotations = textContent.annotations().get(); + totalAnnotations += annotations.size(); + + System.out.println(" ✅ ANNOTATIONS FOUND: " + annotations.size() + " annotation(s)"); + + for (int i = 0; i < annotations.size(); i++) { + Annotation ann = annotations.get(i); + System.out.println("\n Annotation " + (i + 1) + ":"); + System.out.println(" Start Index: " + ann.startIndex().orElse(null)); + System.out.println(" End Index: " + ann.endIndex().orElse(null)); + System.out.println(" Source: " + ann.source().orElse("(none)")); + + // Show the cited text segment if indices are present + if (ann.startIndex().isPresent() + && ann.endIndex().isPresent() + && textContent.text().isPresent()) { + String text = textContent.text().get(); + int start = ann.startIndex().get(); + int end = ann.endIndex().get(); + if (start >= 0 && end <= text.length() && start < end) { + String citedText = text.substring(start, end); + System.out.println(" Cited Text: \"" + citedText + "\""); + } + } + } + } else { + System.out.println(" ❌ No annotations found in this TextContent"); + } + } + } + + System.out.println("\n SUMMARY:"); + System.out.println(" Total TextContent blocks: " + textContentCount); + System.out.println(" Total annotations found: " + totalAnnotations); + + if (foundAnnotations) { + System.out.println(" ✅ SUCCESS: The API returned annotations!"); + } else { + System.out.println(" ⚠️ The API did not return annotations for this request."); + System.out.println(" This could mean:"); + System.out.println(" - The model didn't use external sources"); + System.out.println(" - The annotations feature may not be enabled yet"); + System.out.println(" - This specific prompt didn't trigger citations"); + } + } + + private InteractionsTextAnnotations() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsThoughtContent.java b/examples/src/main/java/com/google/genai/examples/InteractionsThoughtContent.java new file mode 100644 index 00000000000..3e25df03dff --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsThoughtContent.java @@ -0,0 +1,295 @@ +/* + * 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. + */ + +/** + * Usage: + * + *

Note: The Interactions API is currently only available via the Gemini Developer API (not + * Vertex AI). + * + *

1. Set an API key environment variable. You can find a list of available API keys here: + * https://aistudio.google.com/app/apikey + * + *

export GOOGLE_API_KEY=YOUR_API_KEY + * + *

2. Compile the java package and run the sample code. + * + *

mvn clean compile + * + *

mvn exec:java + * -Dexec.mainClass="com.google.genai.examples.InteractionsThoughtContent" + */ +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.ImageContent; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; +import com.google.genai.types.interactions.content.ThoughtContent; +import com.google.genai.types.interactions.content.ThoughtSummaryContent; +import java.util.List; + +/** + * ThoughtContent Testing Example + * + *

This example tests the ThoughtContent functionality to understand how the model's internal + * reasoning is exposed through the Interactions API. + * + *

Structure: ThoughtContent contains: + * - signature: A cryptographic signature for the thought + * - summary: Optional list of Content (TextContent, ImageContent, etc.) + * + *

The summary field matches Python's structure: Optional[List[Union[TextContent, ImageContent]]] + * allowing the model to provide reasoning summaries with both text and visual elements. + * + *

Test Cases: 1. Simple Math Reasoning - Basic arithmetic with step-by-step thinking 2. Complex + * Logic Puzzle - Multi-step reasoning task 3. Reasoning with extended thinking mode + * + *

Note: The Interactions API is in beta and subject to change. + */ +public final class InteractionsThoughtContent { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== ThoughtContent Testing Example ===\n"); + System.out.println("Testing model's internal reasoning (ThoughtContent)\n"); + + // Test Case 1: Simple Math Reasoning + testSimpleMathReasoning(client); + + // Test Case 2: Complex Logic Puzzle + testComplexLogicPuzzle(client); + + // Test Case 3: Extended Thinking Mode + testExtendedThinking(client); + + System.out.println("\n=== All tests completed ==="); + } + + /** + * Test Case 1: Simple Math Reasoning + * + *

Tests if ThoughtContent appears for basic arithmetic reasoning. + */ + private static void testSimpleMathReasoning(Client client) { + System.out.println("\n--- Test Case 1: Simple Math Reasoning ---\n"); + + String prompt = "Think step by step: What is 15 * 24?"; + CreateInteractionConfig config = + CreateInteractionConfig.builder().model("gemini-2.5-flash").input(prompt).build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + try { + Interaction response = client.interactions.create(config); + + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + printResults(response); + analyzeContent(response); + + } catch (Exception e) { + System.err.println("ERROR in Test Case 1:"); + System.err.println(" Exception: " + e.getClass().getName()); + System.err.println(" Message: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Test Case 2: Complex Logic Puzzle + * + *

Tests ThoughtContent with a more complex reasoning task that requires multiple steps. + */ + private static void testComplexLogicPuzzle(Client client) { + System.out.println("\n--- Test Case 2: Complex Logic Puzzle ---\n"); + + String prompt = + "Solve this logic puzzle step by step:\n" + + "Three friends - Alice, Bob, and Carol - each have a different favorite color (red," + + " blue, or green).\n" + + "- Alice doesn't like blue\n" + + "- Bob's favorite color is not red\n" + + "- Carol likes green\n" + + "What is each person's favorite color? Show your reasoning."; + + CreateInteractionConfig config = + CreateInteractionConfig.builder().model("gemini-2.5-flash").input(prompt).build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + try { + Interaction response = client.interactions.create(config); + + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + printResults(response); + analyzeContent(response); + + } catch (Exception e) { + System.err.println("ERROR in Test Case 2:"); + System.err.println(" Exception: " + e.getClass().getName()); + System.err.println(" Message: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Test Case 3: Extended Thinking Mode + * + *

Tests ThoughtContent with a reasoning-heavy task. + */ + private static void testExtendedThinking(Client client) { + System.out.println("\n--- Test Case 3: Extended Thinking Test ---\n"); + + String prompt = + "Calculate the following and explain your reasoning:\n" + + "If a train travels at 60 mph for 2.5 hours, then speeds up to 80 mph for another 1.5" + + " hours, how far does it travel in total?"; + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input(prompt) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + try { + Interaction response = client.interactions.create(config); + + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + printResults(response); + analyzeContent(response); + + } catch (Exception e) { + System.err.println("ERROR in Test Case 3:"); + System.err.println(" Exception: " + e.getClass().getName()); + System.err.println(" Message: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + } + + /** + * Analyzes interaction content to identify and display ThoughtContent. + * + *

This method: - Iterates through all outputs in the interaction - Identifies ThoughtContent + * instances - Displays thought signatures and summaries - Compares actual structure with expected + * structure + */ + private static void analyzeContent(Interaction interaction) { + System.out.println("CONTENT ANALYSIS:"); + + if (!interaction.outputs().isPresent() || interaction.outputs().get().isEmpty()) { + System.out.println(" No outputs found in response"); + return; + } + + int thoughtCount = 0; + int textCount = 0; + int otherCount = 0; + + System.out.println("\n Analyzing " + interaction.outputs().get().size() + " output(s):"); + + for (Content content : interaction.outputs().get()) { + System.out.println("\n Content Type: " + content.getClass().getSimpleName()); + + if (content instanceof ThoughtContent) { + thoughtCount++; + ThoughtContent thought = (ThoughtContent) content; + + System.out.println(" >>> THOUGHT CONTENT FOUND <<<"); + System.out.println(" Signature: " + thought.signature().orElse("(none)")); + + if (thought.summary().isPresent()) { + List summaryContents = thought.summary().get(); + System.out.println(" Summary: " + summaryContents.size() + " item(s)"); + + for (int i = 0; i < summaryContents.size(); i++) { + ThoughtSummaryContent summaryItem = summaryContents.get(i); + System.out.println(" Item " + (i + 1) + ": " + summaryItem.getClass().getSimpleName()); + + if (summaryItem instanceof TextContent) { + String text = ((TextContent) summaryItem).text().orElse("(empty)"); + String preview = text.length() > 100 ? text.substring(0, 100) + "..." : text; + System.out.println(" Text: " + preview); + } else if (summaryItem instanceof ImageContent) { + System.out.println(" Image: " + ((ImageContent) summaryItem).uri().orElse("(no uri)")); + } + } + } else { + System.out.println(" Summary: (none)"); + } + + System.out.println(" Full ThoughtContent JSON:"); + System.out.println(" " + thought.toJson()); + + } else if (content instanceof TextContent) { + textCount++; + TextContent text = (TextContent) content; + String textValue = text.text().orElse("(empty)"); + String preview = textValue.length() > 100 ? textValue.substring(0, 100) + "..." : textValue; + System.out.println(" Text Preview: " + preview); + } else { + otherCount++; + System.out.println(" Content Class: " + content.getClass().getName()); + } + } + + System.out.println("\n SUMMARY:"); + System.out.println(" ThoughtContent instances: " + thoughtCount); + System.out.println(" TextContent instances: " + textCount); + System.out.println(" Other content instances: " + otherCount); + + if (thoughtCount == 0) { + System.out.println( + "\n NOTE: No ThoughtContent found. This may be expected if the model didn't use" + + " internal reasoning for this query."); + } + + // Display final text output + if (interaction.outputs().isPresent() && !interaction.outputs().get().isEmpty()) { + System.out.println("\n FINAL TEXT OUTPUT:"); + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + System.out.println(" " + ((TextContent) output).text().orElse("(empty)")); + } + } + } + } +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsUrlContext.java b/examples/src/main/java/com/google/genai/examples/InteractionsUrlContext.java new file mode 100644 index 00000000000..a9f397d5361 --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsUrlContext.java @@ -0,0 +1,143 @@ +/* + * 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. + */ + +/** + * Usage: + * + *

Note: The Interactions API is currently only available via the Gemini Developer API (not + * Vertex AI). + * + *

1. Set an API key environment variable. You can find a list of available API keys here: + * https://aistudio.google.com/app/apikey + * + *

export GOOGLE_API_KEY=YOUR_API_KEY + * + *

2. Compile the java package and run the sample code. + * + *

mvn clean compile + * + *

mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsUrlContext" + */ +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.UrlContextResult; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; +import com.google.genai.types.interactions.content.UrlContextCallContent; +import com.google.genai.types.interactions.content.UrlContextResultContent; +import com.google.genai.types.interactions.tools.UrlContext; +import java.util.List; + +/** + * Example: URL Context Tool with the Interactions API + * + *

Demonstrates how to use the UrlContext to enable the model to retrieve and use context + * from URLs. The model can fetch web pages and use their content to answer questions. + * + *

Note: The Interactions API is in beta and subject to change. + */ +public final class InteractionsUrlContext { + + public static void main(String[] args) { + // Instantiate the client. The client gets the API key from the environment variable + // `GOOGLE_API_KEY`. + // + Client client = new Client(); + + System.out.println("=== Interactions API: URL Context Tool Example ===\n"); + + // ===== STEP 1: Create UrlContext ===== + System.out.println("STEP 1: Create UrlContext\n"); + + UrlContext urlTool = UrlContext.builder().build(); + + System.out.println("UrlContext created successfully\n"); + + // ===== STEP 2: Create interaction with URL Context enabled ===== + System.out.println("---\n"); + System.out.println("STEP 2: Create interaction with URL Context enabled\n"); + + String userQuestion = + "Please fetch and summarize the content from https://www.wikipedia.org/wiki/Artificial_intelligence"; + System.out.println("User: " + userQuestion + "\n"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input(userQuestion) + .tools(urlTool) + .build(); + + // Print the request JSON + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + Interaction response = client.interactions.create(config); + + // Print the response JSON + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + // ===== STEP 3: Extract and display the results ===== + System.out.println("---\n"); + System.out.println("STEP 3: Extract and display the results\n"); + + System.out.println("Response received. Interaction ID: " + response.id()); + System.out.println(); + + if (response.outputs().isPresent()) { + for (Content content : response.outputs().get()) { + if (content instanceof TextContent) { + System.out.println("Text: " + ((TextContent) content).text().orElse("(empty)")); + System.out.println(); + } else if (content instanceof UrlContextCallContent) { + UrlContextCallContent urlCall = (UrlContextCallContent) content; + System.out.println("URL Context Call:"); + System.out.println(" ID: " + urlCall.id()); + if (urlCall.arguments().isPresent()) { + System.out.println(" URLs: " + urlCall.arguments().get().urls().orElse(java.util.List.of())); + } + System.out.println(); + } else if (content instanceof UrlContextResultContent) { + UrlContextResultContent urlResult = (UrlContextResultContent) content; + System.out.println("URL Context Result:"); + System.out.println(" Call ID: " + urlResult.callId().orElse("N/A")); + System.out.println(" Is Error: " + urlResult.isError().orElse(false)); + + if (urlResult.result().isPresent()) { + List results = urlResult.result().get(); + System.out.println(" Results: " + results.size() + " found"); + for (int i = 0; i < results.size(); i++) { + UrlContextResult result = results.get(i); + System.out.println(" [" + (i + 1) + "] URL: " + result.url().orElse("N/A")); + System.out.println(" Status: " + result.status().map(Object::toString).orElse("N/A")); + } + } + System.out.println(); + } + } + } + + System.out.println("\n=== Example completed ==="); + } + + private InteractionsUrlContext() {} +} diff --git a/examples/src/main/java/com/google/genai/examples/InteractionsVideoContent.java b/examples/src/main/java/com/google/genai/examples/InteractionsVideoContent.java new file mode 100644 index 00000000000..e4a5239f353 --- /dev/null +++ b/examples/src/main/java/com/google/genai/examples/InteractionsVideoContent.java @@ -0,0 +1,106 @@ +/* + * 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. + */ + +package com.google.genai.examples; + +import com.google.genai.Client; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; +import com.google.genai.types.interactions.content.VideoContent; + +/** + * Example demonstrating video analysis using VideoContent with the Interactions API. + * + *

This example shows: + *

+ * + *

Note: Inline base64-encoded video is not shown because video files are typically + * large and not suitable for inline embedding. + * + *

To run this example: + *

    + *
  1. Set the GOOGLE_API_KEY environment variable: {@code export GOOGLE_API_KEY=YOUR_API_KEY} + *
  2. Compile the examples: {@code mvn clean compile} + *
  3. Run: {@code mvn exec:java -Dexec.mainClass="com.google.genai.examples.InteractionsVideoContent"} + *
+ * + *

Note: The Interactions API is currently in beta. + */ +public final class InteractionsVideoContent { + + public static void main(String[] args) { + Client client = new Client(); + + System.out.println("=== Interactions API: Video Content Example ===\n"); + + try { + // ===== Video from URI ===== + System.out.println("--- Video from URI ---\n"); + + String videoUri = "https://storage.googleapis.com/cloud-samples-data/generative-ai/video/pixel8.mp4"; + VideoContent videoContent = VideoContent.fromUri(videoUri, "video/mp4"); + TextContent textPrompt = TextContent.builder() + .text("Describe what happens in this video.") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromContents(textPrompt, videoContent) + .build(); + + System.out.println("=== REQUEST ==="); + System.out.println(config.toJson()); + System.out.println(); + + Interaction response = client.interactions.create(config); + + System.out.println("=== RESPONSE ==="); + System.out.println(response.toJson()); + System.out.println(); + + printResults(response); + + System.out.println("\n=== Example completed ==="); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static void printResults(Interaction interaction) { + System.out.println("Results:"); + System.out.println(" Interaction ID: " + interaction.id()); + System.out.println(" Status: " + interaction.status()); + + if (interaction.outputs().isPresent() && !interaction.outputs().get().isEmpty()) { + for (Content output : interaction.outputs().get()) { + if (output instanceof TextContent) { + System.out.println(" Text: " + ((TextContent) output).text().orElse("(empty)")); + } else { + System.out.println(" " + output.getClass().getSimpleName()); + } + } + } + } + + private InteractionsVideoContent() {} +} diff --git a/pom.xml b/pom.xml index 90a90201ab4..082489f5a2d 100644 --- a/pom.xml +++ b/pom.xml @@ -323,6 +323,7 @@ com/google/genai/types/AutoValue_*.class + com/google/genai/types/interactions/AutoValue_*.class diff --git a/src/main/java/com/google/genai/AsyncInteractions.java b/src/main/java/com/google/genai/AsyncInteractions.java new file mode 100644 index 00000000000..d8af33bb5ac --- /dev/null +++ b/src/main/java/com/google/genai/AsyncInteractions.java @@ -0,0 +1,191 @@ +/* + * 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; + +import com.google.genai.Common.BuiltRequest; +import com.google.genai.types.interactions.CancelInteractionConfig; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.DeleteInteractionConfig; +import com.google.genai.types.interactions.DeleteInteractionResponse; +import com.google.genai.types.interactions.GetInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import java.util.concurrent.CompletableFuture; + +/** + * Provides asynchronous methods for managing interactions. Instantiating this class is not + * required. After instantiating a {@link Client}, access methods through + * `client.async.interactions.methodName(...)` directly. + * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +public final class AsyncInteractions { + Interactions interactions; + ApiClient apiClient; + + public AsyncInteractions(ApiClient apiClient) { + this.apiClient = apiClient; + this.interactions = new Interactions(apiClient); + } + + /** + * Asynchronously creates a new interaction with the specified configuration. + * + *

Either {@code model} or {@code agent} must be specified in the config, but not both. + * + *

Example usage for model-based interaction: + * + *

{@code
+   * CreateInteractionConfig config = CreateInteractionConfig.builder()
+   *     .model("gemini-2.5-flash")
+   *     .input("What is the capital of France?")
+   *     .build();
+   * CompletableFuture future = client.async.interactions.create(config);
+   * future.thenAccept(interaction -> {
+   *     System.out.println("Response: " + interaction.outputs());
+   * });
+   * }
+ * + *

Example usage for agent-based interaction: + * + *

{@code
+   * CreateInteractionConfig config = CreateInteractionConfig.builder()
+   *     .agent("deep-research-pro-preview-12-2025")
+   *     .input("Research the history of quantum computing")
+   *     .build();
+   * CompletableFuture future = client.async.interactions.create(config);
+   * }
+ * + *

Note: The Interactions API is in beta and subject to change. + * + * @param config The configuration for creating the interaction + * @return A CompletableFuture containing the created Interaction with outputs + * @throws IllegalArgumentException if both model and agent are specified, or neither is specified + * @throws UnsupportedOperationException if using Vertex AI (not yet supported) + */ + public CompletableFuture create(CreateInteractionConfig config) { + BuiltRequest builtRequest = interactions.buildRequestForCreate(config); + + return this.apiClient + .asyncRequest("post", builtRequest.path(), builtRequest.body(), builtRequest.httpOptions()) + .thenApplyAsync( + response -> { + try (ApiResponse res = response) { + return interactions.processResponseForCreate(res, config); + } + }); + } + + /** + * Asynchronously retrieves an interaction by its ID. + * + *

Example usage: + * + *

{@code
+   * GetInteractionConfig config = GetInteractionConfig.builder().build();
+   * CompletableFuture future = client.async.interactions.get("interaction-id-123", config);
+   * future.thenAccept(interaction -> {
+   *     System.out.println("Status: " + interaction.status());
+   * });
+   * }
+ * + *

Note: The Interactions API is in beta and subject to change. + * + * @param id The ID of the interaction to retrieve + * @param config The configuration for the get request (can be null) + * @return A CompletableFuture containing the retrieved Interaction + */ + public CompletableFuture get(String id, GetInteractionConfig config) { + BuiltRequest builtRequest = interactions.buildRequestForGet(id, config); + + return this.apiClient + .asyncRequest("get", builtRequest.path(), builtRequest.body(), builtRequest.httpOptions()) + .thenApplyAsync( + response -> { + try (ApiResponse res = response) { + return interactions.processResponseForGet(res, config); + } + }); + } + + /** + * Asynchronously cancels a background interaction that is still in progress. + * + *

Example usage: + * + *

{@code
+   * CancelInteractionConfig config = CancelInteractionConfig.builder().build();
+   * CompletableFuture future = client.async.interactions.cancel("interaction-id-123", config);
+   * future.thenAccept(cancelled -> {
+   *     System.out.println("New status: " + cancelled.status());
+   * });
+   * }
+ * + *

Note: The Interactions API is in beta and subject to change. + * + * @param id The ID of the interaction to cancel + * @param config The configuration for the cancel request (can be null) + * @return A CompletableFuture containing the updated Interaction + */ + public CompletableFuture cancel(String id, CancelInteractionConfig config) { + BuiltRequest builtRequest = interactions.buildRequestForCancel(id, config); + + return this.apiClient + .asyncRequest("post", builtRequest.path(), builtRequest.body(), builtRequest.httpOptions()) + .thenApplyAsync( + response -> { + try (ApiResponse res = response) { + return interactions.processResponseForCancel(res, config); + } + }); + } + + /** + * Asynchronously deletes an interaction by its ID. + * + *

Example usage: + * + *

{@code
+   * DeleteInteractionConfig config = DeleteInteractionConfig.builder().build();
+   * CompletableFuture future =
+   *     client.async.interactions.delete("interaction-id-123", config);
+   * }
+ * + *

Note: The Interactions API is in beta and subject to change. + * + * @param id The ID of the interaction to delete + * @param config The configuration for the delete request (can be null) + * @return A CompletableFuture containing the delete response + */ + public CompletableFuture delete( + String id, DeleteInteractionConfig config) { + BuiltRequest builtRequest = interactions.buildRequestForDelete(id, config); + + return this.apiClient + .asyncRequest( + "delete", builtRequest.path(), builtRequest.body(), builtRequest.httpOptions()) + .thenApplyAsync( + response -> { + try (ApiResponse res = response) { + return interactions.processResponseForDelete(res, config); + } + }); + } +} diff --git a/src/main/java/com/google/genai/Client.java b/src/main/java/com/google/genai/Client.java index 0bc250b550b..22772ec2087 100644 --- a/src/main/java/com/google/genai/Client.java +++ b/src/main/java/com/google/genai/Client.java @@ -41,6 +41,7 @@ public final class Async { public final AsyncTokens authTokens; public final AsyncTunings tunings; public final AsyncFileSearchStores fileSearchStores; + public final AsyncInteractions interactions; public Async(ApiClient apiClient) { this.models = new AsyncModels(apiClient); @@ -53,6 +54,7 @@ public Async(ApiClient apiClient) { this.authTokens = new AsyncTokens(apiClient); this.tunings = new AsyncTunings(apiClient); this.fileSearchStores = new AsyncFileSearchStores(apiClient); + this.interactions = new AsyncInteractions(apiClient); } } @@ -68,6 +70,7 @@ public Async(ApiClient apiClient) { public final Tokens authTokens; public final Tunings tunings; public final FileSearchStores fileSearchStores; + public final Interactions interactions; /** Builder for {@link Client}. */ public static class Builder { @@ -280,6 +283,7 @@ private Client( authTokens = new Tokens(this.apiClient); tunings = new Tunings(this.apiClient); fileSearchStores = new FileSearchStores(this.apiClient); + interactions = new Interactions(this.apiClient); } /** Returns whether the client is using Vertex AI APIs. */ diff --git a/src/main/java/com/google/genai/DebugConfig.java b/src/main/java/com/google/genai/DebugConfig.java index fcefa933332..292c2b839dc 100644 --- a/src/main/java/com/google/genai/DebugConfig.java +++ b/src/main/java/com/google/genai/DebugConfig.java @@ -16,6 +16,7 @@ package com.google.genai; + /** Data class configuration for debugging or testing the Client. */ @ExcludeFromGeneratedCoverageReport final class DebugConfig { diff --git a/src/main/java/com/google/genai/Interactions.java b/src/main/java/com/google/genai/Interactions.java new file mode 100644 index 00000000000..0a1145dd419 --- /dev/null +++ b/src/main/java/com/google/genai/Interactions.java @@ -0,0 +1,632 @@ +/* + * 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. + */ + +package com.google.genai; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.genai.Common.BuiltRequest; +import com.google.genai.errors.GenAiIOException; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.HttpResponse; +import com.google.genai.types.interactions.CancelInteractionConfig; +import com.google.genai.types.interactions.CancelInteractionParameters; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.CreateInteractionParameters; +import com.google.genai.types.interactions.DeleteInteractionConfig; +import com.google.genai.types.interactions.DeleteInteractionParameters; +import com.google.genai.types.interactions.DeleteInteractionResponse; +import com.google.genai.types.interactions.GetInteractionConfig; +import com.google.genai.types.interactions.GetInteractionParameters; +import com.google.genai.types.interactions.Interaction; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import okhttp3.Headers; +import okhttp3.ResponseBody; + +/** + * Provides methods for managing interactions. Instantiating this class is not required. After + * instantiating a {@link Client}, access methods through `client.interactions.methodName(...)` + * directly. + * + *

Note: The Interactions API is in beta and subject to change. + */ +public final class Interactions { + + final ApiClient apiClient; + + public Interactions(ApiClient apiClient) { + this.apiClient = apiClient; + } + + /** + * Copies a field from source to target if it exists, without transformation. + * + * @param fromObject Source JSON node + * @param parentObject Target ObjectNode to populate + * @param fieldName Name of the field to copy + */ + private void copyFieldIfPresent(JsonNode fromObject, ObjectNode parentObject, String fieldName) { + Object value = Common.getValueByPath(fromObject, new String[] {fieldName}); + if (value != null) { + Common.setValueByPath(parentObject, new String[] {fieldName}, value); + } + } + + /** + * Copies a field from source to target with a transformation function. + * + * @param fromObject Source JSON node + * @param parentObject Target ObjectNode to populate + * @param fieldName Name of the field to copy + * @param transformer Function to transform the value + */ + private void copyFieldWithTransform( + JsonNode fromObject, + ObjectNode parentObject, + String fieldName, + java.util.function.Function transformer) { + Object value = Common.getValueByPath(fromObject, new String[] {fieldName}); + if (value != null) { + Common.setValueByPath(parentObject, new String[] {fieldName}, transformer.apply(value)); + } + } + + /** + * Transforms CreateInteractionConfig to the request body format. This transformation is identical + * for both Vertex AI and MLDev platforms. Only the endpoint path differs (handled by + * ApiClient.buildMaybeVertexPath). + * + * @param fromObject Source JSON node containing the config + * @param parentObject Target ObjectNode to populate (modified in place) + */ + @ExcludeFromGeneratedCoverageReport + void createInteractionConfig(JsonNode fromObject, ObjectNode parentObject) { + // Simple fields - no transformation + // Note: Field names must match @JsonProperty annotations (snake_case) + copyFieldIfPresent(fromObject, parentObject, "input"); + copyFieldIfPresent(fromObject, parentObject, "agent"); + copyFieldIfPresent(fromObject, parentObject, "background"); + copyFieldIfPresent(fromObject, parentObject, "generation_config"); + copyFieldIfPresent(fromObject, parentObject, "agent_config"); + copyFieldIfPresent(fromObject, parentObject, "previous_interaction_id"); + copyFieldIfPresent(fromObject, parentObject, "response_format"); + copyFieldIfPresent(fromObject, parentObject, "response_mime_type"); + copyFieldIfPresent(fromObject, parentObject, "response_modalities"); + copyFieldIfPresent(fromObject, parentObject, "store"); + // Tool types serialize directly with their type discriminator + copyFieldIfPresent(fromObject, parentObject, "tools"); + + // Fields with transformation + // Note: Interactions API expects raw model string, not transformed + copyFieldIfPresent(fromObject, parentObject, "model"); + copyFieldWithTransform( + fromObject, + parentObject, + "system_instruction", + value -> JsonSerializable.toJsonNode(Transformers.tContent(value))); + } + + /** + * Transforms CreateInteractionParameters to the request body. Platform-agnostic - works for both + * Vertex AI and MLDev. + * + * @param fromObject Source JSON node containing the parameters + * @return Transformed ObjectNode ready for the request body + */ + @ExcludeFromGeneratedCoverageReport + ObjectNode createInteractionParameters(JsonNode fromObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + + if (Common.getValueByPath(fromObject, new String[] {"config"}) != null) { + createInteractionConfig( + JsonSerializable.toJsonNode(Common.getValueByPath(fromObject, new String[] {"config"})), + toObject); + } + + return toObject; + } + + /** A shared buildRequest method for both sync and async methods. */ + BuiltRequest buildRequestForCreate(CreateInteractionConfig config) { + // Validation: exactly one of model or agent must be specified + boolean hasModel = config.model().isPresent() && !config.model().get().isEmpty(); + boolean hasAgent = config.agent().isPresent() && !config.agent().get().isEmpty(); + + if (hasModel && hasAgent) { + throw new IllegalArgumentException( + "Cannot specify both 'model' and 'agent'. Please provide only one."); + } + + if (!hasModel && !hasAgent) { + throw new IllegalArgumentException( + "Must specify either 'model' or 'agent' in CreateInteractionConfig."); + } + + // Validation: agentConfig only with agent, generationConfig only with model + if (hasAgent && config.generationConfig().isPresent()) { + throw new IllegalArgumentException( + "Cannot use 'generationConfig' with agent-based interactions. " + + "Use 'agentConfig' instead."); + } + + if (hasModel && config.agentConfig().isPresent()) { + throw new IllegalArgumentException( + "Cannot use 'agentConfig' with model-based interactions. " + + "Use 'generationConfig' instead."); + } + + CreateInteractionParameters.Builder parameterBuilder = CreateInteractionParameters.builder(); + + if (!Common.isZero(config)) { + parameterBuilder.config(config); + } + JsonNode parameterNode = JsonSerializable.toJsonNode(parameterBuilder.build()); + + ObjectNode body = createInteractionParameters(parameterNode); + String path = "interactions"; + + // The "_query" key is used internally to store query parameters + // that will be appended to the URL + JsonNode queryParams = body.get("_query"); + if (queryParams != null) { + body.remove("_query"); + path = String.format("%s?%s", path, Common.urlEncode((ObjectNode) queryParams)); + } + + Optional requestHttpOptions = Optional.empty(); + if (config != null) { + requestHttpOptions = config.httpOptions(); + } + + String requestBody = JsonSerializable.toJsonString(body); + return new BuiltRequest(path, requestBody, requestHttpOptions); + } + + /** A shared processResponse function for both sync and async methods. */ + Interaction processResponseForCreate(ApiResponse response, CreateInteractionConfig config) { + ResponseBody responseBody = response.getBody(); + String responseString; + try { + responseString = responseBody.string(); + } catch (IOException e) { + throw new GenAiIOException("Failed to read HTTP response.", e); + } + + JsonNode responseNode = JsonSerializable.stringToJsonNode(responseString); + + Interaction sdkResponse = JsonSerializable.fromJsonNode(responseNode, Interaction.class); + Headers responseHeaders = response.getHeaders(); + if (responseHeaders == null) { + return sdkResponse; + } + Map headers = new HashMap<>(); + for (String headerName : responseHeaders.names()) { + headers.put(headerName, responseHeaders.get(headerName)); + } + return sdkResponse.toBuilder().sdkHttpResponse(HttpResponse.builder().headers(headers)).build(); + } + + /** A shared buildRequest method for both sync and async methods. */ + BuiltRequest buildRequestForGet(String id, GetInteractionConfig config) { + + // Validation: id must be non-empty + if (id == null || id.isEmpty()) { + throw new IllegalArgumentException("Interaction ID must not be empty"); + } + + GetInteractionParameters.Builder parameterBuilder = GetInteractionParameters.builder(); + + if (!Common.isZero(id)) { + parameterBuilder.id(id); + } + if (!Common.isZero(config)) { + parameterBuilder.config(config); + } + JsonNode parameterNode = JsonSerializable.toJsonNode(parameterBuilder.build()); + + // Transform ID parameter into URL placeholder using special "_url" key + // The "_url" key is used internally to store URL path parameters + ObjectNode body = transformIdParameter(parameterNode); + + String path = body.get("_url") != null ? Common.formatMap("{id}", body.get("_url")) : "{id}"; + body.remove("_url"); + + // The "_query" key is used internally to store query parameters + // that will be appended to the URL + JsonNode queryParams = body.get("_query"); + if (queryParams != null) { + body.remove("_query"); + path = String.format("%s?%s", path, Common.urlEncode((ObjectNode) queryParams)); + } + + Optional requestHttpOptions = Optional.empty(); + if (config != null) { + requestHttpOptions = config.httpOptions(); + } + + return new BuiltRequest(path, JsonSerializable.toJsonString(body), requestHttpOptions); + } + + /** A shared processResponse function for both sync and async methods. */ + Interaction processResponseForGet(ApiResponse response, GetInteractionConfig config) { + ResponseBody responseBody = response.getBody(); + String responseString; + try { + responseString = responseBody.string(); + } catch (IOException e) { + throw new GenAiIOException("Failed to read HTTP response.", e); + } + + JsonNode responseNode = JsonSerializable.stringToJsonNode(responseString); + + Interaction sdkResponse = JsonSerializable.fromJsonNode(responseNode, Interaction.class); + Headers responseHeaders = response.getHeaders(); + if (responseHeaders == null) { + return sdkResponse; + } + Map headers = new HashMap<>(); + for (String headerName : responseHeaders.names()) { + headers.put(headerName, responseHeaders.get(headerName)); + } + return sdkResponse.toBuilder().sdkHttpResponse(HttpResponse.builder().headers(headers)).build(); + } + + /** A shared buildRequest method for both sync and async methods. */ + BuiltRequest buildRequestForCancel(String id, CancelInteractionConfig config) { + + // Validation: id must be non-empty + if (id == null || id.isEmpty()) { + throw new IllegalArgumentException("Interaction ID must not be empty"); + } + + CancelInteractionParameters.Builder parameterBuilder = CancelInteractionParameters.builder(); + + if (!Common.isZero(id)) { + parameterBuilder.id(id); + } + if (!Common.isZero(config)) { + parameterBuilder.config(config); + } + JsonNode parameterNode = JsonSerializable.toJsonNode(parameterBuilder.build()); + + // Transform ID parameter into URL placeholder using special "_url" key + // The "_url" key is used internally to store URL path parameters + ObjectNode body = transformIdParameter(parameterNode); + + // Cancel uses POST to /interactions/{id}/cancel + String path = + body.get("_url") != null + ? Common.formatMap("{id}/cancel", body.get("_url")) + : "{id}/cancel"; + body.remove("_url"); + + // The "_query" key is used internally to store query parameters + // that will be appended to the URL + JsonNode queryParams = body.get("_query"); + if (queryParams != null) { + body.remove("_query"); + path = String.format("%s?%s", path, Common.urlEncode((ObjectNode) queryParams)); + } + + Optional requestHttpOptions = Optional.empty(); + if (config != null) { + requestHttpOptions = config.httpOptions(); + } + + return new BuiltRequest(path, JsonSerializable.toJsonString(body), requestHttpOptions); + } + + /** A shared processResponse function for both sync and async methods. */ + Interaction processResponseForCancel(ApiResponse response, CancelInteractionConfig config) { + ResponseBody responseBody = response.getBody(); + String responseString; + try { + responseString = responseBody.string(); + } catch (IOException e) { + throw new GenAiIOException("Failed to read HTTP response.", e); + } + + JsonNode responseNode = JsonSerializable.stringToJsonNode(responseString); + + Interaction sdkResponse = JsonSerializable.fromJsonNode(responseNode, Interaction.class); + Headers responseHeaders = response.getHeaders(); + if (responseHeaders == null) { + return sdkResponse; + } + Map headers = new HashMap<>(); + for (String headerName : responseHeaders.names()) { + headers.put(headerName, responseHeaders.get(headerName)); + } + return sdkResponse.toBuilder().sdkHttpResponse(HttpResponse.builder().headers(headers)).build(); + } + + /** + * Shared transformer for interaction ID parameters (used by GET, CANCEL, DELETE operations). + * Transforms the "id" field from the input into a URL parameter. + * + * @param fromObject Source JSON node containing the ID + * @return Transformed ObjectNode with ID in _url.id path + */ + @ExcludeFromGeneratedCoverageReport + private ObjectNode transformIdParameter(JsonNode fromObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + + if (Common.getValueByPath(fromObject, new String[] {"id"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"_url", "id"}, + Transformers.tInteractionId( + this.apiClient, Common.getValueByPath(fromObject, new String[] {"id"}))); + } + + return toObject; + } + + /** + * Shared transformer for delete interaction responses. Extracts sdkHttpResponse from the API + * response. + * + * @param fromObject Source JSON node from the API response + * @return Transformed ObjectNode with sdkHttpResponse field + */ + @ExcludeFromGeneratedCoverageReport + private ObjectNode transformDeleteInteractionResponse(JsonNode fromObject) { + ObjectNode toObject = JsonSerializable.objectMapper().createObjectNode(); + + if (Common.getValueByPath(fromObject, new String[] {"sdkHttpResponse"}) != null) { + Common.setValueByPath( + toObject, + new String[] {"sdkHttpResponse"}, + Common.getValueByPath(fromObject, new String[] {"sdkHttpResponse"})); + } + + return toObject; + } + + /** A shared buildRequest method for both sync and async methods. */ + BuiltRequest buildRequestForDelete(String id, DeleteInteractionConfig config) { + + // Validation: id must be non-empty + if (id == null || id.isEmpty()) { + throw new IllegalArgumentException("Interaction ID must not be empty"); + } + + DeleteInteractionParameters.Builder parameterBuilder = DeleteInteractionParameters.builder(); + + if (!Common.isZero(id)) { + parameterBuilder.id(id); + } + if (!Common.isZero(config)) { + parameterBuilder.config(config); + } + JsonNode parameterNode = JsonSerializable.toJsonNode(parameterBuilder.build()); + + // Transform ID parameter into URL placeholder using special "_url" key + // The "_url" key is used internally to store URL path parameters + ObjectNode body = transformIdParameter(parameterNode); + + String path = body.get("_url") != null ? Common.formatMap("{id}", body.get("_url")) : "{id}"; + body.remove("_url"); + + // The "_query" key is used internally to store query parameters + // that will be appended to the URL + JsonNode queryParams = body.get("_query"); + if (queryParams != null) { + body.remove("_query"); + path = String.format("%s?%s", path, Common.urlEncode((ObjectNode) queryParams)); + } + + Optional requestHttpOptions = Optional.empty(); + if (config != null) { + requestHttpOptions = config.httpOptions(); + } + + return new BuiltRequest(path, JsonSerializable.toJsonString(body), requestHttpOptions); + } + + /** A shared processResponse function for both sync and async methods. */ + DeleteInteractionResponse processResponseForDelete( + ApiResponse response, DeleteInteractionConfig config) { + ResponseBody responseBody = response.getBody(); + String responseString; + try { + responseString = responseBody.string(); + } catch (IOException e) { + throw new GenAiIOException("Failed to read HTTP response.", e); + } + + JsonNode responseNode = JsonSerializable.stringToJsonNode(responseString); + + // Transform response - identical for both platforms + responseNode = transformDeleteInteractionResponse(responseNode); + + DeleteInteractionResponse sdkResponse = + JsonSerializable.fromJsonNode(responseNode, DeleteInteractionResponse.class); + Headers responseHeaders = response.getHeaders(); + if (responseHeaders == null) { + return sdkResponse; + } + Map headers = new HashMap<>(); + for (String headerName : responseHeaders.names()) { + headers.put(headerName, responseHeaders.get(headerName)); + } + return sdkResponse.toBuilder().sdkHttpResponse(HttpResponse.builder().headers(headers)).build(); + } + + /** + * Creates a new interaction with the specified configuration. + * + *

Either {@code model} or {@code agent} must be specified in the config, but not both. + * + *

When using function calling tools, the application must handle the function execution loop + * manually. If the interaction returns {@code status: "requires_action"}, extract the function + * calls from the outputs, execute them, and create a new interaction with the results using + * {@code previousInteractionId}. + * + *

Example usage for model-based interaction: + * + *

{@code
+   * CreateInteractionConfig config = CreateInteractionConfig.builder()
+   *     .model("gemini-2.5-flash")
+   *     .input("What is the capital of France?")
+   *     .build();
+   * Interaction response = client.interactions.create(config);
+   * }
+ * + *

Example usage for agent-based interaction: + * + *

{@code
+   * CreateInteractionConfig config = CreateInteractionConfig.builder()
+   *     .agent("deep-research-pro-preview-12-2025")
+   *     .input("Research the history of quantum computing")
+   *     .build();
+   * Interaction response = client.interactions.create(config);
+   * }
+ * + *

Example usage with manual function calling: + * + *

{@code
+   * CreateInteractionConfig config = CreateInteractionConfig.builder()
+   *     .model("gemini-2.5-flash")
+   *     .input("What's the weather in Paris?")
+   *     .tools(Function.of("getWeather", "Gets weather for a city", schema))
+   *     .build();
+   *
+   * Interaction interaction = client.interactions.create(config);
+   *
+   * // Manual loop for function calling
+   * while ("requires_action".equals(interaction.status().toString())) {
+   *   // Extract and execute functions manually
+   *   List results = executeFunctions(interaction);
+   *
+   *   // Continue conversation with results
+   *   interaction = client.interactions.create(
+   *     config.toBuilder()
+   *       .previousInteractionId(interaction.id())
+   *       .inputFromContents(results)
+   *       .build()
+   *   );
+   * }
+   * }
+ * + *

Note: The Interactions API is in beta and subject to change. + * + * @param config The configuration for creating the interaction + * @return The created Interaction with outputs + * @throws IllegalArgumentException if both model and agent are specified, or neither is specified + * @throws GenAiIOException if the API request fails + */ + public Interaction create(CreateInteractionConfig config) { + BuiltRequest builtRequest = buildRequestForCreate(config); + + try (ApiResponse response = + this.apiClient.request( + "post", builtRequest.path(), builtRequest.body(), builtRequest.httpOptions())) { + return processResponseForCreate(response, config); + } + } + + /** + * Retrieves an interaction by its ID. + * + *

Example usage: + * + *

{@code
+   * GetInteractionConfig config = GetInteractionConfig.builder().build();
+   * Interaction interaction = client.interactions.get("interaction-id-123", config);
+   * System.out.println("Status: " + interaction.status());
+   * }
+ * + *

Note: The Interactions API is in beta and subject to change. + * + * @param id The ID of the interaction to retrieve + * @param config The configuration for the get request (can be null) + * @return The retrieved Interaction + * @throws IllegalArgumentException if the ID is empty + * @throws GenAiIOException if the API request fails + */ + public Interaction get(String id, GetInteractionConfig config) { + BuiltRequest builtRequest = buildRequestForGet(id, config); + + try (ApiResponse response = + this.apiClient.request( + "get", builtRequest.path(), builtRequest.body(), builtRequest.httpOptions())) { + return processResponseForGet(response, config); + } + } + + /** + * Cancels a background interaction that is still in progress. + * + *

Only applies to interactions created with background=true that are in IN_PROGRESS status. + * + *

Example usage: + * + *

{@code
+   * CancelInteractionConfig config = CancelInteractionConfig.builder().build();
+   * Interaction cancelled = client.interactions.cancel("interaction-id-123", config);
+   * System.out.println("New status: " + cancelled.status()); // Should be CANCELLED
+   * }
+ * + *

Note: The Interactions API is in beta and subject to change. + * + * @param id The ID of the interaction to cancel + * @param config The configuration for the cancel request (can be null) + * @return The updated Interaction with CANCELLED status + * @throws IllegalArgumentException if the ID is empty + * @throws GenAiIOException if the API request fails + */ + public Interaction cancel(String id, CancelInteractionConfig config) { + BuiltRequest builtRequest = buildRequestForCancel(id, config); + + try (ApiResponse response = + this.apiClient.request( + "post", builtRequest.path(), builtRequest.body(), builtRequest.httpOptions())) { + return processResponseForCancel(response, config); + } + } + + /** + * Deletes an interaction by its ID. + * + *

Example usage: + * + *

{@code
+   * DeleteInteractionConfig config = DeleteInteractionConfig.builder().build();
+   * DeleteInteractionResponse response = client.interactions.delete("interaction-id-123", config);
+   * }
+ * + *

Note: The Interactions API is in beta and subject to change. + * + * @param id The ID of the interaction to delete + * @param config The configuration for the delete request (can be null) + * @return The delete response with HTTP headers + * @throws IllegalArgumentException if the ID is empty + * @throws GenAiIOException if the API request fails + */ + public DeleteInteractionResponse delete(String id, DeleteInteractionConfig config) { + BuiltRequest builtRequest = buildRequestForDelete(id, config); + + try (ApiResponse response = + this.apiClient.request( + "delete", builtRequest.path(), builtRequest.body(), builtRequest.httpOptions())) { + return processResponseForDelete(response, config); + } + } +} diff --git a/src/main/java/com/google/genai/Transformers.java b/src/main/java/com/google/genai/Transformers.java index 65eab1ed837..8d1f10f03c7 100644 --- a/src/main/java/com/google/genai/Transformers.java +++ b/src/main/java/com/google/genai/Transformers.java @@ -409,6 +409,21 @@ public static String tCachedContentName(ApiClient apiClient, Object origin) { "Unsupported cached content name type: " + origin.getClass()); } + /** Transforms an object to an interaction ID for the API. */ + public static String tInteractionId(ApiClient apiClient, Object origin) { + if (origin == null) { + return null; + } else if (origin instanceof String) { + return getResourceName(apiClient, (String) origin, "interactions"); + } else if (origin instanceof JsonNode) { + String interactionId = JsonSerializable.toJsonString((JsonNode) origin); + interactionId = interactionId.replace("\"", ""); + return getResourceName(apiClient, interactionId, "interactions"); + } + throw new IllegalArgumentException( + "Unsupported interaction ID type: " + origin.getClass()); + } + /** Transforms an object to a list of Content for the embedding API. */ @SuppressWarnings("unchecked") public static @Nullable List tContentsForEmbed(ApiClient apiClient, Object origin) { diff --git a/src/main/java/com/google/genai/errors/ExcludeFromGeneratedCoverageReport.java b/src/main/java/com/google/genai/errors/ExcludeFromGeneratedCoverageReport.java index c7e60717c3d..ab99c3092f6 100644 --- a/src/main/java/com/google/genai/errors/ExcludeFromGeneratedCoverageReport.java +++ b/src/main/java/com/google/genai/errors/ExcludeFromGeneratedCoverageReport.java @@ -28,4 +28,4 @@ */ @Retention(RetentionPolicy.CLASS) @Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE}) -@interface ExcludeFromGeneratedCoverageReport {} \ No newline at end of file +@interface ExcludeFromGeneratedCoverageReport {} diff --git a/src/main/java/com/google/genai/types/ExcludeFromGeneratedCoverageReport.java b/src/main/java/com/google/genai/types/ExcludeFromGeneratedCoverageReport.java index 73d98f6f869..43981930475 100644 --- a/src/main/java/com/google/genai/types/ExcludeFromGeneratedCoverageReport.java +++ b/src/main/java/com/google/genai/types/ExcludeFromGeneratedCoverageReport.java @@ -28,4 +28,4 @@ */ @Retention(RetentionPolicy.CLASS) @Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE}) -@interface ExcludeFromGeneratedCoverageReport {} +public @interface ExcludeFromGeneratedCoverageReport {} diff --git a/src/main/java/com/google/genai/types/ThinkingLevel.java b/src/main/java/com/google/genai/types/ThinkingLevel.java index 5985dc0d52c..865a796eb01 100644 --- a/src/main/java/com/google/genai/types/ThinkingLevel.java +++ b/src/main/java/com/google/genai/types/ThinkingLevel.java @@ -23,30 +23,102 @@ import com.google.common.base.Ascii; import java.util.Objects; -/** The number of thoughts tokens that the model should generate. */ +/** + * Controls the depth and extent of the model's reasoning process. + * + *

The thinking level determines how many internal reasoning tokens the model should generate + * before producing its final response. Higher thinking levels allow the model to engage in more + * thorough analysis, exploration of alternative approaches, and step-by-step reasoning, which can + * lead to more accurate and well-reasoned responses for complex tasks. + * + *

This class is shared across multiple APIs (Interactions API, etc.) and follows the standard + * enum wrapper pattern used throughout the codebase. The {@code Known} enum contains all + * recognized values from the OpenAPI specification, and {@code THINKING_LEVEL_UNSPECIFIED} serves + * as a fallback for forward compatibility. + * + *

Example usage: + * + *

{@code
+ * // For simple tasks, use minimal thinking
+ * ThinkingLevel level = new ThinkingLevel(ThinkingLevel.Known.MINIMAL);
+ *
+ * // For complex reasoning tasks, use high thinking
+ * ThinkingLevel level = new ThinkingLevel(ThinkingLevel.Known.HIGH);
+ *
+ * // Include in generation config
+ * GenerationConfig config = GenerationConfig.builder()
+ *     .thinkingLevel(new ThinkingLevel(ThinkingLevel.Known.MEDIUM))
+ *     .build();
+ * }
+ */ public class ThinkingLevel { - /** Enum representing the known values for ThinkingLevel. */ + /** + * Enum representing the known values for ThinkingLevel. + * + *

These values control the amount of internal reasoning the model performs before generating + * its response. + */ public enum Known { - /** Unspecified thinking level. */ + /** + * Unspecified thinking level. + * + *

This is the fallback value used when the API returns an unknown thinking level not + * recognized by this version of the SDK. This ensures forward compatibility when new thinking + * levels are added to the API. + */ THINKING_LEVEL_UNSPECIFIED, - /** Low thinking level. */ + /** + * Low thinking level. + * + *

The model performs a limited amount of internal reasoning. Use this for tasks that + * benefit from some deliberation but don't require extensive analysis. Generates fewer + * thinking tokens than MEDIUM or HIGH. + */ LOW, - /** Medium thinking level. */ + /** + * Medium thinking level. + * + *

The model performs a moderate amount of internal reasoning. This is a balanced option + * suitable for tasks of average complexity that benefit from careful consideration. Generates + * more thinking tokens than LOW but fewer than HIGH. + */ MEDIUM, - /** High thinking level. */ + /** + * High thinking level. + * + *

The model performs extensive internal reasoning. Use this for complex tasks that require + * deep analysis, multi-step reasoning, or exploration of multiple approaches. Generates the + * most thinking tokens, which may increase latency but can significantly improve response + * quality for challenging problems. + */ HIGH, - /** MINIMAL thinking level. */ + /** + * Minimal thinking level. + * + *

The model performs the least amount of internal reasoning. Use this for simple, + * straightforward tasks where immediate responses are preferred and extensive deliberation + * would not improve the output quality. Generates the fewest thinking tokens. + */ MINIMAL } private Known thinkingLevelEnum; private final String value; + /** + * Creates a ThinkingLevel from a string value. + * + *

This constructor is used by Jackson during JSON deserialization. It attempts to match the + * provided string value to a known enum value (case-insensitive). If no match is found, it falls + * back to {@code THINKING_LEVEL_UNSPECIFIED}. + * + * @param value the string representation of the thinking level + */ @JsonCreator public ThinkingLevel(String value) { this.value = value; @@ -61,6 +133,14 @@ public ThinkingLevel(String value) { } } + /** + * Creates a ThinkingLevel from a known enum value. + * + *

This is the recommended constructor for creating ThinkingLevel instances in application + * code. + * + * @param knownValue the known thinking level enum value + */ public ThinkingLevel(Known knownValue) { this.thinkingLevelEnum = knownValue; this.value = knownValue.toString(); @@ -110,6 +190,14 @@ public int hashCode() { } } + /** + * Returns the known enum value for this thinking level. + * + *

If the value was not recognized during deserialization, this will return {@code + * THINKING_LEVEL_UNSPECIFIED}. + * + * @return the known enum value + */ @ExcludeFromGeneratedCoverageReport public Known knownEnum() { return this.thinkingLevelEnum; diff --git a/src/main/java/com/google/genai/types/Turn.java b/src/main/java/com/google/genai/types/Turn.java new file mode 100644 index 00000000000..287422365d0 --- /dev/null +++ b/src/main/java/com/google/genai/types/Turn.java @@ -0,0 +1,141 @@ +/* + * 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 static com.google.common.collect.ImmutableList.toImmutableList; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** Represents a conversation turn with role and content. */ +@AutoValue +@JsonDeserialize(builder = Turn.Builder.class) +public abstract class Turn extends JsonSerializable { + /** The content of the turn. Can be a string or list of Content objects. */ + @JsonProperty("content") + public abstract Optional> content(); + + /** Optional. The role in the conversation ('user' or 'model'). */ + @JsonProperty("role") + public abstract Optional role(); + + /** Instantiates a builder for Turn. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_Turn.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for Turn. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use `Turn.builder()` for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_Turn.Builder(); + } + + /** + * Setter for content. + * + *

content: The content of the turn as a list of Content objects. + */ + @JsonProperty("content") + public abstract Builder content(List content); + + /** + * Setter for content (varargs convenience method). + * + *

content: The content of the turn. + */ + @CanIgnoreReturnValue + public Builder content(Content... content) { + return content(Arrays.asList(content)); + } + + /** + * Setter for content builder (varargs convenience method). + * + *

content: The content of the turn. + */ + @CanIgnoreReturnValue + public Builder content(Content.Builder... contentBuilders) { + return content( + Arrays.asList(contentBuilders).stream() + .map(Content.Builder::build) + .collect(toImmutableList())); + } + + /** Internal setter for content with Optional. */ + @ExcludeFromGeneratedCoverageReport + abstract Builder content(Optional> content); + + /** + * Clear method for content. + * + *

Removes the content field. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearContent() { + return content(Optional.empty()); + } + + /** + * Setter for role. + * + *

role: The role in the conversation ('user' or 'model'). + */ + @JsonProperty("role") + public abstract Builder role(String role); + + /** Internal setter for role with Optional. */ + @ExcludeFromGeneratedCoverageReport + abstract Builder role(Optional role); + + /** + * Clear method for role. + * + *

Removes the role field. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearRole() { + return role(Optional.empty()); + } + + /** Builds the Turn instance. */ + public abstract Turn build(); + } + + /** Deserializes a Turn from a JSON string. */ + @ExcludeFromGeneratedCoverageReport + public static Turn fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, Turn.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/AgentConfig.java b/src/main/java/com/google/genai/types/interactions/AgentConfig.java new file mode 100644 index 00000000000..878d8b8be98 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/AgentConfig.java @@ -0,0 +1,61 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +/** + * Base interface for agent configuration types using a type discriminator. + * + *

AgentConfig is a polymorphic type that configures agent behavior. Alternative to {@code + * generation_config}. Only applicable when {@code agent} is set. + * + *

Concrete subtypes: + * + *

+ * + *

Example usage: + * + *

{@code
+ * // Dynamic agent configuration
+ * AgentConfig config = DynamicAgentConfig.create();
+ *
+ * // Deep Research agent with thinking summaries
+ * AgentConfig config = DeepResearchAgentConfig.builder()
+ *     .thinkingSummaries(new ThinkingSummaries(ThinkingSummaries.Known.AUTO))
+ *     .build();
+ *
+ * // Use in CreateInteractionConfig
+ * CreateInteractionConfig interactionConfig = CreateInteractionConfig.builder()
+ *     .agent("agent-name")
+ *     .input("Your input")
+ *     .agentConfig(config)
+ *     .build();
+ * }
+ */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = DynamicAgentConfig.class, name = "dynamic"), + @JsonSubTypes.Type(value = DeepResearchAgentConfig.class, name = "deep-research") +}) +public interface AgentConfig { + // Marker interface - Jackson handles type discrimination via annotations +} diff --git a/src/main/java/com/google/genai/types/interactions/AllowedTools.java b/src/main/java/com/google/genai/types/interactions/AllowedTools.java new file mode 100644 index 00000000000..cc88aa86901 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/AllowedTools.java @@ -0,0 +1,150 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * Configuration for allowed tools in an MCP server. + * + *

This type matches the Python SDK's AllowedTools: + * + *

+ * class AllowedTools(BaseModel):
+ *     mode: Optional[ToolChoiceType] = None  # Literal["auto", "any", "none", "validated"]
+ *     tools: Optional[List[str]] = None
+ * 
+ * + *

Example usage: + * + *

{@code
+ * AllowedTools allowedTools = AllowedTools.builder()
+ *     .mode("auto")
+ *     .tools("get_weather", "get_forecast")
+ *     .build();
+ * }
+ */ +@AutoValue +@JsonDeserialize(builder = AllowedTools.Builder.class) +public abstract class AllowedTools extends JsonSerializable { + + /** + * The mode of the tool choice. + * + *

Valid values: "auto", "any", "none", "validated" + */ + @JsonProperty("mode") + public abstract Optional mode(); + + /** The names of the allowed tools. */ + @JsonProperty("tools") + public abstract Optional> tools(); + + /** Instantiates a builder for AllowedTools. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_AllowedTools.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for AllowedTools. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code AllowedTools.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_AllowedTools.Builder(); + } + + /** + * Setter for mode. + * + *

mode: The mode of the tool choice. Valid values: "auto", "any", "none", "validated" + */ + @JsonProperty("mode") + public abstract Builder mode(String mode); + + @ExcludeFromGeneratedCoverageReport + abstract Builder mode(Optional mode); + + /** Clears the value of mode field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearMode() { + return mode(Optional.empty()); + } + + /** + * Setter for tools. + * + *

tools: The names of the allowed tools. + */ + @JsonProperty("tools") + public abstract Builder tools(List tools); + + /** + * Setter for tools (varargs convenience method). + * + *

tools: The names of the allowed tools. + */ + @CanIgnoreReturnValue + public Builder tools(String... tools) { + return tools(Arrays.asList(tools)); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder tools(Optional> tools); + + /** Clears the value of tools field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearTools() { + return tools(Optional.empty()); + } + + public abstract AllowedTools build(); + } + + /** Deserializes a JSON string to an AllowedTools object. */ + @ExcludeFromGeneratedCoverageReport + public static AllowedTools fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, AllowedTools.class); + } + + /** Convenience factory method. */ + @ExcludeFromGeneratedCoverageReport + public static AllowedTools of(String mode, List tools) { + return builder().mode(mode).tools(tools).build(); + } + + /** Convenience factory method (varargs). */ + @ExcludeFromGeneratedCoverageReport + public static AllowedTools of(String mode, String... tools) { + return builder().mode(mode).tools(Arrays.asList(tools)).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/Annotation.java b/src/main/java/com/google/genai/types/interactions/Annotation.java new file mode 100644 index 00000000000..04ac0cf7b86 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/Annotation.java @@ -0,0 +1,160 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Optional; + +/** + * Citation information for model-generated content in the Interactions API. + * + *

Annotations provide attribution for specific segments of text by mapping byte indices to their + * source references (such as URLs or titles). This enables proper citation and source tracking for + * content generated by the model. + * + *

Example usage: + * + *

{@code
+ * Annotation citation = Annotation.builder()
+ *     .startIndex(0)
+ *     .endIndex(50)
+ *     .source("https://example.com/research-paper")
+ *     .build();
+ *
+ * // Or using the convenience factory method
+ * Annotation citation2 = Annotation.of(51, 100, "Wikipedia: Machine Learning");
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = Annotation.Builder.class) +public abstract class Annotation extends JsonSerializable { + + /** + * Start of segment of the response that is attributed to this source. Index indicates the start + * of the segment, measured in bytes. + */ + @JsonProperty("start_index") + public abstract Optional startIndex(); + + /** + * End of the attributed segment, exclusive. Index indicates the end of the segment, measured in + * bytes. + */ + @JsonProperty("end_index") + public abstract Optional endIndex(); + + /** Source attributed for a portion of the text. Could be a URL, title, or other identifier. */ + @JsonProperty("source") + public abstract Optional source(); + + /** Instantiates a builder for Annotation. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_Annotation.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for Annotation. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code Annotation.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_Annotation.Builder(); + } + + /** + * Setter for startIndex. + * + *

startIndex: Start of segment of the response that is attributed to this source. + */ + @JsonProperty("start_index") + public abstract Builder startIndex(Integer startIndex); + + @ExcludeFromGeneratedCoverageReport + abstract Builder startIndex(Optional startIndex); + + /** Clears the value of startIndex field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearStartIndex() { + return startIndex(Optional.empty()); + } + + /** + * Setter for endIndex. + * + *

endIndex: End of the attributed segment, exclusive. + */ + @JsonProperty("end_index") + public abstract Builder endIndex(Integer endIndex); + + @ExcludeFromGeneratedCoverageReport + abstract Builder endIndex(Optional endIndex); + + /** Clears the value of endIndex field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearEndIndex() { + return endIndex(Optional.empty()); + } + + /** + * Setter for source. + * + *

source: Source attributed for a portion of the text. + */ + @JsonProperty("source") + public abstract Builder source(String source); + + @ExcludeFromGeneratedCoverageReport + abstract Builder source(Optional source); + + /** Clears the value of source field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearSource() { + return source(Optional.empty()); + } + + public abstract Annotation build(); + } + + /** Deserializes a JSON string to an Annotation object. */ + @ExcludeFromGeneratedCoverageReport + public static Annotation fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, Annotation.class); + } + + /** Convenience factory method. */ + @ExcludeFromGeneratedCoverageReport + public static Annotation of(Integer startIndex, Integer endIndex, String source) { + return builder().startIndex(startIndex).endIndex(endIndex).source(source).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/AudioMimeType.java b/src/main/java/com/google/genai/types/interactions/AudioMimeType.java new file mode 100644 index 00000000000..36830727902 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/AudioMimeType.java @@ -0,0 +1,152 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.base.Ascii; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Objects; + +/** + * MIME type for audio content. + * + *

Supports known audio MIME types with extensibility for future formats. + */ +public class AudioMimeType { + + /** Enum representing the known audio MIME types. */ + public enum Known { + /** Unspecified or unknown audio MIME type. */ + AUDIO_MIME_TYPE_UNSPECIFIED("unspecified"), + + /** WAV audio format. */ + AUDIO_WAV("audio/wav"), + + /** MP3 audio format. */ + AUDIO_MP3("audio/mp3"), + + /** AIFF audio format. */ + AUDIO_AIFF("audio/aiff"), + + /** AAC audio format. */ + AUDIO_AAC("audio/aac"), + + /** OGG audio format. */ + AUDIO_OGG("audio/ogg"), + + /** FLAC audio format. */ + AUDIO_FLAC("audio/flac"); + + private final String value; + + Known(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + } + + private Known mimeTypeEnum; + private final String value; + + /** + * Constructs an AudioMimeType from a string value. + * + * @param value The MIME type string (e.g., "audio/mp3") + */ + @JsonCreator + public AudioMimeType(String value) { + this.value = value; + for (Known mimeTypeEnum : Known.values()) { + if (Ascii.equalsIgnoreCase(mimeTypeEnum.toString(), value)) { + this.mimeTypeEnum = mimeTypeEnum; + break; + } + } + if (this.mimeTypeEnum == null) { + this.mimeTypeEnum = Known.AUDIO_MIME_TYPE_UNSPECIFIED; + } + } + + /** + * Constructs an AudioMimeType from a known enum value. + * + * @param knownValue The known MIME type + */ + public AudioMimeType(Known knownValue) { + this.mimeTypeEnum = knownValue; + this.value = knownValue.toString(); + } + + @ExcludeFromGeneratedCoverageReport + @Override + @JsonValue + public String toString() { + return this.value; + } + + @ExcludeFromGeneratedCoverageReport + @SuppressWarnings("PatternMatchingInstanceof") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + + if (!(o instanceof AudioMimeType)) { + return false; + } + + AudioMimeType other = (AudioMimeType) o; + + if (this.mimeTypeEnum != Known.AUDIO_MIME_TYPE_UNSPECIFIED + && other.mimeTypeEnum != Known.AUDIO_MIME_TYPE_UNSPECIFIED) { + return this.mimeTypeEnum == other.mimeTypeEnum; + } else if (this.mimeTypeEnum == Known.AUDIO_MIME_TYPE_UNSPECIFIED + && other.mimeTypeEnum == Known.AUDIO_MIME_TYPE_UNSPECIFIED) { + return this.value.equals(other.value); + } + return false; + } + + @ExcludeFromGeneratedCoverageReport + @Override + public int hashCode() { + if (this.mimeTypeEnum != Known.AUDIO_MIME_TYPE_UNSPECIFIED) { + return this.mimeTypeEnum.hashCode(); + } else { + return Objects.hashCode(this.value); + } + } + + /** + * Returns the known enum value if this is a recognized MIME type. + * + * @return The known enum value + */ + @ExcludeFromGeneratedCoverageReport + public Known knownEnum() { + return this.mimeTypeEnum; + } +} diff --git a/src/main/java/com/google/genai/types/interactions/CancelInteractionConfig.java b/src/main/java/com/google/genai/types/interactions/CancelInteractionConfig.java new file mode 100644 index 00000000000..1fa649f97f5 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/CancelInteractionConfig.java @@ -0,0 +1,109 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.HttpOptions; +import java.util.Optional; + +/** + * Configuration for canceling an interaction in the Interactions API. + * + *

Optional parameters for the interactions.cancel method. Currently supports HTTP request + * options for customizing the cancel operation. + * + *

Example usage: + * + *

{@code
+ * CancelInteractionConfig config = CancelInteractionConfig.builder()
+ *     .httpOptions(HttpOptions.builder().timeout(5000).build())
+ *     .build();
+ * client.interactions.cancel("interaction-id", config);
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = CancelInteractionConfig.Builder.class) +public abstract class CancelInteractionConfig extends JsonSerializable { + /** Used to override HTTP request options. */ + @JsonProperty("httpOptions") + public abstract Optional httpOptions(); + + /** Instantiates a builder for CancelInteractionConfig. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_CancelInteractionConfig.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for CancelInteractionConfig. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use `CancelInteractionConfig.builder()` for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_CancelInteractionConfig.Builder(); + } + + /** + * Setter for httpOptions. + * + *

httpOptions: Used to override HTTP request options. + */ + @JsonProperty("httpOptions") + public abstract Builder httpOptions(HttpOptions httpOptions); + + /** + * Setter for httpOptions builder. + * + *

httpOptions: Used to override HTTP request options. + */ + @CanIgnoreReturnValue + public Builder httpOptions(HttpOptions.Builder httpOptionsBuilder) { + return httpOptions(httpOptionsBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder httpOptions(Optional httpOptions); + + /** Clears the value of httpOptions field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearHttpOptions() { + return httpOptions(Optional.empty()); + } + + public abstract CancelInteractionConfig build(); + } + + /** Deserializes a JSON string to a CancelInteractionConfig object. */ + @ExcludeFromGeneratedCoverageReport + public static CancelInteractionConfig fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, CancelInteractionConfig.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/CancelInteractionParameters.java b/src/main/java/com/google/genai/types/interactions/CancelInteractionParameters.java new file mode 100644 index 00000000000..a1caa2c5c2e --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/CancelInteractionParameters.java @@ -0,0 +1,116 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.api.core.InternalApi; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Optional; + +/** Parameters for interactions.cancel method. */ +@AutoValue +@InternalApi +@JsonDeserialize(builder = CancelInteractionParameters.Builder.class) +public abstract class CancelInteractionParameters extends JsonSerializable { + /** The ID of the interaction to cancel. */ + @JsonProperty("id") + public abstract Optional id(); + + /** Optional parameters for the request. */ + @JsonProperty("config") + public abstract Optional config(); + + /** Instantiates a builder for CancelInteractionParameters. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_CancelInteractionParameters.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for CancelInteractionParameters. */ + @AutoValue.Builder + public abstract static class Builder { + /** + * For internal usage. Please use `CancelInteractionParameters.builder()` for instantiation. + */ + @JsonCreator + private static Builder create() { + return new AutoValue_CancelInteractionParameters.Builder(); + } + + /** + * Setter for id. + * + *

id: The ID of the interaction to cancel. + */ + @JsonProperty("id") + public abstract Builder id(String id); + + @ExcludeFromGeneratedCoverageReport + abstract Builder id(Optional id); + + /** Clears the value of id field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearId() { + return id(Optional.empty()); + } + + /** + * Setter for config. + * + *

config: Optional parameters for the request. + */ + @JsonProperty("config") + public abstract Builder config(CancelInteractionConfig config); + + /** + * Setter for config builder. + * + *

config: Optional parameters for the request. + */ + @CanIgnoreReturnValue + public Builder config(CancelInteractionConfig.Builder configBuilder) { + return config(configBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder config(Optional config); + + /** Clears the value of config field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearConfig() { + return config(Optional.empty()); + } + + public abstract CancelInteractionParameters build(); + } + + /** Deserializes a JSON string to a CancelInteractionParameters object. */ + @ExcludeFromGeneratedCoverageReport + public static CancelInteractionParameters fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, CancelInteractionParameters.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/CodeExecutionCallArguments.java b/src/main/java/com/google/genai/types/interactions/CodeExecutionCallArguments.java new file mode 100644 index 00000000000..8d46ae6cf6d --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/CodeExecutionCallArguments.java @@ -0,0 +1,88 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Optional; + +@AutoValue +@JsonDeserialize(builder = CodeExecutionCallArguments.Builder.class) +public abstract class CodeExecutionCallArguments extends JsonSerializable { + + @JsonProperty("language") + public abstract Optional language(); + + @JsonProperty("code") + public abstract Optional code(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_CodeExecutionCallArguments.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_CodeExecutionCallArguments.Builder(); + } + + @JsonProperty("language") + public abstract Builder language(String language); + + @ExcludeFromGeneratedCoverageReport + abstract Builder language(Optional language); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearLanguage() { + return language(Optional.empty()); + } + + @JsonProperty("code") + public abstract Builder code(String code); + + @ExcludeFromGeneratedCoverageReport + abstract Builder code(Optional code); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearCode() { + return code(Optional.empty()); + } + + public abstract CodeExecutionCallArguments build(); + } + + @ExcludeFromGeneratedCoverageReport + public static CodeExecutionCallArguments fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, CodeExecutionCallArguments.class); + } + + @ExcludeFromGeneratedCoverageReport + public static CodeExecutionCallArguments of(String language, String code) { + return builder().language(language).code(code).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/CreateInteractionConfig.java b/src/main/java/com/google/genai/types/interactions/CreateInteractionConfig.java new file mode 100644 index 00000000000..3adfbe48adb --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/CreateInteractionConfig.java @@ -0,0 +1,549 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.interactions.AgentConfig; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.tools.Tool; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * Configuration for creating an interaction. + * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = CreateInteractionConfig.Builder.class) +public abstract class CreateInteractionConfig extends JsonSerializable { + /** Used to override HTTP request options. */ + @JsonProperty("httpOptions") + public abstract Optional httpOptions(); + + /** Required: The input for the interaction. */ + @JsonProperty("input") + public abstract Input input(); + + /** The model to use for the interaction. Either model or agent must be specified. */ + @JsonProperty("model") + public abstract Optional model(); + + /** The agent to use for the interaction. Either model or agent must be specified. */ + @JsonProperty("agent") + public abstract Optional agent(); + + /** Whether to run the interaction in the background. */ + @JsonProperty("background") + public abstract Optional background(); + + /** Whether to stream the interaction response. */ + @JsonProperty("stream") + public abstract Optional stream(); + + /** Configuration for generation (only used with model-based interactions). */ + @JsonProperty("generation_config") + public abstract Optional generationConfig(); + + /** Configuration for the agent (only used with agent-based interactions). */ + @JsonProperty("agent_config") + public abstract Optional agentConfig(); + + /** The ID of the previous interaction for conversation continuity. */ + @JsonProperty("previous_interaction_id") + public abstract Optional previousInteractionId(); + + /** The expected response format. */ + @JsonProperty("response_format") + public abstract Optional responseFormat(); + + /** The MIME type for the response. */ + @JsonProperty("response_mime_type") + public abstract Optional responseMimeType(); + + /** + * The requested modalities of the response. + * + *

Represents the set of modalities that the model can return. + * + *

Supported values: + * + *

    + *
  • {@link ResponseModality.Known#TEXT} - Text content + *
  • {@link ResponseModality.Known#IMAGE} - Image content + *
  • {@link ResponseModality.Known#AUDIO} - Audio content + *
+ */ + @JsonProperty("response_modalities") + public abstract Optional> responseModalities(); + + /** Whether to store the interaction history. */ + @JsonProperty("store") + public abstract Optional store(); + + /** Developer set system instruction. */ + @JsonProperty("system_instruction") + public abstract Optional systemInstruction(); + + /** + * A list of tools the model may use to generate the next response. + * + *

Use the dedicated Interactions tool types such as {@code Function}, {@code GoogleSearch}, + * {@code CodeExecution}, etc. + */ + @JsonProperty("tools") + public abstract Optional> tools(); + + /** Instantiates a builder for CreateInteractionConfig. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_CreateInteractionConfig.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for CreateInteractionConfig. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use `CreateInteractionConfig.builder()` for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_CreateInteractionConfig.Builder(); + } + + /** + * Setter for httpOptions. + * + *

httpOptions: Used to override HTTP request options. + */ + @JsonProperty("httpOptions") + public abstract Builder httpOptions(HttpOptions httpOptions); + + /** + * Setter for httpOptions builder. + * + *

httpOptions: Used to override HTTP request options. + */ + @CanIgnoreReturnValue + public Builder httpOptions(HttpOptions.Builder httpOptionsBuilder) { + return httpOptions(httpOptionsBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder httpOptions(Optional httpOptions); + + /** Clears the value of httpOptions field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearHttpOptions() { + return httpOptions(Optional.empty()); + } + + /** + * Setter for input. + * + *

input: The input for the interaction. + */ + @JsonProperty("input") + public abstract Builder input(Input input); + + /** + * Convenience setter for input from a string. + * + *

input: The input text for the interaction. + */ + @CanIgnoreReturnValue + public Builder input(String text) { + return input(Input.fromString(text)); + } + + /** + * Convenience setter for input from a list of Content. + * + *

input: The input content for the interaction. + */ + @CanIgnoreReturnValue + public Builder inputFromContents(List contents) { + return input(Input.fromContents(contents)); + } + + /** + * Convenience setter for input from Content objects (varargs). + * + *

input: The input content for the interaction. + */ + @CanIgnoreReturnValue + public Builder inputFromContents(Content... contents) { + return input(Input.fromContents(contents)); + } + + /** + * Convenience setter for input from a list of Turn. + * + *

input: The input turns for the interaction. + */ + @CanIgnoreReturnValue + public Builder inputFromTurns(List turns) { + return input(Input.fromTurns(turns)); + } + + /** + * Convenience setter for input from Turn objects (varargs). + * + *

input: The input turns for the interaction. + */ + @CanIgnoreReturnValue + public Builder inputFromTurns(Turn... turns) { + return input(Input.fromTurns(turns)); + } + + /** + * Setter for model. + * + *

model: The model to use for the interaction. + */ + @JsonProperty("model") + public abstract Builder model(String model); + + @ExcludeFromGeneratedCoverageReport + abstract Builder model(Optional model); + + /** Clears the value of model field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearModel() { + return model(Optional.empty()); + } + + /** + * Setter for agent. + * + *

agent: The agent to use for the interaction. + */ + @JsonProperty("agent") + public abstract Builder agent(String agent); + + @ExcludeFromGeneratedCoverageReport + abstract Builder agent(Optional agent); + + /** Clears the value of agent field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearAgent() { + return agent(Optional.empty()); + } + + /** + * Setter for background. + * + *

background: Whether to run the interaction in the background. + */ + @JsonProperty("background") + public abstract Builder background(Boolean background); + + @ExcludeFromGeneratedCoverageReport + abstract Builder background(Optional background); + + /** Clears the value of background field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearBackground() { + return background(Optional.empty()); + } + + /** + * Setter for stream. + * + *

stream: Whether to stream the interaction response. + */ + @JsonProperty("stream") + public abstract Builder stream(Boolean stream); + + @ExcludeFromGeneratedCoverageReport + abstract Builder stream(Optional stream); + + /** Clears the value of stream field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearStream() { + return stream(Optional.empty()); + } + + /** + * Setter for generationConfig. + * + *

generationConfig: Configuration for generation. + */ + @JsonProperty("generation_config") + public abstract Builder generationConfig(GenerationConfig generationConfig); + + /** + * Setter for generationConfig builder. + * + *

generationConfig: Configuration for generation. + */ + @CanIgnoreReturnValue + public Builder generationConfig(GenerationConfig.Builder generationConfigBuilder) { + return generationConfig(generationConfigBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder generationConfig(Optional generationConfig); + + /** Clears the value of generationConfig field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearGenerationConfig() { + return generationConfig(Optional.empty()); + } + + /** + * Setter for agentConfig. + * + *

agentConfig: Configuration for the agent. + */ + @JsonProperty("agent_config") + public abstract Builder agentConfig(AgentConfig agentConfig); + + @ExcludeFromGeneratedCoverageReport + abstract Builder agentConfig(Optional agentConfig); + + /** Clears the value of agentConfig field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearAgentConfig() { + return agentConfig(Optional.empty()); + } + + /** + * Setter for previousInteractionId. + * + *

previousInteractionId: The ID of the previous interaction. + */ + @JsonProperty("previous_interaction_id") + public abstract Builder previousInteractionId(String previousInteractionId); + + @ExcludeFromGeneratedCoverageReport + abstract Builder previousInteractionId(Optional previousInteractionId); + + /** Clears the value of previousInteractionId field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearPreviousInteractionId() { + return previousInteractionId(Optional.empty()); + } + + /** + * Setter for responseFormat. + * + *

responseFormat: The expected response format. + */ + @JsonProperty("response_format") + public abstract Builder responseFormat(Object responseFormat); + + @ExcludeFromGeneratedCoverageReport + abstract Builder responseFormat(Optional responseFormat); + + /** Clears the value of responseFormat field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearResponseFormat() { + return responseFormat(Optional.empty()); + } + + /** + * Setter for responseMimeType. + * + *

responseMimeType: The MIME type for the response. + */ + @JsonProperty("response_mime_type") + public abstract Builder responseMimeType(String responseMimeType); + + @ExcludeFromGeneratedCoverageReport + abstract Builder responseMimeType(Optional responseMimeType); + + /** Clears the value of responseMimeType field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearResponseMimeType() { + return responseMimeType(Optional.empty()); + } + + /** + * Setter for responseModalities. + * + *

responseModalities: The modalities for the response. + */ + @JsonProperty("response_modalities") + public abstract Builder responseModalities(List responseModalities); + + /** + * Setter for responseModalities. + * + *

responseModalities: The modalities for the response. + */ + public Builder responseModalities(ResponseModality... responseModalities) { + return responseModalities(Arrays.asList(responseModalities)); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder responseModalities(Optional> responseModalities); + + /** Clears the value of responseModalities field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearResponseModalities() { + return responseModalities(Optional.empty()); + } + + /** + * Setter for responseModalities given a varargs of strings. + * + *

responseModalities: The modalities for the response. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder responseModalities(String... responseModalities) { + return responseModalitiesFromString(Arrays.asList(responseModalities)); + } + + /** + * Setter for responseModalities given a varargs of known enums. + * + *

responseModalities: The modalities for the response. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder responseModalities(ResponseModality.Known... knownTypes) { + return responseModalitiesFromKnown(Arrays.asList(knownTypes)); + } + + /** + * Setter for responseModalities given a list of known enums. + * + *

responseModalities: The modalities for the response. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder responseModalitiesFromKnown(List knownTypes) { + ImmutableList listItems = + knownTypes.stream().map(ResponseModality::new).collect(toImmutableList()); + return responseModalities(listItems); + } + + /** + * Setter for responseModalities given a list of strings. + * + *

responseModalities: The modalities for the response. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder responseModalitiesFromString(List responseModalities) { + ImmutableList listItems = + responseModalities.stream().map(ResponseModality::new).collect(toImmutableList()); + return responseModalities(listItems); + } + + /** + * Setter for store. + * + *

store: Whether to store the interaction history. + */ + @JsonProperty("store") + public abstract Builder store(Boolean store); + + @ExcludeFromGeneratedCoverageReport + abstract Builder store(Optional store); + + /** Clears the value of store field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearStore() { + return store(Optional.empty()); + } + + /** + * Setter for systemInstruction. + * + *

systemInstruction: Developer set system instruction. + */ + @JsonProperty("system_instruction") + public abstract Builder systemInstruction(String systemInstruction); + + @ExcludeFromGeneratedCoverageReport + abstract Builder systemInstruction(Optional systemInstruction); + + /** Clears the value of systemInstruction field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearSystemInstruction() { + return systemInstruction(Optional.empty()); + } + + /** + * Setter for tools. + * + *

tools: A list of tools the model may use to generate the next response. Use the dedicated + * Interactions tool types such as {@code FunctionTool}, {@code GoogleSearchTool}, etc. + */ + @JsonProperty("tools") + public abstract Builder tools(List tools); + + /** + * Setter for tools (varargs convenience method). + * + *

tools: A list of tools the model may use to generate the next response. + */ + @CanIgnoreReturnValue + public Builder tools(Tool... tools) { + return tools(Arrays.asList(tools)); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder tools(Optional> tools); + + /** Clears the value of tools field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearTools() { + return tools(Optional.empty()); + } + + /** Builds the CreateInteractionConfig instance. */ + public abstract CreateInteractionConfig build(); + } + + /** Deserializes a CreateInteractionConfig from a JSON string. */ + @ExcludeFromGeneratedCoverageReport + public static CreateInteractionConfig fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, CreateInteractionConfig.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/CreateInteractionParameters.java b/src/main/java/com/google/genai/types/interactions/CreateInteractionParameters.java new file mode 100644 index 00000000000..891faa1b87e --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/CreateInteractionParameters.java @@ -0,0 +1,95 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.api.core.InternalApi; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Optional; + +/** Parameters for interactions.create method. */ +@AutoValue +@InternalApi +@JsonDeserialize(builder = CreateInteractionParameters.Builder.class) +public abstract class CreateInteractionParameters extends JsonSerializable { + /** Configuration that contains parameters for the interaction. */ + @JsonProperty("config") + public abstract Optional config(); + + /** Instantiates a builder for CreateInteractionParameters. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_CreateInteractionParameters.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for CreateInteractionParameters. */ + @AutoValue.Builder + public abstract static class Builder { + /** + * For internal usage. Please use `CreateInteractionParameters.builder()` for instantiation. + */ + @JsonCreator + private static Builder create() { + return new AutoValue_CreateInteractionParameters.Builder(); + } + + /** + * Setter for config. + * + *

config: Configuration that contains parameters for the interaction. + */ + @JsonProperty("config") + public abstract Builder config(CreateInteractionConfig config); + + /** + * Setter for config builder. + * + *

config: Configuration that contains parameters for the interaction. + */ + @CanIgnoreReturnValue + public Builder config(CreateInteractionConfig.Builder configBuilder) { + return config(configBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder config(Optional config); + + /** Clears the value of config field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearConfig() { + return config(Optional.empty()); + } + + /** Builds the CreateInteractionParameters instance. */ + public abstract CreateInteractionParameters build(); + } + + /** Deserializes a CreateInteractionParameters from a JSON string. */ + @ExcludeFromGeneratedCoverageReport + public static CreateInteractionParameters fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, CreateInteractionParameters.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/DeepResearchAgentConfig.java b/src/main/java/com/google/genai/types/interactions/DeepResearchAgentConfig.java new file mode 100644 index 00000000000..38b08e59a85 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/DeepResearchAgentConfig.java @@ -0,0 +1,119 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Optional; + +/** + * Configuration for the Deep Research agent. + * + *

DeepResearchAgentConfig allows configuration of the Deep Research agent, including control + * over thinking summaries in the response. + * + *

Example usage: + * + *

{@code
+ * // Create with thinking summaries
+ * AgentConfig config = DeepResearchAgentConfig.builder()
+ *     .thinkingSummaries(new ThinkingSummaries(ThinkingSummaries.Known.AUTO))
+ *     .build();
+ *
+ * // Create without thinking summaries
+ * AgentConfig config = DeepResearchAgentConfig.builder()
+ *     .thinkingSummaries(new ThinkingSummaries(ThinkingSummaries.Known.NONE))
+ *     .build();
+ *
+ * CreateInteractionConfig interactionConfig = CreateInteractionConfig.builder()
+ *     .agent("deep-research-pro-preview-12-2025")
+ *     .input("Your research question")
+ *     .agentConfig(config)
+ *     .build();
+ * }
+ */ +@AutoValue +@JsonDeserialize(builder = DeepResearchAgentConfig.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("deep-research") +public abstract class DeepResearchAgentConfig extends JsonSerializable implements AgentConfig { + + /** Instantiates a builder for DeepResearchAgentConfig. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_DeepResearchAgentConfig.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** + * Whether to include thought summaries in response. + * + *

Controls whether the Deep Research agent includes summaries of its thinking process. Use + * AUTO to let the agent decide, or NONE to exclude summaries. + */ + @JsonProperty("thinking_summaries") + public abstract Optional thinkingSummaries(); + + /** Builder for DeepResearchAgentConfig. */ + @AutoValue.Builder + public abstract static class Builder { + /** + * For internal usage. Please use {@code DeepResearchAgentConfig.builder()} for instantiation. + */ + @JsonCreator + private static Builder create() { + return new AutoValue_DeepResearchAgentConfig.Builder(); + } + + /** + * Setter for thinking_summaries. + * + * @param thinkingSummaries Whether to include thought summaries in response + */ + @JsonProperty("thinking_summaries") + public abstract Builder thinkingSummaries(ThinkingSummaries thinkingSummaries); + + /** Internal setter for thinking_summaries with Optional. */ + @ExcludeFromGeneratedCoverageReport + abstract Builder thinkingSummaries(Optional thinkingSummaries); + + /** Clears the value of thinking_summaries field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearThinkingSummaries() { + return thinkingSummaries(Optional.empty()); + } + + /** Builds the DeepResearchAgentConfig instance. */ + public abstract DeepResearchAgentConfig build(); + } + + /** Deserializes a DeepResearchAgentConfig from a JSON string. */ + @ExcludeFromGeneratedCoverageReport + public static DeepResearchAgentConfig fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, DeepResearchAgentConfig.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/DeleteInteractionConfig.java b/src/main/java/com/google/genai/types/interactions/DeleteInteractionConfig.java new file mode 100644 index 00000000000..fc5109cdeb4 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/DeleteInteractionConfig.java @@ -0,0 +1,109 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.HttpOptions; +import java.util.Optional; + +/** + * Configuration for deleting an interaction in the Interactions API. + * + *

Optional parameters for the interactions.delete method. Currently supports HTTP request + * options for customizing the delete operation. + * + *

Example usage: + * + *

{@code
+ * DeleteInteractionConfig config = DeleteInteractionConfig.builder()
+ *     .httpOptions(HttpOptions.builder().timeout(5000).build())
+ *     .build();
+ * client.interactions.delete("interaction-id", config);
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = DeleteInteractionConfig.Builder.class) +public abstract class DeleteInteractionConfig extends JsonSerializable { + /** Used to override HTTP request options. */ + @JsonProperty("httpOptions") + public abstract Optional httpOptions(); + + /** Instantiates a builder for DeleteInteractionConfig. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_DeleteInteractionConfig.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for DeleteInteractionConfig. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use `DeleteInteractionConfig.builder()` for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_DeleteInteractionConfig.Builder(); + } + + /** + * Setter for httpOptions. + * + *

httpOptions: Used to override HTTP request options. + */ + @JsonProperty("httpOptions") + public abstract Builder httpOptions(HttpOptions httpOptions); + + /** + * Setter for httpOptions builder. + * + *

httpOptions: Used to override HTTP request options. + */ + @CanIgnoreReturnValue + public Builder httpOptions(HttpOptions.Builder httpOptionsBuilder) { + return httpOptions(httpOptionsBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder httpOptions(Optional httpOptions); + + /** Clears the value of httpOptions field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearHttpOptions() { + return httpOptions(Optional.empty()); + } + + public abstract DeleteInteractionConfig build(); + } + + /** Deserializes a JSON string to a DeleteInteractionConfig object. */ + @ExcludeFromGeneratedCoverageReport + public static DeleteInteractionConfig fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, DeleteInteractionConfig.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/DeleteInteractionParameters.java b/src/main/java/com/google/genai/types/interactions/DeleteInteractionParameters.java new file mode 100644 index 00000000000..76008c2e39e --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/DeleteInteractionParameters.java @@ -0,0 +1,116 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.api.core.InternalApi; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Optional; + +/** Parameters for interactions.delete method. */ +@AutoValue +@InternalApi +@JsonDeserialize(builder = DeleteInteractionParameters.Builder.class) +public abstract class DeleteInteractionParameters extends JsonSerializable { + /** The ID of the interaction to delete. */ + @JsonProperty("id") + public abstract Optional id(); + + /** Optional parameters for the request. */ + @JsonProperty("config") + public abstract Optional config(); + + /** Instantiates a builder for DeleteInteractionParameters. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_DeleteInteractionParameters.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for DeleteInteractionParameters. */ + @AutoValue.Builder + public abstract static class Builder { + /** + * For internal usage. Please use `DeleteInteractionParameters.builder()` for instantiation. + */ + @JsonCreator + private static Builder create() { + return new AutoValue_DeleteInteractionParameters.Builder(); + } + + /** + * Setter for id. + * + *

id: The ID of the interaction to delete. + */ + @JsonProperty("id") + public abstract Builder id(String id); + + @ExcludeFromGeneratedCoverageReport + abstract Builder id(Optional id); + + /** Clears the value of id field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearId() { + return id(Optional.empty()); + } + + /** + * Setter for config. + * + *

config: Optional parameters for the request. + */ + @JsonProperty("config") + public abstract Builder config(DeleteInteractionConfig config); + + /** + * Setter for config builder. + * + *

config: Optional parameters for the request. + */ + @CanIgnoreReturnValue + public Builder config(DeleteInteractionConfig.Builder configBuilder) { + return config(configBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder config(Optional config); + + /** Clears the value of config field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearConfig() { + return config(Optional.empty()); + } + + public abstract DeleteInteractionParameters build(); + } + + /** Deserializes a JSON string to a DeleteInteractionParameters object. */ + @ExcludeFromGeneratedCoverageReport + public static DeleteInteractionParameters fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, DeleteInteractionParameters.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/DeleteInteractionResponse.java b/src/main/java/com/google/genai/types/interactions/DeleteInteractionResponse.java new file mode 100644 index 00000000000..4fe4df5176a --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/DeleteInteractionResponse.java @@ -0,0 +1,91 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.HttpResponse; +import java.util.Optional; + +/** Empty response for interactions.delete method. */ +@AutoValue +@JsonDeserialize(builder = DeleteInteractionResponse.Builder.class) +public abstract class DeleteInteractionResponse extends JsonSerializable { + /** Used to retain the full HTTP response. */ + @JsonProperty("sdkHttpResponse") + public abstract Optional sdkHttpResponse(); + + /** Instantiates a builder for DeleteInteractionResponse. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_DeleteInteractionResponse.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for DeleteInteractionResponse. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use `DeleteInteractionResponse.builder()` for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_DeleteInteractionResponse.Builder(); + } + + /** + * Setter for sdkHttpResponse. + * + *

sdkHttpResponse: Used to retain the full HTTP response. + */ + @JsonProperty("sdkHttpResponse") + public abstract Builder sdkHttpResponse(HttpResponse sdkHttpResponse); + + /** + * Setter for sdkHttpResponse builder. + * + *

sdkHttpResponse: Used to retain the full HTTP response. + */ + @CanIgnoreReturnValue + public Builder sdkHttpResponse(HttpResponse.Builder sdkHttpResponseBuilder) { + return sdkHttpResponse(sdkHttpResponseBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder sdkHttpResponse(Optional sdkHttpResponse); + + /** Clears the value of sdkHttpResponse field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearSdkHttpResponse() { + return sdkHttpResponse(Optional.empty()); + } + + public abstract DeleteInteractionResponse build(); + } + + /** Deserializes a JSON string to a DeleteInteractionResponse object. */ + @ExcludeFromGeneratedCoverageReport + public static DeleteInteractionResponse fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, DeleteInteractionResponse.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/DocumentMimeType.java b/src/main/java/com/google/genai/types/interactions/DocumentMimeType.java new file mode 100644 index 00000000000..8923db43ed8 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/DocumentMimeType.java @@ -0,0 +1,137 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.base.Ascii; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Objects; + +/** + * MIME type for document content. + * + *

Supports known document MIME types with extensibility for future formats. + */ +public class DocumentMimeType { + + /** Enum representing the known document MIME types. */ + public enum Known { + /** Unspecified or unknown document MIME type. */ + DOCUMENT_MIME_TYPE_UNSPECIFIED("unspecified"), + + /** PDF document format. */ + APPLICATION_PDF("application/pdf"); + + private final String value; + + Known(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + } + + private Known mimeTypeEnum; + private final String value; + + /** + * Constructs a DocumentMimeType from a string value. + * + * @param value The MIME type string (e.g., "application/pdf") + */ + @JsonCreator + public DocumentMimeType(String value) { + this.value = value; + for (Known mimeTypeEnum : Known.values()) { + if (Ascii.equalsIgnoreCase(mimeTypeEnum.toString(), value)) { + this.mimeTypeEnum = mimeTypeEnum; + break; + } + } + if (this.mimeTypeEnum == null) { + this.mimeTypeEnum = Known.DOCUMENT_MIME_TYPE_UNSPECIFIED; + } + } + + /** + * Constructs a DocumentMimeType from a known enum value. + * + * @param knownValue The known MIME type + */ + public DocumentMimeType(Known knownValue) { + this.mimeTypeEnum = knownValue; + this.value = knownValue.toString(); + } + + @ExcludeFromGeneratedCoverageReport + @Override + @JsonValue + public String toString() { + return this.value; + } + + @ExcludeFromGeneratedCoverageReport + @SuppressWarnings("PatternMatchingInstanceof") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + + if (!(o instanceof DocumentMimeType)) { + return false; + } + + DocumentMimeType other = (DocumentMimeType) o; + + if (this.mimeTypeEnum != Known.DOCUMENT_MIME_TYPE_UNSPECIFIED + && other.mimeTypeEnum != Known.DOCUMENT_MIME_TYPE_UNSPECIFIED) { + return this.mimeTypeEnum == other.mimeTypeEnum; + } else if (this.mimeTypeEnum == Known.DOCUMENT_MIME_TYPE_UNSPECIFIED + && other.mimeTypeEnum == Known.DOCUMENT_MIME_TYPE_UNSPECIFIED) { + return this.value.equals(other.value); + } + return false; + } + + @ExcludeFromGeneratedCoverageReport + @Override + public int hashCode() { + if (this.mimeTypeEnum != Known.DOCUMENT_MIME_TYPE_UNSPECIFIED) { + return this.mimeTypeEnum.hashCode(); + } else { + return Objects.hashCode(this.value); + } + } + + /** + * Returns the known enum value if this is a recognized MIME type. + * + * @return The known enum value + */ + @ExcludeFromGeneratedCoverageReport + public Known knownEnum() { + return this.mimeTypeEnum; + } +} diff --git a/src/main/java/com/google/genai/types/interactions/DynamicAgentConfig.java b/src/main/java/com/google/genai/types/interactions/DynamicAgentConfig.java new file mode 100644 index 00000000000..7bdd554291e --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/DynamicAgentConfig.java @@ -0,0 +1,84 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; + +/** + * Configuration for dynamic agents. + * + *

DynamicAgentConfig allows configuration of agents with dynamic behavior. This is a minimal + * configuration type with extensibility support. + * + *

Example usage: + * + *

{@code
+ * AgentConfig config = DynamicAgentConfig.create();
+ *
+ * CreateInteractionConfig interactionConfig = CreateInteractionConfig.builder()
+ *     .agent("agent-name")
+ *     .input("Your input")
+ *     .agentConfig(config)
+ *     .build();
+ * }
+ */ +@AutoValue +@JsonDeserialize(builder = DynamicAgentConfig.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("dynamic") +public abstract class DynamicAgentConfig extends JsonSerializable implements AgentConfig { + + /** Creates a new DynamicAgentConfig instance. */ + @ExcludeFromGeneratedCoverageReport + public static DynamicAgentConfig create() { + return new AutoValue_DynamicAgentConfig.Builder().build(); + } + + /** Instantiates a builder for DynamicAgentConfig. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_DynamicAgentConfig.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for DynamicAgentConfig. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code DynamicAgentConfig.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_DynamicAgentConfig.Builder(); + } + + /** Builds the DynamicAgentConfig instance. */ + public abstract DynamicAgentConfig build(); + } + + /** Deserializes a DynamicAgentConfig from a JSON string. */ + @ExcludeFromGeneratedCoverageReport + public static DynamicAgentConfig fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, DynamicAgentConfig.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/FileSearchResult.java b/src/main/java/com/google/genai/types/interactions/FileSearchResult.java new file mode 100644 index 00000000000..8943accb07b --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/FileSearchResult.java @@ -0,0 +1,103 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Optional; + +@AutoValue +@JsonDeserialize(builder = FileSearchResult.Builder.class) +public abstract class FileSearchResult extends JsonSerializable { + + @JsonProperty("title") + public abstract Optional title(); + + @JsonProperty("text") + public abstract Optional text(); + + @JsonProperty("file_search_store") + public abstract Optional fileSearchStore(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_FileSearchResult.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_FileSearchResult.Builder(); + } + + @JsonProperty("title") + public abstract Builder title(String title); + + @ExcludeFromGeneratedCoverageReport + abstract Builder title(Optional title); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearTitle() { + return title(Optional.empty()); + } + + @JsonProperty("text") + public abstract Builder text(String text); + + @ExcludeFromGeneratedCoverageReport + abstract Builder text(Optional text); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearText() { + return text(Optional.empty()); + } + + @JsonProperty("file_search_store") + public abstract Builder fileSearchStore(String fileSearchStore); + + @ExcludeFromGeneratedCoverageReport + abstract Builder fileSearchStore(Optional fileSearchStore); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearFileSearchStore() { + return fileSearchStore(Optional.empty()); + } + + public abstract FileSearchResult build(); + } + + @ExcludeFromGeneratedCoverageReport + public static FileSearchResult fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, FileSearchResult.class); + } + + @ExcludeFromGeneratedCoverageReport + public static FileSearchResult of(String title, String text, String fileSearchStore) { + return builder().title(title).text(text).fileSearchStore(fileSearchStore).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/GenerationConfig.java b/src/main/java/com/google/genai/types/interactions/GenerationConfig.java new file mode 100644 index 00000000000..e2cd368e698 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/GenerationConfig.java @@ -0,0 +1,353 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * Generation configuration specific to the Interactions API. + * + *

This configuration contains exactly the 10 fields defined in the Interactions API + * specification for the generationConfig parameter. It differs from the general {@code + * GenerationConfig} class which is shared across multiple APIs and has 25+ fields. + * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

The 10 fields are: + * + *

    + *
  1. temperature - Controls randomness of predictions + *
  2. topP - Nucleus sampling parameter + *
  3. seed - Random seed for reproducibility + *
  4. stopSequences - List of sequences that stop generation + *
  5. toolChoice - Controls tool usage behavior + *
  6. thinkingLevel - Controls model thinking depth + *
  7. thinkingSummaries - Controls inclusion of thinking summaries + *
  8. maxOutputTokens - Maximum tokens to generate + *
  9. speechConfig - Configuration for speech generation + *
  10. imageConfig - Configuration for image generation + *
+ */ +@AutoValue +@JsonDeserialize(builder = GenerationConfig.Builder.class) +public abstract class GenerationConfig extends JsonSerializable { + + /** Controls the randomness of predictions. Higher values increase randomness. */ + @JsonProperty("temperature") + public abstract Optional temperature(); + + /** + * Nucleus sampling parameter. Only tokens with cumulative probability up to topP are considered. + */ + @JsonProperty("top_p") + public abstract Optional topP(); + + /** Random seed for reproducible generation. */ + @JsonProperty("seed") + public abstract Optional seed(); + + /** List of sequences that will stop generation when encountered. */ + @JsonProperty("stop_sequences") + public abstract Optional> stopSequences(); + + /** + * Controls tool usage behavior. + * + *

Can be a simple type (auto, any, none, validated) or a configuration object specifying + * allowed tools. + */ + @JsonProperty("tool_choice") + public abstract Optional toolChoice(); + + /** + * Controls the amount of thinking the model performs. + * + *

Supported values: MINIMAL, LOW, MEDIUM, HIGH. + */ + @JsonProperty("thinking_level") + public abstract Optional thinkingLevel(); + + /** + * Controls whether thinking summaries are included in the response. + * + *

Supported values: AUTO, NONE. + */ + @JsonProperty("thinking_summaries") + public abstract Optional thinkingSummaries(); + + /** The maximum number of output tokens to generate. */ + @JsonProperty("max_output_tokens") + public abstract Optional maxOutputTokens(); + + /** Configuration for speech generation (voice, language, speaker). */ + @JsonProperty("speech_config") + public abstract Optional speechConfig(); + + /** Configuration for image generation (aspect ratio, image size). */ + @JsonProperty("image_config") + public abstract Optional imageConfig(); + + /** Instantiates a builder for GenerationConfig. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_GenerationConfig.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for GenerationConfig. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code GenerationConfig.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_GenerationConfig.Builder(); + } + + /** + * Setter for temperature. + * + *

temperature: Controls the randomness of predictions. Higher values increase randomness. + */ + @JsonProperty("temperature") + public abstract Builder temperature(Float temperature); + + @ExcludeFromGeneratedCoverageReport + abstract Builder temperature(Optional temperature); + + /** Clears the value of temperature field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearTemperature() { + return temperature(Optional.empty()); + } + + /** + * Setter for topP. + * + *

topP: Nucleus sampling parameter. Only tokens with cumulative probability up to topP are + * considered. + */ + @JsonProperty("top_p") + public abstract Builder topP(Float topP); + + @ExcludeFromGeneratedCoverageReport + abstract Builder topP(Optional topP); + + /** Clears the value of topP field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearTopP() { + return topP(Optional.empty()); + } + + /** + * Setter for seed. + * + *

seed: Random seed for reproducible generation. + */ + @JsonProperty("seed") + public abstract Builder seed(Integer seed); + + @ExcludeFromGeneratedCoverageReport + abstract Builder seed(Optional seed); + + /** Clears the value of seed field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearSeed() { + return seed(Optional.empty()); + } + + /** + * Setter for stopSequences. + * + *

stopSequences: List of sequences that will stop generation when encountered. + */ + @JsonProperty("stop_sequences") + public abstract Builder stopSequences(List stopSequences); + + /** + * Setter for stopSequences (varargs convenience method). + * + *

stopSequences: List of sequences that will stop generation when encountered. + */ + @CanIgnoreReturnValue + public Builder stopSequences(String... stopSequences) { + return stopSequences(Arrays.asList(stopSequences)); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder stopSequences(Optional> stopSequences); + + /** Clears the value of stopSequences field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearStopSequences() { + return stopSequences(Optional.empty()); + } + + /** + * Setter for toolChoice. + * + *

toolChoice: Controls tool usage behavior. Can be a simple type (auto, any, none, + * validated) or a configuration object specifying allowed tools. + */ + @JsonProperty("tool_choice") + public abstract Builder toolChoice(ToolChoice toolChoice); + + @ExcludeFromGeneratedCoverageReport + abstract Builder toolChoice(Optional toolChoice); + + /** Clears the value of toolChoice field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearToolChoice() { + return toolChoice(Optional.empty()); + } + + /** + * Setter for thinkingLevel. + * + *

thinkingLevel: Controls the amount of thinking the model performs. Supported values: + * MINIMAL, LOW, MEDIUM, HIGH. + */ + @JsonProperty("thinking_level") + public abstract Builder thinkingLevel(ThinkingLevel thinkingLevel); + + @ExcludeFromGeneratedCoverageReport + abstract Builder thinkingLevel(Optional thinkingLevel); + + /** Clears the value of thinkingLevel field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearThinkingLevel() { + return thinkingLevel(Optional.empty()); + } + + /** + * Setter for thinkingSummaries. + * + *

thinkingSummaries: Controls whether thinking summaries are included in the response. + * Supported values: AUTO, NONE. + */ + @JsonProperty("thinking_summaries") + public abstract Builder thinkingSummaries(ThinkingSummaries thinkingSummaries); + + @ExcludeFromGeneratedCoverageReport + abstract Builder thinkingSummaries(Optional thinkingSummaries); + + /** Clears the value of thinkingSummaries field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearThinkingSummaries() { + return thinkingSummaries(Optional.empty()); + } + + /** + * Setter for maxOutputTokens. + * + *

maxOutputTokens: The maximum number of output tokens to generate. + */ + @JsonProperty("max_output_tokens") + public abstract Builder maxOutputTokens(Integer maxOutputTokens); + + @ExcludeFromGeneratedCoverageReport + abstract Builder maxOutputTokens(Optional maxOutputTokens); + + /** Clears the value of maxOutputTokens field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearMaxOutputTokens() { + return maxOutputTokens(Optional.empty()); + } + + /** + * Setter for speechConfig. + * + *

speechConfig: Configuration for speech generation (voice, language, speaker). + */ + @JsonProperty("speech_config") + public abstract Builder speechConfig(SpeechConfig speechConfig); + + /** + * Convenience setter for speechConfig using a builder. + * + *

speechConfig: Configuration for speech generation (voice, language, speaker). + */ + @CanIgnoreReturnValue + public Builder speechConfig(SpeechConfig.Builder speechConfigBuilder) { + return speechConfig(speechConfigBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder speechConfig(Optional speechConfig); + + /** Clears the value of speechConfig field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearSpeechConfig() { + return speechConfig(Optional.empty()); + } + + /** + * Setter for imageConfig. + * + *

imageConfig: Configuration for image generation (aspect ratio, image size). + */ + @JsonProperty("image_config") + public abstract Builder imageConfig(ImageConfig imageConfig); + + /** + * Convenience setter for imageConfig using a builder. + * + *

imageConfig: Configuration for image generation (aspect ratio, image size). + */ + @CanIgnoreReturnValue + public Builder imageConfig(ImageConfig.Builder imageConfigBuilder) { + return imageConfig(imageConfigBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder imageConfig(Optional imageConfig); + + /** Clears the value of imageConfig field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearImageConfig() { + return imageConfig(Optional.empty()); + } + + public abstract GenerationConfig build(); + } + + /** Deserializes a JSON string to an GenerationConfig object. */ + @ExcludeFromGeneratedCoverageReport + public static GenerationConfig fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, GenerationConfig.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/GetInteractionConfig.java b/src/main/java/com/google/genai/types/interactions/GetInteractionConfig.java new file mode 100644 index 00000000000..c0e6890e88c --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/GetInteractionConfig.java @@ -0,0 +1,91 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.HttpOptions; +import java.util.Optional; + +/** Optional parameters for interactions.get method. */ +@AutoValue +@JsonDeserialize(builder = GetInteractionConfig.Builder.class) +public abstract class GetInteractionConfig extends JsonSerializable { + /** Used to override HTTP request options. */ + @JsonProperty("httpOptions") + public abstract Optional httpOptions(); + + /** Instantiates a builder for GetInteractionConfig. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_GetInteractionConfig.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for GetInteractionConfig. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use `GetInteractionConfig.builder()` for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_GetInteractionConfig.Builder(); + } + + /** + * Setter for httpOptions. + * + *

httpOptions: Used to override HTTP request options. + */ + @JsonProperty("httpOptions") + public abstract Builder httpOptions(HttpOptions httpOptions); + + /** + * Setter for httpOptions builder. + * + *

httpOptions: Used to override HTTP request options. + */ + @CanIgnoreReturnValue + public Builder httpOptions(HttpOptions.Builder httpOptionsBuilder) { + return httpOptions(httpOptionsBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder httpOptions(Optional httpOptions); + + /** Clears the value of httpOptions field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearHttpOptions() { + return httpOptions(Optional.empty()); + } + + public abstract GetInteractionConfig build(); + } + + /** Deserializes a JSON string to a GetInteractionConfig object. */ + @ExcludeFromGeneratedCoverageReport + public static GetInteractionConfig fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, GetInteractionConfig.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/GetInteractionParameters.java b/src/main/java/com/google/genai/types/interactions/GetInteractionParameters.java new file mode 100644 index 00000000000..c85b2cd3668 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/GetInteractionParameters.java @@ -0,0 +1,114 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.api.core.InternalApi; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Optional; + +/** Parameters for interactions.get method. */ +@AutoValue +@InternalApi +@JsonDeserialize(builder = GetInteractionParameters.Builder.class) +public abstract class GetInteractionParameters extends JsonSerializable { + /** The ID of the interaction to retrieve. */ + @JsonProperty("id") + public abstract Optional id(); + + /** Optional parameters for the request. */ + @JsonProperty("config") + public abstract Optional config(); + + /** Instantiates a builder for GetInteractionParameters. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_GetInteractionParameters.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for GetInteractionParameters. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use `GetInteractionParameters.builder()` for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_GetInteractionParameters.Builder(); + } + + /** + * Setter for id. + * + *

id: The ID of the interaction to retrieve. + */ + @JsonProperty("id") + public abstract Builder id(String id); + + @ExcludeFromGeneratedCoverageReport + abstract Builder id(Optional id); + + /** Clears the value of id field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearId() { + return id(Optional.empty()); + } + + /** + * Setter for config. + * + *

config: Optional parameters for the request. + */ + @JsonProperty("config") + public abstract Builder config(GetInteractionConfig config); + + /** + * Setter for config builder. + * + *

config: Optional parameters for the request. + */ + @CanIgnoreReturnValue + public Builder config(GetInteractionConfig.Builder configBuilder) { + return config(configBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder config(Optional config); + + /** Clears the value of config field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearConfig() { + return config(Optional.empty()); + } + + public abstract GetInteractionParameters build(); + } + + /** Deserializes a JSON string to a GetInteractionParameters object. */ + @ExcludeFromGeneratedCoverageReport + public static GetInteractionParameters fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, GetInteractionParameters.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/GoogleSearchCallArguments.java b/src/main/java/com/google/genai/types/interactions/GoogleSearchCallArguments.java new file mode 100644 index 00000000000..2db25c19ec4 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/GoogleSearchCallArguments.java @@ -0,0 +1,91 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.List; +import java.util.Optional; + +/** + * Arguments for Google Search tool calls in the Interactions API. + * + *

Specifies the search queries to be executed when the model invokes the Google Search tool. + * + *

Example usage: + * + *

{@code
+ * GoogleSearchCallArguments args = GoogleSearchCallArguments.builder()
+ *     .queries(List.of("latest AI research", "machine learning trends"))
+ *     .build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = GoogleSearchCallArguments.Builder.class) +public abstract class GoogleSearchCallArguments extends JsonSerializable { + + @JsonProperty("queries") + public abstract Optional> queries(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_GoogleSearchCallArguments.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_GoogleSearchCallArguments.Builder(); + } + + @JsonProperty("queries") + public abstract Builder queries(List queries); + + @ExcludeFromGeneratedCoverageReport + abstract Builder queries(Optional> queries); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearQueries() { + return queries(Optional.empty()); + } + + public abstract GoogleSearchCallArguments build(); + } + + @ExcludeFromGeneratedCoverageReport + public static GoogleSearchCallArguments fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, GoogleSearchCallArguments.class); + } + + @ExcludeFromGeneratedCoverageReport + public static GoogleSearchCallArguments of(List queries) { + return builder().queries(queries).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/GoogleSearchResult.java b/src/main/java/com/google/genai/types/interactions/GoogleSearchResult.java new file mode 100644 index 00000000000..8702f1720cf --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/GoogleSearchResult.java @@ -0,0 +1,103 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Optional; + +@AutoValue +@JsonDeserialize(builder = GoogleSearchResult.Builder.class) +public abstract class GoogleSearchResult extends JsonSerializable { + + @JsonProperty("url") + public abstract Optional url(); + + @JsonProperty("title") + public abstract Optional title(); + + @JsonProperty("rendered_content") + public abstract Optional renderedContent(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_GoogleSearchResult.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_GoogleSearchResult.Builder(); + } + + @JsonProperty("url") + public abstract Builder url(String url); + + @ExcludeFromGeneratedCoverageReport + abstract Builder url(Optional url); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearUrl() { + return url(Optional.empty()); + } + + @JsonProperty("title") + public abstract Builder title(String title); + + @ExcludeFromGeneratedCoverageReport + abstract Builder title(Optional title); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearTitle() { + return title(Optional.empty()); + } + + @JsonProperty("rendered_content") + public abstract Builder renderedContent(String renderedContent); + + @ExcludeFromGeneratedCoverageReport + abstract Builder renderedContent(Optional renderedContent); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearRenderedContent() { + return renderedContent(Optional.empty()); + } + + public abstract GoogleSearchResult build(); + } + + @ExcludeFromGeneratedCoverageReport + public static GoogleSearchResult fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, GoogleSearchResult.class); + } + + @ExcludeFromGeneratedCoverageReport + public static GoogleSearchResult of(String url, String title, String renderedContent) { + return builder().url(url).title(title).renderedContent(renderedContent).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/ImageConfig.java b/src/main/java/com/google/genai/types/interactions/ImageConfig.java new file mode 100644 index 00000000000..53c65cdbf9f --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/ImageConfig.java @@ -0,0 +1,179 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Optional; + +/** + * Configuration for image generation in the Interactions API. + * + *

This class controls the visual properties of generated images, including aspect ratio and + * resolution. It is specific to the Interactions API and provides a simplified interface compared + * to image configuration in other APIs. + * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Supported aspect ratios include: + * + *

    + *
  • "1:1" - Square format + *
  • "2:3", "3:2" - Standard photo formats + *
  • "3:4", "4:3" - Traditional screen formats + *
  • "9:16", "16:9" - Mobile and widescreen formats + *
  • "21:9" - Ultra-widescreen format + *
+ * + *

Supported image sizes: "1K", "2K", "4K" + * + *

Example usage: + * + *

{@code
+ * // Configure square images at 2K resolution
+ * ImageConfig imageConfig = ImageConfig.builder()
+ *     .aspectRatio("1:1")
+ *     .imageSize("2K")
+ *     .build();
+ *
+ * // Configure widescreen 4K images
+ * ImageConfig widescreenConfig = ImageConfig.builder()
+ *     .aspectRatio("16:9")
+ *     .imageSize("4K")
+ *     .build();
+ *
+ * // Use in generation config
+ * GenerationConfig config = GenerationConfig.builder()
+ *     .imageConfig(imageConfig)
+ *     .build();
+ * }
+ */ +@AutoValue +@JsonDeserialize(builder = ImageConfig.Builder.class) +public abstract class ImageConfig extends JsonSerializable { + + /** + * Aspect ratio of the generated images. + * + *

Supported values: + * + *

    + *
  • {@code "1:1"} - Square (1:1) + *
  • {@code "2:3"} - Portrait (2:3) + *
  • {@code "3:2"} - Landscape (3:2) + *
  • {@code "3:4"} - Portrait (3:4) + *
  • {@code "4:3"} - Landscape (4:3) + *
  • {@code "4:5"} - Portrait (4:5) + *
  • {@code "5:4"} - Landscape (5:4) + *
  • {@code "9:16"} - Vertical video (9:16) + *
  • {@code "16:9"} - Horizontal video (16:9) + *
  • {@code "21:9"} - Ultra-wide (21:9) + *
+ * + *

This field accepts any string value to support future aspect ratios. + */ + @JsonProperty("aspect_ratio") + public abstract Optional aspectRatio(); + + /** + * Specifies the size of generated images. + * + *

Supported values: + * + *

    + *
  • {@code "1K"} - 1024x1024 pixels (default) + *
  • {@code "2K"} - 2048x2048 pixels + *
  • {@code "4K"} - 4096x4096 pixels + *
+ * + *

If not specified, the model will use default value {@code "1K"}. + * + *

This field accepts any string value to support future image sizes. + */ + @JsonProperty("image_size") + public abstract Optional imageSize(); + + /** Instantiates a builder for ImageConfig. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_ImageConfig.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for ImageConfig. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code ImageConfig.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_ImageConfig.Builder(); + } + + /** + * Setter for aspectRatio. + * + *

aspectRatio: Aspect ratio of the generated images. + * + *

Supported values: "1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9". + */ + @JsonProperty("aspect_ratio") + public abstract Builder aspectRatio(String aspectRatio); + + @ExcludeFromGeneratedCoverageReport + abstract Builder aspectRatio(Optional aspectRatio); + + /** Clears the value of aspectRatio field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearAspectRatio() { + return aspectRatio(Optional.empty()); + } + + /** + * Setter for imageSize. + * + *

imageSize: Size of the generated images. Supported values are "1K", "2K", and "4K". + */ + @JsonProperty("image_size") + public abstract Builder imageSize(String imageSize); + + @ExcludeFromGeneratedCoverageReport + abstract Builder imageSize(Optional imageSize); + + /** Clears the value of imageSize field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearImageSize() { + return imageSize(Optional.empty()); + } + + public abstract ImageConfig build(); + } + + /** Deserializes a JSON string to an ImageConfig object. */ + @ExcludeFromGeneratedCoverageReport + public static ImageConfig fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, ImageConfig.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/ImageMimeType.java b/src/main/java/com/google/genai/types/interactions/ImageMimeType.java new file mode 100644 index 00000000000..c0ea936dbc2 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/ImageMimeType.java @@ -0,0 +1,149 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.base.Ascii; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Objects; + +/** + * MIME type for image content. + * + *

Supports known image MIME types with extensibility for future formats. + */ +public class ImageMimeType { + + /** Enum representing the known image MIME types. */ + public enum Known { + /** Unspecified or unknown image MIME type. */ + IMAGE_MIME_TYPE_UNSPECIFIED("unspecified"), + + /** PNG image format. */ + IMAGE_PNG("image/png"), + + /** JPEG image format. */ + IMAGE_JPEG("image/jpeg"), + + /** WebP image format. */ + IMAGE_WEBP("image/webp"), + + /** HEIC image format. */ + IMAGE_HEIC("image/heic"), + + /** HEIF image format. */ + IMAGE_HEIF("image/heif"); + + private final String value; + + Known(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + } + + private Known mimeTypeEnum; + private final String value; + + /** + * Constructs an ImageMimeType from a string value. + * + * @param value The MIME type string (e.g., "image/png") + */ + @JsonCreator + public ImageMimeType(String value) { + this.value = value; + for (Known mimeTypeEnum : Known.values()) { + if (Ascii.equalsIgnoreCase(mimeTypeEnum.toString(), value)) { + this.mimeTypeEnum = mimeTypeEnum; + break; + } + } + if (this.mimeTypeEnum == null) { + this.mimeTypeEnum = Known.IMAGE_MIME_TYPE_UNSPECIFIED; + } + } + + /** + * Constructs an ImageMimeType from a known enum value. + * + * @param knownValue The known MIME type + */ + public ImageMimeType(Known knownValue) { + this.mimeTypeEnum = knownValue; + this.value = knownValue.toString(); + } + + @ExcludeFromGeneratedCoverageReport + @Override + @JsonValue + public String toString() { + return this.value; + } + + @ExcludeFromGeneratedCoverageReport + @SuppressWarnings("PatternMatchingInstanceof") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + + if (!(o instanceof ImageMimeType)) { + return false; + } + + ImageMimeType other = (ImageMimeType) o; + + if (this.mimeTypeEnum != Known.IMAGE_MIME_TYPE_UNSPECIFIED + && other.mimeTypeEnum != Known.IMAGE_MIME_TYPE_UNSPECIFIED) { + return this.mimeTypeEnum == other.mimeTypeEnum; + } else if (this.mimeTypeEnum == Known.IMAGE_MIME_TYPE_UNSPECIFIED + && other.mimeTypeEnum == Known.IMAGE_MIME_TYPE_UNSPECIFIED) { + return this.value.equals(other.value); + } + return false; + } + + @ExcludeFromGeneratedCoverageReport + @Override + public int hashCode() { + if (this.mimeTypeEnum != Known.IMAGE_MIME_TYPE_UNSPECIFIED) { + return this.mimeTypeEnum.hashCode(); + } else { + return Objects.hashCode(this.value); + } + } + + /** + * Returns the known enum value if this is a recognized MIME type. + * + * @return The known enum value + */ + @ExcludeFromGeneratedCoverageReport + public Known knownEnum() { + return this.mimeTypeEnum; + } +} diff --git a/src/main/java/com/google/genai/types/interactions/Input.java b/src/main/java/com/google/genai/types/interactions/Input.java new file mode 100644 index 00000000000..51ae38ea3cc --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/Input.java @@ -0,0 +1,105 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.genai.JsonSerializable; +import com.google.genai.types.interactions.content.Content; +import java.util.Arrays; +import java.util.List; + +/** + * Union type for interaction input. Can be a string, list of Content objects, or list of Turn + * objects. + */ +@JsonSerialize(using = InputSerializer.class) +public final class Input extends JsonSerializable { + private final Object value; + + private Input(Object value) { + this.value = value; + } + + /** + * Creates an Input from a string. + * + * @param text The input text + * @return An Input instance wrapping the string + */ + public static Input fromString(String text) { + return new Input(text); + } + + /** + * Creates an Input from a single Content object. + * + * @param content The interaction content object + * @return An Input instance wrapping the content + */ + public static Input fromContent(Content content) { + return new Input(content); + } + + /** + * Creates an Input from a list of Content objects. + * + * @param contents The list of interaction content objects + * @return An Input instance wrapping the contents + */ + public static Input fromContents(List contents) { + return new Input(contents); + } + + /** + * Creates an Input from Content objects (varargs). + * + * @param contents The interaction content objects + * @return An Input instance wrapping the contents + */ + public static Input fromContents(Content... contents) { + return new Input(Arrays.asList(contents)); + } + + /** + * Creates an Input from a list of Turn objects. + * + * @param turns The list of conversation turns + * @return An Input instance wrapping the turns + */ + public static Input fromTurns(List turns) { + return new Input(turns); + } + + /** + * Creates an Input from Turn objects (varargs). + * + * @param turns The conversation turns + * @return An Input instance wrapping the turns + */ + public static Input fromTurns(Turn... turns) { + return new Input(Arrays.asList(turns)); + } + + /** + * Gets the underlying value. + * + * @return The wrapped value (String, List<Content>, or List<Turn>) + */ + public Object getValue() { + return value; + } +} diff --git a/src/main/java/com/google/genai/types/interactions/InputSerializer.java b/src/main/java/com/google/genai/types/interactions/InputSerializer.java new file mode 100644 index 00000000000..5ffa4409865 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/InputSerializer.java @@ -0,0 +1,64 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.google.genai.types.interactions.content.Content; +import java.io.IOException; +import java.util.List; + +/** + * Custom serializer for Input that ensures proper polymorphic type information is included when + * serializing lists of Content or Turn objects. + * + *

This is necessary because the @JsonValue annotation on a field typed as Object loses type + * information for contained elements. This serializer explicitly handles the different cases + * (String, List of Content, List of Turn) and ensures that polymorphic content types include their + * "type" property. + */ +public class InputSerializer extends JsonSerializer { + + @Override + public void serialize(Input value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + Object innerValue = value.getValue(); + + if (innerValue == null) { + gen.writeNull(); + } else if (innerValue instanceof String) { + // String input - write directly + gen.writeString((String) innerValue); + } else if (innerValue instanceof Content) { + // Single Content - serialize with type info + serializers.defaultSerializeValue(innerValue, gen); + } else if (innerValue instanceof List) { + // List input - serialize with proper type handling + List list = (List) innerValue; + gen.writeStartArray(); + for (Object item : list) { + // Use the default serializer which respects @JsonTypeInfo on the item's class + serializers.defaultSerializeValue(item, gen); + } + gen.writeEndArray(); + } else { + // Fallback for unknown types + serializers.defaultSerializeValue(innerValue, gen); + } + } +} diff --git a/src/main/java/com/google/genai/types/interactions/Interaction.java b/src/main/java/com/google/genai/types/interactions/Interaction.java new file mode 100644 index 00000000000..de37f991351 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/Interaction.java @@ -0,0 +1,380 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.HttpResponse; +import com.google.genai.types.interactions.content.Content; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * Represents an interaction with a model or agent. + * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = Interaction.Builder.class) +public abstract class Interaction extends JsonSerializable { + /** + * Unique identifier for the interaction. + * + *

This field is always present in API responses and is required. + */ + @JsonProperty("id") + public abstract String id(); + + /** + * The status of the interaction. + * + *

This field is always present in API responses and is required. + */ + @JsonProperty("status") + public abstract InteractionStatus status(); + + /** The agent identifier (e.g., "deep-research-pro-preview-12-2025"). */ + @JsonProperty("agent") + public abstract Optional agent(); + + /** The model used for the interaction. */ + @JsonProperty("model") + public abstract Optional model(); + + /** + * The output content from the interaction. + * + *

Note: Outputs use Content (discriminated union with type field), not the standard Content + * type with parts. + */ + @JsonProperty("outputs") + public abstract Optional> outputs(); + + /** The ID of the previous interaction for conversation continuity. */ + @JsonProperty("previous_interaction_id") + public abstract Optional previousInteractionId(); + + /** The role in the conversation. */ + @JsonProperty("role") + public abstract Optional role(); + + /** The creation timestamp. */ + @JsonProperty("created") + public abstract Optional created(); + + /** The last update timestamp. */ + @JsonProperty("updated") + public abstract Optional updated(); + + /** Token usage statistics for the interaction. */ + @JsonProperty("usage") + public abstract Optional usage(); + + /** Used to retain the full HTTP response. */ + @JsonProperty("sdkHttpResponse") + public abstract Optional sdkHttpResponse(); + + /** Instantiates a builder for Interaction. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_Interaction.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for Interaction. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use `Interaction.builder()` for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_Interaction.Builder(); + } + + /** + * Setter for id. + * + *

id: Unique identifier for the interaction. This field is required. + */ + @JsonProperty("id") + public abstract Builder id(String id); + + /** + * Setter for status. + * + *

status: The status of the interaction. This field is required. + */ + @JsonProperty("status") + public abstract Builder status(InteractionStatus status); + + /** + * Setter for agent. + * + *

agent: The agent identifier. + */ + @JsonProperty("agent") + public abstract Builder agent(String agent); + + /** Internal setter for agent with Optional. */ + @ExcludeFromGeneratedCoverageReport + abstract Builder agent(Optional agent); + + /** + * Clear method for agent. + * + *

Removes the agent field. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearAgent() { + return agent(Optional.empty()); + } + + /** + * Setter for model. + * + *

model: The model used for the interaction. + */ + @JsonProperty("model") + public abstract Builder model(String model); + + /** Internal setter for model with Optional. */ + @ExcludeFromGeneratedCoverageReport + abstract Builder model(Optional model); + + /** + * Clear method for model. + * + *

Removes the model field. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearModel() { + return model(Optional.empty()); + } + + /** + * Setter for outputs. + * + *

outputs: The output content from the interaction. + */ + @JsonProperty("outputs") + public abstract Builder outputs(List outputs); + + /** + * Setter for outputs (varargs convenience method). + * + *

outputs: The output content from the interaction. + */ + @CanIgnoreReturnValue + public Builder outputs(Content... outputs) { + return outputs(Arrays.asList(outputs)); + } + + /** Internal setter for outputs with Optional. */ + @ExcludeFromGeneratedCoverageReport + abstract Builder outputs(Optional> outputs); + + /** + * Clear method for outputs. + * + *

Removes the outputs field. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearOutputs() { + return outputs(Optional.empty()); + } + + /** + * Setter for previousInteractionId. + * + *

previousInteractionId: The ID of the previous interaction for conversation continuity. + */ + @JsonProperty("previous_interaction_id") + public abstract Builder previousInteractionId(String previousInteractionId); + + /** Internal setter for previousInteractionId with Optional. */ + @ExcludeFromGeneratedCoverageReport + abstract Builder previousInteractionId(Optional previousInteractionId); + + /** + * Clear method for previousInteractionId. + * + *

Removes the previousInteractionId field. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearPreviousInteractionId() { + return previousInteractionId(Optional.empty()); + } + + /** + * Setter for role. + * + *

role: The role in the conversation. + */ + @JsonProperty("role") + public abstract Builder role(String role); + + /** Internal setter for role with Optional. */ + @ExcludeFromGeneratedCoverageReport + abstract Builder role(Optional role); + + /** + * Clear method for role. + * + *

Removes the role field. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearRole() { + return role(Optional.empty()); + } + + /** + * Setter for created. + * + *

created: The creation timestamp. + */ + @JsonProperty("created") + public abstract Builder created(Instant created); + + /** Internal setter for created with Optional. */ + @ExcludeFromGeneratedCoverageReport + abstract Builder created(Optional created); + + /** + * Clear method for created. + * + *

Removes the created field. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearCreated() { + return created(Optional.empty()); + } + + /** + * Setter for updated. + * + *

updated: The last update timestamp. + */ + @JsonProperty("updated") + public abstract Builder updated(Instant updated); + + /** Internal setter for updated with Optional. */ + @ExcludeFromGeneratedCoverageReport + abstract Builder updated(Optional updated); + + /** + * Clear method for updated. + * + *

Removes the updated field. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearUpdated() { + return updated(Optional.empty()); + } + + /** + * Setter for usage. + * + *

usage: Token usage statistics for the interaction. + */ + @JsonProperty("usage") + public abstract Builder usage(Usage usage); + + /** + * Setter for usage builder. + * + *

usage: Token usage statistics for the interaction. + */ + @CanIgnoreReturnValue + public Builder usage(Usage.Builder usageBuilder) { + return usage(usageBuilder.build()); + } + + /** Internal setter for usage with Optional. */ + @ExcludeFromGeneratedCoverageReport + abstract Builder usage(Optional usage); + + /** + * Clear method for usage. + * + *

Removes the usage field. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearUsage() { + return usage(Optional.empty()); + } + + /** + * Setter for sdkHttpResponse. + * + *

sdkHttpResponse: Used to retain the full HTTP response. + */ + @JsonProperty("sdkHttpResponse") + public abstract Builder sdkHttpResponse(HttpResponse sdkHttpResponse); + + /** + * Setter for sdkHttpResponse builder. + * + *

sdkHttpResponse: Used to retain the full HTTP response. + */ + @CanIgnoreReturnValue + public Builder sdkHttpResponse(HttpResponse.Builder sdkHttpResponseBuilder) { + return sdkHttpResponse(sdkHttpResponseBuilder.build()); + } + + /** Internal setter for sdkHttpResponse with Optional. */ + @ExcludeFromGeneratedCoverageReport + abstract Builder sdkHttpResponse(Optional sdkHttpResponse); + + /** + * Clear method for sdkHttpResponse. + * + *

Removes the sdkHttpResponse field. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearSdkHttpResponse() { + return sdkHttpResponse(Optional.empty()); + } + + /** Builds the Interaction instance. */ + public abstract Interaction build(); + } + + /** Deserializes an Interaction from a JSON string. */ + @ExcludeFromGeneratedCoverageReport + public static Interaction fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, Interaction.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/InteractionStatus.java b/src/main/java/com/google/genai/types/interactions/InteractionStatus.java new file mode 100644 index 00000000000..e8ee842620c --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/InteractionStatus.java @@ -0,0 +1,42 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** Represents the status of an interaction. */ +public enum InteractionStatus { + /** The interaction is currently in progress. */ + @JsonProperty("in_progress") + IN_PROGRESS, + + /** The interaction requires user action to continue. */ + @JsonProperty("requires_action") + REQUIRES_ACTION, + + /** The interaction has completed successfully. */ + @JsonProperty("completed") + COMPLETED, + + /** The interaction has failed. */ + @JsonProperty("failed") + FAILED, + + /** The interaction has been cancelled. */ + @JsonProperty("cancelled") + CANCELLED +} diff --git a/src/main/java/com/google/genai/types/interactions/MediaResolution.java b/src/main/java/com/google/genai/types/interactions/MediaResolution.java new file mode 100644 index 00000000000..104a6225114 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/MediaResolution.java @@ -0,0 +1,161 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.base.Ascii; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Objects; + +/** + * The resolution of the media (images and videos). + * + *

Controls the quality/resolution of media content in interactions. + * + *

This class supports both known resolution values via the {@link Known} enum and unknown/custom + * values via the string constructor. Known values are serialized to lowercase JSON strings (e.g., + * "low", "medium", "high", "ultra_high"). + * + *

Example usage: + * + *

{@code
+ * // Using Known enum values
+ * MediaResolution high = new MediaResolution(MediaResolution.Known.HIGH);
+ * MediaResolution low = new MediaResolution(MediaResolution.Known.LOW);
+ *
+ * // Using string for unknown/custom values
+ * MediaResolution custom = new MediaResolution("custom_resolution");
+ * }
+ */ +public class MediaResolution { + + /** Enum representing the known values for MediaResolution. */ + public enum Known { + /** Unspecified or unknown media resolution. */ + MEDIA_RESOLUTION_UNSPECIFIED("unspecified"), + + /** Low resolution. */ + LOW("low"), + + /** Medium resolution. */ + MEDIUM("medium"), + + /** High resolution. */ + HIGH("high"), + + /** Ultra high resolution. */ + ULTRA_HIGH("ultra_high"); + + private final String value; + + Known(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + } + + private Known resolutionEnum; + private final String value; + + /** + * Constructs a MediaResolution from a string value. + * + * @param value The resolution string (e.g., "high") + */ + @JsonCreator + public MediaResolution(String value) { + this.value = value; + for (Known resolutionEnum : Known.values()) { + if (Ascii.equalsIgnoreCase(resolutionEnum.toString(), value)) { + this.resolutionEnum = resolutionEnum; + break; + } + } + if (this.resolutionEnum == null) { + this.resolutionEnum = Known.MEDIA_RESOLUTION_UNSPECIFIED; + } + } + + /** + * Constructs a MediaResolution from a known enum value. + * + * @param knownValue The known resolution value + */ + public MediaResolution(Known knownValue) { + this.resolutionEnum = knownValue; + this.value = knownValue.toString(); + } + + @ExcludeFromGeneratedCoverageReport + @Override + @JsonValue + public String toString() { + return this.value; + } + + @ExcludeFromGeneratedCoverageReport + @SuppressWarnings("PatternMatchingInstanceof") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + + if (!(o instanceof MediaResolution)) { + return false; + } + + MediaResolution other = (MediaResolution) o; + + if (this.resolutionEnum != Known.MEDIA_RESOLUTION_UNSPECIFIED + && other.resolutionEnum != Known.MEDIA_RESOLUTION_UNSPECIFIED) { + return this.resolutionEnum == other.resolutionEnum; + } else if (this.resolutionEnum == Known.MEDIA_RESOLUTION_UNSPECIFIED + && other.resolutionEnum == Known.MEDIA_RESOLUTION_UNSPECIFIED) { + return this.value.equals(other.value); + } + return false; + } + + @ExcludeFromGeneratedCoverageReport + @Override + public int hashCode() { + if (this.resolutionEnum != Known.MEDIA_RESOLUTION_UNSPECIFIED) { + return this.resolutionEnum.hashCode(); + } else { + return Objects.hashCode(this.value); + } + } + + /** + * Returns the known enum value if this is a recognized resolution. + * + * @return The known enum value + */ + @ExcludeFromGeneratedCoverageReport + public Known knownEnum() { + return this.resolutionEnum; + } +} diff --git a/src/main/java/com/google/genai/types/interactions/ModalityTokens.java b/src/main/java/com/google/genai/types/interactions/ModalityTokens.java new file mode 100644 index 00000000000..bc108d87edc --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/ModalityTokens.java @@ -0,0 +1,121 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Optional; + +/** + * Token count for a specific modality in the Interactions API. + * + *

This class represents the number of tokens used for a particular modality (such as text, + * image, or audio) in an interaction. + * + *

Example usage: + * + *

{@code
+ * ModalityTokens tokenCount = ModalityTokens.builder()
+ *     .modality(new ResponseModality(ResponseModality.Known.TEXT))
+ *     .tokens(100)
+ *     .build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = ModalityTokens.Builder.class) +public abstract class ModalityTokens extends JsonSerializable { + + /** The modality associated with the token count. */ + @JsonProperty("modality") + public abstract Optional modality(); + + /** Number of tokens for the modality. */ + @JsonProperty("tokens") + public abstract Optional tokens(); + + /** Instantiates a builder for ModalityTokens. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_ModalityTokens.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for ModalityTokens. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code ModalityTokens.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_ModalityTokens.Builder(); + } + + /** + * Setter for modality. + * + *

modality: The modality associated with the token count. + */ + @JsonProperty("modality") + public abstract Builder modality(ResponseModality modality); + + @ExcludeFromGeneratedCoverageReport + abstract Builder modality(Optional modality); + + /** Clears the value of modality field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearModality() { + return modality(Optional.empty()); + } + + /** + * Setter for tokens. + * + *

tokens: Number of tokens for the modality. + */ + @JsonProperty("tokens") + public abstract Builder tokens(Integer tokens); + + @ExcludeFromGeneratedCoverageReport + abstract Builder tokens(Optional tokens); + + /** Clears the value of tokens field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearTokens() { + return tokens(Optional.empty()); + } + + public abstract ModalityTokens build(); + } + + /** Deserializes a JSON string to a ModalityTokens object. */ + @ExcludeFromGeneratedCoverageReport + public static ModalityTokens fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, ModalityTokens.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/ResponseModality.java b/src/main/java/com/google/genai/types/interactions/ResponseModality.java new file mode 100644 index 00000000000..02ba152ebf3 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/ResponseModality.java @@ -0,0 +1,158 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.base.Ascii; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Objects; + +/** + * The modality of the response content. + * + *

Controls the type of content that the model can return in the response. + * + *

This class supports both known modality values via the {@link Known} enum and unknown/custom + * values via the string constructor. Known values are serialized to lowercase JSON strings (e.g., + * "text", "image", "audio"). + * + *

Example usage: + * + *

{@code
+ * // Using Known enum values
+ * ResponseModality text = new ResponseModality(ResponseModality.Known.TEXT);
+ * ResponseModality image = new ResponseModality(ResponseModality.Known.IMAGE);
+ *
+ * // Using string for unknown/custom values
+ * ResponseModality custom = new ResponseModality("custom_modality");
+ * }
+ */ +public class ResponseModality { + + /** Enum representing the known values for ResponseModality. */ + public enum Known { + /** Unspecified or unknown response modality. */ + RESPONSE_MODALITY_UNSPECIFIED("unspecified"), + + /** Text content. */ + TEXT("text"), + + /** Image content. */ + IMAGE("image"), + + /** Audio content. */ + AUDIO("audio"); + + private final String value; + + Known(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + } + + private Known modalityEnum; + private final String value; + + /** + * Constructs a ResponseModality from a string value. + * + * @param value The modality string (e.g., "text") + */ + @JsonCreator + public ResponseModality(String value) { + this.value = value; + for (Known modalityEnum : Known.values()) { + if (Ascii.equalsIgnoreCase(modalityEnum.toString(), value)) { + this.modalityEnum = modalityEnum; + break; + } + } + if (this.modalityEnum == null) { + this.modalityEnum = Known.RESPONSE_MODALITY_UNSPECIFIED; + } + } + + /** + * Constructs a ResponseModality from a known enum value. + * + * @param knownValue The known modality value + */ + public ResponseModality(Known knownValue) { + this.modalityEnum = knownValue; + this.value = knownValue.toString(); + } + + @ExcludeFromGeneratedCoverageReport + @Override + @JsonValue + public String toString() { + return this.value; + } + + @ExcludeFromGeneratedCoverageReport + @SuppressWarnings("PatternMatchingInstanceof") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + + if (!(o instanceof ResponseModality)) { + return false; + } + + ResponseModality other = (ResponseModality) o; + + if (this.modalityEnum != Known.RESPONSE_MODALITY_UNSPECIFIED + && other.modalityEnum != Known.RESPONSE_MODALITY_UNSPECIFIED) { + return this.modalityEnum == other.modalityEnum; + } else if (this.modalityEnum == Known.RESPONSE_MODALITY_UNSPECIFIED + && other.modalityEnum == Known.RESPONSE_MODALITY_UNSPECIFIED) { + return this.value.equals(other.value); + } + return false; + } + + @ExcludeFromGeneratedCoverageReport + @Override + public int hashCode() { + if (this.modalityEnum != Known.RESPONSE_MODALITY_UNSPECIFIED) { + return this.modalityEnum.hashCode(); + } else { + return Objects.hashCode(this.value); + } + } + + /** + * Returns the known enum value if this is a recognized modality. + * + * @return The known enum value + */ + @ExcludeFromGeneratedCoverageReport + public Known knownEnum() { + return this.modalityEnum; + } +} diff --git a/src/main/java/com/google/genai/types/interactions/ResultItems.java b/src/main/java/com/google/genai/types/interactions/ResultItems.java new file mode 100644 index 00000000000..6342c361d30 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/ResultItems.java @@ -0,0 +1,123 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * Represents a structured result containing an array of items. + * + *

This type is used for function and MCP tool results that return structured data. Each item in + * the array can be a string, an ImageContent object, or any other object. + * + *

This matches the Python SDK's ResultItems type which is defined as: + * + *

+ * class ResultItems(BaseModel):
+ *     items: Optional[List[ResultItemsItem]] = None
+ *
+ * where ResultItemsItem = Union[str, ImageContent, object]
+ * 
+ */ +@AutoValue +@JsonDeserialize(builder = ResultItems.Builder.class) +public abstract class ResultItems extends JsonSerializable { + + /** + * The list of result items. + * + *

Each item can be a String, an ImageContent, or any other Object. + */ + @JsonProperty("items") + public abstract Optional> items(); + + /** Instantiates a builder for ResultItems. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_ResultItems.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for ResultItems. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code ResultItems.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_ResultItems.Builder(); + } + + /** + * Setter for items. + * + *

items: The list of result items. + */ + @JsonProperty("items") + public abstract Builder items(List items); + + /** + * Setter for items (varargs convenience method). + * + *

items: The list of result items. + */ + @CanIgnoreReturnValue + public Builder items(Object... items) { + return items(Arrays.asList(items)); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder items(Optional> items); + + /** Clears the value of items field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearItems() { + return items(Optional.empty()); + } + + public abstract ResultItems build(); + } + + /** Deserializes a JSON string to a ResultItems object. */ + @ExcludeFromGeneratedCoverageReport + public static ResultItems fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, ResultItems.class); + } + + /** Convenience factory method. */ + @ExcludeFromGeneratedCoverageReport + public static ResultItems of(List items) { + return builder().items(items).build(); + } + + /** Convenience factory method (varargs). */ + @ExcludeFromGeneratedCoverageReport + public static ResultItems of(Object... items) { + return builder().items(Arrays.asList(items)).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/SpeechConfig.java b/src/main/java/com/google/genai/types/interactions/SpeechConfig.java new file mode 100644 index 00000000000..7f77ea9e8f4 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/SpeechConfig.java @@ -0,0 +1,155 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Optional; + +/** + * Configuration for speech generation in the Interactions API. + * + *

This class controls how the model generates speech output, including voice selection, language + * preferences, and speaker identification for multi-speaker scenarios. It is specific to the + * Interactions API and has a simpler structure than speech configuration classes used in other + * APIs. + * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Example usage: + * + *

{@code
+ * // Configure speech with all options
+ * SpeechConfig speechConfig = SpeechConfig.builder()
+ *     .voice("en-US-Studio-O")
+ *     .language("en-US")
+ *     .speaker("speaker1")
+ *     .build();
+ *
+ * // Use in generation config
+ * GenerationConfig config = GenerationConfig.builder()
+ *     .speechConfig(speechConfig)
+ *     .build();
+ *
+ * // Or use builder convenience method
+ * GenerationConfig config2 = GenerationConfig.builder()
+ *     .speechConfig(SpeechConfig.builder().voice("en-GB-Studio-B"))
+ *     .build();
+ * }
+ */ +@AutoValue +@JsonDeserialize(builder = SpeechConfig.Builder.class) +public abstract class SpeechConfig extends JsonSerializable { + + /** The voice to use for speech generation. */ + @JsonProperty("voice") + public abstract Optional voice(); + + /** The language code for speech generation (e.g., "en-US"). */ + @JsonProperty("language") + public abstract Optional language(); + + /** The speaker identifier for multi-speaker scenarios. */ + @JsonProperty("speaker") + public abstract Optional speaker(); + + /** Instantiates a builder for SpeechConfig. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_SpeechConfig.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for SpeechConfig. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code SpeechConfig.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_SpeechConfig.Builder(); + } + + /** + * Setter for voice. + * + *

voice: The voice to use for speech generation. + */ + @JsonProperty("voice") + public abstract Builder voice(String voice); + + @ExcludeFromGeneratedCoverageReport + abstract Builder voice(Optional voice); + + /** Clears the value of voice field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearVoice() { + return voice(Optional.empty()); + } + + /** + * Setter for language. + * + *

language: The language code for speech generation (e.g., "en-US"). + */ + @JsonProperty("language") + public abstract Builder language(String language); + + @ExcludeFromGeneratedCoverageReport + abstract Builder language(Optional language); + + /** Clears the value of language field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearLanguage() { + return language(Optional.empty()); + } + + /** + * Setter for speaker. + * + *

speaker: The speaker identifier for multi-speaker scenarios. + */ + @JsonProperty("speaker") + public abstract Builder speaker(String speaker); + + @ExcludeFromGeneratedCoverageReport + abstract Builder speaker(Optional speaker); + + /** Clears the value of speaker field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearSpeaker() { + return speaker(Optional.empty()); + } + + public abstract SpeechConfig build(); + } + + /** Deserializes a JSON string to an SpeechConfig object. */ + @ExcludeFromGeneratedCoverageReport + public static SpeechConfig fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, SpeechConfig.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/ThinkingLevel.java b/src/main/java/com/google/genai/types/interactions/ThinkingLevel.java new file mode 100644 index 00000000000..8efb55a9873 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/ThinkingLevel.java @@ -0,0 +1,215 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.base.Ascii; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Objects; + +/** + * Controls the depth and extent of the model's reasoning process for the Interactions API. + * + *

The thinking level determines how many internal reasoning tokens the model should generate + * before producing its final response. Higher thinking levels allow the model to engage in more + * thorough analysis, exploration of alternative approaches, and step-by-step reasoning, which can + * lead to more accurate and well-reasoned responses for complex tasks. + * + *

This class is specific to the Interactions API and serializes enum values to lowercase (e.g., + * "low", "medium", "high") per the OpenAPI specification. The {@code Known} enum contains all + * recognized values from the OpenAPI specification, and {@code THINKING_LEVEL_UNSPECIFIED} serves + * as a fallback for forward compatibility. + * + *

Example usage: + * + *

{@code
+ * // For simple tasks, use minimal thinking
+ * ThinkingLevel level = new ThinkingLevel(ThinkingLevel.Known.MINIMAL);
+ *
+ * // For complex reasoning tasks, use high thinking
+ * ThinkingLevel level = new ThinkingLevel(ThinkingLevel.Known.HIGH);
+ *
+ * // Include in generation config
+ * GenerationConfig config = GenerationConfig.builder()
+ *     .thinkingLevel(new ThinkingLevel(ThinkingLevel.Known.MEDIUM))
+ *     .build();
+ * }
+ */ +public class ThinkingLevel { + + /** + * Enum representing the known values for ThinkingLevel. + * + *

These values control the amount of internal reasoning the model performs before generating + * its response. + */ + public enum Known { + /** + * Unspecified thinking level. + * + *

This is the fallback value used when the API returns an unknown thinking level not + * recognized by this version of the SDK. This ensures forward compatibility when new thinking + * levels are added to the API. + */ + THINKING_LEVEL_UNSPECIFIED("unspecified"), + + /** + * Low thinking level. + * + *

The model performs a limited amount of internal reasoning. Use this for tasks that + * benefit from some deliberation but don't require extensive analysis. Generates fewer + * thinking tokens than MEDIUM or HIGH. + */ + LOW("low"), + + /** + * Medium thinking level. + * + *

The model performs a moderate amount of internal reasoning. This is a balanced option + * suitable for tasks of average complexity that benefit from careful consideration. Generates + * more thinking tokens than LOW but fewer than HIGH. + */ + MEDIUM("medium"), + + /** + * High thinking level. + * + *

The model performs extensive internal reasoning. Use this for complex tasks that require + * deep analysis, multi-step reasoning, or exploration of multiple approaches. Generates the + * most thinking tokens, which may increase latency but can significantly improve response + * quality for challenging problems. + */ + HIGH("high"), + + /** + * Minimal thinking level. + * + *

The model performs the least amount of internal reasoning. Use this for simple, + * straightforward tasks where immediate responses are preferred and extensive deliberation + * would not improve the output quality. Generates the fewest thinking tokens. + */ + MINIMAL("minimal"); + + private final String value; + + Known(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + } + + private Known thinkingLevelEnum; + private final String value; + + /** + * Creates a ThinkingLevel from a string value. + * + *

This constructor is used by Jackson during JSON deserialization. It attempts to match the + * provided string value to a known enum value (case-insensitive). If no match is found, it falls + * back to {@code THINKING_LEVEL_UNSPECIFIED}. + * + * @param value the string representation of the thinking level + */ + @JsonCreator + public ThinkingLevel(String value) { + this.value = value; + for (Known thinkingLevelEnum : Known.values()) { + if (Ascii.equalsIgnoreCase(thinkingLevelEnum.toString(), value)) { + this.thinkingLevelEnum = thinkingLevelEnum; + break; + } + } + if (this.thinkingLevelEnum == null) { + this.thinkingLevelEnum = Known.THINKING_LEVEL_UNSPECIFIED; + } + } + + /** + * Creates a ThinkingLevel from a known enum value. + * + *

This is the recommended constructor for creating ThinkingLevel instances in application + * code. + * + * @param knownValue the known thinking level enum value + */ + public ThinkingLevel(Known knownValue) { + this.thinkingLevelEnum = knownValue; + this.value = knownValue.toString(); + } + + @ExcludeFromGeneratedCoverageReport + @Override + @JsonValue + public String toString() { + return this.value; + } + + @ExcludeFromGeneratedCoverageReport + @SuppressWarnings("PatternMatchingInstanceof") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + + if (!(o instanceof ThinkingLevel)) { + return false; + } + + ThinkingLevel other = (ThinkingLevel) o; + + if (this.thinkingLevelEnum != Known.THINKING_LEVEL_UNSPECIFIED + && other.thinkingLevelEnum != Known.THINKING_LEVEL_UNSPECIFIED) { + return this.thinkingLevelEnum == other.thinkingLevelEnum; + } else if (this.thinkingLevelEnum == Known.THINKING_LEVEL_UNSPECIFIED + && other.thinkingLevelEnum == Known.THINKING_LEVEL_UNSPECIFIED) { + return this.value.equals(other.value); + } + return false; + } + + @ExcludeFromGeneratedCoverageReport + @Override + public int hashCode() { + if (this.thinkingLevelEnum != Known.THINKING_LEVEL_UNSPECIFIED) { + return this.thinkingLevelEnum.hashCode(); + } else { + return Objects.hashCode(this.value); + } + } + + /** + * Returns the known enum value for this thinking level. + * + *

If the value was not recognized during deserialization, this will return {@code + * THINKING_LEVEL_UNSPECIFIED}. + * + * @return the known enum value + */ + @ExcludeFromGeneratedCoverageReport + public Known knownEnum() { + return this.thinkingLevelEnum; + } +} diff --git a/src/main/java/com/google/genai/types/interactions/ThinkingSummaries.java b/src/main/java/com/google/genai/types/interactions/ThinkingSummaries.java new file mode 100644 index 00000000000..317cb2fd0fe --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/ThinkingSummaries.java @@ -0,0 +1,196 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.base.Ascii; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Objects; + +/** + * Controls whether thinking summaries are included in the response. + * + *

When the model uses thinking tokens (controlled by {@link ThinkingLevel}), it can optionally + * provide summaries of its reasoning + * process. These summaries give insight into how the model arrived at its response, which can be + * valuable for understanding the model's decision-making process, debugging unexpected outputs, or + * building trust in the model's reasoning. + * + *

This class follows the standard enum wrapper pattern used throughout the codebase. The {@code + * Known} enum contains all recognized values from the OpenAPI specification, and {@code + * THINKING_SUMMARIES_UNSPECIFIED} serves as a fallback for forward compatibility. + * + *

Example usage: + * + *

{@code
+ * // Automatically include thinking summaries when appropriate
+ * ThinkingSummaries summaries = new ThinkingSummaries(ThinkingSummaries.Known.AUTO);
+ *
+ * // Exclude thinking summaries from the response
+ * ThinkingSummaries summaries = new ThinkingSummaries(ThinkingSummaries.Known.NONE);
+ *
+ * // Include in generation config with thinking level
+ * GenerationConfig config = GenerationConfig.builder()
+ *     .thinkingLevel(new ThinkingLevel(ThinkingLevel.Known.HIGH))
+ *     .thinkingSummaries(new ThinkingSummaries(ThinkingSummaries.Known.AUTO))
+ *     .build();
+ * }
+ */ +public class ThinkingSummaries { + + /** + * Enum representing the known values for ThinkingSummaries. + * + *

These values control whether the model's reasoning process is summarized in the response. + */ + public enum Known { + /** + * Unspecified thinking summaries mode. + * + *

This is the fallback value used when the API returns an unknown thinking summaries mode + * not recognized by this version of the SDK. This ensures forward compatibility when new modes + * are added to the API. + */ + THINKING_SUMMARIES_UNSPECIFIED("unspecified"), + + /** + * Automatically include thinking summaries when appropriate. + * + *

In AUTO mode, the model determines whether to include summaries of its thinking process + * based on the context and the complexity of the reasoning performed. Summaries are more likely + * to be included for complex tasks where the reasoning process provides valuable insight. + */ + AUTO("auto"), + + /** + * Do not include thinking summaries in the response. + * + *

In NONE mode, the model will not provide summaries of its thinking process, even if it + * uses thinking tokens internally. Use this when you only need the final response and want to + * minimize response size. + */ + NONE("none"); + + private final String value; + + Known(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + } + + private Known thinkingSummariesEnum; + private final String value; + + /** + * Creates a ThinkingSummaries from a string value. + * + *

This constructor is used by Jackson during JSON deserialization. It attempts to match the + * provided string value to a known enum value (case-insensitive). If no match is found, it falls + * back to {@code THINKING_SUMMARIES_UNSPECIFIED}. + * + * @param value the string representation of the thinking summaries mode + */ + @JsonCreator + public ThinkingSummaries(String value) { + this.value = value; + for (Known thinkingSummariesEnum : Known.values()) { + if (Ascii.equalsIgnoreCase(thinkingSummariesEnum.toString(), value)) { + this.thinkingSummariesEnum = thinkingSummariesEnum; + break; + } + } + if (this.thinkingSummariesEnum == null) { + this.thinkingSummariesEnum = Known.THINKING_SUMMARIES_UNSPECIFIED; + } + } + + /** + * Creates a ThinkingSummaries from a known enum value. + * + *

This is the recommended constructor for creating ThinkingSummaries instances in application + * code. + * + * @param knownValue the known thinking summaries enum value + */ + public ThinkingSummaries(Known knownValue) { + this.thinkingSummariesEnum = knownValue; + this.value = knownValue.toString(); + } + + @ExcludeFromGeneratedCoverageReport + @Override + @JsonValue + public String toString() { + return this.value; + } + + @ExcludeFromGeneratedCoverageReport + @SuppressWarnings("PatternMatchingInstanceof") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + + if (!(o instanceof ThinkingSummaries)) { + return false; + } + + ThinkingSummaries other = (ThinkingSummaries) o; + + if (this.thinkingSummariesEnum != Known.THINKING_SUMMARIES_UNSPECIFIED + && other.thinkingSummariesEnum != Known.THINKING_SUMMARIES_UNSPECIFIED) { + return this.thinkingSummariesEnum == other.thinkingSummariesEnum; + } else if (this.thinkingSummariesEnum == Known.THINKING_SUMMARIES_UNSPECIFIED + && other.thinkingSummariesEnum == Known.THINKING_SUMMARIES_UNSPECIFIED) { + return this.value.equals(other.value); + } + return false; + } + + @ExcludeFromGeneratedCoverageReport + @Override + public int hashCode() { + if (this.thinkingSummariesEnum != Known.THINKING_SUMMARIES_UNSPECIFIED) { + return this.thinkingSummariesEnum.hashCode(); + } else { + return Objects.hashCode(this.value); + } + } + + /** + * Returns the known enum value for this thinking summaries mode. + * + *

If the value was not recognized during deserialization, this will return {@code + * THINKING_SUMMARIES_UNSPECIFIED}. + * + * @return the known enum value + */ + @ExcludeFromGeneratedCoverageReport + public Known knownEnum() { + return this.thinkingSummariesEnum; + } +} diff --git a/src/main/java/com/google/genai/types/interactions/ToolChoice.java b/src/main/java/com/google/genai/types/interactions/ToolChoice.java new file mode 100644 index 00000000000..4615df658b4 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/ToolChoice.java @@ -0,0 +1,179 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.genai.JsonSerializable; + +/** + * Union type for tool choice in the Interactions API. + * + *

This class represents the {@code oneOf} constraint in the OpenAPI specification where {@code + * tool_choice} can be either: + * + *

    + *
  1. A simple {@link ToolChoiceType} - controls overall tool usage behavior (auto, any, none, + * validated) + *
  2. A {@link ToolChoiceConfig} object - provides fine-grained control over which specific tools + * are allowed + *
+ * + *

This design pattern is used because {@code @AutoValue} cannot represent {@code oneOf} union + * types from OpenAPI specifications. Instead, this class uses a custom serializer ({@link + * ToolChoiceSerializer}) and deserializer ({@link ToolChoiceDeserializer}) to handle JSON + * serialization and deserialization correctly. + * + *

Example usage: + * + *

{@code
+ * // Simple type-based control - let the model decide
+ * ToolChoice toolChoice = ToolChoice.fromType(ToolChoiceType.Known.AUTO);
+ *
+ * // Simple type-based control - force tool usage
+ * ToolChoice toolChoice = ToolChoice.fromType(ToolChoiceType.Known.ANY);
+ *
+ * // Fine-grained control - allow specific tools
+ * ToolChoiceConfig config = ToolChoiceConfig.builder()
+ *     .type(new ToolChoiceType(ToolChoiceType.Known.ANY))
+ *     .allowedFunctionNames(Arrays.asList("get_weather", "search_web"))
+ *     .build();
+ * ToolChoice toolChoice = ToolChoice.fromConfig(config);
+ *
+ * // Use in generation config
+ * GenerationConfig genConfig = GenerationConfig.builder()
+ *     .toolChoice(ToolChoice.fromType(ToolChoiceType.Known.AUTO))
+ *     .build();
+ * }
+ */ +@JsonSerialize(using = ToolChoiceSerializer.class) +@JsonDeserialize(using = ToolChoiceDeserializer.class) +public final class ToolChoice extends JsonSerializable { + private final Object value; + + private ToolChoice(Object value) { + this.value = value; + } + + /** + * Creates a ToolChoice from a ToolChoiceType. + * + * @param type The tool choice type (auto, any, none, validated) + * @return A ToolChoice instance wrapping the type + */ + public static ToolChoice fromType(ToolChoiceType type) { + return new ToolChoice(type); + } + + /** + * Creates a ToolChoice from a ToolChoiceType.Known enum. + * + * @param knownType The tool choice known type + * @return A ToolChoice instance wrapping the type + */ + public static ToolChoice fromType(ToolChoiceType.Known knownType) { + return new ToolChoice(new ToolChoiceType(knownType)); + } + + /** + * Creates a ToolChoice from a string value. + * + * @param typeString The tool choice type as a string + * @return A ToolChoice instance wrapping the type + */ + public static ToolChoice fromString(String typeString) { + return new ToolChoice(new ToolChoiceType(typeString)); + } + + /** + * Creates a ToolChoice from a ToolChoiceConfig. + * + * @param config The tool choice configuration + * @return A ToolChoice instance wrapping the config + */ + public static ToolChoice fromConfig(ToolChoiceConfig config) { + return new ToolChoice(config); + } + + /** + * Creates a ToolChoice from a ToolChoiceConfig builder. + * + * @param configBuilder The tool choice configuration builder + * @return A ToolChoice instance wrapping the config + */ + public static ToolChoice fromConfig(ToolChoiceConfig.Builder configBuilder) { + return new ToolChoice(configBuilder.build()); + } + + /** + * Gets the underlying value. + * + * @return The wrapped value (ToolChoiceType or ToolChoiceConfig) + */ + public Object getValue() { + return value; + } + + /** + * Checks if this ToolChoice is a ToolChoiceType. + * + * @return true if the value is a ToolChoiceType + */ + public boolean isType() { + return value instanceof ToolChoiceType; + } + + /** + * Checks if this ToolChoice is a ToolChoiceConfig. + * + * @return true if the value is a ToolChoiceConfig + */ + public boolean isConfig() { + return value instanceof ToolChoiceConfig; + } + + /** + * Gets the value as a ToolChoiceType. + * + * @return The ToolChoiceType value + * @throws IllegalStateException if the value is not a ToolChoiceType + */ + public ToolChoiceType asType() { + if (!isType()) { + throw new IllegalStateException("ToolChoice is not a ToolChoiceType"); + } + return (ToolChoiceType) value; + } + + /** + * Gets the value as a ToolChoiceConfig. + * + * @return The ToolChoiceConfig value + * @throws IllegalStateException if the value is not a ToolChoiceConfig + */ + public ToolChoiceConfig asConfig() { + if (!isConfig()) { + throw new IllegalStateException("ToolChoice is not a ToolChoiceConfig"); + } + return (ToolChoiceConfig) value; + } + + /** Deserializes a JSON string to a ToolChoice object. */ + public static ToolChoice fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, ToolChoice.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/ToolChoiceConfig.java b/src/main/java/com/google/genai/types/interactions/ToolChoiceConfig.java new file mode 100644 index 00000000000..e764aeefb40 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/ToolChoiceConfig.java @@ -0,0 +1,127 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Optional; + +/** + * Configuration for fine-grained tool choice behavior. + * + *

This class provides detailed control over which specific tools the model is allowed to use + * during generation. It is used as part of a {@link ToolChoice} union type when you need more + * granular control than the simple type-based modes (auto, any, none, validated). + * + *

The configuration allows you to specify exactly which tools are permitted through the {@link + * AllowedTools} field, enabling scenarios where you want to restrict the model to a subset of + * available tools rather than allowing all or none. + * + *

Example usage: + * + *

{@code
+ * // Allow only specific function tools
+ * AllowedTools allowed = AllowedTools.builder()
+ *     .functionNames(Arrays.asList("get_weather", "search_web"))
+ *     .build();
+ *
+ * ToolChoiceConfig config = ToolChoiceConfig.builder()
+ *     .allowedTools(allowed)
+ *     .build();
+ *
+ * // Use with ToolChoice
+ * ToolChoice toolChoice = ToolChoice.fromConfig(config);
+ *
+ * // Include in generation config
+ * GenerationConfig genConfig = GenerationConfig.builder()
+ *     .toolChoice(toolChoice)
+ *     .build();
+ * }
+ */ +@AutoValue +@JsonDeserialize(builder = ToolChoiceConfig.Builder.class) +public abstract class ToolChoiceConfig extends JsonSerializable { + + /** + * Configuration specifying which tools are allowed. + * + *

When set, this restricts the model to only use the specified tools. If not set, the behavior + * depends on the overall tool choice type. + */ + @JsonProperty("allowed_tools") + public abstract Optional allowedTools(); + + /** Instantiates a builder for ToolChoiceConfig. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_ToolChoiceConfig.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for ToolChoiceConfig. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code ToolChoiceConfig.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_ToolChoiceConfig.Builder(); + } + + /** + * Setter for allowedTools. + * + *

allowedTools: Configuration for allowed tools. + */ + @JsonProperty("allowed_tools") + public abstract Builder allowedTools(AllowedTools allowedTools); + + /** + * Convenience setter for allowedTools using a builder. + * + *

allowedTools: Configuration for allowed tools. + */ + @CanIgnoreReturnValue + public Builder allowedTools(AllowedTools.Builder allowedToolsBuilder) { + return allowedTools(allowedToolsBuilder.build()); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder allowedTools(Optional allowedTools); + + /** Clears the value of allowedTools field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearAllowedTools() { + return allowedTools(Optional.empty()); + } + + public abstract ToolChoiceConfig build(); + } + + /** Deserializes a JSON string to a ToolChoiceConfig object. */ + @ExcludeFromGeneratedCoverageReport + public static ToolChoiceConfig fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, ToolChoiceConfig.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/ToolChoiceDeserializer.java b/src/main/java/com/google/genai/types/interactions/ToolChoiceDeserializer.java new file mode 100644 index 00000000000..3d39cfb5332 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/ToolChoiceDeserializer.java @@ -0,0 +1,57 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import java.io.IOException; + +/** + * Custom deserializer for ToolChoice that handles the oneOf union type. + * + *

This deserializer handles two cases: + * + *

    + *
  • String value - deserializes as ToolChoiceType (e.g., "auto", "any", "none", "validated") + *
  • Object value - deserializes as ToolChoiceConfig with allowed_tools field + *
+ */ +public class ToolChoiceDeserializer extends JsonDeserializer { + + @Override + public ToolChoice deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonToken token = p.currentToken(); + + if (token == JsonToken.VALUE_NULL) { + return null; + } else if (token == JsonToken.VALUE_STRING) { + // String value - deserialize as ToolChoiceType + String value = p.getText(); + ToolChoiceType type = new ToolChoiceType(value); + return ToolChoice.fromType(type); + } else if (token == JsonToken.START_OBJECT) { + // Object value - deserialize as ToolChoiceConfig + ToolChoiceConfig config = p.readValueAs(ToolChoiceConfig.class); + return ToolChoice.fromConfig(config); + } else { + throw ctxt.wrongTokenException(p, ToolChoice.class, JsonToken.VALUE_STRING, + "Expected string or object for ToolChoice"); + } + } +} diff --git a/src/main/java/com/google/genai/types/interactions/ToolChoiceSerializer.java b/src/main/java/com/google/genai/types/interactions/ToolChoiceSerializer.java new file mode 100644 index 00000000000..f37d2be64b4 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/ToolChoiceSerializer.java @@ -0,0 +1,54 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import java.io.IOException; + +/** + * Custom serializer for ToolChoice that handles the oneOf union type. + * + *

This serializer handles two cases: + * + *

    + *
  • ToolChoiceType - serializes as a string (e.g., "auto", "any", "none", "validated") + *
  • ToolChoiceConfig - serializes as an object with allowed_tools field + *
+ */ +public class ToolChoiceSerializer extends JsonSerializer { + + @Override + public void serialize(ToolChoice value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + Object innerValue = value.getValue(); + + if (innerValue == null) { + gen.writeNull(); + } else if (innerValue instanceof ToolChoiceType) { + // ToolChoiceType - serialize as string + serializers.defaultSerializeValue(innerValue, gen); + } else if (innerValue instanceof ToolChoiceConfig) { + // ToolChoiceConfig - serialize as object + serializers.defaultSerializeValue(innerValue, gen); + } else { + // Fallback for unknown types + serializers.defaultSerializeValue(innerValue, gen); + } + } +} diff --git a/src/main/java/com/google/genai/types/interactions/ToolChoiceType.java b/src/main/java/com/google/genai/types/interactions/ToolChoiceType.java new file mode 100644 index 00000000000..0428fa5257d --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/ToolChoiceType.java @@ -0,0 +1,209 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.base.Ascii; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Objects; + +/** + * The type of tool choice behavior for the model in the Interactions API. + * + *

This class defines how the model should handle tool calls during generation. It supports both + * simple type-based control (auto, any, none, validated) and can be used in conjunction with {@link + * ToolChoiceConfig} for more fine-grained control over which specific tools are allowed. + * + *

The class follows the standard enum wrapper pattern used throughout the codebase (see {@code + * HarmBlockThreshold}, {@code FunctionCallingConfigMode}). The {@code Known} enum contains all + * recognized values from the OpenAPI specification, and the {@code TOOL_CHOICE_TYPE_UNSPECIFIED} + * value serves as a fallback for forward compatibility with unknown values from the API. + * + *

Example usage: + * + *

{@code
+ * // Use AUTO to let the model decide
+ * ToolChoice toolChoice = ToolChoice.ofType(new ToolChoiceType(ToolChoiceType.Known.AUTO));
+ *
+ * // Force the model to use a tool
+ * ToolChoice toolChoice = ToolChoice.ofType(new ToolChoiceType(ToolChoiceType.Known.ANY));
+ *
+ * // Prevent the model from using tools
+ * ToolChoice toolChoice = ToolChoice.ofType(new ToolChoiceType(ToolChoiceType.Known.NONE));
+ * }
+ */ +public class ToolChoiceType { + + /** + * Enum representing the known values for ToolChoiceType. + * + *

These values control how the model interacts with available tools during generation. + */ + public enum Known { + /** + * Unspecified tool choice type. + * + *

This is the fallback value used when the API returns an unknown tool choice type not + * recognized by this version of the SDK. This ensures forward compatibility when new tool + * choice types are added to the API. + */ + TOOL_CHOICE_TYPE_UNSPECIFIED("unspecified"), + + /** + * The model decides whether to use tools based on the context. + * + *

In AUTO mode, the model will analyze the user's request and determine whether calling a + * tool would be helpful. The model has full autonomy to use tools or generate a direct response + * as appropriate. + */ + AUTO("auto"), + + /** + * The model must use at least one of the available tools. + * + *

In ANY mode, the model is required to make a tool call rather than generating a direct + * text response. If multiple tools are available, the model chooses which one(s) to call based + * on the context. + */ + ANY("any"), + + /** + * The model must not use any tools. + * + *

In NONE mode, the model is prohibited from making any tool calls and must generate a + * direct text response. Use this when you want to ensure the model only produces natural + * language output. + */ + NONE("none"), + + /** + * The model must use tools that have been validated. + * + *

In VALIDATED mode, the model is restricted to using only tools that have passed validation + * checks. This provides an additional safety layer for tool usage. + */ + VALIDATED("validated"); + + private final String value; + + Known(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + } + + private Known toolChoiceTypeEnum; + private final String value; + + /** + * Creates a ToolChoiceType from a string value. + * + *

This constructor is used by Jackson during JSON deserialization. It attempts to match the + * provided string value to a known enum value (case-insensitive). If no match is found, it falls + * back to {@code TOOL_CHOICE_TYPE_UNSPECIFIED}. + * + * @param value the string representation of the tool choice type + */ + @JsonCreator + public ToolChoiceType(String value) { + this.value = value; + for (Known toolChoiceTypeEnum : Known.values()) { + if (Ascii.equalsIgnoreCase(toolChoiceTypeEnum.toString(), value)) { + this.toolChoiceTypeEnum = toolChoiceTypeEnum; + break; + } + } + if (this.toolChoiceTypeEnum == null) { + this.toolChoiceTypeEnum = Known.TOOL_CHOICE_TYPE_UNSPECIFIED; + } + } + + /** + * Creates a ToolChoiceType from a known enum value. + * + *

This is the recommended constructor for creating ToolChoiceType instances in application + * code. + * + * @param knownValue the known tool choice type enum value + */ + public ToolChoiceType(Known knownValue) { + this.toolChoiceTypeEnum = knownValue; + this.value = knownValue.toString(); + } + + @ExcludeFromGeneratedCoverageReport + @Override + @JsonValue + public String toString() { + return this.value; + } + + @ExcludeFromGeneratedCoverageReport + @SuppressWarnings("PatternMatchingInstanceof") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + + if (!(o instanceof ToolChoiceType)) { + return false; + } + + ToolChoiceType other = (ToolChoiceType) o; + + if (this.toolChoiceTypeEnum != Known.TOOL_CHOICE_TYPE_UNSPECIFIED + && other.toolChoiceTypeEnum != Known.TOOL_CHOICE_TYPE_UNSPECIFIED) { + return this.toolChoiceTypeEnum == other.toolChoiceTypeEnum; + } else if (this.toolChoiceTypeEnum == Known.TOOL_CHOICE_TYPE_UNSPECIFIED + && other.toolChoiceTypeEnum == Known.TOOL_CHOICE_TYPE_UNSPECIFIED) { + return this.value.equals(other.value); + } + return false; + } + + @ExcludeFromGeneratedCoverageReport + @Override + public int hashCode() { + if (this.toolChoiceTypeEnum != Known.TOOL_CHOICE_TYPE_UNSPECIFIED) { + return this.toolChoiceTypeEnum.hashCode(); + } else { + return Objects.hashCode(this.value); + } + } + + /** + * Returns the known enum value for this tool choice type. + * + *

If the value was not recognized during deserialization, this will return {@code + * TOOL_CHOICE_TYPE_UNSPECIFIED}. + * + * @return the known enum value + */ + @ExcludeFromGeneratedCoverageReport + public Known knownEnum() { + return this.toolChoiceTypeEnum; + } +} diff --git a/src/main/java/com/google/genai/types/interactions/Turn.java b/src/main/java/com/google/genai/types/interactions/Turn.java new file mode 100644 index 00000000000..87ae06d8fc2 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/Turn.java @@ -0,0 +1,150 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.content.Content; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * Represents a conversation turn in the Interactions API. + * + *

A turn contains the content of a single message in a conversation along with the role (either + * 'user' or 'model') that produced it. Turns are used to build conversation history and maintain + * context across multiple interactions. + * + *

Example usage: + * + *

{@code
+ * Turn userTurn = Turn.builder()
+ *     .role("user")
+ *     .content(TextContent.of("What is the weather today?"))
+ *     .build();
+ *
+ * Turn modelTurn = Turn.builder()
+ *     .role("model")
+ *     .content(TextContent.of("The weather is sunny and warm."))
+ *     .build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = Turn.Builder.class) +public abstract class Turn extends JsonSerializable { + /** The content of the turn as a list of Content objects. */ + @JsonProperty("content") + public abstract Optional> content(); + + /** Optional. The role in the conversation ('user' or 'model'). */ + @JsonProperty("role") + public abstract Optional role(); + + /** Instantiates a builder for Turn. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_Turn.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for Turn. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use `Turn.builder()` for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_Turn.Builder(); + } + + /** + * Setter for content. + * + *

content: The content of the turn as a list of Content objects. + */ + @JsonProperty("content") + public abstract Builder content(List content); + + /** + * Setter for content (varargs convenience method). + * + *

content: The content of the turn. + */ + @CanIgnoreReturnValue + public Builder content(Content... content) { + return content(Arrays.asList(content)); + } + + /** Internal setter for content with Optional. */ + @ExcludeFromGeneratedCoverageReport + abstract Builder content(Optional> content); + + /** + * Clear method for content. + * + *

Removes the content field. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearContent() { + return content(Optional.empty()); + } + + /** + * Setter for role. + * + *

role: The role in the conversation ('user' or 'model'). + */ + @JsonProperty("role") + public abstract Builder role(String role); + + /** Internal setter for role with Optional. */ + @ExcludeFromGeneratedCoverageReport + abstract Builder role(Optional role); + + /** + * Clear method for role. + * + *

Removes the role field. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearRole() { + return role(Optional.empty()); + } + + /** Builds the Turn instance. */ + public abstract Turn build(); + } + + /** Deserializes an Turn from a JSON string. */ + @ExcludeFromGeneratedCoverageReport + public static Turn fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, Turn.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/UrlContextCallArguments.java b/src/main/java/com/google/genai/types/interactions/UrlContextCallArguments.java new file mode 100644 index 00000000000..8e27456451d --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/UrlContextCallArguments.java @@ -0,0 +1,74 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.List; +import java.util.Optional; + +@AutoValue +@JsonDeserialize(builder = UrlContextCallArguments.Builder.class) +public abstract class UrlContextCallArguments extends JsonSerializable { + + @JsonProperty("urls") + public abstract Optional> urls(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_UrlContextCallArguments.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_UrlContextCallArguments.Builder(); + } + + @JsonProperty("urls") + public abstract Builder urls(List urls); + + @ExcludeFromGeneratedCoverageReport + abstract Builder urls(Optional> urls); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearUrls() { + return urls(Optional.empty()); + } + + public abstract UrlContextCallArguments build(); + } + + @ExcludeFromGeneratedCoverageReport + public static UrlContextCallArguments fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, UrlContextCallArguments.class); + } + + @ExcludeFromGeneratedCoverageReport + public static UrlContextCallArguments of(List urls) { + return builder().urls(urls).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/UrlContextResult.java b/src/main/java/com/google/genai/types/interactions/UrlContextResult.java new file mode 100644 index 00000000000..3abee67d449 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/UrlContextResult.java @@ -0,0 +1,89 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.UrlContextResultStatus; +import java.util.Optional; + +@AutoValue +@JsonDeserialize(builder = UrlContextResult.Builder.class) +public abstract class UrlContextResult extends JsonSerializable { + + @JsonProperty("url") + public abstract Optional url(); + + @JsonProperty("status") + public abstract Optional status(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_UrlContextResult.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_UrlContextResult.Builder(); + } + + @JsonProperty("url") + public abstract Builder url(String url); + + @ExcludeFromGeneratedCoverageReport + abstract Builder url(Optional url); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearUrl() { + return url(Optional.empty()); + } + + @JsonProperty("status") + public abstract Builder status(UrlContextResultStatus status); + + @ExcludeFromGeneratedCoverageReport + abstract Builder status(Optional status); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearStatus() { + return status(Optional.empty()); + } + + public abstract UrlContextResult build(); + } + + @ExcludeFromGeneratedCoverageReport + public static UrlContextResult fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, UrlContextResult.class); + } + + @ExcludeFromGeneratedCoverageReport + public static UrlContextResult of(String url, UrlContextResultStatus status) { + return builder().url(url).status(status).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/UrlContextResultStatus.java b/src/main/java/com/google/genai/types/interactions/UrlContextResultStatus.java new file mode 100644 index 00000000000..0a0d9974103 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/UrlContextResultStatus.java @@ -0,0 +1,38 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** Represents the status of URL context retrieval in an interaction. */ +public enum UrlContextResultStatus { + /** URL retrieval is successful. */ + @JsonProperty("success") + SUCCESS, + + /** URL retrieval failed due to an error. */ + @JsonProperty("error") + ERROR, + + /** URL retrieval failed because content is behind a paywall. */ + @JsonProperty("paywall") + PAYWALL, + + /** URL retrieval failed because content is unsafe. */ + @JsonProperty("unsafe") + UNSAFE +} diff --git a/src/main/java/com/google/genai/types/interactions/Usage.java b/src/main/java/com/google/genai/types/interactions/Usage.java new file mode 100644 index 00000000000..940f90e7e60 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/Usage.java @@ -0,0 +1,344 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * Token usage metadata for the Interactions API. + * + *

This class represents token usage statistics returned by the Interactions API. Unlike the + * standard {@code UsageMetadata} class used by GenerateContent, the Interactions API returns + * usage data with snake_case field names (e.g., {@code total_input_tokens} instead of + * {@code promptTokenCount}). + * + *

Example usage: + * + *

{@code
+ * Interaction interaction = client.interactions.create(config);
+ * if (interaction.usage().isPresent()) {
+ *   Usage usage = interaction.usage().get();
+ *   System.out.println("Input tokens: " + usage.totalInputTokens().orElse(0));
+ *   System.out.println("Output tokens: " + usage.totalOutputTokens().orElse(0));
+ *   System.out.println("Total tokens: " + usage.totalTokens().orElse(0));
+ * }
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = Usage.Builder.class) +public abstract class Usage extends JsonSerializable { + + /** Number of tokens in the prompt (context). */ + @JsonProperty("total_input_tokens") + public abstract Optional totalInputTokens(); + + /** A breakdown of input token usage by modality. */ + @JsonProperty("input_tokens_by_modality") + public abstract Optional> inputTokensByModality(); + + /** Number of tokens in the cached part of the prompt (the cached content). */ + @JsonProperty("total_cached_tokens") + public abstract Optional totalCachedTokens(); + + /** A breakdown of cached token usage by modality. */ + @JsonProperty("cached_tokens_by_modality") + public abstract Optional> cachedTokensByModality(); + + /** Total number of tokens across all the generated responses. */ + @JsonProperty("total_output_tokens") + public abstract Optional totalOutputTokens(); + + /** A breakdown of output token usage by modality. */ + @JsonProperty("output_tokens_by_modality") + public abstract Optional> outputTokensByModality(); + + /** Number of tokens present in tool-use prompt(s). */ + @JsonProperty("total_tool_use_tokens") + public abstract Optional totalToolUseTokens(); + + /** A breakdown of tool-use token usage by modality. */ + @JsonProperty("tool_use_tokens_by_modality") + public abstract Optional> toolUseTokensByModality(); + + /** Number of tokens of thoughts for thinking models. */ + @JsonProperty("total_thought_tokens") + public abstract Optional totalThoughtTokens(); + + /** Total token count for the interaction request (prompt + responses + other internal tokens). */ + @JsonProperty("total_tokens") + public abstract Optional totalTokens(); + + /** Instantiates a builder for Usage. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_Usage.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for Usage. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code Usage.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_Usage.Builder(); + } + + /** + * Setter for totalInputTokens. + * + *

totalInputTokens: Number of tokens in the prompt (context). + */ + @JsonProperty("total_input_tokens") + public abstract Builder totalInputTokens(Integer totalInputTokens); + + @ExcludeFromGeneratedCoverageReport + abstract Builder totalInputTokens(Optional totalInputTokens); + + /** Clears the value of totalInputTokens field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearTotalInputTokens() { + return totalInputTokens(Optional.empty()); + } + + /** + * Setter for inputTokensByModality. + * + *

inputTokensByModality: A breakdown of input token usage by modality. + */ + @JsonProperty("input_tokens_by_modality") + public abstract Builder inputTokensByModality(List inputTokensByModality); + + /** + * Setter for inputTokensByModality (varargs convenience method). + * + *

inputTokensByModality: A breakdown of input token usage by modality. + */ + @CanIgnoreReturnValue + public Builder inputTokensByModality(ModalityTokens... inputTokensByModality) { + return inputTokensByModality(Arrays.asList(inputTokensByModality)); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder inputTokensByModality(Optional> inputTokensByModality); + + /** Clears the value of inputTokensByModality field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearInputTokensByModality() { + return inputTokensByModality(Optional.empty()); + } + + /** + * Setter for totalCachedTokens. + * + *

totalCachedTokens: Number of tokens in the cached part of the prompt. + */ + @JsonProperty("total_cached_tokens") + public abstract Builder totalCachedTokens(Integer totalCachedTokens); + + @ExcludeFromGeneratedCoverageReport + abstract Builder totalCachedTokens(Optional totalCachedTokens); + + /** Clears the value of totalCachedTokens field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearTotalCachedTokens() { + return totalCachedTokens(Optional.empty()); + } + + /** + * Setter for cachedTokensByModality. + * + *

cachedTokensByModality: A breakdown of cached token usage by modality. + */ + @JsonProperty("cached_tokens_by_modality") + public abstract Builder cachedTokensByModality(List cachedTokensByModality); + + /** + * Setter for cachedTokensByModality (varargs convenience method). + * + *

cachedTokensByModality: A breakdown of cached token usage by modality. + */ + @CanIgnoreReturnValue + public Builder cachedTokensByModality(ModalityTokens... cachedTokensByModality) { + return cachedTokensByModality(Arrays.asList(cachedTokensByModality)); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder cachedTokensByModality(Optional> cachedTokensByModality); + + /** Clears the value of cachedTokensByModality field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearCachedTokensByModality() { + return cachedTokensByModality(Optional.empty()); + } + + /** + * Setter for totalOutputTokens. + * + *

totalOutputTokens: Total number of tokens across all the generated responses. + */ + @JsonProperty("total_output_tokens") + public abstract Builder totalOutputTokens(Integer totalOutputTokens); + + @ExcludeFromGeneratedCoverageReport + abstract Builder totalOutputTokens(Optional totalOutputTokens); + + /** Clears the value of totalOutputTokens field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearTotalOutputTokens() { + return totalOutputTokens(Optional.empty()); + } + + /** + * Setter for outputTokensByModality. + * + *

outputTokensByModality: A breakdown of output token usage by modality. + */ + @JsonProperty("output_tokens_by_modality") + public abstract Builder outputTokensByModality(List outputTokensByModality); + + /** + * Setter for outputTokensByModality (varargs convenience method). + * + *

outputTokensByModality: A breakdown of output token usage by modality. + */ + @CanIgnoreReturnValue + public Builder outputTokensByModality(ModalityTokens... outputTokensByModality) { + return outputTokensByModality(Arrays.asList(outputTokensByModality)); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder outputTokensByModality(Optional> outputTokensByModality); + + /** Clears the value of outputTokensByModality field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearOutputTokensByModality() { + return outputTokensByModality(Optional.empty()); + } + + /** + * Setter for totalToolUseTokens. + * + *

totalToolUseTokens: Number of tokens present in tool-use prompt(s). + */ + @JsonProperty("total_tool_use_tokens") + public abstract Builder totalToolUseTokens(Integer totalToolUseTokens); + + @ExcludeFromGeneratedCoverageReport + abstract Builder totalToolUseTokens(Optional totalToolUseTokens); + + /** Clears the value of totalToolUseTokens field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearTotalToolUseTokens() { + return totalToolUseTokens(Optional.empty()); + } + + /** + * Setter for toolUseTokensByModality. + * + *

toolUseTokensByModality: A breakdown of tool-use token usage by modality. + */ + @JsonProperty("tool_use_tokens_by_modality") + public abstract Builder toolUseTokensByModality(List toolUseTokensByModality); + + /** + * Setter for toolUseTokensByModality (varargs convenience method). + * + *

toolUseTokensByModality: A breakdown of tool-use token usage by modality. + */ + @CanIgnoreReturnValue + public Builder toolUseTokensByModality(ModalityTokens... toolUseTokensByModality) { + return toolUseTokensByModality(Arrays.asList(toolUseTokensByModality)); + } + + @ExcludeFromGeneratedCoverageReport + abstract Builder toolUseTokensByModality(Optional> toolUseTokensByModality); + + /** Clears the value of toolUseTokensByModality field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearToolUseTokensByModality() { + return toolUseTokensByModality(Optional.empty()); + } + + /** + * Setter for totalThoughtTokens. + * + *

totalThoughtTokens: Number of tokens of thoughts for thinking models. + */ + @JsonProperty("total_thought_tokens") + public abstract Builder totalThoughtTokens(Integer totalThoughtTokens); + + @ExcludeFromGeneratedCoverageReport + abstract Builder totalThoughtTokens(Optional totalThoughtTokens); + + /** Clears the value of totalThoughtTokens field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearTotalThoughtTokens() { + return totalThoughtTokens(Optional.empty()); + } + + /** + * Setter for totalTokens. + * + *

totalTokens: Total token count for the interaction request. + */ + @JsonProperty("total_tokens") + public abstract Builder totalTokens(Integer totalTokens); + + @ExcludeFromGeneratedCoverageReport + abstract Builder totalTokens(Optional totalTokens); + + /** Clears the value of totalTokens field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearTotalTokens() { + return totalTokens(Optional.empty()); + } + + public abstract Usage build(); + } + + /** Deserializes a JSON string to a Usage object. */ + @ExcludeFromGeneratedCoverageReport + public static Usage fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, Usage.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/VideoMimeType.java b/src/main/java/com/google/genai/types/interactions/VideoMimeType.java new file mode 100644 index 00000000000..960493c3929 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/VideoMimeType.java @@ -0,0 +1,161 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.base.Ascii; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Objects; + +/** + * MIME type for video content. + * + *

Supports known video MIME types with extensibility for future formats. + */ +public class VideoMimeType { + + /** Enum representing the known video MIME types. */ + public enum Known { + /** Unspecified or unknown video MIME type. */ + VIDEO_MIME_TYPE_UNSPECIFIED("unspecified"), + + /** MP4 video format. */ + VIDEO_MP4("video/mp4"), + + /** MPEG video format. */ + VIDEO_MPEG("video/mpeg"), + + /** MOV video format. */ + VIDEO_MOV("video/mov"), + + /** AVI video format. */ + VIDEO_AVI("video/avi"), + + /** FLV video format. */ + VIDEO_X_FLV("video/x-flv"), + + /** MPG video format. */ + VIDEO_MPG("video/mpg"), + + /** WebM video format. */ + VIDEO_WEBM("video/webm"), + + /** WMV video format. */ + VIDEO_WMV("video/wmv"), + + /** 3GPP video format. */ + VIDEO_3GPP("video/3gpp"); + + private final String value; + + Known(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + } + + private Known mimeTypeEnum; + private final String value; + + /** + * Constructs a VideoMimeType from a string value. + * + * @param value The MIME type string (e.g., "video/mp4") + */ + @JsonCreator + public VideoMimeType(String value) { + this.value = value; + for (Known mimeTypeEnum : Known.values()) { + if (Ascii.equalsIgnoreCase(mimeTypeEnum.toString(), value)) { + this.mimeTypeEnum = mimeTypeEnum; + break; + } + } + if (this.mimeTypeEnum == null) { + this.mimeTypeEnum = Known.VIDEO_MIME_TYPE_UNSPECIFIED; + } + } + + /** + * Constructs a VideoMimeType from a known enum value. + * + * @param knownValue The known MIME type + */ + public VideoMimeType(Known knownValue) { + this.mimeTypeEnum = knownValue; + this.value = knownValue.toString(); + } + + @ExcludeFromGeneratedCoverageReport + @Override + @JsonValue + public String toString() { + return this.value; + } + + @ExcludeFromGeneratedCoverageReport + @SuppressWarnings("PatternMatchingInstanceof") + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + + if (!(o instanceof VideoMimeType)) { + return false; + } + + VideoMimeType other = (VideoMimeType) o; + + if (this.mimeTypeEnum != Known.VIDEO_MIME_TYPE_UNSPECIFIED + && other.mimeTypeEnum != Known.VIDEO_MIME_TYPE_UNSPECIFIED) { + return this.mimeTypeEnum == other.mimeTypeEnum; + } else if (this.mimeTypeEnum == Known.VIDEO_MIME_TYPE_UNSPECIFIED + && other.mimeTypeEnum == Known.VIDEO_MIME_TYPE_UNSPECIFIED) { + return this.value.equals(other.value); + } + return false; + } + + @ExcludeFromGeneratedCoverageReport + @Override + public int hashCode() { + if (this.mimeTypeEnum != Known.VIDEO_MIME_TYPE_UNSPECIFIED) { + return this.mimeTypeEnum.hashCode(); + } else { + return Objects.hashCode(this.value); + } + } + + /** + * Returns the known enum value if this is a recognized MIME type. + * + * @return The known enum value + */ + @ExcludeFromGeneratedCoverageReport + public Known knownEnum() { + return this.mimeTypeEnum; + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/AudioContent.java b/src/main/java/com/google/genai/types/interactions/content/AudioContent.java new file mode 100644 index 00000000000..07699996752 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/AudioContent.java @@ -0,0 +1,151 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.AudioMimeType; +import java.util.Optional; + +/** + * Audio content for the Interactions API. + * + *

Represents audio data that can be included in interaction inputs or outputs. Audio can be + * provided either as base64-encoded data or as a URI. + * + *

Example usage with data: + * + *

{@code
+ * AudioContent audio = AudioContent.fromData(base64Data, "audio/mp3");
+ * }
+ * + *

Example usage with URI: + * + *

{@code
+ * AudioContent audio = AudioContent.fromUri("https://example.com/audio.mp3", "audio/mp3");
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = AudioContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("audio") +public abstract class AudioContent extends JsonSerializable implements Content { + + @JsonProperty("data") + public abstract Optional data(); + + @JsonProperty("uri") + public abstract Optional uri(); + + /** + * The MIME type of the audio. + * + *

Supported values: + * + *

    + *
  • {@link AudioMimeType.Known#AUDIO_WAV} - WAV format + *
  • {@link AudioMimeType.Known#AUDIO_MP3} - MP3 format + *
  • {@link AudioMimeType.Known#AUDIO_AIFF} - AIFF format + *
  • {@link AudioMimeType.Known#AUDIO_AAC} - AAC format + *
  • {@link AudioMimeType.Known#AUDIO_OGG} - OGG format + *
  • {@link AudioMimeType.Known#AUDIO_FLAC} - FLAC format + *
+ * + *

This field accepts any MIME type string to support future audio formats. + */ + @JsonProperty("mime_type") + public abstract Optional mimeType(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_AudioContent.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_AudioContent.Builder(); + } + + @JsonProperty("data") + public abstract Builder data(String data); + + @ExcludeFromGeneratedCoverageReport + abstract Builder data(Optional data); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearData() { + return data(Optional.empty()); + } + + @JsonProperty("uri") + public abstract Builder uri(String uri); + + @ExcludeFromGeneratedCoverageReport + abstract Builder uri(Optional uri); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearUri() { + return uri(Optional.empty()); + } + + @JsonProperty("mime_type") + public abstract Builder mimeType(AudioMimeType mimeType); + + @ExcludeFromGeneratedCoverageReport + abstract Builder mimeType(Optional mimeType); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearMimeType() { + return mimeType(Optional.empty()); + } + + public abstract AudioContent build(); + } + + @ExcludeFromGeneratedCoverageReport + public static AudioContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, AudioContent.class); + } + + @ExcludeFromGeneratedCoverageReport + public static AudioContent fromData(String data, String mimeType) { + return builder().data(data).mimeType(new AudioMimeType(mimeType)).build(); + } + + @ExcludeFromGeneratedCoverageReport + public static AudioContent fromUri(String uri, String mimeType) { + return builder().uri(uri).mimeType(new AudioMimeType(mimeType)).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/CodeExecutionCallContent.java b/src/main/java/com/google/genai/types/interactions/content/CodeExecutionCallContent.java new file mode 100644 index 00000000000..297309cb32d --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/CodeExecutionCallContent.java @@ -0,0 +1,115 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.CodeExecutionCallArguments; +import java.util.Optional; + +/** + * Code execution call content for the Interactions API. + * + *

Represents a request from the model to execute code. This content type appears in interaction + * outputs when the model wants to run code as part of its reasoning process. + * + *

Example usage: + * + *

{@code
+ * CodeExecutionCallContent codeCall = CodeExecutionCallContent.builder()
+ *     .id("call_123")
+ *     .arguments(CodeExecutionCallArguments.builder()
+ *         .code("print('Hello, World!')")
+ *         .language("python")
+ *         .build())
+ *     .build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = CodeExecutionCallContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("code_execution_call") +public abstract class CodeExecutionCallContent extends JsonSerializable implements Content { + + @JsonProperty("arguments") + public abstract Optional arguments(); + + @JsonProperty("id") + public abstract Optional id(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_CodeExecutionCallContent.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_CodeExecutionCallContent.Builder(); + } + + @JsonProperty("arguments") + public abstract Builder arguments(CodeExecutionCallArguments arguments); + + @ExcludeFromGeneratedCoverageReport + abstract Builder arguments(Optional arguments); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearArguments() { + return arguments(Optional.empty()); + } + + @JsonProperty("id") + public abstract Builder id(String id); + + @ExcludeFromGeneratedCoverageReport + abstract Builder id(Optional id); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearId() { + return id(Optional.empty()); + } + + public abstract CodeExecutionCallContent build(); + } + + @ExcludeFromGeneratedCoverageReport + public static CodeExecutionCallContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, CodeExecutionCallContent.class); + } + + @ExcludeFromGeneratedCoverageReport + public static CodeExecutionCallContent of(String language, String code, String id) { + return builder().arguments(CodeExecutionCallArguments.of(language, code)).id(id).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/CodeExecutionResultContent.java b/src/main/java/com/google/genai/types/interactions/content/CodeExecutionResultContent.java new file mode 100644 index 00000000000..f05fec1f31e --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/CodeExecutionResultContent.java @@ -0,0 +1,146 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Optional; + +/** + * Code execution result content for the Interactions API. + * + *

Represents the result of executing code requested by the model. This content type appears when + * providing the output from a code execution call back to the model. + * + *

Example usage: + * + *

{@code
+ * CodeExecutionResultContent result = CodeExecutionResultContent.builder()
+ *     .callId("call_123")
+ *     .result("Hello, World!")
+ *     .build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = CodeExecutionResultContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("code_execution_result") +public abstract class CodeExecutionResultContent extends JsonSerializable implements Content { + + @JsonProperty("result") + public abstract Optional result(); + + @JsonProperty("is_error") + public abstract Optional isError(); + + @JsonProperty("signature") + public abstract Optional signature(); + + @JsonProperty("call_id") + public abstract Optional callId(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_CodeExecutionResultContent.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_CodeExecutionResultContent.Builder(); + } + + @JsonProperty("result") + public abstract Builder result(String result); + + @ExcludeFromGeneratedCoverageReport + abstract Builder result(Optional result); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearResult() { + return result(Optional.empty()); + } + + @JsonProperty("is_error") + public abstract Builder isError(Boolean isError); + + @ExcludeFromGeneratedCoverageReport + abstract Builder isError(Optional isError); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearIsError() { + return isError(Optional.empty()); + } + + @JsonProperty("signature") + public abstract Builder signature(String signature); + + @ExcludeFromGeneratedCoverageReport + abstract Builder signature(Optional signature); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearSignature() { + return signature(Optional.empty()); + } + + @JsonProperty("call_id") + public abstract Builder callId(String callId); + + @ExcludeFromGeneratedCoverageReport + abstract Builder callId(Optional callId); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearCallId() { + return callId(Optional.empty()); + } + + public abstract CodeExecutionResultContent build(); + } + + @ExcludeFromGeneratedCoverageReport + public static CodeExecutionResultContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, CodeExecutionResultContent.class); + } + + @ExcludeFromGeneratedCoverageReport + public static CodeExecutionResultContent of(String result, String callId) { + return builder().result(result).callId(callId).isError(false).build(); + } + + @ExcludeFromGeneratedCoverageReport + public static CodeExecutionResultContent ofError(String result, String callId) { + return builder().result(result).callId(callId).isError(true).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/Content.java b/src/main/java/com/google/genai/types/interactions/content/Content.java new file mode 100644 index 00000000000..12305463749 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/Content.java @@ -0,0 +1,56 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +/** + * Base interface for content types using a type discriminator. + * + *

This follows Jackson best practices for polymorphism: + * + *

    + *
  • {@code @JsonTypeInfo} on the base type with property "type" + *
  • Explicit {@code @JsonSubTypes} registration + *
  • Jackson handles type discrimination via annotations + *
+ */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = TextContent.class, name = "text"), + @JsonSubTypes.Type(value = FunctionCallContent.class, name = "function_call"), + @JsonSubTypes.Type(value = FunctionResultContent.class, name = "function_result"), + @JsonSubTypes.Type(value = ImageContent.class, name = "image"), + @JsonSubTypes.Type(value = AudioContent.class, name = "audio"), + @JsonSubTypes.Type(value = VideoContent.class, name = "video"), + @JsonSubTypes.Type(value = DocumentContent.class, name = "document"), + @JsonSubTypes.Type(value = CodeExecutionCallContent.class, name = "code_execution_call"), + @JsonSubTypes.Type(value = CodeExecutionResultContent.class, name = "code_execution_result"), + @JsonSubTypes.Type(value = UrlContextCallContent.class, name = "url_context_call"), + @JsonSubTypes.Type(value = UrlContextResultContent.class, name = "url_context_result"), + @JsonSubTypes.Type(value = GoogleSearchCallContent.class, name = "google_search_call"), + @JsonSubTypes.Type(value = GoogleSearchResultContent.class, name = "google_search_result"), + @JsonSubTypes.Type(value = McpServerToolCallContent.class, name = "mcp_server_tool_call"), + @JsonSubTypes.Type(value = McpServerToolResultContent.class, name = "mcp_server_tool_result"), + @JsonSubTypes.Type(value = ThoughtContent.class, name = "thought"), + @JsonSubTypes.Type(value = FileSearchCallContent.class, name = "file_search_call"), + @JsonSubTypes.Type(value = FileSearchResultContent.class, name = "file_search_result") +}) +public interface Content { + // Marker interface - Jackson handles type discrimination via annotations +} diff --git a/src/main/java/com/google/genai/types/interactions/content/DocumentContent.java b/src/main/java/com/google/genai/types/interactions/content/DocumentContent.java new file mode 100644 index 00000000000..336a59405d0 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/DocumentContent.java @@ -0,0 +1,146 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.DocumentMimeType; +import java.util.Optional; + +/** + * Document content for the Interactions API. + * + *

Represents document data (e.g., PDF, text files) that can be included in interaction inputs or + * outputs. Documents can be provided either as base64-encoded data or as a URI. + * + *

Example usage with data: + * + *

{@code
+ * DocumentContent doc = DocumentContent.fromData(base64Data, "application/pdf");
+ * }
+ * + *

Example usage with URI: + * + *

{@code
+ * DocumentContent doc = DocumentContent.fromUri("https://example.com/doc.pdf", "application/pdf");
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = DocumentContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("document") +public abstract class DocumentContent extends JsonSerializable implements Content { + + @JsonProperty("data") + public abstract Optional data(); + + @JsonProperty("uri") + public abstract Optional uri(); + + /** + * The MIME type of the document. + * + *

Supported values: + * + *

    + *
  • {@link DocumentMimeType.Known#APPLICATION_PDF} - PDF format + *
+ * + *

This field accepts any MIME type string to support future document formats. + */ + @JsonProperty("mime_type") + public abstract Optional mimeType(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_DocumentContent.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_DocumentContent.Builder(); + } + + @JsonProperty("data") + public abstract Builder data(String data); + + @ExcludeFromGeneratedCoverageReport + abstract Builder data(Optional data); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearData() { + return data(Optional.empty()); + } + + @JsonProperty("uri") + public abstract Builder uri(String uri); + + @ExcludeFromGeneratedCoverageReport + abstract Builder uri(Optional uri); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearUri() { + return uri(Optional.empty()); + } + + @JsonProperty("mime_type") + public abstract Builder mimeType(DocumentMimeType mimeType); + + @ExcludeFromGeneratedCoverageReport + abstract Builder mimeType(Optional mimeType); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearMimeType() { + return mimeType(Optional.empty()); + } + + public abstract DocumentContent build(); + } + + @ExcludeFromGeneratedCoverageReport + public static DocumentContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, DocumentContent.class); + } + + @ExcludeFromGeneratedCoverageReport + public static DocumentContent fromData(String data, String mimeType) { + return builder().data(data).mimeType(new DocumentMimeType(mimeType)).build(); + } + + @ExcludeFromGeneratedCoverageReport + public static DocumentContent fromUri(String uri, String mimeType) { + return builder().uri(uri).mimeType(new DocumentMimeType(mimeType)).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/FileSearchCallContent.java b/src/main/java/com/google/genai/types/interactions/content/FileSearchCallContent.java new file mode 100644 index 00000000000..58c91471a43 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/FileSearchCallContent.java @@ -0,0 +1,95 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Optional; + +/** + * File search call content for the Interactions API. + * + *

Represents a request from the model to search through file stores. This content type appears + * in interaction outputs when the model wants to retrieve information from uploaded files. + * + *

Example usage: + * + *

{@code
+ * FileSearchCallContent fileSearchCall = FileSearchCallContent.builder()
+ *     .id("call_123")
+ *     .build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = FileSearchCallContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("file_search_call") +public abstract class FileSearchCallContent extends JsonSerializable implements Content { + + @JsonProperty("id") + public abstract Optional id(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_FileSearchCallContent.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_FileSearchCallContent.Builder(); + } + + @JsonProperty("id") + public abstract Builder id(String id); + + @ExcludeFromGeneratedCoverageReport + abstract Builder id(Optional id); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearId() { + return id(Optional.empty()); + } + + public abstract FileSearchCallContent build(); + } + + @ExcludeFromGeneratedCoverageReport + public static FileSearchCallContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, FileSearchCallContent.class); + } + + @ExcludeFromGeneratedCoverageReport + public static FileSearchCallContent of(String id) { + return builder().id(id).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/FileSearchResultContent.java b/src/main/java/com/google/genai/types/interactions/content/FileSearchResultContent.java new file mode 100644 index 00000000000..bcaf8b82435 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/FileSearchResultContent.java @@ -0,0 +1,79 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.FileSearchResult; +import java.util.List; +import java.util.Optional; + +@AutoValue +@JsonDeserialize(builder = FileSearchResultContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("file_search_result") +public abstract class FileSearchResultContent extends JsonSerializable implements Content { + + @JsonProperty("result") + public abstract Optional> result(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_FileSearchResultContent.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_FileSearchResultContent.Builder(); + } + + @JsonProperty("result") + public abstract Builder result(List result); + + @ExcludeFromGeneratedCoverageReport + abstract Builder result(Optional> result); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearResult() { + return result(Optional.empty()); + } + + public abstract FileSearchResultContent build(); + } + + @ExcludeFromGeneratedCoverageReport + public static FileSearchResultContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, FileSearchResultContent.class); + } + + @ExcludeFromGeneratedCoverageReport + public static FileSearchResultContent of(List result) { + return builder().result(result).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/FunctionCallContent.java b/src/main/java/com/google/genai/types/interactions/content/FunctionCallContent.java new file mode 100644 index 00000000000..70bb8601fa1 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/FunctionCallContent.java @@ -0,0 +1,136 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Map; + +/** + * Function call content for the Interactions API. + * + *

Represents a request from the model to execute a function. When the model determines it needs + * to call a function, it returns this content type with the function name, arguments, and a unique + * identifier for tracking the call-result pair. + * + *

Example usage: + * + *

{@code
+ * FunctionCallContent call = FunctionCallContent.builder()
+ *     .id("call-123")
+ *     .name("get_weather")
+ *     .arguments(Map.of("location", "San Francisco"))
+ *     .build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = FunctionCallContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("function_call") +public abstract class FunctionCallContent extends JsonSerializable implements Content { + + /** + * The unique identifier for this function call. Used to match with FunctionResultContent. + * + *

This field is always present when the model returns a function call and is required. + */ + @JsonProperty("id") + public abstract String id(); + + /** + * The name of the function to call. + * + *

This field is always present when the model returns a function call and is required. + */ + @JsonProperty("name") + public abstract String name(); + + /** + * The arguments to pass to the function, as a map of parameter names to values. + * + *

This field is always present when the model returns a function call and is required. + */ + @JsonProperty("arguments") + public abstract Map arguments(); + + /** Instantiates a builder for FunctionCallContent. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_FunctionCallContent.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for FunctionCallContent. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code FunctionCallContent.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_FunctionCallContent.Builder(); + } + + /** + * Setter for id. + * + *

id: The unique identifier for this function call. This field is required. + */ + @JsonProperty("id") + public abstract Builder id(String id); + + /** + * Setter for name. + * + *

name: The name of the function to call. This field is required. + */ + @JsonProperty("name") + public abstract Builder name(String name); + + /** + * Setter for arguments. + * + *

arguments: The arguments to pass to the function. This field is required. + */ + @JsonProperty("arguments") + public abstract Builder arguments(Map arguments); + + public abstract FunctionCallContent build(); + } + + /** Deserializes a JSON string to a FunctionCallContent object. */ + @ExcludeFromGeneratedCoverageReport + public static FunctionCallContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, FunctionCallContent.class); + } + + /** Convenience factory method. */ + @ExcludeFromGeneratedCoverageReport + public static FunctionCallContent of(String id, String name, Map arguments) { + return builder().id(id).name(name).arguments(arguments).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/FunctionResultContent.java b/src/main/java/com/google/genai/types/interactions/content/FunctionResultContent.java new file mode 100644 index 00000000000..15818cc1344 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/FunctionResultContent.java @@ -0,0 +1,266 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.ResultItems; +import java.util.Map; +import java.util.Optional; + +/** Function result content representing the result of a function call. */ +@AutoValue +@JsonDeserialize(builder = FunctionResultContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("function_result") +public abstract class FunctionResultContent extends JsonSerializable implements Content { + + /** + * The unique identifier matching the corresponding FunctionCallContent. + * + *

This field is always present and is required. + */ + @JsonProperty("call_id") + public abstract String id(); + + /** + * The name of the function that was called. + * + *

This field is optional. + */ + @JsonProperty("name") + public abstract Optional name(); + + /** + * Whether the tool call resulted in an error. + * + *

This field is optional. + */ + @JsonProperty("is_error") + public abstract Optional isError(); + + /** + * The result returned by the function. + * + *

This field is always present and is required. The result can be one of three types: + * + *

    + *
  • {@link ResultItems} - structured data with an items array + *
  • {@link String} - a plain string result + *
  • {@link Map} or other object - arbitrary structured data + *
+ * + *

This matches the Python SDK's Result type: {@code Union[ResultItems, str, object]} + * + *

Use {@link #resultAsString()}, {@link #resultAsResultItems()}, or {@link #resultAsMap()} for + * type-safe access. + */ + @JsonProperty("result") + public abstract Object result(); + + /** + * Returns the result as a String if it is a String, otherwise returns empty. + * + * @return Optional containing the result as a String, or empty if result is not a String + */ + public Optional resultAsString() { + if (result() instanceof String) { + return Optional.of((String) result()); + } + return Optional.empty(); + } + + /** + * Returns the result as a ResultItems if it matches the ResultItems structure, otherwise returns + * empty. + * + * @return Optional containing the result as ResultItems, or empty if result is not ResultItems + */ + public Optional resultAsResultItems() { + if (result() instanceof ResultItems) { + return Optional.of((ResultItems) result()); + } + // Handle case where Jackson deserializes as Map with "items" key + if (result() instanceof Map) { + @SuppressWarnings("unchecked") + Map map = (Map) result(); + if (map.containsKey("items") && map.get("items") instanceof java.util.List) { + @SuppressWarnings("unchecked") + java.util.List items = (java.util.List) map.get("items"); + return Optional.of(ResultItems.of(items)); + } + } + return Optional.empty(); + } + + /** + * Returns the result as a Map if it is a Map, otherwise returns empty. + * + * @return Optional containing the result as a Map, or empty if result is not a Map + */ + @SuppressWarnings("unchecked") + public Optional> resultAsMap() { + if (result() instanceof Map) { + return Optional.of((Map) result()); + } + return Optional.empty(); + } + + /** Instantiates a builder for FunctionResultContent. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_FunctionResultContent.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for FunctionResultContent. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code FunctionResultContent.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_FunctionResultContent.Builder(); + } + + /** + * Setter for id. + * + *

id: The unique identifier matching the corresponding FunctionCallContent. This field is + * required. + */ + @JsonProperty("call_id") + public abstract Builder id(String id); + + /** + * Setter for name. + * + *

name: The name of the function that was called. + */ + @JsonProperty("name") + public abstract Builder name(String name); + + @ExcludeFromGeneratedCoverageReport + abstract Builder name(Optional name); + + /** Clears the value of name field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearName() { + return name(Optional.empty()); + } + + /** + * Setter for isError. + * + *

isError: Whether the tool call resulted in an error. + */ + @JsonProperty("is_error") + public abstract Builder isError(Boolean isError); + + @ExcludeFromGeneratedCoverageReport + abstract Builder isError(Optional isError); + + /** Clears the value of isError field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearIsError() { + return isError(Optional.empty()); + } + + /** + * Setter for result. + * + *

result: The result returned by the function. This field is required. Can be a string, an + * object, or structured data. + */ + @JsonProperty("result") + public abstract Builder result(Object result); + + public abstract FunctionResultContent build(); + } + + /** Deserializes a JSON string to a FunctionResultContent object. */ + @ExcludeFromGeneratedCoverageReport + public static FunctionResultContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, FunctionResultContent.class); + } + + /** Convenience factory method. */ + @ExcludeFromGeneratedCoverageReport + public static FunctionResultContent of(String id, String name, Map result) { + return builder().id(id).name(name).result(result).build(); + } + + /** Convenience factory method for successful result. */ + @ExcludeFromGeneratedCoverageReport + public static FunctionResultContent ofSuccess( + String callId, String name, Map result) { + return builder().id(callId).name(name).result(result).isError(false).build(); + } + + /** Convenience factory method for error result. */ + @ExcludeFromGeneratedCoverageReport + public static FunctionResultContent ofError( + String callId, String name, Map result) { + return builder().id(callId).name(name).result(result).isError(true).build(); + } + + /** Convenience factory method with String result. */ + @ExcludeFromGeneratedCoverageReport + public static FunctionResultContent of(String callId, String name, String result) { + return builder().id(callId).name(name).result(result).build(); + } + + /** Convenience factory method with ResultItems result. */ + @ExcludeFromGeneratedCoverageReport + public static FunctionResultContent of(String callId, String name, ResultItems result) { + return builder().id(callId).name(name).result(result).build(); + } + + /** Convenience factory method for successful result with String. */ + @ExcludeFromGeneratedCoverageReport + public static FunctionResultContent ofSuccess(String callId, String name, String result) { + return builder().id(callId).name(name).result(result).isError(false).build(); + } + + /** Convenience factory method for successful result with ResultItems. */ + @ExcludeFromGeneratedCoverageReport + public static FunctionResultContent ofSuccess(String callId, String name, ResultItems result) { + return builder().id(callId).name(name).result(result).isError(false).build(); + } + + /** Convenience factory method for error result with String. */ + @ExcludeFromGeneratedCoverageReport + public static FunctionResultContent ofError(String callId, String name, String result) { + return builder().id(callId).name(name).result(result).isError(true).build(); + } + + /** Convenience factory method for error result with ResultItems. */ + @ExcludeFromGeneratedCoverageReport + public static FunctionResultContent ofError(String callId, String name, ResultItems result) { + return builder().id(callId).name(name).result(result).isError(true).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/GoogleSearchCallContent.java b/src/main/java/com/google/genai/types/interactions/content/GoogleSearchCallContent.java new file mode 100644 index 00000000000..ae5ede7770b --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/GoogleSearchCallContent.java @@ -0,0 +1,115 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.GoogleSearchCallArguments; +import java.util.List; +import java.util.Optional; + +/** + * Google Search call content for the Interactions API. + * + *

Represents a request from the model to perform a Google Search. This content type appears in + * interaction outputs when the model wants to search the web for information. + * + *

Example usage: + * + *

{@code
+ * GoogleSearchCallContent searchCall = GoogleSearchCallContent.builder()
+ *     .id("call_123")
+ *     .arguments(GoogleSearchCallArguments.builder()
+ *         .query("latest AI developments")
+ *         .build())
+ *     .build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = GoogleSearchCallContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("google_search_call") +public abstract class GoogleSearchCallContent extends JsonSerializable implements Content { + + @JsonProperty("arguments") + public abstract Optional arguments(); + + @JsonProperty("id") + public abstract Optional id(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_GoogleSearchCallContent.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_GoogleSearchCallContent.Builder(); + } + + @JsonProperty("arguments") + public abstract Builder arguments(GoogleSearchCallArguments arguments); + + @ExcludeFromGeneratedCoverageReport + abstract Builder arguments(Optional arguments); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearArguments() { + return arguments(Optional.empty()); + } + + @JsonProperty("id") + public abstract Builder id(String id); + + @ExcludeFromGeneratedCoverageReport + abstract Builder id(Optional id); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearId() { + return id(Optional.empty()); + } + + public abstract GoogleSearchCallContent build(); + } + + @ExcludeFromGeneratedCoverageReport + public static GoogleSearchCallContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, GoogleSearchCallContent.class); + } + + @ExcludeFromGeneratedCoverageReport + public static GoogleSearchCallContent of(List queries, String id) { + return builder().arguments(GoogleSearchCallArguments.of(queries)).id(id).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/GoogleSearchResultContent.java b/src/main/java/com/google/genai/types/interactions/content/GoogleSearchResultContent.java new file mode 100644 index 00000000000..ccaa924fafc --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/GoogleSearchResultContent.java @@ -0,0 +1,124 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.GoogleSearchResult; +import java.util.List; +import java.util.Optional; + +@AutoValue +@JsonDeserialize(builder = GoogleSearchResultContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("google_search_result") +public abstract class GoogleSearchResultContent extends JsonSerializable implements Content { + + @JsonProperty("signature") + public abstract Optional signature(); + + @JsonProperty("result") + public abstract Optional> result(); + + @JsonProperty("is_error") + public abstract Optional isError(); + + @JsonProperty("call_id") + public abstract Optional callId(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_GoogleSearchResultContent.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_GoogleSearchResultContent.Builder(); + } + + @JsonProperty("signature") + public abstract Builder signature(String signature); + + @ExcludeFromGeneratedCoverageReport + abstract Builder signature(Optional signature); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearSignature() { + return signature(Optional.empty()); + } + + @JsonProperty("result") + public abstract Builder result(List result); + + @ExcludeFromGeneratedCoverageReport + abstract Builder result(Optional> result); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearResult() { + return result(Optional.empty()); + } + + @JsonProperty("is_error") + public abstract Builder isError(Boolean isError); + + @ExcludeFromGeneratedCoverageReport + abstract Builder isError(Optional isError); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearIsError() { + return isError(Optional.empty()); + } + + @JsonProperty("call_id") + public abstract Builder callId(String callId); + + @ExcludeFromGeneratedCoverageReport + abstract Builder callId(Optional callId); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearCallId() { + return callId(Optional.empty()); + } + + public abstract GoogleSearchResultContent build(); + } + + @ExcludeFromGeneratedCoverageReport + public static GoogleSearchResultContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, GoogleSearchResultContent.class); + } + + @ExcludeFromGeneratedCoverageReport + public static GoogleSearchResultContent of(List result, String callId) { + return builder().result(result).callId(callId).isError(false).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/ImageContent.java b/src/main/java/com/google/genai/types/interactions/content/ImageContent.java new file mode 100644 index 00000000000..2b594ff9a5c --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/ImageContent.java @@ -0,0 +1,179 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.ImageMimeType; +import com.google.genai.types.interactions.MediaResolution; +import java.util.Optional; + +/** + * Image content for the Interactions API. + * + *

Represents image data that can be included in interaction inputs or outputs. Images can be + * provided either as base64-encoded data or as a URI. + * + *

Example usage with data: + * + *

{@code
+ * ImageContent image = ImageContent.fromData(base64Data, "image/png");
+ * }
+ * + *

Example usage with URI: + * + *

{@code
+ * ImageContent image = ImageContent.fromUri("https://example.com/image.png", "image/png");
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = ImageContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("image") +public abstract class ImageContent extends JsonSerializable + implements Content, ThoughtSummaryContent { + + @JsonProperty("data") + public abstract Optional data(); + + @JsonProperty("uri") + public abstract Optional uri(); + + /** + * The MIME type of the image. + * + *

Supported values: + * + *

    + *
  • {@link ImageMimeType.Known#IMAGE_PNG} - PNG format + *
  • {@link ImageMimeType.Known#IMAGE_JPEG} - JPEG format + *
  • {@link ImageMimeType.Known#IMAGE_WEBP} - WebP format + *
  • {@link ImageMimeType.Known#IMAGE_HEIC} - HEIC format + *
  • {@link ImageMimeType.Known#IMAGE_HEIF} - HEIF format + *
+ * + *

This field accepts any MIME type string to support future image formats. + */ + @JsonProperty("mime_type") + public abstract Optional mimeType(); + + /** + * The resolution of the image. + * + *

Possible values: + * + *

    + *
  • {@link MediaResolution#LOW} - Low resolution + *
  • {@link MediaResolution#MEDIUM} - Medium resolution + *
  • {@link MediaResolution#HIGH} - High resolution + *
  • {@link MediaResolution#ULTRA_HIGH} - Ultra high resolution + *
+ */ + @JsonProperty("resolution") + public abstract Optional resolution(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_ImageContent.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_ImageContent.Builder(); + } + + @JsonProperty("data") + public abstract Builder data(String data); + + @ExcludeFromGeneratedCoverageReport + abstract Builder data(Optional data); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearData() { + return data(Optional.empty()); + } + + @JsonProperty("uri") + public abstract Builder uri(String uri); + + @ExcludeFromGeneratedCoverageReport + abstract Builder uri(Optional uri); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearUri() { + return uri(Optional.empty()); + } + + @JsonProperty("mime_type") + public abstract Builder mimeType(ImageMimeType mimeType); + + @ExcludeFromGeneratedCoverageReport + abstract Builder mimeType(Optional mimeType); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearMimeType() { + return mimeType(Optional.empty()); + } + + @JsonProperty("resolution") + public abstract Builder resolution(MediaResolution resolution); + + @ExcludeFromGeneratedCoverageReport + abstract Builder resolution(Optional resolution); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearResolution() { + return resolution(Optional.empty()); + } + + public abstract ImageContent build(); + } + + @ExcludeFromGeneratedCoverageReport + public static ImageContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, ImageContent.class); + } + + @ExcludeFromGeneratedCoverageReport + public static ImageContent fromData(String data, String mimeType) { + return builder().data(data).mimeType(new ImageMimeType(mimeType)).build(); + } + + @ExcludeFromGeneratedCoverageReport + public static ImageContent fromUri(String uri, String mimeType) { + return builder().uri(uri).mimeType(new ImageMimeType(mimeType)).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/McpServerToolCallContent.java b/src/main/java/com/google/genai/types/interactions/content/McpServerToolCallContent.java new file mode 100644 index 00000000000..13c17a9084a --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/McpServerToolCallContent.java @@ -0,0 +1,86 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Map; + +@AutoValue +@JsonDeserialize(builder = McpServerToolCallContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("mcp_server_tool_call") +public abstract class McpServerToolCallContent extends JsonSerializable implements Content { + + @JsonProperty("id") + public abstract String id(); + + @JsonProperty("name") + public abstract String name(); + + @JsonProperty("server_name") + public abstract String serverName(); + + @JsonProperty("arguments") + public abstract Map arguments(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_McpServerToolCallContent.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_McpServerToolCallContent.Builder(); + } + + @JsonProperty("id") + public abstract Builder id(String id); + + @JsonProperty("name") + public abstract Builder name(String name); + + @JsonProperty("server_name") + public abstract Builder serverName(String serverName); + + @JsonProperty("arguments") + public abstract Builder arguments(Map arguments); + + public abstract McpServerToolCallContent build(); + } + + @ExcludeFromGeneratedCoverageReport + public static McpServerToolCallContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, McpServerToolCallContent.class); + } + + @ExcludeFromGeneratedCoverageReport + public static McpServerToolCallContent of( + String id, String name, String serverName, Map arguments) { + return builder().id(id).name(name).serverName(serverName).arguments(arguments).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/McpServerToolResultContent.java b/src/main/java/com/google/genai/types/interactions/content/McpServerToolResultContent.java new file mode 100644 index 00000000000..19835f38576 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/McpServerToolResultContent.java @@ -0,0 +1,215 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.ResultItems; +import java.util.Map; +import java.util.Optional; + +@AutoValue +@JsonDeserialize(builder = McpServerToolResultContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("mcp_server_tool_result") +public abstract class McpServerToolResultContent extends JsonSerializable implements Content { + + @JsonProperty("call_id") + public abstract String callId(); + + /** + * The result returned by the MCP server tool. + * + *

This field is always present and is required. The result can be one of three types: + * + *

    + *
  • {@link ResultItems} - structured data with an items array + *
  • {@link String} - a plain string result + *
  • {@link Map} or other object - arbitrary structured data + *
+ * + *

This matches the Python SDK's Result type: {@code Union[ResultItems, str, object]} + * + *

Use {@link #resultAsString()}, {@link #resultAsResultItems()}, or {@link #resultAsMap()} for + * type-safe access. + */ + @JsonProperty("result") + public abstract Object result(); + + /** + * Returns the result as a String if it is a String, otherwise returns empty. + * + * @return Optional containing the result as a String, or empty if result is not a String + */ + public Optional resultAsString() { + if (result() instanceof String) { + return Optional.of((String) result()); + } + return Optional.empty(); + } + + /** + * Returns the result as a ResultItems if it matches the ResultItems structure, otherwise returns + * empty. + * + * @return Optional containing the result as ResultItems, or empty if result is not ResultItems + */ + public Optional resultAsResultItems() { + if (result() instanceof ResultItems) { + return Optional.of((ResultItems) result()); + } + // Handle case where Jackson deserializes as Map with "items" key + if (result() instanceof Map) { + @SuppressWarnings("unchecked") + Map map = (Map) result(); + if (map.containsKey("items") && map.get("items") instanceof java.util.List) { + @SuppressWarnings("unchecked") + java.util.List items = (java.util.List) map.get("items"); + return Optional.of(ResultItems.of(items)); + } + } + return Optional.empty(); + } + + /** + * Returns the result as a Map if it is a Map, otherwise returns empty. + * + * @return Optional containing the result as a Map, or empty if result is not a Map + */ + @SuppressWarnings("unchecked") + public Optional> resultAsMap() { + if (result() instanceof Map) { + return Optional.of((Map) result()); + } + return Optional.empty(); + } + + @JsonProperty("name") + public abstract Optional name(); + + @JsonProperty("server_name") + public abstract Optional serverName(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_McpServerToolResultContent.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_McpServerToolResultContent.Builder(); + } + + @JsonProperty("call_id") + public abstract Builder callId(String callId); + + @JsonProperty("result") + public abstract Builder result(Object result); + + @JsonProperty("name") + public abstract Builder name(String name); + + @ExcludeFromGeneratedCoverageReport + abstract Builder name(Optional name); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearName() { + return name(Optional.empty()); + } + + @JsonProperty("server_name") + public abstract Builder serverName(String serverName); + + @ExcludeFromGeneratedCoverageReport + abstract Builder serverName(Optional serverName); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearServerName() { + return serverName(Optional.empty()); + } + + public abstract McpServerToolResultContent build(); + } + + @ExcludeFromGeneratedCoverageReport + public static McpServerToolResultContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, McpServerToolResultContent.class); + } + + @ExcludeFromGeneratedCoverageReport + public static McpServerToolResultContent of(String callId, Object result) { + return builder().callId(callId).result(result).build(); + } + + @ExcludeFromGeneratedCoverageReport + public static McpServerToolResultContent of( + String callId, Object result, String name, String serverName) { + return builder().callId(callId).result(result).name(name).serverName(serverName).build(); + } + + /** Convenience factory method with String result. */ + @ExcludeFromGeneratedCoverageReport + public static McpServerToolResultContent of(String callId, String result) { + return builder().callId(callId).result(result).build(); + } + + /** Convenience factory method with ResultItems result. */ + @ExcludeFromGeneratedCoverageReport + public static McpServerToolResultContent of(String callId, ResultItems result) { + return builder().callId(callId).result(result).build(); + } + + /** Convenience factory method with Map result. */ + @ExcludeFromGeneratedCoverageReport + public static McpServerToolResultContent of(String callId, Map result) { + return builder().callId(callId).result(result).build(); + } + + /** Convenience factory method with String result and all fields. */ + @ExcludeFromGeneratedCoverageReport + public static McpServerToolResultContent of( + String callId, String result, String name, String serverName) { + return builder().callId(callId).result(result).name(name).serverName(serverName).build(); + } + + /** Convenience factory method with ResultItems result and all fields. */ + @ExcludeFromGeneratedCoverageReport + public static McpServerToolResultContent of( + String callId, ResultItems result, String name, String serverName) { + return builder().callId(callId).result(result).name(name).serverName(serverName).build(); + } + + /** Convenience factory method with Map result and all fields. */ + @ExcludeFromGeneratedCoverageReport + public static McpServerToolResultContent of( + String callId, Map result, String name, String serverName) { + return builder().callId(callId).result(result).name(name).serverName(serverName).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/TextContent.java b/src/main/java/com/google/genai/types/interactions/content/TextContent.java new file mode 100644 index 00000000000..6939af0bb9d --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/TextContent.java @@ -0,0 +1,150 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.Annotation; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * Text content for the Interactions API. + * + *

Represents textual information in interactions, either from user input or model output. Text + * content can include optional annotations for citations and source attribution. + * + *

Example usage: + * + *

{@code
+ * // Simple text content
+ * TextContent text = TextContent.of("Hello, how can I help you?");
+ *
+ * // Text with annotations
+ * TextContent textWithAnnotations = TextContent.builder()
+ *     .text("According to recent research...")
+ *     .annotations(Annotation.of(0, 25, "https://example.com/research"))
+ *     .build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = TextContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("text") +public abstract class TextContent extends JsonSerializable + implements Content, ThoughtSummaryContent { + + /** Instantiates a builder for TextContent. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_TextContent.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Returns the text content. */ + @JsonProperty("text") + public abstract Optional text(); + + /** Returns citation information for model-generated content. */ + @JsonProperty("annotations") + public abstract Optional> annotations(); + + /** Builder for TextContent. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code TextContent.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_TextContent.Builder(); + } + + /** + * Setter for text. + * + *

text: The text content. + */ + @JsonProperty("text") + public abstract Builder text(String text); + + @ExcludeFromGeneratedCoverageReport + abstract Builder text(Optional text); + + /** Clears the value of text field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearText() { + return text(Optional.empty()); + } + + /** + * Setter for annotations. + * + *

annotations: Citation information for model-generated content. + */ + @JsonProperty("annotations") + public abstract Builder annotations(List annotations); + + @ExcludeFromGeneratedCoverageReport + abstract Builder annotations(Optional> annotations); + + /** Clears the value of annotations field. */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearAnnotations() { + return annotations(Optional.empty()); + } + + /** + * Setter for annotations using varargs. + * + *

annotations: Citation information for model-generated content. + */ + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder annotations(Annotation... annotations) { + return annotations(Arrays.asList(annotations)); + } + + public abstract TextContent build(); + } + + /** Deserializes a JSON string to a TextContent object. */ + @ExcludeFromGeneratedCoverageReport + public static TextContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, TextContent.class); + } + + /** Convenience factory method. */ + @ExcludeFromGeneratedCoverageReport + public static TextContent of(String text) { + return builder().text(text).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/ThoughtContent.java b/src/main/java/com/google/genai/types/interactions/content/ThoughtContent.java new file mode 100644 index 00000000000..1a2d0e3874e --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/ThoughtContent.java @@ -0,0 +1,124 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * Thought content for the Interactions API. + * + *

Represents the model's internal reasoning process when thinking is enabled. This content type + * captures the model's thought signatures and summaries that explain its reasoning steps. + * + *

Example usage: + * + *

{@code
+ * ThoughtContent thought = ThoughtContent.builder()
+ *     .signature("reasoning-step-1")
+ *     .summary(TextContent.of("Analyzing the problem..."))
+ *     .build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = ThoughtContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("thought") +public abstract class ThoughtContent extends JsonSerializable implements Content { + + @JsonProperty("signature") + public abstract Optional signature(); + + @JsonProperty("summary") + public abstract Optional> summary(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_ThoughtContent.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_ThoughtContent.Builder(); + } + + @JsonProperty("signature") + public abstract Builder signature(String signature); + + @ExcludeFromGeneratedCoverageReport + abstract Builder signature(Optional signature); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearSignature() { + return signature(Optional.empty()); + } + + @JsonProperty("summary") + public abstract Builder summary(List summary); + + @ExcludeFromGeneratedCoverageReport + abstract Builder summary(Optional> summary); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearSummary() { + return summary(Optional.empty()); + } + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder summary(ThoughtSummaryContent... summary) { + return summary(Arrays.asList(summary)); + } + + public abstract ThoughtContent build(); + } + + @ExcludeFromGeneratedCoverageReport + public static ThoughtContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, ThoughtContent.class); + } + + @ExcludeFromGeneratedCoverageReport + public static ThoughtContent of(List summary) { + return builder().summary(summary).build(); + } + + @ExcludeFromGeneratedCoverageReport + public static ThoughtContent of(ThoughtSummaryContent... summary) { + return builder().summary(summary).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/ThoughtSummaryContent.java b/src/main/java/com/google/genai/types/interactions/content/ThoughtSummaryContent.java new file mode 100644 index 00000000000..211c07acd29 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/ThoughtSummaryContent.java @@ -0,0 +1,38 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +/** + * Marker interface for content types that can appear in ThoughtContent.summary. + * + *

This corresponds to Python's Summary type alias: {@code Summary = Union[TextContent, + * ImageContent]} + * + *

Only TextContent and ImageContent can be used in thought summaries, which is a more restricted + * set than the full Content union. + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = TextContent.class, name = "text"), + @JsonSubTypes.Type(value = ImageContent.class, name = "image") +}) +public interface ThoughtSummaryContent { + // Marker interface - Jackson handles polymorphism via @JsonSubTypes +} diff --git a/src/main/java/com/google/genai/types/interactions/content/UrlContextCallContent.java b/src/main/java/com/google/genai/types/interactions/content/UrlContextCallContent.java new file mode 100644 index 00000000000..55dec50ec10 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/UrlContextCallContent.java @@ -0,0 +1,115 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.UrlContextCallArguments; +import java.util.List; +import java.util.Optional; + +/** + * URL context call content for the Interactions API. + * + *

Represents a request from the model to retrieve and analyze content from URLs. This content + * type appears in interaction outputs when the model wants to access web pages for context. + * + *

Example usage: + * + *

{@code
+ * UrlContextCallContent urlCall = UrlContextCallContent.builder()
+ *     .id("call_123")
+ *     .arguments(UrlContextCallArguments.builder()
+ *         .urls(Arrays.asList("https://example.com"))
+ *         .build())
+ *     .build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = UrlContextCallContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("url_context_call") +public abstract class UrlContextCallContent extends JsonSerializable implements Content { + + @JsonProperty("arguments") + public abstract Optional arguments(); + + @JsonProperty("id") + public abstract Optional id(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_UrlContextCallContent.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_UrlContextCallContent.Builder(); + } + + @JsonProperty("arguments") + public abstract Builder arguments(UrlContextCallArguments arguments); + + @ExcludeFromGeneratedCoverageReport + abstract Builder arguments(Optional arguments); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearArguments() { + return arguments(Optional.empty()); + } + + @JsonProperty("id") + public abstract Builder id(String id); + + @ExcludeFromGeneratedCoverageReport + abstract Builder id(Optional id); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearId() { + return id(Optional.empty()); + } + + public abstract UrlContextCallContent build(); + } + + @ExcludeFromGeneratedCoverageReport + public static UrlContextCallContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, UrlContextCallContent.class); + } + + @ExcludeFromGeneratedCoverageReport + public static UrlContextCallContent of(List urls, String id) { + return builder().arguments(UrlContextCallArguments.of(urls)).id(id).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/UrlContextResultContent.java b/src/main/java/com/google/genai/types/interactions/content/UrlContextResultContent.java new file mode 100644 index 00000000000..137b3880ce1 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/UrlContextResultContent.java @@ -0,0 +1,124 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.UrlContextResult; +import java.util.List; +import java.util.Optional; + +@AutoValue +@JsonDeserialize(builder = UrlContextResultContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("url_context_result") +public abstract class UrlContextResultContent extends JsonSerializable implements Content { + + @JsonProperty("signature") + public abstract Optional signature(); + + @JsonProperty("result") + public abstract Optional> result(); + + @JsonProperty("is_error") + public abstract Optional isError(); + + @JsonProperty("call_id") + public abstract Optional callId(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_UrlContextResultContent.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_UrlContextResultContent.Builder(); + } + + @JsonProperty("signature") + public abstract Builder signature(String signature); + + @ExcludeFromGeneratedCoverageReport + abstract Builder signature(Optional signature); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearSignature() { + return signature(Optional.empty()); + } + + @JsonProperty("result") + public abstract Builder result(List result); + + @ExcludeFromGeneratedCoverageReport + abstract Builder result(Optional> result); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearResult() { + return result(Optional.empty()); + } + + @JsonProperty("is_error") + public abstract Builder isError(Boolean isError); + + @ExcludeFromGeneratedCoverageReport + abstract Builder isError(Optional isError); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearIsError() { + return isError(Optional.empty()); + } + + @JsonProperty("call_id") + public abstract Builder callId(String callId); + + @ExcludeFromGeneratedCoverageReport + abstract Builder callId(Optional callId); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearCallId() { + return callId(Optional.empty()); + } + + public abstract UrlContextResultContent build(); + } + + @ExcludeFromGeneratedCoverageReport + public static UrlContextResultContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, UrlContextResultContent.class); + } + + @ExcludeFromGeneratedCoverageReport + public static UrlContextResultContent of(List result, String callId) { + return builder().result(result).callId(callId).isError(false).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/content/VideoContent.java b/src/main/java/com/google/genai/types/interactions/content/VideoContent.java new file mode 100644 index 00000000000..edafae7f623 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/content/VideoContent.java @@ -0,0 +1,182 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.MediaResolution; +import com.google.genai.types.interactions.VideoMimeType; +import java.util.Optional; + +/** + * Video content for the Interactions API. + * + *

Represents video data that can be included in interaction inputs or outputs. Videos can be + * provided either as base64-encoded data or as a URI. + * + *

Example usage with data: + * + *

{@code
+ * VideoContent video = VideoContent.fromData(base64Data, "video/mp4");
+ * }
+ * + *

Example usage with URI: + * + *

{@code
+ * VideoContent video = VideoContent.fromUri("https://example.com/video.mp4", "video/mp4");
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = VideoContent.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("video") +public abstract class VideoContent extends JsonSerializable implements Content { + + @JsonProperty("data") + public abstract Optional data(); + + @JsonProperty("uri") + public abstract Optional uri(); + + /** + * The MIME type of the video. + * + *

Supported values: + * + *

    + *
  • {@link VideoMimeType.Known#VIDEO_MP4} - MP4 format + *
  • {@link VideoMimeType.Known#VIDEO_MPEG} - MPEG format + *
  • {@link VideoMimeType.Known#VIDEO_MOV} - MOV format + *
  • {@link VideoMimeType.Known#VIDEO_AVI} - AVI format + *
  • {@link VideoMimeType.Known#VIDEO_X_FLV} - FLV format + *
  • {@link VideoMimeType.Known#VIDEO_MPG} - MPG format + *
  • {@link VideoMimeType.Known#VIDEO_WEBM} - WebM format + *
  • {@link VideoMimeType.Known#VIDEO_WMV} - WMV format + *
  • {@link VideoMimeType.Known#VIDEO_3GPP} - 3GPP format + *
+ * + *

This field accepts any MIME type string to support future video formats. + */ + @JsonProperty("mime_type") + public abstract Optional mimeType(); + + /** + * The resolution of the video. + * + *

Possible values: + * + *

    + *
  • {@link MediaResolution#LOW} - Low resolution + *
  • {@link MediaResolution#MEDIUM} - Medium resolution + *
  • {@link MediaResolution#HIGH} - High resolution + *
  • {@link MediaResolution#ULTRA_HIGH} - Ultra high resolution + *
+ */ + @JsonProperty("resolution") + public abstract Optional resolution(); + + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_VideoContent.Builder(); + } + + public abstract Builder toBuilder(); + + @AutoValue.Builder + public abstract static class Builder { + @JsonCreator + private static Builder create() { + return new AutoValue_VideoContent.Builder(); + } + + @JsonProperty("data") + public abstract Builder data(String data); + + @ExcludeFromGeneratedCoverageReport + abstract Builder data(Optional data); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearData() { + return data(Optional.empty()); + } + + @JsonProperty("uri") + public abstract Builder uri(String uri); + + @ExcludeFromGeneratedCoverageReport + abstract Builder uri(Optional uri); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearUri() { + return uri(Optional.empty()); + } + + @JsonProperty("mime_type") + public abstract Builder mimeType(VideoMimeType mimeType); + + @ExcludeFromGeneratedCoverageReport + abstract Builder mimeType(Optional mimeType); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearMimeType() { + return mimeType(Optional.empty()); + } + + @JsonProperty("resolution") + public abstract Builder resolution(MediaResolution resolution); + + @ExcludeFromGeneratedCoverageReport + abstract Builder resolution(Optional resolution); + + @ExcludeFromGeneratedCoverageReport + @CanIgnoreReturnValue + public Builder clearResolution() { + return resolution(Optional.empty()); + } + + public abstract VideoContent build(); + } + + @ExcludeFromGeneratedCoverageReport + public static VideoContent fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, VideoContent.class); + } + + @ExcludeFromGeneratedCoverageReport + public static VideoContent fromData(String data, String mimeType) { + return builder().data(data).mimeType(new VideoMimeType(mimeType)).build(); + } + + @ExcludeFromGeneratedCoverageReport + public static VideoContent fromUri(String uri, String mimeType) { + return builder().uri(uri).mimeType(new VideoMimeType(mimeType)).build(); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/tools/CodeExecution.java b/src/main/java/com/google/genai/types/interactions/tools/CodeExecution.java new file mode 100644 index 00000000000..673b221140f --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/tools/CodeExecution.java @@ -0,0 +1,74 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.tools; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; + +/** + * Code execution tool for the Interactions API. + * + *

Enables the model to execute code as part of generation. + * + *

Example usage: + * + *

{@code
+ * CodeExecution codeTool = CodeExecution.builder().build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = CodeExecution.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("code_execution") +public abstract class CodeExecution extends JsonSerializable implements Tool { + + /** Instantiates a builder for CodeExecution. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_CodeExecution.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for CodeExecution. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code CodeExecution.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_CodeExecution.Builder(); + } + + public abstract CodeExecution build(); + } + + /** Deserializes a JSON string to a CodeExecution object. */ + @ExcludeFromGeneratedCoverageReport + public static CodeExecution fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, CodeExecution.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/tools/ComputerUse.java b/src/main/java/com/google/genai/types/interactions/tools/ComputerUse.java new file mode 100644 index 00000000000..3c345bbb63d --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/tools/ComputerUse.java @@ -0,0 +1,132 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.tools; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * Computer use tool for the Interactions API. + * + *

Enables the model to interact with a computer environment. + * + *

Example usage: + * + *

{@code
+ * ComputerUse computerTool = ComputerUse.builder()
+ *     .environment("browser")
+ *     .build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = ComputerUse.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("computer_use") +public abstract class ComputerUse extends JsonSerializable implements Tool { + + /** The environment for computer use (e.g., "browser", "desktop"). */ + @JsonProperty("environment") + public abstract Optional environment(); + + /** List of predefined functions to exclude from computer use. */ + @JsonProperty("excludedPredefinedFunctions") + public abstract Optional> excludedPredefinedFunctions(); + + /** Instantiates a builder for ComputerUse. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_ComputerUse.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for ComputerUse. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code ComputerUse.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_ComputerUse.Builder(); + } + + /** + * Setter for environment. + * + *

environment: The environment for computer use. + */ + @JsonProperty("environment") + public abstract Builder environment(String environment); + + abstract Builder environment(Optional environment); + + /** Clears the value of environment field. */ + @CanIgnoreReturnValue + public Builder clearEnvironment() { + return environment(Optional.empty()); + } + + /** + * Setter for excludedPredefinedFunctions. + * + *

excludedPredefinedFunctions: List of predefined functions to exclude. + */ + @JsonProperty("excludedPredefinedFunctions") + public abstract Builder excludedPredefinedFunctions(List excludedPredefinedFunctions); + + /** + * Setter for excludedPredefinedFunctions (varargs convenience method). + * + *

excludedPredefinedFunctions: List of predefined functions to exclude. + */ + @CanIgnoreReturnValue + public Builder excludedPredefinedFunctions(String... excludedPredefinedFunctions) { + return excludedPredefinedFunctions(Arrays.asList(excludedPredefinedFunctions)); + } + + abstract Builder excludedPredefinedFunctions( + Optional> excludedPredefinedFunctions); + + /** Clears the value of excludedPredefinedFunctions field. */ + @CanIgnoreReturnValue + public Builder clearExcludedPredefinedFunctions() { + return excludedPredefinedFunctions(Optional.empty()); + } + + public abstract ComputerUse build(); + } + + /** Deserializes a JSON string to a ComputerUse object. */ + @ExcludeFromGeneratedCoverageReport + public static ComputerUse fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, ComputerUse.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/tools/FileSearch.java b/src/main/java/com/google/genai/types/interactions/tools/FileSearch.java new file mode 100644 index 00000000000..57b45f4859e --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/tools/FileSearch.java @@ -0,0 +1,152 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.tools; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * File search tool for the Interactions API. + * + *

Enables the model to search through file stores. + * + *

Example usage: + * + *

{@code
+ * FileSearch fileSearchTool = FileSearch.builder()
+ *     .fileSearchStoreNames("my-store-1", "my-store-2")
+ *     .topK(10)
+ *     .build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = FileSearch.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("file_search") +public abstract class FileSearch extends JsonSerializable implements Tool { + + /** The names of the file search stores to search. */ + @JsonProperty("file_search_store_names") + public abstract Optional> fileSearchStoreNames(); + + /** The maximum number of results to return. */ + @JsonProperty("top_k") + public abstract Optional topK(); + + /** Optional metadata filter for the search. */ + @JsonProperty("metadata_filter") + public abstract Optional metadataFilter(); + + /** Instantiates a builder for FileSearch. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_FileSearch.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for FileSearch. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code FileSearch.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_FileSearch.Builder(); + } + + /** + * Setter for fileSearchStoreNames. + * + *

fileSearchStoreNames: The names of the file search stores to search. + */ + @JsonProperty("file_search_store_names") + public abstract Builder fileSearchStoreNames(List fileSearchStoreNames); + + /** + * Setter for fileSearchStoreNames (varargs convenience method). + * + *

fileSearchStoreNames: The names of the file search stores to search. + */ + @CanIgnoreReturnValue + public Builder fileSearchStoreNames(String... fileSearchStoreNames) { + return fileSearchStoreNames(Arrays.asList(fileSearchStoreNames)); + } + + abstract Builder fileSearchStoreNames(Optional> fileSearchStoreNames); + + /** Clears the value of fileSearchStoreNames field. */ + @CanIgnoreReturnValue + public Builder clearFileSearchStoreNames() { + return fileSearchStoreNames(Optional.empty()); + } + + /** + * Setter for topK. + * + *

topK: The maximum number of results to return. + */ + @JsonProperty("top_k") + public abstract Builder topK(Integer topK); + + abstract Builder topK(Optional topK); + + /** Clears the value of topK field. */ + @CanIgnoreReturnValue + public Builder clearTopK() { + return topK(Optional.empty()); + } + + /** + * Setter for metadataFilter. + * + *

metadataFilter: Optional metadata filter for the search. + */ + @JsonProperty("metadata_filter") + public abstract Builder metadataFilter(String metadataFilter); + + abstract Builder metadataFilter(Optional metadataFilter); + + /** Clears the value of metadataFilter field. */ + @CanIgnoreReturnValue + public Builder clearMetadataFilter() { + return metadataFilter(Optional.empty()); + } + + public abstract FileSearch build(); + } + + /** Deserializes a JSON string to a FileSearch object. */ + @ExcludeFromGeneratedCoverageReport + public static FileSearch fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, FileSearch.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/tools/Function.java b/src/main/java/com/google/genai/types/interactions/tools/Function.java new file mode 100644 index 00000000000..24274da33a9 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/tools/Function.java @@ -0,0 +1,162 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.tools; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.Schema; +import java.util.Optional; + +/** + * Function tool for the Interactions API. + * + *

Represents a callable function that the model can invoke. Unlike the GenerateContent API where + * one Tool can contain multiple FunctionDeclarations, in the Interactions API each Function + * represents a single function. + * + *

Example usage with manual declaration: + * + *

{@code
+ * Function weatherTool = Function.builder()
+ *     .name("get_weather")
+ *     .description("Get the current weather for a location")
+ *     .parameters(Schema.builder()
+ *         .type("object")
+ *         .properties(Map.of(
+ *             "location", Schema.builder().type("string").build()))
+ *         .required("location")
+ *         .build())
+ *     .build();
+ * }
+ * + *

The Interactions API does not support Automatic Function Calling (AFC). All function execution + * must be handled manually by the application. + * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = Function.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("function") +public abstract class Function extends JsonSerializable implements Tool { + + /** The name of the function to call. */ + @JsonProperty("name") + public abstract Optional name(); + + /** A description of what the function does, used by the model to decide when to call it. */ + @JsonProperty("description") + public abstract Optional description(); + + /** The parameters schema for the function in JSON Schema format. */ + @JsonProperty("parameters") + public abstract Optional parameters(); + + /** Instantiates a builder for Function. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_Function.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for Function. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code Function.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_Function.Builder(); + } + + /** + * Setter for name. + * + *

name: The name of the function to call. + */ + @JsonProperty("name") + public abstract Builder name(String name); + + abstract Builder name(Optional name); + + /** Clears the value of name field. */ + @CanIgnoreReturnValue + public Builder clearName() { + return name(Optional.empty()); + } + + /** + * Setter for description. + * + *

description: A description of what the function does. + */ + @JsonProperty("description") + public abstract Builder description(String description); + + abstract Builder description(Optional description); + + /** Clears the value of description field. */ + @CanIgnoreReturnValue + public Builder clearDescription() { + return description(Optional.empty()); + } + + /** + * Setter for parameters. + * + *

parameters: The parameters schema for the function. + */ + @JsonProperty("parameters") + public abstract Builder parameters(Schema parameters); + + /** + * Setter for parameters builder. + * + *

parameters: The parameters schema for the function. + */ + @CanIgnoreReturnValue + public Builder parameters(Schema.Builder parametersBuilder) { + return parameters(parametersBuilder.build()); + } + + abstract Builder parameters(Optional parameters); + + /** Clears the value of parameters field. */ + @CanIgnoreReturnValue + public Builder clearParameters() { + return parameters(Optional.empty()); + } + + public abstract Function build(); + } + + /** Deserializes a JSON string to a Function object. */ + @ExcludeFromGeneratedCoverageReport + public static Function fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, Function.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/tools/GoogleSearch.java b/src/main/java/com/google/genai/types/interactions/tools/GoogleSearch.java new file mode 100644 index 00000000000..c1379c3d518 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/tools/GoogleSearch.java @@ -0,0 +1,74 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.tools; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; + +/** + * Google Search tool for the Interactions API. + * + *

Enables the model to search the web using Google Search. + * + *

Example usage: + * + *

{@code
+ * GoogleSearch searchTool = GoogleSearch.builder().build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = GoogleSearch.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("google_search") +public abstract class GoogleSearch extends JsonSerializable implements Tool { + + /** Instantiates a builder for GoogleSearch. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_GoogleSearch.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for GoogleSearch. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code GoogleSearch.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_GoogleSearch.Builder(); + } + + public abstract GoogleSearch build(); + } + + /** Deserializes a JSON string to a GoogleSearch object. */ + @ExcludeFromGeneratedCoverageReport + public static GoogleSearch fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, GoogleSearch.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/tools/McpServer.java b/src/main/java/com/google/genai/types/interactions/tools/McpServer.java new file mode 100644 index 00000000000..b7f7f5ce7a8 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/tools/McpServer.java @@ -0,0 +1,174 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.tools; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; +import com.google.genai.types.interactions.AllowedTools; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * MCP (Model Context Protocol) server tool for the Interactions API. + * + *

Enables the model to interact with an MCP server. + * + *

Example usage: + * + *

{@code
+ * McpServer mcpTool = McpServer.builder()
+ *     .name("my-mcp-server")
+ *     .url("https://mcp.example.com/endpoint")
+ *     .build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = McpServer.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("mcp_server") +public abstract class McpServer extends JsonSerializable implements Tool { + + /** The name of the MCP server. */ + @JsonProperty("name") + public abstract Optional name(); + + /** The URL of the MCP server endpoint. */ + @JsonProperty("url") + public abstract Optional url(); + + /** Optional headers to include in requests to the MCP server. */ + @JsonProperty("headers") + public abstract Optional> headers(); + + /** List of allowed tools configuration from the MCP server. */ + @JsonProperty("allowed_tools") + public abstract Optional> allowedTools(); + + /** Instantiates a builder for McpServer. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_McpServer.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for McpServer. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code McpServer.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_McpServer.Builder(); + } + + /** + * Setter for name. + * + *

name: The name of the MCP server. + */ + @JsonProperty("name") + public abstract Builder name(String name); + + abstract Builder name(Optional name); + + /** Clears the value of name field. */ + @CanIgnoreReturnValue + public Builder clearName() { + return name(Optional.empty()); + } + + /** + * Setter for url. + * + *

url: The URL of the MCP server endpoint. + */ + @JsonProperty("url") + public abstract Builder url(String url); + + abstract Builder url(Optional url); + + /** Clears the value of url field. */ + @CanIgnoreReturnValue + public Builder clearUrl() { + return url(Optional.empty()); + } + + /** + * Setter for headers. + * + *

headers: Optional headers to include in requests. + */ + @JsonProperty("headers") + public abstract Builder headers(Map headers); + + abstract Builder headers(Optional> headers); + + /** Clears the value of headers field. */ + @CanIgnoreReturnValue + public Builder clearHeaders() { + return headers(Optional.empty()); + } + + /** + * Setter for allowedTools. + * + *

allowedTools: List of allowed tools configuration from the MCP server. + */ + @JsonProperty("allowed_tools") + public abstract Builder allowedTools(List allowedTools); + + /** + * Setter for allowedTools (varargs convenience method). + * + *

allowedTools: List of allowed tools configuration from the MCP server. + */ + @CanIgnoreReturnValue + public Builder allowedTools(AllowedTools... allowedTools) { + return allowedTools(Arrays.asList(allowedTools)); + } + + abstract Builder allowedTools(Optional> allowedTools); + + /** Clears the value of allowedTools field. */ + @CanIgnoreReturnValue + public Builder clearAllowedTools() { + return allowedTools(Optional.empty()); + } + + public abstract McpServer build(); + } + + /** Deserializes a JSON string to a McpServer object. */ + @ExcludeFromGeneratedCoverageReport + public static McpServer fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, McpServer.class); + } +} diff --git a/src/main/java/com/google/genai/types/interactions/tools/Tool.java b/src/main/java/com/google/genai/types/interactions/tools/Tool.java new file mode 100644 index 00000000000..eefc1f43ed2 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/tools/Tool.java @@ -0,0 +1,49 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.tools; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +/** + * Base interface for tool types using a type discriminator. + * + *

This follows Jackson best practices for polymorphism: + * + *

    + *
  • {@code @JsonTypeInfo} on the base type with property "type" + *
  • Explicit {@code @JsonSubTypes} registration + *
  • Jackson handles type discrimination via annotations + *
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = Function.class, name = "function"), + @JsonSubTypes.Type(value = GoogleSearch.class, name = "google_search"), + @JsonSubTypes.Type(value = CodeExecution.class, name = "code_execution"), + @JsonSubTypes.Type(value = UrlContext.class, name = "url_context"), + @JsonSubTypes.Type(value = ComputerUse.class, name = "computer_use"), + @JsonSubTypes.Type(value = McpServer.class, name = "mcp_server"), + @JsonSubTypes.Type(value = FileSearch.class, name = "file_search"), +}) +public interface Tool { + // Marker interface - Jackson handles type discrimination via annotations +} diff --git a/src/main/java/com/google/genai/types/interactions/tools/UrlContext.java b/src/main/java/com/google/genai/types/interactions/tools/UrlContext.java new file mode 100644 index 00000000000..f81ef1ccaf1 --- /dev/null +++ b/src/main/java/com/google/genai/types/interactions/tools/UrlContext.java @@ -0,0 +1,74 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.tools; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import com.google.genai.JsonSerializable; +import com.google.genai.types.ExcludeFromGeneratedCoverageReport; + +/** + * URL context tool for the Interactions API. + * + *

Enables the model to retrieve and use context from URLs. + * + *

Example usage: + * + *

{@code
+ * UrlContext urlTool = UrlContext.builder().build();
+ * }
+ * + *

The Interactions API is available in both Vertex AI and Gemini API. + * + *

Note: The Interactions API is in beta and subject to change. + */ +@AutoValue +@JsonDeserialize(builder = UrlContext.Builder.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonTypeName("url_context") +public abstract class UrlContext extends JsonSerializable implements Tool { + + /** Instantiates a builder for UrlContext. */ + @ExcludeFromGeneratedCoverageReport + public static Builder builder() { + return new AutoValue_UrlContext.Builder(); + } + + /** Creates a builder with the same values as this instance. */ + public abstract Builder toBuilder(); + + /** Builder for UrlContext. */ + @AutoValue.Builder + public abstract static class Builder { + /** For internal usage. Please use {@code UrlContext.builder()} for instantiation. */ + @JsonCreator + private static Builder create() { + return new AutoValue_UrlContext.Builder(); + } + + public abstract UrlContext build(); + } + + /** Deserializes a JSON string to a UrlContext object. */ + @ExcludeFromGeneratedCoverageReport + public static UrlContext fromJson(String jsonString) { + return JsonSerializable.fromJsonString(jsonString, UrlContext.class); + } +} diff --git a/src/test/java/com/google/genai/AsyncInteractionsTest.java b/src/test/java/com/google/genai/AsyncInteractionsTest.java new file mode 100644 index 00000000000..09ae9e3d7eb --- /dev/null +++ b/src/test/java/com/google/genai/AsyncInteractionsTest.java @@ -0,0 +1,115 @@ +/* + * 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. + */ + +package com.google.genai; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.GenerationConfig; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; +import org.junit.jupiter.api.Test; + +/** Tests for the AsyncInteractions resource. */ +public class AsyncInteractionsTest { + + @Test + public void testAsyncValidationBothModelAndAgent() throws ExecutionException, InterruptedException { + // Arrange + Client client = Client.builder().apiKey("test-key").build(); + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .agent("deep-research-pro-preview-12-2025") + .input("Test input") + .build(); + + // Act & Assert + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> client.async.interactions.create(config).get()); + + assertNotNull(exception.getMessage()); + assertTrue(exception.getMessage().contains("Cannot specify both 'model' and 'agent'")); + } + + @Test + public void testAsyncValidationNeitherModelNorAgent() + throws ExecutionException, InterruptedException { + // Arrange + Client client = Client.builder().apiKey("test-key").build(); + CreateInteractionConfig config = + CreateInteractionConfig.builder().input("Test input").build(); + + // Act & Assert + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> client.async.interactions.create(config).get()); + + assertNotNull(exception.getMessage()); + assertTrue(exception.getMessage().contains("Must specify either 'model' or 'agent'")); + } + + @Test + public void testAsyncValidationAgentWithGenerationConfig() + throws ExecutionException, InterruptedException { + // Arrange + Client client = Client.builder().apiKey("test-key").build(); + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .agent("deep-research-pro-preview-12-2025") + .input("Test input") + .generationConfig( + GenerationConfig.builder().temperature(0.5f).build()) + .build(); + + // Act & Assert + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> client.async.interactions.create(config).get()); + + assertNotNull(exception.getMessage()); + assertTrue(exception.getMessage().contains("Cannot use 'generationConfig' with agent-based")); + } + + @Test + public void testAsyncValidationModelWithAgentConfig() + throws ExecutionException, InterruptedException { + // Arrange + Client client = Client.builder().apiKey("test-key").build(); + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("Test input") + .agentConfig(com.google.genai.types.interactions.DynamicAgentConfig.create()) + .build(); + + // Act & Assert + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> client.async.interactions.create(config).get()); + + assertNotNull(exception.getMessage()); + assertTrue(exception.getMessage().contains("Cannot use 'agentConfig' with model-based")); + } + + // Note: Vertex AI support was added in PR3. + // The testAsyncVertexAIUnsupported test has been removed since Vertex AI is now supported. + // Integration tests for Vertex AI are in examples/VertexAIInteractionTest.java +} diff --git a/src/test/java/com/google/genai/InteractionsApiTest.java b/src/test/java/com/google/genai/InteractionsApiTest.java new file mode 100644 index 00000000000..c05d386fa21 --- /dev/null +++ b/src/test/java/com/google/genai/InteractionsApiTest.java @@ -0,0 +1,225 @@ +/* + * 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. + */ + +package com.google.genai; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.genai.types.interactions.CancelInteractionConfig; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.DeleteInteractionConfig; +import com.google.genai.types.interactions.GetInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.InteractionStatus; +import org.junit.jupiter.api.Test; + +/** + * API-level tests for Interactions resource. + * + *

These tests cover the request building and parameter validation logic without making real API + * calls. + */ +public class InteractionsApiTest { + + @Test + public void testCreateWithNullConfig() { + // Arrange + Client client = Client.builder().apiKey("test-key").build(); + + // Act & Assert + assertThrows(NullPointerException.class, () -> client.interactions.create(null)); + } + + @Test + public void testGetWithNullId() { + // Arrange + Client client = Client.builder().apiKey("test-key").build(); + + // Act & Assert + assertThrows( + IllegalArgumentException.class, () -> client.interactions.get(null, GetInteractionConfig.builder().build())); + } + + @Test + public void testGetWithEmptyId() { + // Arrange + Client client = Client.builder().apiKey("test-key").build(); + + // Act & Assert + assertThrows( + IllegalArgumentException.class, + () -> client.interactions.get("", GetInteractionConfig.builder().build())); + } + + @Test + public void testCancelWithNullId() { + // Arrange + Client client = Client.builder().apiKey("test-key").build(); + + // Act & Assert + assertThrows( + IllegalArgumentException.class, + () -> client.interactions.cancel(null, CancelInteractionConfig.builder().build())); + } + + @Test + public void testCancelWithEmptyId() { + // Arrange + Client client = Client.builder().apiKey("test-key").build(); + + // Act & Assert + assertThrows( + IllegalArgumentException.class, + () -> client.interactions.cancel("", CancelInteractionConfig.builder().build())); + } + + @Test + public void testDeleteWithNullId() { + // Arrange + Client client = Client.builder().apiKey("test-key").build(); + + // Act & Assert + assertThrows( + IllegalArgumentException.class, + () -> client.interactions.delete(null, DeleteInteractionConfig.builder().build())); + } + + @Test + public void testDeleteWithEmptyId() { + // Arrange + Client client = Client.builder().apiKey("test-key").build(); + + // Act & Assert + assertThrows( + IllegalArgumentException.class, + () -> client.interactions.delete("", DeleteInteractionConfig.builder().build())); + } + + @Test + public void testCreateConfigBuilderDefaults() { + // Act + CreateInteractionConfig config = + CreateInteractionConfig.builder().model("gemini-2.5-flash").input("test").build(); + + // Assert + assertTrue(config.model().isPresent()); + assertNotNull(config.input()); + } + + @Test + public void testGetConfigBuilder() { + // Act + GetInteractionConfig config = GetInteractionConfig.builder().build(); + + // Assert + assertNotNull(config); + } + + @Test + public void testCancelConfigBuilder() { + // Act + CancelInteractionConfig config = CancelInteractionConfig.builder().build(); + + // Assert + assertNotNull(config); + } + + @Test + public void testDeleteConfigBuilder() { + // Act + DeleteInteractionConfig config = DeleteInteractionConfig.builder().build(); + + // Assert + assertNotNull(config); + } + + // Note: JSON deserialization tests are complex due to Jackson configuration + // and are better tested through integration tests + + @Test + public void testInteractionSerialization() { + // Arrange + Interaction interaction = + Interaction.builder() + .id("test-id") + .status(InteractionStatus.IN_PROGRESS) + .model("gemini-2.5-flash") + .build(); + + // Act + String json = interaction.toJson(); + + // Assert - Just verify JSON is generated + assertNotNull(json); + assertTrue(json.length() > 0); + } + + @Test + public void testCreateConfigSerialization() { + // Arrange + CreateInteractionConfig config = + CreateInteractionConfig.builder().model("gemini-2.5-flash").input("What is AI?").build(); + + // Act + String json = config.toJson(); + + // Assert - Just verify JSON is generated + assertNotNull(json); + assertTrue(json.length() > 0); + } + + @Test + public void testInteractionWithPreviousId() { + // Act + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("Follow up question") + .previousInteractionId("previous-id-123") + .build(); + + // Assert + assertTrue(config.previousInteractionId().isPresent()); + assertEquals("previous-id-123", config.previousInteractionId().get()); + } + + @Test + public void testInteractionClearPreviousId() { + // Arrange + CreateInteractionConfig.Builder builder = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("test") + .previousInteractionId("id-123"); + + // Act + CreateInteractionConfig config = builder.clearPreviousInteractionId().build(); + + // Assert + assertTrue(!config.previousInteractionId().isPresent()); + } + + @Test + public void testInteractionStatusValues() { + // Test that all status values are accessible + assertNotNull(InteractionStatus.COMPLETED); + assertNotNull(InteractionStatus.IN_PROGRESS); + assertNotNull(InteractionStatus.FAILED); + } +} diff --git a/src/test/java/com/google/genai/InteractionsChatTest.java b/src/test/java/com/google/genai/InteractionsChatTest.java new file mode 100644 index 00000000000..4076eda704b --- /dev/null +++ b/src/test/java/com/google/genai/InteractionsChatTest.java @@ -0,0 +1,442 @@ +/* + * 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. + */ + +package com.google.genai; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.InteractionStatus; +import com.google.genai.types.interactions.content.Content; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import okhttp3.MediaType; +import okhttp3.ResponseBody; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +/** + * Chat-like multi-turn conversation tests for Interactions API. + * + *

Mirrors the pattern from ChatTest.java but specific to Interactions previousId-based + * conversations. + */ +public class InteractionsChatTest { + + private static final String MODEL_ID = "gemini-2.5-flash"; + private ApiClient mockedClient; + private ApiResponse mockedResponse; + private Client client; + + @BeforeEach + void setUp() throws Exception { + mockedClient = Mockito.mock(ApiClient.class); + mockedResponse = Mockito.mock(ApiResponse.class); + + String apiKey = "test-key"; + client = Client.builder().apiKey(apiKey).vertexAI(false).build(); + + // Use reflection to inject mocked client + Field apiClientField = Interactions.class.getDeclaredField("apiClient"); + apiClientField.setAccessible(true); + apiClientField.set(client.interactions, mockedClient); + } + + @Test + public void testSingleInteraction() throws Exception { + // Arrange + String responseJson = + "{" + + "\"id\":\"interaction-1\"," + + "\"status\":\"completed\"," + + "\"outputs\":[{\"type\":\"text\",\"text\":\"Hello!\"}]" + + "}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + CreateInteractionConfig config = + CreateInteractionConfig.builder().model(MODEL_ID).input("Hi").build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertEquals("interaction-1", interaction.id()); + assertEquals(InteractionStatus.COMPLETED, interaction.status()); + } + + @Test + public void testTwoTurnConversation() throws Exception { + // Arrange - First interaction + String response1Json = + "{" + + "\"id\":\"interaction-1\"," + + "\"status\":\"completed\"," + + "\"outputs\":[{\"type\":\"text\",\"text\":\"Hello! How can I help?\"}]" + + "}"; + ResponseBody body1 = ResponseBody.create(response1Json, MediaType.get("application/json")); + ApiResponse mockedResponse1 = createMockedResponse(body1); + + // Arrange - Second interaction + String response2Json = + "{" + + "\"id\":\"interaction-2\"," + + "\"status\":\"completed\"," + + "\"previous_interaction_id\":\"interaction-1\"," + + "\"outputs\":[{\"type\":\"text\",\"text\":\"The answer is 4.\"}]" + + "}"; + ResponseBody body2 = ResponseBody.create(response2Json, MediaType.get("application/json")); + ApiResponse mockedResponse2 = createMockedResponse(body2); + + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse1, mockedResponse2); + + // Act - First turn + CreateInteractionConfig config1 = + CreateInteractionConfig.builder().model(MODEL_ID).input("Hello").build(); + Interaction interaction1 = client.interactions.create(config1); + + // Act - Second turn with previousId + CreateInteractionConfig config2 = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("What is 2+2?") + .previousInteractionId(interaction1.id()) + .build(); + Interaction interaction2 = client.interactions.create(config2); + + // Assert + assertEquals("interaction-1", interaction1.id()); + assertEquals("interaction-2", interaction2.id()); + assertTrue(interaction2.previousInteractionId().isPresent()); + assertEquals("interaction-1", interaction2.previousInteractionId().get()); + } + + @Test + public void testMultiTurnConversationChain() throws Exception { + // Create a conversation chain with 3 interactions + List interactions = new ArrayList<>(); + String previousId = null; + + for (int i = 1; i <= 3; i++) { + String interactionId = "interaction-" + i; + String prevIdField = previousId != null ? ",\"previous_interaction_id\":\"" + previousId + "\"" : ""; + String responseJson = + "{" + + "\"id\":\"" + + interactionId + + "\"," + + "\"status\":\"completed\"" + + prevIdField + + "," + + "\"outputs\":[{\"type\":\"text\",\"text\":\"Response " + + i + + "\"}]" + + "}"; + + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + ApiResponse mockedResp = createMockedResponse(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResp); + + CreateInteractionConfig.Builder configBuilder = + CreateInteractionConfig.builder().model(MODEL_ID).input("Question " + i); + + if (previousId != null) { + configBuilder.previousInteractionId(previousId); + } + + Interaction interaction = client.interactions.create(configBuilder.build()); + interactions.add(interaction); + previousId = interaction.id(); + } + + // Assert conversation chain + assertEquals(3, interactions.size()); + assertEquals("interaction-1", interactions.get(0).id()); + assertEquals("interaction-2", interactions.get(1).id()); + assertEquals("interaction-3", interactions.get(2).id()); + + // Verify previousId chain + assertTrue(interactions.get(1).previousInteractionId().isPresent()); + assertEquals("interaction-1", interactions.get(1).previousInteractionId().get()); + + assertTrue(interactions.get(2).previousInteractionId().isPresent()); + assertEquals("interaction-2", interactions.get(2).previousInteractionId().get()); + } + + @Test + public void testConversationWithSameModel() throws Exception { + // Ensure all interactions in a conversation use the same model + String response1Json = + "{\"id\":\"int-1\",\"status\":\"completed\",\"model\":\"" + + MODEL_ID + + "\",\"outputs\":[{\"type\":\"text\",\"text\":\"Response 1\"}]}"; + String response2Json = + "{\"id\":\"int-2\",\"status\":\"completed\",\"model\":\"" + + MODEL_ID + + "\",\"previous_interaction_id\":\"int-1\",\"outputs\":[{\"type\":\"text\",\"text\":\"Response 2\"}]}"; + + ApiResponse resp1 = createMockedResponse(ResponseBody.create(response1Json, MediaType.get("application/json"))); + ApiResponse resp2 = createMockedResponse(ResponseBody.create(response2Json, MediaType.get("application/json"))); + + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(resp1, resp2); + + CreateInteractionConfig config1 = + CreateInteractionConfig.builder().model(MODEL_ID).input("First").build(); + Interaction int1 = client.interactions.create(config1); + + CreateInteractionConfig config2 = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Second") + .previousInteractionId(int1.id()) + .build(); + Interaction int2 = client.interactions.create(config2); + + // Both should have the same model + assertEquals(MODEL_ID, int1.model().get()); + assertEquals(MODEL_ID, int2.model().get()); + } + + @Test + public void testClearPreviousIdStartsNewConversation() { + // Test that clearing previousId starts a new conversation thread + CreateInteractionConfig.Builder builder = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("test") + .previousInteractionId("some-id"); + + // Clear the previousId + CreateInteractionConfig config = builder.clearPreviousInteractionId().build(); + + // Assert previousId is not present + assertTrue(!config.previousInteractionId().isPresent()); + } + + @Test + public void testConversationContextPreservation() throws Exception { + // Simulate a conversation where context is preserved + String response1Json = + "{\"id\":\"ctx-1\",\"status\":\"completed\",\"outputs\":[{\"type\":\"text\",\"text\":\"My name is Claude.\"}]}"; + String response2Json = + "{\"id\":\"ctx-2\",\"status\":\"completed\",\"previous_interaction_id\":\"ctx-1\",\"outputs\":[{\"type\":\"text\",\"text\":\"I told you, I'm Claude.\"}]}"; + + ApiResponse ctx1 = createMockedResponse(ResponseBody.create(response1Json, MediaType.get("application/json"))); + ApiResponse ctx2 = createMockedResponse(ResponseBody.create(response2Json, MediaType.get("application/json"))); + + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(ctx1, ctx2); + + CreateInteractionConfig config1 = + CreateInteractionConfig.builder().model(MODEL_ID).input("What is your name?").build(); + Interaction int1 = client.interactions.create(config1); + + CreateInteractionConfig config2 = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("What did you just say?") + .previousInteractionId(int1.id()) + .build(); + Interaction int2 = client.interactions.create(config2); + + // The second response references the first (context preserved) + assertTrue(int2.previousInteractionId().isPresent()); + assertEquals(int1.id(), int2.previousInteractionId().get()); + } + + @Test + public void testInProgressInteractionInConversation() throws Exception { + // Test conversation where one interaction is still in progress + String response1Json = + "{\"id\":\"prog-1\",\"status\":\"completed\",\"outputs\":[{\"type\":\"text\",\"text\":\"First response\"}]}"; + String response2Json = + "{\"id\":\"prog-2\",\"status\":\"in_progress\",\"previous_interaction_id\":\"prog-1\"}"; + + ApiResponse prog1 = createMockedResponse(ResponseBody.create(response1Json, MediaType.get("application/json"))); + ApiResponse prog2 = createMockedResponse(ResponseBody.create(response2Json, MediaType.get("application/json"))); + + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(prog1, prog2); + + CreateInteractionConfig config1 = + CreateInteractionConfig.builder().model(MODEL_ID).input("Question 1").build(); + Interaction int1 = client.interactions.create(config1); + + CreateInteractionConfig config2 = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Question 2") + .previousInteractionId(int1.id()) + .build(); + Interaction int2 = client.interactions.create(config2); + + // First is completed, second is in progress + assertEquals(InteractionStatus.COMPLETED, int1.status()); + assertEquals(InteractionStatus.IN_PROGRESS, int2.status()); + } + + @Test + public void testBranchingConversations() throws Exception { + // Test creating multiple follow-ups from the same interaction (branching) + String baseJson = + "{\"id\":\"base\",\"status\":\"completed\",\"outputs\":[{\"type\":\"text\",\"text\":\"Base response\"}]}"; + String branch1Json = + "{\"id\":\"branch-1\",\"status\":\"completed\",\"previous_interaction_id\":\"base\",\"outputs\":[{\"type\":\"text\",\"text\":\"Branch 1\"}]}"; + String branch2Json = + "{\"id\":\"branch-2\",\"status\":\"completed\",\"previous_interaction_id\":\"base\",\"outputs\":[{\"type\":\"text\",\"text\":\"Branch 2\"}]}"; + + ApiResponse baseResp = createMockedResponse(ResponseBody.create(baseJson, MediaType.get("application/json"))); + ApiResponse branch1Resp = createMockedResponse(ResponseBody.create(branch1Json, MediaType.get("application/json"))); + ApiResponse branch2Resp = createMockedResponse(ResponseBody.create(branch2Json, MediaType.get("application/json"))); + + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(baseResp, branch1Resp, branch2Resp); + + // Create base interaction + CreateInteractionConfig baseConfig = + CreateInteractionConfig.builder().model(MODEL_ID).input("Base question").build(); + Interaction base = client.interactions.create(baseConfig); + + // Create two branches from the same base + CreateInteractionConfig branch1Config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Follow-up A") + .previousInteractionId(base.id()) + .build(); + Interaction branch1 = client.interactions.create(branch1Config); + + CreateInteractionConfig branch2Config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Follow-up B") + .previousInteractionId(base.id()) + .build(); + Interaction branch2 = client.interactions.create(branch2Config); + + // Both branches reference the same parent + assertEquals(base.id(), branch1.previousInteractionId().get()); + assertEquals(base.id(), branch2.previousInteractionId().get()); + } + + @Test + public void testLongConversationChain() throws Exception { + // Test a long conversation chain (5+ turns) + int conversationLength = 5; + List chain = new ArrayList<>(); + String previousId = null; + + for (int i = 1; i <= conversationLength; i++) { + String id = "long-" + i; + String prevField = previousId != null ? ",\"previous_interaction_id\":\"" + previousId + "\"" : ""; + String json = + "{\"id\":\"" + id + "\",\"status\":\"completed\"" + prevField + ",\"outputs\":[{\"type\":\"text\",\"text\":\"Turn " + i + "\"}]}"; + + ApiResponse turnResp = createMockedResponse(ResponseBody.create(json, MediaType.get("application/json"))); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(turnResp); + + CreateInteractionConfig.Builder builder = + CreateInteractionConfig.builder().model(MODEL_ID).input("Message " + i); + if (previousId != null) { + builder.previousInteractionId(previousId); + } + + Interaction interaction = client.interactions.create(builder.build()); + chain.add(interaction); + previousId = interaction.id(); + } + + // Verify chain integrity + assertEquals(conversationLength, chain.size()); + for (int i = 1; i < conversationLength; i++) { + assertTrue(chain.get(i).previousInteractionId().isPresent()); + assertEquals(chain.get(i - 1).id(), chain.get(i).previousInteractionId().get()); + } + } + + @Test + public void testConversationHistoryTracking() throws Exception { + // Manually track conversation history (similar to Chat.getHistory) + List history = new ArrayList<>(); + + String json1 = + "{\"id\":\"hist-1\",\"status\":\"completed\",\"outputs\":[{\"type\":\"text\",\"text\":\"Response 1\"}]}"; + String json2 = + "{\"id\":\"hist-2\",\"status\":\"completed\",\"previous_interaction_id\":\"hist-1\",\"outputs\":[{\"type\":\"text\",\"text\":\"Response 2\"}]}"; + String json3 = + "{\"id\":\"hist-3\",\"status\":\"completed\",\"previous_interaction_id\":\"hist-2\",\"outputs\":[{\"type\":\"text\",\"text\":\"Response 3\"}]}"; + + ApiResponse hist1 = createMockedResponse(ResponseBody.create(json1, MediaType.get("application/json"))); + ApiResponse hist2 = createMockedResponse(ResponseBody.create(json2, MediaType.get("application/json"))); + ApiResponse hist3 = createMockedResponse(ResponseBody.create(json3, MediaType.get("application/json"))); + + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(hist1, hist2, hist3); + + // Build conversation and track history + CreateInteractionConfig config1 = + CreateInteractionConfig.builder().model(MODEL_ID).input("Turn 1").build(); + Interaction int1 = client.interactions.create(config1); + history.add(int1); + + CreateInteractionConfig config2 = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Turn 2") + .previousInteractionId(int1.id()) + .build(); + Interaction int2 = client.interactions.create(config2); + history.add(int2); + + CreateInteractionConfig config3 = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Turn 3") + .previousInteractionId(int2.id()) + .build(); + Interaction int3 = client.interactions.create(config3); + history.add(int3); + + // Verify history + assertEquals(3, history.size()); + assertEquals("hist-1", history.get(0).id()); + assertEquals("hist-2", history.get(1).id()); + assertEquals("hist-3", history.get(2).id()); + } + + // Helper method + private ApiResponse createMockedResponse(ResponseBody body) { + ApiResponse response = Mockito.mock(ApiResponse.class); + when(response.getBody()).thenReturn(body); + return response; + } +} diff --git a/src/test/java/com/google/genai/InteractionsComprehensiveTest.java b/src/test/java/com/google/genai/InteractionsComprehensiveTest.java new file mode 100644 index 00000000000..29c16370f41 --- /dev/null +++ b/src/test/java/com/google/genai/InteractionsComprehensiveTest.java @@ -0,0 +1,926 @@ +/* + * 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. + */ + +package com.google.genai; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.genai.types.Content; +import com.google.genai.types.FunctionDeclaration; +import com.google.genai.types.Part; +import com.google.genai.types.Schema; +import com.google.genai.types.Type; +import com.google.genai.types.interactions.CancelInteractionConfig; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.DeleteInteractionConfig; +import com.google.genai.types.interactions.DeleteInteractionResponse; +import com.google.genai.types.interactions.GenerationConfig; +import com.google.genai.types.interactions.GetInteractionConfig; +import com.google.genai.types.interactions.ImageConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.ResponseModality; +import com.google.genai.types.interactions.content.AudioContent; +import com.google.genai.types.interactions.content.DocumentContent; +import com.google.genai.types.interactions.content.ImageContent; +import com.google.genai.types.interactions.content.TextContent; +import com.google.genai.types.interactions.content.VideoContent; +import com.google.genai.types.interactions.tools.CodeExecution; +import com.google.genai.types.interactions.tools.Function; +import com.google.genai.types.interactions.tools.GoogleSearch; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * Comprehensive replay-based synchronous tests for Interactions API. + * + *

This test suite follows the same pattern as ModelsTest.java, providing comprehensive coverage + * of all Interactions API features including CRUD operations, model-based interactions, + * agent-based interactions, tools, multimodal content, and error scenarios. + * + *

To run these tests, set: export GOOGLE_GENAI_REPLAYS_DIRECTORY="/path/to/genai/replays" + */ +@EnabledIfEnvironmentVariable( + named = "GOOGLE_GENAI_REPLAYS_DIRECTORY", + matches = ".*genai/replays.*") +@ExtendWith(EnvironmentVariablesMockingExtension.class) +public class InteractionsComprehensiveTest { + + private static final String MODEL_ID = "gemini-2.5-flash"; + private static final String AGENT_ID = "deep-research-pro-preview-12-2025"; + + // ==================== Core CRUD Operations ==================== + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withBasicModel(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/comprehensive/create_basic_model." + suffix + ".json"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("What is the capital of France?") + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + assertNotNull(interaction.status()); + assertTrue(interaction.model().isPresent()); + assertEquals(MODEL_ID, interaction.model().get()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testGet_withValidId(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/comprehensive/get_valid_id." + suffix + ".json"); + + String interactionId = + vertexAI + ? "projects/test-project/locations/us-central1/interactions/test-interaction-id" + : "v1_test-interaction-id"; + + GetInteractionConfig config = GetInteractionConfig.builder().build(); + + // Act + Interaction interaction = client.interactions.get(interactionId, config); + + // Assert + assertNotNull(interaction); + assertEquals(interactionId, interaction.id()); + assertNotNull(interaction.status()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCancel_backgroundInteraction(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/cancel_background." + suffix + ".json"); + + String interactionId = + vertexAI + ? "projects/test-project/locations/us-central1/interactions/background-id" + : "v1_background-id"; + + CancelInteractionConfig config = CancelInteractionConfig.builder().build(); + + // Act + Interaction interaction = client.interactions.cancel(interactionId, config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + assertNotNull(interaction.status()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testDelete_existingInteraction(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/comprehensive/delete_existing." + suffix + ".json"); + + String interactionId = + vertexAI + ? "projects/test-project/locations/us-central1/interactions/delete-id" + : "v1_delete-id"; + + DeleteInteractionConfig config = DeleteInteractionConfig.builder().build(); + + // Act + DeleteInteractionResponse response = client.interactions.delete(interactionId, config); + + // Assert + assertNotNull(response); + } + + // ==================== Model-Based Interactions ==================== + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withGenerationConfig(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_generation_config." + suffix + ".json"); + + GenerationConfig generationConfig = + GenerationConfig.builder() + .temperature(0.7f) + .topP(0.9f) + .maxOutputTokens(1024) + .stopSequences(ImmutableList.of("END", "STOP")) + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Write a creative story.") + .generationConfig(generationConfig) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + assertTrue(interaction.model().isPresent()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withSystemInstruction(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_system_instruction." + suffix + ".json"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Explain quantum mechanics.") + .systemInstruction("You are a helpful assistant specialized in science.") + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withResponseFormat(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_response_format." + suffix + ".json"); + + Schema responseSchema = + Schema.builder() + .type(new Type(Type.Known.OBJECT)) + .properties(ImmutableMap.of( + "name", Schema.builder().type(new Type(Type.Known.STRING)).build(), + "age", Schema.builder().type(new Type(Type.Known.INTEGER)).build())) + .required("name", "age") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Generate a person's information.") + .responseFormat(responseSchema) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withResponseMimeType(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_response_mime_type." + suffix + ".json"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Return JSON with user data.") + .responseMimeType("application/json") + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withResponseModalities(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_response_modalities." + suffix + ".json"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Describe a sunset.") + .responseModalities(ResponseModality.Known.TEXT, ResponseModality.Known.IMAGE) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withStoreTrue(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/comprehensive/create_store_true." + suffix + ".json"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Remember this conversation.") + .store(true) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withBackgroundTrue(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_background_true." + suffix + ".json"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Process this in the background.") + .background(true) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withPreviousInteractionId(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_previous_interaction_id." + + suffix + + ".json"); + + String previousId = + vertexAI + ? "projects/test-project/locations/us-central1/interactions/previous-id" + : "v1_previous-id"; + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Continue the conversation.") + .previousInteractionId(previousId) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + // ==================== Agent-Based Interactions ==================== + + @ParameterizedTest + @ValueSource(booleans = {false}) // Agent is MLDev only + public void testCreate_withAgent(boolean vertexAI) throws Exception { + // Arrange + String suffix = "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/comprehensive/create_with_agent." + suffix + ".json"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .agent(AGENT_ID) + .input("Research the history of quantum computing.") + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + assertTrue(interaction.agent().isPresent()); + assertEquals(AGENT_ID, interaction.agent().get()); + } + + @ParameterizedTest + @ValueSource(booleans = {false}) // Agent is MLDev only + public void testCreate_withAgentConfig(boolean vertexAI) throws Exception { + // Arrange + String suffix = "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_with_agent_config." + suffix + ".json"); + + com.google.genai.types.interactions.AgentConfig agentConfig = + com.google.genai.types.interactions.DeepResearchAgentConfig.builder() + .thinkingSummaries( + new com.google.genai.types.interactions.ThinkingSummaries( + com.google.genai.types.interactions.ThinkingSummaries.Known.AUTO)) + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .agent(AGENT_ID) + .input("Deep research on AI ethics.") + .agentConfig(agentConfig) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false}) // Agent is MLDev only + public void testCreate_agentWithBackground(boolean vertexAI) throws Exception { + // Arrange + String suffix = "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_agent_background." + suffix + ".json"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .agent(AGENT_ID) + .input("Long-running research task.") + .background(true) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + // ==================== Tools & Function Calling ==================== + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withFunction(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_function_tool." + suffix + ".json"); + + Function functionTool = Function.builder() + .name("get_weather") + .description("Get the current weather for a location") + .parameters( + Schema.builder() + .type(new Type(Type.Known.OBJECT)) + .properties(ImmutableMap.of( + "location", Schema.builder().type(new Type(Type.Known.STRING)).build())) + .required("location") + .build()) + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("What's the weather in San Francisco?") + .tools(ImmutableList.of(functionTool)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withGoogleSearch(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_google_search." + suffix + ".json"); + + GoogleSearch googleSearch = GoogleSearch.builder().build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Search for latest AI news.") + .tools(ImmutableList.of(googleSearch)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withCodeExecution(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_code_execution." + suffix + ".json"); + + CodeExecution codeExecution = CodeExecution.builder().build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Calculate the factorial of 10.") + .tools(ImmutableList.of(codeExecution)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {true}) // VertexAI only + public void testCreate_withVertexAISearch(boolean vertexAI) throws Exception { + // Arrange + String suffix = "vertex"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_vertex_search." + suffix + ".json"); + + // Note: VertexAISearchTool would be used here if available + // For now, we'll use a placeholder or skip this test if the tool doesn't exist + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Search Vertex AI documentation.") + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {true}) // VertexAI only + public void testCreate_withRagRetrieval(boolean vertexAI) throws Exception { + // Arrange + String suffix = "vertex"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_rag_retrieval." + suffix + ".json"); + + // Note: RagRetrievalTool would be used here if available + // For now, we'll use a placeholder or skip this test if the tool doesn't exist + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Retrieve relevant documents.") + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withMultipleTools(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_multiple_tools." + suffix + ".json"); + + Function functionTool = Function.builder() + .name("get_weather") + .description("Get weather") + .parameters( + Schema.builder() + .type(new Type(Type.Known.OBJECT)) + .properties(ImmutableMap.of( + "location", Schema.builder().type(new Type(Type.Known.STRING)).build())) + .build()) + .build(); + GoogleSearch googleSearch = GoogleSearch.builder().build(); + CodeExecution codeExecution = CodeExecution.builder().build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Use multiple tools to answer my question.") + .tools(ImmutableList.of(functionTool, googleSearch, codeExecution)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + // ==================== Multimodal Content Tests ==================== + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withImageContent(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_image_content." + suffix + ".json"); + + URL resourceUrl = getClass().getClassLoader().getResource("google.png"); + Path filePath = Paths.get(resourceUrl.toURI()); + + ImageContent imageContent = + ImageContent.builder() + .uri(filePath.toAbsolutePath().toString()) + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder().text("Describe this image.").build(), + imageContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withAudioContent(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_audio_content." + suffix + ".json"); + + // Assuming we have a test audio file + AudioContent audioContent = + AudioContent.builder() + .uri("gs://test-bucket/audio.mp3") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder().text("Transcribe this audio.").build(), + audioContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withVideoContent(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_video_content." + suffix + ".json"); + + VideoContent videoContent = + VideoContent.builder() + .uri("gs://test-bucket/video.mp4") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder().text("Analyze this video.").build(), + videoContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withDocumentContent(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_document_content." + suffix + ".json"); + + DocumentContent documentContent = + DocumentContent.builder() + .uri("gs://test-bucket/document.pdf") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder().text("Summarize this document.").build(), + documentContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_withMixedMediaContent(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/create_mixed_media." + suffix + ".json"); + + URL resourceUrl = getClass().getClassLoader().getResource("google.png"); + Path filePath = Paths.get(resourceUrl.toURI()); + + ImageContent imageContent = + ImageContent.builder() + .uri(filePath.toAbsolutePath().toString()) + .build(); + + AudioContent audioContent = + AudioContent.builder() + .uri("gs://test-bucket/audio.mp3") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder().text("Analyze these media files.").build(), + imageContent, + audioContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + // ==================== Error Scenarios ==================== + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_bothModelAndAgent_throwsException(boolean vertexAI) throws Exception { + // Arrange + Client client = TestUtils.createClient(vertexAI, "unused"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .agent(AGENT_ID) + .input("Invalid config") + .build(); + + // Act & Assert + assertThrows( + IllegalArgumentException.class, + () -> client.interactions.create(config)); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testCreate_neitherModelNorAgent_throwsException(boolean vertexAI) throws Exception { + // Arrange + Client client = TestUtils.createClient(vertexAI, "unused"); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .input("Invalid config - no model or agent") + .build(); + + // Act & Assert + assertThrows( + IllegalArgumentException.class, + () -> client.interactions.create(config)); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testGet_invalidId_throwsException(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/get_invalid_id." + suffix + ".json"); + + String invalidId = "invalid-interaction-id"; + GetInteractionConfig config = GetInteractionConfig.builder().build(); + + // Act & Assert + // Assuming the API returns a 404 or similar error + assertThrows( + Exception.class, // Could be more specific based on actual exception type + () -> client.interactions.get(invalidId, config)); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testDelete_alreadyDeleted_throwsException(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, + "tests/interactions/comprehensive/delete_already_deleted." + suffix + ".json"); + + String deletedId = + vertexAI + ? "projects/test-project/locations/us-central1/interactions/deleted-id" + : "v1_deleted-id"; + + DeleteInteractionConfig config = DeleteInteractionConfig.builder().build(); + + // Act & Assert + assertThrows( + Exception.class, // Could be more specific based on actual exception type + () -> client.interactions.delete(deletedId, config)); + } +} diff --git a/src/test/java/com/google/genai/InteractionsGenerationConfigTest.java b/src/test/java/com/google/genai/InteractionsGenerationConfigTest.java new file mode 100644 index 00000000000..41ed9b239a2 --- /dev/null +++ b/src/test/java/com/google/genai/InteractionsGenerationConfigTest.java @@ -0,0 +1,622 @@ +/* + * 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. + */ + +package com.google.genai; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.common.collect.ImmutableList; +import com.google.genai.types.interactions.ThinkingLevel; +import com.google.genai.types.interactions.AllowedTools; +import com.google.genai.types.interactions.GenerationConfig; +import com.google.genai.types.interactions.ImageConfig; +import com.google.genai.types.interactions.SpeechConfig; +import com.google.genai.types.interactions.ThinkingSummaries; +import com.google.genai.types.interactions.ToolChoice; +import com.google.genai.types.interactions.ToolChoiceConfig; +import com.google.genai.types.interactions.ToolChoiceType; +import org.junit.jupiter.api.Test; + +/** + * Comprehensive testing for all 10 GenerationConfig fields. + * + *

This test suite ensures 100% coverage of GenerationConfig: + * + *

    + *
  • Basic controls: temperature, topP, seed, maxOutputTokens (5 tests) + *
  • Stop sequences: stopSequences (1 test) + *
  • Tool choice: toolChoice with types and config (6 tests) + *
  • Thinking controls: thinkingLevel, thinkingSummaries (6 tests) + *
  • Speech config: speechConfig (3 tests) + *
  • Image config: imageConfig (11 tests) + *
  • Combined configs: all fields together (2 tests) + *
  • Serialization: JSON roundtrip (1 test) + *
+ */ +public class InteractionsGenerationConfigTest { + + // ====================== + // BASIC CONTROLS (5 tests) + // ====================== + + @Test + public void testTemperature() { + GenerationConfig config = + GenerationConfig.builder().temperature(0.7f).build(); + + assertTrue(config.temperature().isPresent()); + assertEquals(0.7f, config.temperature().get(), 0.001); + } + + @Test + public void testTopP() { + GenerationConfig config = GenerationConfig.builder().topP(0.9f).build(); + + assertTrue(config.topP().isPresent()); + assertEquals(0.9f, config.topP().get(), 0.001); + } + + @Test + public void testSeed() { + GenerationConfig config = GenerationConfig.builder().seed(42).build(); + + assertTrue(config.seed().isPresent()); + assertEquals(42, config.seed().get()); + } + + @Test + public void testMaxOutputTokens() { + GenerationConfig config = + GenerationConfig.builder().maxOutputTokens(2048).build(); + + assertTrue(config.maxOutputTokens().isPresent()); + assertEquals(2048, config.maxOutputTokens().get()); + } + + @Test + public void testBasicControlsCombined() { + GenerationConfig config = + GenerationConfig.builder() + .temperature(0.8f) + .topP(0.95f) + .seed(123) + .maxOutputTokens(1000) + .build(); + + assertTrue(config.temperature().isPresent()); + assertEquals(0.8f, config.temperature().get(), 0.001); + assertTrue(config.topP().isPresent()); + assertEquals(0.95f, config.topP().get(), 0.001); + assertTrue(config.seed().isPresent()); + assertEquals(123, config.seed().get()); + assertTrue(config.maxOutputTokens().isPresent()); + assertEquals(1000, config.maxOutputTokens().get()); + } + + // ====================== + // STOP SEQUENCES (1 test) + // ====================== + + @Test + public void testStopSequences() { + ImmutableList stopSeqs = ImmutableList.of("STOP", "END", "\n\n"); + GenerationConfig config = + GenerationConfig.builder().stopSequences(stopSeqs).build(); + + assertTrue(config.stopSequences().isPresent()); + assertEquals(3, config.stopSequences().get().size()); + assertEquals("STOP", config.stopSequences().get().get(0)); + assertEquals("END", config.stopSequences().get().get(1)); + assertEquals("\n\n", config.stopSequences().get().get(2)); + } + + // ====================== + // TOOL CHOICE (6 tests) + // ====================== + + @Test + public void testToolChoice_AUTO() { + ToolChoice toolChoice = ToolChoice.fromType(ToolChoiceType.Known.AUTO); + GenerationConfig config = + GenerationConfig.builder().toolChoice(toolChoice).build(); + + assertTrue(config.toolChoice().isPresent()); + assertTrue(config.toolChoice().get().isType()); + assertEquals( + ToolChoiceType.Known.AUTO, config.toolChoice().get().asType().knownEnum()); + } + + @Test + public void testToolChoice_ANY() { + ToolChoice toolChoice = ToolChoice.fromType(ToolChoiceType.Known.ANY); + GenerationConfig config = + GenerationConfig.builder().toolChoice(toolChoice).build(); + + assertTrue(config.toolChoice().isPresent()); + assertTrue(config.toolChoice().get().isType()); + assertEquals( + ToolChoiceType.Known.ANY, config.toolChoice().get().asType().knownEnum()); + } + + @Test + public void testToolChoice_NONE() { + ToolChoice toolChoice = ToolChoice.fromType(ToolChoiceType.Known.NONE); + GenerationConfig config = + GenerationConfig.builder().toolChoice(toolChoice).build(); + + assertTrue(config.toolChoice().isPresent()); + assertTrue(config.toolChoice().get().isType()); + assertEquals( + ToolChoiceType.Known.NONE, config.toolChoice().get().asType().knownEnum()); + } + + @Test + public void testToolChoice_VALIDATED() { + ToolChoice toolChoice = ToolChoice.fromType(ToolChoiceType.Known.VALIDATED); + GenerationConfig config = + GenerationConfig.builder().toolChoice(toolChoice).build(); + + assertTrue(config.toolChoice().isPresent()); + assertTrue(config.toolChoice().get().isType()); + assertEquals( + ToolChoiceType.Known.VALIDATED, config.toolChoice().get().asType().knownEnum()); + } + + @Test + public void testToolChoice_withConfig() { + AllowedTools allowedTools = + AllowedTools.builder() + .mode("auto") + .tools(ImmutableList.of("search_tool", "calculator")) + .build(); + + ToolChoiceConfig toolChoiceConfig = + ToolChoiceConfig.builder().allowedTools(allowedTools).build(); + + ToolChoice toolChoice = ToolChoice.fromConfig(toolChoiceConfig); + GenerationConfig config = + GenerationConfig.builder().toolChoice(toolChoice).build(); + + assertTrue(config.toolChoice().isPresent()); + assertTrue(config.toolChoice().get().isConfig()); + ToolChoiceConfig retrievedConfig = config.toolChoice().get().asConfig(); + assertTrue(retrievedConfig.allowedTools().isPresent()); + assertTrue(retrievedConfig.allowedTools().get().mode().isPresent()); + assertEquals("auto", retrievedConfig.allowedTools().get().mode().get()); + assertTrue(retrievedConfig.allowedTools().get().tools().isPresent()); + assertEquals(2, retrievedConfig.allowedTools().get().tools().get().size()); + } + + @Test + public void testToolChoice_fromString() { + ToolChoice toolChoice = ToolChoice.fromString("auto"); + GenerationConfig config = + GenerationConfig.builder().toolChoice(toolChoice).build(); + + assertTrue(config.toolChoice().isPresent()); + assertTrue(config.toolChoice().get().isType()); + } + + // ====================== + // THINKING CONTROLS (6 tests) + // ====================== + + @Test + public void testThinkingLevel_MINIMAL() { + ThinkingLevel thinkingLevel = new ThinkingLevel(ThinkingLevel.Known.MINIMAL); + GenerationConfig config = + GenerationConfig.builder().thinkingLevel(thinkingLevel).build(); + + assertTrue(config.thinkingLevel().isPresent()); + assertEquals(ThinkingLevel.Known.MINIMAL, config.thinkingLevel().get().knownEnum()); + } + + @Test + public void testThinkingLevel_LOW() { + ThinkingLevel thinkingLevel = new ThinkingLevel(ThinkingLevel.Known.LOW); + GenerationConfig config = + GenerationConfig.builder().thinkingLevel(thinkingLevel).build(); + + assertTrue(config.thinkingLevel().isPresent()); + assertEquals(ThinkingLevel.Known.LOW, config.thinkingLevel().get().knownEnum()); + } + + @Test + public void testThinkingLevel_MEDIUM() { + ThinkingLevel thinkingLevel = new ThinkingLevel(ThinkingLevel.Known.MEDIUM); + GenerationConfig config = + GenerationConfig.builder().thinkingLevel(thinkingLevel).build(); + + assertTrue(config.thinkingLevel().isPresent()); + assertEquals(ThinkingLevel.Known.MEDIUM, config.thinkingLevel().get().knownEnum()); + } + + @Test + public void testThinkingLevel_HIGH() { + ThinkingLevel thinkingLevel = new ThinkingLevel(ThinkingLevel.Known.HIGH); + GenerationConfig config = + GenerationConfig.builder().thinkingLevel(thinkingLevel).build(); + + assertTrue(config.thinkingLevel().isPresent()); + assertEquals(ThinkingLevel.Known.HIGH, config.thinkingLevel().get().knownEnum()); + } + + @Test + public void testThinkingSummaries_AUTO() { + ThinkingSummaries thinkingSummaries = new ThinkingSummaries(ThinkingSummaries.Known.AUTO); + GenerationConfig config = + GenerationConfig.builder().thinkingSummaries(thinkingSummaries).build(); + + assertTrue(config.thinkingSummaries().isPresent()); + assertEquals(ThinkingSummaries.Known.AUTO, config.thinkingSummaries().get().knownEnum()); + } + + @Test + public void testThinkingSummaries_NONE() { + ThinkingSummaries thinkingSummaries = new ThinkingSummaries(ThinkingSummaries.Known.NONE); + GenerationConfig config = + GenerationConfig.builder().thinkingSummaries(thinkingSummaries).build(); + + assertTrue(config.thinkingSummaries().isPresent()); + assertEquals(ThinkingSummaries.Known.NONE, config.thinkingSummaries().get().knownEnum()); + } + + // ====================== + // SPEECH CONFIG (3 tests) + // ====================== + + @Test + public void testSpeechConfig_allFields() { + SpeechConfig speechConfig = + SpeechConfig.builder() + .voice("en-US-Studio-O") + .language("en-US") + .speaker("speaker1") + .build(); + + GenerationConfig config = + GenerationConfig.builder().speechConfig(speechConfig).build(); + + assertTrue(config.speechConfig().isPresent()); + assertTrue(config.speechConfig().get().voice().isPresent()); + assertEquals("en-US-Studio-O", config.speechConfig().get().voice().get()); + assertTrue(config.speechConfig().get().language().isPresent()); + assertEquals("en-US", config.speechConfig().get().language().get()); + assertTrue(config.speechConfig().get().speaker().isPresent()); + assertEquals("speaker1", config.speechConfig().get().speaker().get()); + } + + @Test + public void testSpeechConfig_voiceOnly() { + SpeechConfig speechConfig = + SpeechConfig.builder().voice("en-GB-Studio-B").build(); + + GenerationConfig config = + GenerationConfig.builder().speechConfig(speechConfig).build(); + + assertTrue(config.speechConfig().isPresent()); + assertTrue(config.speechConfig().get().voice().isPresent()); + assertEquals("en-GB-Studio-B", config.speechConfig().get().voice().get()); + assertFalse(config.speechConfig().get().language().isPresent()); + assertFalse(config.speechConfig().get().speaker().isPresent()); + } + + @Test + public void testSpeechConfig_builderConvenience() { + GenerationConfig config = + GenerationConfig.builder() + .speechConfig(SpeechConfig.builder().voice("en-US-Studio-O")) + .build(); + + assertTrue(config.speechConfig().isPresent()); + assertTrue(config.speechConfig().get().voice().isPresent()); + assertEquals("en-US-Studio-O", config.speechConfig().get().voice().get()); + } + + // ====================== + // IMAGE CONFIG (11 tests) + // ====================== + + @Test + public void testImageConfig_aspectRatio_1_1() { + ImageConfig imageConfig = + ImageConfig.builder().aspectRatio("1:1").build(); + + GenerationConfig config = + GenerationConfig.builder().imageConfig(imageConfig).build(); + + assertTrue(config.imageConfig().isPresent()); + assertTrue(config.imageConfig().get().aspectRatio().isPresent()); + assertEquals("1:1", config.imageConfig().get().aspectRatio().get()); + } + + @Test + public void testImageConfig_allAspectRatios() { + String[] aspectRatios = {"1:1", "2:3", "3:2", "3:4", "4:3", "9:16", "16:9", "21:9"}; + + for (String aspectRatio : aspectRatios) { + ImageConfig imageConfig = + ImageConfig.builder().aspectRatio(aspectRatio).build(); + + GenerationConfig config = + GenerationConfig.builder().imageConfig(imageConfig).build(); + + assertTrue(config.imageConfig().isPresent()); + assertTrue(config.imageConfig().get().aspectRatio().isPresent()); + assertEquals(aspectRatio, config.imageConfig().get().aspectRatio().get()); + } + } + + @Test + public void testImageConfig_imageSize_1K() { + ImageConfig imageConfig = ImageConfig.builder().imageSize("1K").build(); + + GenerationConfig config = + GenerationConfig.builder().imageConfig(imageConfig).build(); + + assertTrue(config.imageConfig().isPresent()); + assertTrue(config.imageConfig().get().imageSize().isPresent()); + assertEquals("1K", config.imageConfig().get().imageSize().get()); + } + + @Test + public void testImageConfig_imageSize_2K() { + ImageConfig imageConfig = ImageConfig.builder().imageSize("2K").build(); + + GenerationConfig config = + GenerationConfig.builder().imageConfig(imageConfig).build(); + + assertTrue(config.imageConfig().isPresent()); + assertTrue(config.imageConfig().get().imageSize().isPresent()); + assertEquals("2K", config.imageConfig().get().imageSize().get()); + } + + @Test + public void testImageConfig_imageSize_4K() { + ImageConfig imageConfig = ImageConfig.builder().imageSize("4K").build(); + + GenerationConfig config = + GenerationConfig.builder().imageConfig(imageConfig).build(); + + assertTrue(config.imageConfig().isPresent()); + assertTrue(config.imageConfig().get().imageSize().isPresent()); + assertEquals("4K", config.imageConfig().get().imageSize().get()); + } + + @Test + public void testImageConfig_aspectRatioAndSize() { + ImageConfig imageConfig = + ImageConfig.builder().aspectRatio("16:9").imageSize("4K").build(); + + GenerationConfig config = + GenerationConfig.builder().imageConfig(imageConfig).build(); + + assertTrue(config.imageConfig().isPresent()); + assertTrue(config.imageConfig().get().aspectRatio().isPresent()); + assertEquals("16:9", config.imageConfig().get().aspectRatio().get()); + assertTrue(config.imageConfig().get().imageSize().isPresent()); + assertEquals("4K", config.imageConfig().get().imageSize().get()); + } + + @Test + public void testImageConfig_builderConvenience() { + GenerationConfig config = + GenerationConfig.builder() + .imageConfig(ImageConfig.builder().aspectRatio("16:9")) + .build(); + + assertTrue(config.imageConfig().isPresent()); + assertTrue(config.imageConfig().get().aspectRatio().isPresent()); + assertEquals("16:9", config.imageConfig().get().aspectRatio().get()); + } + + // Additional tests for edge cases + + @Test + public void testImageConfig_onlyAspectRatio() { + ImageConfig imageConfig = + ImageConfig.builder().aspectRatio("3:4").build(); + + GenerationConfig config = + GenerationConfig.builder().imageConfig(imageConfig).build(); + + assertTrue(config.imageConfig().isPresent()); + assertTrue(config.imageConfig().get().aspectRatio().isPresent()); + assertEquals("3:4", config.imageConfig().get().aspectRatio().get()); + assertFalse(config.imageConfig().get().imageSize().isPresent()); + } + + @Test + public void testImageConfig_onlyImageSize() { + ImageConfig imageConfig = ImageConfig.builder().imageSize("2K").build(); + + GenerationConfig config = + GenerationConfig.builder().imageConfig(imageConfig).build(); + + assertTrue(config.imageConfig().isPresent()); + assertFalse(config.imageConfig().get().aspectRatio().isPresent()); + assertTrue(config.imageConfig().get().imageSize().isPresent()); + assertEquals("2K", config.imageConfig().get().imageSize().get()); + } + + @Test + public void testImageConfig_widescreen() { + ImageConfig imageConfig = + ImageConfig.builder().aspectRatio("21:9").imageSize("4K").build(); + + GenerationConfig config = + GenerationConfig.builder().imageConfig(imageConfig).build(); + + assertTrue(config.imageConfig().isPresent()); + assertEquals("21:9", config.imageConfig().get().aspectRatio().get()); + assertEquals("4K", config.imageConfig().get().imageSize().get()); + } + + @Test + public void testImageConfig_portrait() { + ImageConfig imageConfig = + ImageConfig.builder().aspectRatio("9:16").imageSize("2K").build(); + + GenerationConfig config = + GenerationConfig.builder().imageConfig(imageConfig).build(); + + assertTrue(config.imageConfig().isPresent()); + assertEquals("9:16", config.imageConfig().get().aspectRatio().get()); + assertEquals("2K", config.imageConfig().get().imageSize().get()); + } + + // ====================== + // COMBINED CONFIG (2 tests) + // ====================== + + @Test + public void testCombinedConfig_allFields() { + AllowedTools allowedTools = + AllowedTools.builder() + .mode("auto") + .tools(ImmutableList.of("search", "calc")) + .build(); + + ToolChoiceConfig toolChoiceConfig = + ToolChoiceConfig.builder().allowedTools(allowedTools).build(); + + GenerationConfig config = + GenerationConfig.builder() + .temperature(0.7f) + .topP(0.9f) + .seed(12345) + .maxOutputTokens(500) + .stopSequences(ImmutableList.of("\n\n", "---")) + .toolChoice(ToolChoice.fromConfig(toolChoiceConfig)) + .thinkingLevel(new ThinkingLevel(ThinkingLevel.Known.MEDIUM)) + .thinkingSummaries(new ThinkingSummaries(ThinkingSummaries.Known.AUTO)) + .speechConfig( + SpeechConfig.builder() + .voice("en-US-Studio-O") + .language("en-US") + .build()) + .imageConfig( + ImageConfig.builder().aspectRatio("16:9").imageSize("2K").build()) + .build(); + + // Verify all 10 fields are present + assertTrue(config.temperature().isPresent()); + assertTrue(config.topP().isPresent()); + assertTrue(config.seed().isPresent()); + assertTrue(config.maxOutputTokens().isPresent()); + assertTrue(config.stopSequences().isPresent()); + assertTrue(config.toolChoice().isPresent()); + assertTrue(config.thinkingLevel().isPresent()); + assertTrue(config.thinkingSummaries().isPresent()); + assertTrue(config.speechConfig().isPresent()); + assertTrue(config.imageConfig().isPresent()); + + // Verify values + assertEquals(0.7f, config.temperature().get(), 0.001); + assertEquals(0.9f, config.topP().get(), 0.001); + assertEquals(12345, config.seed().get()); + assertEquals(500, config.maxOutputTokens().get()); + assertEquals(2, config.stopSequences().get().size()); + assertTrue(config.toolChoice().get().isConfig()); + assertEquals(ThinkingLevel.Known.MEDIUM, config.thinkingLevel().get().knownEnum()); + assertEquals(ThinkingSummaries.Known.AUTO, config.thinkingSummaries().get().knownEnum()); + assertEquals("en-US-Studio-O", config.speechConfig().get().voice().get()); + assertEquals("16:9", config.imageConfig().get().aspectRatio().get()); + } + + @Test + public void testCombinedConfig_emptyOptionals() { + GenerationConfig config = GenerationConfig.builder().build(); + + // All fields should be absent + assertFalse(config.temperature().isPresent()); + assertFalse(config.topP().isPresent()); + assertFalse(config.seed().isPresent()); + assertFalse(config.maxOutputTokens().isPresent()); + assertFalse(config.stopSequences().isPresent()); + assertFalse(config.toolChoice().isPresent()); + assertFalse(config.thinkingLevel().isPresent()); + assertFalse(config.thinkingSummaries().isPresent()); + assertFalse(config.speechConfig().isPresent()); + assertFalse(config.imageConfig().isPresent()); + } + + // ====================== + // SERIALIZATION (1 test) + // ====================== + + @Test + public void testSerialization_allFields() { + AllowedTools allowedTools = + AllowedTools.builder() + .mode("validated") + .tools(ImmutableList.of("tool1", "tool2")) + .build(); + + ToolChoiceConfig toolChoiceConfig = + ToolChoiceConfig.builder().allowedTools(allowedTools).build(); + + GenerationConfig config = + GenerationConfig.builder() + .temperature(0.5f) + .topP(0.85f) + .seed(999) + .maxOutputTokens(1024) + .stopSequences(ImmutableList.of("STOP")) + .toolChoice(ToolChoice.fromConfig(toolChoiceConfig)) + .thinkingLevel(new ThinkingLevel(ThinkingLevel.Known.HIGH)) + .thinkingSummaries(new ThinkingSummaries(ThinkingSummaries.Known.NONE)) + .speechConfig( + SpeechConfig.builder() + .voice("en-AU-Studio-A") + .language("en-AU") + .speaker("speaker2") + .build()) + .imageConfig( + ImageConfig.builder().aspectRatio("4:3").imageSize("1K").build()) + .build(); + + // Serialize to JSON + String json = config.toJson(); + assertNotNull(json); + assertFalse(json.isEmpty()); + + // Verify JSON contains expected field names + assertTrue(json.contains("\"temperature\"")); + assertTrue(json.contains("\"top_p\"")); + assertTrue(json.contains("\"seed\"")); + assertTrue(json.contains("\"max_output_tokens\"")); + assertTrue(json.contains("\"stop_sequences\"")); + assertTrue(json.contains("\"tool_choice\"")); + assertTrue(json.contains("\"thinking_level\"")); + assertTrue(json.contains("\"thinking_summaries\"")); + assertTrue(json.contains("\"speech_config\"")); + assertTrue(json.contains("\"image_config\"")); + + // Deserialize and verify + GenerationConfig deserialized = GenerationConfig.fromJson(json); + assertNotNull(deserialized); + assertEquals(0.5f, deserialized.temperature().get(), 0.001); + assertEquals(0.85f, deserialized.topP().get(), 0.001); + assertEquals(999, deserialized.seed().get()); + assertEquals(1024, deserialized.maxOutputTokens().get()); + assertEquals(ThinkingLevel.Known.HIGH, deserialized.thinkingLevel().get().knownEnum()); + assertEquals(ThinkingSummaries.Known.NONE, deserialized.thinkingSummaries().get().knownEnum()); + } +} diff --git a/src/test/java/com/google/genai/InteractionsMediaContentTest.java b/src/test/java/com/google/genai/InteractionsMediaContentTest.java new file mode 100644 index 00000000000..f10f75c3038 --- /dev/null +++ b/src/test/java/com/google/genai/InteractionsMediaContentTest.java @@ -0,0 +1,610 @@ +/* + * 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. + */ + +package com.google.genai; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.common.collect.ImmutableList; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.GenerationConfig; +import com.google.genai.types.interactions.ImageConfig; +import com.google.genai.types.interactions.Interaction; +import com.google.genai.types.interactions.MediaResolution; +import com.google.genai.types.interactions.content.AudioContent; +import com.google.genai.types.interactions.content.DocumentContent; +import com.google.genai.types.interactions.content.ImageContent; +import com.google.genai.types.interactions.content.TextContent; +import com.google.genai.types.interactions.content.VideoContent; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * Comprehensive tests for multimodal content handling in Interactions API. + * + *

This test suite validates ImageContent, AudioContent, VideoContent, DocumentContent, and + * mixed media scenarios with various configurations. + * + *

To run these tests, set: export GOOGLE_GENAI_REPLAYS_DIRECTORY="/path/to/genai/replays" + */ +@EnabledIfEnvironmentVariable( + named = "GOOGLE_GENAI_REPLAYS_DIRECTORY", + matches = ".*genai/replays.*") +@ExtendWith(EnvironmentVariablesMockingExtension.class) +public class InteractionsMediaContentTest { + + private static final String MODEL_ID = "gemini-2.5-flash"; + + // ==================== ImageContent Tests ==================== + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testImageContent_fromFile(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/media/image_from_file." + suffix + ".json"); + + URL resourceUrl = getClass().getClassLoader().getResource("google.png"); + Path filePath = Paths.get(resourceUrl.toURI()); + + ImageContent imageContent = + ImageContent.builder() + .uri(filePath.toAbsolutePath().toString()) + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder().text("Describe this logo.").build(), + imageContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testImageContent_fromBytes(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/media/image_from_bytes." + suffix + ".json"); + + URL resourceUrl = getClass().getClassLoader().getResource("shapes.jpg"); + Path filePath = Paths.get(resourceUrl.toURI()); + + ImageContent imageContent = + ImageContent.builder() + .uri(filePath.toAbsolutePath().toString()) + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder().text("What shapes are in this image?").build(), + imageContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testImageContent_withImageConfig(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/media/image_with_config." + suffix + ".json"); + + URL resourceUrl = getClass().getClassLoader().getResource("watercolor_night_sky.jpg"); + Path filePath = Paths.get(resourceUrl.toURI()); + + // ImageContent can specify resolution for input images + ImageContent imageContent = + ImageContent.builder() + .uri(filePath.toAbsolutePath().toString()) + .resolution(new MediaResolution(MediaResolution.Known.HIGH)) + .build(); + + // ImageConfig is used within GenerationConfig for image generation settings + // (aspect ratio, image size for generated output images) + ImageConfig imageConfig = + ImageConfig.builder() + .aspectRatio("16:9") + .imageSize("2K") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder().text("Analyze this watercolor painting.").build(), + imageContent)) + .generationConfig( + GenerationConfig.builder() + .imageConfig(imageConfig) + .build()) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testImageContent_multipleImages(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/media/multiple_images." + suffix + ".json"); + + URL resource1 = getClass().getClassLoader().getResource("google.png"); + URL resource2 = getClass().getClassLoader().getResource("logo.jpg"); + Path file1 = Paths.get(resource1.toURI()); + Path file2 = Paths.get(resource2.toURI()); + + ImageContent image1 = + ImageContent.builder() + .uri(file1.toAbsolutePath().toString()) + .build(); + + ImageContent image2 = + ImageContent.builder() + .uri(file2.toAbsolutePath().toString()) + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder().text("Compare these two logos.").build(), + image1, + image2)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + // ==================== AudioContent Tests ==================== + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testAudioContent_fromFile(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/media/audio_from_file." + suffix + ".json"); + + // Use a GCS URI for audio since we don't have local audio files in test resources + AudioContent audioContent = + AudioContent.builder() + .uri("gs://cloud-samples-data/generative-ai/audio/pixel.mp3") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder().text("Transcribe this audio.").build(), + audioContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testAudioContent_withFormat(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/media/audio_with_format." + suffix + ".json"); + + AudioContent audioContent = + AudioContent.builder() + .uri("gs://cloud-samples-data/generative-ai/audio/sample.wav") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder().text("What is said in this audio?").build(), + audioContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testAudioContent_withTimestamp(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/media/audio_with_timestamp." + suffix + ".json"); + + AudioContent audioContent = + AudioContent.builder() + .uri("gs://cloud-samples-data/generative-ai/audio/audio-sample.mp3") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder() + .text("Provide a transcription with timestamps.") + .build(), + audioContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + // ==================== VideoContent Tests ==================== + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testVideoContent_fromFile(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/media/video_from_file." + suffix + ".json"); + + VideoContent videoContent = + VideoContent.builder() + .uri("gs://cloud-samples-data/generative-ai/video/animals.mp4") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder().text("Describe what happens in this video.").build(), + videoContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testVideoContent_withFormat(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/media/video_with_format." + suffix + ".json"); + + VideoContent videoContent = + VideoContent.builder() + .uri("gs://cloud-samples-data/generative-ai/video/sample.mp4") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder().text("Analyze this video content.").build(), + videoContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + // Note: VideoContent does not currently support start/end offset parameters + // This test is commented out until that functionality is added to the API + + // ==================== DocumentContent Tests ==================== + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testDocumentContent_fromPdf(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/media/document_from_pdf." + suffix + ".json"); + + DocumentContent documentContent = + DocumentContent.builder() + .uri("gs://cloud-samples-data/generative-ai/pdf/sample-document.pdf") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder().text("Summarize this PDF document.").build(), + documentContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testDocumentContent_fromDocx(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/media/document_from_docx." + suffix + ".json"); + + DocumentContent documentContent = + DocumentContent.builder() + .uri("gs://cloud-samples-data/generative-ai/documents/document.docx") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder().text("Extract key points from this document.").build(), + documentContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testDocumentContent_withMetadata(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/media/document_with_metadata." + suffix + ".json"); + + DocumentContent documentContent = + DocumentContent.builder() + .uri("gs://cloud-samples-data/generative-ai/documents/report.pdf") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder() + .text("Analyze this report and extract metadata.") + .build(), + documentContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + // ==================== Mixed Media Tests ==================== + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testMixedContent_textAndImage(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/media/mixed_text_image." + suffix + ".json"); + + URL resourceUrl = getClass().getClassLoader().getResource("bridge1.png"); + Path filePath = Paths.get(resourceUrl.toURI()); + + ImageContent imageContent = + ImageContent.builder() + .uri(filePath.toAbsolutePath().toString()) + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder() + .text("Describe this bridge and suggest a caption.") + .build(), + imageContent, + TextContent.builder().text("Make the caption poetic.").build())) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testMixedContent_textImageAudio(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/media/mixed_text_image_audio." + suffix + ".json"); + + URL resourceUrl = getClass().getClassLoader().getResource("umbrella.jpg"); + Path filePath = Paths.get(resourceUrl.toURI()); + + ImageContent imageContent = + ImageContent.builder() + .uri(filePath.toAbsolutePath().toString()) + .build(); + + AudioContent audioContent = + AudioContent.builder() + .uri("gs://cloud-samples-data/generative-ai/audio/rain-sounds.mp3") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder() + .text("Describe the umbrella image and transcribe the audio.") + .build(), + imageContent, + audioContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testMixedContent_inputFromContents(boolean vertexAI) throws Exception { + // Arrange + String suffix = vertexAI ? "vertex" : "mldev"; + Client client = + TestUtils.createClient( + vertexAI, "tests/interactions/media/mixed_input_from_contents." + suffix + ".json"); + + URL imageUrl = getClass().getClassLoader().getResource("google.png"); + Path imagePath = Paths.get(imageUrl.toURI()); + + ImageContent imageContent = + ImageContent.builder() + .uri(imagePath.toAbsolutePath().toString()) + .build(); + + VideoContent videoContent = + VideoContent.builder() + .uri("gs://cloud-samples-data/generative-ai/video/demo.mp4") + .build(); + + DocumentContent documentContent = + DocumentContent.builder() + .uri("gs://cloud-samples-data/generative-ai/documents/branding-guide.pdf") + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .inputFromContents( + ImmutableList.of( + TextContent.builder() + .text( + "Analyze the logo, video, and branding document. " + + "Provide a cohesive brand analysis.") + .build(), + imageContent, + videoContent, + documentContent)) + .build(); + + // Act + Interaction interaction = client.interactions.create(config); + + // Assert + assertNotNull(interaction); + assertNotNull(interaction.id()); + assertTrue(interaction.outputs().isPresent()); + } +} diff --git a/src/test/java/com/google/genai/InteractionsMockitoTest.java b/src/test/java/com/google/genai/InteractionsMockitoTest.java new file mode 100644 index 00000000000..d5819cc6d39 --- /dev/null +++ b/src/test/java/com/google/genai/InteractionsMockitoTest.java @@ -0,0 +1,722 @@ +/* + * 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. + */ + +package com.google.genai; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableMap; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.interactions.CancelInteractionConfig; +import com.google.genai.types.interactions.CreateInteractionConfig; +import com.google.genai.types.interactions.DeleteInteractionConfig; +import com.google.genai.types.interactions.DeleteInteractionResponse; +import com.google.genai.types.interactions.GetInteractionConfig; +import com.google.genai.types.interactions.Interaction; +import java.lang.reflect.Field; +import okhttp3.MediaType; +import okhttp3.ResponseBody; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +/** + * Mockito-based unit tests for Interactions API. + * + *

Fine-grained testing using mocked ApiClient, similar to ChatTest.java mockito patterns. + */ +public class InteractionsMockitoTest { + + private static final String MODEL_ID = "gemini-2.5-flash"; + private static final String INTERACTION_ID = "test-interaction-123"; + + private ApiClient mockedClient; + private ApiResponse mockedResponse; + private Client client; + + @BeforeEach + void setUp() throws Exception { + mockedClient = Mockito.mock(ApiClient.class); + mockedResponse = Mockito.mock(ApiResponse.class); + + String apiKey = "test-key"; + client = Client.builder().apiKey(apiKey).vertexAI(false).build(); + + // Use reflection to inject mocked client + Field apiClientField = Interactions.class.getDeclaredField("apiClient"); + apiClientField.setAccessible(true); + apiClientField.set(client.interactions, mockedClient); + } + + @Test + public void testCreate_requestBuilding() throws Exception { + // Arrange + String responseJson = + "{\"id\":\"" + INTERACTION_ID + "\",\"status\":\"completed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + CreateInteractionConfig config = + CreateInteractionConfig.builder().model(MODEL_ID).input("Test input").build(); + + // Act + Interaction result = client.interactions.create(config); + + // Assert + assertNotNull(result); + assertEquals(INTERACTION_ID, result.id()); + + // Verify request was made + verify(mockedClient).request(anyString(), anyString(), anyString(), any()); + } + + @Test + public void testGet_requestBuilding() throws Exception { + // Arrange + String responseJson = + "{\"id\":\"" + INTERACTION_ID + "\",\"status\":\"completed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + GetInteractionConfig config = GetInteractionConfig.builder().build(); + + // Act + Interaction result = client.interactions.get(INTERACTION_ID, config); + + // Assert + assertNotNull(result); + assertEquals(INTERACTION_ID, result.id()); + + // Verify request was made with correct ID + verify(mockedClient).request(anyString(), anyString(), anyString(), any()); + } + + @Test + public void testCancel_requestBuilding() throws Exception { + // Arrange + String responseJson = + "{\"id\":\"" + INTERACTION_ID + "\",\"status\":\"failed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + CancelInteractionConfig config = CancelInteractionConfig.builder().build(); + + // Act + Interaction result = client.interactions.cancel(INTERACTION_ID, config); + + // Assert + assertNotNull(result); + assertEquals(INTERACTION_ID, result.id()); + + // Verify request was made + verify(mockedClient).request(anyString(), anyString(), anyString(), any()); + } + + @Test + public void testDelete_requestBuilding() throws Exception { + // Arrange + String responseJson = "{}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + DeleteInteractionConfig config = DeleteInteractionConfig.builder().build(); + + // Act + DeleteInteractionResponse result = client.interactions.delete(INTERACTION_ID, config); + + // Assert + assertNotNull(result); + + // Verify request was made + verify(mockedClient).request(anyString(), anyString(), anyString(), any()); + } + + @Test + public void testCreate_withHttpOptions() throws Exception { + // Arrange + String responseJson = + "{\"id\":\"" + INTERACTION_ID + "\",\"status\":\"completed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + HttpOptions httpOptions = + HttpOptions.builder() + .headers(ImmutableMap.of("X-Custom-Header", "custom-value")) + .build(); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Test") + .httpOptions(httpOptions) + .build(); + + // Act + Interaction result = client.interactions.create(config); + + // Assert + assertNotNull(result); + assertTrue(config.httpOptions().isPresent()); + assertTrue(config.httpOptions().get().headers().isPresent()); + } + + @Test + public void testCreate_responseHandling() throws Exception { + // Arrange - Test that response is properly parsed + String responseJson = + "{" + + "\"id\":\"" + + INTERACTION_ID + + "\"," + + "\"status\":\"completed\"," + + "\"model\":\"" + + MODEL_ID + + "\"," + + "\"outputs\":[{\"type\":\"text\",\"text\":\"Response text\"}]" + + "}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + CreateInteractionConfig config = + CreateInteractionConfig.builder().model(MODEL_ID).input("Test").build(); + + // Act + Interaction result = client.interactions.create(config); + + // Assert - Verify all fields are parsed correctly + assertEquals(INTERACTION_ID, result.id()); + assertTrue(result.model().isPresent()); + assertEquals(MODEL_ID, result.model().get()); + assertTrue(result.outputs().isPresent()); + assertEquals(1, result.outputs().get().size()); + } + + @Test + public void testCreate_withPreviousInteractionId() throws Exception { + // Arrange + String previousId = "previous-123"; + String responseJson = + "{" + + "\"id\":\"" + + INTERACTION_ID + + "\"," + + "\"status\":\"completed\"," + + "\"previous_interaction_id\":\"" + + previousId + + "\"" + + "}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Follow-up") + .previousInteractionId(previousId) + .build(); + + // Act + Interaction result = client.interactions.create(config); + + // Assert + assertTrue(result.previousInteractionId().isPresent()); + assertEquals(previousId, result.previousInteractionId().get()); + } + + @Test + public void testCreate_withGenerationConfig() throws Exception { + // Arrange + String responseJson = + "{\"id\":\"" + INTERACTION_ID + "\",\"status\":\"completed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Test") + .generationConfig( + com.google.genai.types.interactions.GenerationConfig.builder() + .temperature(0.7f) + .maxOutputTokens(1000) + .topP(0.9f) + .build()) + .build(); + + // Act + Interaction result = client.interactions.create(config); + + // Assert + assertNotNull(result); + assertTrue(config.generationConfig().isPresent()); + } + + @Test + public void testCreate_multipleSequentialCalls() throws Exception { + // Test making multiple sequential calls + for (int i = 1; i <= 3; i++) { + String interactionId = "interaction-" + i; + String responseJson = "{\"id\":\"" + interactionId + "\",\"status\":\"completed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + CreateInteractionConfig config = + CreateInteractionConfig.builder().model(MODEL_ID).input("Input " + i).build(); + + Interaction result = client.interactions.create(config); + + assertEquals(interactionId, result.id()); + } + + // Verify request was called 3 times + verify(mockedClient, Mockito.times(3)) + .request(anyString(), anyString(), anyString(), any()); + } + + @Test + public void testGet_withConfig() throws Exception { + // Arrange + String responseJson = + "{" + + "\"id\":\"" + + INTERACTION_ID + + "\"," + + "\"status\":\"completed\"," + + "\"outputs\":[{\"type\":\"text\",\"text\":\"Retrieved response\"}]" + + "}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + GetInteractionConfig config = + GetInteractionConfig.builder() + .httpOptions(HttpOptions.builder().headers(ImmutableMap.of("X-Test", "value")).build()) + .build(); + + // Act + Interaction result = client.interactions.get(INTERACTION_ID, config); + + // Assert + assertNotNull(result); + assertTrue(result.outputs().isPresent()); + } + + @Test + public void testResponseBodyParsing() throws Exception { + // Test various response body formats + String complexResponseJson = + "{" + + "\"id\":\"" + + INTERACTION_ID + + "\"," + + "\"status\":\"completed\"," + + "\"model\":\"" + + MODEL_ID + + "\"," + + "\"outputs\":[" + + "{\"type\":\"text\",\"text\":\"First output\"}," + + "{\"type\":\"text\",\"text\":\"Second output\"}" + + "]," + + "\"created\":\"2025-01-22T10:00:00Z\"," + + "\"updated\":\"2025-01-22T10:01:00Z\"," + + "\"usage\":{\"total_token_count\":100}" + + "}"; + + ResponseBody body = ResponseBody.create(complexResponseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + CreateInteractionConfig config = + CreateInteractionConfig.builder().model(MODEL_ID).input("Test").build(); + + // Act + Interaction result = client.interactions.create(config); + + // Assert - Verify complex response is fully parsed + assertEquals(INTERACTION_ID, result.id()); + assertTrue(result.outputs().isPresent()); + assertEquals(2, result.outputs().get().size()); + assertTrue(result.created().isPresent()); + assertTrue(result.updated().isPresent()); + assertTrue(result.usage().isPresent()); + } + + @Test + public void testApiClientVerification() throws Exception { + // Verify that ApiClient is being called with correct parameters + String responseJson = + "{\"id\":\"" + INTERACTION_ID + "\",\"status\":\"completed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + + ArgumentCaptor methodCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor pathCaptor = ArgumentCaptor.forClass(String.class); + + when(mockedClient.request( + methodCaptor.capture(), pathCaptor.capture(), anyString(), any())) + .thenReturn(mockedResponse); + + CreateInteractionConfig config = + CreateInteractionConfig.builder().model(MODEL_ID).input("Test").build(); + + // Act + client.interactions.create(config); + + // Assert - Verify HTTP method and path + assertEquals("post", methodCaptor.getValue()); + // Path will be interactions-specific + assertNotNull(pathCaptor.getValue()); + } + + // ==================== Request Building Validation ==================== + + @Test + public void testCreate_requestBodyStructure() throws Exception { + // Arrange + String responseJson = "{\"id\":\"" + INTERACTION_ID + "\",\"status\":\"completed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + + ArgumentCaptor bodyCaptor = ArgumentCaptor.forClass(String.class); + when(mockedClient.request(anyString(), anyString(), bodyCaptor.capture(), any())) + .thenReturn(mockedResponse); + + CreateInteractionConfig config = + CreateInteractionConfig.builder().model(MODEL_ID).input("Test input").build(); + + // Act + client.interactions.create(config); + + // Assert - Verify JSON body structure contains expected fields + String requestBody = bodyCaptor.getValue(); + assertNotNull(requestBody); + assertTrue(requestBody.contains("\"model\"")); + assertTrue(requestBody.contains(MODEL_ID)); + } + + @Test + public void testGet_pathFormatting() throws Exception { + // Arrange + String responseJson = "{\"id\":\"" + INTERACTION_ID + "\",\"status\":\"completed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + + ArgumentCaptor pathCaptor = ArgumentCaptor.forClass(String.class); + when(mockedClient.request(anyString(), pathCaptor.capture(), anyString(), any())) + .thenReturn(mockedResponse); + + GetInteractionConfig config = GetInteractionConfig.builder().build(); + + // Act + client.interactions.get(INTERACTION_ID, config); + + // Assert - Verify path includes interaction ID + String path = pathCaptor.getValue(); + assertNotNull(path); + assertTrue(path.contains(INTERACTION_ID) || path.contains("interactions")); + } + + @Test + public void testCancel_pathIncludesCancel() throws Exception { + // Arrange + String responseJson = "{\"id\":\"" + INTERACTION_ID + "\",\"status\":\"failed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + + ArgumentCaptor pathCaptor = ArgumentCaptor.forClass(String.class); + when(mockedClient.request(anyString(), pathCaptor.capture(), anyString(), any())) + .thenReturn(mockedResponse); + + CancelInteractionConfig config = CancelInteractionConfig.builder().build(); + + // Act + client.interactions.cancel(INTERACTION_ID, config); + + // Assert - Verify path includes :cancel + String path = pathCaptor.getValue(); + assertNotNull(path); + assertTrue(path.contains("cancel") || path.contains(INTERACTION_ID)); + } + + @Test + public void testDelete_httpMethod() throws Exception { + // Arrange + String responseJson = "{}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + + ArgumentCaptor methodCaptor = ArgumentCaptor.forClass(String.class); + when(mockedClient.request(methodCaptor.capture(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + DeleteInteractionConfig config = DeleteInteractionConfig.builder().build(); + + // Act + client.interactions.delete(INTERACTION_ID, config); + + // Assert - Verify DELETE HTTP method + assertEquals("delete", methodCaptor.getValue()); + } + + @Test + public void testCreate_httpMethod() throws Exception { + // Arrange + String responseJson = "{\"id\":\"" + INTERACTION_ID + "\",\"status\":\"completed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + + ArgumentCaptor methodCaptor = ArgumentCaptor.forClass(String.class); + when(mockedClient.request(methodCaptor.capture(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + CreateInteractionConfig config = + CreateInteractionConfig.builder().model(MODEL_ID).input("Test").build(); + + // Act + client.interactions.create(config); + + // Assert - Verify POST HTTP method + assertEquals("post", methodCaptor.getValue()); + } + + // ==================== Platform-Specific Behavior ==================== + + @Test + public void testCreate_mldevClient() throws Exception { + // Arrange - Ensure we're using MLDev client + String responseJson = "{\"id\":\"" + INTERACTION_ID + "\",\"status\":\"completed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + CreateInteractionConfig config = + CreateInteractionConfig.builder().model(MODEL_ID).input("Test").build(); + + // Act + Interaction result = client.interactions.create(config); + + // Assert + assertNotNull(result); + assertEquals(INTERACTION_ID, result.id()); + } + + @Test + public void testCreate_vertexClient() throws Exception { + // Arrange - Create a Vertex AI client + Client vertexClient = Client.builder() + .vertexAI(true) + .project("test-project") + .location("us-central1") + .build(); + + // Inject mocked client + Field apiClientField = Interactions.class.getDeclaredField("apiClient"); + apiClientField.setAccessible(true); + apiClientField.set(vertexClient.interactions, mockedClient); + + String responseJson = "{\"id\":\"" + INTERACTION_ID + "\",\"status\":\"completed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + CreateInteractionConfig config = + CreateInteractionConfig.builder().model(MODEL_ID).input("Test").build(); + + // Act + Interaction result = vertexClient.interactions.create(config); + + // Assert + assertNotNull(result); + } + + @Test + public void testGet_vertexIdFormat() throws Exception { + // Arrange + String vertexId = "projects/test-project/locations/us-central1/interactions/test-id"; + String responseJson = "{\"id\":\"" + vertexId + "\",\"status\":\"completed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + GetInteractionConfig config = GetInteractionConfig.builder().build(); + + // Act + Interaction result = client.interactions.get(vertexId, config); + + // Assert + assertNotNull(result); + assertEquals(vertexId, result.id()); + } + + // ==================== Edge Cases ==================== + + @Test + public void testCreate_emptyOptionalFields() throws Exception { + // Arrange + String responseJson = "{\"id\":\"" + INTERACTION_ID + "\",\"status\":\"completed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + // Create config with only required fields + CreateInteractionConfig config = + CreateInteractionConfig.builder().model(MODEL_ID).input("Minimal config").build(); + + // Act + Interaction result = client.interactions.create(config); + + // Assert + assertNotNull(result); + assertEquals(INTERACTION_ID, result.id()); + } + + @Test + public void testGet_responseWithAllFields() throws Exception { + // Arrange - Comprehensive response with all possible fields + String fullResponseJson = + "{" + + "\"id\":\"" + INTERACTION_ID + "\"," + + "\"status\":\"completed\"," + + "\"model\":\"" + MODEL_ID + "\"," + + "\"created\":\"2025-01-24T10:00:00Z\"," + + "\"updated\":\"2025-01-24T10:01:00Z\"," + + "\"outputs\":[{\"type\":\"text\",\"text\":\"Response\"}]," + + "\"usage\":{\"total_token_count\":150,\"prompt_token_count\":50,\"candidates_token_count\":100}," + + "\"previous_interaction_id\":\"prev-123\"" + + "}"; + + ResponseBody body = ResponseBody.create(fullResponseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + GetInteractionConfig config = GetInteractionConfig.builder().build(); + + // Act + Interaction result = client.interactions.get(INTERACTION_ID, config); + + // Assert - Verify all fields are present + assertNotNull(result); + assertEquals(INTERACTION_ID, result.id()); + assertTrue(result.model().isPresent()); + assertTrue(result.created().isPresent()); + assertTrue(result.updated().isPresent()); + assertTrue(result.outputs().isPresent()); + assertTrue(result.usage().isPresent()); + assertTrue(result.previousInteractionId().isPresent()); + } + + @Test + public void testDelete_responseHandling() throws Exception { + // Arrange + String deleteResponseJson = "{\"success\":true}"; + ResponseBody body = ResponseBody.create(deleteResponseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + DeleteInteractionConfig config = DeleteInteractionConfig.builder().build(); + + // Act + DeleteInteractionResponse result = client.interactions.delete(INTERACTION_ID, config); + + // Assert + assertNotNull(result); + } + + @Test + public void testCreate_withBackgroundTrue() throws Exception { + // Arrange + String responseJson = + "{\"id\":\"" + INTERACTION_ID + "\",\"status\":\"in_progress\",\"background\":true}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Background task") + .background(true) + .build(); + + // Act + Interaction result = client.interactions.create(config); + + // Assert + assertNotNull(result); + assertTrue(config.background().isPresent()); + assertTrue(config.background().get()); + } + + @Test + public void testCreate_withStoreTrue() throws Exception { + // Arrange + String responseJson = "{\"id\":\"" + INTERACTION_ID + "\",\"status\":\"completed\"}"; + ResponseBody body = ResponseBody.create(responseJson, MediaType.get("application/json")); + when(mockedResponse.getBody()).thenReturn(body); + when(mockedClient.request(anyString(), anyString(), anyString(), any())) + .thenReturn(mockedResponse); + + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model(MODEL_ID) + .input("Store conversation") + .store(true) + .build(); + + // Act + Interaction result = client.interactions.create(config); + + // Assert + assertNotNull(result); + assertTrue(config.store().isPresent()); + assertTrue(config.store().get()); + } +} diff --git a/src/test/java/com/google/genai/InteractionsTest.java b/src/test/java/com/google/genai/InteractionsTest.java new file mode 100644 index 00000000000..25d297974d4 --- /dev/null +++ b/src/test/java/com/google/genai/InteractionsTest.java @@ -0,0 +1,106 @@ +/* + * 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. + */ + +package com.google.genai; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.genai.types.interactions.CreateInteractionConfig; +import org.junit.jupiter.api.Test; + +/** Tests for the Interactions resource. */ +public class InteractionsTest { + + @Test + public void testValidationBothModelAndAgent() { + // Arrange + Client client = Client.builder().apiKey("test-key").build(); + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .agent("deep-research-pro-preview-12-2025") + .input("Test input") + .build(); + + // Act & Assert + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> client.interactions.create(config)); + + assertNotNull(exception.getMessage()); + assert exception.getMessage().contains("Cannot specify both 'model' and 'agent'"); + } + + @Test + public void testValidationNeitherModelNorAgent() { + // Arrange + Client client = Client.builder().apiKey("test-key").build(); + CreateInteractionConfig config = + CreateInteractionConfig.builder().input("Test input").build(); + + // Act & Assert + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> client.interactions.create(config)); + + assertNotNull(exception.getMessage()); + assert exception.getMessage().contains("Must specify either 'model' or 'agent'"); + } + + @Test + public void testValidationAgentWithGenerationConfig() { + // Arrange + Client client = Client.builder().apiKey("test-key").build(); + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .agent("deep-research-pro-preview-12-2025") + .input("Test input") + .generationConfig( + com.google.genai.types.interactions.GenerationConfig.builder() + .temperature(0.5f) + .build()) + .build(); + + // Act & Assert + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> client.interactions.create(config)); + + assertNotNull(exception.getMessage()); + assert exception.getMessage().contains("Cannot use 'generationConfig' with agent-based"); + } + + @Test + public void testValidationModelWithAgentConfig() { + // Arrange + Client client = Client.builder().apiKey("test-key").build(); + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("Test input") + .agentConfig(com.google.genai.types.interactions.DynamicAgentConfig.create()) + .build(); + + // Act & Assert + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> client.interactions.create(config)); + + assertNotNull(exception.getMessage()); + assert exception.getMessage().contains("Cannot use 'agentConfig' with model-based"); + } + + // Note: Vertex AI support was added in PR3. + // The testVertexAIUnsupported test has been removed since Vertex AI is now supported. + // Integration tests for Vertex AI are in examples/VertexAIInteractionTest.java +} diff --git a/src/test/java/com/google/genai/types/interactions/AllowedToolsTest.java b/src/test/java/com/google/genai/types/interactions/AllowedToolsTest.java new file mode 100644 index 00000000000..2013e48d2cd --- /dev/null +++ b/src/test/java/com/google/genai/types/interactions/AllowedToolsTest.java @@ -0,0 +1,520 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Comprehensive tests for AllowedTools configuration class. + * + *

AllowedTools specifies which tools are permitted in tool choice configurations, with a mode + * (auto, any, none, validated) and a list of tool names. + */ +public class AllowedToolsTest { + + // ========== Builder Tests ========== + + @Test + public void testAllowedToolsBuilderWithModeAndTools() { + // Arrange & Act + AllowedTools allowedTools = AllowedTools.builder() + .mode("auto") + .tools("get_weather", "get_forecast", "search_web") + .build(); + + // Assert + assertTrue(allowedTools.mode().isPresent()); + assertEquals("auto", allowedTools.mode().get()); + assertTrue(allowedTools.tools().isPresent()); + assertEquals(3, allowedTools.tools().get().size()); + assertEquals("get_weather", allowedTools.tools().get().get(0)); + assertEquals("get_forecast", allowedTools.tools().get().get(1)); + assertEquals("search_web", allowedTools.tools().get().get(2)); + } + + @Test + public void testAllowedToolsBuilderWithModeOnly() { + // Arrange & Act + AllowedTools allowedTools = AllowedTools.builder() + .mode("any") + .build(); + + // Assert + assertTrue(allowedTools.mode().isPresent()); + assertEquals("any", allowedTools.mode().get()); + assertFalse(allowedTools.tools().isPresent()); + } + + @Test + public void testAllowedToolsBuilderWithToolsOnly() { + // Arrange & Act + AllowedTools allowedTools = AllowedTools.builder() + .tools("tool1", "tool2") + .build(); + + // Assert + assertFalse(allowedTools.mode().isPresent()); + assertTrue(allowedTools.tools().isPresent()); + assertEquals(2, allowedTools.tools().get().size()); + } + + @Test + public void testAllowedToolsBuilderEmpty() { + // Arrange & Act + AllowedTools allowedTools = AllowedTools.builder().build(); + + // Assert + assertFalse(allowedTools.mode().isPresent()); + assertFalse(allowedTools.tools().isPresent()); + } + + @Test + public void testAllowedToolsBuilderWithList() { + // Arrange + List toolsList = Arrays.asList("calculator", "converter", "translator"); + + // Act + AllowedTools allowedTools = AllowedTools.builder() + .mode("validated") + .tools(toolsList) + .build(); + + // Assert + assertTrue(allowedTools.mode().isPresent()); + assertEquals("validated", allowedTools.mode().get()); + assertTrue(allowedTools.tools().isPresent()); + assertEquals(3, allowedTools.tools().get().size()); + assertEquals(toolsList, allowedTools.tools().get()); + } + + // ========== Factory Method Tests ========== + + @Test + public void testAllowedToolsOfWithVarargs() { + // Arrange & Act + AllowedTools allowedTools = AllowedTools.of("auto", "weather_tool", "news_tool"); + + // Assert + assertTrue(allowedTools.mode().isPresent()); + assertEquals("auto", allowedTools.mode().get()); + assertTrue(allowedTools.tools().isPresent()); + assertEquals(2, allowedTools.tools().get().size()); + assertEquals("weather_tool", allowedTools.tools().get().get(0)); + assertEquals("news_tool", allowedTools.tools().get().get(1)); + } + + @Test + public void testAllowedToolsOfWithList() { + // Arrange + List tools = Arrays.asList("search", "browse", "analyze"); + + // Act + AllowedTools allowedTools = AllowedTools.of("any", tools); + + // Assert + assertEquals("any", allowedTools.mode().get()); + assertEquals(3, allowedTools.tools().get().size()); + assertEquals(tools, allowedTools.tools().get()); + } + + @Test + public void testAllowedToolsOfWithSingleTool() { + // Arrange & Act + AllowedTools allowedTools = AllowedTools.of("validated", "single_tool"); + + // Assert + assertEquals("validated", allowedTools.mode().get()); + assertEquals(1, allowedTools.tools().get().size()); + assertEquals("single_tool", allowedTools.tools().get().get(0)); + } + + // ========== Mode Value Tests ========== + + @Test + public void testAllowedToolsModeAuto() { + // Arrange & Act + AllowedTools allowedTools = AllowedTools.builder().mode("auto").build(); + + // Assert + assertEquals("auto", allowedTools.mode().get()); + } + + @Test + public void testAllowedToolsModeAny() { + // Arrange & Act + AllowedTools allowedTools = AllowedTools.builder().mode("any").build(); + + // Assert + assertEquals("any", allowedTools.mode().get()); + } + + @Test + public void testAllowedToolsModeNone() { + // Arrange & Act + AllowedTools allowedTools = AllowedTools.builder().mode("none").build(); + + // Assert + assertEquals("none", allowedTools.mode().get()); + } + + @Test + public void testAllowedToolsModeValidated() { + // Arrange & Act + AllowedTools allowedTools = AllowedTools.builder().mode("validated").build(); + + // Assert + assertEquals("validated", allowedTools.mode().get()); + } + + @Test + public void testAllowedToolsCustomMode() { + // Arrange & Act - test with custom/unknown mode value + AllowedTools allowedTools = AllowedTools.builder().mode("custom_mode").build(); + + // Assert + assertEquals("custom_mode", allowedTools.mode().get()); + } + + // ========== Clear Methods Tests ========== + + @Test + public void testAllowedToolsClearMode() { + // Arrange + AllowedTools.Builder builder = AllowedTools.builder() + .mode("auto") + .tools("tool1"); + + // Act + AllowedTools allowedTools = builder.clearMode().build(); + + // Assert + assertFalse(allowedTools.mode().isPresent()); + assertTrue(allowedTools.tools().isPresent()); + } + + @Test + public void testAllowedToolsClearTools() { + // Arrange + AllowedTools.Builder builder = AllowedTools.builder() + .mode("any") + .tools("tool1", "tool2"); + + // Act + AllowedTools allowedTools = builder.clearTools().build(); + + // Assert + assertTrue(allowedTools.mode().isPresent()); + assertFalse(allowedTools.tools().isPresent()); + } + + @Test + public void testAllowedToolsClearBoth() { + // Arrange + AllowedTools.Builder builder = AllowedTools.builder() + .mode("validated") + .tools("tool1"); + + // Act + AllowedTools allowedTools = builder.clearMode().clearTools().build(); + + // Assert + assertFalse(allowedTools.mode().isPresent()); + assertFalse(allowedTools.tools().isPresent()); + } + + // ========== ToBuilder Tests ========== + + @Test + public void testAllowedToolsToBuilder() { + // Arrange + AllowedTools original = AllowedTools.of("auto", "tool_a", "tool_b"); + + // Act + AllowedTools modified = original.toBuilder() + .mode("any") + .build(); + + // Assert + assertEquals("any", modified.mode().get()); + assertEquals(2, modified.tools().get().size()); + assertEquals("tool_a", modified.tools().get().get(0)); + } + + @Test + public void testAllowedToolsToBuilderPreservesValues() { + // Arrange + AllowedTools original = AllowedTools.of("validated", "x", "y", "z"); + + // Act + AllowedTools copy = original.toBuilder().build(); + + // Assert + assertEquals(original.mode().get(), copy.mode().get()); + assertEquals(original.tools().get().size(), copy.tools().get().size()); + assertEquals(original.tools().get(), copy.tools().get()); + } + + // ========== JSON Serialization Tests ========== + + @Test + public void testAllowedToolsJsonSerialization() { + // Arrange + AllowedTools allowedTools = AllowedTools.of("auto", "get_weather", "search_web"); + + // Act + String json = allowedTools.toJson(); + + // Assert + assertTrue(json.contains("\"mode\":\"auto\"")); + assertTrue(json.contains("\"tools\":[")); + assertTrue(json.contains("\"get_weather\"")); + assertTrue(json.contains("\"search_web\"")); + } + + @Test + public void testAllowedToolsJsonSerializationModeOnly() { + // Arrange + AllowedTools allowedTools = AllowedTools.builder().mode("none").build(); + + // Act + String json = allowedTools.toJson(); + + // Assert + assertTrue(json.contains("\"mode\":\"none\"")); + assertFalse(json.contains("\"tools\"")); + } + + @Test + public void testAllowedToolsJsonSerializationToolsOnly() { + // Arrange + AllowedTools allowedTools = AllowedTools.builder() + .tools("tool1", "tool2") + .build(); + + // Act + String json = allowedTools.toJson(); + + // Assert + assertTrue(json.contains("\"tools\":[\"tool1\",\"tool2\"]")); + assertFalse(json.contains("\"mode\"")); + } + + @Test + public void testAllowedToolsJsonSerializationEmpty() { + // Arrange + AllowedTools allowedTools = AllowedTools.builder().build(); + + // Act + String json = allowedTools.toJson(); + + // Assert + assertEquals("{}", json); + } + + // ========== JSON Deserialization Tests ========== + + @Test + public void testAllowedToolsJsonDeserialization() { + // Arrange + String json = "{\"mode\":\"validated\",\"tools\":[\"analyzer\",\"parser\"]}"; + + // Act + AllowedTools allowedTools = AllowedTools.fromJson(json); + + // Assert + assertTrue(allowedTools.mode().isPresent()); + assertEquals("validated", allowedTools.mode().get()); + assertTrue(allowedTools.tools().isPresent()); + assertEquals(2, allowedTools.tools().get().size()); + assertEquals("analyzer", allowedTools.tools().get().get(0)); + assertEquals("parser", allowedTools.tools().get().get(1)); + } + + @Test + public void testAllowedToolsJsonDeserializationModeOnly() { + // Arrange + String json = "{\"mode\":\"auto\"}"; + + // Act + AllowedTools allowedTools = AllowedTools.fromJson(json); + + // Assert + assertEquals("auto", allowedTools.mode().get()); + assertFalse(allowedTools.tools().isPresent()); + } + + @Test + public void testAllowedToolsJsonDeserializationToolsOnly() { + // Arrange + String json = "{\"tools\":[\"a\",\"b\",\"c\"]}"; + + // Act + AllowedTools allowedTools = AllowedTools.fromJson(json); + + // Assert + assertFalse(allowedTools.mode().isPresent()); + assertEquals(3, allowedTools.tools().get().size()); + } + + @Test + public void testAllowedToolsJsonDeserializationEmpty() { + // Arrange + String json = "{}"; + + // Act + AllowedTools allowedTools = AllowedTools.fromJson(json); + + // Assert + assertFalse(allowedTools.mode().isPresent()); + assertFalse(allowedTools.tools().isPresent()); + } + + // ========== Round-trip Serialization Tests ========== + + @Test + public void testAllowedToolsRoundTrip() { + // Arrange + AllowedTools original = AllowedTools.of("any", "func1", "func2", "func3"); + + // Act + String json = original.toJson(); + AllowedTools deserialized = AllowedTools.fromJson(json); + + // Assert + assertEquals(original.mode().get(), deserialized.mode().get()); + assertEquals(original.tools().get().size(), deserialized.tools().get().size()); + assertEquals(original.tools().get(), deserialized.tools().get()); + } + + @Test + public void testAllowedToolsRoundTripModeOnly() { + // Arrange + AllowedTools original = AllowedTools.builder().mode("validated").build(); + + // Act + String json = original.toJson(); + AllowedTools deserialized = AllowedTools.fromJson(json); + + // Assert + assertEquals(original.mode().get(), deserialized.mode().get()); + assertFalse(deserialized.tools().isPresent()); + } + + // ========== Edge Cases ========== + + @Test + public void testAllowedToolsWithEmptyToolsList() { + // Arrange + AllowedTools allowedTools = AllowedTools.builder() + .mode("auto") + .tools(Arrays.asList()) + .build(); + + // Assert - empty list is still present + assertTrue(allowedTools.tools().isPresent()); + assertEquals(0, allowedTools.tools().get().size()); + } + + @Test + public void testAllowedToolsWithManyTools() { + // Arrange - test with many tools + AllowedTools allowedTools = AllowedTools.of( + "any", + "tool1", "tool2", "tool3", "tool4", "tool5", + "tool6", "tool7", "tool8", "tool9", "tool10" + ); + + // Assert + assertEquals(10, allowedTools.tools().get().size()); + assertEquals("tool1", allowedTools.tools().get().get(0)); + assertEquals("tool10", allowedTools.tools().get().get(9)); + } + + @Test + public void testAllowedToolsWithSpecialCharactersInToolNames() { + // Arrange + AllowedTools allowedTools = AllowedTools.of( + "auto", + "get_weather-v2", + "search::web", + "tool.name.with.dots" + ); + + // Assert + assertEquals(3, allowedTools.tools().get().size()); + assertTrue(allowedTools.tools().get().contains("get_weather-v2")); + assertTrue(allowedTools.tools().get().contains("search::web")); + assertTrue(allowedTools.tools().get().contains("tool.name.with.dots")); + } + + @Test + public void testAllowedToolsNotNull() { + // Arrange + AllowedTools allowedTools = AllowedTools.builder().build(); + + // Assert + assertNotNull(allowedTools); + assertNotNull(allowedTools.mode()); + assertNotNull(allowedTools.tools()); + } + + // ========== Integration Tests ========== + + @Test + public void testAllowedToolsInToolChoiceConfig() { + // Arrange + AllowedTools allowedTools = AllowedTools.of("validated", "weather", "news"); + ToolChoiceConfig config = ToolChoiceConfig.builder() + .allowedTools(allowedTools) + .build(); + + // Act + String json = config.toJson(); + + // Assert + assertTrue(json.contains("\"allowed_tools\"")); + assertTrue(json.contains("\"mode\":\"validated\"")); + assertTrue(json.contains("\"weather\"")); + assertTrue(json.contains("\"news\"")); + } + + @Test + public void testAllowedToolsInToolChoice() { + // Arrange + AllowedTools allowedTools = AllowedTools.of("any", "search", "calculate"); + ToolChoiceConfig config = ToolChoiceConfig.builder() + .allowedTools(allowedTools) + .build(); + ToolChoice toolChoice = ToolChoice.fromConfig(config); + + // Act + String json = toolChoice.toJson(); + + // Assert + assertTrue(json.contains("\"allowed_tools\"")); + assertTrue(json.contains("\"mode\":\"any\"")); + assertTrue(json.contains("\"search\"")); + assertTrue(json.contains("\"calculate\"")); + } +} diff --git a/src/test/java/com/google/genai/types/interactions/AnnotationTest.java b/src/test/java/com/google/genai/types/interactions/AnnotationTest.java new file mode 100644 index 00000000000..ff492a80e78 --- /dev/null +++ b/src/test/java/com/google/genai/types/interactions/AnnotationTest.java @@ -0,0 +1,157 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** Tests for Annotation type. */ +public class AnnotationTest { + + @Test + public void testAnnotationBuilder() { + // Arrange & Act + Annotation annotation = + Annotation.builder() + .startIndex(0) + .endIndex(25) + .source("https://example.com/source1") + .build(); + + // Assert + assertTrue(annotation.startIndex().isPresent()); + assertEquals(0, annotation.startIndex().get()); + assertTrue(annotation.endIndex().isPresent()); + assertEquals(25, annotation.endIndex().get()); + assertTrue(annotation.source().isPresent()); + assertEquals("https://example.com/source1", annotation.source().get()); + } + + @Test + public void testAnnotationOfFactoryMethod() { + // Arrange & Act + Annotation annotation = Annotation.of(10, 50, "Wikipedia: AI Article"); + + // Assert + assertTrue(annotation.startIndex().isPresent()); + assertEquals(10, annotation.startIndex().get()); + assertTrue(annotation.endIndex().isPresent()); + assertEquals(50, annotation.endIndex().get()); + assertTrue(annotation.source().isPresent()); + assertEquals("Wikipedia: AI Article", annotation.source().get()); + } + + @Test + public void testAnnotationWithOptionalFields() { + // Arrange & Act - Build annotation with only source + Annotation annotation = Annotation.builder().source("https://source.com").build(); + + // Assert + assertFalse(annotation.startIndex().isPresent()); + assertFalse(annotation.endIndex().isPresent()); + assertTrue(annotation.source().isPresent()); + assertEquals("https://source.com", annotation.source().get()); + } + + @Test + public void testAnnotationClearMethods() { + // Arrange + Annotation.Builder builder = + Annotation.builder() + .startIndex(0) + .endIndex(10) + .source("https://example.com"); + + // Act - Clear fields + builder.clearStartIndex(); + builder.clearEndIndex(); + builder.clearSource(); + Annotation annotation = builder.build(); + + // Assert + assertFalse(annotation.startIndex().isPresent()); + assertFalse(annotation.endIndex().isPresent()); + assertFalse(annotation.source().isPresent()); + } + + @Test + public void testAnnotationJsonSerialization() { + // Arrange + Annotation annotation = Annotation.of(0, 25, "https://example.com/source1"); + + // Act + String json = annotation.toJson(); + + // Assert + assertNotNull(json); + assertTrue(json.contains("\"start_index\":0")); + assertTrue(json.contains("\"end_index\":25")); + assertTrue(json.contains("\"source\":\"https://example.com/source1\"")); + } + + @Test + public void testAnnotationJsonDeserialization() { + // Arrange + String json = + "{\"start_index\":15,\"end_index\":30,\"source\":\"https://example.com/citation\"}"; + + // Act + Annotation annotation = Annotation.fromJson(json); + + // Assert + assertTrue(annotation.startIndex().isPresent()); + assertEquals(15, annotation.startIndex().get()); + assertTrue(annotation.endIndex().isPresent()); + assertEquals(30, annotation.endIndex().get()); + assertTrue(annotation.source().isPresent()); + assertEquals("https://example.com/citation", annotation.source().get()); + } + + @Test + public void testAnnotationRoundTripSerialization() { + // Arrange + Annotation original = Annotation.of(5, 20, "https://source.org"); + + // Act - Serialize and deserialize + String json = original.toJson(); + Annotation deserialized = Annotation.fromJson(json); + + // Assert - Fields match + assertEquals(original.startIndex(), deserialized.startIndex()); + assertEquals(original.endIndex(), deserialized.endIndex()); + assertEquals(original.source(), deserialized.source()); + } + + @Test + public void testAnnotationToBuilder() { + // Arrange + Annotation original = Annotation.of(10, 50, "https://example.com"); + + // Act - Modify via toBuilder + Annotation modified = original.toBuilder().source("https://new-source.com").build(); + + // Assert - Original unchanged, modified has new source + assertEquals("https://example.com", original.source().get()); + assertEquals("https://new-source.com", modified.source().get()); + assertEquals(original.startIndex(), modified.startIndex()); + assertEquals(original.endIndex(), modified.endIndex()); + } +} diff --git a/src/test/java/com/google/genai/types/interactions/EnumSerializationTest.java b/src/test/java/com/google/genai/types/interactions/EnumSerializationTest.java new file mode 100644 index 00000000000..30ddf51b75d --- /dev/null +++ b/src/test/java/com/google/genai/types/interactions/EnumSerializationTest.java @@ -0,0 +1,323 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Tests that all Interactions API enums serialize to lowercase per the OpenAPI specification. + * + *

The Interactions API OpenAPI spec requires enum values to be lowercase: + *

    + *
  • ToolChoiceType: "auto", "any", "none", "validated" + *
  • ThinkingLevel: "minimal", "low", "medium", "high" + *
  • ThinkingSummaries: "auto", "none" + *
  • MediaResolution: "low", "medium", "high" + *
  • ResponseModality: "text", "image", "audio" + *
+ */ +public class EnumSerializationTest { + + @Nested + @DisplayName("ToolChoiceType serialization") + class ToolChoiceTypeSerializationTest { + + @Test + public void testAuto_serializesToLowercase() { + ToolChoiceType type = new ToolChoiceType(ToolChoiceType.Known.AUTO); + assertEquals("auto", type.toString()); + } + + @Test + public void testAny_serializesToLowercase() { + ToolChoiceType type = new ToolChoiceType(ToolChoiceType.Known.ANY); + assertEquals("any", type.toString()); + } + + @Test + public void testNone_serializesToLowercase() { + ToolChoiceType type = new ToolChoiceType(ToolChoiceType.Known.NONE); + assertEquals("none", type.toString()); + } + + @Test + public void testValidated_serializesToLowercase() { + ToolChoiceType type = new ToolChoiceType(ToolChoiceType.Known.VALIDATED); + assertEquals("validated", type.toString()); + } + + @Test + public void testUnspecified_serializesToLowercase() { + ToolChoiceType type = new ToolChoiceType(ToolChoiceType.Known.TOOL_CHOICE_TYPE_UNSPECIFIED); + assertEquals("unspecified", type.toString()); + } + + @Test + public void testFromString_caseInsensitive() { + ToolChoiceType fromLower = new ToolChoiceType("auto"); + ToolChoiceType fromUpper = new ToolChoiceType("AUTO"); + ToolChoiceType fromMixed = new ToolChoiceType("Auto"); + + assertEquals(ToolChoiceType.Known.AUTO, fromLower.knownEnum()); + assertEquals(ToolChoiceType.Known.AUTO, fromUpper.knownEnum()); + assertEquals(ToolChoiceType.Known.AUTO, fromMixed.knownEnum()); + } + } + + @Nested + @DisplayName("ThinkingLevel serialization") + class ThinkingLevelSerializationTest { + + @Test + public void testMinimal_serializesToLowercase() { + ThinkingLevel level = new ThinkingLevel(ThinkingLevel.Known.MINIMAL); + assertEquals("minimal", level.toString()); + } + + @Test + public void testLow_serializesToLowercase() { + ThinkingLevel level = new ThinkingLevel(ThinkingLevel.Known.LOW); + assertEquals("low", level.toString()); + } + + @Test + public void testMedium_serializesToLowercase() { + ThinkingLevel level = new ThinkingLevel(ThinkingLevel.Known.MEDIUM); + assertEquals("medium", level.toString()); + } + + @Test + public void testHigh_serializesToLowercase() { + ThinkingLevel level = new ThinkingLevel(ThinkingLevel.Known.HIGH); + assertEquals("high", level.toString()); + } + + @Test + public void testUnspecified_serializesToLowercase() { + ThinkingLevel level = new ThinkingLevel(ThinkingLevel.Known.THINKING_LEVEL_UNSPECIFIED); + assertEquals("unspecified", level.toString()); + } + + @Test + public void testFromString_caseInsensitive() { + ThinkingLevel fromLower = new ThinkingLevel("high"); + ThinkingLevel fromUpper = new ThinkingLevel("HIGH"); + ThinkingLevel fromMixed = new ThinkingLevel("High"); + + assertEquals(ThinkingLevel.Known.HIGH, fromLower.knownEnum()); + assertEquals(ThinkingLevel.Known.HIGH, fromUpper.knownEnum()); + assertEquals(ThinkingLevel.Known.HIGH, fromMixed.knownEnum()); + } + } + + @Nested + @DisplayName("ThinkingSummaries serialization") + class ThinkingSummariesSerializationTest { + + @Test + public void testAuto_serializesToLowercase() { + ThinkingSummaries summaries = new ThinkingSummaries(ThinkingSummaries.Known.AUTO); + assertEquals("auto", summaries.toString()); + } + + @Test + public void testNone_serializesToLowercase() { + ThinkingSummaries summaries = new ThinkingSummaries(ThinkingSummaries.Known.NONE); + assertEquals("none", summaries.toString()); + } + + @Test + public void testUnspecified_serializesToLowercase() { + ThinkingSummaries summaries = + new ThinkingSummaries(ThinkingSummaries.Known.THINKING_SUMMARIES_UNSPECIFIED); + assertEquals("unspecified", summaries.toString()); + } + + @Test + public void testFromString_caseInsensitive() { + ThinkingSummaries fromLower = new ThinkingSummaries("auto"); + ThinkingSummaries fromUpper = new ThinkingSummaries("AUTO"); + ThinkingSummaries fromMixed = new ThinkingSummaries("Auto"); + + assertEquals(ThinkingSummaries.Known.AUTO, fromLower.knownEnum()); + assertEquals(ThinkingSummaries.Known.AUTO, fromUpper.knownEnum()); + assertEquals(ThinkingSummaries.Known.AUTO, fromMixed.knownEnum()); + } + } + + @Nested + @DisplayName("MediaResolution serialization") + class MediaResolutionSerializationTest { + + @Test + public void testLow_serializesToLowercase() { + MediaResolution resolution = new MediaResolution(MediaResolution.Known.LOW); + assertEquals("low", resolution.toString()); + } + + @Test + public void testMedium_serializesToLowercase() { + MediaResolution resolution = new MediaResolution(MediaResolution.Known.MEDIUM); + assertEquals("medium", resolution.toString()); + } + + @Test + public void testHigh_serializesToLowercase() { + MediaResolution resolution = new MediaResolution(MediaResolution.Known.HIGH); + assertEquals("high", resolution.toString()); + } + + @Test + public void testFromString_caseInsensitive() { + MediaResolution fromLower = new MediaResolution("medium"); + MediaResolution fromUpper = new MediaResolution("MEDIUM"); + MediaResolution fromMixed = new MediaResolution("Medium"); + + assertEquals(MediaResolution.Known.MEDIUM, fromLower.knownEnum()); + assertEquals(MediaResolution.Known.MEDIUM, fromUpper.knownEnum()); + assertEquals(MediaResolution.Known.MEDIUM, fromMixed.knownEnum()); + } + } + + @Nested + @DisplayName("ResponseModality serialization") + class ResponseModalitySerializationTest { + + @Test + public void testText_serializesToLowercase() { + ResponseModality modality = new ResponseModality(ResponseModality.Known.TEXT); + assertEquals("text", modality.toString()); + } + + @Test + public void testImage_serializesToLowercase() { + ResponseModality modality = new ResponseModality(ResponseModality.Known.IMAGE); + assertEquals("image", modality.toString()); + } + + @Test + public void testAudio_serializesToLowercase() { + ResponseModality modality = new ResponseModality(ResponseModality.Known.AUDIO); + assertEquals("audio", modality.toString()); + } + + @Test + public void testFromString_caseInsensitive() { + ResponseModality fromLower = new ResponseModality("text"); + ResponseModality fromUpper = new ResponseModality("TEXT"); + ResponseModality fromMixed = new ResponseModality("Text"); + + assertEquals(ResponseModality.Known.TEXT, fromLower.knownEnum()); + assertEquals(ResponseModality.Known.TEXT, fromUpper.knownEnum()); + assertEquals(ResponseModality.Known.TEXT, fromMixed.knownEnum()); + } + } + + @Nested + @DisplayName("GenerationConfig JSON serialization") + class GenerationConfigJsonSerializationTest { + + @Test + public void testThinkingLevel_serializesAsLowercaseInJson() { + GenerationConfig config = + GenerationConfig.builder() + .thinkingLevel(new ThinkingLevel(ThinkingLevel.Known.HIGH)) + .build(); + + String json = config.toJson(); + + // Verify lowercase value in JSON + assertTrue(json.contains("\"thinking_level\":\"high\""), + "Expected lowercase 'high' in JSON, got: " + json); + } + + @Test + public void testThinkingSummaries_serializesAsLowercaseInJson() { + GenerationConfig config = + GenerationConfig.builder() + .thinkingSummaries(new ThinkingSummaries(ThinkingSummaries.Known.AUTO)) + .build(); + + String json = config.toJson(); + + // Verify lowercase value in JSON + assertTrue(json.contains("\"thinking_summaries\":\"auto\""), + "Expected lowercase 'auto' in JSON, got: " + json); + } + + @Test + public void testAllEnums_serializeAsLowercaseInJson() { + GenerationConfig config = + GenerationConfig.builder() + .thinkingLevel(new ThinkingLevel(ThinkingLevel.Known.MEDIUM)) + .thinkingSummaries(new ThinkingSummaries(ThinkingSummaries.Known.NONE)) + .toolChoice(ToolChoice.fromString("any")) + .build(); + + String json = config.toJson(); + + // Verify all lowercase values in JSON + assertTrue(json.contains("\"thinking_level\":\"medium\""), + "Expected lowercase 'medium' in JSON"); + assertTrue(json.contains("\"thinking_summaries\":\"none\""), + "Expected lowercase 'none' in JSON"); + assertTrue(json.contains("\"any\""), + "Expected lowercase 'any' in JSON"); + } + + @Test + public void testJsonRoundTrip_preservesEnumValues() { + GenerationConfig original = + GenerationConfig.builder() + .thinkingLevel(new ThinkingLevel(ThinkingLevel.Known.HIGH)) + .thinkingSummaries(new ThinkingSummaries(ThinkingSummaries.Known.AUTO)) + .build(); + + String json = original.toJson(); + GenerationConfig deserialized = GenerationConfig.fromJson(json); + + assertEquals(ThinkingLevel.Known.HIGH, deserialized.thinkingLevel().get().knownEnum()); + assertEquals(ThinkingSummaries.Known.AUTO, deserialized.thinkingSummaries().get().knownEnum()); + } + + @Test + public void testJsonDeserialization_fromLowercaseValues() { + String json = "{\"thinking_level\":\"low\",\"thinking_summaries\":\"none\"}"; + + GenerationConfig config = GenerationConfig.fromJson(json); + + assertEquals(ThinkingLevel.Known.LOW, config.thinkingLevel().get().knownEnum()); + assertEquals(ThinkingSummaries.Known.NONE, config.thinkingSummaries().get().knownEnum()); + } + + @Test + public void testJsonDeserialization_fromUppercaseValues() { + // API might return uppercase, ensure we handle it + String json = "{\"thinking_level\":\"HIGH\",\"thinking_summaries\":\"AUTO\"}"; + + GenerationConfig config = GenerationConfig.fromJson(json); + + assertEquals(ThinkingLevel.Known.HIGH, config.thinkingLevel().get().knownEnum()); + assertEquals(ThinkingSummaries.Known.AUTO, config.thinkingSummaries().get().knownEnum()); + } + } +} diff --git a/src/test/java/com/google/genai/types/interactions/InputSerializerTest.java b/src/test/java/com/google/genai/types/interactions/InputSerializerTest.java new file mode 100644 index 00000000000..70f2d7c8073 --- /dev/null +++ b/src/test/java/com/google/genai/types/interactions/InputSerializerTest.java @@ -0,0 +1,347 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.genai.types.interactions.content.ImageContent; +import com.google.genai.types.interactions.content.TextContent; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +/** + * Tests for InputSerializer to ensure proper JSON serialization of Input union types. + * + *

InputSerializer handles three different input types: + * + *

    + *
  • String input - serialized as a JSON string + *
  • Single or list of Content objects - serialized with type discriminators + *
  • List of Turn objects - serialized with proper polymorphic handling + *
+ */ +public class InputSerializerTest { + + // ========== String Input Tests ========== + + @Test + public void testSerializeStringInput() { + // Arrange + Input input = Input.fromString("What is the capital of France?"); + + // Act + String json = input.toJson(); + + // Assert - should serialize as a plain JSON string + assertEquals("\"What is the capital of France?\"", json); + } + + @Test + public void testSerializeStringInputWithSpecialCharacters() { + // Arrange + Input input = Input.fromString("Hello \"world\" with\nnewlines and\ttabs"); + + // Act + String json = input.toJson(); + + // Assert - should properly escape special characters + assertTrue(json.contains("\\n")); + assertTrue(json.contains("\\t")); + assertTrue(json.contains("\\\"")); + } + + @Test + public void testSerializeEmptyString() { + // Arrange + Input input = Input.fromString(""); + + // Act + String json = input.toJson(); + + // Assert + assertEquals("\"\"", json); + } + + // ========== Single Content Input Tests ========== + + @Test + public void testSerializeSingleTextContent() { + // Arrange + TextContent text = TextContent.of("This is a text message"); + Input input = Input.fromContent(text); + + // Act + String json = input.toJson(); + + // Assert - should include type discriminator + assertTrue(json.contains("\"type\":\"text\"")); + assertTrue(json.contains("\"text\":\"This is a text message\"")); + } + + @Test + public void testSerializeSingleImageContent() { + // Arrange + ImageContent image = ImageContent.fromUri("gs://bucket/image.png", "image/png"); + Input input = Input.fromContent(image); + + // Act + String json = input.toJson(); + + // Assert - should include type discriminator + assertTrue(json.contains("\"type\":\"image\"")); + assertTrue(json.contains("\"uri\":\"gs://bucket/image.png\"")); + assertTrue(json.contains("\"mime_type\":\"image/png\"")); + } + + // ========== List of Content Input Tests ========== + + @Test + public void testSerializeContentListFromList() { + // Arrange + TextContent text1 = TextContent.of("First message"); + TextContent text2 = TextContent.of("Second message"); + Input input = Input.fromContents(Arrays.asList(text1, text2)); + + // Act + String json = input.toJson(); + + // Assert - should serialize as a JSON array with type discriminators + assertTrue(json.startsWith("[")); + assertTrue(json.endsWith("]")); + assertTrue(json.contains("\"type\":\"text\"")); + assertTrue(json.contains("\"text\":\"First message\"")); + assertTrue(json.contains("\"text\":\"Second message\"")); + } + + @Test + public void testSerializeContentListFromVarargs() { + // Arrange + TextContent text = TextContent.of("Text message"); + ImageContent image = ImageContent.fromUri("gs://bucket/img.jpg", "image/jpeg"); + Input input = Input.fromContents(text, image); + + // Act + String json = input.toJson(); + + // Assert - should serialize mixed content types with their discriminators + assertTrue(json.startsWith("[")); + assertTrue(json.contains("\"type\":\"text\"")); + assertTrue(json.contains("\"type\":\"image\"")); + assertTrue(json.contains("\"text\":\"Text message\"")); + assertTrue(json.contains("\"uri\":\"gs://bucket/img.jpg\"")); + } + + @Test + public void testSerializeEmptyContentList() { + // Arrange + Input input = Input.fromContents(Arrays.asList()); + + // Act + String json = input.toJson(); + + // Assert - should serialize as empty array + assertEquals("[]", json); + } + + // ========== Turn Input Tests ========== + + @Test + public void testSerializeTurnListFromList() { + // Arrange + Turn userTurn = Turn.builder() + .role("user") + .content(TextContent.of("What is AI?")) + .build(); + Turn modelTurn = Turn.builder() + .role("model") + .content(TextContent.of("AI stands for Artificial Intelligence.")) + .build(); + Input input = Input.fromTurns(Arrays.asList(userTurn, modelTurn)); + + // Act + String json = input.toJson(); + + // Assert - should serialize as array with proper Turn structure + assertTrue(json.startsWith("[")); + assertTrue(json.contains("\"role\":\"user\"")); + assertTrue(json.contains("\"role\":\"model\"")); + assertTrue(json.contains("\"What is AI?\"")); + assertTrue(json.contains("\"AI stands for Artificial Intelligence.\"")); + } + + @Test + public void testSerializeTurnListFromVarargs() { + // Arrange + Turn turn1 = Turn.builder() + .role("user") + .content(TextContent.of("First turn")) + .build(); + Turn turn2 = Turn.builder() + .role("model") + .content(TextContent.of("Second turn")) + .build(); + Input input = Input.fromTurns(turn1, turn2); + + // Act + String json = input.toJson(); + + // Assert + assertTrue(json.startsWith("[")); + assertTrue(json.contains("\"First turn\"")); + assertTrue(json.contains("\"Second turn\"")); + } + + @Test + public void testSerializeTurnWithMultipleContent() { + // Arrange + Turn turn = Turn.builder() + .role("user") + .content( + TextContent.of("Check this image"), + ImageContent.fromUri("gs://bucket/photo.png", "image/png")) + .build(); + Input input = Input.fromTurns(turn); + + // Act + String json = input.toJson(); + + // Assert - should serialize turn with multiple content items + assertTrue(json.contains("\"type\":\"text\"")); + assertTrue(json.contains("\"type\":\"image\"")); + assertTrue(json.contains("\"Check this image\"")); + assertTrue(json.contains("\"gs://bucket/photo.png\"")); + } + + @Test + public void testSerializeEmptyTurnList() { + // Arrange + Input input = Input.fromTurns(Arrays.asList()); + + // Act + String json = input.toJson(); + + // Assert - should serialize as empty array + assertEquals("[]", json); + } + + // ========== Round-trip Serialization Tests ========== + + @Test + public void testRoundTripStringInput() { + // Arrange + String originalText = "Hello, world!"; + Input input = Input.fromString(originalText); + + // Act + String json = input.toJson(); + + // Assert - verify it's a valid JSON string + assertTrue(json.startsWith("\"")); + assertTrue(json.endsWith("\"")); + assertTrue(json.contains("Hello, world!")); + } + + @Test + public void testRoundTripContentListInput() { + // Arrange + TextContent text1 = TextContent.of("Message 1"); + TextContent text2 = TextContent.of("Message 2"); + Input input = Input.fromContents(text1, text2); + + // Act + String json = input.toJson(); + + // Assert - verify array structure with type info + assertTrue(json.startsWith("[")); + assertTrue(json.endsWith("]")); + // Count occurrences of "type":"text" (should be 2) + int typeCount = json.split("\"type\":\"text\"", -1).length - 1; + assertEquals(2, typeCount); + } + + @Test + public void testRoundTripTurnListInput() { + // Arrange + Turn turn = Turn.builder() + .role("user") + .content(TextContent.of("Test message")) + .build(); + Input input = Input.fromTurns(turn); + + // Act + String json = input.toJson(); + + // Assert - verify turn serialization preserves structure + assertTrue(json.contains("\"role\"")); + assertTrue(json.contains("\"content\"")); + assertTrue(json.contains("\"type\":\"text\"")); + } + + // ========== Edge Cases ========== + + @Test + public void testSerializeSingleContentInList() { + // Arrange - single content in a list + TextContent text = TextContent.of("Single item"); + Input input = Input.fromContents(Arrays.asList(text)); + + // Act + String json = input.toJson(); + + // Assert - should still serialize as array (not unwrapped) + assertTrue(json.startsWith("[")); + assertTrue(json.endsWith("]")); + assertTrue(json.contains("\"type\":\"text\"")); + } + + @Test + public void testSerializeComplexContent() { + // Arrange - content with annotations + Annotation annotation = Annotation.of(0, 10, "https://example.com"); + TextContent text = TextContent.builder() + .text("Source text") + .annotations(annotation) + .build(); + Input input = Input.fromContent(text); + + // Act + String json = input.toJson(); + + // Assert - should include all nested fields + assertTrue(json.contains("\"type\":\"text\"")); + assertTrue(json.contains("\"text\":\"Source text\"")); + assertTrue(json.contains("\"annotations\"")); + assertTrue(json.contains("\"source\":\"https://example.com\"")); + } + + @Test + public void testSerializeMixedContentTypes() { + // Arrange - various content types in one list + TextContent text = TextContent.of("Description"); + ImageContent image = ImageContent.fromData("base64data", "image/png"); + Input input = Input.fromContents(text, image); + + // Act + String json = input.toJson(); + + // Assert - each content type should have correct discriminator + assertTrue(json.contains("\"type\":\"text\"")); + assertTrue(json.contains("\"type\":\"image\"")); + assertTrue(json.contains("\"data\":\"base64data\"")); + } +} diff --git a/src/test/java/com/google/genai/types/interactions/InteractionTest.java b/src/test/java/com/google/genai/types/interactions/InteractionTest.java new file mode 100644 index 00000000000..052d2209b38 --- /dev/null +++ b/src/test/java/com/google/genai/types/interactions/InteractionTest.java @@ -0,0 +1,483 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.genai.types.interactions.GenerationConfig; +import com.google.genai.types.interactions.content.Content; +import com.google.genai.types.interactions.content.TextContent; +import com.google.genai.types.interactions.content.ThoughtContent; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** Tests for Interaction type and related classes. */ +public class InteractionTest { + + @Test + public void testInteractionBuilder() { + // Arrange & Act + Interaction interaction = + Interaction.builder() + .id("test-id") + .status(InteractionStatus.COMPLETED) + .model("gemini-2.5-flash") + .role("user") + .build(); + + // Assert + assertEquals("test-id", interaction.id()); + assertEquals(InteractionStatus.COMPLETED, interaction.status()); + assertTrue(interaction.model().isPresent()); + assertEquals("gemini-2.5-flash", interaction.model().get()); + assertTrue(interaction.role().isPresent()); + assertEquals("user", interaction.role().get()); + } + + @Test + public void testInteractionWithOutputs() { + // Arrange + TextContent textContent = TextContent.builder().text("Paris").build(); + + // Act + Interaction interaction = + Interaction.builder() + .id("test-id") + .status(InteractionStatus.COMPLETED) + .outputs(textContent) + .build(); + + // Assert + assertTrue(interaction.outputs().isPresent()); + assertEquals(1, interaction.outputs().get().size()); + assertTrue(interaction.outputs().get().get(0) instanceof TextContent); + } + + @Test + public void testInteractionWithTimestamps() { + // Arrange + Instant now = Instant.now(); + + // Act + Interaction interaction = + Interaction.builder() + .id("test-id") + .status(InteractionStatus.COMPLETED) + .created(now) + .updated(now) + .build(); + + // Assert + assertTrue(interaction.created().isPresent()); + assertEquals(now, interaction.created().get()); + assertTrue(interaction.updated().isPresent()); + assertEquals(now, interaction.updated().get()); + } + + @Test + public void testInteractionWithUsage() { + // Arrange + Usage usage = + Usage.builder() + .totalInputTokens(10) + .totalOutputTokens(20) + .totalTokens(30) + .build(); + + // Act + Interaction interaction = + Interaction.builder() + .id("test-id") + .status(InteractionStatus.COMPLETED) + .usage(usage) + .build(); + + // Assert + assertTrue(interaction.usage().isPresent()); + assertEquals(10, interaction.usage().get().totalInputTokens().get()); + assertEquals(20, interaction.usage().get().totalOutputTokens().get()); + assertEquals(30, interaction.usage().get().totalTokens().get()); + } + + @Test + public void testInteractionStatus() { + // Test all status values + assertEquals("COMPLETED", InteractionStatus.COMPLETED.toString()); + assertEquals("IN_PROGRESS", InteractionStatus.IN_PROGRESS.toString()); + assertEquals("FAILED", InteractionStatus.FAILED.toString()); + } + + @Test + public void testTextContent() { + // Act + TextContent content = TextContent.builder().text("Hello, world!").build(); + + // Assert + assertTrue(content instanceof TextContent); + assertTrue(content.text().isPresent()); + assertEquals("Hello, world!", content.text().get()); + } + + @Test + public void testThoughtContent() { + // Act + ThoughtContent content = ThoughtContent.builder().signature("thought-sig-123").build(); + + // Assert + assertTrue(content instanceof ThoughtContent); + assertTrue(content.signature().isPresent()); + assertEquals("thought-sig-123", content.signature().get()); + } + + @Test + public void testCreateInteractionConfig() { + // Act + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("What is the capital of France?") + .build(); + + // Assert + assertTrue(config.model().isPresent()); + assertEquals("gemini-2.5-flash", config.model().get()); + assertNotNull(config.input()); + } + + @Test + public void testCreateInteractionConfigWithAgent() { + // Act + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .agent("deep-research-pro-preview-12-2025") + .input("Research quantum computing") + .build(); + + // Assert + assertTrue(config.agent().isPresent()); + assertEquals("deep-research-pro-preview-12-2025", config.agent().get()); + assertFalse(config.model().isPresent()); + assertNotNull(config.input()); + } + + @Test + public void testCreateInteractionConfigWithGenerationConfig() { + // Arrange + GenerationConfig genConfig = GenerationConfig.builder().temperature(0.7f).topP(0.9f).build(); + + // Act + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("Test") + .generationConfig(genConfig) + .build(); + + // Assert + assertTrue(config.generationConfig().isPresent()); + assertEquals(0.7f, config.generationConfig().get().temperature().get()); + assertEquals(0.9f, config.generationConfig().get().topP().get()); + } + + @Test + public void testCreateInteractionConfig_InputIsRequired() { + // Arrange & Act + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("What is AI?") + .build(); + + // Assert - input() returns Input directly, not Optional + Input input = config.input(); // No Optional unwrapping needed + assertNotNull(input); + assertEquals("What is AI?", input.getValue()); + } + + @Test + public void testCreateInteractionConfig_InputFromString() { + // Arrange & Act + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("Hello, world!") + .build(); + + // Assert + assertNotNull(config.input()); + assertEquals("Hello, world!", config.input().getValue()); + } + + @Test + public void testCreateInteractionConfig_InputFromContentsList() { + // Arrange + List contents = + Arrays.asList( + TextContent.builder().text("First part").build(), + TextContent.builder().text("Second part").build()); + + // Act + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromContents(contents) + .build(); + + // Assert + assertNotNull(config.input()); + assertTrue(config.input().getValue() instanceof List); + @SuppressWarnings("unchecked") + List resultContents = (List) config.input().getValue(); + assertEquals(2, resultContents.size()); + } + + @Test + public void testCreateInteractionConfig_InputFromContentsVarargs() { + // Arrange + TextContent content1 = TextContent.builder().text("Part 1").build(); + TextContent content2 = TextContent.builder().text("Part 2").build(); + + // Act + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromContents(content1, content2) + .build(); + + // Assert + assertNotNull(config.input()); + assertTrue(config.input().getValue() instanceof List); + @SuppressWarnings("unchecked") + List resultContents = (List) config.input().getValue(); + assertEquals(2, resultContents.size()); + } + + @Test + public void testCreateInteractionConfig_InputFromTurnsList() { + // Arrange + List turns = + Arrays.asList( + Turn.builder() + .role("user") + .content(TextContent.builder().text("Hello").build()) + .build(), + Turn.builder() + .role("model") + .content(TextContent.builder().text("Hi there").build()) + .build()); + + // Act + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromTurns(turns) + .build(); + + // Assert + assertNotNull(config.input()); + assertTrue(config.input().getValue() instanceof List); + @SuppressWarnings("unchecked") + List resultTurns = (List) config.input().getValue(); + assertEquals(2, resultTurns.size()); + } + + @Test + public void testCreateInteractionConfig_InputFromTurnsVarargs() { + // Arrange + Turn turn1 = + Turn.builder() + .role("user") + .content(TextContent.builder().text("Question").build()) + .build(); + Turn turn2 = + Turn.builder() + .role("model") + .content(TextContent.builder().text("Answer").build()) + .build(); + + // Act + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .inputFromTurns(turn1, turn2) + .build(); + + // Assert + assertNotNull(config.input()); + assertTrue(config.input().getValue() instanceof List); + @SuppressWarnings("unchecked") + List resultTurns = (List) config.input().getValue(); + assertEquals(2, resultTurns.size()); + } + + @Test + public void testCreateInteractionConfig_InputDirectAccess() { + // Arrange & Act + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .model("gemini-2.5-flash") + .input("Direct access test") + .build(); + + // Assert - No Optional unwrapping needed + // OLD way (when it was Optional): config.input().get().getValue() + // NEW way (required Input): config.input().getValue() + String inputValue = (String) config.input().getValue(); + assertEquals("Direct access test", inputValue); + } + + @Test + public void testCreateInteractionConfig_InputWithAgent() { + // Arrange & Act + CreateInteractionConfig config = + CreateInteractionConfig.builder() + .agent("deep-research-pro-preview-12-2025") + .input("Research quantum computing") + .build(); + + // Assert + assertNotNull(config.input()); + assertEquals("Research quantum computing", config.input().getValue()); + assertTrue(config.agent().isPresent()); + assertFalse(config.model().isPresent()); + } + + @Test + public void testGetInteractionConfig() { + // Act + GetInteractionConfig config = GetInteractionConfig.builder().build(); + + // Assert + assertNotNull(config); + } + + @Test + public void testCancelInteractionConfig() { + // Act + CancelInteractionConfig config = CancelInteractionConfig.builder().build(); + + // Assert + assertNotNull(config); + } + + @Test + public void testDeleteInteractionConfig() { + // Act + DeleteInteractionConfig config = DeleteInteractionConfig.builder().build(); + + // Assert + assertNotNull(config); + } + + @Test + public void testDeleteInteraction() { + // Act + DeleteInteractionResponse response = DeleteInteractionResponse.builder().build(); + + // Assert + assertNotNull(response); + } + + @Test + public void testInteractionToBuilder() { + // Arrange + Interaction original = + Interaction.builder() + .id("test-id") + .status(InteractionStatus.COMPLETED) + .model("gemini-2.5-flash") + .build(); + + // Act + Interaction modified = original.toBuilder().role("assistant").build(); + + // Assert + assertEquals("test-id", modified.id()); + assertEquals(InteractionStatus.COMPLETED, modified.status()); + assertEquals("gemini-2.5-flash", modified.model().get()); + assertTrue(modified.role().isPresent()); + assertEquals("assistant", modified.role().get()); + } + + @Test + public void testInteractionClearMethods() { + // Arrange + Interaction.Builder builder = + Interaction.builder().id("test-id").status(InteractionStatus.COMPLETED).model("test-model"); + + // Act - Note: id() and status() are now required fields, so clearId() and clearStatus() no longer exist + Interaction interaction = builder.clearModel().build(); + + // Assert + assertEquals("test-id", interaction.id()); + assertEquals(InteractionStatus.COMPLETED, interaction.status()); + assertFalse(interaction.model().isPresent()); + } + + @Test + public void testInteractionInputSerializationWithMultipleContents() { + // Arrange - create multiple TextContent objects + TextContent content1 = TextContent.builder().text("Hello").build(); + TextContent content2 = TextContent.builder().text("World").build(); + Input input = Input.fromContents(content1, content2); + + // Act - serialize to JSON + String json = input.toJson(); + + // Assert - verify the "type" property is included + assertTrue(json.contains("\"type\":\"text\""), "JSON should contain type discriminator"); + assertTrue(json.contains("\"text\":\"Hello\""), "JSON should contain first text"); + assertTrue(json.contains("\"text\":\"World\""), "JSON should contain second text"); + // Expected format: [{"type":"text","text":"Hello"},{"type":"text","text":"World"}] + assertTrue(json.startsWith("["), "JSON should be an array"); + assertTrue(json.endsWith("]"), "JSON should end with array bracket"); + } + + @Test + public void testInteractionInputSerializationWithSingleContent() { + // Arrange - create a single TextContent object + TextContent content = TextContent.builder().text("Hello").build(); + Input input = Input.fromContent(content); + + // Act - serialize to JSON + String json = input.toJson(); + + // Assert - verify the "type" property is included + assertTrue(json.contains("\"type\":\"text\""), "JSON should contain type discriminator"); + assertTrue(json.contains("\"text\":\"Hello\""), "JSON should contain text"); + // Expected format: {"type":"text","text":"Hello"} + assertTrue(json.startsWith("{"), "JSON should be an object"); + assertTrue(json.endsWith("}"), "JSON should end with object bracket"); + } + + @Test + public void testInteractionInputSerializationWithString() { + // Arrange + Input input = Input.fromString("Hello, world!"); + + // Act - serialize to JSON + String json = input.toJson(); + + // Assert + assertEquals("\"Hello, world!\"", json); + } +} diff --git a/src/test/java/com/google/genai/types/interactions/InteractionTypesTest.java b/src/test/java/com/google/genai/types/interactions/InteractionTypesTest.java new file mode 100644 index 00000000000..18e82361cd8 --- /dev/null +++ b/src/test/java/com/google/genai/types/interactions/InteractionTypesTest.java @@ -0,0 +1,1004 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.genai.types.interactions.content.FileSearchResultContent; +import com.google.genai.types.interactions.content.FunctionResultContent; +import com.google.genai.types.interactions.content.ImageContent; +import com.google.genai.types.interactions.content.McpServerToolCallContent; +import com.google.genai.types.interactions.content.McpServerToolResultContent; +import com.google.genai.types.interactions.content.TextContent; +import com.google.genai.types.interactions.content.ThoughtContent; +import com.google.genai.types.interactions.content.ThoughtSummaryContent; +import com.google.genai.types.interactions.tools.FileSearch; +import com.google.genai.types.interactions.tools.McpServer; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +/** + * Tests for Interactions API types that were updated to match OpenAPI spec. + */ +public class InteractionTypesTest { + + // ========== FileSearchResultContent Tests ========== + + @Test + public void testFileSearchResultContentWithListResult() { + // Arrange + FileSearchResult result1 = FileSearchResult.builder() + .text("First result text") + .title("First Title") + .fileSearchStore("store-1") + .build(); + FileSearchResult result2 = FileSearchResult.builder() + .text("Second result text") + .title("Second Title") + .fileSearchStore("store-2") + .build(); + List results = Arrays.asList(result1, result2); + + // Act + FileSearchResultContent content = FileSearchResultContent.builder() + .result(results) + .build(); + + // Assert + assertTrue(content.result().isPresent()); + assertEquals(2, content.result().get().size()); + assertEquals("First result text", content.result().get().get(0).text().get()); + assertEquals("Second Title", content.result().get().get(1).title().get()); + } + + @Test + public void testFileSearchResultContentJsonSerialization() { + // Arrange + FileSearchResult result = FileSearchResult.builder() + .text("Test text") + .title("Test Title") + .fileSearchStore("my-store") + .build(); + FileSearchResultContent content = FileSearchResultContent.builder() + .result(Arrays.asList(result)) + .build(); + + // Act + String json = content.toJson(); + + // Assert + assertTrue(json.contains("\"type\":\"file_search_result\"")); + assertTrue(json.contains("\"result\":[")); + assertTrue(json.contains("\"text\":\"Test text\"")); + assertTrue(json.contains("\"title\":\"Test Title\"")); + assertTrue(json.contains("\"file_search_store\":\"my-store\"")); + } + + @Test + public void testFileSearchResultContentJsonDeserialization() { + // Arrange + String json = "{\"type\":\"file_search_result\",\"result\":[{\"text\":\"Deserialized text\",\"title\":\"Deserialized Title\",\"file_search_store\":\"deserialized-store\"}]}"; + + // Act + FileSearchResultContent content = FileSearchResultContent.fromJson(json); + + // Assert + assertTrue(content.result().isPresent()); + assertEquals(1, content.result().get().size()); + assertEquals("Deserialized text", content.result().get().get(0).text().get()); + assertEquals("Deserialized Title", content.result().get().get(0).title().get()); + assertEquals("deserialized-store", content.result().get().get(0).fileSearchStore().get()); + } + + // ========== McpServerToolCallContent Tests ========== + + @Test + public void testMcpServerToolCallContentRequiredFields() { + // Arrange + Map arguments = new HashMap<>(); + arguments.put("param1", "value1"); + arguments.put("param2", 42); + + // Act + McpServerToolCallContent content = McpServerToolCallContent.builder() + .id("call-123") + .name("my_tool") + .serverName("my-server") + .arguments(arguments) + .build(); + + // Assert - all fields are required (non-Optional) + assertEquals("call-123", content.id()); + assertEquals("my_tool", content.name()); + assertEquals("my-server", content.serverName()); + assertEquals(2, content.arguments().size()); + assertEquals("value1", content.arguments().get("param1")); + assertEquals(42, content.arguments().get("param2")); + } + + @Test + public void testMcpServerToolCallContentMissingRequiredFieldThrows() { + // Arrange + Map arguments = new HashMap<>(); + arguments.put("param1", "value1"); + + // Act & Assert - missing id should throw + assertThrows(IllegalStateException.class, () -> { + McpServerToolCallContent.builder() + .name("my_tool") + .serverName("my-server") + .arguments(arguments) + .build(); + }); + } + + @Test + public void testMcpServerToolCallContentJsonSerialization() { + // Arrange + Map arguments = new HashMap<>(); + arguments.put("query", "test query"); + McpServerToolCallContent content = McpServerToolCallContent.builder() + .id("call-456") + .name("search_tool") + .serverName("search-server") + .arguments(arguments) + .build(); + + // Act + String json = content.toJson(); + + // Assert + assertTrue(json.contains("\"type\":\"mcp_server_tool_call\"")); + assertTrue(json.contains("\"id\":\"call-456\"")); + assertTrue(json.contains("\"name\":\"search_tool\"")); + assertTrue(json.contains("\"server_name\":\"search-server\"")); + assertTrue(json.contains("\"arguments\":{")); + assertTrue(json.contains("\"query\":\"test query\"")); + } + + @Test + public void testMcpServerToolCallContentJsonDeserialization() { + // Arrange + String json = "{\"type\":\"mcp_server_tool_call\",\"id\":\"call-789\",\"name\":\"calc_tool\",\"server_name\":\"calc-server\",\"arguments\":{\"x\":10,\"y\":20}}"; + + // Act + McpServerToolCallContent content = McpServerToolCallContent.fromJson(json); + + // Assert + assertEquals("call-789", content.id()); + assertEquals("calc_tool", content.name()); + assertEquals("calc-server", content.serverName()); + assertEquals(10, content.arguments().get("x")); + assertEquals(20, content.arguments().get("y")); + } + + @Test + public void testMcpServerToolCallContentOfFactory() { + // Arrange + Map arguments = new HashMap<>(); + arguments.put("key", "value"); + + // Act + McpServerToolCallContent content = McpServerToolCallContent.of( + "id-001", "tool_name", "server_name", arguments); + + // Assert + assertEquals("id-001", content.id()); + assertEquals("tool_name", content.name()); + assertEquals("server_name", content.serverName()); + assertEquals("value", content.arguments().get("key")); + } + + // ========== McpServerToolResultContent Tests ========== + + @Test + public void testMcpServerToolResultContentRequiredFields() { + // Arrange + Map result = new HashMap<>(); + result.put("output", "success"); + + // Act + McpServerToolResultContent content = McpServerToolResultContent.builder() + .callId("call-123") + .result(result) + .build(); + + // Assert - callId and result are required (non-Optional) + assertEquals("call-123", content.callId()); + assertNotNull(content.result()); + } + + @Test + public void testMcpServerToolResultContentMissingCallIdThrows() { + // Arrange + Map result = new HashMap<>(); + result.put("output", "success"); + + // Act & Assert - missing callId should throw + assertThrows(IllegalStateException.class, () -> { + McpServerToolResultContent.builder() + .result(result) + .build(); + }); + } + + @Test + public void testMcpServerToolResultContentMissingResultThrows() { + // Act & Assert - missing result should throw + assertThrows(IllegalStateException.class, () -> { + McpServerToolResultContent.builder() + .callId("call-123") + .build(); + }); + } + + @Test + public void testMcpServerToolResultContentWithOptionalFields() { + // Arrange + String result = "tool output"; + + // Act + McpServerToolResultContent content = McpServerToolResultContent.builder() + .callId("call-456") + .result(result) + .name("my_tool") + .serverName("my-server") + .build(); + + // Assert + assertEquals("call-456", content.callId()); + assertEquals("tool output", content.result()); + assertTrue(content.name().isPresent()); + assertEquals("my_tool", content.name().get()); + assertTrue(content.serverName().isPresent()); + assertEquals("my-server", content.serverName().get()); + } + + @Test + public void testMcpServerToolResultContentJsonSerialization() { + // Arrange + McpServerToolResultContent content = McpServerToolResultContent.builder() + .callId("call-789") + .result("result data") + .name("tool_name") + .serverName("server_name") + .build(); + + // Act + String json = content.toJson(); + + // Assert + assertTrue(json.contains("\"type\":\"mcp_server_tool_result\"")); + assertTrue(json.contains("\"call_id\":\"call-789\"")); + assertTrue(json.contains("\"result\":\"result data\"")); + assertTrue(json.contains("\"name\":\"tool_name\"")); + assertTrue(json.contains("\"server_name\":\"server_name\"")); + } + + @Test + public void testMcpServerToolResultContentJsonDeserialization() { + // Arrange + String json = "{\"type\":\"mcp_server_tool_result\",\"call_id\":\"call-abc\",\"result\":{\"value\":42},\"name\":\"test_tool\",\"server_name\":\"test_server\"}"; + + // Act + McpServerToolResultContent content = McpServerToolResultContent.fromJson(json); + + // Assert + assertEquals("call-abc", content.callId()); + assertNotNull(content.result()); + assertTrue(content.name().isPresent()); + assertEquals("test_tool", content.name().get()); + assertTrue(content.serverName().isPresent()); + assertEquals("test_server", content.serverName().get()); + } + + @Test + public void testMcpServerToolResultContentOfFactory() { + // Act + McpServerToolResultContent content = McpServerToolResultContent.of("call-id", "result-value"); + + // Assert + assertEquals("call-id", content.callId()); + assertEquals("result-value", content.result()); + } + + @Test + public void testMcpServerToolResultContentOfFactoryWithAllFields() { + // Act + McpServerToolResultContent content = McpServerToolResultContent.of( + "call-id", "result-value", "tool-name", "server-name"); + + // Assert + assertEquals("call-id", content.callId()); + assertEquals("result-value", content.result()); + assertEquals("tool-name", content.name().get()); + assertEquals("server-name", content.serverName().get()); + } + + // ========== FileSearch Tests ========== + + @Test + public void testFileSearchMetadataFilterIsString() { + // Act + FileSearch tool = FileSearch.builder() + .fileSearchStoreNames("store-1", "store-2") + .topK(10) + .metadataFilter("category = 'documents' AND year > 2020") + .build(); + + // Assert + assertTrue(tool.fileSearchStoreNames().isPresent()); + assertEquals(2, tool.fileSearchStoreNames().get().size()); + assertTrue(tool.topK().isPresent()); + assertEquals(10, tool.topK().get()); + assertTrue(tool.metadataFilter().isPresent()); + assertEquals("category = 'documents' AND year > 2020", tool.metadataFilter().get()); + } + + @Test + public void testFileSearchJsonSerializationSnakeCase() { + // Arrange + FileSearch tool = FileSearch.builder() + .fileSearchStoreNames("my-store") + .topK(5) + .metadataFilter("status = 'active'") + .build(); + + // Act + String json = tool.toJson(); + + // Assert - verify snake_case JSON property names + assertTrue(json.contains("\"type\":\"file_search\"")); + assertTrue(json.contains("\"file_search_store_names\":[\"my-store\"]")); + assertTrue(json.contains("\"top_k\":5")); + assertTrue(json.contains("\"metadata_filter\":\"status = 'active'\"")); + // Should NOT contain camelCase + assertTrue(!json.contains("\"fileSearchStoreNames\"")); + assertTrue(!json.contains("\"topK\"")); + assertTrue(!json.contains("\"metadataFilter\"")); + } + + @Test + public void testFileSearchJsonDeserialization() { + // Arrange - JSON with snake_case property names + String json = "{\"type\":\"file_search\",\"file_search_store_names\":[\"store-a\",\"store-b\"],\"top_k\":15,\"metadata_filter\":\"type = 'pdf'\"}"; + + // Act + FileSearch tool = FileSearch.fromJson(json); + + // Assert + assertTrue(tool.fileSearchStoreNames().isPresent()); + assertEquals(2, tool.fileSearchStoreNames().get().size()); + assertEquals("store-a", tool.fileSearchStoreNames().get().get(0)); + assertEquals("store-b", tool.fileSearchStoreNames().get().get(1)); + assertTrue(tool.topK().isPresent()); + assertEquals(15, tool.topK().get()); + assertTrue(tool.metadataFilter().isPresent()); + assertEquals("type = 'pdf'", tool.metadataFilter().get()); + } + + @Test + public void testFileSearchWithoutOptionalFields() { + // Act + FileSearch tool = FileSearch.builder().build(); + + // Assert - all fields are optional + assertTrue(!tool.fileSearchStoreNames().isPresent()); + assertTrue(!tool.topK().isPresent()); + assertTrue(!tool.metadataFilter().isPresent()); + } + + @Test + public void testFileSearchClearMethods() { + // Arrange + FileSearch.Builder builder = FileSearch.builder() + .fileSearchStoreNames("store-1") + .topK(10) + .metadataFilter("filter"); + + // Act + FileSearch tool = builder + .clearFileSearchStoreNames() + .clearTopK() + .clearMetadataFilter() + .build(); + + // Assert + assertTrue(!tool.fileSearchStoreNames().isPresent()); + assertTrue(!tool.topK().isPresent()); + assertTrue(!tool.metadataFilter().isPresent()); + } + + // ========== ResultItems Tests ========== + + @Test + public void testResultItemsBuilder() { + // Arrange + List items = Arrays.asList("text item", 42, new HashMap()); + + // Act + ResultItems resultItems = ResultItems.builder() + .items(items) + .build(); + + // Assert + assertTrue(resultItems.items().isPresent()); + assertEquals(3, resultItems.items().get().size()); + assertEquals("text item", resultItems.items().get().get(0)); + assertEquals(42, resultItems.items().get().get(1)); + } + + @Test + public void testResultItemsVarargs() { + // Act + ResultItems resultItems = ResultItems.of("item1", "item2", "item3"); + + // Assert + assertTrue(resultItems.items().isPresent()); + assertEquals(3, resultItems.items().get().size()); + } + + @Test + public void testResultItemsJsonSerialization() { + // Arrange + ResultItems resultItems = ResultItems.of("hello", "world"); + + // Act + String json = resultItems.toJson(); + + // Assert + assertTrue(json.contains("\"items\":[\"hello\",\"world\"]")); + } + + @Test + public void testResultItemsJsonDeserialization() { + // Arrange + String json = "{\"items\":[\"a\",\"b\",\"c\"]}"; + + // Act + ResultItems resultItems = ResultItems.fromJson(json); + + // Assert + assertTrue(resultItems.items().isPresent()); + assertEquals(3, resultItems.items().get().size()); + assertEquals("a", resultItems.items().get().get(0)); + assertEquals("b", resultItems.items().get().get(1)); + assertEquals("c", resultItems.items().get().get(2)); + } + + // ========== FunctionResultContent Tests ========== + + @Test + public void testFunctionResultContentWithStringResult() { + // Act + FunctionResultContent content = FunctionResultContent.of("call-1", "my_function", "success"); + + // Assert + assertEquals("call-1", content.id()); + assertEquals("my_function", content.name().get()); + assertEquals("success", content.result()); + assertTrue(content.resultAsString().isPresent()); + assertEquals("success", content.resultAsString().get()); + assertTrue(!content.resultAsMap().isPresent()); + } + + @Test + public void testFunctionResultContentWithMapResult() { + // Arrange + Map result = new HashMap<>(); + result.put("key", "value"); + result.put("count", 42); + + // Act + FunctionResultContent content = FunctionResultContent.of("call-2", "my_function", result); + + // Assert + assertEquals("call-2", content.id()); + assertTrue(content.resultAsMap().isPresent()); + assertEquals("value", content.resultAsMap().get().get("key")); + assertEquals(42, content.resultAsMap().get().get("count")); + assertTrue(!content.resultAsString().isPresent()); + } + + @Test + public void testFunctionResultContentWithResultItems() { + // Arrange + ResultItems resultItems = ResultItems.of("item1", "item2"); + + // Act + FunctionResultContent content = FunctionResultContent.of("call-3", "my_function", resultItems); + + // Assert + assertEquals("call-3", content.id()); + assertEquals(resultItems, content.result()); + assertTrue(content.resultAsResultItems().isPresent()); + assertEquals(2, content.resultAsResultItems().get().items().get().size()); + } + + @Test + public void testFunctionResultContentResultAsResultItemsFromMap() { + // Test that resultAsResultItems() works when Jackson deserializes to Map with "items" key + // Arrange + Map mapWithItems = new HashMap<>(); + mapWithItems.put("items", Arrays.asList("a", "b", "c")); + + FunctionResultContent content = FunctionResultContent.builder() + .id("call-4") + .result(mapWithItems) + .build(); + + // Act + assertTrue(content.resultAsResultItems().isPresent()); + ResultItems items = content.resultAsResultItems().get(); + + // Assert + assertEquals(3, items.items().get().size()); + assertEquals("a", items.items().get().get(0)); + } + + @Test + public void testFunctionResultContentJsonSerializationWithString() { + // Arrange + FunctionResultContent content = FunctionResultContent.of("call-5", "test_func", "string result"); + + // Act + String json = content.toJson(); + + // Assert + assertTrue(json.contains("\"type\":\"function_result\"")); + assertTrue(json.contains("\"call_id\":\"call-5\"")); + assertTrue(json.contains("\"name\":\"test_func\"")); + assertTrue(json.contains("\"result\":\"string result\"")); + } + + @Test + public void testFunctionResultContentJsonDeserializationWithString() { + // Arrange + String json = "{\"type\":\"function_result\",\"call_id\":\"call-6\",\"name\":\"func\",\"result\":\"string value\"}"; + + // Act + FunctionResultContent content = FunctionResultContent.fromJson(json); + + // Assert + assertEquals("call-6", content.id()); + assertEquals("func", content.name().get()); + assertTrue(content.resultAsString().isPresent()); + assertEquals("string value", content.resultAsString().get()); + } + + @Test + public void testFunctionResultContentJsonDeserializationWithResultItems() { + // Arrange + String json = "{\"type\":\"function_result\",\"call_id\":\"call-7\",\"name\":\"func\",\"result\":{\"items\":[\"x\",\"y\",\"z\"]}}"; + + // Act + FunctionResultContent content = FunctionResultContent.fromJson(json); + + // Assert + assertEquals("call-7", content.id()); + assertTrue(content.resultAsResultItems().isPresent()); + assertEquals(3, content.resultAsResultItems().get().items().get().size()); + } + + @Test + public void testFunctionResultContentSuccessFactory() { + // Act + FunctionResultContent content = FunctionResultContent.ofSuccess("call-8", "func", "ok"); + + // Assert + assertEquals("call-8", content.id()); + assertTrue(content.isError().isPresent()); + assertEquals(false, content.isError().get()); + } + + @Test + public void testFunctionResultContentErrorFactory() { + // Act + FunctionResultContent content = FunctionResultContent.ofError("call-9", "func", "error message"); + + // Assert + assertEquals("call-9", content.id()); + assertTrue(content.isError().isPresent()); + assertEquals(true, content.isError().get()); + } + + // ========== McpServerToolResultContent Result Type Tests ========== + + @Test + public void testMcpServerToolResultContentWithStringResultTypeSafe() { + // Act + McpServerToolResultContent content = McpServerToolResultContent.of("call-1", "string result"); + + // Assert + assertTrue(content.resultAsString().isPresent()); + assertEquals("string result", content.resultAsString().get()); + assertTrue(!content.resultAsMap().isPresent()); + } + + @Test + public void testMcpServerToolResultContentWithMapResultTypeSafe() { + // Arrange + Map result = new HashMap<>(); + result.put("status", "ok"); + + // Act + McpServerToolResultContent content = McpServerToolResultContent.of("call-2", result); + + // Assert + assertTrue(content.resultAsMap().isPresent()); + assertEquals("ok", content.resultAsMap().get().get("status")); + assertTrue(!content.resultAsString().isPresent()); + } + + @Test + public void testMcpServerToolResultContentWithResultItems() { + // Arrange + ResultItems resultItems = ResultItems.of("a", "b", "c"); + + // Act + McpServerToolResultContent content = McpServerToolResultContent.of("call-3", resultItems); + + // Assert + assertEquals(resultItems, content.result()); + assertTrue(content.resultAsResultItems().isPresent()); + assertEquals(3, content.resultAsResultItems().get().items().get().size()); + } + + @Test + public void testMcpServerToolResultContentResultAsResultItemsFromMap() { + // Arrange - simulates Jackson deserializing {"items": [...]} as Map + Map mapWithItems = new HashMap<>(); + mapWithItems.put("items", Arrays.asList("x", "y")); + + McpServerToolResultContent content = McpServerToolResultContent.builder() + .callId("call-4") + .result(mapWithItems) + .build(); + + // Act + assertTrue(content.resultAsResultItems().isPresent()); + ResultItems items = content.resultAsResultItems().get(); + + // Assert + assertEquals(2, items.items().get().size()); + } + + @Test + public void testMcpServerToolResultContentJsonDeserializationWithResultItems() { + // Arrange + String json = "{\"type\":\"mcp_server_tool_result\",\"call_id\":\"call-5\",\"result\":{\"items\":[\"1\",\"2\",\"3\"]}}"; + + // Act + McpServerToolResultContent content = McpServerToolResultContent.fromJson(json); + + // Assert + assertTrue(content.resultAsResultItems().isPresent()); + assertEquals(3, content.resultAsResultItems().get().items().get().size()); + } + + // ========== AllowedTools Tests ========== + + @Test + public void testAllowedToolsBuilder() { + // Act + AllowedTools allowedTools = AllowedTools.builder() + .mode("auto") + .tools("tool1", "tool2", "tool3") + .build(); + + // Assert + assertTrue(allowedTools.mode().isPresent()); + assertEquals("auto", allowedTools.mode().get()); + assertTrue(allowedTools.tools().isPresent()); + assertEquals(3, allowedTools.tools().get().size()); + assertEquals("tool1", allowedTools.tools().get().get(0)); + } + + @Test + public void testAllowedToolsOfFactory() { + // Act + AllowedTools allowedTools = AllowedTools.of("any", "get_weather", "get_forecast"); + + // Assert + assertEquals("any", allowedTools.mode().get()); + assertEquals(2, allowedTools.tools().get().size()); + } + + @Test + public void testAllowedToolsJsonSerialization() { + // Arrange + AllowedTools allowedTools = AllowedTools.of("validated", "tool_a", "tool_b"); + + // Act + String json = allowedTools.toJson(); + + // Assert + assertTrue(json.contains("\"mode\":\"validated\"")); + assertTrue(json.contains("\"tools\":[\"tool_a\",\"tool_b\"]")); + } + + @Test + public void testAllowedToolsJsonDeserialization() { + // Arrange + String json = "{\"mode\":\"none\",\"tools\":[\"x\",\"y\",\"z\"]}"; + + // Act + AllowedTools allowedTools = AllowedTools.fromJson(json); + + // Assert + assertEquals("none", allowedTools.mode().get()); + assertEquals(3, allowedTools.tools().get().size()); + } + + // ========== McpServer Tests ========== + + @Test + public void testMcpServerWithAllowedToolsList() { + // Arrange + AllowedTools allowed1 = AllowedTools.of("auto", "get_weather", "get_forecast"); + AllowedTools allowed2 = AllowedTools.of("any", "search", "query"); + + // Act + McpServer tool = McpServer.builder() + .name("my-mcp-server") + .url("https://mcp.example.com") + .allowedTools(allowed1, allowed2) + .build(); + + // Assert + assertTrue(tool.name().isPresent()); + assertEquals("my-mcp-server", tool.name().get()); + assertTrue(tool.allowedTools().isPresent()); + assertEquals(2, tool.allowedTools().get().size()); + assertEquals("auto", tool.allowedTools().get().get(0).mode().get()); + assertEquals(2, tool.allowedTools().get().get(0).tools().get().size()); + } + + @Test + public void testMcpServerJsonSerializationSnakeCase() { + // Arrange + AllowedTools allowed = AllowedTools.of("auto", "my_tool"); + McpServer tool = McpServer.builder() + .name("test-server") + .url("https://example.com") + .allowedTools(allowed) + .build(); + + // Act + String json = tool.toJson(); + + // Assert - verify snake_case JSON property name + assertTrue(json.contains("\"type\":\"mcp_server\"")); + assertTrue(json.contains("\"allowed_tools\":[")); + assertTrue(json.contains("\"mode\":\"auto\"")); + assertTrue(json.contains("\"tools\":[\"my_tool\"]")); + // Should NOT contain camelCase + assertTrue(!json.contains("\"allowedTools\"")); + } + + @Test + public void testMcpServerJsonDeserialization() { + // Arrange - JSON with snake_case property names + String json = "{\"type\":\"mcp_server\",\"name\":\"deserialized-server\",\"url\":\"https://test.com\",\"allowed_tools\":[{\"mode\":\"any\",\"tools\":[\"a\",\"b\"]}]}"; + + // Act + McpServer tool = McpServer.fromJson(json); + + // Assert + assertTrue(tool.name().isPresent()); + assertEquals("deserialized-server", tool.name().get()); + assertTrue(tool.url().isPresent()); + assertEquals("https://test.com", tool.url().get()); + assertTrue(tool.allowedTools().isPresent()); + assertEquals(1, tool.allowedTools().get().size()); + assertEquals("any", tool.allowedTools().get().get(0).mode().get()); + assertEquals(2, tool.allowedTools().get().get(0).tools().get().size()); + } + + // ========== ThoughtContent Summary Type Restriction Tests ========== + + @Test + public void testThoughtContentSummaryWithTextContent() { + // Arrange + TextContent text1 = TextContent.builder().text("Summary point 1").build(); + TextContent text2 = TextContent.builder().text("Summary point 2").build(); + + // Act + ThoughtContent thought = ThoughtContent.builder() + .signature("sig123") + .summary(text1, text2) + .build(); + + // Assert + assertTrue(thought.summary().isPresent()); + assertEquals(2, thought.summary().get().size()); + assertTrue(thought.summary().get().get(0) instanceof TextContent); + assertEquals("Summary point 1", ((TextContent) thought.summary().get().get(0)).text().get()); + } + + @Test + public void testThoughtContentSummaryWithImageContent() { + // Arrange + ImageContent image = ImageContent.fromUri("gs://bucket/image.png", "image/png"); + + // Act + ThoughtContent thought = ThoughtContent.builder() + .summary(image) + .build(); + + // Assert + assertTrue(thought.summary().isPresent()); + assertEquals(1, thought.summary().get().size()); + assertTrue(thought.summary().get().get(0) instanceof ImageContent); + } + + @Test + public void testThoughtContentSummaryWithMixedContent() { + // Arrange - mixing TextContent and ImageContent is allowed + TextContent text = TextContent.builder().text("Description of image").build(); + ImageContent image = ImageContent.fromUri("gs://bucket/diagram.png", "image/png"); + + // Act + ThoughtContent thought = ThoughtContent.of(text, image); + + // Assert + assertTrue(thought.summary().isPresent()); + assertEquals(2, thought.summary().get().size()); + assertTrue(thought.summary().get().get(0) instanceof TextContent); + assertTrue(thought.summary().get().get(1) instanceof ImageContent); + } + + @Test + public void testThoughtContentSummaryWithList() { + // Arrange + List summaryList = Arrays.asList( + TextContent.of("First item"), + TextContent.of("Second item"), + ImageContent.fromData("base64data", "image/jpeg") + ); + + // Act + ThoughtContent thought = ThoughtContent.of(summaryList); + + // Assert + assertTrue(thought.summary().isPresent()); + assertEquals(3, thought.summary().get().size()); + } + + @Test + public void testThoughtContentJsonSerializationWithSummary() { + // Arrange + ThoughtContent thought = ThoughtContent.builder() + .signature("test-signature") + .summary(TextContent.of("Summary text")) + .build(); + + // Act + String json = thought.toJson(); + + // Assert + assertTrue(json.contains("\"type\":\"thought\"")); + assertTrue(json.contains("\"signature\":\"test-signature\"")); + assertTrue(json.contains("\"summary\":[")); + assertTrue(json.contains("\"text\":\"Summary text\"")); + } + + @Test + public void testThoughtContentJsonDeserializationWithTextSummary() { + // Arrange + String json = "{\"type\":\"thought\",\"signature\":\"sig-abc\",\"summary\":[{\"type\":\"text\",\"text\":\"Deserialized summary\"}]}"; + + // Act + ThoughtContent thought = ThoughtContent.fromJson(json); + + // Assert + assertTrue(thought.signature().isPresent()); + assertEquals("sig-abc", thought.signature().get()); + assertTrue(thought.summary().isPresent()); + assertEquals(1, thought.summary().get().size()); + assertTrue(thought.summary().get().get(0) instanceof TextContent); + assertEquals("Deserialized summary", ((TextContent) thought.summary().get().get(0)).text().get()); + } + + @Test + public void testThoughtContentJsonDeserializationWithImageSummary() { + // Arrange + String json = "{\"type\":\"thought\",\"summary\":[{\"type\":\"image\",\"uri\":\"gs://bucket/img.png\",\"mime_type\":\"image/png\"}]}"; + + // Act + ThoughtContent thought = ThoughtContent.fromJson(json); + + // Assert + assertTrue(thought.summary().isPresent()); + assertEquals(1, thought.summary().get().size()); + assertTrue(thought.summary().get().get(0) instanceof ImageContent); + ImageContent image = (ImageContent) thought.summary().get().get(0); + assertEquals("gs://bucket/img.png", image.uri().get()); + assertEquals("image/png", image.mimeType().get().toString()); + } + + @Test + public void testThoughtContentJsonDeserializationWithMixedSummary() { + // Arrange + String json = "{\"type\":\"thought\",\"summary\":[{\"type\":\"text\",\"text\":\"Text part\"},{\"type\":\"image\",\"uri\":\"gs://bucket/photo.jpg\",\"mime_type\":\"image/jpeg\"}]}"; + + // Act + ThoughtContent thought = ThoughtContent.fromJson(json); + + // Assert + assertTrue(thought.summary().isPresent()); + assertEquals(2, thought.summary().get().size()); + assertTrue(thought.summary().get().get(0) instanceof TextContent); + assertTrue(thought.summary().get().get(1) instanceof ImageContent); + } + + @Test + public void testThoughtContentToBuilderPreservesSummary() { + // Arrange + ThoughtContent original = ThoughtContent.builder() + .signature("original-sig") + .summary(TextContent.of("Original summary")) + .build(); + + // Act + ThoughtContent modified = original.toBuilder() + .signature("modified-sig") + .build(); + + // Assert + assertEquals("modified-sig", modified.signature().get()); + assertTrue(modified.summary().isPresent()); + assertEquals(1, modified.summary().get().size()); + assertEquals("Original summary", ((TextContent) modified.summary().get().get(0)).text().get()); + } + + @Test + public void testThoughtContentClearSummary() { + // Arrange + ThoughtContent.Builder builder = ThoughtContent.builder() + .signature("sig") + .summary(TextContent.of("Will be cleared")); + + // Act + ThoughtContent thought = builder.clearSummary().build(); + + // Assert + assertTrue(!thought.summary().isPresent()); + } + + @Test + public void testTextContentImplementsThoughtSummaryContent() { + // Verify compile-time type safety + ThoughtSummaryContent content = TextContent.of("test"); + assertTrue(content instanceof TextContent); + assertTrue(content instanceof ThoughtSummaryContent); + } + + @Test + public void testImageContentImplementsThoughtSummaryContent() { + // Verify compile-time type safety + ThoughtSummaryContent content = ImageContent.fromUri("gs://bucket/test.png", "image/png"); + assertTrue(content instanceof ImageContent); + assertTrue(content instanceof ThoughtSummaryContent); + } +} diff --git a/src/test/java/com/google/genai/types/interactions/MimeTypesTest.java b/src/test/java/com/google/genai/types/interactions/MimeTypesTest.java new file mode 100644 index 00000000000..fe4ac564c02 --- /dev/null +++ b/src/test/java/com/google/genai/types/interactions/MimeTypesTest.java @@ -0,0 +1,260 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Tests for MIME type enums in the Interactions API. + * + *

Tests all four MIME type classes: AudioMimeType, DocumentMimeType, ImageMimeType, and + * VideoMimeType to ensure proper value validation and serialization. + */ +public class MimeTypesTest { + + // ========== AudioMimeType Tests ========== + + @Test + public void testAudioMimeTypeKnownValues() { + // Test all known audio MIME types + assertEquals("audio/aac", AudioMimeType.Known.AUDIO_AAC.toString()); + assertEquals("audio/flac", AudioMimeType.Known.AUDIO_FLAC.toString()); + assertEquals("audio/mp3", AudioMimeType.Known.AUDIO_MP3.toString()); + assertEquals("audio/wav", AudioMimeType.Known.AUDIO_WAV.toString()); + assertEquals("audio/aiff", AudioMimeType.Known.AUDIO_AIFF.toString()); + assertEquals("audio/ogg", AudioMimeType.Known.AUDIO_OGG.toString()); + } + + @Test + public void testAudioMimeTypeConstructor() { + // Test creating AudioMimeType from string value + AudioMimeType mimeType = new AudioMimeType("audio/mp3"); + assertEquals("audio/mp3", mimeType.toString()); + } + + @Test + public void testAudioMimeTypeConstructorFromKnown() { + // Test creating AudioMimeType from Known enum + AudioMimeType mimeType = new AudioMimeType(AudioMimeType.Known.AUDIO_WAV); + assertEquals("audio/wav", mimeType.toString()); + } + + @Test + public void testAudioMimeTypeToString() { + // Test toString returns the MIME type value + assertEquals("audio/wav", AudioMimeType.Known.AUDIO_WAV.toString()); + } + + @Test + public void testAudioMimeTypeCustomValue() { + // Test with custom/unknown MIME type + AudioMimeType customType = new AudioMimeType("audio/custom"); + assertEquals("audio/custom", customType.toString()); + } + + // ========== DocumentMimeType Tests ========== + + @Test + public void testDocumentMimeTypeKnownValues() { + // Test all known document MIME types + assertEquals("application/pdf", DocumentMimeType.Known.APPLICATION_PDF.toString()); + } + + @Test + public void testDocumentMimeTypeConstructor() { + // Test creating DocumentMimeType from string value + DocumentMimeType mimeType = new DocumentMimeType("application/pdf"); + assertEquals("application/pdf", mimeType.toString()); + } + + @Test + public void testDocumentMimeTypeConstructorFromKnown() { + // Test creating DocumentMimeType from Known enum + DocumentMimeType mimeType = new DocumentMimeType(DocumentMimeType.Known.APPLICATION_PDF); + assertEquals("application/pdf", mimeType.toString()); + } + + @Test + public void testDocumentMimeTypeConstructorCustomValue() { + // Test with custom document MIME type (e.g., text/plain) + DocumentMimeType customType = new DocumentMimeType("text/plain"); + assertEquals("text/plain", customType.toString()); + } + + @Test + public void testDocumentMimeTypeToString() { + // Test toString returns the MIME type value + assertEquals("application/pdf", DocumentMimeType.Known.APPLICATION_PDF.toString()); + } + + // ========== ImageMimeType Tests ========== + + @Test + public void testImageMimeTypeKnownValues() { + // Test all known image MIME types + assertEquals("image/png", ImageMimeType.Known.IMAGE_PNG.toString()); + assertEquals("image/jpeg", ImageMimeType.Known.IMAGE_JPEG.toString()); + assertEquals("image/webp", ImageMimeType.Known.IMAGE_WEBP.toString()); + assertEquals("image/heic", ImageMimeType.Known.IMAGE_HEIC.toString()); + assertEquals("image/heif", ImageMimeType.Known.IMAGE_HEIF.toString()); + } + + @Test + public void testImageMimeTypeConstructor() { + // Test creating ImageMimeType from string value + ImageMimeType mimeType = new ImageMimeType("image/png"); + assertEquals("image/png", mimeType.toString()); + } + + @Test + public void testImageMimeTypeConstructorFromKnown() { + // Test creating ImageMimeType from Known enum + ImageMimeType mimeType = new ImageMimeType(ImageMimeType.Known.IMAGE_JPEG); + assertEquals("image/jpeg", mimeType.toString()); + } + + @Test + public void testImageMimeTypeToString() { + // Test toString returns the MIME type value + assertEquals("image/webp", ImageMimeType.Known.IMAGE_WEBP.toString()); + } + + // ========== VideoMimeType Tests ========== + + @Test + public void testVideoMimeTypeKnownValues() { + // Test all known video MIME types + assertEquals("video/mp4", VideoMimeType.Known.VIDEO_MP4.toString()); + assertEquals("video/mpeg", VideoMimeType.Known.VIDEO_MPEG.toString()); + assertEquals("video/mov", VideoMimeType.Known.VIDEO_MOV.toString()); + assertEquals("video/avi", VideoMimeType.Known.VIDEO_AVI.toString()); + assertEquals("video/x-flv", VideoMimeType.Known.VIDEO_X_FLV.toString()); + assertEquals("video/mpg", VideoMimeType.Known.VIDEO_MPG.toString()); + assertEquals("video/webm", VideoMimeType.Known.VIDEO_WEBM.toString()); + assertEquals("video/wmv", VideoMimeType.Known.VIDEO_WMV.toString()); + assertEquals("video/3gpp", VideoMimeType.Known.VIDEO_3GPP.toString()); + } + + @Test + public void testVideoMimeTypeConstructor() { + // Test creating VideoMimeType from string value + VideoMimeType mimeType = new VideoMimeType("video/mp4"); + assertEquals("video/mp4", mimeType.toString()); + } + + @Test + public void testVideoMimeTypeConstructorFromKnown() { + // Test creating VideoMimeType from Known enum + VideoMimeType mimeType = new VideoMimeType(VideoMimeType.Known.VIDEO_WEBM); + assertEquals("video/webm", mimeType.toString()); + } + + @Test + public void testVideoMimeTypeToString() { + // Test toString returns the MIME type value + assertEquals("video/webm", VideoMimeType.Known.VIDEO_WEBM.toString()); + } + + // ========== MediaResolution Tests ========== + + @Test + public void testMediaResolutionKnownValues() { + // Test all media resolution Known enum values + assertNotNull(MediaResolution.Known.LOW); + assertNotNull(MediaResolution.Known.HIGH); + assertNotNull(MediaResolution.Known.ULTRA_HIGH); + assertNotNull(MediaResolution.Known.MEDIA_RESOLUTION_UNSPECIFIED); + } + + @Test + public void testMediaResolutionConstructor() { + // Test creating MediaResolution from string value + MediaResolution resolution = new MediaResolution("high"); + assertEquals("high", resolution.toString()); + } + + @Test + public void testMediaResolutionConstructorFromKnown() { + // Test creating MediaResolution from Known enum + MediaResolution resolution = new MediaResolution(MediaResolution.Known.HIGH); + assertEquals("high", resolution.toString()); + } + + @Test + public void testMediaResolutionToString() { + // Test toString returns the resolution value + assertEquals("low", MediaResolution.Known.LOW.toString()); + assertEquals("high", MediaResolution.Known.HIGH.toString()); + assertEquals("ultra_high", MediaResolution.Known.ULTRA_HIGH.toString()); + } + + @Test + public void testMediaResolutionKnownEnum() { + // Test knownEnum returns the correct Known value + MediaResolution resolution = new MediaResolution(MediaResolution.Known.HIGH); + assertEquals(MediaResolution.Known.HIGH, resolution.knownEnum()); + } + + @Test + public void testMediaResolutionUnknownFallback() { + // Test unknown values fall back to MEDIA_RESOLUTION_UNSPECIFIED + MediaResolution resolution = new MediaResolution("custom_resolution"); + assertEquals(MediaResolution.Known.MEDIA_RESOLUTION_UNSPECIFIED, resolution.knownEnum()); + assertEquals("custom_resolution", resolution.toString()); + } + + // ========== Cross-Enum Validation Tests ========== + + @Test + public void testAllMimeTypeEnumsNotNull() { + // Verify all known MIME type values are not null + assertNotNull(AudioMimeType.Known.AUDIO_MP3.toString()); + assertNotNull(DocumentMimeType.Known.APPLICATION_PDF.toString()); + assertNotNull(ImageMimeType.Known.IMAGE_PNG.toString()); + assertNotNull(VideoMimeType.Known.VIDEO_MP4.toString()); + assertNotNull(MediaResolution.Known.HIGH.toString()); + } + + @Test + public void testMimeTypeValuesAreNotEmpty() { + // Verify MIME type values are not empty strings + assertTrue(!AudioMimeType.Known.AUDIO_WAV.toString().isEmpty()); + assertTrue(!DocumentMimeType.Known.APPLICATION_PDF.toString().isEmpty()); + assertTrue(!ImageMimeType.Known.IMAGE_JPEG.toString().isEmpty()); + assertTrue(!VideoMimeType.Known.VIDEO_WEBM.toString().isEmpty()); + } + + @Test + public void testMimeTypeEquality() { + // Test that two instances with the same value are equal + AudioMimeType audio1 = new AudioMimeType("audio/mp3"); + AudioMimeType audio2 = new AudioMimeType("audio/mp3"); + assertEquals(audio1, audio2); + } + + @Test + public void testMimeTypeHashCode() { + // Test that two instances with the same value have the same hash code + AudioMimeType audio1 = new AudioMimeType("audio/mp3"); + AudioMimeType audio2 = new AudioMimeType("audio/mp3"); + assertEquals(audio1.hashCode(), audio2.hashCode()); + } +} diff --git a/src/test/java/com/google/genai/types/interactions/ResponseModalityTest.java b/src/test/java/com/google/genai/types/interactions/ResponseModalityTest.java new file mode 100644 index 00000000000..f2a053a710f --- /dev/null +++ b/src/test/java/com/google/genai/types/interactions/ResponseModalityTest.java @@ -0,0 +1,188 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** Tests for ResponseModality class. */ +public class ResponseModalityTest { + + @Test + public void testResponseModalityFromKnownEnum_TEXT() { + ResponseModality modality = new ResponseModality(ResponseModality.Known.TEXT); + assertEquals("text", modality.toString()); + assertEquals(ResponseModality.Known.TEXT, modality.knownEnum()); + } + + @Test + public void testResponseModalityFromKnownEnum_IMAGE() { + ResponseModality modality = new ResponseModality(ResponseModality.Known.IMAGE); + assertEquals("image", modality.toString()); + assertEquals(ResponseModality.Known.IMAGE, modality.knownEnum()); + } + + @Test + public void testResponseModalityFromKnownEnum_AUDIO() { + ResponseModality modality = new ResponseModality(ResponseModality.Known.AUDIO); + assertEquals("audio", modality.toString()); + assertEquals(ResponseModality.Known.AUDIO, modality.knownEnum()); + } + + @Test + public void testResponseModalityFromString_TEXT() { + ResponseModality modality = new ResponseModality("text"); + assertEquals("text", modality.toString()); + assertEquals(ResponseModality.Known.TEXT, modality.knownEnum()); + } + + @Test + public void testResponseModalityFromString_IMAGE() { + ResponseModality modality = new ResponseModality("image"); + assertEquals("image", modality.toString()); + assertEquals(ResponseModality.Known.IMAGE, modality.knownEnum()); + } + + @Test + public void testResponseModalityFromString_AUDIO() { + ResponseModality modality = new ResponseModality("audio"); + assertEquals("audio", modality.toString()); + assertEquals(ResponseModality.Known.AUDIO, modality.knownEnum()); + } + + @Test + public void testResponseModalityFromString_caseInsensitive() { + ResponseModality modality1 = new ResponseModality("TEXT"); + ResponseModality modality2 = new ResponseModality("Text"); + ResponseModality modality3 = new ResponseModality("text"); + + assertEquals(ResponseModality.Known.TEXT, modality1.knownEnum()); + assertEquals(ResponseModality.Known.TEXT, modality2.knownEnum()); + assertEquals(ResponseModality.Known.TEXT, modality3.knownEnum()); + } + + @Test + public void testResponseModalityFromString_unknownValue() { + ResponseModality modality = new ResponseModality("video"); + assertEquals("video", modality.toString()); + assertEquals(ResponseModality.Known.RESPONSE_MODALITY_UNSPECIFIED, modality.knownEnum()); + } + + @Test + public void testResponseModalityEquals_sameKnownEnum() { + ResponseModality modality1 = new ResponseModality(ResponseModality.Known.TEXT); + ResponseModality modality2 = new ResponseModality(ResponseModality.Known.TEXT); + assertEquals(modality1, modality2); + } + + @Test + public void testResponseModalityEquals_differentKnownEnum() { + ResponseModality modality1 = new ResponseModality(ResponseModality.Known.TEXT); + ResponseModality modality2 = new ResponseModality(ResponseModality.Known.IMAGE); + assertNotEquals(modality1, modality2); + } + + @Test + public void testResponseModalityEquals_sameUnknownValue() { + ResponseModality modality1 = new ResponseModality("custom_modality"); + ResponseModality modality2 = new ResponseModality("custom_modality"); + assertEquals(modality1, modality2); + } + + @Test + public void testResponseModalityEquals_differentUnknownValue() { + ResponseModality modality1 = new ResponseModality("custom1"); + ResponseModality modality2 = new ResponseModality("custom2"); + assertNotEquals(modality1, modality2); + } + + @Test + public void testResponseModalityHashCode_sameKnownEnum() { + ResponseModality modality1 = new ResponseModality(ResponseModality.Known.TEXT); + ResponseModality modality2 = new ResponseModality(ResponseModality.Known.TEXT); + assertEquals(modality1.hashCode(), modality2.hashCode()); + } + + @Test + public void testResponseModalityInCreateInteractionConfig() { + CreateInteractionConfig config = CreateInteractionConfig.builder() + .input("test input") + .model("gemini-2.0-flash") + .responseModalities(ResponseModality.Known.TEXT, ResponseModality.Known.IMAGE) + .build(); + + assertTrue(config.responseModalities().isPresent()); + List modalities = config.responseModalities().get(); + assertEquals(2, modalities.size()); + assertEquals("text", modalities.get(0).toString()); + assertEquals("image", modalities.get(1).toString()); + } + + @Test + public void testResponseModalityJsonSerialization_lowercase() { + CreateInteractionConfig config = CreateInteractionConfig.builder() + .input("test input") + .model("gemini-2.0-flash") + .responseModalities(ResponseModality.Known.TEXT, ResponseModality.Known.AUDIO) + .build(); + + String json = config.toJson(); + // Verify that response_modalities contains lowercase values + assertTrue(json.contains("\"response_modalities\":[\"text\",\"audio\"]")); + // Verify uppercase is NOT present + assertTrue(!json.contains("\"TEXT\"")); + assertTrue(!json.contains("\"AUDIO\"")); + } + + @Test + public void testResponseModalityFromStrings() { + CreateInteractionConfig config = CreateInteractionConfig.builder() + .input("test input") + .model("gemini-2.0-flash") + .responseModalities("text", "image", "audio") + .build(); + + assertTrue(config.responseModalities().isPresent()); + List modalities = config.responseModalities().get(); + assertEquals(3, modalities.size()); + assertEquals(ResponseModality.Known.TEXT, modalities.get(0).knownEnum()); + assertEquals(ResponseModality.Known.IMAGE, modalities.get(1).knownEnum()); + assertEquals(ResponseModality.Known.AUDIO, modalities.get(2).knownEnum()); + } + + @Test + public void testResponseModalitiesFromKnownList() { + List knownList = Arrays.asList( + ResponseModality.Known.TEXT, + ResponseModality.Known.IMAGE + ); + + CreateInteractionConfig config = CreateInteractionConfig.builder() + .input("test input") + .model("gemini-2.0-flash") + .responseModalitiesFromKnown(knownList) + .build(); + + assertTrue(config.responseModalities().isPresent()); + assertEquals(2, config.responseModalities().get().size()); + } +} diff --git a/src/test/java/com/google/genai/types/interactions/ToolChoiceDeserializerTest.java b/src/test/java/com/google/genai/types/interactions/ToolChoiceDeserializerTest.java new file mode 100644 index 00000000000..c55c952f384 --- /dev/null +++ b/src/test/java/com/google/genai/types/interactions/ToolChoiceDeserializerTest.java @@ -0,0 +1,560 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Tests for ToolChoiceDeserializer to ensure proper JSON deserialization of ToolChoice union types. + * + *

ToolChoiceDeserializer handles two different types: + * + *

    + *
  • ToolChoiceType - deserialized from a simple string (e.g., "auto", "any", "none", + * "validated") + *
  • ToolChoiceConfig - deserialized from an object with allowed_tools field + *
+ * + *

This test class mirrors the structure of ToolChoiceSerializerTest for consistency. + */ +public class ToolChoiceDeserializerTest { + + // ========== ToolChoiceType Deserialization Tests ========== + + @Test + public void testDeserializeToolChoiceTypeAuto() { + // Arrange - JSON string + String json = "\"auto\""; + + // Act - deserialize + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert + assertNotNull(toolChoice); + assertTrue(toolChoice.isType()); + assertFalse(toolChoice.isConfig()); + assertEquals("auto", toolChoice.asType().toString()); + } + + @Test + public void testDeserializeToolChoiceTypeAny() { + // Arrange - JSON string + String json = "\"any\""; + + // Act - deserialize + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert + assertNotNull(toolChoice); + assertTrue(toolChoice.isType()); + assertFalse(toolChoice.isConfig()); + assertEquals("any", toolChoice.asType().toString()); + } + + @Test + public void testDeserializeToolChoiceTypeNone() { + // Arrange - JSON string + String json = "\"none\""; + + // Act - deserialize + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert + assertNotNull(toolChoice); + assertTrue(toolChoice.isType()); + assertEquals("none", toolChoice.asType().toString()); + } + + @Test + public void testDeserializeToolChoiceTypeValidated() { + // Arrange - JSON string + String json = "\"validated\""; + + // Act - deserialize + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert + assertNotNull(toolChoice); + assertTrue(toolChoice.isType()); + assertEquals("validated", toolChoice.asType().toString()); + } + + @Test + public void testDeserializeToolChoiceTypeUnspecified() { + // Arrange - JSON string + String json = "\"unspecified\""; + + // Act - deserialize + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert + assertNotNull(toolChoice); + assertTrue(toolChoice.isType()); + assertEquals("unspecified", toolChoice.asType().toString()); + } + + @Test + public void testDeserializeToolChoiceTypeFromString() { + // Arrange - custom string value + String json = "\"custom_mode\""; + + // Act - deserialize + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert + assertNotNull(toolChoice); + assertTrue(toolChoice.isType()); + assertEquals("custom_mode", toolChoice.asType().toString()); + } + + @Test + public void testDeserializeToolChoiceTypeVerifyIsType() { + // Arrange + String json = "\"auto\""; + + // Act + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert + assertTrue(toolChoice.isType()); + assertFalse(toolChoice.isConfig()); + } + + // ========== ToolChoiceConfig Deserialization Tests ========== + + @Test + public void testDeserializeToolChoiceConfigWithAllowedTools() { + // Arrange - JSON object + String json = + "{\"allowed_tools\":{\"mode\":\"any\",\"tools\":[\"get_weather\",\"search_web\"]}}"; + + // Act - deserialize + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert + assertNotNull(toolChoice); + assertTrue(toolChoice.isConfig()); + assertFalse(toolChoice.isType()); + ToolChoiceConfig config = toolChoice.asConfig(); + assertTrue(config.allowedTools().isPresent()); + assertEquals("any", config.allowedTools().get().mode().get()); + assertTrue(config.allowedTools().get().tools().isPresent()); + assertEquals(2, config.allowedTools().get().tools().get().size()); + assertTrue(config.allowedTools().get().tools().get().contains("get_weather")); + assertTrue(config.allowedTools().get().tools().get().contains("search_web")); + } + + @Test + public void testDeserializeToolChoiceConfigEmpty() { + // Arrange - empty object {} + String json = "{}"; + + // Act - deserialize + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert + assertNotNull(toolChoice); + assertTrue(toolChoice.isConfig()); + ToolChoiceConfig config = toolChoice.asConfig(); + assertFalse(config.allowedTools().isPresent()); + } + + @Test + public void testDeserializeToolChoiceConfigComplexTools() { + // Arrange - multiple tools + String json = + "{\"allowed_tools\":{\"mode\":\"validated\",\"tools\":[\"tool1\",\"tool2\",\"tool3\",\"tool4\"]}}"; + + // Act - deserialize + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert + assertNotNull(toolChoice); + assertTrue(toolChoice.isConfig()); + ToolChoiceConfig config = toolChoice.asConfig(); + assertTrue(config.allowedTools().isPresent()); + assertEquals("validated", config.allowedTools().get().mode().get()); + assertEquals(4, config.allowedTools().get().tools().get().size()); + } + + @Test + public void testDeserializeToolChoiceConfigWithAutoMode() { + // Arrange - mode: "auto" + String json = "{\"allowed_tools\":{\"mode\":\"auto\",\"tools\":[\"weather_tool\"]}}"; + + // Act - deserialize + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert + assertNotNull(toolChoice); + assertTrue(toolChoice.isConfig()); + ToolChoiceConfig config = toolChoice.asConfig(); + assertTrue(config.allowedTools().isPresent()); + assertEquals("auto", config.allowedTools().get().mode().get()); + assertTrue(config.allowedTools().get().tools().get().contains("weather_tool")); + } + + @Test + public void testDeserializeToolChoiceConfigVerifyIsConfig() { + // Arrange + String json = "{\"allowed_tools\":{\"mode\":\"any\",\"tools\":[\"tool1\"]}}"; + + // Act + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert + assertTrue(toolChoice.isConfig()); + assertFalse(toolChoice.isType()); + } + + // ========== Round-trip Tests ========== + + @Test + public void testRoundTripToolChoiceTypeAuto() { + // Arrange + ToolChoice original = ToolChoice.fromType(ToolChoiceType.Known.AUTO); + + // Act - serialize then deserialize + String json = original.toJson(); + ToolChoice deserialized = ToolChoice.fromJson(json); + + // Assert + assertNotNull(deserialized); + assertTrue(deserialized.isType()); + assertEquals("auto", deserialized.asType().toString()); + assertEquals(original.getValue().toString(), deserialized.getValue().toString()); + } + + @Test + public void testRoundTripToolChoiceTypeValidated() { + // Arrange + ToolChoice original = ToolChoice.fromType(ToolChoiceType.Known.VALIDATED); + + // Act - serialize then deserialize + String json = original.toJson(); + ToolChoice deserialized = ToolChoice.fromJson(json); + + // Assert + assertNotNull(deserialized); + assertTrue(deserialized.isType()); + assertEquals("validated", deserialized.asType().toString()); + assertEquals(original.getValue().toString(), deserialized.getValue().toString()); + } + + @Test + public void testRoundTripToolChoiceConfig() { + // Arrange + AllowedTools allowedTools = AllowedTools.of("any", "func1", "func2", "func3"); + ToolChoiceConfig config = ToolChoiceConfig.builder().allowedTools(allowedTools).build(); + ToolChoice original = ToolChoice.fromConfig(config); + + // Act - serialize then deserialize + String json = original.toJson(); + ToolChoice deserialized = ToolChoice.fromJson(json); + + // Assert + assertNotNull(deserialized); + assertTrue(deserialized.isConfig()); + ToolChoiceConfig deserializedConfig = deserialized.asConfig(); + assertTrue(deserializedConfig.allowedTools().isPresent()); + assertEquals("any", deserializedConfig.allowedTools().get().mode().get()); + assertEquals(3, deserializedConfig.allowedTools().get().tools().get().size()); + assertTrue(deserializedConfig.allowedTools().get().tools().get().contains("func1")); + assertTrue(deserializedConfig.allowedTools().get().tools().get().contains("func2")); + assertTrue(deserializedConfig.allowedTools().get().tools().get().contains("func3")); + } + + @Test + public void testRoundTripToolChoiceConfigComplex() { + // Arrange - complex config round-trip + AllowedTools allowedTools = + AllowedTools.builder() + .mode("validated") + .tools("get_weather", "get_news", "search_web", "calculator") + .build(); + ToolChoiceConfig config = ToolChoiceConfig.builder().allowedTools(allowedTools).build(); + ToolChoice original = ToolChoice.fromConfig(config); + + // Act - serialize then deserialize + String json = original.toJson(); + ToolChoice deserialized = ToolChoice.fromJson(json); + + // Assert + assertNotNull(deserialized); + assertTrue(deserialized.isConfig()); + ToolChoiceConfig deserializedConfig = deserialized.asConfig(); + assertTrue(deserializedConfig.allowedTools().isPresent()); + assertEquals("validated", deserializedConfig.allowedTools().get().mode().get()); + assertEquals(4, deserializedConfig.allowedTools().get().tools().get().size()); + } + + // ========== Integration with GenerationConfig Tests ========== + + @Test + public void testDeserializeToolChoiceTypeFromGenerationConfig() { + // Arrange - full GenerationConfig JSON + String json = "{\"tool_choice\":\"auto\",\"temperature\":0.7}"; + + // Act + GenerationConfig config = GenerationConfig.fromJson(json); + + // Assert + assertNotNull(config); + assertTrue(config.toolChoice().isPresent()); + ToolChoice toolChoice = config.toolChoice().get(); + assertTrue(toolChoice.isType()); + assertEquals("auto", toolChoice.asType().toString()); + } + + @Test + public void testDeserializeToolChoiceConfigFromGenerationConfig() { + // Arrange - full GenerationConfig JSON with ToolChoiceConfig + String json = + "{\"tool_choice\":{\"allowed_tools\":{\"mode\":\"validated\",\"tools\":[\"weather_tool\"]}},\"temperature\":0.5}"; + + // Act + GenerationConfig config = GenerationConfig.fromJson(json); + + // Assert + assertNotNull(config); + assertTrue(config.toolChoice().isPresent()); + ToolChoice toolChoice = config.toolChoice().get(); + assertTrue(toolChoice.isConfig()); + ToolChoiceConfig toolChoiceConfig = toolChoice.asConfig(); + assertTrue(toolChoiceConfig.allowedTools().isPresent()); + assertEquals("validated", toolChoiceConfig.allowedTools().get().mode().get()); + assertTrue(toolChoiceConfig.allowedTools().get().tools().get().contains("weather_tool")); + } + + // ========== Edge Cases ========== + + @Test + public void testDeserializeNull() { + // Arrange + String json = "null"; + + // Act + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert + assertNull(toolChoice); + } + + @Test + public void testDeserializeInvalidTokenThrows() { + // Arrange - number instead of string/object + String json = "123"; + + // Act & Assert + assertThrows( + Exception.class, + () -> { + ToolChoice.fromJson(json); + }); + } + + @Test + public void testDeserializeBooleanThrows() { + // Arrange - boolean instead of string/object + String json = "true"; + + // Act & Assert + assertThrows( + Exception.class, + () -> { + ToolChoice.fromJson(json); + }); + } + + @Test + public void testDeserializeMalformedJsonThrows() { + // Arrange - malformed JSON + String json = "{\"allowed_tools\":invalid}"; + + // Act & Assert + assertThrows( + Exception.class, + () -> { + ToolChoice.fromJson(json); + }); + } + + @Test + public void testDeserializeToolChoiceGetValue() { + // Arrange + String typeJson = "\"auto\""; + String configJson = "{\"allowed_tools\":{\"mode\":\"any\",\"tools\":[\"tool1\"]}}"; + + // Act + ToolChoice typeChoice = ToolChoice.fromJson(typeJson); + ToolChoice configChoice = ToolChoice.fromJson(configJson); + + // Assert - getValue() returns correct type + assertTrue(typeChoice.getValue() instanceof ToolChoiceType); + assertTrue(configChoice.getValue() instanceof ToolChoiceConfig); + } + + @Test + public void testDeserializeToolChoiceAsType() { + // Arrange + String json = "\"validated\""; + + // Act + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert - asType() works after deserialization + assertNotNull(toolChoice); + ToolChoiceType type = toolChoice.asType(); + assertEquals("validated", type.toString()); + } + + @Test + public void testDeserializeToolChoiceAsConfig() { + // Arrange + String json = "{\"allowed_tools\":{\"mode\":\"auto\",\"tools\":[\"tool1\",\"tool2\"]}}"; + + // Act + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert - asConfig() works after deserialization + assertNotNull(toolChoice); + ToolChoiceConfig config = toolChoice.asConfig(); + assertTrue(config.allowedTools().isPresent()); + assertEquals("auto", config.allowedTools().get().mode().get()); + } + + @Test + public void testDeserializeToolChoiceAsTypeThrows() { + // Arrange - deserialize a config + String json = "{\"allowed_tools\":{\"mode\":\"any\",\"tools\":[\"tool1\"]}}"; + + // Act + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert - asType() throws when it's a config + assertThrows( + IllegalStateException.class, + () -> { + toolChoice.asType(); + }); + } + + @Test + public void testDeserializeToolChoiceAsConfigThrows() { + // Arrange - deserialize a type + String json = "\"auto\""; + + // Act + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert - asConfig() throws when it's a type + assertThrows( + IllegalStateException.class, + () -> { + toolChoice.asConfig(); + }); + } + + @Test + public void testDeserializeArrayThrows() { + // Arrange - array instead of string/object + String json = "[\"auto\",\"any\"]"; + + // Act & Assert + assertThrows( + Exception.class, + () -> { + ToolChoice.fromJson(json); + }); + } + + @Test + public void testDeserializeEmptyStringThrows() { + // Arrange - empty string (not a valid ToolChoiceType) + String json = "\"\""; + + // Act - deserialize (should work, but results in empty type string) + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert - should deserialize as a type with empty string value + assertNotNull(toolChoice); + assertTrue(toolChoice.isType()); + assertEquals("", toolChoice.asType().toString()); + } + + @Test + public void testDeserializeToolChoiceConfigWithMissingMode() { + // Arrange - allowed_tools without mode field + String json = "{\"allowed_tools\":{\"tools\":[\"tool1\",\"tool2\"]}}"; + + // Act + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert - should deserialize successfully (mode is optional) + assertNotNull(toolChoice); + assertTrue(toolChoice.isConfig()); + ToolChoiceConfig config = toolChoice.asConfig(); + assertTrue(config.allowedTools().isPresent()); + assertFalse(config.allowedTools().get().mode().isPresent()); + assertTrue(config.allowedTools().get().tools().isPresent()); + assertEquals(2, config.allowedTools().get().tools().get().size()); + } + + @Test + public void testDeserializeToolChoiceConfigWithEmptyTools() { + // Arrange - allowed_tools with empty tools array + String json = "{\"allowed_tools\":{\"mode\":\"any\",\"tools\":[]}}"; + + // Act + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert + assertNotNull(toolChoice); + assertTrue(toolChoice.isConfig()); + ToolChoiceConfig config = toolChoice.asConfig(); + assertTrue(config.allowedTools().isPresent()); + assertEquals("any", config.allowedTools().get().mode().get()); + assertTrue(config.allowedTools().get().tools().isPresent()); + assertEquals(0, config.allowedTools().get().tools().get().size()); + } + + @Test + public void testDeserializeMultipleToolChoiceTypes() { + // Arrange - test multiple different type values + String[] typeValues = {"auto", "any", "none", "validated", "unspecified"}; + + for (String typeValue : typeValues) { + // Arrange + String json = "\"" + typeValue + "\""; + + // Act + ToolChoice toolChoice = ToolChoice.fromJson(json); + + // Assert + assertNotNull(toolChoice, "Failed for type: " + typeValue); + assertTrue(toolChoice.isType(), "Failed for type: " + typeValue); + assertEquals(typeValue, toolChoice.asType().toString(), "Failed for type: " + typeValue); + } + } +} diff --git a/src/test/java/com/google/genai/types/interactions/ToolChoiceSerializerTest.java b/src/test/java/com/google/genai/types/interactions/ToolChoiceSerializerTest.java new file mode 100644 index 00000000000..70aeb37214a --- /dev/null +++ b/src/test/java/com/google/genai/types/interactions/ToolChoiceSerializerTest.java @@ -0,0 +1,376 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Tests for ToolChoiceSerializer to ensure proper JSON serialization of ToolChoice union types. + * + *

ToolChoiceSerializer handles two different types: + * + *

    + *
  • ToolChoiceType - serialized as a simple string (e.g., "auto", "any", "none", "validated") + *
  • ToolChoiceConfig - serialized as an object with allowed_tools field + *
+ */ +public class ToolChoiceSerializerTest { + + // ========== ToolChoiceType Serialization Tests ========== + + @Test + public void testSerializeToolChoiceTypeAuto() { + // Arrange + ToolChoice toolChoice = ToolChoice.fromType(ToolChoiceType.Known.AUTO); + + // Act + String json = toolChoice.toJson(); + + // Assert - should serialize as lowercase string (matching API format) + assertEquals("\"auto\"", json); + } + + @Test + public void testSerializeToolChoiceTypeAny() { + // Arrange + ToolChoice toolChoice = ToolChoice.fromType(ToolChoiceType.Known.ANY); + + // Act + String json = toolChoice.toJson(); + + // Assert - should serialize as lowercase string (matching API format) + assertEquals("\"any\"", json); + } + + @Test + public void testSerializeToolChoiceTypeNone() { + // Arrange + ToolChoice toolChoice = ToolChoice.fromType(ToolChoiceType.Known.NONE); + + // Act + String json = toolChoice.toJson(); + + // Assert - should serialize as lowercase string (matching API format) + assertEquals("\"none\"", json); + } + + @Test + public void testSerializeToolChoiceTypeValidated() { + // Arrange + ToolChoice toolChoice = ToolChoice.fromType(ToolChoiceType.Known.VALIDATED); + + // Act + String json = toolChoice.toJson(); + + // Assert - should serialize as lowercase string (matching API format) + assertEquals("\"validated\"", json); + } + + @Test + public void testSerializeToolChoiceTypeFromString() { + // Arrange + ToolChoice toolChoice = ToolChoice.fromString("auto"); + + // Act + String json = toolChoice.toJson(); + + // Assert + assertEquals("\"auto\"", json); + } + + @Test + public void testSerializeToolChoiceTypeCustomValue() { + // Arrange - test with a custom/unknown type value + ToolChoice toolChoice = ToolChoice.fromString("custom_mode"); + + // Act + String json = toolChoice.toJson(); + + // Assert + assertEquals("\"custom_mode\"", json); + } + + @Test + public void testToolChoiceTypeIsType() { + // Arrange + ToolChoice toolChoice = ToolChoice.fromType(ToolChoiceType.Known.AUTO); + + // Act & Assert + assertTrue(toolChoice.isType()); + assertFalse(toolChoice.isConfig()); + } + + @Test + public void testToolChoiceTypeAsType() { + // Arrange + ToolChoice toolChoice = ToolChoice.fromType(ToolChoiceType.Known.ANY); + + // Act + ToolChoiceType type = toolChoice.asType(); + + // Assert - toString() returns the lowercase API value + assertEquals("any", type.toString()); + } + + @Test + public void testToolChoiceTypeAsConfigThrows() { + // Arrange + ToolChoice toolChoice = ToolChoice.fromType(ToolChoiceType.Known.AUTO); + + // Act & Assert + assertThrows(IllegalStateException.class, () -> { + toolChoice.asConfig(); + }); + } + + // ========== ToolChoiceConfig Serialization Tests ========== + + @Test + public void testSerializeToolChoiceConfigWithAllowedTools() { + // Arrange + AllowedTools allowedTools = AllowedTools.builder() + .mode("any") + .tools("get_weather", "search_web") + .build(); + ToolChoiceConfig config = ToolChoiceConfig.builder() + .allowedTools(allowedTools) + .build(); + ToolChoice toolChoice = ToolChoice.fromConfig(config); + + // Act + String json = toolChoice.toJson(); + + // Assert - should serialize as an object with allowed_tools field + assertTrue(json.contains("\"allowed_tools\"")); + assertTrue(json.contains("\"mode\":\"any\"")); + assertTrue(json.contains("\"tools\":[")); + assertTrue(json.contains("\"get_weather\"")); + assertTrue(json.contains("\"search_web\"")); + } + + @Test + public void testSerializeToolChoiceConfigEmpty() { + // Arrange + ToolChoiceConfig config = ToolChoiceConfig.builder().build(); + ToolChoice toolChoice = ToolChoice.fromConfig(config); + + // Act + String json = toolChoice.toJson(); + + // Assert - should serialize as empty object + assertEquals("{}", json); + } + + @Test + public void testSerializeToolChoiceConfigFromBuilder() { + // Arrange - using builder directly + ToolChoice toolChoice = ToolChoice.fromConfig( + ToolChoiceConfig.builder() + .allowedTools(AllowedTools.of("validated", "tool1", "tool2")) + ); + + // Act + String json = toolChoice.toJson(); + + // Assert + assertTrue(json.contains("\"allowed_tools\"")); + assertTrue(json.contains("\"mode\":\"validated\"")); + assertTrue(json.contains("\"tool1\"")); + assertTrue(json.contains("\"tool2\"")); + } + + @Test + public void testToolChoiceConfigIsConfig() { + // Arrange + ToolChoiceConfig config = ToolChoiceConfig.builder().build(); + ToolChoice toolChoice = ToolChoice.fromConfig(config); + + // Act & Assert + assertTrue(toolChoice.isConfig()); + assertFalse(toolChoice.isType()); + } + + @Test + public void testToolChoiceConfigAsConfig() { + // Arrange + AllowedTools allowedTools = AllowedTools.of("auto", "tool_a"); + ToolChoiceConfig originalConfig = ToolChoiceConfig.builder() + .allowedTools(allowedTools) + .build(); + ToolChoice toolChoice = ToolChoice.fromConfig(originalConfig); + + // Act + ToolChoiceConfig retrievedConfig = toolChoice.asConfig(); + + // Assert + assertTrue(retrievedConfig.allowedTools().isPresent()); + assertEquals("auto", retrievedConfig.allowedTools().get().mode().get()); + } + + @Test + public void testToolChoiceConfigAsTypeThrows() { + // Arrange + ToolChoiceConfig config = ToolChoiceConfig.builder().build(); + ToolChoice toolChoice = ToolChoice.fromConfig(config); + + // Act & Assert + assertThrows(IllegalStateException.class, () -> { + toolChoice.asType(); + }); + } + + // ========== Round-trip Serialization Tests ========== + + @Test + public void testRoundTripToolChoiceTypeAuto() { + // Arrange + ToolChoice original = ToolChoice.fromType(ToolChoiceType.Known.AUTO); + + // Act + String json = original.toJson(); + + // Assert - verify JSON structure (lowercase for API) + assertEquals("\"auto\"", json); + assertTrue(original.isType()); + assertEquals("auto", original.asType().toString()); + } + + @Test + public void testRoundTripToolChoiceConfig() { + // Arrange + AllowedTools allowedTools = AllowedTools.of("any", "func1", "func2", "func3"); + ToolChoiceConfig config = ToolChoiceConfig.builder() + .allowedTools(allowedTools) + .build(); + ToolChoice original = ToolChoice.fromConfig(config); + + // Act + String json = original.toJson(); + + // Assert - verify JSON structure + assertTrue(json.contains("\"allowed_tools\"")); + assertTrue(json.contains("\"mode\":\"any\"")); + assertTrue(json.contains("\"tools\":[")); + assertTrue(original.isConfig()); + } + + // ========== Integration with GenerationConfig Tests ========== + + @Test + public void testToolChoiceTypeInGenerationConfig() { + // Arrange + GenerationConfig genConfig = GenerationConfig.builder() + .toolChoice(ToolChoice.fromType(ToolChoiceType.Known.AUTO)) + .build(); + + // Act + String json = genConfig.toJson(); + + // Assert - should include tool_choice as a lowercase string (matching API format) + assertTrue(json.contains("\"tool_choice\":\"auto\"")); + } + + @Test + public void testToolChoiceConfigInGenerationConfig() { + // Arrange + AllowedTools allowedTools = AllowedTools.of("validated", "weather_tool"); + ToolChoiceConfig config = ToolChoiceConfig.builder() + .allowedTools(allowedTools) + .build(); + GenerationConfig genConfig = GenerationConfig.builder() + .toolChoice(ToolChoice.fromConfig(config)) + .build(); + + // Act + String json = genConfig.toJson(); + + // Assert - should include tool_choice as an object + assertTrue(json.contains("\"tool_choice\":{")); + assertTrue(json.contains("\"allowed_tools\"")); + assertTrue(json.contains("\"mode\":\"validated\"")); + assertTrue(json.contains("\"weather_tool\"")); + } + + // ========== Edge Cases ========== + + @Test + public void testSerializeToolChoiceWithUnspecifiedType() { + // Arrange + ToolChoice toolChoice = ToolChoice.fromType(ToolChoiceType.Known.TOOL_CHOICE_TYPE_UNSPECIFIED); + + // Act + String json = toolChoice.toJson(); + + // Assert - should serialize as lowercase string (matching API format) + assertEquals("\"unspecified\"", json); + } + + @Test + public void testToolChoiceGetValue() { + // Arrange + ToolChoice typeChoice = ToolChoice.fromType(ToolChoiceType.Known.AUTO); + ToolChoice configChoice = ToolChoice.fromConfig(ToolChoiceConfig.builder().build()); + + // Act & Assert + assertTrue(typeChoice.getValue() instanceof ToolChoiceType); + assertTrue(configChoice.getValue() instanceof ToolChoiceConfig); + } + + @Test + public void testSerializeComplexToolChoiceConfig() { + // Arrange - config with multiple tools + AllowedTools allowedTools = AllowedTools.builder() + .mode("any") + .tools("get_weather", "get_news", "search_web", "calculator") + .build(); + ToolChoiceConfig config = ToolChoiceConfig.builder() + .allowedTools(allowedTools) + .build(); + ToolChoice toolChoice = ToolChoice.fromConfig(config); + + // Act + String json = toolChoice.toJson(); + + // Assert - verify all tools are present + assertTrue(json.contains("\"get_weather\"")); + assertTrue(json.contains("\"get_news\"")); + assertTrue(json.contains("\"search_web\"")); + assertTrue(json.contains("\"calculator\"")); + } + + @Test + public void testSerializeToolChoiceConfigWithAutoMode() { + // Arrange + AllowedTools allowedTools = AllowedTools.of("auto", "tool1"); + ToolChoiceConfig config = ToolChoiceConfig.builder() + .allowedTools(allowedTools) + .build(); + ToolChoice toolChoice = ToolChoice.fromConfig(config); + + // Act + String json = toolChoice.toJson(); + + // Assert + assertTrue(json.contains("\"mode\":\"auto\"")); + assertTrue(json.contains("\"tool1\"")); + } +} diff --git a/src/test/java/com/google/genai/types/interactions/content/TextContentAnnotationsTest.java b/src/test/java/com/google/genai/types/interactions/content/TextContentAnnotationsTest.java new file mode 100644 index 00000000000..373709e519b --- /dev/null +++ b/src/test/java/com/google/genai/types/interactions/content/TextContentAnnotationsTest.java @@ -0,0 +1,282 @@ +/* + * 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. + */ + +package com.google.genai.types.interactions.content; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.genai.types.interactions.Annotation; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** Tests for TextContent with annotations field. */ +public class TextContentAnnotationsTest { + + @Test + public void testTextContentWithoutAnnotations() { + // Arrange & Act + TextContent textContent = TextContent.of("This is a simple text without citations."); + + // Assert + assertTrue(textContent.text().isPresent()); + assertEquals("This is a simple text without citations.", textContent.text().get()); + assertFalse(textContent.annotations().isPresent()); + } + + @Test + public void testTextContentWithAnnotationsList() { + // Arrange + Annotation annotation1 = + Annotation.builder() + .startIndex(0) + .endIndex(25) + .source("https://example.com/source1") + .build(); + + Annotation annotation2 = + Annotation.builder() + .startIndex(26) + .endIndex(50) + .source("Wikipedia: AI Article") + .build(); + + List annotations = Arrays.asList(annotation1, annotation2); + + // Act + TextContent textContent = + TextContent.builder() + .text("This text has citations from multiple sources for attribution.") + .annotations(annotations) + .build(); + + // Assert + assertTrue(textContent.text().isPresent()); + assertTrue(textContent.annotations().isPresent()); + assertEquals(2, textContent.annotations().get().size()); + assertEquals("https://example.com/source1", textContent.annotations().get().get(0).source().get()); + assertEquals("Wikipedia: AI Article", textContent.annotations().get().get(1).source().get()); + } + + @Test + public void testTextContentWithAnnotationsVarargs() { + // Arrange + Annotation annotation1 = Annotation.of(0, 15, "https://source1.com"); + Annotation annotation2 = Annotation.of(16, 30, "https://source2.com"); + + // Act + TextContent textContent = + TextContent.builder() + .text("Annotated content using varargs syntax.") + .annotations(annotation1, annotation2) + .build(); + + // Assert + assertTrue(textContent.annotations().isPresent()); + assertEquals(2, textContent.annotations().get().size()); + assertEquals("https://source1.com", textContent.annotations().get().get(0).source().get()); + assertEquals("https://source2.com", textContent.annotations().get().get(1).source().get()); + } + + @Test + public void testTextContentClearAnnotations() { + // Arrange + TextContent.Builder builder = + TextContent.builder() + .text("Text with annotations") + .annotations(Annotation.of(0, 5, "https://example.com")); + + // Act + builder.clearAnnotations(); + TextContent textContent = builder.build(); + + // Assert + assertFalse(textContent.annotations().isPresent()); + } + + @Test + public void testTextContentJsonSerializationWithoutAnnotations() { + // Arrange + TextContent textContent = TextContent.of("This is a simple text without citations."); + + // Act + String json = textContent.toJson(); + + // Assert + assertNotNull(json); + assertTrue(json.contains("\"type\":\"text\"")); + assertTrue(json.contains("\"text\":\"This is a simple text without citations.\"")); + assertFalse(json.contains("\"annotations\"")); + } + + @Test + public void testTextContentJsonSerializationWithAnnotations() { + // Arrange + Annotation annotation1 = Annotation.of(0, 25, "https://example.com/source1"); + Annotation annotation2 = Annotation.of(26, 50, "Wikipedia: AI Article"); + + TextContent textContent = + TextContent.builder() + .text("This text has citations from multiple sources for attribution.") + .annotations(Arrays.asList(annotation1, annotation2)) + .build(); + + // Act + String json = textContent.toJson(); + + // Assert + assertNotNull(json); + assertTrue(json.contains("\"type\":\"text\"")); + assertTrue(json.contains("\"text\":\"This text has citations")); + assertTrue(json.contains("\"annotations\"")); + assertTrue(json.contains("\"start_index\":0")); + assertTrue(json.contains("\"end_index\":25")); + assertTrue(json.contains("\"source\":\"https://example.com/source1\"")); + assertTrue(json.contains("\"source\":\"Wikipedia: AI Article\"")); + } + + @Test + public void testTextContentJsonDeserializationWithAnnotations() { + // Arrange + String json = + "{\"type\":\"text\",\"text\":\"Sample text with citations.\"," + + "\"annotations\":[" + + "{\"start_index\":0,\"end_index\":15,\"source\":\"https://source1.com\"}," + + "{\"start_index\":16,\"end_index\":30,\"source\":\"https://source2.com\"}" + + "]}"; + + // Act + TextContent textContent = TextContent.fromJson(json); + + // Assert + assertTrue(textContent.text().isPresent()); + assertEquals("Sample text with citations.", textContent.text().get()); + assertTrue(textContent.annotations().isPresent()); + assertEquals(2, textContent.annotations().get().size()); + + Annotation firstAnnotation = textContent.annotations().get().get(0); + assertEquals(0, firstAnnotation.startIndex().get()); + assertEquals(15, firstAnnotation.endIndex().get()); + assertEquals("https://source1.com", firstAnnotation.source().get()); + + Annotation secondAnnotation = textContent.annotations().get().get(1); + assertEquals(16, secondAnnotation.startIndex().get()); + assertEquals(30, secondAnnotation.endIndex().get()); + assertEquals("https://source2.com", secondAnnotation.source().get()); + } + + @Test + public void testTextContentRoundTripSerializationWithAnnotations() { + // Arrange + Annotation annotation1 = Annotation.of(0, 20, "https://example.org"); + Annotation annotation2 = Annotation.of(21, 40, "Research Paper: AI"); + + TextContent original = + TextContent.builder() + .text("Text with multiple citation sources.") + .annotations(annotation1, annotation2) + .build(); + + // Act - Serialize and deserialize + String json = original.toJson(); + TextContent deserialized = TextContent.fromJson(json); + + // Assert - Fields match + assertEquals(original.text(), deserialized.text()); + assertTrue(deserialized.annotations().isPresent()); + assertEquals(2, deserialized.annotations().get().size()); + + // Verify annotations content + List originalAnnotations = original.annotations().get(); + List deserializedAnnotations = deserialized.annotations().get(); + + for (int i = 0; i < originalAnnotations.size(); i++) { + assertEquals( + originalAnnotations.get(i).startIndex(), deserializedAnnotations.get(i).startIndex()); + assertEquals( + originalAnnotations.get(i).endIndex(), deserializedAnnotations.get(i).endIndex()); + assertEquals(originalAnnotations.get(i).source(), deserializedAnnotations.get(i).source()); + } + } + + @Test + public void testTextContentToBuilder() { + // Arrange + TextContent original = + TextContent.builder() + .text("Original text") + .annotations(Annotation.of(0, 10, "https://source.com")) + .build(); + + // Act - Modify via toBuilder + TextContent modified = + original.toBuilder() + .text("Modified text") + .annotations(Annotation.of(0, 15, "https://new-source.com")) + .build(); + + // Assert - Original unchanged + assertEquals("Original text", original.text().get()); + assertEquals("https://source.com", original.annotations().get().get(0).source().get()); + + // Modified has new values + assertEquals("Modified text", modified.text().get()); + assertEquals("https://new-source.com", modified.annotations().get().get(0).source().get()); + } + + @Test + public void testTextContentWithEmptyAnnotationsList() { + // Arrange & Act + TextContent textContent = + TextContent.builder() + .text("Text with empty annotations list") + .annotations(Arrays.asList()) + .build(); + + // Assert + assertTrue(textContent.annotations().isPresent()); + assertTrue(textContent.annotations().get().isEmpty()); + } + + @Test + public void testTextContentAnnotationsMatchOpenApiSpec() { + // This test verifies the structure matches the OpenAPI specification + // OpenAPI spec defines: TextContent { text: string (optional), annotations: array of Annotation (optional) } + + // Arrange + Annotation annotation = Annotation.of(0, 10, "https://example.com"); + + // Act + TextContent textContent = + TextContent.builder() + .text("Sample text") + .annotations(annotation) + .build(); + + String json = textContent.toJson(); + + // Assert - Verify JSON structure matches OpenAPI spec + assertTrue(json.contains("\"type\":\"text\"")); + assertTrue(json.contains("\"text\":")); + assertTrue(json.contains("\"annotations\":")); + assertTrue(json.contains("\"start_index\":")); + assertTrue(json.contains("\"end_index\":")); + assertTrue(json.contains("\"source\":")); + } +}